@enspirit/emb 0.14.0 → 0.15.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 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.14.0 darwin-x64 node-v22.18.0
17
+ @enspirit/emb/0.15.0 darwin-arm64 node-v22.21.1
18
18
  $ emb --help [COMMAND]
19
19
  USAGE
20
20
  $ emb COMMAND
@@ -0,0 +1,7 @@
1
+ import { Hook } from '@oclif/core';
2
+ /**
3
+ * Postrun hook that patches completion scripts after autocomplete generation.
4
+ * This adds task name completion for the 'tasks run' and 'run' commands.
5
+ */
6
+ declare const hook: Hook.Postrun;
7
+ export default hook;
@@ -0,0 +1,128 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ /**
5
+ * Task completion function to inject into the bash completion script.
6
+ * This function is called when completing arguments for `tasks run` or `run`.
7
+ */
8
+ const TASK_COMPLETION_FUNCTION = `
9
+ # EMB: Task name completion for 'tasks run' and 'run' commands
10
+ _emb_complete_tasks() {
11
+ local cur="\${1:-}"
12
+ local tasks
13
+
14
+ # Get task IDs from emb tasks --json, extract the id field
15
+ tasks=$(emb tasks --json 2>/dev/null | grep -o '"id": *"[^"]*"' | sed 's/"id": *"//g' | sed 's/"//g')
16
+
17
+ if [[ -z "$tasks" ]]; then
18
+ return
19
+ fi
20
+
21
+ # Filter by substring match if provided (more flexible than prefix-only)
22
+ if [[ -n "$cur" ]]; then
23
+ local matches=""
24
+ for task in $tasks; do
25
+ if [[ "$task" == *"$cur"* ]]; then
26
+ matches="$matches $task"
27
+ fi
28
+ done
29
+ COMPREPLY=($matches)
30
+ else
31
+ COMPREPLY=($tasks)
32
+ fi
33
+ }
34
+ `;
35
+ /**
36
+ * Enhanced completion logic to inject after normalizedCommand is calculated.
37
+ * Checks if we're completing task arguments for 'tasks run' or 'run'.
38
+ */
39
+ const TASK_COMPLETION_LOGIC = `
40
+ # EMB: Check if we're completing task names for 'tasks run' or 'run'
41
+ if [[ "$normalizedCommand" == tasks:run:* ]] || [[ "$normalizedCommand" == run:* ]] || [[ "$normalizedCommand" == "tasks:run" ]] || [[ "$normalizedCommand" == "run" ]]; then
42
+ _emb_complete_tasks "$cur"
43
+ return
44
+ fi
45
+ `;
46
+ /**
47
+ * Zsh task completion function to inject.
48
+ * Uses zsh's compadd to add task completions.
49
+ * Note: We use compadd instead of _describe because task IDs contain colons
50
+ * (e.g., "frontend:test") and _describe uses colons as value:description delimiter.
51
+ */
52
+ const ZSH_TASK_COMPLETION_FUNCTION = `
53
+ # EMB: Task name completion for 'tasks run' and 'run' commands
54
+ _emb_complete_tasks() {
55
+ local tasks
56
+ # Get task IDs from emb tasks --json, extract the id field
57
+ tasks=(\${(f)"$(emb tasks --json 2>/dev/null | grep -o '"id": *"[^"]*"' | sed 's/"id": *"//g' | sed 's/"//g')"})
58
+
59
+ if [[ \${#tasks[@]} -gt 0 ]]; then
60
+ # Use compadd instead of _describe because task IDs contain colons
61
+ # which _describe interprets as value:description separators
62
+ compadd -a tasks
63
+ fi
64
+ }
65
+ `;
66
+ /**
67
+ * Patches the bash completion script.
68
+ */
69
+ function patchBashCompletion() {
70
+ const possiblePaths = [
71
+ join(homedir(), 'Library/Caches/emb/autocomplete/functions/bash/emb.bash'),
72
+ join(homedir(), '.cache/emb/autocomplete/functions/bash/emb.bash'),
73
+ ];
74
+ const scriptPath = possiblePaths.find((p) => existsSync(p));
75
+ if (!scriptPath) {
76
+ return;
77
+ }
78
+ const content = readFileSync(scriptPath, 'utf8');
79
+ // Check if already patched
80
+ if (content.includes('_emb_complete_tasks')) {
81
+ return;
82
+ }
83
+ // Insert task completion function after the join_by function
84
+ let patched = content.replace(/function join_by \{[^}]+\}/, (match) => `${match}\n${TASK_COMPLETION_FUNCTION}`);
85
+ // Insert task completion logic right before the "if [[ -z "$normalizedCommand" ]]" check
86
+ patched = patched.replace('if [[ -z "$normalizedCommand" ]]; then', `${TASK_COMPLETION_LOGIC.trim()}\n\n if [[ -z "$normalizedCommand" ]]; then`);
87
+ writeFileSync(scriptPath, patched, 'utf8');
88
+ }
89
+ /**
90
+ * Patches the zsh completion script.
91
+ */
92
+ function patchZshCompletion() {
93
+ const possiblePaths = [
94
+ join(homedir(), 'Library/Caches/emb/autocomplete/functions/zsh/_emb'),
95
+ join(homedir(), '.cache/emb/autocomplete/functions/zsh/_emb'),
96
+ ];
97
+ const scriptPath = possiblePaths.find((p) => existsSync(p));
98
+ if (!scriptPath) {
99
+ return;
100
+ }
101
+ const content = readFileSync(scriptPath, 'utf8');
102
+ // Check if already patched
103
+ if (content.includes('_emb_complete_tasks')) {
104
+ return;
105
+ }
106
+ // Insert task completion function after #compdef line
107
+ let patched = content.replace('#compdef emb', `#compdef emb\n${ZSH_TASK_COMPLETION_FUNCTION}`);
108
+ // Replace _files with _emb_complete_tasks in run command sections
109
+ // Pattern 1: Inside _emb_tasks for "tasks run"
110
+ patched = patched.replace(/("run"\)\s*\n\s*_arguments -S[\s\S]*?)"?\*: :_files"(\s*;;)/, '$1"*: :_emb_complete_tasks"$2');
111
+ // Pattern 2: Top-level run) command (the alias)
112
+ patched = patched.replace(/(^run\)\n_arguments -S[\s\S]*?)"?\*: :_files"(\s*;;)/m, '$1"*: :_emb_complete_tasks"$2');
113
+ writeFileSync(scriptPath, patched, 'utf8');
114
+ }
115
+ /**
116
+ * Postrun hook that patches completion scripts after autocomplete generation.
117
+ * This adds task name completion for the 'tasks run' and 'run' commands.
118
+ */
119
+ const hook = async function (options) {
120
+ const commandId = options.Command?.id;
121
+ // Only patch after autocomplete commands
122
+ if (commandId !== 'autocomplete' && commandId !== 'autocomplete:create') {
123
+ return;
124
+ }
125
+ patchBashCompletion();
126
+ patchZshCompletion();
127
+ };
128
+ export default hook;
@@ -50,7 +50,9 @@ export class ContainerExecOperation extends AbstractOperation {
50
50
  process.stdin.pipe(stream);
51
51
  }
52
52
  const out = input.interactive ? process.stdout : this.out;
53
- exec.modem.demuxStream(stream, out, out);
53
+ if (out) {
54
+ exec.modem.demuxStream(stream, out, out);
55
+ }
54
56
  await new Promise((resolve, reject) => {
55
57
  const onError = (err) => reject(err);
56
58
  const onEnd = async () => {
@@ -19,12 +19,22 @@ class DockerImageResourceBuilder extends SentinelFileBasedBuilder {
19
19
  : buildContext.monorepo.join(buildContext.component.rootDir);
20
20
  }
21
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}`);
22
+ const configTag = this.config?.tag;
23
+ let imageNamePart;
24
+ let tagPart;
25
+ if (configTag && configTag.includes(':')) {
26
+ // config.tag contains both image name and tag (e.g., "myimage:v1.0.0")
27
+ const colonIndex = configTag.lastIndexOf(':');
28
+ imageNamePart = configTag.slice(0, colonIndex);
29
+ tagPart = configTag.slice(colonIndex + 1);
30
+ }
31
+ else {
32
+ // config.tag is just an image name or undefined
33
+ imageNamePart = configTag || this.component.name;
34
+ tagPart = this.monorepo.defaults.docker?.tag || 'latest';
35
+ }
36
+ const imageName = [this.monorepo.name, imageNamePart].join('/');
37
+ return this.monorepo.expand(`${imageName}:${tagPart}`);
28
38
  }
29
39
  get monorepo() {
30
40
  return this.buildContext.monorepo;
@@ -1,7 +1,6 @@
1
1
  import { PRESET_TIMER, } from 'listr2';
2
2
  import * as z from 'zod';
3
- import { EMBCollection, findRunOrder, } from '../../index.js';
4
- import { ResourceFactory } from '../../resources/ResourceFactory.js';
3
+ import { EMBCollection, findRunOrder, ResourceFactory, } from '../../index.js';
5
4
  import { AbstractOperation } from '../../../operations/index.js';
6
5
  const schema = z.object({
7
6
  resources: z
@@ -75,7 +75,7 @@ export class RunTasksOperation {
75
75
  async runDocker(task, out) {
76
76
  const { monorepo, compose } = getContext();
77
77
  const containerID = await compose.getContainer(task.component);
78
- return monorepo.run(new ContainerExecOperation(), {
78
+ return monorepo.run(new ContainerExecOperation(task.interactive ? undefined : out), {
79
79
  container: containerID,
80
80
  script: task.script,
81
81
  interactive: task.interactive || false,
@@ -1,3 +1,4 @@
1
1
  import './FileResourceBuilder.js';
2
2
  export * from './abstract/index.js';
3
+ export * from './ResourceFactory.js';
3
4
  export * from './types.js';
@@ -1,3 +1,4 @@
1
1
  import './FileResourceBuilder.js';
2
2
  export * from './abstract/index.js';
3
+ export * from './ResourceFactory.js';
3
4
  export * from './types.js';
@@ -334,6 +334,53 @@
334
334
  "up.js"
335
335
  ]
336
336
  },
337
+ "config:print": {
338
+ "aliases": [],
339
+ "args": {},
340
+ "description": "Print the current config.",
341
+ "examples": [
342
+ "<%= config.bin %> <%= command.id %>"
343
+ ],
344
+ "flags": {
345
+ "json": {
346
+ "description": "Format output as json.",
347
+ "helpGroup": "GLOBAL",
348
+ "name": "json",
349
+ "allowNo": false,
350
+ "type": "boolean"
351
+ },
352
+ "verbose": {
353
+ "name": "verbose",
354
+ "allowNo": true,
355
+ "type": "boolean"
356
+ },
357
+ "flavor": {
358
+ "description": "Specify the flavor to use.",
359
+ "name": "flavor",
360
+ "required": false,
361
+ "hasDynamicHelp": false,
362
+ "multiple": false,
363
+ "type": "option"
364
+ }
365
+ },
366
+ "hasDynamicHelp": false,
367
+ "hiddenAliases": [],
368
+ "id": "config:print",
369
+ "pluginAlias": "@enspirit/emb",
370
+ "pluginName": "@enspirit/emb",
371
+ "pluginType": "core",
372
+ "strict": true,
373
+ "enableJsonFlag": true,
374
+ "isESM": true,
375
+ "relativePath": [
376
+ "dist",
377
+ "src",
378
+ "cli",
379
+ "commands",
380
+ "config",
381
+ "print.js"
382
+ ]
383
+ },
337
384
  "components": {
338
385
  "aliases": [],
339
386
  "args": {},
@@ -477,53 +524,6 @@
477
524
  "shell.js"
478
525
  ]
479
526
  },
480
- "config:print": {
481
- "aliases": [],
482
- "args": {},
483
- "description": "Print the current config.",
484
- "examples": [
485
- "<%= config.bin %> <%= command.id %>"
486
- ],
487
- "flags": {
488
- "json": {
489
- "description": "Format output as json.",
490
- "helpGroup": "GLOBAL",
491
- "name": "json",
492
- "allowNo": false,
493
- "type": "boolean"
494
- },
495
- "verbose": {
496
- "name": "verbose",
497
- "allowNo": true,
498
- "type": "boolean"
499
- },
500
- "flavor": {
501
- "description": "Specify the flavor to use.",
502
- "name": "flavor",
503
- "required": false,
504
- "hasDynamicHelp": false,
505
- "multiple": false,
506
- "type": "option"
507
- }
508
- },
509
- "hasDynamicHelp": false,
510
- "hiddenAliases": [],
511
- "id": "config:print",
512
- "pluginAlias": "@enspirit/emb",
513
- "pluginName": "@enspirit/emb",
514
- "pluginType": "core",
515
- "strict": true,
516
- "enableJsonFlag": true,
517
- "isESM": true,
518
- "relativePath": [
519
- "dist",
520
- "src",
521
- "cli",
522
- "commands",
523
- "config",
524
- "print.js"
525
- ]
526
- },
527
527
  "containers": {
528
528
  "aliases": [],
529
529
  "args": {},
@@ -1264,5 +1264,5 @@
1264
1264
  ]
1265
1265
  }
1266
1266
  },
1267
- "version": "0.14.0"
1267
+ "version": "0.15.0"
1268
1268
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@enspirit/emb",
3
3
  "type": "module",
4
- "version": "0.14.0",
4
+ "version": "0.15.0",
5
5
  "keywords": [
6
6
  "monorepo",
7
7
  "docker",
@@ -32,69 +32,71 @@
32
32
  "posttest": "npm run lint",
33
33
  "prepack": "oclif manifest && oclif readme",
34
34
  "test": "npm run build:types && npm run test:unit && npm run test:integration",
35
- "test:unit": "vitest run tests/unit",
36
- "test:unit:watch": "vitest tests/unit",
37
- "test:integration": "vitest run tests/integration",
38
- "test:integration:watch": "vitest tests/integration",
35
+ "test:unit": "vitest run --project unit",
36
+ "test:unit:watch": "vitest --project unit",
37
+ "test:integration": "npm run test:integration:mocked && npm run test:integration:docker",
38
+ "test:integration:mocked": "vitest run --project integration-mocked",
39
+ "test:integration:docker": "vitest run --project integration-docker --project integration-compose",
40
+ "test:integration:watch": "vitest --project integration-mocked",
39
41
  "test:watch": "vitest",
40
42
  "version": "oclif readme && git add README.md"
41
43
  },
42
44
  "dependencies": {
43
45
  "@fastify/deepmerge": "^3.1.0",
44
- "@kubernetes/client-node": "1.3.0",
45
- "@listr2/manager": "^3.0.3",
46
+ "@kubernetes/client-node": "1.4.0",
47
+ "@listr2/manager": "^3.0.5",
46
48
  "@oclif/core": "^4.8.0",
47
49
  "@oclif/plugin-autocomplete": "^3.2.39",
48
50
  "@oclif/plugin-help": "^6.2.36",
49
51
  "@oclif/plugin-update": "^4.7.16",
50
52
  "@oclif/table": "^0.5.1",
51
53
  "ajv": "^8.17.1",
52
- "ansi-escapes": "^7.0.0",
54
+ "ansi-escapes": "^7.2.0",
53
55
  "colorette": "^2.0.20",
54
56
  "docker-compose": "^1.3.0",
55
- "dockerode": "^4.0.7",
56
- "dotenv": "^17.2.1",
57
- "execa": "^9.6.0",
57
+ "dockerode": "^4.0.9",
58
+ "dotenv": "^17.2.3",
59
+ "execa": "^9.6.1",
58
60
  "fast-json-patch": "^3.1.1",
59
61
  "fdir": "^6.5.0",
60
62
  "find-up": "^7.0.0",
61
- "glob": "^11.0.3",
63
+ "glob": "^11.1.0",
62
64
  "graphlib": "^2.1.8",
63
- "@inquirer/prompts": "^7.8.4",
64
- "@listr2/prompt-adapter-inquirer": "^3.0.3",
65
- "listr2": "^9.0.3",
66
- "luxon": "^3.7.1",
65
+ "@inquirer/prompts": "^7.10.1",
66
+ "@listr2/prompt-adapter-inquirer": "^3.0.5",
67
+ "listr2": "^9.0.5",
68
+ "luxon": "^3.7.2",
67
69
  "protobufjs": "^7.5.4",
68
- "p-map": "^7.0.3",
69
- "simple-git": "^3.28.0",
70
- "yaml": "^2.8.1",
71
- "zod": "^4.1.5"
70
+ "p-map": "^7.0.4",
71
+ "simple-git": "^3.30.0",
72
+ "yaml": "^2.8.2",
73
+ "zod": "^4.3.5"
72
74
  },
73
75
  "devDependencies": {
74
76
  "@eslint/eslintrc": "^3.3.1",
75
77
  "@eslint/js": "^9.34.0",
76
78
  "@oclif/prettier-config": "^0.2.1",
77
79
  "@oclif/test": "^4",
78
- "@tsconfig/node20": "^20.1.6",
79
- "@types/dockerode": "^3.3.43",
80
+ "@tsconfig/node20": "^20.1.8",
81
+ "@types/dockerode": "^3.3.47",
80
82
  "@types/graphlib": "^2.1.12",
81
83
  "@types/luxon": "^3.7.1",
82
- "@types/node": "^24.3.0",
84
+ "@types/node": "^24.10.8",
83
85
  "@typescript-eslint/eslint-plugin": "^8.41.0",
84
86
  "@typescript-eslint/parser": "^8.41.0",
85
- "eslint": "^9.34.0",
87
+ "eslint": "^9.39.2",
86
88
  "eslint-config-oclif": "^6",
87
89
  "eslint-config-prettier": "^10.1.8",
88
90
  "eslint-plugin-prettier": "^5.5.4",
89
91
  "json-schema-to-typescript": "^15.0.4",
90
92
  "oclif": "^4",
91
- "prettier": "^3.6.2",
92
- "rimraf": "^6.0.1",
93
+ "prettier": "^3.7.4",
94
+ "rimraf": "^6.1.2",
93
95
  "shx": "^0.4.0",
94
96
  "ts-node": "^10.9.2",
95
97
  "tsc-alias": "^1.8.16",
96
- "tsx": "^4.20.5",
97
- "typescript": "^5.9.2",
98
+ "tsx": "^4.21.0",
99
+ "typescript": "^5.9.3",
98
100
  "vitest": "^3.2.4",
99
101
  "vite-tsconfig-paths": "^5.1.4"
100
102
  },
@@ -113,7 +115,8 @@
113
115
  "commands": "./dist/src/cli/commands",
114
116
  "hooks": {
115
117
  "init": "./dist/src/cli/hooks/init",
116
- "command_not_found": "./dist/src/cli/hooks/not-found"
118
+ "command_not_found": "./dist/src/cli/hooks/not-found",
119
+ "postrun": "./dist/src/cli/hooks/postrun"
117
120
  },
118
121
  "macos": {
119
122
  "identifier": "dev.enspirit.emb"
@@ -148,5 +151,5 @@
148
151
  }
149
152
  }
150
153
  },
151
- "packageManager": "pnpm@10.15.1"
154
+ "packageManager": "pnpm@10.28.0"
152
155
  }