Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support transitive injected dependency #19

Merged
merged 4 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
12 changes: 11 additions & 1 deletion packages/pnpm-sync-lib/etc/pnpm-sync-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface ILockfile {
importers: Record<string, ILockfileImporter>;
// (undocumented)
lockfileVersion: number | string;
// (undocumented)
packages: Record<string, ILockfilePackage>;
}

// @beta (undocumented)
Expand All @@ -30,6 +32,12 @@ export interface ILockfileImporter {
optionalDependencies?: Record<string, IVersionSpecifier>;
}

// @beta (undocumented)
export interface ILockfilePackage {
dependencies?: Record<string, IVersionSpecifier>;
optionalDependencies?: Record<string, IVersionSpecifier>;
}

// @beta (undocumented)
export interface ILogMessageCallbackOptions {
// (undocumented)
Expand Down Expand Up @@ -82,6 +90,8 @@ export interface IPnpmSyncCopyOptions {

// @beta (undocumented)
export interface IPnpmSyncPrepareOptions {
// (undocumented)
ensureFolder: (folderPath: string) => Promise<void>;
// (undocumented)
lockfilePath: string;
// (undocumented)
Expand Down Expand Up @@ -131,6 +141,6 @@ export enum LogMessageKind {
export function pnpmSyncCopyAsync({ pnpmSyncJsonPath, getPackageIncludedFiles, forEachAsyncWithConcurrency, ensureFolder, logMessageCallback }: IPnpmSyncCopyOptions): Promise<void>;

// @beta
export function pnpmSyncPrepareAsync({ lockfilePath, storePath, readPnpmLockfile, logMessageCallback }: IPnpmSyncPrepareOptions): Promise<void>;
export function pnpmSyncPrepareAsync({ lockfilePath, storePath, ensureFolder, readPnpmLockfile, logMessageCallback }: IPnpmSyncPrepareOptions): Promise<void>;

```
2 changes: 1 addition & 1 deletion packages/pnpm-sync-lib/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pnpm-sync-lib",
"version": "0.1.6",
"version": "0.1.7",
"description": "API library for integrating \"pnpm-sync\" with your toolchain",
"repository": {
"type": "git",
Expand Down
1 change: 1 addition & 0 deletions packages/pnpm-sync-lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export { LogMessageIdentifier, LogMessageKind } from './interfaces';
export type {
ILockfile,
ILockfileImporter,
ILockfilePackage,
IVersionSpecifier,
IDependencyMeta,
ILogMessageCallbackOptions
Expand Down
11 changes: 11 additions & 0 deletions packages/pnpm-sync-lib/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ export interface ILockfileImporter {
dependenciesMeta?: Record<string, IDependencyMeta>;
}
g-chao marked this conversation as resolved.
Show resolved Hide resolved

/**
* @beta
*/
export interface ILockfilePackage {
/** The list of dependencies and the resolved version */
dependencies?: Record<string, IVersionSpecifier>;
/** The list of optional dependencies and the resolved version */
optionalDependencies?: Record<string, IVersionSpecifier>;
}

g-chao marked this conversation as resolved.
Show resolved Hide resolved
/**
* An abstraction of the pnpm lockfile
g-chao marked this conversation as resolved.
Show resolved Hide resolved
*
Expand All @@ -110,4 +120,5 @@ export interface ILockfileImporter {
export interface ILockfile {
g-chao marked this conversation as resolved.
Show resolved Hide resolved
lockfileVersion: number | string;
g-chao marked this conversation as resolved.
Show resolved Hide resolved
importers: Record<string, ILockfileImporter>;
g-chao marked this conversation as resolved.
Show resolved Hide resolved
packages: Record<string, ILockfilePackage>;
}
75 changes: 71 additions & 4 deletions packages/pnpm-sync-lib/src/pnpmSyncPrepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
LogMessageKind,
LogMessageIdentifier,
IPnpmSyncJson,
IVersionSpecifier
IVersionSpecifier,
ILockfilePackage
} from './interfaces';

/**
Expand All @@ -17,6 +18,7 @@ import {
export interface IPnpmSyncPrepareOptions {
lockfilePath: string;
storePath: string;
ensureFolder: (folderPath: string) => Promise<void>;
readPnpmLockfile: (
lockfilePath: string,
options: { ignoreIncompatible: boolean }
Expand All @@ -38,6 +40,7 @@ export interface IPnpmSyncPrepareOptions {
export async function pnpmSyncPrepareAsync({
g-chao marked this conversation as resolved.
Show resolved Hide resolved
lockfilePath,
storePath,
ensureFolder,
readPnpmLockfile,
logMessageCallback
}: IPnpmSyncPrepareOptions): Promise<void> {
Expand Down Expand Up @@ -78,6 +81,9 @@ export async function pnpmSyncPrepareAsync({
// find injected dependency and all its available versions
const injectedDependencyToVersion: Map<string, Set<string>> = getInjectedDependencyToVersion(pnpmLockfile);

// check and process transitive injected dependency
processTransitiveInjectedDependency(pnpmLockfile, injectedDependencyToVersion);

// get pnpm-lock.yaml folder path
const pnpmLockFolder = lockfilePath.slice(0, lockfilePath.length - 'pnpm-lock.yaml'.length);

g-chao marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -107,9 +113,14 @@ export async function pnpmSyncPrepareAsync({
continue;
}

// make sure the node_modules folder exists
if (!fs.existsSync(`${projectFolder}/node_modules`)) {
continue;
// make sure the node_modules folder exists, if not, create it
// why?
// in the transitive injected dependencies case
// it is possible that node_modules folder a package is not exist
// but we need to generate .pnpm-sync.json for that package
const projectNodeModulesFolderPath = `${projectFolder}/node_modules`;
if (!fs.existsSync(projectNodeModulesFolderPath)) {
await ensureFolder(projectNodeModulesFolderPath);
}

const pnpmSyncJsonPath = `${projectFolder}/node_modules/.pnpm-sync.json`;
Expand Down Expand Up @@ -229,3 +240,59 @@ function processDependencies(
}
}
}

// process all dependencies and devDependencies to find potential transitive injected dependencies
// and add to injectedDependencyToFilePath map
function processTransitiveInjectedDependency(
pnpmLockfile: ILockfile | undefined,
injectedDependencyToVersion: Map<string, Set<string>>
): void {
const potentialTransitiveInjectedDependencyVersionQueue: Array<string> = [];
for (const injectedDependencyVersion of [...injectedDependencyToVersion.values()]) {
for (const version of [...injectedDependencyVersion]) {
potentialTransitiveInjectedDependencyVersionQueue.push(version);
g-chao marked this conversation as resolved.
Show resolved Hide resolved
}
}

const lockfilePackages: Record<string, ILockfilePackage> | undefined = pnpmLockfile?.packages;

if (lockfilePackages) {
while (potentialTransitiveInjectedDependencyVersionQueue.length > 0) {
const transitiveInjectedDependencyVersion: string | undefined =
potentialTransitiveInjectedDependencyVersionQueue.shift();
if (transitiveInjectedDependencyVersion) {
const { dependencies, optionalDependencies } = lockfilePackages[transitiveInjectedDependencyVersion];
processInjectedDependencies(
dependencies,
injectedDependencyToVersion,
potentialTransitiveInjectedDependencyVersionQueue
);
processInjectedDependencies(
optionalDependencies,
injectedDependencyToVersion,
potentialTransitiveInjectedDependencyVersionQueue
);
}
}
}
}
function processInjectedDependencies(
dependencies: Record<string, IVersionSpecifier> | undefined,
injectedDependencyToVersion: Map<string, Set<string>>,
potentialTransitiveInjectedDependencyVersionQueue: Array<string>
): void {
if (dependencies) {
for (const [dependency, specifier] of Object.entries(dependencies)) {
const specifierToUse: string = typeof specifier === 'string' ? specifier : specifier.version;
g-chao marked this conversation as resolved.
Show resolved Hide resolved

// if the version is set with file: protocol, then it is a transitive injected dependency
if (specifierToUse.startsWith('file:')) {
if (!injectedDependencyToVersion.has(dependency)) {
injectedDependencyToVersion.set(dependency, new Set());
}
injectedDependencyToVersion.get(dependency)?.add(specifierToUse);
potentialTransitiveInjectedDependencyVersionQueue.push(specifierToUse);
}
}
}
}
2 changes: 1 addition & 1 deletion packages/pnpm-sync/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pnpm-sync",
"version": "0.1.6",
"version": "0.1.7",
"description": "Recopy injected dependencies whenever a project is rebuilt in your PNPM workspace",
"keywords": [
"rush",
Expand Down
27 changes: 25 additions & 2 deletions packages/pnpm-sync/src/start.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Command } from 'commander';
import { pnpmSyncCopyAsync, pnpmSyncPrepareAsync, type ILogMessageCallbackOptions } from 'pnpm-sync-lib';
import {
type ILockfile,
type ILockfilePackage,
pnpmSyncCopyAsync,
pnpmSyncPrepareAsync,
type ILogMessageCallbackOptions
} from 'pnpm-sync-lib';
import { FileSystem, Async } from '@rushstack/node-core-library';
import { PackageExtractor } from '@rushstack/package-extractor';
import { readWantedLockfile, Lockfile } from '@pnpm/lockfile-file';
Expand Down Expand Up @@ -45,6 +51,7 @@ program
await pnpmSyncPrepareAsync({
lockfilePath: lockfile,
storePath: store,
ensureFolder: FileSystem.ensureFolderAsync,
readPnpmLockfile: async (
lockfilePath: string,
options: {
Expand All @@ -58,7 +65,23 @@ program
if (lockfile === null) {
return undefined;
} else {
return lockfile;
const lockfilePackages: Record<string, ILockfilePackage> = {};
g-chao marked this conversation as resolved.
Show resolved Hide resolved
for (const packagePath in lockfile.packages) {
g-chao marked this conversation as resolved.
Show resolved Hide resolved
if (lockfile.packages[packagePath]) {
g-chao marked this conversation as resolved.
Show resolved Hide resolved
lockfilePackages[packagePath] = {
dependencies: lockfile.packages[packagePath].dependencies,
optionalDependencies: lockfile.packages[packagePath].optionalDependencies
g-chao marked this conversation as resolved.
Show resolved Hide resolved
};
}
}

const result: ILockfile = {
lockfileVersion: lockfile.lockfileVersion,
importers: lockfile.importers,
packages: lockfilePackages
};

return result;
}
},
logMessageCallback: (options: ILogMessageCallbackOptions) => {
Expand Down
Loading