forked from woocommerce/woocommerce
-
Notifications
You must be signed in to change notification settings - Fork 0
/
.pnpmfile.cjs
265 lines (226 loc) · 9.66 KB
/
.pnpmfile.cjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
/**
* External dependencies.
*/
const fs = require( 'fs' );
const path = require( 'path' );
// A cache for package files so that we don't keep loading them unnecessarily.
const packageFileCache = {};
/**
* Loads a package file or pull it from the cache.
*
* @param {string} packagePath The path to the package directory.
*
* @return {Object} The package file.
*/
function loadPackageFile( packagePath ) {
// Resolve the absolute path for consistency when loading and updating.
packagePath = path.resolve( __dirname, packagePath );
if ( packageFileCache[ packagePath ] ) {
return packageFileCache[ packagePath ];
}
const packageFile = JSON.parse(
fs.readFileSync( path.join( packagePath, 'package.json' ), 'utf8' )
);
packageFileCache[ packagePath ] = packageFile;
return packageFile;
}
/**
* Updates a package file on disk and in the cache.
*
* @param {string} packagePath The path to the package file to update.
* @param {Object} packageFile The new package file contents.
*/
function updatePackageFile( packagePath, packageFile ) {
// Resolve the absolute path for consistency when loading and updating.
packagePath = path.resolve( __dirname, packagePath );
packageFileCache[ packagePath ] = packageFile;
fs.writeFileSync(
path.join( packagePath, 'package.json' ),
// Make sure to keep the newline at the end of the file.
JSON.stringify( packageFile, null, '\t' ) + "\n",
'utf8'
);
}
/**
* Gets the outputs for a given package.
*
* @param {string} packageFile The package file to read file outputs from.
*
* @return {Object.<string, Array.<string>} The include and exclude globs describing the package's files.
*/
function getPackageOutputs( packageFile ) {
// All of the outputs should be relative to the package's path instead of the monorepo root.
// This is how wireit expects the files to be configured.
const basePath = path.join( 'node_modules', packageFile.name );
// We're going to construct the package outputs according to the same rules that NPM follows when packaging.
// Note: In order to work with wireit optimally we need to put the excludes at the very end of the list.
const packageOutputs = {
include: [],
exclude: [],
};
// Packages that don't explicitly define files should be excluded from the fingerprint entirely.
if ( ! packageFile.files ) {
return packageOutputs;
}
// We're going to make the glob relative to the package directory instead of the dependency directory.
// To do this though, we need to transform the path a little bit.
for ( const fileGlob of packageFile.files ) {
let relativeGlob = fileGlob;
// Negation globs need to move the exclamation point to the beginning of the output glob.
let negation = relativeGlob.startsWith( '!' ) ? true : false;
if ( negation ) {
relativeGlob = relativeGlob.substring( 1 );
}
// Remove leading slashes.
if ( relativeGlob.startsWith( '/' ) ) {
relativeGlob = relativeGlob.substring( 1 );
}
// Now we can construct a glob relative to the package directory.
if ( negation ) {
packageOutputs.exclude.push( `!${ basePath }/${ relativeGlob }` );
} else {
packageOutputs.include.push( `${ basePath }/${ relativeGlob }` );
}
}
return packageOutputs;
}
/**
* Checks to see if a package is linked and returns the path if it is.
*
* @param {string} packagePath The path to the package we're checking.
* @param {string} lockVersion The package version from the lock file.
*
* @return {string|false} Returns the linked package path or false if the package is not linked.
*/
function isLinkedPackage( packagePath, lockVersion ) {
// We can parse the version that PNPM stores in order to get the relative path to the package.
// file: dependencies use a relative path with dependencies listed in parentheses after it.
// workspace: dependencies just store the relative path from the package itself.
const match = lockVersion.match( /^(?:file:|link:)([^\^<>:"|?*()]+)/i );
if ( ! match ) {
return false;
}
let relativePath = match[ 1 ];
// Linked paths are relative to the package instead of the monorepo.
if ( lockVersion.startsWith( 'link:' ) ) {
relativePath = path.join( packagePath, relativePath );
}
// Local relative paths won't always start with './' so we want to make sure that the path
// exists before we return it. We do this instead of checking for the existeince of the
// package.json file later because we want to be able to detect cases where the
// package file should exist but for some reason can't be loaded.
if ( ! match[ 1 ].startsWith( '.' ) && ! fs.existsSync( relativePath ) ) {
return false;
}
return relativePath;
}
/**
* Gets the paths to any packages linked in the lock file.
*
* @param {string} packagePath The path to the package to check.
* @param {Object} lockPackage The package information from the lock file.
*
* @return {Array.<Object>} The linked package file keyed by the relative path to the package.
*/
function getLinkedPackages( packagePath, lockPackage ) {
// Include both the dependencies and devDependencies in the list of packages to check.
const possiblePackages = Object.assign(
{},
lockPackage.dependencies || {},
lockPackage.devDependencies || {}
);
// We need to check all of the possible packages and figure out whether or not they're linked.
const linkedPackages = {};
for ( const packageName in possiblePackages ) {
const linkedPackagePath = isLinkedPackage(
packagePath,
possiblePackages[ packageName ],
);
if ( ! linkedPackagePath ) {
continue;
}
// Load the linked package file and mark it as a dependency.
linkedPackages[ linkedPackagePath ] =
loadPackageFile( linkedPackagePath );
}
return Object.values( linkedPackages );
}
/**
* Hooks up all of the dependency outputs as file dependencies for wireit to fingerprint them.
*
* @param {Object.<string, Object>} lockPackages The paths to all of the packages we're processing.
* @param {Object} context The hook context object.
* @param {Function.<string>} context.log Logs a message to the console.
*/
function updateWireitDependencies( lockPackages, context ) {
context.log( '[wireit] Updating Dependency Lists' );
// Rather than using wireit for task orchestration we are going to rely on PNPM in order to provide a more consistent developer experience.
// In order to achieve this, however, we need to make sure that all of the dependencies are included in the fingerprint. If we don't, then
// changes in dependency packages won't invalidate the cache and downstream packages won't be rebuilt unless they themselves change. This
// is problematic because it means that we can't rely on the cache to be up to date and we'll have to rebuild everything every time.
for ( const packagePath in lockPackages ) {
const packageFile = loadPackageFile( packagePath );
// We only care about packages using wireit.
if ( ! packageFile.wireit ) {
continue;
}
context.log( `[wireit][${ packageFile.name }] Updating Configuration` );
// Only the packages that are linked need to be considered. The packages installed from the
// registry are already included in the fingerprint by their very nature. If they are
// changed then the lock file will be updated and the fingerprint will change too.
const linkedPackages = getLinkedPackages(
packagePath,
lockPackages[ packagePath ],
);
// In order to make maintaining the list easy we use a wireit-only script named "dependencies" to keep the list up to date.
// This is an automatically generated script and that we own and so we should make sure it's always as-expected.
packageFile.wireit.dependencyOutputs = {
// This is needed so we can reference files in `node_modules`.
allowUsuallyExcludedPaths: true,
// The files list will include globs for dependency files that we should fingerprint.
files: [ "package.json" ],
};
// We're going to spin through all of the dependencies for the package and add
// their outputs to the list. We can then use these are file dependencies for
// wireit and it will fingerprint them for us.
for ( const linkedPackage of linkedPackages ) {
const packageOutputs = getPackageOutputs( linkedPackage, context );
if ( ! packageOutputs.include.length && ! packageOutputs.include.length ) {
context.log(
`[wireit][${ packageFile.name }] Missing '${ linkedPackage.name }' Output Definition`
);
continue;
}
// Put includes at the front and excludes at the end. This is important because otherwise
// wireit will blow the call stack due to the way it handles negation globs.
packageFile.wireit.dependencyOutputs.files.unshift( ...packageOutputs.include );
packageFile.wireit.dependencyOutputs.files.push( ...packageOutputs.exclude );
context.log(
`[wireit][${ packageFile.name }] Added '${ linkedPackage.name }' Outputs`
);
}
updatePackageFile( packagePath, packageFile );
}
context.log( '[wireit] Done' );
}
/**
* This hook allows for the mutation of the lockfile before it is serialized.
*
* @param {Object} lockfile The lock file that was produced by PNPM.
* @param {string} lockfile.lockfileVersion The version of the lock file spec.
* @param {Object.<string, Object>} lockfile.importers The packages in the workspace that are included in the lock file, keyed by the relative path to the package.
* @param {Object} context The hook context object.
* @param {Function.<string>} context.log Logs a message to the console.
*
* @return {Object} lockfile The updated lockfile.
*/
function afterAllResolved( lockfile, context ) {
updateWireitDependencies( lockfile.importers, context );
return lockfile;
}
// Note: The hook function names are important. They are used by PNPM when determining what functions to call.
module.exports = {
hooks: {
afterAllResolved,
},
};