From fd581f079ae2ded4f557ced0ee3eb491a42c3116 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Mon, 20 Apr 2026 20:31:00 -0400 Subject: [PATCH 01/18] fix: Added example and default configs --- package-lock.json | 12 ++++++------ package.json | 4 ++-- src/plugin/plugin.ts | 7 +++++-- src/resource/parsed-resource-settings.ts | 4 ++++ src/resource/resource-settings.ts | 24 ++++++++++++++++++++++++ 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2bda35f..cad35f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@codifycli/plugin-core", - "version": "1.0.0-beta6", + "version": "1.1.0-beta5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codifycli/plugin-core", - "version": "1.0.0-beta6", + "version": "1.1.0-beta5", "license": "ISC", "dependencies": { - "@codifycli/schemas": "1.0.0", + "@codifycli/schemas": "1.1.0-beta3", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", @@ -94,9 +94,9 @@ } }, "node_modules/@codifycli/schemas": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@codifycli/schemas/-/schemas-1.0.0.tgz", - "integrity": "sha512-E7F56uA7DENvQJP4Wnwe1y+gwl5SWcGsbOH4gNNs6FL5BE2WagVDz0jR6/dm1Bfjmg6N0AvROIQJmUaRW+To2g==", + "version": "1.1.0-beta3", + "resolved": "https://registry.npmjs.org/@codifycli/schemas/-/schemas-1.1.0-beta3.tgz", + "integrity": "sha512-5yThq+6Ks5/NVagbpBOoQceCQLe89PufB21Eyic7mzj36JdXUKl5TPyAaixMn32Mbick6pgoLifnBnd01mv3ug==", "license": "ISC", "dependencies": { "ajv": "^8.18.0" diff --git a/package.json b/package.json index 98bdb42..137972c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.0.1", + "version": "1.1.0-beta5", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -35,7 +35,7 @@ }, "license": "ISC", "dependencies": { - "@codifycli/schemas": "1.0.0", + "@codifycli/schemas": "1.1.0-beta3", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts index 9ebcc48..858dbb8 100644 --- a/src/plugin/plugin.ts +++ b/src/plugin/plugin.ts @@ -11,7 +11,8 @@ import { PlanRequestData, PlanResponseData, ResourceConfig, - ResourceJson, SetVerbosityRequestData, + ResourceJson, + SetVerbosityRequestData, ValidateRequestData, ValidateResponseData } from '@codifycli/schemas'; @@ -127,7 +128,9 @@ export class Plugin { operatingSystems: resource.settings.operatingSystems, linuxDistros: resource.settings.linuxDistros, sensitiveParameters, - allowMultiple + allowMultiple, + defaultConfig: resource.settings.defaultConfig, + exampleConfigs: resource.settings.exampleConfigs, } } diff --git a/src/resource/parsed-resource-settings.ts b/src/resource/parsed-resource-settings.ts index b19f3ab..1afd04f 100644 --- a/src/resource/parsed-resource-settings.ts +++ b/src/resource/parsed-resource-settings.ts @@ -6,6 +6,7 @@ import { StatefulParameterController } from '../stateful-parameter/stateful-para import { ArrayParameterSetting, DefaultParameterSetting, + ExampleConfigs, InputTransformation, ParameterSetting, resolveElementEqualsFn, @@ -56,6 +57,9 @@ export class ParsedResourceSettings implements Re isSensitive?: boolean; + defaultConfig!: Partial; + exampleConfigs!: ExampleConfigs; + private settings: ResourceSettings; constructor(settings: ResourceSettings) { diff --git a/src/resource/resource-settings.ts b/src/resource/resource-settings.ts index 6acac52..a497486 100644 --- a/src/resource/resource-settings.ts +++ b/src/resource/resource-settings.ts @@ -15,6 +15,17 @@ import { import { ParsedResourceSettings } from './parsed-resource-settings.js'; import { RefreshContext } from './resource.js'; +export interface ExampleConfig extends Record { + title: string; + configs: Array>; + description?: string; +} + +export interface ExampleConfigs { + example1?: ExampleConfig; + example2?: ExampleConfig; +} + export interface InputTransformation { to: (input: any) => Promise | any; from: (current: any, original: any) => Promise | any; @@ -194,6 +205,19 @@ export interface ResourceSettings { */ refreshMapper?: (input: Partial, context: RefreshContext) => Partial; } + + /** + * Represents the default config that is added to the editor with prefilled properties. For some resources + * + * @type {Partial} + */ + defaultConfig?: Partial + + /** + * A collection of example configs used to give users an idea on how to use this specific resource. These examples + * don't need to be limited to just the current resource. They can include other resources as well + */ + exampleConfigs?: ExampleConfigs } /** From 623ba48520477ee8a176e04563ff68e9007a9333 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Wed, 22 Apr 2026 12:56:54 -0400 Subject: [PATCH 02/18] fix: Shell cannot be detected in a non-login environment --- package.json | 2 +- src/utils/index.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 137972c..2a96208 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta5", + "version": "1.1.0-beta7", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/utils/index.ts b/src/utils/index.ts index 13dcd76..f055404 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,7 +4,7 @@ import * as fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; -import { SpawnStatus, getPty } from '../pty/index.js'; +import { getPty, SpawnStatus } from '../pty/index.js'; export function isDebug(): boolean { return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library @@ -73,7 +73,7 @@ export const Utils = { }, getShell(): Shell | undefined { - const shell = process.env.SHELL || ''; + const shell = process.env.SHELL || os.userInfo().shell || ''; if (shell.endsWith('bash')) { return Shell.BASH @@ -108,7 +108,7 @@ export const Utils = { }, getShellRcFiles(): string[] { - const shell = process.env.SHELL || ''; + const shell = process.env.SHELL || os.userInfo().shell || ''; const homeDir = os.homedir(); if (shell.endsWith('bash')) { @@ -209,9 +209,9 @@ Brew can be installed using Codify: const isAptInstalled = await $.spawnSafe('which apt'); if (isAptInstalled.status === SpawnStatus.SUCCESS) { await $.spawn('apt-get update', { requiresRoot: true }); - const { status, data } = await $.spawnSafe(`apt-get -y install ${packageName}`, { + const { status, data } = await $.spawnSafe(`apt-get -y -qq install -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0 ${packageName}`, { requiresRoot: true, - env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' } + env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a', } }); if (status === SpawnStatus.ERROR && data.includes('E: dpkg was interrupted, you must manually run \'sudo dpkg --configure -a\' to correct the problem.')) { @@ -265,7 +265,7 @@ Brew can be installed using Codify: if (Utils.isLinux()) { const isAptInstalled = await $.spawnSafe('which apt'); if (isAptInstalled.status === SpawnStatus.SUCCESS) { - const { status } = await $.spawnSafe(`apt-get autoremove -y --purge ${packageName}`, { + const { status } = await $.spawnSafe(`apt-get -qq autoremove -y -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0 --purge ${packageName}`, { requiresRoot: true, env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' } }); From 4f235959d96601a592bb06e2f584ba0db050d3a0 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Wed, 22 Apr 2026 14:31:32 -0400 Subject: [PATCH 03/18] feat: Add create shell rc if not exist --- package.json | 2 +- src/utils/file-utils.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a96208..de75cb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta7", + "version": "1.1.0-beta8", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/utils/file-utils.ts b/src/utils/file-utils.ts index be45571..c5e5c97 100644 --- a/src/utils/file-utils.ts +++ b/src/utils/file-utils.ts @@ -26,6 +26,8 @@ export class FileUtils { } static async addToShellRc(line: string): Promise { + await FileUtils.createShellRcIfNotExists(); + const lineToInsert = addLeadingSpacer( addTrailingSpacer(line) ); @@ -46,6 +48,8 @@ export class FileUtils { } static async addAllToShellRc(lines: string[]): Promise { + await FileUtils.createShellRcIfNotExists(); + const formattedLines = '\n' + lines.join('\n') + '\n'; const shellRc = Utils.getPrimaryShellRc(); @@ -62,6 +66,8 @@ ${lines.join('\n')}`) * @param prepend - Whether to prepend the path to the existing PATH variable. */ static async addPathToShellRc(value: string, prepend: boolean): Promise { + await FileUtils.createShellRcIfNotExists(); + if (await Utils.isDirectoryOnPath(value)) { return; } @@ -228,4 +234,11 @@ ${lines.join('\n')}`) } } } + + static async createShellRcIfNotExists(): Promise { + const shellRc = Utils.getPrimaryShellRc(); + if (!await FileUtils.fileExists(shellRc)) { + await fs.writeFile(shellRc, '', 'utf8'); + } + } } From 6d80003af21633b820f903ac8bd356ce44221763 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Wed, 22 Apr 2026 20:48:12 -0400 Subject: [PATCH 04/18] feat: Add pty for validate. Change the way that the output is combined --- package.json | 2 +- src/plugin/plugin.ts | 8 +++++--- src/pty/seqeuntial-pty.ts | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index de75cb8..eb39e4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta8", + "version": "1.1.0-beta10", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts index 858dbb8..6d217db 100644 --- a/src/plugin/plugin.ts +++ b/src/plugin/plugin.ts @@ -174,9 +174,11 @@ export class Plugin { throw new Error(`Resource type not found: ${core.type}`); } - const validation = await this.resourceControllers - .get(core.type)! - .validate(core, parameters); + const validation = await ptyLocalStorage.run(this.planPty, () => + this.resourceControllers + .get(core.type)! + .validate(core, parameters) + ); validationResults.push(validation); } diff --git a/src/pty/seqeuntial-pty.ts b/src/pty/seqeuntial-pty.ts index ab0e7c7..c704b88 100644 --- a/src/pty/seqeuntial-pty.ts +++ b/src/pty/seqeuntial-pty.ts @@ -100,10 +100,12 @@ export class SequentialPty implements IPty { mPty.onExit((result) => { process.stdout.off('resize', resizeListener); + const raw = stripAnsi(output.join('')).replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim(); + resolve({ status: result.exitCode === 0 ? SpawnStatus.SUCCESS : SpawnStatus.ERROR, exitCode: result.exitCode, - data: stripAnsi(output.join('\n').trim()), + data: raw, }) }) }) From bb1e327742275945e99b1b244adeab5aee150409 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Wed, 22 Apr 2026 23:27:39 -0400 Subject: [PATCH 05/18] feat: Add distro support --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index cad35f2..15f9b42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta5", + "version": "1.1.0-beta10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta5", + "version": "1.1.0-beta10", "license": "ISC", "dependencies": { - "@codifycli/schemas": "1.1.0-beta3", + "@codifycli/schemas": "1.1.0-beta4", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", @@ -94,9 +94,9 @@ } }, "node_modules/@codifycli/schemas": { - "version": "1.1.0-beta3", - "resolved": "https://registry.npmjs.org/@codifycli/schemas/-/schemas-1.1.0-beta3.tgz", - "integrity": "sha512-5yThq+6Ks5/NVagbpBOoQceCQLe89PufB21Eyic7mzj36JdXUKl5TPyAaixMn32Mbick6pgoLifnBnd01mv3ug==", + "version": "1.1.0-beta4", + "resolved": "https://registry.npmjs.org/@codifycli/schemas/-/schemas-1.1.0-beta4.tgz", + "integrity": "sha512-bBEr9c+MqMcs+Ke5//JfQ3+Vmixh+8TvMqeJKh0OKPEntzwGmLVTqv6g8CDk9/M8H+To2KASLw2pjEHEBiJGSw==", "license": "ISC", "dependencies": { "ajv": "^8.18.0" diff --git a/package.json b/package.json index eb39e4d..edefdbc 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ }, "license": "ISC", "dependencies": { - "@codifycli/schemas": "1.1.0-beta3", + "@codifycli/schemas": "1.1.0-beta4", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", From 696dbdbdd914a6ac5be8a75e75a8904d76fc4210 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 23 Apr 2026 08:43:58 -0400 Subject: [PATCH 06/18] feat: Add distro support to splitting function --- package-lock.json | 4 ++-- package.json | 2 +- src/utils/functions.ts | 3 ++- src/utils/internal-utils.test.ts | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15f9b42..6ac2c6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta10", + "version": "1.1.0-beta11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta10", + "version": "1.1.0-beta11", "license": "ISC", "dependencies": { "@codifycli/schemas": "1.1.0-beta4", diff --git a/package.json b/package.json index edefdbc..2bb9fda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta10", + "version": "1.1.0-beta12", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/utils/functions.ts b/src/utils/functions.ts index 888f0c0..16011b0 100644 --- a/src/utils/functions.ts +++ b/src/utils/functions.ts @@ -10,10 +10,11 @@ export function splitUserConfig( ...(config.name ? { name: config.name } : {}), ...(config.dependsOn ? { dependsOn: config.dependsOn } : {}), ...(config.os ? { os: config.os } : {}), + ...(config.distro ? { distro: config.distro } : {}) }; // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { type, name, dependsOn, os, ...parameters } = config; + const { type, name, dependsOn, os, distro, ...parameters } = config; return { parameters: parameters as T, diff --git a/src/utils/internal-utils.test.ts b/src/utils/internal-utils.test.ts index dc63d8d..1323eb5 100644 --- a/src/utils/internal-utils.test.ts +++ b/src/utils/internal-utils.test.ts @@ -9,6 +9,7 @@ describe('Utils tests', () => { name: 'name', dependsOn: ['a', 'b', 'c'], os: ['linux'], + distro: ['debian-based'], propA: 'propA', propB: 'propB', propC: 'propC', From 46824067e15434e1ce9e0df0c52eb801f30a05a5 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sat, 25 Apr 2026 10:17:29 -0400 Subject: [PATCH 07/18] fix: Fixed settings parameters not being excluded from destroy. Fixed tests. Added requiresSudoAskpass --- package-lock.json | 12 +++---- package.json | 4 +-- src/plan/change-set.test.ts | 46 ++++++++++++++++++++++++ src/plan/change-set.ts | 1 + src/pty/index.ts | 1 + src/pty/seqeuntial-pty.ts | 2 +- src/resource/parsed-resource-settings.ts | 2 +- 7 files changed, 58 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ac2c6c..6b14718 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta11", + "version": "1.1.0-beta13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta11", + "version": "1.1.0-beta13", "license": "ISC", "dependencies": { - "@codifycli/schemas": "1.1.0-beta4", + "@codifycli/schemas": "1.1.0-beta5", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", @@ -94,9 +94,9 @@ } }, "node_modules/@codifycli/schemas": { - "version": "1.1.0-beta4", - "resolved": "https://registry.npmjs.org/@codifycli/schemas/-/schemas-1.1.0-beta4.tgz", - "integrity": "sha512-bBEr9c+MqMcs+Ke5//JfQ3+Vmixh+8TvMqeJKh0OKPEntzwGmLVTqv6g8CDk9/M8H+To2KASLw2pjEHEBiJGSw==", + "version": "1.1.0-beta5", + "resolved": "https://registry.npmjs.org/@codifycli/schemas/-/schemas-1.1.0-beta5.tgz", + "integrity": "sha512-xxfh6b48KW7Kav2uyrJb5E3dtuBDg4EUIaVKPJvJnocaaYFKGPwEUWvYwiNxqWDNBhrsbsGmNRatHGwXfhTT2A==", "license": "ISC", "dependencies": { "ajv": "^8.18.0" diff --git a/package.json b/package.json index 2bb9fda..8f431b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta12", + "version": "1.1.0-beta15", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -35,7 +35,7 @@ }, "license": "ISC", "dependencies": { - "@codifycli/schemas": "1.1.0-beta4", + "@codifycli/schemas": "1.1.0-beta5", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", diff --git a/src/plan/change-set.test.ts b/src/plan/change-set.test.ts index a9ac405..5e386ca 100644 --- a/src/plan/change-set.test.ts +++ b/src/plan/change-set.test.ts @@ -107,6 +107,7 @@ describe('Change set tests', () => { const parameterSettings = new ParsedResourceSettings({ id: 'type', + operatingSystems: [], parameterSettings: { propA: { type: 'array' } } @@ -129,6 +130,7 @@ describe('Change set tests', () => { const parameterSettings = new ParsedResourceSettings({ id: 'type', + operatingSystems: [], parameterSettings: { propA: { type: 'array' } } @@ -152,6 +154,7 @@ describe('Change set tests', () => { const parameterSettings = new ParsedResourceSettings({ id: 'type', + operatingSystems: [], parameterSettings: { propA: { canModify: true } } @@ -176,6 +179,7 @@ describe('Change set tests', () => { const parameterSettings = new ParsedResourceSettings({ id: 'type', + operatingSystems: [], parameterSettings: { propA: { canModify: true }, propB: { canModify: true } @@ -196,6 +200,7 @@ describe('Change set tests', () => { const parameterSettings = new ParsedResourceSettings({ id: 'type', + operatingSystems: [], parameterSettings: { propA: { type: 'array' } }, @@ -213,6 +218,7 @@ describe('Change set tests', () => { const parameterSettings = new ParsedResourceSettings({ id: 'type', + operatingSystems: [], parameterSettings: { propA: { type: 'array' } }, @@ -229,6 +235,7 @@ describe('Change set tests', () => { const parameterSettings = new ParsedResourceSettings({ id: 'type', + operatingSystems: [], parameterSettings: { propA: { type: 'array' } }, @@ -245,6 +252,7 @@ describe('Change set tests', () => { const parameterSettings = new ParsedResourceSettings({ id: 'type', + operatingSystems: [], parameterSettings: { propA: { type: 'array', @@ -259,12 +267,50 @@ describe('Change set tests', () => { expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY); }) + it('excludes setting parameters from destroy change set', () => { + const settings = { + id: 'type', + operatingSystems: [], + parameterSettings: { + propA: {}, + propB: { setting: true }, + }, + }; + + const cs = ChangeSet.destroy({ propA: 'val', propB: true }, settings as any); + expect(cs.parameterChanges.length).to.eq(1); + expect(cs.parameterChanges[0].name).to.eq('propA'); + expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.REMOVE); + expect(cs.operation).to.eq(ResourceOperation.DESTROY); + }) + + it('excludes multiple setting parameters from destroy change set', () => { + const settings = { + id: 'type', + operatingSystems: [], + parameterSettings: { + skipAlreadyInstalledCasks: { type: 'boolean', default: true, setting: true }, + onlyPlanUserInstalled: { type: 'boolean', default: true, setting: true }, + directory: { type: 'directory' }, + }, + }; + + const cs = ChangeSet.destroy( + { skipAlreadyInstalledCasks: true, onlyPlanUserInstalled: true, directory: '/opt/homebrew' }, + settings as any + ); + expect(cs.parameterChanges.length).to.eq(1); + expect(cs.parameterChanges[0].name).to.eq('directory'); + expect(cs.operation).to.eq(ResourceOperation.DESTROY); + }) + it('correctly determines array equality 5', () => { const arrA = [{ key1: 'b' }, { key1: 'a' }, { key1: 'a' }]; const arrB = [{ key1: 'a' }, { key1: 'a' }, { key1: 'b' }]; const parameterSettings = new ParsedResourceSettings({ id: 'type', + operatingSystems: [], parameterSettings: { propA: { type: 'array', diff --git a/src/plan/change-set.ts b/src/plan/change-set.ts index 2f90298..aa7b979 100644 --- a/src/plan/change-set.ts +++ b/src/plan/change-set.ts @@ -94,6 +94,7 @@ export class ChangeSet { static destroy(current: Partial, settings?: ResourceSettings): ChangeSet { const parameterChanges = Object.entries(current) + .filter(([k]) => !settings?.parameterSettings?.[k]?.setting) .map(([k, v]) => ({ name: k, operation: ParameterOperation.REMOVE, diff --git a/src/pty/index.ts b/src/pty/index.ts index a23a78b..7c78403 100644 --- a/src/pty/index.ts +++ b/src/pty/index.ts @@ -34,6 +34,7 @@ export interface SpawnOptions { env?: Record; interactive?: boolean; requiresRoot?: boolean; + requiresSudoAskpass?: boolean; stdin?: boolean; disableWrapping?: boolean; } diff --git a/src/pty/seqeuntial-pty.ts b/src/pty/seqeuntial-pty.ts index c704b88..ea6911e 100644 --- a/src/pty/seqeuntial-pty.ts +++ b/src/pty/seqeuntial-pty.ts @@ -46,7 +46,7 @@ export class SequentialPty implements IPty { } // If sudo is required, we must delegate to the main codify process. - if (options?.stdin || options?.requiresRoot) { + if (options?.stdin || options?.requiresRoot || options?.requiresSudoAskpass) { return this.externalSpawn(cmd, options); } diff --git a/src/resource/parsed-resource-settings.ts b/src/resource/parsed-resource-settings.ts index 1afd04f..b82e590 100644 --- a/src/resource/parsed-resource-settings.ts +++ b/src/resource/parsed-resource-settings.ts @@ -76,7 +76,7 @@ export class ParsedResourceSettings implements Re if (ctx.path.length === 0) { ctx.jsonSchema.title = settings.id; ctx.jsonSchema.description = schema.description ?? settings.description ?? `${settings.id} resource. Can be used to manage ${settings.id}`; - ctx.jsonSchema.$comment = (schema.meta() as Record).$comment; + ctx.jsonSchema.$comment = (schema.meta() as Record | undefined)?.$comment; } } }) as JSONSchemaType From 2c257f29647ee4cee11a75db3ea9473a9f7e49c7 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sat, 25 Apr 2026 21:23:21 -0400 Subject: [PATCH 08/18] feat: Add minSupportedCliVersion --- package-lock.json | 22 +++++++++++----------- package.json | 6 +++--- src/plugin/plugin.ts | 14 +++++++++++--- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b14718..db3fc20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta13", + "version": "1.1.0-beta16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta13", + "version": "1.1.0-beta16", "license": "ISC", "dependencies": { - "@codifycli/schemas": "1.1.0-beta5", + "@codifycli/schemas": "1.1.0-beta6", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", @@ -17,7 +17,7 @@ "lodash.isequal": "^4.5.0", "nanoid": "^5.0.9", "strip-ansi": "^7.1.0", - "uuid": "^10.0.0", + "uuid": "^14.0.0", "zod": "4.1.13" }, "bin": { @@ -94,9 +94,9 @@ } }, "node_modules/@codifycli/schemas": { - "version": "1.1.0-beta5", - "resolved": "https://registry.npmjs.org/@codifycli/schemas/-/schemas-1.1.0-beta5.tgz", - "integrity": "sha512-xxfh6b48KW7Kav2uyrJb5E3dtuBDg4EUIaVKPJvJnocaaYFKGPwEUWvYwiNxqWDNBhrsbsGmNRatHGwXfhTT2A==", + "version": "1.1.0-beta6", + "resolved": "https://registry.npmjs.org/@codifycli/schemas/-/schemas-1.1.0-beta6.tgz", + "integrity": "sha512-OqyLSzvREQNswAbQp2wqvNb3l/bZsJqMahfghoOfx/otVZbnTmUyM0pFzD0J4bXqm3lRcjaUOBhrUUIw/sSaFw==", "license": "ISC", "dependencies": { "ajv": "^8.18.0" @@ -8216,16 +8216,16 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist-node/bin/uuid" } }, "node_modules/v8-compile-cache-lib": { diff --git a/package.json b/package.json index 8f431b5..59427a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta15", + "version": "1.1.0-beta16", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -35,7 +35,7 @@ }, "license": "ISC", "dependencies": { - "@codifycli/schemas": "1.1.0-beta5", + "@codifycli/schemas": "1.1.0-beta6", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", @@ -43,7 +43,7 @@ "lodash.isequal": "^4.5.0", "nanoid": "^5.0.9", "strip-ansi": "^7.1.0", - "uuid": "^10.0.0", + "uuid": "^14.0.0", "zod": "4.1.13" }, "devDependencies": { diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts index 6d217db..3dce753 100644 --- a/src/plugin/plugin.ts +++ b/src/plugin/plugin.ts @@ -31,15 +31,22 @@ import { VerbosityLevel } from '../utils/verbosity-level.js'; export class Plugin { planStorage: Map>; planPty = new BackgroundPty(); + minSupportedCliVersion: string | undefined; constructor( public name: string, - public resourceControllers: Map> + public resourceControllers: Map>, + options?: { minSupportedCliVersion?: string } ) { this.planStorage = new Map(); + this.minSupportedCliVersion = options?.minSupportedCliVersion; } - static create(name: string, resources: Resource[]) { + static create( + name: string, + resources: Resource[], + options?: { minSupportedCliVersion?: string } + ) { const controllers = resources .map((resource) => new ResourceController(resource)) @@ -47,7 +54,7 @@ export class Plugin { controllers.map((r) => [r.typeId, r] as const) ); - return new Plugin(name, controllersMap); + return new Plugin(name, controllersMap, options); } async initialize(data: InitializeRequestData): Promise { @@ -60,6 +67,7 @@ export class Plugin { } return { + minSupportedCliVersion: this.minSupportedCliVersion, resourceDefinitions: [...this.resourceControllers.values()] .map((r) => { const sensitiveParameters = Object.entries(r.settings.parameterSettings ?? {}) From b1a778ce56add27cbfd790087a0336b35b560887 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sun, 26 Apr 2026 21:57:49 -0400 Subject: [PATCH 09/18] feat: Initial error imrprovement change. Add support for Apply Validation Error. --- package-lock.json | 32 ++++++++++++++++++++++---------- package.json | 4 ++-- src/messages/handlers.ts | 20 +++++++++++++++++++- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index db3fc20..0e25f0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta16", + "version": "1.1.0-beta18", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta16", + "version": "1.1.0-beta18", "license": "ISC", "dependencies": { - "@codifycli/schemas": "1.1.0-beta6", + "@codifycli/schemas": "^1.1.0-beta7", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", @@ -50,6 +50,23 @@ "node": ">=22.0.0" } }, + "../codify-schemas": { + "name": "@codifycli/schemas", + "version": "1.1.0-beta7", + "license": "ISC", + "dependencies": { + "ajv": "^8.18.0" + }, + "devDependencies": { + "@supabase/supabase-js": "^2.50.0", + "@types/node": "20.17.14", + "ajv-formats": "^3.0.1", + "dotenv": "^16.5.0", + "tsx": "^4.20.3", + "typescript": "^5.3.3", + "vitest": "^4.0.18" + } + }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "11.9.3", "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", @@ -94,13 +111,8 @@ } }, "node_modules/@codifycli/schemas": { - "version": "1.1.0-beta6", - "resolved": "https://registry.npmjs.org/@codifycli/schemas/-/schemas-1.1.0-beta6.tgz", - "integrity": "sha512-OqyLSzvREQNswAbQp2wqvNb3l/bZsJqMahfghoOfx/otVZbnTmUyM0pFzD0J4bXqm3lRcjaUOBhrUUIw/sSaFw==", - "license": "ISC", - "dependencies": { - "ajv": "^8.18.0" - } + "resolved": "../codify-schemas", + "link": true }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", diff --git a/package.json b/package.json index 59427a6..6973f68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta16", + "version": "1.1.0-beta18", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -35,7 +35,7 @@ }, "license": "ISC", "dependencies": { - "@codifycli/schemas": "1.1.0-beta6", + "@codifycli/schemas": "^1.1.0-beta7", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", diff --git a/src/messages/handlers.ts b/src/messages/handlers.ts index 53b591a..99c3be2 100644 --- a/src/messages/handlers.ts +++ b/src/messages/handlers.ts @@ -1,6 +1,7 @@ import { ApplyRequestDataSchema, EmptyResponseDataSchema, + ErrorCode, GetResourceInfoRequestDataSchema, GetResourceInfoResponseDataSchema, ImportRequestDataSchema, @@ -24,6 +25,7 @@ import { import { Ajv, SchemaObject, ValidateFunction } from 'ajv'; import addFormats from 'ajv-formats'; +import { ApplyValidationError } from '../common/errors.js'; import { SudoError } from '../errors.js'; import { Plugin } from '../plugin/plugin.js'; @@ -168,13 +170,29 @@ export class MessageHandler { }) } + if (e instanceof ApplyValidationError) { + return process.send?.({ + cmd, + // @ts-expect-error TS2239 + requestId: message.requestId || undefined, + data: { + errorCode: ErrorCode.APPLY_VALIDATION, + plan: e.plan.toResponse(), + }, + status: MessageStatus.ERROR, + }); + } + const isDebug = process.env.DEBUG?.includes('*') ?? false; process.send?.({ cmd, // @ts-expect-error TS2239 requestId: message.requestId || undefined, - data: isDebug ? e.stack : e.message, + data: { + errorCode: ErrorCode.UNKNOWN, + message: isDebug ? (e.stack ?? e.message) : e.message, + }, status: MessageStatus.ERROR, }) } From cdb518ece8e7b7f61185c1240412f5e7f7399c6c Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 30 Apr 2026 15:10:00 -0400 Subject: [PATCH 10/18] feat: Improved error payload --- package-lock.json | 4 +-- package.json | 4 +-- src/messages/handlers.ts | 56 +++++++++++++++++----------------------- src/plugin/plugin.ts | 2 +- 4 files changed, 28 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e25f0d..6c735e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.1.0-beta18", "license": "ISC", "dependencies": { - "@codifycli/schemas": "^1.1.0-beta7", + "@codifycli/schemas": "^1.1.0-beta8", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", @@ -52,7 +52,7 @@ }, "../codify-schemas": { "name": "@codifycli/schemas", - "version": "1.1.0-beta7", + "version": "1.1.0-beta8", "license": "ISC", "dependencies": { "ajv": "^8.18.0" diff --git a/package.json b/package.json index 6973f68..990fedf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta18", + "version": "1.1.0-beta20", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -35,7 +35,7 @@ }, "license": "ISC", "dependencies": { - "@codifycli/schemas": "^1.1.0-beta7", + "@codifycli/schemas": "^1.1.0-beta8", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", diff --git a/src/messages/handlers.ts b/src/messages/handlers.ts index 99c3be2..c3a5782 100644 --- a/src/messages/handlers.ts +++ b/src/messages/handlers.ts @@ -1,7 +1,6 @@ import { ApplyRequestDataSchema, EmptyResponseDataSchema, - ErrorCode, GetResourceInfoRequestDataSchema, GetResourceInfoResponseDataSchema, ImportRequestDataSchema, @@ -17,6 +16,7 @@ import { MessageStatus, PlanRequestDataSchema, PlanResponseDataSchema, + PluginErrorData, ResourceSchema, SetVerbosityRequestDataSchema, ValidateRequestDataSchema, @@ -159,41 +159,31 @@ export class MessageHandler { // @ts-expect-error TS2239 const cmd = message.cmd + '_Response'; + // @ts-expect-error TS2239 + const requestId = message.requestId || undefined; - if (e instanceof SudoError) { - return process.send?.({ - cmd, - // @ts-expect-error TS2239 - requestId: message.requestId || undefined, - data: `Plugin: '${this.plugin.name}'. Forbidden usage of sudo for command '${e.command}'. Please contact the plugin developer to fix this.`, - status: MessageStatus.ERROR, - }) - } + let errorPayload: PluginErrorData; - if (e instanceof ApplyValidationError) { - return process.send?.({ - cmd, - // @ts-expect-error TS2239 - requestId: message.requestId || undefined, - data: { - errorCode: ErrorCode.APPLY_VALIDATION, - plan: e.plan.toResponse(), - }, - status: MessageStatus.ERROR, - }); + if (e instanceof SudoError) { + errorPayload = { + errorType: 'sudo_error', + message: `Plugin: '${this.plugin.name}'. Forbidden usage of sudo for command '${e.command}'. Please contact the plugin developer to fix this.`, + data: { command: e.command, pluginName: this.plugin.name }, + }; + } else if (e instanceof ApplyValidationError) { + errorPayload = { + errorType: 'apply_validation', + message: e.message, + data: { plan: e.plan.toResponse() }, + }; + } else { + const isDebug = process.env.DEBUG?.includes('*') ?? false; + errorPayload = { + errorType: 'unknown', + message: isDebug ? (e.stack ?? e.message) : e.message, + }; } - const isDebug = process.env.DEBUG?.includes('*') ?? false; - - process.send?.({ - cmd, - // @ts-expect-error TS2239 - requestId: message.requestId || undefined, - data: { - errorCode: ErrorCode.UNKNOWN, - message: isDebug ? (e.stack ?? e.message) : e.message, - }, - status: MessageStatus.ERROR, - }) + process.send?.({ cmd, requestId, data: errorPayload, status: MessageStatus.ERROR }); } } diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts index 3dce753..b7c115c 100644 --- a/src/plugin/plugin.ts +++ b/src/plugin/plugin.ts @@ -268,7 +268,7 @@ export class Plugin { }) if (validationPlan.requiresChanges()) { - throw new ApplyValidationError(plan); + throw new ApplyValidationError(validationPlan); } } From 33c47aed54683dc1301de2a6ff25b0fb8defb130 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sat, 23 May 2026 13:42:04 -0400 Subject: [PATCH 11/18] feat: Added log buffer and get logs --- package.json | 2 +- src/common/errors.ts | 4 +++- src/messages/handlers.ts | 2 +- src/plugin/plugin.ts | 8 ++++++-- src/pty/background-pty.ts | 4 ++++ src/pty/index.ts | 9 +++++++-- src/pty/seqeuntial-pty.ts | 22 ++++++++++++++++++++-- 7 files changed, 42 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 990fedf..e4438e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta20", + "version": "1.1.0-beta23", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/common/errors.ts b/src/common/errors.ts index eefe577..57b4ba0 100644 --- a/src/common/errors.ts +++ b/src/common/errors.ts @@ -4,13 +4,15 @@ export class ApplyValidationError extends Error { resourceType: string; resourceName?: string; plan: Plan; + logs: string[]; - constructor(plan: Plan) { + constructor(plan: Plan, logs: string[] = []) { super(`Failed to apply changes to resource: "${plan.resourceId}". Additional changes are needed to complete apply.\nChanges remaining:\n${ApplyValidationError.prettyPrintPlan(plan)}`); this.resourceType = plan.coreParameters.type; this.resourceName = plan.coreParameters.name; this.plan = plan; + this.logs = logs; } private static prettyPrintPlan(plan: Plan): string { diff --git a/src/messages/handlers.ts b/src/messages/handlers.ts index c3a5782..1e93ee1 100644 --- a/src/messages/handlers.ts +++ b/src/messages/handlers.ts @@ -174,7 +174,7 @@ export class MessageHandler { errorPayload = { errorType: 'apply_validation', message: e.message, - data: { plan: e.plan.toResponse() }, + data: { plan: e.plan.toResponse(), logs: e.logs }, }; } else { const isDebug = process.env.DEBUG?.includes('*') ?? false; diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts index b7c115c..6b55def 100644 --- a/src/plugin/plugin.ts +++ b/src/plugin/plugin.ts @@ -250,7 +250,11 @@ export class Plugin { throw new Error('Malformed plan with resource that cannot be found'); } - await ptyLocalStorage.run(new SequentialPty(), async () => resource.apply(plan)) + let applyLogs: string[] = []; + await ptyLocalStorage.run(new SequentialPty(), async () => { + await resource.apply(plan); + applyLogs = getPty().getLogs(); + }); // Validate using desired/desired. If the apply was successful, no changes should be reported back. // Default back desired back to current if it is not defined (for destroys only) @@ -268,7 +272,7 @@ export class Plugin { }) if (validationPlan.requiresChanges()) { - throw new ApplyValidationError(validationPlan); + throw new ApplyValidationError(validationPlan, applyLogs); } } diff --git a/src/pty/background-pty.ts b/src/pty/background-pty.ts index ce96179..82b8917 100644 --- a/src/pty/background-pty.ts +++ b/src/pty/background-pty.ts @@ -148,6 +148,10 @@ export class BackgroundPty implements IPty { }) } + getLogs(): string[] { + return []; + } + private getDefaultShell(): string { return process.env.SHELL!; } diff --git a/src/pty/index.ts b/src/pty/index.ts index 7c78403..ea5f2be 100644 --- a/src/pty/index.ts +++ b/src/pty/index.ts @@ -44,8 +44,11 @@ export class SpawnError extends Error { cmd: string; exitCode: number; - constructor(cmd: string, exitCode: number, data: string) { - super(`Spawn Error: on command "${cmd}" with exit code: ${exitCode}\nOutput:\n${data}`); + constructor(cmd: string, exitCode: number, data: string, logs?: string[]) { + const logSection = logs?.length + ? `\nLast logs:\n${logs.join('\n')}` + : ''; + super(`Spawn Error: on command "${cmd}" with exit code: ${exitCode}\nOutput:\n${data}${logSection}`); this.data = data; this.cmd = cmd; @@ -60,6 +63,8 @@ export interface IPty { spawnSafe(cmd: string | string[], options?: SpawnOptions): Promise kill(): Promise<{ exitCode: number, signal?: number | undefined }> + + getLogs(): string[] } export function getPty(): IPty { diff --git a/src/pty/seqeuntial-pty.ts b/src/pty/seqeuntial-pty.ts index ea6911e..b12e69a 100644 --- a/src/pty/seqeuntial-pty.ts +++ b/src/pty/seqeuntial-pty.ts @@ -28,11 +28,26 @@ const validateSudoRequestResponse = ajv.compile(CommandRequestResponseDataSchema * without a tty (or even a stdin) attached so interactive commands will not work. */ export class SequentialPty implements IPty { + private logBuffer: string[] = []; + private static readonly MAX_LOG_LINES = 30; + + getLogs(): string[] { + return [...this.logBuffer]; + } + + private appendLog(data: string): void { + const lines = data.split('\n'); + this.logBuffer.push(...lines); + if (this.logBuffer.length > SequentialPty.MAX_LOG_LINES) { + this.logBuffer = this.logBuffer.slice(-SequentialPty.MAX_LOG_LINES); + } + } + async spawn(cmd: string | string[], options?: SpawnOptions): Promise { const spawnResult = await this.spawnSafe(cmd, options); if (spawnResult.status !== 'success') { - throw new SpawnError(Array.isArray(cmd) ? cmd.join('\n') : cmd, spawnResult.exitCode, spawnResult.data); + throw new SpawnError(Array.isArray(cmd) ? cmd.join('\n') : cmd, spawnResult.exitCode, spawnResult.data, this.logBuffer); } return spawnResult; @@ -50,7 +65,9 @@ export class SequentialPty implements IPty { return this.externalSpawn(cmd, options); } - console.log(`Running command: ${Array.isArray(cmd) ? cmd.join('\\\n') : cmd}` + (options?.cwd ? `(${options?.cwd})` : '')) + const cmdLine = `Running command: ${Array.isArray(cmd) ? cmd.join('\\\n') : cmd}` + (options?.cwd ? `(${options?.cwd})` : ''); + console.log(cmdLine); + this.appendLog(cmdLine); return new Promise((resolve) => { const output: string[] = []; @@ -87,6 +104,7 @@ export class SequentialPty implements IPty { } output.push(data.toString()); + this.appendLog(data.toString()); }) const resizeListener = () => { From bfc0d9d28b6b208cac28dea3c666a2015993c854 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 4 Jun 2026 19:12:06 -0400 Subject: [PATCH 12/18] feat: Added retry for broken packages --- package.json | 2 +- src/utils/index.ts | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e4438e4..06ed69a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta23", + "version": "1.1.0-beta24", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/utils/index.ts b/src/utils/index.ts index f055404..844369c 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -225,7 +225,24 @@ Brew can be installed using Codify: } if (status === SpawnStatus.ERROR) { - throw new Error(`Failed to install package ${packageName} via apt: ${data}`); + // Attempt to fix broken dependencies then retry + const fixResult = await $.spawnSafe('apt-get install -f -y -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0', { + requiresRoot: true, + env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' } + }); + + if (fixResult.status === SpawnStatus.ERROR) { + throw new Error(`Failed to install package ${packageName} via apt: ${data}`); + } + + const retryResult = await $.spawnSafe(`apt-get -y -qq install -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0 ${packageName}`, { + requiresRoot: true, + env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' } + }); + + if (retryResult.status === SpawnStatus.ERROR) { + throw new Error(`Failed to install package ${packageName} via apt after fixing dependencies: ${retryResult.data}`); + } } } From 4af990a95ec76e7fbe485e6d6fcfeac027e9af9e Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 4 Jun 2026 21:50:46 -0400 Subject: [PATCH 13/18] feat: Added apply notes support --- package-lock.json | 8 ++++---- package.json | 4 ++-- src/common/apply-notes.ts | 5 +++++ src/index.ts | 1 + src/messages/sender.ts | 18 +++++++++++++++++- 5 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 src/common/apply-notes.ts diff --git a/package-lock.json b/package-lock.json index 6c735e3..b067c99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta18", + "version": "1.1.0-beta24", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta18", + "version": "1.1.0-beta24", "license": "ISC", "dependencies": { - "@codifycli/schemas": "^1.1.0-beta8", + "@codifycli/schemas": "^1.1.0-beta.9", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", @@ -52,7 +52,7 @@ }, "../codify-schemas": { "name": "@codifycli/schemas", - "version": "1.1.0-beta8", + "version": "1.1.0-beta.9", "license": "ISC", "dependencies": { "ajv": "^8.18.0" diff --git a/package.json b/package.json index 06ed69a..2b962f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta24", + "version": "1.1.0-beta.25", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -35,7 +35,7 @@ }, "license": "ISC", "dependencies": { - "@codifycli/schemas": "^1.1.0-beta8", + "@codifycli/schemas": "^1.1.0-beta.9", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", diff --git a/src/common/apply-notes.ts b/src/common/apply-notes.ts new file mode 100644 index 0000000..db90aa0 --- /dev/null +++ b/src/common/apply-notes.ts @@ -0,0 +1,5 @@ +export const ApplyNotes = { + RESTART_REQUIRED: 'A system restart is required for changes to take effect.', + NEW_SHELL_REQUIRED: 'Open a new terminal session for the changes to be reflected.', + SOURCE_SHELL_RC: 'Source your shell rc file (e.g. ~/.zshrc) for the changes to be reflected.', +} as const; diff --git a/src/index.ts b/src/index.ts index 1584cb9..b4155ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { MessageHandler } from './messages/handlers.js'; import { Plugin } from './plugin/plugin.js'; +export * from './common/apply-notes.js' export * from './errors.js' export * from './messages/sender.js' export * from './plan/change-set.js' diff --git a/src/messages/sender.ts b/src/messages/sender.ts index 8e4d288..bc3863f 100644 --- a/src/messages/sender.ts +++ b/src/messages/sender.ts @@ -1,4 +1,10 @@ -import { IpcMessageV2, IpcMessageV2Schema, MessageCmd, PressKeyToContinueRequestData } from '@codifycli/schemas'; +import { + ApplyNoteRequestData, + IpcMessageV2, + IpcMessageV2Schema, + MessageCmd, + PressKeyToContinueRequestData +} from '@codifycli/schemas'; import { Ajv } from 'ajv'; import { nanoid } from 'nanoid'; @@ -21,6 +27,16 @@ class CodifyCliSenderImpl { }) } + async sendApplyNote(message: string, resourceType: string): Promise { + await this.sendAndWaitForResponse({ + cmd: MessageCmd.APPLY_NOTE_REQUEST, + data: { + message, + resourceType, + } + }); + } + async getCodifyCliCredentials(): Promise { const data = await this.sendAndWaitForResponse({ cmd: MessageCmd.CODIFY_CREDENTIALS_REQUEST, From 7036e705b2eb795b104daf64850e4aca1a07f1c2 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 4 Jun 2026 22:28:08 -0400 Subject: [PATCH 14/18] feat: Made the final apply message dynamic for apply notes. This way only resources that update the shell rc files will display the message --- package-lock.json | 8 ++++---- package.json | 4 ++-- src/common/apply-notes.ts | 9 ++++++++- src/messages/sender.ts | 8 ++++++-- src/utils/file-utils.ts | 9 +++++++-- 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index b067c99..dd1a5ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta24", + "version": "1.1.0-beta.26", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta24", + "version": "1.1.0-beta.26", "license": "ISC", "dependencies": { - "@codifycli/schemas": "^1.1.0-beta.9", + "@codifycli/schemas": "^1.1.0-beta.10", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", @@ -52,7 +52,7 @@ }, "../codify-schemas": { "name": "@codifycli/schemas", - "version": "1.1.0-beta.9", + "version": "1.1.0-beta.10", "license": "ISC", "dependencies": { "ajv": "^8.18.0" diff --git a/package.json b/package.json index 2b962f5..12f043c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta.25", + "version": "1.1.0-beta.26", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -35,7 +35,7 @@ }, "license": "ISC", "dependencies": { - "@codifycli/schemas": "^1.1.0-beta.9", + "@codifycli/schemas": "^1.1.0-beta.10", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", diff --git a/src/common/apply-notes.ts b/src/common/apply-notes.ts index db90aa0..6a98531 100644 --- a/src/common/apply-notes.ts +++ b/src/common/apply-notes.ts @@ -1,5 +1,12 @@ +import path from 'node:path'; + +import { Utils } from '../utils/index.js'; + export const ApplyNotes = { RESTART_REQUIRED: 'A system restart is required for changes to take effect.', NEW_SHELL_REQUIRED: 'Open a new terminal session for the changes to be reflected.', - SOURCE_SHELL_RC: 'Source your shell rc file (e.g. ~/.zshrc) for the changes to be reflected.', + sourceShellRc(): string { + const rc = path.basename(Utils.getPrimaryShellRc()); + return `Source '~/${rc}' for the changes to be reflected.`; + }, } as const; diff --git a/src/messages/sender.ts b/src/messages/sender.ts index bc3863f..0a8ab03 100644 --- a/src/messages/sender.ts +++ b/src/messages/sender.ts @@ -27,12 +27,16 @@ class CodifyCliSenderImpl { }) } - async sendApplyNote(message: string, resourceType: string): Promise { + async sendApplyNote(message: string, resourceType?: string): Promise { + if (!process.send || !process.connected) { + return; + } + await this.sendAndWaitForResponse({ cmd: MessageCmd.APPLY_NOTE_REQUEST, data: { message, - resourceType, + ...(resourceType && { resourceType }), } }); } diff --git a/src/utils/file-utils.ts b/src/utils/file-utils.ts index c5e5c97..dda9e2f 100644 --- a/src/utils/file-utils.ts +++ b/src/utils/file-utils.ts @@ -4,6 +4,8 @@ import path from 'node:path'; import { Readable } from 'node:stream'; import { finished } from 'node:stream/promises'; +import { ApplyNotes } from '../common/apply-notes.js'; +import { CodifyCliSender } from '../messages/sender.js'; import { Utils } from './index.js'; const SPACE_REGEX = /^\s*$/ @@ -33,6 +35,7 @@ export class FileUtils { ); await fs.appendFile(Utils.getPrimaryShellRc(), lineToInsert) + await CodifyCliSender.sendApplyNote(ApplyNotes.sourceShellRc()); function addLeadingSpacer(line: string): string { return line.startsWith('\n') @@ -57,6 +60,7 @@ export class FileUtils { ${lines.join('\n')}`) await fs.appendFile(shellRc, formattedLines) + await CodifyCliSender.sendApplyNote(ApplyNotes.sourceShellRc()); } /** @@ -77,10 +81,11 @@ ${lines.join('\n')}`) if (prepend) { await fs.appendFile(shellRc, `\nexport PATH=$PATH:${value};`, { encoding: 'utf8' }); - return; + } else { + await fs.appendFile(shellRc, `\nexport PATH=${value}:$PATH;`, { encoding: 'utf8' }); } - await fs.appendFile(shellRc, `\nexport PATH=${value}:$PATH;`, { encoding: 'utf8' }); + await CodifyCliSender.sendApplyNote(ApplyNotes.sourceShellRc()); } static async removeFromFile(filePath: string, search: string): Promise { From 9fb046149d83b4a3da6cf39a6fe4051add3c57a2 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 4 Jun 2026 22:33:45 -0400 Subject: [PATCH 15/18] chore: bump package version --- package-lock.json | 8 ++++---- package.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd1a5ce..e43e66d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta.26", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta.26", + "version": "1.2.0", "license": "ISC", "dependencies": { - "@codifycli/schemas": "^1.1.0-beta.10", + "@codifycli/schemas": "^1.2.0", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", @@ -52,7 +52,7 @@ }, "../codify-schemas": { "name": "@codifycli/schemas", - "version": "1.1.0-beta.10", + "version": "1.2.0", "license": "ISC", "dependencies": { "ajv": "^8.18.0" diff --git a/package.json b/package.json index 12f043c..d7a6477 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codifycli/plugin-core", - "version": "1.1.0-beta.26", + "version": "1.2.0", "description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -35,7 +35,7 @@ }, "license": "ISC", "dependencies": { - "@codifycli/schemas": "^1.1.0-beta.10", + "@codifycli/schemas": "^1.2.0", "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", From e9393297bedd0dc298eb4e139df261a9f78ff31d Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 4 Jun 2026 22:39:16 -0400 Subject: [PATCH 16/18] fix: @codifycli/schemas issue --- package-lock.json | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index e43e66d..3524d1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,23 +50,6 @@ "node": ">=22.0.0" } }, - "../codify-schemas": { - "name": "@codifycli/schemas", - "version": "1.2.0", - "license": "ISC", - "dependencies": { - "ajv": "^8.18.0" - }, - "devDependencies": { - "@supabase/supabase-js": "^2.50.0", - "@types/node": "20.17.14", - "ajv-formats": "^3.0.1", - "dotenv": "^16.5.0", - "tsx": "^4.20.3", - "typescript": "^5.3.3", - "vitest": "^4.0.18" - } - }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "11.9.3", "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", @@ -111,8 +94,13 @@ } }, "node_modules/@codifycli/schemas": { - "resolved": "../codify-schemas", - "link": true + "version": "1.2.0", + "resolved": "https://npm.pkg.github.com/download/@codifycli/schemas/1.2.0/c6a4549051bfa081d49525cc042e8b3f1bd22e43", + "integrity": "sha512-ZUx8+IsW8ZvBWu+ilbUsK+vE1oMaiDvsDlgQoe92scRZUh1pFMPw6303N1T9BTep+sooRE5X4Y9IRloPQOjRjQ==", + "license": "ISC", + "dependencies": { + "ajv": "^8.18.0" + } }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", From 13408b5df4afe82ae268569237c6660d7eaff947 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 4 Jun 2026 22:44:56 -0400 Subject: [PATCH 17/18] fix: force public registry --- .npmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..ed477ce --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@codifycli:registry=https://registry.npmjs.org/ \ No newline at end of file From 128f3e737f394fc5ef505dee5c4f164c871c6a55 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 4 Jun 2026 22:53:14 -0400 Subject: [PATCH 18/18] fix: update package-lock --- .npmrc | 1 - package-lock.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 .npmrc diff --git a/.npmrc b/.npmrc deleted file mode 100644 index ed477ce..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -@codifycli:registry=https://registry.npmjs.org/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3524d1c..29dfcf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -95,7 +95,7 @@ }, "node_modules/@codifycli/schemas": { "version": "1.2.0", - "resolved": "https://npm.pkg.github.com/download/@codifycli/schemas/1.2.0/c6a4549051bfa081d49525cc042e8b3f1bd22e43", + "resolved": "https://registry.npmjs.org/@codifycli/schemas/-/schemas-1.2.0.tgz", "integrity": "sha512-ZUx8+IsW8ZvBWu+ilbUsK+vE1oMaiDvsDlgQoe92scRZUh1pFMPw6303N1T9BTep+sooRE5X4Y9IRloPQOjRjQ==", "license": "ISC", "dependencies": {