Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions docs/resources/(resources)/go/goenv.mdx
Original file line number Diff line number Diff line change
@@ -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 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:

- **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 <version>`). 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.
- 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.
4 changes: 4 additions & 0 deletions docs/resources/(resources)/go/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "go",
"pages": ["goenv"]
}
1 change: 1 addition & 0 deletions docs/resources/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -73,6 +74,7 @@ runPlugin(Plugin.create(
new TerraformResource(),
new NvmResource(),
new JenvResource(),
new GoenvResource(),
new PgcliResource(),
new VscodeResource(),
new GitRepositoryResource(),
Expand Down
40 changes: 40 additions & 0 deletions src/resources/go/goenv/global-parameter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { getPty, ParameterSetting, SpawnStatus, StatefulParameter } from '@codifycli/plugin-core';

import { GoenvConfig } from './goenv.js';

export class GoenvGlobalParameter extends StatefulParameter<GoenvConfig, string> {
getSettings(): ParameterSetting {
return {
type: 'version',
};
}

override async refresh(): Promise<string | null> {
const $ = getPty();
const { data, status } = await $.spawnSafe('goenv global');
if (status === SpawnStatus.ERROR) {
return null;
}
return parseGlobalVersion(data);
}

override async add(valueToAdd: string): Promise<void> {
const $ = getPty();
await $.spawn(`goenv global ${valueToAdd}`, { interactive: true });
}

override async modify(newValue: string): Promise<void> {
const $ = getPty();
await $.spawn(`goenv global ${newValue}`, { interactive: true });
}

override async remove(): Promise<void> {
const $ = getPty();
await $.spawn('goenv global system', { interactive: true });
}
}

function parseGlobalVersion(output: string): string | null {
const version = output.trim();
return version === 'system' ? null : version;
}
28 changes: 28 additions & 0 deletions src/resources/go/goenv/go-versions-parameter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ArrayStatefulParameter, getPty } from '@codifycli/plugin-core';

import { GoenvConfig } from './goenv.js';

export class GoVersionsParameter extends ArrayStatefulParameter<GoenvConfig, string> {
override async refresh(_desired: string[] | null): Promise<string[] | null> {
const $ = getPty();
const { data } = await $.spawnSafe('goenv versions --bare');
return parseInstalledVersions(data);
}

override async addItem(version: string): Promise<void> {
const $ = getPty();
await $.spawn(`goenv install ${version}`, { interactive: true });
}

override async removeItem(version: string): Promise<void> {
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);
}
155 changes: 155 additions & 0 deletions src/resources/go/goenv/goenv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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<typeof schema>;

const defaultConfig: Partial<GoenvConfig> = {
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<GoenvConfig> {
getSettings(): ResourceSettings<GoenvConfig> {
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<Partial<GoenvConfig> | 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<void> {
if (Utils.isMacOS()) {
await installOnMacOS();
} else {
await installOnLinux();
}
}

override async destroy(): Promise<void> {
if (Utils.isMacOS()) {
await uninstallOnMacOS();
} else {
await uninstallOnLinux();
}
}
}

async function installOnMacOS(): Promise<void> {
const $ = getPty();
await $.spawn('brew install goenv', {
interactive: true,
env: { HOMEBREW_NO_AUTO_UPDATE: '1' },
});
await FileUtils.addToShellRc(GOENV_INIT);
}

async function installOnLinux(): Promise<void> {
await Utils.installViaPkgMgr('git');

const $ = getPty();
await $.spawn(`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<void> {
const $ = getPty();
await $.spawnSafe('brew uninstall goenv', {
env: { HOMEBREW_NO_AUTO_UPDATE: '1' },
});
await removeGoenvFromShellRc([GOENV_INIT]);
}

async function uninstallOnLinux(): Promise<void> {
const $ = getPty();
await $.spawnSafe(`rm -rf ${GOENV_ROOT}`);
await removeGoenvFromShellRc([GOENV_ROOT_EXPORT, GOENV_PATH_EXPORT, GOENV_INIT]);
}

async function removeGoenvFromShellRc(lines: string[]): Promise<void> {
const shellRc = Utils.getPrimaryShellRc();
if (!(await FileUtils.fileExists(shellRc))) {
return;
}
for (const line of lines) {
await FileUtils.removeLineFromShellRc(line);
}
}
43 changes: 43 additions & 0 deletions test/go/goenv.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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');

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();
if (fs.existsSync(shellRc)) {
const shellRcContents = fs.readFileSync(shellRc, 'utf-8');
expect(shellRcContents).not.toContain('goenv init');
}
},
});
});
});
Loading