vercel 28.11.0 → 28.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,8 +41,8 @@ To develop Vercel CLI, first check out the source code, install dependencies, an
41
41
  ```bash
42
42
  git clone https://github.com/vercel/vercel.git
43
43
  cd vercel
44
- yarn
45
- yarn build
44
+ pnpm install
45
+ pnpm build
46
46
  ```
47
47
 
48
48
  At this point you can make modifications to the CLI source code and test them out locally. The CLI source code is located in the `packages/cli` directory.
@@ -51,15 +51,15 @@ At this point you can make modifications to the CLI source code and test them ou
51
51
  cd packages/cli
52
52
  ```
53
53
 
54
- ### `yarn dev <cli-commands...>`
54
+ ### `pnpm dev <cli-commands...>`
55
55
 
56
56
  From within the `packages/cli` directory, you can use the "dev" script to quickly execute Vercel CLI from its TypeScript source code directly (without having to manually compile first). For example:
57
57
 
58
58
  ```bash
59
- yarn dev deploy
60
- yarn dev whoami
61
- yarn dev login
62
- yarn dev switch --debug
59
+ pnpm dev deploy
60
+ pnpm dev whoami
61
+ pnpm dev login
62
+ pnpm dev switch --debug
63
63
  ```
64
64
 
65
65
  When you are satisfied with your changes, make a commit and create a pull request!
@@ -0,0 +1,225 @@
1
+ /**
2
+ * This file is spawned in the background and checks npm for the latest version
3
+ * of the CLI, then writes the version to the cache file.
4
+ *
5
+ * NOTE: Since this file runs asynchronously in the background, it's possible
6
+ * for multiple instances of this file to be running at the same time leading
7
+ * to a race condition where the most recent instance will overwrite the
8
+ * previous cache file resetting the `notified` flag and cause the update
9
+ * notification to appear for multiple consequetive commands. Not the end of
10
+ * the world, but something to be aware of.
11
+ *
12
+ * IMPORTANT! This file must NOT depend on any 3rd party dependencies. This
13
+ * file is NOT bundled by `ncc` and thus any 3rd party dependencies will never
14
+ * be available.
15
+ */
16
+
17
+ const https = require('https');
18
+ const { mkdirSync, writeFileSync } = require('fs');
19
+ const { access, mkdir, readFile, unlink, writeFile } = require('fs/promises');
20
+ const path = require('path');
21
+ const { format, inspect } = require('util');
22
+
23
+ /**
24
+ * An simple output helper which accumulates error and debug log messages in
25
+ * memory for potential persistance to disk while immediately outputting errors
26
+ * and debug messages, when the `--debug` flag is set, to `stderr`.
27
+ */
28
+ class WorkerOutput {
29
+ debugLog = [];
30
+ logFile = null;
31
+
32
+ constructor({ debug = true }) {
33
+ this.debugOutputEnabled = debug;
34
+ }
35
+
36
+ debug(...args) {
37
+ this.print('debug', args);
38
+ }
39
+
40
+ error(...args) {
41
+ this.print('error', args);
42
+ }
43
+
44
+ print(type, args) {
45
+ // note: `args` may contain an `Error` that will be toString()'d and thus
46
+ // no stack trace
47
+ const str = format(
48
+ ...args.map(s => (typeof s === 'string' ? s : inspect(s)))
49
+ );
50
+ this.debugLog.push(`[${new Date().toISOString()}] [${type}] ${str}`);
51
+ if (type === 'debug' && this.debugOutputEnabled) {
52
+ console.error(`> '[debug] [${new Date().toISOString()}] ${str}`);
53
+ } else if (type === 'error') {
54
+ console.error(`Error: ${str}`);
55
+ }
56
+ }
57
+
58
+ setLogFile(file) {
59
+ // wire up the exit handler the first time the log file is set
60
+ if (this.logFile === null) {
61
+ process.on('exit', () => {
62
+ if (this.debugLog.length) {
63
+ mkdirSync(path.dirname(this.logFile), { recursive: true });
64
+ writeFileSync(this.logFile, this.debugLog.join('\n'));
65
+ }
66
+ });
67
+ }
68
+ this.logFile = file;
69
+ }
70
+ }
71
+
72
+ const output = new WorkerOutput({
73
+ // enable the debug logging if the `--debug` is set or if this worker script
74
+ // was directly executed
75
+ debug: process.argv.includes('--debug') || !process.connected,
76
+ });
77
+
78
+ process.on('unhandledRejection', err => {
79
+ output.error('Exiting worker due to unhandled rejection:', err);
80
+ process.exit(1);
81
+ });
82
+
83
+ // this timer will prevent this worker process from running longer than 10s
84
+ const timer = setTimeout(() => {
85
+ output.error('Worker timed out after 10 seconds');
86
+ process.exit(1);
87
+ }, 10000);
88
+
89
+ // wait for the parent to give us the work payload
90
+ process.once('message', async msg => {
91
+ output.debug('Received message from parent:', msg);
92
+
93
+ output.debug('Disconnecting from parent');
94
+ process.disconnect();
95
+
96
+ const { cacheFile, distTag, name, updateCheckInterval } = msg;
97
+ const cacheFileParsed = path.parse(cacheFile);
98
+ await mkdir(cacheFileParsed.dir, { recursive: true });
99
+
100
+ output.setLogFile(
101
+ path.join(cacheFileParsed.dir, `${cacheFileParsed.name}.log`)
102
+ );
103
+
104
+ const lockFile = path.join(
105
+ cacheFileParsed.dir,
106
+ `${cacheFileParsed.name}.lock`
107
+ );
108
+
109
+ try {
110
+ // check for a lock file and either bail if running or write our pid and continue
111
+ output.debug(`Checking lock file: ${lockFile}`);
112
+ if (await isRunning(lockFile)) {
113
+ output.debug('Worker already running, exiting');
114
+ process.exit(1);
115
+ }
116
+ output.debug(`Initializing lock file with pid ${process.pid}`);
117
+ await writeFile(lockFile, String(process.pid), 'utf-8');
118
+
119
+ // fetch the latest version from npm
120
+ const agent = new https.Agent({
121
+ keepAlive: true,
122
+ maxSockets: 15, // See: `npm config get maxsockets`
123
+ });
124
+ const headers = {
125
+ accept:
126
+ 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*',
127
+ };
128
+ const url = `https://registry.npmjs.org/-/package/${name}/dist-tags`;
129
+ output.debug(`Fetching ${url}`);
130
+
131
+ const tags = await new Promise((resolve, reject) => {
132
+ const req = https.get(
133
+ url,
134
+ {
135
+ agent,
136
+ headers,
137
+ },
138
+ res => {
139
+ let buf = '';
140
+ res.on('data', chunk => {
141
+ buf += chunk;
142
+ });
143
+ res.on('end', () => {
144
+ try {
145
+ resolve(JSON.parse(buf));
146
+ } catch (err) {
147
+ reject(err);
148
+ }
149
+ });
150
+ }
151
+ );
152
+
153
+ req.on('error', reject);
154
+ req.end();
155
+ });
156
+
157
+ const version = tags[distTag];
158
+
159
+ if (version) {
160
+ output.debug(`Found dist tag "${distTag}" with version "${version}"`);
161
+ } else {
162
+ output.error(`Dist tag "${distTag}" not found`);
163
+ output.debug('Available dist tags:', Object.keys(tags));
164
+ }
165
+
166
+ output.debug(`Writing cache file: ${cacheFile}`);
167
+ await writeFile(
168
+ cacheFile,
169
+ JSON.stringify({
170
+ expireAt: Date.now() + updateCheckInterval,
171
+ notified: false,
172
+ version,
173
+ })
174
+ );
175
+ } catch (err) {
176
+ output.error(`Failed to get package info:`, err);
177
+ } finally {
178
+ clearTimeout(timer);
179
+
180
+ if (await fileExists(lockFile)) {
181
+ output.debug(`Releasing lock file: ${lockFile}`);
182
+ await unlink(lockFile);
183
+ }
184
+
185
+ output.debug(`Worker finished successfully!`);
186
+
187
+ // force the worker to exit
188
+ process.exit(0);
189
+ }
190
+ });
191
+
192
+ // signal the parent process we're ready
193
+ if (process.connected) {
194
+ output.debug("Notifying parent we're ready");
195
+ process.send({ type: 'ready' });
196
+ } else {
197
+ console.error('No IPC bridge detected, exiting');
198
+ process.exit(1);
199
+ }
200
+
201
+ async function fileExists(file) {
202
+ return access(file)
203
+ .then(() => true)
204
+ .catch(() => false);
205
+ }
206
+
207
+ async function isRunning(lockFile) {
208
+ try {
209
+ const pid = parseInt(await readFile(lockFile, 'utf-8'));
210
+ output.debug(`Found lock file with pid: ${pid}`);
211
+
212
+ // checks for existence of a process; throws if not found
213
+ process.kill(pid, 0);
214
+
215
+ // process is still running
216
+ return true;
217
+ } catch (err) {
218
+ if (await fileExists(lockFile)) {
219
+ // lock file does not exist or process is not running and pid is stale
220
+ output.debug(`Resetting lock file: ${err.toString()}`);
221
+ await unlink(lockFile);
222
+ }
223
+ return false;
224
+ }
225
+ }