@enspirit/emb 0.1.3 → 0.3.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 +53 -1
- package/dist/src/cli/commands/components/shell.d.ts +14 -0
- package/dist/src/cli/commands/components/shell.js +41 -0
- package/dist/src/docker/compose/operations/ComposeExecOperation.d.ts +13 -0
- package/dist/src/docker/compose/operations/ComposeExecOperation.js +59 -0
- package/dist/src/docker/compose/operations/ComposeExecShellOperation.d.ts +11 -0
- package/dist/src/docker/compose/operations/ComposeExecShellOperation.js +56 -0
- package/dist/src/docker/compose/operations/index.d.ts +2 -0
- package/dist/src/docker/compose/operations/index.js +2 -0
- package/dist/src/docker/operations/containers/index.d.ts +1 -1
- package/dist/src/docker/operations/containers/index.js +1 -1
- package/dist/src/docker/resources/DockerImageResource.js +68 -49
- package/dist/src/errors.d.ts +19 -0
- package/dist/src/errors.js +35 -1
- package/dist/src/monorepo/operations/resources/BuildResourcesOperation.d.ts +1 -0
- package/dist/src/monorepo/operations/resources/BuildResourcesOperation.js +27 -13
- package/dist/src/monorepo/operations/tasks/RunTasksOperation.js +6 -7
- package/dist/src/monorepo/resources/FileResource.js +10 -6
- package/dist/src/monorepo/resources/ResourceFactory.d.ts +4 -2
- package/dist/src/monorepo/store/index.d.ts +1 -0
- package/dist/src/monorepo/store/index.js +12 -1
- package/oclif.manifest.json +52 -1
- package/package.json +1 -1
- /package/dist/src/docker/operations/containers/{ExecContainerOperation.d.ts → ContainerExecOperation.d.ts} +0 -0
- /package/dist/src/docker/operations/containers/{ExecContainerOperation.js → ContainerExecOperation.js} +0 -0
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ $ npm install -g @enspirit/emb
|
|
|
14
14
|
$ emb COMMAND
|
|
15
15
|
running command...
|
|
16
16
|
$ emb (--version)
|
|
17
|
-
@enspirit/emb/0.
|
|
17
|
+
@enspirit/emb/0.3.0 darwin-x64 node-v22.12.0
|
|
18
18
|
$ emb --help [COMMAND]
|
|
19
19
|
USAGE
|
|
20
20
|
$ emb COMMAND
|
|
@@ -27,6 +27,7 @@ USAGE
|
|
|
27
27
|
* [`emb clean`](#emb-clean)
|
|
28
28
|
* [`emb components`](#emb-components)
|
|
29
29
|
* [`emb components logs COMPONENT`](#emb-components-logs-component)
|
|
30
|
+
* [`emb components shell COMPONENT`](#emb-components-shell-component)
|
|
30
31
|
* [`emb config print`](#emb-config-print)
|
|
31
32
|
* [`emb containers`](#emb-containers)
|
|
32
33
|
* [`emb containers prune`](#emb-containers-prune)
|
|
@@ -39,6 +40,7 @@ USAGE
|
|
|
39
40
|
* [`emb ps`](#emb-ps)
|
|
40
41
|
* [`emb resources`](#emb-resources)
|
|
41
42
|
* [`emb resources build [COMPONENT]`](#emb-resources-build-component)
|
|
43
|
+
* [`emb shell COMPONENT`](#emb-shell-component)
|
|
42
44
|
* [`emb tasks`](#emb-tasks)
|
|
43
45
|
* [`emb tasks run TASK`](#emb-tasks-run-task)
|
|
44
46
|
* [`emb up`](#emb-up)
|
|
@@ -142,6 +144,31 @@ EXAMPLES
|
|
|
142
144
|
$ emb components logs
|
|
143
145
|
```
|
|
144
146
|
|
|
147
|
+
## `emb components shell COMPONENT`
|
|
148
|
+
|
|
149
|
+
Get a shell on a running component.
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
USAGE
|
|
153
|
+
$ emb components shell COMPONENT [--flavor <value>] [-s <value>]
|
|
154
|
+
|
|
155
|
+
ARGUMENTS
|
|
156
|
+
COMPONENT The component you want to get a shell on
|
|
157
|
+
|
|
158
|
+
FLAGS
|
|
159
|
+
-s, --shell=<value> [default: bash] The shell to run
|
|
160
|
+
--flavor=<value> Specify the flavor to use.
|
|
161
|
+
|
|
162
|
+
DESCRIPTION
|
|
163
|
+
Get a shell on a running component.
|
|
164
|
+
|
|
165
|
+
ALIASES
|
|
166
|
+
$ emb shell
|
|
167
|
+
|
|
168
|
+
EXAMPLES
|
|
169
|
+
$ emb components shell
|
|
170
|
+
```
|
|
171
|
+
|
|
145
172
|
## `emb config print`
|
|
146
173
|
|
|
147
174
|
Print the current config.
|
|
@@ -406,6 +433,31 @@ EXAMPLES
|
|
|
406
433
|
$ emb resources build build --flavor development
|
|
407
434
|
```
|
|
408
435
|
|
|
436
|
+
## `emb shell COMPONENT`
|
|
437
|
+
|
|
438
|
+
Get a shell on a running component.
|
|
439
|
+
|
|
440
|
+
```
|
|
441
|
+
USAGE
|
|
442
|
+
$ emb shell COMPONENT [--flavor <value>] [-s <value>]
|
|
443
|
+
|
|
444
|
+
ARGUMENTS
|
|
445
|
+
COMPONENT The component you want to get a shell on
|
|
446
|
+
|
|
447
|
+
FLAGS
|
|
448
|
+
-s, --shell=<value> [default: bash] The shell to run
|
|
449
|
+
--flavor=<value> Specify the flavor to use.
|
|
450
|
+
|
|
451
|
+
DESCRIPTION
|
|
452
|
+
Get a shell on a running component.
|
|
453
|
+
|
|
454
|
+
ALIASES
|
|
455
|
+
$ emb shell
|
|
456
|
+
|
|
457
|
+
EXAMPLES
|
|
458
|
+
$ emb shell
|
|
459
|
+
```
|
|
460
|
+
|
|
409
461
|
## `emb tasks`
|
|
410
462
|
|
|
411
463
|
List tasks.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FlavoredCommand } from '../../index.js';
|
|
2
|
+
export default class ComponentsLogs extends FlavoredCommand<typeof ComponentsLogs> {
|
|
3
|
+
static aliases: string[];
|
|
4
|
+
static description: string;
|
|
5
|
+
static enableJsonFlag: boolean;
|
|
6
|
+
static examples: string[];
|
|
7
|
+
static flags: {
|
|
8
|
+
shell: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
static args: {
|
|
11
|
+
component: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { FlavoredCommand, getContext } from '../../index.js';
|
|
3
|
+
import { ComposeExecShellOperation } from '../../../docker/index.js';
|
|
4
|
+
import { ShellExitError } from '../../../errors.js';
|
|
5
|
+
export default class ComponentsLogs extends FlavoredCommand {
|
|
6
|
+
static aliases = ['shell'];
|
|
7
|
+
static description = 'Get a shell on a running component.';
|
|
8
|
+
static enableJsonFlag = false;
|
|
9
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
10
|
+
static flags = {
|
|
11
|
+
shell: Flags.string({
|
|
12
|
+
name: 'shell',
|
|
13
|
+
char: 's',
|
|
14
|
+
description: 'The shell to run',
|
|
15
|
+
default: 'bash',
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
static args = {
|
|
19
|
+
component: Args.string({
|
|
20
|
+
name: 'component',
|
|
21
|
+
description: 'The component you want to get a shell on',
|
|
22
|
+
required: true,
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
const { flags, args } = await this.parse(ComponentsLogs);
|
|
27
|
+
const { monorepo } = await getContext();
|
|
28
|
+
try {
|
|
29
|
+
await monorepo.run(new ComposeExecShellOperation(), {
|
|
30
|
+
service: args.component,
|
|
31
|
+
shell: flags.shell,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error instanceof ShellExitError) {
|
|
36
|
+
this.error(error);
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import { AbstractOperation } from '../../../operations/index.js';
|
|
4
|
+
declare const schema: z.ZodObject<{
|
|
5
|
+
command: z.ZodString;
|
|
6
|
+
service: z.ZodString;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export declare class ComposeExecOperation extends AbstractOperation<typeof schema, void> {
|
|
9
|
+
protected out?: Writable | undefined;
|
|
10
|
+
constructor(out?: Writable | undefined);
|
|
11
|
+
protected _run(input: z.input<typeof schema>): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import { ComposeExecError } from '../../../errors.js';
|
|
4
|
+
import { AbstractOperation } from '../../../operations/index.js';
|
|
5
|
+
const schema = z.object({
|
|
6
|
+
command: z.string().describe('The command to execute on the service'),
|
|
7
|
+
service: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe('The name of the compose service to exec a shell'),
|
|
10
|
+
});
|
|
11
|
+
export class ComposeExecOperation extends AbstractOperation {
|
|
12
|
+
out;
|
|
13
|
+
constructor(out) {
|
|
14
|
+
super(schema);
|
|
15
|
+
this.out = out;
|
|
16
|
+
}
|
|
17
|
+
async _run(input) {
|
|
18
|
+
const { monorepo } = this.context;
|
|
19
|
+
const cmd = 'docker';
|
|
20
|
+
const args = ['compose', 'exec', input.service, input.command];
|
|
21
|
+
const child = spawn(cmd, args, {
|
|
22
|
+
stdio: 'pipe',
|
|
23
|
+
shell: true,
|
|
24
|
+
cwd: monorepo.rootDir,
|
|
25
|
+
});
|
|
26
|
+
if (this.out) {
|
|
27
|
+
child.stderr.pipe(this.out);
|
|
28
|
+
child.stdout.pipe(this.out);
|
|
29
|
+
}
|
|
30
|
+
const forward = (sig) => {
|
|
31
|
+
try {
|
|
32
|
+
child.kill(sig);
|
|
33
|
+
}
|
|
34
|
+
catch { }
|
|
35
|
+
};
|
|
36
|
+
const signals = [
|
|
37
|
+
'SIGINT',
|
|
38
|
+
'SIGTERM',
|
|
39
|
+
'SIGHUP',
|
|
40
|
+
'SIGQUIT',
|
|
41
|
+
];
|
|
42
|
+
signals.forEach((sig) => {
|
|
43
|
+
process.on(sig, () => forward(sig));
|
|
44
|
+
});
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
child.on('error', (err) => {
|
|
47
|
+
reject(new Error(`Failed to execute docker compose: ${err.message}`));
|
|
48
|
+
});
|
|
49
|
+
child.on('exit', (code, signal) => {
|
|
50
|
+
if (code !== null && code !== 0) {
|
|
51
|
+
reject(new ComposeExecError(`The shell exited unexpectedly. ${code}`, code, signal));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
resolve();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
import { AbstractOperation } from '../../../operations/index.js';
|
|
3
|
+
declare const schema: z.ZodObject<{
|
|
4
|
+
shell: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
5
|
+
service: z.ZodString;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
export declare class ComposeExecShellOperation extends AbstractOperation<typeof schema, void> {
|
|
8
|
+
constructor();
|
|
9
|
+
protected _run(input: z.input<typeof schema>): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import { ShellExitError } from '../../../errors.js';
|
|
4
|
+
import { AbstractOperation } from '../../../operations/index.js';
|
|
5
|
+
const schema = z.object({
|
|
6
|
+
shell: z.string().default('bash').optional(),
|
|
7
|
+
service: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe('The name of the compose service to exec a shell'),
|
|
10
|
+
});
|
|
11
|
+
export class ComposeExecShellOperation extends AbstractOperation {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(schema);
|
|
14
|
+
}
|
|
15
|
+
async _run(input) {
|
|
16
|
+
const { monorepo } = this.context;
|
|
17
|
+
const cmd = 'docker';
|
|
18
|
+
const args = ['compose', 'exec', input.service, input.shell || 'bash'];
|
|
19
|
+
const child = spawn(cmd, args, {
|
|
20
|
+
stdio: 'inherit',
|
|
21
|
+
cwd: monorepo.rootDir,
|
|
22
|
+
env: {
|
|
23
|
+
...process.env,
|
|
24
|
+
DOCKER_CLI_HINTS: 'false',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const forward = (sig) => {
|
|
28
|
+
try {
|
|
29
|
+
child.kill(sig);
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
};
|
|
33
|
+
const signals = [
|
|
34
|
+
'SIGINT',
|
|
35
|
+
'SIGTERM',
|
|
36
|
+
'SIGHUP',
|
|
37
|
+
'SIGQUIT',
|
|
38
|
+
];
|
|
39
|
+
signals.forEach((sig) => {
|
|
40
|
+
process.on(sig, () => forward(sig));
|
|
41
|
+
});
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
child.on('error', (err) => {
|
|
44
|
+
reject(new Error(`Failed to execute docker: ${err.message}`));
|
|
45
|
+
});
|
|
46
|
+
child.on('exit', (code, signal) => {
|
|
47
|
+
if (code !== null && code !== 0) {
|
|
48
|
+
reject(new ShellExitError(`The shell exited unexpectedly. ${code}`, input.service, code, signal));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
resolve();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,42 +1,74 @@
|
|
|
1
|
-
import { stat, statfs } from 'node:fs/promises';
|
|
1
|
+
import { readdir, stat, statfs } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import pMap from 'p-map';
|
|
4
4
|
import { GitPrerequisitePlugin } from '../../prerequisites/index.js';
|
|
5
5
|
import { ResourceFactory, } from '../../monorepo/resources/ResourceFactory.js';
|
|
6
6
|
import { BuildImageOperation } from '../operations/index.js';
|
|
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
|
-
|
|
7
|
+
class DockerImageResourceBuilder {
|
|
8
|
+
buildContext;
|
|
9
|
+
context;
|
|
10
|
+
constructor(buildContext) {
|
|
11
|
+
this.buildContext = buildContext;
|
|
12
|
+
this.context = this.config.context
|
|
13
|
+
? this.config.context[0] === '/'
|
|
14
|
+
? buildContext.monorepo.join(this.config.context)
|
|
15
|
+
: buildContext.component.join(this.config.context)
|
|
16
|
+
: buildContext.monorepo.join(buildContext.component.rootDir);
|
|
17
|
+
}
|
|
18
|
+
get monorepo() {
|
|
19
|
+
return this.buildContext.monorepo;
|
|
20
|
+
}
|
|
21
|
+
get config() {
|
|
22
|
+
return (this.buildContext.config.params || {});
|
|
23
|
+
}
|
|
24
|
+
get component() {
|
|
25
|
+
return this.buildContext.component;
|
|
26
|
+
}
|
|
27
|
+
async build() {
|
|
28
|
+
// Ensure the folder exists
|
|
29
|
+
await statfs(this.context);
|
|
30
|
+
const imageName = [
|
|
31
|
+
this.monorepo.name,
|
|
32
|
+
this.config.tag || this.component.name,
|
|
33
|
+
].join('/');
|
|
34
|
+
const tagName = this.config.tag || this.monorepo.defaults.docker?.tag || 'latest';
|
|
35
|
+
const sources = await readdir(this.context, { recursive: true });
|
|
36
|
+
const buildParams = {
|
|
37
|
+
context: this.context,
|
|
38
|
+
dockerfile: this.config.dockerfile || 'Dockerfile',
|
|
39
|
+
src: sources,
|
|
40
|
+
buildArgs: await this.monorepo.expand({
|
|
41
|
+
...this.monorepo.defaults.docker?.buildArgs,
|
|
42
|
+
...this.config.buildArgs,
|
|
43
|
+
}),
|
|
44
|
+
tag: await this.monorepo.expand(`${imageName}:${tagName}`),
|
|
45
|
+
labels: await this.monorepo.expand({
|
|
46
|
+
...this.config.labels,
|
|
47
|
+
'emb/project': this.monorepo.name,
|
|
48
|
+
'emb/component': this.component.name,
|
|
49
|
+
'emb/flavor': this.monorepo.currentFlavor,
|
|
50
|
+
}),
|
|
51
|
+
target: this.config.target,
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
input: buildParams,
|
|
55
|
+
operation: new BuildImageOperation(),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async mustBuild(sentinel) {
|
|
59
|
+
const plugin = new GitPrerequisitePlugin();
|
|
60
|
+
const sources = await plugin.collect(this.context);
|
|
61
|
+
const lastUpdated = await this.lastUpdatedInfo(sources);
|
|
62
|
+
if (!sentinel) {
|
|
63
|
+
return lastUpdated;
|
|
64
|
+
}
|
|
65
|
+
return lastUpdated && lastUpdated.time.getTime() > sentinel.mtime
|
|
66
|
+
? lastUpdated
|
|
67
|
+
: undefined;
|
|
68
|
+
}
|
|
69
|
+
async lastUpdatedInfo(sources) {
|
|
38
70
|
const stats = await pMap(sources, async (s) => {
|
|
39
|
-
const stats = await stat(join(context, s.path));
|
|
71
|
+
const stats = await stat(join(this.context, s.path));
|
|
40
72
|
return {
|
|
41
73
|
time: stats.mtime,
|
|
42
74
|
path: s.path,
|
|
@@ -48,20 +80,7 @@ const DockerImageOpFactory = async ({ config, component, monorepo }) => {
|
|
|
48
80
|
return stats.reduce((last, entry) => {
|
|
49
81
|
return last.time > entry.time ? last : entry;
|
|
50
82
|
}, stats[0]);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async mustBuild(sentinel) {
|
|
54
|
-
const lastUpdated = await lastUpdatedInfo(sources);
|
|
55
|
-
if (!sentinel) {
|
|
56
|
-
return lastUpdated;
|
|
57
|
-
}
|
|
58
|
-
return lastUpdated && lastUpdated.time.getTime() > sentinel.mtime
|
|
59
|
-
? lastUpdated
|
|
60
|
-
: undefined;
|
|
61
|
-
},
|
|
62
|
-
input: buildParams,
|
|
63
|
-
operation: new BuildImageOperation(),
|
|
64
|
-
};
|
|
65
|
-
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
66
85
|
// Bring better abstraction and register as part of the plugin initialization
|
|
67
|
-
ResourceFactory.register('docker/image',
|
|
86
|
+
ResourceFactory.register('docker/image', async (context) => new DockerImageResourceBuilder(context));
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -58,3 +58,22 @@ export declare class CircularDependencyError extends EMBError {
|
|
|
58
58
|
readonly deps: Array<Array<string>>;
|
|
59
59
|
constructor(msg: string, deps: Array<Array<string>>);
|
|
60
60
|
}
|
|
61
|
+
export declare class ShellExitError extends EMBError {
|
|
62
|
+
service: string;
|
|
63
|
+
exitCode: number;
|
|
64
|
+
signal?: (NodeJS.Signals | null) | undefined;
|
|
65
|
+
constructor(msg: string, service: string, exitCode: number, signal?: (NodeJS.Signals | null) | undefined);
|
|
66
|
+
}
|
|
67
|
+
export declare class NoContainerFoundError extends EMBError {
|
|
68
|
+
component: string;
|
|
69
|
+
constructor(msg: string, component: string);
|
|
70
|
+
}
|
|
71
|
+
export declare class MultipleContainersFoundError extends EMBError {
|
|
72
|
+
component: string;
|
|
73
|
+
constructor(msg: string, component: string);
|
|
74
|
+
}
|
|
75
|
+
export declare class ComposeExecError extends EMBError {
|
|
76
|
+
exitCode: number;
|
|
77
|
+
signal?: (NodeJS.Signals | null) | undefined;
|
|
78
|
+
constructor(msg: string, exitCode: number, signal?: (NodeJS.Signals | null) | undefined);
|
|
79
|
+
}
|
package/dist/src/errors.js
CHANGED
|
@@ -44,7 +44,7 @@ export class AmbiguousReferenceError extends EMBError {
|
|
|
44
44
|
ref;
|
|
45
45
|
matches;
|
|
46
46
|
constructor(msg, ref, matches) {
|
|
47
|
-
super('
|
|
47
|
+
super('AMBIGUOUS_REF', msg);
|
|
48
48
|
this.ref = ref;
|
|
49
49
|
this.matches = matches;
|
|
50
50
|
}
|
|
@@ -70,3 +70,37 @@ export class CircularDependencyError extends EMBError {
|
|
|
70
70
|
this.deps = deps;
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
export class ShellExitError extends EMBError {
|
|
74
|
+
service;
|
|
75
|
+
exitCode;
|
|
76
|
+
signal;
|
|
77
|
+
constructor(msg, service, exitCode, signal) {
|
|
78
|
+
super('SHELL_EXIT_ERR', msg);
|
|
79
|
+
this.service = service;
|
|
80
|
+
this.exitCode = exitCode;
|
|
81
|
+
this.signal = signal;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export class NoContainerFoundError extends EMBError {
|
|
85
|
+
component;
|
|
86
|
+
constructor(msg, component) {
|
|
87
|
+
super('CMP_NO_CONTAINER', msg);
|
|
88
|
+
this.component = component;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export class MultipleContainersFoundError extends EMBError {
|
|
92
|
+
component;
|
|
93
|
+
constructor(msg, component) {
|
|
94
|
+
super('CMP_NO_CONTAINER', msg);
|
|
95
|
+
this.component = component;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export class ComposeExecError extends EMBError {
|
|
99
|
+
exitCode;
|
|
100
|
+
signal;
|
|
101
|
+
constructor(msg, exitCode, signal) {
|
|
102
|
+
super('COMPOSE_EXEC_ERR', msg);
|
|
103
|
+
this.exitCode = exitCode;
|
|
104
|
+
this.signal = signal;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -84,35 +84,42 @@ export class BuildResourcesOperation extends AbstractOperation {
|
|
|
84
84
|
},
|
|
85
85
|
// Actual build
|
|
86
86
|
{
|
|
87
|
-
title: `
|
|
87
|
+
title: `Checking cache for ${resource.id}`,
|
|
88
88
|
/** Skip the build if the builder knows it can be skipped */
|
|
89
|
-
task: async (ctx
|
|
89
|
+
task: async (ctx) => {
|
|
90
90
|
if (ctx.builder?.mustBuild) {
|
|
91
91
|
const previousSentinelData = await this.readSentinelFile(resource);
|
|
92
92
|
ctx.sentinelData =
|
|
93
93
|
await ctx.builder.mustBuild(previousSentinelData);
|
|
94
94
|
ctx.cacheHit = !ctx.sentinelData;
|
|
95
95
|
}
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
title: `Build image for ${resource.id}`,
|
|
100
|
+
async task(ctx, task) {
|
|
101
|
+
const skip = (prefix) => {
|
|
98
102
|
parentTask.title = `${prefix} ${resource.id}`;
|
|
99
103
|
task.skip();
|
|
100
104
|
return parentTask.skip();
|
|
105
|
+
};
|
|
106
|
+
if (ctx.cacheHit && !ctx.force && !ctx.dryRun) {
|
|
107
|
+
return skip('[cache hit]');
|
|
108
|
+
}
|
|
109
|
+
const { input, operation } = await ctx.builder.build();
|
|
110
|
+
ctx.builderInput = input;
|
|
111
|
+
if (ctx.dryRun) {
|
|
112
|
+
return skip('[dry run]');
|
|
101
113
|
}
|
|
102
|
-
return
|
|
114
|
+
return operation.run(ctx.builderInput);
|
|
103
115
|
},
|
|
104
116
|
},
|
|
105
117
|
{
|
|
106
118
|
// Return build meta data and dump
|
|
107
119
|
// cache data into sentinel file
|
|
108
120
|
task: async (ctx) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
delete ctx.builder.mustBuild;
|
|
112
|
-
}
|
|
113
|
-
if (ctx.builder?.operation) {
|
|
114
|
-
// @ts-expect-error duynno
|
|
115
|
-
delete ctx.builder.operation;
|
|
121
|
+
if (ctx.builder) {
|
|
122
|
+
delete ctx.builder;
|
|
116
123
|
}
|
|
117
124
|
//
|
|
118
125
|
parentContext[resource.id] = ctx;
|
|
@@ -140,7 +147,14 @@ export class BuildResourcesOperation extends AbstractOperation {
|
|
|
140
147
|
}
|
|
141
148
|
async readSentinelFile(resource) {
|
|
142
149
|
const path = this.sentinelFilePath(resource);
|
|
150
|
+
const stats = await this.context.monorepo.store.stat(path, false);
|
|
151
|
+
if (!stats) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
143
154
|
const data = await this.context.monorepo.store.readFile(path, false);
|
|
144
|
-
return
|
|
155
|
+
return {
|
|
156
|
+
data,
|
|
157
|
+
mtime: stats.mtime.getTime(),
|
|
158
|
+
};
|
|
145
159
|
}
|
|
146
160
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getContext } from '../../../index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { ComposeExecOperation } from '../../../docker/index.js';
|
|
3
3
|
import { EMBCollection, findRunOrder, taskManagerFactory, } from '../../index.js';
|
|
4
|
-
import { ExecuteLocalCommandOperation
|
|
4
|
+
import { ExecuteLocalCommandOperation } from '../index.js';
|
|
5
5
|
export var ExecutorType;
|
|
6
6
|
(function (ExecutorType) {
|
|
7
7
|
ExecutorType["container"] = "container";
|
|
@@ -53,18 +53,17 @@ export class RunTasksOperation {
|
|
|
53
53
|
}
|
|
54
54
|
async runDocker(task, out) {
|
|
55
55
|
const { monorepo } = getContext();
|
|
56
|
-
|
|
57
|
-
return monorepo.run(new ContainerExecOperation(out), {
|
|
56
|
+
return monorepo.run(new ComposeExecOperation(out), {
|
|
58
57
|
attachStderr: true,
|
|
59
58
|
attachStdout: true,
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
service: task.component,
|
|
60
|
+
command: task.script,
|
|
62
61
|
});
|
|
63
62
|
}
|
|
64
63
|
async runLocal(task) {
|
|
65
64
|
const { monorepo } = getContext();
|
|
66
65
|
const cwd = task.component
|
|
67
|
-
? monorepo.component(task.component).rootDir
|
|
66
|
+
? monorepo.join(monorepo.component(task.component).rootDir)
|
|
68
67
|
: monorepo.rootDir;
|
|
69
68
|
return monorepo.run(new ExecuteLocalCommandOperation(), {
|
|
70
69
|
script: task.script,
|
|
@@ -2,12 +2,16 @@ import { CreateFileOperation } from '../index.js';
|
|
|
2
2
|
import { ResourceFactory } from './ResourceFactory.js';
|
|
3
3
|
// Bring better abstraction and register as part of the plugin initialization
|
|
4
4
|
ResourceFactory.register('file', async ({ config, component }) => {
|
|
5
|
-
const fromConfig = (config.params || {});
|
|
6
|
-
const input = {
|
|
7
|
-
path: component.join(fromConfig?.path || config.name),
|
|
8
|
-
};
|
|
9
5
|
return {
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
async build() {
|
|
7
|
+
const fromConfig = (config.params || {});
|
|
8
|
+
const input = {
|
|
9
|
+
path: component.join(fromConfig?.path || config.name),
|
|
10
|
+
};
|
|
11
|
+
return {
|
|
12
|
+
input,
|
|
13
|
+
operation: new CreateFileOperation(),
|
|
14
|
+
};
|
|
15
|
+
},
|
|
12
16
|
};
|
|
13
17
|
});
|
|
@@ -10,8 +10,10 @@ export type SentinelData<T = void> = {
|
|
|
10
10
|
data: T;
|
|
11
11
|
};
|
|
12
12
|
export type ResourceBuilderInfo<I, O, D = unknown> = {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
build(): Promise<{
|
|
14
|
+
input: I;
|
|
15
|
+
operation: IOperation<I, O>;
|
|
16
|
+
}>;
|
|
15
17
|
mustBuild?: (previousSentinelData: SentinelData<D> | undefined) => Promise<undefined | unknown>;
|
|
16
18
|
};
|
|
17
19
|
export type ResourceFactoryOutput<I, O> = Promise<ResourceBuilderInfo<I, O>>;
|
|
@@ -14,6 +14,7 @@ export declare class EMBStore {
|
|
|
14
14
|
init(): Promise<void>;
|
|
15
15
|
join(path: string): string;
|
|
16
16
|
mkdirp(path: string): Promise<void>;
|
|
17
|
+
stat(path: string, mustExist?: boolean): Promise<import("fs").Stats | undefined>;
|
|
17
18
|
readFile(path: string, mustExist?: boolean): Promise<string | undefined>;
|
|
18
19
|
trash(): Promise<void>;
|
|
19
20
|
writeFile(path: string, data: string): Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { constants, createReadStream, createWriteStream } from 'node:fs';
|
|
2
|
-
import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { access, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join, normalize } from 'node:path';
|
|
4
4
|
/**
|
|
5
5
|
* A first implementation of a "store" where
|
|
@@ -54,6 +54,17 @@ export class EMBStore {
|
|
|
54
54
|
const normalized = normalize(join('/', path));
|
|
55
55
|
await mkdir(this.join(normalized), { recursive: true });
|
|
56
56
|
}
|
|
57
|
+
async stat(path, mustExist = true) {
|
|
58
|
+
try {
|
|
59
|
+
return await stat(this.join(path));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error.code === 'ENOENT' && !mustExist) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
57
68
|
async readFile(path, mustExist = true) {
|
|
58
69
|
try {
|
|
59
70
|
return (await readFile(this.join(path))).toString();
|
package/oclif.manifest.json
CHANGED
|
@@ -217,6 +217,57 @@
|
|
|
217
217
|
"logs.js"
|
|
218
218
|
]
|
|
219
219
|
},
|
|
220
|
+
"components:shell": {
|
|
221
|
+
"aliases": [
|
|
222
|
+
"shell"
|
|
223
|
+
],
|
|
224
|
+
"args": {
|
|
225
|
+
"component": {
|
|
226
|
+
"description": "The component you want to get a shell on",
|
|
227
|
+
"name": "component",
|
|
228
|
+
"required": true
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
"description": "Get a shell on a running component.",
|
|
232
|
+
"examples": [
|
|
233
|
+
"<%= config.bin %> <%= command.id %>"
|
|
234
|
+
],
|
|
235
|
+
"flags": {
|
|
236
|
+
"flavor": {
|
|
237
|
+
"description": "Specify the flavor to use.",
|
|
238
|
+
"name": "flavor",
|
|
239
|
+
"required": false,
|
|
240
|
+
"hasDynamicHelp": false,
|
|
241
|
+
"multiple": false,
|
|
242
|
+
"type": "option"
|
|
243
|
+
},
|
|
244
|
+
"shell": {
|
|
245
|
+
"char": "s",
|
|
246
|
+
"description": "The shell to run",
|
|
247
|
+
"name": "shell",
|
|
248
|
+
"default": "bash",
|
|
249
|
+
"hasDynamicHelp": false,
|
|
250
|
+
"multiple": false,
|
|
251
|
+
"type": "option"
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
"hasDynamicHelp": false,
|
|
255
|
+
"hiddenAliases": [],
|
|
256
|
+
"id": "components:shell",
|
|
257
|
+
"pluginAlias": "@enspirit/emb",
|
|
258
|
+
"pluginName": "@enspirit/emb",
|
|
259
|
+
"pluginType": "core",
|
|
260
|
+
"enableJsonFlag": false,
|
|
261
|
+
"isESM": true,
|
|
262
|
+
"relativePath": [
|
|
263
|
+
"dist",
|
|
264
|
+
"src",
|
|
265
|
+
"cli",
|
|
266
|
+
"commands",
|
|
267
|
+
"components",
|
|
268
|
+
"shell.js"
|
|
269
|
+
]
|
|
270
|
+
},
|
|
220
271
|
"config:print": {
|
|
221
272
|
"aliases": [],
|
|
222
273
|
"args": {},
|
|
@@ -667,5 +718,5 @@
|
|
|
667
718
|
]
|
|
668
719
|
}
|
|
669
720
|
},
|
|
670
|
-
"version": "0.
|
|
721
|
+
"version": "0.3.0"
|
|
671
722
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enspirit/emb",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"keywords": ["monorepo", "docker", "taskrunner", "ci", "docker compose", "sentinel", "makefile"],
|
|
6
6
|
"author": "Louis Lambeau <louis.lambeau@enspirit.be>",
|
|
7
7
|
"license": "ISC",
|
|
File without changes
|
|
File without changes
|