-
-
Notifications
You must be signed in to change notification settings - Fork 31
/
update-lockfile-version.ts
203 lines (187 loc) · 7.72 KB
/
update-lockfile-version.ts
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
import log from 'npmlog';
import path from 'path';
import loadJsonFile from 'load-json-file';
import { promises as fsPromises, renameSync } from 'node:fs';
import os from 'os';
import semver from 'semver';
import writeJsonFile from 'write-json-file';
import { exec, execSync, Package } from '@lerna-lite/core';
/**
* From a folder path provided, try to load a `package-lock.json` file if it exists.
* @param {String} lockFileFolderPath
* @returns Promise<{path: string; json: Object; lockFileVersion: number; }>
*/
export async function loadPackageLockFileWhenExists<T = any>(lockFileFolderPath: string) {
try {
const lockFilePath = path.join(lockFileFolderPath, 'package-lock.json');
const pkgLockFileObj = await loadJsonFile<T>(lockFilePath);
const lockfileVersion = +(pkgLockFileObj?.['lockfileVersion'] ?? 1);
return {
path: lockFilePath,
json: pkgLockFileObj,
lockfileVersion,
};
} catch (error) {} // eslint-disable-line
}
/**
* Update NPM Lock File (when found), the lock file might be version 1 (exist in package folder) or version 2 (exist in workspace root)
* Depending on the version type, the structure of the lock file will be different and will be updated accordingly
* @param {Object} pkg
* @param {Object} project
* @returns Promise<string>
*/
export async function updateClassicLockfileVersion(pkg: Package): Promise<string | undefined> {
try {
// "lockfileVersion" = 1, package lock file might be located in the package folder
const lockFilePath = path.join(pkg.location, 'package-lock.json');
const pkgLockFileObj: any = await loadJsonFile(lockFilePath);
if (pkgLockFileObj) {
pkgLockFileObj.version = pkg.version;
// update version for a npm lockfile v2 format
if (pkgLockFileObj.packages?.['']) {
pkgLockFileObj.packages[''].version = pkg.version;
}
await writeJsonFile(lockFilePath, pkgLockFileObj, {
detectIndent: true,
indent: 2,
});
return lockFilePath;
}
} catch (error) {} // eslint-disable-line
}
/**
* Update NPM Lock File (when found), the lock file must be version 2 or higher and is considered as modern lockfile,
* its structure is different and all version properties will be updated accordingly
* @param {Object} pkg
* @param {Object} project
* @returns Promise<string>
*/
export function updateTempModernLockfileVersion(pkg: Package, projLockFileObj: any) {
// OR "lockfileVersion" >= 2 in the project root, will have a global package lock file located in the root folder and is formatted
if (projLockFileObj) {
updateNpmLockFileVersion2(projLockFileObj, pkg.name, pkg.version);
}
}
/**
* Save a lockfile by providing a full path and an updated json object
* @param {String} filePath
* @param {Object} updateLockFileObj
* @returns Promise<String | undefined> - file path will be returned when it was found and updated
*/
export async function saveUpdatedLockJsonFile(filePath: string, updateLockFileObj: any): Promise<string | undefined> {
try {
await writeJsonFile(filePath, updateLockFileObj, {
detectIndent: true,
indent: 2,
});
return filePath;
} catch (error) {} // eslint-disable-line
}
/**
* Update workspace root NPM Lock File Version Type 2 (considerd modern lockfile)
* @param {Object} obj
* @param {String} pkgName
* @param {String} newVersion
*/
export function updateNpmLockFileVersion2(obj: any, pkgName: string, newVersion: string) {
if (typeof obj === 'object' && pkgName && newVersion) {
for (const k in obj) {
if (typeof obj[k] === 'object' && obj[k] !== null) {
updateNpmLockFileVersion2(obj[k], pkgName, newVersion);
} else {
if (k === pkgName) {
// e.g.: "@lerna-lite/core": "^0.1.2",
const [_, versionPrefix, _versionStr] = obj[k].match(/^([\^~])?(.*)$/);
obj[k] = `${versionPrefix}${newVersion}`;
} else if (k === 'name' && obj[k] === pkgName && obj['version'] !== undefined) {
// e.g. "packages/version": { "name": "@lerna-lite/version", "version": "0.1.2" }
if (obj['version'] !== undefined) {
obj['version'] = newVersion;
}
}
}
}
}
}
/**
* Run `npm install --package-lock-only` or equivalent depending on the package manager defined in `npmClient`
* @param {'npm' | 'pnpm' | 'yarn'} npmClient
* @param {String} cwd
* @returns {Promise<string | undefined>} lockfile name if executed successfully
*/
export async function runInstallLockFileOnly(
npmClient: 'npm' | 'pnpm' | 'yarn',
cwd: string
): Promise<string | undefined> {
let inputLockfileName = '';
let outputLockfileName: string | undefined;
switch (npmClient) {
case 'pnpm':
inputLockfileName = 'pnpm-lock.yaml';
if (await validateFileExists(path.join(cwd, inputLockfileName))) {
log.verbose('lock', `updating lock file via "pnpm install --lockfile-only"`);
await exec('pnpm', ['install', '--lockfile-only', '--no-frozen-lockfile'], { cwd });
outputLockfileName = inputLockfileName;
}
break;
case 'yarn':
inputLockfileName = 'yarn.lock';
if (await validateFileExists(path.join(cwd, inputLockfileName))) {
log.verbose('lock', `updating lock file via "yarn install --mode update-lockfile"`);
await exec('yarn', ['install', '--mode', 'update-lockfile'], { cwd });
outputLockfileName = inputLockfileName;
}
break;
case 'npm':
default:
inputLockfileName = 'package-lock.json';
if (await validateFileExists(path.join(cwd, inputLockfileName))) {
const localNpmVersion = execSync('npm', ['--version']);
log.silly(`npm`, `current local npm version is "${localNpmVersion}"`);
// for npm version >=8.5.0 we can call "npm install --package-lock-only"
// when lower then we call "npm shrinkwrap --package-lock-only" and rename "npm-shrinkwrap.json" back to "package-lock.json"
if (semver.gte(localNpmVersion, '8.5.0')) {
log.verbose('lock', `updating lock file via "npm install --package-lock-only"`);
await exec('npm', ['install', '--package-lock-only'], { cwd });
} else {
// TODO: eventually remove in future and/or major release
// with npm, we need to do update the lock file in 2 steps
// 1. using shrinkwrap will delete current lock file and create new "npm-shrinkwrap.json" but will avoid npm retrieving package version info from registry
log.verbose('lock', `updating lock file via "npm shrinkwrap --package-lock-only".`);
log.warn(
`npm`,
`Your npm version is lower than 8.5.0, we recommend upgrading your npm client to avoid the use of "npm shrinkwrap" instead of the regular (better) "npm install --package-lock-only".`
);
await exec('npm', ['shrinkwrap', '--package-lock-only'], { cwd });
// 2. rename "npm-shrinkwrap.json" back to "package-lock.json"
log.verbose('lock', `renaming "npm-shrinkwrap.json" file back to "package-lock.json"`);
renameSync('npm-shrinkwrap.json', 'package-lock.json');
}
outputLockfileName = inputLockfileName;
}
break;
}
if (!outputLockfileName) {
log.error(
'lock',
[
`we could not sync or locate "${inputLockfileName}" by using "${npmClient}" client at location ${cwd}`,
`Note: if you were expecting a different lock file name, make sure to add "npmClient" into your "lerna.json" config.`,
].join(os.EOL)
);
}
return outputLockfileName;
}
/**
* Simply validates if a file exists
* @param {String} filePath - file path
* @returns {Boolean}
*/
export async function validateFileExists(filePath: string) {
try {
await fsPromises.access(filePath);
return true;
} catch {
return false;
}
}