From 0cb200ab4614a49335d79d085e12cb668eaa4abb Mon Sep 17 00:00:00 2001 From: kevinwang5658 <20214115+kevinwang5658@users.noreply.github.com> Date: Wed, 13 May 2026 20:11:11 +0000 Subject: [PATCH 1/3] feat: Add go-version-manager resource (auto-generated from issue #34) --- docs/resources/(resources)/go/goenv.mdx | 56 +++++++ docs/resources/(resources)/go/meta.json | 4 + docs/resources/index.mdx | 1 + src/index.ts | 2 + src/resources/go/goenv/global-parameter.ts | 40 +++++ .../go/goenv/go-versions-parameter.ts | 28 ++++ src/resources/go/goenv/goenv.ts | 157 ++++++++++++++++++ test/go/goenv.test.ts | 39 +++++ 8 files changed, 327 insertions(+) create mode 100644 docs/resources/(resources)/go/goenv.mdx create mode 100644 docs/resources/(resources)/go/meta.json create mode 100644 src/resources/go/goenv/global-parameter.ts create mode 100644 src/resources/go/goenv/go-versions-parameter.ts create mode 100644 src/resources/go/goenv/goenv.ts create mode 100644 test/go/goenv.test.ts diff --git a/docs/resources/(resources)/go/goenv.mdx b/docs/resources/(resources)/go/goenv.mdx new file mode 100644 index 00000000..ed9e502e --- /dev/null +++ b/docs/resources/(resources)/go/goenv.mdx @@ -0,0 +1,56 @@ +--- +title: goenv +description: A reference page for the goenv resource +--- + +The goenv resource installs [goenv](https://github.com/go-nv/goenv), a Go version manager modelled after pyenv and rbenv that lets you install and switch between multiple Go versions. On macOS it is installed via Homebrew; on Linux it is cloned from GitHub into `~/.goenv`. + +## Parameters: + +- **goVersions**: *(array[string])* Go versions to install via goenv (e.g. `["1.22.0", "1.21.5"]`). Versions must match those available from `goenv install --list`. Codify adds missing versions and removes versions that are no longer listed. + +- **global**: *(string)* The Go version to set as the global default (equivalent to running `goenv global `). The version must be in `goVersions` or already installed on the system. + +## Example usage: + +### Install goenv with a single Go version + +```json title="codify.jsonc" +[ + { + "type": "goenv", + "goVersions": ["1.22.0"], + "global": "1.22.0" + } +] +``` + +### Install goenv with multiple Go versions + +```json title="codify.jsonc" +[ + { + "type": "goenv", + "goVersions": ["1.21.0", "1.22.0", "1.23.0"], + "global": "1.23.0" + } +] +``` + +### Install goenv without installing any Go versions + +```json title="codify.jsonc" +[ + { + "type": "goenv" + } +] +``` + +## Notes: + +- On macOS, Homebrew must be installed before applying the goenv resource. The [homebrew](/docs/resources/package-managers/homebrew) resource can install it. The `mercurial` and `bison` packages are installed automatically as part of the goenv install. +- On Linux, several system dependencies are required (`curl`, `git`, `mercurial`, `make`, `binutils`, `bison`, `gcc`, `build-essential`). Codify installs these automatically via the system package manager before cloning goenv. +- On Linux, goenv is cloned to `~/.goenv` and added to `PATH` in your shell RC file. On macOS, Homebrew manages the installation path. +- After applying, open a new terminal session (or source your shell RC file) for the `goenv` shims and `GOROOT`/`GOPATH` environment variables to become active. +- To find available Go versions, run `goenv install --list` after installing goenv. diff --git a/docs/resources/(resources)/go/meta.json b/docs/resources/(resources)/go/meta.json new file mode 100644 index 00000000..a4a2b7c7 --- /dev/null +++ b/docs/resources/(resources)/go/meta.json @@ -0,0 +1,4 @@ +{ + "title": "go", + "pages": ["goenv"] +} diff --git a/docs/resources/index.mdx b/docs/resources/index.mdx index 6373739f..a812bdb4 100644 --- a/docs/resources/index.mdx +++ b/docs/resources/index.mdx @@ -33,6 +33,7 @@ Install and manage multiple versions of programming languages: - **[rbenv](/docs/resources/ruby/rbenv)** - Ruby version management - **[jenv](/docs/resources/jenv)** - Java version management - **[asdf](/docs/resources/asdf/asdf)** - Universal version manager for multiple languages +- **[goenv](/docs/resources/go/goenv)** - Go version management ### Programming Languages & Tools diff --git a/src/index.ts b/src/index.ts index b7f1cec1..5f68f6a3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import { AsdfPluginResource } from './resources/asdf/asdf-plugin.js'; import { AwsCliResource } from './resources/aws-cli/cli/aws-cli.js'; import { AwsProfileResource } from './resources/aws-cli/profile/aws-profile.js'; import { DnfResource } from './resources/dnf/dnf.js'; +import { GoenvResource } from './resources/go/goenv/goenv.js'; import { DockerResource } from './resources/docker/docker.js'; import { FileResource } from './resources/file/file.js'; import { RemoteFileResource } from './resources/file/remote-file.js'; @@ -73,6 +74,7 @@ runPlugin(Plugin.create( new TerraformResource(), new NvmResource(), new JenvResource(), + new GoenvResource(), new PgcliResource(), new VscodeResource(), new GitRepositoryResource(), diff --git a/src/resources/go/goenv/global-parameter.ts b/src/resources/go/goenv/global-parameter.ts new file mode 100644 index 00000000..22170bee --- /dev/null +++ b/src/resources/go/goenv/global-parameter.ts @@ -0,0 +1,40 @@ +import { getPty, ParameterSetting, SpawnStatus, StatefulParameter } from '@codifycli/plugin-core'; + +import { GoenvConfig } from './goenv.js'; + +export class GoenvGlobalParameter extends StatefulParameter { + getSettings(): ParameterSetting { + return { + type: 'version', + }; + } + + override async refresh(): Promise { + const $ = getPty(); + const { data, status } = await $.spawnSafe('goenv global'); + if (status === SpawnStatus.ERROR) { + return null; + } + return parseGlobalVersion(data); + } + + override async add(valueToAdd: string): Promise { + const $ = getPty(); + await $.spawn(`goenv global ${valueToAdd}`, { interactive: true }); + } + + override async modify(newValue: string): Promise { + const $ = getPty(); + await $.spawn(`goenv global ${newValue}`, { interactive: true }); + } + + override async remove(): Promise { + const $ = getPty(); + await $.spawn('goenv global system', { interactive: true }); + } +} + +function parseGlobalVersion(output: string): string | null { + const version = output.trim(); + return version === 'system' ? null : version; +} diff --git a/src/resources/go/goenv/go-versions-parameter.ts b/src/resources/go/goenv/go-versions-parameter.ts new file mode 100644 index 00000000..871152f3 --- /dev/null +++ b/src/resources/go/goenv/go-versions-parameter.ts @@ -0,0 +1,28 @@ +import { ArrayStatefulParameter, getPty } from '@codifycli/plugin-core'; + +import { GoenvConfig } from './goenv.js'; + +export class GoVersionsParameter extends ArrayStatefulParameter { + override async refresh(_desired: string[] | null): Promise { + const $ = getPty(); + const { data } = await $.spawnSafe('goenv versions --bare'); + return parseInstalledVersions(data); + } + + override async addItem(version: string): Promise { + const $ = getPty(); + await $.spawn(`goenv install ${version}`, { interactive: true }); + } + + override async removeItem(version: string): Promise { + const $ = getPty(); + await $.spawn(`goenv uninstall --force ${version}`, { interactive: true }); + } +} + +function parseInstalledVersions(output: string): string[] { + return output + .split('\n') + .map((line) => line.trim()) + .filter(Boolean); +} diff --git a/src/resources/go/goenv/goenv.ts b/src/resources/go/goenv/goenv.ts new file mode 100644 index 00000000..9a40d6ce --- /dev/null +++ b/src/resources/go/goenv/goenv.ts @@ -0,0 +1,157 @@ +import { + ExampleConfig, + FileUtils, + getPty, + Resource, + ResourceSettings, + SpawnStatus, + Utils, + z, +} from '@codifycli/plugin-core'; +import { OS } from '@codifycli/schemas'; +import os from 'node:os'; +import path from 'node:path'; + +import { GoenvGlobalParameter } from './global-parameter.js'; +import { GoVersionsParameter } from './go-versions-parameter.js'; + +const GOENV_ROOT = path.join(os.homedir(), '.goenv'); +const GOENV_ROOT_EXPORT = 'export GOENV_ROOT="$HOME/.goenv"'; +const GOENV_PATH_EXPORT = 'export PATH="$GOENV_ROOT/bin:$PATH"'; +const GOENV_INIT = 'eval "$(goenv init -)"'; + +const schema = z + .object({ + goVersions: z + .array(z.string()) + .describe('Go versions to install via goenv (e.g. ["1.22.0", "1.21.5"])') + .optional(), + global: z + .string() + .describe('The global Go version set by goenv.') + .optional(), + }) + .describe('goenv resource — install and manage multiple Go versions'); + +export type GoenvConfig = z.infer; + +const defaultConfig: Partial = { + goVersions: [], +}; + +const exampleBasic: ExampleConfig = { + title: 'Install goenv with a global Go version', + description: 'Install goenv, download Go 1.22.0, and set it as the default global version.', + configs: [ + { + type: 'goenv', + goVersions: ['1.22.0'], + global: '1.22.0', + }, + ], +}; + +const exampleMultiVersion: ExampleConfig = { + title: 'Manage multiple Go versions', + description: 'Install goenv with multiple Go versions for cross-version testing, setting the latest as the default.', + configs: [ + { + type: 'goenv', + goVersions: ['1.21.0', '1.22.0', '1.23.0'], + global: '1.23.0', + }, + ], +}; + +export class GoenvResource extends Resource { + getSettings(): ResourceSettings { + return { + id: 'goenv', + defaultConfig, + exampleConfigs: { + example1: exampleBasic, + example2: exampleMultiVersion, + }, + operatingSystems: [OS.Darwin, OS.Linux], + schema, + parameterSettings: { + goVersions: { type: 'stateful', definition: new GoVersionsParameter(), order: 1 }, + global: { type: 'stateful', definition: new GoenvGlobalParameter(), order: 2 }, + }, + }; + } + + override async refresh(): Promise | null> { + const $ = getPty(); + const { status } = await $.spawnSafe('goenv --version'); + if (status === SpawnStatus.SUCCESS) return {}; + + // goenv may be installed via git clone but not yet on PATH in the current session + const { status: binStatus } = await $.spawnSafe( + `test -f ${path.join(GOENV_ROOT, 'bin', 'goenv')}` + ); + return binStatus === SpawnStatus.SUCCESS ? {} : null; + } + + override async create(): Promise { + if (Utils.isMacOS()) { + await installOnMacOS(); + } else { + await installOnLinux(); + } + } + + override async destroy(): Promise { + if (Utils.isMacOS()) { + await uninstallOnMacOS(); + } else { + await uninstallOnLinux(); + } + } +} + +async function installOnMacOS(): Promise { + const $ = getPty(); + await $.spawn('brew install goenv mercurial bison', { + interactive: true, + env: { HOMEBREW_NO_AUTO_UPDATE: '1' }, + }); + await FileUtils.addToShellRc(GOENV_INIT); +} + +async function installOnLinux(): Promise { + await Utils.installViaPkgMgr( + 'curl git mercurial make binutils bison gcc build-essential' + ); + + const $ = getPty(); + await $.spawnSafe(`git clone https://github.com/go-nv/goenv.git ${GOENV_ROOT}`, { + interactive: true, + }); + + await FileUtils.addAllToShellRc([GOENV_ROOT_EXPORT, GOENV_PATH_EXPORT, GOENV_INIT]); +} + +async function uninstallOnMacOS(): Promise { + const $ = getPty(); + await $.spawnSafe('brew uninstall goenv', { + env: { HOMEBREW_NO_AUTO_UPDATE: '1' }, + }); + await removeGoenvFromShellRc([GOENV_INIT]); +} + +async function uninstallOnLinux(): Promise { + const $ = getPty(); + await $.spawnSafe(`rm -rf ${GOENV_ROOT}`); + await removeGoenvFromShellRc([GOENV_ROOT_EXPORT, GOENV_PATH_EXPORT, GOENV_INIT]); +} + +async function removeGoenvFromShellRc(lines: string[]): Promise { + const shellRc = Utils.getPrimaryShellRc(); + if (!(await FileUtils.fileExists(shellRc))) { + return; + } + for (const line of lines) { + await FileUtils.removeLineFromShellRc(line); + } +} diff --git a/test/go/goenv.test.ts b/test/go/goenv.test.ts new file mode 100644 index 00000000..b215d1d2 --- /dev/null +++ b/test/go/goenv.test.ts @@ -0,0 +1,39 @@ +import { SpawnStatus } from '@codifycli/plugin-core'; +import { PluginTester, testSpawn } from '@codifycli/plugin-test'; +import { describe, expect, it } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +import { TestUtils } from '../test-utils.js'; + +describe('Goenv resource integration tests', () => { + const pluginPath = path.resolve('./src/index.ts'); + + it('Installs goenv, installs a Go version, and sets a global', { timeout: 600000 }, async () => { + await PluginTester.fullTest(pluginPath, [ + { + type: 'goenv', + goVersions: ['1.22.0'], + global: '1.22.0', + }, + ], { + validateApply: async () => { + const goenvCheck = await testSpawn('goenv --version'); + expect(goenvCheck.status).toBe(SpawnStatus.SUCCESS); + + const { data: versions } = await testSpawn('goenv versions'); + expect(versions).toContain('1.22.0'); + + const { data: globalVersion } = await testSpawn('goenv global'); + expect(globalVersion.trim()).toBe('1.22.0'); + }, + validateDestroy: () => { + const shellRc = TestUtils.getPrimaryShellRc(); + if (fs.existsSync(shellRc)) { + const shellRcContents = fs.readFileSync(shellRc, 'utf-8'); + expect(shellRcContents).not.toContain('goenv init'); + } + }, + }); + }); +}); From f6ed25b3cae24a73f3e1c82b7cfd89115fa62201 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Wed, 13 May 2026 21:39:00 -0400 Subject: [PATCH 2/3] fix: fix for linux install. Remove bison and mercurial requirements --- package.json | 2 +- src/resources/go/goenv/goenv.ts | 8 +++----- test/go/goenv.test.ts | 4 ++++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index c8721afd..fd66d017 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "default", - "version": "1.1.0-beta.18", + "version": "1.1.0-beta.20", "description": "Default plugin for Codify - provides 50+ declarative resources for managing development tools and system configuration across macOS and Linux", "main": "dist/index.js", "scripts": { diff --git a/src/resources/go/goenv/goenv.ts b/src/resources/go/goenv/goenv.ts index 9a40d6ce..e7d4e3ae 100644 --- a/src/resources/go/goenv/goenv.ts +++ b/src/resources/go/goenv/goenv.ts @@ -112,7 +112,7 @@ export class GoenvResource extends Resource { async function installOnMacOS(): Promise { const $ = getPty(); - await $.spawn('brew install goenv mercurial bison', { + await $.spawn('brew install goenv', { interactive: true, env: { HOMEBREW_NO_AUTO_UPDATE: '1' }, }); @@ -120,12 +120,10 @@ async function installOnMacOS(): Promise { } async function installOnLinux(): Promise { - await Utils.installViaPkgMgr( - 'curl git mercurial make binutils bison gcc build-essential' - ); + await Utils.installViaPkgMgr('git'); const $ = getPty(); - await $.spawnSafe(`git clone https://github.com/go-nv/goenv.git ${GOENV_ROOT}`, { + await $.spawn(`git clone https://github.com/go-nv/goenv.git ${GOENV_ROOT}`, { interactive: true, }); diff --git a/test/go/goenv.test.ts b/test/go/goenv.test.ts index b215d1d2..e112b4de 100644 --- a/test/go/goenv.test.ts +++ b/test/go/goenv.test.ts @@ -26,6 +26,10 @@ describe('Goenv resource integration tests', () => { const { data: globalVersion } = await testSpawn('goenv global'); expect(globalVersion.trim()).toBe('1.22.0'); + + const { data: goVersion, status: goStatus } = await testSpawn('go version'); + expect(goStatus).toBe(SpawnStatus.SUCCESS); + expect(goVersion).toContain('go1.22.0'); }, validateDestroy: () => { const shellRc = TestUtils.getPrimaryShellRc(); From cfa01f5723c0f19832a0d0d7965c872b9a17b94c Mon Sep 17 00:00:00 2001 From: kevinwang Date: Wed, 13 May 2026 21:42:04 -0400 Subject: [PATCH 3/3] feat: Updated documentation to match --- docs/resources/(resources)/go/goenv.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/resources/(resources)/go/goenv.mdx b/docs/resources/(resources)/go/goenv.mdx index ed9e502e..91a8a97c 100644 --- a/docs/resources/(resources)/go/goenv.mdx +++ b/docs/resources/(resources)/go/goenv.mdx @@ -3,7 +3,7 @@ title: goenv description: A reference page for the goenv resource --- -The goenv resource installs [goenv](https://github.com/go-nv/goenv), a Go version manager modelled after pyenv and rbenv that lets you install and switch between multiple Go versions. On macOS it is installed via Homebrew; on Linux it is cloned from GitHub into `~/.goenv`. +The goenv resource installs [goenv](https://github.com/go-nv/goenv), a Go version manager modeled after pyenv and rbenv that lets you install and switch between multiple Go versions. On macOS it is installed via Homebrew; on Linux it is cloned from GitHub into `~/.goenv`. ## Parameters: @@ -49,8 +49,8 @@ The goenv resource installs [goenv](https://github.com/go-nv/goenv), a Go versio ## Notes: -- On macOS, Homebrew must be installed before applying the goenv resource. The [homebrew](/docs/resources/package-managers/homebrew) resource can install it. The `mercurial` and `bison` packages are installed automatically as part of the goenv install. -- On Linux, several system dependencies are required (`curl`, `git`, `mercurial`, `make`, `binutils`, `bison`, `gcc`, `build-essential`). Codify installs these automatically via the system package manager before cloning goenv. +- On macOS, Homebrew must be installed before applying the goenv resource. The [homebrew](/docs/resources/package-managers/homebrew) resource can install it. +- On Linux, `git` is required and installed automatically via the system package manager before cloning goenv. - On Linux, goenv is cloned to `~/.goenv` and added to `PATH` in your shell RC file. On macOS, Homebrew manages the installation path. - After applying, open a new terminal session (or source your shell RC file) for the `goenv` shims and `GOROOT`/`GOPATH` environment variables to become active. - To find available Go versions, run `goenv install --list` after installing goenv.