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