@enspirit/emb 0.1.1 → 0.1.3

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 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.1 darwin-x64 node-v22.12.0
17
+ @enspirit/emb/0.1.3 darwin-x64 node-v22.12.0
18
18
  $ emb --help [COMMAND]
19
19
  USAGE
20
20
  $ emb COMMAND
@@ -26,6 +26,7 @@ USAGE
26
26
  * [`emb autocomplete [SHELL]`](#emb-autocomplete-shell)
27
27
  * [`emb clean`](#emb-clean)
28
28
  * [`emb components`](#emb-components)
29
+ * [`emb components logs COMPONENT`](#emb-components-logs-component)
29
30
  * [`emb config print`](#emb-config-print)
30
31
  * [`emb containers`](#emb-containers)
31
32
  * [`emb containers prune`](#emb-containers-prune)
@@ -34,6 +35,7 @@ USAGE
34
35
  * [`emb images`](#emb-images)
35
36
  * [`emb images delete`](#emb-images-delete)
36
37
  * [`emb images prune`](#emb-images-prune)
38
+ * [`emb logs COMPONENT`](#emb-logs-component)
37
39
  * [`emb ps`](#emb-ps)
38
40
  * [`emb resources`](#emb-resources)
39
41
  * [`emb resources build [COMPONENT]`](#emb-resources-build-component)
@@ -115,6 +117,31 @@ EXAMPLES
115
117
  $ emb components
116
118
  ```
117
119
 
120
+ ## `emb components logs COMPONENT`
121
+
122
+ Get components logs.
123
+
124
+ ```
125
+ USAGE
126
+ $ emb components logs COMPONENT [--flavor <value>] [-f]
127
+
128
+ ARGUMENTS
129
+ COMPONENT The component you want to see the logs of
130
+
131
+ FLAGS
132
+ -f, --follow Follow log output
133
+ --flavor=<value> Specify the flavor to use.
134
+
135
+ DESCRIPTION
136
+ Get components logs.
137
+
138
+ ALIASES
139
+ $ emb logs
140
+
141
+ EXAMPLES
142
+ $ emb components logs
143
+ ```
144
+
118
145
  ## `emb config print`
119
146
 
120
147
  Print the current config.
@@ -225,10 +252,11 @@ List docker images.
225
252
 
226
253
  ```
227
254
  USAGE
228
- $ emb images [--json] [-a]
255
+ $ emb images [--json] [--flavor <value>] [-a]
229
256
 
230
257
  FLAGS
231
- -a, --all Show all images. Only images from a final layer (no children) are shown by default.
258
+ -a, --all Show all images. Only images from a final layer (no children) are shown by default.
259
+ --flavor=<value> Specify the flavor to use.
232
260
 
233
261
  GLOBAL FLAGS
234
262
  --json Format output as json.
@@ -282,6 +310,31 @@ EXAMPLES
282
310
  $ emb images prune
283
311
  ```
284
312
 
313
+ ## `emb logs COMPONENT`
314
+
315
+ Get components logs.
316
+
317
+ ```
318
+ USAGE
319
+ $ emb logs COMPONENT [--flavor <value>] [-f]
320
+
321
+ ARGUMENTS
322
+ COMPONENT The component you want to see the logs of
323
+
324
+ FLAGS
325
+ -f, --follow Follow log output
326
+ --flavor=<value> Specify the flavor to use.
327
+
328
+ DESCRIPTION
329
+ Get components logs.
330
+
331
+ ALIASES
332
+ $ emb logs
333
+
334
+ EXAMPLES
335
+ $ emb logs
336
+ ```
337
+
285
338
  ## `emb ps`
286
339
 
287
340
  List docker containers.
@@ -9,5 +9,8 @@ export declare abstract class FlavoredCommand<T extends typeof Command> extends
9
9
  static enableJsonFlag: boolean;
10
10
  protected args: Args<T>;
11
11
  protected flags: Flags<T>;
12
+ protected catch(err: Error & {
13
+ exitCode?: number;
14
+ }): Promise<void>;
12
15
  init(): Promise<void>;
13
16
  }
@@ -1,5 +1,6 @@
1
1
  import { getContext, setContext } from '../../index.js';
2
2
  import { Flags } from '@oclif/core';
3
+ import { JsonPatchError } from 'fast-json-patch';
3
4
  import { BaseCommand } from './BaseCommand.js';
4
5
  export class FlavoredCommand extends BaseCommand {
5
6
  // define flags that can be inherited by any command that extends FlavoredCommand
@@ -14,6 +15,19 @@ export class FlavoredCommand extends BaseCommand {
14
15
  static enableJsonFlag = true;
15
16
  args;
16
17
  flags;
18
+ async catch(err) {
19
+ if (err instanceof JsonPatchError) {
20
+ this.log('INVALID', err.operation);
21
+ this.error('Invalid patch detected while applying flavor', {
22
+ code: err.name,
23
+ message: `Path \`${err.operation?.path}\``,
24
+ });
25
+ return;
26
+ }
27
+ // add any custom logic to handle errors from the command
28
+ // or simply return the parent class error handling
29
+ return super.catch(err);
30
+ }
17
31
  async init() {
18
32
  await super.init();
19
33
  const { args, flags } = await this.parse({
@@ -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
+ follow: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ };
10
+ static args: {
11
+ component: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,60 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { FlavoredCommand, getContext } from '../../index.js';
3
+ import { ListContainersOperation } from '../../../docker/index.js';
4
+ export default class ComponentsLogs extends FlavoredCommand {
5
+ static aliases = ['logs'];
6
+ static description = 'Get components logs.';
7
+ static enableJsonFlag = false;
8
+ static examples = ['<%= config.bin %> <%= command.id %>'];
9
+ static flags = {
10
+ follow: Flags.boolean({
11
+ name: 'follow',
12
+ char: 'f',
13
+ description: 'Follow log output',
14
+ default: false,
15
+ }),
16
+ };
17
+ static args = {
18
+ component: Args.string({
19
+ name: 'component',
20
+ description: 'The component you want to see the logs of',
21
+ required: true,
22
+ }),
23
+ };
24
+ async run() {
25
+ const { flags, args } = await this.parse(ComponentsLogs);
26
+ const { monorepo, docker } = await getContext();
27
+ const component = monorepo.component(args.component);
28
+ const containers = await monorepo.run(new ListContainersOperation(), {
29
+ filters: {
30
+ label: [
31
+ `emb/project=${monorepo.name}`,
32
+ `emb/component=${component.name}`,
33
+ `emb/flavor=${monorepo.currentFlavor}`,
34
+ ],
35
+ },
36
+ });
37
+ if (containers.length === 0) {
38
+ return this.error(`No container found for component \`${component.name}\``);
39
+ }
40
+ if (containers.length > 1) {
41
+ return this.error(`More than one container found for component \`${component.name}\``);
42
+ }
43
+ const container = await docker.getContainer(containers[0].Id);
44
+ if (flags.follow) {
45
+ const stream = await container.logs({
46
+ follow: true,
47
+ stderr: true,
48
+ stdout: true,
49
+ });
50
+ stream.pipe(process.stdout);
51
+ }
52
+ else {
53
+ const res = await container.logs({
54
+ stderr: true,
55
+ stdout: true,
56
+ });
57
+ this.log(res.toString());
58
+ }
59
+ }
60
+ }
@@ -1,4 +1,4 @@
1
- import { BaseCommand } from '../../index.js';
1
+ import { FlavoredCommand } from '../../index.js';
2
2
  export type ImageInfo = {
3
3
  created: Date;
4
4
  imageId: string;
@@ -6,7 +6,7 @@ export type ImageInfo = {
6
6
  size: number;
7
7
  tag: string;
8
8
  };
9
- export default class ImagesIndex extends BaseCommand {
9
+ export default class ImagesIndex extends FlavoredCommand<typeof ImagesIndex> {
10
10
  static description: string;
11
11
  static enableJsonFlag: boolean;
12
12
  static examples: string[];
@@ -1,10 +1,10 @@
1
1
  import { getContext } from '../../../index.js';
2
2
  import { Flags } from '@oclif/core';
3
3
  import { printTable } from '@oclif/table';
4
- import { BaseCommand, TABLE_DEFAULTS } from '../../index.js';
4
+ import { FlavoredCommand, TABLE_DEFAULTS } from '../../index.js';
5
5
  import { listImages, shortId } from '../../../docker/index.js';
6
6
  import { timeAgo } from '../../../utils/index.js';
7
- export default class ImagesIndex extends BaseCommand {
7
+ export default class ImagesIndex extends FlavoredCommand {
8
8
  static description = 'List docker images.';
9
9
  static enableJsonFlag = true;
10
10
  static examples = ['<%= config.bin %> <%= command.id %>'];
@@ -20,6 +20,9 @@ export default class UpCommand extends FlavoredCommand {
20
20
  if (flags.force) {
21
21
  buildFlags.push('--force');
22
22
  }
23
+ if (flags.flavor) {
24
+ buildFlags.push('--flavor', flags.flavor);
25
+ }
23
26
  await this.config.runCommand('resources:build', buildFlags);
24
27
  await monorepo.run(new ComposeUpOperation(), {
25
28
  forceRecreate: flags.force,
@@ -129,6 +129,5 @@ export interface JsonPatchCopyOperation {
129
129
  from: string;
130
130
  }
131
131
  export interface ProjectFlavorConfig {
132
- defaults?: DefaultsConfig;
133
132
  patches?: JsonPatchOperation[];
134
133
  }
@@ -200,9 +200,6 @@
200
200
  "additionalProperties": false,
201
201
  "required": [],
202
202
  "properties": {
203
- "defaults": {
204
- "$ref": "#/$defs/DefaultsConfig"
205
- },
206
203
  "patches": {
207
204
  "type": "array",
208
205
  "items": {
@@ -15,18 +15,23 @@ const DockerImageOpFactory = async ({ config, component, monorepo }) => {
15
15
  await statfs(context);
16
16
  const plugin = new GitPrerequisitePlugin();
17
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';
18
20
  const buildParams = {
19
21
  context,
20
22
  dockerfile: fromConfig.dockerfile || 'Dockerfile',
21
23
  src: sources.map((s) => s.path),
22
- buildArgs: fromConfig.buildArgs || {},
23
- tag: [monorepo.name, fromConfig.tag || component.name].join('/'),
24
- labels: {
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({
25
30
  ...fromConfig.labels,
26
31
  'emb/project': monorepo.name,
27
32
  'emb/component': component.name,
28
33
  'emb/flavor': monorepo.currentFlavor,
29
- },
34
+ }),
30
35
  target: fromConfig.target,
31
36
  };
32
37
  const lastUpdatedInfo = async (sources) => {
@@ -54,7 +59,7 @@ const DockerImageOpFactory = async ({ config, component, monorepo }) => {
54
59
  ? lastUpdated
55
60
  : undefined;
56
61
  },
57
- input: await monorepo.expand(buildParams),
62
+ input: buildParams,
58
63
  operation: new BuildImageOperation(),
59
64
  };
60
65
  };
@@ -41,7 +41,7 @@ export class Component {
41
41
  const patches = this.flavor(name).patches || [];
42
42
  const errors = jsonpatch.validate(patches, original);
43
43
  if (errors) {
44
- throw new Error('Invalid patch(es) detected');
44
+ throw errors;
45
45
  }
46
46
  const patched = patches.reduce((doc, patch, index) => {
47
47
  return jsonpatch.applyReducer(doc, patch, index);
@@ -167,13 +167,13 @@ export class Monorepo {
167
167
  const original = this._config.toJSON();
168
168
  const errors = jsonpatch.validate(patches || [], original);
169
169
  if (errors) {
170
- throw new Error('Invalid patch(es) detected');
170
+ throw errors;
171
171
  }
172
172
  const withComponentPatches = this.components.reduce((config, cmp) => {
173
173
  const componentPatches = cmp.flavor(flavorName, false)?.patches || [];
174
174
  const errors = jsonpatch.validate(componentPatches || [], config.components[cmp.name]);
175
175
  if (errors) {
176
- throw new Error('Invalid patch(es) detected');
176
+ throw errors;
177
177
  }
178
178
  config.components[cmp.name] = componentPatches.reduce((doc, patch, index) => {
179
179
  return jsonpatch.applyReducer(doc, patch, index);
@@ -168,12 +168,59 @@
168
168
  "index.js"
169
169
  ]
170
170
  },
171
- "containers": {
171
+ "components:logs": {
172
172
  "aliases": [
173
- "ps"
173
+ "logs"
174
+ ],
175
+ "args": {
176
+ "component": {
177
+ "description": "The component you want to see the logs of",
178
+ "name": "component",
179
+ "required": true
180
+ }
181
+ },
182
+ "description": "Get components logs.",
183
+ "examples": [
184
+ "<%= config.bin %> <%= command.id %>"
174
185
  ],
186
+ "flags": {
187
+ "flavor": {
188
+ "description": "Specify the flavor to use.",
189
+ "name": "flavor",
190
+ "required": false,
191
+ "hasDynamicHelp": false,
192
+ "multiple": false,
193
+ "type": "option"
194
+ },
195
+ "follow": {
196
+ "char": "f",
197
+ "description": "Follow log output",
198
+ "name": "follow",
199
+ "allowNo": false,
200
+ "type": "boolean"
201
+ }
202
+ },
203
+ "hasDynamicHelp": false,
204
+ "hiddenAliases": [],
205
+ "id": "components:logs",
206
+ "pluginAlias": "@enspirit/emb",
207
+ "pluginName": "@enspirit/emb",
208
+ "pluginType": "core",
209
+ "enableJsonFlag": false,
210
+ "isESM": true,
211
+ "relativePath": [
212
+ "dist",
213
+ "src",
214
+ "cli",
215
+ "commands",
216
+ "components",
217
+ "logs.js"
218
+ ]
219
+ },
220
+ "config:print": {
221
+ "aliases": [],
175
222
  "args": {},
176
- "description": "List docker containers.",
223
+ "description": "Print the current config.",
177
224
  "examples": [
178
225
  "<%= config.bin %> <%= command.id %>"
179
226
  ],
@@ -185,22 +232,21 @@
185
232
  "allowNo": false,
186
233
  "type": "boolean"
187
234
  },
188
- "all": {
189
- "char": "a",
190
- "description": "Retun all containers. By default, only running containers are shown",
191
- "name": "all",
235
+ "flavor": {
236
+ "description": "Specify the flavor to use.",
237
+ "name": "flavor",
192
238
  "required": false,
193
- "allowNo": false,
194
- "type": "boolean"
239
+ "hasDynamicHelp": false,
240
+ "multiple": false,
241
+ "type": "option"
195
242
  }
196
243
  },
197
244
  "hasDynamicHelp": false,
198
245
  "hiddenAliases": [],
199
- "id": "containers",
246
+ "id": "config:print",
200
247
  "pluginAlias": "@enspirit/emb",
201
248
  "pluginName": "@enspirit/emb",
202
249
  "pluginType": "core",
203
- "strict": true,
204
250
  "enableJsonFlag": true,
205
251
  "isESM": true,
206
252
  "relativePath": [
@@ -208,14 +254,16 @@
208
254
  "src",
209
255
  "cli",
210
256
  "commands",
211
- "containers",
212
- "index.js"
257
+ "config",
258
+ "print.js"
213
259
  ]
214
260
  },
215
- "containers:prune": {
216
- "aliases": [],
261
+ "containers": {
262
+ "aliases": [
263
+ "ps"
264
+ ],
217
265
  "args": {},
218
- "description": "Prune containers.",
266
+ "description": "List docker containers.",
219
267
  "examples": [
220
268
  "<%= config.bin %> <%= command.id %>"
221
269
  ],
@@ -226,11 +274,19 @@
226
274
  "name": "json",
227
275
  "allowNo": false,
228
276
  "type": "boolean"
277
+ },
278
+ "all": {
279
+ "char": "a",
280
+ "description": "Retun all containers. By default, only running containers are shown",
281
+ "name": "all",
282
+ "required": false,
283
+ "allowNo": false,
284
+ "type": "boolean"
229
285
  }
230
286
  },
231
287
  "hasDynamicHelp": false,
232
288
  "hiddenAliases": [],
233
- "id": "containers:prune",
289
+ "id": "containers",
234
290
  "pluginAlias": "@enspirit/emb",
235
291
  "pluginName": "@enspirit/emb",
236
292
  "pluginType": "core",
@@ -243,13 +299,13 @@
243
299
  "cli",
244
300
  "commands",
245
301
  "containers",
246
- "prune.js"
302
+ "index.js"
247
303
  ]
248
304
  },
249
- "config:print": {
305
+ "containers:prune": {
250
306
  "aliases": [],
251
307
  "args": {},
252
- "description": "Print the current config.",
308
+ "description": "Prune containers.",
253
309
  "examples": [
254
310
  "<%= config.bin %> <%= command.id %>"
255
311
  ],
@@ -260,22 +316,15 @@
260
316
  "name": "json",
261
317
  "allowNo": false,
262
318
  "type": "boolean"
263
- },
264
- "flavor": {
265
- "description": "Specify the flavor to use.",
266
- "name": "flavor",
267
- "required": false,
268
- "hasDynamicHelp": false,
269
- "multiple": false,
270
- "type": "option"
271
319
  }
272
320
  },
273
321
  "hasDynamicHelp": false,
274
322
  "hiddenAliases": [],
275
- "id": "config:print",
323
+ "id": "containers:prune",
276
324
  "pluginAlias": "@enspirit/emb",
277
325
  "pluginName": "@enspirit/emb",
278
326
  "pluginType": "core",
327
+ "strict": true,
279
328
  "enableJsonFlag": true,
280
329
  "isESM": true,
281
330
  "relativePath": [
@@ -283,8 +332,8 @@
283
332
  "src",
284
333
  "cli",
285
334
  "commands",
286
- "config",
287
- "print.js"
335
+ "containers",
336
+ "prune.js"
288
337
  ]
289
338
  },
290
339
  "images:delete": {
@@ -344,6 +393,14 @@
344
393
  "allowNo": false,
345
394
  "type": "boolean"
346
395
  },
396
+ "flavor": {
397
+ "description": "Specify the flavor to use.",
398
+ "name": "flavor",
399
+ "required": false,
400
+ "hasDynamicHelp": false,
401
+ "multiple": false,
402
+ "type": "option"
403
+ },
347
404
  "all": {
348
405
  "char": "a",
349
406
  "description": "Show all images. Only images from a final layer (no children) are shown by default.",
@@ -359,7 +416,6 @@
359
416
  "pluginAlias": "@enspirit/emb",
360
417
  "pluginName": "@enspirit/emb",
361
418
  "pluginType": "core",
362
- "strict": true,
363
419
  "enableJsonFlag": true,
364
420
  "isESM": true,
365
421
  "relativePath": [
@@ -611,5 +667,5 @@
611
667
  ]
612
668
  }
613
669
  },
614
- "version": "0.1.1"
670
+ "version": "0.1.3"
615
671
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@enspirit/emb",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "keywords": ["monorepo", "docker", "taskrunner", "ci", "docker compose", "sentinel", "makefile"],
6
6
  "author": "Louis Lambeau <louis.lambeau@enspirit.be>",
7
7
  "license": "ISC",