diff --git a/esbuild.ts b/esbuild.ts new file mode 100644 index 0000000..1584851 --- /dev/null +++ b/esbuild.ts @@ -0,0 +1,20 @@ +import { build } from 'esbuild'; + +const result = await build({ + entryPoints: ['src/index.ts'], + bundle: true, + minify: false, + splitting: true, + platform: 'node', + outdir: 'dist', + format: 'esm', + loader: { + '.node': 'file', + '.cc': 'file', + }, + // external: ['node-pty'], + // packages: 'external', + logLevel: 'debug', +}); + +console.log(result); diff --git a/package.json b/package.json index 527c810..cddca04 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "homebrew-plugin", - "version": "1.0.0", + "name": "default", + "version": "0.11.0", "description": "", "main": "dist/index.js", "scripts": { @@ -12,7 +12,8 @@ "test:integration": "vitest test/**/*.test.ts", "test": "vitest", "rollup": "rollup -c", - "build": "tsx ./scripts/build.ts" + "build": "tsx ./scripts/build.ts", + "deploy": "tsx ./scripts/deploy.ts" }, "keywords": [], "author": "", @@ -22,13 +23,14 @@ "ajv": "^8.12.0", "ajv-formats": "^2.1.1", "semver": "^7.6.0", - "codify-plugin-lib": "1.0.107", - "codify-schemas": "1.0.52", + "codify-plugin-lib": "1.0.132", + "codify-schemas": "1.0.63", "chalk": "^5.3.0", "debug": "^4.3.4", "plist": "^3.1.0", "lodash.isequal": "^4.5.0", - "strip-ansi": "^7.1.0" + "strip-ansi": "^7.1.0", + "nanoid": "^5.0.9" }, "devDependencies": { "rollup": "^4.12.0", @@ -36,6 +38,7 @@ "@rollup/plugin-typescript": "^11.1.6", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-terser": "^0.4.4", "@oclif/prettier-config": "^0.2.1", "@oclif/test": "^3", @@ -47,7 +50,7 @@ "@types/debug": "4.1.12", "@types/plist": "^3.0.5", "@types/lodash.isequal": "^4.5.8", - "codify-plugin-test": "0.0.26", + "codify-plugin-test": "0.0.47", "commander": "^12.1.0", "eslint": "^8.51.0", "eslint-config-oclif": "^5", diff --git a/rollup.config.js b/rollup.config.js index 4875f58..4eb944e 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,8 +1,8 @@ -import typescript from '@rollup/plugin-typescript'; -import json from '@rollup/plugin-json'; import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; import nodeResolve from '@rollup/plugin-node-resolve'; import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; export default { input: 'src/index.ts', @@ -10,9 +10,10 @@ export default { dir:'dist', format: 'cjs' }, + external: ['@homebridge/node-pty-prebuilt-multiarch'], plugins: [ json(), - nodeResolve({ exportConditions: ['node']}), + nodeResolve({ exportConditions: ['node'] }), typescript(), commonjs(), terser() diff --git a/scripts/build.ts b/scripts/build.ts index 99ab11d..cd6ad70 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -3,6 +3,9 @@ import { Ajv } from 'ajv'; import { IpcMessage, IpcMessageSchema, MessageStatus, ResourceSchema } from 'codify-schemas'; import mergeJsonSchemas from 'merge-json-schemas'; import { ChildProcess, fork } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import * as url from 'node:url'; import { codifySpawn } from '../src/utils/codify-spawn.js'; @@ -88,8 +91,13 @@ const mergedSchemas = [...schemasMap.entries()].map(([type, schema]) => { await codifySpawn('rm -rf ./dist') await codifySpawn('npm run rollup'); // re-run rollup without building for es -console.log('JSON Schemas for all resources') -console.log(JSON.stringify(mergedSchemas, null, 2)); +console.log('Generated JSON Schemas for all resources') + +const distFolder = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '..', 'dist'); +const schemaOutputPath = path.resolve(distFolder, 'schemas.json'); +fs.writeFileSync(schemaOutputPath, JSON.stringify(mergedSchemas, null, 2)); + +console.log('Successfully wrote schema to ./dist/schemas.json') // eslint-disable-next-line n/no-process-exit,unicorn/no-process-exit process.exit(0) diff --git a/scripts/deploy.ts b/scripts/deploy.ts new file mode 100644 index 0000000..29e7a36 --- /dev/null +++ b/scripts/deploy.ts @@ -0,0 +1,21 @@ +import * as cp from 'node:child_process'; +import path from 'node:path'; +import * as url from 'node:url'; + +// This should run the build +cp.spawnSync('source ~/.zshrc; npm run build', { shell: 'zsh', stdio: 'inherit' }); + +const version = process.env.npm_package_version; +if (!version) { + throw new Error('Unable to find version'); +} + +const name = process.env.npm_package_name; +if (!name) { + throw new Error('Unable to find package name'); +} + +console.log(`Uploading plugin ${name}, version ${version} to cloudflare!`) + +const outputFilePath = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '..', 'dist', 'index.js') +cp.spawnSync(`source ~/.zshrc; npx wrangler r2 object put plugins/${name}/${version}/index.js --file=${outputFilePath}`, { shell: 'zsh', stdio: 'inherit' }); diff --git a/scripts/run-tests.ts b/scripts/run-tests.ts index c004e18..9b734ff 100644 --- a/scripts/run-tests.ts +++ b/scripts/run-tests.ts @@ -67,26 +67,30 @@ async function run(cmd: string, debug: boolean, simple = true) { const [_, ip] = data.toString().match(IP_REGEX); console.log(`Copying ssh keys to ${ip}`) - spawnSync(`source $HOME/.zshrc; sshpass -p admin ssh-copy-id -o "StrictHostKeyChecking=no" admin@${ip}`, { stdio: 'inherit', shell: 'zsh' }); + // spawnSync(`source $HOME/.zshrc; sshpass -p admin ssh-copy-id -o "StrictHostKeyChecking=no" admin@${ip}`, { stdio: 'inherit', shell: 'zsh' }); console.log('Enabling port forwarding') - const portForward1 = spawn(`ssh -L 9229:localhost:9229 -Nf admin@${ip}`, { stdio: 'pipe', shell: 'zsh' }); + const sshStatement1 = `ssh${ Array.from({ length: 20 }, (i: number) => i + 9000).map((i) => ` -L ${i}:localhost:${i}`)} admin@${ip}`; + const sshStatement2 = `source $HOME/.zshrc; sshpass -p admin ssh -L 9221:localhost:9221 -L 9229:localhost:9229 -N -o "StrictHostKeyChecking=no" admin@${ip}` + + const portForward1 = spawn(sshStatement2, { stdio: 'pipe', shell: 'zsh' }); + portForward1.stderr.pipe(process.stdout) portForward1.stdout.on('data', data => { console.log(data.toString()); if (data.toString().includes('Address already in use')) { throw new Error('Port 9229 already in use!') } }) - console.log('Enabled on port 9229') - - const portForward2 = spawn(`ssh -L 9221:localhost:9221 -Nf admin@${ip}`, { stdio: 'pipe', shell: 'zsh' }); - portForward2.stdout.on('data', data => { - console.log(data.toString()); - if (data.toString().includes('Address already in use')) { - throw new Error('Port 9221 already in use!') - } - }); - console.log('Enabled on port 9221') + // console.log('Enabled on port 9229') + + // const portForward2 = spawn(`ssh -L 9221:localhost:9221 -Nf admin@${ip}`, { stdio: 'pipe', shell: 'zsh' }); + // portForward2.stdout.on('data', data => { + // console.log(data.toString()); + // if (data.toString().includes('Address already in use')) { + // throw new Error('Port 9221 already in use!') + // } + // }); + // console.log('Enabled on port 9221') } }) } diff --git a/src/resources/asdf/asdf-global.ts b/src/resources/asdf/asdf-global.ts index 3f8ed97..5263d4b 100644 --- a/src/resources/asdf/asdf-global.ts +++ b/src/resources/asdf/asdf-global.ts @@ -1,4 +1,4 @@ -import { CreatePlan, DestroyPlan, Resource, ResourceSettings, } from 'codify-plugin-lib'; +import { CreatePlan, DestroyPlan, Resource, ResourceSettings, getPty, } from 'codify-plugin-lib'; import { ResourceConfig } from 'codify-schemas'; import os from 'node:os'; import path from 'node:path'; @@ -29,16 +29,15 @@ export class AsdfGlobalResource extends Resource { } async refresh(parameters: Partial): Promise | Partial[] | null> { - if ((await codifySpawn('which asdf', { throws: false })).status === SpawnStatus.ERROR) { - return null; - } + const $ = getPty(); - if ((await codifySpawn(`asdf list ${parameters.plugin}`, { throws: false })).status === SpawnStatus.ERROR) { + const plugins = await $.spawnSafe(`asdf list ${parameters.plugin}`); + if (plugins.status === SpawnStatus.ERROR) { return null; } // Only check for the installed version matches if it's not latest. The latest version could be out of date. - const installedVersions = new Set((await codifySpawn(`asdf list ${parameters.plugin}`)) + const installedVersions = new Set(plugins .data .split(/\n/) .filter(Boolean) @@ -53,7 +52,7 @@ export class AsdfGlobalResource extends Resource { return null; } - const { status } = await codifySpawn(`asdf current ${parameters.plugin}`, { throws: false, cwd: os.homedir() }); + const { status } = await $.spawnSafe(`asdf current ${parameters.plugin}`, { cwd: os.homedir() }); return status === SpawnStatus.ERROR ? null : parameters; diff --git a/src/resources/asdf/asdf-install.ts b/src/resources/asdf/asdf-install.ts index 71c9dc1..95cfc30 100644 --- a/src/resources/asdf/asdf-install.ts +++ b/src/resources/asdf/asdf-install.ts @@ -1,4 +1,4 @@ -import { CreatePlan, DestroyPlan, Resource, ResourceSettings, untildify, } from 'codify-plugin-lib'; +import { CreatePlan, DestroyPlan, getPty, Resource, ResourceSettings, untildify, } from 'codify-plugin-lib'; import { ResourceConfig } from 'codify-schemas'; import * as fs from 'node:fs/promises'; import path from 'node:path'; @@ -26,12 +26,12 @@ export class AsdfInstallResource extends Resource { schema: AsdfInstallSchema, parameterSettings: { directory: { type: 'directory', inputTransformation: (input) => untildify(input) }, - versions: { type: 'stateful', definition: new AsdfPluginVersionsParameter() } + versions: { type: 'array' } }, import: { requiredParameters: ['directory'], refreshKeys: ['directory'] - } + }, } } @@ -50,7 +50,9 @@ export class AsdfInstallResource extends Resource { } async refresh(parameters: Partial): Promise | Partial[] | null> { - if ((await codifySpawn('which asdf', { throws: false })).status === SpawnStatus.ERROR) { + const $ = getPty(); + + if ((await $.spawnSafe('which asdf')).status === SpawnStatus.ERROR) { return null; } @@ -58,7 +60,7 @@ export class AsdfInstallResource extends Resource { const desiredTools = await this.getToolVersions(parameters.directory); for (const { plugin, version } of desiredTools) { - const { status, data } = await codifySpawn(`asdf current ${plugin}`, { throws: false, cwd: parameters.directory }); + const { status, data } = await $.spawnSafe(`asdf current ${plugin}`, { cwd: parameters.directory }); if (status === SpawnStatus.ERROR || data.trim() === '') { return null; } @@ -74,12 +76,25 @@ export class AsdfInstallResource extends Resource { }; } - if ((await codifySpawn('which asdf', { throws: false })).status === SpawnStatus.ERROR) { + // Directly check plugin version + const versionsQuery = await $.spawnSafe(`asdf list ${parameters.plugin}`); + if (versionsQuery.status === SpawnStatus.ERROR || versionsQuery.data.trim() === 'No versions installed') { return null; } + const latest = parameters.versions?.includes('latest') + ? (await codifySpawn(`asdf latest ${parameters.plugin}`)).data.trim() + : null; + + const versions = versionsQuery.data.split(/\n/) + .map((l) => l.trim()) + .map((l) => l.replaceAll('*', '')) + .map((l) => l.trim() === latest ? 'latest' : l) + .filter(Boolean); + return { plugin: parameters.plugin, + versions, } } @@ -95,7 +110,10 @@ export class AsdfInstallResource extends Resource { } await codifySpawn('asdf install', { cwd: plan.desiredConfig.directory }); + return; } + + await codifySpawn(`asdf install ${plan.desiredConfig?.plugin} ${plan.desiredConfig.versions?.join(' ')}`); } async destroy(plan: DestroyPlan): Promise { @@ -106,7 +124,12 @@ export class AsdfInstallResource extends Resource { for (const { plugin, version } of desiredTools) { await codifySpawn(`asdf uninstall ${plugin} ${version}`); } + + return; } + + // Other path is uninstalled through the stateful parameter + await codifySpawn(`asdf uninstall ${plan.currentConfig?.plugin} ${plan.currentConfig.versions?.join(' ')}`); } private async getToolVersions(directory: string): Promise> { diff --git a/src/resources/asdf/asdf-local.ts b/src/resources/asdf/asdf-local.ts index 99358ce..0adc741 100644 --- a/src/resources/asdf/asdf-local.ts +++ b/src/resources/asdf/asdf-local.ts @@ -1,4 +1,12 @@ -import { CreatePlan, DestroyPlan, ModifyPlan, ParameterChange, Resource, ResourceSettings, } from 'codify-plugin-lib'; +import { + CreatePlan, + DestroyPlan, + getPty, + ModifyPlan, + ParameterChange, + Resource, + ResourceSettings, +} from 'codify-plugin-lib'; import { ResourceConfig } from 'codify-schemas'; import * as fs from 'node:fs'; import path from 'node:path'; @@ -66,16 +74,15 @@ export class AsdfLocalResource extends Resource { } async refresh(parameters: Partial): Promise | Partial[] | null> { - if ((await codifySpawn('which asdf', { throws: false })).status === SpawnStatus.ERROR) { - return null; - } + const $ = getPty(); - if ((await codifySpawn(`asdf list ${parameters.plugin}`, { throws: false })).status === SpawnStatus.ERROR) { + const plugins = await $.spawnSafe(`asdf list ${parameters.plugin}`); + if (plugins.status === SpawnStatus.ERROR) { return null; } // Only check for the installed version matches if it's not latest. The latest version could be out of date. - const installedVersions = new Set((await codifySpawn(`asdf list ${parameters.plugin}`)) + const installedVersions = new Set(plugins .data .split(/\n/) .filter(Boolean) @@ -91,7 +98,7 @@ export class AsdfLocalResource extends Resource { } if (parameters.directory) { - const { status, data } = await codifySpawn(`asdf current ${parameters.plugin}`, { throws: false, cwd: parameters.directory }); + const { status, data } = await $.spawnSafe(`asdf current ${parameters.plugin}`, { cwd: parameters.directory }); if (status === SpawnStatus.ERROR || data.trim() === '') { return null; @@ -109,7 +116,7 @@ export class AsdfLocalResource extends Resource { let versionUpToDate = true; let latestVersion = null; for (const dir of parameters.directories!) { - const { status, data } = await codifySpawn(`asdf current ${parameters.plugin}`, { throws: false, cwd: dir }); + const { status, data } = await $.spawnSafe(`asdf current ${parameters.plugin}`, { cwd: dir }); if (status === SpawnStatus.ERROR || data.trim() === '') { continue; } @@ -173,9 +180,7 @@ export class AsdfLocalResource extends Resource { } for (const dir of plan.currentConfig.directories!) { - console.log(path.join(dir, '.tool-versions')) await FileUtils.removeLineFromFile(path.join(dir, '.tool-versions'), plan.currentConfig.plugin); - console.log(fs.readFileSync(path.join(dir, '.tool-versions')).toString()); } } } diff --git a/src/resources/asdf/asdf-plugin.ts b/src/resources/asdf/asdf-plugin.ts index 1f674d1..d1ebbcb 100644 --- a/src/resources/asdf/asdf-plugin.ts +++ b/src/resources/asdf/asdf-plugin.ts @@ -1,4 +1,4 @@ -import { CreatePlan, DestroyPlan, Resource, ResourceSettings, SpawnStatus, } from 'codify-plugin-lib'; +import { CreatePlan, DestroyPlan, Resource, ResourceSettings, SpawnStatus, untildify } from 'codify-plugin-lib'; import { ResourceConfig } from 'codify-schemas'; import { codifySpawn } from '../../utils/codify-spawn.js'; @@ -37,7 +37,6 @@ export class AsdfPluginResource extends Resource { .map((l) => l.trim()) .map((l) => l.replaceAll('*', '')) .map((l) => { - console.log(l); const matches = l.match(PLUGIN_LIST_REGEX) if (!matches) { return null; diff --git a/src/resources/asdf/asdf.ts b/src/resources/asdf/asdf.ts index 2ece826..85bb190 100644 --- a/src/resources/asdf/asdf.ts +++ b/src/resources/asdf/asdf.ts @@ -1,4 +1,4 @@ -import { CreatePlan, DestroyPlan, Resource, ResourceSettings } from 'codify-plugin-lib'; +import { CreatePlan, DestroyPlan, getPty, Resource, ResourceSettings } from 'codify-plugin-lib'; import { ResourceConfig } from 'codify-schemas'; import { SpawnStatus, codifySpawn } from '../../utils/codify-spawn.js'; @@ -22,8 +22,9 @@ export class AsdfResource extends Resource { } async refresh(parameters: Partial): Promise | Partial[] | null> { - const { status } = await codifySpawn('which asdf', { throws: false }); + const $ = getPty(); + const { status } = await $.spawnSafe('which asdf'); return status === SpawnStatus.SUCCESS ? {} : null; } diff --git a/src/resources/asdf/plugins-parameter.ts b/src/resources/asdf/plugins-parameter.ts index 28558f6..ebff1c3 100644 --- a/src/resources/asdf/plugins-parameter.ts +++ b/src/resources/asdf/plugins-parameter.ts @@ -1,13 +1,19 @@ -import { ArrayStatefulParameter, Plan, StatefulParameter } from 'codify-plugin-lib'; +import { ArrayStatefulParameter, getPty, Plan, StatefulParameter } from 'codify-plugin-lib'; -import { codifySpawn } from '../../utils/codify-spawn.js'; +import { codifySpawn, SpawnStatus } from '../../utils/codify-spawn.js'; import { AsdfConfig } from './asdf.js'; export class AsdfPluginsParameter extends ArrayStatefulParameter { async refresh(desired: null | string[]): Promise { - const { data: plugins } = await codifySpawn('asdf plugin list ') + const $ = getPty(); + + const plugins = await $.spawnSafe('asdf plugin list ') + if (plugins.status === SpawnStatus.ERROR) { + return null; + } return plugins + .data .split(/\n/) .filter(Boolean); } diff --git a/src/resources/asdf/version-parameter.ts b/src/resources/asdf/version-parameter.ts index ccdfa8b..3f2de53 100644 --- a/src/resources/asdf/version-parameter.ts +++ b/src/resources/asdf/version-parameter.ts @@ -1,4 +1,4 @@ -import { ArrayStatefulParameter, Plan } from 'codify-plugin-lib'; +import { ArrayStatefulParameter, getPty, Plan, SpawnStatus } from 'codify-plugin-lib'; import { codifySpawn } from '../../utils/codify-spawn.js'; import { AsdfPluginConfig } from './asdf-plugin.js'; @@ -6,13 +6,18 @@ import { AsdfPluginConfig } from './asdf-plugin.js'; export class AsdfPluginVersionsParameter extends ArrayStatefulParameter { async refresh(desired: null | string[], config: Partial): Promise { - const { data: versions } = await codifySpawn(`asdf list ${config.plugin}`); + const $ = getPty(); + + const versions = await $.spawnSafe(`asdf list ${config.plugin}`); + if (versions.status === SpawnStatus.ERROR || versions.data.trim() === 'No versions installed') { + return null; + } const latest = desired?.includes('latest') ? (await codifySpawn(`asdf latest ${config.plugin}`)).data.trim() : null; - return versions.split(/\n/) + return versions.data.split(/\n/) .map((l) => l.trim()) .map((l) => l.replaceAll('*', '')) .map((l) => l.trim() === latest ? 'latest' : l) @@ -24,7 +29,7 @@ export class AsdfPluginVersionsParameter extends ArrayStatefulParameter): Promise { - await codifySpawn(`asdf install ${plan.desiredConfig?.plugin} ${item}`); + await codifySpawn(`asdf uninstall ${plan.currentConfig?.plugin} ${item}`); } } diff --git a/src/resources/aws-cli/cli/aws-cli.ts b/src/resources/aws-cli/cli/aws-cli.ts index d3f5066..6a2fff8 100644 --- a/src/resources/aws-cli/cli/aws-cli.ts +++ b/src/resources/aws-cli/cli/aws-cli.ts @@ -1,4 +1,4 @@ -import { Resource, ResourceSettings } from 'codify-plugin-lib'; +import { getPty, Resource, ResourceSettings } from 'codify-plugin-lib'; import { StringIndexedObject } from 'codify-schemas'; import { SpawnStatus, codifySpawn } from '../../../utils/codify-spawn.js'; @@ -22,7 +22,9 @@ export class AwsCliResource extends Resource { override async refresh(): Promise | null> { - const awsCliInfo = await codifySpawn('which aws', { throws: false }); + const $ = getPty(); + + const awsCliInfo = await $.spawnSafe('which aws'); if (awsCliInfo.status === SpawnStatus.ERROR) { return null; } diff --git a/src/resources/aws-cli/profile/aws-profile.ts b/src/resources/aws-cli/profile/aws-profile.ts index 935bddd..7447265 100644 --- a/src/resources/aws-cli/profile/aws-profile.ts +++ b/src/resources/aws-cli/profile/aws-profile.ts @@ -1,4 +1,12 @@ -import { CreatePlan, DestroyPlan, ModifyPlan, ParameterChange, Resource, ResourceSettings } from 'codify-plugin-lib'; +import { + CreatePlan, + DestroyPlan, + getPty, + ModifyPlan, + ParameterChange, + Resource, + ResourceSettings +} from 'codify-plugin-lib'; import { StringIndexedObject } from 'codify-schemas'; import * as fs from 'node:fs/promises'; import os from 'node:os'; @@ -50,16 +58,18 @@ export class AwsProfileResource extends Resource { } override async refresh(parameters: Partial): Promise | null> { + const $ = getPty(); + const profile = parameters.profile!; // Make sure aws-cli is installed - const { status: awsStatus } = await codifySpawn('which aws', { throws: false }); + const { status: awsStatus } = await $.spawnSafe('which aws'); if (awsStatus === SpawnStatus.ERROR) { return null; } // Check if the profile exists - const { data: profiles } = await codifySpawn('aws configure list-profiles'); + const { data: profiles } = await $.spawn('aws configure list-profiles'); if (!profiles.includes(profile)) { return null; } @@ -160,7 +170,9 @@ export class AwsProfileResource extends Resource { } private async getAwsConfigureValueOrNull(key: string, profile: string): Promise { - const { data, status } = await codifySpawn(`aws configure get ${key} --profile ${profile}`, { throws: false }); + const $ = getPty(); + + const { data, status } = await $.spawnSafe(`aws configure get ${key} --profile ${profile}`); if (status === SpawnStatus.ERROR) { return undefined; } diff --git a/src/resources/git/clone/git-clone.ts b/src/resources/git/clone/git-clone.ts index 2cb1177..b0fa56c 100644 --- a/src/resources/git/clone/git-clone.ts +++ b/src/resources/git/clone/git-clone.ts @@ -1,4 +1,4 @@ -import { CreatePlan, DestroyPlan, Resource, ResourceSettings } from 'codify-plugin-lib'; +import { CreatePlan, DestroyPlan, getPty, Resource, ResourceSettings } from 'codify-plugin-lib'; import { ResourceConfig } from 'codify-schemas'; import path from 'node:path'; @@ -37,6 +37,8 @@ export class GitCloneResource extends Resource { } override async refresh(parameters: Partial): Promise | null> { + const $ = getPty(); + if (parameters.parentDirectory) { const folderName = this.extractBasename(parameters.repository!); if (!folderName) { @@ -50,7 +52,7 @@ export class GitCloneResource extends Resource { return null; } - const { data: url } = await codifySpawn('git config --get remote.origin.url', { cwd: fullPath }); + const { data: url } = await $.spawn('git config --get remote.origin.url', { cwd: fullPath }); return { parentDirectory: parameters.parentDirectory, @@ -65,7 +67,7 @@ export class GitCloneResource extends Resource { return null; } - const { data: url } = await codifySpawn('git config --get remote.origin.url', { cwd: parameters.directory }); + const { data: url } = await $.spawn('git config --get remote.origin.url', { cwd: parameters.directory }); return { directory: parameters.directory, diff --git a/src/resources/git/git/git-email-paramater.ts b/src/resources/git/git/git-email-paramater.ts index 7dea78f..e1bb423 100644 --- a/src/resources/git/git/git-email-paramater.ts +++ b/src/resources/git/git/git-email-paramater.ts @@ -1,4 +1,4 @@ -import { StatefulParameter } from 'codify-plugin-lib'; +import { getPty, StatefulParameter } from 'codify-plugin-lib'; import { SpawnStatus, codifySpawn } from '../../../utils/codify-spawn.js'; import { GitConfig } from './git-resource.js'; @@ -6,8 +6,9 @@ import { GitConfig } from './git-resource.js'; export class GitEmailParameter extends StatefulParameter { async refresh(): Promise { - const { data: email, status } = await codifySpawn('git config --global user.email', { throws: false }) + const $ = getPty(); + const { data: email, status } = await $.spawnSafe('git config --global user.email') return status === SpawnStatus.ERROR ? null : email.trim() } diff --git a/src/resources/git/git/git-name-parameter.ts b/src/resources/git/git/git-name-parameter.ts index 2edffa9..fb42317 100644 --- a/src/resources/git/git/git-name-parameter.ts +++ b/src/resources/git/git/git-name-parameter.ts @@ -1,4 +1,4 @@ -import { StatefulParameter } from 'codify-plugin-lib'; +import { getPty, StatefulParameter } from 'codify-plugin-lib'; import { SpawnStatus, codifySpawn } from '../../../utils/codify-spawn.js'; import { GitConfig } from './git-resource.js'; @@ -6,8 +6,9 @@ import { GitConfig } from './git-resource.js'; export class GitNameParameter extends StatefulParameter { async refresh(): Promise { - const { data: name, status } = await codifySpawn('git config --global user.name', { throws: false }) + const $ = getPty() + const { data: name, status } = await $.spawnSafe('git config --global user.name') return status === SpawnStatus.ERROR ? null : name.trim() } diff --git a/src/resources/git/git/git-resource.ts b/src/resources/git/git/git-resource.ts index 0244cf7..138d464 100644 --- a/src/resources/git/git/git-resource.ts +++ b/src/resources/git/git/git-resource.ts @@ -1,4 +1,4 @@ -import { Resource, ResourceSettings } from 'codify-plugin-lib'; +import { getPty, Resource, ResourceSettings } from 'codify-plugin-lib'; import { StringIndexedObject } from 'codify-schemas'; import { SpawnStatus, codifySpawn } from '../../../utils/codify-spawn.js'; @@ -27,7 +27,9 @@ export class GitResource extends Resource { } async refresh(): Promise | null> { - const { status } = await codifySpawn('which git', { throws: false }) + const $ = getPty(); + + const { status } = await $.spawnSafe('which git') return status === SpawnStatus.ERROR ? null : {} } diff --git a/src/resources/git/lfs/git-lfs.ts b/src/resources/git/lfs/git-lfs.ts index 1cd3941..b1b196f 100644 --- a/src/resources/git/lfs/git-lfs.ts +++ b/src/resources/git/lfs/git-lfs.ts @@ -1,4 +1,4 @@ -import { Resource, ResourceSettings, SpawnStatus } from 'codify-plugin-lib'; +import { getPty, Resource, ResourceSettings, SpawnStatus } from 'codify-plugin-lib'; import { ResourceConfig } from 'codify-schemas'; import * as os from 'node:os'; @@ -19,7 +19,9 @@ export class GitLfsResource extends Resource { } override async refresh(): Promise | null> { - const result = await codifySpawn('git lfs', { throws: false }); + const $ = getPty(); + + const result = await $.spawnSafe('git lfs'); if (result.status === SpawnStatus.ERROR) { return null; @@ -52,8 +54,9 @@ export class GitLfsResource extends Resource { } private async checkIfGitLfsIsInstalled(): Promise { - const gitLfsStatus = await codifySpawn('git lfs env', { cwd: os.homedir() }); + const $ = getPty(); + const gitLfsStatus = await $.spawn('git lfs env', { cwd: os.homedir() }); const lines = gitLfsStatus.data.split('\n'); // When git lfs exists but git lfs install hasn't been called then git lfs env returns: diff --git a/src/resources/homebrew/casks-parameter.ts b/src/resources/homebrew/casks-parameter.ts index d7282cd..324aeef 100644 --- a/src/resources/homebrew/casks-parameter.ts +++ b/src/resources/homebrew/casks-parameter.ts @@ -1,4 +1,4 @@ -import { ParameterSetting, Plan, SpawnStatus, StatefulParameter } from 'codify-plugin-lib'; +import { getPty, ParameterSetting, Plan, SpawnStatus, StatefulParameter } from 'codify-plugin-lib'; import path from 'node:path'; import { codifySpawn } from '../../utils/codify-spawn.js'; @@ -25,7 +25,9 @@ export class CasksParameter extends StatefulParameter } async refresh(desired: string[], config: Partial | null): Promise { - const caskQuery = await codifySpawn('brew list --casks -1') + const $ = getPty(); + + const caskQuery = await $.spawnSafe('brew list --casks -1') if (caskQuery.status === SpawnStatus.SUCCESS && caskQuery.data !== null && caskQuery.data !== undefined) { const installedCasks = caskQuery.data @@ -120,7 +122,16 @@ export class CasksParameter extends StatefulParameter } private async findConflicts(casks: string[]): Promise { - const brewInfo = JSON.parse((await codifySpawn(`brew info -q --json=v2 ${casks.join(' ')}`)).data.replaceAll('\n', '')); + const $ = getPty(); + + let result: string; + if ($) { + result = (await $.spawnSafe(`brew info -q --json=v2 ${casks.join(' ')}`)).data.replaceAll('\n', '') + } else { + result = (await codifySpawn(`brew info -q --json=v2 ${casks.join(' ')}`)).data.replaceAll('\n', '') + } + + const brewInfo = JSON.parse(result); const casksWithConflicts = new Array(); for (const caskInfo of brewInfo.casks) { diff --git a/src/resources/homebrew/formulae-parameter.ts b/src/resources/homebrew/formulae-parameter.ts index 2e867ad..a92f2f1 100644 --- a/src/resources/homebrew/formulae-parameter.ts +++ b/src/resources/homebrew/formulae-parameter.ts @@ -1,4 +1,4 @@ -import { ArrayParameterSetting, SpawnStatus, StatefulParameter } from 'codify-plugin-lib'; +import { ArrayParameterSetting, getPty, SpawnStatus, StatefulParameter } from 'codify-plugin-lib'; import { codifySpawn } from '../../utils/codify-spawn.js'; import { HomebrewConfig } from './homebrew.js'; @@ -22,7 +22,8 @@ export class FormulaeParameter extends StatefulParameter { - const formulaeQuery = await codifySpawn('brew list --formula -1') + const $ = getPty(); + const formulaeQuery = await $.spawnSafe('brew list --formula -1') if (formulaeQuery.status === SpawnStatus.SUCCESS && formulaeQuery.data !== null && formulaeQuery.data !== undefined) { return formulaeQuery.data diff --git a/src/resources/homebrew/homebrew.ts b/src/resources/homebrew/homebrew.ts index 65be490..dadc108 100644 --- a/src/resources/homebrew/homebrew.ts +++ b/src/resources/homebrew/homebrew.ts @@ -1,4 +1,4 @@ -import { CreatePlan, Resource, ResourceSettings, SpawnStatus } from 'codify-plugin-lib'; +import { CreatePlan, getPty, Resource, ResourceSettings, SpawnStatus } from 'codify-plugin-lib'; import { ResourceConfig } from 'codify-schemas'; import * as fsSync from 'node:fs'; import * as fs from 'node:fs/promises'; @@ -40,7 +40,9 @@ export class HomebrewResource extends Resource { } override async refresh(parameters: Partial): Promise | null> { - const homebrewInfo = await codifySpawn('brew config', { throws: false }); + const $ = getPty(); + + const homebrewInfo = await $.spawnSafe('brew config'); if (homebrewInfo.status === SpawnStatus.ERROR) { return null; } diff --git a/src/resources/homebrew/tap-parameter.ts b/src/resources/homebrew/tap-parameter.ts index 28cfb64..6c29cff 100644 --- a/src/resources/homebrew/tap-parameter.ts +++ b/src/resources/homebrew/tap-parameter.ts @@ -1,4 +1,4 @@ -import { ParameterSetting, SpawnStatus, StatefulParameter } from 'codify-plugin-lib'; +import { getPty, ParameterSetting, SpawnStatus, StatefulParameter } from 'codify-plugin-lib'; import { codifySpawn } from '../../utils/codify-spawn.js' import { HomebrewConfig } from './homebrew.js'; @@ -12,16 +12,16 @@ export class TapsParameter extends StatefulParameter { } override async refresh(): Promise { - const tapsQuery = await codifySpawn('brew tap') + const $ = getPty(); + const tapsQuery = await $.spawnSafe('brew tap') if (tapsQuery.status === SpawnStatus.SUCCESS && tapsQuery.data !== null && tapsQuery.data !== undefined) { return tapsQuery.data .split('\n') .filter(Boolean) } - return null; - + return null; } override async add(valueToAdd: string[]): Promise { @@ -46,7 +46,6 @@ export class TapsParameter extends StatefulParameter { } await codifySpawn(`brew tap ${taps.join(' ')}`) - console.log(`Installed taps: ${taps}`); } private async uninstallTaps(taps: string[]): Promise { @@ -55,7 +54,6 @@ export class TapsParameter extends StatefulParameter { } await codifySpawn(`brew untap ${taps.join(' ')}`) - console.log(`Uninstalled taps: ${taps}`); } } diff --git a/src/resources/java/jenv/global-parameter.ts b/src/resources/java/jenv/global-parameter.ts index 21b01ac..455f4cd 100644 --- a/src/resources/java/jenv/global-parameter.ts +++ b/src/resources/java/jenv/global-parameter.ts @@ -1,4 +1,4 @@ -import { ParameterSetting, SpawnStatus, StatefulParameter } from 'codify-plugin-lib'; +import { getPty, ParameterSetting, SpawnStatus, StatefulParameter } from 'codify-plugin-lib'; import { codifySpawn } from '../../../utils/codify-spawn.js'; import { JenvConfig } from './jenv.js'; @@ -12,8 +12,9 @@ export class JenvGlobalParameter extends StatefulParameter{ } override async refresh(): Promise { - const { data, status } = await codifySpawn('jenv global', { throws: false }) + const $ = getPty(); + const { data, status } = await $.spawnSafe('jenv global') if (status === SpawnStatus.ERROR) { return null; } diff --git a/src/resources/java/jenv/java-versions-parameter.ts b/src/resources/java/jenv/java-versions-parameter.ts index cd6ab54..5b0adc4 100644 --- a/src/resources/java/jenv/java-versions-parameter.ts +++ b/src/resources/java/jenv/java-versions-parameter.ts @@ -1,4 +1,4 @@ -import { ArrayStatefulParameter } from 'codify-plugin-lib'; +import { ArrayStatefulParameter, getPty } from 'codify-plugin-lib'; import { SpawnStatus, codifySpawn } from '../../../utils/codify-spawn.js'; import { Utils } from '../../../utils/index.js'; @@ -9,7 +9,9 @@ export const JAVA_VERSION_INTEGER = /^\d+$/; export class JenvAddParameter extends ArrayStatefulParameter { override async refresh(desired: null | string[]): Promise { - const { data } = await codifySpawn('jenv versions') + const $ = getPty(); + + const { data } = await $.spawn('jenv versions') /** Example: * system diff --git a/src/resources/java/jenv/jenv.ts b/src/resources/java/jenv/jenv.ts index 040a9ec..89e806f 100644 --- a/src/resources/java/jenv/jenv.ts +++ b/src/resources/java/jenv/jenv.ts @@ -1,4 +1,4 @@ -import { Resource, ResourceSettings, SpawnStatus } from 'codify-plugin-lib'; +import { getPty, Resource, ResourceSettings, SpawnStatus } from 'codify-plugin-lib'; import { ResourceConfig } from 'codify-schemas'; import * as fs from 'node:fs'; @@ -49,14 +49,16 @@ export class JenvResource extends Resource { } override async refresh(): Promise | null> { - const jenvQuery = await codifySpawn('which jenv', { throws: false }) + const $ = getPty(); + + const jenvQuery = await $.spawnSafe('which jenv') if (jenvQuery.status === SpawnStatus.ERROR) { return null } // For some reason jenv doctor will return with a non-zero status code even // if it's successful. We can ignore the status code and only check for the text - const jenvDoctor = await codifySpawn('jenv doctor', { throws: false }) + const jenvDoctor = await $.spawnSafe('jenv doctor') if (jenvDoctor.data.includes('Jenv is not loaded in')) { return null } diff --git a/src/resources/node/nvm/global-parameter.ts b/src/resources/node/nvm/global-parameter.ts index 7295d5f..4afabfc 100644 --- a/src/resources/node/nvm/global-parameter.ts +++ b/src/resources/node/nvm/global-parameter.ts @@ -1,4 +1,4 @@ -import { ParameterSetting, SpawnStatus, StatefulParameter } from 'codify-plugin-lib'; +import { getPty, ParameterSetting, SpawnStatus, StatefulParameter } from 'codify-plugin-lib'; import { codifySpawn } from '../../../utils/codify-spawn.js'; import { NvmConfig } from './nvm.js'; @@ -12,7 +12,9 @@ export class NvmGlobalParameter extends StatefulParameter{ } override async refresh(): Promise { - const { data, status } = await codifySpawn('nvm ls default --no-colors', { throws: false }) + const $ = getPty(); + + const { data, status } = await $.spawnSafe('nvm ls default --no-colors') if (status === SpawnStatus.ERROR) { return null; diff --git a/src/resources/node/nvm/node-versions-parameter.ts b/src/resources/node/nvm/node-versions-parameter.ts index 16df876..d30ea04 100644 --- a/src/resources/node/nvm/node-versions-parameter.ts +++ b/src/resources/node/nvm/node-versions-parameter.ts @@ -1,4 +1,4 @@ -import { ArrayParameterSetting, ArrayStatefulParameter } from 'codify-plugin-lib'; +import { ArrayParameterSetting, ArrayStatefulParameter, getPty } from 'codify-plugin-lib'; import { SpawnStatus, codifySpawn } from '../../../utils/codify-spawn.js'; import { NvmConfig } from './nvm.js'; @@ -12,7 +12,9 @@ export class NvmNodeVersionsParameter extends ArrayStatefulParameter { - const { data } = await codifySpawn('nvm ls --no-colors --no-alias') + const $ = getPty() + + const { data } = await $.spawnSafe('nvm ls --no-colors --no-alias') const versions = data.split(/\n/) .map((l) => @@ -27,7 +29,7 @@ export class NvmNodeVersionsParameter extends ArrayStatefulParameter { } override async refresh(): Promise | null> { - const nvmQuery = await codifySpawn('command -v nvm', { throws: false }) + const $ = getPty() + + const nvmQuery = await $.spawnSafe('command -v nvm') if (nvmQuery.status === SpawnStatus.ERROR) { return null } diff --git a/src/resources/pgcli/pgcli.ts b/src/resources/pgcli/pgcli.ts index 4162ac7..7a4a123 100644 --- a/src/resources/pgcli/pgcli.ts +++ b/src/resources/pgcli/pgcli.ts @@ -1,4 +1,4 @@ -import { Resource, ResourceSettings, SpawnStatus } from 'codify-plugin-lib'; +import { getPty, Resource, ResourceSettings, SpawnStatus } from 'codify-plugin-lib'; import { ResourceConfig } from 'codify-schemas'; import { codifySpawn } from '../../utils/codify-spawn.js'; @@ -16,8 +16,9 @@ export class PgcliResource extends Resource { } override async refresh(): Promise | null> { - const result = await codifySpawn('which pgcli', { throws: false }); + const $ = getPty(); + const result = await $.spawnSafe('which pgcli'); if (result.status === SpawnStatus.ERROR) { return null; } diff --git a/src/resources/python/pyenv/global-parameter.ts b/src/resources/python/pyenv/global-parameter.ts index af61d51..65cd0d0 100644 --- a/src/resources/python/pyenv/global-parameter.ts +++ b/src/resources/python/pyenv/global-parameter.ts @@ -1,4 +1,4 @@ -import { ParameterSetting, SpawnStatus, StatefulParameter } from 'codify-plugin-lib'; +import { getPty, ParameterSetting, SpawnStatus, StatefulParameter } from 'codify-plugin-lib'; import { codifySpawn } from '../../../utils/codify-spawn.js'; import { PyenvConfig } from './pyenv.js'; @@ -12,8 +12,9 @@ export class PyenvGlobalParameter extends StatefulParameter } override async refresh(): Promise { - const { data, status } = await codifySpawn('pyenv global') + const $ = getPty(); + const { data, status } = await $.spawnSafe('pyenv global') if (status === SpawnStatus.ERROR) { return null; } diff --git a/src/resources/python/pyenv/pyenv.ts b/src/resources/python/pyenv/pyenv.ts index 6064994..6121d40 100644 --- a/src/resources/python/pyenv/pyenv.ts +++ b/src/resources/python/pyenv/pyenv.ts @@ -1,4 +1,4 @@ -import { Resource, ResourceSettings } from 'codify-plugin-lib'; +import { getPty, Resource, ResourceSettings } from 'codify-plugin-lib'; import { ResourceConfig } from 'codify-schemas'; import * as fs from 'node:fs'; import os from 'node:os'; @@ -29,7 +29,9 @@ export class PyenvResource extends Resource { } override async refresh(): Promise | null> { - const pyenvVersion = await codifySpawn('pyenv --version', { throws: false }) + const $ = getPty(); + + const pyenvVersion = await $.spawnSafe('pyenv --version') if (pyenvVersion.status === SpawnStatus.ERROR) { return null } diff --git a/src/resources/python/pyenv/python-versions-parameter.ts b/src/resources/python/pyenv/python-versions-parameter.ts index 48c5c7d..81eb16d 100644 --- a/src/resources/python/pyenv/python-versions-parameter.ts +++ b/src/resources/python/pyenv/python-versions-parameter.ts @@ -1,11 +1,13 @@ -import { ArrayStatefulParameter } from 'codify-plugin-lib'; +import { ArrayStatefulParameter, getPty } from 'codify-plugin-lib'; import { codifySpawn, SpawnStatus } from '../../../utils/codify-spawn.js'; import { PyenvConfig } from './pyenv.js'; export class PythonVersionsParameter extends ArrayStatefulParameter { override async refresh(desired: string[]): Promise { - const { data } = await codifySpawn('pyenv versions --bare --skip-aliases --skip-envs') + const $ = getPty(); + + const { data } = await $.spawnSafe('pyenv versions --bare --skip-aliases --skip-envs') const versions = data.split(/\n/) .map((l) => l.trim()) @@ -15,7 +17,7 @@ export class PythonVersionsParameter extends ArrayStatefulParameter { } async refresh(parameters: Partial): Promise | Partial[] | null> { + const $ = getPty(); + // Always run if condition doesn't exist // TODO: Remove hack. Right now we're returning null to simulate CREATE and a value for NO-OP if (!parameters.condition) { @@ -30,7 +32,7 @@ export class ActionResource extends Resource { } const { condition, action, cwd } = parameters; - const { status } = await codifySpawn(condition, { throws: false, cwd: cwd ?? undefined }); + const { status } = await $.spawnSafe(condition, { cwd: cwd ?? undefined }); return status === SpawnStatus.ERROR ? null diff --git a/src/resources/shell/alias/alias-resource.ts b/src/resources/shell/alias/alias-resource.ts index f26a7bf..c524252 100644 --- a/src/resources/shell/alias/alias-resource.ts +++ b/src/resources/shell/alias/alias-resource.ts @@ -1,4 +1,12 @@ -import { CreatePlan, DestroyPlan, ModifyPlan, ParameterChange, Resource, ResourceSettings } from 'codify-plugin-lib'; +import { + CreatePlan, + DestroyPlan, + getPty, + ModifyPlan, + ParameterChange, + Resource, + ResourceSettings +} from 'codify-plugin-lib'; import { StringIndexedObject } from 'codify-schemas'; import fs from 'node:fs/promises'; import os from 'node:os'; @@ -26,9 +34,10 @@ export class AliasResource extends Resource { } override async refresh(parameters: Partial): Promise | null> { - const { alias: desired } = parameters; + const $ = getPty(); - const { data, status } = await codifySpawn(`alias ${desired}`, { throws: false }) + const { alias: desired } = parameters; + const { data, status } = await $.spawnSafe(`alias ${desired}`) if (status === SpawnStatus.ERROR) { return null; diff --git a/src/resources/shell/path/path-resource.ts b/src/resources/shell/path/path-resource.ts index fb509d4..4ddd42f 100644 --- a/src/resources/shell/path/path-resource.ts +++ b/src/resources/shell/path/path-resource.ts @@ -1,4 +1,12 @@ -import { CreatePlan, DestroyPlan, ModifyPlan, ParameterChange, Resource, ResourceSettings } from 'codify-plugin-lib'; +import { + CreatePlan, + DestroyPlan, + getPty, + ModifyPlan, + ParameterChange, + Resource, + ResourceSettings +} from 'codify-plugin-lib'; import { StringIndexedObject } from 'codify-schemas'; import fs from 'node:fs/promises'; import os from 'node:os'; @@ -58,8 +66,9 @@ export class PathResource extends Resource { } override async refresh(parameters: Partial): Promise | null> { - const { data: existingPaths } = await codifySpawn('echo $PATH') + const $ = getPty(); + const { data: existingPaths } = await $.spawnSafe('echo $PATH') if (parameters.path && (existingPaths.includes(parameters.path) || existingPaths.includes(untildify(parameters.path)))) { return parameters; } diff --git a/src/resources/ssh/ssh-add.ts b/src/resources/ssh/ssh-add.ts index 3dfe65b..b270469 100644 --- a/src/resources/ssh/ssh-add.ts +++ b/src/resources/ssh/ssh-add.ts @@ -1,4 +1,4 @@ -import { CreatePlan, DestroyPlan, Resource, ResourceSettings } from 'codify-plugin-lib'; +import { CreatePlan, DestroyPlan, getPty, Resource, ResourceSettings } from 'codify-plugin-lib'; import { StringIndexedObject } from 'codify-schemas'; import path from 'node:path'; @@ -31,19 +31,21 @@ export class SshAddResource extends Resource { } async refresh(parameters: Partial): Promise | null> { + const $ = getPty(); + const sshPath = parameters.path!; if (!(await FileUtils.fileExists(sshPath))) { return null; } - await codifySpawn('eval "$(ssh-agent -s)"') + await $.spawnSafe('eval "$(ssh-agent -s)"') - const { data: keyFingerprint, status: keygenStatus } = await codifySpawn(`ssh-keygen -lf ${sshPath}`, { throws: false }); + const { data: keyFingerprint, status: keygenStatus } = await $.spawnSafe(`ssh-keygen -lf ${sshPath}`); if (keygenStatus === SpawnStatus.ERROR) { return null; } - const { data: loadedSshKeys, status: sshAddStatus } = await codifySpawn('/usr/bin/ssh-add -l', { throws: false }); + const { data: loadedSshKeys, status: sshAddStatus } = await $.spawnSafe('/usr/bin/ssh-add -l'); if (sshAddStatus === SpawnStatus.ERROR) { return null; } diff --git a/src/resources/ssh/ssh-config-hosts-parameter.ts b/src/resources/ssh/ssh-config-hosts-parameter.ts index e5ffa56..9b833a5 100644 --- a/src/resources/ssh/ssh-config-hosts-parameter.ts +++ b/src/resources/ssh/ssh-config-hosts-parameter.ts @@ -52,10 +52,7 @@ export class SshConfigHostsParameter extends StatefulParameter { diff --git a/src/resources/ssh/ssh-key.ts b/src/resources/ssh/ssh-key.ts index c65bd54..6f84f6f 100644 --- a/src/resources/ssh/ssh-key.ts +++ b/src/resources/ssh/ssh-key.ts @@ -1,4 +1,12 @@ -import { CreatePlan, DestroyPlan, ModifyPlan, ParameterChange, Resource, ResourceSettings } from 'codify-plugin-lib'; +import { + CreatePlan, + DestroyPlan, + getPty, + ModifyPlan, + ParameterChange, + Resource, + ResourceSettings +} from 'codify-plugin-lib'; import { StringIndexedObject } from 'codify-schemas'; import os from 'node:os'; import path from 'node:path'; @@ -51,6 +59,8 @@ export class SshKeyResource extends Resource { } override async refresh(parameters: Partial): Promise | null> { + const $ = getPty(); + if (!(await FileUtils.dirExists(parameters.folder!))) { return null; } @@ -60,7 +70,7 @@ export class SshKeyResource extends Resource { return null; } - const { data: existingKey } = await codifySpawn(`ssh-keygen -l -f ${parameters.fileName}`, { cwd: parameters.folder! }); + const { data: existingKey } = await $.spawn(`ssh-keygen -l -f ${parameters.fileName}`, { cwd: parameters.folder! }); const [_, bits, __, ___, comment, type] = existingKey .trim() .match(SSH_KEYGEN_FINGERPRINT_REGEX) ?? []; diff --git a/src/resources/terraform/terraform.ts b/src/resources/terraform/terraform.ts index 58763ed..77b1f79 100644 --- a/src/resources/terraform/terraform.ts +++ b/src/resources/terraform/terraform.ts @@ -1,4 +1,4 @@ -import { CreatePlan, Resource, ResourceSettings, SpawnStatus } from 'codify-plugin-lib'; +import { CreatePlan, getPty, Resource, ResourceSettings, SpawnStatus } from 'codify-plugin-lib'; import { StringIndexedObject } from 'codify-schemas'; import semver from 'semver'; @@ -39,7 +39,9 @@ export class TerraformResource extends Resource { } override async refresh(parameters: Partial): Promise | null> { - const terraformInfo = await codifySpawn('which terraform', { throws: false }); + const $ = getPty(); + + const terraformInfo = await $.spawnSafe('which terraform'); if (terraformInfo.status === SpawnStatus.ERROR) { return null; } @@ -53,7 +55,7 @@ export class TerraformResource extends Resource { } if (parameters.version) { - const versionQuery = await codifySpawn('terraform version -json'); + const versionQuery = await $.spawn('terraform version -json'); const versionJson = JSON.parse(versionQuery.data.trim().replaceAll('\n', '')) as TerraformVersionInfo; results.version = versionJson.terraform_version; diff --git a/src/resources/xcode-tools/xcode-tools.ts b/src/resources/xcode-tools/xcode-tools.ts index 7928f9b..92ddfa0 100644 --- a/src/resources/xcode-tools/xcode-tools.ts +++ b/src/resources/xcode-tools/xcode-tools.ts @@ -1,4 +1,4 @@ -import { Resource, ResourceSettings } from 'codify-plugin-lib'; +import { getPty, Resource, ResourceSettings } from 'codify-plugin-lib'; import { StringIndexedObject } from 'codify-schemas'; import path from 'node:path'; @@ -15,7 +15,9 @@ export class XcodeToolsResource extends Resource { } override async refresh(): Promise | null> { - const { data, status } = await codifySpawn('xcode-select -p', { throws: false }) + const $ = getPty() + + const { data, status } = await $.spawnSafe('xcode-select -p') // The last check, ensures that a valid path is returned. if (status === SpawnStatus.ERROR || !data || path.basename(data) === data) { diff --git a/src/utils/codify-spawn.ts b/src/utils/codify-spawn.ts index 1b1197a..95c3f84 100644 --- a/src/utils/codify-spawn.ts +++ b/src/utils/codify-spawn.ts @@ -1,8 +1,15 @@ import { Ajv } from 'ajv'; import { SudoError } from 'codify-plugin-lib'; -import { IpcMessage, MessageCmd, SudoRequestResponseData, SudoRequestResponseDataSchema } from 'codify-schemas'; +import { + IpcMessage, + IpcMessageV2, + MessageCmd, + SudoRequestResponseData, + SudoRequestResponseDataSchema +} from 'codify-schemas'; import { SpawnOptions, spawn } from 'node:child_process'; import stripAnsi from 'strip-ansi'; +import { nanoid } from 'nanoid'; const ajv = new Ajv({ strict: true, @@ -150,9 +157,11 @@ async function externalSpawnWithSudo( cmd: string, opts: CodifySpawnOptions ): Promise<{ status: SpawnStatus, data: string }> { - return await new Promise((resolve) => { - const listener = (data: IpcMessage)=> { - if (data.cmd === MessageCmd.SUDO_REQUEST + '_Response') { + return new Promise((resolve) => { + const requestId = nanoid(8); + + const listener = (data: IpcMessageV2)=> { + if (data.requestId === requestId) { process.removeListener('message', listener); if (!validateSudoRequestResponse(data.data)) { @@ -165,12 +174,13 @@ async function externalSpawnWithSudo( process.on('message', listener); - process.send!({ + process.send!({ cmd: MessageCmd.SUDO_REQUEST, data: { command: cmd, options: opts ?? {}, - } + }, + requestId }) }); } diff --git a/test/android/android-studio.test.ts b/test/android/android-studio.test.ts index 43a3088..630de3f 100644 --- a/test/android/android-studio.test.ts +++ b/test/android/android-studio.test.ts @@ -5,14 +5,10 @@ import * as fs from 'node:fs/promises'; import os from 'node:os'; describe('Android studios tests', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can install the latest Android studios', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'android-studio' } ], { validateApply: async () => { @@ -26,8 +22,4 @@ describe('Android studios tests', async () => { } }); }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/asdf/asdf-install.test.ts b/test/asdf/asdf-install.test.ts index ef3380d..f47c74c 100644 --- a/test/asdf/asdf-install.test.ts +++ b/test/asdf/asdf-install.test.ts @@ -6,11 +6,7 @@ import os from 'node:os'; import cp from 'child_process'; describe('Asdf install tests', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can install a .tool-versions file', { timeout: 300000 }, async () => { await fs.mkdir(path.join(os.homedir(), 'toolDir')); @@ -20,7 +16,7 @@ describe('Asdf install tests', async () => { 'golang 1.23.2' ) - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'asdf', }, @@ -44,7 +40,7 @@ describe('Asdf install tests', async () => { }) it('Can install a plugin and then a version', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'asdf', plugins: ['nodejs'] @@ -65,8 +61,4 @@ describe('Asdf install tests', async () => { } }); }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/asdf/asdf.test.ts b/test/asdf/asdf.test.ts index 60adb2c..f77a840 100644 --- a/test/asdf/asdf.test.ts +++ b/test/asdf/asdf.test.ts @@ -7,14 +7,10 @@ import * as cp from 'child_process' import { PlanRequestDataSchema, PlanResponseDataSchema } from 'codify-schemas'; describe('Asdf tests', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can install asdf and plugins', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'asdf', plugins: ['nodejs', 'ruby'] @@ -42,7 +38,7 @@ describe('Asdf tests', async () => { }) it('Support plugins resource', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'asdf', }, @@ -64,7 +60,7 @@ describe('Asdf tests', async () => { }) it('Can install custom gitUrls', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'asdf', }, @@ -78,7 +74,7 @@ describe('Asdf tests', async () => { skipUninstall: true, }); - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'asdf', }, @@ -105,7 +101,7 @@ describe('Asdf tests', async () => { it('Can install a local version', { timeout: 300000 }, async () => { await fs.mkdir(path.join(os.homedir(), 'localDir')); - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'asdf', plugins: ['nodejs'], @@ -128,7 +124,7 @@ describe('Asdf tests', async () => { // localDir1 is created in the previous test await fs.mkdir(path.join(os.homedir(), 'localDir2')); - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'asdf', plugins: ['nodejs'], @@ -148,7 +144,7 @@ describe('Asdf tests', async () => { skipUninstall: true, }); - await plugin.uninstall([ + await PluginTester.uninstall(pluginPath, [ { type: 'asdf-local', plugin: 'golang', @@ -156,24 +152,5 @@ describe('Asdf tests', async () => { directories: ['~/localDir', '~/localDir2'] } ]) - - const plan = await plugin.plan({ - desired: { - type: 'asdf-plugin', - plugin: 'golang', - versions: ['latest'], - }, - state: undefined, - isStateful: false - }); - - expect(plan).toMatchObject({ - resourceType: 'asdf-plugin', - operation: 'noop' - }) - }) - - afterEach(() => { - plugin.kill(); }) }) diff --git a/test/aws-cli/aws-cli.test.ts b/test/aws-cli/aws-cli.test.ts index 041b102..0540395 100644 --- a/test/aws-cli/aws-cli.test.ts +++ b/test/aws-cli/aws-cli.test.ts @@ -4,14 +4,10 @@ import * as path from 'node:path'; import cp from 'child_process'; describe('Test aws-cli', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can install aws-cli', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'homebrew' }, { type: 'aws-cli' }, ], { diff --git a/test/aws-cli/aws-profile.test.ts b/test/aws-cli/aws-profile.test.ts index 2ab3ad0..8f8ab6e 100644 --- a/test/aws-cli/aws-profile.test.ts +++ b/test/aws-cli/aws-profile.test.ts @@ -5,14 +5,10 @@ import { execSync } from 'node:child_process'; import * as fs from 'node:fs/promises'; describe('Aws profile tests', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can add a aws-cli profile', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'homebrew' }, { type: 'aws-cli' }, { @@ -37,7 +33,7 @@ describe('Aws profile tests', async () => { }) it('Always defaults output to json + can modify a previous profile', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'aws-profile', profile: 'default', @@ -82,7 +78,7 @@ describe('Aws profile tests', async () => { AKIA,zhKpjk `, 'utf-8'); - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'aws-profile', profile: 'codify3', @@ -107,10 +103,6 @@ AKIA,zhKpjk }); }) - afterEach(() => { - plugin.kill(); - }) - function validateProfile(profile: { name: string; region: string; diff --git a/test/git/git-clone.test.ts b/test/git/git-clone.test.ts index 08654a2..2b742c4 100644 --- a/test/git/git-clone.test.ts +++ b/test/git/git-clone.test.ts @@ -6,14 +6,10 @@ import * as os from 'node:os'; import { execSync } from 'child_process'; describe('Git clone integration tests', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can install git repo to parent dir', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'git-clone', parentDirectory: '~/projects/test', @@ -36,7 +32,7 @@ describe('Git clone integration tests', async () => { }) it('Can install git repo to specified dir', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'git-clone', directory: '~/projects/nested/codify-plugin', @@ -55,8 +51,4 @@ describe('Git clone integration tests', async () => { } }); }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/git/git-lfs.test.ts b/test/git/git-lfs.test.ts index b411a0b..208991e 100644 --- a/test/git/git-lfs.test.ts +++ b/test/git/git-lfs.test.ts @@ -4,14 +4,10 @@ import * as path from 'node:path'; import { execSync } from 'node:child_process'; describe('Git lfs integration tests', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can install git-lfs', { timeout: 500000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'homebrew' }, { type: 'git-lfs' } ], { @@ -27,8 +23,4 @@ describe('Git lfs integration tests', async () => { } }); }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/git/git.test.ts b/test/git/git.test.ts index 71be9cf..060290d 100644 --- a/test/git/git.test.ts +++ b/test/git/git.test.ts @@ -4,14 +4,10 @@ import * as path from 'node:path'; import { execSync } from 'node:child_process'; describe('Git integration tests', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can add global user name and email', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'git', username: 'test', @@ -30,7 +26,7 @@ describe('Git integration tests', async () => { }) it('Can modify git user name and email', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'git', username: 'test2', @@ -48,8 +44,4 @@ describe('Git integration tests', async () => { } }); }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/homebrew/custom-install.test.ts b/test/homebrew/custom-install.test.ts index 4689ba8..edf56d0 100644 --- a/test/homebrew/custom-install.test.ts +++ b/test/homebrew/custom-install.test.ts @@ -4,14 +4,10 @@ import * as path from 'node:path'; import { execSync } from 'child_process'; describe('Homebrew custom install integration tests', () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it ('Creates brew in a custom location', { timeout: 300000 }, async () => { - await plugin.fullTest([{ + await PluginTester.fullTest(pluginPath, [{ type: 'homebrew', directory: '~/.homebrew', formulae: [ @@ -24,8 +20,4 @@ describe('Homebrew custom install integration tests', () => { } }) }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/homebrew/default.test.ts b/test/homebrew/default.test.ts index 48d88c9..09dd2f6 100644 --- a/test/homebrew/default.test.ts +++ b/test/homebrew/default.test.ts @@ -6,15 +6,11 @@ import fs from 'node:fs/promises'; import os from 'node:os'; describe('Homebrew main resource integration tests', () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Creates brew and can install formulas', { timeout: 300000 }, async () => { // Plans correctly and detects that brew is not installed - await plugin.fullTest([{ + await PluginTester.fullTest(pluginPath, [{ type: 'homebrew', formulae: [ 'apr', @@ -56,7 +52,7 @@ describe('Homebrew main resource integration tests', () => { it('Can handle casks that were already installed by skipping in the plan', { timeout: 300000 }, async () => { // Install vscode outside of cask - await plugin.fullTest([{ + await PluginTester.fullTest(pluginPath, [{ type: 'vscode', }, { type: 'homebrew' @@ -70,7 +66,7 @@ describe('Homebrew main resource integration tests', () => { }) // Without skipping vscode install this should throw - await plugin.fullTest([{ + await PluginTester.fullTest(pluginPath, [{ type: 'homebrew', casks: ['visual-studio-code'], }], { @@ -109,20 +105,22 @@ describe('Homebrew main resource integration tests', () => { } }) - expect(async () => await plugin.fullTest([{ + await expect(async () => PluginTester.fullTest(pluginPath, [{ type: 'homebrew', casks: ['visual-studio-code'], skipAlreadyInstalledCasks: false, }])).rejects.toThrowError(); - await plugin.uninstall([{ + await PluginTester.uninstall(pluginPath, [{ type: 'vscode', + }, { + type: 'homebrew', }]) }) it('Can handle casks that were already installed by skipping in the install (only applicable to the initial)', { timeout: 300000 }, async () => { // Install vscode outside of cask - await plugin.fullTest([{ + await PluginTester.fullTest(pluginPath, [{ type: 'vscode', }, { type: 'homebrew', @@ -158,8 +156,4 @@ describe('Homebrew main resource integration tests', () => { } }) }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/homebrew/taps.test.ts b/test/homebrew/taps.test.ts index 5b4d3f9..183584d 100644 --- a/test/homebrew/taps.test.ts +++ b/test/homebrew/taps.test.ts @@ -4,15 +4,11 @@ import * as path from 'node:path'; import { execSync } from 'child_process'; describe('Homebrew taps tests', () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can install homebrew and add a tap', { timeout: 300000 }, async () => { // Plans correctly and detects that brew is not installed - await plugin.fullTest([{ + await PluginTester.fullTest(pluginPath, [{ type: 'homebrew', taps: ['cirruslabs/cli'], }], { @@ -43,8 +39,4 @@ describe('Homebrew taps tests', () => { } }); }); - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/java/jenv/jenv.test.ts b/test/java/jenv/jenv.test.ts index 240e236..d283943 100644 --- a/test/java/jenv/jenv.test.ts +++ b/test/java/jenv/jenv.test.ts @@ -6,14 +6,10 @@ import * as fs from 'node:fs/promises'; import os from 'node:os'; describe('Jenv resource integration tests', () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Installs jenv and java with homebrew', { timeout: 500000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'homebrew' }, { type: 'jenv', @@ -48,8 +44,4 @@ describe('Jenv resource integration tests', () => { } }); }); - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/node/nvm/nvm.test.ts b/test/node/nvm/nvm.test.ts index 92fc09e..7758533 100644 --- a/test/node/nvm/nvm.test.ts +++ b/test/node/nvm/nvm.test.ts @@ -5,14 +5,10 @@ import { execSync } from 'child_process'; // Example test suite describe('nvm tests', () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can install nvm and node', { timeout: 500000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'nvm', global: '20', @@ -43,5 +39,3 @@ describe('nvm tests', () => { }); }); }); - -export {}; diff --git a/test/pgcli/pgcli.test.ts b/test/pgcli/pgcli.test.ts index 229ae8b..3082ec1 100644 --- a/test/pgcli/pgcli.test.ts +++ b/test/pgcli/pgcli.test.ts @@ -4,14 +4,10 @@ import * as path from 'node:path'; import { execSync } from 'child_process'; describe('Pgcli integration tests', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can install pgcli', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'homebrew' }, { type: 'pgcli' } ], { @@ -25,8 +21,4 @@ describe('Pgcli integration tests', async () => { } }) }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/python/pyenv.test.ts b/test/python/pyenv.test.ts index 68c34bb..af92c2c 100644 --- a/test/python/pyenv.test.ts +++ b/test/python/pyenv.test.ts @@ -6,14 +6,10 @@ import fs from 'node:fs'; import os from 'node:os'; describe('Pyenv resource integration tests', () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Installs pyenv and python (this installs on a clean system without readline, openSSL, etc.)', { timeout: 500000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'pyenv', pythonVersions: ['3.11'] @@ -27,7 +23,7 @@ describe('Pyenv resource integration tests', () => { }); it ('Can install additional python versions. (this installs after openSSL and readline have been installed)', { timeout: 700000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'homebrew', formulae: ['readline', 'openssl@3'] @@ -52,8 +48,4 @@ describe('Pyenv resource integration tests', () => { } }) }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/scripting/action.test.ts b/test/scripting/action.test.ts index e53d6e7..456682e 100644 --- a/test/scripting/action.test.ts +++ b/test/scripting/action.test.ts @@ -1,19 +1,16 @@ -import { beforeEach, describe, expect, it } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { PluginTester } from 'codify-plugin-test'; import path from 'node:path'; import { ResourceOperation } from 'codify-schemas'; import fs from 'node:fs'; import os from 'node:os'; -describe('Action tests', () => { - let plugin: PluginTester; +const pluginPath = path.resolve('./src/index.ts'); - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) +describe('Action tests', () => { it('Can run an action if the condition returns as non-zero', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'action', condition: '[ -d ~/tmp ]', action: 'mkdir ~/tmp; touch ~/tmp/testFile' } ], { skipUninstall: true, @@ -29,8 +26,8 @@ describe('Action tests', () => { }) it('It will return NO-OP when the return is 0', { timeout: 300000 }, async () => { - await plugin.fullTest([ - { type: 'action', condition: 'exit 0;', action: 'mkdir ~/tmp; touch ~/tmp/testFile' } + await PluginTester.fullTest(pluginPath, [ + { type: 'action', condition: 'echo okay', action: 'mkdir ~/tmp; touch ~/tmp/testFile' } ], { skipUninstall: true, validatePlan: (plans) => { @@ -43,7 +40,7 @@ describe('Action tests', () => { it('It can use the cwd parameter to run all commands from a specific directory', { timeout: 300000 }, async () => { fs.mkdirSync(path.resolve(os.homedir(), 'tmp2')) - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'action', condition: '[ -e testFile ]', action: 'touch testFile', cwd: '~/tmp2' } ], { skipUninstall: true, @@ -56,7 +53,7 @@ describe('Action tests', () => { expect(fs.existsSync(path.resolve(os.homedir(), 'tmp2', 'testFile'))).to.be.true; - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'action', condition: '[ -e testFile ]', action: 'touch testFile', cwd: '~/tmp2' } ], { skipUninstall: true, diff --git a/test/scripting/file.test.ts b/test/scripting/file.test.ts index f1d7951..5e82e0e 100644 --- a/test/scripting/file.test.ts +++ b/test/scripting/file.test.ts @@ -6,11 +6,7 @@ import fs from 'node:fs'; import os from 'node:os'; describe('File resource tests', () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can create a file, modify the contents and delete it', { timeout: 300000 }, async () => { const contents = 'AWS_ACCESS_KEY_ID=\n' + @@ -18,7 +14,7 @@ describe('File resource tests', () => { 'AWS_S3_ENDPOINT=\n' + 'AWS_REGION=\n'; - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'file', path: '~/.env', contents @@ -53,7 +49,7 @@ describe('File resource tests', () => { it('Will throw an error if the path given is a directory', { timeout: 300000 }, async () => { fs.mkdirSync(path.resolve(os.homedir(), 'tmp')) - await expect(async () => plugin.fullTest([ + await expect(async () => PluginTester.fullTest(pluginPath, [ { type: 'file', path: '~/tmp', contents: 'anything' } ])).rejects.toThrow(); }) @@ -62,7 +58,7 @@ describe('File resource tests', () => { const filePath = path.resolve(os.homedir(), 'testFile'); fs.writeFileSync(filePath, 'this is the previous file', 'utf-8') - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'file', path: filePath, contents: 'anything', onlyCreate: true } ], { skipUninstall: true, diff --git a/test/shell/alias.test.ts b/test/shell/alias.test.ts index b758b47..8b36766 100644 --- a/test/shell/alias.test.ts +++ b/test/shell/alias.test.ts @@ -1,19 +1,14 @@ -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { describe, expect, it } from 'vitest'; import { PluginTester } from 'codify-plugin-test'; import * as path from 'node:path'; import { execSync } from 'child_process'; -import fs from 'node:fs/promises'; import os from 'node:os'; describe('Alias resource integration tests', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can add an alias to zshrc', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'alias', alias: 'my-alias', @@ -49,7 +44,7 @@ describe('Alias resource integration tests', async () => { }) it('Validates against invalid alias', { timeout: 300000 }, async () => { - expect(async () => plugin.fullTest([ + expect(async () => PluginTester.fullTest(pluginPath, [ { type: 'alias', alias: 'test$$$', @@ -57,8 +52,4 @@ describe('Alias resource integration tests', async () => { } ])).rejects.toThrowError(); }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/shell/path.test.ts b/test/shell/path.test.ts index a7a9452..a0e0f5c 100644 --- a/test/shell/path.test.ts +++ b/test/shell/path.test.ts @@ -7,15 +7,11 @@ import { ParameterOperation, ResourceOperation } from 'codify-schemas'; import { execSync } from 'child_process'; describe('Path resource integration tests', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can add a path to zshrc', { timeout: 300000 }, async () => { const tempDir1 = await fs.mkdtemp(os.tmpdir() + '/'); - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'path', path: tempDir1, @@ -34,7 +30,7 @@ describe('Path resource integration tests', async () => { const tempDir1 = await fs.mkdtemp(os.tmpdir() + '/'); const tempDir2 = await fs.mkdtemp(os.tmpdir() + '/'); - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'path', paths: [tempDir1, tempDir2], @@ -57,7 +53,7 @@ describe('Path resource integration tests', async () => { const tempDir1 = await fs.mkdtemp(os.tmpdir() + '/'); const tempDir2 = await fs.mkdtemp(os.tmpdir() + '/'); - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'path', paths: [tempDir1, tempDir2], @@ -83,7 +79,7 @@ describe('Path resource integration tests', async () => { const tempDir3 = await fs.mkdtemp(os.tmpdir() + '/'); const tempDir4 = await fs.mkdtemp(os.tmpdir() + '/'); - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'path', paths: [tempDir1, tempDir2], @@ -132,15 +128,11 @@ describe('Path resource integration tests', async () => { it('Supports tildy for home', { timeout: 300000 }, async () => { await fs.mkdir(path.resolve(os.homedir(), 'temp', 'dir'), { recursive: true }); - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'path', path: '~/temp/dir', } ]); }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/ssh/ssh-add.test.ts b/test/ssh/ssh-add.test.ts index d9dc4e5..9742cad 100644 --- a/test/ssh/ssh-add.test.ts +++ b/test/ssh/ssh-add.test.ts @@ -3,12 +3,6 @@ import { PluginTester } from 'codify-plugin-test'; import path from 'node:path'; describe('Ssh Add tests', () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) - // Currently having a hard time testing this because it cannot start the agent and keep it alive via ssh. it('Can generate and then add key to ssh-agent + keychain', { timeout: 300000 }, async () => { // await plugin.fullTest([ @@ -34,8 +28,4 @@ describe('Ssh Add tests', () => { // ]) }) - afterEach(() => { - plugin.kill(); - }) - }) diff --git a/test/ssh/ssh-config.test.ts b/test/ssh/ssh-config.test.ts index cc79ad4..6068234 100644 --- a/test/ssh/ssh-config.test.ts +++ b/test/ssh/ssh-config.test.ts @@ -5,15 +5,11 @@ import * as fs from 'node:fs/promises'; import * as os from 'node:os'; describe('Ssh config tests', () => { - let plugin: PluginTester; - - beforeEach(async () => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can generate a new .ssh/config file if it doesn\'t exist', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'ssh-config', hosts: [ @@ -52,7 +48,7 @@ Host github.com }) it('Can modify an existing file', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'ssh-config', hosts: [ @@ -97,7 +93,7 @@ Host github.com }) it('Can match similar host names + destroy a .ssh/config file by renaming it', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'ssh-config', hosts: [ @@ -138,8 +134,4 @@ Host new.com_2 } }) }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/ssh/ssh-key.test.ts b/test/ssh/ssh-key.test.ts index ecd37e2..85efc6a 100644 --- a/test/ssh/ssh-key.test.ts +++ b/test/ssh/ssh-key.test.ts @@ -6,14 +6,10 @@ import os from 'node:os'; import { execSync } from 'child_process'; describe('Ssh key tests', () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can generate and delete an ssh key', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'ssh-key', passphrase: '', @@ -35,7 +31,7 @@ describe('Ssh key tests', () => { testModify: { modifiedConfigs: [{ type: 'ssh-key', - comment: 'my comment', + comment: 'commenting', }], validateModify: (plans) => { expect(plans[0]).toMatchObject({ @@ -44,7 +40,7 @@ describe('Ssh key tests', () => { "parameters": expect.arrayContaining([ expect.objectContaining({ "name": "comment", - "newValue": "my comment", + "newValue": "commenting", "operation": "modify" }) ]) @@ -52,7 +48,7 @@ describe('Ssh key tests', () => { const location = path.resolve(os.homedir(), '.ssh', 'id_ed25519.pub'); const key = fs.readFileSync(location).toString('utf-8'); - expect(key).to.include('my comment'); // updated comment + expect(key).to.include('commenting'); // updated comment } }, validateDestroy: () => { @@ -63,7 +59,7 @@ describe('Ssh key tests', () => { }) it('Can generate and delete a custom key', { timeout: 300000 }, async () => { - await plugin.fullTest([ + await PluginTester.fullTest(pluginPath, [ { type: 'ssh-key', keyType: "rsa", @@ -84,9 +80,4 @@ describe('Ssh key tests', () => { } }) }) - - afterEach(() => { - plugin.kill(); - }) - }) diff --git a/test/terraform/terraform.test.ts b/test/terraform/terraform.test.ts index f500aaa..3e888b9 100644 --- a/test/terraform/terraform.test.ts +++ b/test/terraform/terraform.test.ts @@ -3,15 +3,12 @@ import { PluginTester } from 'codify-plugin-test'; import * as path from 'node:path'; import { execSync } from 'child_process'; -describe('Terraform tests', async () => { - let plugin: PluginTester; +const pluginPath = path.resolve('./src/index.ts'); - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) +describe('Terraform tests', async () => { it('Can install the latest terraform in the default location', { timeout: 300000 }, async () => { - await plugin.fullTest([{ + await PluginTester.fullTest(pluginPath, [{ type: "terraform" }], { validateApply: () => { @@ -24,7 +21,7 @@ describe('Terraform tests', async () => { }) it('Can install the latest terraform in a custom location', { timeout: 300000 }, async () => { - await plugin.fullTest([{ + await PluginTester.fullTest(pluginPath, [{ type: "terraform", directory: '~/path/to/bin' }], { @@ -38,7 +35,7 @@ describe('Terraform tests', async () => { }) it('Can install the a custom version of Terraform', { timeout: 300000 }, async () => { - await plugin.fullTest([{ + await PluginTester.fullTest(pluginPath, [{ type: "terraform", version: '1.4.2', }], { @@ -53,7 +50,7 @@ describe('Terraform tests', async () => { }) it('Can upgrade the version of Terraform', { timeout: 300000 }, async () => { - await plugin.fullTest([{ + await PluginTester.fullTest(pluginPath, [{ type: "terraform", version: '1.5.2', }], { @@ -65,8 +62,4 @@ describe('Terraform tests', async () => { } }) }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/vscode/vscode.test.ts b/test/vscode/vscode.test.ts index 8f018b9..670de2c 100644 --- a/test/vscode/vscode.test.ts +++ b/test/vscode/vscode.test.ts @@ -4,14 +4,10 @@ import * as path from 'node:path'; import fs from 'node:fs/promises'; describe('Vscode integration tests', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) + const pluginPath = path.resolve('./src/index.ts'); it('Can install vscode', { timeout: 300000 }, async () => { - await plugin.fullTest([{ + await PluginTester.fullTest(pluginPath, [{ type: 'vscode', directory: '/Applications' }], { @@ -26,8 +22,4 @@ describe('Vscode integration tests', async () => { } }); }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/test/xcode-tools/xcode-tools.test.ts b/test/xcode-tools/xcode-tools.test.ts index 9a6a68b..0be21bd 100644 --- a/test/xcode-tools/xcode-tools.test.ts +++ b/test/xcode-tools/xcode-tools.test.ts @@ -1,27 +1,19 @@ -import { afterEach, beforeEach, describe, it } from 'vitest'; +import { describe, it } from 'vitest'; import { PluginTester } from 'codify-plugin-test'; import * as path from 'node:path'; -describe('XCode tools install tests', async () => { - let plugin: PluginTester; - - beforeEach(() => { - plugin = new PluginTester(path.resolve('./src/index.ts')); - }) +const pluginPath = path.resolve('./src/index.ts'); +describe('XCode tools install tests', async () => { it('Can uninstall xcode tools', { timeout: 300000 }, async () => { - await plugin.uninstall([{ + await PluginTester.uninstall(pluginPath, [{ type: 'xcode-tools' }]) }) it('Can install xcode tools', { timeout: 300000 }, async () => { - await plugin.fullTest([{ + await PluginTester.fullTest(pluginPath, [{ type: 'xcode-tools', }]); }) - - afterEach(() => { - plugin.kill(); - }) }) diff --git a/vitest.config.ts b/vitest.config.ts index 8795c3c..388d618 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,9 +2,10 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { + pool: 'forks', onConsoleLog: (log) => { - console.log(log); + process.stdout.write(log); }, watch: false, }, -}) \ No newline at end of file +})