@oclif/plugin-update 3.1.31 → 4.0.1-qa.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 = (0, tslib_1.__importStar)(require("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 cross_spawn_1 = (0, tslib_1.__importDefault)(require("cross-spawn"));
7
- const fs = (0, tslib_1.__importStar)(require("fs-extra"));
8
- const path = (0, tslib_1.__importStar)(require("path"));
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 fs.stat(f);
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 = Object.assign(Object.assign({}, process.env), { [this.config.scopedEnvVarKey('TIMESTAMPS')]: '1', [this.config.scopedEnvVarKey('SKIP_ANALYTICS')]: '1' });
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 (await fs.pathExists(clientDir))
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 fs.outputFile(autoupdatefile, '');
60
+ await (0, promises_1.writeFile)(autoupdatefile, '');
56
61
  debug(`spawning autoupdate on ${binPath}`);
57
- const fd = await fs.open(autoupdatelogfile, 'a');
58
- fs.write(fd, timestamp(`starting \`${binPath} update --autoupdate\` from ${process.argv.slice(1, 3).join(' ')}\n`));
59
- (0, cross_spawn_1.default)(binPath, ['update', '--autoupdate'], {
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', fd, fd],
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 = (0, tslib_1.__importStar)(require("fs-extra"));
6
- const path = (0, tslib_1.__importStar)(require("path"));
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 (fs.pathExistsSync(tmp))
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 (await fs.pathExists(output)) {
64
+ if ((0, node_fs_1.existsSync)(output)) {
64
65
  try {
65
66
  const tmp = getTmp();
66
- await fs.move(output, tmp);
67
- await fs.remove(tmp).catch(debug);
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.remove(output);
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.remove(tmp).catch(debug);
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.remove(tmp).catch(process.emitWarning);
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 fs = (0, tslib_1.__importStar)(require("fs-extra"));
9
- const http_call_1 = (0, tslib_1.__importDefault)(require("http-call"));
10
- const path = (0, tslib_1.__importStar)(require("path"));
11
- const lodash_throttle_1 = (0, tslib_1.__importDefault)(require("lodash.throttle"));
12
- const filesize_1 = (0, tslib_1.__importDefault)(require("filesize"));
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.debounce();
29
+ await debounce(this.config.cacheDir);
29
30
  core_1.ux.action.start(`${this.config.name}: Updating CLI`);
30
- if (this.notUpdatable()) {
31
+ if (notUpdatable(this.config)) {
31
32
  core_1.ux.action.stop('not updatable');
32
33
  return;
33
34
  }
34
- const channel = options.channel || await this.determineChannel(version);
35
- const current = await this.determineCurrentVersion();
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 (this.alreadyOnVersion(current, localVersion || null)) {
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 this.fetchChannelManifest(channel);
65
+ const manifest = await fetchChannelManifest(channel, this.config);
63
66
  const updated = manifest.sha ? `${manifest.version}-${manifest.sha}` : manifest.version;
64
- if (!force && this.alreadyOnVersion(current, updated)) {
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.ensureClientDir();
80
- return fs
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.s3VersionIndexKey());
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 (_a) {
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 = this.s3VersionManifestKey(version, hash);
152
- return this.fetchManifest(s3Key);
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 ${color_1.default.green(current)} to ${color_1.default.green(updated)}${channel === 'stable' ? '' : ' (' + color_1.default.yellow(channel) + ')'}`);
200
- await this.ensureClientDir();
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 || !await fs.pathExists(output))
203
- await this.downloadAndExtract(output, manifest, channel);
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 this.setChannel(channel);
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 ${color_1.default.green(current)} to ${color_1.default.green(updated)}`);
210
- await this.ensureClientDir();
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 (!await fs.pathExists(root))
136
+ if (!(0, node_fs_1.existsSync)(root))
309
137
  return;
310
138
  const files = await (0, util_1.ls)(root);
311
- const promises = files.map(async (f) => {
312
- if (['bin', 'current', this.config.version].includes(path.basename(f.path)))
313
- return;
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
- if (mtime < new Date()) {
317
- await fs.remove(f.path);
318
- }
319
- });
320
- for (const p of promises)
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 (!await fs.pathExists(p))
158
+ if (!(0, node_fs_1.existsSync)(p))
334
159
  return;
335
- await fs.utimes(p, new Date(), new Date());
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 fs.outputFile(dst, body);
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 fs.remove(dst);
379
- await fs.outputFile(dst, body);
380
- await fs.chmod(dst, 0o755);
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
@@ -5,5 +5,4 @@ export declare function ls(dir: string): Promise<Array<{
5
5
  path: string;
6
6
  stat: fs.Stats;
7
7
  }>>;
8
- export declare function rm(dir: string): Promise<void>;
9
8
  export declare function wait(ms: number, unref?: boolean): Promise<void>;
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.rm = exports.ls = exports.touch = void 0;
3
+ exports.wait = exports.ls = exports.touch = void 0;
4
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"));
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 (_a) {
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);
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.1.31",
2
+ "version": "4.0.1-qa.0",
3
3
  "commands": {
4
4
  "update": {
5
5
  "id": "update",
package/package.json CHANGED
@@ -1,37 +1,34 @@
1
1
  {
2
2
  "name": "@oclif/plugin-update",
3
- "version": "3.1.31",
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
- "cross-spawn": "^7.0.3",
8
+ "chalk": "^4",
10
9
  "debug": "^4.3.1",
11
10
  "filesize": "^6.1.0",
12
- "fs-extra": "^9.0.1",
11
+ "fs-extra": "^11.1.1",
13
12
  "http-call": "^5.3.0",
14
- "inquirer": "^8.2.5",
13
+ "inquirer": "^8.2.6",
15
14
  "lodash.throttle": "^4.1.1",
16
15
  "log-chopper": "^1.0.2",
17
16
  "semver": "^7.5.4",
18
17
  "tar-fs": "^2.1.1"
19
18
  },
20
19
  "devDependencies": {
21
- "@oclif/plugin-help": "^5.2.16",
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": "^8.0.1",
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": "^14.18.54",
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",
@@ -39,15 +36,15 @@
39
36
  "globby": "^11.0.2",
40
37
  "mocha": "^9",
41
38
  "nock": "^13.3.2",
42
- "oclif": "^3.10.0",
39
+ "oclif": "^3.11.3",
43
40
  "qqjs": "^0.3.11",
44
41
  "sinon": "^12.0.1",
45
42
  "ts-node": "^9.1.1",
46
43
  "tslib": "^2.6.1",
47
- "typescript": "4.4.3"
44
+ "typescript": "^5.1.6"
48
45
  },
49
46
  "engines": {
50
- "node": ">=12.0.0"
47
+ "node": ">=16.0.0"
51
48
  },
52
49
  "files": [
53
50
  "oclif.manifest.json",