@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.
@@ -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.32",
2
+ "version": "4.0.1-qa.0",
3
3
  "commands": {
4
4
  "update": {
5
5
  "id": "update",
package/package.json CHANGED
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "name": "@oclif/plugin-update",
3
- "version": "3.1.32",
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
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": "^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",
@@ -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": "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",