@thanhphi/create-360-miniapp 0.2.0 → 0.2.2
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/README.md +313 -41
- package/package.json +21 -6
- package/template/README.md +140 -159
- package/template/rspack.config.cjs +2 -2
- package/template/src/common/mini-app/provider.tsx +6 -5
- package/template/src/modules/__MODULE__/components/ProfileCard.tsx +3 -2
- package/template/src/modules/__MODULE__/screens/home/index.tsx +5 -2
- package/template/src/modules/auth/screens/LoginScreen.tsx +5 -2
- package/template/src/themes/ui-geometry.ts +12 -0
package/README.md
CHANGED
|
@@ -1,80 +1,352 @@
|
|
|
1
1
|
# @thanhphi/create-360-miniapp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Tạo mini app React Native chạy bên trong MySGR, có thể chạy độc lập để phát triển và kiểm thử.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Project được tạo sẵn gồm TypeScript, navigation, đăng nhập qua host, HTTP client có refresh token, màn hồ sơ mẫu, theme 360, cấu hình môi trường và pipeline Azure DevOps.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Mục lục
|
|
8
|
+
|
|
9
|
+
- [Bắt đầu nhanh](#bắt-đầu-nhanh)
|
|
10
|
+
- [Yêu cầu](#yêu-cầu)
|
|
11
|
+
- [Các lựa chọn khi tạo app](#các-lựa-chọn-khi-tạo-app)
|
|
12
|
+
- [Cấu trúc project](#cấu-trúc-project)
|
|
13
|
+
- [Cấu hình môi trường và API](#cấu-hình-môi-trường-và-api)
|
|
14
|
+
- [Chạy trong MySGR](#chạy-trong-mysgr)
|
|
15
|
+
- [Chạy độc lập](#chạy-độc-lập)
|
|
16
|
+
- [Thêm màn hình và gọi API](#thêm-màn-hình-và-gọi-api)
|
|
17
|
+
- [Màu sắc và kích thước UI](#màu-sắc-và-kích-thước-ui)
|
|
18
|
+
- [Đóng gói và triển khai](#đóng-gói-và-triển-khai)
|
|
19
|
+
- [Xử lý vấn đề thường gặp](#xử-lý-vấn-đề-thường-gặp)
|
|
20
|
+
|
|
21
|
+
## Bắt đầu nhanh
|
|
22
|
+
|
|
23
|
+
Cú pháp tổng quát:
|
|
8
24
|
|
|
9
25
|
```sh
|
|
10
|
-
|
|
26
|
+
npx @thanhphi/create-360-miniapp@latest [PROJECT_NAME] --module [MODULE_NAME] --app-code [APPCODE] --port [PORT]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Thay các giá trị trong `[...]` bằng thông tin của app, không giữ dấu ngoặc vuông khi chạy lệnh:
|
|
30
|
+
|
|
31
|
+
| Tham số | Ý nghĩa |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `PROJECT_NAME` | Tên thư mục và npm package, viết thường và nối bằng dấu gạch ngang |
|
|
34
|
+
| `MODULE_NAME` | Tên thư mục nghiệp vụ trong `src/modules/` |
|
|
35
|
+
| `APPCODE` | Mã app trên backend, viết hoa |
|
|
36
|
+
| `PORT` | Cổng Re.Pack dev server, ví dụ `8085` |
|
|
37
|
+
|
|
38
|
+
Ví dụ tạo mini app quản lý nghỉ phép:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
npx @thanhphi/create-360-miniapp@latest leave-management --module leave --app-code LVE --port 8085
|
|
11
42
|
cd leave-management
|
|
12
43
|
npm install
|
|
13
44
|
npm run env:staging
|
|
14
45
|
npm run start:mf
|
|
15
46
|
```
|
|
16
47
|
|
|
17
|
-
Lệnh
|
|
48
|
+
Lệnh cuối mở Re.Pack dev server. Để xem giao diện, mở app đã đăng ký từ **host MySGR có cấu hình trỏ tới dev server này**. Đây không phải lệnh khởi chạy một ứng dụng độc lập trên điện thoại.
|
|
18
49
|
|
|
19
|
-
|
|
50
|
+
Có thể tạo project bằng npm create thay cho npx:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npm create @thanhphi/360-miniapp@latest [PROJECT_NAME] -- --module [MODULE_NAME] --app-code [APPCODE] --port [PORT]
|
|
54
|
+
```
|
|
20
55
|
|
|
21
|
-
|
|
56
|
+
Ví dụ tương ứng:
|
|
22
57
|
|
|
23
58
|
```sh
|
|
24
|
-
|
|
59
|
+
npm create @thanhphi/360-miniapp@latest leave-management -- --module leave --app-code LVE --port 8085
|
|
25
60
|
```
|
|
26
61
|
|
|
27
|
-
|
|
62
|
+
Generator chỉ tạo thư mục mới, không ghi đè project có sẵn và không tự tạo Git repository. App vừa sinh có README riêng với các giá trị cấu hình đã được điền.
|
|
28
63
|
|
|
29
|
-
##
|
|
64
|
+
## Yêu cầu
|
|
65
|
+
|
|
66
|
+
Bộ phiên bản của template:
|
|
67
|
+
|
|
68
|
+
| Thành phần | Phiên bản |
|
|
69
|
+
| ------------------------- | ----------------------------------- |
|
|
70
|
+
| Node.js | Từ 20.19.4; khuyến nghị Node 22 LTS |
|
|
71
|
+
| Expo | SDK 55 |
|
|
72
|
+
| React / React Native | 19.2.0 / 0.83.6 |
|
|
73
|
+
| UI360 / miniapp-sdk | 0.2.0 / 0.1.0 |
|
|
74
|
+
| Unistyles / Nitro Modules | 3.2.5 / 0.36.5 |
|
|
75
|
+
| Re.Pack | 5.2.x |
|
|
76
|
+
|
|
77
|
+
- Chạy trong MySGR: cần host cung cấp các native dependencies tương thích, cấu hình tải remote và `hostCapabilities` theo SDK.
|
|
78
|
+
- Chạy độc lập trên iOS: cần macOS, Xcode và CocoaPods.
|
|
79
|
+
- Chạy độc lập trên Android: cần Android SDK, JDK và emulator hoặc thiết bị thật.
|
|
80
|
+
- Không dùng Expo Go cho template này; các native dependencies yêu cầu development build.
|
|
81
|
+
- UI360 được đóng gói trong mini app, không bắt buộc host dùng cùng phiên bản UI360. React, Unistyles và các native dependencies vẫn dùng chung với host.
|
|
82
|
+
|
|
83
|
+
## Các lựa chọn khi tạo app
|
|
84
|
+
|
|
85
|
+
Ví dụ cấu hình đầy đủ:
|
|
30
86
|
|
|
31
87
|
```sh
|
|
32
|
-
npx @thanhphi/create-360-miniapp@
|
|
33
|
-
# npm create là alias npm init và ánh xạ tới @thanhphi/create-360-miniapp:
|
|
34
|
-
npm create @thanhphi/360-miniapp@0.2.0 leave-management -- --module leave --app-code LVE
|
|
88
|
+
npx @thanhphi/create-360-miniapp@latest leave-management --module leave --container LeaveManagement --app-code LVE --display-name "Quản lý nghỉ phép" --bundle-id com.sungroup.leave --port 8085 --env staging
|
|
35
89
|
```
|
|
36
90
|
|
|
37
|
-
|
|
91
|
+
| Lựa chọn | Ý nghĩa | Mặc định |
|
|
92
|
+
| ---------------------- | --------------------------------------------------- | -------------------------------------------------- |
|
|
93
|
+
| Tên project | Tên thư mục và npm package, dạng `leave-management` | Bắt buộc |
|
|
94
|
+
| `--module` | Thư mục nghiệp vụ trong `src/modules/` | Tên project |
|
|
95
|
+
| `--container` | Tên container Re.Pack, dạng PascalCase | Tên project chuyển thành PascalCase |
|
|
96
|
+
| `--app-code` | Mã nghiệp vụ của app trên backend | Tên project bỏ dấu gạch, viết hoa, tối đa 12 ký tự |
|
|
97
|
+
| `--display-name` | Tên hiển thị khi chạy độc lập | Tên container |
|
|
98
|
+
| `--bundle-id` | ID ứng dụng native, phải riêng cho mỗi app | `com.sungroup.<tên-project-bỏ-dấu-gạch>` |
|
|
99
|
+
| `--port` | Port Re.Pack, từ 1024 đến 65535 | `8085` |
|
|
100
|
+
| `--env` | Môi trường ban đầu: `staging`, `beta`, `prod` | `staging` |
|
|
101
|
+
| `--install` | Tạo project rồi chạy npm install | Không tự cài |
|
|
102
|
+
| `--native` | Cài dependencies và chạy Expo prebuild | Không tạo ios/android |
|
|
103
|
+
| `--help` / `--version` | Xem trợ giúp / phiên bản generator | — |
|
|
38
104
|
|
|
39
|
-
|
|
105
|
+
App ID trong catalog mặc định bằng tên project, ví dụ `leave-management`. App code, container và app ID là ba giá trị khác nhau; kiểm tra bảng cấu hình trong README của project được sinh trước khi tích hợp host.
|
|
40
106
|
|
|
41
|
-
|
|
107
|
+
Không đặt module là `auth`, `common`, `library`, `redux`, `navigation` hoặc `themes`; đây là các thư mục của base.
|
|
42
108
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
109
|
+
## Cấu trúc project
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
env/ staging.json, beta.json, prod.json
|
|
113
|
+
pipelines/ Pipeline build và deployment
|
|
114
|
+
scripts/ Chọn env, dọn output bundle
|
|
115
|
+
src/
|
|
116
|
+
common/mini-app/ Tích hợp host, provider và runtime
|
|
117
|
+
library/networking/ HTTP client dùng miniapp-sdk
|
|
118
|
+
modules/
|
|
119
|
+
auth/ Trạng thái phiên và đăng nhập standalone
|
|
120
|
+
<module>/ Màn hình, hooks, services, Redux nghiệp vụ
|
|
121
|
+
navigation/ Root navigator
|
|
122
|
+
redux/ Store và typed hooks
|
|
123
|
+
services/application.ts Khởi tạo store, QueryClient và HTTP client
|
|
124
|
+
themes/ Palette, geometry và cấu hình standalone
|
|
125
|
+
App.tsx Entry được host tải
|
|
126
|
+
StandaloneApp.tsx Entry chạy độc lập
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Phần lớn code tính năng mới đặt trong `src/modules/<module>/`. Không đặt endpoint nghiệp vụ hoặc logic màn hình trong provider/runtime.
|
|
130
|
+
|
|
131
|
+
Commit lockfile được sinh sau khi cài dependencies. Các lần cài tiếp theo và CI dùng `npm ci` để giữ phiên bản nhất quán.
|
|
132
|
+
|
|
133
|
+
## Cấu hình môi trường và API
|
|
134
|
+
|
|
135
|
+
Sửa `env/staging.json`, `env/beta.json` và `env/prod.json`. Những URL, issuer và client ID mẫu phải được thay bằng cấu hình của app.
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"ENV": "staging",
|
|
140
|
+
"BASE_URL": "https://api.example.com",
|
|
141
|
+
"API_TIMEOUT_MS": 15000,
|
|
142
|
+
"AUTH": {
|
|
143
|
+
"issuer": "https://identity.example.com",
|
|
144
|
+
"clientId": "native-client-id",
|
|
145
|
+
"redirectUrl": "com.sungroup.leave.auth://oauth/redirect",
|
|
146
|
+
"scopes": ["openid", "profile", "offline_access"]
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Chọn môi trường trước khi chạy Re.Pack hoặc bundle:
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
npm run env:staging
|
|
155
|
+
# Hoặc npm run env:beta / npm run env:prod
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Script sinh `env/env-config.ts`; không sửa file này bằng tay. Đổi env cần restart dev server.
|
|
159
|
+
|
|
160
|
+
**Các giá trị env được đưa vào bundle. Không đặt mật khẩu, client secret, private key hoặc token vào đây.** Lưu secret deployment trong cấu hình secret của CI.
|
|
161
|
+
|
|
162
|
+
Màn mẫu chỉ gọi API khi người dùng nhấn **Tải hồ sơ**. Endpoint mặc định là `/me`; sửa `PROFILE_PATH` và mapping response trong `src/modules/<module>/services/profile.service.ts` theo backend thật.
|
|
163
|
+
|
|
164
|
+
## Chạy trong MySGR
|
|
165
|
+
|
|
166
|
+
### Cấu hình phía host
|
|
167
|
+
|
|
168
|
+
Đăng ký các thông tin sau trong registry/resolver của host và catalog tương ứng:
|
|
169
|
+
|
|
170
|
+
- App ID và app code.
|
|
171
|
+
- Tên container, ví dụ `LeaveManagement`.
|
|
172
|
+
- Module được expose: `LeaveManagement/MiniApp`.
|
|
173
|
+
- Dev server URL/port khi phát triển, hoặc URL artifact khi triển khai.
|
|
174
|
+
|
|
175
|
+
Các giá trị này có trong README được tạo cùng project. CLI không tự sửa repository host hay đăng ký dữ liệu backend.
|
|
176
|
+
|
|
177
|
+
### Chạy local
|
|
178
|
+
|
|
179
|
+
```sh
|
|
180
|
+
npm run env:staging
|
|
181
|
+
npm run start:mf
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Dùng thiết bị thật:
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
npm run start:mf:device
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Host phải trỏ tới IP LAN của máy phát triển và port đã chọn. Kiểm tra thiết bị truy cập được dev server; `localhost` trên điện thoại không phải máy tính.
|
|
191
|
+
|
|
192
|
+
### Phiên đăng nhập
|
|
193
|
+
|
|
194
|
+
Host truyền `hostCapabilities` vào mini app. Contract có `auth.getSnapshot()`, `auth.subscribe()`, `auth.refreshIfNeeded()` và `closeMiniApp()`.
|
|
195
|
+
|
|
196
|
+
Base đã nối sẵn các chức năng này. HTTP client tự gắn access token, nhờ host refresh và retry tối đa một lần sau HTTP 401. Code màn hình/service không cần tự lưu token hoặc đăng nhập lại.
|
|
197
|
+
|
|
198
|
+
- Host sở hữu refresh token và persistence.
|
|
199
|
+
- Không persist token do host cấp trong mini app.
|
|
200
|
+
- Session đổi khi người dùng đổi tài khoản; refresh access token trong cùng session không reset dữ liệu nghiệp vụ.
|
|
201
|
+
- `param_supper_app` chứa dữ liệu mở app/thông báo, không dùng thay cho auth.
|
|
202
|
+
- Nếu thiếu `hostCapabilities`, kiểm tra cấu hình tích hợp; hosted entry không tự chuyển sang đăng nhập standalone.
|
|
203
|
+
|
|
204
|
+
## Chạy độc lập
|
|
205
|
+
|
|
206
|
+
Cấu hình `AUTH` trong env trước, đăng ký OAuth native client và redirect URL với nhà cung cấp đăng nhập, sau đó:
|
|
56
207
|
|
|
57
208
|
```sh
|
|
58
|
-
npm run
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
209
|
+
npm run env:staging
|
|
210
|
+
npx expo prebuild
|
|
211
|
+
npm run ios:staging
|
|
212
|
+
# Hoặc npm run android:staging
|
|
62
213
|
```
|
|
63
214
|
|
|
64
|
-
|
|
215
|
+
Sau khi có development build, có thể dùng `npm run start:staging` để chạy Metro.
|
|
216
|
+
|
|
217
|
+
- Đăng nhập OIDC sử dụng `react-native-app-auth`.
|
|
218
|
+
- Token standalone mặc định chỉ giữ trong bộ nhớ; đóng app/reset runtime sẽ mất phiên.
|
|
219
|
+
- Scopes mẫu không tự cấp quyền API. Cấu hình audience/scopes đúng với backend.
|
|
220
|
+
- Nếu backend yêu cầu đổi token OIDC sang token riêng, bổ sung flow exchange trong service auth.
|
|
221
|
+
- Đổi redirect URL hoặc thêm native dependency cần cập nhật native build; đổi JavaScript không nhất thiết cần prebuild lại.
|
|
222
|
+
|
|
223
|
+
Standalone là entry riêng; không xác định chế độ chạy chỉ bằng việc thiếu props host.
|
|
224
|
+
|
|
225
|
+
## Thêm màn hình và gọi API
|
|
226
|
+
|
|
227
|
+
1. Thêm screen/hook/service trong `src/modules/<module>/`.
|
|
228
|
+
2. Đăng ký route trong navigator của module.
|
|
229
|
+
3. Lấy HTTP client đã được tạo sẵn từ `useApplicationServices()`.
|
|
230
|
+
4. Dùng Redux cho state nghiệp vụ và TanStack Query cho server data.
|
|
231
|
+
|
|
232
|
+
Ví dụ service:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import type { ApiClient } from "../../../library/networking/client";
|
|
236
|
+
|
|
237
|
+
export function getItems(api: ApiClient, signal?: AbortSignal) {
|
|
238
|
+
return api.get<{ id: string; name: string }[]>("/items", { signal });
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Ví dụ trong component/hook; điều chỉnh relative imports theo vị trí file:
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
import { useQuery } from "@tanstack/react-query";
|
|
246
|
+
import { useApplicationServices } from "./common/mini-app/provider";
|
|
247
|
+
|
|
248
|
+
const { api } = useApplicationServices();
|
|
249
|
+
const query = useQuery({
|
|
250
|
+
queryKey: ["items"],
|
|
251
|
+
queryFn: ({ signal }) => getItems(api, signal),
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Ví dụ trên giả định API trả trực tiếp một array. Nếu backend trả envelope, unwrap trong service module. Nếu API báo hết hạn token trong HTTP 200, cấu hình thêm `isAuthExpired` trong HTTP client theo contract backend.
|
|
256
|
+
|
|
257
|
+
Khi query chứa dữ liệu cá nhân, đưa session ID vào query key như `useHome()` mẫu. Không lưu token trong query key.
|
|
258
|
+
|
|
259
|
+
## Màu sắc và kích thước UI
|
|
260
|
+
|
|
261
|
+
- `StyleSheet.configure()` cấp theme cho Unistyles.
|
|
262
|
+
- `UIThemeProvider` cấp theme cho component UI360.
|
|
263
|
+
- `createUITheme()` tạo object dữ liệu; không đăng ký hoặc thay đổi theme global.
|
|
264
|
+
- `create360Theme()` tạo cùng dữ liệu và thêm `type/color` để dùng với Unistyles.
|
|
265
|
+
|
|
266
|
+
Khi hosted, base giữ nguyên mode/màu của Unistyles đang hoạt động. Không map tên màu legacy, không trộn palette khác và không ghi đè runtime host.
|
|
267
|
+
|
|
268
|
+
Geometry được tạo sẵn trong `src/themes/ui-geometry.ts` từ factory UI360, scale theo chiều rộng thiết kế 375. Provider và style mẫu cùng dùng geometry này; không cần host cung cấp spacing/radius/typography. Không nhân scale lại tại màn hình.
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
import { StyleSheet } from "react-native-unistyles";
|
|
272
|
+
import { uiGeometry } from "./themes/ui-geometry";
|
|
273
|
+
|
|
274
|
+
const { spacing, radius } = uiGeometry;
|
|
275
|
+
const styles = StyleSheet.create(({ color }) => ({
|
|
276
|
+
card: {
|
|
277
|
+
padding: spacing.xxl,
|
|
278
|
+
borderRadius: radius.lg,
|
|
279
|
+
backgroundColor: color.background,
|
|
280
|
+
},
|
|
281
|
+
}));
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Màu guideline: `content1` là chữ nội dung, `textPrimary` là chữ trên primary, `background` là nền card và `base3` là nền màn.
|
|
285
|
+
|
|
286
|
+
Palette local nằm trong `src/themes/colors/light.ts` và `dark.ts`, được dùng để khởi tạo khi standalone. Cấu hình standalone nằm trong `src/themes/standalone.ts`.
|
|
287
|
+
|
|
288
|
+
**Tách phiên bản UI360 không tự tách theme Unistyles.** Host vẫn cần cung cấp các tên màu mà app sử dụng; factory không tự đổi `bg_1` thành `background`. Nếu cần palette riêng khi hosted, phải thiết kế phạm vi theme riêng; template không tự bật cơ chế đó.
|
|
289
|
+
|
|
290
|
+
## Đóng gói và triển khai
|
|
291
|
+
|
|
292
|
+
Chọn env rồi tạo remote bundles:
|
|
293
|
+
|
|
294
|
+
```sh
|
|
295
|
+
npm run env:staging
|
|
296
|
+
npm run bundle:mf:android
|
|
297
|
+
npm run bundle:mf:ios
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Artifact cần triển khai nằm ở:
|
|
301
|
+
|
|
302
|
+
```text
|
|
303
|
+
build/outputs/android/remotes/
|
|
304
|
+
build/outputs/ios/remotes/
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
ZIP **toàn bộ nội dung** thư mục `remotes/` của từng platform, gồm container và các chunk; loại bỏ `*.map`. Không chỉ upload file container. Không dùng `build/generated/<platform>/index.bundle` làm remote artifact.
|
|
308
|
+
|
|
309
|
+
Các lệnh trên chỉ tạo JavaScript/assets, không tạo IPA/APK. Thêm native module mới vẫn cần binary host hỗ trợ trước khi deploy remote.
|
|
310
|
+
|
|
311
|
+
### Azure DevOps
|
|
312
|
+
|
|
313
|
+
Chọn pipeline `pipelines/staging.yml`, `beta.yml` hoặc `production.yml`.
|
|
314
|
+
|
|
315
|
+
Mặc định `deploy: false`: pipeline cài dependencies, kiểm tra, bundle hai platform, đóng ZIP và lưu artifacts. Chỉ bật deployment sau khi cấu hình:
|
|
316
|
+
|
|
317
|
+
| Biến | Giá trị cần cung cấp |
|
|
318
|
+
| ------------------------------------ | -------------------------- |
|
|
319
|
+
| `MINIAPP_AZURE_SUBSCRIPTION` | Azure service connection |
|
|
320
|
+
| `MINIAPP_STORAGE_ACCOUNT` | Storage Account |
|
|
321
|
+
| `MINIAPP_STORAGE_CONTAINER` | Blob container |
|
|
322
|
+
| `DB_HOST`, `DB_LOGIN`, `DB_DATABASE` | Kết nối PostgreSQL catalog |
|
|
323
|
+
| `DB_PASSWORD` | Mật khẩu, đánh dấu secret |
|
|
324
|
+
|
|
325
|
+
Kiểm tra `appName`, `appId`, `appCode`, `envBuild`, `envFolder` và `miniAppVersion` trong YAML. Agent deployment cần Azure CLI, psql và quyền truy cập hạ tầng. Pipeline không tự tạo Storage Account, database/schema hay đăng ký registry trong code host.
|
|
65
326
|
|
|
66
|
-
|
|
327
|
+
### Trước khi phát hành
|
|
67
328
|
|
|
68
329
|
```sh
|
|
69
|
-
npm
|
|
330
|
+
npm run typecheck
|
|
331
|
+
npm run lint
|
|
332
|
+
npm test
|
|
70
333
|
```
|
|
71
334
|
|
|
72
|
-
|
|
335
|
+
Sau đó kiểm tra trên host: mở/đóng app, refresh token, đổi tài khoản, lỗi mạng, hiển thị theme, dialog/loading và remote bundle đúng platform. Xác nhận các dependencies native tương thích trước khi cập nhật catalog.
|
|
73
336
|
|
|
74
|
-
|
|
337
|
+
## Xử lý vấn đề thường gặp
|
|
75
338
|
|
|
76
|
-
|
|
339
|
+
| Hiện tượng | Kiểm tra |
|
|
340
|
+
| ---------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
341
|
+
| Không tải được mini app | Container/module name, port hoặc URL resolver; artifact phải đủ chunk |
|
|
342
|
+
| Thiếu hostCapabilities | Cách host mở app và object capabilities truyền vào |
|
|
343
|
+
| Lỗi đọc spacing.xxl | Dùng provider/geometry của base mới; không lấy geometry từ theme host chưa có token này |
|
|
344
|
+
| Thiếu màu hoặc màu không đúng | Tên token Unistyles mà host cung cấp; không thêm mapping màu chỉ để sửa geometry |
|
|
345
|
+
| Standalone không đăng nhập được | Issuer, client ID, redirect URL, scopes và native build |
|
|
346
|
+
| API trả 401 sau đăng nhập | Audience/scopes hoặc flow token exchange của backend |
|
|
347
|
+
| Thêm native package nhưng remote vẫn lỗi | Binary host phải có module native tương ứng |
|
|
348
|
+
| Thay template nhưng app cũ không đổi | Generator chỉ tạo project; cập nhật app đã sinh theo từng thay đổi, không chạy đè lên thư mục cũ |
|
|
77
349
|
|
|
78
|
-
|
|
350
|
+
## License
|
|
79
351
|
|
|
80
|
-
|
|
352
|
+
MIT.
|
package/package.json
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thanhphi/create-360-miniapp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Create a MySGR Expo and Re.Pack mini app with modules, host auth and Azure pipelines",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"bin": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-360-miniapp": "bin/create-360-miniapp.cjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"lib",
|
|
12
|
+
"template",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test tests/*.test.cjs"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public",
|
|
21
|
+
"registry": "https://registry.npmjs.org/"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
}
|
|
11
26
|
}
|
package/template/README.md
CHANGED
|
@@ -1,234 +1,215 @@
|
|
|
1
1
|
# __DISPLAY_NAME__
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Mini app TypeScript chạy trong MySGR qua Re.Pack. Chạy độc lập bằng Expo là lựa chọn phục vụ phát triển và kiểm thử.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
- Mã hex nằm ngay trong src/themes/colors/light.ts và dark.ts của app. @thanhphi/ui360 chỉ chia sẻ contract tên màu/component/factory, không sở hữu palette thương hiệu.
|
|
8
|
-
- src/themes/index.ts có một factory tạo đủ màu/mode, spacing, radius và typography; chỉ khai báo giá trị/hàm khi import, không tự cấu hình runtime. Hosted src/App.tsx không import setup global.
|
|
9
|
-
- src/themes/standalone.ts chọn mode/override phục vụ dev và gọi configureStandaloneTheme từ factory đó. StandaloneApp và bootstrap standalone import file này sau ranh giới federation bootstrap.
|
|
10
|
-
- ApplicationProvider đọc đầy đủ token đang hoạt động từ Unistyles rồi chuyển tiếp type → mode, color → colors cùng spacing/radius/typography vào UIThemeProvider. Không tạo bộ default thứ hai hoặc scale lại ở provider; không dùng props.uiTheme để override riêng shared UI.
|
|
11
|
-
- Navigation và màn hình thuộc app dùng useUnistyles để lấy color/spacing/typography; style riêng tiếp tục StyleSheet.create(({ color }) => (...)), xem ProfileCard. useUITheme nằm bên trong thư viện shared.
|
|
12
|
-
- Factory mặc định dùng scale DEVICE_WIDTH / 375 như host 360, với typography chuẩn UI360 (body 14/title 18 trước scale). Scale chỉ áp dụng một lần lúc dựng default geometry; override số trong standaloneThemeOptions đã là dp. Hosted nhận đầy đủ token của host, không ép default standalone lên host hoặc scale lại ở provider/màn hình.
|
|
13
|
-
- Màn mẫu dùng Button/UIText từ shared, spacing/radius/typography từ theme. Không tạo ActionButton chỉ để bọc Button hoặc ép một chuẩn chiều cao khác.
|
|
14
|
-
- Chưa tách runtime riêng. Palette local phục vụ khởi tạo mặc định/standalone, không tự cô lập global Unistyles. Muốn đổi màu riêng toàn mini app khi hosted cần áp dụng theme đồng bộ cho native và shared theo phạm vi app.
|
|
15
|
-
- content1 là chữ nội dung; textPrimary là chữ trên nút primary. base3 là nền màn, base2 là nền hồng có chủ ý; card trắng dùng background.
|
|
16
|
-
- Native host cần Unistyles 3.2.5/Nitro 0.36.5 và UI360 0.2.0. Base khai báo expo-image vì đây là peer của UI360. Cài đặt lại dependencies sau khi publisher phát hành package tương ứng.
|
|
5
|
+
## Thông tin project
|
|
17
6
|
|
|
18
7
|
| Cấu hình | Giá trị |
|
|
19
8
|
| --- | --- |
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
9
|
+
| npm package / thư mục | `__PACKAGE_NAME__` |
|
|
10
|
+
| Container Re.Pack | `__APP_NAME__` |
|
|
11
|
+
| Module được expose | `__APP_NAME__/MiniApp` |
|
|
12
|
+
| App ID trong catalog | `__APP_ID__` |
|
|
13
|
+
| App code | `__APP_CODE__` |
|
|
14
|
+
| Module nghiệp vụ | `src/modules/__MODULE__` |
|
|
15
|
+
| Port local | `__PORT__` |
|
|
16
|
+
| Bundle ID standalone | `__BUNDLE_ID__` |
|
|
26
17
|
|
|
27
|
-
|
|
18
|
+
Yêu cầu Node.js >=20.19.4, khuyến nghị Node 22 LTS. Bộ phiên bản được khai báo trong package.json: Expo 55, React Native 0.83.6, React 19.2.0, UI360 0.2.0, Unistyles 3.2.5 và Nitro 0.36.5. Không tự nâng riêng React/native dependencies nếu host chưa hỗ trợ phiên bản đó.
|
|
28
19
|
|
|
29
|
-
|
|
20
|
+
## 1. Cài dependencies và chọn môi trường
|
|
30
21
|
|
|
31
22
|
```sh
|
|
32
|
-
|
|
33
|
-
npm
|
|
23
|
+
npm install
|
|
24
|
+
npm run env:staging
|
|
34
25
|
```
|
|
35
26
|
|
|
36
|
-
|
|
27
|
+
Commit package-lock.json sau lần cài đầu. Các lần sau dùng npm ci.
|
|
37
28
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
Sửa env/staging.json, env/beta.json và env/prod.json:
|
|
30
|
+
|
|
31
|
+
- BASE_URL: URL gốc của backend.
|
|
32
|
+
- API_TIMEOUT_MS: timeout của request.
|
|
33
|
+
- AUTH: cấu hình OIDC khi chạy độc lập.
|
|
34
|
+
|
|
35
|
+
Script env sinh env/env-config.ts; không sửa file sinh bằng tay. Đổi môi trường cần restart dev server.
|
|
41
36
|
|
|
42
|
-
|
|
37
|
+
Tất cả giá trị env đi vào bundle. Không lưu client secret, mật khẩu hoặc token trong JSON. Secret deployment phải đặt trong CI.
|
|
43
38
|
|
|
44
|
-
## Chạy
|
|
39
|
+
## 2. Chạy trong MySGR
|
|
45
40
|
|
|
46
|
-
|
|
41
|
+
**Hosted** nghĩa là mở mini app bên trong MySGR. Host cung cấp phiên đăng nhập và các native modules.
|
|
47
42
|
|
|
48
43
|
```sh
|
|
49
|
-
npm install
|
|
50
44
|
npm run env:staging
|
|
51
|
-
|
|
52
|
-
npm run ios:staging
|
|
53
|
-
# hoặc
|
|
54
|
-
npm run android:staging
|
|
45
|
+
npm run start:mf
|
|
55
46
|
```
|
|
56
47
|
|
|
57
|
-
|
|
48
|
+
Đăng ký app phía host/catalog theo bảng đầu tài liệu, trỏ resolver tới dev server port __PORT__, sau đó mở app từ MySGR. CLI không tự sửa repository host hoặc dữ liệu backend.
|
|
58
49
|
|
|
59
|
-
|
|
50
|
+
Thiết bị thật cần IP LAN của máy phát triển:
|
|
60
51
|
|
|
61
52
|
```sh
|
|
62
|
-
npm run start:
|
|
63
|
-
npm run start:beta
|
|
64
|
-
npm run start:prod
|
|
65
|
-
npm run typecheck
|
|
66
|
-
npm run lint
|
|
67
|
-
npm test
|
|
53
|
+
npm run start:mf:device
|
|
68
54
|
```
|
|
69
55
|
|
|
70
|
-
|
|
56
|
+
Nếu cần reset cache dev server:
|
|
71
57
|
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
declare/ # ambient declarations cho assets
|
|
75
|
-
env/ # JSON các flavor và env-config.ts sinh ra
|
|
76
|
-
pipelines/templates/ # Azure build/ZIP, upload/register
|
|
77
|
-
scripts/ # sync env và build remote
|
|
78
|
-
src/
|
|
79
|
-
assets/ # assets nhập từ source
|
|
80
|
-
common/ # mini-app runtime và host adapter
|
|
81
|
-
config/ # cấu hình dùng trong app
|
|
82
|
-
constants/ # hằng số cấp app
|
|
83
|
-
hooks/ # hook dùng chung
|
|
84
|
-
library/
|
|
85
|
-
components/ # UI dùng lại trong app
|
|
86
|
-
networking/ # SDK HTTP client
|
|
87
|
-
utils/ # helper không thuộc nghiệp vụ
|
|
88
|
-
modules/
|
|
89
|
-
auth/ # màn/login service/redux/navigation auth
|
|
90
|
-
__MODULE__/ # screens/navigation/redux/services/types nghiệp vụ
|
|
91
|
-
navigation/ # root navigator
|
|
92
|
-
redux/ # store factory, reducers và typed hooks
|
|
93
|
-
services/ # application composition, runtime/store/cache/client
|
|
94
|
-
themes/ # theme của app
|
|
95
|
-
types/ # contract app, assets và kiểu dùng chung
|
|
96
|
-
App.tsx
|
|
97
|
-
bootstrap.tsx
|
|
58
|
+
```sh
|
|
59
|
+
npm run start:mf:reset
|
|
98
60
|
```
|
|
99
61
|
|
|
100
|
-
|
|
62
|
+
Host phải truyền hostCapabilities theo miniapp-sdk. Base đã nối sẵn auth.getSnapshot/subscribe/refreshIfNeeded và closeMiniApp; màn hình/service không cần tự lưu token hoặc đăng nhập lại.
|
|
101
63
|
|
|
102
|
-
|
|
64
|
+
- Host quản lý refresh token và persistence.
|
|
65
|
+
- Mini app không persist token do host cấp.
|
|
66
|
+
- Đổi tài khoản làm đổi session và xóa dữ liệu phiên cũ.
|
|
67
|
+
- Refresh access token trong cùng session không reset dữ liệu nghiệp vụ.
|
|
68
|
+
- param_supper_app dùng cho dữ liệu mở app/thông báo, không thay cho auth.
|
|
69
|
+
- Thiếu hostCapabilities là lỗi cấu hình tích hợp; không tự chuyển sang standalone login.
|
|
103
70
|
|
|
104
|
-
|
|
71
|
+
## 3. Chạy độc lập để phát triển
|
|
105
72
|
|
|
106
|
-
|
|
107
|
-
{
|
|
108
|
-
"ENV": "staging",
|
|
109
|
-
"BASE_URL": "https://api.example.com",
|
|
110
|
-
"API_TIMEOUT_MS": 15000,
|
|
111
|
-
"AUTH": {
|
|
112
|
-
"issuer": "https://identity.example.com",
|
|
113
|
-
"clientId": "your-native-client-id",
|
|
114
|
-
"redirectUrl": "__BUNDLE_ID__.auth://oauth/redirect",
|
|
115
|
-
"scopes": ["openid", "profile", "offline_access"]
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
```
|
|
73
|
+
**Standalone** nghĩa là chạy app riêng, không nằm trong MySGR. Cần Xcode/CocoaPods cho iOS hoặc Android SDK/JDK cho Android; template không chạy bằng Expo Go.
|
|
119
74
|
|
|
120
|
-
|
|
75
|
+
Đăng ký OAuth native client với provider, cấu hình issuer/clientId/scopes và redirect URL trong env. Redirect mặc định có dạng __BUNDLE_ID__.auth://oauth/redirect.
|
|
121
76
|
|
|
122
|
-
|
|
77
|
+
```sh
|
|
78
|
+
npm run env:staging
|
|
79
|
+
npx expo prebuild
|
|
80
|
+
npm run ios:staging
|
|
81
|
+
# Hoặc npm run android:staging
|
|
82
|
+
```
|
|
123
83
|
|
|
124
|
-
|
|
84
|
+
Đã có development build thì dùng npm run start:staging để chạy Metro. Có các lệnh tương ứng với beta và prod.
|
|
125
85
|
|
|
126
|
-
|
|
86
|
+
Token standalone mặc định chỉ ở bộ nhớ; khởi động lại sẽ mất phiên. Scopes OIDC mẫu không tự cấp quyền API. Nếu backend yêu cầu token exchange, bổ sung flow đó trong service auth. Không dùng ID token thay API access token.
|
|
127
87
|
|
|
128
|
-
|
|
88
|
+
Đổi redirect URL hoặc thêm native dependency cần cập nhật native build.
|
|
129
89
|
|
|
130
|
-
|
|
131
|
-
import MiniApp from './src/App';
|
|
132
|
-
import type { HostCapabilities } from '@thanhphi/miniapp-sdk';
|
|
133
|
-
|
|
134
|
-
const capabilities: HostCapabilities = {
|
|
135
|
-
auth: {
|
|
136
|
-
getSnapshot: () => hostAuth.getSnapshot(),
|
|
137
|
-
refreshIfNeeded: input => hostAuth.refreshIfNeeded(input),
|
|
138
|
-
subscribe: listener => hostAuth.subscribe(listener),
|
|
139
|
-
},
|
|
140
|
-
closeMiniApp: () => navigation.goBack(),
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// Phía host: `hostAuth` và `navigation` là service/navigation thật.
|
|
144
|
-
// Giữ object capabilities ổn định (ví dụ useMemo trong component host).
|
|
145
|
-
<MiniApp
|
|
146
|
-
hostCapabilities={capabilities}
|
|
147
|
-
param_supper_app={{ notifications: notification }}
|
|
148
|
-
/>;
|
|
149
|
-
```
|
|
90
|
+
## 4. Nơi thêm code
|
|
150
91
|
|
|
151
|
-
|
|
92
|
+
```text
|
|
93
|
+
env/ Cấu hình staging, beta, prod
|
|
94
|
+
pipelines/ CI và deployment Azure
|
|
95
|
+
src/
|
|
96
|
+
common/mini-app/ Provider, runtime, host integration
|
|
97
|
+
library/networking/ HTTP client
|
|
98
|
+
modules/auth/ Phiên đăng nhập
|
|
99
|
+
modules/__MODULE__/
|
|
100
|
+
screens/ Màn hình và hooks
|
|
101
|
+
navigation/ Routes của module
|
|
102
|
+
services/ Endpoints và mapping response
|
|
103
|
+
redux/ State nghiệp vụ
|
|
104
|
+
navigation/ Root navigator
|
|
105
|
+
services/application.ts Khởi tạo client/store/QueryClient
|
|
106
|
+
themes/ Palette, geometry, standalone setup
|
|
107
|
+
```
|
|
152
108
|
|
|
153
|
-
|
|
109
|
+
Đặt tính năng mới trong src/modules/__MODULE__, đăng ký route trong navigator của module. Không đặt endpoints trong root provider.
|
|
154
110
|
|
|
155
|
-
|
|
111
|
+
### API và dữ liệu mẫu
|
|
156
112
|
|
|
157
|
-
|
|
113
|
+
Màn Home chỉ gọi API sau khi nhấn **Tải hồ sơ**. Chỉnh PROFILE_PATH và mapping response trong src/modules/__MODULE__/services/profile.service.ts theo backend thật; endpoint mẫu là /me.
|
|
158
114
|
|
|
159
|
-
|
|
115
|
+
Lấy client qua useApplicationServices() trong component/hook, rồi truyền client vào service:
|
|
160
116
|
|
|
161
117
|
```ts
|
|
162
|
-
import type { ApiClient } from '
|
|
118
|
+
import type { ApiClient } from '../../../library/networking/client';
|
|
163
119
|
|
|
164
|
-
export
|
|
165
|
-
return api.get<{ id: string; name: string }>(
|
|
166
|
-
`/items/${encodeURIComponent(itemId)}`,
|
|
167
|
-
);
|
|
120
|
+
export function getItems(api: ApiClient, signal?: AbortSignal) {
|
|
121
|
+
return api.get<{ id: string; name: string }[]>('/items', { signal });
|
|
168
122
|
}
|
|
169
123
|
```
|
|
170
124
|
|
|
171
|
-
|
|
125
|
+
Ví dụ này giả định response là array trực tiếp; unwrap envelope trong service nếu backend trả cấu trúc khác. HTTP client tự gắn access token và refresh/retry tối đa một lần sau HTTP 401.
|
|
172
126
|
|
|
173
|
-
|
|
127
|
+
Mẫu useHome() có QueryClient, request cancellation và session trong query key; dùng làm mẫu cho dữ liệu cá nhân. Nếu backend báo hết hạn token trong HTTP 200, cấu hình isAuthExpired tại src/library/networking/client.ts.
|
|
174
128
|
|
|
175
|
-
|
|
129
|
+
## 5. Màu và geometry
|
|
176
130
|
|
|
177
|
-
|
|
131
|
+
Unistyles phục vụ style của app; UIThemeProvider phục vụ component từ UI360.
|
|
178
132
|
|
|
179
|
-
|
|
133
|
+
- Hosted giữ nguyên mode và color từ runtime host.
|
|
134
|
+
- UI360 được bundle trong mini app, không share implementation với host.
|
|
135
|
+
- Geometry mặc định được tạo bằng factory UI360 tại src/themes/ui-geometry.ts. Màn mẫu và provider dùng cùng spacing/radius/typography, không yêu cầu host có những trường này.
|
|
136
|
+
- Palette local tại src/themes/colors/light.ts và dark.ts dùng để cấu hình standalone.
|
|
137
|
+
- Không mapping key màu legacy, không trộn palette và không ghi đè theme global khi hosted.
|
|
138
|
+
- Tách phiên bản UI360 không tự tách runtime Unistyles. Host vẫn cần có tên token màu mà app sử dụng.
|
|
180
139
|
|
|
181
|
-
|
|
182
|
-
npm run env:staging
|
|
183
|
-
# hoặc env:beta / env:prod
|
|
184
|
-
npm run start:mf
|
|
140
|
+
Ví dụ style:
|
|
185
141
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
142
|
+
```tsx
|
|
143
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
144
|
+
import { uiGeometry } from './themes/ui-geometry';
|
|
145
|
+
|
|
146
|
+
const { spacing, radius } = uiGeometry;
|
|
147
|
+
const styles = StyleSheet.create(({ color }) => ({
|
|
148
|
+
card: {
|
|
149
|
+
backgroundColor: color.background,
|
|
150
|
+
padding: spacing.xxl,
|
|
151
|
+
borderRadius: radius.lg,
|
|
152
|
+
},
|
|
153
|
+
}));
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Điều chỉnh import theo vị trí file. content1 là chữ nội dung, textPrimary là chữ trên primary; background là nền card, base3 là nền màn. Geometry đã scale theo thiết kế rộng 375, không nhân scale lại.
|
|
189
157
|
|
|
190
|
-
|
|
158
|
+
Để đổi geometry, cấu hình tại ui-geometry.ts. Override số truyền vào factory là dp và không bị scale lại. createUITheme/create360Theme chỉ tạo object; chỉ entry standalone mới gọi StyleSheet.configure qua src/themes/standalone.ts.
|
|
159
|
+
|
|
160
|
+
## 6. Đóng gói remote
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
npm run env:staging
|
|
191
164
|
npm run bundle:mf:android
|
|
192
165
|
npm run bundle:mf:ios
|
|
193
166
|
```
|
|
194
167
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
Output deploy:
|
|
168
|
+
Deploy các thư mục:
|
|
198
169
|
|
|
199
170
|
```text
|
|
200
|
-
build/outputs/android/remotes/
|
|
201
|
-
build/outputs/ios/remotes/
|
|
202
|
-
build/outputs/<platform>/remotes/*.chunk.bundle
|
|
171
|
+
build/outputs/android/remotes/
|
|
172
|
+
build/outputs/ios/remotes/
|
|
203
173
|
```
|
|
204
174
|
|
|
205
|
-
ZIP toàn bộ nội dung
|
|
175
|
+
ZIP toàn bộ nội dung remotes của mỗi platform, gồm __APP_NAME__.container.js.bundle và các chunk, bỏ các file .map. Không chỉ upload container. Không dùng build/generated/<platform>/index.bundle để triển khai mini app.
|
|
176
|
+
|
|
177
|
+
Các lệnh này tạo JavaScript/assets, không tạo IPA/APK. Các native dependencies vẫn phải có trong binary host.
|
|
206
178
|
|
|
207
|
-
##
|
|
179
|
+
## 7. CI và deployment
|
|
208
180
|
|
|
209
|
-
|
|
181
|
+
Azure DevOps có pipelines/staging.yml, beta.yml và production.yml. Mặc định deploy=false: cài dependencies, kiểm tra, build remote, ZIP và lưu artifacts.
|
|
210
182
|
|
|
211
|
-
|
|
183
|
+
Muốn bật deploy, cấu hình service connection/variable group và các biến:
|
|
212
184
|
|
|
213
185
|
| Biến | Mục đích |
|
|
214
186
|
| --- | --- |
|
|
215
|
-
|
|
|
216
|
-
|
|
|
217
|
-
|
|
|
218
|
-
|
|
|
219
|
-
|
|
|
220
|
-
| `appName`, `appId`, `appCode` | Container `__APP_NAME__`, catalog ID `__APP_ID__`, code `__APP_CODE__` |
|
|
221
|
-
| `envBuild`, `envFolder` | Flavor env và folder Blob của môi trường |
|
|
222
|
-
| `miniAppVersion` | Version artifact, mặc định `$(Build.BuildNumber)` |
|
|
223
|
-
|
|
224
|
-
Pipeline cần agent có Node/npm, `zip`, `sha1sum`; deployment cần Azure CLI và `psql`, truy cập mạng đến PostgreSQL. DB kết nối SSL (`PGSSLMODE=require`); schema cần bảng `mini_app` và `mini_app_version` phù hợp host. ZIP nằm ở `appId/envFolder/platform/version.zip` trong Blob. Metadata được ghi sau upload thành công. Version nên duy nhất vì upload không ghi đè archive đã có. Cài lần đầu có thể dùng npm install khi chưa có lockfile; các lần sau dùng npm ci từ lockfile đã commit.
|
|
187
|
+
| MINIAPP_AZURE_SUBSCRIPTION | Azure service connection |
|
|
188
|
+
| MINIAPP_STORAGE_ACCOUNT | Storage Account |
|
|
189
|
+
| MINIAPP_STORAGE_CONTAINER | Blob container |
|
|
190
|
+
| DB_HOST, DB_LOGIN, DB_DATABASE | PostgreSQL catalog |
|
|
191
|
+
| DB_PASSWORD | Mật khẩu, đánh dấu secret |
|
|
225
192
|
|
|
226
|
-
|
|
193
|
+
Kiểm tra appName/appId/appCode, envBuild/envFolder và miniAppVersion trong YAML. Agent deploy cần Azure CLI, psql và quyền mạng tới hạ tầng. Pipeline không tự tạo hạ tầng, schema database hoặc đăng ký app trong source host.
|
|
227
194
|
|
|
228
|
-
|
|
195
|
+
## 8. Kiểm tra trước khi phát hành
|
|
229
196
|
|
|
230
|
-
|
|
197
|
+
```sh
|
|
198
|
+
npm run typecheck
|
|
199
|
+
npm run lint
|
|
200
|
+
npm test
|
|
201
|
+
```
|
|
231
202
|
|
|
232
|
-
|
|
203
|
+
Sau đó thử trên host: mở/đóng app, refresh token, đổi tài khoản, lỗi mạng, dialog/loading, theme và artifact hai platform.
|
|
233
204
|
|
|
234
|
-
|
|
205
|
+
| Vấn đề | Kiểm tra |
|
|
206
|
+
| --- | --- |
|
|
207
|
+
| Không tải remote | Container name, module name, URL/port và đủ chunk |
|
|
208
|
+
| Thiếu hostCapabilities | Props/cách host mở mini app |
|
|
209
|
+
| Không có spacing.xxl | Dùng uiGeometry/UIThemeProvider, không đọc geometry thiếu từ host |
|
|
210
|
+
| Màu thiếu hoặc không đúng | Token màu trong theme Unistyles đang hoạt động |
|
|
211
|
+
| Standalone không đăng nhập | OAuth client, redirect URL, scopes, native build |
|
|
212
|
+
| API vẫn trả 401 | API audience/scopes hoặc token exchange |
|
|
213
|
+
| Lỗi native module | Binary host có module/phiên bản cần dùng chưa |
|
|
214
|
+
|
|
215
|
+
Hướng dẫn tạo project mới và các CLI options: [@thanhphi/create-360-miniapp trên npm](https://www.npmjs.com/package/@thanhphi/create-360-miniapp).
|
|
@@ -8,7 +8,8 @@ const aliases = Object.fromEntries(Object.entries(require('./aliases.cjs')).map(
|
|
|
8
8
|
const sharedNames = [
|
|
9
9
|
'react', 'react-native', 'expo', 'expo-image',
|
|
10
10
|
'react-native-unistyles', 'react-native-nitro-modules',
|
|
11
|
-
|
|
11
|
+
// UI360 stays in this remote bundle, independent of the host's UI version.
|
|
12
|
+
'@thanhphi/miniapp-sdk',
|
|
12
13
|
'@react-navigation/native', '@react-navigation/native-stack',
|
|
13
14
|
'react-native-gesture-handler', 'react-native-safe-area-context',
|
|
14
15
|
'react-native-screens', 'react-native-svg', 'react-native-calendars',
|
|
@@ -16,7 +17,6 @@ const sharedNames = [
|
|
|
16
17
|
];
|
|
17
18
|
const shared = Object.fromEntries(sharedNames.map(name => [name, {
|
|
18
19
|
singleton: true,
|
|
19
|
-
strictVersion: name === '@thanhphi/ui360',
|
|
20
20
|
eager: false,
|
|
21
21
|
packageName: name,
|
|
22
22
|
requiredVersion: pkg.dependencies[name],
|
|
@@ -3,8 +3,9 @@ import { Provider } from 'react-redux';
|
|
|
3
3
|
import { QueryClientProvider } from '@tanstack/react-query';
|
|
4
4
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
5
5
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
6
|
-
import { UIThemeProvider } from '@thanhphi/ui360';
|
|
6
|
+
import { createUITheme, UIThemeProvider } from '@thanhphi/ui360';
|
|
7
7
|
import { useUnistyles } from 'react-native-unistyles';
|
|
8
|
+
import { uiGeometry } from '../../themes/ui-geometry';
|
|
8
9
|
import type {} from '../../themes';
|
|
9
10
|
import type { ApplicationServices } from '../../services/application';
|
|
10
11
|
|
|
@@ -21,12 +22,12 @@ export function ApplicationProvider({
|
|
|
21
22
|
children,
|
|
22
23
|
}: React.PropsWithChildren<{ services: ApplicationServices }>) {
|
|
23
24
|
const {
|
|
24
|
-
theme: { type, color
|
|
25
|
+
theme: { type, color },
|
|
25
26
|
} = useUnistyles();
|
|
26
|
-
//
|
|
27
|
+
// Host supplies colors/mode; local UI360 supplies geometry without global setup.
|
|
27
28
|
const uiTheme = React.useMemo(
|
|
28
|
-
() => ({ mode: type, colors: color,
|
|
29
|
-
[type, color
|
|
29
|
+
() => createUITheme({ mode: type, colors: color, ...uiGeometry }),
|
|
30
|
+
[type, color],
|
|
30
31
|
);
|
|
31
32
|
return (
|
|
32
33
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
@@ -3,6 +3,7 @@ import { UIText } from '@thanhphi/ui360';
|
|
|
3
3
|
import { View } from 'react-native';
|
|
4
4
|
import { StyleSheet } from 'react-native-unistyles';
|
|
5
5
|
import type {} from '../../../themes';
|
|
6
|
+
import { uiGeometry } from '../../../themes/ui-geometry';
|
|
6
7
|
import type { Profile } from '../services/profile.service';
|
|
7
8
|
|
|
8
9
|
export function ProfileCard({ profile }: { profile: Profile }) {
|
|
@@ -25,8 +26,8 @@ export function ProfileCard({ profile }: { profile: Profile }) {
|
|
|
25
26
|
);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
const styles = StyleSheet.create(({ color
|
|
29
|
+
const { spacing, radius } = uiGeometry;
|
|
30
|
+
const styles = StyleSheet.create(({ color }) => ({
|
|
30
31
|
card: {
|
|
31
32
|
padding: spacing.xxl,
|
|
32
33
|
gap: spacing.sm,
|
|
@@ -5,14 +5,16 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
5
5
|
import { StyleSheet, useUnistyles } from 'react-native-unistyles';
|
|
6
6
|
import { errorMessage } from '../../../../library/networking/errors';
|
|
7
7
|
import type {} from '../../../../themes';
|
|
8
|
+
import { uiGeometry } from '../../../../themes/ui-geometry';
|
|
8
9
|
import { useAuth } from '../../../auth/hooks/use-auth';
|
|
9
10
|
import { ProfileCard } from '../../components/ProfileCard';
|
|
10
11
|
import { useHome } from './hooks/use-home';
|
|
11
12
|
|
|
12
13
|
export default function HomeScreen() {
|
|
13
14
|
const {
|
|
14
|
-
theme: { color
|
|
15
|
+
theme: { color },
|
|
15
16
|
} = useUnistyles();
|
|
17
|
+
const { spacing } = uiGeometry;
|
|
16
18
|
const query = useHome();
|
|
17
19
|
const auth = useAuth();
|
|
18
20
|
const insets = useSafeAreaInsets();
|
|
@@ -86,7 +88,8 @@ export default function HomeScreen() {
|
|
|
86
88
|
);
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
const
|
|
91
|
+
const { spacing } = uiGeometry;
|
|
92
|
+
const styles = StyleSheet.create(({ color }) => ({
|
|
90
93
|
content: {
|
|
91
94
|
padding: spacing.xxl,
|
|
92
95
|
gap: spacing.xxl,
|
|
@@ -4,12 +4,14 @@ import { ActivityIndicator, ScrollView, View } from 'react-native';
|
|
|
4
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
5
|
import { StyleSheet, useUnistyles } from 'react-native-unistyles';
|
|
6
6
|
import type {} from '../../../themes';
|
|
7
|
+
import { uiGeometry } from '../../../themes/ui-geometry';
|
|
7
8
|
import { useAuth } from '../hooks/use-auth';
|
|
8
9
|
|
|
9
10
|
export default function LoginScreen() {
|
|
10
11
|
const {
|
|
11
|
-
theme: { color
|
|
12
|
+
theme: { color },
|
|
12
13
|
} = useUnistyles();
|
|
14
|
+
const { spacing } = uiGeometry;
|
|
13
15
|
const auth = useAuth();
|
|
14
16
|
const insets = useSafeAreaInsets();
|
|
15
17
|
return (
|
|
@@ -64,7 +66,8 @@ export default function LoginScreen() {
|
|
|
64
66
|
);
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
const
|
|
69
|
+
const { spacing } = uiGeometry;
|
|
70
|
+
const styles = StyleSheet.create(({ color }) => ({
|
|
68
71
|
content: {
|
|
69
72
|
padding: spacing.xxl,
|
|
70
73
|
gap: spacing.xxl,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createUITheme } from '@thanhphi/ui360';
|
|
2
|
+
import { ratioW } from '../library/utils/responsive';
|
|
3
|
+
import { lightColors } from './colors/light';
|
|
4
|
+
|
|
5
|
+
// Only export geometry. The palette here never replaces the active host colors.
|
|
6
|
+
// Pure factory: no Unistyles registration, no dependency on host geometry.
|
|
7
|
+
const { spacing, radius, typography } = createUITheme({
|
|
8
|
+
colors: lightColors,
|
|
9
|
+
scale: ratioW(1),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const uiGeometry = { spacing, radius, typography };
|