Skip to content

Commit 34e4d56

Browse files
Kevin/init improvements (#15)
* feat: Added verbosity level and automatic required parameter generation for inits * feat: bug fixes and improvements * feat: bug with commands still being written to the history * fix: removed path.resolve from directory transformation * feat: Add cli message sender and the first use case for it (sending a request for showing a <press any key to continue> prompt
1 parent 90df563 commit 34e4d56

10 files changed

Lines changed: 124 additions & 23 deletions

File tree

package-lock.json

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

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codify-plugin-lib",
3-
"version": "1.0.166",
3+
"version": "1.0.173",
44
"description": "Library plugin library",
55
"main": "dist/index.js",
66
"typings": "dist/index.d.ts",
@@ -16,7 +16,7 @@
1616
"dependencies": {
1717
"ajv": "^8.12.0",
1818
"ajv-formats": "^2.1.1",
19-
"codify-schemas": "1.0.73",
19+
"codify-schemas": "1.0.76",
2020
"@npmcli/promise-spawn": "^7.0.1",
2121
"@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5",
2222
"uuid": "^10.0.0",

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { MessageHandler } from './messages/handlers.js';
22
import { Plugin } from './plugin/plugin.js';
33

44
export * from './errors.js'
5+
export * from './messages/sender.js'
56
export * from './plan/change-set.js'
67
export * from './plan/plan.js'
78
export * from './plan/plan-types.js'

src/messages/handlers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { Plugin } from '../plugin/plugin.js';
2828

2929
const SupportedRequests: Record<string, { handler: (plugin: Plugin, data: any) => Promise<unknown>; requestValidator: SchemaObject; responseValidator: SchemaObject }> = {
3030
'initialize': {
31-
handler: async (plugin: Plugin) => plugin.initialize(),
31+
handler: async (plugin: Plugin, data: any) => plugin.initialize(data),
3232
requestValidator: InitializeRequestDataSchema,
3333
responseValidator: InitializeResponseDataSchema
3434
},

src/messages/sender.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Ajv } from 'ajv';
2+
import { IpcMessageV2, IpcMessageV2Schema, MessageCmd, PressKeyToContinueRequestData } from 'codify-schemas';
3+
import { nanoid } from 'nanoid';
4+
5+
const ajv = new Ajv({
6+
strict: true,
7+
});
8+
9+
/**
10+
* Send requests to the Codify CLI
11+
*/
12+
class CodifyCliSenderImpl {
13+
private readonly validateIpcMessageV2 = ajv.compile(IpcMessageV2Schema);
14+
15+
async requestPressKeyToContinuePrompt(message?: string): Promise<void> {
16+
await this.sendAndWaitForResponse(<IpcMessageV2>{
17+
cmd: MessageCmd.PRESS_KEY_TO_CONTINUE_REQUEST,
18+
data: <PressKeyToContinueRequestData>{
19+
promptMessage: message,
20+
}
21+
})
22+
}
23+
24+
private async sendAndWaitForResponse(message: IpcMessageV2): Promise<IpcMessageV2> {
25+
return new Promise((resolve) => {
26+
const requestId = nanoid(8);
27+
const listener = (data: IpcMessageV2) => {
28+
if (data.requestId === requestId) {
29+
process.removeListener('message', listener);
30+
31+
if (!this.validateIpcMessageV2(data)) {
32+
throw new Error(`Invalid response for request.
33+
Request:
34+
${JSON.stringify(message, null, 2)}
35+
Response:
36+
${JSON.stringify(data, null, 2)}
37+
Error:
38+
${JSON.stringify(this.validateIpcMessageV2.errors, null, 2)}`);
39+
}
40+
41+
resolve(data);
42+
}
43+
}
44+
45+
process.on('message', listener);
46+
47+
const ipcMessage = {
48+
...message,
49+
requestId,
50+
}
51+
process.send!(ipcMessage)
52+
})
53+
}
54+
}
55+
56+
export const CodifyCliSender = new CodifyCliSenderImpl();

src/plugin/plugin.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
GetResourceInfoResponseData,
66
ImportRequestData,
77
ImportResponseData,
8+
InitializeRequestData,
89
InitializeResponseData,
910
MatchRequestData,
1011
MatchResponseData,
@@ -23,6 +24,7 @@ import { getPty } from '../pty/index.js';
2324
import { Resource } from '../resource/resource.js';
2425
import { ResourceController } from '../resource/resource-controller.js';
2526
import { ptyLocalStorage } from '../utils/pty-local-storage.js';
27+
import { VerbosityLevel } from '../utils/utils.js';
2628

2729
export class Plugin {
2830
planStorage: Map<string, Plan<any>>;
@@ -46,7 +48,11 @@ export class Plugin {
4648
return new Plugin(name, controllersMap);
4749
}
4850

49-
async initialize(): Promise<InitializeResponseData> {
51+
async initialize(data: InitializeRequestData): Promise<InitializeResponseData> {
52+
if (data.verbosityLevel) {
53+
VerbosityLevel.set(data.verbosityLevel);
54+
}
55+
5056
for (const controller of this.resourceControllers.values()) {
5157
await controller.initialize();
5258
}
@@ -107,7 +113,7 @@ export class Plugin {
107113
}
108114

109115
async import(data: ImportRequestData): Promise<ImportResponseData> {
110-
const { core, parameters } = data;
116+
const { core, parameters, autoSearchAll } = data;
111117

112118
if (!this.resourceControllers.has(core.type)) {
113119
throw new Error(`Cannot get info for resource ${core.type}, resource doesn't exist`);
@@ -116,7 +122,7 @@ export class Plugin {
116122
const result = await ptyLocalStorage.run(this.planPty, () =>
117123
this.resourceControllers
118124
.get(core.type!)
119-
?.import(core, parameters)
125+
?.import(core, parameters, autoSearchAll)
120126
)
121127

122128
return {

src/pty/background-pty.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as fs from 'node:fs/promises';
66
import stripAnsi from 'strip-ansi';
77

88
import { debugLog } from '../utils/debug.js';
9+
import { VerbosityLevel } from '../utils/utils.js';
910
import { IPty, SpawnError, SpawnOptions, SpawnResult } from './index.js';
1011
import { PromiseQueue } from './promise-queue.js';
1112

@@ -76,7 +77,10 @@ export class BackgroundPty implements IPty {
7677
data: strippedData,
7778
});
7879
} else {
79-
process.stdout.write(data);
80+
// Print to stdout if the verbosity level is above 0
81+
if (VerbosityLevel.get() > 0) {
82+
process.stdout.write(data);
83+
}
8084
}
8185
})
8286

@@ -85,7 +89,7 @@ export class BackgroundPty implements IPty {
8589
// Redirecting everything to the pipe and running in theb background avoids most if not all back-pressure problems
8690
// Done is used to denote the end of the command
8791
// Use the \\" at the end differentiate between command and response. \\" will evaluate to " in the terminal
88-
const command = `((${cdCommand}${cmd}; echo %%%$?%%%done%%%\\") > "/tmp/${cid}" 2>&1 &); echo %%%done%%%${cid}\\";`
92+
const command = ` ((${cdCommand}${cmd}; echo %%%$?%%%done%%%\\") > "/tmp/${cid}" 2>&1 &); echo %%%done%%%${cid}\\";`
8993

9094
let output = '';
9195
const listener = this.basePty.onData((data: any) => {
@@ -97,7 +101,7 @@ export class BackgroundPty implements IPty {
97101
}
98102
});
99103

100-
console.log(`Running command ${cmd}`)
104+
console.log(`Running command ${cmd}${options?.cwd ? ` (cwd: ${options.cwd})` : ''}`)
101105
this.basePty.write(`${command}\r`);
102106

103107
}));
@@ -123,10 +127,10 @@ export class BackgroundPty implements IPty {
123127
let outputBuffer = '';
124128

125129
return new Promise(resolve => {
126-
this.basePty.write('set +o history;\n');
127-
this.basePty.write('unset PS1;\n');
128-
this.basePty.write('unset PS0;\n')
129-
this.basePty.write('echo setup complete\\"\n')
130+
this.basePty.write('setopt hist_ignore_space;\n');
131+
this.basePty.write(' unset PS1;\n');
132+
this.basePty.write(' unset PS0;\n')
133+
this.basePty.write(' echo setup complete\\"\n')
130134

131135
const listener = this.basePty.onData((data: string) => {
132136
outputBuffer += data;

src/resource/resource-controller.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,8 @@ export class ResourceController<T extends StringIndexedObject> {
258258

259259
async import(
260260
core: ResourceConfig,
261-
parameters: Partial<T>
261+
parameters: Partial<T>,
262+
autoSearchAll = false,
262263
): Promise<Array<ResourceJson> | null> {
263264
if (this.settings.importAndDestroy?.preventImport) {
264265
throw new Error(`Type: ${this.typeId} cannot be imported`);
@@ -270,6 +271,20 @@ export class ResourceController<T extends StringIndexedObject> {
270271
originalDesiredConfig: structuredClone(parameters),
271272
};
272273

274+
// Auto search means that no required parameters will be provided. We will try to generate it ourselves or return an
275+
// empty array if they can't be.
276+
if (autoSearchAll && this.settings.allowMultiple) {
277+
if (this.settings.allowMultiple === true || !this.settings.allowMultiple.findAllParameters?.()) {
278+
return [];
279+
}
280+
281+
const parametersToImport = await this.settings.allowMultiple.findAllParameters?.();
282+
const results = await Promise.all(parametersToImport.map((p) =>
283+
this.import(core, p).catch(() => null))
284+
);
285+
return results.filter(Boolean).flat() as ResourceJson[];
286+
}
287+
273288
this.addDefaultValues(parameters);
274289
await this.applyTransformParameters(parameters);
275290

src/resource/resource-settings.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export interface ResourceSettings<T extends StringIndexedObject> {
4343
* If paramA is required, then if resource1.paramA === resource2.paramA then are the same resource.
4444
* If resource1.paramA !== resource1.paramA, then they are different.
4545
*/
46-
identifyingParameters?: string[]
46+
identifyingParameters?: string[];
4747

4848
/**
4949
* If multiple copies are allowed then a matcher must be defined to match the desired
@@ -54,7 +54,14 @@ export interface ResourceSettings<T extends StringIndexedObject> {
5454
*
5555
* @return The matched resource.
5656
*/
57-
matcher?: (desired: Partial<T>, current: Partial<T>) => boolean
57+
matcher?: (desired: Partial<T>, current: Partial<T>) => boolean;
58+
59+
/**
60+
* This method if supported by the resource returns an array of parameters that represent all of the possible
61+
* instances of a resource on the system. An example of this is for the git-repository resource, this method returns
62+
* a list of directories which are git repositories.
63+
*/
64+
findAllParameters?: () => Promise<Array<Partial<T>>>
5865
} | boolean
5966

6067
/**
@@ -391,7 +398,7 @@ export function resolveFnFromEqualsFnOrString(
391398

392399
const ParameterTransformationDefaults: Partial<Record<ParameterSettingType, InputTransformation>> = {
393400
'directory': {
394-
to: (a: unknown) => path.resolve(resolvePathWithVariables((untildify(String(a))))),
401+
to: (a: unknown) => resolvePathWithVariables((untildify(String(a)))),
395402
from: (a: unknown, original) => {
396403
if (ParameterEqualsDefaults.directory!(a, original)) {
397404
return original;

src/utils/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@ import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
22
import os from 'node:os';
33
import path from 'node:path';
44

5+
export const VerbosityLevel = new class {
6+
level = 0;
7+
8+
get() {
9+
return this.level;
10+
}
11+
12+
set(level: number) {
13+
this.level = level;
14+
}
15+
}
16+
517
export function isDebug(): boolean {
618
return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
719
}

0 commit comments

Comments
 (0)