Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/npm/installStateHash.ts
13379 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as crypto from 'crypto';
7
import * as fs from 'fs';
8
import path from 'path';
9
import { dirs } from './dirs.ts';
10
11
export const root = fs.realpathSync.native(path.dirname(path.dirname(import.meta.dirname)));
12
export const stateFile = path.join(root, 'node_modules', '.postinstall-state');
13
export const stateContentsFile = path.join(root, 'node_modules', '.postinstall-state-contents');
14
export const forceInstallMessage = 'Run \x1b[36mnode build/npm/fast-install.ts --force\x1b[0m to force a full install.';
15
16
export function collectInputFiles(): string[] {
17
const files: string[] = [];
18
19
for (const dir of dirs) {
20
const base = dir === '' ? root : path.join(root, dir);
21
for (const file of ['package.json', 'package-lock.json', '.npmrc']) {
22
const filePath = path.join(base, file);
23
if (fs.existsSync(filePath)) {
24
files.push(filePath);
25
}
26
}
27
}
28
29
files.push(path.join(root, '.nvmrc'));
30
31
return files;
32
}
33
34
export interface PostinstallState {
35
readonly nodeVersion: string;
36
readonly fileHashes: Record<string, string>;
37
}
38
39
const packageJsonRelevantKeys = new Set([
40
'name',
41
'dependencies',
42
'devDependencies',
43
'optionalDependencies',
44
'peerDependencies',
45
'peerDependenciesMeta',
46
'overrides',
47
'engines',
48
'workspaces',
49
'bundledDependencies',
50
'bundleDependencies',
51
]);
52
53
const packageLockJsonIgnoredKeys = new Set(['version']);
54
55
function normalizeFileContent(filePath: string): string {
56
const raw = fs.readFileSync(filePath, 'utf8');
57
const basename = path.basename(filePath);
58
if (basename === 'package.json') {
59
const json = JSON.parse(raw);
60
const filtered: Record<string, unknown> = {};
61
for (const key of packageJsonRelevantKeys) {
62
// eslint-disable-next-line local/code-no-in-operator
63
if (key in json) {
64
filtered[key] = json[key];
65
}
66
}
67
return JSON.stringify(filtered, null, '\t') + '\n';
68
}
69
if (basename === 'package-lock.json') {
70
const json = JSON.parse(raw);
71
for (const key of packageLockJsonIgnoredKeys) {
72
delete json[key];
73
}
74
if (json.packages?.['']) {
75
for (const key of packageLockJsonIgnoredKeys) {
76
delete json.packages[''][key];
77
}
78
}
79
return JSON.stringify(json, null, '\t') + '\n';
80
}
81
return raw;
82
}
83
84
function hashContent(content: string): string {
85
const hash = crypto.createHash('sha256');
86
hash.update(content);
87
return hash.digest('hex');
88
}
89
90
export function computeState(options?: { ignoreNodeVersion?: boolean }): PostinstallState {
91
const fileHashes: Record<string, string> = {};
92
for (const filePath of collectInputFiles()) {
93
const key = path.relative(root, filePath);
94
try {
95
fileHashes[key] = hashContent(normalizeFileContent(filePath));
96
} catch {
97
// file may not be readable
98
}
99
}
100
return { nodeVersion: options?.ignoreNodeVersion ? '' : process.versions.node, fileHashes };
101
}
102
103
export function computeContents(): Record<string, string> {
104
const fileContents: Record<string, string> = {};
105
for (const filePath of collectInputFiles()) {
106
try {
107
fileContents[path.relative(root, filePath)] = normalizeFileContent(filePath);
108
} catch {
109
// file may not be readable
110
}
111
}
112
return fileContents;
113
}
114
115
export function readSavedState(): PostinstallState | undefined {
116
try {
117
const { nodeVersion, fileHashes } = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
118
return { nodeVersion, fileHashes };
119
} catch {
120
return undefined;
121
}
122
}
123
124
export function isUpToDate(): boolean {
125
const saved = readSavedState();
126
if (!saved) {
127
return false;
128
}
129
const current = computeState();
130
return saved.nodeVersion === current.nodeVersion
131
&& JSON.stringify(saved.fileHashes) === JSON.stringify(current.fileHashes);
132
}
133
134
export function readSavedContents(): Record<string, string> | undefined {
135
try {
136
return JSON.parse(fs.readFileSync(stateContentsFile, 'utf8'));
137
} catch {
138
return undefined;
139
}
140
}
141
142
// When run directly, output state as JSON for tooling (e.g. the vscode-extras extension).
143
if (import.meta.filename === process.argv[1]) {
144
const args = new Set(process.argv.slice(2));
145
146
if (args.has('--normalize-file')) {
147
const filePath = process.argv[process.argv.indexOf('--normalize-file') + 1];
148
if (!filePath) {
149
process.exit(1);
150
}
151
process.stdout.write(normalizeFileContent(filePath));
152
} else {
153
const ignoreNodeVersion = args.has('--ignore-node-version');
154
const current = computeState({ ignoreNodeVersion });
155
const saved = readSavedState();
156
console.log(JSON.stringify({
157
root,
158
stateContentsFile,
159
current,
160
saved: saved && ignoreNodeVersion ? { nodeVersion: '', fileHashes: saved.fileHashes } : saved,
161
files: [...collectInputFiles(), stateFile],
162
}));
163
}
164
}
165
166