@enspirit/emb 0.11.0 → 0.13.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 (32) hide show
  1. package/README.md +100 -2
  2. package/dist/src/cli/abstract/BaseCommand.js +2 -0
  3. package/dist/src/cli/abstract/KubernetesCommand.d.ts +7 -0
  4. package/dist/src/cli/abstract/KubernetesCommand.js +15 -0
  5. package/dist/src/cli/abstract/index.d.ts +1 -0
  6. package/dist/src/cli/abstract/index.js +1 -0
  7. package/dist/src/cli/commands/components/shell.d.ts +1 -1
  8. package/dist/src/cli/commands/components/shell.js +2 -2
  9. package/dist/src/cli/commands/kubernetes/logs.d.ts +14 -0
  10. package/dist/src/cli/commands/kubernetes/logs.js +53 -0
  11. package/dist/src/cli/commands/kubernetes/ps.d.ts +10 -0
  12. package/dist/src/cli/commands/kubernetes/ps.js +41 -0
  13. package/dist/src/cli/commands/kubernetes/restart.d.ts +10 -0
  14. package/dist/src/cli/commands/kubernetes/restart.js +22 -0
  15. package/dist/src/cli/commands/kubernetes/shell.d.ts +14 -0
  16. package/dist/src/cli/commands/kubernetes/shell.js +64 -0
  17. package/dist/src/cli/commands/restart.js +3 -7
  18. package/dist/src/kubernetes/client.d.ts +6 -0
  19. package/dist/src/kubernetes/client.js +10 -0
  20. package/dist/src/kubernetes/index.d.ts +2 -0
  21. package/dist/src/kubernetes/index.js +2 -0
  22. package/dist/src/kubernetes/operations/GetDeploymentPodsOperation.d.ts +12 -0
  23. package/dist/src/kubernetes/operations/GetDeploymentPodsOperation.js +20 -0
  24. package/dist/src/kubernetes/operations/RestartPodsOperation.d.ts +13 -0
  25. package/dist/src/kubernetes/operations/RestartPodsOperation.js +50 -0
  26. package/dist/src/kubernetes/operations/index.d.ts +1 -0
  27. package/dist/src/kubernetes/operations/index.js +1 -0
  28. package/dist/src/types.d.ts +6 -0
  29. package/dist/src/utils/time.d.ts +1 -1
  30. package/dist/src/utils/time.js +28 -17
  31. package/oclif.manifest.json +223 -1
  32. package/package.json +24 -19
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.11.0 darwin-x64 node-v22.18.0
17
+ @enspirit/emb/0.13.0 darwin-x64 node-v22.18.0
18
18
  $ emb --help [COMMAND]
19
19
  USAGE
20
20
  $ emb COMMAND
@@ -37,6 +37,10 @@ USAGE
37
37
  * [`emb images delete`](#emb-images-delete)
38
38
  * [`emb images prune`](#emb-images-prune)
39
39
  * [`emb images push`](#emb-images-push)
40
+ * [`emb kubernetes logs COMPONENT`](#emb-kubernetes-logs-component)
41
+ * [`emb kubernetes ps`](#emb-kubernetes-ps)
42
+ * [`emb kubernetes restart [DEPLOYMENT]`](#emb-kubernetes-restart-deployment)
43
+ * [`emb kubernetes shell COMPONENT`](#emb-kubernetes-shell-component)
40
44
  * [`emb logs COMPONENT`](#emb-logs-component)
41
45
  * [`emb ps`](#emb-ps)
42
46
  * [`emb resources`](#emb-resources)
@@ -379,6 +383,100 @@ EXAMPLES
379
383
  $ emb images push --registry my.registry.io --retag newtag
380
384
  ```
381
385
 
386
+ ## `emb kubernetes logs COMPONENT`
387
+
388
+ Follow kubernetes logs.
389
+
390
+ ```
391
+ USAGE
392
+ $ emb kubernetes logs COMPONENT -n <value> [--verbose] [-f]
393
+
394
+ ARGUMENTS
395
+ COMPONENT The component you want to see the logs of
396
+
397
+ FLAGS
398
+ -f, --[no-]follow Follow log output
399
+ -n, --namespace=<value> (required) The Kubernetes namespace to target
400
+ --[no-]verbose
401
+
402
+ DESCRIPTION
403
+ Follow kubernetes logs.
404
+
405
+ ALIASES
406
+ $ emb logs
407
+
408
+ EXAMPLES
409
+ $ emb kubernetes logs
410
+ ```
411
+
412
+ ## `emb kubernetes ps`
413
+
414
+ Show running pods.
415
+
416
+ ```
417
+ USAGE
418
+ $ emb kubernetes ps -n <value> [--verbose] [--watch]
419
+
420
+ FLAGS
421
+ -n, --namespace=<value> (required) The Kubernetes namespace to target
422
+ --[no-]verbose
423
+ --[no-]watch
424
+
425
+ DESCRIPTION
426
+ Show running pods.
427
+
428
+ EXAMPLES
429
+ $ emb kubernetes ps
430
+ ```
431
+
432
+ ## `emb kubernetes restart [DEPLOYMENT]`
433
+
434
+ Restart pods of an instance.
435
+
436
+ ```
437
+ USAGE
438
+ $ emb kubernetes restart [DEPLOYMENT...] -n <value> [--verbose]
439
+
440
+ ARGUMENTS
441
+ DEPLOYMENT... The deployment(s) to restart
442
+
443
+ FLAGS
444
+ -n, --namespace=<value> (required) The Kubernetes namespace to target
445
+ --[no-]verbose
446
+
447
+ DESCRIPTION
448
+ Restart pods of an instance.
449
+
450
+ EXAMPLES
451
+ $ emb kubernetes restart
452
+ ```
453
+
454
+ ## `emb kubernetes shell COMPONENT`
455
+
456
+ Get a shell on a deployed component.
457
+
458
+ ```
459
+ USAGE
460
+ $ emb kubernetes shell COMPONENT -n <value> [--verbose] [-s <value>]
461
+
462
+ ARGUMENTS
463
+ COMPONENT The component you want to get a shell on
464
+
465
+ FLAGS
466
+ -n, --namespace=<value> (required) The Kubernetes namespace to target
467
+ -s, --shell=<value> [default: bash] The shell to run
468
+ --[no-]verbose
469
+
470
+ DESCRIPTION
471
+ Get a shell on a deployed component.
472
+
473
+ ALIASES
474
+ $ emb shell
475
+
476
+ EXAMPLES
477
+ $ emb kubernetes shell
478
+ ```
479
+
382
480
  ## `emb logs COMPONENT`
383
481
 
384
482
  Get components logs.
@@ -717,5 +815,5 @@ EXAMPLES
717
815
  $ emb update --available
718
816
  ```
719
817
 
720
- _See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.7.3/src/commands/update.ts)_
818
+ _See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.7.4/src/commands/update.ts)_
721
819
  <!-- commandsstop -->
@@ -2,6 +2,7 @@ import { DockerComposeClient, getContext, setContext } from '../../index.js';
2
2
  import { Command, Flags } from '@oclif/core';
3
3
  import Dockerode from 'dockerode';
4
4
  import { loadConfig } from '../../config/index.js';
5
+ import { createKubernetesClient } from '../../kubernetes/client.js';
5
6
  import { Monorepo } from '../../monorepo/monorepo.js';
6
7
  import { withMarker } from '../utils.js';
7
8
  export class BaseCommand extends Command {
@@ -31,6 +32,7 @@ export class BaseCommand extends Command {
31
32
  docker: new Dockerode(),
32
33
  monorepo,
33
34
  compose,
35
+ kubernetes: createKubernetesClient(),
34
36
  });
35
37
  }
36
38
  catch (error) {
@@ -0,0 +1,7 @@
1
+ import { BaseCommand } from './BaseCommand.js';
2
+ export declare abstract class KubernetesCommand extends BaseCommand {
3
+ static baseFlags: {
4
+ namespace: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
5
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
+ };
7
+ }
@@ -0,0 +1,15 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from './BaseCommand.js';
3
+ export class KubernetesCommand extends BaseCommand {
4
+ static baseFlags = {
5
+ ...BaseCommand.baseFlags,
6
+ namespace: Flags.string({
7
+ name: 'namespace',
8
+ description: 'The Kubernetes namespace to target',
9
+ aliases: ['ns'],
10
+ char: 'n',
11
+ required: true,
12
+ env: 'K8S_NAMESPACE',
13
+ }),
14
+ };
15
+ }
@@ -1,2 +1,3 @@
1
1
  export * from './BaseCommand.js';
2
2
  export * from './FlavouredCommand.js';
3
+ export * from './KubernetesCommand.js';
@@ -1,2 +1,3 @@
1
1
  export * from './BaseCommand.js';
2
2
  export * from './FlavouredCommand.js';
3
+ export * from './KubernetesCommand.js';
@@ -1,5 +1,5 @@
1
1
  import { BaseCommand } from '../../index.js';
2
- export default class ComponentsLogs extends BaseCommand {
2
+ export default class ComponentShellCommand extends BaseCommand {
3
3
  static aliases: string[];
4
4
  static description: string;
5
5
  static enableJsonFlag: boolean;
@@ -2,7 +2,7 @@ import { Args, Flags } from '@oclif/core';
2
2
  import { BaseCommand, getContext } from '../../index.js';
3
3
  import { ComposeExecShellOperation } from '../../../docker/index.js';
4
4
  import { ShellExitError } from '../../../errors.js';
5
- export default class ComponentsLogs extends BaseCommand {
5
+ export default class ComponentShellCommand extends BaseCommand {
6
6
  static aliases = ['shell'];
7
7
  static description = 'Get a shell on a running component.';
8
8
  static enableJsonFlag = false;
@@ -23,7 +23,7 @@ export default class ComponentsLogs extends BaseCommand {
23
23
  }),
24
24
  };
25
25
  async run() {
26
- const { flags, args } = await this.parse(ComponentsLogs);
26
+ const { flags, args } = await this.parse(ComponentShellCommand);
27
27
  const { monorepo } = await getContext();
28
28
  try {
29
29
  await monorepo.run(new ComposeExecShellOperation(), {
@@ -0,0 +1,14 @@
1
+ import { KubernetesCommand } from '../../index.js';
2
+ export default class KubernetesLogs extends KubernetesCommand {
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,53 @@
1
+ import { Log } from '@kubernetes/client-node';
2
+ import { Args, Flags } from '@oclif/core';
3
+ import { PassThrough } from 'node:stream';
4
+ import { KubernetesCommand } from '../../index.js';
5
+ import { GetDeploymentPodsOperation } from '../../../kubernetes/operations/GetDeploymentPodsOperation.js';
6
+ export default class KubernetesLogs extends KubernetesCommand {
7
+ static aliases = ['logs'];
8
+ static description = 'Follow kubernetes logs.';
9
+ static enableJsonFlag = false;
10
+ static examples = ['<%= config.bin %> <%= command.id %>'];
11
+ static flags = {
12
+ follow: Flags.boolean({
13
+ name: 'follow',
14
+ char: 'f',
15
+ allowNo: true,
16
+ description: 'Follow log output',
17
+ default: true,
18
+ }),
19
+ };
20
+ static args = {
21
+ component: Args.string({
22
+ name: 'component',
23
+ description: 'The component you want to see the logs of',
24
+ required: true,
25
+ }),
26
+ };
27
+ async run() {
28
+ const { flags, args } = await this.parse(KubernetesLogs);
29
+ const { monorepo, kubernetes } = this.context;
30
+ // Check the component name is valid (would raise otherwise)
31
+ monorepo.component(args.component);
32
+ const pods = await monorepo.run(new GetDeploymentPodsOperation(), {
33
+ namespace: flags.namespace,
34
+ deployment: args.component,
35
+ });
36
+ if (pods.length === 0) {
37
+ throw new Error(`No running pod found for component ${args.component}`);
38
+ }
39
+ const k8sLogs = new Log(kubernetes.config);
40
+ const transform = new PassThrough();
41
+ transform.on('data', (chunk) => {
42
+ process.stdout.write(chunk);
43
+ });
44
+ const pod = pods[0];
45
+ const container = pod.spec.containers[0];
46
+ await k8sLogs.log(flags.namespace, pod.metadata.name, container.name, transform, {
47
+ follow: flags.follow,
48
+ tailLines: 50,
49
+ pretty: false,
50
+ timestamps: true,
51
+ });
52
+ }
53
+ }
@@ -0,0 +1,10 @@
1
+ import { KubernetesCommand } from '../../index.js';
2
+ export default class KPSCommand extends KubernetesCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ watch: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ };
8
+ static strict: boolean;
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,41 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { printTable } from '@oclif/table';
3
+ import { getContext, KubernetesCommand, TABLE_DEFAULTS } from '../../index.js';
4
+ import { timeAgo } from '../../../utils/time.js';
5
+ export default class KPSCommand extends KubernetesCommand {
6
+ static description = 'Show running pods.';
7
+ static examples = ['<%= config.bin %> <%= command.id %>'];
8
+ static flags = {
9
+ watch: Flags.boolean({
10
+ name: 'watch',
11
+ allowNo: true,
12
+ }),
13
+ };
14
+ static strict = false;
15
+ async run() {
16
+ const { flags } = await this.parse(KPSCommand);
17
+ const { kubernetes } = getContext();
18
+ const { items } = await kubernetes.core.listNamespacedPod({
19
+ namespace: flags.namespace,
20
+ });
21
+ const pods = items.map((i) => {
22
+ const restarts = i.status?.containerStatuses
23
+ ?.filter((s) => s.restartCount > 0)
24
+ .map((c) => ({
25
+ count: c.restartCount,
26
+ ago: timeAgo(c.lastState?.terminated?.finishedAt),
27
+ })) || [];
28
+ const restart = restarts.length > 0 ? restarts[0] : null;
29
+ return {
30
+ name: i.metadata?.name,
31
+ status: i.status?.phase,
32
+ restarts: restart ? `${restart?.count} (${restart?.ago} ago)` : '',
33
+ age: timeAgo(i.status?.startTime),
34
+ };
35
+ });
36
+ printTable({
37
+ ...TABLE_DEFAULTS,
38
+ data: pods,
39
+ });
40
+ }
41
+ }
@@ -0,0 +1,10 @@
1
+ import { KubernetesCommand } from '../../index.js';
2
+ export default class KRestartCommand extends KubernetesCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ deployment: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static strict: boolean;
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,22 @@
1
+ import { Args } from '@oclif/core';
2
+ import { getContext, KubernetesCommand } from '../../index.js';
3
+ import { PodsRestartOperation } from '../../../kubernetes/index.js';
4
+ export default class KRestartCommand extends KubernetesCommand {
5
+ static description = 'Restart pods of an instance.';
6
+ static examples = ['<%= config.bin %> <%= command.id %>'];
7
+ static args = {
8
+ deployment: Args.string({
9
+ name: 'deployment',
10
+ description: 'The deployment(s) to restart',
11
+ }),
12
+ };
13
+ static strict = false;
14
+ async run() {
15
+ const { flags, argv } = await this.parse(KRestartCommand);
16
+ const { monorepo } = getContext();
17
+ await monorepo.run(new PodsRestartOperation(), {
18
+ namespace: flags.namespace,
19
+ deployments: argv.length > 0 ? argv : undefined,
20
+ });
21
+ }
22
+ }
@@ -0,0 +1,14 @@
1
+ import { KubernetesCommand } from '../../index.js';
2
+ export default class PodShellCommand extends KubernetesCommand {
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,64 @@
1
+ import { Exec } from '@kubernetes/client-node';
2
+ import { Args, Flags } from '@oclif/core';
3
+ import { getContext, KubernetesCommand } from '../../index.js';
4
+ import { GetDeploymentPodsOperation } from '../../../kubernetes/operations/GetDeploymentPodsOperation.js';
5
+ function enableRawMode(stdin) {
6
+ const wasRaw = stdin.isRaw;
7
+ const { isTTY } = stdin;
8
+ if (isTTY) {
9
+ stdin.setRawMode?.(true);
10
+ stdin.resume();
11
+ // Do NOT set encoding; keep it binary so control chars pass through.
12
+ }
13
+ // Let the remote shell handle Ctrl+C. If you prefer local exit on ^C, uncomment:
14
+ // stdin.on('data', (buf) => { if (buf.equals(Buffer.from([3]))) process.exit(130); });
15
+ return () => {
16
+ if (isTTY) {
17
+ stdin.setRawMode?.(Boolean(wasRaw));
18
+ if (!wasRaw) {
19
+ stdin.pause();
20
+ }
21
+ }
22
+ };
23
+ }
24
+ export default class PodShellCommand extends KubernetesCommand {
25
+ static aliases = ['shell'];
26
+ static description = 'Get a shell on a deployed component.';
27
+ static enableJsonFlag = false;
28
+ static examples = ['<%= config.bin %> <%= command.id %>'];
29
+ static flags = {
30
+ shell: Flags.string({
31
+ name: 'shell',
32
+ char: 's',
33
+ description: 'The shell to run',
34
+ default: 'bash',
35
+ }),
36
+ };
37
+ static args = {
38
+ component: Args.string({
39
+ name: 'component',
40
+ description: 'The component you want to get a shell on',
41
+ required: true,
42
+ }),
43
+ };
44
+ async run() {
45
+ const { flags, args } = await this.parse(PodShellCommand);
46
+ const { monorepo, kubernetes } = await getContext();
47
+ const pods = await monorepo.run(new GetDeploymentPodsOperation(), {
48
+ namespace: flags.namespace,
49
+ deployment: args.component,
50
+ });
51
+ if (pods.length === 0) {
52
+ throw new Error(`No running pod found for component ${args.component}`);
53
+ }
54
+ const pod = pods[0];
55
+ const container = pod.spec.containers[0];
56
+ const exec = new Exec(kubernetes.config);
57
+ enableRawMode(process.stdin);
58
+ const res = await exec.exec(flags.namespace, pod.metadata.name, container.name, [flags.shell], process.stdout, process.stderr, process.stdin, true);
59
+ res.on('close', () => {
60
+ // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
61
+ process.exit(0);
62
+ });
63
+ }
64
+ }
@@ -23,13 +23,9 @@ export default class RestartComand extends BaseCommand {
23
23
  async run() {
24
24
  const { flags, argv } = await this.parse(RestartComand);
25
25
  const { monorepo } = getContext();
26
- let components;
27
- if (argv.length > 0) {
28
- components =
29
- argv.length > 0
30
- ? argv.map((name) => monorepo.component(name))
31
- : monorepo.components;
32
- }
26
+ const components = argv.length > 0
27
+ ? argv.map((name) => monorepo.component(name))
28
+ : monorepo.components;
33
29
  await monorepo.run(new ComposeRestartOperation(), {
34
30
  noDeps: flags['no-deps'],
35
31
  services: components?.map((c) => c.name),
@@ -0,0 +1,6 @@
1
+ import { AppsV1Api, CoreV1Api, KubeConfig } from '@kubernetes/client-node';
2
+ export declare const createKubernetesClient: () => {
3
+ config: KubeConfig;
4
+ core: CoreV1Api;
5
+ apps: AppsV1Api;
6
+ };
@@ -0,0 +1,10 @@
1
+ import { AppsV1Api, CoreV1Api, KubeConfig } from '@kubernetes/client-node';
2
+ export const createKubernetesClient = () => {
3
+ const kc = new KubeConfig();
4
+ kc.loadFromDefault();
5
+ return {
6
+ config: kc,
7
+ core: kc.makeApiClient(CoreV1Api),
8
+ apps: kc.makeApiClient(AppsV1Api),
9
+ };
10
+ };
@@ -0,0 +1,2 @@
1
+ export * from './client.js';
2
+ export * from './operations/index.js';
@@ -0,0 +1,2 @@
1
+ export * from './client.js';
2
+ export * from './operations/index.js';
@@ -0,0 +1,12 @@
1
+ import { V1Pod } from '@kubernetes/client-node';
2
+ import * as z from 'zod';
3
+ import { AbstractOperation } from '../../operations/index.js';
4
+ declare const schema: z.ZodObject<{
5
+ namespace: z.ZodString;
6
+ deployment: z.ZodString;
7
+ }, z.core.$strip>;
8
+ export declare class GetDeploymentPodsOperation extends AbstractOperation<typeof schema, Array<V1Pod>> {
9
+ constructor();
10
+ protected _run(input: z.input<typeof schema>): Promise<Array<V1Pod>>;
11
+ }
12
+ export {};
@@ -0,0 +1,20 @@
1
+ import { getContext } from '../../index.js';
2
+ import * as z from 'zod';
3
+ import { AbstractOperation } from '../../operations/index.js';
4
+ const schema = z.object({
5
+ namespace: z.string().describe('The namespace in which to restart pods'),
6
+ deployment: z.string(),
7
+ });
8
+ export class GetDeploymentPodsOperation extends AbstractOperation {
9
+ constructor() {
10
+ super(schema);
11
+ }
12
+ async _run(input) {
13
+ const { kubernetes } = getContext();
14
+ const res = await kubernetes.core.listNamespacedPod({
15
+ namespace: input.namespace,
16
+ labelSelector: `component=${input.deployment}`,
17
+ });
18
+ return res.items;
19
+ }
20
+ }
@@ -0,0 +1,13 @@
1
+ import * as z from 'zod';
2
+ import { AbstractOperation } from '../../operations/index.js';
3
+ declare const schema: z.ZodObject<{
4
+ namespace: z.ZodString;
5
+ deployments: z.ZodOptional<z.ZodArray<z.ZodString>>;
6
+ }, z.core.$strip>;
7
+ export declare class PodsRestartOperation extends AbstractOperation<typeof schema, void> {
8
+ constructor();
9
+ protected _run(input: z.input<typeof schema>): Promise<void>;
10
+ private patchDeployment;
11
+ private listDeployments;
12
+ }
13
+ export {};
@@ -0,0 +1,50 @@
1
+ import { getContext } from '../../index.js';
2
+ import * as z from 'zod';
3
+ import { AbstractOperation } from '../../operations/index.js';
4
+ const schema = z.object({
5
+ namespace: z.string().describe('The namespace in which to restart pods'),
6
+ deployments: z
7
+ .array(z.string())
8
+ .optional()
9
+ .describe('The list of deployments to restart'),
10
+ });
11
+ export class PodsRestartOperation extends AbstractOperation {
12
+ constructor() {
13
+ super(schema);
14
+ }
15
+ async _run(input) {
16
+ const { monorepo } = getContext();
17
+ const manager = monorepo.taskManager();
18
+ const deployments = input?.deployments || (await this.listDeployments(input.namespace));
19
+ const tasks = deployments.map((name) => {
20
+ return {
21
+ title: `Restart ${name}`,
22
+ task: async () => {
23
+ return this.patchDeployment(input.namespace, name);
24
+ },
25
+ };
26
+ });
27
+ manager.add(tasks);
28
+ await manager.runAll();
29
+ }
30
+ async patchDeployment(namespace, name) {
31
+ const patch = [
32
+ {
33
+ op: 'add',
34
+ path: '/spec/template/metadata/annotations/kubectl.kubernetes.io~1restartedAt',
35
+ value: new Date().toISOString(),
36
+ },
37
+ ];
38
+ return this.context.kubernetes.apps.patchNamespacedDeployment({
39
+ namespace,
40
+ name,
41
+ body: patch,
42
+ });
43
+ }
44
+ async listDeployments(namespace) {
45
+ const { items } = await this.context.kubernetes.apps.listNamespacedDeployment({
46
+ namespace,
47
+ });
48
+ return items.map((i) => i.metadata?.name);
49
+ }
50
+ }
@@ -0,0 +1 @@
1
+ export * from './RestartPodsOperation.js';
@@ -0,0 +1 @@
1
+ export * from './RestartPodsOperation.js';
@@ -1,4 +1,5 @@
1
1
  import type Docker from 'dockerode';
2
+ import { AppsV1Api, CoreV1Api, KubeConfig } from '@kubernetes/client-node';
2
3
  import { Monorepo } from './monorepo/index.js';
3
4
  import { DockerComposeClient } from './docker/index.js';
4
5
  /**
@@ -11,5 +12,10 @@ import { DockerComposeClient } from './docker/index.js';
11
12
  export interface EmbContext {
12
13
  compose: DockerComposeClient;
13
14
  docker: Docker;
15
+ kubernetes: {
16
+ config: KubeConfig;
17
+ apps: AppsV1Api;
18
+ core: CoreV1Api;
19
+ };
14
20
  monorepo: Monorepo;
15
21
  }
@@ -1,2 +1,2 @@
1
1
  import { DateTime } from 'luxon';
2
- export declare const timeAgo: (date: Date | DateTime) => string;
2
+ export declare const timeAgo: (date: Date | DateTime | null | undefined) => string;
@@ -1,19 +1,30 @@
1
- import { DateTime } from 'luxon';
2
- const units = [
3
- 'year',
4
- 'month',
5
- 'week',
6
- 'day',
7
- 'hour',
8
- 'minute',
9
- 'second',
10
- ];
11
1
  export const timeAgo = (date) => {
12
- const dateTime = date instanceof Date ? DateTime.fromJSDate(date) : date;
13
- const diff = dateTime.diffNow().shiftTo(...units);
14
- const unit = units.find((unit) => diff.get(unit) !== 0) || 'second';
15
- const relativeFormatter = new Intl.RelativeTimeFormat('en', {
16
- numeric: 'auto',
17
- });
18
- return relativeFormatter.format(Math.trunc(diff.as(unit)), unit);
2
+ if (!date) {
3
+ return '';
4
+ }
5
+ const now = Date.now();
6
+ const then = date instanceof Date ? date.getTime() : date.toJSDate().getTime();
7
+ const diff = Math.max(0, now - then); // in ms
8
+ let seconds = Math.floor(diff / 1000);
9
+ const units = [
10
+ { label: 'y', secs: 60 * 60 * 24 * 365 },
11
+ { label: 'mo', secs: 60 * 60 * 24 * 30 },
12
+ { label: 'w', secs: 60 * 60 * 24 * 7 },
13
+ { label: 'd', secs: 60 * 60 * 24 },
14
+ { label: 'h', secs: 60 * 60 },
15
+ { label: 'm', secs: 60 },
16
+ { label: 's', secs: 1 },
17
+ ];
18
+ const parts = [];
19
+ for (const { label, secs } of units) {
20
+ const value = Math.floor(seconds / secs);
21
+ if (value > 0) {
22
+ parts.push(`${value}${label}`);
23
+ seconds -= value * secs;
24
+ }
25
+ if (parts.length === 2) {
26
+ break;
27
+ } // only 2 units max
28
+ }
29
+ return parts.join('');
19
30
  };
@@ -996,7 +996,229 @@
996
996
  "tasks",
997
997
  "run.js"
998
998
  ]
999
+ },
1000
+ "kubernetes:logs": {
1001
+ "aliases": [
1002
+ "logs"
1003
+ ],
1004
+ "args": {
1005
+ "component": {
1006
+ "description": "The component you want to see the logs of",
1007
+ "name": "component",
1008
+ "required": true
1009
+ }
1010
+ },
1011
+ "description": "Follow kubernetes logs.",
1012
+ "examples": [
1013
+ "<%= config.bin %> <%= command.id %>"
1014
+ ],
1015
+ "flags": {
1016
+ "verbose": {
1017
+ "name": "verbose",
1018
+ "allowNo": true,
1019
+ "type": "boolean"
1020
+ },
1021
+ "namespace": {
1022
+ "aliases": [
1023
+ "ns"
1024
+ ],
1025
+ "char": "n",
1026
+ "description": "The Kubernetes namespace to target",
1027
+ "env": "K8S_NAMESPACE",
1028
+ "name": "namespace",
1029
+ "required": true,
1030
+ "hasDynamicHelp": false,
1031
+ "multiple": false,
1032
+ "type": "option"
1033
+ },
1034
+ "follow": {
1035
+ "char": "f",
1036
+ "description": "Follow log output",
1037
+ "name": "follow",
1038
+ "allowNo": true,
1039
+ "type": "boolean"
1040
+ }
1041
+ },
1042
+ "hasDynamicHelp": false,
1043
+ "hiddenAliases": [],
1044
+ "id": "kubernetes:logs",
1045
+ "pluginAlias": "@enspirit/emb",
1046
+ "pluginName": "@enspirit/emb",
1047
+ "pluginType": "core",
1048
+ "strict": true,
1049
+ "enableJsonFlag": false,
1050
+ "isESM": true,
1051
+ "relativePath": [
1052
+ "dist",
1053
+ "src",
1054
+ "cli",
1055
+ "commands",
1056
+ "kubernetes",
1057
+ "logs.js"
1058
+ ]
1059
+ },
1060
+ "kubernetes:ps": {
1061
+ "aliases": [],
1062
+ "args": {},
1063
+ "description": "Show running pods.",
1064
+ "examples": [
1065
+ "<%= config.bin %> <%= command.id %>"
1066
+ ],
1067
+ "flags": {
1068
+ "verbose": {
1069
+ "name": "verbose",
1070
+ "allowNo": true,
1071
+ "type": "boolean"
1072
+ },
1073
+ "namespace": {
1074
+ "aliases": [
1075
+ "ns"
1076
+ ],
1077
+ "char": "n",
1078
+ "description": "The Kubernetes namespace to target",
1079
+ "env": "K8S_NAMESPACE",
1080
+ "name": "namespace",
1081
+ "required": true,
1082
+ "hasDynamicHelp": false,
1083
+ "multiple": false,
1084
+ "type": "option"
1085
+ },
1086
+ "watch": {
1087
+ "name": "watch",
1088
+ "allowNo": true,
1089
+ "type": "boolean"
1090
+ }
1091
+ },
1092
+ "hasDynamicHelp": false,
1093
+ "hiddenAliases": [],
1094
+ "id": "kubernetes:ps",
1095
+ "pluginAlias": "@enspirit/emb",
1096
+ "pluginName": "@enspirit/emb",
1097
+ "pluginType": "core",
1098
+ "strict": false,
1099
+ "enableJsonFlag": false,
1100
+ "isESM": true,
1101
+ "relativePath": [
1102
+ "dist",
1103
+ "src",
1104
+ "cli",
1105
+ "commands",
1106
+ "kubernetes",
1107
+ "ps.js"
1108
+ ]
1109
+ },
1110
+ "kubernetes:restart": {
1111
+ "aliases": [],
1112
+ "args": {
1113
+ "deployment": {
1114
+ "description": "The deployment(s) to restart",
1115
+ "name": "deployment"
1116
+ }
1117
+ },
1118
+ "description": "Restart pods of an instance.",
1119
+ "examples": [
1120
+ "<%= config.bin %> <%= command.id %>"
1121
+ ],
1122
+ "flags": {
1123
+ "verbose": {
1124
+ "name": "verbose",
1125
+ "allowNo": true,
1126
+ "type": "boolean"
1127
+ },
1128
+ "namespace": {
1129
+ "aliases": [
1130
+ "ns"
1131
+ ],
1132
+ "char": "n",
1133
+ "description": "The Kubernetes namespace to target",
1134
+ "env": "K8S_NAMESPACE",
1135
+ "name": "namespace",
1136
+ "required": true,
1137
+ "hasDynamicHelp": false,
1138
+ "multiple": false,
1139
+ "type": "option"
1140
+ }
1141
+ },
1142
+ "hasDynamicHelp": false,
1143
+ "hiddenAliases": [],
1144
+ "id": "kubernetes:restart",
1145
+ "pluginAlias": "@enspirit/emb",
1146
+ "pluginName": "@enspirit/emb",
1147
+ "pluginType": "core",
1148
+ "strict": false,
1149
+ "enableJsonFlag": false,
1150
+ "isESM": true,
1151
+ "relativePath": [
1152
+ "dist",
1153
+ "src",
1154
+ "cli",
1155
+ "commands",
1156
+ "kubernetes",
1157
+ "restart.js"
1158
+ ]
1159
+ },
1160
+ "kubernetes:shell": {
1161
+ "aliases": [
1162
+ "shell"
1163
+ ],
1164
+ "args": {
1165
+ "component": {
1166
+ "description": "The component you want to get a shell on",
1167
+ "name": "component",
1168
+ "required": true
1169
+ }
1170
+ },
1171
+ "description": "Get a shell on a deployed component.",
1172
+ "examples": [
1173
+ "<%= config.bin %> <%= command.id %>"
1174
+ ],
1175
+ "flags": {
1176
+ "verbose": {
1177
+ "name": "verbose",
1178
+ "allowNo": true,
1179
+ "type": "boolean"
1180
+ },
1181
+ "namespace": {
1182
+ "aliases": [
1183
+ "ns"
1184
+ ],
1185
+ "char": "n",
1186
+ "description": "The Kubernetes namespace to target",
1187
+ "env": "K8S_NAMESPACE",
1188
+ "name": "namespace",
1189
+ "required": true,
1190
+ "hasDynamicHelp": false,
1191
+ "multiple": false,
1192
+ "type": "option"
1193
+ },
1194
+ "shell": {
1195
+ "char": "s",
1196
+ "description": "The shell to run",
1197
+ "name": "shell",
1198
+ "default": "bash",
1199
+ "hasDynamicHelp": false,
1200
+ "multiple": false,
1201
+ "type": "option"
1202
+ }
1203
+ },
1204
+ "hasDynamicHelp": false,
1205
+ "hiddenAliases": [],
1206
+ "id": "kubernetes:shell",
1207
+ "pluginAlias": "@enspirit/emb",
1208
+ "pluginName": "@enspirit/emb",
1209
+ "pluginType": "core",
1210
+ "strict": true,
1211
+ "enableJsonFlag": false,
1212
+ "isESM": true,
1213
+ "relativePath": [
1214
+ "dist",
1215
+ "src",
1216
+ "cli",
1217
+ "commands",
1218
+ "kubernetes",
1219
+ "shell.js"
1220
+ ]
999
1221
  }
1000
1222
  },
1001
- "version": "0.11.0"
1223
+ "version": "0.13.0"
1002
1224
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@enspirit/emb",
3
3
  "type": "module",
4
- "version": "0.11.0",
4
+ "version": "0.13.0",
5
5
  "keywords": [
6
6
  "monorepo",
7
7
  "docker",
@@ -41,49 +41,51 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@fastify/deepmerge": "^3.1.0",
44
- "@listr2/manager": "^3.0.1",
44
+ "@kubernetes/client-node": "1.3.0",
45
+ "@listr2/manager": "^3.0.3",
45
46
  "@oclif/core": "^4.5.2",
46
47
  "@oclif/plugin-autocomplete": "^3.2.34",
47
48
  "@oclif/plugin-help": "^6.2.32",
48
- "@oclif/plugin-update": "^4.7.3",
49
+ "@oclif/plugin-update": "^4.7.4",
49
50
  "@oclif/table": "^0.4.12",
50
51
  "ajv": "^8.17.1",
52
+ "ansi-escapes": "^7.0.0",
51
53
  "colorette": "^2.0.20",
52
- "docker-compose": "^1.2.0",
54
+ "docker-compose": "^1.3.0",
53
55
  "dockerode": "^4.0.7",
54
56
  "dotenv": "^17.2.1",
55
57
  "execa": "^9.6.0",
56
58
  "fast-json-patch": "^3.1.1",
57
- "fdir": "^6.4.6",
59
+ "fdir": "^6.5.0",
58
60
  "find-up": "^7.0.0",
59
61
  "glob": "^11.0.3",
60
62
  "graphlib": "^2.1.8",
61
- "@inquirer/prompts": "^7.8.3",
62
- "@listr2/prompt-adapter-inquirer": "^3.0.2",
63
- "listr2": "^9.0.1",
63
+ "@inquirer/prompts": "^7.8.4",
64
+ "@listr2/prompt-adapter-inquirer": "^3.0.3",
65
+ "listr2": "^9.0.3",
64
66
  "luxon": "^3.7.1",
65
- "protobufjs": "^7.5.3",
67
+ "protobufjs": "^7.5.4",
66
68
  "p-map": "^7.0.3",
67
69
  "simple-git": "^3.28.0",
68
70
  "yaml": "^2.8.1",
69
- "zod": "^4.0.16"
71
+ "zod": "^4.1.5"
70
72
  },
71
73
  "devDependencies": {
72
74
  "@eslint/eslintrc": "^3.3.1",
73
- "@eslint/js": "^9.32.0",
75
+ "@eslint/js": "^9.34.0",
74
76
  "@oclif/prettier-config": "^0.2.1",
75
77
  "@oclif/test": "^4",
76
78
  "@tsconfig/node20": "^20.1.6",
77
- "@types/dockerode": "^3.3.42",
79
+ "@types/dockerode": "^3.3.43",
78
80
  "@types/graphlib": "^2.1.12",
79
81
  "@types/luxon": "^3.7.1",
80
- "@types/node": "^24.2.0",
81
- "@typescript-eslint/eslint-plugin": "^8.39.0",
82
- "@typescript-eslint/parser": "^8.39.0",
83
- "eslint": "^9.32.0",
82
+ "@types/node": "^24.3.0",
83
+ "@typescript-eslint/eslint-plugin": "^8.41.0",
84
+ "@typescript-eslint/parser": "^8.41.0",
85
+ "eslint": "^9.34.0",
84
86
  "eslint-config-oclif": "^6",
85
87
  "eslint-config-prettier": "^10.1.8",
86
- "eslint-plugin-prettier": "^5.5.3",
88
+ "eslint-plugin-prettier": "^5.5.4",
87
89
  "json-schema-to-typescript": "^15.0.4",
88
90
  "oclif": "^4",
89
91
  "prettier": "^3.6.2",
@@ -91,7 +93,7 @@
91
93
  "shx": "^0.4.0",
92
94
  "ts-node": "^10.9.2",
93
95
  "tsc-alias": "^1.8.16",
94
- "tsx": "^4.20.3",
96
+ "tsx": "^4.20.5",
95
97
  "typescript": "^5.9.2",
96
98
  "vitest": "^3.2.4",
97
99
  "vite-tsconfig-paths": "^5.1.4"
@@ -140,8 +142,11 @@
140
142
  },
141
143
  "tasks": {
142
144
  "description": "List and run tasks"
145
+ },
146
+ "kubernetes": {
147
+ "description": "Manage project instances on kubernetes"
143
148
  }
144
149
  }
145
150
  },
146
- "packageManager": "pnpm@9.11.0+sha512.0a203ffaed5a3f63242cd064c8fb5892366c103e328079318f78062f24ea8c9d50bc6a47aa3567cabefd824d170e78fa2745ed1f16b132e16436146b7688f19b"
151
+ "packageManager": "pnpm@10.15.1"
147
152
  }