From cb5657ae7a865b1864719676cb3180d77d286b1d Mon Sep 17 00:00:00 2001 From: kevinwang Date: Tue, 4 Mar 2025 19:09:25 -0500 Subject: [PATCH 1/5] feat: Added verbosity level and automatic required parameter generation for inits --- package-lock.json | 12 ++++++------ package.json | 4 ++-- src/messages/handlers.ts | 2 +- src/plugin/plugin.ts | 10 ++++++++-- src/pty/background-pty.ts | 6 +++++- src/resource/resource-controller.ts | 15 ++++++++++++++- src/resource/resource-settings.ts | 11 +++++++++-- src/utils/utils.ts | 12 ++++++++++++ 8 files changed, 57 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index afe21f0..842c405 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "codify-plugin-lib", - "version": "1.0.155", + "version": "1.0.166", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codify-plugin-lib", - "version": "1.0.155", + "version": "1.0.166", "license": "ISC", "dependencies": { "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5", "@npmcli/promise-spawn": "^7.0.1", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", - "codify-schemas": "1.0.73", + "codify-schemas": "1.0.74", "lodash.isequal": "^4.5.0", "nanoid": "^5.0.9", "strip-ansi": "^7.1.0", @@ -2283,9 +2283,9 @@ } }, "node_modules/codify-schemas": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/codify-schemas/-/codify-schemas-1.0.73.tgz", - "integrity": "sha512-rokit3Ayz/B32NBZm4fsCb3/0Ju0lqhWu/K/soDV5ls3BqbuHd8Cg35D3FGgTxNqoGWLfLt5odHOuvMqS4Q/KA==", + "version": "1.0.74", + "resolved": "https://registry.npmjs.org/codify-schemas/-/codify-schemas-1.0.74.tgz", + "integrity": "sha512-PqcG1JhsYmIUxf2RlSkJU1W+nchBs7lbWTNqoNUUZsZWhs42ZhdbA5rqW7+c0vjTd6wJOv3LDFTk/QxewrZTEw==", "dependencies": { "ajv": "^8.12.0" } diff --git a/package.json b/package.json index d99d7cb..2beb35b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codify-plugin-lib", - "version": "1.0.166", + "version": "1.0.167", "description": "Library plugin library", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -16,7 +16,7 @@ "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^2.1.1", - "codify-schemas": "1.0.73", + "codify-schemas": "1.0.74", "@npmcli/promise-spawn": "^7.0.1", "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5", "uuid": "^10.0.0", diff --git a/src/messages/handlers.ts b/src/messages/handlers.ts index f728327..100342d 100644 --- a/src/messages/handlers.ts +++ b/src/messages/handlers.ts @@ -28,7 +28,7 @@ import { Plugin } from '../plugin/plugin.js'; const SupportedRequests: Record Promise; requestValidator: SchemaObject; responseValidator: SchemaObject }> = { 'initialize': { - handler: async (plugin: Plugin) => plugin.initialize(), + handler: async (plugin: Plugin, data: any) => plugin.initialize(data), requestValidator: InitializeRequestDataSchema, responseValidator: InitializeResponseDataSchema }, diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts index 12ad448..dd60912 100644 --- a/src/plugin/plugin.ts +++ b/src/plugin/plugin.ts @@ -5,6 +5,7 @@ import { GetResourceInfoResponseData, ImportRequestData, ImportResponseData, + InitializeRequestData, InitializeResponseData, MatchRequestData, MatchResponseData, @@ -23,6 +24,7 @@ import { getPty } from '../pty/index.js'; import { Resource } from '../resource/resource.js'; import { ResourceController } from '../resource/resource-controller.js'; import { ptyLocalStorage } from '../utils/pty-local-storage.js'; +import { VerbosityLevel } from '../utils/utils.js'; export class Plugin { planStorage: Map>; @@ -46,7 +48,11 @@ export class Plugin { return new Plugin(name, controllersMap); } - async initialize(): Promise { + async initialize(data: InitializeRequestData): Promise { + if (data.verbosityLevel) { + VerbosityLevel.set(data.verbosityLevel); + } + for (const controller of this.resourceControllers.values()) { await controller.initialize(); } @@ -116,7 +122,7 @@ export class Plugin { const result = await ptyLocalStorage.run(this.planPty, () => this.resourceControllers .get(core.type!) - ?.import(core, parameters) + ?.import(core, parameters, data.autoSearchAll) ) return { diff --git a/src/pty/background-pty.ts b/src/pty/background-pty.ts index 380b4a0..8c46dbd 100644 --- a/src/pty/background-pty.ts +++ b/src/pty/background-pty.ts @@ -6,6 +6,7 @@ import * as fs from 'node:fs/promises'; import stripAnsi from 'strip-ansi'; import { debugLog } from '../utils/debug.js'; +import { VerbosityLevel } from '../utils/utils.js'; import { IPty, SpawnError, SpawnOptions, SpawnResult } from './index.js'; import { PromiseQueue } from './promise-queue.js'; @@ -76,7 +77,10 @@ export class BackgroundPty implements IPty { data: strippedData, }); } else { - process.stdout.write(data); + // Print to stdout if the verbosity level is above 0 + if (VerbosityLevel.get() > 0) { + process.stdout.write(data); + } } }) diff --git a/src/resource/resource-controller.ts b/src/resource/resource-controller.ts index 548f4a2..238dd36 100644 --- a/src/resource/resource-controller.ts +++ b/src/resource/resource-controller.ts @@ -258,7 +258,8 @@ export class ResourceController { async import( core: ResourceConfig, - parameters: Partial + parameters: Partial, + autoSearchAll = false, ): Promise | null> { if (this.settings.importAndDestroy?.preventImport) { throw new Error(`Type: ${this.typeId} cannot be imported`); @@ -270,6 +271,18 @@ export class ResourceController { originalDesiredConfig: structuredClone(parameters), }; + // Auto search means that no required parameters will be provided. We will try to generate it ourselves or return an + // empty array if they can't be. + if (autoSearchAll && this.settings.allowMultiple) { + if (this.settings.allowMultiple === true || !this.settings.allowMultiple.findAllParameters?.()) { + return []; + } + + const parametersToImport = await this.settings.allowMultiple.findAllParameters?.(); + const results = await Promise.all(parametersToImport.map((p) => this.import(core, p))); + return results.filter(Boolean).flat() as ResourceJson[]; + } + this.addDefaultValues(parameters); await this.applyTransformParameters(parameters); diff --git a/src/resource/resource-settings.ts b/src/resource/resource-settings.ts index 26dc568..358df90 100644 --- a/src/resource/resource-settings.ts +++ b/src/resource/resource-settings.ts @@ -43,7 +43,7 @@ export interface ResourceSettings { * If paramA is required, then if resource1.paramA === resource2.paramA then are the same resource. * If resource1.paramA !== resource1.paramA, then they are different. */ - identifyingParameters?: string[] + identifyingParameters?: string[]; /** * If multiple copies are allowed then a matcher must be defined to match the desired @@ -54,7 +54,14 @@ export interface ResourceSettings { * * @return The matched resource. */ - matcher?: (desired: Partial, current: Partial) => boolean + matcher?: (desired: Partial, current: Partial) => boolean; + + /** + * This method if supported by the resource returns an array of parameters that represent all of the possible + * instances of a resource on the system. An example of this is for the git-repository resource, this method returns + * a list of directories which are git repositories. + */ + findAllParameters?: () => Promise>> } | boolean /** diff --git a/src/utils/utils.ts b/src/utils/utils.ts index aaa5868..da5f99c 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -2,6 +2,18 @@ import { ResourceConfig, StringIndexedObject } from 'codify-schemas'; import os from 'node:os'; import path from 'node:path'; +export const VerbosityLevel = new class { + private level = 0; + + get() { + return this.level; + } + + set(level: number) { + this.level = level; + } +} + export function isDebug(): boolean { return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library } From f739057525941d18c9ca0e1f1c569a1acfc8847f Mon Sep 17 00:00:00 2001 From: kevinwang Date: Fri, 7 Mar 2025 10:12:07 -0500 Subject: [PATCH 2/5] feat: bug fixes and improvements --- package.json | 2 +- src/plugin/plugin.ts | 4 ++-- src/pty/background-pty.ts | 2 +- src/resource/resource-controller.ts | 4 +++- src/utils/utils.ts | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2beb35b..d6d8005 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codify-plugin-lib", - "version": "1.0.167", + "version": "1.0.169", "description": "Library plugin library", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts index dd60912..60a0bf7 100644 --- a/src/plugin/plugin.ts +++ b/src/plugin/plugin.ts @@ -113,7 +113,7 @@ export class Plugin { } async import(data: ImportRequestData): Promise { - const { core, parameters } = data; + const { core, parameters, autoSearchAll } = data; if (!this.resourceControllers.has(core.type)) { throw new Error(`Cannot get info for resource ${core.type}, resource doesn't exist`); @@ -122,7 +122,7 @@ export class Plugin { const result = await ptyLocalStorage.run(this.planPty, () => this.resourceControllers .get(core.type!) - ?.import(core, parameters, data.autoSearchAll) + ?.import(core, parameters, autoSearchAll) ) return { diff --git a/src/pty/background-pty.ts b/src/pty/background-pty.ts index 8c46dbd..cba7323 100644 --- a/src/pty/background-pty.ts +++ b/src/pty/background-pty.ts @@ -101,7 +101,7 @@ export class BackgroundPty implements IPty { } }); - console.log(`Running command ${cmd}`) + console.log(`Running command ${cmd}${options?.cwd ? ` (cwd: ${options.cwd})` : ''}`) this.basePty.write(`${command}\r`); })); diff --git a/src/resource/resource-controller.ts b/src/resource/resource-controller.ts index 238dd36..d4ed647 100644 --- a/src/resource/resource-controller.ts +++ b/src/resource/resource-controller.ts @@ -279,7 +279,9 @@ export class ResourceController { } const parametersToImport = await this.settings.allowMultiple.findAllParameters?.(); - const results = await Promise.all(parametersToImport.map((p) => this.import(core, p))); + const results = await Promise.all(parametersToImport.map((p) => + this.import(core, p).catch(() => null)) + ); return results.filter(Boolean).flat() as ResourceJson[]; } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index da5f99c..618f833 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -3,7 +3,7 @@ import os from 'node:os'; import path from 'node:path'; export const VerbosityLevel = new class { - private level = 0; + level = 0; get() { return this.level; From a5c889368d4a7dec4be3da0e8a834badf71f4843 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sun, 9 Mar 2025 12:19:07 -0400 Subject: [PATCH 3/5] feat: bug with commands still being written to the history --- package.json | 2 +- src/pty/background-pty.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d6d8005..6609337 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codify-plugin-lib", - "version": "1.0.169", + "version": "1.0.170", "description": "Library plugin library", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/pty/background-pty.ts b/src/pty/background-pty.ts index cba7323..7d8be47 100644 --- a/src/pty/background-pty.ts +++ b/src/pty/background-pty.ts @@ -89,7 +89,7 @@ export class BackgroundPty implements IPty { // Redirecting everything to the pipe and running in theb background avoids most if not all back-pressure problems // Done is used to denote the end of the command // Use the \\" at the end differentiate between command and response. \\" will evaluate to " in the terminal - const command = `((${cdCommand}${cmd}; echo %%%$?%%%done%%%\\") > "/tmp/${cid}" 2>&1 &); echo %%%done%%%${cid}\\";` + const command = ` ((${cdCommand}${cmd}; echo %%%$?%%%done%%%\\") > "/tmp/${cid}" 2>&1 &); echo %%%done%%%${cid}\\";` let output = ''; const listener = this.basePty.onData((data: any) => { @@ -127,10 +127,10 @@ export class BackgroundPty implements IPty { let outputBuffer = ''; return new Promise(resolve => { - this.basePty.write('set +o history;\n'); - this.basePty.write('unset PS1;\n'); - this.basePty.write('unset PS0;\n') - this.basePty.write('echo setup complete\\"\n') + this.basePty.write('setopt hist_ignore_space;\n'); + this.basePty.write(' unset PS1;\n'); + this.basePty.write(' unset PS0;\n') + this.basePty.write(' echo setup complete\\"\n') const listener = this.basePty.onData((data: string) => { outputBuffer += data; From 68c8b22bb04080035d02ef3c4665b196e3ca431c Mon Sep 17 00:00:00 2001 From: kevinwang Date: Mon, 17 Mar 2025 16:55:10 -0400 Subject: [PATCH 4/5] fix: removed path.resolve from directory transformation --- package.json | 2 +- src/resource/resource-settings.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6609337..59371c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codify-plugin-lib", - "version": "1.0.170", + "version": "1.0.171", "description": "Library plugin library", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/resource/resource-settings.ts b/src/resource/resource-settings.ts index 358df90..1415b21 100644 --- a/src/resource/resource-settings.ts +++ b/src/resource/resource-settings.ts @@ -398,7 +398,7 @@ export function resolveFnFromEqualsFnOrString( const ParameterTransformationDefaults: Partial> = { 'directory': { - to: (a: unknown) => path.resolve(resolvePathWithVariables((untildify(String(a))))), + to: (a: unknown) => resolvePathWithVariables((untildify(String(a)))), from: (a: unknown, original) => { if (ParameterEqualsDefaults.directory!(a, original)) { return original; From d216954c0dd183c1933ded70f82db3a687aed4ea Mon Sep 17 00:00:00 2001 From: kevinwang Date: Mon, 17 Mar 2025 17:53:43 -0400 Subject: [PATCH 5/5] feat: Add cli message sender and the first use case for it (sending a request for showing a prompt --- package-lock.json | 12 ++++----- package.json | 4 +-- src/index.ts | 1 + src/messages/sender.ts | 56 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 src/messages/sender.ts diff --git a/package-lock.json b/package-lock.json index 842c405..dd2fdbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "codify-plugin-lib", - "version": "1.0.166", + "version": "1.0.171", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codify-plugin-lib", - "version": "1.0.166", + "version": "1.0.171", "license": "ISC", "dependencies": { "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5", "@npmcli/promise-spawn": "^7.0.1", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", - "codify-schemas": "1.0.74", + "codify-schemas": "1.0.76", "lodash.isequal": "^4.5.0", "nanoid": "^5.0.9", "strip-ansi": "^7.1.0", @@ -2283,9 +2283,9 @@ } }, "node_modules/codify-schemas": { - "version": "1.0.74", - "resolved": "https://registry.npmjs.org/codify-schemas/-/codify-schemas-1.0.74.tgz", - "integrity": "sha512-PqcG1JhsYmIUxf2RlSkJU1W+nchBs7lbWTNqoNUUZsZWhs42ZhdbA5rqW7+c0vjTd6wJOv3LDFTk/QxewrZTEw==", + "version": "1.0.76", + "resolved": "https://registry.npmjs.org/codify-schemas/-/codify-schemas-1.0.76.tgz", + "integrity": "sha512-ZytwEUc2xf6vJ98tZ3K16E4zcmI+80Y25eH1Zl+9nh5V1CoOKTv5xx/FREsfI2mTV0h4LCZL6USzqlOylfXxIg==", "dependencies": { "ajv": "^8.12.0" } diff --git a/package.json b/package.json index 59371c4..7b5cbd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codify-plugin-lib", - "version": "1.0.171", + "version": "1.0.173", "description": "Library plugin library", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -16,7 +16,7 @@ "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^2.1.1", - "codify-schemas": "1.0.74", + "codify-schemas": "1.0.76", "@npmcli/promise-spawn": "^7.0.1", "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5", "uuid": "^10.0.0", diff --git a/src/index.ts b/src/index.ts index 4c1d3db..e3d7427 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { MessageHandler } from './messages/handlers.js'; import { Plugin } from './plugin/plugin.js'; export * from './errors.js' +export * from './messages/sender.js' export * from './plan/change-set.js' export * from './plan/plan.js' export * from './plan/plan-types.js' diff --git a/src/messages/sender.ts b/src/messages/sender.ts new file mode 100644 index 0000000..9923b10 --- /dev/null +++ b/src/messages/sender.ts @@ -0,0 +1,56 @@ +import { Ajv } from 'ajv'; +import { IpcMessageV2, IpcMessageV2Schema, MessageCmd, PressKeyToContinueRequestData } from 'codify-schemas'; +import { nanoid } from 'nanoid'; + +const ajv = new Ajv({ + strict: true, +}); + +/** + * Send requests to the Codify CLI + */ +class CodifyCliSenderImpl { + private readonly validateIpcMessageV2 = ajv.compile(IpcMessageV2Schema); + + async requestPressKeyToContinuePrompt(message?: string): Promise { + await this.sendAndWaitForResponse({ + cmd: MessageCmd.PRESS_KEY_TO_CONTINUE_REQUEST, + data: { + promptMessage: message, + } + }) + } + + private async sendAndWaitForResponse(message: IpcMessageV2): Promise { + return new Promise((resolve) => { + const requestId = nanoid(8); + const listener = (data: IpcMessageV2) => { + if (data.requestId === requestId) { + process.removeListener('message', listener); + + if (!this.validateIpcMessageV2(data)) { + throw new Error(`Invalid response for request. +Request: +${JSON.stringify(message, null, 2)} +Response: +${JSON.stringify(data, null, 2)} +Error: +${JSON.stringify(this.validateIpcMessageV2.errors, null, 2)}`); + } + + resolve(data); + } + } + + process.on('message', listener); + + const ipcMessage = { + ...message, + requestId, + } + process.send!(ipcMessage) + }) + } +} + +export const CodifyCliSender = new CodifyCliSenderImpl();