@multiplatform.one/cli 6.1.0 → 6.4.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 (108) hide show
  1. package/README.md +72 -0
  2. package/bin/createMultiplatformApp.mjs +77 -0
  3. package/lib/bin/multiplatformOne.mjs +35 -10
  4. package/lib/commands/e2e.mjs +3 -3
  5. package/lib/commands/init.mjs +16 -12
  6. package/lib/commands/initApp.mjs +175 -0
  7. package/lib/commands/updateApp.mjs +260 -0
  8. package/lib/generateVscode.mjs +27 -10
  9. package/package.json +23 -12
  10. package/src/bin/multiplatformOne.ts +83 -20
  11. package/src/commands/init.ts +7 -18
  12. package/src/commands/initApp.spec.ts +210 -0
  13. package/src/commands/initApp.ts +274 -0
  14. package/src/commands/updateApp.ts +277 -0
  15. package/src/generateVscode.ts +31 -9
  16. package/templates/app/AGENTS.md +142 -0
  17. package/templates/app/README.md +62 -0
  18. package/templates/app/apps/__NAME__/index.html +36 -0
  19. package/templates/app/apps/__NAME__/package.json +30 -0
  20. package/templates/app/apps/__NAME__/public/fonts/inter-400.woff2 +0 -0
  21. package/templates/app/apps/__NAME__/public/fonts/inter-700.woff2 +0 -0
  22. package/templates/app/apps/__NAME__/src/App.tsx +9 -0
  23. package/templates/app/apps/__NAME__/src/main.tsx +14 -0
  24. package/templates/app/apps/__NAME__/tsconfig.json +8 -0
  25. package/templates/app/apps/__NAME__/vite.config.ts +17 -0
  26. package/templates/app/features/__NAME__/package.json +34 -0
  27. package/templates/app/features/__NAME__/src/HomeScreen.tsx +70 -0
  28. package/templates/app/features/__NAME__/src/index.ts +1 -0
  29. package/templates/app/features/__NAME__/tsconfig.json +5 -0
  30. package/templates/app/gitignore +8 -0
  31. package/templates/app/npmrc +2 -0
  32. package/templates/app/oxlintrc.json +39 -0
  33. package/templates/app/package.json +20 -0
  34. package/templates/app/packages/config/config.json +4 -0
  35. package/templates/app/packages/config/index.ts +5 -0
  36. package/templates/app/packages/config/package.json +17 -0
  37. package/templates/app/packages/config/tsconfig.json +5 -0
  38. package/templates/app/pnpm-workspace.yaml +4 -0
  39. package/templates/app/tsconfig.base.json +16 -0
  40. package/templates/universal/AGENTS.md +142 -0
  41. package/templates/universal/README.md +97 -0
  42. package/templates/universal/apps/__NAME__/app.config.js +31 -0
  43. package/templates/universal/apps/__NAME__/config/tamagui.config.ts +4 -0
  44. package/templates/universal/apps/__NAME__/package.json +61 -0
  45. package/templates/universal/apps/__NAME__/polyfills.native.ts +23 -0
  46. package/templates/universal/apps/__NAME__/polyfills.ts +3 -0
  47. package/templates/universal/apps/__NAME__/public/fonts/inter-400.woff2 +0 -0
  48. package/templates/universal/apps/__NAME__/public/fonts/inter-700.woff2 +0 -0
  49. package/templates/universal/apps/__NAME__/routes/_layout.tsx +22 -0
  50. package/templates/universal/apps/__NAME__/routes/_layout.web.tsx +21 -0
  51. package/templates/universal/apps/__NAME__/routes/about.tsx +3 -0
  52. package/templates/universal/apps/__NAME__/routes/index.tsx +3 -0
  53. package/templates/universal/apps/__NAME__/src/app.ts +10 -0
  54. package/templates/universal/apps/__NAME__/tamagui.css +3 -0
  55. package/templates/universal/apps/__NAME__/tsconfig.json +8 -0
  56. package/templates/universal/apps/__NAME__/types.d.ts +9 -0
  57. package/templates/universal/apps/__NAME__/vite.config.ts +66 -0
  58. package/templates/universal/features/__NAME__/package.json +35 -0
  59. package/templates/universal/features/__NAME__/src/AboutScreen.tsx +27 -0
  60. package/templates/universal/features/__NAME__/src/AppLayout.tsx +50 -0
  61. package/templates/universal/features/__NAME__/src/ContactForm.spec.tsx +22 -0
  62. package/templates/universal/features/__NAME__/src/ContactForm.tsx +55 -0
  63. package/templates/universal/features/__NAME__/src/HomeScreen.tsx +40 -0
  64. package/templates/universal/features/__NAME__/src/index.ts +4 -0
  65. package/templates/universal/features/__NAME__/src/screens.spec.ts +12 -0
  66. package/templates/universal/features/__NAME__/tsconfig.json +5 -0
  67. package/templates/universal/features/__NAME__/vitest.config.mjs +7 -0
  68. package/templates/universal/gitignore +12 -0
  69. package/templates/universal/npmrc +3 -0
  70. package/templates/universal/oxlintrc.json +39 -0
  71. package/templates/universal/package.json +27 -0
  72. package/templates/universal/packages/config/config.json +4 -0
  73. package/templates/universal/packages/config/index.ts +5 -0
  74. package/templates/universal/packages/config/package.json +17 -0
  75. package/templates/universal/packages/config/tsconfig.json +5 -0
  76. package/templates/universal/packages/i18n/en/common.json +30 -0
  77. package/templates/universal/packages/i18n/es/common.json +30 -0
  78. package/templates/universal/packages/i18n/i18n.d.ts +16 -0
  79. package/templates/universal/packages/i18n/index.ts +26 -0
  80. package/templates/universal/packages/i18n/package.json +20 -0
  81. package/templates/universal/packages/i18n/resources.ts +13 -0
  82. package/templates/universal/packages/i18n/tsconfig.json +5 -0
  83. package/templates/universal/packages/themes/accent.ts +32 -0
  84. package/templates/universal/packages/themes/base.ts +50 -0
  85. package/templates/universal/packages/themes/fonts.ts +8 -0
  86. package/templates/universal/packages/themes/index.ts +33 -0
  87. package/templates/universal/packages/themes/package.json +23 -0
  88. package/templates/universal/packages/themes/tamagui.config.ts +14 -0
  89. package/templates/universal/packages/themes/tsconfig.json +5 -0
  90. package/templates/universal/pnpm-workspace.yaml +4 -0
  91. package/templates/universal/tsconfig.base.json +21 -0
  92. package/templates/universal/types/env.d.ts +9 -0
  93. package/types/bin/multiplatformOne.d.ts +11 -0
  94. package/types/bin/multiplatformOne.d.ts.map +1 -0
  95. package/types/commands/e2e.d.ts +14 -0
  96. package/types/commands/e2e.d.ts.map +1 -0
  97. package/types/commands/init.d.ts +16 -0
  98. package/types/commands/init.d.ts.map +1 -0
  99. package/types/commands/initApp.d.ts +41 -0
  100. package/types/commands/initApp.d.ts.map +1 -0
  101. package/types/commands/updateApp.d.ts +24 -0
  102. package/types/commands/updateApp.d.ts.map +1 -0
  103. package/types/generateVscode.d.ts +7 -0
  104. package/types/generateVscode.d.ts.map +1 -0
  105. package/types/index.d.ts +2 -0
  106. package/types/index.d.ts.map +1 -0
  107. package/types/types.d.ts +6 -0
  108. package/types/types.d.ts.map +1 -0
@@ -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,3 @@
1
+ import { AboutScreen } from "@features/__NAME__";
2
+
3
+ export default AboutScreen;
@@ -0,0 +1,3 @@
1
+ import { HomeScreen } from "@features/__NAME__";
2
+
3
+ export default HomeScreen;
@@ -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,3 @@
1
+ /* Generated by @tamagui/vite-plugin (outputCSS). This stub keeps the
2
+ `import "../tamagui.css"` in routes/_layout.web.tsx resolvable on a fresh
3
+ scaffold; dev/build regenerate it. */
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "types": ["vite/client"]
5
+ },
6
+ "include": ["./config/**/*", "./routes/**/*", "./src/**/*", "./*.ts", "./types.d.ts"],
7
+ "exclude": ["**/node_modules/**", "dist/**", "ios/**", "android/**"]
8
+ }
@@ -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
+ }
@@ -0,0 +1,22 @@
1
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
2
+ import { fireEvent } from "@testing-library/react";
3
+ import { describe, expect, it } from "vitest";
4
+ import { ContactForm } from "./ContactForm";
5
+
6
+ describe("ContactForm", () => {
7
+ it("renders the starter form fields", () => {
8
+ const result = renderWithProviders(<ContactForm />);
9
+ expect(result.findTextElement("Contact")).toBeDefined();
10
+ expect(result.findTextElement("Name")).toBeDefined();
11
+ expect(result.findTextElement("Message")).toBeDefined();
12
+ expect(result.getButton("Send")).toBeDefined();
13
+ });
14
+
15
+ it("shows required errors when submitting empty", () => {
16
+ const result = renderWithProviders(<ContactForm />);
17
+ const send = result.getButton("Send");
18
+ expect(send).toBeDefined();
19
+ fireEvent.click(send as Element);
20
+ expect(result.findTextElement("This field is required")).toBeDefined();
21
+ });
22
+ });
@@ -0,0 +1,55 @@
1
+ import { H2, Paragraph, YStack } from "@multiplatform.one/components";
2
+ import { Button, Input, TextArea } from "@multiplatform.one/forms";
3
+ import { useState } from "react";
4
+ import { useTranslation } from "react-i18next";
5
+
6
+ /**
7
+ * Starter form demonstrating @multiplatform.one/forms fields (Input, TextArea,
8
+ * Button) with the canonical onChangeText/value contract, helper/error slots,
9
+ * and a submit handler. Replace the console.info with a real mutation.
10
+ */
11
+ export function ContactForm() {
12
+ const { t } = useTranslation();
13
+ const [name, setName] = useState("");
14
+ const [message, setMessage] = useState("");
15
+ const [submitted, setSubmitted] = useState(false);
16
+ const [sent, setSent] = useState(false);
17
+
18
+ const requiredError = t("screens.contact.required", "This field is required");
19
+
20
+ function handleSend() {
21
+ setSubmitted(true);
22
+ if (!name.trim() || !message.trim()) return;
23
+ // eslint-disable-next-line no-console
24
+ console.info("[__NAME__] contact form:", { name, message });
25
+ setSent(true);
26
+ }
27
+
28
+ return (
29
+ <YStack gap="$4" maxWidth={480}>
30
+ <H2>{t("screens.contact.title", "Contact")}</H2>
31
+ <Input
32
+ label={t("screens.contact.name", "Name")}
33
+ placeholder={t("screens.contact.namePlaceholder", "Your name")}
34
+ value={name}
35
+ onChangeText={setName}
36
+ required
37
+ error={submitted && !name.trim() ? requiredError : undefined}
38
+ />
39
+ <TextArea
40
+ label={t("screens.contact.message", "Message")}
41
+ placeholder={t("screens.contact.messagePlaceholder", "What do you want to say?")}
42
+ value={message}
43
+ onChangeText={setMessage}
44
+ required
45
+ error={submitted && !message.trim() ? requiredError : undefined}
46
+ />
47
+ <Button onPress={handleSend}>{t("screens.contact.send", "Send")}</Button>
48
+ {sent ? (
49
+ <Paragraph color="$color11">
50
+ {t("screens.contact.sent", "Message sent (demo) — check the console.")}
51
+ </Paragraph>
52
+ ) : null}
53
+ </YStack>
54
+ );
55
+ }
@@ -0,0 +1,40 @@
1
+ import { H1, Paragraph, Screen, Separator, YStack } from "@multiplatform.one/components";
2
+ import { useResolvedKnobs } from "@multiplatform.one/theme";
3
+ import { Link } from "one";
4
+ import { useTranslation } from "react-i18next";
5
+ import { ContactForm } from "./ContactForm";
6
+
7
+ /**
8
+ * Home screen (route: /). Keep product UI in features/<name>; the app shell
9
+ * (apps/<name>) only wires routes and providers.
10
+ */
11
+ export function HomeScreen() {
12
+ const { t } = useTranslation();
13
+ // Knob wiring: spread complete knobProps fragments — never cherry-pick
14
+ // resolved tokens (see AGENTS.md, knobs system).
15
+ const { knobProps } = useResolvedKnobs({ component: "Card" });
16
+
17
+ return (
18
+ <Screen>
19
+ <YStack
20
+ {...knobProps.surface}
21
+ {...knobProps.borderRadius}
22
+ {...knobProps.panelPadding}
23
+ {...knobProps.gap}
24
+ backgroundColor="$color2"
25
+ borderColor="$borderColor"
26
+ >
27
+ <H1 {...knobProps.heading}>{t("screens.home.welcome", "Welcome to __NAME_PASCAL__")}</H1>
28
+ <Paragraph {...knobProps.body} color="$color11">
29
+ {t(
30
+ "screens.home.message",
31
+ "One codebase — web, iOS, and Android. Edit features/__NAME__/src/HomeScreen.tsx to get started.",
32
+ )}
33
+ </Paragraph>
34
+ <Link href="/about">{t("nav.about", "About")}</Link>
35
+ </YStack>
36
+ <Separator />
37
+ <ContactForm />
38
+ </Screen>
39
+ );
40
+ }
@@ -0,0 +1,4 @@
1
+ export { AboutScreen } from "./AboutScreen";
2
+ export { AppLayout } from "./AppLayout";
3
+ export { ContactForm } from "./ContactForm";
4
+ export { HomeScreen } from "./HomeScreen";
@@ -0,0 +1,12 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { AboutScreen, AppLayout, ContactForm, HomeScreen } from "./index";
3
+
4
+ // Source-shape smoke: every starter screen is exported and renderable
5
+ // (component functions). Full render coverage lives next to each component.
6
+ describe("feature exports", () => {
7
+ it("exports the starter screens", () => {
8
+ for (const Component of [AboutScreen, AppLayout, ContactForm, HomeScreen]) {
9
+ expect(typeof Component).toBe("function");
10
+ }
11
+ });
12
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["./src/**/*", "../../types/env.d.ts"],
4
+ "exclude": ["**/node_modules/**"]
5
+ }
@@ -0,0 +1,7 @@
1
+ import { createVitestConfig } from "@multiplatform.one/config/vitest";
2
+
3
+ export default createVitestConfig({
4
+ tamaguiConfig: "../../packages/themes/tamagui.config.ts",
5
+ setupFiles: ["@multiplatform.one/test-utils/setup"],
6
+ include: ["src/**/*.spec.{ts,tsx}"],
7
+ });
@@ -0,0 +1,12 @@
1
+ node_modules
2
+ dist
3
+ .turbo
4
+ .vite
5
+ .tamagui
6
+ .expo
7
+ *.log
8
+ .env
9
+ .env.local
10
+ .DS_Store
11
+ apps/__NAME__/ios
12
+ apps/__NAME__/android
@@ -0,0 +1,3 @@
1
+ auto-install-peers=true
2
+ strict-peer-dependencies=false
3
+ node-linker=hoisted
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "rules": {
4
+ "no-unused-vars": [
5
+ "warn",
6
+ {
7
+ "argsIgnorePattern": "^_",
8
+ "varsIgnorePattern": "^_"
9
+ }
10
+ ],
11
+ "no-console": "warn",
12
+ "eqeqeq": "error",
13
+ "no-var": "error",
14
+ "prefer-const": "error"
15
+ },
16
+ "overrides": [
17
+ {
18
+ "files": [
19
+ "apps/**/*.{ts,tsx,js,jsx}",
20
+ "features/**/*.{ts,tsx,js,jsx}",
21
+ "packages/**/*.{ts,tsx,js,jsx}"
22
+ ],
23
+ "rules": {
24
+ "no-restricted-imports": [
25
+ "error",
26
+ {
27
+ "paths": [
28
+ {
29
+ "name": "tamagui",
30
+ "message": "Raw tamagui is only allowed inside framework packages. Import from @multiplatform.one/components, @multiplatform.one/forms, or @multiplatform.one/theme. See AGENTS.md."
31
+ }
32
+ ]
33
+ }
34
+ ]
35
+ }
36
+ }
37
+ ],
38
+ "ignorePatterns": ["**/node_modules/**", "**/dist/**", "**/build/**"]
39
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "__NAME__",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "description": "__NAME__ — multiplatform.one universal app (web + iOS + Android)",
6
+ "license": "MIT",
7
+ "scripts": {
8
+ "dev": "pnpm --filter @app/__NAME__ dev",
9
+ "build": "pnpm --filter @app/__NAME__ build",
10
+ "serve": "pnpm --filter @app/__NAME__ serve",
11
+ "native": "pnpm --filter @app/__NAME__ prebuild:native",
12
+ "ios": "pnpm --filter @app/__NAME__ ios",
13
+ "android": "pnpm --filter @app/__NAME__ android",
14
+ "test": "pnpm -r test",
15
+ "lint": "oxlint -c oxlintrc.json .",
16
+ "typecheck": "pnpm -r typecheck"
17
+ },
18
+ "devDependencies": {
19
+ "@types/chrome": "^0.2.5",
20
+ "@types/firefox-webext-browser": "^143.0.0",
21
+ "oxlint": "^1.62.0"
22
+ },
23
+ "engines": {
24
+ "node": ">=20"
25
+ },
26
+ "packageManager": "pnpm@10.32.1"
27
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "private": ["SECRET"],
3
+ "public": ["BASE_URL", "DEBUG", "I18N_DEFAULT_LANGUAGE"]
4
+ }
@@ -0,0 +1,5 @@
1
+ import configJson from "./config.json";
2
+
3
+ export const config = configJson;
4
+ export type AppConfig = typeof configJson;
5
+ export default config;
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@package/config",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts",
9
+ "./config.json": "./config.json"
10
+ },
11
+ "scripts": {
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "devDependencies": {
15
+ "typescript": "~5.9.3"
16
+ }
17
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["./**/*"],
4
+ "exclude": ["**/node_modules/**"]
5
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "app": {
3
+ "name": "__NAME_PASCAL__"
4
+ },
5
+ "nav": {
6
+ "home": "Home",
7
+ "about": "About"
8
+ },
9
+ "screens": {
10
+ "home": {
11
+ "welcome": "Welcome to __NAME_PASCAL__",
12
+ "message": "One codebase — web, iOS, and Android. Edit features/__NAME__/src/HomeScreen.tsx to get started."
13
+ },
14
+ "about": {
15
+ "title": "About",
16
+ "message": "This screen lives in features/__NAME__ and is wired to the /about route by apps/__NAME__/routes/about.tsx.",
17
+ "back": "Back home"
18
+ },
19
+ "contact": {
20
+ "title": "Contact",
21
+ "name": "Name",
22
+ "namePlaceholder": "Your name",
23
+ "message": "Message",
24
+ "messagePlaceholder": "What do you want to say?",
25
+ "send": "Send",
26
+ "required": "This field is required",
27
+ "sent": "Message sent (demo) — check the console."
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "app": {
3
+ "name": "__NAME_PASCAL__"
4
+ },
5
+ "nav": {
6
+ "home": "Inicio",
7
+ "about": "Acerca de"
8
+ },
9
+ "screens": {
10
+ "home": {
11
+ "welcome": "Bienvenido a __NAME_PASCAL__",
12
+ "message": "Un solo código — web, iOS y Android. Edita features/__NAME__/src/HomeScreen.tsx para empezar."
13
+ },
14
+ "about": {
15
+ "title": "Acerca de",
16
+ "message": "Esta pantalla vive en features/__NAME__ y se conecta a la ruta /about desde apps/__NAME__/routes/about.tsx.",
17
+ "back": "Volver al inicio"
18
+ },
19
+ "contact": {
20
+ "title": "Contacto",
21
+ "name": "Nombre",
22
+ "namePlaceholder": "Tu nombre",
23
+ "message": "Mensaje",
24
+ "messagePlaceholder": "¿Qué quieres decir?",
25
+ "send": "Enviar",
26
+ "required": "Este campo es obligatorio",
27
+ "sent": "Mensaje enviado (demo) — mira la consola."
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * i18next TypeScript type augmentations.
3
+ *
4
+ * Key typing is intentionally loose (t() accepts any string; missing keys fall
5
+ * back at runtime). To restore strict typed keys, add
6
+ * `resources: { common: typeof import("./en/common.json") }` to
7
+ * CustomTypeOptions.
8
+ */
9
+ import "i18next";
10
+
11
+ declare module "i18next" {
12
+ interface CustomTypeOptions {
13
+ defaultNS: "common";
14
+ returnNull: false;
15
+ }
16
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Supported languages. Add a language by creating `<lng>/common.json` and
3
+ * listing the code here (and in resources.ts for native).
4
+ */
5
+ export const languages = ["en", "es"] as const;
6
+
7
+ /** Translation namespaces — one JSON file per namespace per language. */
8
+ export const namespaces = ["common"] as const;
9
+
10
+ /** Fallback language when a translation is missing. */
11
+ export const defaultLanguage = "en" as const;
12
+
13
+ /** Namespace used when t() calls do not specify one. */
14
+ export const defaultNamespace = "common" as const;
15
+
16
+ export type Language = (typeof languages)[number];
17
+ export type Namespace = (typeof namespaces)[number];
18
+
19
+ // Re-export framework i18n utilities
20
+ export {
21
+ createI18nConfig,
22
+ detectLanguage,
23
+ getPersistedLanguage,
24
+ persistLanguage,
25
+ } from "@multiplatform.one/i18n";
26
+ export type { I18nConfigOptions } from "@multiplatform.one/i18n";
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@package/i18n",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts",
9
+ "./resources": "./resources.ts"
10
+ },
11
+ "scripts": {
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "@multiplatform.one/i18n": "__MPO_VERSION__"
16
+ },
17
+ "devDependencies": {
18
+ "typescript": "~5.9.3"
19
+ }
20
+ }