Skip to content

Commit 58a58db

Browse files
committed
feat: Add ability for plugins to display a <press key to continue> prompt
1 parent 93ca189 commit 58a58db

File tree

12 files changed

+105
-27
lines changed

12 files changed

+105
-27
lines changed

codify.json

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"default": "../codify-homebrew-plugin/src/index.ts"
66
}
77
},
8+
{ "type": "virtualenv" },
9+
{ "type": "virtualenv-project", "dest": ".venv12", "cwd": "~/Projects/python-temp2", "automaticallyInstallRequirementsTxt": true },
10+
{ "type": "pnpm", "version": "9.15.5", "globalEnv": "20.15" },
811
{ "type": "path", "path": "$HOME/.bun/bin"},
912
{
1013
"type": "path",
@@ -56,7 +59,7 @@
5659
"zstd"
5760
]
5861
},
59-
{"version":"1.10.5","type":"terraform"},
62+
{ "version":"1.10.5","type":"terraform" },
6063
{ "type": "alias", "alias": "gcdsdd", "value": "git clone" },
6164
{
6265
"type": "ssh-config",
@@ -130,6 +133,9 @@
130133
"directory": "~/Projects/codify",
131134
"repository": "git@github.com:kevinwang5658/codify.git"
132135
},
136+
{
137+
"type": "wait-github-ssh-key"
138+
},
133139
{
134140
"type": "git-lfs"
135141
},
@@ -148,18 +154,6 @@
148154
"email": "kevinwang5658@gmail.com",
149155
"username": "kevinwang"
150156
},
151-
{
152-
"type": "nvm",
153-
"global": "20.15.1",
154-
"nodeVersions": [
155-
"iojs-2.5.0",
156-
"18.20.3",
157-
"20.15.0",
158-
"20.15.1",
159-
"22.4.1",
160-
"23.3.0"
161-
]
162-
},
163157
{
164158
"type": "android-studio",
165159
"version": "2023.3.1.20"

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"ajv": "^8.12.0",
1515
"ajv-formats": "^3.0.1",
1616
"chalk": "^5.3.0",
17-
"codify-schemas": "^1.0.74",
17+
"codify-schemas": "^1.0.76",
1818
"debug": "^4.3.4",
1919
"detect-indent": "^7.0.1",
2020
"diff": "^7.0.0",

src/common/base-command.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Command, Flags } from '@oclif/core';
22
import { OutputFlags } from '@oclif/core/interfaces';
33
import chalk from 'chalk';
4-
import { SudoRequestData } from 'codify-schemas';
4+
import { PressKeyToContinueRequestData, SudoRequestData } from 'codify-schemas';
55
import createDebug from 'debug';
66

77
import { Event, ctx } from '../events/context.js';
@@ -58,6 +58,11 @@ export abstract class BaseCommand extends Command {
5858
this.catch(error as Error);
5959
}
6060
});
61+
62+
ctx.on(Event.PRESS_KEY_TO_CONTINUE_REQUEST, async (pluginName: string, data: PressKeyToContinueRequestData) => {
63+
await this.reporter.promptPressKeyToContinue(data.promptMessage)
64+
ctx.pressKeyToContinueCompleted(pluginName)
65+
})
6166
}
6267

6368
protected async catch(err: Error): Promise<void> {

src/entities/resource-info.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ interface ParameterInfo {
66
name: string;
77
type?: string;
88
description?: string;
9-
isRequired: boolean;
9+
isRequiredForImport: boolean;
1010
value?: unknown;
1111
}
1212

@@ -66,15 +66,20 @@ export class ResourceInfo implements GetResourceInfoResponseData {
6666

6767
this.parametersCache = Object.entries(properties)
6868
.map(([propertyName, info]) => {
69-
const isRequired = this.importAndDestroy?.requiredParameters?.some((name) => name === propertyName)
69+
const isRequiredForImport = this.importAndDestroy?.requiredParameters?.some((name) => name === propertyName)
70+
?? (required as string[] | undefined)?.includes(propertyName)
71+
?? false;
72+
73+
const isRequiredForDestroy = this.importAndDestroy?.requiredParameters?.some((name) => name === propertyName)
7074
?? (required as string[] | undefined)?.includes(propertyName)
7175
?? false;
7276

7377
return {
7478
name: propertyName,
7579
type: info.type ?? null,
7680
description: info.description,
77-
isRequired
81+
isRequiredForImport,
82+
isRequiredForDestroy,
7883
}
7984
});
8085
}
@@ -84,6 +89,6 @@ export class ResourceInfo implements GetResourceInfoResponseData {
8489

8590
getRequiredParameters(): ParameterInfo[] {
8691
return this.getParameterInfo()
87-
.filter((info) => info.isRequired);
92+
.filter((info) => info.isRequiredForImport);
8893
}
8994
}

src/events/context.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export enum Event {
1515
SUB_PROCESS_START = 'sub_process_start',
1616
SUDO_REQUEST = 'sudo_request',
1717
SUDO_REQUEST_GRANTED = 'sudo_request_granted',
18+
PRESS_KEY_TO_CONTINUE_REQUEST = 'press_key_to_continue_request',
19+
PRESS_KEY_TO_CONTINUE_COMPLETED = 'press_key_to_continue_completed',
1820
}
1921

2022
export enum ProcessName {
@@ -105,6 +107,14 @@ export const ctx = new class {
105107
this.emitter.emit(Event.SUDO_REQUEST_GRANTED, pluginName, data);
106108
}
107109

110+
pressToContinueRequested(pluginName: string, data: any) {
111+
this.emitter.emit(Event.PRESS_KEY_TO_CONTINUE_REQUEST, pluginName, data);
112+
}
113+
114+
pressKeyToContinueCompleted(pluginName: string) {
115+
this.emitter.emit(Event.PRESS_KEY_TO_CONTINUE_COMPLETED, pluginName);
116+
}
117+
108118
async subprocess<T>(name: string, run: () => Promise<T>): Promise<T> {
109119
this.emitter.emit(Event.SUB_PROCESS_START, name);
110120
const result = await run();

src/plugins/plugin-process.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import { IpcMessageV2, IpcMessageV2Schema, MessageCmd, SudoRequestData, SudoRequestDataSchema } from 'codify-schemas';
1+
import {
2+
IpcMessageV2,
3+
IpcMessageV2Schema,
4+
MessageCmd,
5+
PressKeyToContinueRequestData,
6+
PressKeyToContinueRequestDataSchema,
7+
SudoRequestData,
8+
SudoRequestDataSchema
9+
} from 'codify-schemas';
210
import { ChildProcess, fork } from 'node:child_process';
311
import { createRequire } from 'node:module';
412

@@ -9,6 +17,7 @@ import { PluginMessage } from './plugin-message.js';
917

1018
export const ipcMessageValidator = ajv.compile(IpcMessageV2Schema);
1119
export const sudoRequestValidator = ajv.compile(SudoRequestDataSchema);
20+
export const pressKeyToContinueRequestValidator = ajv.compile(PressKeyToContinueRequestDataSchema);
1221

1322
const DEFAULT_NODE_MODULES_DIR = '/usr/local/lib/codify/node_modules/'
1423

@@ -88,7 +97,27 @@ export class PluginProcess {
8897
}
8998
})
9099

91-
ctx.sudoRequested(pluginName, data as unknown as SudoRequestData);
100+
return ctx.sudoRequested(pluginName, data as unknown as SudoRequestData);
101+
}
102+
103+
if (message.cmd === MessageCmd.PRESS_KEY_TO_CONTINUE_REQUEST) {
104+
const { data, requestId } = message;
105+
if (!pressKeyToContinueRequestValidator(data)) {
106+
throw new Error(`Invalid press key to continue request from plugin ${pluginName}. ${JSON.stringify(pressKeyToContinueRequestValidator.errors, null, 2)}`);
107+
}
108+
109+
// Send out sudo granted events
110+
ctx.once(Event.PRESS_KEY_TO_CONTINUE_COMPLETED, (_pluginName) => {
111+
if (_pluginName === pluginName) {
112+
process.send({
113+
cmd: returnMessageCmd(MessageCmd.PRESS_KEY_TO_CONTINUE_REQUEST),
114+
requestId,
115+
data: {},
116+
})
117+
}
118+
})
119+
120+
return ctx.pressToContinueRequested(pluginName, data as unknown as PressKeyToContinueRequestData);
92121
}
93122
})
94123
}

src/ui/components/default-component.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ import { FileModificationDisplay } from './file-modification/FileModification.js
1717
import { ImportResultComponent } from './import/import-result.js';
1818
import { ImportWarning } from './import/import-warning.js';
1919
import { InitBanner } from './init/InitBanner.js';
20+
import { MultiSelect } from './multi-select/MultiSelect.js';
2021
import { PlanComponent } from './plan/plan.js';
2122
import { ProgressDisplay } from './progress/progress-display.js';
22-
import { MultiSelect } from './multi-select/MultiSelect.js';
23+
import { PromptPressKeyToContinue } from './widgets/PromptPressKeyToContinue.js';
2324

2425
const spinnerEmitter = new EventEmitter();
2526

@@ -157,9 +158,14 @@ export function DefaultComponent(props: {
157158
<Box flexDirection='column'>
158159
<Text bold>{renderData.prompt}</Text>
159160
{ renderData.error && (<Text color='red'>{renderData.error}</Text>) }
160-
<TextInput placeholder='~/codify.json' onSubmit={(result) => emitter.emit(RenderEvent.PROMPT_RESULT, result)} />
161+
<TextInput onSubmit={(result) => emitter.emit(RenderEvent.PROMPT_RESULT, result)} placeholder='~/codify.json' />
161162
</Box>
162163
)
163164
}
165+
{
166+
renderStatus === RenderStatus.PROMPT_PRESS_KEY_TO_CONTINUE && (
167+
<PromptPressKeyToContinue message={renderData as string | undefined} onInput={() => emitter.emit(RenderEvent.PROMPT_RESULT)} />
168+
)
169+
}
164170
</Box>
165171
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Box, Text, useInput } from 'ink';
2+
import React from 'react';
3+
4+
export function PromptPressKeyToContinue(props: {
5+
message?: string;
6+
onInput: () => void
7+
}) {
8+
useInput(() => {
9+
props.onInput();
10+
});
11+
12+
return <Box flexDirection='column' marginTop={1}>
13+
{ props.message && (<Text>{props.message}</Text>) }
14+
<Text> </Text>
15+
<Text color='gray' dimColor>{'<Press any key to continue>'}</Text>
16+
</Box>
17+
}

src/ui/reporters/default-reporter.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ export class DefaultReporter implements Reporter {
4949
ctx.on(Event.SUB_PROCESS_FINISH, (name, additionalName) => this.onSubprocessFinishEvent(name, additionalName));
5050
}
5151

52+
async promptPressKeyToContinue(message?: string): Promise<void> {
53+
await this.updateStateAndAwaitEvent<boolean>(
54+
() => this.updateRenderState(RenderStatus.PROMPT_PRESS_KEY_TO_CONTINUE, message),
55+
RenderEvent.PROMPT_RESULT,
56+
)
57+
58+
this.updateRenderState(RenderStatus.NOTHING);
59+
}
60+
5261
async displayInitBanner(): Promise<void> {
5362
await this.updateStateAndAwaitEvent<boolean>(
5463
() => this.updateRenderState(RenderStatus.DISPLAY_INIT_BANNER),

0 commit comments

Comments
 (0)