@multiplatform.one/cli 6.0.4 → 6.3.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/README.md +72 -0
- package/bin/createMultiplatformApp.mjs +77 -0
- package/lib/bin/multiplatformOne.mjs +23 -9
- package/lib/commands/e2e.mjs +3 -3
- package/lib/commands/init.mjs +16 -12
- package/lib/commands/initApp.mjs +145 -0
- package/lib/generateVscode.mjs +27 -10
- package/package.json +23 -12
- package/src/bin/multiplatformOne.ts +54 -16
- package/src/commands/init.ts +7 -18
- package/src/commands/initApp.spec.ts +210 -0
- package/src/commands/initApp.ts +228 -0
- package/src/generateVscode.ts +31 -9
- package/templates/app/AGENTS.md +142 -0
- package/templates/app/README.md +62 -0
- package/templates/app/apps/__NAME__/index.html +36 -0
- package/templates/app/apps/__NAME__/package.json +30 -0
- package/templates/app/apps/__NAME__/public/fonts/inter-400.woff2 +0 -0
- package/templates/app/apps/__NAME__/public/fonts/inter-700.woff2 +0 -0
- package/templates/app/apps/__NAME__/src/App.tsx +9 -0
- package/templates/app/apps/__NAME__/src/main.tsx +14 -0
- package/templates/app/apps/__NAME__/tsconfig.json +8 -0
- package/templates/app/apps/__NAME__/vite.config.ts +17 -0
- package/templates/app/features/__NAME__/package.json +34 -0
- package/templates/app/features/__NAME__/src/HomeScreen.tsx +70 -0
- package/templates/app/features/__NAME__/src/index.ts +1 -0
- package/templates/app/features/__NAME__/tsconfig.json +5 -0
- package/templates/app/gitignore +8 -0
- package/templates/app/npmrc +2 -0
- package/templates/app/oxlintrc.json +39 -0
- package/templates/app/package.json +20 -0
- package/templates/app/packages/config/config.json +4 -0
- package/templates/app/packages/config/index.ts +5 -0
- package/templates/app/packages/config/package.json +17 -0
- package/templates/app/packages/config/tsconfig.json +5 -0
- package/templates/app/pnpm-workspace.yaml +4 -0
- package/templates/app/tsconfig.base.json +16 -0
- package/templates/universal/AGENTS.md +142 -0
- package/templates/universal/README.md +97 -0
- package/templates/universal/apps/__NAME__/app.config.js +31 -0
- package/templates/universal/apps/__NAME__/config/tamagui.config.ts +4 -0
- package/templates/universal/apps/__NAME__/package.json +61 -0
- package/templates/universal/apps/__NAME__/polyfills.native.ts +23 -0
- package/templates/universal/apps/__NAME__/polyfills.ts +3 -0
- package/templates/universal/apps/__NAME__/public/fonts/inter-400.woff2 +0 -0
- package/templates/universal/apps/__NAME__/public/fonts/inter-700.woff2 +0 -0
- package/templates/universal/apps/__NAME__/routes/_layout.tsx +22 -0
- package/templates/universal/apps/__NAME__/routes/_layout.web.tsx +21 -0
- package/templates/universal/apps/__NAME__/routes/about.tsx +3 -0
- package/templates/universal/apps/__NAME__/routes/index.tsx +3 -0
- package/templates/universal/apps/__NAME__/src/app.ts +10 -0
- package/templates/universal/apps/__NAME__/tamagui.css +3 -0
- package/templates/universal/apps/__NAME__/tsconfig.json +8 -0
- package/templates/universal/apps/__NAME__/types.d.ts +9 -0
- package/templates/universal/apps/__NAME__/vite.config.ts +66 -0
- package/templates/universal/features/__NAME__/package.json +35 -0
- package/templates/universal/features/__NAME__/src/AboutScreen.tsx +27 -0
- package/templates/universal/features/__NAME__/src/AppLayout.tsx +50 -0
- package/templates/universal/features/__NAME__/src/ContactForm.spec.tsx +22 -0
- package/templates/universal/features/__NAME__/src/ContactForm.tsx +55 -0
- package/templates/universal/features/__NAME__/src/HomeScreen.tsx +40 -0
- package/templates/universal/features/__NAME__/src/index.ts +4 -0
- package/templates/universal/features/__NAME__/src/screens.spec.ts +12 -0
- package/templates/universal/features/__NAME__/tsconfig.json +5 -0
- package/templates/universal/features/__NAME__/vitest.config.mjs +7 -0
- package/templates/universal/gitignore +12 -0
- package/templates/universal/npmrc +3 -0
- package/templates/universal/oxlintrc.json +39 -0
- package/templates/universal/package.json +27 -0
- package/templates/universal/packages/config/config.json +4 -0
- package/templates/universal/packages/config/index.ts +5 -0
- package/templates/universal/packages/config/package.json +17 -0
- package/templates/universal/packages/config/tsconfig.json +5 -0
- package/templates/universal/packages/i18n/en/common.json +30 -0
- package/templates/universal/packages/i18n/es/common.json +30 -0
- package/templates/universal/packages/i18n/i18n.d.ts +16 -0
- package/templates/universal/packages/i18n/index.ts +26 -0
- package/templates/universal/packages/i18n/package.json +20 -0
- package/templates/universal/packages/i18n/resources.ts +13 -0
- package/templates/universal/packages/i18n/tsconfig.json +5 -0
- package/templates/universal/packages/themes/accent.ts +32 -0
- package/templates/universal/packages/themes/base.ts +50 -0
- package/templates/universal/packages/themes/fonts.ts +8 -0
- package/templates/universal/packages/themes/index.ts +33 -0
- package/templates/universal/packages/themes/package.json +23 -0
- package/templates/universal/packages/themes/tamagui.config.ts +14 -0
- package/templates/universal/packages/themes/tsconfig.json +5 -0
- package/templates/universal/pnpm-workspace.yaml +4 -0
- package/templates/universal/tsconfig.base.json +21 -0
- package/templates/universal/types/env.d.ts +9 -0
- package/types/bin/multiplatformOne.d.ts +11 -0
- package/types/bin/multiplatformOne.d.ts.map +1 -0
- package/types/commands/e2e.d.ts +14 -0
- package/types/commands/e2e.d.ts.map +1 -0
- package/types/commands/init.d.ts +16 -0
- package/types/commands/init.d.ts.map +1 -0
- package/types/commands/initApp.d.ts +25 -0
- package/types/commands/initApp.d.ts.map +1 -0
- package/types/generateVscode.d.ts +7 -0
- package/types/generateVscode.d.ts.map +1 -0
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -0
- package/types/types.d.ts +6 -0
- package/types/types.d.ts.map +1 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# AGENTS.md — multiplatform.one conventions
|
|
2
|
+
|
|
3
|
+
Machine-readable house rules for humans and coding agents. Prefer this file over
|
|
4
|
+
reconstructing conventions from meeting notes. Detailed standards live under
|
|
5
|
+
`agent-os/standards/`; the living UX rulebook is linked below.
|
|
6
|
+
|
|
7
|
+
## Workspace layout
|
|
8
|
+
|
|
9
|
+
| Path | Role |
|
|
10
|
+
| ------------------ | ------------------------------------------------------------------- |
|
|
11
|
+
| `apps/<name>/` | Private applications (not published) |
|
|
12
|
+
| `features/<name>/` | Feature modules for the matching app (screens, stories, playground) |
|
|
13
|
+
| `packages/` | Private app-integration packages (`@package/*`) |
|
|
14
|
+
| `public/` | Published `@multiplatform.one/*` packages |
|
|
15
|
+
|
|
16
|
+
**App ↔ features twin:** every product app `apps/<name>` MUST have `features/<name>`.
|
|
17
|
+
Platform/tooling shells (frappe, keycloak, storybook\*, vocs, vscode, webext, chain
|
|
18
|
+
shells) are exempt in the convention linter; consumer apps from `mpo init --app`
|
|
19
|
+
are not. Prefer feature code in `features/`, thin wiring in `apps/`.
|
|
20
|
+
|
|
21
|
+
**Internal deps:** monorepo packages use `workspace:*`. Consumer / app-only
|
|
22
|
+
templates use semver ranges from npm (`@multiplatform.one/*`).
|
|
23
|
+
|
|
24
|
+
## Package boundaries
|
|
25
|
+
|
|
26
|
+
| Package | Owns | Must not |
|
|
27
|
+
| ------------------------------- | ---------------------------------------------------- | -------------------------------------- |
|
|
28
|
+
| `@multiplatform.one/theme` | Knobs, presets, intents, recipes, `useResolvedKnobs` | App screens |
|
|
29
|
+
| `@multiplatform.one/components` | Catalog UI (layouts, surfaces, feedback, …) | Frappe/API assumptions |
|
|
30
|
+
| `@multiplatform.one/forms` | Fields, Form, InputParts, field contracts | Table chrome / Frappe fetch |
|
|
31
|
+
| `@multiplatform.one/table` | DataTable, cells, filters, URL table state | Frappe/API assumptions |
|
|
32
|
+
| `@multiplatform.one/frappe-ui` | Frappe adapters (`FrappeTable`, field registry) | Re-implementing table/forms primitives |
|
|
33
|
+
| `@multiplatform.one/frappe` | Frappe client / live query | UI |
|
|
34
|
+
|
|
35
|
+
Dependency direction: `frappe-ui` → `table` / `forms` / `components` / `theme`.
|
|
36
|
+
`table` and `forms` do not depend on `frappe-ui` or `frappe`.
|
|
37
|
+
|
|
38
|
+
### Import rules (enforced)
|
|
39
|
+
|
|
40
|
+
1. **No raw `tamagui` outside `public/`** — apps, features, and packages import
|
|
41
|
+
`@multiplatform.one/components`, `@multiplatform.one/forms`, or
|
|
42
|
+
`@multiplatform.one/theme` instead.
|
|
43
|
+
2. **Apps never relative-import `public/`** — use package names
|
|
44
|
+
(`@multiplatform.one/…`), never `../../../public/…`.
|
|
45
|
+
3. **No `.css` files in `apps/*`** (except ignored bench/vendor trees) — styling
|
|
46
|
+
goes through Tamagui + knobs.
|
|
47
|
+
4. **No hex color literals in `features/**`** — use theme tokens (`$color10`,
|
|
48
|
+
semantic ramps, knob recipes).
|
|
49
|
+
|
|
50
|
+
Run: `pnpm lint` (oxlint + `scripts/lint-conventions.mjs`).
|
|
51
|
+
|
|
52
|
+
## Knobs system (canonical)
|
|
53
|
+
|
|
54
|
+
Structural styling (radius, space, elevation, typography, animation) is independent
|
|
55
|
+
of Tamagui color themes. Full standard: `agent-os/standards/frontend/knobs-system.md`.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
const { knobProps, control, text, elevation } = useResolvedKnobs({
|
|
59
|
+
intent, // "error" | "accent" | "warning" | "success"
|
|
60
|
+
component: "Button",
|
|
61
|
+
compact,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
<View
|
|
65
|
+
{...knobProps.surface}
|
|
66
|
+
{...knobProps.borderRadius}
|
|
67
|
+
{...knobProps.panelPadding}
|
|
68
|
+
{...knobProps.gap}
|
|
69
|
+
{...knobProps.body}
|
|
70
|
+
hoverStyle={{ ...control.hoverKnobProps }}
|
|
71
|
+
pressStyle={{ ...control.pressKnobProps }}
|
|
72
|
+
focusVisibleStyle={{ ...control.focusVisibleKnobProps }}
|
|
73
|
+
/>;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Rules:
|
|
77
|
+
|
|
78
|
+
- Always spread complete `knobProps.*` fragments — never cherry-pick resolved tokens.
|
|
79
|
+
- `resolveKnobs()` is the only canonical mapping from abstract knobs → Tamagui props.
|
|
80
|
+
- Consumer `{...props}` come last (explicit eject), not internal defaults that fight recipes.
|
|
81
|
+
- Presets cascade (`<Preset preset="…" theme="…">`); nested `overrides` merge by key.
|
|
82
|
+
- One knob flip restyles the screen (LC-18 KNOB-TOTALITY), or the knob is broken.
|
|
83
|
+
|
|
84
|
+
## Forms contract
|
|
85
|
+
|
|
86
|
+
Standard refs: `agent-os/standards/frontend/field-architecture.md`,
|
|
87
|
+
`field-layout.md`, `form-colors.md`, `input-parts.md`.
|
|
88
|
+
|
|
89
|
+
- Initialize with `useFormField()` + `useResolvedValidators()`.
|
|
90
|
+
- Three-branch render: skeleton → standalone → form-integrated (`<Field>`).
|
|
91
|
+
- Canonical change prop is `onChange(value)` (LC-14). Deprecated aliases may fire
|
|
92
|
+
but must not be the only path.
|
|
93
|
+
- Field slot order: Label → Hint → Error → Control; error replaces helper in one slot.
|
|
94
|
+
- Use form color ramps from `shared/colorRamps.ts` — no hardcoded color tokens.
|
|
95
|
+
- Fields auto-detect `useIsInTableCell()` and render chromeless inside cells.
|
|
96
|
+
- Capability surfaces (camera, geo, …) MUST preflight before mount (LC-19).
|
|
97
|
+
|
|
98
|
+
## Table contract
|
|
99
|
+
|
|
100
|
+
Standard ref: `public/table/README.md` (package boundaries), cell/editor anatomy in
|
|
101
|
+
the living rulebook.
|
|
102
|
+
|
|
103
|
+
- `@multiplatform.one/table` is framework-agnostic display/filter/sort/paginate.
|
|
104
|
+
- Cell editors larger than the anchor use popover (web) / sheet (native) — never
|
|
105
|
+
grow the row (LC-06). Editor popups dismiss only on dismissal intent (LC-21).
|
|
106
|
+
- Frappe integration belongs in `@multiplatform.one/frappe-ui` (`FrappeTable`,
|
|
107
|
+
registry adapters), not in `table`.
|
|
108
|
+
- Child-table / TableInput follow the same field `onChange` contract as forms.
|
|
109
|
+
|
|
110
|
+
## Living rulebook
|
|
111
|
+
|
|
112
|
+
**Normative UX + composition rules (LC-\* and recipe taxonomy):**
|
|
113
|
+
|
|
114
|
+
→ [`docs/theme-propagation-spec.md`](./docs/theme-propagation-spec.md)
|
|
115
|
+
|
|
116
|
+
That document is the living rulebook: every owner-reported spacing/UX issue is
|
|
117
|
+
adjudicated there in-session as VIOLATION or GAP (new LC-\* rule + VERIFY). Agents
|
|
118
|
+
changing catalog components MUST read it first; a contradicting change updates the
|
|
119
|
+
spec in the same change. Companion docs:
|
|
120
|
+
|
|
121
|
+
- Catalog guidelines: `docs/design-guidelines.md` + `docs/design-guidelines-blueprint.md`
|
|
122
|
+
- Click-matrix / VERIFY arm: `docs/storybook-knob-edge-cases.md`
|
|
123
|
+
- Agent OS standards index: `agent-os/standards/index.yml`
|
|
124
|
+
|
|
125
|
+
## Tooling
|
|
126
|
+
|
|
127
|
+
| Command | Purpose |
|
|
128
|
+
| ----------------------- | -------------------------------------------------------------- |
|
|
129
|
+
| `pnpm lint` | oxlint (incl. convention rules) + structural convention checks |
|
|
130
|
+
| `pnpm lint:conventions` | Structural checks only (css twin, features twin) |
|
|
131
|
+
| `pnpm format` | oxfmt |
|
|
132
|
+
| `pnpm ci:local` | format + lint + typecheck + build + test + tree-clean |
|
|
133
|
+
|
|
134
|
+
Use **oxlint / oxfmt**, not ESLint / Prettier
|
|
135
|
+
(`agent-os/standards/global/linting-formatting.md`).
|
|
136
|
+
|
|
137
|
+
## Coding defaults
|
|
138
|
+
|
|
139
|
+
- Named exports only; cut dead/legacy aliases unless a task explicitly needs them.
|
|
140
|
+
- Cross-platform: shared code first; `.native.tsx` / `.web.tsx` only when required.
|
|
141
|
+
- Stories and tests live next to the component / feature they cover.
|
|
142
|
+
- Deeper style rules: `agent-os/standards/global/coding-style.md`.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# **NAME**
|
|
2
|
+
|
|
3
|
+
Universal multiplatform.one project scaffolded by
|
|
4
|
+
[`@multiplatform.one/cli`](https://www.npmjs.com/package/@multiplatform.one/cli)
|
|
5
|
+
(`mpo init`). One codebase serves **web (SSR)**, **iOS**, and **Android** through
|
|
6
|
+
the [`one`](https://onestack.dev) framework.
|
|
7
|
+
|
|
8
|
+
This is **not** a fork of the multiplatform.one monorepo. Framework packages come
|
|
9
|
+
from npm at semver ranges (`@multiplatform.one/*`: `__MPO_VERSION__`), never
|
|
10
|
+
`workspace:*`.
|
|
11
|
+
|
|
12
|
+
## Layout
|
|
13
|
+
|
|
14
|
+
| Path | Role |
|
|
15
|
+
| -------------------- | ------------------------------------------------------------------- |
|
|
16
|
+
| `apps/__NAME__/` | Universal app shell: file-based routes, providers, vite/expo config |
|
|
17
|
+
| `features/__NAME__/` | Feature screens / UI — import from here, not from `apps/` |
|
|
18
|
+
| `packages/config/` | Shared app config (`@package/config`) |
|
|
19
|
+
| `packages/i18n/` | Translations (`@package/i18n`) |
|
|
20
|
+
| `packages/themes/` | Tamagui theme config (`@package/themes`) |
|
|
21
|
+
|
|
22
|
+
Convention: every `apps/<name>` has a matching `features/<name>`.
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm install
|
|
28
|
+
|
|
29
|
+
# web
|
|
30
|
+
pnpm dev # dev server (web + native bundles) on :3456
|
|
31
|
+
pnpm build # production web build (one build)
|
|
32
|
+
pnpm serve # serve the production build
|
|
33
|
+
|
|
34
|
+
# native (Expo dev client via one)
|
|
35
|
+
pnpm native # generate ios/ + android/ projects (one prebuild)
|
|
36
|
+
pnpm ios # build & run on iOS simulator/device (one run:ios)
|
|
37
|
+
pnpm android # build & run on Android emulator/device (one run:android)
|
|
38
|
+
|
|
39
|
+
# quality
|
|
40
|
+
pnpm test # vitest (features)
|
|
41
|
+
pnpm lint # oxlint
|
|
42
|
+
pnpm typecheck # tsc across workspace packages
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Native flow: `pnpm ios` / `pnpm android` builds a dev client and connects it to
|
|
46
|
+
the dev server started by `pnpm dev`. Run `pnpm dev` in another terminal first.
|
|
47
|
+
|
|
48
|
+
## Routing
|
|
49
|
+
|
|
50
|
+
Routes are files in `apps/__NAME__/routes/`:
|
|
51
|
+
|
|
52
|
+
- `routes/index.tsx` → `/` (`HomeScreen`)
|
|
53
|
+
- `routes/about.tsx` → `/about` (`AboutScreen`)
|
|
54
|
+
- `routes/_layout.tsx` / `_layout.web.tsx` — root layout, providers, i18n
|
|
55
|
+
|
|
56
|
+
Navigate with `Link` / `useRouter` / `usePathname` from `one` — the same
|
|
57
|
+
pattern the framework's own layouts use.
|
|
58
|
+
|
|
59
|
+
## Starter screens
|
|
60
|
+
|
|
61
|
+
- `HomeScreen` — theme/knob-wired welcome card (`useResolvedKnobs`) + navigation
|
|
62
|
+
- `AboutScreen` — router navigation demo
|
|
63
|
+
- `ContactForm` — `@multiplatform.one/forms` fields (`Input`, `TextArea`, `Button`)
|
|
64
|
+
|
|
65
|
+
## i18n
|
|
66
|
+
|
|
67
|
+
Translations live in `packages/i18n/<lng>/common.json`. Add a language by adding
|
|
68
|
+
a folder + `common.json` and listing it in `packages/i18n/index.ts`. Use
|
|
69
|
+
`useTranslation()` from `react-i18next` in screens.
|
|
70
|
+
|
|
71
|
+
## Dependencies
|
|
72
|
+
|
|
73
|
+
Framework packages come from npm at semver ranges, for example:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"@multiplatform.one/components": "__MPO_VERSION__",
|
|
78
|
+
"@multiplatform.one/forms": "__MPO_VERSION__",
|
|
79
|
+
"@multiplatform.one/router": "__MPO_VERSION__",
|
|
80
|
+
"@multiplatform.one/theme": "__MPO_VERSION__"
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Upgrade with `pnpm update "@multiplatform.one/*"`.
|
|
85
|
+
|
|
86
|
+
## Conventions
|
|
87
|
+
|
|
88
|
+
See [`AGENTS.md`](./AGENTS.md) for package boundaries, the apps ↔ features twin,
|
|
89
|
+
import rules (no raw `tamagui` outside framework packages, no hex literals in
|
|
90
|
+
features), and knobs/forms/table contracts.
|
|
91
|
+
|
|
92
|
+
## Next steps
|
|
93
|
+
|
|
94
|
+
1. Replace the starter screens in `features/__NAME__/src/`.
|
|
95
|
+
2. Add routes under `apps/__NAME__/routes/` that re-export feature screens.
|
|
96
|
+
3. Adjust the theme in `packages/themes/index.ts` (`createDefaultThemeConfig`).
|
|
97
|
+
4. Do **not** copy `public/` from the framework monorepo into this project.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Expo config consumed by `one prebuild` / `one run:ios` / `one run:android`.
|
|
2
|
+
// Evaluated through @expo/config's CJS pipeline, so CommonJS is required here.
|
|
3
|
+
const { public: publicConfigKeys } = require("../../packages/config/config.json");
|
|
4
|
+
|
|
5
|
+
const publicConfig = publicConfigKeys.reduce((acc, key) => {
|
|
6
|
+
if (process.env[key]) acc[key] = process.env[key];
|
|
7
|
+
return acc;
|
|
8
|
+
}, {});
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
expo: {
|
|
12
|
+
name: "__NAME_PASCAL__",
|
|
13
|
+
slug: "__NAME__",
|
|
14
|
+
scheme: "__NAME__",
|
|
15
|
+
version: "1.0.0",
|
|
16
|
+
orientation: "portrait",
|
|
17
|
+
userInterfaceStyle: "automatic",
|
|
18
|
+
platforms: ["ios", "android"],
|
|
19
|
+
ios: {
|
|
20
|
+
bundleIdentifier: "com.__NAME_APPID__.app",
|
|
21
|
+
supportsTablet: true,
|
|
22
|
+
},
|
|
23
|
+
android: {
|
|
24
|
+
package: "com.__NAME_APPID__.app",
|
|
25
|
+
},
|
|
26
|
+
extra: {
|
|
27
|
+
...publicConfig,
|
|
28
|
+
},
|
|
29
|
+
plugins: ["vxrn/expo-plugin"],
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@app/__NAME__",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "one dev --port 3456",
|
|
8
|
+
"build": "one build",
|
|
9
|
+
"serve": "one serve",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"prebuild:native": "one prebuild",
|
|
12
|
+
"prebuild:ios": "one prebuild --platform ios",
|
|
13
|
+
"prebuild:android": "one prebuild --platform android",
|
|
14
|
+
"ios": "one run:ios",
|
|
15
|
+
"android": "one run:android"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@features/__NAME__": "workspace:*",
|
|
19
|
+
"@multiplatform.one/components": "__MPO_VERSION__",
|
|
20
|
+
"@multiplatform.one/core": "__MPO_VERSION__",
|
|
21
|
+
"@multiplatform.one/frappe": "__MPO_VERSION__",
|
|
22
|
+
"@multiplatform.one/frappe-ui": "__MPO_VERSION__",
|
|
23
|
+
"@multiplatform.one/i18n": "__MPO_VERSION__",
|
|
24
|
+
"@multiplatform.one/keycloak": "__MPO_VERSION__",
|
|
25
|
+
"@multiplatform.one/platform": "__MPO_VERSION__",
|
|
26
|
+
"@multiplatform.one/theme": "__MPO_VERSION__",
|
|
27
|
+
"@package/config": "workspace:*",
|
|
28
|
+
"@package/i18n": "workspace:*",
|
|
29
|
+
"@package/themes": "workspace:*",
|
|
30
|
+
"@react-navigation/native": "~7.2.2",
|
|
31
|
+
"@tamagui/core": "2.0.0-rc.41",
|
|
32
|
+
"@tanstack/react-query": "^5.100.7",
|
|
33
|
+
"@tanstack/react-query-devtools": "^5.100.7",
|
|
34
|
+
"@tanstack/react-virtual": "^3.13.24",
|
|
35
|
+
"expo": "~55.0.26",
|
|
36
|
+
"i18next": "^25.10.10",
|
|
37
|
+
"one": "1.16.5",
|
|
38
|
+
"react": "19.2.5",
|
|
39
|
+
"react-dom": "19.2.5",
|
|
40
|
+
"react-i18next": "^16.6.6",
|
|
41
|
+
"react-native": "0.83.2",
|
|
42
|
+
"react-native-gesture-handler": "~2.31.1",
|
|
43
|
+
"react-native-reanimated": "~4.3.0",
|
|
44
|
+
"react-native-safe-area-context": "~5.7.0",
|
|
45
|
+
"react-native-screens": "^4.24.0",
|
|
46
|
+
"react-native-svg": "15.15.4",
|
|
47
|
+
"react-native-web": "^0.21.2",
|
|
48
|
+
"react-native-worklets": "~0.7.4",
|
|
49
|
+
"shiki": "^4.0.2",
|
|
50
|
+
"tamagui": "2.0.0-rc.41",
|
|
51
|
+
"text-encoding-utf-8": "^1.0.2"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@multiplatform.one/config": "__MPO_VERSION__",
|
|
55
|
+
"@react-native-community/cli": "^20.1.3",
|
|
56
|
+
"@types/react": "~19.2.14",
|
|
57
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
58
|
+
"typescript": "~5.9.3",
|
|
59
|
+
"vite": "^8.0.10"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Hermes (React Native) ships without the WHATWG TextEncoder/TextDecoder
|
|
2
|
+
// globals. Install them as the first side-effect of the native root layout,
|
|
3
|
+
// before any provider mounts.
|
|
4
|
+
import { TextDecoder, TextEncoder } from "text-encoding-utf-8";
|
|
5
|
+
|
|
6
|
+
const globals = globalThis as Record<string, unknown>;
|
|
7
|
+
if (typeof globals.TextEncoder === "undefined") globals.TextEncoder = TextEncoder;
|
|
8
|
+
if (typeof globals.TextDecoder === "undefined") globals.TextDecoder = TextDecoder;
|
|
9
|
+
|
|
10
|
+
// React Native's built-in URL exposes protocol/pathname/search/hash as
|
|
11
|
+
// GETTER-ONLY. expo-asset's getManifestBaseUrl() assigns to those properties at
|
|
12
|
+
// module init, which throws in strict mode and takes down the native root
|
|
13
|
+
// layout at boot. Install tolerant no-op setters so the assignment silently
|
|
14
|
+
// no-ops. Guarded so a future spec-complete URL polyfill is never overwritten.
|
|
15
|
+
const urlProto = (globals.URL as { prototype?: object } | undefined)?.prototype;
|
|
16
|
+
if (urlProto) {
|
|
17
|
+
for (const prop of ["protocol", "pathname", "search", "hash"]) {
|
|
18
|
+
const desc = Object.getOwnPropertyDescriptor(urlProto, prop);
|
|
19
|
+
if (desc && typeof desc.get === "function" && typeof desc.set !== "function") {
|
|
20
|
+
Object.defineProperty(urlProto, prop, { ...desc, set() {} });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import "../polyfills"; // installs TextEncoder/TextDecoder before any provider mounts (Hermes lacks them)
|
|
2
|
+
import { AppLayout } from "@features/__NAME__";
|
|
3
|
+
import { createRootLayout } from "@multiplatform.one/core";
|
|
4
|
+
import { defaultLanguage, defaultNamespace, languages, namespaces } from "@package/i18n";
|
|
5
|
+
import { resources } from "@package/i18n/resources";
|
|
6
|
+
import { importFonts } from "@package/themes/fonts";
|
|
7
|
+
import { AppProvider } from "../src/app";
|
|
8
|
+
|
|
9
|
+
// Native root layout (web resolves _layout.web.tsx). Translations are imported
|
|
10
|
+
// statically here; web loads them through the vite i18next loader.
|
|
11
|
+
export default createRootLayout({
|
|
12
|
+
AppProvider,
|
|
13
|
+
RootLayout: AppLayout,
|
|
14
|
+
fonts: importFonts(),
|
|
15
|
+
i18n: {
|
|
16
|
+
languages,
|
|
17
|
+
namespaces,
|
|
18
|
+
defaultLanguage,
|
|
19
|
+
defaultNamespace,
|
|
20
|
+
resources,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import "@tamagui/core/reset.css";
|
|
2
|
+
import "../tamagui.css";
|
|
3
|
+
import resources from "virtual:i18next-loader";
|
|
4
|
+
import { AppLayout } from "@features/__NAME__";
|
|
5
|
+
import { createRootLayout } from "@multiplatform.one/core";
|
|
6
|
+
import { defaultLanguage, defaultNamespace, languages, namespaces } from "@package/i18n";
|
|
7
|
+
import { AppProvider } from "../src/app";
|
|
8
|
+
|
|
9
|
+
// Web root layout (native resolves _layout.tsx). Translations come from the
|
|
10
|
+
// vite i18next loader (packages/i18n) so they hot-reload in dev.
|
|
11
|
+
export default createRootLayout({
|
|
12
|
+
AppProvider,
|
|
13
|
+
RootLayout: AppLayout,
|
|
14
|
+
i18n: {
|
|
15
|
+
languages,
|
|
16
|
+
namespaces,
|
|
17
|
+
defaultLanguage,
|
|
18
|
+
defaultNamespace,
|
|
19
|
+
resources,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createApp } from "@multiplatform.one/core";
|
|
2
|
+
import { themeConfig } from "@package/themes";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* App-wide provider stack. Add keycloak / frappe / tanstack / web3 here as the
|
|
6
|
+
* app grows — see CreateAppConfig in @multiplatform.one/core.
|
|
7
|
+
*/
|
|
8
|
+
export const { AppProvider, useAppConfig, withApp } = createApp({
|
|
9
|
+
theme: themeConfig,
|
|
10
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="../../packages/i18n/i18n.d.ts" />
|
|
2
|
+
/// <reference types="@multiplatform.one/platform/rnw-overrides" />
|
|
3
|
+
|
|
4
|
+
declare module "virtual:*";
|
|
5
|
+
|
|
6
|
+
// Published core@6.1.0 sources carry a type skew against published
|
|
7
|
+
// frappe-ui@6.1.0 (FrappeProvider auth.token). The app never imports
|
|
8
|
+
// frappe-ui directly; treat it as untyped until fixed packages publish.
|
|
9
|
+
declare module "@multiplatform.one/frappe-ui";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { createViteConfig } from "@multiplatform.one/config/vite";
|
|
4
|
+
import { one } from "one/vite";
|
|
5
|
+
import type { Plugin } from "vite";
|
|
6
|
+
import { public as publicConfigKeys } from "../../packages/config/config.json";
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Some @multiplatform.one packages at 6.1.0 publish web export targets that
|
|
12
|
+
* don't exist in their tarballs (exports say dist/esm/…/index.js; the build
|
|
13
|
+
* emitted index.mjs), which breaks resolution for anything importing them —
|
|
14
|
+
* e.g. forms → router and frappe-ui → frappe. Resolve those bare imports to
|
|
15
|
+
* the real web builds. Native is unaffected (vxrn's native resolver uses the
|
|
16
|
+
* packages' valid react-native export condition). Remove this plugin once
|
|
17
|
+
* fixed packages are published.
|
|
18
|
+
*/
|
|
19
|
+
const BROKEN_PUBLISHED_ENTRIES: Record<string, { pkg: string; entry: string }> = {
|
|
20
|
+
"@multiplatform.one/router": {
|
|
21
|
+
pkg: "@multiplatform.one/router",
|
|
22
|
+
entry: "dist/esm/index.mjs",
|
|
23
|
+
},
|
|
24
|
+
"@multiplatform.one/frappe": {
|
|
25
|
+
pkg: "@multiplatform.one/frappe",
|
|
26
|
+
entry: "dist/esm/index.mjs",
|
|
27
|
+
},
|
|
28
|
+
"@multiplatform.one/frappe/devtools": {
|
|
29
|
+
pkg: "@multiplatform.one/frappe",
|
|
30
|
+
entry: "dist/esm/devtools/index.mjs",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function fixPublishedEsmEntries(): Plugin {
|
|
35
|
+
return {
|
|
36
|
+
name: "fix-multiplatform-published-esm-entries",
|
|
37
|
+
enforce: "pre",
|
|
38
|
+
resolveId(id) {
|
|
39
|
+
const broken = BROKEN_PUBLISHED_ENTRIES[id];
|
|
40
|
+
if (!broken) return null;
|
|
41
|
+
try {
|
|
42
|
+
const pkgJson = require.resolve(`${broken.pkg}/package.json`);
|
|
43
|
+
const entry = pkgJson.replace(/package\.json$/, broken.entry);
|
|
44
|
+
return existsSync(entry) ? entry : null;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default createViteConfig({
|
|
53
|
+
publicConfigKeys,
|
|
54
|
+
one: true,
|
|
55
|
+
tamagui: { config: "./config/tamagui.config.ts", outputCSS: "./tamagui.css" },
|
|
56
|
+
i18n: { paths: ["../../packages/i18n"] },
|
|
57
|
+
plugins: [
|
|
58
|
+
one({
|
|
59
|
+
router: { root: "routes" },
|
|
60
|
+
web: { deploy: "node", defaultRenderMode: "ssr" },
|
|
61
|
+
react: { compiler: true },
|
|
62
|
+
native: { key: "__NAME_PASCAL__" },
|
|
63
|
+
}),
|
|
64
|
+
fixPublishedEsmEntries(),
|
|
65
|
+
],
|
|
66
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@features/__NAME__",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./*": "./src/*.ts"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"typecheck": "tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@multiplatform.one/components": "__MPO_VERSION__",
|
|
17
|
+
"@multiplatform.one/forms": "__MPO_VERSION__",
|
|
18
|
+
"@multiplatform.one/theme": "__MPO_VERSION__",
|
|
19
|
+
"@package/config": "workspace:*",
|
|
20
|
+
"one": "1.16.5",
|
|
21
|
+
"react": "19.2.5",
|
|
22
|
+
"react-i18next": "^16.6.6"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@multiplatform.one/config": "__MPO_VERSION__",
|
|
26
|
+
"@multiplatform.one/test-utils": "__MPO_VERSION__",
|
|
27
|
+
"@types/react": "~19.2.14",
|
|
28
|
+
"jsdom": "^26.0.0",
|
|
29
|
+
"typescript": "~5.9.3",
|
|
30
|
+
"vitest": "^4.1.5"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"react": "^19.1.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Button, H2, Paragraph, Screen, YStack } from "@multiplatform.one/components";
|
|
2
|
+
import { useRouter } from "one";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* About screen (route: /about) — demonstrates programmatic navigation with
|
|
7
|
+
* the router hooks from `one` (same pattern as the framework's own layouts).
|
|
8
|
+
*/
|
|
9
|
+
export function AboutScreen() {
|
|
10
|
+
const { t } = useTranslation();
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<Screen>
|
|
15
|
+
<YStack gap="$3" alignItems="flex-start">
|
|
16
|
+
<H2>{t("screens.about.title", "About")}</H2>
|
|
17
|
+
<Paragraph color="$color11">
|
|
18
|
+
{t(
|
|
19
|
+
"screens.about.message",
|
|
20
|
+
"This screen lives in features/__NAME__ and is wired to the /about route by apps/__NAME__/routes/about.tsx.",
|
|
21
|
+
)}
|
|
22
|
+
</Paragraph>
|
|
23
|
+
<Button onPress={() => router.navigate("/")}>{t("screens.about.back", "Back home")}</Button>
|
|
24
|
+
</YStack>
|
|
25
|
+
</Screen>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Button, SizableText, XStack, YStack } from "@multiplatform.one/components";
|
|
2
|
+
import { usePathname, useRouter } from "one";
|
|
3
|
+
import type { PropsWithChildren } from "react";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Root layout: top navigation + content. Wired into the router by
|
|
8
|
+
* apps/__NAME__/routes/_layout(.web).tsx via createRootLayout. Uses router
|
|
9
|
+
* hooks (not window.location) so it works on native as well as web.
|
|
10
|
+
*/
|
|
11
|
+
export function AppLayout({ children }: PropsWithChildren) {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
const router = useRouter();
|
|
14
|
+
const path = usePathname() || "/";
|
|
15
|
+
|
|
16
|
+
const items = [
|
|
17
|
+
{ label: t("nav.home", "Home"), href: "/", active: path === "/" },
|
|
18
|
+
{ label: t("nav.about", "About"), href: "/about", active: path.startsWith("/about") },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<YStack flex={1} backgroundColor="$background">
|
|
23
|
+
<XStack
|
|
24
|
+
alignItems="center"
|
|
25
|
+
gap="$4"
|
|
26
|
+
paddingHorizontal="$4"
|
|
27
|
+
paddingVertical="$3"
|
|
28
|
+
borderBottomWidth={1}
|
|
29
|
+
borderBottomColor="$borderColor"
|
|
30
|
+
>
|
|
31
|
+
<SizableText fontWeight="700" size="$5">
|
|
32
|
+
{t("app.name", "__NAME_PASCAL__")}
|
|
33
|
+
</SizableText>
|
|
34
|
+
<XStack gap="$2" marginLeft="auto">
|
|
35
|
+
{items.map((item) => (
|
|
36
|
+
<Button
|
|
37
|
+
key={item.href}
|
|
38
|
+
size="$3"
|
|
39
|
+
chromeless={!item.active}
|
|
40
|
+
onPress={() => router.navigate(item.href)}
|
|
41
|
+
>
|
|
42
|
+
{item.label}
|
|
43
|
+
</Button>
|
|
44
|
+
))}
|
|
45
|
+
</XStack>
|
|
46
|
+
</XStack>
|
|
47
|
+
{children}
|
|
48
|
+
</YStack>
|
|
49
|
+
);
|
|
50
|
+
}
|