TypeScript validation during Metro bundling (#195)

* Create a typescript validation plugin for Metro. Add the plugin to our test-app package. Update test-app source files to typescript, and change the Metro entry file from lib (transpiled) to src.

Separately, split tsconfig into options and file-specs. Update all packages to import the options-only shared tsconfig, so they can specify their own source files.

* Add a VSCode launch profile for attaching to running node process.

* Update dependencies

* Change files

* Fix string handling bug

* Move code into its own source file, exporting everything for testability.

* Add tests and a README file

* Split tsconfig options out into a shared file that can be included in all projects without specifying any source files.

* Change files

* Switch to shared babel config

* Wipe out change files

* Change files

* Remove unnecessary yargs churn in yarn.lock

* fix path bugs on windows

* Fix another windows/posix path bug. Add more tests to cover the missed case.

* Updates based on PR feedback
This commit is contained in:
Adam Foxman 2021-05-07 12:20:53 -07:00 коммит произвёл GitHub
Родитель b43bad128a
Коммит 368a92fbae
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 754 добавлений и 1 удалений

15
.vscode/launch.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach by Process ID",
"processId": "${command:PickProcess}",
"request": "attach",
"skipFiles": ["<node_internals>/**"],
"type": "pwa-node"
}
]
}

Просмотреть файл

@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Create a typescript validation plugin for Metro. Add the plugin to our test-app package. Update test-app source files to typescript, and change the Metro entry file from lib (transpiled) to src.",
"packageName": "@rnx-kit/metro-plugin-typescript-validation",
"email": "afoxman@microsoft.com",
"dependentChangeType": "patch"
}

Просмотреть файл

@ -0,0 +1,24 @@
# @rnx-kit/metro-plugin-typescript-validation
`@rnx-kit/metro-plugin-typescript-validation` checks TypeScript source files in your package for syntactic and semantic correctness.
## Usage
Add this plugin in your `metro.config.js` using `@rnx-kit/metro-serializer`:
```js
const { makeMetroConfig } = require("@rnx-kit/metro-config");
const {
TypeScriptValidation,
} = require("@rnx-kit/metro-plugin-typescript-validation");
const { MetroSerializer } = require("@rnx-kit/metro-serializer");
module.exports = makeMetroConfig({
projectRoot: __dirname,
serializer: {
customSerializer: MetroSerializer([TypeScriptValidation()]),
},
});
```
This plugin runs as part of Metro bundling. When a type error occurs, it is displayed console output and bundle creation fails (no files are written).

Просмотреть файл

@ -0,0 +1,3 @@
module.exports = {
presets: ["@rnx-kit/babel-preset-jest-typescript"],
};

Просмотреть файл

@ -0,0 +1,2 @@
const { configureJust } = require("rnx-kit-scripts");
configureJust();

Просмотреть файл

@ -0,0 +1,37 @@
{
"name": "@rnx-kit/metro-plugin-typescript-validation",
"version": "1.0.0",
"description": "Typescript validation during Metro bundling",
"homepage": "https://github.com/microsoft/rnx-kit/tree/main/packages/metro-plugin-typescript-validation#rnx-kitmetro-plugin-typescript-validation",
"license": "MIT",
"files": [
"lib/*"
],
"main": "lib/index.js",
"repository": {
"type": "git",
"url": "https://github.com/microsoft/rnx-kit",
"directory": "packages/metro-plugin-typescript-validation"
},
"scripts": {
"build": "rnx-kit-scripts build",
"test": "rnx-kit-scripts test"
},
"dependencies": {
"@msfast/typescript-platform-resolution": "^4.2.4-midgard.0",
"yargs": "^16.2.0"
},
"devDependencies": {
"@types/node": "^12.0.0",
"@types/yargs": "^16.0.0",
"@rnx-kit/babel-preset-jest-typescript": "*",
"@rnx-kit/metro-serializer": "*",
"rnx-kit-scripts": "*"
},
"jest": {
"roots": [
"test"
],
"testRegex": "/test/.*\\.test\\.ts$"
}
}

Просмотреть файл

@ -0,0 +1 @@
export { TypeScriptValidation } from "./plugin";

Просмотреть файл

@ -0,0 +1,139 @@
import type {
Graph,
MetroPlugin,
Module,
SerializerOptions,
} from "@rnx-kit/metro-serializer";
import * as fs from "fs";
import * as path from "path";
import * as child_process from "child_process";
const yargs = require("yargs/yargs");
import { hideBin } from "yargs/helpers";
export function getModuleRoot(module: string): string {
return path.dirname(require.resolve(module + "/package.json"));
}
export function readTsConfig(projectRoot: string) {
const p = path.join(projectRoot, "tsconfig.json");
return JSON.parse(fs.readFileSync(p, { encoding: "utf8" }));
}
export function writeMetroTsConfig(
projectRoot: string,
tsconfig: object
): string {
const json = JSON.stringify(tsconfig);
const tsconfigMetroPath = path.join(
projectRoot,
"node_modules",
"tsconfig-metro.json"
);
fs.writeFileSync(tsconfigMetroPath, json);
return tsconfigMetroPath;
}
export function runTypeScriptCompiler(projectPath: string) {
const tscPath = path.join(
getModuleRoot("@msfast/typescript-platform-resolution"),
"lib",
"tsc.js"
);
const spawnOptions: child_process.SpawnSyncOptions = {
cwd: process.cwd(),
stdio: "inherit",
};
const args = [tscPath, "--project", projectPath];
const processInfo = child_process.spawnSync(
process.execPath,
args,
spawnOptions
);
if (processInfo.status) {
throw (
processInfo.error ||
new Error(
"TypeScript validation failed with exit code " + processInfo.status
)
);
} else if (processInfo.signal) {
throw (
processInfo.error ||
new Error("TypeScript validation crashed due to " + processInfo.signal)
);
}
}
export async function visit(
modulePath: string,
graph: Graph,
scopePath: string,
visited: Record<string, boolean>,
files: Array<string>
) {
// avoid circular references in the dependency graph
if (modulePath in visited) {
return;
}
visited[modulePath] = true;
// collect any file that is in scope
if (modulePath.startsWith(scopePath)) {
files.push(modulePath);
}
// recursively visit children
graph.dependencies
.get(modulePath)
?.dependencies?.forEach((m) =>
visit(m.absolutePath, graph, scopePath, visited, files)
);
}
export function TypeScriptValidation(): MetroPlugin {
// read the --platform argument from the Metro command-line
const argv = yargs(hideBin(process.argv)).argv;
const platform = argv.platform.toLowerCase();
const resolutionPlatforms = ["win32", "windows"].includes(platform)
? [platform, "win", "native"]
: [platform, "native"];
return (
_entryPoint: string,
_preModules: ReadonlyArray<Module>,
graph: Graph,
options: SerializerOptions
) => {
const visited: Record<string, boolean> = {};
const files: Array<string> = [];
graph.entryPoints.forEach((m) =>
visit(m, graph, options.projectRoot, visited, files)
);
const tsconfig = readTsConfig(options.projectRoot);
// remove include/exclude directives
delete tsconfig.include;
delete tsconfig.exclude;
// set the specific list of files to type-check
tsconfig.files = files;
// compiler options:
// - don't emit any codegen files
// - resolve modules using platform overrides
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
tsconfig.compilerOptions.noEmit = true;
tsconfig.compilerOptions.resolutionPlatforms = resolutionPlatforms;
// write the altered tsconfig, run TSC, and then cleanup the altered tsconfig
const tsconfigMetroPath = writeMetroTsConfig(options.projectRoot, tsconfig);
try {
runTypeScriptCompiler(tsconfigMetroPath);
} finally {
fs.unlinkSync(tsconfigMetroPath);
}
};
}

Просмотреть файл

@ -0,0 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`readTsConfig() reads the contents of tsconfig.json 1`] = `
Object {
"compilerOptions": Object {
"noEmit": true,
},
"include": Array [
"src",
],
}
`;
exports[`runTypeScriptCompiler() throws when the process crashes 1`] = `"TypeScript validation crashed due to SIGKILL"`;
exports[`runTypeScriptCompiler() throws when the process exit code is non-zero 1`] = `"TypeScript validation failed with exit code 1"`;
exports[`runTypeScriptCompiler() throws when the process exit code is non-zero and an error is given 1`] = `"simulated error message from typescript compiler"`;
exports[`runTypeScriptCompiler() throws when the process exit code is non-zero and an error is given 2`] = `"simulated crash message caused by sending SIGKILL to the typescript compiler"`;
exports[`visit() returns a list of files in the current package (posix) 1`] = `
Array [
"/repos/yoyodyne/packages/overthruster/src/main.ts",
"/repos/yoyodyne/packages/overthruster/src/propulsion.ts",
"/repos/yoyodyne/packages/overthruster/src/dimensions.ts",
]
`;
exports[`visit() returns a list of files in the current package (win32) 1`] = `
Array [
"C:\\\\repos\\\\yoyodyne\\\\packages\\\\overthruster\\\\src\\\\main.ts",
"C:\\\\repos\\\\yoyodyne\\\\packages\\\\overthruster\\\\src\\\\propulsion.ts",
"C:\\\\repos\\\\yoyodyne\\\\packages\\\\overthruster\\\\src\\\\dimensions.ts",
]
`;
exports[`visit() traverses the entire graph (posix) 1`] = `
Object {
"/repos/yoyodyne/node_modules/react-native/index.js": true,
"/repos/yoyodyne/packages/overthruster/src/dimensions.ts": true,
"/repos/yoyodyne/packages/overthruster/src/main.ts": true,
"/repos/yoyodyne/packages/overthruster/src/propulsion.ts": true,
}
`;
exports[`visit() traverses the entire graph (win32) 1`] = `
Object {
"/repos/yoyodyne/packages/overthruster\\\\src\\\\propulsion.ts": true,
"C:\\\\repos\\\\yoyodyne\\\\node_modules\\\\react-native\\\\index.js": true,
"C:\\\\repos\\\\yoyodyne\\\\packages\\\\overthruster\\\\src\\\\dimensions.ts": true,
"C:\\\\repos\\\\yoyodyne\\\\packages\\\\overthruster\\\\src\\\\main.ts": true,
"C:\\\\repos\\\\yoyodyne\\\\packages\\\\overthruster\\\\src\\\\propulsion.ts": true,
}
`;

Просмотреть файл

@ -0,0 +1,456 @@
import type {
Dependency,
Graph,
Module,
MixedOutput,
SerializerOptions,
} from "@rnx-kit/metro-serializer";
import {
getModuleRoot,
readTsConfig,
writeMetroTsConfig,
runTypeScriptCompiler,
visit,
TypeScriptValidation,
} from "../src/plugin";
import path from "path";
const child_process = require("child_process");
const fs = require("fs");
const yargs = require("yargs/yargs");
import { hideBin } from "yargs/helpers";
jest.mock("child_process");
jest.mock("fs");
jest.mock("yargs/yargs");
jest.mock("yargs/helpers");
// test data
const rootPathPosix = "/repos/yoyodyne/packages/overthruster";
const rootPathWin32 = path.win32.normalize("C:" + rootPathPosix);
const graphPosix: Graph = {
dependencies: new Map<string, Module<MixedOutput>>(),
importBundleNames: new Set(),
entryPoints: [rootPathPosix + "/src/main.ts"],
};
const graphWin32: Graph = {
dependencies: new Map<string, Module<MixedOutput>>(),
importBundleNames: new Set(),
entryPoints: [rootPathWin32 + "\\src\\main.ts"],
};
function addGraphDependency(g: Graph, modulePath: string) {
g.dependencies.set(modulePath, {
dependencies: new Map<string, Dependency>(),
inverseDependencies: new Set<string>(),
output: [],
path: modulePath,
getSource: () => {
return null;
},
});
return g.dependencies.get(modulePath);
}
function addModuleDependency(
g: Graph,
parent,
moduleName: string,
modulePath: string
) {
parent.dependencies.set(moduleName, {
absolutePath: modulePath,
});
return addGraphDependency(g, modulePath);
}
const index_ts_posix = addGraphDependency(
graphPosix,
rootPathPosix + "/src/main.ts"
);
const index_ts_win32 = addGraphDependency(
graphWin32,
rootPathWin32 + "\\src\\main.ts"
);
const propulsion_ts_posix = addModuleDependency(
graphPosix,
index_ts_posix,
"./src/propulsion.ts",
rootPathPosix + "/src/propulsion.ts"
);
const propulsion_ts_win32 = addModuleDependency(
graphWin32,
index_ts_win32,
".\\src\\propulsion.ts",
rootPathWin32 + "\\src\\propulsion.ts"
);
const dimension_ts_posix = addModuleDependency(
graphPosix,
index_ts_posix,
"./src/dimensions.ts",
rootPathPosix + "/src/dimensions.ts"
);
const dimension_ts_win32 = addModuleDependency(
graphWin32,
index_ts_win32,
".\\src\\dimensions.ts",
rootPathWin32 + "\\src\\dimensions.ts"
);
const react_native_posix = addModuleDependency(
graphPosix,
index_ts_posix,
"react-native",
"/repos/yoyodyne/node_modules/react-native/index.js"
);
const react_native_win32 = addModuleDependency(
graphWin32,
index_ts_win32,
"react-native",
"C:\\repos\\yoyodyne\\node_modules\\react-native\\index.js"
);
// create a circular dependency
propulsion_ts_posix.dependencies.set("./src/dimensions.ts", {
absolutePath: rootPathPosix + "/src/dimensions.ts",
data: null,
});
propulsion_ts_win32.dependencies.set(".\\src\\dimensions.ts", {
absolutePath: rootPathWin32 + "\\src\\dimensions.ts",
data: null,
});
dimension_ts_posix.dependencies.set("./src/propulsion.ts", {
absolutePath: rootPathPosix + "/src/propulsion.ts",
data: null,
});
dimension_ts_win32.dependencies.set(".\\src\\propulsion.ts", {
absolutePath: rootPathPosix + "\\src\\propulsion.ts",
data: null,
});
// test suite
describe("getModuleRoot()", () => {
test("throws on unknown module", () => {
expect(() => {
getModuleRoot("not-a-real-module");
}).toThrowError();
});
test("resolves to the root of @msfast/typescript-platform-resolution", () => {
expect(getModuleRoot("@msfast/typescript-platform-resolution")).toEqual(
expect.stringMatching(/@msfast[/\\]typescript-platform-resolution$/)
);
});
});
describe("readTsConfig()", () => {
afterEach(() => {
jest.resetAllMocks();
});
test("reads the contents of tsconfig.json", () => {
fs.readFileSync.mockReturnValue(
JSON.stringify({
compilerOptions: { noEmit: true },
include: ["src"],
})
);
const p = path.normalize("/path/to/file");
expect(readTsConfig(p)).toMatchSnapshot();
expect(fs.readFileSync).toBeCalledTimes(1);
expect(fs.readFileSync).toBeCalledWith(
path.join(p, "tsconfig.json"),
expect.anything()
);
});
});
describe("writeMetroTsConfig()", () => {
afterEach(() => {
jest.resetAllMocks();
});
test("writes the contents of tsconfig to a temp file in node_modules", () => {
const projectRoot = "/root/path/here";
const tsconfig = { a: 123 };
writeMetroTsConfig(projectRoot, tsconfig);
expect(fs.writeFileSync).toBeCalledTimes(1);
expect(fs.writeFileSync).toBeCalledWith(
expect.stringMatching(/[/\\]node_modules[/\\]tsconfig-metro.json$/),
'{"a":123}'
);
});
});
describe("runTypeScriptCompiler()", () => {
afterEach(() => {
jest.resetAllMocks();
});
test("executes the typescript compiler using node.js", () => {
child_process.spawnSync.mockReturnValue({});
runTypeScriptCompiler("/path/to/project/tsconfig.json");
expect(child_process.spawnSync).toBeCalledTimes(1);
const spawnSyncParams = child_process.spawnSync.mock.calls[0];
const { [0]: executable, [1]: args } = spawnSyncParams;
expect(executable).toEqual(process.execPath);
expect(args[0]).toEqual(
expect.stringMatching(
/[/\\]@msfast[/\\]typescript-platform-resolution.*[/\\]tsc.js$/
)
);
});
test("succeeds when the process exit code is zero", () => {
child_process.spawnSync.mockReturnValue({
status: 0,
});
runTypeScriptCompiler("/path/to/project/tsconfig.json");
expect(child_process.spawnSync).toBeCalledTimes(1);
});
test("throws when the process exit code is non-zero", () => {
child_process.spawnSync.mockReturnValue({
status: 1,
});
expect(() =>
runTypeScriptCompiler("/path/to/project/tsconfig.json")
).toThrowErrorMatchingSnapshot();
});
test("throws when the process exit code is non-zero and an error is given", () => {
child_process.spawnSync.mockReturnValue({
status: 1,
error: new Error("simulated error message from typescript compiler"),
});
expect(() =>
runTypeScriptCompiler("/path/to/project/tsconfig.json")
).toThrowErrorMatchingSnapshot();
});
test("throws when the process crashes", () => {
child_process.spawnSync.mockReturnValue({
signal: "SIGKILL",
});
expect(() =>
runTypeScriptCompiler("/path/to/project/tsconfig.json")
).toThrowErrorMatchingSnapshot();
});
test("throws when the process exit code is non-zero and an error is given", () => {
child_process.spawnSync.mockReturnValue({
signal: "SIGKILL",
error: new Error(
"simulated crash message caused by sending SIGKILL to the typescript compiler"
),
});
expect(() =>
runTypeScriptCompiler("/path/to/project/tsconfig.json")
).toThrowErrorMatchingSnapshot();
});
});
describe("visit()", () => {
afterEach(() => {
jest.resetAllMocks();
});
test("traverses the entire graph (posix)", () => {
const visited: Record<string, boolean> = {};
const files: Array<string> = [];
graphPosix.entryPoints.forEach((m) =>
visit(m, graphPosix, rootPathPosix, visited, files)
);
expect(visited).toMatchSnapshot();
});
test("traverses the entire graph (win32)", () => {
const visited: Record<string, boolean> = {};
const files: Array<string> = [];
graphWin32.entryPoints.forEach((m) =>
visit(m, graphWin32, rootPathWin32, visited, files)
);
expect(visited).toMatchSnapshot();
});
test("returns a list of files in the current package (posix)", () => {
const visited: Record<string, boolean> = {};
const files: Array<string> = [];
graphPosix.entryPoints.forEach((m) =>
visit(m, graphPosix, rootPathPosix, visited, files)
);
expect(files).toMatchSnapshot();
});
test("returns a list of files in the current package (win32)", () => {
const visited: Record<string, boolean> = {};
const files: Array<string> = [];
graphWin32.entryPoints.forEach((m) =>
visit(m, graphWin32, rootPathWin32, visited, files)
);
expect(files).toMatchSnapshot();
});
});
describe("TypeScriptValidation()", () => {
beforeEach(() => {
yargs.mockReturnValue({
argv: {
platform: "test-platform",
},
});
fs.readFileSync.mockReturnValue(
JSON.stringify({
include: ["src"],
})
);
child_process.spawnSync.mockReturnValue({
status: 0,
});
});
afterEach(() => {
jest.resetAllMocks();
});
test("replaces file specification with list from graph", () => {
TypeScriptValidation()(undefined, undefined, graphPosix, {
projectRoot: rootPathPosix,
} as SerializerOptions);
expect(fs.writeFileSync).toBeCalledTimes(1);
const tsconfig = JSON.parse(fs.writeFileSync.mock.calls[0][1]);
expect(tsconfig?.include).toBeFalsy();
expect(tsconfig?.exclude).toBeFalsy();
expect(tsconfig?.files).toBeTruthy();
expect(Array.isArray(tsconfig.files)).toBeTruthy();
expect(tsconfig.files.length).toBeGreaterThan(0);
});
test("adds noEmit compiler option", () => {
TypeScriptValidation()(undefined, undefined, graphPosix, {
projectRoot: rootPathPosix,
} as SerializerOptions);
expect(fs.writeFileSync).toBeCalledTimes(1);
const tsconfig = JSON.parse(fs.writeFileSync.mock.calls[0][1]);
expect(tsconfig?.compilerOptions?.noEmit).toBeTruthy();
});
function testResolutionPlatforms(
platform: string,
expectedPlatforms: string[]
) {
yargs.mockReturnValue({
argv: {
platform,
},
});
TypeScriptValidation()(undefined, undefined, graphPosix, {
projectRoot: rootPathPosix,
} as SerializerOptions);
expect(fs.writeFileSync).toBeCalledTimes(1);
const tsconfig = JSON.parse(fs.writeFileSync.mock.calls[0][1]);
expect(tsconfig?.compilerOptions?.resolutionPlatforms).toEqual(
expectedPlatforms
);
}
test("adds list of resolution platforms (android)", () =>
testResolutionPlatforms("android", ["android", "native"]));
test("adds list of resolution platforms (ios)", () =>
testResolutionPlatforms("ios", ["ios", "native"]));
test("adds list of resolution platforms (macos)", () =>
testResolutionPlatforms("macos", ["macos", "native"]));
test("adds list of resolution platforms (windows)", () =>
testResolutionPlatforms("windows", ["windows", "win", "native"]));
test("adds list of resolution platforms (win32)", () =>
testResolutionPlatforms("win32", ["win32", "win", "native"]));
test("adds list of resolution platforms (win)", () =>
testResolutionPlatforms("win", ["win", "native"]));
test("adds list of resolution platforms (FaKe-PLAtfORm)", () =>
testResolutionPlatforms("FaKe-PLAtfORm", ["fake-platform", "native"]));
test("runs typescript compiler", () => {
TypeScriptValidation()(undefined, undefined, graphPosix, {
projectRoot: rootPathPosix,
} as SerializerOptions);
expect(child_process.spawnSync).toBeCalledTimes(1);
const spawnSyncParams = child_process.spawnSync.mock.calls[0];
const { [1]: args } = spawnSyncParams;
expect(args[0]).toEqual(
expect.stringMatching(
/[/\\]@msfast[/\\]typescript-platform-resolution.*[/\\]tsc.js$/
)
);
});
test("runs typescript compiler", () => {
TypeScriptValidation()(undefined, undefined, graphPosix, {
projectRoot: rootPathPosix,
} as SerializerOptions);
expect(child_process.spawnSync).toBeCalledTimes(1);
const spawnSyncParams = child_process.spawnSync.mock.calls[0];
const { [1]: args } = spawnSyncParams;
expect(args[0]).toEqual(
expect.stringMatching(
/[/\\]@msfast[/\\]typescript-platform-resolution.*[/\\]tsc.js$/
)
);
});
test("cleans up temporary tsconfig file when typescript compiler succeeds", () => {
TypeScriptValidation()(undefined, undefined, graphPosix, {
projectRoot: rootPathPosix,
} as SerializerOptions);
expect(fs.unlinkSync).toBeCalledTimes(1);
expect(fs.unlinkSync).toBeCalledWith(
expect.stringMatching(/[/\\]node_modules[/\\]tsconfig-metro.json$/)
);
});
test("cleans up temporary tsconfig file when typescript compiler fails", () => {
child_process.spawnSync.mockReturnValue({
status: 1,
});
expect(() => {
TypeScriptValidation()(undefined, undefined, graphPosix, {
projectRoot: rootPathPosix,
} as SerializerOptions);
}).toThrowError();
});
});

Просмотреть файл

@ -0,0 +1,4 @@
{
"extends": "rnx-kit-scripts/tsconfig-shared.json",
"include": ["src"]
}

Просмотреть файл

@ -6,6 +6,9 @@ const {
DuplicateDependencies,
} = require("@rnx-kit/metro-plugin-duplicates-checker");
const { MetroSerializer } = require("@rnx-kit/metro-serializer");
const {
TypeScriptValidation,
} = require("@rnx-kit/metro-plugin-typescript-validation");
module.exports = makeMetroConfig({
projectRoot: __dirname,
@ -13,6 +16,7 @@ module.exports = makeMetroConfig({
customSerializer: MetroSerializer([
CyclicDependencies(),
DuplicateDependencies(),
TypeScriptValidation(),
]),
},
});

Просмотреть файл

@ -33,6 +33,7 @@
"@rnx-kit/metro-config": "*",
"@rnx-kit/metro-plugin-cyclic-dependencies-detector": "*",
"@rnx-kit/metro-plugin-duplicates-checker": "*",
"@rnx-kit/metro-plugin-typescript-validation": "*",
"@rnx-kit/metro-serializer": "*",
"@types/react": "^17.0.2",
"@types/react-native": "^0.63.50",
@ -50,7 +51,7 @@
"reactNativeVersion": "^0.63",
"kitType": "app",
"bundle": {
"entryPath": "lib/src/index.js",
"entryPath": "src/index.ts",
"distPath": "dist",
"assetsPath": "dist",
"bundlePrefix": "main",

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -1442,6 +1442,11 @@
memory-streams "^0.1.3"
p-graph "^1.1.0"
"@msfast/typescript-platform-resolution@^4.2.4-midgard.0":
version "4.2.4-midgard.0"
resolved "https://registry.yarnpkg.com/@msfast/typescript-platform-resolution/-/typescript-platform-resolution-4.2.4-midgard.0.tgz#c29f4fbc7d7e772517f2eb207603831de6bfcff1"
integrity sha512-o0jiFEWlTVkvu3UbbF9rLel8sf1mMZBLpRrJl6wM3odYzXNZwbPXPGd6Nj3IYYAd+laEUkm1gqurDUJJWaiskQ==
"@nodelib/fs.scandir@2.1.4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69"