@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 thanhphi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,80 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @thanhphi/create-360-miniapp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Khởi tạo mini app theo cấu trúc Event và Time-Management: Expo 55, React Native 0.83.6, TypeScript, React Navigation, Redux Toolkit, TanStack Query và Re.Pack 5. Template có sẵn module auth, module nghiệp vụ mẫu, kết nối token host qua `@thanhphi/miniapp-sdk`, env staging/beta/prod và pipeline Azure.
|
|
4
|
+
|
|
5
|
+
## Dùng ngay từ source
|
|
6
|
+
|
|
7
|
+
Từ `mysgr-shared`:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
node packages/create-360-miniapp/bin/create-360-miniapp.cjs leave-management --module leave --app-code LVE --port 8085
|
|
11
|
+
cd leave-management
|
|
12
|
+
npm install
|
|
13
|
+
npm run env:staging
|
|
14
|
+
npm run start:mf
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Lệnh chỉ tạo thư mục mới, không ghi đè app hiện có. Mặc định không install hoặc tạo Git repository. `--install` cài dependencies; `--native` cài rồi chạy Expo prebuild để tạo project `ios/` và `android/`. Native project dùng ID riêng của app mới. Chi tiết auth, cấu trúc và pipeline nằm trong README của app được sinh.
|
|
18
|
+
|
|
19
|
+
## Chạy bằng npx từ tarball local
|
|
20
|
+
|
|
21
|
+
Từ `mysgr-shared`, sau `npm run pack:check`:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npx --offline --yes --cache ./artifacts/.npm-cache --package ./artifacts/thanhphi-create-360-miniapp-0.2.0.tgz create-360-miniapp leave-management --module leave --app-code LVE
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Lệnh này dùng package đã đóng gói, không cần publish registry hoặc source Event/Time-Management. Cache nằm trong `artifacts`, cùng workspace.
|
|
28
|
+
|
|
29
|
+
## Qua npm sau khi publish package
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npx @thanhphi/create-360-miniapp@0.2.0 leave-management --module leave --app-code LVE
|
|
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
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Theo [quy tắc npm initializer](https://docs.npmjs.com/cli/v11/commands/npm-init/), `npm create @thanhphi/360-miniapp` chạy package `@thanhphi/create-360-miniapp`. Package này cần được publish trước khi tên registry hoạt động.
|
|
38
|
+
|
|
39
|
+
Tên package có scope `@thanhphi/create-360-miniapp` là tên đầy đủ trên npm; `create-360-miniapp` là tên executable trong `bin`. Package `create-360-miniapp` không có scope và package cùng tên dưới scope khác có thể do người khác sở hữu. Để chọn đúng generator, dùng tên có scope như ví dụ trên hoặc chỉ định tarball bằng `--package`. Scope cho phép cùng tên package dưới các namespace khác nhau; publish cần quyền trong scope tương ứng. Xem [npm scopes](https://docs.npmjs.com/about-scopes/).
|
|
40
|
+
|
|
41
|
+
## Options
|
|
42
|
+
|
|
43
|
+
| Option | Mặc định |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `--module` | Tên app, dùng cho `src/modules/<module>` |
|
|
46
|
+
| `--container` | PascalCase tên app, dùng cho federation container |
|
|
47
|
+
| `--app-code` | Tối đa 12 ký tự đầu tên app, viết hoa |
|
|
48
|
+
| `--bundle-id` | `com.sungroup.<tên-app-bỏ-dấu-gạch>` |
|
|
49
|
+
| `--display-name` | Tên container |
|
|
50
|
+
| `--port` | `8085`; chọn port khác nếu có mini app đang chạy |
|
|
51
|
+
| `--env` | `staging`; hỗ trợ `beta`, `prod` |
|
|
52
|
+
| `--install` | Không cài dependencies nếu bỏ option |
|
|
53
|
+
| `--native` | Không prebuild nếu bỏ option |
|
|
54
|
+
|
|
55
|
+
## Kiểm tra và đóng gói
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
npm run test:create
|
|
59
|
+
npm run pack:check
|
|
60
|
+
# Hoặc chỉ package generator:
|
|
61
|
+
npm pack --workspace @thanhphi/create-360-miniapp --pack-destination artifacts
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Generator không có runtime dependency: tarball chứa CLI và toàn bộ template, không cần truy cập thư mục Event/Time-Management khi người dùng chạy CLI. Bộ kiểm tra source/generator có guard cho entry hosted–standalone, capability auth, token không persist, provider chuyển tiếp token và cách dùng shared UI. Kết quả kiểm thử app sinh của bản trước là snapshot lịch sử; đợt chuẩn hóa hiện tại chưa chạy lại build/test/typecheck hoặc tạo tarball mới.
|
|
65
|
+
|
|
66
|
+
Để publish sau khi kiểm tra tarball và đăng nhập đúng tài khoản:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
npm publish --workspace @thanhphi/create-360-miniapp --access public
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Không publish root workspace. Đây là hướng dẫn phát hành; việc tạo source/tarball không tự publish lên npm.
|
|
73
|
+
|
|
74
|
+
Template 0.2.0 dùng contract tên màu của UI360 0.2.0, Unistyles 3.2.5/Nitro 0.36.5. Generator tạo đầy đủ mã hex trong src/themes/colors/light.ts và dark.ts để app tự quản lý. Một factory native tạo đủ màu/mode/spacing/radius/typography; ApplicationProvider chỉ chuyển tiếp token đang hoạt động, không tạo lại default hoặc scale. Màn hình/navigation dùng Unistyles color; shared UI giữ useUITheme bên trong package.
|
|
75
|
+
|
|
76
|
+
Hosted không khởi tạo global, nhận toàn bộ native theme của host. Standalone cấu hình tại src/themes/standalone.ts; factory mặc định dùng DEVICE_WIDTH / 375 như host 360 và typography UI360 body 14/title 18 trước scale, không giữ body 16/title 22/scale 1 của template cũ. Scale áp dụng một lần tại factory, override số đã là dp; không qua props.uiTheme. Màn mẫu dùng Button/UIText trực tiếp và style lấy token theme, không có ActionButton chỉ để bọc shared Button.
|
|
77
|
+
|
|
78
|
+
App mới bắt buộc nhận HostCapabilities khi hosted: identity/token từ auth.getSnapshot/subscribe/refreshIfNeeded và đóng app qua closeMiniApp. Không có adapter props token/callback/sessionId cũ, không suy diễn identity từ access token. Exposed App thiếu capability báo host không tương thích, không tự chuyển sang OIDC; chỉ entry standalone chọn adapter đăng nhập độc lập. Host hiện tại đã cấp capability/session ID, nhưng cần cập nhật hoặc gate catalog cho các bản host cũ trước rollout. param_supper_app vẫn phục vụ dữ liệu mở app/thông báo.
|
|
79
|
+
|
|
80
|
+
Chưa tách runtime riêng. Card trắng dùng background, nền màn dùng base3; textPrimary không còn là chữ nội dung. Cần đóng gói/phát hành generator mới để npx từ registry có template này; thay source chưa tự cập nhật tarball hoặc app đã sinh. Lần thay đổi source này không chạy install, build/test, typecheck/lint hoặc publish.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawnSync } = require('node:child_process');
|
|
3
|
+
const { createApp } = require('../lib/create.cjs');
|
|
4
|
+
|
|
5
|
+
const help = `Create a MySGR mini app (Expo 55 / React Native 0.83 / Re.Pack 5)
|
|
6
|
+
|
|
7
|
+
npx @thanhphi/create-360-miniapp my-app [options]
|
|
8
|
+
npm create @thanhphi/360-miniapp@latest my-app -- [options]
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--module <slug> Feature module folder (default: app name)
|
|
12
|
+
--container <Name> Federation container (default: PascalCase app name)
|
|
13
|
+
--app-code <CODE> Backend mini-app code
|
|
14
|
+
--bundle-id <id> Unique Android/iOS application ID
|
|
15
|
+
--display-name <name> Native display name
|
|
16
|
+
--port <number> Re.Pack dev server port (default: 8085)
|
|
17
|
+
--env <flavor> Initial staging, beta or prod (default: staging)
|
|
18
|
+
--install Install dependencies and create package-lock.json
|
|
19
|
+
--native Install if needed, then generate ios/ and android/
|
|
20
|
+
--help Show usage
|
|
21
|
+
--version Show generator version
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
function main(argv) {
|
|
25
|
+
if (argv.includes('--help') || argv.includes('-h')) return process.stdout.write(help);
|
|
26
|
+
if (argv.includes('--version')) return process.stdout.write(require('../package.json').version + '\n');
|
|
27
|
+
const options = {};
|
|
28
|
+
const values = { '--module': 'module', '--container': 'container', '--app-code': 'appCode', '--bundle-id': 'bundleId', '--display-name': 'displayName', '--port': 'port', '--env': 'env' };
|
|
29
|
+
for (let i = 0; i < argv.length; i++) {
|
|
30
|
+
const arg = argv[i];
|
|
31
|
+
if (arg === '--') continue;
|
|
32
|
+
if (arg === '--install' || arg === '--native') options[arg.slice(2)] = true;
|
|
33
|
+
else if (values[arg]) {
|
|
34
|
+
const value = argv[++i];
|
|
35
|
+
if (!value || value.startsWith('--')) throw new Error(`Missing value for ${arg}.`);
|
|
36
|
+
options[values[arg]] = value;
|
|
37
|
+
} else if (arg.startsWith('-')) throw new Error(`Unknown option: ${arg}.`);
|
|
38
|
+
else if (options.name) throw new Error(`Unexpected argument: ${arg}.`);
|
|
39
|
+
else options.name = arg;
|
|
40
|
+
}
|
|
41
|
+
const app = createApp(options);
|
|
42
|
+
process.stdout.write(`Created ${app.appName} at ${app.destination}\n`);
|
|
43
|
+
function run(args) {
|
|
44
|
+
const result = spawnSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', args, { cwd: app.destination, stdio: 'inherit', shell: process.platform === 'win32' });
|
|
45
|
+
if (result.error) throw result.error;
|
|
46
|
+
if (result.status !== 0) throw new Error(`npm ${args.join(' ')} failed. App files remain in ${app.destination}; fix the error and retry there.`);
|
|
47
|
+
}
|
|
48
|
+
if (options.install || options.native) run(['install']);
|
|
49
|
+
if (options.native) run(['exec', '--', 'expo', 'prebuild']);
|
|
50
|
+
process.stdout.write(`\nNext:\n cd ${options.name}\n${options.install || options.native ? '' : ' npm install\n'} npm run env:${app.environment}\n npm run start:mf\n\nStandalone: npm run ios:${app.environment} or android:${app.environment}\nRead README.md for env, auth, host registration and pipeline setup.\n`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try { main(process.argv.slice(2)); }
|
|
54
|
+
catch (error) { process.stderr.write(`create-360-miniapp: ${error.message}\n`); process.exitCode = 1; }
|
package/lib/create.cjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
const template = path.resolve(__dirname, '../template');
|
|
5
|
+
const reserved = new Set(['auth', 'common', 'library', 'redux', 'navigation', 'themes']);
|
|
6
|
+
|
|
7
|
+
function createApp(options, cwd = process.cwd()) {
|
|
8
|
+
const name = options.name;
|
|
9
|
+
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(name || '')) {
|
|
10
|
+
throw new Error('App name must be a lowercase slug, e.g. leave-management.');
|
|
11
|
+
}
|
|
12
|
+
const moduleName = options.module || name;
|
|
13
|
+
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(moduleName) || reserved.has(moduleName)) {
|
|
14
|
+
throw new Error('Module must be a lowercase feature name; auth and framework folders are reserved.');
|
|
15
|
+
}
|
|
16
|
+
const appName = options.container || name.split('-').map(part => part[0].toUpperCase() + part.slice(1)).join('');
|
|
17
|
+
if (!/^[A-Z][A-Za-z0-9]*$/.test(appName)) throw new Error('Container must be a PascalCase identifier.');
|
|
18
|
+
const appCode = options.appCode || name.replace(/-/g, '').slice(0, 12).toUpperCase();
|
|
19
|
+
if (!/^[A-Z][A-Z0-9_]{0,31}$/.test(appCode)) throw new Error('App code must contain uppercase letters, digits or underscore.');
|
|
20
|
+
const port = Number(options.port || 8085);
|
|
21
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new Error('Port must be an integer between 1024 and 65535.');
|
|
22
|
+
const bundleId = options.bundleId || `com.sungroup.${name.replace(/-/g, '')}`;
|
|
23
|
+
if (!/^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*){2,}$/.test(bundleId)) throw new Error('Bundle ID must be a lowercase reverse-domain identifier without underscores.');
|
|
24
|
+
const displayName = options.displayName || appName;
|
|
25
|
+
// The same replacement is used in JS, JSON, Markdown and YAML. Keep it safe in all formats.
|
|
26
|
+
if (!/^[\p{L}\p{N} _.-]{1,80}$/u.test(displayName)) throw new Error('Display name can contain letters, numbers, spaces, dots, underscores and hyphens.');
|
|
27
|
+
const environment = options.env || 'staging';
|
|
28
|
+
if (!['staging', 'beta', 'prod'].includes(environment)) throw new Error('Environment must be staging, beta or prod.');
|
|
29
|
+
const destination = path.resolve(cwd, name);
|
|
30
|
+
if (fs.existsSync(destination)) throw new Error(`Destination already exists: ${destination}. Choose a new directory.`);
|
|
31
|
+
const replacements = {
|
|
32
|
+
__APP_NAME__: appName, __DISPLAY_NAME__: displayName, __PACKAGE_NAME__: name,
|
|
33
|
+
__APP_ID__: name, __APP_CODE__: appCode, __MODULE__: moduleName,
|
|
34
|
+
__PORT__: String(port), __BUNDLE_ID__: bundleId,
|
|
35
|
+
};
|
|
36
|
+
function render(value) {
|
|
37
|
+
return value.replace(/__(?:APP_NAME|DISPLAY_NAME|PACKAGE_NAME|APP_ID|APP_CODE|MODULE|PORT|BUNDLE_ID)__/g, key => replacements[key]);
|
|
38
|
+
}
|
|
39
|
+
function copy(directory, target) {
|
|
40
|
+
fs.mkdirSync(target, { recursive: true });
|
|
41
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
42
|
+
const source = path.join(directory, entry.name);
|
|
43
|
+
// npm strips .gitignore; keep it as a normal file inside the published template.
|
|
44
|
+
const output = path.join(target, entry.name === 'gitignore' ? '.gitignore' : render(entry.name));
|
|
45
|
+
if (entry.isDirectory()) copy(source, output);
|
|
46
|
+
else if (entry.isFile()) fs.writeFileSync(output, render(fs.readFileSync(source, 'utf8')));
|
|
47
|
+
else throw new Error(`Unsupported template entry: ${source}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
copy(template, destination);
|
|
51
|
+
const { syncEnv } = require(path.join(destination, 'scripts/sync-env.js'));
|
|
52
|
+
syncEnv(environment, destination);
|
|
53
|
+
return { destination, appName, moduleName, environment, port };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { createApp };
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thanhphi/create-360-miniapp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
}
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Create a MySGR Expo and Re.Pack mini app with modules, host auth and Azure pipelines",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": { "create-360-miniapp": "bin/create-360-miniapp.cjs" },
|
|
7
|
+
"files": ["bin", "lib", "template", "README.md", "LICENSE"],
|
|
8
|
+
"scripts": { "test": "node --test tests/*.test.cjs" },
|
|
9
|
+
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
|
|
10
|
+
"engines": { "node": ">=20" }
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = { root: true, extends: '@react-native', ignorePatterns: ['build/', 'env/env-config.ts'] };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = { singleQuote: true, trailingComma: 'all', arrowParens: 'avoid' };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
package/template/App.tsx
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# __DISPLAY_NAME__
|
|
2
|
+
|
|
3
|
+
Base TypeScript lấy Event làm nền, giữ thêm `constants`, `hooks`, `services` quen thuộc của Time Management; expose `__APP_NAME__/MiniApp` để MySGR tải qua Re.Pack, chạy độc lập bằng Expo là tùy chọn phục vụ dev. Đây là chuẩn cho app mới, không mang adapter host cũ hoặc quy tắc giao diện legacy của các app nguồn.
|
|
4
|
+
|
|
5
|
+
## Theme 360 / UI360 0.2.0
|
|
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.
|
|
17
|
+
|
|
18
|
+
| Cấu hình | Giá trị |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| Package | `__PACKAGE_NAME__` |
|
|
21
|
+
| Federation container | `__APP_NAME__` |
|
|
22
|
+
| App ID / code trong catalog | `__APP_ID__` / `__APP_CODE__` |
|
|
23
|
+
| Module nghiệp vụ | `__MODULE__` |
|
|
24
|
+
| Re.Pack port | `__PORT__` |
|
|
25
|
+
| Native bundle ID | `__BUNDLE_ID__` |
|
|
26
|
+
|
|
27
|
+
## Tạo project khác từ base
|
|
28
|
+
|
|
29
|
+
Sau khi `@thanhphi/create-360-miniapp` được publish lên registry:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npx @thanhphi/create-360-miniapp@latest demo --module sample --port 8085 --bundle-id com.sungroup.demo
|
|
33
|
+
npm create @thanhphi/360-miniapp@latest demo -- --module sample --port 8085 --bundle-id com.sungroup.demo
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`npm create @thanhphi/360-miniapp` ánh xạ tới `@thanhphi/create-360-miniapp` theo [npm init/create](https://docs.npmjs.com/cli/v11/commands/npm-init/). Khi chưa publish, chạy từ thư mục `mysgr-shared`:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
node packages/create-360-miniapp/bin/create-360-miniapp.cjs demo --module sample --port 8085 --bundle-id com.sungroup.demo
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
CLI mặc định chỉ sinh file. Thêm `--install` để chạy npm install, thêm `--native` để cài dependencies rồi prebuild native. CLI không khởi tạo git repository.
|
|
43
|
+
|
|
44
|
+
## Chạy lần đầu
|
|
45
|
+
|
|
46
|
+
Dùng Node/npm theo `package.json`, Xcode/CocoaPods để chạy iOS, Android SDK/JDK để chạy Android. Cấu hình env trước khi tạo native build.
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
npm install
|
|
50
|
+
npm run env:staging
|
|
51
|
+
npx expo prebuild
|
|
52
|
+
npm run ios:staging
|
|
53
|
+
# hoặc
|
|
54
|
+
npm run android:staging
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`npx expo prebuild` tạo project `ios/` và `android/` theo `__BUNDLE_ID__`; source/env/pipeline giữ nguyên. AppAuth và các native dependencies cần development build. Xem [Expo SDK 55](https://docs.expo.dev/versions/v55.0.0/) và [Expo prebuild](https://docs.expo.dev/workflow/continuous-native-generation/).
|
|
58
|
+
|
|
59
|
+
Commit lockfile được sinh sau `npm install` để CI có thể dùng `npm ci`. Lần sau dùng:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
npm run start:staging
|
|
63
|
+
npm run start:beta
|
|
64
|
+
npm run start:prod
|
|
65
|
+
npm run typecheck
|
|
66
|
+
npm run lint
|
|
67
|
+
npm test
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Thư mục và nơi thêm code
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
assets/ # static assets cấp app
|
|
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
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Thêm màn/service/state trong `src/modules/__MODULE__`; endpoint thuộc service của module. `src/services/application.ts` chỉ nối các thành phần cấp app. `ios/`/`android/` được Expo prebuild sinh khi chạy độc lập. Không cần native build riêng để chỉ tạo remote bundle.
|
|
101
|
+
|
|
102
|
+
## Env
|
|
103
|
+
|
|
104
|
+
Sửa `env/staging.json`, `env/beta.json`, `env/prod.json`. Script sinh `env/env-config.ts`; không sửa file sinh trực tiếp. Các lệnh có hậu tố môi trường sync JSON trước khi start/build.
|
|
105
|
+
|
|
106
|
+
```json
|
|
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
|
+
```
|
|
119
|
+
|
|
120
|
+
Issuer, client ID, scope và URL là placeholder; thay bằng cấu hình public của provider/API. Mọi giá trị env này được đưa vào bundle. Không đặt client secret, private key, mật khẩu hay token trong JSON. Secret CI dùng secret variables/service connections.
|
|
121
|
+
|
|
122
|
+
Các script gọi trực tiếp Expo/Re.Pack như Event và Time Management. Chọn môi trường bằng `env:staging`, `env:beta` hoặc `env:prod` trước khi chạy Re.Pack hoặc bundle. Các lệnh `start:<flavor>`, `ios:<flavor>` và `android:<flavor>` chọn env rồi gọi Expo trực tiếp. Đổi env cần restart Metro/Re.Pack.
|
|
123
|
+
|
|
124
|
+
Khi đổi redirect URL, cập nhật cấu hình provider và chạy lại prebuild/build để native redirect được áp dụng. App config đọc redirect cho AppAuth plugin. Xem [AppAuth Expo config plugin](https://github.com/FormidableLabs/react-native-app-auth/blob/main/docs/docs/usage/expo-setup.md).
|
|
125
|
+
|
|
126
|
+
## Auth và token khi chạy trong MySGR
|
|
127
|
+
|
|
128
|
+
Hosted bắt buộc dùng `HostCapabilities`. Host hiện tại đã cấp capability/session ID chung; template không nhận các prop token/callback/session ID legacy và không có adapter tự suy diễn identity từ access token.
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
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
|
+
```
|
|
150
|
+
|
|
151
|
+
Host cấp session ID mới khi login/logout/đổi tài khoản, giữ session ID khi refresh token; cập nhật snapshot trước khi phát subscription. Host sở hữu refresh token, persistence và coordinator refresh chung. Không đưa refresh token vào props mini app.
|
|
152
|
+
|
|
153
|
+
Mini app đọc identity/token bằng `auth.getSnapshot()`, theo dõi thay đổi bằng `auth.subscribe()` và nhờ host refresh qua `auth.refreshIfNeeded()`; đóng app qua `closeMiniApp()`. `param_supper_app` vẫn là dữ liệu mở app/thông báo, không dùng để cấp token hay nhận diện phiên. Một lần refresh chỉ đổi access token không được reset dữ liệu nghiệp vụ nếu session ID không đổi.
|
|
154
|
+
|
|
155
|
+
Exposed `App` luôn là hosted: thiếu capability thì báo cấu hình host không tương thích, không tự chuyển sang màn OIDC standalone. Chỉ entry standalone chủ động chọn adapter OIDC. Host đã phát hành nhưng chưa có capability không tương thích template mới; phải cập nhật binary/host hoặc chặn phiên bản qua catalog trước khi deploy remote này. Base không thay host registry; thêm app ID `__APP_ID__`, container `__APP_NAME__`, import `__APP_NAME__/MiniApp` và URL/port `__PORT__` trong registry/resolver của MySGR theo môi trường triển khai.
|
|
156
|
+
|
|
157
|
+
HTTP dùng `@thanhphi/miniapp-sdk`: tự gắn `Authorization: Bearer <accessToken>`, refresh và retry tối đa một lần sau HTTP 401. Client nhận route tương đối theo `BASE_URL`; service module tự unwrap response envelope. Demo gọi đường dẫn `/me`; chỉnh `PROFILE_PATH` trong service của module cho backend thật. Nếu backend báo token hết hạn trong HTTP 200 envelope, cấu hình `isAuthExpired` trong `src/library/networking/client.ts` theo response contract của API.
|
|
158
|
+
|
|
159
|
+
Service dùng client được tạo theo runtime, chẳng hạn:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import type { ApiClient } from './src/library/networking/client';
|
|
163
|
+
|
|
164
|
+
export async function loadItem(api: ApiClient, itemId: string) {
|
|
165
|
+
return api.get<{ id: string; name: string }>(
|
|
166
|
+
`/items/${encodeURIComponent(itemId)}`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Runtime/store/client/QueryClient có vòng đời theo mount. Identity đổi reset auth/state và cancel/clear queries của phiên cũ; unmount hủy subscriptions, cancel/clear cache và dispose client/runtime. `AuthError.kind` phân biệt `expired`, `session-changed`, `unavailable`; lỗi mạng tạm thời không được coi như logout.
|
|
172
|
+
|
|
173
|
+
## Auth standalone
|
|
174
|
+
|
|
175
|
+
Chỉ khi chạy entry standalone, app dùng adapter riêng và có màn đăng nhập OIDC qua `react-native-app-auth`. Không phát hiện standalone bằng việc thiếu props host. Adapter standalone đóng vai host, giữ access/refresh token trong bộ nhớ, dùng coordinator để chia sẻ refresh và kiểm tra identity trước khi lưu kết quả. Đóng app hoặc reset runtime sẽ mất phiên này; muốn lưu phiên cần thêm secure native storage. Không persist token do host cấp vào Redux/storage của mini app.
|
|
176
|
+
|
|
177
|
+
Đăng ký native OAuth client và redirect `__BUNDLE_ID__.auth://oauth/redirect` với provider; chọn scopes cho API audience, rồi thay env của từng flavor. `openid/profile/offline_access` không tự cấp quyền gọi API của bạn. Backend MySGR có thể yêu cầu đổi Azure token sang token riêng: thêm flow exchange trong service auth, với endpoint/body đúng backend. Base không giả định endpoint exchange, không dùng ID token làm API access token. Login và profile demo chỉ có dữ liệu thật sau khi provider/API được cấu hình.
|
|
178
|
+
|
|
179
|
+
## Re.Pack và build remote
|
|
180
|
+
|
|
181
|
+
```sh
|
|
182
|
+
npm run env:staging
|
|
183
|
+
# hoặc env:beta / env:prod
|
|
184
|
+
npm run start:mf
|
|
185
|
+
|
|
186
|
+
# Thiết bị thật hoặc reset cache
|
|
187
|
+
npm run start:mf:device
|
|
188
|
+
npm run start:mf:reset
|
|
189
|
+
|
|
190
|
+
# Bundle dùng env đã chọn
|
|
191
|
+
npm run bundle:mf:android
|
|
192
|
+
npm run bundle:mf:ios
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Dev server chạy port `__PORT__`; resolver của host phải trỏ đúng server đó. Với thiết bị thật, dùng IP LAN của máy và bảo đảm thiết bị truy cập được dev server. Host phải có native modules và singleton versions tương thích với dependencies remote; thêm native package vào mini app cần cập nhật binary host trước khi sử dụng.
|
|
196
|
+
|
|
197
|
+
Output deploy:
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
build/outputs/android/remotes/__APP_NAME__.container.js.bundle
|
|
201
|
+
build/outputs/ios/remotes/__APP_NAME__.container.js.bundle
|
|
202
|
+
build/outputs/<platform>/remotes/*.chunk.bundle
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
ZIP toàn bộ nội dung `remotes`, bỏ `*.map`; container cần các chunk đi kèm. `build/generated/<platform>/index.bundle` là entry standalone và không upload cho host. Script clean output từng platform trước bundle để không đưa chunk cũ vào archive. Pipeline giữ SHA-1 vì catalog/host hiện dùng checksum này.
|
|
206
|
+
|
|
207
|
+
## Pipeline Azure DevOps
|
|
208
|
+
|
|
209
|
+
Các YAML trong `pipelines/` chọn flavor/branch và dùng chung `pipelines/templates`. Mặc định chỉ build, đóng ZIP và publish artifacts. Bật deployment sau khi cấu hình Azure/DB thật; xem các parameters/variables trong YAML, không đưa secret vào git.
|
|
210
|
+
|
|
211
|
+
Pipeline có `parameters.deploy: false`, `parameters.agentPool` mặc định Ubuntu và `parameters.nodeVersion`. Các flavor là `pipelines/staging.yml`, `beta.yml`, `production.yml`. Khi `deploy: true`, cấu hình các biến sau trong Azure Pipeline/variable group:
|
|
212
|
+
|
|
213
|
+
| Biến | Mục đích |
|
|
214
|
+
| --- | --- |
|
|
215
|
+
| `MINIAPP_AZURE_SUBSCRIPTION` | Azure service connection có quyền upload Blob |
|
|
216
|
+
| `MINIAPP_STORAGE_ACCOUNT` | Tên Storage Account |
|
|
217
|
+
| `MINIAPP_STORAGE_CONTAINER` | Blob container chứa ZIP |
|
|
218
|
+
| `DB_HOST`, `DB_LOGIN`, `DB_DATABASE` | PostgreSQL host/user/database của mini app catalog |
|
|
219
|
+
| `DB_PASSWORD` | Mật khẩu PostgreSQL, đánh dấu secret |
|
|
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.
|
|
225
|
+
|
|
226
|
+
Build cả Android/iOS remote chỉ tạo JavaScript artifacts, không tạo IPA/APK. Có thể chạy build-only CI trước khi cấu hình deployment.
|
|
227
|
+
|
|
228
|
+
Base chưa tự đăng ký app trên hạ tầng và không tự publish npm package. Provider login/native redirect, native binary, tích hợp host và deployment vào Azure thật cần được kiểm chứng trong môi trường của app.
|
|
229
|
+
|
|
230
|
+
## Kiểm tra trước khi phát hành
|
|
231
|
+
|
|
232
|
+
Source generator có guard kiểm tra entry hosted không kéo standalone setup, chỉ nhận capability auth, không persist token hosted, provider không tạo bộ token thứ hai và màn mẫu dùng shared UI/theme token thay vì wrapper/alias legacy. Chạy `npm run test:create` từ workspace để kiểm tra source generator; các guard này không thay thế thử runtime/native trên host.
|
|
233
|
+
|
|
234
|
+
Đợt chuẩn hóa source này không chạy build/test, install, typecheck/lint, đóng gói hoặc publish. App đã sinh, tarball cũ và package trên registry không tự cập nhật theo template nguồn.
|