@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.
- package/README.md +121 -111
- package/bin/createMultiplatformApp.mjs +39 -37
- package/lib/bin/multiplatformOne.mjs +65 -75
- package/lib/commands/addApp.mjs +78 -0
- package/lib/commands/adoptApp.mjs +11 -7
- package/lib/commands/doctypeTypes.mjs +148 -0
- package/lib/commands/e2e.mjs +2 -2
- package/lib/commands/init.mjs +142 -459
- package/lib/commands/merge.mjs +359 -0
- package/lib/commands/pieces.mjs +236 -0
- package/lib/commands/provenance.mjs +108 -0
- package/lib/commands/scaffold.mjs +105 -0
- package/lib/commands/updateApp.mjs +211 -528
- package/lib/source.json +5 -0
- package/lib/source.mjs +63 -0
- package/package.json +7 -7
- package/scripts/frappe-bootstrap.sh +29 -3
- package/scripts/frappe-dev.sh +1 -2
- package/scripts/frappe-helpers.sh +50 -2
- package/scripts/frappe.sh +5 -0
- package/scripts/scaffold.sh +937 -0
- package/scripts/stamp-source.mjs +51 -0
- package/scripts/stamp-source.spec.ts +86 -0
- package/src/bin/multiplatformOne.ts +116 -226
- package/src/commands/addApp.ts +102 -0
- package/src/commands/adoptApp.spec.ts +5 -1
- package/src/commands/adoptApp.ts +15 -10
- package/src/commands/doctypeTypes.spec.ts +155 -0
- package/src/commands/doctypeTypes.ts +214 -0
- package/src/commands/init.spec.ts +66 -0
- package/src/commands/init.ts +201 -643
- package/src/commands/merge.spec.ts +81 -0
- package/src/commands/merge.ts +411 -0
- package/src/commands/pieces.spec.ts +62 -0
- package/src/commands/pieces.ts +340 -0
- package/src/commands/provenance.spec.ts +92 -0
- package/src/commands/provenance.ts +169 -0
- package/src/commands/scaffold.ts +131 -0
- package/src/commands/updateApp.spec.ts +540 -331
- package/src/commands/updateApp.ts +255 -668
- package/src/source.spec.ts +45 -0
- package/src/source.ts +88 -0
- package/templates/AGENTS.md +161 -0
- package/templates/README.md +62 -0
- package/templates/pieces/frappe/universal/README.md.partial +7 -32
- package/templates/pieces/frappe/universal/env.example.partial +3 -3
- package/templates/pieces/frappe-bench/universal/README.md.partial +28 -0
- package/templates/pieces/{frappe → frappe-bench}/universal/docker/compose.frappe.yaml +2 -2
- package/templates/pieces/tauri/universal/README.md.partial +7 -5
- package/templates/pieces/tauri/universal/apps/__NAME__/src-tauri/src/dev.rs +463 -0
- package/templates/pieces/tauri/universal/apps/__NAME__/src-tauri/src/lib.rs +6 -0
- package/templates/pieces/tauri/universal/apps/__NAME__/src-tauri/tauri.conf.json +2 -1
- package/templates/pieces/webext/universal/apps/__NAME__/package.json.partial +1 -0
- package/templates/pieces/webext/universal/apps/__NAME__/vite.config.webext.ts +6 -2
- package/types/bin/multiplatformOne.d.ts.map +1 -1
- package/types/commands/addApp.d.ts +20 -0
- package/types/commands/addApp.d.ts.map +1 -0
- package/types/commands/adoptApp.d.ts +4 -3
- package/types/commands/adoptApp.d.ts.map +1 -1
- package/types/commands/doctypeTypes.d.ts +40 -0
- package/types/commands/doctypeTypes.d.ts.map +1 -0
- package/types/commands/init.d.ts +30 -13
- package/types/commands/init.d.ts.map +1 -1
- package/types/commands/merge.d.ts +54 -0
- package/types/commands/merge.d.ts.map +1 -0
- package/types/commands/pieces.d.ts +54 -0
- package/types/commands/pieces.d.ts.map +1 -0
- package/types/commands/provenance.d.ts +52 -0
- package/types/commands/provenance.d.ts.map +1 -0
- package/types/commands/scaffold.d.ts +43 -0
- package/types/commands/scaffold.d.ts.map +1 -0
- package/types/commands/updateApp.d.ts +19 -27
- package/types/commands/updateApp.d.ts.map +1 -1
- package/types/source.d.ts +35 -0
- package/types/source.d.ts.map +1 -0
- package/lib/commands/initApp.mjs +0 -431
- package/lib/generateVscode.mjs +0 -184
- package/scripts/clone.sh +0 -17
- package/scripts/update.sh +0 -46
- package/src/commands/initApp.spec.ts +0 -720
- package/src/commands/initApp.ts +0 -706
- package/src/generateVscode.ts +0 -238
- package/templates/app/AGENTS.md +0 -142
- package/templates/app/README.md +0 -62
- package/templates/app/apps/__NAME__/index.html +0 -36
- package/templates/app/apps/__NAME__/package.json +0 -31
- 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 +0 -9
- package/templates/app/apps/__NAME__/src/main.tsx +0 -14
- package/templates/app/apps/__NAME__/tsconfig.json +0 -8
- package/templates/app/apps/__NAME__/vite.config.ts +0 -17
- package/templates/app/features/__NAME__/package.json +0 -34
- package/templates/app/features/__NAME__/src/HomeScreen.tsx +0 -70
- package/templates/app/features/__NAME__/src/index.ts +0 -1
- package/templates/app/features/__NAME__/tsconfig.json +0 -5
- package/templates/app/gitignore +0 -9
- package/templates/app/npmrc +0 -2
- package/templates/app/oxlintrc.json +0 -39
- package/templates/app/package.json +0 -38
- package/templates/app/packages/config/config.json +0 -4
- package/templates/app/packages/config/index.ts +0 -5
- package/templates/app/packages/config/package.json +0 -17
- package/templates/app/packages/config/tsconfig.json +0 -5
- package/templates/app/pnpm-workspace.yaml +0 -4
- package/templates/app/tsconfig.base.json +0 -16
- package/templates/universal/AGENTS.md +0 -142
- package/templates/universal/README.md +0 -97
- package/templates/universal/apps/__NAME__/app.config.js +0 -31
- package/templates/universal/apps/__NAME__/config/tamagui.config.ts +0 -4
- package/templates/universal/apps/__NAME__/package.json +0 -62
- package/templates/universal/apps/__NAME__/polyfills.native.ts +0 -23
- package/templates/universal/apps/__NAME__/polyfills.ts +0 -3
- 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 +0 -22
- package/templates/universal/apps/__NAME__/routes/_layout.web.tsx +0 -21
- package/templates/universal/apps/__NAME__/routes/about.tsx +0 -3
- package/templates/universal/apps/__NAME__/routes/index.tsx +0 -3
- package/templates/universal/apps/__NAME__/src/app.ts +0 -10
- package/templates/universal/apps/__NAME__/tamagui.css +0 -3
- package/templates/universal/apps/__NAME__/tsconfig.json +0 -8
- package/templates/universal/apps/__NAME__/types.d.ts +0 -9
- package/templates/universal/apps/__NAME__/vite.config.ts +0 -20
- package/templates/universal/features/__NAME__/package.json +0 -35
- package/templates/universal/features/__NAME__/src/AboutScreen.tsx +0 -27
- package/templates/universal/features/__NAME__/src/AppLayout.tsx +0 -50
- package/templates/universal/features/__NAME__/src/ContactForm.spec.tsx +0 -22
- package/templates/universal/features/__NAME__/src/ContactForm.tsx +0 -55
- package/templates/universal/features/__NAME__/src/HomeScreen.tsx +0 -40
- package/templates/universal/features/__NAME__/src/index.ts +0 -4
- package/templates/universal/features/__NAME__/src/screens.spec.ts +0 -12
- package/templates/universal/features/__NAME__/tsconfig.json +0 -5
- package/templates/universal/features/__NAME__/vitest.config.mjs +0 -7
- package/templates/universal/gitignore +0 -14
- package/templates/universal/npmrc +0 -3
- package/templates/universal/oxlintrc.json +0 -39
- package/templates/universal/package.json +0 -51
- package/templates/universal/packages/config/config.json +0 -4
- package/templates/universal/packages/config/index.ts +0 -5
- package/templates/universal/packages/config/package.json +0 -17
- package/templates/universal/packages/config/tsconfig.json +0 -5
- package/templates/universal/packages/i18n/en/common.json +0 -30
- package/templates/universal/packages/i18n/es/common.json +0 -30
- package/templates/universal/packages/i18n/i18n.d.ts +0 -16
- package/templates/universal/packages/i18n/index.ts +0 -26
- package/templates/universal/packages/i18n/package.json +0 -20
- package/templates/universal/packages/i18n/resources.ts +0 -13
- package/templates/universal/packages/i18n/tsconfig.json +0 -5
- package/templates/universal/packages/themes/accent.ts +0 -32
- package/templates/universal/packages/themes/base.ts +0 -50
- package/templates/universal/packages/themes/fonts.ts +0 -8
- package/templates/universal/packages/themes/index.ts +0 -33
- package/templates/universal/packages/themes/package.json +0 -23
- package/templates/universal/packages/themes/tamagui.config.ts +0 -14
- package/templates/universal/packages/themes/tsconfig.json +0 -5
- package/templates/universal/pnpm-workspace.yaml +0 -4
- package/templates/universal/tsconfig.base.json +0 -21
- package/templates/universal/types/env.d.ts +0 -9
- package/types/commands/initApp.d.ts +0 -114
- package/types/commands/initApp.d.ts.map +0 -1
- package/types/generateVscode.d.ts +0 -7
- 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
|
|
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
|
|
5
|
-
(both are `public` env keys in `packages/config/config.json`,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
#
|
|
7
|
-
#
|
|
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__ (
|
|
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
|
|
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)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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>`.
|