@enspirit/emb 0.10.1 → 0.12.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 (34) hide show
  1. package/README.md +72 -1
  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/kubernetes/logs.d.ts +14 -0
  8. package/dist/src/cli/commands/kubernetes/logs.js +53 -0
  9. package/dist/src/cli/commands/kubernetes/ps.d.ts +10 -0
  10. package/dist/src/cli/commands/kubernetes/ps.js +41 -0
  11. package/dist/src/cli/commands/kubernetes/restart.d.ts +10 -0
  12. package/dist/src/cli/commands/kubernetes/restart.js +22 -0
  13. package/dist/src/cli/commands/resources/index.js +2 -2
  14. package/dist/src/cli/commands/restart.js +3 -7
  15. package/dist/src/docker/compose/client.js +4 -0
  16. package/dist/src/kubernetes/client.d.ts +6 -0
  17. package/dist/src/kubernetes/client.js +10 -0
  18. package/dist/src/kubernetes/index.d.ts +2 -0
  19. package/dist/src/kubernetes/index.js +2 -0
  20. package/dist/src/kubernetes/operations/GetDeploymentPodsOperation.d.ts +12 -0
  21. package/dist/src/kubernetes/operations/GetDeploymentPodsOperation.js +20 -0
  22. package/dist/src/kubernetes/operations/RestartPodsOperation.d.ts +13 -0
  23. package/dist/src/kubernetes/operations/RestartPodsOperation.js +50 -0
  24. package/dist/src/kubernetes/operations/index.d.ts +1 -0
  25. package/dist/src/kubernetes/operations/index.js +1 -0
  26. package/dist/src/monorepo/plugins/AutoDockerPlugin.js +4 -2
  27. package/dist/src/monorepo/plugins/EmbfileLoaderPlugin.js +1 -0
  28. package/dist/src/monorepo/resources/FileResourceBuilder.d.ts +3 -1
  29. package/dist/src/monorepo/resources/FileResourceBuilder.js +13 -1
  30. package/dist/src/types.d.ts +6 -0
  31. package/dist/src/utils/time.d.ts +1 -1
  32. package/dist/src/utils/time.js +28 -17
  33. package/oclif.manifest.json +161 -1
  34. package/package.json +6 -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.10.1 darwin-arm64 node-v22.14.0
17
+ @enspirit/emb/0.12.0 darwin-x64 node-v22.18.0
18
18
  $ emb --help [COMMAND]
19
19
  USAGE
20
20
  $ emb COMMAND
@@ -37,6 +37,9 @@ 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)
40
43
  * [`emb logs COMPONENT`](#emb-logs-component)
41
44
  * [`emb ps`](#emb-ps)
42
45
  * [`emb resources`](#emb-resources)
@@ -379,6 +382,74 @@ EXAMPLES
379
382
  $ emb images push --registry my.registry.io --retag newtag
380
383
  ```
381
384
 
385
+ ## `emb kubernetes logs COMPONENT`
386
+
387
+ Follow kubernetes logs.
388
+
389
+ ```
390
+ USAGE
391
+ $ emb kubernetes logs COMPONENT -n <value> [--verbose] [-f]
392
+
393
+ ARGUMENTS
394
+ COMPONENT The component you want to see the logs of
395
+
396
+ FLAGS
397
+ -f, --[no-]follow Follow log output
398
+ -n, --namespace=<value> (required) The Kubernetes namespace to target
399
+ --[no-]verbose
400
+
401
+ DESCRIPTION
402
+ Follow kubernetes logs.
403
+
404
+ ALIASES
405
+ $ emb logs
406
+
407
+ EXAMPLES
408
+ $ emb kubernetes logs
409
+ ```
410
+
411
+ ## `emb kubernetes ps`
412
+
413
+ Show running pods.
414
+
415
+ ```
416
+ USAGE
417
+ $ emb kubernetes ps -n <value> [--verbose] [--watch]
418
+
419
+ FLAGS
420
+ -n, --namespace=<value> (required) The Kubernetes namespace to target
421
+ --[no-]verbose
422
+ --[no-]watch
423
+
424
+ DESCRIPTION
425
+ Show running pods.
426
+
427
+ EXAMPLES
428
+ $ emb kubernetes ps
429
+ ```
430
+
431
+ ## `emb kubernetes restart [DEPLOYMENT]`
432
+
433
+ Restart pods of an instance.
434
+
435
+ ```
436
+ USAGE
437
+ $ emb kubernetes restart [DEPLOYMENT...] -n <value> [--verbose]
438
+
439
+ ARGUMENTS
440
+ DEPLOYMENT... The deployment(s) to restart
441
+
442
+ FLAGS
443
+ -n, --namespace=<value> (required) The Kubernetes namespace to target
444
+ --[no-]verbose
445
+
446
+ DESCRIPTION
447
+ Restart pods of an instance.
448
+
449
+ EXAMPLES
450
+ $ emb kubernetes restart
451
+ ```
452
+
382
453
  ## `emb logs COMPONENT`
383
454
 
384
455
  Get components logs.
@@ -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';
@@ -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
+ }
@@ -24,10 +24,10 @@ export default class ResourcesIndex extends FlavoredCommand {
24
24
  if (!flags.json) {
25
25
  printTable({
26
26
  ...TABLE_DEFAULTS,
27
- columns: ['name', 'type', 'reference', 'id'],
27
+ columns: ['id', 'name', 'type', 'reference'],
28
28
  data: resources,
29
29
  sort: {
30
- name: 'asc',
30
+ id: 'asc',
31
31
  },
32
32
  });
33
33
  }
@@ -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),
@@ -51,6 +51,10 @@ export class DockerComposeClient {
51
51
  const { stdout } = await execa({
52
52
  cwd: this.monorepo.rootDir,
53
53
  }) `docker compose ps -a --format json`;
54
+ if (!stdout.trim()) {
55
+ this.services = services;
56
+ return;
57
+ }
54
58
  this.services = stdout
55
59
  .split('\n')
56
60
  .map((l) => JSON.parse(l))
@@ -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,6 +1,6 @@
1
1
  import deepmerge from '@fastify/deepmerge';
2
2
  import { glob } from 'glob';
3
- import { dirname } from 'node:path';
3
+ import { basename, dirname } from 'node:path';
4
4
  import { MonorepoConfig } from '../index.js';
5
5
  import { AbstractPlugin } from './plugin.js';
6
6
  export const AutoDockerPluginDefaultOptions = {
@@ -22,9 +22,11 @@ export class AutoDockerPlugin extends AbstractPlugin {
22
22
  cwd: this.monorepo.rootDir,
23
23
  });
24
24
  const overrides = files.reduce((cmps, path) => {
25
- const name = dirname(path);
25
+ const rootDir = dirname(path);
26
+ const name = basename(rootDir);
26
27
  const component = config.components[name];
27
28
  const cfg = {
29
+ rootDir,
28
30
  resources: {
29
31
  image: {
30
32
  type: 'docker/image',
@@ -24,6 +24,7 @@ export class EmbfileLoaderPlugin extends AbstractPlugin {
24
24
  const files = await glob(this.config.glob, {
25
25
  ...this.config,
26
26
  cwd: this.monorepo.rootDir,
27
+ follow: true,
27
28
  });
28
29
  const newConfig = await files.reduce(async (pConfig, path) => {
29
30
  const config = await pConfig;
@@ -2,10 +2,12 @@ import { Writable } from 'node:stream';
2
2
  import { CreateFileOperation, IResourceBuilder, ResourceInfo } from '../index.js';
3
3
  import { OpInput, OpOutput } from '../../operations/index.js';
4
4
  import { ResourceBuildContext } from './ResourceFactory.js';
5
- export declare class FileResourceBuilder implements IResourceBuilder<OpInput<CreateFileOperation>, OpOutput<CreateFileOperation>, void> {
5
+ export declare class FileResourceBuilder implements IResourceBuilder<OpInput<CreateFileOperation>, OpOutput<CreateFileOperation>, boolean> {
6
6
  protected context: ResourceBuildContext<OpInput<CreateFileOperation>>;
7
7
  constructor(context: ResourceBuildContext<OpInput<CreateFileOperation>>);
8
8
  getReference(): Promise<string>;
9
+ getPath(): Promise<string>;
10
+ mustBuild(): Promise<true | undefined>;
9
11
  build(resource: ResourceInfo<OpInput<CreateFileOperation>>, out?: Writable): Promise<{
10
12
  input: {
11
13
  path: string;
@@ -1,3 +1,4 @@
1
+ import { statfs } from 'node:fs/promises';
1
2
  import { CreateFileOperation, } from '../index.js';
2
3
  import { ResourceFactory } from './ResourceFactory.js';
3
4
  export class FileResourceBuilder {
@@ -8,9 +9,20 @@ export class FileResourceBuilder {
8
9
  async getReference() {
9
10
  return this.context.component.relative(this.context.config.params?.path || this.context.config.name);
10
11
  }
12
+ async getPath() {
13
+ return this.context.component.join(this.context.config.params?.path || this.context.config.name);
14
+ }
15
+ async mustBuild() {
16
+ try {
17
+ await statfs(await this.getPath());
18
+ }
19
+ catch {
20
+ return true;
21
+ }
22
+ }
11
23
  async build(resource, out) {
12
24
  const input = {
13
- path: await this.context.component.join(this.context.config.params?.path || this.context.config.name),
25
+ path: await this.getPath(),
14
26
  script: resource.params?.script,
15
27
  cwd: this.context.component.join('./'),
16
28
  };
@@ -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
  };
@@ -777,6 +777,166 @@
777
777
  "push.js"
778
778
  ]
779
779
  },
780
+ "kubernetes:logs": {
781
+ "aliases": [
782
+ "logs"
783
+ ],
784
+ "args": {
785
+ "component": {
786
+ "description": "The component you want to see the logs of",
787
+ "name": "component",
788
+ "required": true
789
+ }
790
+ },
791
+ "description": "Follow kubernetes logs.",
792
+ "examples": [
793
+ "<%= config.bin %> <%= command.id %>"
794
+ ],
795
+ "flags": {
796
+ "verbose": {
797
+ "name": "verbose",
798
+ "allowNo": true,
799
+ "type": "boolean"
800
+ },
801
+ "namespace": {
802
+ "aliases": [
803
+ "ns"
804
+ ],
805
+ "char": "n",
806
+ "description": "The Kubernetes namespace to target",
807
+ "env": "K8S_NAMESPACE",
808
+ "name": "namespace",
809
+ "required": true,
810
+ "hasDynamicHelp": false,
811
+ "multiple": false,
812
+ "type": "option"
813
+ },
814
+ "follow": {
815
+ "char": "f",
816
+ "description": "Follow log output",
817
+ "name": "follow",
818
+ "allowNo": true,
819
+ "type": "boolean"
820
+ }
821
+ },
822
+ "hasDynamicHelp": false,
823
+ "hiddenAliases": [],
824
+ "id": "kubernetes:logs",
825
+ "pluginAlias": "@enspirit/emb",
826
+ "pluginName": "@enspirit/emb",
827
+ "pluginType": "core",
828
+ "strict": true,
829
+ "enableJsonFlag": false,
830
+ "isESM": true,
831
+ "relativePath": [
832
+ "dist",
833
+ "src",
834
+ "cli",
835
+ "commands",
836
+ "kubernetes",
837
+ "logs.js"
838
+ ]
839
+ },
840
+ "kubernetes:ps": {
841
+ "aliases": [],
842
+ "args": {},
843
+ "description": "Show running pods.",
844
+ "examples": [
845
+ "<%= config.bin %> <%= command.id %>"
846
+ ],
847
+ "flags": {
848
+ "verbose": {
849
+ "name": "verbose",
850
+ "allowNo": true,
851
+ "type": "boolean"
852
+ },
853
+ "namespace": {
854
+ "aliases": [
855
+ "ns"
856
+ ],
857
+ "char": "n",
858
+ "description": "The Kubernetes namespace to target",
859
+ "env": "K8S_NAMESPACE",
860
+ "name": "namespace",
861
+ "required": true,
862
+ "hasDynamicHelp": false,
863
+ "multiple": false,
864
+ "type": "option"
865
+ },
866
+ "watch": {
867
+ "name": "watch",
868
+ "allowNo": true,
869
+ "type": "boolean"
870
+ }
871
+ },
872
+ "hasDynamicHelp": false,
873
+ "hiddenAliases": [],
874
+ "id": "kubernetes:ps",
875
+ "pluginAlias": "@enspirit/emb",
876
+ "pluginName": "@enspirit/emb",
877
+ "pluginType": "core",
878
+ "strict": false,
879
+ "enableJsonFlag": false,
880
+ "isESM": true,
881
+ "relativePath": [
882
+ "dist",
883
+ "src",
884
+ "cli",
885
+ "commands",
886
+ "kubernetes",
887
+ "ps.js"
888
+ ]
889
+ },
890
+ "kubernetes:restart": {
891
+ "aliases": [],
892
+ "args": {
893
+ "deployment": {
894
+ "description": "The deployment(s) to restart",
895
+ "name": "deployment"
896
+ }
897
+ },
898
+ "description": "Restart pods of an instance.",
899
+ "examples": [
900
+ "<%= config.bin %> <%= command.id %>"
901
+ ],
902
+ "flags": {
903
+ "verbose": {
904
+ "name": "verbose",
905
+ "allowNo": true,
906
+ "type": "boolean"
907
+ },
908
+ "namespace": {
909
+ "aliases": [
910
+ "ns"
911
+ ],
912
+ "char": "n",
913
+ "description": "The Kubernetes namespace to target",
914
+ "env": "K8S_NAMESPACE",
915
+ "name": "namespace",
916
+ "required": true,
917
+ "hasDynamicHelp": false,
918
+ "multiple": false,
919
+ "type": "option"
920
+ }
921
+ },
922
+ "hasDynamicHelp": false,
923
+ "hiddenAliases": [],
924
+ "id": "kubernetes:restart",
925
+ "pluginAlias": "@enspirit/emb",
926
+ "pluginName": "@enspirit/emb",
927
+ "pluginType": "core",
928
+ "strict": false,
929
+ "enableJsonFlag": false,
930
+ "isESM": true,
931
+ "relativePath": [
932
+ "dist",
933
+ "src",
934
+ "cli",
935
+ "commands",
936
+ "kubernetes",
937
+ "restart.js"
938
+ ]
939
+ },
780
940
  "resources:build": {
781
941
  "aliases": [],
782
942
  "args": {
@@ -998,5 +1158,5 @@
998
1158
  ]
999
1159
  }
1000
1160
  },
1001
- "version": "0.10.1"
1161
+ "version": "0.12.0"
1002
1162
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@enspirit/emb",
3
3
  "type": "module",
4
- "version": "0.10.1",
4
+ "version": "0.12.0",
5
5
  "keywords": [
6
6
  "monorepo",
7
7
  "docker",
@@ -41,6 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@fastify/deepmerge": "^3.1.0",
44
+ "@kubernetes/client-node": "1.3.0",
44
45
  "@listr2/manager": "^3.0.1",
45
46
  "@oclif/core": "^4.5.2",
46
47
  "@oclif/plugin-autocomplete": "^3.2.34",
@@ -48,6 +49,7 @@
48
49
  "@oclif/plugin-update": "^4.7.3",
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
54
  "docker-compose": "^1.2.0",
53
55
  "dockerode": "^4.0.7",
@@ -140,6 +142,9 @@
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
  },