@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.
@@ -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
@@ -0,0 +1,3 @@
1
+ export { createStorybookViteConfig, type CreateStorybookViteConfigOptions } from "./storybook";
2
+ export { createViteConfig, type CreateViteConfigOptions } from "./vite";
3
+ export { createVitestConfig, type CreateVitestConfigOptions } from "./vitest";