@teardown/metro-config 2.0.44
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/dist/bun.d.ts +24 -0
- package/dist/bun.d.ts.map +1 -0
- package/dist/bun.js +91 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +141 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/workspace.d.ts +65 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +227 -0
- package/package.json +55 -0
- package/src/bun.ts +95 -0
- package/src/index.ts +169 -0
- package/src/types.ts +50 -0
- package/src/workspace.ts +233 -0
- package/tsconfig.json +17 -0
package/dist/bun.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun-specific Metro configuration utilities.
|
|
3
|
+
*
|
|
4
|
+
* Handles blocking of .bun directories which can cause Metro resolution issues.
|
|
5
|
+
*/
|
|
6
|
+
import type { ResolveRequestFn } from "./types";
|
|
7
|
+
/**
|
|
8
|
+
* Block pattern for .bun directories.
|
|
9
|
+
* Metro's blockList prevents processing of matched paths.
|
|
10
|
+
*/
|
|
11
|
+
export declare const BUN_BLOCK_PATTERN: RegExp;
|
|
12
|
+
/**
|
|
13
|
+
* Create a custom resolver that filters out .bun paths during module resolution.
|
|
14
|
+
*
|
|
15
|
+
* Metro's blockList only prevents processing after resolution, not during.
|
|
16
|
+
* This resolver intercepts resolution and redirects away from .bun paths.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createBunAwareResolver(projectRoot: string, defaultResolveRequest: ResolveRequestFn): ResolveRequestFn;
|
|
19
|
+
/**
|
|
20
|
+
* Get blockList patterns for Metro config.
|
|
21
|
+
* Includes .bun directory blocking.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getBlockList(existingBlockList?: RegExp | RegExp[]): RegExp[];
|
|
24
|
+
//# sourceMappingURL=bun.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../src/bun.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGhD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAAwB,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,qBAAqB,EAAE,gBAAgB,GAAG,gBAAgB,CAwBrH;AAmCD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CAQ5E"}
|
package/dist/bun.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Bun-specific Metro configuration utilities.
|
|
4
|
+
*
|
|
5
|
+
* Handles blocking of .bun directories which can cause Metro resolution issues.
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.BUN_BLOCK_PATTERN = void 0;
|
|
12
|
+
exports.createBunAwareResolver = createBunAwareResolver;
|
|
13
|
+
exports.getBlockList = getBlockList;
|
|
14
|
+
const node_fs_1 = require("node:fs");
|
|
15
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
16
|
+
const workspace_1 = require("./workspace");
|
|
17
|
+
/**
|
|
18
|
+
* Block pattern for .bun directories.
|
|
19
|
+
* Metro's blockList prevents processing of matched paths.
|
|
20
|
+
*/
|
|
21
|
+
exports.BUN_BLOCK_PATTERN = /.*[/\\]\.bun[/\\].*/;
|
|
22
|
+
/**
|
|
23
|
+
* Create a custom resolver that filters out .bun paths during module resolution.
|
|
24
|
+
*
|
|
25
|
+
* Metro's blockList only prevents processing after resolution, not during.
|
|
26
|
+
* This resolver intercepts resolution and redirects away from .bun paths.
|
|
27
|
+
*/
|
|
28
|
+
function createBunAwareResolver(projectRoot, defaultResolveRequest) {
|
|
29
|
+
const modulesPaths = (0, workspace_1.getModulesPaths)(projectRoot);
|
|
30
|
+
return (context, moduleName, platform) => {
|
|
31
|
+
// Use Metro's default resolution
|
|
32
|
+
const result = defaultResolveRequest(context, moduleName, platform);
|
|
33
|
+
// If resolved path contains .bun, try to find alternative
|
|
34
|
+
if (result?.filePath?.includes("/.bun/")) {
|
|
35
|
+
for (const modulesPath of modulesPaths) {
|
|
36
|
+
if (modulesPath.includes("/.bun/"))
|
|
37
|
+
continue;
|
|
38
|
+
const packagePath = node_path_1.default.join(modulesPath, moduleName);
|
|
39
|
+
if ((0, node_fs_1.existsSync)(packagePath)) {
|
|
40
|
+
const mainFile = resolvePackageMain(packagePath);
|
|
41
|
+
if (mainFile) {
|
|
42
|
+
return { type: "sourceFile", filePath: mainFile };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the main entry point of a package.
|
|
52
|
+
*/
|
|
53
|
+
function resolvePackageMain(packagePath) {
|
|
54
|
+
const packageJsonPath = node_path_1.default.join(packagePath, "package.json");
|
|
55
|
+
if ((0, node_fs_1.existsSync)(packageJsonPath)) {
|
|
56
|
+
try {
|
|
57
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, "utf8"));
|
|
58
|
+
const main = pkg.main || pkg.module || "index.js";
|
|
59
|
+
const mainPath = node_path_1.default.join(packagePath, main);
|
|
60
|
+
if ((0, node_fs_1.existsSync)(mainPath)) {
|
|
61
|
+
return mainPath;
|
|
62
|
+
}
|
|
63
|
+
// Try with .js extension
|
|
64
|
+
const mainPathJs = mainPath + ".js";
|
|
65
|
+
if ((0, node_fs_1.existsSync)(mainPathJs)) {
|
|
66
|
+
return mainPathJs;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Ignore parse errors
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Fallback to index.js
|
|
74
|
+
const indexPath = node_path_1.default.join(packagePath, "index.js");
|
|
75
|
+
if ((0, node_fs_1.existsSync)(indexPath)) {
|
|
76
|
+
return indexPath;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get blockList patterns for Metro config.
|
|
82
|
+
* Includes .bun directory blocking.
|
|
83
|
+
*/
|
|
84
|
+
function getBlockList(existingBlockList) {
|
|
85
|
+
const blockListArray = Array.isArray(existingBlockList)
|
|
86
|
+
? existingBlockList
|
|
87
|
+
: existingBlockList
|
|
88
|
+
? [existingBlockList]
|
|
89
|
+
: [];
|
|
90
|
+
return [...blockListArray, exports.BUN_BLOCK_PATTERN];
|
|
91
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @teardown/metro-config
|
|
3
|
+
*
|
|
4
|
+
* Metro configuration wrapper that provides:
|
|
5
|
+
* - Rust-powered transforms via Facetpack (36x faster than Babel)
|
|
6
|
+
* - Automatic monorepo/workspace detection and configuration
|
|
7
|
+
* - Bun-specific handling (blocks .bun directories)
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```js
|
|
11
|
+
* // metro.config.js
|
|
12
|
+
* const { withTeardown } = require('@teardown/metro-config')
|
|
13
|
+
* const { getDefaultConfig } = require('@react-native/metro-config')
|
|
14
|
+
*
|
|
15
|
+
* module.exports = withTeardown(getDefaultConfig(__dirname))
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
import { getStoredOptions } from "@ecrindigital/facetpack";
|
|
21
|
+
import type { MetroConfig, TeardownMetroOptions } from "./types";
|
|
22
|
+
/**
|
|
23
|
+
* Wraps a Metro configuration with Teardown's enhancements.
|
|
24
|
+
*
|
|
25
|
+
* This function applies:
|
|
26
|
+
* - Facetpack for Rust-powered transforms (36x faster)
|
|
27
|
+
* - Automatic monorepo detection and watch folder configuration
|
|
28
|
+
* - Bun-specific handling (.bun directory blocking)
|
|
29
|
+
* - Proper nodeModulesPaths for monorepo resolution
|
|
30
|
+
*
|
|
31
|
+
* @param config - Base Metro configuration
|
|
32
|
+
* @param options - Optional configuration options
|
|
33
|
+
* @returns Enhanced Metro configuration
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```js
|
|
37
|
+
* // Basic usage - all monorepo handling is automatic
|
|
38
|
+
* const { withTeardown } = require('@teardown/metro-config')
|
|
39
|
+
* const { getDefaultConfig } = require('@react-native/metro-config')
|
|
40
|
+
*
|
|
41
|
+
* module.exports = withTeardown(getDefaultConfig(__dirname))
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```js
|
|
46
|
+
* // With options
|
|
47
|
+
* const { withTeardown } = require('@teardown/metro-config')
|
|
48
|
+
* const { getDefaultConfig } = require('@react-native/metro-config')
|
|
49
|
+
*
|
|
50
|
+
* module.exports = withTeardown(getDefaultConfig(__dirname), {
|
|
51
|
+
* verbose: true,
|
|
52
|
+
* projectRoot: __dirname, // explicitly set project root
|
|
53
|
+
* })
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function withTeardown(config: MetroConfig, options?: TeardownMetroOptions): MetroConfig;
|
|
57
|
+
export { getStoredOptions };
|
|
58
|
+
export { BUN_BLOCK_PATTERN, getBlockList } from "./bun";
|
|
59
|
+
export { getMetroServerRoot, getModulesPaths, getRelativeProjectRoot, getWatchFolders, getWorkspaceRoot, isInMonorepo, } from "./workspace";
|
|
60
|
+
export type { TeardownMetroOptions, MetroConfig };
|
|
61
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,gBAAgB,EAAiB,MAAM,yBAAyB,CAAC;AAE1E,OAAO,KAAK,EAAE,WAAW,EAAoB,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAUnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,oBAAyB,GAAG,WAAW,CAoFjG;AAGD,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAG5B,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAExD,OAAO,EACN,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,YAAY,GACZ,MAAM,aAAa,CAAC;AAGrB,YAAY,EAAE,oBAAoB,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @teardown/metro-config
|
|
4
|
+
*
|
|
5
|
+
* Metro configuration wrapper that provides:
|
|
6
|
+
* - Rust-powered transforms via Facetpack (36x faster than Babel)
|
|
7
|
+
* - Automatic monorepo/workspace detection and configuration
|
|
8
|
+
* - Bun-specific handling (blocks .bun directories)
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```js
|
|
12
|
+
* // metro.config.js
|
|
13
|
+
* const { withTeardown } = require('@teardown/metro-config')
|
|
14
|
+
* const { getDefaultConfig } = require('@react-native/metro-config')
|
|
15
|
+
*
|
|
16
|
+
* module.exports = withTeardown(getDefaultConfig(__dirname))
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @packageDocumentation
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.isInMonorepo = exports.getWorkspaceRoot = exports.getWatchFolders = exports.getRelativeProjectRoot = exports.getModulesPaths = exports.getMetroServerRoot = exports.getBlockList = exports.BUN_BLOCK_PATTERN = exports.getStoredOptions = void 0;
|
|
23
|
+
exports.withTeardown = withTeardown;
|
|
24
|
+
const facetpack_1 = require("@ecrindigital/facetpack");
|
|
25
|
+
Object.defineProperty(exports, "getStoredOptions", { enumerable: true, get: function () { return facetpack_1.getStoredOptions; } });
|
|
26
|
+
const bun_1 = require("./bun");
|
|
27
|
+
const workspace_1 = require("./workspace");
|
|
28
|
+
/**
|
|
29
|
+
* Wraps a Metro configuration with Teardown's enhancements.
|
|
30
|
+
*
|
|
31
|
+
* This function applies:
|
|
32
|
+
* - Facetpack for Rust-powered transforms (36x faster)
|
|
33
|
+
* - Automatic monorepo detection and watch folder configuration
|
|
34
|
+
* - Bun-specific handling (.bun directory blocking)
|
|
35
|
+
* - Proper nodeModulesPaths for monorepo resolution
|
|
36
|
+
*
|
|
37
|
+
* @param config - Base Metro configuration
|
|
38
|
+
* @param options - Optional configuration options
|
|
39
|
+
* @returns Enhanced Metro configuration
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```js
|
|
43
|
+
* // Basic usage - all monorepo handling is automatic
|
|
44
|
+
* const { withTeardown } = require('@teardown/metro-config')
|
|
45
|
+
* const { getDefaultConfig } = require('@react-native/metro-config')
|
|
46
|
+
*
|
|
47
|
+
* module.exports = withTeardown(getDefaultConfig(__dirname))
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```js
|
|
52
|
+
* // With options
|
|
53
|
+
* const { withTeardown } = require('@teardown/metro-config')
|
|
54
|
+
* const { getDefaultConfig } = require('@react-native/metro-config')
|
|
55
|
+
*
|
|
56
|
+
* module.exports = withTeardown(getDefaultConfig(__dirname), {
|
|
57
|
+
* verbose: true,
|
|
58
|
+
* projectRoot: __dirname, // explicitly set project root
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
function withTeardown(config, options = {}) {
|
|
63
|
+
const { verbose, projectRoot: explicitProjectRoot, ...facetpackOptions } = options;
|
|
64
|
+
// Determine project root
|
|
65
|
+
const projectRoot = explicitProjectRoot || config.projectRoot || process.cwd();
|
|
66
|
+
if (verbose) {
|
|
67
|
+
console.log("[teardown/metro-config] Applying Teardown configuration...");
|
|
68
|
+
console.log(`[teardown/metro-config] Project root: ${projectRoot}`);
|
|
69
|
+
}
|
|
70
|
+
// Check if in monorepo
|
|
71
|
+
const inMonorepo = (0, workspace_1.isInMonorepo)(projectRoot);
|
|
72
|
+
if (verbose && inMonorepo) {
|
|
73
|
+
const workspaceRoot = (0, workspace_1.getWorkspaceRoot)(projectRoot);
|
|
74
|
+
console.log(`[teardown/metro-config] Monorepo detected: ${workspaceRoot}`);
|
|
75
|
+
}
|
|
76
|
+
// Get monorepo-aware watch folders
|
|
77
|
+
const additionalWatchFolders = (0, workspace_1.getWatchFolders)(projectRoot);
|
|
78
|
+
const existingWatchFolders = config.watchFolders || [];
|
|
79
|
+
// Get monorepo-aware node modules paths
|
|
80
|
+
const modulesPaths = (0, workspace_1.getModulesPaths)(projectRoot);
|
|
81
|
+
const existingNodeModulesPaths = config.resolver?.nodeModulesPaths || [];
|
|
82
|
+
// Get block list with .bun blocking
|
|
83
|
+
const blockList = (0, bun_1.getBlockList)(config.resolver?.blockList);
|
|
84
|
+
// Create Bun-aware resolver
|
|
85
|
+
const defaultResolveRequest = config.resolver?.resolveRequest ||
|
|
86
|
+
((context, moduleName, platform) => context.resolveRequest(context, moduleName, platform));
|
|
87
|
+
const bunAwareResolver = (0, bun_1.createBunAwareResolver)(projectRoot, defaultResolveRequest);
|
|
88
|
+
// Build enhanced config
|
|
89
|
+
const enhancedConfig = {
|
|
90
|
+
...config,
|
|
91
|
+
projectRoot,
|
|
92
|
+
watchFolders: [...new Set([...existingWatchFolders, ...additionalWatchFolders])],
|
|
93
|
+
resolver: {
|
|
94
|
+
...config.resolver,
|
|
95
|
+
blockList,
|
|
96
|
+
nodeModulesPaths: [...new Set([...modulesPaths, ...existingNodeModulesPaths])],
|
|
97
|
+
resolveRequest: bunAwareResolver,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
// Set unstable_serverRoot for monorepo web support
|
|
101
|
+
if (inMonorepo) {
|
|
102
|
+
const serverRoot = (0, workspace_1.getMetroServerRoot)(projectRoot);
|
|
103
|
+
enhancedConfig.unstable_serverRoot = serverRoot;
|
|
104
|
+
// Add relative project root to transformer cache key for cache collision prevention
|
|
105
|
+
const relativeRoot = (0, workspace_1.getRelativeProjectRoot)(projectRoot);
|
|
106
|
+
if (relativeRoot) {
|
|
107
|
+
const existingCacheKey = config.transformer?.customTransformOptions || {};
|
|
108
|
+
enhancedConfig.transformer = {
|
|
109
|
+
...config.transformer,
|
|
110
|
+
customTransformOptions: {
|
|
111
|
+
...existingCacheKey,
|
|
112
|
+
_teardownRelativeProjectRoot: relativeRoot,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (verbose) {
|
|
118
|
+
const watchFoldersCount = enhancedConfig.watchFolders?.length || 0;
|
|
119
|
+
const nodeModulesPathsCount = enhancedConfig.resolver?.nodeModulesPaths?.length || 0;
|
|
120
|
+
console.log(`[teardown/metro-config] Watch folders: ${watchFoldersCount}`);
|
|
121
|
+
console.log(`[teardown/metro-config] Node modules paths: ${nodeModulesPathsCount}`);
|
|
122
|
+
}
|
|
123
|
+
// Apply Facetpack for Rust-powered transforms
|
|
124
|
+
const finalConfig = (0, facetpack_1.withFacetpack)(enhancedConfig, facetpackOptions);
|
|
125
|
+
if (verbose) {
|
|
126
|
+
console.log("[teardown/metro-config] Metro config enhanced successfully");
|
|
127
|
+
}
|
|
128
|
+
return finalConfig;
|
|
129
|
+
}
|
|
130
|
+
// Re-export Bun utilities
|
|
131
|
+
const bun_2 = require("./bun");
|
|
132
|
+
Object.defineProperty(exports, "BUN_BLOCK_PATTERN", { enumerable: true, get: function () { return bun_2.BUN_BLOCK_PATTERN; } });
|
|
133
|
+
Object.defineProperty(exports, "getBlockList", { enumerable: true, get: function () { return bun_2.getBlockList; } });
|
|
134
|
+
// Re-export workspace utilities for advanced use cases
|
|
135
|
+
const workspace_2 = require("./workspace");
|
|
136
|
+
Object.defineProperty(exports, "getMetroServerRoot", { enumerable: true, get: function () { return workspace_2.getMetroServerRoot; } });
|
|
137
|
+
Object.defineProperty(exports, "getModulesPaths", { enumerable: true, get: function () { return workspace_2.getModulesPaths; } });
|
|
138
|
+
Object.defineProperty(exports, "getRelativeProjectRoot", { enumerable: true, get: function () { return workspace_2.getRelativeProjectRoot; } });
|
|
139
|
+
Object.defineProperty(exports, "getWatchFolders", { enumerable: true, get: function () { return workspace_2.getWatchFolders; } });
|
|
140
|
+
Object.defineProperty(exports, "getWorkspaceRoot", { enumerable: true, get: function () { return workspace_2.getWorkspaceRoot; } });
|
|
141
|
+
Object.defineProperty(exports, "isInMonorepo", { enumerable: true, get: function () { return workspace_2.isInMonorepo; } });
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for @teardown/metro-config
|
|
3
|
+
*/
|
|
4
|
+
import type { MetroConfig as FacetpackMetroConfig } from "@ecrindigital/facetpack";
|
|
5
|
+
/**
|
|
6
|
+
* Extended Metro configuration type with proper resolver and transformer types
|
|
7
|
+
*/
|
|
8
|
+
export interface MetroConfig extends FacetpackMetroConfig {
|
|
9
|
+
projectRoot?: string;
|
|
10
|
+
watchFolders?: string[];
|
|
11
|
+
resolver?: {
|
|
12
|
+
blockList?: RegExp | RegExp[];
|
|
13
|
+
nodeModulesPaths?: string[];
|
|
14
|
+
resolveRequest?: ResolveRequestFn;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
};
|
|
17
|
+
transformer?: {
|
|
18
|
+
customTransformOptions?: Record<string, unknown>;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
};
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Metro resolve request function type
|
|
25
|
+
*/
|
|
26
|
+
export type ResolveRequestFn = (context: {
|
|
27
|
+
resolveRequest: ResolveRequestFn;
|
|
28
|
+
}, moduleName: string, platform: string | null) => {
|
|
29
|
+
type: string;
|
|
30
|
+
filePath: string;
|
|
31
|
+
} | null;
|
|
32
|
+
/**
|
|
33
|
+
* Teardown-specific Metro configuration options
|
|
34
|
+
*/
|
|
35
|
+
export interface TeardownMetroOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Enable verbose logging for debugging
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
verbose?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Explicit project root path.
|
|
43
|
+
* If not provided, uses config.projectRoot or process.cwd().
|
|
44
|
+
*/
|
|
45
|
+
projectRoot?: string;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,IAAI,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAEnF;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,oBAAoB;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE;QACV,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAC9B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;QAC5B,cAAc,CAAC,EAAE,gBAAgB,CAAC;QAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACvB,CAAC;IACF,WAAW,CAAC,EAAE;QACb,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACvB,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC9B,OAAO,EAAE;IAAE,cAAc,EAAE,gBAAgB,CAAA;CAAE,EAC7C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,KACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace and monorepo detection utilities for Metro configuration.
|
|
3
|
+
*
|
|
4
|
+
* Uses `resolve-workspace-root` to detect workspace roots across:
|
|
5
|
+
* - Yarn workspaces
|
|
6
|
+
* - npm workspaces
|
|
7
|
+
* - pnpm workspaces
|
|
8
|
+
* - Bun workspaces
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Glob all package.json paths in a workspace.
|
|
12
|
+
* Matches Expo's implementation pattern.
|
|
13
|
+
*
|
|
14
|
+
* @param workspaceRoot Root file path for the workspace
|
|
15
|
+
* @param linkedPackages List of folders that contain linked node modules, ex: `['packages/*', 'apps/*']`
|
|
16
|
+
* @returns List of valid package.json file paths
|
|
17
|
+
*/
|
|
18
|
+
export declare function globAllPackageJsonPaths(workspaceRoot: string, linkedPackages: string[]): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Resolve all workspace package.json paths.
|
|
21
|
+
*
|
|
22
|
+
* @param workspaceRoot root file path for a workspace.
|
|
23
|
+
* @returns list of package.json file paths that are linked to the workspace.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveAllWorkspacePackageJsonPaths(workspaceRoot: string): string[];
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the workspace root directory.
|
|
28
|
+
* Returns the project root if not in a workspace.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getWorkspaceRoot(projectRoot: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Get workspace glob patterns from the workspace root.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getWorkspaceGlobs(workspaceRoot: string): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Check if the project is in a monorepo (workspace root differs from project root).
|
|
37
|
+
*/
|
|
38
|
+
export declare function isInMonorepo(projectRoot: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Get the Metro server root for monorepo support.
|
|
41
|
+
* This should be set as `unstable_serverRoot` in Metro config.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getMetroServerRoot(projectRoot: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Get watch folders for Metro in a monorepo setup.
|
|
46
|
+
*
|
|
47
|
+
* Returns:
|
|
48
|
+
* - Empty array if not in a monorepo
|
|
49
|
+
* - Workspace root node_modules + all workspace package directories if in monorepo
|
|
50
|
+
*/
|
|
51
|
+
export declare function getWatchFolders(projectRoot: string): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Get node module paths for Metro resolver in a monorepo setup.
|
|
54
|
+
*
|
|
55
|
+
* Returns paths in priority order:
|
|
56
|
+
* 1. Project's local node_modules
|
|
57
|
+
* 2. Workspace root node_modules (if in monorepo)
|
|
58
|
+
*/
|
|
59
|
+
export declare function getModulesPaths(projectRoot: string): string[];
|
|
60
|
+
/**
|
|
61
|
+
* Get the relative project root from workspace root.
|
|
62
|
+
* Used for cache key generation to prevent collisions in monorepos.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getRelativeProjectRoot(projectRoot: string): string;
|
|
65
|
+
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA0BH;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAWjG;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,CAQnF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAY5D;AAiCD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,CAwBjE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAM9D;AASD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAe7D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAa7D;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAMlE"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Workspace and monorepo detection utilities for Metro configuration.
|
|
4
|
+
*
|
|
5
|
+
* Uses `resolve-workspace-root` to detect workspace roots across:
|
|
6
|
+
* - Yarn workspaces
|
|
7
|
+
* - npm workspaces
|
|
8
|
+
* - pnpm workspaces
|
|
9
|
+
* - Bun workspaces
|
|
10
|
+
*/
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.globAllPackageJsonPaths = globAllPackageJsonPaths;
|
|
16
|
+
exports.resolveAllWorkspacePackageJsonPaths = resolveAllWorkspacePackageJsonPaths;
|
|
17
|
+
exports.getWorkspaceRoot = getWorkspaceRoot;
|
|
18
|
+
exports.getWorkspaceGlobs = getWorkspaceGlobs;
|
|
19
|
+
exports.isInMonorepo = isInMonorepo;
|
|
20
|
+
exports.getMetroServerRoot = getMetroServerRoot;
|
|
21
|
+
exports.getWatchFolders = getWatchFolders;
|
|
22
|
+
exports.getModulesPaths = getModulesPaths;
|
|
23
|
+
exports.getRelativeProjectRoot = getRelativeProjectRoot;
|
|
24
|
+
const node_fs_1 = require("node:fs");
|
|
25
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
26
|
+
const glob_1 = require("glob");
|
|
27
|
+
/**
|
|
28
|
+
* Read and parse a JSON file, returning null if invalid.
|
|
29
|
+
*/
|
|
30
|
+
function readJsonFile(filePath) {
|
|
31
|
+
const file = (0, node_fs_1.readFileSync)(filePath, "utf8");
|
|
32
|
+
return JSON.parse(file);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if a file is a valid JSON file.
|
|
36
|
+
*/
|
|
37
|
+
function isValidJsonFile(filePath) {
|
|
38
|
+
try {
|
|
39
|
+
readJsonFile(filePath);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Glob all package.json paths in a workspace.
|
|
48
|
+
* Matches Expo's implementation pattern.
|
|
49
|
+
*
|
|
50
|
+
* @param workspaceRoot Root file path for the workspace
|
|
51
|
+
* @param linkedPackages List of folders that contain linked node modules, ex: `['packages/*', 'apps/*']`
|
|
52
|
+
* @returns List of valid package.json file paths
|
|
53
|
+
*/
|
|
54
|
+
function globAllPackageJsonPaths(workspaceRoot, linkedPackages) {
|
|
55
|
+
return linkedPackages
|
|
56
|
+
.flatMap((pattern) => {
|
|
57
|
+
// Globs should only contain `/` as separator, even on Windows.
|
|
58
|
+
return (0, glob_1.globSync)(node_path_1.default.posix.join(pattern, "package.json").replace(/\\/g, "/"), {
|
|
59
|
+
cwd: workspaceRoot,
|
|
60
|
+
absolute: true,
|
|
61
|
+
ignore: ["**/@(Carthage|Pods|node_modules)/**"],
|
|
62
|
+
}).filter((pkgPath) => isValidJsonFile(pkgPath));
|
|
63
|
+
})
|
|
64
|
+
.map((p) => node_path_1.default.join(p));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve all workspace package.json paths.
|
|
68
|
+
*
|
|
69
|
+
* @param workspaceRoot root file path for a workspace.
|
|
70
|
+
* @returns list of package.json file paths that are linked to the workspace.
|
|
71
|
+
*/
|
|
72
|
+
function resolveAllWorkspacePackageJsonPaths(workspaceRoot) {
|
|
73
|
+
try {
|
|
74
|
+
const workspaceGlobs = getWorkspaceGlobs(workspaceRoot);
|
|
75
|
+
if (!workspaceGlobs?.length)
|
|
76
|
+
return [];
|
|
77
|
+
return globAllPackageJsonPaths(workspaceRoot, workspaceGlobs);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the workspace root directory.
|
|
85
|
+
* Returns the project root if not in a workspace.
|
|
86
|
+
*/
|
|
87
|
+
function getWorkspaceRoot(projectRoot) {
|
|
88
|
+
try {
|
|
89
|
+
// Dynamic import to handle the package
|
|
90
|
+
const { resolveWorkspaceRoot } = require("resolve-workspace-root");
|
|
91
|
+
const workspaceRoot = resolveWorkspaceRoot(projectRoot);
|
|
92
|
+
return workspaceRoot || projectRoot;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Fallback: check for turbo.jsonc or package.json with workspaces
|
|
96
|
+
return findMonorepoRootFallback(projectRoot);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Fallback monorepo root detection for when resolve-workspace-root fails.
|
|
101
|
+
* Checks for turbo.jsonc or package.json with workspaces field.
|
|
102
|
+
*/
|
|
103
|
+
function findMonorepoRootFallback(startDir) {
|
|
104
|
+
let current = startDir;
|
|
105
|
+
while (current !== node_path_1.default.dirname(current)) {
|
|
106
|
+
const turboConfig = node_path_1.default.join(current, "turbo.jsonc");
|
|
107
|
+
const turboJson = node_path_1.default.join(current, "turbo.json");
|
|
108
|
+
const packageJson = node_path_1.default.join(current, "package.json");
|
|
109
|
+
if ((0, node_fs_1.existsSync)(turboConfig) || (0, node_fs_1.existsSync)(turboJson)) {
|
|
110
|
+
return current;
|
|
111
|
+
}
|
|
112
|
+
if ((0, node_fs_1.existsSync)(packageJson)) {
|
|
113
|
+
try {
|
|
114
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)(packageJson, "utf8"));
|
|
115
|
+
if (pkg.workspaces) {
|
|
116
|
+
return current;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Ignore parse errors
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
current = node_path_1.default.dirname(current);
|
|
124
|
+
}
|
|
125
|
+
return startDir;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get workspace glob patterns from the workspace root.
|
|
129
|
+
*/
|
|
130
|
+
function getWorkspaceGlobs(workspaceRoot) {
|
|
131
|
+
try {
|
|
132
|
+
const { getWorkspaceGlobs: getGlobs } = require("resolve-workspace-root");
|
|
133
|
+
return getGlobs(workspaceRoot) ?? [];
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Fallback: try to read from package.json
|
|
137
|
+
const packageJsonPath = node_path_1.default.join(workspaceRoot, "package.json");
|
|
138
|
+
if ((0, node_fs_1.existsSync)(packageJsonPath)) {
|
|
139
|
+
try {
|
|
140
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, "utf8"));
|
|
141
|
+
if (Array.isArray(pkg.workspaces)) {
|
|
142
|
+
return pkg.workspaces;
|
|
143
|
+
}
|
|
144
|
+
if (pkg.workspaces?.packages) {
|
|
145
|
+
return pkg.workspaces.packages;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Ignore parse errors
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Check if the project is in a monorepo (workspace root differs from project root).
|
|
157
|
+
*/
|
|
158
|
+
function isInMonorepo(projectRoot) {
|
|
159
|
+
const workspaceRoot = getWorkspaceRoot(projectRoot);
|
|
160
|
+
return workspaceRoot !== projectRoot;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get the Metro server root for monorepo support.
|
|
164
|
+
* This should be set as `unstable_serverRoot` in Metro config.
|
|
165
|
+
*/
|
|
166
|
+
function getMetroServerRoot(projectRoot) {
|
|
167
|
+
// Can be disabled with environment variable
|
|
168
|
+
if (process.env.TEARDOWN_NO_METRO_WORKSPACE_ROOT) {
|
|
169
|
+
return projectRoot;
|
|
170
|
+
}
|
|
171
|
+
return getWorkspaceRoot(projectRoot);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Helper to get unique items from an array.
|
|
175
|
+
*/
|
|
176
|
+
function uniqueItems(items) {
|
|
177
|
+
return [...new Set(items)];
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get watch folders for Metro in a monorepo setup.
|
|
181
|
+
*
|
|
182
|
+
* Returns:
|
|
183
|
+
* - Empty array if not in a monorepo
|
|
184
|
+
* - Workspace root node_modules + all workspace package directories if in monorepo
|
|
185
|
+
*/
|
|
186
|
+
function getWatchFolders(projectRoot) {
|
|
187
|
+
const resolvedProjectRoot = node_path_1.default.resolve(projectRoot);
|
|
188
|
+
const workspaceRoot = getMetroServerRoot(resolvedProjectRoot);
|
|
189
|
+
// Rely on default behavior in standard projects.
|
|
190
|
+
if (workspaceRoot === resolvedProjectRoot) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
const packages = resolveAllWorkspacePackageJsonPaths(workspaceRoot);
|
|
194
|
+
if (!packages?.length) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
return uniqueItems([node_path_1.default.join(workspaceRoot, "node_modules"), ...packages.map((pkg) => node_path_1.default.dirname(pkg))]);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get node module paths for Metro resolver in a monorepo setup.
|
|
201
|
+
*
|
|
202
|
+
* Returns paths in priority order:
|
|
203
|
+
* 1. Project's local node_modules
|
|
204
|
+
* 2. Workspace root node_modules (if in monorepo)
|
|
205
|
+
*/
|
|
206
|
+
function getModulesPaths(projectRoot) {
|
|
207
|
+
const paths = [];
|
|
208
|
+
// Only add paths if in a monorepo - minimizes chance of Metro resolver breaking
|
|
209
|
+
const resolvedProjectRoot = node_path_1.default.resolve(projectRoot);
|
|
210
|
+
const workspaceRoot = getMetroServerRoot(resolvedProjectRoot);
|
|
211
|
+
if (workspaceRoot !== resolvedProjectRoot) {
|
|
212
|
+
paths.push(node_path_1.default.resolve(projectRoot, "node_modules"));
|
|
213
|
+
paths.push(node_path_1.default.resolve(workspaceRoot, "node_modules"));
|
|
214
|
+
}
|
|
215
|
+
return paths;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Get the relative project root from workspace root.
|
|
219
|
+
* Used for cache key generation to prevent collisions in monorepos.
|
|
220
|
+
*/
|
|
221
|
+
function getRelativeProjectRoot(projectRoot) {
|
|
222
|
+
const workspaceRoot = getWorkspaceRoot(projectRoot);
|
|
223
|
+
if (workspaceRoot === projectRoot) {
|
|
224
|
+
return "";
|
|
225
|
+
}
|
|
226
|
+
return node_path_1.default.relative(workspaceRoot, projectRoot);
|
|
227
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@teardown/metro-config",
|
|
3
|
+
"version": "2.0.44",
|
|
4
|
+
"description": "Metro configuration for Teardown - Rust-powered transforms via Facetpack",
|
|
5
|
+
"private": false,
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"require": "./dist/index.js",
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"typecheck": "bun x tsgo --noEmit --project ./tsconfig.json",
|
|
22
|
+
"build": "bun x tsgo --project ./tsconfig.json",
|
|
23
|
+
"dev": "bun x tsgo --watch --project ./tsconfig.json"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@ecrindigital/facetpack": "^0.2.0",
|
|
27
|
+
"metro": "*",
|
|
28
|
+
"metro-config": "*"
|
|
29
|
+
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"metro": {
|
|
32
|
+
"optional": true
|
|
33
|
+
},
|
|
34
|
+
"metro-config": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"glob": "^10.0.0",
|
|
40
|
+
"resolve-workspace-root": "^2.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@teardown/tsconfig": "2.0.44",
|
|
44
|
+
"@types/bun": "1.3.5",
|
|
45
|
+
"typescript": "5.9.3"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"react-native",
|
|
49
|
+
"metro",
|
|
50
|
+
"facetpack",
|
|
51
|
+
"bundler",
|
|
52
|
+
"rust",
|
|
53
|
+
"oxc"
|
|
54
|
+
]
|
|
55
|
+
}
|
package/src/bun.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun-specific Metro configuration utilities.
|
|
3
|
+
*
|
|
4
|
+
* Handles blocking of .bun directories which can cause Metro resolution issues.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import type { ResolveRequestFn } from "./types";
|
|
10
|
+
import { getModulesPaths } from "./workspace";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Block pattern for .bun directories.
|
|
14
|
+
* Metro's blockList prevents processing of matched paths.
|
|
15
|
+
*/
|
|
16
|
+
export const BUN_BLOCK_PATTERN = /.*[/\\]\.bun[/\\].*/;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a custom resolver that filters out .bun paths during module resolution.
|
|
20
|
+
*
|
|
21
|
+
* Metro's blockList only prevents processing after resolution, not during.
|
|
22
|
+
* This resolver intercepts resolution and redirects away from .bun paths.
|
|
23
|
+
*/
|
|
24
|
+
export function createBunAwareResolver(projectRoot: string, defaultResolveRequest: ResolveRequestFn): ResolveRequestFn {
|
|
25
|
+
const modulesPaths = getModulesPaths(projectRoot);
|
|
26
|
+
|
|
27
|
+
return (context: { resolveRequest: ResolveRequestFn }, moduleName: string, platform: string | null) => {
|
|
28
|
+
// Use Metro's default resolution
|
|
29
|
+
const result = defaultResolveRequest(context, moduleName, platform);
|
|
30
|
+
|
|
31
|
+
// If resolved path contains .bun, try to find alternative
|
|
32
|
+
if (result?.filePath?.includes("/.bun/")) {
|
|
33
|
+
for (const modulesPath of modulesPaths) {
|
|
34
|
+
if (modulesPath.includes("/.bun/")) continue;
|
|
35
|
+
|
|
36
|
+
const packagePath = path.join(modulesPath, moduleName);
|
|
37
|
+
if (existsSync(packagePath)) {
|
|
38
|
+
const mainFile = resolvePackageMain(packagePath);
|
|
39
|
+
if (mainFile) {
|
|
40
|
+
return { type: "sourceFile", filePath: mainFile };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the main entry point of a package.
|
|
52
|
+
*/
|
|
53
|
+
function resolvePackageMain(packagePath: string): string | null {
|
|
54
|
+
const packageJsonPath = path.join(packagePath, "package.json");
|
|
55
|
+
if (existsSync(packageJsonPath)) {
|
|
56
|
+
try {
|
|
57
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
58
|
+
const main = pkg.main || pkg.module || "index.js";
|
|
59
|
+
const mainPath = path.join(packagePath, main);
|
|
60
|
+
|
|
61
|
+
if (existsSync(mainPath)) {
|
|
62
|
+
return mainPath;
|
|
63
|
+
}
|
|
64
|
+
// Try with .js extension
|
|
65
|
+
const mainPathJs = mainPath + ".js";
|
|
66
|
+
if (existsSync(mainPathJs)) {
|
|
67
|
+
return mainPathJs;
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Ignore parse errors
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Fallback to index.js
|
|
75
|
+
const indexPath = path.join(packagePath, "index.js");
|
|
76
|
+
if (existsSync(indexPath)) {
|
|
77
|
+
return indexPath;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get blockList patterns for Metro config.
|
|
85
|
+
* Includes .bun directory blocking.
|
|
86
|
+
*/
|
|
87
|
+
export function getBlockList(existingBlockList?: RegExp | RegExp[]): RegExp[] {
|
|
88
|
+
const blockListArray = Array.isArray(existingBlockList)
|
|
89
|
+
? existingBlockList
|
|
90
|
+
: existingBlockList
|
|
91
|
+
? [existingBlockList]
|
|
92
|
+
: [];
|
|
93
|
+
|
|
94
|
+
return [...blockListArray, BUN_BLOCK_PATTERN];
|
|
95
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @teardown/metro-config
|
|
3
|
+
*
|
|
4
|
+
* Metro configuration wrapper that provides:
|
|
5
|
+
* - Rust-powered transforms via Facetpack (36x faster than Babel)
|
|
6
|
+
* - Automatic monorepo/workspace detection and configuration
|
|
7
|
+
* - Bun-specific handling (blocks .bun directories)
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```js
|
|
11
|
+
* // metro.config.js
|
|
12
|
+
* const { withTeardown } = require('@teardown/metro-config')
|
|
13
|
+
* const { getDefaultConfig } = require('@react-native/metro-config')
|
|
14
|
+
*
|
|
15
|
+
* module.exports = withTeardown(getDefaultConfig(__dirname))
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { getStoredOptions, withFacetpack } from "@ecrindigital/facetpack";
|
|
22
|
+
import { createBunAwareResolver, getBlockList } from "./bun";
|
|
23
|
+
import type { MetroConfig, ResolveRequestFn, TeardownMetroOptions } from "./types";
|
|
24
|
+
import {
|
|
25
|
+
getMetroServerRoot,
|
|
26
|
+
getModulesPaths,
|
|
27
|
+
getRelativeProjectRoot,
|
|
28
|
+
getWatchFolders,
|
|
29
|
+
getWorkspaceRoot,
|
|
30
|
+
isInMonorepo,
|
|
31
|
+
} from "./workspace";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Wraps a Metro configuration with Teardown's enhancements.
|
|
35
|
+
*
|
|
36
|
+
* This function applies:
|
|
37
|
+
* - Facetpack for Rust-powered transforms (36x faster)
|
|
38
|
+
* - Automatic monorepo detection and watch folder configuration
|
|
39
|
+
* - Bun-specific handling (.bun directory blocking)
|
|
40
|
+
* - Proper nodeModulesPaths for monorepo resolution
|
|
41
|
+
*
|
|
42
|
+
* @param config - Base Metro configuration
|
|
43
|
+
* @param options - Optional configuration options
|
|
44
|
+
* @returns Enhanced Metro configuration
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```js
|
|
48
|
+
* // Basic usage - all monorepo handling is automatic
|
|
49
|
+
* const { withTeardown } = require('@teardown/metro-config')
|
|
50
|
+
* const { getDefaultConfig } = require('@react-native/metro-config')
|
|
51
|
+
*
|
|
52
|
+
* module.exports = withTeardown(getDefaultConfig(__dirname))
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```js
|
|
57
|
+
* // With options
|
|
58
|
+
* const { withTeardown } = require('@teardown/metro-config')
|
|
59
|
+
* const { getDefaultConfig } = require('@react-native/metro-config')
|
|
60
|
+
*
|
|
61
|
+
* module.exports = withTeardown(getDefaultConfig(__dirname), {
|
|
62
|
+
* verbose: true,
|
|
63
|
+
* projectRoot: __dirname, // explicitly set project root
|
|
64
|
+
* })
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function withTeardown(config: MetroConfig, options: TeardownMetroOptions = {}): MetroConfig {
|
|
68
|
+
const { verbose, projectRoot: explicitProjectRoot, ...facetpackOptions } = options;
|
|
69
|
+
|
|
70
|
+
// Determine project root
|
|
71
|
+
const projectRoot = explicitProjectRoot || (config.projectRoot as string | undefined) || process.cwd();
|
|
72
|
+
|
|
73
|
+
if (verbose) {
|
|
74
|
+
console.log("[teardown/metro-config] Applying Teardown configuration...");
|
|
75
|
+
console.log(`[teardown/metro-config] Project root: ${projectRoot}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if in monorepo
|
|
79
|
+
const inMonorepo = isInMonorepo(projectRoot);
|
|
80
|
+
if (verbose && inMonorepo) {
|
|
81
|
+
const workspaceRoot = getWorkspaceRoot(projectRoot);
|
|
82
|
+
console.log(`[teardown/metro-config] Monorepo detected: ${workspaceRoot}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Get monorepo-aware watch folders
|
|
86
|
+
const additionalWatchFolders = getWatchFolders(projectRoot);
|
|
87
|
+
const existingWatchFolders = (config.watchFolders as string[] | undefined) || [];
|
|
88
|
+
|
|
89
|
+
// Get monorepo-aware node modules paths
|
|
90
|
+
const modulesPaths = getModulesPaths(projectRoot);
|
|
91
|
+
const existingNodeModulesPaths = (config.resolver?.nodeModulesPaths as string[] | undefined) || [];
|
|
92
|
+
|
|
93
|
+
// Get block list with .bun blocking
|
|
94
|
+
const blockList = getBlockList(config.resolver?.blockList as RegExp | RegExp[] | undefined);
|
|
95
|
+
|
|
96
|
+
// Create Bun-aware resolver
|
|
97
|
+
const defaultResolveRequest: ResolveRequestFn =
|
|
98
|
+
(config.resolver?.resolveRequest as ResolveRequestFn | undefined) ||
|
|
99
|
+
((context, moduleName, platform) => context.resolveRequest(context, moduleName, platform));
|
|
100
|
+
|
|
101
|
+
const bunAwareResolver = createBunAwareResolver(projectRoot, defaultResolveRequest);
|
|
102
|
+
|
|
103
|
+
// Build enhanced config
|
|
104
|
+
const enhancedConfig: MetroConfig = {
|
|
105
|
+
...config,
|
|
106
|
+
projectRoot,
|
|
107
|
+
watchFolders: [...new Set([...existingWatchFolders, ...additionalWatchFolders])],
|
|
108
|
+
resolver: {
|
|
109
|
+
...config.resolver,
|
|
110
|
+
blockList,
|
|
111
|
+
nodeModulesPaths: [...new Set([...modulesPaths, ...existingNodeModulesPaths])],
|
|
112
|
+
resolveRequest: bunAwareResolver,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Set unstable_serverRoot for monorepo web support
|
|
117
|
+
if (inMonorepo) {
|
|
118
|
+
const serverRoot = getMetroServerRoot(projectRoot);
|
|
119
|
+
(enhancedConfig as Record<string, unknown>).unstable_serverRoot = serverRoot;
|
|
120
|
+
|
|
121
|
+
// Add relative project root to transformer cache key for cache collision prevention
|
|
122
|
+
const relativeRoot = getRelativeProjectRoot(projectRoot);
|
|
123
|
+
if (relativeRoot) {
|
|
124
|
+
const existingCacheKey =
|
|
125
|
+
(config.transformer as Record<string, unknown> | undefined)?.customTransformOptions || {};
|
|
126
|
+
enhancedConfig.transformer = {
|
|
127
|
+
...config.transformer,
|
|
128
|
+
customTransformOptions: {
|
|
129
|
+
...(existingCacheKey as Record<string, unknown>),
|
|
130
|
+
_teardownRelativeProjectRoot: relativeRoot,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (verbose) {
|
|
137
|
+
const watchFoldersCount = (enhancedConfig.watchFolders as string[] | undefined)?.length || 0;
|
|
138
|
+
const nodeModulesPathsCount = (enhancedConfig.resolver?.nodeModulesPaths as string[] | undefined)?.length || 0;
|
|
139
|
+
console.log(`[teardown/metro-config] Watch folders: ${watchFoldersCount}`);
|
|
140
|
+
console.log(`[teardown/metro-config] Node modules paths: ${nodeModulesPathsCount}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Apply Facetpack for Rust-powered transforms
|
|
144
|
+
const finalConfig = withFacetpack(enhancedConfig, facetpackOptions);
|
|
145
|
+
|
|
146
|
+
if (verbose) {
|
|
147
|
+
console.log("[teardown/metro-config] Metro config enhanced successfully");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return finalConfig;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Re-export useful utilities from Facetpack
|
|
154
|
+
export { getStoredOptions };
|
|
155
|
+
|
|
156
|
+
// Re-export Bun utilities
|
|
157
|
+
export { BUN_BLOCK_PATTERN, getBlockList } from "./bun";
|
|
158
|
+
// Re-export workspace utilities for advanced use cases
|
|
159
|
+
export {
|
|
160
|
+
getMetroServerRoot,
|
|
161
|
+
getModulesPaths,
|
|
162
|
+
getRelativeProjectRoot,
|
|
163
|
+
getWatchFolders,
|
|
164
|
+
getWorkspaceRoot,
|
|
165
|
+
isInMonorepo,
|
|
166
|
+
} from "./workspace";
|
|
167
|
+
|
|
168
|
+
// Re-export types
|
|
169
|
+
export type { TeardownMetroOptions, MetroConfig };
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for @teardown/metro-config
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { MetroConfig as FacetpackMetroConfig } from "@ecrindigital/facetpack";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extended Metro configuration type with proper resolver and transformer types
|
|
9
|
+
*/
|
|
10
|
+
export interface MetroConfig extends FacetpackMetroConfig {
|
|
11
|
+
projectRoot?: string;
|
|
12
|
+
watchFolders?: string[];
|
|
13
|
+
resolver?: {
|
|
14
|
+
blockList?: RegExp | RegExp[];
|
|
15
|
+
nodeModulesPaths?: string[];
|
|
16
|
+
resolveRequest?: ResolveRequestFn;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
};
|
|
19
|
+
transformer?: {
|
|
20
|
+
customTransformOptions?: Record<string, unknown>;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
};
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Metro resolve request function type
|
|
28
|
+
*/
|
|
29
|
+
export type ResolveRequestFn = (
|
|
30
|
+
context: { resolveRequest: ResolveRequestFn },
|
|
31
|
+
moduleName: string,
|
|
32
|
+
platform: string | null
|
|
33
|
+
) => { type: string; filePath: string } | null;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Teardown-specific Metro configuration options
|
|
37
|
+
*/
|
|
38
|
+
export interface TeardownMetroOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Enable verbose logging for debugging
|
|
41
|
+
* @default false
|
|
42
|
+
*/
|
|
43
|
+
verbose?: boolean;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Explicit project root path.
|
|
47
|
+
* If not provided, uses config.projectRoot or process.cwd().
|
|
48
|
+
*/
|
|
49
|
+
projectRoot?: string;
|
|
50
|
+
}
|
package/src/workspace.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace and monorepo detection utilities for Metro configuration.
|
|
3
|
+
*
|
|
4
|
+
* Uses `resolve-workspace-root` to detect workspace roots across:
|
|
5
|
+
* - Yarn workspaces
|
|
6
|
+
* - npm workspaces
|
|
7
|
+
* - pnpm workspaces
|
|
8
|
+
* - Bun workspaces
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { globSync } from "glob";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Read and parse a JSON file, returning null if invalid.
|
|
17
|
+
*/
|
|
18
|
+
function readJsonFile(filePath: string): unknown {
|
|
19
|
+
const file = readFileSync(filePath, "utf8");
|
|
20
|
+
return JSON.parse(file);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check if a file is a valid JSON file.
|
|
25
|
+
*/
|
|
26
|
+
function isValidJsonFile(filePath: string): boolean {
|
|
27
|
+
try {
|
|
28
|
+
readJsonFile(filePath);
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Glob all package.json paths in a workspace.
|
|
37
|
+
* Matches Expo's implementation pattern.
|
|
38
|
+
*
|
|
39
|
+
* @param workspaceRoot Root file path for the workspace
|
|
40
|
+
* @param linkedPackages List of folders that contain linked node modules, ex: `['packages/*', 'apps/*']`
|
|
41
|
+
* @returns List of valid package.json file paths
|
|
42
|
+
*/
|
|
43
|
+
export function globAllPackageJsonPaths(workspaceRoot: string, linkedPackages: string[]): string[] {
|
|
44
|
+
return linkedPackages
|
|
45
|
+
.flatMap((pattern) => {
|
|
46
|
+
// Globs should only contain `/` as separator, even on Windows.
|
|
47
|
+
return globSync(path.posix.join(pattern, "package.json").replace(/\\/g, "/"), {
|
|
48
|
+
cwd: workspaceRoot,
|
|
49
|
+
absolute: true,
|
|
50
|
+
ignore: ["**/@(Carthage|Pods|node_modules)/**"],
|
|
51
|
+
}).filter((pkgPath) => isValidJsonFile(pkgPath));
|
|
52
|
+
})
|
|
53
|
+
.map((p) => path.join(p));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve all workspace package.json paths.
|
|
58
|
+
*
|
|
59
|
+
* @param workspaceRoot root file path for a workspace.
|
|
60
|
+
* @returns list of package.json file paths that are linked to the workspace.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveAllWorkspacePackageJsonPaths(workspaceRoot: string): string[] {
|
|
63
|
+
try {
|
|
64
|
+
const workspaceGlobs = getWorkspaceGlobs(workspaceRoot);
|
|
65
|
+
if (!workspaceGlobs?.length) return [];
|
|
66
|
+
return globAllPackageJsonPaths(workspaceRoot, workspaceGlobs);
|
|
67
|
+
} catch {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve the workspace root directory.
|
|
74
|
+
* Returns the project root if not in a workspace.
|
|
75
|
+
*/
|
|
76
|
+
export function getWorkspaceRoot(projectRoot: string): string {
|
|
77
|
+
try {
|
|
78
|
+
// Dynamic import to handle the package
|
|
79
|
+
const { resolveWorkspaceRoot } = require("resolve-workspace-root") as {
|
|
80
|
+
resolveWorkspaceRoot: (cwd: string) => string | null;
|
|
81
|
+
};
|
|
82
|
+
const workspaceRoot = resolveWorkspaceRoot(projectRoot);
|
|
83
|
+
return workspaceRoot || projectRoot;
|
|
84
|
+
} catch {
|
|
85
|
+
// Fallback: check for turbo.jsonc or package.json with workspaces
|
|
86
|
+
return findMonorepoRootFallback(projectRoot);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Fallback monorepo root detection for when resolve-workspace-root fails.
|
|
92
|
+
* Checks for turbo.jsonc or package.json with workspaces field.
|
|
93
|
+
*/
|
|
94
|
+
function findMonorepoRootFallback(startDir: string): string {
|
|
95
|
+
let current = startDir;
|
|
96
|
+
while (current !== path.dirname(current)) {
|
|
97
|
+
const turboConfig = path.join(current, "turbo.jsonc");
|
|
98
|
+
const turboJson = path.join(current, "turbo.json");
|
|
99
|
+
const packageJson = path.join(current, "package.json");
|
|
100
|
+
|
|
101
|
+
if (existsSync(turboConfig) || existsSync(turboJson)) {
|
|
102
|
+
return current;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (existsSync(packageJson)) {
|
|
106
|
+
try {
|
|
107
|
+
const pkg = JSON.parse(readFileSync(packageJson, "utf8"));
|
|
108
|
+
if (pkg.workspaces) {
|
|
109
|
+
return current;
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore parse errors
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
current = path.dirname(current);
|
|
117
|
+
}
|
|
118
|
+
return startDir;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get workspace glob patterns from the workspace root.
|
|
123
|
+
*/
|
|
124
|
+
export function getWorkspaceGlobs(workspaceRoot: string): string[] {
|
|
125
|
+
try {
|
|
126
|
+
const { getWorkspaceGlobs: getGlobs } = require("resolve-workspace-root") as {
|
|
127
|
+
getWorkspaceGlobs: (cwd: string) => string[] | null;
|
|
128
|
+
};
|
|
129
|
+
return getGlobs(workspaceRoot) ?? [];
|
|
130
|
+
} catch {
|
|
131
|
+
// Fallback: try to read from package.json
|
|
132
|
+
const packageJsonPath = path.join(workspaceRoot, "package.json");
|
|
133
|
+
if (existsSync(packageJsonPath)) {
|
|
134
|
+
try {
|
|
135
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
136
|
+
if (Array.isArray(pkg.workspaces)) {
|
|
137
|
+
return pkg.workspaces;
|
|
138
|
+
}
|
|
139
|
+
if (pkg.workspaces?.packages) {
|
|
140
|
+
return pkg.workspaces.packages;
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
// Ignore parse errors
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Check if the project is in a monorepo (workspace root differs from project root).
|
|
152
|
+
*/
|
|
153
|
+
export function isInMonorepo(projectRoot: string): boolean {
|
|
154
|
+
const workspaceRoot = getWorkspaceRoot(projectRoot);
|
|
155
|
+
return workspaceRoot !== projectRoot;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get the Metro server root for monorepo support.
|
|
160
|
+
* This should be set as `unstable_serverRoot` in Metro config.
|
|
161
|
+
*/
|
|
162
|
+
export function getMetroServerRoot(projectRoot: string): string {
|
|
163
|
+
// Can be disabled with environment variable
|
|
164
|
+
if (process.env.TEARDOWN_NO_METRO_WORKSPACE_ROOT) {
|
|
165
|
+
return projectRoot;
|
|
166
|
+
}
|
|
167
|
+
return getWorkspaceRoot(projectRoot);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Helper to get unique items from an array.
|
|
172
|
+
*/
|
|
173
|
+
function uniqueItems(items: string[]): string[] {
|
|
174
|
+
return [...new Set(items)];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get watch folders for Metro in a monorepo setup.
|
|
179
|
+
*
|
|
180
|
+
* Returns:
|
|
181
|
+
* - Empty array if not in a monorepo
|
|
182
|
+
* - Workspace root node_modules + all workspace package directories if in monorepo
|
|
183
|
+
*/
|
|
184
|
+
export function getWatchFolders(projectRoot: string): string[] {
|
|
185
|
+
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
186
|
+
const workspaceRoot = getMetroServerRoot(resolvedProjectRoot);
|
|
187
|
+
|
|
188
|
+
// Rely on default behavior in standard projects.
|
|
189
|
+
if (workspaceRoot === resolvedProjectRoot) {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const packages = resolveAllWorkspacePackageJsonPaths(workspaceRoot);
|
|
194
|
+
if (!packages?.length) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return uniqueItems([path.join(workspaceRoot, "node_modules"), ...packages.map((pkg) => path.dirname(pkg))]);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get node module paths for Metro resolver in a monorepo setup.
|
|
203
|
+
*
|
|
204
|
+
* Returns paths in priority order:
|
|
205
|
+
* 1. Project's local node_modules
|
|
206
|
+
* 2. Workspace root node_modules (if in monorepo)
|
|
207
|
+
*/
|
|
208
|
+
export function getModulesPaths(projectRoot: string): string[] {
|
|
209
|
+
const paths: string[] = [];
|
|
210
|
+
|
|
211
|
+
// Only add paths if in a monorepo - minimizes chance of Metro resolver breaking
|
|
212
|
+
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
213
|
+
const workspaceRoot = getMetroServerRoot(resolvedProjectRoot);
|
|
214
|
+
|
|
215
|
+
if (workspaceRoot !== resolvedProjectRoot) {
|
|
216
|
+
paths.push(path.resolve(projectRoot, "node_modules"));
|
|
217
|
+
paths.push(path.resolve(workspaceRoot, "node_modules"));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return paths;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get the relative project root from workspace root.
|
|
225
|
+
* Used for cache key generation to prevent collisions in monorepos.
|
|
226
|
+
*/
|
|
227
|
+
export function getRelativeProjectRoot(projectRoot: string): string {
|
|
228
|
+
const workspaceRoot = getWorkspaceRoot(projectRoot);
|
|
229
|
+
if (workspaceRoot === projectRoot) {
|
|
230
|
+
return "";
|
|
231
|
+
}
|
|
232
|
+
return path.relative(workspaceRoot, projectRoot);
|
|
233
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@teardown/tsconfig/tsconfig.bun.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"module": "CommonJS",
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"declarationMap": true,
|
|
11
|
+
"emitDeclarationOnly": false,
|
|
12
|
+
"allowImportingTsExtensions": false,
|
|
13
|
+
"verbatimModuleSyntax": false
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|