Skip to content

Commit 7550fd1

Browse files
author
Murat Mehmet
committed
feat: add verbose and interactive options, display compact progress
1 parent 61f9557 commit 7550fd1

File tree

11 files changed

+234
-13
lines changed

11 files changed

+234
-13
lines changed

src/__tests__/unit/integrate.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const mockParseConfig = jest.spyOn(
1515
import path from 'path';
1616
import { Constants } from '../../constants';
1717
import { integrate } from '../../integrate';
18+
import { options } from '../../options';
1819
import { mockPrompter, writeMockProject } from '../mocks/mockAll';
1920

2021
describe('integrate', () => {
@@ -199,6 +200,7 @@ describe('integrate', () => {
199200
expect(lockData.packages['mock-package']).toEqual(undefined);
200201
});
201202
it('should handle user rejecting to integrate', async () => {
203+
options.get().interactive = true;
202204
mockPrompter.confirm.mockImplementationOnce(() => false);
203205
mockPrompter.log.step.mockReset();
204206
const lockPath = writeMockLock({

src/cli.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'isomorphic-fetch';
33
import { getInfo } from './getInfo';
44
import { integrate } from './integrate';
55
import { options } from './options';
6+
import { progress } from './progress';
67
import { logError, logIntro, logOutro } from './prompter';
78
import { IntegratorOptions } from './types/integrator.types';
89
import { upgrade } from './upgrade';
@@ -12,18 +13,33 @@ import { getErrMessage } from './utils/getErrMessage';
1213
const { version } = require('../package.json');
1314

1415
const program = new Command();
16+
program.enablePositionalOptions();
1517

1618
program
1719
.version(version as string)
1820
.name('integrate')
1921
.description('Integrate new packages into your project.')
2022
.argument('[package-name]', 'Specify a package to integrate')
21-
.option('-d, --debug', 'enables verbose logging', false)
23+
.option('-v, --verbose', 'enables verbose logging', false)
2224
.action(async (packageName: string, args: IntegratorOptions) => {
2325
options.set(args);
2426
logIntro();
25-
await integrate(packageName);
26-
logOutro();
27+
try {
28+
progress.setOptions({
29+
title: 'integrating',
30+
total: 8,
31+
step: 0,
32+
});
33+
if (!args.verbose) progress.display();
34+
await integrate(packageName);
35+
progress.hide();
36+
logOutro();
37+
} catch (e) {
38+
progress.hide();
39+
const errMessage = getErrMessage(e);
40+
logError(errMessage);
41+
logOutro('integration failed', true);
42+
}
2743
})
2844
.showHelpAfterError();
2945

@@ -47,13 +63,23 @@ program
4763
'enables manual upgrade, you must run this command in the folder of the new created project',
4864
false
4965
)
66+
.option('-v, --verbose', 'enables verbose logging', false)
67+
.option('-i, --interactive', 'allow', false)
5068
.action(async (args: IntegratorOptions) => {
5169
options.set(args);
5270
logIntro('react-native-integrate - upgrade project');
5371
try {
72+
progress.setOptions({
73+
title: 'upgrading project',
74+
total: 8,
75+
step: 0,
76+
});
77+
if (!args.verbose) progress.display();
5478
await upgrade();
79+
progress.hide();
5580
logOutro('completed project upgrade');
5681
} catch (e) {
82+
progress.hide();
5783
const errMessage = getErrMessage(e);
5884
logError(errMessage);
5985
logOutro('project upgrade failed', true);

src/integrate.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import color from 'picocolors';
22
import semver from 'semver/preload';
3+
import { options } from './options';
4+
import { progress } from './progress';
35
import {
46
confirm,
57
log,
@@ -31,7 +33,16 @@ import { updateIntegrationStatus } from './utils/updateIntegrationStatus';
3133
import { getText, transformTextInObject, variables } from './variables';
3234

3335
export async function integrate(packageName?: string): Promise<void> {
36+
progress.setOptions({
37+
stage: 'checking for updates',
38+
});
3439
await checkForUpdate();
40+
let stage = 1;
41+
42+
progress.setOptions({
43+
step: stage++,
44+
stage: 'analyzing packages',
45+
});
3546
startSpinner('analyzing packages');
3647
const analyzedPackages = analyzePackages(packageName);
3748
const { deletedPackages, installedPackages, integratedPackages } =
@@ -82,6 +93,11 @@ export async function integrate(packageName?: string): Promise<void> {
8293
let packagesToIntegrate: PackageWithConfig[] = [];
8394
if (newPackages.length) {
8495
const globalInfo = [];
96+
97+
progress.setOptions({
98+
step: stage++,
99+
stage: 'checking package configuration',
100+
});
85101
startSpinner('checking package configuration');
86102
for (let i = 0; i < newPackages.length; i++) {
87103
const [packageName, version] = newPackages[i];
@@ -250,11 +266,18 @@ export async function integrate(packageName?: string): Promise<void> {
250266
const { packageName, version, configPath, config } =
251267
packagesToIntegrate[i];
252268

269+
progress.setOptions({
270+
step: stage++,
271+
stage: `integrating ${color.blue(packageName)}`,
272+
});
253273
logInfo(
254274
color.bold(color.bgBlue(' new package ')) +
255275
color.bold(color.blue(` ${packageName} `))
256276
);
257-
if (await confirm('would you like to integrate this package?')) {
277+
if (
278+
!options.get().interactive ||
279+
(await confirm('would you like to integrate this package?'))
280+
) {
258281
variables.clear(); // reset variables
259282
if (config.env) {
260283
Object.entries(config.env).forEach(([name, value]) =>
@@ -370,6 +393,10 @@ export async function integrate(packageName?: string): Promise<void> {
370393
});
371394
}
372395
updateIntegrationStatus(packageLockUpdates);
396+
397+
if (!options.get().verbose) {
398+
progress.hide();
399+
}
373400
}
374401

375402
function checkIfJustCreatedLockFile(analyzedPackages: AnalyzedPackages) {

src/options.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { IntegratorOptions } from './types/integrator.types';
22

33
let _opts: IntegratorOptions = {
4-
debug: false,
4+
interactive: false,
5+
verbose: false,
56
manual: false,
67
};
78
export const options = {

src/progress.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { spinner } from '@clack/prompts';
2+
import color from 'picocolors';
3+
4+
class Progress {
5+
spinner?: {
6+
start: (msg?: string) => void;
7+
stop: (msg?: string, code?: number) => void;
8+
message: (msg?: string) => void;
9+
};
10+
options: ProgressOptions = {
11+
title: '',
12+
step: 0,
13+
total: 1,
14+
stage: '',
15+
};
16+
isActive = false;
17+
18+
setOptions(opts: Partial<ProgressOptions>) {
19+
Object.assign(this.options, opts);
20+
if (this.isActive) this.spinner?.message(this.drawMessage());
21+
}
22+
23+
drawMessage() {
24+
const stepRate = this.options.step / this.options.total;
25+
const barSize = 20;
26+
const scaledStep = Math.floor(stepRate * barSize);
27+
const completeBars = new Array(scaledStep).fill('\u2588').join('');
28+
const uncompleteBars = new Array(barSize - scaledStep)
29+
.fill('\u2588')
30+
.join('');
31+
const bar = `${color.cyan(completeBars)}${color.dim(color.gray(uncompleteBars))}`;
32+
33+
return `${this.options.title} [${bar}] ${color.cyan(this.options.stage)}`;
34+
}
35+
36+
display() {
37+
if (!this.isActive) {
38+
this.isActive = true;
39+
this.spinner = spinner();
40+
this.spinner.start(this.drawMessage());
41+
}
42+
}
43+
44+
hide() {
45+
if (this.isActive) {
46+
this.isActive = false;
47+
this.spinner?.stop();
48+
process.stdout.cursorTo(0); // up one line
49+
process.stdout.moveCursor(0, -2); // up one line
50+
process.stdout.clearLine(1); // from cursor to end
51+
}
52+
}
53+
}
54+
55+
export const progress = new Progress();
56+
57+
export type ProgressOptions = {
58+
title: string;
59+
step: number;
60+
total: number;
61+
stage: string;
62+
};

src/prompter.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
text as promptText,
1313
} from '@clack/prompts';
1414
import color from 'picocolors';
15+
import { progress } from './progress';
1516
import {
1617
ConfirmPromptArgs,
1718
MultiselectPromptArgs,
@@ -22,31 +23,44 @@ import {
2223
import { listenForKeys } from './utils/waitInputToContinue';
2324

2425
export function log(msg: string): void {
26+
if (progress.isActive) return;
2527
promptLog.step(msg);
2628
}
2729

2830
export function logSuccess(msg: string): void {
31+
const isProgressActive = progress.isActive;
32+
if (isProgressActive) progress.hide();
2933
promptLog.success(msg);
34+
if (isProgressActive) progress.display();
3035
}
3136

3237
export function logMessage(msg: string): void {
38+
if (progress.isActive) return;
3339
promptLog.message('⦿ ' + msg);
3440
}
3541

3642
export function logMessageGray(msg: string): void {
43+
if (progress.isActive) return;
3744
promptLog.message(color.gray('⦿ ' + msg));
3845
}
3946

4047
export function logWarning(msg: string, noColor?: boolean): void {
48+
const isProgressActive = progress.isActive;
49+
if (isProgressActive) progress.hide();
4150
promptLog.warning(noColor ? msg : color.yellow(msg));
51+
if (isProgressActive) progress.display();
4252
}
4353

4454
export function logInfo(msg: string): void {
55+
if (progress.isActive) return;
4556
promptLog.info(msg);
4657
}
4758

4859
export function logError(msg: string, noColor?: boolean): void {
60+
const isProgressActive = progress.isActive;
61+
if (isProgressActive) progress.hide();
4962
promptLog.error(noColor ? msg : color.red(msg));
63+
if (isProgressActive) progress.display();
5064
}
5165

5266
export function logNote(msg: string, title?: string): void {
@@ -68,15 +82,18 @@ export function startSpinner(
6882
msg: string,
6983
onCancel?: (key: string) => void
7084
): void {
85+
if (progress.isActive) return;
7186
s.start(msg);
7287
if (onCancel) releaseListener = listenForKeys('s', onCancel);
7388
}
7489

7590
export function updateSpinner(msg: string): void {
91+
if (progress.isActive) return;
7692
s.message(msg);
7793
}
7894

7995
export function stopSpinner(msg: string): void {
96+
if (progress.isActive) return;
8097
s.stop(msg);
8198
if (releaseListener) {
8299
releaseListener();
@@ -88,6 +105,8 @@ export async function multiselect(
88105
msg: string,
89106
args: MultiselectPromptArgs
90107
): Promise<OptionValue[]> {
108+
const isProgressActive = progress.isActive;
109+
if (isProgressActive) progress.hide();
91110
const response = await promptMultiselect({
92111
message: msg,
93112
required: args.required,
@@ -102,6 +121,7 @@ export async function multiselect(
102121
cancel('operation cancelled');
103122
process.abort();
104123
}
124+
if (isProgressActive) progress.display();
105125
// @ts-ignore
106126
return response;
107127
}
@@ -110,6 +130,8 @@ export async function select(
110130
msg: string,
111131
args: SelectPromptArgs
112132
): Promise<OptionValue> {
133+
const isProgressActive = progress.isActive;
134+
if (isProgressActive) progress.hide();
113135
const response = await promptSelect({
114136
message: msg,
115137
options: args.options.map(x => ({
@@ -124,6 +146,7 @@ export async function select(
124146
cancel('operation cancelled');
125147
process.abort();
126148
}
149+
if (isProgressActive) progress.display();
127150
// @ts-ignore
128151
return response;
129152
}
@@ -132,6 +155,8 @@ export async function confirm(
132155
msg: string,
133156
args: ConfirmPromptArgs = {}
134157
): Promise<boolean> {
158+
const isProgressActive = progress.isActive;
159+
if (isProgressActive) progress.hide();
135160
const response = await promptConfirm({
136161
message: msg,
137162
active: args.positive || 'yes',
@@ -142,13 +167,16 @@ export async function confirm(
142167
cancel('operation cancelled');
143168
process.abort();
144169
}
170+
if (isProgressActive) progress.display();
145171
return response;
146172
}
147173

148174
export async function text(
149175
msg: string,
150176
args: TextPromptArgs = {}
151177
): Promise<string> {
178+
const isProgressActive = progress.isActive;
179+
if (isProgressActive) progress.hide();
152180
const response = await promptText({
153181
message: msg,
154182
defaultValue: args.defaultValue,
@@ -160,6 +188,7 @@ export async function text(
160188
cancel('operation cancelled');
161189
process.abort();
162190
}
191+
if (isProgressActive) progress.display();
163192
return response;
164193
}
165194

src/types/integrator.types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export interface IntegratorOptions {
2-
debug: boolean;
2+
verbose: boolean;
3+
interactive: boolean;
34
manual: boolean;
45
}
56

0 commit comments

Comments
 (0)