Skip to content

Commit dcbe194

Browse files
committed
breaking: Removed object option from apt. Split git-repository into singular and plural versions
1 parent a2b69be commit dcbe194

10 files changed

Lines changed: 402 additions & 298 deletions

File tree

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { FileResource } from './resources/file/file.js';
1313
import { RemoteFileResource } from './resources/file/remote-file.js';
1414
import { GitResource } from './resources/git/git/git-resource.js';
1515
import { GitLfsResource } from './resources/git/lfs/git-lfs.js';
16+
import { GitRepositoriesResource } from './resources/git/repositories/git-repositories.js';
1617
import { GitRepositoryResource } from './resources/git/repository/git-repository.js';
1718
import { WaitGithubSshKey } from './resources/git/wait-github-ssh-key/wait-github-ssh-key.js';
1819
import { HomebrewResource } from './resources/homebrew/homebrew.js';
@@ -70,6 +71,7 @@ runPlugin(Plugin.create(
7071
new PgcliResource(),
7172
new VscodeResource(),
7273
new GitRepositoryResource(),
74+
new GitRepositoriesResource(),
7375
new AndroidStudioResource(),
7476
new AsdfResource(),
7577
new AsdfPluginResource(),

src/resources/apt/apt-schema.json

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,9 @@
88
"properties": {
99
"install": {
1010
"type": "array",
11-
"description": "Installs packages using apt.",
11+
"description": "Installs packages using apt. Use 'name=version' syntax to pin a version (e.g. 'nodejs=20.*').",
1212
"items": {
13-
"oneOf": [
14-
{ "type": "string" },
15-
{
16-
"type": "object",
17-
"properties": {
18-
"name": { "type": "string" },
19-
"version": { "type": "string" }
20-
},
21-
"required": ["name"]
22-
}
23-
]
13+
"type": "string"
2414
}
2515
},
2616
"update": {

src/resources/apt/apt.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { CreatePlan, ExampleConfig, Resource, ResourceSettings, SpawnStatus, get
22
import { OS, ResourceConfig, ResourceOs } from '@codifycli/schemas';
33

44
import schema from './apt-schema.json';
5-
import { AptInstallParameter, AptPackage } from './install-parameter.js';
5+
import { AptInstallParameter } from './install-parameter.js';
66

77
export interface AptConfig extends ResourceConfig {
8-
install: Array<AptPackage | string>;
8+
install: string[];
99
update?: boolean;
1010
}
1111

@@ -32,8 +32,8 @@ const exampleVersionPinned: ExampleConfig = {
3232
os: [ResourceOs.LINUX],
3333
install: [
3434
'curl',
35-
{ name: 'nodejs', version: '20.*' },
36-
{ name: 'python3', version: '3.12.*' },
35+
'nodejs=20.*',
36+
'python3=3.12.*',
3737
],
3838
}]
3939
}

src/resources/apt/install-parameter.ts

Lines changed: 32 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,24 @@ import { ParameterSetting, Plan, StatefulParameter, getPty } from '@codifycli/pl
22

33
import { AptConfig } from './apt.js';
44

5-
export interface AptPackage {
6-
name: string;
7-
version?: string;
5+
// Extracts the package name without the version specifier (e.g. "nodejs=20.*" → "nodejs")
6+
function packageName(pkg: string): string {
7+
const eqIndex = pkg.indexOf('=')
8+
return eqIndex > 0 ? pkg.slice(0, eqIndex) : pkg
89
}
910

10-
export class AptInstallParameter extends StatefulParameter<AptConfig, Array<AptPackage | string>> {
11+
export class AptInstallParameter extends StatefulParameter<AptConfig, string[]> {
1112

1213
getSettings(): ParameterSetting {
1314
return {
1415
type: 'array',
1516
filterInStatelessMode: (desired, current) =>
16-
current.filter((c) => desired.some((d) => this.isSamePackage(d, c))),
17+
current.filter((c) => desired.some((d) => packageName(d) === packageName(c))),
1718
isElementEqual: this.isEqual,
1819
}
1920
}
2021

21-
async refresh(desired: Array<AptPackage | string> | null, _config: Partial<AptConfig>): Promise<Array<AptPackage | string> | null> {
22+
async refresh(desired: string[] | null, _config: Partial<AptConfig>): Promise<string[] | null> {
2223
const $ = getPty()
2324
const { data: installed } = await $.spawnSafe('dpkg-query -W -f=\'${Package} ${Version}\\n\'');
2425

@@ -29,51 +30,36 @@ export class AptInstallParameter extends StatefulParameter<AptConfig, Array<AptP
2930
const r = installed.split(/\n/)
3031
.filter(Boolean)
3132
.map((l) => {
32-
const [name, version] = l.split(/\s+/)
33-
.filter(Boolean)
34-
33+
const [name, version] = l.split(/\s+/).filter(Boolean)
3534
return { name, version }
3635
})
37-
.filter((pkg) =>
38-
// Only return packages that are in the desired list
39-
desired?.some((d) => {
40-
if (typeof d === 'string') {
41-
return d === pkg.name;
42-
}
43-
44-
return d.name === pkg.name;
45-
})
46-
)
47-
.map((installed) => {
48-
if (desired?.find((d) => typeof d === 'string' && d === installed.name)) {
49-
return installed.name;
50-
}
51-
52-
if (desired?.find((d) => typeof d === 'object' && d.name === installed.name && !d.version)) {
53-
return { name: installed.name }
36+
.filter((pkg) => desired?.some((d) => packageName(d) === pkg.name))
37+
.map((pkg) => {
38+
// If desired entry has a version specifier, return name=version so equality checks work
39+
if (desired?.some((d) => d.includes('=') && packageName(d) === pkg.name)) {
40+
return `${pkg.name}=${pkg.version}`
5441
}
55-
56-
return installed;
42+
return pkg.name
5743
})
5844

5945
return r.length > 0 ? r : null;
6046
}
6147

62-
async add(valueToAdd: Array<AptPackage | string>, plan: Plan<AptConfig>): Promise<void> {
48+
async add(valueToAdd: string[], plan: Plan<AptConfig>): Promise<void> {
6349
await this.updateIfNeeded(plan);
6450
await this.install(valueToAdd);
6551
}
6652

67-
async modify(newValue: (AptPackage | string)[], previousValue: (AptPackage | string)[], plan: Plan<AptConfig>): Promise<void> {
68-
const valuesToAdd = newValue.filter((n) => !previousValue.some((p) => this.isSamePackage(n, p)));
69-
const valuesToRemove = previousValue.filter((p) => !newValue.some((n) => this.isSamePackage(n, p)));
53+
async modify(newValue: string[], previousValue: string[], plan: Plan<AptConfig>): Promise<void> {
54+
const valuesToAdd = newValue.filter((n) => !previousValue.some((p) => packageName(n) === packageName(p)));
55+
const valuesToRemove = previousValue.filter((p) => !newValue.some((n) => packageName(n) === packageName(p)));
7056

7157
await this.uninstall(valuesToRemove);
7258
await this.updateIfNeeded(plan);
7359
await this.install(valuesToAdd);
7460
}
7561

76-
async remove(valueToRemove: (AptPackage | string)[], _plan: Plan<AptConfig>): Promise<void> {
62+
async remove(valueToRemove: string[], _plan: Plan<AptConfig>): Promise<void> {
7763
await this.uninstall(valueToRemove);
7864
}
7965

@@ -86,90 +72,35 @@ export class AptInstallParameter extends StatefulParameter<AptConfig, Array<AptP
8672
await $.spawn('apt-get update', { requiresRoot: true, interactive: true });
8773
}
8874

89-
private async install(packages: Array<AptPackage | string>): Promise<void> {
75+
private async install(packages: string[]): Promise<void> {
9076
if (!packages || packages.length === 0) {
9177
return;
9278
}
9379

9480
const $ = getPty();
95-
const toInstall = packages.map((p) => {
96-
if (typeof p === 'string') {
97-
return p;
98-
}
99-
100-
if (p.version) {
101-
return `${p.name}=${p.version}`;
102-
}
103-
104-
return p.name;
105-
}).join(' ');
106-
107-
await $.spawn(`apt-get install -y ${toInstall}`, {
81+
await $.spawn(`apt-get install -y ${packages.join(' ')}`, {
10882
requiresRoot: true,
10983
env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' }
11084
});
11185
}
11286

113-
private async uninstall(packages: Array<AptPackage | string>): Promise<void> {
87+
private async uninstall(packages: string[]): Promise<void> {
11488
if (!packages || packages.length === 0) {
11589
return;
11690
}
11791

11892
const $ = getPty();
119-
const toUninstall = packages.map((p) => {
120-
if (typeof p === 'string') {
121-
return p;
122-
}
123-
124-
return p.name;
125-
}).join(' ');
126-
127-
await $.spawn(`apt-get auto-remove -y ${toUninstall}`, { requiresRoot: true, env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' }});
128-
}
129-
130-
isSamePackage(a: AptPackage | string, b: AptPackage | string): boolean {
131-
if (typeof a === 'string' || typeof b === 'string') {
132-
if (typeof a === 'string' && typeof b === 'string') {
133-
return a === b;
134-
}
135-
136-
if (typeof a === 'string' && typeof b === 'object') {
137-
return a === b.name;
138-
}
139-
140-
if (typeof a === 'object' && typeof b === 'string') {
141-
return a.name === b;
142-
}
143-
}
144-
145-
if (typeof a === 'object' && typeof b === 'object') {
146-
return a.name === b.name;
147-
}
148-
149-
return false;
93+
await $.spawn(`apt-get auto-remove -y ${packages.map(packageName).join(' ')}`, {
94+
requiresRoot: true,
95+
env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' }
96+
});
15097
}
15198

152-
isEqual(desired: AptPackage | string, current: AptPackage | string): boolean {
153-
if (typeof desired === 'string' || typeof current === 'string') {
154-
if (typeof desired === 'string' && typeof current === 'string') {
155-
return desired === current;
156-
}
157-
158-
if (typeof desired === 'string' && typeof current === 'object') {
159-
return desired === current.name;
160-
}
161-
162-
if (typeof desired === 'object' && typeof current === 'string') {
163-
return desired.name === current;
164-
}
165-
}
166-
167-
if (typeof desired === 'object' && typeof current === 'object') {
168-
return desired.version
169-
? desired.version === current.version && desired.name === current.name
170-
: desired.name === current.name;
99+
isEqual(desired: string, current: string): boolean {
100+
// If no version specified in desired, match by name only
101+
if (!desired.includes('=')) {
102+
return packageName(desired) === packageName(current)
171103
}
172-
173-
return false;
104+
return desired === current
174105
}
175-
}
106+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"$id": "https://www.codifycli.com/git-repositories.json",
4+
"$comment": "https://codifycli.com/docs/resources/git/git-repositories/",
5+
"title": "Git-repositories resource",
6+
"description": "Clone multiple Git repositories into a shared parent directory.",
7+
"type": "object",
8+
"properties": {
9+
"repositories": {
10+
"type": "array",
11+
"description": "Remote repository URLs to clone.",
12+
"items": {
13+
"type": "string"
14+
}
15+
},
16+
"parentDirectory": {
17+
"type": "string",
18+
"description": "Parent directory to clone repositories into. Each repository is cloned as a subdirectory using the repository name."
19+
},
20+
"autoVerifySSH": {
21+
"type": "boolean",
22+
"description": "Automatically verifies the ssh connection for ssh git clones. Defaults to true."
23+
}
24+
},
25+
"required": ["repositories", "parentDirectory"],
26+
"additionalProperties": false
27+
}

0 commit comments

Comments
 (0)