@optimizely/ocp-cli 1.2.7 → 1.2.9
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/dist/commands/app/BaseBuildCommand.js +1 -1
- package/dist/commands/app/BaseBuildCommand.js.map +1 -1
- package/dist/lib/AppUpdater.js +5 -29
- package/dist/lib/AppUpdater.js.map +1 -1
- package/dist/lib/checkForUpdate.js +15 -9
- package/dist/lib/checkForUpdate.js.map +1 -1
- package/dist/lib/packageManagerHandler.d.ts +53 -0
- package/dist/lib/packageManagerHandler.js +162 -0
- package/dist/lib/packageManagerHandler.js.map +1 -0
- package/dist/oo-cli.manifest.json +1 -1
- package/dist/test/AppUpdater.test.d.ts +1 -0
- package/dist/test/AppUpdater.test.js +101 -0
- package/dist/test/AppUpdater.test.js.map +1 -0
- package/dist/test/packageManagerHandler.test.d.ts +1 -0
- package/dist/test/packageManagerHandler.test.js +175 -0
- package/dist/test/packageManagerHandler.test.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/app/BaseBuildCommand.ts +1 -1
- package/src/lib/AppUpdater.ts +7 -40
- package/src/lib/checkForUpdate.ts +24 -13
- package/src/lib/packageManagerHandler.ts +178 -0
- package/src/test/AppUpdater.test.ts +123 -0
- package/src/test/packageManagerHandler.test.ts +199 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/* tslint:disable:max-line-length */
|
|
4
|
+
const packageManagerHandler_1 = require("../lib/packageManagerHandler");
|
|
5
|
+
const TerminalOutput_1 = require("../lib/TerminalOutput");
|
|
6
|
+
jest.mock('../lib/TerminalOutput', () => ({
|
|
7
|
+
TerminalOutput: {
|
|
8
|
+
exec: jest.fn(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
describe('PackageManagerHandler', () => {
|
|
12
|
+
let packageManager;
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
jest.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
describe('isLegacyYarnAvailable', () => {
|
|
20
|
+
it('should return true when yarn 1.x is available', () => {
|
|
21
|
+
TerminalOutput_1.TerminalOutput.exec.mockReturnValue({
|
|
22
|
+
status: 0,
|
|
23
|
+
stdout: '1.22.19\n',
|
|
24
|
+
stderr: '',
|
|
25
|
+
});
|
|
26
|
+
expect(packageManagerHandler_1.default.isLegacyYarnAvailable()).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it('should return false when yarn 2.x is available', () => {
|
|
29
|
+
TerminalOutput_1.TerminalOutput.exec.mockReturnValue({
|
|
30
|
+
status: 0,
|
|
31
|
+
stdout: '2.0.0\n',
|
|
32
|
+
stderr: '',
|
|
33
|
+
});
|
|
34
|
+
expect(packageManagerHandler_1.default.isLegacyYarnAvailable()).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('upgradeGlobalPackageToLatest', () => {
|
|
38
|
+
it('should use yarn when the cli is installed using yarn', () => {
|
|
39
|
+
TerminalOutput_1.TerminalOutput.exec
|
|
40
|
+
.mockReturnValueOnce({
|
|
41
|
+
status: 0,
|
|
42
|
+
stdout: '/foo/bar/yarn/bin/test-cli\n',
|
|
43
|
+
stderr: '',
|
|
44
|
+
}) // yarn check
|
|
45
|
+
.mockReturnValueOnce({ status: 0, stdout: '1.22.0', stderr: '' }) // check for yarn 1.x
|
|
46
|
+
.mockReturnValueOnce({ status: 0, stdout: 'success', stderr: '' }); // upgrade command
|
|
47
|
+
packageManager = new packageManagerHandler_1.default('test-package', 'test-cli');
|
|
48
|
+
const [success, _] = packageManager.upgradeGlobalPackageToLatest();
|
|
49
|
+
expect(success).toBe(true);
|
|
50
|
+
expect(TerminalOutput_1.TerminalOutput.exec).toHaveBeenCalledWith('yarn global upgrade test-package --latest');
|
|
51
|
+
});
|
|
52
|
+
it('should notify failure when the cli is installed using yarn 1.x but current yarn version is newer than 1.x', () => {
|
|
53
|
+
TerminalOutput_1.TerminalOutput.exec
|
|
54
|
+
.mockReturnValueOnce({
|
|
55
|
+
status: 0,
|
|
56
|
+
stdout: '/foo/bar/yarn/bin/test-cli\n',
|
|
57
|
+
stderr: '',
|
|
58
|
+
}) // yarn check
|
|
59
|
+
.mockReturnValueOnce({ status: 0, stdout: '2.22.0', stderr: '' }); // check for yarn 1.x
|
|
60
|
+
packageManager = new packageManagerHandler_1.default('test-package', 'test-cli');
|
|
61
|
+
const [success, message] = packageManager.upgradeGlobalPackageToLatest();
|
|
62
|
+
expect(success).toBe(false);
|
|
63
|
+
expect(message).toContain('not compatible');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('fetchLatestPackage', () => {
|
|
67
|
+
it('should fetch latest version using --silent flag to avoid messing with the npm output', () => {
|
|
68
|
+
TerminalOutput_1.TerminalOutput.exec.mockReturnValue({
|
|
69
|
+
status: 0,
|
|
70
|
+
stdout: '1.0.0',
|
|
71
|
+
stderr: '',
|
|
72
|
+
});
|
|
73
|
+
packageManager = new packageManagerHandler_1.default('test-package', 'test-cli');
|
|
74
|
+
const [success, version] = packageManager.fetchLatestPackage();
|
|
75
|
+
expect(success).toBe(true);
|
|
76
|
+
expect(version).toBe('1.0.0');
|
|
77
|
+
expect(TerminalOutput_1.TerminalOutput.exec).toHaveBeenCalledWith(expect.stringContaining('npm show --silent'));
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('getPackageDependencies', () => {
|
|
81
|
+
let mockNpmOutput;
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
mockNpmOutput = JSON.stringify({
|
|
84
|
+
dependencies: {
|
|
85
|
+
'test-pkg': {
|
|
86
|
+
version: '1.0.0',
|
|
87
|
+
dependencies: {
|
|
88
|
+
'nested-test': {
|
|
89
|
+
version: '2.0.0',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
'other-pkg': {
|
|
94
|
+
version: '3.0.0',
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
TerminalOutput_1.TerminalOutput.exec.mockReturnValue({
|
|
99
|
+
status: 0,
|
|
100
|
+
stdout: mockNpmOutput,
|
|
101
|
+
stderr: '',
|
|
102
|
+
}); // list command
|
|
103
|
+
});
|
|
104
|
+
it('should parse npm dependencies into yarn-like format', () => {
|
|
105
|
+
const [success, deps] = packageManagerHandler_1.default.getPackageDependencies(/test/);
|
|
106
|
+
expect(success).toBe(true);
|
|
107
|
+
expect(deps).toEqual({
|
|
108
|
+
type: 'tree',
|
|
109
|
+
data: {
|
|
110
|
+
type: 'list',
|
|
111
|
+
trees: [
|
|
112
|
+
{
|
|
113
|
+
name: 'test-pkg@1.0.0',
|
|
114
|
+
children: [],
|
|
115
|
+
hint: null,
|
|
116
|
+
color: 'bold',
|
|
117
|
+
depth: 0,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'nested-test@2.0.0',
|
|
121
|
+
children: [],
|
|
122
|
+
hint: null,
|
|
123
|
+
color: null,
|
|
124
|
+
depth: 1,
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
it('should call the npm command with the --silent flag to avoid messing up with the ls output', () => {
|
|
131
|
+
packageManagerHandler_1.default.getPackageDependencies(/other/);
|
|
132
|
+
expect(TerminalOutput_1.TerminalOutput.exec).toHaveBeenCalledWith(expect.stringContaining('npm ls --silent'));
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('getPackageUpgradeCommand', () => {
|
|
136
|
+
it('should return yarn upgrade command when cli is installed with yarn', () => {
|
|
137
|
+
TerminalOutput_1.TerminalOutput.exec
|
|
138
|
+
.mockReturnValueOnce({
|
|
139
|
+
status: 0,
|
|
140
|
+
stdout: '/foo/bar/yarn/bin/test-cli\n',
|
|
141
|
+
stderr: '',
|
|
142
|
+
}) // yarn check
|
|
143
|
+
.mockReturnValueOnce({ status: 0, stdout: '1.22.0', stderr: '' }); // check for yarn 1.x
|
|
144
|
+
packageManager = new packageManagerHandler_1.default('test-package', 'test-cli');
|
|
145
|
+
const [success, command] = packageManager.getPackageUpgradeCommand();
|
|
146
|
+
expect(success).toBe(true);
|
|
147
|
+
expect(command).toBe('yarn global upgrade test-package --latest');
|
|
148
|
+
});
|
|
149
|
+
it('should return npm upgrade command when cli is installed with npm', () => {
|
|
150
|
+
TerminalOutput_1.TerminalOutput.exec.mockReturnValueOnce({
|
|
151
|
+
status: 0,
|
|
152
|
+
stdout: '/usr/local/bin/test-cli\n',
|
|
153
|
+
stderr: '',
|
|
154
|
+
}); // npm check
|
|
155
|
+
packageManager = new packageManagerHandler_1.default('test-package', 'test-cli');
|
|
156
|
+
const [success, command] = packageManager.getPackageUpgradeCommand();
|
|
157
|
+
expect(success).toBe(true);
|
|
158
|
+
expect(command).toBe('npm install --global test-package@latest');
|
|
159
|
+
});
|
|
160
|
+
it('should return error when yarn version is not 1.x', () => {
|
|
161
|
+
TerminalOutput_1.TerminalOutput.exec
|
|
162
|
+
.mockReturnValueOnce({
|
|
163
|
+
status: 0,
|
|
164
|
+
stdout: '/foo/bar/yarn/bin/test-cli\n',
|
|
165
|
+
stderr: '',
|
|
166
|
+
}) // yarn check
|
|
167
|
+
.mockReturnValueOnce({ status: 0, stdout: '2.22.0', stderr: '' }); // check for yarn 1.x
|
|
168
|
+
packageManager = new packageManagerHandler_1.default('test-package', 'test-cli');
|
|
169
|
+
const [success, message] = packageManager.getPackageUpgradeCommand();
|
|
170
|
+
expect(success).toBe(false);
|
|
171
|
+
expect(message).toContain('not compatible');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
//# sourceMappingURL=packageManagerHandler.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packageManagerHandler.test.js","sourceRoot":"","sources":["../../src/test/packageManagerHandler.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -80,7 +80,7 @@ export abstract class BaseBuildCommand {
|
|
|
80
80
|
|
|
81
81
|
console.log(chalk.gray('Performing local validation...'));
|
|
82
82
|
try {
|
|
83
|
-
execSync('yarn
|
|
83
|
+
execSync('yarn validate', {stdio: 'inherit'});
|
|
84
84
|
} catch (e) {
|
|
85
85
|
process.exit(1);
|
|
86
86
|
}
|
package/src/lib/AppUpdater.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as chalk from 'chalk';
|
|
2
|
-
import * as child_process from 'child_process';
|
|
3
2
|
import fetch from 'node-fetch';
|
|
4
3
|
import {compare, major} from 'semver';
|
|
5
4
|
import {getDependencyFileUrl} from './Config';
|
|
@@ -8,6 +7,7 @@ import {TerminalPassthru} from './TeminalPassthru';
|
|
|
8
7
|
import {TerminalConfirm} from './TerminalConfirm';
|
|
9
8
|
import table = require('text-table');
|
|
10
9
|
import {DependencyStage} from './DependencyStage';
|
|
10
|
+
import PackageManagerHandler from './packageManagerHandler';
|
|
11
11
|
|
|
12
12
|
export namespace AppUpdater {
|
|
13
13
|
/**
|
|
@@ -20,8 +20,6 @@ export namespace AppUpdater {
|
|
|
20
20
|
ignoreDevUpdates: boolean = true,
|
|
21
21
|
stage: DependencyStage = 'stable'
|
|
22
22
|
) {
|
|
23
|
-
ensurePublicPackageUsage();
|
|
24
|
-
|
|
25
23
|
const response = await fetch(getDependencyFileUrl(runtime));
|
|
26
24
|
const zipModules = await response.json();
|
|
27
25
|
const projectProdDeps = AppUpdater.getPackageDependencies(true);
|
|
@@ -127,21 +125,20 @@ export namespace AppUpdater {
|
|
|
127
125
|
|
|
128
126
|
export function getPackageDependencies(prod: boolean = false): {[name: string]: string} {
|
|
129
127
|
try {
|
|
130
|
-
const result =
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
const [success, result] = PackageManagerHandler.getPackageDependencies(
|
|
129
|
+
/@zaiusinc\//,
|
|
130
|
+
prod
|
|
133
131
|
);
|
|
134
132
|
|
|
135
|
-
if (
|
|
136
|
-
const tree = JSON.parse(result.stdout.toString());
|
|
133
|
+
if (success) {
|
|
137
134
|
const output: {[name: string]: string} = {};
|
|
138
|
-
for (const item of
|
|
135
|
+
for (const item of result.data.trees) {
|
|
139
136
|
const [name, version] = item.name.split(/(?!^)@/);
|
|
140
137
|
output[name] = version;
|
|
141
138
|
}
|
|
142
139
|
return output;
|
|
143
140
|
} else {
|
|
144
|
-
console.error(result
|
|
141
|
+
console.error(result);
|
|
145
142
|
}
|
|
146
143
|
} catch (e) {
|
|
147
144
|
console.error(e);
|
|
@@ -153,34 +150,4 @@ export namespace AppUpdater {
|
|
|
153
150
|
function isCurrent(ocpVersion: string, projectModuleversion: string) {
|
|
154
151
|
return compare(ocpVersion, projectModuleversion) === 0;
|
|
155
152
|
}
|
|
156
|
-
|
|
157
|
-
function ensurePublicPackageUsage() {
|
|
158
|
-
try {
|
|
159
|
-
const result = child_process.spawnSync(
|
|
160
|
-
'yarn --no-progress --prod -s list --json --pattern="@zaius/app-sdk|@zaius/node-sdk" --non-interactive',
|
|
161
|
-
{cwd: process.cwd(), shell: true}
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
if (result.status === 0) {
|
|
165
|
-
const tree = JSON.parse(result.stdout.toString());
|
|
166
|
-
if (!tree.data.trees.length) {
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
console.log(
|
|
171
|
-
chalk.red(
|
|
172
|
-
'@zaius/app-sdk & @zaius/node-sdk are no longer supported. ' +
|
|
173
|
-
'Please update your package.json to use the @zaiusinc scope for these packages.'
|
|
174
|
-
)
|
|
175
|
-
);
|
|
176
|
-
process.exit(1);
|
|
177
|
-
} else {
|
|
178
|
-
console.error(result.stderr.toString());
|
|
179
|
-
}
|
|
180
|
-
} catch (e) {
|
|
181
|
-
console.error(e);
|
|
182
|
-
}
|
|
183
|
-
console.log(chalk.red("Unable to get package dependencies. Are you in your app's working directory?"));
|
|
184
|
-
process.exit(1);
|
|
185
|
-
}
|
|
186
153
|
}
|
|
@@ -4,9 +4,8 @@ import * as os from 'os';
|
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import * as semver from 'semver';
|
|
6
6
|
import { ManifestDefinition } from 'oo-cli/dist/types/manifest';
|
|
7
|
-
import { TerminalOutput } from './TerminalOutput';
|
|
8
7
|
import { TerminalConfirm } from './TerminalConfirm';
|
|
9
|
-
import
|
|
8
|
+
import PackageManagerHandler from './packageManagerHandler';
|
|
10
9
|
|
|
11
10
|
interface LastCheck {
|
|
12
11
|
date: Date;
|
|
@@ -20,13 +19,24 @@ export async function checkForUpdate(manifest: ManifestDefinition, noPrompt: boo
|
|
|
20
19
|
const installed = manifest.package?.version!;
|
|
21
20
|
const prerelease = semver.prerelease(installed);
|
|
22
21
|
const isBeta = prerelease && prerelease[0] === 'beta';
|
|
22
|
+
const packageManagerHandler = new PackageManagerHandler(
|
|
23
|
+
'@optimizely/ocp-cli',
|
|
24
|
+
'ocp'
|
|
25
|
+
);
|
|
23
26
|
|
|
24
27
|
if (isBeta) {
|
|
28
|
+
const [success, result] = packageManagerHandler.getPackageUpgradeCommand();
|
|
29
|
+
if (!success) {
|
|
30
|
+
console.log(chalk.red(result));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
25
33
|
console.log(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
chalk.yellow(
|
|
35
|
+
`You are running beta version (${installed})'. ` +
|
|
36
|
+
`Run ${chalk.italic.grey(
|
|
37
|
+
`${result}`
|
|
38
|
+
)} to upgrade to the latest release version`
|
|
39
|
+
)
|
|
30
40
|
);
|
|
31
41
|
return;
|
|
32
42
|
}
|
|
@@ -41,13 +51,18 @@ export async function checkForUpdate(manifest: ManifestDefinition, noPrompt: boo
|
|
|
41
51
|
|
|
42
52
|
try {
|
|
43
53
|
let upToDate = true;
|
|
44
|
-
const latest =
|
|
45
|
-
if (latest) {
|
|
54
|
+
const [success, latest] = packageManagerHandler.fetchLatestPackage();
|
|
55
|
+
if (success && latest) {
|
|
46
56
|
if (semver.gt(latest, installed)) {
|
|
47
57
|
upToDate = false;
|
|
48
58
|
if (await TerminalConfirm.ask(
|
|
49
59
|
chalk.yellow('A new version of OCP CLI is available. Update now?'), noPrompt, true)) {
|
|
50
|
-
|
|
60
|
+
const [passed, error] =
|
|
61
|
+
packageManagerHandler.upgradeGlobalPackageToLatest();
|
|
62
|
+
if (!passed) {
|
|
63
|
+
console.log(chalk.red(error));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
51
66
|
}
|
|
52
67
|
}
|
|
53
68
|
}
|
|
@@ -57,10 +72,6 @@ export async function checkForUpdate(manifest: ManifestDefinition, noPrompt: boo
|
|
|
57
72
|
}
|
|
58
73
|
}
|
|
59
74
|
|
|
60
|
-
async function fetchLatest() {
|
|
61
|
-
return TerminalOutput.exec('yarn info -s @optimizely/ocp-cli dist-tags.latest').stdout.trim();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
75
|
function getLastCheck(): LastCheck | undefined {
|
|
65
76
|
const file = path.join(os.homedir(), `.ocp/${LAST_VERSION_CHECK_FILE}`);
|
|
66
77
|
if (fs.existsSync(file)) {
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { TerminalOutput } from './TerminalOutput';
|
|
2
|
+
|
|
3
|
+
class PackageManagerHandler {
|
|
4
|
+
public static isLegacyYarnAvailable(): boolean {
|
|
5
|
+
const [success, output] =
|
|
6
|
+
PackageManagerHandler.executeCommand('yarn --version');
|
|
7
|
+
return success && output.startsWith('1.');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Retrieves package dependencies based on a scope pattern and production flag.
|
|
12
|
+
*
|
|
13
|
+
* @param scope - Regular expression pattern to filter package names
|
|
14
|
+
* @param prod - When true, only returns production dependencies. Defaults to false
|
|
15
|
+
* @returns A tuple where:
|
|
16
|
+
* - First element is a boolean indicating success/failure
|
|
17
|
+
* - Second element is either:
|
|
18
|
+
* - On success: An object containing dependency tree data
|
|
19
|
+
* - On failure: An error message string
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* - It uses NPM `ls` and transforms the dependency tree to match Yarn's format
|
|
23
|
+
*
|
|
24
|
+
* @throws Will not throw errors directly, but catches and returns them as failure results
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const [success, dependencies] = getPackageDependencies(/^@scope\//, true);
|
|
29
|
+
* if (success) {
|
|
30
|
+
* // Process dependency tree
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
public static getPackageDependencies(
|
|
35
|
+
scope: RegExp,
|
|
36
|
+
prod: boolean = false
|
|
37
|
+
): [boolean, any] {
|
|
38
|
+
const command = `npm ls --silent --json ${prod ? '--prod ' : ''}`;
|
|
39
|
+
try {
|
|
40
|
+
const [success, output] = PackageManagerHandler.executeCommand(command);
|
|
41
|
+
if (!success) {
|
|
42
|
+
return [false, output];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// if we're using npm, then we need to parse the tree to have a similar output to yarn
|
|
46
|
+
const tree = JSON.parse(output);
|
|
47
|
+
const trees: any[] = [];
|
|
48
|
+
function collect(deps: Record<string, any> = {}, depth = 0) {
|
|
49
|
+
for (const [name, info] of Object.entries(deps)) {
|
|
50
|
+
if (scope.test(name)) {
|
|
51
|
+
trees.push({
|
|
52
|
+
name: `${name}@${info.version ?? 'unknown'}`,
|
|
53
|
+
children: [],
|
|
54
|
+
hint: null,
|
|
55
|
+
color: depth === 0 ? 'bold' : null,
|
|
56
|
+
depth,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (info.dependencies) {
|
|
60
|
+
collect(info.dependencies, depth + 1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
collect(tree.dependencies);
|
|
66
|
+
|
|
67
|
+
return [
|
|
68
|
+
true,
|
|
69
|
+
{
|
|
70
|
+
type: 'tree',
|
|
71
|
+
data: {
|
|
72
|
+
type: 'list',
|
|
73
|
+
trees,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.error('Unable to get package dependencies:', e);
|
|
79
|
+
return [false, 'Unable to get package dependencies.'];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private static executeCommand(command: string): [boolean, string] {
|
|
84
|
+
const result = TerminalOutput.exec(command);
|
|
85
|
+
if (result.status === 0) {
|
|
86
|
+
const output = result.stdout.trim();
|
|
87
|
+
return [true, output];
|
|
88
|
+
} else {
|
|
89
|
+
return [false, result.stderr.trim()];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private targetPackageManager: 'yarn' | 'npm' | 'none' = 'none';
|
|
94
|
+
constructor(private packageName: string, cli: string) {
|
|
95
|
+
this.targetPackageManager = this.cliInstalledWith(cli);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public getPackageUpgradeCommand(): [boolean, string] {
|
|
99
|
+
const [possible, error] = this.isUpgradePossible();
|
|
100
|
+
if (!possible) {
|
|
101
|
+
return [false, error];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return [
|
|
105
|
+
true,
|
|
106
|
+
this.useYarn()
|
|
107
|
+
? `yarn global upgrade ${this.packageName} --latest`
|
|
108
|
+
: `npm install --global ${this.packageName}@latest`,
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Upgrades the specified package to its latest version globally.
|
|
114
|
+
* The function determines whether to use Yarn or NPM based on internal configuration.
|
|
115
|
+
*
|
|
116
|
+
* @returns {[boolean, string]} A tuple where:
|
|
117
|
+
* - First element (boolean) indicates if the upgrade was successful
|
|
118
|
+
* - Second element (string) contains error message if upgrade failed, or success message if upgrade succeeded
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* const [success, message] = packageManager.upgradeGlobalPackageToLatest();
|
|
122
|
+
* if (!success) {
|
|
123
|
+
* console.error(`Failed to upgrade package: ${message}`);
|
|
124
|
+
* }
|
|
125
|
+
*/
|
|
126
|
+
public upgradeGlobalPackageToLatest(): [boolean, string] {
|
|
127
|
+
const [possible, error] = this.isUpgradePossible();
|
|
128
|
+
if (!possible) {
|
|
129
|
+
return [false, error];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const command = this.useYarn()
|
|
133
|
+
? `yarn global upgrade ${this.packageName} --latest`
|
|
134
|
+
: `npm install --global ${this.packageName}@latest`;
|
|
135
|
+
return PackageManagerHandler.executeCommand(command);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public fetchLatestPackage(): [boolean, string] {
|
|
139
|
+
return PackageManagerHandler.executeCommand(
|
|
140
|
+
`npm show --silent ${this.packageName} dist-tags.latest`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private isUpgradePossible(): [boolean, string] {
|
|
145
|
+
if (this.useYarn() && !PackageManagerHandler.isLegacyYarnAvailable()) {
|
|
146
|
+
return [
|
|
147
|
+
false,
|
|
148
|
+
`${this.packageName} is installed using yarn 1.x, but the current version of yarn is not compatible with the upgrade command. Please use npm to install ${this.packageName} instead`,
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
return [true, ''];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private cliInstalledWith(cli: string): 'yarn' | 'npm' | 'none' {
|
|
155
|
+
const whichCommand = process.platform === 'win32' ? 'where' : 'which';
|
|
156
|
+
const [success, output] = PackageManagerHandler.executeCommand(
|
|
157
|
+
`${whichCommand} ${cli}`
|
|
158
|
+
);
|
|
159
|
+
if (success) {
|
|
160
|
+
// just in case we have multiple paths, we just take the first one
|
|
161
|
+
const cliPath = output.split('\n')[0].trim();
|
|
162
|
+
if (cliPath.includes('yarn')) {
|
|
163
|
+
// assuming only legacy yarn (1.x) is able to install the cli globally
|
|
164
|
+
return 'yarn';
|
|
165
|
+
} else {
|
|
166
|
+
// else we assume it's npm as this is how we recommend installing the cli
|
|
167
|
+
return 'npm';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return 'none';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private useYarn(): boolean {
|
|
174
|
+
return this.targetPackageManager === 'yarn';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export default PackageManagerHandler;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { AppUpdater } from '../lib/AppUpdater';
|
|
2
|
+
import PackageManagerHandler from '../lib/packageManagerHandler';
|
|
3
|
+
|
|
4
|
+
jest.mock('../lib/packageManagerHandler');
|
|
5
|
+
|
|
6
|
+
describe('AppUpdater', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.clearAllMocks();
|
|
9
|
+
});
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
jest.restoreAllMocks();
|
|
12
|
+
});
|
|
13
|
+
describe('getPackageDependencies', () => {
|
|
14
|
+
it('should parse dependencies correctly', () => {
|
|
15
|
+
const mockDeps = {
|
|
16
|
+
type: 'tree',
|
|
17
|
+
data: {
|
|
18
|
+
type: 'list',
|
|
19
|
+
trees: [
|
|
20
|
+
{
|
|
21
|
+
name: '@zaiusinc/package1@1.0.0',
|
|
22
|
+
children: [],
|
|
23
|
+
hint: null,
|
|
24
|
+
color: 'bold',
|
|
25
|
+
depth: 0,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: '@zaiusinc/package2@2.0.0',
|
|
29
|
+
children: [],
|
|
30
|
+
hint: null,
|
|
31
|
+
color: 'bold',
|
|
32
|
+
depth: 0,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
(
|
|
39
|
+
PackageManagerHandler.getPackageDependencies as jest.Mock
|
|
40
|
+
).mockReturnValueOnce([true, mockDeps]);
|
|
41
|
+
|
|
42
|
+
const result = AppUpdater.getPackageDependencies();
|
|
43
|
+
|
|
44
|
+
expect(result).toEqual({
|
|
45
|
+
'@zaiusinc/package1': '1.0.0',
|
|
46
|
+
'@zaiusinc/package2': '2.0.0',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle prod dependencies flag', () => {
|
|
51
|
+
const mockDeps = {
|
|
52
|
+
type: 'tree',
|
|
53
|
+
data: {
|
|
54
|
+
type: 'list',
|
|
55
|
+
trees: [
|
|
56
|
+
{
|
|
57
|
+
name: '@zaiusinc/package1@1.0.0',
|
|
58
|
+
children: [],
|
|
59
|
+
hint: null,
|
|
60
|
+
color: 'bold',
|
|
61
|
+
depth: 0,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const mockHandler = (
|
|
68
|
+
PackageManagerHandler.getPackageDependencies as jest.Mock
|
|
69
|
+
).mockReturnValueOnce([true, mockDeps]);
|
|
70
|
+
|
|
71
|
+
AppUpdater.getPackageDependencies(true);
|
|
72
|
+
|
|
73
|
+
expect(mockHandler).toHaveBeenCalledWith(/@zaiusinc\//, true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('error handling', () => {
|
|
77
|
+
let mockExit: jest.SpyInstance;
|
|
78
|
+
let mockConsole: jest.SpyInstance;
|
|
79
|
+
let mockConsoleError: jest.SpyInstance;
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
mockExit = jest
|
|
82
|
+
.spyOn(process, 'exit')
|
|
83
|
+
.mockImplementation(() => undefined as never);
|
|
84
|
+
mockConsole = jest
|
|
85
|
+
.spyOn(console, 'log')
|
|
86
|
+
.mockImplementation(() => undefined as never);
|
|
87
|
+
mockConsoleError = jest
|
|
88
|
+
.spyOn(console, 'error')
|
|
89
|
+
.mockImplementation(() => undefined as never);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
afterEach(() => {
|
|
93
|
+
mockExit.mockRestore();
|
|
94
|
+
mockConsole.mockRestore();
|
|
95
|
+
mockConsoleError.mockRestore();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should exit with error on package manager failure', () => {
|
|
99
|
+
(
|
|
100
|
+
PackageManagerHandler.getPackageDependencies as jest.Mock
|
|
101
|
+
).mockReturnValueOnce([false, 'error']);
|
|
102
|
+
|
|
103
|
+
AppUpdater.getPackageDependencies();
|
|
104
|
+
|
|
105
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
106
|
+
expect(mockConsoleError).toHaveBeenCalledWith('error');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should exit with error on exception', () => {
|
|
110
|
+
(
|
|
111
|
+
PackageManagerHandler.getPackageDependencies as jest.Mock
|
|
112
|
+
).mockImplementation(() => {
|
|
113
|
+
throw new Error('test error');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
AppUpdater.getPackageDependencies();
|
|
117
|
+
|
|
118
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
119
|
+
expect(mockConsoleError).toHaveBeenCalledWith(new Error('test error'));
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|