@enspirit/emb 0.5.3 → 0.7.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 (35) hide show
  1. package/README.md +27 -1
  2. package/dist/src/cli/abstract/BaseCommand.js +3 -1
  3. package/dist/src/cli/commands/components/logs.js +4 -17
  4. package/dist/src/cli/commands/containers/index.js +1 -1
  5. package/dist/src/cli/commands/images/push.d.ts +11 -0
  6. package/dist/src/cli/commands/images/push.js +29 -0
  7. package/dist/src/cli/commands/resources/index.js +9 -1
  8. package/dist/src/config/schema.json +3 -0
  9. package/dist/src/docker/compose/client.d.ts +24 -0
  10. package/dist/src/docker/compose/client.js +55 -0
  11. package/dist/src/docker/compose/index.d.ts +1 -0
  12. package/dist/src/docker/compose/index.js +1 -0
  13. package/dist/src/docker/index.d.ts +0 -1
  14. package/dist/src/docker/index.js +0 -1
  15. package/dist/src/docker/operations/images/PushImagesOperation.d.ts +9 -3
  16. package/dist/src/docker/operations/images/PushImagesOperation.js +104 -6
  17. package/dist/src/docker/resources/DockerImageResource.js +9 -6
  18. package/dist/src/monorepo/component.d.ts +1 -0
  19. package/dist/src/monorepo/component.js +3 -0
  20. package/dist/src/monorepo/monorepo.js +1 -8
  21. package/dist/src/monorepo/operations/fs/CreateFileOperation.d.ts +2 -0
  22. package/dist/src/monorepo/operations/fs/CreateFileOperation.js +18 -4
  23. package/dist/src/monorepo/operations/tasks/RunTasksOperation.js +4 -17
  24. package/dist/src/monorepo/resources/FileResourceBuilder.d.ts +3 -0
  25. package/dist/src/monorepo/resources/FileResourceBuilder.js +6 -1
  26. package/dist/src/monorepo/resources/ResourceFactory.js +1 -1
  27. package/dist/src/monorepo/resources/abstract/AbstractResourceBuilder.d.ts +1 -0
  28. package/dist/src/monorepo/resources/types.d.ts +6 -0
  29. package/dist/src/types.d.ts +2 -0
  30. package/oclif.manifest.json +57 -1
  31. package/package.json +1 -1
  32. package/dist/src/docker/containers/getContainer.d.ts +0 -2
  33. package/dist/src/docker/containers/getContainer.js +0 -5
  34. package/dist/src/docker/containers/index.d.ts +0 -1
  35. package/dist/src/docker/containers/index.js +0 -1
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.5.3 darwin-x64 node-v22.18.0
17
+ @enspirit/emb/0.7.0 darwin-arm64 node-v22.14.0
18
18
  $ emb --help [COMMAND]
19
19
  USAGE
20
20
  $ emb COMMAND
@@ -36,6 +36,7 @@ USAGE
36
36
  * [`emb images`](#emb-images)
37
37
  * [`emb images delete`](#emb-images-delete)
38
38
  * [`emb images prune`](#emb-images-prune)
39
+ * [`emb images push`](#emb-images-push)
39
40
  * [`emb logs COMPONENT`](#emb-logs-component)
40
41
  * [`emb ps`](#emb-ps)
41
42
  * [`emb resources`](#emb-resources)
@@ -338,6 +339,31 @@ EXAMPLES
338
339
  $ emb images prune
339
340
  ```
340
341
 
342
+ ## `emb images push`
343
+
344
+ Push docker images.
345
+
346
+ ```
347
+ USAGE
348
+ $ emb images push [--json] [--flavor <value>] [--registry <value>] [--retag <value>]
349
+
350
+ FLAGS
351
+ --flavor=<value> Specify the flavor to use.
352
+ --registry=<value> Override the registry to push to
353
+ --retag=<value> Override the original tag to push to a new tag
354
+
355
+ GLOBAL FLAGS
356
+ --json Format output as json.
357
+
358
+ DESCRIPTION
359
+ Push docker images.
360
+
361
+ EXAMPLES
362
+ $ emb images push
363
+
364
+ $ emb images push --registry my.registry.io --retag newtag
365
+ ```
366
+
341
367
  ## `emb logs COMPONENT`
342
368
 
343
369
  Get components logs.
@@ -1,4 +1,4 @@
1
- import { getContext, setContext } from '../../index.js';
1
+ import { DockerComposeClient, getContext, setContext } from '../../index.js';
2
2
  import { Command, Performance } from '@oclif/core';
3
3
  import Dockerode from 'dockerode';
4
4
  import { loadConfig } from '../../config/index.js';
@@ -21,9 +21,11 @@ export class BaseCommand extends Command {
21
21
  const monorepo = await withMarker('emb:monorepo', 'init', () => {
22
22
  return new Monorepo(config, rootDir).init();
23
23
  });
24
+ const compose = new DockerComposeClient(monorepo);
24
25
  this.context = setContext({
25
26
  docker: new Dockerode(),
26
27
  monorepo,
28
+ compose,
27
29
  });
28
30
  }
29
31
  catch (error) {
@@ -1,6 +1,5 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import { BaseCommand, getContext } from '../../index.js';
3
- import { ListContainersOperation } from '../../../docker/index.js';
4
3
  export default class ComponentsLogs extends BaseCommand {
5
4
  static aliases = ['logs'];
6
5
  static description = 'Get components logs.';
@@ -24,24 +23,12 @@ export default class ComponentsLogs extends BaseCommand {
24
23
  };
25
24
  async run() {
26
25
  const { flags, args } = await this.parse(ComponentsLogs);
27
- const { monorepo, docker } = await getContext();
26
+ const { monorepo, docker, compose } = await getContext();
28
27
  const component = monorepo.component(args.component);
29
- const containers = await monorepo.run(new ListContainersOperation(), {
30
- all: true,
31
- filters: {
32
- label: [
33
- `emb/project=${monorepo.name}`,
34
- `emb/component=${component.name}`,
35
- ],
36
- },
28
+ const containerId = await compose.getContainer(component.name, {
29
+ mustBeRunning: false,
37
30
  });
38
- if (containers.length === 0) {
39
- return this.error(`No container found for component \`${component.name}\``);
40
- }
41
- if (containers.length > 1) {
42
- return this.error(`More than one container found for component \`${component.name}\``);
43
- }
44
- const container = await docker.getContainer(containers[0].Id);
31
+ const container = await docker.getContainer(containerId);
45
32
  if (flags.follow) {
46
33
  const stream = await container.logs({
47
34
  follow: true,
@@ -36,7 +36,7 @@ export default class ContainersIndex extends BaseCommand {
36
36
  id: shortId(c.Id),
37
37
  image: c.Image,
38
38
  name: c.Names[0] || c.Id,
39
- ports: c.Ports.map((p) => {
39
+ ports: c.Ports?.map((p) => {
40
40
  const parts = [];
41
41
  if (p.IP) {
42
42
  parts.push(p.IP, ':', p.PublicPort, '->');
@@ -0,0 +1,11 @@
1
+ import { FlavoredCommand } from '../../index.js';
2
+ export default class ImagesPush extends FlavoredCommand<typeof ImagesPush> {
3
+ static description: string;
4
+ static enableJsonFlag: boolean;
5
+ static examples: string[];
6
+ static flags: {
7
+ registry: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ retag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ run(): Promise<void>;
11
+ }
@@ -0,0 +1,29 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { FlavoredCommand } from '../../index.js';
3
+ import { PushImagesOperation } from '../../../docker/operations/images/PushImagesOperation.js';
4
+ export default class ImagesPush extends FlavoredCommand {
5
+ static description = 'Push docker images.';
6
+ static enableJsonFlag = true;
7
+ static examples = [
8
+ '<%= config.bin %> <%= command.id %>',
9
+ '<%= config.bin %> <%= command.id %> --registry my.registry.io --retag newtag',
10
+ ];
11
+ static flags = {
12
+ registry: Flags.string({
13
+ name: 'registry',
14
+ description: 'Override the registry to push to',
15
+ }),
16
+ retag: Flags.string({
17
+ name: 'retag',
18
+ description: 'Override the original tag to push to a new tag',
19
+ }),
20
+ };
21
+ async run() {
22
+ const { flags } = await this.parse(ImagesPush);
23
+ const { monorepo } = this.context;
24
+ await monorepo.run(new PushImagesOperation(process.stdout), {
25
+ registry: flags.registry,
26
+ retag: flags.retag,
27
+ });
28
+ }
29
+ }
@@ -1,5 +1,6 @@
1
1
  import { printTable } from '@oclif/table';
2
2
  import { FlavoredCommand, getContext, TABLE_DEFAULTS } from '../../index.js';
3
+ import { ResourceFactory } from '../../../monorepo/resources/ResourceFactory.js';
3
4
  export default class ResourcesIndex extends FlavoredCommand {
4
5
  static description = 'List resources.';
5
6
  static enableJsonFlag = true;
@@ -9,14 +10,21 @@ export default class ResourcesIndex extends FlavoredCommand {
9
10
  const { flags } = await this.parse(ResourcesIndex);
10
11
  const { monorepo } = await getContext();
11
12
  const resources = await Promise.all(monorepo.resources.map(async (config) => {
13
+ const component = monorepo.component(config.component);
14
+ const builder = ResourceFactory.factor(config.type, {
15
+ config,
16
+ monorepo,
17
+ component,
18
+ });
12
19
  return {
13
20
  ...config,
21
+ reference: await builder.getReference(),
14
22
  };
15
23
  }));
16
24
  if (!flags.json) {
17
25
  printTable({
18
26
  ...TABLE_DEFAULTS,
19
- columns: ['name', 'type', 'id'],
27
+ columns: ['name', 'type', 'reference', 'id'],
20
28
  data: resources,
21
29
  sort: {
22
30
  name: 'asc',
@@ -268,6 +268,9 @@
268
268
  "properties": {
269
269
  "path": {
270
270
  "type": "string"
271
+ },
272
+ "script": {
273
+ "type": "string"
271
274
  }
272
275
  }
273
276
  }
@@ -0,0 +1,24 @@
1
+ import { Monorepo } from '../../index.js';
2
+ export type ComposeContainer = {
3
+ Name: string;
4
+ ID: string;
5
+ State: 'exited' | 'running';
6
+ };
7
+ export type ComposeService = {
8
+ name: string;
9
+ containers: Array<ComposeContainer>;
10
+ };
11
+ export type ComposeServices = Map<string, ComposeService>;
12
+ export type GetContainerOptions = {
13
+ mustBeRunning: boolean;
14
+ mustBeUnique: boolean;
15
+ };
16
+ export declare const DefaultGetContainerOptions: GetContainerOptions;
17
+ export declare class DockerComposeClient {
18
+ protected monorepo: Monorepo;
19
+ protected containers?: ComposeServices;
20
+ constructor(monorepo: Monorepo);
21
+ init(): Promise<void>;
22
+ getContainer(serviceName: string, options?: Partial<GetContainerOptions>): Promise<string>;
23
+ private loadContainers;
24
+ }
@@ -0,0 +1,55 @@
1
+ import { execa } from 'execa';
2
+ export const DefaultGetContainerOptions = {
3
+ mustBeRunning: true,
4
+ mustBeUnique: true,
5
+ };
6
+ export class DockerComposeClient {
7
+ monorepo;
8
+ containers;
9
+ constructor(monorepo) {
10
+ this.monorepo = monorepo;
11
+ }
12
+ async init() {
13
+ await this.loadContainers();
14
+ }
15
+ async getContainer(serviceName, options = DefaultGetContainerOptions) {
16
+ const getOptions = { ...DefaultGetContainerOptions, ...options };
17
+ await this.loadContainers();
18
+ const service = this.containers?.get(serviceName);
19
+ if (!service) {
20
+ throw new Error(`No compose service found with name ${serviceName}`);
21
+ }
22
+ const containers = getOptions.mustBeRunning
23
+ ? service.containers.filter((c) => c.State === 'running')
24
+ : service.containers;
25
+ if (containers.length === 0) {
26
+ throw new Error(`No container found for service ${serviceName}`);
27
+ }
28
+ if (containers.length > 1 && getOptions.mustBeUnique) {
29
+ throw new Error(`Multiple containers found`);
30
+ }
31
+ return containers[0].ID;
32
+ }
33
+ async loadContainers(force = false) {
34
+ if (this.containers && !force) {
35
+ return;
36
+ }
37
+ const { stdout } = await execa({
38
+ cwd: this.monorepo.rootDir,
39
+ }) `docker compose ps -a --format json`;
40
+ this.containers = stdout
41
+ .split('\n')
42
+ .map((l) => JSON.parse(l))
43
+ .reduce((services, entry) => {
44
+ if (!services.has(entry.Service)) {
45
+ services.set(entry.Service, {
46
+ name: entry.Service,
47
+ containers: [],
48
+ });
49
+ }
50
+ const svc = services.get(entry.Service);
51
+ svc?.containers.push(entry);
52
+ return services;
53
+ }, new Map());
54
+ }
55
+ }
@@ -1 +1,2 @@
1
+ export * from './client.js';
1
2
  export * from './operations/index.js';
@@ -1 +1,2 @@
1
+ export * from './client.js';
1
2
  export * from './operations/index.js';
@@ -1,6 +1,5 @@
1
1
  import './resources/index.js';
2
2
  export * from './compose/index.js';
3
- export * from './containers/index.js';
4
3
  export * from './images/index.js';
5
4
  export * from './operations/index.js';
6
5
  export * from './protobuf/index.js';
@@ -1,6 +1,5 @@
1
1
  import './resources/index.js';
2
2
  export * from './compose/index.js';
3
- export * from './containers/index.js';
4
3
  export * from './images/index.js';
5
4
  export * from './operations/index.js';
6
5
  export * from './protobuf/index.js';
@@ -1,3 +1,4 @@
1
+ import { Writable } from 'node:stream';
1
2
  import * as z from 'zod';
2
3
  import { AbstractOperation } from '../../../operations/index.js';
3
4
  /**
@@ -5,10 +6,15 @@ import { AbstractOperation } from '../../../operations/index.js';
5
6
  */
6
7
  declare const schema: z.ZodObject<{
7
8
  images: z.ZodOptional<z.ZodArray<z.ZodString>>;
8
- tag: z.ZodDefault<z.ZodOptional<z.ZodString>>;
9
+ tag: z.ZodOptional<z.ZodString>;
10
+ registry: z.ZodOptional<z.ZodString>;
11
+ retag: z.ZodOptional<z.ZodString>;
9
12
  }, z.core.$strip>;
10
13
  export declare class PushImagesOperation extends AbstractOperation<typeof schema, void> {
11
- constructor();
12
- protected _run(_input: z.input<typeof schema>): Promise<void>;
14
+ protected out?: Writable | undefined;
15
+ constructor(out?: Writable | undefined);
16
+ protected _run(input: z.input<typeof schema>): Promise<void>;
17
+ private retagIfNecessary;
18
+ private pushImage;
13
19
  }
14
20
  export {};
@@ -1,4 +1,8 @@
1
+ import { taskManagerFactory } from '../../../index.js';
2
+ import { join } from 'node:path/posix';
3
+ import { Transform } from 'node:stream';
1
4
  import * as z from 'zod';
5
+ import { ResourceFactory } from '../../../monorepo/resources/ResourceFactory.js';
2
6
  import { AbstractOperation } from '../../../operations/index.js';
3
7
  /**
4
8
  * https://docs.docker.com/reference/api/engine/version/v1.37/#tag/Image/operation/ImagePush
@@ -7,16 +11,110 @@ const schema = z.object({
7
11
  images: z
8
12
  .array(z.string())
9
13
  .optional()
10
- .describe('The names of images to push (The name should be provided without tag. Use the `tag` parameter to specify why tag to push)'),
11
- tag: z
14
+ .describe('The names of images to push (The name should be provided without tag. Use the `tag` parameter to specify which tag to push)'),
15
+ tag: z.string().optional().describe('Tag of the images to push'),
16
+ registry: z.string().optional().describe('Override the registry to push to'),
17
+ retag: z
12
18
  .string()
13
19
  .optional()
14
- .default('latest')
15
- .describe('Tag of the images to push'),
20
+ .describe('Override the original tag to push as a new tag'),
16
21
  });
17
22
  export class PushImagesOperation extends AbstractOperation {
18
- constructor() {
23
+ out;
24
+ constructor(out) {
19
25
  super(schema);
26
+ this.out = out;
27
+ }
28
+ async _run(input) {
29
+ const { monorepo } = this.context;
30
+ const references = await Promise.all(monorepo.resources
31
+ .filter((r) => r.type === 'docker/image')
32
+ .map(async (config) => {
33
+ const component = monorepo.component(config.component);
34
+ const builder = ResourceFactory.factor(config.type, {
35
+ config,
36
+ monorepo,
37
+ component,
38
+ });
39
+ return builder.getReference();
40
+ }));
41
+ const manager = taskManagerFactory();
42
+ const tasks = references.map((fullName) => {
43
+ return {
44
+ title: `Push ${fullName}`,
45
+ task: async (ctx, task) => {
46
+ const { imgName, tag } = await this.retagIfNecessary(fullName, input.retag, input.registry);
47
+ task.title = `Pushing ${imgName}:${tag}`;
48
+ return this.pushImage(imgName, tag, task.stdout());
49
+ },
50
+ };
51
+ });
52
+ return manager.run([
53
+ {
54
+ title: 'Push imags',
55
+ async task(ctx, task) {
56
+ return task.newListr(tasks, {
57
+ rendererOptions: {
58
+ collapseSubtasks: false,
59
+ collapseSkips: true,
60
+ },
61
+ });
62
+ },
63
+ },
64
+ ]);
65
+ }
66
+ async retagIfNecessary(fullName, retag, registry) {
67
+ let [imgName, tag] = fullName.split(':');
68
+ // Retag if necessary
69
+ if (retag || registry) {
70
+ const dockerImage = await this.context.docker.getImage(fullName);
71
+ tag = retag || tag;
72
+ imgName = registry ? join(registry, imgName) : imgName;
73
+ await dockerImage.tag({
74
+ tag,
75
+ repo: imgName,
76
+ });
77
+ }
78
+ return { imgName, tag };
79
+ }
80
+ async pushImage(repo, tag, out) {
81
+ const dockerImage = await this.context.docker.getImage(`${repo}:${tag}`);
82
+ const stream = await dockerImage.push({
83
+ authconfig: {
84
+ username: process.env.DOCKER_USERNAME,
85
+ password: process.env.DOCKER_PASSWORD,
86
+ },
87
+ });
88
+ const transform = new Transform({
89
+ transform(chunk, encoding, callback) {
90
+ const lines = chunk.toString().split('\n');
91
+ lines.forEach((line) => {
92
+ if (!line.trim()) {
93
+ return;
94
+ }
95
+ try {
96
+ const { status } = JSON.parse(line.trim());
97
+ out?.write(status + '\n');
98
+ }
99
+ catch (error) {
100
+ out?.write(error + '\n');
101
+ }
102
+ });
103
+ callback();
104
+ },
105
+ });
106
+ stream.pipe(transform).pipe(process.stdout);
107
+ await new Promise((resolve, reject) => {
108
+ this.context.docker.modem.followProgress(stream, (err, data) => {
109
+ if (err) {
110
+ return reject(err);
111
+ }
112
+ const hasError = data.find((d) => Boolean(d.error));
113
+ if (hasError) {
114
+ return reject(new Error(hasError.error));
115
+ }
116
+ resolve(null);
117
+ });
118
+ });
20
119
  }
21
- async _run(_input) { }
22
120
  }
@@ -18,6 +18,14 @@ class DockerImageResourceBuilder extends SentinelFileBasedBuilder {
18
18
  : buildContext.component.join(this.config.context)
19
19
  : buildContext.monorepo.join(buildContext.component.rootDir);
20
20
  }
21
+ async getReference() {
22
+ const imageName = [
23
+ this.monorepo.name,
24
+ this.config?.tag || this.component.name,
25
+ ].join('/');
26
+ const tagName = this.config?.tag || this.monorepo.defaults.docker?.tag || 'latest';
27
+ return this.monorepo.expand(`${imageName}:${tagName}`);
28
+ }
21
29
  get monorepo() {
22
30
  return this.buildContext.monorepo;
23
31
  }
@@ -30,11 +38,6 @@ class DockerImageResourceBuilder extends SentinelFileBasedBuilder {
30
38
  async _build(_resource, out) {
31
39
  // Ensure the folder exists
32
40
  await statfs(this.dockerContext);
33
- const imageName = [
34
- this.monorepo.name,
35
- this.config?.tag || this.component.name,
36
- ].join('/');
37
- const tagName = this.config?.tag || this.monorepo.defaults.docker?.tag || 'latest';
38
41
  const crawler = new Fdir();
39
42
  const sources = await crawler
40
43
  .withRelativePaths()
@@ -48,7 +51,7 @@ class DockerImageResourceBuilder extends SentinelFileBasedBuilder {
48
51
  ...this.monorepo.defaults.docker?.buildArgs,
49
52
  ...this.config?.buildArgs,
50
53
  }),
51
- tag: await this.monorepo.expand(`${imageName}:${tagName}`),
54
+ tag: await this.getReference(),
52
55
  labels: await this.monorepo.expand({
53
56
  ...this.config?.labels,
54
57
  'emb/project': this.monorepo.name,
@@ -15,4 +15,5 @@ export declare class Component implements ComponentConfig {
15
15
  toJSON(): ComponentConfig;
16
16
  withFlavor(name: string): Component;
17
17
  join(path: string): string;
18
+ relative(path: string): string;
18
19
  }
@@ -54,4 +54,7 @@ export class Component {
54
54
  join(path) {
55
55
  return this.monorepo.join(join(this.rootDir, path));
56
56
  }
57
+ relative(path) {
58
+ return join(this.rootDir, path);
59
+ }
57
60
  }
@@ -75,14 +75,7 @@ export class Monorepo {
75
75
  }
76
76
  get resources() {
77
77
  return this.components.reduce((resources, cmp) => {
78
- const cmpResources = Object.entries(cmp.resources || {}).map(([name, task]) => {
79
- return {
80
- ...task,
81
- name,
82
- id: `${cmp.name}:${name}`,
83
- };
84
- });
85
- return [...resources, ...cmpResources];
78
+ return [...resources, ...Object.values(cmp.resources)];
86
79
  }, []);
87
80
  }
88
81
  resource(nameOrId) {
@@ -3,6 +3,8 @@ import * as z from 'zod';
3
3
  import { AbstractOperation } from '../../../operations/index.js';
4
4
  declare const schema: z.ZodObject<{
5
5
  path: z.ZodString;
6
+ script: z.ZodOptional<z.ZodString>;
7
+ cwd: z.ZodOptional<z.ZodString>;
6
8
  force: z.ZodOptional<z.ZodBoolean>;
7
9
  }, z.core.$strip>;
8
10
  export declare class CreateFileOperation extends AbstractOperation<typeof schema, unknown> {
@@ -1,9 +1,12 @@
1
+ import { execa } from 'execa';
1
2
  import { open, statfs, utimes } from 'node:fs/promises';
2
3
  import * as z from 'zod';
3
4
  import { AbstractOperation } from '../../../operations/index.js';
4
5
  const schema = z.object({
5
6
  //
6
7
  path: z.string().describe('Path to the file to create'),
8
+ script: z.string().optional().describe('The script to generate the file'),
9
+ cwd: z.string().optional().describe('Working directory to execute scripts'),
7
10
  force: z
8
11
  .boolean()
9
12
  .optional()
@@ -16,6 +19,7 @@ export class CreateFileOperation extends AbstractOperation {
16
19
  this.out = out;
17
20
  }
18
21
  async _run(input) {
22
+ // Check if the file exists, if so our work is done here
19
23
  try {
20
24
  await statfs(input.path);
21
25
  if (input.force) {
@@ -23,11 +27,21 @@ export class CreateFileOperation extends AbstractOperation {
23
27
  }
24
28
  }
25
29
  catch (error) {
26
- if (error?.code === 'ENOENT') {
27
- const fn = await open(input.path, 'a');
28
- return fn.close();
30
+ // Ignore ENOENT error (file does not exist)
31
+ if (error?.code !== 'ENOENT') {
32
+ throw error;
29
33
  }
30
- throw error;
34
+ }
35
+ if (input.script) {
36
+ await execa(input.script, {
37
+ all: true,
38
+ cwd: input.cwd,
39
+ shell: true,
40
+ });
41
+ }
42
+ else {
43
+ const fn = await open(input.path, 'a');
44
+ fn.close();
31
45
  }
32
46
  }
33
47
  }
@@ -1,6 +1,6 @@
1
1
  import { getContext } from '../../../index.js';
2
2
  import { PassThrough } from 'node:stream';
3
- import { ContainerExecOperation, ListContainersOperation } from '../../../docker/index.js';
3
+ import { ContainerExecOperation } from '../../../docker/index.js';
4
4
  import { EMBCollection, findRunOrder, taskManagerFactory, } from '../../index.js';
5
5
  import { ExecuteLocalCommandOperation } from '../index.js';
6
6
  export var ExecutorType;
@@ -57,23 +57,10 @@ export class RunTasksOperation {
57
57
  return ordered;
58
58
  }
59
59
  async runDocker(task, out) {
60
- const { monorepo } = getContext();
61
- const containers = await monorepo.run(new ListContainersOperation(), {
62
- filters: {
63
- label: [
64
- `emb/project=${monorepo.name}`,
65
- `emb/component=${task.component}`,
66
- ],
67
- },
68
- });
69
- if (containers.length === 0) {
70
- throw new Error(`No container found for component \`${task.component}\``);
71
- }
72
- if (containers.length > 1) {
73
- throw new Error(`More than one container found for component \`${task.component}\``);
74
- }
60
+ const { monorepo, compose } = getContext();
61
+ const containerID = await compose.getContainer(task.component);
75
62
  return monorepo.run(new ContainerExecOperation(out), {
76
- container: containers[0].Id,
63
+ container: containerID,
77
64
  script: task.script,
78
65
  env: await monorepo.expand(task.vars || {}),
79
66
  });
@@ -5,9 +5,12 @@ import { ResourceBuildContext } from './ResourceFactory.js';
5
5
  export declare class FileResourceBuilder implements IResourceBuilder<OpInput<CreateFileOperation>, OpOutput<CreateFileOperation>, void> {
6
6
  protected context: ResourceBuildContext<OpInput<CreateFileOperation>>;
7
7
  constructor(context: ResourceBuildContext<OpInput<CreateFileOperation>>);
8
+ getReference(): Promise<string>;
8
9
  build(resource: ResourceInfo<OpInput<CreateFileOperation>>, out?: Writable): Promise<{
9
10
  input: {
10
11
  path: string;
12
+ script?: string | undefined;
13
+ cwd?: string | undefined;
11
14
  force?: boolean | undefined;
12
15
  };
13
16
  operation: CreateFileOperation;
@@ -5,9 +5,14 @@ export class FileResourceBuilder {
5
5
  constructor(context) {
6
6
  this.context = context;
7
7
  }
8
+ async getReference() {
9
+ return this.context.component.relative(this.context.config.params?.path || this.context.config.name);
10
+ }
8
11
  async build(resource, out) {
9
12
  const input = {
10
- path: this.context.component.join(resource.params?.path || resource.name),
13
+ path: await this.context.component.join(this.context.config.params?.path || this.context.config.name),
14
+ script: resource.params?.script,
15
+ cwd: this.context.component.join('./'),
11
16
  };
12
17
  return {
13
18
  input,
@@ -9,7 +9,7 @@ export class ResourceFactory {
9
9
  static factor(type, context) {
10
10
  const BuilderClass = this.types[type];
11
11
  if (!BuilderClass) {
12
- throw new Error(`Unknown resource type \`${type}\``);
12
+ throw new Error(`Unknown resource type \`${type}\` (${context.config.id})`);
13
13
  }
14
14
  return new BuilderClass(context);
15
15
  }
@@ -20,4 +20,5 @@ export declare abstract class AbstractResourceBuilder<I, O, R> implements IResou
20
20
  publish?(resource: ResourceInfo<I>, out?: Writable): Promise<void>;
21
21
  abstract _commit(resource: ResourceInfo<I>, output: O, reason: R): Promise<void>;
22
22
  commit(resource: ResourceInfo<I>, output: O, reason: R): Promise<void>;
23
+ abstract getReference(): Promise<string>;
23
24
  }
@@ -2,6 +2,12 @@ import { ResourceInfo } from '../../index.js';
2
2
  import { Writable } from 'node:stream';
3
3
  import { IOperation } from '../../operations/types.js';
4
4
  export type IResourceBuilder<Input, Output, Reason> = {
5
+ /**
6
+ * Returns a string representing the resource to build
7
+ * Eg. the full name of a docker image (repo/imgname:tag)
8
+ * Eg. a file path
9
+ */
10
+ getReference(): Promise<string>;
5
11
  /**
6
12
  * Returns input and operation required to actually
7
13
  * build the resources.
@@ -1,5 +1,6 @@
1
1
  import type Docker from 'dockerode';
2
2
  import { Monorepo } from './monorepo/index.js';
3
+ import { DockerComposeClient } from './docker/index.js';
3
4
  /**
4
5
  * The context is meant to be what all plugins can decorate
5
6
  * to install their own things
@@ -8,6 +9,7 @@ import { Monorepo } from './monorepo/index.js';
8
9
  * and install here things that to be accessible by operations during a CLI run
9
10
  */
10
11
  export interface EmbContext {
12
+ compose: DockerComposeClient;
11
13
  docker: Docker;
12
14
  monorepo: Monorepo;
13
15
  }
@@ -597,6 +597,62 @@
597
597
  "prune.js"
598
598
  ]
599
599
  },
600
+ "images:push": {
601
+ "aliases": [],
602
+ "args": {},
603
+ "description": "Push docker images.",
604
+ "examples": [
605
+ "<%= config.bin %> <%= command.id %>",
606
+ "<%= config.bin %> <%= command.id %> --registry my.registry.io --retag newtag"
607
+ ],
608
+ "flags": {
609
+ "json": {
610
+ "description": "Format output as json.",
611
+ "helpGroup": "GLOBAL",
612
+ "name": "json",
613
+ "allowNo": false,
614
+ "type": "boolean"
615
+ },
616
+ "flavor": {
617
+ "description": "Specify the flavor to use.",
618
+ "name": "flavor",
619
+ "required": false,
620
+ "hasDynamicHelp": false,
621
+ "multiple": false,
622
+ "type": "option"
623
+ },
624
+ "registry": {
625
+ "description": "Override the registry to push to",
626
+ "name": "registry",
627
+ "hasDynamicHelp": false,
628
+ "multiple": false,
629
+ "type": "option"
630
+ },
631
+ "retag": {
632
+ "description": "Override the original tag to push to a new tag",
633
+ "name": "retag",
634
+ "hasDynamicHelp": false,
635
+ "multiple": false,
636
+ "type": "option"
637
+ }
638
+ },
639
+ "hasDynamicHelp": false,
640
+ "hiddenAliases": [],
641
+ "id": "images:push",
642
+ "pluginAlias": "@enspirit/emb",
643
+ "pluginName": "@enspirit/emb",
644
+ "pluginType": "core",
645
+ "enableJsonFlag": true,
646
+ "isESM": true,
647
+ "relativePath": [
648
+ "dist",
649
+ "src",
650
+ "cli",
651
+ "commands",
652
+ "images",
653
+ "push.js"
654
+ ]
655
+ },
600
656
  "resources:build": {
601
657
  "aliases": [],
602
658
  "args": {
@@ -797,5 +853,5 @@
797
853
  ]
798
854
  }
799
855
  },
800
- "version": "0.5.3"
856
+ "version": "0.7.0"
801
857
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@enspirit/emb",
3
3
  "type": "module",
4
- "version": "0.5.3",
4
+ "version": "0.7.0",
5
5
  "keywords": [
6
6
  "monorepo",
7
7
  "docker",
@@ -1,2 +0,0 @@
1
- import { Container } from 'dockerode';
2
- export declare const getContainer: (id: string) => Promise<Container>;
@@ -1,5 +0,0 @@
1
- import { getContext } from '../../index.js';
2
- export const getContainer = async (id) => {
3
- const { docker } = getContext();
4
- return docker.getContainer(id);
5
- };
@@ -1 +0,0 @@
1
- export * from './getContainer.js';
@@ -1 +0,0 @@
1
- export * from './getContainer.js';