Skip to content

Commit 664b509

Browse files
authored
Merge pull request #39 from L-Qun/main
Extract the creation of the pnpm-sync.json file for easy reuse by Rush.
2 parents a148969 + 03c3e3a commit 664b509

File tree

7 files changed

+231
-158
lines changed

7 files changed

+231
-158
lines changed

packages/pnpm-sync-lib/etc/pnpm-sync-lib.api.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,27 @@ export interface IPnpmSyncCopyOptions {
5858
}
5959

6060
// @beta (undocumented)
61-
export interface IPnpmSyncPrepareOptions {
61+
export interface IPnpmSyncPrepareOptions extends IPnpmSyncUpdateFileBaseOptions {
6262
dotPnpmFolder: string;
6363
ensureFolderAsync: (folderPath: string) => Promise<void>;
64-
lockfileId?: string;
6564
lockfilePath: string;
66-
logMessageCallback: (options: ILogMessageCallbackOptions) => void;
6765
readPnpmLockfile: (lockfilePath: string, options: {
6866
ignoreIncompatible: boolean;
6967
}) => Promise<ILockfile | undefined>;
7068
}
7169

70+
// @beta (undocumented)
71+
export interface IPnpmSyncUpdateFileBaseOptions {
72+
lockfileId?: string;
73+
logMessageCallback: (options: ILogMessageCallbackOptions) => void;
74+
}
75+
76+
// @beta (undocumented)
77+
export interface IPnpmSyncUpdateFileOptions extends IPnpmSyncUpdateFileBaseOptions {
78+
sourceProjectFolder: string;
79+
targetFolders: Array<string>;
80+
}
81+
7282
// @beta (undocumented)
7383
export type IVersionSpecifier = string | {
7484
specifier: string;
@@ -95,13 +105,13 @@ export type LogMessageDetails = {
95105
} | {
96106
messageIdentifier: LogMessageIdentifier.PREPARE_REPLACING_FILE;
97107
pnpmSyncJsonPath: string;
98-
projectFolder: string;
108+
sourceProjectFolder: string;
99109
actualVersion: string;
100110
expectedVersion: string;
101111
} | {
102112
messageIdentifier: LogMessageIdentifier.PREPARE_WRITING_FILE;
103113
pnpmSyncJsonPath: string;
104-
projectFolder: string;
114+
sourceProjectFolder: string;
105115
} | {
106116
messageIdentifier: LogMessageIdentifier.PREPARE_FINISHING;
107117
lockfilePath: string;
@@ -173,4 +183,7 @@ export function pnpmSyncGetJsonVersion(): string;
173183
// @beta
174184
export function pnpmSyncPrepareAsync(options: IPnpmSyncPrepareOptions): Promise<void>;
175185

186+
// @beta (undocumented)
187+
export function pnpmSyncUpdateFileAsync(options: IPnpmSyncUpdateFileOptions): Promise<void>;
188+
176189
```

packages/pnpm-sync-lib/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "pnpm-sync-lib",
3-
"version": "0.2.9",
3+
"version": "0.3.0",
44
"description": "API library for integrating \"pnpm-sync\" with your toolchain",
55
"repository": {
66
"type": "git",

packages/pnpm-sync-lib/src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
*/
77

88
export { pnpmSyncCopyAsync, type IPnpmSyncCopyOptions } from './pnpmSyncCopy';
9-
export { pnpmSyncPrepareAsync, type IPnpmSyncPrepareOptions } from './pnpmSyncPrepare';
9+
export {
10+
pnpmSyncPrepareAsync,
11+
pnpmSyncUpdateFileAsync,
12+
type IPnpmSyncUpdateFileBaseOptions,
13+
type IPnpmSyncPrepareOptions,
14+
type IPnpmSyncUpdateFileOptions
15+
} from './pnpmSyncPrepare';
1016
export { LogMessageIdentifier, LogMessageKind, LogMessageDetails } from './interfaces';
1117
export { pnpmSyncGetJsonVersion } from './utilities';
1218
export type {

packages/pnpm-sync-lib/src/interfaces.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,14 @@ export type LogMessageDetails =
8080
| {
8181
messageIdentifier: LogMessageIdentifier.PREPARE_REPLACING_FILE;
8282
pnpmSyncJsonPath: string;
83-
projectFolder: string;
83+
sourceProjectFolder: string;
8484
actualVersion: string;
8585
expectedVersion: string;
8686
}
8787
| {
8888
messageIdentifier: LogMessageIdentifier.PREPARE_WRITING_FILE;
8989
pnpmSyncJsonPath: string;
90-
projectFolder: string;
90+
sourceProjectFolder: string;
9191
}
9292
| {
9393
messageIdentifier: LogMessageIdentifier.PREPARE_FINISHING;

packages/pnpm-sync-lib/src/pnpmSyncPrepare.ts

Lines changed: 143 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,26 @@ import { pnpmSyncGetJsonVersion } from './utilities';
1919
/**
2020
* @beta
2121
*/
22-
export interface IPnpmSyncPrepareOptions {
22+
export interface IPnpmSyncUpdateFileBaseOptions {
23+
/**
24+
* A lockfileId that can be used to recognize the `pnpm-lock.yaml`
25+
*/
26+
lockfileId?: string;
27+
28+
/**
29+
* A callback for reporting events during the operation.
30+
*
31+
* @remarks
32+
* `LogMessageKind.ERROR` events do NOT cause the promise to reject,
33+
* so they must be handled appropriately.
34+
*/
35+
logMessageCallback: (options: ILogMessageCallbackOptions) => void;
36+
}
37+
38+
/**
39+
* @beta
40+
*/
41+
export interface IPnpmSyncPrepareOptions extends IPnpmSyncUpdateFileBaseOptions {
2342
/**
2443
* The path to the `pnpm-lock.yaml` file
2544
*/
@@ -30,11 +49,6 @@ export interface IPnpmSyncPrepareOptions {
3049
*/
3150
dotPnpmFolder: string;
3251

33-
/**
34-
* A lockfileId that can be used to recognize the `pnpm-lock.yaml`
35-
*/
36-
lockfileId?: string;
37-
3852
/**
3953
* Environment-provided API to avoid an NPM dependency.
4054
* The "pnpm-sync" NPM package provides a reference implementation.
@@ -49,15 +63,121 @@ export interface IPnpmSyncPrepareOptions {
4963
lockfilePath: string,
5064
options: { ignoreIncompatible: boolean }
5165
) => Promise<ILockfile | undefined>;
66+
}
5267

68+
/**
69+
* @beta
70+
*/
71+
export interface IPnpmSyncUpdateFileOptions extends IPnpmSyncUpdateFileBaseOptions {
5372
/**
54-
* A callback for reporting events during the operation.
73+
* The folder path of the project whose build outputs will get synced into the node_modules folder
74+
* of dependent projects.
75+
* @remarks
76+
* It be an absolute file path, such as `/path/to/my-library`, which will have a sync file
77+
* `/path/to/my-library/node_modules/.pnpm-sync.json`
78+
*/
79+
sourceProjectFolder: string;
80+
81+
/**
82+
* A list of destination folders that `pnpmSyncCopyAsync()` will copy into.
5583
*
5684
* @remarks
57-
* `LogMessageKind.ERROR` events do NOT cause the promise to reject,
58-
* so they must be handled appropriately.
85+
* Each string should be an absolute file path to a folder, whose name will generally
86+
* be the same as the `sourceProjectFolder`'s directory name.
87+
*
88+
* For example:
89+
* `/path/to/my-workspace/node_modules/.pnpm/my-library@1.2.3/node_modules/my-library`
90+
*
91+
* When your tool needs to update this set of files, providing the `IPnpmSyncUpdateFileBaseOptions.lockfileId`
92+
* will enable `pnpmSyncUpdateFileAsync()` to clear the old target folders from `.pnpm-sync.json`
93+
* before adding the new ones.
5994
*/
60-
logMessageCallback: (options: ILogMessageCallbackOptions) => void;
95+
targetFolders: Array<string>;
96+
}
97+
98+
/**
99+
* @beta
100+
*/
101+
export async function pnpmSyncUpdateFileAsync(options: IPnpmSyncUpdateFileOptions): Promise<void> {
102+
const { lockfileId, logMessageCallback, sourceProjectFolder, targetFolders } = options;
103+
104+
const pnpmSyncJsonFolder = `${sourceProjectFolder}/node_modules`;
105+
const pnpmSyncJsonPath = `${pnpmSyncJsonFolder}/.pnpm-sync.json`;
106+
const expectedPnpmSyncJsonVersion: string = pnpmSyncGetJsonVersion();
107+
108+
let pnpmSyncJsonFile: IPnpmSyncJson = {
109+
version: expectedPnpmSyncJsonVersion,
110+
postbuildInjectedCopy: {
111+
sourceFolder: '..', // path from pnpmSyncJsonFolder to sourceProjectFolder
112+
targetFolders: []
113+
}
114+
};
115+
116+
// if .pnpm-sync.json already exists, read it first
117+
if (fs.existsSync(pnpmSyncJsonPath)) {
118+
let existingPnpmSyncJsonFile: IPnpmSyncJson | undefined;
119+
try {
120+
existingPnpmSyncJsonFile = JSON.parse(fs.readFileSync(pnpmSyncJsonPath).toString());
121+
} catch (e) {
122+
// no-catch
123+
// Regenerate .pnpm-sync.json when failed to load the current one
124+
}
125+
126+
if (existingPnpmSyncJsonFile) {
127+
const actualPnpmSyncJsonVersion: string = existingPnpmSyncJsonFile.version;
128+
if (actualPnpmSyncJsonVersion === expectedPnpmSyncJsonVersion) {
129+
// If a lockfileId is provided
130+
// then all entries with this lockfileId should be deleted
131+
// they will be regenerated later
132+
if (lockfileId) {
133+
const filteredTargetFolders = existingPnpmSyncJsonFile.postbuildInjectedCopy.targetFolders.filter(
134+
(targetFolder) => targetFolder?.lockfileId !== lockfileId
135+
);
136+
existingPnpmSyncJsonFile.postbuildInjectedCopy.targetFolders = filteredTargetFolders;
137+
}
138+
pnpmSyncJsonFile = existingPnpmSyncJsonFile;
139+
} else {
140+
logMessageCallback({
141+
message: `The .pnpm-sync.json file in ${pnpmSyncJsonFolder} has an incompatible version; pnpm-sync will regenerate it.`,
142+
messageKind: LogMessageKind.VERBOSE,
143+
details: {
144+
messageIdentifier: LogMessageIdentifier.PREPARE_REPLACING_FILE,
145+
pnpmSyncJsonPath,
146+
sourceProjectFolder,
147+
actualVersion: actualPnpmSyncJsonVersion,
148+
expectedVersion: expectedPnpmSyncJsonVersion
149+
}
150+
});
151+
}
152+
}
153+
}
154+
155+
const existingTargetFolderSet: Set<string> = new Set();
156+
157+
for (const targetFolder of pnpmSyncJsonFile.postbuildInjectedCopy.targetFolders) {
158+
existingTargetFolderSet.add(targetFolder.folderPath);
159+
}
160+
161+
for (const targetFolder of targetFolders) {
162+
let relativePath: string = path.relative(pnpmSyncJsonFolder, targetFolder);
163+
164+
// the final path in .pnpm-sync.json will always in posix style
165+
relativePath = relativePath.split(path.sep).join(path.posix.sep);
166+
167+
if (!existingTargetFolderSet.has(relativePath)) {
168+
const targetFolderItem: ITargetFolder = {
169+
folderPath: relativePath
170+
};
171+
172+
if (lockfileId) {
173+
targetFolderItem.lockfileId = lockfileId;
174+
}
175+
176+
pnpmSyncJsonFile.postbuildInjectedCopy.targetFolders.push(targetFolderItem);
177+
}
178+
}
179+
180+
await fs.promises.writeFile(pnpmSyncJsonPath, JSON.stringify(pnpmSyncJsonFile, null, 2));
61181
}
62182

63183
/**
@@ -150,7 +270,7 @@ export async function pnpmSyncPrepareAsync(options: IPnpmSyncPrepareOptions): Pr
150270
const pnpmLockFolder = path.dirname(lockfilePath);
151271

152272
// generate a map, where key is the absolute path of the injectedDependency, value is all available paths in .pnpm folder
153-
const injectedDependencyToFilePathSet: Map<string, Set<string>> = new Map();
273+
const injectedDependencyToFilePathSet: Map<string, Array<string>> = new Map();
154274
for (const [injectedDependency, injectedDependencyVersionSet] of injectedDependencyToVersion) {
155275
for (const injectedDependencyVersion of injectedDependencyVersionSet) {
156276
// this logic is heavily depends on pnpm-lock formate
@@ -159,7 +279,7 @@ export async function pnpmSyncPrepareAsync(options: IPnpmSyncPrepareOptions): Pr
159279
let injectedDependencyPath = injectedDependencyVersion.split('(')[0].slice('file:'.length);
160280
injectedDependencyPath = path.resolve(pnpmLockFolder, injectedDependencyPath);
161281
if (!injectedDependencyToFilePathSet.has(injectedDependencyPath)) {
162-
injectedDependencyToFilePathSet.set(injectedDependencyPath, new Set());
282+
injectedDependencyToFilePathSet.set(injectedDependencyPath, []);
163283
}
164284

165285
const fullPackagePath = path.join(
@@ -169,18 +289,18 @@ export async function pnpmSyncPrepareAsync(options: IPnpmSyncPrepareOptions): Pr
169289
injectedDependency
170290
);
171291

172-
injectedDependencyToFilePathSet.get(injectedDependencyPath)?.add(fullPackagePath);
292+
injectedDependencyToFilePathSet.get(injectedDependencyPath)?.push(fullPackagePath);
173293
}
174294
}
175295

176296
// now, we have everything we need to generate the the .pnpm-sync.json
177297
// console.log('injectedDependencyToFilePathSet =>', injectedDependencyToFilePathSet);
178-
for (const [projectFolder, targetFolderSet] of injectedDependencyToFilePathSet) {
179-
if (targetFolderSet.size === 0) {
298+
for (const [sourceProjectFolder, targetFolders] of injectedDependencyToFilePathSet) {
299+
if (targetFolders.length === 0) {
180300
continue;
181301
}
182302

183-
const pnpmSyncJsonFolder = `${projectFolder}/node_modules`;
303+
const pnpmSyncJsonFolder = `${sourceProjectFolder}/node_modules`;
184304
const pnpmSyncJsonPath = `${pnpmSyncJsonFolder}/.pnpm-sync.json`;
185305

186306
logMessageCallback({
@@ -189,7 +309,7 @@ export async function pnpmSyncPrepareAsync(options: IPnpmSyncPrepareOptions): Pr
189309
details: {
190310
messageIdentifier: LogMessageIdentifier.PREPARE_WRITING_FILE,
191311
pnpmSyncJsonPath,
192-
projectFolder
312+
sourceProjectFolder
193313
}
194314
});
195315

@@ -202,81 +322,12 @@ export async function pnpmSyncPrepareAsync(options: IPnpmSyncPrepareOptions): Pr
202322
await ensureFolderAsync(pnpmSyncJsonFolder);
203323
}
204324

205-
const expectedPnpmSyncJsonVersion: string = pnpmSyncGetJsonVersion();
206-
207-
let pnpmSyncJsonFile: IPnpmSyncJson = {
208-
version: expectedPnpmSyncJsonVersion,
209-
postbuildInjectedCopy: {
210-
sourceFolder: '..', // path from pnpmSyncJsonFolder to projectFolder
211-
targetFolders: []
212-
}
213-
};
214-
215-
// if .pnpm-sync.json already exists, read it first
216-
if (fs.existsSync(pnpmSyncJsonPath)) {
217-
let existingPnpmSyncJsonFile: IPnpmSyncJson | undefined;
218-
try {
219-
existingPnpmSyncJsonFile = JSON.parse(fs.readFileSync(pnpmSyncJsonPath).toString());
220-
} catch (e) {
221-
// no-catch
222-
// Regenerate .pnpm-sync.json when failed to load the current one
223-
}
224-
225-
if (existingPnpmSyncJsonFile) {
226-
const actualPnpmSyncJsonVersion: string = existingPnpmSyncJsonFile.version;
227-
if (actualPnpmSyncJsonVersion === expectedPnpmSyncJsonVersion) {
228-
// If a lockfileId is provided
229-
// then all entries with this lockfileId should be deleted
230-
// they will be regenerated later
231-
if (lockfileId) {
232-
const filteredTargetFolders = existingPnpmSyncJsonFile.postbuildInjectedCopy.targetFolders.filter(
233-
(targetFolder) => targetFolder?.lockfileId !== lockfileId
234-
);
235-
existingPnpmSyncJsonFile.postbuildInjectedCopy.targetFolders = filteredTargetFolders;
236-
}
237-
pnpmSyncJsonFile = existingPnpmSyncJsonFile;
238-
} else {
239-
logMessageCallback({
240-
message: `The .pnpm-sync.json file in ${pnpmSyncJsonFolder} has an incompatible version; pnpm-sync will regenerate it.`,
241-
messageKind: LogMessageKind.VERBOSE,
242-
details: {
243-
messageIdentifier: LogMessageIdentifier.PREPARE_REPLACING_FILE,
244-
pnpmSyncJsonPath,
245-
projectFolder,
246-
actualVersion: actualPnpmSyncJsonVersion,
247-
expectedVersion: expectedPnpmSyncJsonVersion
248-
}
249-
});
250-
}
251-
}
252-
}
253-
254-
const existingTargetFolderSet: Set<string> = new Set();
255-
256-
for (const targetFolder of pnpmSyncJsonFile.postbuildInjectedCopy.targetFolders) {
257-
existingTargetFolderSet.add(targetFolder.folderPath);
258-
}
259-
260-
for (const targetFolder of targetFolderSet) {
261-
let relativePath: string = path.relative(pnpmSyncJsonFolder, targetFolder);
262-
263-
// the final path in .pnpm-sync.json will always in posix style
264-
relativePath = relativePath.split(path.sep).join(path.posix.sep);
265-
266-
if (!existingTargetFolderSet.has(relativePath)) {
267-
const targetFolderItem: ITargetFolder = {
268-
folderPath: relativePath
269-
};
270-
271-
if (lockfileId) {
272-
targetFolderItem.lockfileId = lockfileId;
273-
}
274-
275-
pnpmSyncJsonFile.postbuildInjectedCopy.targetFolders.push(targetFolderItem);
276-
}
277-
}
278-
279-
await fs.promises.writeFile(pnpmSyncJsonPath, JSON.stringify(pnpmSyncJsonFile, null, 2));
325+
await pnpmSyncUpdateFileAsync({
326+
sourceProjectFolder,
327+
targetFolders,
328+
logMessageCallback,
329+
lockfileId
330+
});
280331
}
281332

282333
const endTime = process.hrtime.bigint();

0 commit comments

Comments
 (0)