@oclif/plugin-update 3.2.4 → 4.1.0
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 +14 -10
- package/dist/commands/update.d.ts +20 -0
- package/dist/commands/update.js +83 -0
- package/dist/hooks/init.js +67 -0
- package/dist/tar.d.ts +6 -0
- package/{lib → dist}/tar.js +34 -30
- package/dist/update.d.ts +26 -0
- package/dist/update.js +362 -0
- package/{lib → dist}/util.d.ts +3 -4
- package/dist/util.js +22 -0
- package/oclif.lock +7063 -0
- package/oclif.manifest.json +59 -46
- package/package.json +50 -38
- package/lib/commands/update.d.ts +0 -20
- package/lib/commands/update.js +0 -87
- package/lib/hooks/init.js +0 -67
- package/lib/tar.d.ts +0 -2
- package/lib/update.d.ts +0 -44
- package/lib/update.js +0 -388
- package/lib/util.js +0 -37
- /package/{lib → dist}/hooks/init.d.ts +0 -0
package/lib/update.js
DELETED
@@ -1,388 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.Updater = void 0;
|
4
|
-
const tslib_1 = require("tslib");
|
5
|
-
const chalk_1 = (0, tslib_1.__importDefault)(require("chalk"));
|
6
|
-
const core_1 = require("@oclif/core");
|
7
|
-
const fs = (0, tslib_1.__importStar)(require("fs-extra"));
|
8
|
-
const http_call_1 = (0, tslib_1.__importDefault)(require("http-call"));
|
9
|
-
const path = (0, tslib_1.__importStar)(require("path"));
|
10
|
-
const lodash_throttle_1 = (0, tslib_1.__importDefault)(require("lodash.throttle"));
|
11
|
-
const filesize_1 = (0, tslib_1.__importDefault)(require("filesize"));
|
12
|
-
const tar_1 = require("./tar");
|
13
|
-
const util_1 = require("./util");
|
14
|
-
const filesize = (n) => {
|
15
|
-
const [num, suffix] = (0, filesize_1.default)(n, { output: 'array' });
|
16
|
-
return Number.parseFloat(num).toFixed(1) + ` ${suffix}`;
|
17
|
-
};
|
18
|
-
class Updater {
|
19
|
-
constructor(config) {
|
20
|
-
this.config = config;
|
21
|
-
this.clientRoot = config.scopedEnvVar('OCLIF_CLIENT_HOME') || path.join(config.dataDir, 'client');
|
22
|
-
this.clientBin = path.join(this.clientRoot, 'bin', config.windows ? `${config.bin}.cmd` : config.bin);
|
23
|
-
}
|
24
|
-
async runUpdate(options) {
|
25
|
-
const { autoUpdate, version, force = false } = options;
|
26
|
-
if (autoUpdate)
|
27
|
-
await this.debounce();
|
28
|
-
core_1.ux.action.start(`${this.config.name}: Updating CLI`);
|
29
|
-
if (this.notUpdatable()) {
|
30
|
-
core_1.ux.action.stop('not updatable');
|
31
|
-
return;
|
32
|
-
}
|
33
|
-
const channel = options.channel || await this.determineChannel(version);
|
34
|
-
const current = await this.determineCurrentVersion();
|
35
|
-
if (version) {
|
36
|
-
const localVersion = force ? null : await this.findLocalVersion(version);
|
37
|
-
if (this.alreadyOnVersion(current, localVersion || null)) {
|
38
|
-
core_1.ux.action.stop(this.config.scopedEnvVar('HIDE_UPDATED_MESSAGE') ? 'done' : `already on version ${current}`);
|
39
|
-
return;
|
40
|
-
}
|
41
|
-
await this.config.runHook('preupdate', { channel, version });
|
42
|
-
if (localVersion) {
|
43
|
-
await this.updateToExistingVersion(current, localVersion);
|
44
|
-
}
|
45
|
-
else {
|
46
|
-
const index = await this.fetchVersionIndex();
|
47
|
-
const url = index[version];
|
48
|
-
if (!url) {
|
49
|
-
throw new Error(`${version} not found in index:\n${Object.keys(index).join(', ')}`);
|
50
|
-
}
|
51
|
-
const manifest = await this.fetchVersionManifest(version, url);
|
52
|
-
const updated = manifest.sha ? `${manifest.version}-${manifest.sha}` : manifest.version;
|
53
|
-
await this.update(manifest, current, updated, force, channel);
|
54
|
-
}
|
55
|
-
await this.config.runHook('update', { channel, version });
|
56
|
-
core_1.ux.action.stop();
|
57
|
-
core_1.ux.log();
|
58
|
-
core_1.ux.log(`Updating to a specific version will not update the channel. If autoupdate is enabled, the CLI will eventually be updated back to ${channel}.`);
|
59
|
-
}
|
60
|
-
else {
|
61
|
-
const manifest = await this.fetchChannelManifest(channel);
|
62
|
-
const updated = manifest.sha ? `${manifest.version}-${manifest.sha}` : manifest.version;
|
63
|
-
if (!force && this.alreadyOnVersion(current, updated)) {
|
64
|
-
core_1.ux.action.stop(this.config.scopedEnvVar('HIDE_UPDATED_MESSAGE') ? 'done' : `already on version ${current}`);
|
65
|
-
}
|
66
|
-
else {
|
67
|
-
await this.config.runHook('preupdate', { channel, version: updated });
|
68
|
-
await this.update(manifest, current, updated, force, channel);
|
69
|
-
}
|
70
|
-
await this.config.runHook('update', { channel, version: updated });
|
71
|
-
core_1.ux.action.stop();
|
72
|
-
}
|
73
|
-
await this.touch();
|
74
|
-
await this.tidy();
|
75
|
-
core_1.ux.debug('done');
|
76
|
-
}
|
77
|
-
async findLocalVersions() {
|
78
|
-
await this.ensureClientDir();
|
79
|
-
return fs
|
80
|
-
.readdirSync(this.clientRoot)
|
81
|
-
.filter(dirOrFile => dirOrFile !== 'bin' && dirOrFile !== 'current')
|
82
|
-
.map(f => path.join(this.clientRoot, f));
|
83
|
-
}
|
84
|
-
async fetchVersionIndex() {
|
85
|
-
core_1.ux.action.status = 'fetching version index';
|
86
|
-
const newIndexUrl = this.config.s3Url(this.s3VersionIndexKey());
|
87
|
-
try {
|
88
|
-
const { body } = await http_call_1.default.get(newIndexUrl);
|
89
|
-
if (typeof body === 'string') {
|
90
|
-
return JSON.parse(body);
|
91
|
-
}
|
92
|
-
return body;
|
93
|
-
}
|
94
|
-
catch (_a) {
|
95
|
-
throw new Error(`No version indices exist for ${this.config.name}.`);
|
96
|
-
}
|
97
|
-
}
|
98
|
-
async ensureClientDir() {
|
99
|
-
try {
|
100
|
-
await fs.mkdirp(this.clientRoot);
|
101
|
-
}
|
102
|
-
catch (error) {
|
103
|
-
if (error.code === 'EEXIST') {
|
104
|
-
// for some reason the client directory is sometimes a file
|
105
|
-
// if so, this happens. Delete it and recreate
|
106
|
-
await fs.remove(this.clientRoot);
|
107
|
-
await fs.mkdirp(this.clientRoot);
|
108
|
-
}
|
109
|
-
else {
|
110
|
-
throw error;
|
111
|
-
}
|
112
|
-
}
|
113
|
-
}
|
114
|
-
composeS3SubDir() {
|
115
|
-
let s3SubDir = this.config.pjson.oclif.update.s3.folder || '';
|
116
|
-
if (s3SubDir !== '' && s3SubDir.slice(-1) !== '/')
|
117
|
-
s3SubDir = `${s3SubDir}/`;
|
118
|
-
return s3SubDir;
|
119
|
-
}
|
120
|
-
s3ChannelManifestKey(channel) {
|
121
|
-
const { bin, arch } = this.config;
|
122
|
-
const s3SubDir = this.composeS3SubDir();
|
123
|
-
return path.join(s3SubDir, 'channels', channel, `${bin}-${this.determinePlatform()}-${arch}-buildmanifest`);
|
124
|
-
}
|
125
|
-
s3VersionManifestKey(version, hash) {
|
126
|
-
const { bin, arch } = this.config;
|
127
|
-
const s3SubDir = this.composeS3SubDir();
|
128
|
-
return path.join(s3SubDir, 'versions', version, hash, `${bin}-v${version}-${hash}-${this.determinePlatform()}-${arch}-buildmanifest`);
|
129
|
-
}
|
130
|
-
s3VersionIndexKey() {
|
131
|
-
const { bin, arch } = this.config;
|
132
|
-
const s3SubDir = this.composeS3SubDir();
|
133
|
-
return path.join(s3SubDir, 'versions', `${bin}-${this.determinePlatform()}-${arch}-tar-gz.json`);
|
134
|
-
}
|
135
|
-
async fetchChannelManifest(channel) {
|
136
|
-
const s3Key = this.s3ChannelManifestKey(channel);
|
137
|
-
try {
|
138
|
-
return await this.fetchManifest(s3Key);
|
139
|
-
}
|
140
|
-
catch (error) {
|
141
|
-
if (error.statusCode === 403)
|
142
|
-
throw new Error(`HTTP 403: Invalid channel ${channel}`);
|
143
|
-
throw error;
|
144
|
-
}
|
145
|
-
}
|
146
|
-
async fetchVersionManifest(version, url) {
|
147
|
-
const parts = url.split('/');
|
148
|
-
const hashIndex = parts.indexOf(version) + 1;
|
149
|
-
const hash = parts[hashIndex];
|
150
|
-
const s3Key = this.s3VersionManifestKey(version, hash);
|
151
|
-
return this.fetchManifest(s3Key);
|
152
|
-
}
|
153
|
-
async fetchManifest(s3Key) {
|
154
|
-
core_1.ux.action.status = 'fetching manifest';
|
155
|
-
const url = this.config.s3Url(s3Key);
|
156
|
-
const { body } = await http_call_1.default.get(url);
|
157
|
-
if (typeof body === 'string') {
|
158
|
-
return JSON.parse(body);
|
159
|
-
}
|
160
|
-
return body;
|
161
|
-
}
|
162
|
-
async downloadAndExtract(output, manifest, channel) {
|
163
|
-
const { version, gz, sha256gz } = manifest;
|
164
|
-
const gzUrl = gz || this.config.s3Url(this.config.s3Key('versioned', {
|
165
|
-
version,
|
166
|
-
channel,
|
167
|
-
bin: this.config.bin,
|
168
|
-
platform: this.determinePlatform(),
|
169
|
-
arch: this.config.arch,
|
170
|
-
ext: 'gz',
|
171
|
-
}));
|
172
|
-
const { response: stream } = await http_call_1.default.stream(gzUrl);
|
173
|
-
stream.pause();
|
174
|
-
const baseDir = manifest.baseDir || this.config.s3Key('baseDir', {
|
175
|
-
version,
|
176
|
-
channel,
|
177
|
-
bin: this.config.bin,
|
178
|
-
platform: this.determinePlatform(),
|
179
|
-
arch: this.config.arch,
|
180
|
-
});
|
181
|
-
const extraction = (0, tar_1.extract)(stream, baseDir, output, sha256gz);
|
182
|
-
if (core_1.ux.action.type === 'spinner') {
|
183
|
-
const total = Number.parseInt(stream.headers['content-length'], 10);
|
184
|
-
let current = 0;
|
185
|
-
const updateStatus = (0, lodash_throttle_1.default)((newStatus) => {
|
186
|
-
core_1.ux.action.status = newStatus;
|
187
|
-
}, 250, { leading: true, trailing: false });
|
188
|
-
stream.on('data', data => {
|
189
|
-
current += data.length;
|
190
|
-
updateStatus(`${filesize(current)}/${filesize(total)}`);
|
191
|
-
});
|
192
|
-
}
|
193
|
-
stream.resume();
|
194
|
-
await extraction;
|
195
|
-
}
|
196
|
-
// eslint-disable-next-line max-params
|
197
|
-
async update(manifest, current, updated, force, channel) {
|
198
|
-
core_1.ux.action.start(`${this.config.name}: Updating CLI from ${chalk_1.default.green(current)} to ${chalk_1.default.green(updated)}${channel === 'stable' ? '' : ' (' + chalk_1.default.yellow(channel) + ')'}`);
|
199
|
-
await this.ensureClientDir();
|
200
|
-
const output = path.join(this.clientRoot, updated);
|
201
|
-
if (force || !await fs.pathExists(output))
|
202
|
-
await this.downloadAndExtract(output, manifest, channel);
|
203
|
-
await this.refreshConfig(updated);
|
204
|
-
await this.setChannel(channel);
|
205
|
-
await this.createBin(updated);
|
206
|
-
}
|
207
|
-
async updateToExistingVersion(current, updated) {
|
208
|
-
core_1.ux.action.start(`${this.config.name}: Updating CLI from ${chalk_1.default.green(current)} to ${chalk_1.default.green(updated)}`);
|
209
|
-
await this.ensureClientDir();
|
210
|
-
await this.refreshConfig(updated);
|
211
|
-
await this.createBin(updated);
|
212
|
-
}
|
213
|
-
notUpdatable() {
|
214
|
-
if (!this.config.binPath) {
|
215
|
-
const instructions = this.config.scopedEnvVar('UPDATE_INSTRUCTIONS');
|
216
|
-
if (instructions)
|
217
|
-
core_1.ux.warn(instructions);
|
218
|
-
return true;
|
219
|
-
}
|
220
|
-
return false;
|
221
|
-
}
|
222
|
-
alreadyOnVersion(current, updated) {
|
223
|
-
return current === updated;
|
224
|
-
}
|
225
|
-
async determineChannel(version) {
|
226
|
-
var _a, _b;
|
227
|
-
const channelPath = path.join(this.config.dataDir, 'channel');
|
228
|
-
const channel = fs.existsSync(channelPath) ? (await fs.readFile(channelPath, 'utf8')).trim() : 'stable';
|
229
|
-
try {
|
230
|
-
const { body } = await http_call_1.default.get(`${(_a = this.config.npmRegistry) !== null && _a !== void 0 ? _a : 'https://registry.npmjs.org'}/${this.config.pjson.name}`);
|
231
|
-
const tags = body['dist-tags'];
|
232
|
-
const tag = (_b = Object.keys(tags).find(v => tags[v] === version)) !== null && _b !== void 0 ? _b : channel;
|
233
|
-
// convert from npm style tag defaults to OCLIF style
|
234
|
-
if (tag === 'latest')
|
235
|
-
return 'stable';
|
236
|
-
if (tag === 'latest-rc')
|
237
|
-
return 'stable-rc';
|
238
|
-
return tag;
|
239
|
-
}
|
240
|
-
catch (_c) {
|
241
|
-
return channel;
|
242
|
-
}
|
243
|
-
}
|
244
|
-
determinePlatform() {
|
245
|
-
return this.config.platform === 'wsl' ? 'linux' : this.config.platform;
|
246
|
-
}
|
247
|
-
async determineCurrentVersion() {
|
248
|
-
try {
|
249
|
-
const currentVersion = await fs.readFile(this.clientBin, 'utf8');
|
250
|
-
const matches = currentVersion.match(/\.\.[/\\|](.+)[/\\|]bin/);
|
251
|
-
return matches ? matches[1] : this.config.version;
|
252
|
-
}
|
253
|
-
catch (error) {
|
254
|
-
core_1.ux.debug(error);
|
255
|
-
}
|
256
|
-
return this.config.version;
|
257
|
-
}
|
258
|
-
async findLocalVersion(version) {
|
259
|
-
const versions = await this.findLocalVersions();
|
260
|
-
return versions
|
261
|
-
.map(file => path.basename(file))
|
262
|
-
.find(file => file.startsWith(version));
|
263
|
-
}
|
264
|
-
async setChannel(channel) {
|
265
|
-
const channelPath = path.join(this.config.dataDir, 'channel');
|
266
|
-
await fs.writeFile(channelPath, channel, 'utf8');
|
267
|
-
}
|
268
|
-
async logChop() {
|
269
|
-
try {
|
270
|
-
core_1.ux.debug('log chop');
|
271
|
-
const logChopper = require('log-chopper').default;
|
272
|
-
await logChopper.chop(this.config.errlog);
|
273
|
-
}
|
274
|
-
catch (error) {
|
275
|
-
core_1.ux.debug(error.message);
|
276
|
-
}
|
277
|
-
}
|
278
|
-
async mtime(f) {
|
279
|
-
const { mtime } = await fs.stat(f);
|
280
|
-
return mtime;
|
281
|
-
}
|
282
|
-
// when autoupdating, wait until the CLI isn't active
|
283
|
-
async debounce() {
|
284
|
-
let output = false;
|
285
|
-
const lastrunfile = path.join(this.config.cacheDir, 'lastrun');
|
286
|
-
const m = await this.mtime(lastrunfile);
|
287
|
-
m.setHours(m.getHours() + 1);
|
288
|
-
if (m > new Date()) {
|
289
|
-
const msg = `waiting until ${m.toISOString()} to update`;
|
290
|
-
if (output) {
|
291
|
-
core_1.ux.debug(msg);
|
292
|
-
}
|
293
|
-
else {
|
294
|
-
core_1.ux.log(msg);
|
295
|
-
output = true;
|
296
|
-
}
|
297
|
-
await (0, util_1.wait)(60 * 1000); // wait 1 minute
|
298
|
-
return this.debounce();
|
299
|
-
}
|
300
|
-
core_1.ux.log('time to update');
|
301
|
-
}
|
302
|
-
// removes any unused CLIs
|
303
|
-
async tidy() {
|
304
|
-
core_1.ux.debug('tidy');
|
305
|
-
try {
|
306
|
-
const root = this.clientRoot;
|
307
|
-
if (!await fs.pathExists(root))
|
308
|
-
return;
|
309
|
-
const files = await (0, util_1.ls)(root);
|
310
|
-
const promises = files.map(async (f) => {
|
311
|
-
if (['bin', 'current'].includes(path.basename(f.path)))
|
312
|
-
return;
|
313
|
-
// if 1.2.3-shasha7 starts with 1.2.3
|
314
|
-
if (path.basename(f.path).startsWith(this.config.version))
|
315
|
-
return;
|
316
|
-
const mtime = f.stat.mtime;
|
317
|
-
mtime.setHours(mtime.getHours() + (42 * 24));
|
318
|
-
if (mtime < new Date()) {
|
319
|
-
await fs.remove(f.path);
|
320
|
-
}
|
321
|
-
});
|
322
|
-
for (const p of promises)
|
323
|
-
await p; // eslint-disable-line no-await-in-loop
|
324
|
-
await this.logChop();
|
325
|
-
}
|
326
|
-
catch (error) {
|
327
|
-
core_1.ux.warn(error);
|
328
|
-
}
|
329
|
-
}
|
330
|
-
async touch() {
|
331
|
-
// touch the client so it won't be tidied up right away
|
332
|
-
try {
|
333
|
-
const p = path.join(this.clientRoot, this.config.version);
|
334
|
-
core_1.ux.debug('touching client at', p);
|
335
|
-
if (!await fs.pathExists(p))
|
336
|
-
return;
|
337
|
-
await fs.utimes(p, new Date(), new Date());
|
338
|
-
}
|
339
|
-
catch (error) {
|
340
|
-
core_1.ux.warn(error);
|
341
|
-
}
|
342
|
-
}
|
343
|
-
async refreshConfig(version) {
|
344
|
-
this.config = await core_1.Config.load({ root: path.join(this.clientRoot, version) });
|
345
|
-
}
|
346
|
-
async createBin(version) {
|
347
|
-
const dst = this.clientBin;
|
348
|
-
const { bin, windows } = this.config;
|
349
|
-
const binPathEnvVar = this.config.scopedEnvVarKey('BINPATH');
|
350
|
-
const redirectedEnvVar = this.config.scopedEnvVarKey('REDIRECTED');
|
351
|
-
if (windows) {
|
352
|
-
const body = `@echo off
|
353
|
-
setlocal enableextensions
|
354
|
-
set ${redirectedEnvVar}=1
|
355
|
-
set ${binPathEnvVar}=%~dp0${bin}
|
356
|
-
"%~dp0..\\${version}\\bin\\${bin}.cmd" %*
|
357
|
-
`;
|
358
|
-
await fs.outputFile(dst, body);
|
359
|
-
}
|
360
|
-
else {
|
361
|
-
/* eslint-disable no-useless-escape */
|
362
|
-
const body = `#!/usr/bin/env bash
|
363
|
-
set -e
|
364
|
-
get_script_dir () {
|
365
|
-
SOURCE="\${BASH_SOURCE[0]}"
|
366
|
-
# While $SOURCE is a symlink, resolve it
|
367
|
-
while [ -h "$SOURCE" ]; do
|
368
|
-
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
369
|
-
SOURCE="$( readlink "$SOURCE" )"
|
370
|
-
# If $SOURCE was a relative symlink (so no "/" as prefix, need to resolve it relative to the symlink base directory
|
371
|
-
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
|
372
|
-
done
|
373
|
-
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
374
|
-
echo "$DIR"
|
375
|
-
}
|
376
|
-
DIR=$(get_script_dir)
|
377
|
-
${binPathEnvVar}="\$DIR/${bin}" ${redirectedEnvVar}=1 "$DIR/../${version}/bin/${bin}" "$@"
|
378
|
-
`;
|
379
|
-
/* eslint-enable no-useless-escape */
|
380
|
-
await fs.remove(dst);
|
381
|
-
await fs.outputFile(dst, body);
|
382
|
-
await fs.chmod(dst, 0o755);
|
383
|
-
await fs.remove(path.join(this.clientRoot, 'current'));
|
384
|
-
await fs.symlink(`./${version}`, path.join(this.clientRoot, 'current'));
|
385
|
-
}
|
386
|
-
}
|
387
|
-
}
|
388
|
-
exports.Updater = Updater;
|
package/lib/util.js
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.wait = exports.rm = exports.ls = exports.touch = void 0;
|
4
|
-
const tslib_1 = require("tslib");
|
5
|
-
const fs = (0, tslib_1.__importStar)(require("fs-extra"));
|
6
|
-
const path = (0, tslib_1.__importStar)(require("path"));
|
7
|
-
async function touch(p) {
|
8
|
-
try {
|
9
|
-
await fs.utimes(p, new Date(), new Date());
|
10
|
-
}
|
11
|
-
catch (_a) {
|
12
|
-
await fs.outputFile(p, '');
|
13
|
-
}
|
14
|
-
}
|
15
|
-
exports.touch = touch;
|
16
|
-
async function ls(dir) {
|
17
|
-
const files = await fs.readdir(dir);
|
18
|
-
const paths = files.map(f => path.join(dir, f));
|
19
|
-
return Promise.all(paths.map(path => fs.stat(path).then(stat => ({ path, stat }))));
|
20
|
-
}
|
21
|
-
exports.ls = ls;
|
22
|
-
async function rm(dir) {
|
23
|
-
return new Promise(resolve => {
|
24
|
-
fs.rm(dir, { recursive: true, force: true }, () => {
|
25
|
-
resolve();
|
26
|
-
});
|
27
|
-
});
|
28
|
-
}
|
29
|
-
exports.rm = rm;
|
30
|
-
function wait(ms, unref = false) {
|
31
|
-
return new Promise(resolve => {
|
32
|
-
const t = setTimeout(() => resolve(), ms);
|
33
|
-
if (unref)
|
34
|
-
t.unref();
|
35
|
-
});
|
36
|
-
}
|
37
|
-
exports.wait = wait;
|
File without changes
|