@multiplatform.one/config 7.7.6 → 7.10.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/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { t as createViteConfig } from "./vite-DF5gYMDn.js";
2
- import { t as createStorybookViteConfig } from "./storybook-BuO1e8Sb.js";
1
+ import { t as createViteConfig } from "./vite-D6vMjPwv.js";
2
+ import { t as createStorybookViteConfig } from "./storybook-CGNSgSBS.js";
3
3
  import { n as tamaguiWorkspacePathsFile, t as ensureTamaguiWorkspacePaths } from "./tamaguiWorkspacePaths-ZIcqybtn.js";
4
4
  import { createVitestConfig } from "./vitest.js";
5
5
 
@@ -1,103 +1,9 @@
1
- import { r as resolvePackageMainSource, t as discoverPublicPackageRoots } from "./workspacePublicPackages-COicQSj4.js";
1
+ import { i as withSingletonDepAliases, r as resolvePackageMainSource, t as discoverPublicPackageRoots } from "./workspacePublicPackages-B4FGgRYG.js";
2
2
  import { t as ensureTamaguiWorkspacePaths } from "./tamaguiWorkspacePaths-ZIcqybtn.js";
3
3
  import { createRequire } from "node:module";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
6
 
7
- //#region src/singletonDepAliases.ts
8
- /**
9
- * Packages that create a React context at module scope and must therefore
10
- * resolve to exactly ONE module record across the whole bundle.
11
- *
12
- * MPO-215. `react-cookie` runs `React.createContext(null)` when its entry
13
- * evaluates. `@multiplatform.one/storybook` renders `<CookiesProvider>` in the
14
- * framework decorator and `@multiplatform.one/theme`'s `useTheme` reads it via
15
- * `useCookies`, so provider and consumer live in two different workspace
16
- * packages. When those two packages resolve `react-cookie` to two different
17
- * realpaths — a hoisted `node_modules/react-cookie` for the importer that does
18
- * not declare it, the pnpm store copy for the one that does — the entry
19
- * evaluates twice, there are two contexts, and `useCookies` throws
20
- * `Missing <CookiesProvider>` with the provider sitting two lines above it in
21
- * the same tree.
22
- *
23
- * Nothing catches that today. The dev server pre-bundles bare imports by
24
- * package NAME, which collapses both realpaths onto one `deps/react-cookie.js`
25
- * and renders fine; the production build has no optimizer, so both paths
26
- * survive, every story renders Storybook's error panel, and
27
- * `storybook build` still exits 0.
28
- *
29
- * `resolve.dedupe` does not fix it: measured against Vite 8 / Rolldown, a
30
- * build with `dedupe: ["react-cookie"]` emitted a byte-identical iframe chunk
31
- * and the story still threw. An alias to one absolute directory does, which is
32
- * the same instrument the storybook config already uses to pin react,
33
- * react-dom and react-is.
34
- *
35
- * MPO-217. `@tamagui/web` is the same shape. It owns the theme context, the
36
- * style registry and the config singleton; `@tamagui/core` re-exports it and
37
- * `tamagui` re-exports that, so every tamagui component in the tree reads the
38
- * theme through whichever `@tamagui/web` record its own import chain reached.
39
- *
40
- * Measured on the build's module graph at e19a0fda3: `tamagui` resolved to
41
- * two realpaths. 562 importers (public/components, public/backoffice,
42
- * public/storybook and the rest) took the pnpm store copy, whose chain is
43
- * store `tamagui` -> store `@tamagui/core` -> store `@tamagui/web`; five took
44
- * the hoisted `node_modules/tamagui`, which has no nested node_modules and so
45
- * walks up to the hoisted `@tamagui/core` and `@tamagui/web`. Those five were
46
- * public/frappe's story files — the only modules in the repo that import
47
- * `tamagui` from a package with no copy of its own — and their 14 stories were
48
- * the 14 that threw `Missing theme.` on storybook-static while rendering on
49
- * dev. The theme provider is mounted from the store record; their `Button`,
50
- * `Text` and `YStack` read the hoisted record's context, which no provider
51
- * ever wrote to.
52
- *
53
- * `tamagui` itself is deliberately NOT pinned. A directory alias resolves
54
- * `tamagui/linear-gradient` to `node_modules/tamagui/linear-gradient/`, a
55
- * metro-compat stub that `require`s the CJS build, instead of the ESM target
56
- * the package's exports map picks for the browser. Pinning core and web is
57
- * enough: both `tamagui` records import `@tamagui/core` by bare specifier, so
58
- * they funnel into one web record and one theme context.
59
- */
60
- const CONTEXT_SINGLETONS = [
61
- "react-cookie",
62
- "@tamagui/core",
63
- "@tamagui/web"
64
- ];
65
- /**
66
- * Vite `resolve.alias` entries pinning each context singleton to one directory.
67
- *
68
- * Alias keys match the whole specifier or a `key + "/"` prefix, so subpath
69
- * imports follow the same copy instead of resolving independently. Entries are
70
- * omitted when the package is not installed, so a consumer that does not use
71
- * cookies is unaffected.
72
- *
73
- * @param root Directory whose `node_modules` the packages resolve from.
74
- */
75
- function singletonDepAliases(root = process.cwd()) {
76
- const aliases = {};
77
- const requireFrom = createRequire(path.join(root, "package.json"));
78
- for (const pkgName of CONTEXT_SINGLETONS) {
79
- const pkgDir = resolvePackageDir$1(pkgName, root, requireFrom);
80
- if (pkgDir) aliases[pkgName] = pkgDir;
81
- }
82
- return aliases;
83
- }
84
- function resolvePackageDir$1(pkgName, root, requireFrom) {
85
- try {
86
- return path.dirname(requireFrom.resolve(`${pkgName}/package.json`));
87
- } catch {
88
- try {
89
- let dir = path.dirname(requireFrom.resolve(pkgName));
90
- while (dir !== path.dirname(dir)) {
91
- if (fs.existsSync(path.join(dir, "package.json"))) return dir;
92
- dir = path.dirname(dir);
93
- }
94
- } catch {}
95
- const hoisted = path.join(root, "node_modules", ...pkgName.split("/"));
96
- return fs.existsSync(path.join(hoisted, "package.json")) ? hoisted : void 0;
97
- }
98
- }
99
-
100
- //#endregion
101
7
  //#region src/unexportedDepAliases.ts
102
8
  /**
103
9
  * Package subtrees that consumers deep-import but the publisher never declared
@@ -186,15 +92,11 @@ function resolvePackageDir(pkgName, root, requireFrom) {
186
92
  function createStorybookViteConfig(options = {}) {
187
93
  ensureTamaguiWorkspacePaths();
188
94
  const workspaceRoot = options.workspaceRoot || findWorkspaceRoot();
189
- const aliases = {
95
+ const sortedAliases = withSingletonDepAliases({
190
96
  ...discoverPackageAliases(path.join(workspaceRoot, "packages")),
191
97
  ...discoverPublicPackageViteAliases(workspaceRoot),
192
- ...unexportedDepAliases(workspaceRoot),
193
- ...singletonDepAliases(workspaceRoot)
194
- };
195
- if (options.aliases) Object.assign(aliases, options.aliases);
196
- const sortedAliases = {};
197
- for (const key of Object.keys(aliases).sort((a, b) => b.length - a.length)) sortedAliases[key] = aliases[key];
98
+ ...unexportedDepAliases(workspaceRoot)
99
+ }, workspaceRoot, options.aliases);
198
100
  return {
199
101
  define: {
200
102
  "process.env.VITE_ENVIRONMENT": JSON.stringify("client"),
package/lib/storybook.js CHANGED
@@ -1,3 +1,3 @@
1
- import { t as createStorybookViteConfig } from "./storybook-BuO1e8Sb.js";
1
+ import { t as createStorybookViteConfig } from "./storybook-CGNSgSBS.js";
2
2
 
3
3
  export { createStorybookViteConfig };
@@ -1,4 +1,4 @@
1
- import { t as discoverPublicPackageRoots } from "./workspacePublicPackages-COicQSj4.js";
1
+ import { i as withSingletonDepAliases, t as discoverPublicPackageRoots } from "./workspacePublicPackages-B4FGgRYG.js";
2
2
  import { t as ensureTamaguiWorkspacePaths } from "./tamaguiWorkspacePaths-ZIcqybtn.js";
3
3
  import { createRequire } from "node:module";
4
4
  import fs from "node:fs";
@@ -199,10 +199,10 @@ function createViteConfig(options = {}) {
199
199
  },
200
200
  ssr: ssr || defaultSsr,
201
201
  resolve: {
202
- alias: {
202
+ alias: withSingletonDepAliases({
203
203
  "react.mjs": "react",
204
204
  "react-compiler-runtime": "react/compiler-runtime"
205
- },
205
+ }, projectRoot),
206
206
  dedupe: ["react-is"]
207
207
  },
208
208
  server: defaultServer,
package/lib/vite.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as createViteConfig } from "./vite-DF5gYMDn.js";
2
- import { n as publicPackageViteSourceAliases, t as discoverPublicPackageRoots } from "./workspacePublicPackages-COicQSj4.js";
1
+ import { t as createViteConfig } from "./vite-D6vMjPwv.js";
2
+ import { n as publicPackageViteSourceAliases, t as discoverPublicPackageRoots } from "./workspacePublicPackages-B4FGgRYG.js";
3
3
 
4
4
  export { createViteConfig, discoverPublicPackageRoots, publicPackageViteSourceAliases };
package/lib/vitest.js CHANGED
@@ -130,6 +130,7 @@ function createVitestConfig(options = {}) {
130
130
  "tamagui",
131
131
  /@tanstack\//,
132
132
  "use-sync-external-store",
133
+ "react-i18next",
133
134
  ...inlineDeps
134
135
  ],
135
136
  ...externalDeps.length > 0 ? { external: [pinReactRequireSetup, ...externalDeps] } : { external: [pinReactRequireSetup] },
@@ -0,0 +1,218 @@
1
+ import { createRequire } from "node:module";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ //#region src/singletonDepAliases.ts
6
+ /**
7
+ * Packages that create a React context at module scope and must therefore
8
+ * resolve to exactly ONE module record across the whole bundle.
9
+ *
10
+ * MPO-215. `react-cookie` runs `React.createContext(null)` when its entry
11
+ * evaluates. `@multiplatform.one/storybook` renders `<CookiesProvider>` in the
12
+ * framework decorator and `@multiplatform.one/theme`'s `useTheme` reads it via
13
+ * `useCookies`, so provider and consumer live in two different workspace
14
+ * packages. When those two packages resolve `react-cookie` to two different
15
+ * realpaths — a hoisted `node_modules/react-cookie` for the importer that does
16
+ * not declare it, the pnpm store copy for the one that does — the entry
17
+ * evaluates twice, there are two contexts, and `useCookies` throws
18
+ * `Missing <CookiesProvider>` with the provider sitting two lines above it in
19
+ * the same tree.
20
+ *
21
+ * Nothing catches that today. The dev server pre-bundles bare imports by
22
+ * package NAME, which collapses both realpaths onto one `deps/react-cookie.js`
23
+ * and renders fine; the production build has no optimizer, so both paths
24
+ * survive, every story renders Storybook's error panel, and
25
+ * `storybook build` still exits 0.
26
+ *
27
+ * `resolve.dedupe` does not fix it: measured against Vite 8 / Rolldown, a
28
+ * build with `dedupe: ["react-cookie"]` emitted a byte-identical iframe chunk
29
+ * and the story still threw. An alias to one absolute directory does, which is
30
+ * the same instrument the storybook config already uses to pin react,
31
+ * react-dom and react-is.
32
+ *
33
+ * MPO-217. `@tamagui/web` is the same shape. It owns the theme context, the
34
+ * style registry and the config singleton; `@tamagui/core` re-exports it and
35
+ * `tamagui` re-exports that, so every tamagui component in the tree reads the
36
+ * theme through whichever `@tamagui/web` record its own import chain reached.
37
+ *
38
+ * Measured on the build's module graph at e19a0fda3: `tamagui` resolved to
39
+ * two realpaths. 562 importers (public/components, public/backoffice,
40
+ * public/storybook and the rest) took the pnpm store copy, whose chain is
41
+ * store `tamagui` -> store `@tamagui/core` -> store `@tamagui/web`; five took
42
+ * the hoisted `node_modules/tamagui`, which has no nested node_modules and so
43
+ * walks up to the hoisted `@tamagui/core` and `@tamagui/web`. Those five were
44
+ * public/frappe's story files — the only modules in the repo that import
45
+ * `tamagui` from a package with no copy of its own — and their 14 stories were
46
+ * the 14 that threw `Missing theme.` on storybook-static while rendering on
47
+ * dev. The theme provider is mounted from the store record; their `Button`,
48
+ * `Text` and `YStack` read the hoisted record's context, which no provider
49
+ * ever wrote to.
50
+ *
51
+ * `tamagui` itself is deliberately NOT pinned. A directory alias resolves
52
+ * `tamagui/linear-gradient` to `node_modules/tamagui/linear-gradient/`, a
53
+ * metro-compat stub that `require`s the CJS build, instead of the ESM target
54
+ * the package's exports map picks for the browser. Pinning core and web is
55
+ * enough: both `tamagui` records import `@tamagui/core` by bare specifier, so
56
+ * they funnel into one web record and one theme context.
57
+ *
58
+ * MPO-225. `@tanstack/db` is the same shape one layer down, and it is the one
59
+ * that reaches generated apps. `@multiplatform.one/frappe`'s collections are
60
+ * TanStack DB, and TanStack reaches React through `use-sync-external-store`,
61
+ * so a second module record subscribes against a store nothing armed and the
62
+ * hook reads `useSyncExternalStore` off null. Measured on a fresh
63
+ * `mpo init --universal --frappe --tauri` at cli 7.7.1: the first page
64
+ * mounting a data-bound panel threw `Cannot read properties of null (reading
65
+ * 'useSyncExternalStore')` on every load, and so did every data-bound page
66
+ * after it.
67
+ *
68
+ * MPO-288. THE CRITERION, so the next addition is a decision against a rule
69
+ * rather than a guess. A package belongs here when a second module record
70
+ * would break it: it holds state at module scope, it subscribes an external
71
+ * store, or it provides a React context consumers compare by identity.
72
+ * Grepping the installed package for `useSyncExternalStore` is a cheap test
73
+ * for the second clause, and `singletonDepAliases.spec.ts` re-runs it against
74
+ * the tree so a tanstack upgrade is checked rather than assumed. It is one
75
+ * clause and not the whole rule: `@tanstack/db` references the hook nowhere
76
+ * and is pinned anyway, for the collection registry it holds at module scope.
77
+ *
78
+ * `@tanstack/react-query` references the hook in 25 shipped files and is not
79
+ * listed. That is recorded rather than settled — no split has been observed
80
+ * there, and pinning it reaches further than this measurement does.
81
+ */
82
+ const CONTEXT_SINGLETONS = [
83
+ "react-cookie",
84
+ "@tamagui/core",
85
+ "@tamagui/web",
86
+ "@tanstack/db",
87
+ "@tanstack/react-db",
88
+ "@tanstack/react-store"
89
+ ];
90
+ /**
91
+ * Vite `resolve.alias` entries pinning each context singleton to one directory.
92
+ *
93
+ * Alias keys match the whole specifier or a `key + "/"` prefix, so subpath
94
+ * imports follow the same copy instead of resolving independently. Entries are
95
+ * omitted when the package is not installed, so a consumer that does not use
96
+ * cookies is unaffected.
97
+ *
98
+ * @param root Directory whose `node_modules` the packages resolve from.
99
+ */
100
+ function singletonDepAliases(root = process.cwd()) {
101
+ const aliases = {};
102
+ const requireFrom = createRequire(path.join(root, "package.json"));
103
+ for (const pkgName of CONTEXT_SINGLETONS) {
104
+ const pkgDir = resolvePackageDir(pkgName, root, requireFrom);
105
+ if (pkgDir) aliases[pkgName] = pkgDir;
106
+ }
107
+ return aliases;
108
+ }
109
+ /**
110
+ * Merges `base` with the singleton pins and orders the result for Vite.
111
+ *
112
+ * Both entry points build `resolve.alias` through here so the list cannot go
113
+ * out of reach again: before MPO-225 only `createStorybookViteConfig` applied
114
+ * `singletonDepAliases`, and a project generated by `mpo init` builds through
115
+ * `createViteConfig` alone, so every package on the list resolved to two
116
+ * records there.
117
+ *
118
+ * MPO-215: singletons are merged last so a discovered workspace or dependency
119
+ * alias can never shadow one. Keys are sorted longest-first because Vite tries
120
+ * alias entries in insertion order and matches a key or its `key + "/"` prefix.
121
+ * `overrides` are applied after the pins: a caller naming a package explicitly
122
+ * means it.
123
+ *
124
+ * @param base Aliases the pins may shadow.
125
+ * @param root Directory whose `node_modules` the packages resolve from.
126
+ * @param overrides Aliases that win over everything, including the pins.
127
+ */
128
+ function withSingletonDepAliases(base, root = process.cwd(), overrides = {}) {
129
+ const merged = {
130
+ ...base,
131
+ ...singletonDepAliases(root),
132
+ ...overrides
133
+ };
134
+ const sorted = {};
135
+ for (const key of Object.keys(merged).sort((a, b) => b.length - a.length)) sorted[key] = merged[key];
136
+ return sorted;
137
+ }
138
+ function resolvePackageDir(pkgName, root, requireFrom) {
139
+ try {
140
+ return path.dirname(requireFrom.resolve(`${pkgName}/package.json`));
141
+ } catch {
142
+ try {
143
+ let dir = path.dirname(requireFrom.resolve(pkgName));
144
+ while (dir !== path.dirname(dir)) {
145
+ if (fs.existsSync(path.join(dir, "package.json"))) return dir;
146
+ dir = path.dirname(dir);
147
+ }
148
+ } catch {}
149
+ const hoisted = path.join(root, "node_modules", ...pkgName.split("/"));
150
+ return fs.existsSync(path.join(hoisted, "package.json")) ? hoisted : void 0;
151
+ }
152
+ }
153
+
154
+ //#endregion
155
+ //#region src/workspacePublicPackages.ts
156
+ /**
157
+ * Every direct child of `public/` with a `package.json` `name` is treated as a
158
+ * publishable workspace package (same rule as Metro in `apps/storybook-expo`).
159
+ *
160
+ * Tamagui’s internal esbuild (bundling `tamagui.config` + `components`) reads
161
+ * `compilerOptions.paths` from the repo `tsconfig.base.json` chain, including
162
+ * `tamagui-workspace-paths.generated.json` (see `scripts/generate-tamagui-workspace-paths.mjs`).
163
+ */
164
+ function discoverPublicPackageRoots(workspaceRoot) {
165
+ const byName = /* @__PURE__ */ new Map();
166
+ const publicDir = path.join(workspaceRoot, "public");
167
+ if (!fs.existsSync(publicDir)) return byName;
168
+ for (const ent of fs.readdirSync(publicDir, { withFileTypes: true })) {
169
+ if (!ent.isDirectory()) continue;
170
+ const pkgRoot = path.join(publicDir, ent.name);
171
+ const pkgJsonPath = path.join(pkgRoot, "package.json");
172
+ if (!fs.existsSync(pkgJsonPath)) continue;
173
+ try {
174
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
175
+ if (typeof pkg.name === "string" && pkg.name.length > 0) byName.set(pkg.name, pkgRoot);
176
+ } catch {}
177
+ }
178
+ return byName;
179
+ }
180
+ /**
181
+ * Main entry file for Vite/Storybook-style “compile from source” aliases.
182
+ *
183
+ * These aliases are only consumed by WEB Vite configs (Storybook, SPA), so
184
+ * `.web.*` entries take priority over the plain (native) ones — otherwise a
185
+ * package like @package/fonts resolves to its native index.ts and its
186
+ * web-only side effects (fontsource @font-face CSS imports) never load.
187
+ */
188
+ function resolvePackageMainSource(pkgDir) {
189
+ return [
190
+ path.join(pkgDir, "src", "index.storybook.ts"),
191
+ path.join(pkgDir, "src", "index.storybook.tsx"),
192
+ path.join(pkgDir, "src", "index.web.ts"),
193
+ path.join(pkgDir, "src", "index.web.tsx"),
194
+ path.join(pkgDir, "src", "index.ts"),
195
+ path.join(pkgDir, "src", "index.tsx"),
196
+ path.join(pkgDir, "index.storybook.ts"),
197
+ path.join(pkgDir, "index.storybook.tsx"),
198
+ path.join(pkgDir, "index.web.ts"),
199
+ path.join(pkgDir, "index.web.tsx"),
200
+ path.join(pkgDir, "index.ts"),
201
+ path.join(pkgDir, "index.tsx")
202
+ ].find((c) => fs.existsSync(c));
203
+ }
204
+ /**
205
+ * Vite `resolve.alias` entries: each public package name → its TypeScript main.
206
+ * For SPA / ad-hoc Vite configs that should not list packages by hand.
207
+ */
208
+ function publicPackageViteSourceAliases(workspaceRoot) {
209
+ const aliases = {};
210
+ for (const [name, dir] of discoverPublicPackageRoots(workspaceRoot)) {
211
+ const main = resolvePackageMainSource(dir);
212
+ if (main) aliases[name] = main;
213
+ }
214
+ return aliases;
215
+ }
216
+
217
+ //#endregion
218
+ export { withSingletonDepAliases as i, publicPackageViteSourceAliases as n, resolvePackageMainSource as r, discoverPublicPackageRoots as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/config",
3
- "version": "7.7.6",
3
+ "version": "7.10.0",
4
4
  "description": "Shared build and test configuration presets for multiplatform.one",
5
5
  "keywords": [
6
6
  "config",
@@ -102,7 +102,7 @@
102
102
  "vite-plugin-external": "^6.2.2",
103
103
  "vite-plugin-i18next-loader": "^3.1.3",
104
104
  "vitest": "^4.1.5",
105
- "@multiplatform.one/utils": "7.7.6"
105
+ "@multiplatform.one/utils": "7.10.0"
106
106
  },
107
107
  "devDependencies": {
108
108
  "tsdown": "^0.21.10",
@@ -290,7 +290,7 @@ describe("no-hex-literals comments and palette (MPO-17 AC-4)", () => {
290
290
 
291
291
  it("stays silent in packages/themes/base.ts even for rgba", () => {
292
292
  const reports: Report[] = [];
293
- const rule = plugin.rules?.["no-hex-literals"] as Creatable & {
293
+ const rule = plugin.rules?.["no-hex-literals"] as {
294
294
  create: (c: unknown) => { Literal?: (n: unknown) => void };
295
295
  };
296
296
  const visitors = rule.create({
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { describe, expect, it } from "vitest";
4
4
  import { createStorybookViteConfig } from "./storybook";
5
5
  import { singletonDepAliases } from "./singletonDepAliases";
6
+ import { createViteConfig } from "./vite";
6
7
 
7
8
  const workspaceRoot = path.resolve(__dirname, "../../..");
8
9
 
@@ -42,6 +43,42 @@ describe("singletonDepAliases", () => {
42
43
  expect(singletonDepAliases(workspaceRoot).tamagui).toBeUndefined();
43
44
  });
44
45
 
46
+ it("pins the tanstack db record to one package directory (MPO-225)", () => {
47
+ // @multiplatform.one/frappe's collections are TanStack DB, and TanStack
48
+ // reaches React through use-sync-external-store. Two realpaths means the
49
+ // second record subscribes against a store nothing armed, and every
50
+ // data-bound page dies on load with "Cannot read properties of null
51
+ // (reading 'useSyncExternalStore')".
52
+ const aliases = singletonDepAliases(workspaceRoot);
53
+ expect(aliases["@tanstack/db"]).toBeTruthy();
54
+ expect(path.isAbsolute(aliases["@tanstack/db"])).toBe(true);
55
+ expect(fs.existsSync(path.join(aliases["@tanstack/db"], "package.json"))).toBe(true);
56
+ });
57
+
58
+ it("pins the react-facing half too, which is the one that calls the hook (MPO-225)", () => {
59
+ // Measured in the installed tree: @tanstack/db has zero references to
60
+ // useSyncExternalStore and @tanstack/react-db has three, useLiveQuery.js
61
+ // among them. public/frappe depends on both and imports react-db from
62
+ // src/useLiveQuery.ts, so pinning the base package alone leaves the
63
+ // reported crash in place. The pair travels together or neither works.
64
+ const aliases = singletonDepAliases(workspaceRoot);
65
+ expect(aliases["@tanstack/react-db"]).toBeTruthy();
66
+ expect(path.isAbsolute(aliases["@tanstack/react-db"])).toBe(true);
67
+ expect(fs.existsSync(path.join(aliases["@tanstack/react-db"], "package.json"))).toBe(true);
68
+ });
69
+
70
+ it("pins the react half of the store pair, which is the one that calls the hook (MPO-288)", () => {
71
+ // The same split as react-db, measured the same way: @tanstack/store has
72
+ // zero references to useSyncExternalStore and @tanstack/react-store has
73
+ // three, all of them useSelector, which is what useStore calls.
74
+ // public/store and public/forms both reach React through it, so a second
75
+ // record subscribes a store nothing armed.
76
+ const aliases = singletonDepAliases(workspaceRoot);
77
+ expect(aliases["@tanstack/react-store"]).toBeTruthy();
78
+ expect(path.isAbsolute(aliases["@tanstack/react-store"])).toBe(true);
79
+ expect(fs.existsSync(path.join(aliases["@tanstack/react-store"], "package.json"))).toBe(true);
80
+ });
81
+
45
82
  it("omits packages that are not installed", () => {
46
83
  // Resolution runs against the given root, so a consumer without the
47
84
  // package gets no alias rather than a broken one.
@@ -81,3 +118,90 @@ describe("createStorybookViteConfig react-cookie singleton", () => {
81
118
  expect(config.optimizeDeps?.include ?? []).toContain("react-cookie");
82
119
  });
83
120
  });
121
+
122
+ describe("createViteConfig context singletons (MPO-225)", () => {
123
+ const alias = (createViteConfig({ projectRoot: workspaceRoot }).resolve?.alias ?? {}) as Record<
124
+ string,
125
+ string
126
+ >;
127
+
128
+ it("applies every pin on the consumer path, not only in storybook", () => {
129
+ // This is the defect the ticket names. The list existed and only
130
+ // createStorybookViteConfig read it. A project generated by `mpo init`
131
+ // builds through createViteConfig and nothing else, so it inherited none
132
+ // of it.
133
+ const expected = singletonDepAliases(workspaceRoot);
134
+ expect(Object.keys(expected).length).toBeGreaterThan(0);
135
+ for (const [pkg, dir] of Object.entries(expected)) {
136
+ expect(alias[pkg], `${pkg} must be pinned on the consumer path`).toBe(dir);
137
+ }
138
+ });
139
+
140
+ it("keeps the hand-written aliases createViteConfig already carried", () => {
141
+ expect(alias["react.mjs"]).toBe("react");
142
+ expect(alias["react-compiler-runtime"]).toBe("react/compiler-runtime");
143
+ });
144
+
145
+ it("orders keys longest-first so a pin cannot be swallowed by a shorter one", () => {
146
+ // Vite tries object alias entries in insertion order and matches a key or
147
+ // its `key + "/"` prefix, so a subpath entry must precede its package.
148
+ const lengths = Object.keys(alias).map((key) => key.length);
149
+ expect(lengths).toEqual([...lengths].sort((a, b) => b - a));
150
+ });
151
+
152
+ it("does not pin tamagui itself on the consumer path either", () => {
153
+ expect(alias.tamagui).toBeUndefined();
154
+ });
155
+ });
156
+
157
+ describe("CONTEXT_SINGLETONS membership, measured rather than recited (MPO-288)", () => {
158
+ const aliases = singletonDepAliases(workspaceRoot);
159
+
160
+ it("pins every tanstack half that subscribes an external store", () => {
161
+ for (const pkg of ["@tanstack/react-store", "@tanstack/react-db"]) {
162
+ expect(subscribesExternalStore(pkg), `${pkg} must reference the hook`).toBe(true);
163
+ expect(aliases[pkg], `${pkg} subscribes and must be pinned`).toBeTruthy();
164
+ }
165
+ });
166
+
167
+ it("leaves the pacer pair off, which the same measurement confirms", () => {
168
+ for (const pkg of ["@tanstack/pacer", "@tanstack/react-pacer"]) {
169
+ expect(subscribesExternalStore(pkg), `${pkg} must not reference the hook`).toBe(false);
170
+ expect(aliases[pkg], `${pkg} has no measured reason to be pinned`).toBeUndefined();
171
+ }
172
+ });
173
+
174
+ it("does not treat the grep as the whole rule", () => {
175
+ // @tanstack/db is pinned and references the hook nowhere: it holds the
176
+ // collection registry at module scope, which is a different clause of the
177
+ // criterion and one no grep can see. @tanstack/store carries no such
178
+ // registry, so silence there means what it says.
179
+ expect(subscribesExternalStore("@tanstack/db")).toBe(false);
180
+ expect(aliases["@tanstack/db"]).toBeTruthy();
181
+ expect(subscribesExternalStore("@tanstack/store")).toBe(false);
182
+ });
183
+ });
184
+
185
+ /**
186
+ * Whether an installed package references `useSyncExternalStore` in anything
187
+ * it ships. The cheap half of the membership rule, re-run against the tree so
188
+ * the next tanstack upgrade is checked rather than assumed.
189
+ */
190
+ function subscribesExternalStore(pkgName: string): boolean {
191
+ const dir = path.join(workspaceRoot, "node_modules", ...pkgName.split("/"));
192
+ return fs.existsSync(dir) && referencesHook(dir);
193
+ }
194
+
195
+ function referencesHook(dir: string): boolean {
196
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
197
+ if (entry.name === "node_modules") continue;
198
+ const full = path.join(dir, entry.name);
199
+ if (fs.statSync(full).isDirectory()) {
200
+ if (referencesHook(full)) return true;
201
+ continue;
202
+ }
203
+ if (!/\.(?:js|cjs|mjs|ts|tsx)$/.test(entry.name)) continue;
204
+ if (fs.readFileSync(full, "utf8").includes("useSyncExternalStore")) return true;
205
+ }
206
+ return false;
207
+ }
@@ -53,8 +53,52 @@ import path from "node:path";
53
53
  * the package's exports map picks for the browser. Pinning core and web is
54
54
  * enough: both `tamagui` records import `@tamagui/core` by bare specifier, so
55
55
  * they funnel into one web record and one theme context.
56
+ *
57
+ * MPO-225. `@tanstack/db` is the same shape one layer down, and it is the one
58
+ * that reaches generated apps. `@multiplatform.one/frappe`'s collections are
59
+ * TanStack DB, and TanStack reaches React through `use-sync-external-store`,
60
+ * so a second module record subscribes against a store nothing armed and the
61
+ * hook reads `useSyncExternalStore` off null. Measured on a fresh
62
+ * `mpo init --universal --frappe --tauri` at cli 7.7.1: the first page
63
+ * mounting a data-bound panel threw `Cannot read properties of null (reading
64
+ * 'useSyncExternalStore')` on every load, and so did every data-bound page
65
+ * after it.
66
+ *
67
+ * MPO-288. THE CRITERION, so the next addition is a decision against a rule
68
+ * rather than a guess. A package belongs here when a second module record
69
+ * would break it: it holds state at module scope, it subscribes an external
70
+ * store, or it provides a React context consumers compare by identity.
71
+ * Grepping the installed package for `useSyncExternalStore` is a cheap test
72
+ * for the second clause, and `singletonDepAliases.spec.ts` re-runs it against
73
+ * the tree so a tanstack upgrade is checked rather than assumed. It is one
74
+ * clause and not the whole rule: `@tanstack/db` references the hook nowhere
75
+ * and is pinned anyway, for the collection registry it holds at module scope.
76
+ *
77
+ * `@tanstack/react-query` references the hook in 25 shipped files and is not
78
+ * listed. That is recorded rather than settled — no split has been observed
79
+ * there, and pinning it reaches further than this measurement does.
56
80
  */
57
- const CONTEXT_SINGLETONS: readonly string[] = ["react-cookie", "@tamagui/core", "@tamagui/web"];
81
+ const CONTEXT_SINGLETONS: readonly string[] = [
82
+ "react-cookie",
83
+ "@tamagui/core",
84
+ "@tamagui/web",
85
+ "@tanstack/db",
86
+ // The half that actually reaches React. Measured in the installed tree:
87
+ // @tanstack/db contains ZERO references to useSyncExternalStore and
88
+ // @tanstack/react-db contains three, useLiveQuery.js among them, which is
89
+ // the call in the reported crash. public/frappe depends on both and imports
90
+ // react-db from src/useLiveQuery.ts, so pinning only @tanstack/db would
91
+ // leave "Cannot read properties of null (reading 'useSyncExternalStore')"
92
+ // exactly where it was. MPO-225 named @tanstack/db; the measurement says
93
+ // the pair has to travel together.
94
+ "@tanstack/react-db",
95
+ // Measured at 0.11.0: @tanstack/store references useSyncExternalStore in
96
+ // nothing it ships and @tanstack/react-store in three, all useSelector,
97
+ // which is the call behind useStore. public/store and public/forms both
98
+ // reach React through it, so a second record would subscribe a store
99
+ // nothing armed — the null read MPO-225 reported, one package family over.
100
+ "@tanstack/react-store",
101
+ ];
58
102
 
59
103
  /**
60
104
  * Vite `resolve.alias` entries pinning each context singleton to one directory.
@@ -78,6 +122,38 @@ export function singletonDepAliases(root: string = process.cwd()): Record<string
78
122
  return aliases;
79
123
  }
80
124
 
125
+ /**
126
+ * Merges `base` with the singleton pins and orders the result for Vite.
127
+ *
128
+ * Both entry points build `resolve.alias` through here so the list cannot go
129
+ * out of reach again: before MPO-225 only `createStorybookViteConfig` applied
130
+ * `singletonDepAliases`, and a project generated by `mpo init` builds through
131
+ * `createViteConfig` alone, so every package on the list resolved to two
132
+ * records there.
133
+ *
134
+ * MPO-215: singletons are merged last so a discovered workspace or dependency
135
+ * alias can never shadow one. Keys are sorted longest-first because Vite tries
136
+ * alias entries in insertion order and matches a key or its `key + "/"` prefix.
137
+ * `overrides` are applied after the pins: a caller naming a package explicitly
138
+ * means it.
139
+ *
140
+ * @param base Aliases the pins may shadow.
141
+ * @param root Directory whose `node_modules` the packages resolve from.
142
+ * @param overrides Aliases that win over everything, including the pins.
143
+ */
144
+ export function withSingletonDepAliases(
145
+ base: Record<string, string>,
146
+ root: string = process.cwd(),
147
+ overrides: Record<string, string> = {},
148
+ ): Record<string, string> {
149
+ const merged = { ...base, ...singletonDepAliases(root), ...overrides };
150
+ const sorted: Record<string, string> = {};
151
+ for (const key of Object.keys(merged).sort((a, b) => b.length - a.length)) {
152
+ sorted[key] = merged[key];
153
+ }
154
+ return sorted;
155
+ }
156
+
81
157
  function resolvePackageDir(
82
158
  pkgName: string,
83
159
  root: string,
package/src/storybook.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import type { Plugin, UserConfig } from "vite";
4
- import { singletonDepAliases } from "./singletonDepAliases.js";
4
+ import { withSingletonDepAliases } from "./singletonDepAliases.js";
5
5
  import { unexportedDepAliases } from "./unexportedDepAliases.js";
6
6
  import { discoverPublicPackageRoots, resolvePackageMainSource } from "./workspacePublicPackages.js";
7
7
  import { ensureTamaguiWorkspacePaths } from "./tamaguiWorkspacePaths.js";
@@ -56,24 +56,15 @@ export function createStorybookViteConfig(
56
56
  const workspaceRoot = options.workspaceRoot || findWorkspaceRoot();
57
57
  const packagesDir = path.join(workspaceRoot, "packages");
58
58
 
59
- const aliases = {
60
- ...discoverPackageAliases(packagesDir),
61
- ...discoverPublicPackageViteAliases(workspaceRoot),
62
- ...unexportedDepAliases(workspaceRoot),
63
- // MPO-215: last, so a context singleton can never be shadowed by a
64
- // discovered workspace alias.
65
- ...singletonDepAliases(workspaceRoot),
66
- };
67
-
68
- if (options.aliases) {
69
- Object.assign(aliases, options.aliases);
70
- }
71
-
72
- // Sort by key length descending so more-specific aliases match first
73
- const sortedAliases: Record<string, string> = {};
74
- for (const key of Object.keys(aliases).sort((a, b) => b.length - a.length)) {
75
- sortedAliases[key] = aliases[key];
76
- }
59
+ const sortedAliases = withSingletonDepAliases(
60
+ {
61
+ ...discoverPackageAliases(packagesDir),
62
+ ...discoverPublicPackageViteAliases(workspaceRoot),
63
+ ...unexportedDepAliases(workspaceRoot),
64
+ },
65
+ workspaceRoot,
66
+ options.aliases,
67
+ );
77
68
 
78
69
  return {
79
70
  define: {
package/src/vite.ts CHANGED
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { lookupTamaguiModules, resolveConfig } from "@multiplatform.one/utils/dev";
4
4
  import { discoverPublicPackageRoots } from "./workspacePublicPackages.js";
5
+ import { withSingletonDepAliases } from "./singletonDepAliases.js";
5
6
  import { ensureTamaguiWorkspacePaths } from "./tamaguiWorkspacePaths.js";
6
7
  import { tamaguiPlugin } from "@tamagui/vite-plugin";
7
8
  import dotenv from "dotenv";
@@ -461,24 +462,30 @@ export function createViteConfig(options: CreateViteConfigOptions = {}): UserCon
461
462
  },
462
463
  ssr: ssr || defaultSsr,
463
464
  resolve: {
464
- alias: {
465
- // Fix tamagui-build outputting `from "react.mjs"` instead of `from "react"`
466
- "react.mjs": "react",
465
+ // MPO-225: the context-singleton pins are merged in HERE too, not only
466
+ // in storybook.ts. This is the whole build config a generated app has,
467
+ // so a package that resolves to two module records dies here first.
468
+ alias: withSingletonDepAliases(
469
+ {
470
+ // Fix tamagui-build outputting `from "react.mjs"` instead of `from "react"`
471
+ "react.mjs": "react",
467
472
 
468
- // React 19.1+ ships its own compiler runtime. The standalone
469
- // `react-compiler-runtime` beta package (pulled in by `one`) gets
470
- // pre-bundled by Vite with its own React copy, causing a duplicate-
471
- // React dispatcher-is-null crash (`useMemoCache`). Aliasing to
472
- // the built-in entry point ensures a single React instance.
473
- "react-compiler-runtime": "react/compiler-runtime",
473
+ // React 19.1+ ships its own compiler runtime. The standalone
474
+ // `react-compiler-runtime` beta package (pulled in by `one`) gets
475
+ // pre-bundled by Vite with its own React copy, causing a duplicate-
476
+ // React dispatcher-is-null crash (`useMemoCache`). Aliasing to
477
+ // the built-in entry point ensures a single React instance.
478
+ "react-compiler-runtime": "react/compiler-runtime",
474
479
 
475
- // use-sync-external-store is marked as SSR-external by the
476
- // ssrExternalFix plugin in vite.config.ts, so Node's CJS loader
477
- // handles it during SSR (avoiding the "module is not defined" error).
478
- // Do NOT alias it here — Vite's prefix-match aliases catch subpaths
479
- // like /shim/with-selector.js and /with-selector, rewriting them to
480
- // react/with-selector which doesn't exist, crashing dep optimisation.
481
- },
480
+ // use-sync-external-store is marked as SSR-external by the
481
+ // ssrExternalFix plugin in vite.config.ts, so Node's CJS loader
482
+ // handles it during SSR (avoiding the "module is not defined" error).
483
+ // Do NOT alias it here — Vite's prefix-match aliases catch subpaths
484
+ // like /shim/with-selector.js and /with-selector, rewriting them to
485
+ // react/with-selector which doesn't exist, crashing dep optimisation.
486
+ },
487
+ projectRoot,
488
+ ),
482
489
  // @react-navigation/core ships a nested node_modules/react-is (CJS-only).
483
490
  // Deduplication forces the root copy so clientBrokenEsmPlugin's pre-bundle
484
491
  // covers it everywhere.
@@ -128,7 +128,7 @@ describe("dedupeReact aliases", () => {
128
128
  expect(dedupe).not.toContain("react-dom");
129
129
  });
130
130
 
131
- it("inlines the CJS react deps that Node would otherwise require from the hoisted root", () => {
131
+ it("inlines React consumers that Node would otherwise load from the hoisted root", () => {
132
132
  const config = createVitestConfig({ tamaguiConfig: false }) as {
133
133
  test?: { server?: { deps?: { inline?: unknown[]; external?: unknown[] } } };
134
134
  };
@@ -137,6 +137,7 @@ describe("dedupeReact aliases", () => {
137
137
  inline.some((entry) => entry instanceof RegExp && String(entry).includes("@tanstack")),
138
138
  ).toBe(true);
139
139
  expect(inline).toContain("use-sync-external-store");
140
+ expect(inline).toContain("react-i18next");
140
141
  });
141
142
 
142
143
  it("installs a worker setup that pins CJS require('react') to the same copy", () => {
package/src/vitest.ts CHANGED
@@ -306,6 +306,9 @@ export function createVitestConfig(options: CreateVitestConfigOptions = {}): Use
306
306
  "tamagui",
307
307
  /@tanstack\//,
308
308
  "use-sync-external-store",
309
+ // Native ESM bypasses both the Vite React alias and the CJS require
310
+ // pin. Transform react-i18next so useTranslation shares the renderer.
311
+ "react-i18next",
309
312
  ...inlineDeps,
310
313
  ],
311
314
  ...(externalDeps.length > 0
@@ -9,4 +9,24 @@
9
9
  * @param root Directory whose `node_modules` the packages resolve from.
10
10
  */
11
11
  export declare function singletonDepAliases(root?: string): Record<string, string>;
12
+ /**
13
+ * Merges `base` with the singleton pins and orders the result for Vite.
14
+ *
15
+ * Both entry points build `resolve.alias` through here so the list cannot go
16
+ * out of reach again: before MPO-225 only `createStorybookViteConfig` applied
17
+ * `singletonDepAliases`, and a project generated by `mpo init` builds through
18
+ * `createViteConfig` alone, so every package on the list resolved to two
19
+ * records there.
20
+ *
21
+ * MPO-215: singletons are merged last so a discovered workspace or dependency
22
+ * alias can never shadow one. Keys are sorted longest-first because Vite tries
23
+ * alias entries in insertion order and matches a key or its `key + "/"` prefix.
24
+ * `overrides` are applied after the pins: a caller naming a package explicitly
25
+ * means it.
26
+ *
27
+ * @param base Aliases the pins may shadow.
28
+ * @param root Directory whose `node_modules` the packages resolve from.
29
+ * @param overrides Aliases that win over everything, including the pins.
30
+ */
31
+ export declare function withSingletonDepAliases(base: Record<string, string>, root?: string, overrides?: Record<string, string>): Record<string, string>;
12
32
  //# sourceMappingURL=singletonDepAliases.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"singletonDepAliases.d.ts","sourceRoot":"","sources":["../src/singletonDepAliases.ts"],"names":[],"mappings":"AA0DA;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,MAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxF"}
1
+ {"version":3,"file":"singletonDepAliases.d.ts","sourceRoot":"","sources":["../src/singletonDepAliases.ts"],"names":[],"mappings":"AAsGA;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,MAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,IAAI,GAAE,MAAsB,EAC5B,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACrC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"storybook.d.ts","sourceRoot":"","sources":["../src/storybook.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAU,UAAU,EAAE,MAAM,MAAM,CAAC;AAM/C,MAAM,WAAW,gCAAgC;IAC/C;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;OAEG;IACH,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAEhC;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,gCAAqC,GAC7C,UAAU,CAuLZ"}
1
+ {"version":3,"file":"storybook.d.ts","sourceRoot":"","sources":["../src/storybook.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAU,UAAU,EAAE,MAAM,MAAM,CAAC;AAM/C,MAAM,WAAW,gCAAgC;IAC/C;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;OAEG;IACH,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAEhC;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,gCAAqC,GAC7C,UAAU,CA8KZ"}
@@ -1 +1 @@
1
- {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAU,UAAU,EAAE,MAAM,MAAM,CAAC;AAI/C,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE5B;;OAEG;IACH,OAAO,CAAC,EACJ,OAAO,GACP;QACE;;;WAGG;QACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QAEtB;;;WAGG;QACH,MAAM,CAAC,EAAE,MAAM,CAAC;QAEhB;;WAEG;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEN;;OAEG;IACH,IAAI,CAAC,EACD,OAAO,GACP;QACE;;;WAGG;QACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAEjB;;WAEG;QACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;KAC9B,CAAC;IAEN;;;OAGG;IACH,GAAG,CAAC,EACA,OAAO,GACP;QACE,MAAM,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC3B,GAAG,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACtD,KAAK,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC;QAC/B,MAAM,CAAC,EAAE;YAAE,GAAG,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC1B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAChC,CAAC;IAEN;;OAEG;IACH,GAAG,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAExB;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IAE9B;;OAEG;IACH,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAEhC;;OAEG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;OAEG;IACH,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IAEF;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAElC;;;OAGG;IACH,WAAW,CAAC,EAAE;QACZ;;;;;;WAMG;QACH,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB;;;;;;;WAOG;QACH,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,UAAU,CA2TlF;AAED,OAAO,EACL,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,8BAA8B,CAAC"}
1
+ {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAU,UAAU,EAAE,MAAM,MAAM,CAAC;AAI/C,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE5B;;OAEG;IACH,OAAO,CAAC,EACJ,OAAO,GACP;QACE;;;WAGG;QACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QAEtB;;;WAGG;QACH,MAAM,CAAC,EAAE,MAAM,CAAC;QAEhB;;WAEG;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEN;;OAEG;IACH,IAAI,CAAC,EACD,OAAO,GACP;QACE;;;WAGG;QACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAEjB;;WAEG;QACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;KAC9B,CAAC;IAEN;;;OAGG;IACH,GAAG,CAAC,EACA,OAAO,GACP;QACE,MAAM,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC3B,GAAG,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACtD,KAAK,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC;QAC/B,MAAM,CAAC,EAAE;YAAE,GAAG,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC1B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAChC,CAAC;IAEN;;OAEG;IACH,GAAG,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAExB;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IAE9B;;OAEG;IACH,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAEhC;;OAEG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;OAEG;IACH,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IAEF;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAElC;;;OAGG;IACH,WAAW,CAAC,EAAE;QACZ;;;;;;WAMG;QACH,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB;;;;;;;WAOG;QACH,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,UAAU,CAiUlF;AAED,OAAO,EACL,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,8BAA8B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAqDvC,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAE/B;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC;IAE7C;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;OAEG;IACH,QAAQ,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IAEF;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,UAAU,CAiNtF"}
1
+ {"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAqDvC,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAE/B;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC;IAE7C;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;OAEG;IACH,QAAQ,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IAEF;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,UAAU,CAoNtF"}
@@ -1,67 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
-
4
- //#region src/workspacePublicPackages.ts
5
- /**
6
- * Every direct child of `public/` with a `package.json` `name` is treated as a
7
- * publishable workspace package (same rule as Metro in `apps/storybook-expo`).
8
- *
9
- * Tamagui’s internal esbuild (bundling `tamagui.config` + `components`) reads
10
- * `compilerOptions.paths` from the repo `tsconfig.base.json` chain, including
11
- * `tamagui-workspace-paths.generated.json` (see `scripts/generate-tamagui-workspace-paths.mjs`).
12
- */
13
- function discoverPublicPackageRoots(workspaceRoot) {
14
- const byName = /* @__PURE__ */ new Map();
15
- const publicDir = path.join(workspaceRoot, "public");
16
- if (!fs.existsSync(publicDir)) return byName;
17
- for (const ent of fs.readdirSync(publicDir, { withFileTypes: true })) {
18
- if (!ent.isDirectory()) continue;
19
- const pkgRoot = path.join(publicDir, ent.name);
20
- const pkgJsonPath = path.join(pkgRoot, "package.json");
21
- if (!fs.existsSync(pkgJsonPath)) continue;
22
- try {
23
- const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
24
- if (typeof pkg.name === "string" && pkg.name.length > 0) byName.set(pkg.name, pkgRoot);
25
- } catch {}
26
- }
27
- return byName;
28
- }
29
- /**
30
- * Main entry file for Vite/Storybook-style “compile from source” aliases.
31
- *
32
- * These aliases are only consumed by WEB Vite configs (Storybook, SPA), so
33
- * `.web.*` entries take priority over the plain (native) ones — otherwise a
34
- * package like @package/fonts resolves to its native index.ts and its
35
- * web-only side effects (fontsource @font-face CSS imports) never load.
36
- */
37
- function resolvePackageMainSource(pkgDir) {
38
- return [
39
- path.join(pkgDir, "src", "index.storybook.ts"),
40
- path.join(pkgDir, "src", "index.storybook.tsx"),
41
- path.join(pkgDir, "src", "index.web.ts"),
42
- path.join(pkgDir, "src", "index.web.tsx"),
43
- path.join(pkgDir, "src", "index.ts"),
44
- path.join(pkgDir, "src", "index.tsx"),
45
- path.join(pkgDir, "index.storybook.ts"),
46
- path.join(pkgDir, "index.storybook.tsx"),
47
- path.join(pkgDir, "index.web.ts"),
48
- path.join(pkgDir, "index.web.tsx"),
49
- path.join(pkgDir, "index.ts"),
50
- path.join(pkgDir, "index.tsx")
51
- ].find((c) => fs.existsSync(c));
52
- }
53
- /**
54
- * Vite `resolve.alias` entries: each public package name → its TypeScript main.
55
- * For SPA / ad-hoc Vite configs that should not list packages by hand.
56
- */
57
- function publicPackageViteSourceAliases(workspaceRoot) {
58
- const aliases = {};
59
- for (const [name, dir] of discoverPublicPackageRoots(workspaceRoot)) {
60
- const main = resolvePackageMainSource(dir);
61
- if (main) aliases[name] = main;
62
- }
63
- return aliases;
64
- }
65
-
66
- //#endregion
67
- export { publicPackageViteSourceAliases as n, resolvePackageMainSource as r, discoverPublicPackageRoots as t };