@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.
Files changed (25) hide show
  1. package/README.md +53 -1
  2. package/dist/src/cli/commands/components/shell.d.ts +14 -0
  3. package/dist/src/cli/commands/components/shell.js +41 -0
  4. package/dist/src/docker/compose/operations/ComposeExecOperation.d.ts +13 -0
  5. package/dist/src/docker/compose/operations/ComposeExecOperation.js +59 -0
  6. package/dist/src/docker/compose/operations/ComposeExecShellOperation.d.ts +11 -0
  7. package/dist/src/docker/compose/operations/ComposeExecShellOperation.js +56 -0
  8. package/dist/src/docker/compose/operations/index.d.ts +2 -0
  9. package/dist/src/docker/compose/operations/index.js +2 -0
  10. package/dist/src/docker/operations/containers/index.d.ts +1 -1
  11. package/dist/src/docker/operations/containers/index.js +1 -1
  12. package/dist/src/docker/resources/DockerImageResource.js +68 -49
  13. package/dist/src/errors.d.ts +19 -0
  14. package/dist/src/errors.js +35 -1
  15. package/dist/src/monorepo/operations/resources/BuildResourcesOperation.d.ts +1 -0
  16. package/dist/src/monorepo/operations/resources/BuildResourcesOperation.js +27 -13
  17. package/dist/src/monorepo/operations/tasks/RunTasksOperation.js +6 -7
  18. package/dist/src/monorepo/resources/FileResource.js +10 -6
  19. package/dist/src/monorepo/resources/ResourceFactory.d.ts +4 -2
  20. package/dist/src/monorepo/store/index.d.ts +1 -0
  21. package/dist/src/monorepo/store/index.js +12 -1
  22. package/oclif.manifest.json +52 -1
  23. package/package.json +1 -1
  24. /package/dist/src/docker/operations/containers/{ExecContainerOperation.d.ts → ContainerExecOperation.d.ts} +0 -0
  25. /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.1.3 darwin-x64 node-v22.12.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,2 +1,4 @@
1
1
  export * from './ComposeDownOperation.js';
2
+ export * from './ComposeExecOperation.js';
3
+ export * from './ComposeExecShellOperation.js';
2
4
  export * from './ComposeUpOperation.js';
@@ -1,2 +1,4 @@
1
1
  export * from './ComposeDownOperation.js';
2
+ export * from './ComposeExecOperation.js';
3
+ export * from './ComposeExecShellOperation.js';
2
4
  export * from './ComposeUpOperation.js';
@@ -1,3 +1,3 @@
1
- export * from './ExecContainerOperation.js';
1
+ export * from './ContainerExecOperation.js';
2
2
  export * from './ListContainersOperation.js';
3
3
  export * from './PruneContainersOperation.js';
@@ -1,3 +1,3 @@
1
- export * from './ExecContainerOperation.js';
1
+ export * from './ContainerExecOperation.js';
2
2
  export * from './ListContainersOperation.js';
3
3
  export * from './PruneContainersOperation.js';
@@ -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
- const DockerImageOpFactory = async ({ config, component, monorepo }) => {
8
- const fromConfig = (config.params || {});
9
- const context = fromConfig.context
10
- ? fromConfig.context[0] === '/'
11
- ? monorepo.join(fromConfig.context)
12
- : component.join(fromConfig.context)
13
- : monorepo.join(component.rootDir);
14
- // Ensure the folder exists
15
- await statfs(context);
16
- const plugin = new GitPrerequisitePlugin();
17
- const sources = await plugin.collect(context);
18
- const imageName = [monorepo.name, fromConfig.tag || component.name].join('/');
19
- const tagName = fromConfig.tag || monorepo.defaults.docker?.tag || 'latest';
20
- const buildParams = {
21
- context,
22
- dockerfile: fromConfig.dockerfile || 'Dockerfile',
23
- src: sources.map((s) => s.path),
24
- buildArgs: await monorepo.expand({
25
- ...monorepo.defaults.docker?.buildArgs,
26
- ...fromConfig.buildArgs,
27
- }),
28
- tag: await monorepo.expand(`${imageName}:${tagName}`),
29
- labels: await monorepo.expand({
30
- ...fromConfig.labels,
31
- 'emb/project': monorepo.name,
32
- 'emb/component': component.name,
33
- 'emb/flavor': monorepo.currentFlavor,
34
- }),
35
- target: fromConfig.target,
36
- };
37
- const lastUpdatedInfo = async (sources) => {
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
- return {
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', DockerImageOpFactory);
86
+ ResourceFactory.register('docker/image', async (context) => new DockerImageResourceBuilder(context));
@@ -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
+ }
@@ -44,7 +44,7 @@ export class AmbiguousReferenceError extends EMBError {
44
44
  ref;
45
45
  matches;
46
46
  constructor(msg, ref, matches) {
47
- super('AMBIGUOUS_TASK', msg);
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
+ }
@@ -7,6 +7,7 @@ export type BuildResourceMeta = {
7
7
  force?: boolean;
8
8
  resource?: ResourceInfo;
9
9
  builder?: ResourceBuilderInfo<unknown, unknown>;
10
+ builderInput?: unknown;
10
11
  sentinelData?: unknown;
11
12
  cacheHit?: boolean;
12
13
  };
@@ -84,35 +84,42 @@ export class BuildResourcesOperation extends AbstractOperation {
84
84
  },
85
85
  // Actual build
86
86
  {
87
- title: `Build ${resource.id}`,
87
+ title: `Checking cache for ${resource.id}`,
88
88
  /** Skip the build if the builder knows it can be skipped */
89
- task: async (ctx, task) => {
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
- if (!ctx.force && (ctx.dryRun || ctx.cacheHit)) {
97
- const prefix = ctx.dryRun ? '[dry run]' : '[cache hit]';
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 ctx.builder.operation.run(ctx.builder?.input);
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
- // TODO: clean this
110
- if (ctx.builder?.mustBuild) {
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 data ? JSON.parse(data) : undefined;
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 { ContainerExecOperation } from '../../../docker/index.js';
2
+ import { ComposeExecOperation } from '../../../docker/index.js';
3
3
  import { EMBCollection, findRunOrder, taskManagerFactory, } from '../../index.js';
4
- import { ExecuteLocalCommandOperation, GetComponentContainerOperation, } from '../index.js';
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
- const containerInfo = await monorepo.run(new GetComponentContainerOperation(), task.component);
57
- return monorepo.run(new ContainerExecOperation(out), {
56
+ return monorepo.run(new ComposeExecOperation(out), {
58
57
  attachStderr: true,
59
58
  attachStdout: true,
60
- container: containerInfo.Id,
61
- script: task.script,
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
- input,
11
- operation: new CreateFileOperation(),
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
- input: I;
14
- operation: IOperation<I, O>;
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();
@@ -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.1.3"
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.1.3",
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",