-
Notifications
You must be signed in to change notification settings - Fork 1
/
yarn-sync.js
149 lines (126 loc) · 4.2 KB
/
yarn-sync.js
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
/** @format */
const fs = require("fs");
/**
* Removes matching outer quotes from a string.
*
* @param {string} text - String to unquote
* @returns {string} - Unquoted string
*/
const unquote = (text) => /(["'])?(.*)\1/.exec(text)[2];
/**
* @typedef {object} YarnLockItem
* @property {string[]} pkgs - Array of package version specs, e.g. "yippee-ki-yay@^1.23.45", "@foo/bar@^1.6.0"
* @property {string} version - Installed version
* @property {string} resolved - Resolved package URL
* @property {string} integrity - Package integrity
* @property {object.<string, string>} dependencies - Package dependencies and their version specs
*/
/**
* Extracts information about installed packages from yarn.lock file.
*
* NB: functionality for parsing a yarn.lock file exists in a package called `@yarnpkg/lockfile`,
* however this package pulls in way too many dependencies (all of yarn, it seems).
*
* @param {string} filename - Path to yarn.lock file
* @returns {object.<string, YarnLockItem>} Installed package information, keyed by package version spec
*/
const parseYarnLockFile = (s) => {
const lines = s.replace(/\r/g, "").split(/\n/);
const entries = {};
let entry;
let key;
lines.forEach((line) => {
const indent = /^(\s*)/.exec(line)[1].length;
line = line.trim();
if (line === "") {
if (entry) {
// Add an entry for each of the package specs in the item
entry.pkgs.forEach((pkg) => {
entries[pkg] = entry;
});
entry = null;
}
} else if (line[0] === "#") {
// Comment, skip
} else if (indent === 0) {
// Start of entry
entry = {
// Remove trailing colon, split, trim and unquote
pkgs: line
.replace(/:$/, "")
.split(",")
.map((s) => unquote(s.trim())),
};
} else if (indent === 2) {
let match;
if ((match = /^(\w+) (.+)/.exec(line))) {
entry[match[1]] = unquote(match[2]);
} else if ((match = /^(\w+):$/.exec(line))) {
key = match[1];
entry[key] = {};
}
} else if (indent === 4) {
const match = /^(.+) (.+)/.exec(line);
if (match) {
entry[key][unquote(match[1])] = unquote(match[2].trim());
}
} else {
console.warn("Line not understood:", line);
}
});
return entries;
};
const updatePackageJson = (packageJson, yarnLock) => {
let changeCount = 0;
const updateSection = (sectionName) => {
console.log("Updating", sectionName, "...");
const section = packageJson[sectionName];
Object.entries(section).forEach(([pkg, versionSpec]) => {
const dependency = `${pkg}@${versionSpec}`;
// Get the version spec prefix, e.g. '^' or '>=', or none.
// We support version specs containing a single semver, other types of version spec are untested a.t.m.
// (version spec format is documented here: https://docs.npmjs.com/files/package.json#dependencies)
const versionSpecPrefix = /^([^\d]*)/.exec(versionSpec)[1];
const yarnLockEntry = yarnLock[dependency];
if (yarnLockEntry) {
const actualVersion = yarnLockEntry.version;
const actualVersionSpec = `${versionSpecPrefix}${actualVersion}`;
if (actualVersionSpec !== versionSpec) {
console.log(" Updating:", dependency, "=>", actualVersionSpec);
section[pkg] = actualVersionSpec;
++changeCount;
} else {
console.log(" Up-to-date:", dependency);
}
} else {
console.warn(" !!! Missing yarn.lock entry for:", dependency);
}
});
console.log(" Done.");
};
["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"].forEach(
(sectionName) => {
if (sectionName in packageJson) {
updateSection(sectionName);
}
},
);
return changeCount;
};
const main = () => {
console.log("Reading package.json ...");
const packageJsonFile = fs.readFileSync("package.json", "utf8");
const packageJson = JSON.parse(packageJsonFile);
console.log("Reading yarn.lock ...");
const yarnLockFile = fs.readFileSync("yarn.lock", "utf8");
const yarnLock = parseYarnLockFile(yarnLockFile);
const changeCount = updatePackageJson(packageJson, yarnLock);
if (changeCount > 0) {
const outFilename = "package_synced.json";
console.log("Writing changes to:", outFilename);
fs.writeFileSync(outFilename, JSON.stringify(packageJson, null, 2));
} else {
console.log("No changes");
}
};
main();