@multiplatform.one/cli 7.10.0 → 7.15.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.
Files changed (163) hide show
  1. package/README.md +121 -111
  2. package/bin/createMultiplatformApp.mjs +39 -37
  3. package/lib/bin/multiplatformOne.mjs +65 -75
  4. package/lib/commands/addApp.mjs +78 -0
  5. package/lib/commands/adoptApp.mjs +11 -7
  6. package/lib/commands/doctypeTypes.mjs +148 -0
  7. package/lib/commands/e2e.mjs +2 -2
  8. package/lib/commands/init.mjs +142 -459
  9. package/lib/commands/merge.mjs +359 -0
  10. package/lib/commands/pieces.mjs +236 -0
  11. package/lib/commands/provenance.mjs +108 -0
  12. package/lib/commands/scaffold.mjs +105 -0
  13. package/lib/commands/updateApp.mjs +211 -528
  14. package/lib/source.json +5 -0
  15. package/lib/source.mjs +63 -0
  16. package/package.json +7 -7
  17. package/scripts/frappe-bootstrap.sh +29 -3
  18. package/scripts/frappe-dev.sh +1 -2
  19. package/scripts/frappe-helpers.sh +50 -2
  20. package/scripts/frappe.sh +5 -0
  21. package/scripts/scaffold.sh +937 -0
  22. package/scripts/stamp-source.mjs +51 -0
  23. package/scripts/stamp-source.spec.ts +86 -0
  24. package/src/bin/multiplatformOne.ts +116 -226
  25. package/src/commands/addApp.ts +102 -0
  26. package/src/commands/adoptApp.spec.ts +5 -1
  27. package/src/commands/adoptApp.ts +15 -10
  28. package/src/commands/doctypeTypes.spec.ts +155 -0
  29. package/src/commands/doctypeTypes.ts +214 -0
  30. package/src/commands/init.spec.ts +66 -0
  31. package/src/commands/init.ts +201 -643
  32. package/src/commands/merge.spec.ts +81 -0
  33. package/src/commands/merge.ts +411 -0
  34. package/src/commands/pieces.spec.ts +62 -0
  35. package/src/commands/pieces.ts +340 -0
  36. package/src/commands/provenance.spec.ts +92 -0
  37. package/src/commands/provenance.ts +169 -0
  38. package/src/commands/scaffold.ts +131 -0
  39. package/src/commands/updateApp.spec.ts +540 -331
  40. package/src/commands/updateApp.ts +255 -668
  41. package/src/source.spec.ts +45 -0
  42. package/src/source.ts +88 -0
  43. package/templates/AGENTS.md +161 -0
  44. package/templates/README.md +62 -0
  45. package/templates/pieces/frappe/universal/README.md.partial +7 -32
  46. package/templates/pieces/frappe/universal/env.example.partial +3 -3
  47. package/templates/pieces/frappe-bench/universal/README.md.partial +28 -0
  48. package/templates/pieces/{frappe → frappe-bench}/universal/docker/compose.frappe.yaml +2 -2
  49. package/templates/pieces/tauri/universal/README.md.partial +7 -5
  50. package/templates/pieces/tauri/universal/apps/__NAME__/src-tauri/src/dev.rs +463 -0
  51. package/templates/pieces/tauri/universal/apps/__NAME__/src-tauri/src/lib.rs +6 -0
  52. package/templates/pieces/tauri/universal/apps/__NAME__/src-tauri/tauri.conf.json +2 -1
  53. package/templates/pieces/webext/universal/apps/__NAME__/package.json.partial +1 -0
  54. package/templates/pieces/webext/universal/apps/__NAME__/vite.config.webext.ts +6 -2
  55. package/types/bin/multiplatformOne.d.ts.map +1 -1
  56. package/types/commands/addApp.d.ts +20 -0
  57. package/types/commands/addApp.d.ts.map +1 -0
  58. package/types/commands/adoptApp.d.ts +4 -3
  59. package/types/commands/adoptApp.d.ts.map +1 -1
  60. package/types/commands/doctypeTypes.d.ts +40 -0
  61. package/types/commands/doctypeTypes.d.ts.map +1 -0
  62. package/types/commands/init.d.ts +30 -13
  63. package/types/commands/init.d.ts.map +1 -1
  64. package/types/commands/merge.d.ts +54 -0
  65. package/types/commands/merge.d.ts.map +1 -0
  66. package/types/commands/pieces.d.ts +54 -0
  67. package/types/commands/pieces.d.ts.map +1 -0
  68. package/types/commands/provenance.d.ts +52 -0
  69. package/types/commands/provenance.d.ts.map +1 -0
  70. package/types/commands/scaffold.d.ts +43 -0
  71. package/types/commands/scaffold.d.ts.map +1 -0
  72. package/types/commands/updateApp.d.ts +19 -27
  73. package/types/commands/updateApp.d.ts.map +1 -1
  74. package/types/source.d.ts +35 -0
  75. package/types/source.d.ts.map +1 -0
  76. package/lib/commands/initApp.mjs +0 -431
  77. package/lib/generateVscode.mjs +0 -184
  78. package/scripts/clone.sh +0 -17
  79. package/scripts/update.sh +0 -46
  80. package/src/commands/initApp.spec.ts +0 -720
  81. package/src/commands/initApp.ts +0 -706
  82. package/src/generateVscode.ts +0 -238
  83. package/templates/app/AGENTS.md +0 -142
  84. package/templates/app/README.md +0 -62
  85. package/templates/app/apps/__NAME__/index.html +0 -36
  86. package/templates/app/apps/__NAME__/package.json +0 -31
  87. package/templates/app/apps/__NAME__/public/fonts/inter-400.woff2 +0 -0
  88. package/templates/app/apps/__NAME__/public/fonts/inter-700.woff2 +0 -0
  89. package/templates/app/apps/__NAME__/src/App.tsx +0 -9
  90. package/templates/app/apps/__NAME__/src/main.tsx +0 -14
  91. package/templates/app/apps/__NAME__/tsconfig.json +0 -8
  92. package/templates/app/apps/__NAME__/vite.config.ts +0 -17
  93. package/templates/app/features/__NAME__/package.json +0 -34
  94. package/templates/app/features/__NAME__/src/HomeScreen.tsx +0 -70
  95. package/templates/app/features/__NAME__/src/index.ts +0 -1
  96. package/templates/app/features/__NAME__/tsconfig.json +0 -5
  97. package/templates/app/gitignore +0 -9
  98. package/templates/app/npmrc +0 -2
  99. package/templates/app/oxlintrc.json +0 -39
  100. package/templates/app/package.json +0 -38
  101. package/templates/app/packages/config/config.json +0 -4
  102. package/templates/app/packages/config/index.ts +0 -5
  103. package/templates/app/packages/config/package.json +0 -17
  104. package/templates/app/packages/config/tsconfig.json +0 -5
  105. package/templates/app/pnpm-workspace.yaml +0 -4
  106. package/templates/app/tsconfig.base.json +0 -16
  107. package/templates/universal/AGENTS.md +0 -142
  108. package/templates/universal/README.md +0 -97
  109. package/templates/universal/apps/__NAME__/app.config.js +0 -31
  110. package/templates/universal/apps/__NAME__/config/tamagui.config.ts +0 -4
  111. package/templates/universal/apps/__NAME__/package.json +0 -62
  112. package/templates/universal/apps/__NAME__/polyfills.native.ts +0 -23
  113. package/templates/universal/apps/__NAME__/polyfills.ts +0 -3
  114. package/templates/universal/apps/__NAME__/public/fonts/inter-400.woff2 +0 -0
  115. package/templates/universal/apps/__NAME__/public/fonts/inter-700.woff2 +0 -0
  116. package/templates/universal/apps/__NAME__/routes/_layout.tsx +0 -22
  117. package/templates/universal/apps/__NAME__/routes/_layout.web.tsx +0 -21
  118. package/templates/universal/apps/__NAME__/routes/about.tsx +0 -3
  119. package/templates/universal/apps/__NAME__/routes/index.tsx +0 -3
  120. package/templates/universal/apps/__NAME__/src/app.ts +0 -10
  121. package/templates/universal/apps/__NAME__/tamagui.css +0 -3
  122. package/templates/universal/apps/__NAME__/tsconfig.json +0 -8
  123. package/templates/universal/apps/__NAME__/types.d.ts +0 -9
  124. package/templates/universal/apps/__NAME__/vite.config.ts +0 -20
  125. package/templates/universal/features/__NAME__/package.json +0 -35
  126. package/templates/universal/features/__NAME__/src/AboutScreen.tsx +0 -27
  127. package/templates/universal/features/__NAME__/src/AppLayout.tsx +0 -50
  128. package/templates/universal/features/__NAME__/src/ContactForm.spec.tsx +0 -22
  129. package/templates/universal/features/__NAME__/src/ContactForm.tsx +0 -55
  130. package/templates/universal/features/__NAME__/src/HomeScreen.tsx +0 -40
  131. package/templates/universal/features/__NAME__/src/index.ts +0 -4
  132. package/templates/universal/features/__NAME__/src/screens.spec.ts +0 -12
  133. package/templates/universal/features/__NAME__/tsconfig.json +0 -5
  134. package/templates/universal/features/__NAME__/vitest.config.mjs +0 -7
  135. package/templates/universal/gitignore +0 -14
  136. package/templates/universal/npmrc +0 -3
  137. package/templates/universal/oxlintrc.json +0 -39
  138. package/templates/universal/package.json +0 -51
  139. package/templates/universal/packages/config/config.json +0 -4
  140. package/templates/universal/packages/config/index.ts +0 -5
  141. package/templates/universal/packages/config/package.json +0 -17
  142. package/templates/universal/packages/config/tsconfig.json +0 -5
  143. package/templates/universal/packages/i18n/en/common.json +0 -30
  144. package/templates/universal/packages/i18n/es/common.json +0 -30
  145. package/templates/universal/packages/i18n/i18n.d.ts +0 -16
  146. package/templates/universal/packages/i18n/index.ts +0 -26
  147. package/templates/universal/packages/i18n/package.json +0 -20
  148. package/templates/universal/packages/i18n/resources.ts +0 -13
  149. package/templates/universal/packages/i18n/tsconfig.json +0 -5
  150. package/templates/universal/packages/themes/accent.ts +0 -32
  151. package/templates/universal/packages/themes/base.ts +0 -50
  152. package/templates/universal/packages/themes/fonts.ts +0 -8
  153. package/templates/universal/packages/themes/index.ts +0 -33
  154. package/templates/universal/packages/themes/package.json +0 -23
  155. package/templates/universal/packages/themes/tamagui.config.ts +0 -14
  156. package/templates/universal/packages/themes/tsconfig.json +0 -5
  157. package/templates/universal/pnpm-workspace.yaml +0 -4
  158. package/templates/universal/tsconfig.base.json +0 -21
  159. package/templates/universal/types/env.d.ts +0 -9
  160. package/types/commands/initApp.d.ts +0 -114
  161. package/types/commands/initApp.d.ts.map +0 -1
  162. package/types/generateVscode.d.ts +0 -7
  163. package/types/generateVscode.d.ts.map +0 -1
@@ -0,0 +1,45 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { describe, expect, it } from "vitest";
5
+ import { isGitUrl, monorepoCheckout, resolveSource } from "./source";
6
+
7
+ const monorepo = fileURLToPath(new URL("../../..", import.meta.url)).replace(/\/$/, "");
8
+
9
+ describe("resolveSource", () => {
10
+ it("flags win, with HEAD for a path and main for a url", () => {
11
+ expect(resolveSource({ source: "/tmp/mpo" })).toEqual({
12
+ source: "/tmp/mpo",
13
+ ref: "HEAD",
14
+ origin: "flag",
15
+ });
16
+ expect(resolveSource({ source: "https://example.com/mpo.git" })).toEqual({
17
+ source: "https://example.com/mpo.git",
18
+ ref: "main",
19
+ origin: "flag",
20
+ });
21
+ expect(resolveSource({ source: "/tmp/mpo", checkout: "v7.11.0" }).ref).toBe("v7.11.0");
22
+ });
23
+
24
+ it("a CLI running inside the monorepo scaffolds from that checkout's HEAD", () => {
25
+ expect(monorepoCheckout()).toBe(monorepo);
26
+ expect(existsSync(join(monorepo, "apps/one/package.json"))).toBe(true);
27
+ const spec = resolveSource();
28
+ expect(spec).toEqual({ source: monorepo, ref: "HEAD", origin: "monorepo" });
29
+ expect(resolveSource({ checkout: "main" }).ref).toBe("main");
30
+ });
31
+
32
+ it("recognizes git urls", () => {
33
+ for (const url of [
34
+ "https://git.corp.bitspur.com/multiplatform.one/mpo.git",
35
+ "ssh://git@host/repo.git",
36
+ "git@github.com:org/repo.git",
37
+ "file:///tmp/repo",
38
+ ]) {
39
+ expect(isGitUrl(url), url).toBe(true);
40
+ }
41
+ for (const path of ["/tmp/repo", "../repo", "repo"]) {
42
+ expect(isGitUrl(path), path).toBe(false);
43
+ }
44
+ });
45
+ });
package/src/source.ts ADDED
@@ -0,0 +1,88 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { cliRoot, cliVersion } from "./commands/provenance";
4
+
5
+ export const monorepoUrl = "https://git.corp.bitspur.com/multiplatform.one/mpo.git";
6
+
7
+ export interface SourceSpec {
8
+ /** Path to a local clone, or a git URL. */
9
+ source: string;
10
+ /** Commit, tag or branch to scaffold from. */
11
+ ref: string;
12
+ /**
13
+ * flag = --source / --checkout
14
+ * monorepo = the CLI runs from inside a monorepo checkout (dev, `make`)
15
+ * stamp = lib/source.json written by the build
16
+ * tag = no stamp: the release tag of this CLI version
17
+ */
18
+ origin: "flag" | "monorepo" | "stamp" | "tag";
19
+ warning?: string;
20
+ }
21
+
22
+ export interface SourceStamp {
23
+ cliVersion?: string;
24
+ sha?: string;
25
+ url?: string;
26
+ }
27
+
28
+ export function isGitUrl(source: string): boolean {
29
+ return /^(https?|ssh|git|file):\/\//.test(source) || /^[\w.-]+@[\w.-]+:/.test(source);
30
+ }
31
+
32
+ /** The monorepo checkout this CLI runs from, if any (src via tsx, or the workspace link). */
33
+ export function monorepoCheckout(): string | undefined {
34
+ const root = resolve(cliRoot(), "../..");
35
+ if (
36
+ existsSync(join(root, "apps/one/package.json")) &&
37
+ existsSync(join(root, "public/cli/package.json"))
38
+ ) {
39
+ return root;
40
+ }
41
+ return undefined;
42
+ }
43
+
44
+ export function readSourceStamp(): SourceStamp | undefined {
45
+ try {
46
+ return JSON.parse(readFileSync(join(cliRoot(), "lib/source.json"), "utf-8")) as SourceStamp;
47
+ } catch {
48
+ return undefined;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Where `mpo init` clones from. Flags win; a CLI running inside the monorepo
54
+ * uses that checkout's HEAD; a published CLI uses the commit stamped at build
55
+ * time; a build without a stamp falls back to the release tag of its own
56
+ * version, and `init` retries `main` when that tag is missing.
57
+ */
58
+ export function resolveSource(flags: { source?: string; checkout?: string } = {}): SourceSpec {
59
+ if (flags.source) {
60
+ return {
61
+ source: flags.source,
62
+ ref: flags.checkout ?? (isGitUrl(flags.source) ? "main" : "HEAD"),
63
+ origin: "flag",
64
+ };
65
+ }
66
+ const checkout = monorepoCheckout();
67
+ if (checkout) {
68
+ return { source: checkout, ref: flags.checkout ?? "HEAD", origin: "monorepo" };
69
+ }
70
+ const stamp = readSourceStamp();
71
+ const url = stamp?.url ?? monorepoUrl;
72
+ if (flags.checkout) {
73
+ return { source: url, ref: flags.checkout, origin: "flag" };
74
+ }
75
+ if (stamp?.sha) {
76
+ return { source: url, ref: stamp.sha, origin: "stamp" };
77
+ }
78
+ const version = cliVersion();
79
+ const ref = version ? `v${version}` : "main";
80
+ return {
81
+ source: url,
82
+ ref,
83
+ origin: "tag",
84
+ warning:
85
+ `this build of @multiplatform.one/cli carries no source commit (lib/source.json); ` +
86
+ `scaffolding from ${ref} instead`,
87
+ };
88
+ }
@@ -0,0 +1,161 @@
1
+ # AGENTS.md — conventions for this project
2
+
3
+ Machine-readable house rules for humans and coding agents. Prefer this file
4
+ over reconstructing conventions from history.
5
+
6
+ ## What this repository is
7
+
8
+ The multiplatform.one monorepo at one commit, pruned. `.mpo.json` records the
9
+ source commit and url, the product apps and the services kept. `mpo update`
10
+ merges later monorepo commits in as a three-way merge that keeps your commits;
11
+ `mpo add-app <name>` adds a product app. Neither is committed for you.
12
+
13
+ The published `@multiplatform.one/*` packages come from the registry at the
14
+ semver range in each `package.json`. There is no copy of them here: a change to
15
+ one of them is made upstream, in the monorepo, and arrives with the next
16
+ `mpo update`.
17
+
18
+ ## Workspace layout
19
+
20
+ | Path | Role |
21
+ | ------------------ | ------------------------------------------------------------------- |
22
+ | `apps/<name>/` | Product apps and the services kept at init (frappe, keycloak, …) |
23
+ | `features/<name>/` | Feature modules for the matching app (screens, stories, playground) |
24
+ | `packages/` | Private app-integration packages (`@package/*`) |
25
+
26
+ **App ↔ features twin:** every product app `apps/<name>` MUST have
27
+ `features/<name>`. Services are exempt in the convention linter; product apps
28
+ are not. Prefer feature code in `features/`, thin wiring in `apps/`.
29
+
30
+ **Delivery targets live INSIDE the product app**, not as sibling shells:
31
+ `apps/<name>/routes/` (One web + native), `apps/<name>/tauri/` (webview
32
+ desktop shell), `apps/<name>/gnome/` (GTK4/GJS native-widget desktop),
33
+ `apps/<name>/webext/` (browser extension) and `apps/<name>/vscode/` (VS Code
34
+ extension), each with a sibling `vite.config.<target>.ts`. Tauri and GNOME are
35
+ peer desktop targets; pick per product. All targets mount the same `features/`
36
+ code behind ONE routing layer: One's router API, backed on non-One hosts by the
37
+ stack-navigation seam (`@multiplatform.one/router/seam`).
38
+
39
+ **Internal deps:** private workspace packages use `workspace:*`;
40
+ `@multiplatform.one/*` use the registry range.
41
+
42
+ ## Which package for what
43
+
44
+ | Package | Owns |
45
+ | ------------------------------- | ------------------------------------------------------------------------- |
46
+ | `@multiplatform.one/theme` | Knobs, presets, intents, recipes, `useResolvedKnobs` |
47
+ | `@multiplatform.one/components` | Catalog UI (layouts, surfaces, feedback, …) |
48
+ | `@multiplatform.one/forms` | Fields, Form, InputParts, field contracts, form color ramps |
49
+ | `@multiplatform.one/table` | DataTable, cells, filters, URL table state |
50
+ | `@multiplatform.one/frappe-ui` | Frappe adapters (`FrappeTable`, field registry) |
51
+ | `@multiplatform.one/frappe` | Frappe client / live query |
52
+ | `@multiplatform.one/backoffice` | Embeddable desk recreation (BackofficeProvider, shell/list/form surfaces) |
53
+
54
+ `table` and `forms` know nothing of Frappe; Frappe integration belongs in
55
+ `frappe-ui`. `features/backoffice` is a thin wrapper over
56
+ `@multiplatform.one/backoffice` (host wiring: one-router adapter, env-derived
57
+ frappe config, scheme adapter).
58
+
59
+ ### Import rules (enforced)
60
+
61
+ 1. **No raw `tamagui`** in `apps/`, `features/` or `packages/`: import
62
+ `@multiplatform.one/components`, `@multiplatform.one/forms` or
63
+ `@multiplatform.one/theme` instead (`oxlintrc.json`, no-restricted-imports).
64
+ 2. **No `*.css` files in `apps/*`** (except ignored bench/vendor trees): styling
65
+ goes through Tamagui + knobs.
66
+ 3. **No hex color literals in `apps/`, `packages/` or `features/`**: use
67
+ theme tokens (`$color10`, semantic ramps, knob recipes). A file no token
68
+ reaches (GTK widget CSS, a content script, Expo build config) opts out with
69
+ a `// hex-escape: <reason>` comment that says why.
70
+
71
+ Run: `pnpm lint` (oxlint + `scripts/lint-conventions.mjs`).
72
+
73
+ ## Knobs system (canonical)
74
+
75
+ Structural styling (radius, space, elevation, typography, animation) is
76
+ independent of Tamagui color themes. `useResolvedKnobs` comes from
77
+ `@multiplatform.one/theme`.
78
+
79
+ ```tsx
80
+ const { knobProps, control, text, elevation } = useResolvedKnobs({
81
+ intent, // "error" | "accent" | "warning" | "success"
82
+ component: "Button",
83
+ compact,
84
+ });
85
+
86
+ <View
87
+ {...knobProps.surface}
88
+ {...knobProps.borderRadius}
89
+ {...knobProps.panelPadding}
90
+ {...knobProps.gap}
91
+ {...knobProps.body}
92
+ hoverStyle={{ ...control.hoverKnobProps }}
93
+ pressStyle={{ ...control.pressKnobProps }}
94
+ focusVisibleStyle={{ ...control.focusVisibleKnobProps }}
95
+ />;
96
+ ```
97
+
98
+ Rules:
99
+
100
+ - Always spread complete `knobProps.*` fragments; never cherry-pick resolved tokens.
101
+ - `resolveKnobs()` is the only canonical mapping from abstract knobs → Tamagui props.
102
+ - Consumer `{...props}` come last (explicit eject), not internal defaults that fight recipes.
103
+ - Presets cascade (`<Preset preset="…" theme="…">`); nested `overrides` merge by key.
104
+ - One knob flip restyles the screen, or the knob is broken.
105
+
106
+ ## Forms contract
107
+
108
+ - Initialize with `useFormField()` + `useResolvedValidators()`.
109
+ - Three-branch render: skeleton → standalone → form-integrated (`<Field>`).
110
+ - Canonical change prop is `onChange(value)`. Deprecated aliases may fire but
111
+ must not be the only path.
112
+ - Field slot order: Label → Hint → Error → Control; error replaces helper in one slot.
113
+ - Use the form color ramps exported by `@multiplatform.one/forms`
114
+ (`formCommonColors`, `formDropzoneColors`); no hardcoded color tokens.
115
+ - Fields auto-detect `useIsInTableCell()` and render chromeless inside cells.
116
+ - Capability surfaces (camera, geo, …) MUST preflight before mount.
117
+
118
+ ## Table contract
119
+
120
+ - `@multiplatform.one/table` is framework-agnostic display/filter/sort/paginate.
121
+ - Cell editors larger than the anchor use popover (web) / sheet (native); never
122
+ grow the row. Editor popups dismiss only on dismissal intent.
123
+ - Frappe integration belongs in `@multiplatform.one/frappe-ui` (`FrappeTable`,
124
+ registry adapters), not in `table`.
125
+ - Child-table / TableInput follow the same field `onChange` contract as forms.
126
+
127
+ ## Screen data states
128
+
129
+ Every screen-level data fetch, not only Frappe, MUST render loading, empty,
130
+ error and ready through `AsyncBoundary`, `EmptyState` and `Skeleton` from
131
+ `@multiplatform.one/components`. A bare
132
+ `error ? <Paragraph color="$color11">` branch is a defect. Empty ≠ error ≠
133
+ loading.
134
+
135
+ ## Tooling
136
+
137
+ | Command | Purpose |
138
+ | ----------------------- | -------------------------------------------------------------- |
139
+ | `pnpm dev` | The first product app's dev server (`dev:<name>` when several) |
140
+ | `pnpm build` | Build every workspace package except the apps |
141
+ | `pnpm lint` | oxlint (incl. convention rules) + structural convention checks |
142
+ | `pnpm lint:conventions` | Structural checks only (css twin, features twin) |
143
+ | `pnpm format` | oxfmt |
144
+ | `pnpm ci:local` | format + lint + typecheck + build + test + verify + tree-clean |
145
+ | `pnpm doctor:npmrc` | Check registry auth and the installed workspace linker |
146
+
147
+ Use **oxlint / oxfmt**, not ESLint / Prettier. Per-app targets are
148
+ `make <name>/<target>` from the root `Makefile`.
149
+
150
+ Install layout settings live in `pnpm-workspace.yaml`, including
151
+ `nodeLinker: hoisted`. `@react-gnome/*` and `@multiplatform.one/*` both resolve
152
+ from npmjs, the default registry, so an install needs no token.
153
+ `pnpm doctor:npmrc` reports missing or empty auth
154
+ as advisory and fails if the installed tree records a different linker from the
155
+ workspace; an existing isolated tree needs `pnpm install` to repair its layout.
156
+
157
+ ## Coding defaults
158
+
159
+ - Named exports only; cut dead/legacy aliases unless a task explicitly needs them.
160
+ - Cross-platform: shared code first; `*.native.tsx` / `*.web.tsx` only when required.
161
+ - Stories and tests live next to the component / feature they cover.
@@ -0,0 +1,62 @@
1
+ # multiplatform.one project
2
+
3
+ Web, iOS and Android from one React + Tamagui tree, with desktop, browser
4
+ extension and VS Code targets inside each product app. This repository is the
5
+ [multiplatform.one](https://git.corp.bitspur.com/multiplatform.one/mpo)
6
+ monorepo at one commit, pruned to the product apps and services picked at
7
+ `mpo init`. `.mpo.json` records the commit and the selection, and `AGENTS.md`
8
+ holds the conventions.
9
+
10
+ ## Layout
11
+
12
+ | Path | What lives there |
13
+ | ------------------ | ------------------------------------------------------ |
14
+ | `apps/<name>/` | Each product app, and the services kept at init |
15
+ | `features/<name>/` | Screens and feature code for the matching app |
16
+ | `packages/` | Private app packages (`@package/app`, themes, i18n, …) |
17
+
18
+ The `@multiplatform.one/*` packages come from the registry.
19
+
20
+ ## Getting started
21
+
22
+ Prerequisites: Node 20+, pnpm 10+ and git.
23
+
24
+ ```sh
25
+ pnpm install
26
+ pnpm dev # the first product app (dev:<name> picks one of several)
27
+ ```
28
+
29
+ `.env` starts as a copy of `.env.example`.
30
+
31
+ <!-- service:frappe -->
32
+
33
+ ## Frappe
34
+
35
+ `apps/frappe` is the dev bench. `make frappe/bootstrap` creates it once, and
36
+ `make frappe/dev` starts the services it needs from `docker/compose.yaml` and
37
+ serves it on `FRAPPE_PORT` (`http://localhost:8000`), which also proxies the
38
+ web dev server same-origin. The apps reach it through `createApp` in
39
+ `packages/app/app.tsx` while `FRAPPE_ENABLED=1`.
40
+
41
+ <!-- /service:frappe -->
42
+ <!-- no-service:frappe -->
43
+
44
+ ## Frappe
45
+
46
+ This project runs no Frappe server of its own. The apps keep the Frappe
47
+ client: `createApp` in `packages/app/app.tsx` mounts the `FrappeProvider`
48
+ while `FRAPPE_ENABLED=1` and talks to `BASE_URL`, or to the `baseURL` passed
49
+ in `frappe: { baseURL }`. Point it at a Frappe server, or hand it a fixture
50
+ corpus (`frappe: { fixtures }`, an `InMemoryFixtureProvider` from
51
+ `@multiplatform.one/frappe`) and run with no server at all.
52
+
53
+ <!-- /no-service:frappe -->
54
+
55
+ ## Updating
56
+
57
+ ```sh
58
+ pnpm exec mpo update # needs a clean tree; the merge lands uncommitted
59
+ git add -A && git commit -m "chore: mpo update"
60
+ ```
61
+
62
+ `pnpm exec mpo add-app <name>` adds a product app the same way.
@@ -1,37 +1,12 @@
1
- ## Frappe backend (`mpo init --frappe`)
1
+ ## Frappe client (`frappe` piece)
2
2
 
3
3
  `apps/__NAME__/src/app.ts` wires `createApp({ frappe: … })`: the
4
- `FrappeProvider` mounts when `FRAPPE_ENABLED=1` and talks to `FRAPPE_URL`
5
- (both are `public` env keys in `packages/config/config.json`, loaded from
6
- `.env` — start from `.env.example`).
4
+ `FrappeProvider` mounts when `FRAPPE_ENABLED=1` and talks to the Frappe server
5
+ at `FRAPPE_URL` (both are `public` env keys in `packages/config/config.json`,
6
+ loaded from `.env` — start from `.env.example`). This piece brings no server
7
+ of its own; add the `frappe-bench` piece for a dev server in docker.
7
8
 
8
- Run a dev bench with docker:
9
-
10
- ```bash
11
- docker compose -f docker/compose.frappe.yaml up -d
12
- docker compose -f docker/compose.frappe.yaml exec bench bash
13
-
14
- # inside the container (first time only)
15
- bench init --skip-redis-config-generation frappe-bench
16
- cd frappe-bench
17
- bench set-config -g db_host mariadb
18
- bench set-config -g redis_cache redis://redis-cache:6379
19
- bench set-config -g redis_queue redis://redis-queue:6379
20
- bench set-config -g redis_socketio redis://redis-queue:6379
21
- bench new-site __NAME__.localhost \
22
- --mariadb-user-host-login-scope=% \
23
- --db-root-password frappe --admin-password admin
24
- bench --site __NAME__.localhost set-config developer_mode 1
25
-
26
- # every dev session
27
- bench start
28
- ```
29
-
30
- Then set `FRAPPE_URL=http://__NAME__.localhost:8000` (or keep
31
- `http://localhost:8000` and make the site the default with
32
- `bench use __NAME__.localhost`) and restart `pnpm dev`.
33
-
34
- ### Running against fixtures, with no bench
9
+ ### Running against fixtures, with no server
35
10
 
36
11
  `createApp` takes a fixture corpus, so the app runs with no server and no
37
12
  network. Hand `frappe.fixtures` a `FixtureProvider` in
@@ -85,5 +60,5 @@ const fixtures = new InMemoryFixtureProvider({
85
60
  Leave it out and the table still renders its rows; it just has no
86
61
  metadata-derived filter fields.
87
62
 
88
- To point the same app at a real bench again, drop `fixtures` and set
63
+ To point the same app at a real Frappe server again, drop `fixtures` and set
89
64
  `FRAPPE_ENABLED=1` + `FRAPPE_URL` in `.env`.
@@ -1,8 +1,8 @@
1
- # Frappe backend (mpo init --frappe) — copy to .env. Keys listed as `public`
1
+ # Frappe client (the frappe piece) — copy to .env. Keys listed as `public`
2
2
  # in packages/config/config.json are baked into the client bundle at build
3
3
  # time (VITE_MP_CONFIG); FRAPPE_ENABLED=1 mounts the FrappeProvider wired in
4
4
  # apps/__NAME__/src/app.ts.
5
5
  FRAPPE_ENABLED=1
6
- # Bench URL the client talks to (docker/compose.frappe.yaml serves :8000).
7
- # Falls back to BASE_URL, then http://localhost:8000.
6
+ # The Frappe server the client talks to. Falls back to BASE_URL, then
7
+ # http://localhost:8000.
8
8
  FRAPPE_URL=http://localhost:8000
@@ -0,0 +1,28 @@
1
+ ## Frappe dev bench (`frappe-bench` piece)
2
+
3
+ `docker/compose.frappe.yaml` runs a dev bench with its MariaDB and Redis:
4
+
5
+ ```bash
6
+ docker compose -f docker/compose.frappe.yaml up -d
7
+ docker compose -f docker/compose.frappe.yaml exec bench bash
8
+
9
+ # inside the container (first time only)
10
+ bench init --skip-redis-config-generation frappe-bench
11
+ cd frappe-bench
12
+ bench set-config -g db_host mariadb
13
+ bench set-config -g redis_cache redis://redis-cache:6379
14
+ bench set-config -g redis_queue redis://redis-queue:6379
15
+ bench set-config -g redis_socketio redis://redis-queue:6379
16
+ bench new-site __NAME__.localhost \
17
+ --mariadb-user-host-login-scope=% \
18
+ --db-root-password frappe --admin-password admin
19
+ bench --site __NAME__.localhost set-config developer_mode 1
20
+
21
+ # every dev session
22
+ bench start
23
+ ```
24
+
25
+ It serves `http://localhost:8000`. With the `frappe` piece, set
26
+ `FRAPPE_URL=http://__NAME__.localhost:8000` (or keep `http://localhost:8000`
27
+ and make the site the default with `bench use __NAME__.localhost`) and restart
28
+ `pnpm dev`.
@@ -1,11 +1,11 @@
1
- # Dev Frappe bench for __NAME__ (mpo init --frappe).
1
+ # Dev Frappe bench for __NAME__ (the frappe-bench piece).
2
2
  #
3
3
  # docker compose -f docker/compose.frappe.yaml up -d
4
4
  # docker compose -f docker/compose.frappe.yaml exec bench bash
5
5
  #
6
6
  # The frappe/bench image is a development sandbox (frappe user + bench
7
7
  # tooling); the bench itself is created ONCE inside the container — see the
8
- # "Frappe backend" section of the README for the init commands. State
8
+ # "Frappe dev bench" section of the README for the init commands. State
9
9
  # persists in the named volumes.
10
10
  services:
11
11
  bench:
@@ -9,10 +9,12 @@ pnpm tauri:dev # dev window against the vite dev server (:3456)
9
9
  pnpm tauri:build # bundle installers (runs pnpm build first)
10
10
  ```
11
11
 
12
- `tauri:dev` starts `pnpm dev` for you (beforeDevCommand) and opens the
13
- window once http://localhost:3456 responds. Note the web build is an SSR
14
- One app: `tauri:build` embeds `apps/__NAME__/dist` as-is, which suits a
15
- static/SPA export — point `frontendDist` (src-tauri/tauri.conf.json) at
16
- your static output, or keep the desktop shell against a served URL.
12
+ `tauri:dev` starts `pnpm dev` for you (beforeDevCommand); the window holds
13
+ on a waiting page until the app's client modules answer, then loads the
14
+ app (Cmd/Ctrl+R reloads it, Cmd/Ctrl+Alt+I toggles devtools). Note the web
15
+ build is an SSR One app: `tauri:build` embeds `apps/__NAME__/dist` as-is,
16
+ which suits a static/SPA export — point `frontendDist`
17
+ (src-tauri/tauri.conf.json) at your static output, or keep the desktop
18
+ shell against a served URL.
17
19
  Regenerate icons from your own artwork with
18
20
  `pnpm --filter @app/__NAME__ tauri icon <path/to/icon.png>`.