@multiplatform.one/config 6.0.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/LICENSE +201 -0
- package/README.md +57 -0
- package/lib/index.js +5 -0
- package/lib/playwright.js +107 -0
- package/lib/storybook.js +204 -0
- package/lib/vite-BfDEN9iw.js +369 -0
- package/lib/vite.js +4 -0
- package/lib/vitest.js +150 -0
- package/lib/workspacePublicPackages-BPzu1MKc.js +56 -0
- package/package.json +110 -0
- package/src/index.ts +3 -0
- package/src/playwright.ts +239 -0
- package/src/reactNative.config.cjs +5 -0
- package/src/storybook.ts +318 -0
- package/src/vite.ts +623 -0
- package/src/vitest.ts +255 -0
- package/src/workspacePublicPackages.spec.ts +72 -0
- package/src/workspacePublicPackages.ts +59 -0
- package/tsconfig/app.json +22 -0
- package/tsconfig/base.json +39 -0
- package/tsconfig/build.json +24 -0
- package/tsconfig/tamagui_fix.d.ts +29 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { t as discoverPublicPackageRoots } from "./workspacePublicPackages-BPzu1MKc.js";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { tamaguiPlugin } from "@tamagui/vite-plugin";
|
|
7
|
+
import dotenv from "dotenv";
|
|
8
|
+
import createExternal from "vite-plugin-external";
|
|
9
|
+
import i18nextLoader from "vite-plugin-i18next-loader";
|
|
10
|
+
|
|
11
|
+
//#region \0rolldown/runtime.js
|
|
12
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region ../utils/src/dev.ts
|
|
16
|
+
const logger = console;
|
|
17
|
+
function lookupTamaguiModules(packageDirs, { log = true } = {}) {
|
|
18
|
+
const projectRoot = lookupProjectRoot();
|
|
19
|
+
const tamaguiModules = [...new Set(["tamagui", ...[...new Set([
|
|
20
|
+
projectRoot,
|
|
21
|
+
path.resolve(projectRoot, "app"),
|
|
22
|
+
path.resolve(projectRoot, "features"),
|
|
23
|
+
...packageDirs || []
|
|
24
|
+
])].map((packageDir) => {
|
|
25
|
+
const pkgPath = path.join(packageDir, "package.json");
|
|
26
|
+
if (!fs.existsSync(pkgPath)) return [];
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).tamaguiModules || [];
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (log) logger.error(err);
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
})])].flat();
|
|
34
|
+
if (log) logger.debug("tamaguiModules:", tamaguiModules.join(", "));
|
|
35
|
+
return tamaguiModules;
|
|
36
|
+
}
|
|
37
|
+
function resolveConfig(keys = []) {
|
|
38
|
+
return keys.reduce((acc, key) => {
|
|
39
|
+
if (process.env[key]) acc[key] = process.env[key];
|
|
40
|
+
return acc;
|
|
41
|
+
}, {});
|
|
42
|
+
}
|
|
43
|
+
let _projectRoot;
|
|
44
|
+
function lookupProjectRoot() {
|
|
45
|
+
if (_projectRoot) return _projectRoot;
|
|
46
|
+
try {
|
|
47
|
+
const { stdout } = spawnSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" });
|
|
48
|
+
_projectRoot = stdout.trim();
|
|
49
|
+
return _projectRoot;
|
|
50
|
+
} catch {
|
|
51
|
+
_projectRoot = process.cwd();
|
|
52
|
+
return _projectRoot;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/vite.ts
|
|
58
|
+
/**
|
|
59
|
+
* Creates a shared Vite configuration for multiplatform.one apps.
|
|
60
|
+
*
|
|
61
|
+
* This abstracts away the boilerplate that's typically duplicated across
|
|
62
|
+
* app vite.config.ts files, including Tamagui plugin setup, i18n,
|
|
63
|
+
* env variable loading, SSR configuration, and One framework integration.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* // apps/one/vite.config.ts
|
|
68
|
+
* import { createViteConfig } from "@multiplatform.one/config/vite";
|
|
69
|
+
* import { public as publicConfigKeys } from "../../features/config.json";
|
|
70
|
+
*
|
|
71
|
+
* export default createViteConfig({
|
|
72
|
+
* publicConfigKeys,
|
|
73
|
+
* one: { web: { deploy: "node", defaultRenderMode: "ssr" } },
|
|
74
|
+
* tamagui: { config: "./config/tamagui.config.ts" },
|
|
75
|
+
* i18n: { paths: ["../../features/i18n"] },
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
function createViteConfig(options = {}) {
|
|
80
|
+
const { projectRoot: _projectRoot, publicConfigKeys = [], tamagui = true, i18n = false, one = false, ssr, server, plugins: extraPlugins = [], externalReactRouter, ssrDeps, ssrEntries } = options;
|
|
81
|
+
const projectRoot = _projectRoot || findProjectRoot();
|
|
82
|
+
dotenv.config({ path: path.resolve(projectRoot, ".env") });
|
|
83
|
+
if (publicConfigKeys.length > 0) process.env.VITE_MP_CONFIG = JSON.stringify(resolveConfig(publicConfigKeys));
|
|
84
|
+
const vitePlugins = [];
|
|
85
|
+
vitePlugins.push(preferBuiltWorkspaceEntryPlugin());
|
|
86
|
+
if (one) {
|
|
87
|
+
vitePlugins.push(ssrReactNativeAliasPlugin());
|
|
88
|
+
vitePlugins.push(clientBrokenEsmPlugin());
|
|
89
|
+
registerNativeEngineIoBrowserTransports();
|
|
90
|
+
registerNativeWorkspaceEntries();
|
|
91
|
+
}
|
|
92
|
+
if (tamagui) {
|
|
93
|
+
const tamaguiOpts = typeof tamagui === "object" ? tamagui : {};
|
|
94
|
+
vitePlugins.push(tamaguiPlugin({
|
|
95
|
+
components: tamaguiOpts.components || lookupTamaguiModules([process.cwd()]),
|
|
96
|
+
config: tamaguiOpts.config || "./config/tamagui.config.ts",
|
|
97
|
+
outputCSS: tamaguiOpts.outputCSS || "./public/tamagui.css",
|
|
98
|
+
disableWatchTamaguiConfig: true
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
if (i18n) {
|
|
102
|
+
const i18nOpts = typeof i18n === "object" ? i18n : {};
|
|
103
|
+
vitePlugins.push(i18nextLoader({
|
|
104
|
+
paths: i18nOpts.paths || ["../../features/i18n"],
|
|
105
|
+
namespaceResolution: i18nOpts.namespaceResolution || "basename"
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
if (externalReactRouter !== false && one) vitePlugins.push(createExternal({ externals: { "react-router-dom": {} } }));
|
|
109
|
+
vitePlugins.push(...extraPlugins);
|
|
110
|
+
const defaultSsr = one ? {
|
|
111
|
+
noExternal: true,
|
|
112
|
+
optimizeDeps: {
|
|
113
|
+
entries: ssrEntries ?? ["routes/**/*.{ts,tsx}"],
|
|
114
|
+
include: [
|
|
115
|
+
"@tamagui/core",
|
|
116
|
+
"@tamagui/web",
|
|
117
|
+
"tamagui",
|
|
118
|
+
"@tamagui/toast",
|
|
119
|
+
"@tamagui/linear-gradient",
|
|
120
|
+
"react-i18next",
|
|
121
|
+
"i18next",
|
|
122
|
+
"@tanstack/react-store",
|
|
123
|
+
"@tanstack/store",
|
|
124
|
+
"void-elements",
|
|
125
|
+
"use-sync-external-store",
|
|
126
|
+
"use-sync-external-store/shim",
|
|
127
|
+
...ssrDeps?.include || []
|
|
128
|
+
],
|
|
129
|
+
exclude: [
|
|
130
|
+
"@react-native/assets-registry",
|
|
131
|
+
"@react-native-community/datetimepicker",
|
|
132
|
+
"socket.io-client",
|
|
133
|
+
"engine.io-client",
|
|
134
|
+
"xmlhttprequest-ssl",
|
|
135
|
+
"@tanstack/form-devtools",
|
|
136
|
+
"@tanstack/react-form-devtools",
|
|
137
|
+
...ssrDeps?.exclude || []
|
|
138
|
+
],
|
|
139
|
+
rolldownOptions: {
|
|
140
|
+
external: [
|
|
141
|
+
"@tanstack/form-devtools",
|
|
142
|
+
"@tanstack/react-form-devtools",
|
|
143
|
+
...ssrDeps?.external || []
|
|
144
|
+
],
|
|
145
|
+
shimMissingExports: true
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
external: [
|
|
149
|
+
"loglevel",
|
|
150
|
+
"@react-native/assets-registry",
|
|
151
|
+
"@react-native-community/datetimepicker",
|
|
152
|
+
"socket.io-client",
|
|
153
|
+
"engine.io-client",
|
|
154
|
+
"xmlhttprequest-ssl",
|
|
155
|
+
"@tanstack/form-devtools",
|
|
156
|
+
"@tanstack/react-form-devtools",
|
|
157
|
+
...ssrDeps?.external || [],
|
|
158
|
+
"@opentelemetry/semantic-conventions",
|
|
159
|
+
"@opentelemetry/api",
|
|
160
|
+
"@opentelemetry/sdk-trace-base",
|
|
161
|
+
"@opentelemetry/sdk-trace-node"
|
|
162
|
+
]
|
|
163
|
+
} : void 0;
|
|
164
|
+
const onePort = Number(process.env.ONE_PORT) || 3e3;
|
|
165
|
+
const defaultServer = {
|
|
166
|
+
allowedHosts: [
|
|
167
|
+
"localhost",
|
|
168
|
+
"127.0.0.1",
|
|
169
|
+
"app.localhost",
|
|
170
|
+
"app.test"
|
|
171
|
+
],
|
|
172
|
+
strictPort: true,
|
|
173
|
+
watch: { ignored: ["**/src-tauri/**"] },
|
|
174
|
+
...one ? { hmr: {
|
|
175
|
+
path: "/__vxrnhmr",
|
|
176
|
+
clientPort: onePort
|
|
177
|
+
} } : {},
|
|
178
|
+
...server
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
css: { modules: { localsConvention: "camelCase" } },
|
|
182
|
+
build: { chunkSizeWarningLimit: 600 },
|
|
183
|
+
define: { "process.env.VITE_FRAPPE_ENABLED": JSON.stringify(process.env.VITE_FRAPPE_ENABLED === "true" ? "true" : "false") },
|
|
184
|
+
ssr: ssr || defaultSsr,
|
|
185
|
+
resolve: {
|
|
186
|
+
alias: {
|
|
187
|
+
"react.mjs": "react",
|
|
188
|
+
"react-compiler-runtime": "react/compiler-runtime"
|
|
189
|
+
},
|
|
190
|
+
dedupe: ["react-is"]
|
|
191
|
+
},
|
|
192
|
+
server: defaultServer,
|
|
193
|
+
envPrefix: ["VITE_", "TAURI_ENV_"],
|
|
194
|
+
plugins: vitePlugins
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Aliases react-native to react-native-web in the SSR environment.
|
|
199
|
+
*
|
|
200
|
+
* @react-navigation/native statically imports react-native, which causes
|
|
201
|
+
* Rolldown's dep optimizer to load react-native/index.js — a file that contains
|
|
202
|
+
* Flow-only syntax (`import typeof`, `as Type`) that Rolldown cannot parse.
|
|
203
|
+
* Aliasing to react-native-web redirects those imports to the web-safe shim,
|
|
204
|
+
* so the real react-native package is never touched during SSR bundling.
|
|
205
|
+
*/
|
|
206
|
+
function ssrReactNativeAliasPlugin() {
|
|
207
|
+
return {
|
|
208
|
+
name: "multiplatform-ssr-react-native-alias",
|
|
209
|
+
enforce: "post",
|
|
210
|
+
configResolved(config) {
|
|
211
|
+
const ssrEnv = config.environments?.ssr;
|
|
212
|
+
if (!ssrEnv?.resolve) return;
|
|
213
|
+
const alias = ssrEnv.resolve.alias ?? [];
|
|
214
|
+
if (!alias.some((a) => a.find === "react-native")) alias.push({
|
|
215
|
+
find: "react-native",
|
|
216
|
+
replacement: "react-native-web"
|
|
217
|
+
});
|
|
218
|
+
ssrEnv.resolve.alias = alias;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Adds packages that have broken or missing ESM default exports to the client
|
|
224
|
+
* environments pre-bundle. Rolldown resolves them via their "import" condition
|
|
225
|
+
* and generates a proper ESM chunk with synthetic interop default export.
|
|
226
|
+
*
|
|
227
|
+
* use-latest-callback: type=commonjs, exports.import → esm.mjs, but vxrn's
|
|
228
|
+
* client conditions (["vxrn-web"]) skip "import" and land on "default" →
|
|
229
|
+
* lib/src/index.js (CJS, named-only). Pre-bundling forces the import condition.
|
|
230
|
+
*/
|
|
231
|
+
function clientBrokenEsmPlugin() {
|
|
232
|
+
return {
|
|
233
|
+
name: "multiplatform-client-broken-esm",
|
|
234
|
+
configResolved(config) {
|
|
235
|
+
const clientEnv = config.environments?.client;
|
|
236
|
+
if (!clientEnv?.optimizeDeps) return;
|
|
237
|
+
const extras = [
|
|
238
|
+
"use-latest-callback",
|
|
239
|
+
"escape-string-regexp",
|
|
240
|
+
"use-sync-external-store/with-selector",
|
|
241
|
+
"react-is",
|
|
242
|
+
"fast-deep-equal",
|
|
243
|
+
"color",
|
|
244
|
+
"query-string"
|
|
245
|
+
];
|
|
246
|
+
const include = clientEnv.optimizeDeps.include ?? [];
|
|
247
|
+
for (const dep of extras) if (!include.includes(dep)) include.push(dep);
|
|
248
|
+
clientEnv.optimizeDeps.include = include;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Adds engine.io-client's `browser`-field transport redirect to vxrn's NATIVE
|
|
254
|
+
* rolldown pipeline via its `globalThis.__vxrnAddNativePlugins` hook.
|
|
255
|
+
*
|
|
256
|
+
* socket.io-client -> engine.io-client/index.js statically imports its Node
|
|
257
|
+
* transports (`./globals.node.js`, `./transports/{websocket,polling-xhr}.node.js`),
|
|
258
|
+
* which pull `ws` + `xmlhttprequest-ssl` + Node globals. Those `.node` files also
|
|
259
|
+
* contain raw ESM `import` syntax that Hermes can't even COMPILE, so the native
|
|
260
|
+
* bundle dies at boot. engine.io's own `package.json` `browser` field already
|
|
261
|
+
* redirects each `*.node.js` to a browser sibling that uses the global
|
|
262
|
+
* `WebSocket` / `XMLHttpRequest` React Native provides — but vxrn's native
|
|
263
|
+
* resolver omits the `browser` aliasField (and its rolldown binding doesn't even
|
|
264
|
+
* support `aliasFields`). vxrn's native dev engine ignores the Vite config's
|
|
265
|
+
* `resolve.alias`/`resolveId` too — BUT it DOES read `globalThis.__vxrnAddNativePlugins`
|
|
266
|
+
* inside `getNativePlugins`, so a `resolveId` registered there runs on native.
|
|
267
|
+
* This re-asserts the browser redirect with no node_modules patch. Web is
|
|
268
|
+
* unaffected (engine.io is SSR-externalized and the web client already maps `browser`).
|
|
269
|
+
*/
|
|
270
|
+
function registerNativeEngineIoBrowserTransports() {
|
|
271
|
+
const g = globalThis;
|
|
272
|
+
const name = "vxrn-engineio-browser-transports";
|
|
273
|
+
g.__vxrnAddNativePlugins = g.__vxrnAddNativePlugins ?? [];
|
|
274
|
+
if (g.__vxrnAddNativePlugins.some((p) => p?.name === name)) return;
|
|
275
|
+
g.__vxrnAddNativePlugins.push({
|
|
276
|
+
name,
|
|
277
|
+
async resolveId(id, importer) {
|
|
278
|
+
if (importer && /engine\.io-client[\\/]build[\\/]/.test(importer) && id.endsWith(".node.js")) return this.resolve(`${id.slice(0, -8)}.js`, importer, { skipSelf: true });
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Routes workspace (`@multiplatform.one/*`) packages to their `dist/esm/index.native.js`
|
|
285
|
+
* build on NATIVE, via vxrn's `globalThis.__vxrnAddNativePlugins` hook.
|
|
286
|
+
*
|
|
287
|
+
* Why this is needed: `preferBuiltWorkspaceEntryPlugin` (a Vite plugin, web/SSR only)
|
|
288
|
+
* hard-resolves every workspace bare specifier to `<root>/dist/esm/index.mjs` — the WEB
|
|
289
|
+
* build — bypassing the package.json `exports` `react-native` condition. That web
|
|
290
|
+
* resolution propagates into vxrn's native graph (the native engine receives no Vite
|
|
291
|
+
* userPlugins, so it cannot be made native-aware there), pinning the entire workspace
|
|
292
|
+
* chain to web on native and dragging web-only deps (@tiptap, prosemirror, html5-qrcode,
|
|
293
|
+
* @mixmark-io/domino, …) into Hermes. Each affected package ships a `dist/esm/index.native.js`
|
|
294
|
+
* (built by tamagui-build, intra-package imports already rewritten to their `.native`
|
|
295
|
+
* siblings). This resolveId matches both forms — the bare specifier and the already-resolved
|
|
296
|
+
* `…/dist/esm/index.mjs` path — and redirects to the native sibling when it exists, so the
|
|
297
|
+
* react-native build wins on native. Web/SSR are unaffected (this plugin runs ONLY on native).
|
|
298
|
+
*/
|
|
299
|
+
function registerNativeWorkspaceEntries() {
|
|
300
|
+
const g = globalThis;
|
|
301
|
+
const name = "vxrn-workspace-native-entries";
|
|
302
|
+
g.__vxrnAddNativePlugins = g.__vxrnAddNativePlugins ?? [];
|
|
303
|
+
if (g.__vxrnAddNativePlugins.some((p) => p?.name === name)) return;
|
|
304
|
+
let roots;
|
|
305
|
+
try {
|
|
306
|
+
roots = discoverPublicPackageRoots(findProjectRoot());
|
|
307
|
+
} catch {
|
|
308
|
+
roots = /* @__PURE__ */ new Map();
|
|
309
|
+
}
|
|
310
|
+
g.__vxrnAddNativePlugins.push({
|
|
311
|
+
name,
|
|
312
|
+
resolveId(id) {
|
|
313
|
+
const root = roots.get(id);
|
|
314
|
+
if (root) {
|
|
315
|
+
const nativeEntry = path.join(root, "dist/esm/index.native.js");
|
|
316
|
+
return fs.existsSync(nativeEntry) ? nativeEntry : null;
|
|
317
|
+
}
|
|
318
|
+
if (id.endsWith("/dist/esm/index.mjs")) {
|
|
319
|
+
const nativeEntry = `${id.slice(0, -4)}.native.js`;
|
|
320
|
+
if (fs.existsSync(nativeEntry)) return nativeEntry;
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function findProjectRoot() {
|
|
327
|
+
try {
|
|
328
|
+
const { execSync } = __require("node:child_process");
|
|
329
|
+
return execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).trim();
|
|
330
|
+
} catch {
|
|
331
|
+
return process.cwd();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* package.json `exports` cannot branch on whether `dist/` exists. For workspace
|
|
336
|
+
* packages, prefer `dist/esm/index.{mjs,js}` when built; otherwise fall back to
|
|
337
|
+
* `src/index.ts` so local dev works without a build while published tarballs
|
|
338
|
+
* still resolve through `exports` to `dist`.
|
|
339
|
+
*/
|
|
340
|
+
function preferBuiltWorkspaceEntryPlugin() {
|
|
341
|
+
const roots = discoverPublicPackageRoots(findProjectRoot());
|
|
342
|
+
return {
|
|
343
|
+
name: "multiplatform-prefer-built-workspace-main",
|
|
344
|
+
enforce: "pre",
|
|
345
|
+
resolveId(id) {
|
|
346
|
+
const root = roots.get(id);
|
|
347
|
+
if (!root) return null;
|
|
348
|
+
const distCandidates = [path.join(root, "dist/esm/index.mjs"), path.join(root, "dist/esm/index.js")];
|
|
349
|
+
for (const file of distCandidates) if (fs.existsSync(file)) return file;
|
|
350
|
+
const srcTs = path.join(root, "src/index.ts");
|
|
351
|
+
if (fs.existsSync(srcTs)) return srcTs;
|
|
352
|
+
const srcTsx = path.join(root, "src/index.tsx");
|
|
353
|
+
if (fs.existsSync(srcTsx)) return srcTsx;
|
|
354
|
+
const pkgJsonPath = path.join(root, "package.json");
|
|
355
|
+
if (fs.existsSync(pkgJsonPath)) try {
|
|
356
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
357
|
+
const entry = pkg.exports?.["."]?.import || pkg.exports?.["."]?.default || pkg.main || pkg.module;
|
|
358
|
+
if (entry) {
|
|
359
|
+
const resolved = path.resolve(root, entry);
|
|
360
|
+
if (fs.existsSync(resolved)) return resolved;
|
|
361
|
+
}
|
|
362
|
+
} catch {}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
//#endregion
|
|
369
|
+
export { createViteConfig as t };
|
package/lib/vite.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { t as createViteConfig } from "./vite-BfDEN9iw.js";
|
|
2
|
+
import { n as publicPackageViteSourceAliases, t as discoverPublicPackageRoots } from "./workspacePublicPackages-BPzu1MKc.js";
|
|
3
|
+
|
|
4
|
+
export { createViteConfig, discoverPublicPackageRoots, publicPackageViteSourceAliases };
|
package/lib/vitest.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { tamaguiPlugin } from "@tamagui/vite-plugin";
|
|
3
|
+
import react from "@vitejs/plugin-react";
|
|
4
|
+
|
|
5
|
+
//#region src/vitest.ts
|
|
6
|
+
/**
|
|
7
|
+
* Creates a shared Vitest configuration for multiplatform.one packages.
|
|
8
|
+
*
|
|
9
|
+
* This eliminates the ~100 lines of duplicated vitest config across 13+ packages,
|
|
10
|
+
* handling Tamagui transforms, react-native-web replacement, React deduplication,
|
|
11
|
+
* and consistent coverage/environment settings.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* // packages/components/vitest.config.mjs
|
|
16
|
+
* import { createVitestConfig } from "@multiplatform.one/config/vitest";
|
|
17
|
+
*
|
|
18
|
+
* export default createVitestConfig({
|
|
19
|
+
* setupFiles: ["./tests/setup.tsx"],
|
|
20
|
+
* tamaguiConfig: "../../features/tamagui.config.ts",
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
function createVitestConfig(options = {}) {
|
|
25
|
+
const { tamaguiConfig = "./tests/tamagui.config.ts", environment = "jsdom", setupFiles = [], testTimeout = 3e4, inlineDeps = [], externalDeps = [], aliases = {}, coverage, replaceReactNative = true, dedupeReact = true, rootNodeModules: _rootNodeModules, conditions = [
|
|
26
|
+
"source",
|
|
27
|
+
"test",
|
|
28
|
+
"browser"
|
|
29
|
+
], include, exclude, passWithNoTests } = options;
|
|
30
|
+
const rootNodeModules = _rootNodeModules || path.resolve(process.cwd(), "../../node_modules");
|
|
31
|
+
const plugins = [react({ jsxRuntime: "automatic" })];
|
|
32
|
+
if (replaceReactNative) plugins.push({
|
|
33
|
+
name: "replace-react-native",
|
|
34
|
+
transform(code, id) {
|
|
35
|
+
if (id.includes("node_modules")) return;
|
|
36
|
+
return {
|
|
37
|
+
code: code.replace(/from ['"]react-native['"]/g, "from \"react-native-web\""),
|
|
38
|
+
map: null
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
if (tamaguiConfig !== false) plugins.push(tamaguiPlugin({
|
|
43
|
+
components: ["tamagui"],
|
|
44
|
+
config: tamaguiConfig
|
|
45
|
+
}));
|
|
46
|
+
const resolveAliases = {};
|
|
47
|
+
if (replaceReactNative) {
|
|
48
|
+
resolveAliases["react-native"] = "react-native-web";
|
|
49
|
+
resolveAliases["react-native-svg"] = "@tamagui/react-native-svg";
|
|
50
|
+
}
|
|
51
|
+
if (dedupeReact) {
|
|
52
|
+
resolveAliases.react = path.resolve(rootNodeModules, "react");
|
|
53
|
+
resolveAliases["react-dom"] = path.resolve(rootNodeModules, "react-dom");
|
|
54
|
+
resolveAliases["react-dom/client"] = path.resolve(rootNodeModules, "react-dom/client");
|
|
55
|
+
resolveAliases["react-dom/server"] = path.resolve(rootNodeModules, "react-dom/server");
|
|
56
|
+
resolveAliases["react/jsx-runtime"] = path.resolve(rootNodeModules, "react/jsx-runtime");
|
|
57
|
+
resolveAliases["react/jsx-dev-runtime"] = path.resolve(rootNodeModules, "react/jsx-dev-runtime");
|
|
58
|
+
resolveAliases.scheduler = path.resolve(rootNodeModules, "scheduler");
|
|
59
|
+
}
|
|
60
|
+
const testConfig = {
|
|
61
|
+
environment,
|
|
62
|
+
globals: true,
|
|
63
|
+
setupFiles,
|
|
64
|
+
testTimeout,
|
|
65
|
+
coverage: {
|
|
66
|
+
provider: "v8",
|
|
67
|
+
reporter: [
|
|
68
|
+
"text",
|
|
69
|
+
"lcov",
|
|
70
|
+
"html"
|
|
71
|
+
],
|
|
72
|
+
reportsDirectory: "./coverage",
|
|
73
|
+
all: true,
|
|
74
|
+
clean: true,
|
|
75
|
+
exclude: [
|
|
76
|
+
"node_modules/",
|
|
77
|
+
"tests/",
|
|
78
|
+
"*.config.*",
|
|
79
|
+
"**/*.stories.*",
|
|
80
|
+
"**/*.spec.*",
|
|
81
|
+
"**/*.test.*",
|
|
82
|
+
...coverage?.exclude || []
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
server: { deps: {
|
|
86
|
+
inline: [
|
|
87
|
+
"react-native-web",
|
|
88
|
+
"one",
|
|
89
|
+
/@tamagui\//,
|
|
90
|
+
"tamagui",
|
|
91
|
+
...inlineDeps
|
|
92
|
+
],
|
|
93
|
+
...externalDeps.length > 0 ? { external: externalDeps } : {},
|
|
94
|
+
interopDefault: true
|
|
95
|
+
} }
|
|
96
|
+
};
|
|
97
|
+
if (include) testConfig.include = include;
|
|
98
|
+
if (exclude) testConfig.exclude = exclude;
|
|
99
|
+
if (passWithNoTests) testConfig.passWithNoTests = true;
|
|
100
|
+
return {
|
|
101
|
+
plugins,
|
|
102
|
+
test: testConfig,
|
|
103
|
+
resolve: {
|
|
104
|
+
extensions: [
|
|
105
|
+
".test.ts",
|
|
106
|
+
".test.tsx",
|
|
107
|
+
".web.ts",
|
|
108
|
+
".web.tsx",
|
|
109
|
+
".web.js",
|
|
110
|
+
".web.jsx",
|
|
111
|
+
".ts",
|
|
112
|
+
".tsx",
|
|
113
|
+
".js",
|
|
114
|
+
".jsx"
|
|
115
|
+
],
|
|
116
|
+
mainFields: [
|
|
117
|
+
"browser",
|
|
118
|
+
"module",
|
|
119
|
+
"main"
|
|
120
|
+
],
|
|
121
|
+
alias: {
|
|
122
|
+
...resolveAliases,
|
|
123
|
+
...aliases
|
|
124
|
+
},
|
|
125
|
+
conditions,
|
|
126
|
+
dedupe: dedupeReact ? [
|
|
127
|
+
"react",
|
|
128
|
+
"react-dom",
|
|
129
|
+
"scheduler"
|
|
130
|
+
] : []
|
|
131
|
+
},
|
|
132
|
+
define: {
|
|
133
|
+
__DEV__: true,
|
|
134
|
+
global: "globalThis",
|
|
135
|
+
process: JSON.stringify({
|
|
136
|
+
env: {
|
|
137
|
+
NODE_ENV: "test",
|
|
138
|
+
NODE_DEBUG: false
|
|
139
|
+
},
|
|
140
|
+
platform: process.platform,
|
|
141
|
+
version: process.version,
|
|
142
|
+
type: "renderer"
|
|
143
|
+
}),
|
|
144
|
+
"Buffer.isBuffer": "((obj) => obj?.constructor?.name === 'Buffer')"
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
export { createVitestConfig };
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
/** Main entry file for Vite/Storybook-style “compile from source” aliases. */
|
|
30
|
+
function resolvePackageMainSource(pkgDir) {
|
|
31
|
+
return [
|
|
32
|
+
path.join(pkgDir, "src", "index.storybook.ts"),
|
|
33
|
+
path.join(pkgDir, "src", "index.storybook.tsx"),
|
|
34
|
+
path.join(pkgDir, "src", "index.ts"),
|
|
35
|
+
path.join(pkgDir, "src", "index.tsx"),
|
|
36
|
+
path.join(pkgDir, "index.storybook.ts"),
|
|
37
|
+
path.join(pkgDir, "index.storybook.tsx"),
|
|
38
|
+
path.join(pkgDir, "index.ts"),
|
|
39
|
+
path.join(pkgDir, "index.tsx")
|
|
40
|
+
].find((c) => fs.existsSync(c));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Vite `resolve.alias` entries: each public package name → its TypeScript main.
|
|
44
|
+
* For SPA / ad-hoc Vite configs that should not list packages by hand.
|
|
45
|
+
*/
|
|
46
|
+
function publicPackageViteSourceAliases(workspaceRoot) {
|
|
47
|
+
const aliases = {};
|
|
48
|
+
for (const [name, dir] of discoverPublicPackageRoots(workspaceRoot)) {
|
|
49
|
+
const main = resolvePackageMainSource(dir);
|
|
50
|
+
if (main) aliases[name] = main;
|
|
51
|
+
}
|
|
52
|
+
return aliases;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
export { publicPackageViteSourceAliases as n, resolvePackageMainSource as r, discoverPublicPackageRoots as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@multiplatform.one/config",
|
|
3
|
+
"version": "6.0.0",
|
|
4
|
+
"description": "Shared build and test configuration presets for multiplatform.one",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"config",
|
|
7
|
+
"multiplatform.one",
|
|
8
|
+
"playwright",
|
|
9
|
+
"storybook",
|
|
10
|
+
"tsconfig",
|
|
11
|
+
"vite",
|
|
12
|
+
"vitest"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://multiplatform.one",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://gitlab.com/bitspur/multiplatform.one/multiplatform.one/issues",
|
|
17
|
+
"email": "support@risserlabs.com"
|
|
18
|
+
},
|
|
19
|
+
"license": "Apache-2.0",
|
|
20
|
+
"author": "BitSpur <support@risserlabs.com> (https://risserlabs.com)",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://gitlab.com/bitspur/multiplatform.one/multiplatform.one"
|
|
24
|
+
},
|
|
25
|
+
"source": "src/index.ts",
|
|
26
|
+
"files": [
|
|
27
|
+
"src",
|
|
28
|
+
"lib",
|
|
29
|
+
"tsconfig",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"type": "module",
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"main": "lib/index.js",
|
|
35
|
+
"module": "lib/index.js",
|
|
36
|
+
"types": "src/index.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
"./package.json": "./package.json",
|
|
39
|
+
".": {
|
|
40
|
+
"source": "./src/index.ts",
|
|
41
|
+
"types": "./src/index.ts",
|
|
42
|
+
"import": "./lib/index.js",
|
|
43
|
+
"require": "./lib/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./storybook": {
|
|
46
|
+
"source": "./src/storybook.ts",
|
|
47
|
+
"types": "./src/storybook.ts",
|
|
48
|
+
"import": "./lib/storybook.js",
|
|
49
|
+
"require": "./lib/storybook.js"
|
|
50
|
+
},
|
|
51
|
+
"./vite": {
|
|
52
|
+
"source": "./src/vite.ts",
|
|
53
|
+
"types": "./src/vite.ts",
|
|
54
|
+
"import": "./lib/vite.js",
|
|
55
|
+
"require": "./lib/vite.js"
|
|
56
|
+
},
|
|
57
|
+
"./vitest": {
|
|
58
|
+
"source": "./src/vitest.ts",
|
|
59
|
+
"types": "./src/vitest.ts",
|
|
60
|
+
"import": "./lib/vitest.js",
|
|
61
|
+
"require": "./lib/vitest.js"
|
|
62
|
+
},
|
|
63
|
+
"./playwright": {
|
|
64
|
+
"source": "./src/playwright.ts",
|
|
65
|
+
"types": "./src/playwright.ts",
|
|
66
|
+
"import": "./lib/playwright.js",
|
|
67
|
+
"require": "./lib/playwright.js"
|
|
68
|
+
},
|
|
69
|
+
"./tsconfig/base.json": "./tsconfig/base.json",
|
|
70
|
+
"./tsconfig/build.json": "./tsconfig/build.json",
|
|
71
|
+
"./tsconfig/app.json": "./tsconfig/app.json",
|
|
72
|
+
"./react-native-config": "./src/react-native.config.cjs"
|
|
73
|
+
},
|
|
74
|
+
"publishConfig": {
|
|
75
|
+
"access": "public"
|
|
76
|
+
},
|
|
77
|
+
"dependencies": {
|
|
78
|
+
"@tamagui/vite-plugin": "2.0.0-rc.41",
|
|
79
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
80
|
+
"dotenv": "^17.4.2",
|
|
81
|
+
"vite": "^8.0.10",
|
|
82
|
+
"vite-plugin-external": "^6.2.2",
|
|
83
|
+
"vite-plugin-i18next-loader": "^3.1.3",
|
|
84
|
+
"vitest": "^4.1.5",
|
|
85
|
+
"@multiplatform.one/utils": "6.0.0"
|
|
86
|
+
},
|
|
87
|
+
"devDependencies": {
|
|
88
|
+
"tsdown": "^0.21.10",
|
|
89
|
+
"typescript": "~5.9.3"
|
|
90
|
+
},
|
|
91
|
+
"peerDependencies": {
|
|
92
|
+
"@playwright/test": ">=1.20.0",
|
|
93
|
+
"one": "^1.4.10"
|
|
94
|
+
},
|
|
95
|
+
"peerDependenciesMeta": {
|
|
96
|
+
"@playwright/test": {
|
|
97
|
+
"optional": true
|
|
98
|
+
},
|
|
99
|
+
"one": {
|
|
100
|
+
"optional": true
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"engines": {
|
|
104
|
+
"node": ">=18.0.0"
|
|
105
|
+
},
|
|
106
|
+
"scripts": {
|
|
107
|
+
"typecheck": "tsc --noEmit",
|
|
108
|
+
"build": "rm -rf lib 2>/dev/null && tsdown"
|
|
109
|
+
}
|
|
110
|
+
}
|
package/src/index.ts
ADDED