Skip to content

Commit df407a2

Browse files
Kevin/0.14.0 (#15)
* feat: Added venv resource * feat: WIP pip resource * fix: fixes for pip resource + tests * feat: Add pip-sync resource + added schema for pip * chore: add tests for pip-sync * fix: Bug fixes and improvements: - fixed bug with alias not parsing for values with equal signs - fixed formulae and casks not working with fully qualified name (tap + package name) - fixed formulae and casks uninstalling before installing - fixed pip resource not activating virtual environments properly - fixed pip-sync not activating virtual environments properly - fixed pip-sync not installing dependencies to a virtualenv - fixed xcode-tools not installing the latest version. * feat: Print cwd for codifySpawn commands * chore: bump version to 0.14.0
1 parent acd286e commit df407a2

File tree

18 files changed

+748
-16
lines changed

18 files changed

+748
-16
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "default",
3-
"version": "0.13.0",
3+
"version": "0.14.0",
44
"description": "",
55
"main": "dist/index.js",
66
"scripts": {

src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import { JenvResource } from './resources/java/jenv/jenv.js';
1717
import { NvmResource } from './resources/node/nvm/nvm.js';
1818
import { Pnpm } from './resources/node/pnpm/pnpm.js';
1919
import { PgcliResource } from './resources/pgcli/pgcli.js';
20+
import { Pip } from './resources/python/pip/pip.js';
21+
import { PipSync } from './resources/python/pip-sync/pip-sync.js';
2022
import { PyenvResource } from './resources/python/pyenv/pyenv.js';
23+
import { VenvProject } from './resources/python/venv/venv-project.js';
2124
import { Virtualenv } from './resources/python/virtualenv/virtualenv.js';
2225
import { VirtualenvProject } from './resources/python/virtualenv/virtualenv-project.js';
2326
import { ActionResource } from './resources/scripting/action.js';
@@ -63,6 +66,9 @@ runPlugin(Plugin.create(
6366
new Virtualenv(),
6467
new VirtualenvProject(),
6568
new Pnpm(),
66-
new WaitGithubSshKey()
69+
new WaitGithubSshKey(),
70+
new VenvProject(),
71+
new Pip(),
72+
new PipSync()
6773
])
6874
)

src/resources/homebrew/casks-parameter.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,25 @@ export class CasksParameter extends StatefulParameter<HomebrewConfig, string[]>
1313
return {
1414
type: 'array',
1515
isElementEqual(desired, current) {
16+
if (desired === current) {
17+
return true;
18+
}
19+
1620
// Handle the case where the name is fully qualified (tap + name)
1721
if (desired.includes('/')) {
1822
const formulaName = desired.split('/').at(-1);
1923
return formulaName === current;
2024
}
2125

22-
return desired === current;
26+
return false;
2327
},
2428
}
2529
}
2630

2731
async refresh(desired: string[], config: Partial<HomebrewConfig> | null): Promise<null | string[]> {
2832
const $ = getPty();
2933

30-
const caskQuery = await $.spawnSafe('brew list --casks -1')
34+
const caskQuery = await $.spawnSafe('brew list --casks -1 --full-name')
3135

3236
if (caskQuery.status === SpawnStatus.SUCCESS && caskQuery.data !== null && caskQuery.data !== undefined) {
3337
const installedCasks = caskQuery.data
@@ -66,8 +70,9 @@ export class CasksParameter extends StatefulParameter<HomebrewConfig, string[]>
6670
const casksToUninstall = previousValue.filter((x: string) => !newValue.includes(x));
6771

6872
const skipAlreadyInstalledCasks = plan.desiredConfig?.skipAlreadyInstalledCasks ?? plan.currentConfig?.skipAlreadyInstalledCasks;
69-
await this.installCasks(casksToInstall, skipAlreadyInstalledCasks!);
73+
7074
await this.uninstallCasks(casksToUninstall);
75+
await this.installCasks(casksToInstall, skipAlreadyInstalledCasks!);
7176
}
7277

7378
override async remove(valueToRemove: string[]): Promise<void> {
@@ -94,7 +99,7 @@ export class CasksParameter extends StatefulParameter<HomebrewConfig, string[]>
9499
return;
95100
}
96101

97-
const result = await codifySpawn(`SUDO_ASKPASS=${SUDO_ASKPASS_PATH} brew install --casks ${casksToInstall.join(' ')}`, { throws: false })
102+
const result = await codifySpawn(`HOMEBREW_NO_AUTO_UPDATE=1 SUDO_ASKPASS=${SUDO_ASKPASS_PATH} brew install --casks ${casksToInstall.join(' ')}`, { throws: false })
98103
if (result.status === SpawnStatus.SUCCESS) {
99104
// Casks can't detect if a program was installed by other means. If it returns this message, throw an error
100105
if (result.data.includes('It seems there is already an App at')) {

src/resources/homebrew/formulae-parameter.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,24 @@ export class FormulaeParameter extends StatefulParameter<HomebrewConfig, string[
1010
return {
1111
type: 'array',
1212
isElementEqual(desired, current) {
13+
if (desired === current) {
14+
return true;
15+
}
16+
1317
// Handle the case where the name is fully qualified (tap + name)
1418
if (desired.includes('/')) {
1519
const formulaName = desired.split('/').at(-1);
1620
return formulaName === current;
1721
}
1822

19-
return desired === current;
23+
return false;
2024
},
2125
}
2226
}
2327

2428
override async refresh(desired: unknown, config: Partial<HomebrewConfig>): Promise<null | string[]> {
2529
const $ = getPty();
26-
const formulaeQuery = await $.spawnSafe(`brew list --formula -1 ${config.onlyPlanUserInstalled ? '--installed-on-request' : ''}`)
30+
const formulaeQuery = await $.spawnSafe(`brew list --formula -1 --full-name ${config.onlyPlanUserInstalled ? '--installed-on-request' : ''}`)
2731

2832
if (formulaeQuery.status === SpawnStatus.SUCCESS && formulaeQuery.data !== null && formulaeQuery.data !== undefined) {
2933
return formulaeQuery.data
@@ -42,8 +46,8 @@ export class FormulaeParameter extends StatefulParameter<HomebrewConfig, string[
4246
const formulaeToInstall = newValue.filter((x: string) => !previousValue.includes(x));
4347
const formulaeToUninstall = previousValue.filter((x: string) => !newValue.includes(x));
4448

45-
await this.installFormulae(formulaeToInstall);
4649
await this.uninstallFormulae(formulaeToUninstall);
50+
await this.installFormulae(formulaeToInstall);
4751
}
4852

4953
async remove(valueToRemove: string[]): Promise<void> {

src/resources/homebrew/tap-parameter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export class TapsParameter extends StatefulParameter<HomebrewConfig, string[]> {
4545
return;
4646
}
4747

48-
await codifySpawn(`brew tap ${taps.join(' ')}`)
48+
await codifySpawn(`HOMEBREW_NO_AUTO_UPDATE=1 brew tap ${taps.join(' ')}`)
4949
}
5050

5151
private async uninstallTaps(taps: string[]): Promise<void> {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"$id": "https://www.codifycli.com/pip-sync.json",
4+
"title": "Pip-sync resource",
5+
"type": "object",
6+
"description": "Install and manage though pip-tools by installing + uninstalling packages using pip-sync",
7+
"properties": {
8+
"virtualEnv": {
9+
"type": "string",
10+
"description": "A virtual env to activate before issuing commands."
11+
},
12+
"requirementFiles": {
13+
"type": "array",
14+
"items": { "type": "string" },
15+
"description": "A list of requirement files to supply pip-sync."
16+
},
17+
"cwd": {
18+
"type": "string",
19+
"description": "The working directory to run commands from. If cwd is supplied, the other parameters can be specified as relative to cwd."
20+
}
21+
},
22+
"additionalProperties": false,
23+
"required": ["requirementFiles"]
24+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 schema from './pip-sync-schema.json'
6+
import { RequirementFilesParameter } from './requirement-files-parameter.js';
7+
8+
export interface PipSyncConfig extends ResourceConfig {
9+
requirementFiles: string[];
10+
virtualEnv?: string;
11+
cwd?: string;
12+
}
13+
14+
export class PipSync extends Resource<PipSyncConfig> {
15+
getSettings(): ResourceSettings<PipSyncConfig> {
16+
return {
17+
id: 'pip-sync',
18+
schema,
19+
parameterSettings: {
20+
requirementFiles: { type: 'stateful', definition: new RequirementFilesParameter() },
21+
virtualEnv: { type: 'directory', setting: true },
22+
cwd: { type: 'directory', setting: true }
23+
},
24+
dependencies: ['pyenv', 'pip', 'venv-project', 'virtualenv-project', 'virtualenv'],
25+
allowMultiple: {
26+
identifyingParameters: ['virtualEnv'],
27+
}
28+
};
29+
}
30+
31+
async refresh(parameters: Partial<PipSyncConfig>, context: RefreshContext<PipSyncConfig>): Promise<Partial<PipSyncConfig> | Partial<PipSyncConfig>[] | null> {
32+
const pty = getPty()
33+
34+
const { status: pipStatus } = await pty.spawnSafe(PipSync.withVirtualEnv('which pip', parameters.virtualEnv), { cwd: parameters.cwd ?? undefined });
35+
if (pipStatus === 'error') {
36+
return null;
37+
}
38+
39+
const { status: pipSyncStatus } = await pty.spawnSafe(PipSync.withVirtualEnv('which pip-sync', parameters.virtualEnv), { cwd: parameters.cwd ?? undefined })
40+
return pipSyncStatus === 'error' ? null : parameters;
41+
}
42+
43+
async create(plan: CreatePlan<PipSyncConfig>): Promise<void> {
44+
await codifySpawn(PipSync.withVirtualEnv('pip install pip-tools', plan.desiredConfig.virtualEnv), { cwd: plan.desiredConfig.cwd ?? undefined })
45+
}
46+
47+
async destroy(plan: DestroyPlan<PipSyncConfig>): Promise<void> {
48+
await codifySpawn(PipSync.withVirtualEnv('pip uninstall -y pip-tools', plan.currentConfig.virtualEnv), { cwd: plan.currentConfig.cwd ?? undefined })
49+
}
50+
51+
static withVirtualEnv(command: string, virtualEnv?: string, ): string {
52+
if (!virtualEnv) {
53+
return command;
54+
}
55+
56+
return `( set -e; source ${virtualEnv}/bin/activate; ${command}; deactivate )`;
57+
}
58+
59+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { ArrayParameterSetting, Plan, StatefulParameter, getPty } from 'codify-plugin-lib';
2+
3+
import { codifySpawn } from '../../../utils/codify-spawn.js';
4+
import { PipSyncConfig } from './pip-sync.js';
5+
6+
export class RequirementFilesParameter extends StatefulParameter<PipSyncConfig, string[]> {
7+
getSettings(): ArrayParameterSetting {
8+
return {
9+
type: 'array',
10+
itemType: 'directory',
11+
canModify: true,
12+
}
13+
}
14+
15+
async refresh(desired: null | string[], config: Partial<PipSyncConfig>): Promise<null | string[]> {
16+
if (!desired || desired?.length === 0) {
17+
return null;
18+
}
19+
20+
const pty = getPty();
21+
const { status } = await pty.spawnSafe(
22+
this.appendVirtualEnv(`pip-sync -n ${desired?.join(' ')}`, config.virtualEnv),
23+
{ cwd: config.cwd ?? undefined }
24+
)
25+
return status === 'error' ? null : desired;
26+
}
27+
28+
async add(valueToAdd: string[], plan: Plan<PipSyncConfig>): Promise<void> {
29+
await codifySpawn(
30+
this.appendVirtualEnv(`pip-sync ${valueToAdd.join(' ')}`, plan.desiredConfig?.virtualEnv),
31+
{ cwd: plan.desiredConfig?.cwd ?? undefined }
32+
)
33+
}
34+
35+
async modify(newValue: string[], _: string[], plan: Plan<PipSyncConfig>): Promise<void> {
36+
await codifySpawn(
37+
this.appendVirtualEnv(`pip-sync ${newValue.join(' ')}`, plan.desiredConfig?.virtualEnv),
38+
{ cwd: plan.desiredConfig?.cwd ?? undefined }
39+
)
40+
}
41+
42+
async remove(valueToRemove: string[], plan: Plan<PipSyncConfig>): Promise<void> {
43+
await codifySpawn(
44+
this.appendVirtualEnv(`pip-sync ${valueToRemove.join(' ')}`, plan.currentConfig?.virtualEnv),
45+
{ cwd: plan.currentConfig?.cwd ?? undefined }
46+
)
47+
}
48+
49+
private appendVirtualEnv(command: string, virtualEnv?: string): string {
50+
if (!virtualEnv) {
51+
return command;
52+
}
53+
54+
return `( set -e; source ${virtualEnv}/bin/activate; ${command} --python-executable ${virtualEnv}/bin/python; deactivate )`
55+
}
56+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"$id": "https://www.codifycli.com/pip.json",
4+
"title": "Pip resource",
5+
"type": "object",
6+
"description": "Install and manage packages using pip",
7+
"properties": {
8+
"virtualEnv": {
9+
"type": "string",
10+
"description": "A virtual env to activate before issuing pip commands."
11+
},
12+
"install": {
13+
"type": "array",
14+
"description": "Packages to install.",
15+
"items": {
16+
"oneOf": [
17+
{
18+
"type": "string"
19+
},
20+
{
21+
"type": "object",
22+
"properties": {
23+
"name": {
24+
"type": "string"
25+
},
26+
"version": {
27+
"type": "string"
28+
}
29+
},
30+
"required": ["name"]
31+
}
32+
]
33+
}
34+
}
35+
},
36+
"additionalProperties": false,
37+
"required": ["install"]
38+
}

0 commit comments

Comments
 (0)