Files
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

259 lines
10 KiB
Markdown

# Build and Tooling
This page documents the platform-side build pipeline, local dev server plugins, generated artifacts, and validation commands.
MobileGym is a single Vite project, not a monorepo. Runtime code lives under `os/`, `apps/`, and `system/`; benchmark tooling lives under `bench_env/`.
## Package scripts
| Command | Current behavior |
|---|---|
| `npm run dev` | Starts Vite on port `3000`, host `0.0.0.0`. |
| `npm run build` | Runs `vite build`. |
| `npm run preview` | Runs `vite preview`. |
| `npm run test` | Runs `vitest run`. |
| `npm run test:watch` | Runs `vitest`. |
| `npm run lint` | Runs ESLint on `os/` and `apps/`, then `scripts/lint_store_getters.mjs`. |
| `npm run build:web-css` | Builds `web/tailwind.css` with the Tailwind CLI. |
| `npm run watch:web-css` | Watches the standalone web CSS entry. |
## TypeScript
`tsconfig.json` uses:
| Setting | Value |
|---|---|
| target | `ES2022` |
| module | `ESNext` |
| module resolution | `bundler` |
| JSX | `react-jsx` |
| strict | `true` |
| path alias | `@/*` maps to the repository root |
The main TS project includes `index.tsx`, `os/**/*.ts(x)`, `apps/**/*.ts(x)`, and `system/**/*.ts(x)`. It excludes generated/output-heavy directories such as `dist`, `runs`, `bench_env`, `ui_dumps`, and extracted asset folders.
For large runtime changes, run:
```bash
npx tsc --noEmit
```
Small doc/style/data changes do not require a full type check.
## ESLint
`eslint.config.js` applies to runtime code in:
- `os/**/*.{ts,tsx}`
- `apps/**/*.{ts,tsx}`
- `system/**/*.{ts,tsx}`
Rules currently enforced:
| Rule | Why |
|---|---|
| No bare `Date.now()` | Runtime code must use `TimeService.now()` or `TimeService.realNow()` depending on simulated vs wall-clock time. |
| No `new Date(...)` | Runtime code must use `TimeService.getDate()`, `fromTimestamp()`, `fromLocalParts()`, or parsing helpers. |
| React Hooks rules | Standard hook correctness. |
| React Hooks exhaustive deps | Warning-level dependency checks. |
`os/TimeService.ts` is exempt from the Date restrictions because it implements the abstraction.
`scripts/lint_store_getters.mjs` catches Zustand patterns that do not re-render correctly, especially store actions used as query getters and subscriptions to stable function references.
## Vite config
The project uses `vite.config.ts` with React and several project-specific plugins.
### Server settings
| Setting | Value |
|---|---|
| port | `3000` |
| host | `0.0.0.0` |
| allowedHosts | `true` |
| ignored watch path | `**/runs/**` |
### Resolve settings
| Setting | Value |
|---|---|
| alias | `@` → repository root |
| dedupe | `react`, `react-dom`, `lucide-react` |
| optimizeDeps exclude | `@sqlite.org/sqlite-wasm` |
`@sqlite.org/sqlite-wasm` is excluded so its own `new URL("sqlite3.wasm", import.meta.url)` path resolves beside the package assets instead of Vite's optimized dependency cache.
## Vite plugins
| Plugin | Dev | Build/preview | Purpose |
|---|---|---|---|
| `tailwindCliPlugin` | Builds `index.css`, then starts a Tailwind CLI watcher. | Builds `index.css` during `buildStart`. | Uses Tailwind CSS v4 CLI instead of the JS plugin. |
| `react()` | yes | yes | React transform and Fast Refresh. |
| `accessLogPlugin` | yes | preview | Logs method, URL, status, and timing. |
| `serveAppAssetsPlugin` | yes | build | Serves or emits app-owned assets under `/@app-assets/<AppName>/...`. |
| `serveCdnPlugin` | yes | preview | Maps `/cdn/` to repo-local `mobilegym-data/`. |
| `listPublicFilesPlugin` | yes | no | Provides `/api/list-public-files` for local viewers. |
| `fileSystemPlugin` | yes | build | Provides `/api/sdcard` in dev and emits `sdcard/manifest.json` in builds. |
| `runsExplorerPlugin` | yes | no | Provides `/api/runs` for local run exploration. |
| `apiGatewayPlugin` | yes | preview | Provides `/api/gw/<service>/...` same-origin gateway endpoints. |
## App assets
App-owned binary assets should live under:
- `apps/<AppName>/assets/`
- `system/<AppName>/assets/`
They are available through stable URLs:
```text
/@app-assets/<AppName>/<path>
```
WMR bundles owned by an app may live under:
```text
apps/<AppName>/wmr/
system/<AppName>/wmr/
```
and are served as:
```text
/@app-assets/<AppName>/wmr/<path>
```
In dev, the plugin serves files from disk. In production builds, it emits them into `dist/@app-assets/`.
Prefer Vite imports for ordinary component assets when possible. Use the stable URL path when a subsystem needs a URL string, such as WMR XML/image resources.
## CDN data
`serveCdnPlugin` maps:
```text
/cdn/<path>
```
to:
```text
mobilegym-data/<path>
```
The helper in `os/utils/cdn.ts` should be used when runtime code needs URLs for this local/production-compatible data root.
## Virtual SD card
`fileSystemPlugin` scans `public/sdcard`.
| Endpoint/artifact | Behavior |
|---|---|
| `GET /api/sdcard` | Dev-only JSON listing of files and directories. Cached until watched files change. |
| `dist/sdcard/manifest.json` | Build artifact emitted from the same scan logic. |
The scanner intentionally keeps system-style dot directories such as `.data` and `.cache`, but skips known noise files like `.DS_Store` and `.manifest.json`.
## API gateway
`apiGatewayPlugin` is the same-origin gateway used by `NetworkService`.
| Service | Endpoint | Notes |
|---|---|---|
| `fetch` | `POST /api/gw/fetch` | JSON payload with `url`, `method`, `headers`, and string `body`. |
| `proxy` | `/api/gw/proxy?url=https://...` | Streams requests/responses for assets and non-string bodies. |
`VITE_GW_ALLOW_HOSTS` can be set to a comma-separated host allowlist. If it is empty, dev mode allows all hosts.
The gateway keeps a server-side cookie jar per `x-gw-session` and contains defensive handling for upstream compression, aborts, retries, and repeated 404 image/video fetches.
## Navigation artifacts
Run after modifying navigation declarations:
```bash
node scripts/build_nav_artifacts.mjs <AppName>
```
Useful variants:
```bash
node scripts/build_nav_artifacts.mjs <AppName> --data data/index.ts
node scripts/build_nav_artifacts.mjs <AppName> --skip-tasks
node scripts/check_navigation_declaration_consistency.mjs <AppName> --actions
```
Generated files are written under `public/`:
| Artifact | Meaning |
|---|---|
| `<app>_nav_graph.json` | Full schema graph. |
| `<app>_nav_graph_simplified.json` | Route-level graph with uiStates collapsed. |
| `<app>_data_graph.json` | Concrete data-mode graph when `--data` is provided. |
| `<app>_action_tasks.json` / `.jsonl` | Generated action trajectories from the schema graph. |
| `<app>_action_tasks_data.json` | Generated action trajectories from the data graph when `--data` is provided. |
See [`../navigation/declaration.md`](../navigation/declaration.md) and [`../navigation/data-sources.md`](../navigation/data-sources.md) for declaration semantics.
## WMR tooling
WMR is the Widget Markup Runtime used by launcher widgets and app-owned canvas widgets.
Current implementation lives under:
| Area | Files |
|---|---|
| Widget discovery | `os/wmr/WmrWidgetService.ts` |
| Bundle loading/cache | `os/wmr/WmrBundleCache.ts` |
| XML parser | `os/wmr/engine/parser.ts` |
| Expression/variable runtime | `os/wmr/engine/expression.ts`, `os/wmr/engine/variables.ts` |
| Canvas renderer | `os/wmr/engine/renderer.ts`, `os/wmr/WmrRenderer.tsx` |
| Content providers | `os/wmr/engine/contentProviders.ts` |
| Perf metrics | `os/wmr/WmrPerf.ts` via `window.__WMR_PERF__` |
Theme widgets are listed from `cdn('themes')/manifest.json`. App-owned WMR bundles can use inline sources and `/@app-assets/<AppName>/wmr/...` URLs.
## i18n tooling
OS-level i18n lives in:
| File | Role |
|---|---|
| `os/locale.ts` | Locale state, backed by `OsStateStore.settings.global.language`. |
| `os/i18n/index.ts` | `useOsT()`, `osT()`, and app-name localization helpers. |
| `os/i18n/en.ts` | English dictionary and `patchAppNames()`. |
| `os/PackageManagerService.ts` | Auto-discovers manifests and patches `displayNameEn` into the English dictionary. |
Manifest `displayNameEn` values are injected automatically; new apps should not edit `os/i18n/en.ts` just to add their app name.
## Validation checklist
| Change type | Suggested check |
|---|---|
| Navigation declaration or new page route | `node scripts/build_nav_artifacts.mjs <AppName>` |
| Runtime TypeScript behavior across several files | `npx tsc --noEmit` |
| OS/app runtime code | `npm run lint` |
| System app runtime code | `npx eslint system/` |
| Store action/query selector changes | `node scripts/lint_store_getters.mjs [AppName...]` |
| Bench tasks | Follow `bench_env/docs/task/` guides before editing or running tasks. |
## New app checklist
Use this as the compact version of the app onboarding checklist:
| Area | Check |
|---|---|
| Manifest | `manifest.ts` exports `manifest`, has a unique `id`, `displayName`, `displayNameEn`, icon, theme, and any `intentFilters`. |
| Entry component | `<Name>App.tsx` matches `*App.tsx`, has a default export, wraps routes in `MemoryRouter`, and calls `useAppNavigationHandler` inside the router. |
| Navigation | Every route, discrete UI state, transition, action, and scroll container is declared in `navigation.declaration.ts`. |
| Business navigation | Pages use the app's `go()` / `back()` helper, never direct `useNavigate()` imports. |
| DOM tagging | Interactive controls use gesture hooks so they emit `data-trigger*` or `data-action*`; back/close controls bind `system.back`. |
| Data | Structural constants live in `constants.ts`; replaceable runtime defaults live in `data/defaults.json`; `data/index.ts` exports `<APPNAME>_CONFIG`. |
| Large data | Big public datasets stay out of `defaults.json`; add `loader.ts` and decide hook vs store-action vs service consumption. |
| Store | `state.ts` uses `createAppStore*` with the same appId as `manifest.id`; `_temp` is reserved for transient UI state. |
| Resources | App icons are `Ic*` aliases in `res/icons.tsx`; data-driven icon names also use `Ic*`. |
| UI chrome | Page roots reserve status bar space and declare status/navigation bar foreground when needed. |
| Keyboard | Bottom input bars use flex layout plus `data-keep-keyboard`; tab bars that should disappear use `data-hide-on-keyboard`. |
| Validation | Run `node scripts/build_nav_artifacts.mjs <AppName>` after navigation changes; run type/lint checks when the runtime change is broad. |