-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcompose-project.service.ts
More file actions
154 lines (138 loc) · 4.33 KB
/
compose-project.service.ts
File metadata and controls
154 lines (138 loc) · 4.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { APP_METADATA } from '../config/app-metadata.js';
import { createComposeCommandArgs } from '../lib/compose.js';
import type { ComposePsEntry } from '../types/docker.types.js';
import type { GlobalCliOptions, SaveImagesCommandOptions } from '../types/cli.types.js';
import type { ProjectContext } from '../types/project.types.js';
import { runCommand } from '../utils/process.js';
/**
* Lists running Compose entries for the current project checkout.
*/
export async function listRunningComposeEntries(
context: ProjectContext
): Promise<ComposePsEntry[]> {
const result = await runCommand(
'docker',
createComposeCommandArgs(context, ['ps', '--status', 'running', '--format', 'json'], {
includeAll: true
}),
{
allowFailure: true,
captureOutput: true,
cwd: context.projectRoot,
scope: 'compose'
}
);
if (result.exitCode !== 0) {
return [];
}
return parseComposePsEntries(result.stdout);
}
/**
* Returns whether the current Compose project already has running services.
*/
export async function hasRunningComposeServices(context: ProjectContext): Promise<boolean> {
const entries = await listRunningComposeEntries(context);
return entries.length > 0;
}
/**
* Collects all published host ports currently owned by the running Compose project.
*/
export async function getRunningComposePublishedPorts(
context: ProjectContext
): Promise<Set<number>> {
const entries = await listRunningComposeEntries(context);
return collectPublishedPorts(entries);
}
/**
* Lists the Docker images declared by the selected Compose layers.
*/
export async function listConfiguredComposeImages(
context: ProjectContext,
options: Pick<SaveImagesCommandOptions, 'withAiLlm' | 'withWorkbench'>
): Promise<string[]> {
const result = await runCommand(
'docker',
createComposeCommandArgs(context, ['config', '--images'], {
includeAiLlm: Boolean(options.withAiLlm),
includeWorkbench: Boolean(options.withWorkbench)
}),
{
captureOutput: true,
cwd: context.projectRoot,
scope: 'compose'
}
);
return collectUniqueTextLines(result.stdout).map(normalizeImageReference);
}
/**
* Returns the concrete Docker volume names for the selected Compose layers.
*/
export async function listConfiguredDockerVolumes(
context: ProjectContext,
options: Pick<GlobalCliOptions, never> & {
withAiLlm?: boolean;
withWorkbench?: boolean;
}
): Promise<Array<{ dockerName: string; logicalName: string }>> {
const result = await runCommand(
'docker',
createComposeCommandArgs(context, ['config', '--volumes'], {
includeAiLlm: Boolean(options.withAiLlm),
includeWorkbench: Boolean(options.withWorkbench)
}),
{
captureOutput: true,
cwd: context.projectRoot,
scope: 'compose'
}
);
return collectUniqueTextLines(result.stdout).map((logicalName) => ({
dockerName: `${APP_METADATA.codeName}_${logicalName}`,
logicalName
}));
}
/**
* Parses the newline-delimited JSON emitted by `docker compose ps --format json`.
*/
export function parseComposePsEntries(stdout: string): ComposePsEntry[] {
return stdout
.split(/\r?\n/gu)
.map((line) => line.trim())
.filter((line) => line.length > 0)
.map((line) => JSON.parse(line) as ComposePsEntry);
}
/**
* Extracts the distinct published host ports from a list of Compose `ps` entries.
*/
export function collectPublishedPorts(entries: ComposePsEntry[]): Set<number> {
const publishedPorts = new Set<number>();
for (const entry of entries) {
for (const publisher of entry.Publishers ?? []) {
if (typeof publisher.PublishedPort === 'number') {
publishedPorts.add(publisher.PublishedPort);
}
}
}
return publishedPorts;
}
/**
* Collects non-empty unique text lines while preserving the original order.
*/
function collectUniqueTextLines(stdout: string): string[] {
return [...new Set(
stdout
.split(/\r?\n/gu)
.map((line) => line.trim())
.filter((line) => line.length > 0)
)];
}
/**
* Normalizes Docker image references to their explicit `:latest` form when no tag is present.
*/
export function normalizeImageReference(image: string): string {
if (image.includes('@')) {
return image;
}
const finalSegment = image.split('/').at(-1) ?? image;
return finalSegment.includes(':') ? image : `${image}:latest`;
}