Skip to content

Commit c05c297

Browse files
committed
feat: Added virtualenv and virtualenv-project
1 parent 3889e2e commit c05c297

File tree

9 files changed

+225
-4
lines changed

9 files changed

+225
-4
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"ajv": "^8.12.0",
2424
"ajv-formats": "^2.1.1",
2525
"semver": "^7.6.0",
26-
"codify-plugin-lib": "1.0.170",
26+
"codify-plugin-lib": "1.0.171",
2727
"codify-schemas": "1.0.63",
2828
"chalk": "^5.3.0",
2929
"debug": "^4.3.4",
@@ -50,7 +50,7 @@
5050
"@types/debug": "4.1.12",
5151
"@types/plist": "^3.0.5",
5252
"@types/lodash.isequal": "^4.5.8",
53-
"codify-plugin-test": "0.0.49",
53+
"codify-plugin-test": "0.0.50",
5454
"commander": "^12.1.0",
5555
"eslint": "^8.51.0",
5656
"eslint-config-oclif": "^5",

src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { JenvResource } from './resources/java/jenv/jenv.js';
1616
import { NvmResource } from './resources/node/nvm/nvm.js';
1717
import { PgcliResource } from './resources/pgcli/pgcli.js';
1818
import { PyenvResource } from './resources/python/pyenv/pyenv.js';
19+
import { Virtualenv } from './resources/python/virtualenv/virtualenv.js';
20+
import { VirtualenvProject } from './resources/python/virtualenv/virtualenv-project.js';
1921
import { ActionResource } from './resources/scripting/action.js';
2022
import { FileResource } from './resources/scripting/file.js';
2123
import { AliasResource } from './resources/shell/alias/alias-resource.js';
@@ -55,6 +57,8 @@ runPlugin(Plugin.create(
5557
new SshConfigFileResource(),
5658
new SshAddResource(),
5759
new ActionResource(),
58-
new FileResource()
60+
new FileResource(),
61+
new Virtualenv(),
62+
new VirtualenvProject()
5963
])
6064
)

src/resources/homebrew/formulae-parameter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class FormulaeParameter extends StatefulParameter<HomebrewConfig, string[
5555
return;
5656
}
5757

58-
const result = await codifySpawn(`SUDO_ASKPASS=${SUDO_ASKPASS_PATH} brew install --formula ${formulae.join(' ')}`)
58+
const result = await codifySpawn(`HOMEBREW_NO_AUTO_UPDATE=1 SUDO_ASKPASS=${SUDO_ASKPASS_PATH} brew install --formula ${formulae.join(' ')}`)
5959

6060
if (result.status === SpawnStatus.SUCCESS) {
6161
console.log(`Installed formula: ${formulae.join(' ')}`);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"$id": "https://www.codifycli.com/virtualenv-project.json",
4+
"title": "Virtualenv project resource",
5+
"type": "object",
6+
"description": "Install and manage local packages for a project with virtualenv",
7+
"properties": {
8+
"dest": {
9+
"type": "string",
10+
"description": "The directory to create virtualenv at"
11+
},
12+
"python": {
13+
"type": "string",
14+
"description": "A path to the python interpreter to use to create the virtualenv. This defaults to the global python3 version."
15+
},
16+
"noVcsIgnore": {
17+
"type": "boolean",
18+
"description": "Don't create VCS ignore directive in the destination directory (default: False)"
19+
},
20+
"systemSitePackages": {
21+
"type": "boolean",
22+
"description": "Give the virtual environment access to the system site-packages dir (default: False)"
23+
},
24+
"symlinks": {
25+
"type": "boolean",
26+
"description": " Try to use symlinks rather than copies (default: True)"
27+
},
28+
"cwd": {
29+
"type": "string",
30+
"description": "The cwd to create virtualenv from. This allows a relative path to be used for dest."
31+
},
32+
"automaticallyInstallRequirementsTxt": {
33+
"type": "boolean",
34+
"description": "If an requirements.txt is available in the cwd, automatically install it when a virtual env is first created."
35+
}
36+
},
37+
"additionalProperties": false,
38+
"required": ["dest"]
39+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import {
2+
CreatePlan, DestroyPlan, ModifyPlan, ParameterChange, RefreshContext, Resource,
3+
ResourceSettings,
4+
getPty
5+
} from 'codify-plugin-lib';
6+
import { ResourceConfig } from 'codify-schemas';
7+
import fs from 'node:fs/promises';
8+
import path from 'node:path';
9+
10+
import { codifySpawn } from '../../../utils/codify-spawn.js';
11+
import { FileUtils } from '../../../utils/file-utils.js';
12+
import schema from './virtualenv-project-schema.json';
13+
14+
export interface VirtualenvProjectConfig extends ResourceConfig {
15+
dest: string;
16+
python?: string;
17+
noVcsIgnore?: boolean;
18+
systemSitePackages?: boolean;
19+
symlinks?: boolean;
20+
cwd?: string;
21+
automaticallyInstallRequirementsTxt?: boolean;
22+
}
23+
24+
// TODO: Remove path.resolve from cwd.
25+
export class VirtualenvProject extends Resource<VirtualenvProjectConfig> {
26+
27+
getSettings(): ResourceSettings<VirtualenvProjectConfig> {
28+
return {
29+
id: 'virtualenv-project',
30+
schema,
31+
parameterSettings: {
32+
dest: { type: 'directory' },
33+
python: { type: 'string', setting: true },
34+
noVcsIgnore: { type: 'boolean', setting: true },
35+
systemSitePackages: { type: 'boolean', setting: true },
36+
symlinks: { type: 'boolean', setting: true },
37+
cwd: { type: 'directory', setting: true },
38+
automaticallyInstallRequirementsTxt: { type: 'boolean', setting: true },
39+
},
40+
allowMultiple: {
41+
identifyingParameters: ['dest'],
42+
},
43+
dependencies: ['virtualenv', 'homebrew', 'pyenv']
44+
}
45+
}
46+
47+
async refresh(parameters: Partial<VirtualenvProjectConfig>, context: RefreshContext<VirtualenvProjectConfig>): Promise<Partial<VirtualenvProjectConfig> | Partial<VirtualenvProjectConfig>[] | null> {
48+
const dir = parameters.cwd
49+
? path.join(parameters.cwd, parameters.dest!)
50+
: parameters.dest!;
51+
52+
if (!(await FileUtils.exists(dir))) {
53+
return null;
54+
}
55+
56+
if (!(await FileUtils.exists(path.join(dir, 'pyvenv.cfg')))) {
57+
return null;
58+
}
59+
60+
return parameters;
61+
}
62+
63+
async create(plan: CreatePlan<VirtualenvProjectConfig>): Promise<void> {
64+
const desired = plan.desiredConfig;
65+
66+
const command = 'virtualenv ' +
67+
(desired.python ? `-p ${desired.python} ` : '-p $(which python3) ') +
68+
(desired.noVcsIgnore ? `--no-vcs-ignore=${desired.noVcsIgnore} ` : '') +
69+
(desired.systemSitePackages ? `--system-site-packages=${desired.systemSitePackages} ` : '') +
70+
(desired.symlinks ? `--symlinks=${desired.symlinks} ` : '') +
71+
desired.dest;
72+
73+
await codifySpawn(command, { cwd: desired.cwd ?? undefined });
74+
75+
if (desired.automaticallyInstallRequirementsTxt) {
76+
await codifySpawn(`source ${desired.dest}/bin/activate; pip install -r requirements.txt`, { cwd: desired.cwd });
77+
}
78+
}
79+
80+
async destroy(plan: DestroyPlan<VirtualenvProjectConfig>): Promise<void> {
81+
const current = plan.currentConfig;
82+
83+
const dir = current.cwd
84+
? path.join(current.cwd, current.dest!)
85+
: current.dest!;
86+
87+
await fs.rm(dir, { recursive: true, force: true });
88+
}
89+
90+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"$id": "https://www.codifycli.com/virtualenv.json",
4+
"title": "Virtualenv resource",
5+
"type": "object",
6+
"description": "Install and manage local packages with virtualenv",
7+
"properties": {},
8+
"additionalProperties": false
9+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { CreatePlan, DestroyPlan, RefreshContext, Resource, ResourceSettings, getPty } from 'codify-plugin-lib';
2+
import { ResourceConfig } from 'codify-schemas';
3+
4+
import { codifySpawn } from '../../../utils/codify-spawn.js';
5+
import { Utils } from '../../../utils/index.js';
6+
import schema from './virtualenv-schema.json';
7+
8+
export interface VirtualenvConfig extends ResourceConfig {}
9+
10+
export class Virtualenv extends Resource<VirtualenvConfig> {
11+
12+
getSettings(): ResourceSettings<VirtualenvConfig> {
13+
return {
14+
id: 'virtualenv',
15+
schema,
16+
dependencies: ['homebrew'],
17+
}
18+
}
19+
20+
async refresh(parameters: Partial<VirtualenvConfig>): Promise<Partial<VirtualenvConfig> | Partial<VirtualenvConfig>[] | null> {
21+
const pty = getPty()
22+
23+
const { status } = await pty.spawnSafe('which virtualenv');
24+
return status === 'error' ? null : parameters;
25+
}
26+
27+
async create(plan: CreatePlan<VirtualenvConfig>): Promise<void> {
28+
if (!(await Utils.isHomebrewInstalled())) {
29+
throw new Error('Homebrew must be installed in order to use the virtualenv resource');
30+
}
31+
32+
await codifySpawn('HOMEBREW_NO_AUTO_UPDATE=1 brew install virtualenv');
33+
}
34+
35+
async destroy(plan: DestroyPlan<VirtualenvConfig>): Promise<void> {
36+
const installLocation = await codifySpawn('which virtualenv');
37+
if (!installLocation.data.includes('homebrew')) {
38+
throw new Error('virtualenv resource was installed outside of Codify. Please uninstall manually and re-run Codify');
39+
}
40+
41+
await codifySpawn('HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall virtualenv');
42+
}
43+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { describe, it } from 'vitest';
2+
import { PluginTester } from 'codify-plugin-test';
3+
import path from 'node:path';
4+
import fs from 'node:fs/promises';
5+
6+
describe('Virtualenv project tests', () => {
7+
const pluginPath = path.resolve('./src/index.ts');
8+
9+
it('Can install and uninstall a virtualenv directory', { timeout: 300000 }, async () => {
10+
await fs.mkdir('Projects/python-project', { recursive: true });
11+
await fs.writeFile('Projects/python-project/requirements.txt', 'ffmpeg==1.4')
12+
13+
console.log(await fs.readdir('Projects/python-project'));
14+
15+
await PluginTester.fullTest(pluginPath, [
16+
{ type: 'homebrew' },
17+
{ type: 'virtualenv' },
18+
{ type: 'pyenv', pythonVersions: ['3.11'], global: '3.11' },
19+
{ type: 'virtualenv-project', dest: '.venv', cwd: 'Projects/python-project', automaticallyInstallRequirementsTxt: true },
20+
])
21+
})
22+
})

test/python/virtualenv.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { describe, it } from 'vitest';
2+
import { PluginTester } from 'codify-plugin-test';
3+
import path from 'node:path';
4+
5+
describe('Virtualenv tests', () => {
6+
const pluginPath = path.resolve('./src/index.ts');
7+
8+
it('Can install and uninstall virtualenv', { timeout: 300000 }, async () => {
9+
await PluginTester.fullTest(pluginPath, [
10+
{ type: 'homebrew' },
11+
{ type: 'virtualenv' }
12+
])
13+
})
14+
})

0 commit comments

Comments
 (0)