@oclif/plugin-update 2.1.3 → 3.0.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/CHANGELOG.md +55 -0
- package/README.md +23 -3
- package/lib/commands/update.d.ts +9 -4
- package/lib/commands/update.js +70 -13
- package/lib/hooks/init.js +8 -6
- package/lib/tar.js +3 -2
- package/lib/update.d.ts +25 -18
- package/lib/update.js +205 -164
- package/lib/util.d.ts +3 -2
- package/lib/util.js +12 -3
- package/oclif.manifest.json +1 -1
- package/package.json +11 -9
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,61 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
4
4
|
|
5
|
+
## [3.0.0](https://github.com/oclif/plugin-update/compare/v2.2.0...v3.0.0) (2022-02-11)
|
6
|
+
|
7
|
+
|
8
|
+
### ⚠ BREAKING CHANGES
|
9
|
+
|
10
|
+
* rename --from-local to --local
|
11
|
+
|
12
|
+
* chore: update examples
|
13
|
+
|
14
|
+
* feat!: remove --local flag
|
15
|
+
* remove --local flag
|
16
|
+
|
17
|
+
* refactor: Updater
|
18
|
+
|
19
|
+
* fix: handle non-existent version index
|
20
|
+
|
21
|
+
* chore: code review
|
22
|
+
|
23
|
+
* chore: update test
|
24
|
+
|
25
|
+
* fix: skip preupdate hook if using --hard
|
26
|
+
|
27
|
+
* fix: update root after hard update
|
28
|
+
|
29
|
+
* fix: works with sf
|
30
|
+
|
31
|
+
* fix: replace --hard with --force
|
32
|
+
|
33
|
+
* chore: update examples
|
34
|
+
|
35
|
+
* chore: bump to 3.0.0
|
36
|
+
|
37
|
+
* BREAKING CHANGE: add new update features (#368) ([091c176](https://github.com/oclif/plugin-update/commit/091c176ef639a7a0c991ab1b726e832783670324)), closes [#368](https://github.com/oclif/plugin-update/issues/368)
|
38
|
+
|
39
|
+
## [2.2.0](https://github.com/oclif/plugin-update/compare/v2.1.5...v2.2.0) (2022-01-28)
|
40
|
+
|
41
|
+
|
42
|
+
### Features
|
43
|
+
|
44
|
+
* remove cli-ux ([#364](https://github.com/oclif/plugin-update/issues/364)) ([980c612](https://github.com/oclif/plugin-update/commit/980c6121846a9201da4071f6edc2afc0219751f6))
|
45
|
+
|
46
|
+
### [2.1.5](https://github.com/oclif/plugin-update/compare/v2.1.4...v2.1.5) (2022-01-06)
|
47
|
+
|
48
|
+
|
49
|
+
### Bug Fixes
|
50
|
+
|
51
|
+
* add main to package.json ([#361](https://github.com/oclif/plugin-update/issues/361)) ([9daade3](https://github.com/oclif/plugin-update/commit/9daade340a102a8e0a57ae8a278b3803daf32117))
|
52
|
+
|
53
|
+
### [2.1.4](https://github.com/oclif/plugin-update/compare/v2.1.3...v2.1.4) (2022-01-06)
|
54
|
+
|
55
|
+
|
56
|
+
### Bug Fixes
|
57
|
+
|
58
|
+
* bump @oclif/core ([#360](https://github.com/oclif/plugin-update/issues/360)) ([749b949](https://github.com/oclif/plugin-update/commit/749b949e8127a5b594f28fe7468c1e13440f9817))
|
59
|
+
|
5
60
|
### [2.1.3](https://github.com/oclif/plugin-update/compare/v2.1.2...v2.1.3) (2021-12-09)
|
6
61
|
|
7
62
|
### [2.1.2](https://github.com/oclif/plugin-update/compare/v2.1.1...v2.1.2) (2021-12-08)
|
package/README.md
CHANGED
@@ -24,14 +24,34 @@ update the oclif-example CLI
|
|
24
24
|
|
25
25
|
```
|
26
26
|
USAGE
|
27
|
-
$ oclif-example update [CHANNEL] [
|
27
|
+
$ oclif-example update [CHANNEL] [-a] [-v <value> | -i] [--force]
|
28
28
|
|
29
29
|
FLAGS
|
30
|
-
|
30
|
+
-a, --available Install a specific version.
|
31
|
+
-i, --interactive Interactively select version to install. This is ignored if a channel is provided.
|
32
|
+
-v, --version=<value> Install a specific version.
|
33
|
+
--force Force a re-download of the requested version.
|
31
34
|
|
32
35
|
DESCRIPTION
|
33
36
|
update the oclif-example CLI
|
37
|
+
|
38
|
+
EXAMPLES
|
39
|
+
Update to the stable channel:
|
40
|
+
|
41
|
+
$ oclif-example update stable
|
42
|
+
|
43
|
+
Update to a specific version:
|
44
|
+
|
45
|
+
$ oclif-example update --version 1.0.0
|
46
|
+
|
47
|
+
Interactively select version:
|
48
|
+
|
49
|
+
$ oclif-example update --interactive
|
50
|
+
|
51
|
+
See available versions:
|
52
|
+
|
53
|
+
$ oclif-example update --available
|
34
54
|
```
|
35
55
|
|
36
|
-
_See code: [src/commands/update.ts](https://github.com/oclif/plugin-update/blob/
|
56
|
+
_See code: [src/commands/update.ts](https://github.com/oclif/plugin-update/blob/v3.0.0/src/commands/update.ts)_
|
37
57
|
<!-- commandsstop -->
|
package/lib/commands/update.d.ts
CHANGED
@@ -5,12 +5,17 @@ export default class UpdateCommand extends Command {
|
|
5
5
|
name: string;
|
6
6
|
optional: boolean;
|
7
7
|
}[];
|
8
|
+
static examples: {
|
9
|
+
description: string;
|
10
|
+
command: string;
|
11
|
+
}[];
|
8
12
|
static flags: {
|
9
13
|
autoupdate: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
10
|
-
|
14
|
+
available: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
15
|
+
version: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
|
16
|
+
interactive: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
17
|
+
force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
11
18
|
};
|
12
|
-
private channel;
|
13
|
-
private readonly clientRoot;
|
14
|
-
private readonly clientBin;
|
15
19
|
run(): Promise<void>;
|
20
|
+
private promptForVersion;
|
16
21
|
}
|
package/lib/commands/update.js
CHANGED
@@ -1,28 +1,85 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const tslib_1 = require("tslib");
|
3
4
|
const core_1 = require("@oclif/core");
|
4
|
-
const
|
5
|
-
const path = require("path");
|
5
|
+
const inquirer_1 = require("inquirer");
|
6
|
+
const path = (0, tslib_1.__importStar)(require("path"));
|
7
|
+
const semver_1 = require("semver");
|
6
8
|
const update_1 = require("../update");
|
7
|
-
async function getPinToVersion() {
|
8
|
-
return cli_ux_1.default.prompt('Enter a version to update to');
|
9
|
-
}
|
10
9
|
class UpdateCommand extends core_1.Command {
|
11
|
-
constructor() {
|
12
|
-
super(...arguments);
|
13
|
-
this.clientRoot = this.config.scopedEnvVar('OCLIF_CLIENT_HOME') || path.join(this.config.dataDir, 'client');
|
14
|
-
this.clientBin = path.join(this.clientRoot, 'bin', this.config.windows ? `${this.config.bin}.cmd` : this.config.bin);
|
15
|
-
}
|
16
10
|
async run() {
|
17
11
|
const { args, flags } = await this.parse(UpdateCommand);
|
18
|
-
const
|
19
|
-
|
12
|
+
const updater = new update_1.Updater(this.config);
|
13
|
+
if (flags.available) {
|
14
|
+
const index = await updater.fetchVersionIndex();
|
15
|
+
const allVersions = (0, semver_1.sort)(Object.keys(index)).reverse();
|
16
|
+
const localVersions = await updater.findLocalVersions();
|
17
|
+
const table = allVersions.map(version => {
|
18
|
+
const location = localVersions.find(l => path.basename(l).startsWith(version)) || index[version];
|
19
|
+
return { version, location };
|
20
|
+
});
|
21
|
+
core_1.CliUx.ux.table(table, { version: {}, location: {} });
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
if (args.channel && flags.version) {
|
25
|
+
this.error('You cannot specifiy both a version and a channel.');
|
26
|
+
}
|
27
|
+
return updater.runUpdate({
|
28
|
+
channel: args.channel,
|
29
|
+
autoUpdate: flags.autoupdate,
|
30
|
+
force: flags.force,
|
31
|
+
version: flags.interactive ? await this.promptForVersion(updater) : flags.version,
|
32
|
+
});
|
33
|
+
}
|
34
|
+
async promptForVersion(updater) {
|
35
|
+
const choices = (0, semver_1.sort)(Object.keys(await updater.fetchVersionIndex())).reverse();
|
36
|
+
const { version } = await (0, inquirer_1.prompt)({
|
37
|
+
name: 'version',
|
38
|
+
message: 'Select a version to update to',
|
39
|
+
type: 'list',
|
40
|
+
choices: [...choices, new inquirer_1.Separator()],
|
41
|
+
});
|
42
|
+
return version;
|
20
43
|
}
|
21
44
|
}
|
22
45
|
exports.default = UpdateCommand;
|
23
46
|
UpdateCommand.description = 'update the <%= config.bin %> CLI';
|
24
47
|
UpdateCommand.args = [{ name: 'channel', optional: true }];
|
48
|
+
UpdateCommand.examples = [
|
49
|
+
{
|
50
|
+
description: 'Update to the stable channel:',
|
51
|
+
command: '<%= config.bin %> <%= command.id %> stable',
|
52
|
+
},
|
53
|
+
{
|
54
|
+
description: 'Update to a specific version:',
|
55
|
+
command: '<%= config.bin %> <%= command.id %> --version 1.0.0',
|
56
|
+
},
|
57
|
+
{
|
58
|
+
description: 'Interactively select version:',
|
59
|
+
command: '<%= config.bin %> <%= command.id %> --interactive',
|
60
|
+
},
|
61
|
+
{
|
62
|
+
description: 'See available versions:',
|
63
|
+
command: '<%= config.bin %> <%= command.id %> --available',
|
64
|
+
},
|
65
|
+
];
|
25
66
|
UpdateCommand.flags = {
|
26
67
|
autoupdate: core_1.Flags.boolean({ hidden: true }),
|
27
|
-
|
68
|
+
available: core_1.Flags.boolean({
|
69
|
+
char: 'a',
|
70
|
+
description: 'Install a specific version.',
|
71
|
+
}),
|
72
|
+
version: core_1.Flags.string({
|
73
|
+
char: 'v',
|
74
|
+
description: 'Install a specific version.',
|
75
|
+
exclusive: ['interactive'],
|
76
|
+
}),
|
77
|
+
interactive: core_1.Flags.boolean({
|
78
|
+
char: 'i',
|
79
|
+
description: 'Interactively select version to install. This is ignored if a channel is provided.',
|
80
|
+
exclusive: ['version'],
|
81
|
+
}),
|
82
|
+
force: core_1.Flags.boolean({
|
83
|
+
description: 'Force a re-download of the requested version.',
|
84
|
+
}),
|
28
85
|
};
|
package/lib/hooks/init.js
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.init = void 0;
|
4
|
-
const
|
5
|
-
const
|
6
|
-
const
|
7
|
-
const
|
4
|
+
const tslib_1 = require("tslib");
|
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"));
|
8
9
|
const util_1 = require("../util");
|
10
|
+
// eslint-disable-next-line unicorn/prefer-module
|
9
11
|
const debug = require('debug')('cli:updater');
|
10
12
|
function timestamp(msg) {
|
11
13
|
return `[${new Date().toISOString()}] ${msg}`;
|
@@ -36,7 +38,7 @@ const init = async function (opts) {
|
|
36
38
|
}
|
37
39
|
catch (error) {
|
38
40
|
if (error.code !== 'ENOENT')
|
39
|
-
|
41
|
+
core_1.CliUx.ux.error(error.stack);
|
40
42
|
if (global.testing)
|
41
43
|
return false;
|
42
44
|
debug('autoupdate ENOENT');
|
@@ -54,7 +56,7 @@ const init = async function (opts) {
|
|
54
56
|
debug(`spawning autoupdate on ${binPath}`);
|
55
57
|
const fd = await fs.open(autoupdatelogfile, 'a');
|
56
58
|
fs.write(fd, timestamp(`starting \`${binPath} update --autoupdate\` from ${process.argv.slice(1, 3).join(' ')}\n`));
|
57
|
-
|
59
|
+
(0, cross_spawn_1.default)(binPath, ['update', '--autoupdate'], {
|
58
60
|
detached: !this.config.windows,
|
59
61
|
stdio: ['ignore', fd, fd],
|
60
62
|
env: autoupdateEnv,
|
package/lib/tar.js
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.extract = void 0;
|
4
|
-
const
|
5
|
-
const
|
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"));
|
6
7
|
const util_1 = require("./util");
|
7
8
|
const debug = require('debug')('oclif-update');
|
8
9
|
const ignore = (_, header) => {
|
package/lib/update.d.ts
CHANGED
@@ -1,36 +1,43 @@
|
|
1
1
|
import { Config } from '@oclif/core';
|
2
|
-
export
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
export declare namespace Updater {
|
3
|
+
type Options = {
|
4
|
+
autoUpdate: boolean;
|
5
|
+
channel?: string | undefined;
|
6
|
+
version?: string | undefined;
|
7
|
+
force?: boolean;
|
8
|
+
};
|
9
|
+
type VersionIndex = Record<string, string>;
|
9
10
|
}
|
10
|
-
export
|
11
|
-
private
|
12
|
-
private channel;
|
13
|
-
private currentVersion?;
|
14
|
-
private updatedVersion;
|
11
|
+
export declare class Updater {
|
12
|
+
private config;
|
15
13
|
private readonly clientRoot;
|
16
14
|
private readonly clientBin;
|
17
|
-
constructor(
|
18
|
-
runUpdate(): Promise<void>;
|
15
|
+
constructor(config: Config);
|
16
|
+
runUpdate(options: Updater.Options): Promise<void>;
|
17
|
+
findLocalVersions(): Promise<string[]>;
|
18
|
+
fetchVersionIndex(): Promise<Updater.VersionIndex>;
|
19
|
+
private ensureClientDir;
|
20
|
+
private composeS3SubDir;
|
21
|
+
private s3ChannelManifestKey;
|
22
|
+
private s3VersionManifestKey;
|
23
|
+
private s3VersionIndexKey;
|
24
|
+
private fetchChannelManifest;
|
25
|
+
private fetchVersionManifest;
|
19
26
|
private fetchManifest;
|
20
27
|
private downloadAndExtract;
|
21
28
|
private update;
|
22
29
|
private updateToExistingVersion;
|
23
|
-
private
|
30
|
+
private notUpdatable;
|
31
|
+
private alreadyOnVersion;
|
24
32
|
private determineChannel;
|
25
33
|
private determineCurrentVersion;
|
26
|
-
private
|
34
|
+
private findLocalVersion;
|
27
35
|
private setChannel;
|
28
36
|
private logChop;
|
29
37
|
private mtime;
|
30
38
|
private debounce;
|
31
39
|
private tidy;
|
32
40
|
private touch;
|
33
|
-
private
|
41
|
+
private refreshConfig;
|
34
42
|
private createBin;
|
35
|
-
private ensureClientDir;
|
36
43
|
}
|
package/lib/update.js
CHANGED
@@ -1,116 +1,190 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
|
4
|
-
const
|
5
|
-
|
6
|
-
const
|
7
|
-
const
|
8
|
-
const
|
3
|
+
exports.Updater = void 0;
|
4
|
+
const tslib_1 = require("tslib");
|
5
|
+
/* eslint-disable unicorn/prefer-module */
|
6
|
+
const color_1 = (0, tslib_1.__importDefault)(require("@oclif/color"));
|
7
|
+
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"));
|
9
13
|
const tar_1 = require("./tar");
|
10
14
|
const util_1 = require("./util");
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
const filesize = (n) => {
|
16
|
+
const [num, suffix] = (0, filesize_1.default)(n, { output: 'array' });
|
17
|
+
return Number.parseFloat(num).toFixed(1) + ` ${suffix}`;
|
18
|
+
};
|
19
|
+
class Updater {
|
20
|
+
constructor(config) {
|
21
|
+
this.config = config;
|
22
|
+
this.clientRoot = config.scopedEnvVar('OCLIF_CLIENT_HOME') || path.join(config.dataDir, 'client');
|
23
|
+
this.clientBin = path.join(this.clientRoot, 'bin', config.windows ? `${config.bin}.cmd` : config.bin);
|
16
24
|
}
|
17
|
-
async runUpdate() {
|
18
|
-
|
25
|
+
async runUpdate(options) {
|
26
|
+
const { autoUpdate, version, force = false } = options;
|
27
|
+
if (autoUpdate)
|
19
28
|
await this.debounce();
|
20
|
-
|
21
|
-
if (this.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
core_1.CliUx.ux.action.start(`${this.config.name}: Updating CLI`);
|
30
|
+
if (this.notUpdatable()) {
|
31
|
+
core_1.CliUx.ux.action.stop('not updatable');
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
const channel = options.channel || await this.determineChannel();
|
35
|
+
const current = await this.determineCurrentVersion();
|
36
|
+
if (version) {
|
37
|
+
const localVersion = force ? null : await this.findLocalVersion(version);
|
38
|
+
if (this.alreadyOnVersion(current, localVersion || null)) {
|
39
|
+
core_1.CliUx.ux.action.stop(this.config.scopedEnvVar('HIDE_UPDATED_MESSAGE') ? 'done' : `already on version ${current}`);
|
40
|
+
return;
|
41
|
+
}
|
42
|
+
await this.config.runHook('preupdate', { channel, version });
|
43
|
+
if (localVersion) {
|
44
|
+
await this.updateToExistingVersion(current, localVersion);
|
34
45
|
}
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
46
|
+
else {
|
47
|
+
const index = await this.fetchVersionIndex();
|
48
|
+
const url = index[version];
|
49
|
+
if (!url) {
|
50
|
+
throw new Error(`${version} not found in index:\n${Object.keys(index).join(', ')}`);
|
51
|
+
}
|
52
|
+
const manifest = await this.fetchVersionManifest(version, url);
|
53
|
+
const updated = manifest.sha ? `${manifest.version}-${manifest.sha}` : manifest.version;
|
54
|
+
await this.update(manifest, current, updated, force, channel);
|
55
|
+
}
|
56
|
+
await this.config.runHook('update', { channel, version });
|
57
|
+
core_1.CliUx.ux.action.stop();
|
58
|
+
core_1.CliUx.ux.log();
|
59
|
+
core_1.CliUx.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}.`);
|
40
60
|
}
|
41
61
|
else {
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
await this.tidy();
|
54
|
-
await this.options.config.runHook('update', { channel: this.channel });
|
62
|
+
const manifest = await this.fetchChannelManifest(channel);
|
63
|
+
const updated = manifest.sha ? `${manifest.version}-${manifest.sha}` : manifest.version;
|
64
|
+
if (!force && this.alreadyOnVersion(current, updated)) {
|
65
|
+
core_1.CliUx.ux.action.stop(this.config.scopedEnvVar('HIDE_UPDATED_MESSAGE') ? 'done' : `already on version ${current}`);
|
66
|
+
}
|
67
|
+
else {
|
68
|
+
await this.config.runHook('preupdate', { channel, version: updated });
|
69
|
+
await this.update(manifest, current, updated, force, channel);
|
70
|
+
}
|
71
|
+
await this.config.runHook('update', { channel, version: updated });
|
72
|
+
core_1.CliUx.ux.action.stop();
|
55
73
|
}
|
56
|
-
|
57
|
-
|
74
|
+
await this.touch();
|
75
|
+
await this.tidy();
|
76
|
+
core_1.CliUx.ux.debug('done');
|
77
|
+
}
|
78
|
+
async findLocalVersions() {
|
79
|
+
await this.ensureClientDir();
|
80
|
+
return fs
|
81
|
+
.readdirSync(this.clientRoot)
|
82
|
+
.filter(dirOrFile => dirOrFile !== 'bin' && dirOrFile !== 'current')
|
83
|
+
.map(f => path.join(this.clientRoot, f));
|
58
84
|
}
|
59
|
-
async
|
60
|
-
|
61
|
-
|
85
|
+
async fetchVersionIndex() {
|
86
|
+
core_1.CliUx.ux.action.status = 'fetching version index';
|
87
|
+
const newIndexUrl = this.config.s3Url(this.s3VersionIndexKey());
|
62
88
|
try {
|
63
|
-
const
|
64
|
-
channel: this.channel,
|
65
|
-
platform: this.options.config.platform,
|
66
|
-
arch: this.options.config.arch,
|
67
|
-
}));
|
68
|
-
const { body } = await http.get(url);
|
69
|
-
// in case the content-type is not set, parse as a string
|
70
|
-
// this will happen if uploading without `oclif-dev publish`
|
89
|
+
const { body } = await http_call_1.default.get(newIndexUrl);
|
71
90
|
if (typeof body === 'string') {
|
72
91
|
return JSON.parse(body);
|
73
92
|
}
|
74
93
|
return body;
|
75
94
|
}
|
95
|
+
catch (_a) {
|
96
|
+
throw new Error(`No version indices exist for ${this.config.name}.`);
|
97
|
+
}
|
98
|
+
}
|
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, platform, arch } = this.config;
|
123
|
+
const s3SubDir = this.composeS3SubDir();
|
124
|
+
return path.join(s3SubDir, 'channels', channel, `${bin}-${platform}-${arch}-buildmanifest`);
|
125
|
+
}
|
126
|
+
s3VersionManifestKey(version, hash) {
|
127
|
+
const { bin, platform, arch } = this.config;
|
128
|
+
const s3SubDir = this.composeS3SubDir();
|
129
|
+
return path.join(s3SubDir, 'versions', version, hash, `${bin}-v${version}-${hash}-${platform}-${arch}-buildmanifest`);
|
130
|
+
}
|
131
|
+
s3VersionIndexKey() {
|
132
|
+
const { bin, platform, arch } = this.config;
|
133
|
+
const s3SubDir = this.composeS3SubDir();
|
134
|
+
return path.join(s3SubDir, 'versions', `${bin}-${platform}-${arch}-tar-gz.json`);
|
135
|
+
}
|
136
|
+
async fetchChannelManifest(channel) {
|
137
|
+
const s3Key = this.s3ChannelManifestKey(channel);
|
138
|
+
try {
|
139
|
+
return await this.fetchManifest(s3Key);
|
140
|
+
}
|
76
141
|
catch (error) {
|
77
142
|
if (error.statusCode === 403)
|
78
|
-
throw new Error(`HTTP 403: Invalid channel ${
|
143
|
+
throw new Error(`HTTP 403: Invalid channel ${channel}`);
|
79
144
|
throw error;
|
80
145
|
}
|
81
146
|
}
|
147
|
+
async fetchVersionManifest(version, url) {
|
148
|
+
const parts = url.split('/');
|
149
|
+
const hashIndex = parts.indexOf(version) + 1;
|
150
|
+
const hash = parts[hashIndex];
|
151
|
+
const s3Key = this.s3VersionManifestKey(version, hash);
|
152
|
+
return this.fetchManifest(s3Key);
|
153
|
+
}
|
154
|
+
async fetchManifest(s3Key) {
|
155
|
+
core_1.CliUx.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
|
+
}
|
82
163
|
async downloadAndExtract(output, manifest, channel) {
|
83
164
|
const { version, gz, sha256gz } = manifest;
|
84
|
-
const
|
85
|
-
const [num, suffix] = require('filesize')(n, { output: 'array' });
|
86
|
-
return num.toFixed(1) + ` ${suffix}`;
|
87
|
-
};
|
88
|
-
const http = require('http-call').HTTP;
|
89
|
-
const gzUrl = gz || this.options.config.s3Url(this.options.config.s3Key('versioned', {
|
165
|
+
const gzUrl = gz || this.config.s3Url(this.config.s3Key('versioned', {
|
90
166
|
version,
|
91
167
|
channel,
|
92
|
-
bin: this.
|
93
|
-
platform: this.
|
94
|
-
arch: this.
|
168
|
+
bin: this.config.bin,
|
169
|
+
platform: this.config.platform,
|
170
|
+
arch: this.config.arch,
|
95
171
|
ext: 'gz',
|
96
172
|
}));
|
97
|
-
const { response: stream } = await
|
173
|
+
const { response: stream } = await http_call_1.default.stream(gzUrl);
|
98
174
|
stream.pause();
|
99
|
-
const baseDir = manifest.baseDir || this.
|
175
|
+
const baseDir = manifest.baseDir || this.config.s3Key('baseDir', {
|
100
176
|
version,
|
101
177
|
channel,
|
102
|
-
bin: this.
|
103
|
-
platform: this.
|
104
|
-
arch: this.
|
178
|
+
bin: this.config.bin,
|
179
|
+
platform: this.config.platform,
|
180
|
+
arch: this.config.arch,
|
105
181
|
});
|
106
182
|
const extraction = (0, tar_1.extract)(stream, baseDir, output, sha256gz);
|
107
|
-
|
108
|
-
if (cli_ux_1.default.action.frames) {
|
109
|
-
// if spinner action
|
183
|
+
if (core_1.CliUx.ux.action.type === 'spinner') {
|
110
184
|
const total = Number.parseInt(stream.headers['content-length'], 10);
|
111
185
|
let current = 0;
|
112
|
-
const updateStatus =
|
113
|
-
|
186
|
+
const updateStatus = (0, lodash_throttle_1.default)((newStatus) => {
|
187
|
+
core_1.CliUx.ux.action.status = newStatus;
|
114
188
|
}, 250, { leading: true, trailing: false });
|
115
189
|
stream.on('data', data => {
|
116
190
|
current += data.length;
|
@@ -120,73 +194,72 @@ class UpdateCli {
|
|
120
194
|
stream.resume();
|
121
195
|
await extraction;
|
122
196
|
}
|
123
|
-
|
124
|
-
|
197
|
+
// eslint-disable-next-line max-params
|
198
|
+
async update(manifest, current, updated, force, channel) {
|
199
|
+
core_1.CliUx.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) + ')'}`);
|
125
200
|
await this.ensureClientDir();
|
126
|
-
const output = path.join(this.clientRoot,
|
127
|
-
if (!await fs.pathExists(output))
|
201
|
+
const output = path.join(this.clientRoot, updated);
|
202
|
+
if (force || !await fs.pathExists(output))
|
128
203
|
await this.downloadAndExtract(output, manifest, channel);
|
129
|
-
|
130
|
-
await this.setChannel();
|
131
|
-
await this.createBin(
|
132
|
-
await this.touch();
|
133
|
-
await this.reexec();
|
204
|
+
await this.refreshConfig(updated);
|
205
|
+
await this.setChannel(channel);
|
206
|
+
await this.createBin(updated);
|
134
207
|
}
|
135
|
-
async updateToExistingVersion(
|
136
|
-
|
137
|
-
await this.
|
208
|
+
async updateToExistingVersion(current, updated) {
|
209
|
+
core_1.CliUx.ux.action.start(`${this.config.name}: Updating CLI from ${color_1.default.green(current)} to ${color_1.default.green(updated)}`);
|
210
|
+
await this.ensureClientDir();
|
211
|
+
await this.refreshConfig(updated);
|
212
|
+
await this.createBin(updated);
|
138
213
|
}
|
139
|
-
|
140
|
-
if (!this.
|
141
|
-
const instructions = this.
|
214
|
+
notUpdatable() {
|
215
|
+
if (!this.config.binPath) {
|
216
|
+
const instructions = this.config.scopedEnvVar('UPDATE_INSTRUCTIONS');
|
142
217
|
if (instructions)
|
143
|
-
|
144
|
-
return
|
145
|
-
}
|
146
|
-
if (this.currentVersion === this.updatedVersion) {
|
147
|
-
if (this.options.config.scopedEnvVar('HIDE_UPDATED_MESSAGE'))
|
148
|
-
return 'done';
|
149
|
-
return `already on latest version: ${this.currentVersion}`;
|
218
|
+
core_1.CliUx.ux.warn(instructions);
|
219
|
+
return true;
|
150
220
|
}
|
151
221
|
return false;
|
152
222
|
}
|
223
|
+
alreadyOnVersion(current, updated) {
|
224
|
+
return current === updated;
|
225
|
+
}
|
153
226
|
async determineChannel() {
|
154
|
-
const channelPath = path.join(this.
|
227
|
+
const channelPath = path.join(this.config.dataDir, 'channel');
|
155
228
|
if (fs.existsSync(channelPath)) {
|
156
229
|
const channel = await fs.readFile(channelPath, 'utf8');
|
157
230
|
return String(channel).trim();
|
158
231
|
}
|
159
|
-
return this.
|
232
|
+
return this.config.channel || 'stable';
|
160
233
|
}
|
161
234
|
async determineCurrentVersion() {
|
162
235
|
try {
|
163
236
|
const currentVersion = await fs.readFile(this.clientBin, 'utf8');
|
164
237
|
const matches = currentVersion.match(/\.\.[/\\|](.+)[/\\|]bin/);
|
165
|
-
return matches ? matches[1] : this.
|
238
|
+
return matches ? matches[1] : this.config.version;
|
166
239
|
}
|
167
240
|
catch (error) {
|
168
|
-
|
241
|
+
core_1.CliUx.ux.debug(error);
|
169
242
|
}
|
170
|
-
return this.
|
243
|
+
return this.config.version;
|
171
244
|
}
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
245
|
+
async findLocalVersion(version) {
|
246
|
+
const versions = await this.findLocalVersions();
|
247
|
+
return versions
|
248
|
+
.map(file => path.basename(file))
|
249
|
+
.find(file => file.startsWith(version));
|
177
250
|
}
|
178
|
-
async setChannel() {
|
179
|
-
const channelPath = path.join(this.
|
180
|
-
fs.writeFile(channelPath,
|
251
|
+
async setChannel(channel) {
|
252
|
+
const channelPath = path.join(this.config.dataDir, 'channel');
|
253
|
+
fs.writeFile(channelPath, channel, 'utf8');
|
181
254
|
}
|
182
255
|
async logChop() {
|
183
256
|
try {
|
184
|
-
|
257
|
+
core_1.CliUx.ux.debug('log chop');
|
185
258
|
const logChopper = require('log-chopper').default;
|
186
|
-
await logChopper.chop(this.
|
259
|
+
await logChopper.chop(this.config.errlog);
|
187
260
|
}
|
188
261
|
catch (error) {
|
189
|
-
|
262
|
+
core_1.CliUx.ux.debug(error.message);
|
190
263
|
}
|
191
264
|
}
|
192
265
|
async mtime(f) {
|
@@ -196,32 +269,33 @@ class UpdateCli {
|
|
196
269
|
// when autoupdating, wait until the CLI isn't active
|
197
270
|
async debounce() {
|
198
271
|
let output = false;
|
199
|
-
const lastrunfile = path.join(this.
|
272
|
+
const lastrunfile = path.join(this.config.cacheDir, 'lastrun');
|
200
273
|
const m = await this.mtime(lastrunfile);
|
201
274
|
m.setHours(m.getHours() + 1);
|
202
275
|
if (m > new Date()) {
|
203
276
|
const msg = `waiting until ${m.toISOString()} to update`;
|
204
277
|
if (output) {
|
205
|
-
|
278
|
+
core_1.CliUx.ux.debug(msg);
|
206
279
|
}
|
207
280
|
else {
|
208
|
-
|
281
|
+
core_1.CliUx.ux.log(msg);
|
209
282
|
output = true;
|
210
283
|
}
|
211
284
|
await (0, util_1.wait)(60 * 1000); // wait 1 minute
|
212
285
|
return this.debounce();
|
213
286
|
}
|
214
|
-
|
287
|
+
core_1.CliUx.ux.log('time to update');
|
215
288
|
}
|
216
289
|
// removes any unused CLIs
|
217
290
|
async tidy() {
|
291
|
+
core_1.CliUx.ux.debug('tidy');
|
218
292
|
try {
|
219
293
|
const root = this.clientRoot;
|
220
294
|
if (!await fs.pathExists(root))
|
221
295
|
return;
|
222
296
|
const files = await (0, util_1.ls)(root);
|
223
297
|
const promises = files.map(async (f) => {
|
224
|
-
if (['bin', 'current', this.
|
298
|
+
if (['bin', 'current', this.config.version].includes(path.basename(f.path)))
|
225
299
|
return;
|
226
300
|
const mtime = f.stat.mtime;
|
227
301
|
mtime.setHours(mtime.getHours() + (42 * 24));
|
@@ -234,47 +308,30 @@ class UpdateCli {
|
|
234
308
|
await this.logChop();
|
235
309
|
}
|
236
310
|
catch (error) {
|
237
|
-
|
311
|
+
core_1.CliUx.ux.warn(error);
|
238
312
|
}
|
239
313
|
}
|
240
314
|
async touch() {
|
241
315
|
// touch the client so it won't be tidied up right away
|
242
316
|
try {
|
243
|
-
const p = path.join(this.clientRoot, this.
|
244
|
-
|
317
|
+
const p = path.join(this.clientRoot, this.config.version);
|
318
|
+
core_1.CliUx.ux.debug('touching client at', p);
|
245
319
|
if (!await fs.pathExists(p))
|
246
320
|
return;
|
247
321
|
await fs.utimes(p, new Date(), new Date());
|
248
322
|
}
|
249
323
|
catch (error) {
|
250
|
-
|
324
|
+
core_1.CliUx.ux.warn(error);
|
251
325
|
}
|
252
326
|
}
|
253
|
-
async
|
254
|
-
|
255
|
-
return new Promise((_, reject) => {
|
256
|
-
cli_ux_1.default.debug('restarting CLI after update', this.clientBin);
|
257
|
-
spawn(this.clientBin, ['update'], {
|
258
|
-
stdio: 'inherit',
|
259
|
-
env: Object.assign(Object.assign({}, process.env), { [this.options.config.scopedEnvVarKey('HIDE_UPDATED_MESSAGE')]: '1' }),
|
260
|
-
})
|
261
|
-
.on('error', reject)
|
262
|
-
.on('close', (status) => {
|
263
|
-
try {
|
264
|
-
if (status > 0)
|
265
|
-
this.options.exit(status);
|
266
|
-
}
|
267
|
-
catch (error) {
|
268
|
-
reject(error);
|
269
|
-
}
|
270
|
-
});
|
271
|
-
});
|
327
|
+
async refreshConfig(version) {
|
328
|
+
this.config = await core_1.Config.load({ root: path.join(this.clientRoot, version) });
|
272
329
|
}
|
273
330
|
async createBin(version) {
|
274
331
|
const dst = this.clientBin;
|
275
|
-
const { bin, windows } = this.
|
276
|
-
const binPathEnvVar = this.
|
277
|
-
const redirectedEnvVar = this.
|
332
|
+
const { bin, windows } = this.config;
|
333
|
+
const binPathEnvVar = this.config.scopedEnvVarKey('BINPATH');
|
334
|
+
const redirectedEnvVar = this.config.scopedEnvVarKey('REDIRECTED');
|
278
335
|
if (windows) {
|
279
336
|
const body = `@echo off
|
280
337
|
setlocal enableextensions
|
@@ -311,21 +368,5 @@ ${binPathEnvVar}="\$DIR/${bin}" ${redirectedEnvVar}=1 "$DIR/../${version}/bin/${
|
|
311
368
|
await fs.symlink(`./${version}`, path.join(this.clientRoot, 'current'));
|
312
369
|
}
|
313
370
|
}
|
314
|
-
async ensureClientDir() {
|
315
|
-
try {
|
316
|
-
await fs.mkdirp(this.clientRoot);
|
317
|
-
}
|
318
|
-
catch (error) {
|
319
|
-
if (error.code === 'EEXIST') {
|
320
|
-
// for some reason the client directory is sometimes a file
|
321
|
-
// if so, this happens. Delete it and recreate
|
322
|
-
await fs.remove(this.clientRoot);
|
323
|
-
await fs.mkdirp(this.clientRoot);
|
324
|
-
}
|
325
|
-
else {
|
326
|
-
throw error;
|
327
|
-
}
|
328
|
-
}
|
329
|
-
}
|
330
371
|
}
|
331
|
-
exports.
|
372
|
+
exports.Updater = Updater;
|
package/lib/util.d.ts
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
/// <reference types="node" />
|
2
2
|
import * as fs from 'fs-extra';
|
3
3
|
export declare function touch(p: string): Promise<void>;
|
4
|
-
export declare function ls(dir: string): Promise<{
|
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>;
|
8
9
|
export declare function wait(ms: number, unref?: boolean): Promise<void>;
|
package/lib/util.js
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.wait = exports.ls = exports.touch = void 0;
|
4
|
-
const
|
5
|
-
const
|
3
|
+
exports.wait = exports.rm = exports.ls = exports.touch = void 0;
|
4
|
+
const tslib_1 = require("tslib");
|
5
|
+
const fs = (0, tslib_1.__importStar)(require("fs-extra"));
|
6
|
+
const path = (0, tslib_1.__importStar)(require("path"));
|
6
7
|
async function touch(p) {
|
7
8
|
try {
|
8
9
|
await fs.utimes(p, new Date(), new Date());
|
@@ -18,6 +19,14 @@ async function ls(dir) {
|
|
18
19
|
return Promise.all(paths.map(path => fs.stat(path).then(stat => ({ path, stat }))));
|
19
20
|
}
|
20
21
|
exports.ls = ls;
|
22
|
+
async function rm(dir) {
|
23
|
+
return new Promise(resolve => {
|
24
|
+
fs.rm(dir, { recursive: true, force: true }, () => {
|
25
|
+
resolve();
|
26
|
+
});
|
27
|
+
});
|
28
|
+
}
|
29
|
+
exports.rm = rm;
|
21
30
|
function wait(ms, unref = false) {
|
22
31
|
return new Promise(resolve => {
|
23
32
|
const t = setTimeout(() => resolve(), ms);
|
package/oclif.manifest.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":"
|
1
|
+
{"version":"3.0.0","commands":{"update":{"id":"update","description":"update the <%= config.bin %> CLI","strict":true,"pluginName":"@oclif/plugin-update","pluginAlias":"@oclif/plugin-update","pluginType":"core","aliases":[],"examples":[{"description":"Update to the stable channel:","command":"<%= config.bin %> <%= command.id %> stable"},{"description":"Update to a specific version:","command":"<%= config.bin %> <%= command.id %> --version 1.0.0"},{"description":"Interactively select version:","command":"<%= config.bin %> <%= command.id %> --interactive"},{"description":"See available versions:","command":"<%= config.bin %> <%= command.id %> --available"}],"flags":{"autoupdate":{"name":"autoupdate","type":"boolean","hidden":true,"allowNo":false},"available":{"name":"available","type":"boolean","char":"a","description":"Install a specific version.","allowNo":false},"version":{"name":"version","type":"option","char":"v","description":"Install a specific version.","multiple":false,"exclusive":["interactive"]},"interactive":{"name":"interactive","type":"boolean","char":"i","description":"Interactively select version to install. This is ignored if a channel is provided.","allowNo":false,"exclusive":["version"]},"force":{"name":"force","type":"boolean","description":"Force a re-download of the requested version.","allowNo":false}},"args":[{"name":"channel"}]}}}
|
package/package.json
CHANGED
@@ -1,34 +1,35 @@
|
|
1
1
|
{
|
2
2
|
"name": "@oclif/plugin-update",
|
3
|
-
"version": "
|
3
|
+
"version": "3.0.0",
|
4
4
|
"author": "Salesforce",
|
5
5
|
"bugs": "https://github.com/oclif/plugin-update/issues",
|
6
6
|
"dependencies": {
|
7
7
|
"@oclif/color": "^1.0.0",
|
8
|
-
"@oclif/core": "1.0
|
9
|
-
"@types/semver": "^7.3.4",
|
10
|
-
"cli-ux": "6.0.6",
|
8
|
+
"@oclif/core": "^1.3.0",
|
11
9
|
"cross-spawn": "^7.0.3",
|
12
10
|
"debug": "^4.3.1",
|
13
11
|
"filesize": "^6.1.0",
|
14
12
|
"fs-extra": "^9.0.1",
|
15
13
|
"http-call": "^5.3.0",
|
16
|
-
"
|
14
|
+
"inquirer": "^8.2.0",
|
15
|
+
"lodash.throttle": "^4.1.1",
|
17
16
|
"log-chopper": "^1.0.2",
|
18
17
|
"semver": "^7.3.5",
|
19
18
|
"tar-fs": "^2.1.1"
|
20
19
|
},
|
21
20
|
"devDependencies": {
|
22
|
-
"@oclif/plugin-help": "5.1.9",
|
21
|
+
"@oclif/plugin-help": "^5.1.9",
|
23
22
|
"@oclif/test": "^2.0.2",
|
24
23
|
"@types/chai": "^4.2.15",
|
25
24
|
"@types/cross-spawn": "^6.0.2",
|
26
25
|
"@types/execa": "^0.9.0",
|
27
26
|
"@types/fs-extra": "^8.0.1",
|
28
27
|
"@types/glob": "^7.1.3",
|
29
|
-
"@types/
|
28
|
+
"@types/inquirer": "^8.2.0",
|
29
|
+
"@types/lodash.throttle": "^4.1.6",
|
30
30
|
"@types/mocha": "^9",
|
31
31
|
"@types/node": "^14.14.31",
|
32
|
+
"@types/semver": "^7.3.4",
|
32
33
|
"@types/supports-color": "^7.2.0",
|
33
34
|
"@types/write-json-file": "^3.2.1",
|
34
35
|
"chai": "^4.3.4",
|
@@ -38,7 +39,7 @@
|
|
38
39
|
"globby": "^11.0.2",
|
39
40
|
"mocha": "^9",
|
40
41
|
"nock": "^13.2.1",
|
41
|
-
"oclif": "2.
|
42
|
+
"oclif": "^2.4.2",
|
42
43
|
"qqjs": "^0.3.11",
|
43
44
|
"sinon": "^12.0.1",
|
44
45
|
"ts-node": "^9.1.1",
|
@@ -78,5 +79,6 @@
|
|
78
79
|
"postpack": "rm -f oclif.manifest.json",
|
79
80
|
"version": "oclif readme && git add README.md",
|
80
81
|
"build": "rm -rf lib && tsc"
|
81
|
-
}
|
82
|
+
},
|
83
|
+
"main": "lib/index.js"
|
82
84
|
}
|