@enspirit/emb 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -10
- package/bin/release +122 -0
- package/dist/src/cli/commands/components/logs.d.ts +0 -1
- package/dist/src/cli/commands/components/logs.js +0 -1
- package/dist/src/cli/commands/kubernetes/logs.d.ts +0 -1
- package/dist/src/cli/commands/kubernetes/logs.js +0 -1
- package/dist/src/cli/commands/logs/archive.d.ts +16 -0
- package/dist/src/cli/commands/logs/archive.js +59 -0
- package/dist/src/cli/commands/logs/index.d.ts +14 -0
- package/dist/src/cli/commands/logs/index.js +44 -0
- package/dist/src/cli/commands/tasks/run.js +6 -1
- package/dist/src/docker/compose/operations/ComposeLogsArchiveOperation.d.ts +17 -0
- package/dist/src/docker/compose/operations/ComposeLogsArchiveOperation.js +77 -0
- package/dist/src/docker/compose/operations/index.d.ts +1 -0
- package/dist/src/docker/compose/operations/index.js +1 -0
- package/dist/src/monorepo/operations/shell/ExecuteLocalCommandOperation.js +40 -10
- package/oclif.manifest.json +261 -129
- package/package.json +5 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createWriteStream } from 'node:fs';
|
|
3
|
+
import { mkdir } from 'node:fs/promises';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import * as z from 'zod';
|
|
6
|
+
import { AbstractOperation } from '../../../operations/index.js';
|
|
7
|
+
export const ComposeLogsArchiveOperationInputSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
components: z
|
|
10
|
+
.array(z.string())
|
|
11
|
+
.optional()
|
|
12
|
+
.describe('The list of components to archive logs for (all if omitted)'),
|
|
13
|
+
outputDir: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Output directory for log files (defaults to .emb/<flavor>/logs/docker/compose)'),
|
|
17
|
+
timestamps: z.boolean().optional().describe('Include timestamps in logs'),
|
|
18
|
+
tail: z
|
|
19
|
+
.number()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Number of lines to show from the end'),
|
|
22
|
+
})
|
|
23
|
+
.optional();
|
|
24
|
+
export class ComposeLogsArchiveOperation extends AbstractOperation {
|
|
25
|
+
constructor() {
|
|
26
|
+
super(ComposeLogsArchiveOperationInputSchema);
|
|
27
|
+
}
|
|
28
|
+
async _run(input) {
|
|
29
|
+
const { monorepo } = this.context;
|
|
30
|
+
// Determine which components to archive
|
|
31
|
+
const componentNames = input?.components ?? monorepo.components.map((c) => c.name);
|
|
32
|
+
// Validate all component names
|
|
33
|
+
const components = componentNames.map((name) => monorepo.component(name));
|
|
34
|
+
// Determine output directory
|
|
35
|
+
const outputDir = input?.outputDir ?? monorepo.store.join('logs/docker/compose');
|
|
36
|
+
// Ensure output directory exists
|
|
37
|
+
await mkdir(outputDir, { recursive: true });
|
|
38
|
+
// Archive logs for each component
|
|
39
|
+
const archivePromises = components.map(async (component) => {
|
|
40
|
+
const logPath = join(outputDir, `${component.name}.log`);
|
|
41
|
+
await this.archiveComponentLogs(component.name, logPath, input);
|
|
42
|
+
return { component: component.name, path: logPath };
|
|
43
|
+
});
|
|
44
|
+
return Promise.all(archivePromises);
|
|
45
|
+
}
|
|
46
|
+
async archiveComponentLogs(serviceName, outputPath, input) {
|
|
47
|
+
const { monorepo } = this.context;
|
|
48
|
+
// Ensure parent directory exists
|
|
49
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
50
|
+
const cmd = 'docker';
|
|
51
|
+
const args = ['compose', 'logs', '--no-color'];
|
|
52
|
+
if (input?.timestamps) {
|
|
53
|
+
args.push('-t');
|
|
54
|
+
}
|
|
55
|
+
if (input?.tail !== undefined) {
|
|
56
|
+
args.push('--tail', String(input.tail));
|
|
57
|
+
}
|
|
58
|
+
args.push(serviceName);
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const writeStream = createWriteStream(outputPath);
|
|
61
|
+
const child = spawn(cmd, args, {
|
|
62
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
63
|
+
cwd: monorepo.rootDir,
|
|
64
|
+
});
|
|
65
|
+
child.stdout?.pipe(writeStream, { end: false });
|
|
66
|
+
child.stderr?.pipe(writeStream, { end: false });
|
|
67
|
+
child.on('error', (err) => {
|
|
68
|
+
writeStream.end();
|
|
69
|
+
reject(new Error(`Failed to get logs for ${serviceName}: ${err.message}`));
|
|
70
|
+
});
|
|
71
|
+
child.on('exit', () => {
|
|
72
|
+
writeStream.end();
|
|
73
|
+
resolve();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './ComposeDownOperation.js';
|
|
2
2
|
export * from './ComposeExecOperation.js';
|
|
3
3
|
export * from './ComposeExecShellOperation.js';
|
|
4
|
+
export * from './ComposeLogsArchiveOperation.js';
|
|
4
5
|
export * from './ComposeLogsOperation.js';
|
|
5
6
|
export * from './ComposePsOperation.js';
|
|
6
7
|
export * from './ComposeRestartOperation.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './ComposeDownOperation.js';
|
|
2
2
|
export * from './ComposeExecOperation.js';
|
|
3
3
|
export * from './ComposeExecShellOperation.js';
|
|
4
|
+
export * from './ComposeLogsArchiveOperation.js';
|
|
4
5
|
export * from './ComposeLogsOperation.js';
|
|
5
6
|
export * from './ComposePsOperation.js';
|
|
6
7
|
export * from './ComposeRestartOperation.js';
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { CommandExecError } from '../../../index.js';
|
|
1
2
|
import { execa } from 'execa';
|
|
3
|
+
import { Readable } from 'node:stream';
|
|
2
4
|
import * as z from 'zod';
|
|
3
5
|
import { AbstractOperation } from '../../../operations/index.js';
|
|
6
|
+
// Type guard for execa error objects
|
|
7
|
+
function isExecaError(error) {
|
|
8
|
+
return error instanceof Error && 'exitCode' in error;
|
|
9
|
+
}
|
|
4
10
|
/**
|
|
5
11
|
* https://docs.docker.com/reference/api/engine/version/v1.37/#tag/Exec/operation/ContainerExec
|
|
6
12
|
*/
|
|
@@ -27,20 +33,44 @@ export class ExecuteLocalCommandOperation extends AbstractOperation {
|
|
|
27
33
|
this.out = out;
|
|
28
34
|
}
|
|
29
35
|
async _run(input) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
try {
|
|
37
|
+
if (input.interactive) {
|
|
38
|
+
// For interactive mode, inherit all stdio streams so the child process
|
|
39
|
+
// has direct access to the terminal TTY. This allows interactive CLI tools
|
|
40
|
+
// (like ionic, npm, etc.) to detect TTY and show prompts.
|
|
41
|
+
await execa(input.script, {
|
|
42
|
+
cwd: input.workingDir,
|
|
43
|
+
shell: true,
|
|
44
|
+
env: input.env,
|
|
45
|
+
stdio: 'inherit',
|
|
46
|
+
});
|
|
47
|
+
// Return an empty stream for type compatibility - the caller won't use it
|
|
48
|
+
// since interactive mode outputs directly to the terminal
|
|
49
|
+
return new Readable({
|
|
50
|
+
read() {
|
|
51
|
+
this.push(null);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Non-interactive mode: capture output
|
|
56
|
+
const proc = execa(input.script, {
|
|
38
57
|
all: true,
|
|
39
58
|
cwd: input.workingDir,
|
|
40
59
|
shell: true,
|
|
41
60
|
env: input.env,
|
|
42
61
|
});
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
// With all: true, proc.all is always defined
|
|
63
|
+
proc.all.pipe(this.out || process.stdout);
|
|
64
|
+
await proc;
|
|
65
|
+
return proc.all;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (isExecaError(error)) {
|
|
69
|
+
const stderr = error.stderr?.trim();
|
|
70
|
+
const message = stderr || error.shortMessage || error.message;
|
|
71
|
+
throw new CommandExecError(message, error.exitCode ?? 1, error.signal);
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
45
75
|
}
|
|
46
76
|
}
|