Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/middleware/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"test": "vitest --run"
},
"files": [
"dist"
],
Expand Down Expand Up @@ -43,6 +46,7 @@
"tslib": "^2.3.0"
},
"devDependencies": {
"vitest": "^4.0.18",
"@react-native/dev-middleware": "~0.76.0",
"@types/ejs": "^3.1.5",
"@types/express": "^5.0.3",
Expand Down
70 changes: 70 additions & 0 deletions packages/middleware/src/__tests__/pnp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { resolvePackagePathFromVirtualPath, isPnP } from '../pnp.js';

describe('isPnP', () => {
describe('when the project is a Yarn Plug\'n\'Play project', () => {
beforeAll(() => {
process.versions.pnp = '3';
});

afterAll(() => {
delete process.versions.pnp;
})

it('should return true', () => {
expect(isPnP()).toBe(true);
});
});

describe('when the project is not a Yarn Plug\'n\'Play project', () => {
beforeAll(() => {
delete process.versions.pnp;
});

it('should return false', () => {
expect(isPnP()).toBe(false);
});
});
});

describe('resolvePackagePathFromVirtualPath', () => {
describe('when the virtual path is provided', () => {
const SCOPED_PACKAGE_VIRTUAL_PATH = '/path/to/project/.yarn/__virtual__/some-rozenite-plugin-virtual-39bf83846a/0/cache/@rozenite-tanstack-query-plugin-npm-1.2.0-723ced2ce3-a6b5bf6f06.zip/node_modules/@rozenite/tanstack-query-plugin/dist/react-native.cjs';
const NON_SCOPED_PACKAGE_VIRTUAL_PATH = '/path/to/project/.yarn/__virtual__/some-rozenite-plugin-virtual-39bf83846a/0/cache/some-rozenite-plugin-npm-1.2.0-723ced2ce3-a6b5bf6f06.zip/node_modules/some-rozenite-plugin/index.js';

it('should return the package name', () => {
const scoped = resolvePackagePathFromVirtualPath(SCOPED_PACKAGE_VIRTUAL_PATH);
const nonScoped = resolvePackagePathFromVirtualPath(NON_SCOPED_PACKAGE_VIRTUAL_PATH);

expect(scoped.basePath).toBe(
'/path/to/project/.yarn/__virtual__/some-rozenite-plugin-virtual-39bf83846a/0/cache/@rozenite-tanstack-query-plugin-npm-1.2.0-723ced2ce3-a6b5bf6f06.zip/node_modules/@rozenite/tanstack-query-plugin'
);
expect(nonScoped.basePath).toBe(
'/path/to/project/.yarn/__virtual__/some-rozenite-plugin-virtual-39bf83846a/0/cache/some-rozenite-plugin-npm-1.2.0-723ced2ce3-a6b5bf6f06.zip/node_modules/some-rozenite-plugin'
);

expect(scoped.packageName).toBe('@rozenite/tanstack-query-plugin');
expect(nonScoped.packageName).toBe('some-rozenite-plugin');
});
});

describe('when the unplugged virtual path is provided', () => {
const SCOPED_PACKAGE_UNPLUGGED_PATH = '/path/to/project/.yarn/unplugged/@rozenite-tanstack-query-plugin-virtual-39bf83846a/node_modules/@rozenite/tanstack-query-plugin/dist/react-native.cjs';
const NON_SCOPED_PACKAGE_UNPLUGGED_PATH = '/path/to/project/.yarn/unplugged/some-rozenite-plugin-virtual-39bf83846a/node_modules/some-rozenite-plugin/dist/some-rozenite-plugin/index.js';

it('should return the package name', () => {
const scoped = resolvePackagePathFromVirtualPath(SCOPED_PACKAGE_UNPLUGGED_PATH);
const nonScoped = resolvePackagePathFromVirtualPath(NON_SCOPED_PACKAGE_UNPLUGGED_PATH);

expect(scoped.basePath).toBe(
'/path/to/project/.yarn/unplugged/@rozenite-tanstack-query-plugin-virtual-39bf83846a/node_modules/@rozenite/tanstack-query-plugin'
);
expect(nonScoped.basePath).toBe(
'/path/to/project/.yarn/unplugged/some-rozenite-plugin-virtual-39bf83846a/node_modules/some-rozenite-plugin'
);

expect(scoped.packageName).toBe('@rozenite/tanstack-query-plugin');
expect(nonScoped.packageName).toBe('some-rozenite-plugin');
});
});
});
61 changes: 58 additions & 3 deletions packages/middleware/src/auto-discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { logger } from './logger.js';
import { getNodeModulesPaths } from './node-modules-paths.js';
import { ROZENITE_MANIFEST } from './constants.js';
import { RozeniteConfig } from './config.js';
import { isPnP, resolvePackagePathFromVirtualPath } from './pnp.js';

const require = createRequire(import.meta.url);

Expand Down Expand Up @@ -85,8 +86,62 @@ export const getInstalledPlugins = (
return getIncludedPlugins(options);
}

const nodeModulesPaths = getNodeModulesPaths();
return isPnP() ? getInstalledPluginsFromPnP(options) : getInstalledPluginsFromNodeModules(options);
};

const getInstalledPluginsFromPnP = (options: RozeniteConfig): InstalledPlugin[] => {
const plugins: InstalledPlugin[] = [];
const packageJsonPath = path.join(options.projectRoot, 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));

const dependencies = [
...Object.keys(packageJson.dependencies || {}),
...Object.keys(packageJson.devDependencies || {}),
];

for (const dependency of dependencies) {
let packagePath: string;
let actualPackageName: string;

try {
let resolvedPackagePath: string;

try {
// First try to resolve the `<packageName>/package.json` path.
resolvedPackagePath = require.resolve(path.join(dependency, 'package.json'), { paths: [options.projectRoot] });
} catch {
// If the path to the package.json is not found, try to resolve the entry point.
resolvedPackagePath = require.resolve(dependency, { paths: [options.projectRoot] });
}

const resolvedVirtualPath = resolvePackagePathFromVirtualPath(resolvedPackagePath);

packagePath = resolvedVirtualPath.basePath;
actualPackageName = resolvedVirtualPath.packageName;
} catch {
continue;
}

if (
options.exclude &&
options.exclude.includes(actualPackageName)
) {
continue;
}

const plugin = tryExtractPlugin(packagePath, actualPackageName);

if (plugin) {
plugins.push(plugin);
}
}

return plugins;
}

const getInstalledPluginsFromNodeModules = (options: RozeniteConfig): InstalledPlugin[] => {
const plugins: InstalledPlugin[] = [];
const nodeModulesPaths = getNodeModulesPaths();

for (const nodeModulesPath of nodeModulesPaths) {
try {
Expand Down Expand Up @@ -168,12 +223,12 @@ export const getInstalledPlugins = (
packagePath = path.join(nodeModulesPath, packageName);
actualPackageName = packageName;

const plugin = tryExtractPlugin(packagePath, actualPackageName);

if (options.exclude && options.exclude.includes(actualPackageName)) {
continue;
}

const plugin = tryExtractPlugin(packagePath, actualPackageName);

if (plugin) {
plugins.push(plugin);
}
Expand Down
39 changes: 39 additions & 0 deletions packages/middleware/src/pnp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import path from 'path';

export const isPnP = (): boolean => {
return typeof process.versions.pnp !== 'undefined';
}

export interface VirtualPath {
packageName: string;
basePath: string;
}

export const resolvePackagePathFromVirtualPath = (virtualPath: string): VirtualPath => {
const nodeModulesPath = `node_modules${path.sep}`;
const nodeModulesIndex = virtualPath.lastIndexOf(nodeModulesPath);

if (nodeModulesIndex === -1) {
throw new Error(`Could not find package path: ${virtualPath}`);
}

const afterNodeModules = virtualPath.substring(nodeModulesIndex + nodeModulesPath.length);
const beforeNodeModules = virtualPath.substring(0, nodeModulesIndex + nodeModulesPath.length);
let packageSegments: number;

if (afterNodeModules.startsWith('@')) {
// Scoped package
packageSegments = 2;
} else {
// Non-scoped package
packageSegments = 1;
}

const parts = afterNodeModules.split(path.sep);
const packageName = parts.slice(0, packageSegments).join(path.sep);

return {
packageName: packageName,
basePath: beforeNodeModules + packageName,
};
}
Loading