@jakubmazanec/cli 0.1.3-next.e2c3f48 → 0.2.0-unstable.fe83933

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.
@@ -1 +1,3 @@
1
+ export declare const PACKAGE_PATH: string;
2
+ export declare const CACHE_PATH: string;
1
3
  export declare const DELIMITER = "$ ";
package/dist/constants.js CHANGED
@@ -1,3 +1,8 @@
1
+ import path from 'node:path';
2
+ import url from 'node:url';
3
+ let directory = path.dirname(url.fileURLToPath(import.meta.url));
4
+ export const PACKAGE_PATH = path.resolve(directory, '..');
5
+ export const CACHE_PATH = path.resolve(PACKAGE_PATH, 'node_modules/.cache');
1
6
  export const DELIMITER = '$ ';
2
7
 
3
8
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["export const DELIMITER = '$ ';\n"],"names":["DELIMITER"],"mappings":"AAAA,OAAO,MAAMA,YAAY,KAAK"}
1
+ {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["import path from 'node:path';\nimport url from 'node:url';\n\nlet directory = path.dirname(url.fileURLToPath(import.meta.url));\n\nexport const PACKAGE_PATH = path.resolve(directory, '..');\nexport const CACHE_PATH = path.resolve(PACKAGE_PATH, 'node_modules/.cache');\n\nexport const DELIMITER = '$ ';\n"],"names":["path","url","directory","dirname","fileURLToPath","PACKAGE_PATH","resolve","CACHE_PATH","DELIMITER"],"mappings":"AAAA,OAAOA,UAAU,YAAY;AAC7B,OAAOC,SAAS,WAAW;AAE3B,IAAIC,YAAYF,KAAKG,OAAO,CAACF,IAAIG,aAAa,CAAC,YAAYH,GAAG;AAE9D,OAAO,MAAMI,eAAeL,KAAKM,OAAO,CAACJ,WAAW,MAAM;AAC1D,OAAO,MAAMK,aAAaP,KAAKM,OAAO,CAACD,cAAc,uBAAuB;AAE5E,OAAO,MAAMG,YAAY,KAAK"}
@@ -1,5 +1,4 @@
1
1
  import { parseArguments } from '@jakubmazanec/args';
2
- // import {type ProgramCommand} from './ProgramCommand.js';
3
2
  /**
4
3
  * Command encapsulates piece of a CLI's logic and is executed when {@link Program} instance parses that command from the command line arguments.
5
4
  */ export class Command {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/program/Command.ts"],"sourcesContent":["import {\n type Argv,\n parseArguments,\n type OptionsConfig,\n type ParametersConfig,\n type Arguments,\n} from '@jakubmazanec/args';\n\nimport {type CommandAction} from './CommandAction.js';\nimport {type CommandOptions} from './CommandOptions.js';\nimport {type Program} from './Program.js';\n// import {type ProgramCommand} from './ProgramCommand.js';\n\n/**\n * Command encapsulates piece of a CLI's logic and is executed when {@link Program} instance parses that command from the command line arguments.\n */\nexport class Command<\n A extends string,\n const B extends ParametersConfig | undefined = undefined,\n const C extends OptionsConfig | undefined = undefined,\n D extends boolean = false,\n E extends boolean = false,\n> {\n readonly name: A;\n readonly description?: string;\n readonly parameters: B extends ParametersConfig ? B : null;\n readonly options: C extends OptionsConfig ? C : null;\n readonly allowUnknownOptions: D;\n readonly allowUnknownParameters: E;\n private readonly action: CommandAction<A, B, C, D, E>;\n\n /**\n * @param name Command name, ie. space-separated words consisting only of letters, numbers (but not at the start of the word) or dashes.\n * @param action Function that runs when command is executed.\n * @param options Additionl command options.\n */\n constructor(\n name: A,\n action: CommandAction<A, B, C, D, E>,\n {\n description,\n parameters,\n options,\n allowUnknownOptions,\n allowUnknownParameters,\n }: CommandOptions<NonNullable<B>, NonNullable<C>, D, E>,\n ) {\n this.name = name;\n this.action = action;\n\n if (description) {\n this.description = description;\n }\n\n if (parameters) {\n this.parameters = parameters as B extends ParametersConfig ? B : null;\n } else {\n this.parameters = null as B extends ParametersConfig ? B : null;\n }\n\n if (options) {\n this.options = options as C extends OptionsConfig ? C : null;\n } else {\n this.options = null as C extends OptionsConfig ? C : null;\n }\n\n if (allowUnknownOptions) {\n this.allowUnknownOptions = allowUnknownOptions;\n } else {\n this.allowUnknownOptions = false as D;\n }\n\n if (allowUnknownParameters) {\n this.allowUnknownParameters = allowUnknownParameters;\n } else {\n this.allowUnknownParameters = false as E;\n }\n }\n\n /**\n * Parses a list of command line arguments into an arguments object. Parser is pre-configured based on this command.\n */\n parseArguments(argv: Argv): Arguments<{\n commands: readonly [A];\n parameters: B;\n options: C;\n allowUnknownOptions: D;\n allowUnknownParameters: E;\n }> {\n return parseArguments(argv, {\n commands: [this.name],\n parameters: this.parameters ?? undefined,\n options: this.options ?? undefined,\n allowUnknownOptions: this.allowUnknownOptions,\n allowUnknownParameters: this.allowUnknownParameters,\n } as {\n commands: readonly [A];\n parameters: B;\n options: C;\n allowUnknownOptions: D;\n allowUnknownParameters: E;\n });\n }\n\n /**\n * Executes command action with provided parsed arguments and {@link Program} instance.\n */\n async run(\n parsedArgs: Arguments<{\n commands: readonly [A];\n parameters: B;\n options: C;\n allowUnknownOptions: D;\n allowUnknownParameters: E;\n }>,\n program: Program,\n ): Promise<void> {\n await this.action(parsedArgs, program);\n }\n}\n"],"names":["parseArguments","Command","name","description","parameters","options","allowUnknownOptions","allowUnknownParameters","action","constructor","argv","commands","undefined","run","parsedArgs","program"],"mappings":"AAAA,SAEEA,cAAc,QAIT,qBAAqB;AAK5B,2DAA2D;AAE3D;;CAEC,GACD,OAAO,MAAMC;IAOFC,KAAQ;IACRC,YAAqB;IACrBC,WAAkD;IAClDC,QAA4C;IAC5CC,oBAAuB;IACvBC,uBAA0B;IAClBC,OAAqC;IAEtD;;;;GAIC,GACDC,YACEP,IAAO,EACPM,MAAoC,EACpC,EACEL,WAAW,EACXC,UAAU,EACVC,OAAO,EACPC,mBAAmB,EACnBC,sBAAsB,EAC+B,CACvD;QACA,IAAI,CAACL,IAAI,GAAGA;QACZ,IAAI,CAACM,MAAM,GAAGA;QAEd,IAAIL,aAAa;YACf,IAAI,CAACA,WAAW,GAAGA;QACrB;QAEA,IAAIC,YAAY;YACd,IAAI,CAACA,UAAU,GAAGA;QACpB,OAAO;YACL,IAAI,CAACA,UAAU,GAAG;QACpB;QAEA,IAAIC,SAAS;YACX,IAAI,CAACA,OAAO,GAAGA;QACjB,OAAO;YACL,IAAI,CAACA,OAAO,GAAG;QACjB;QAEA,IAAIC,qBAAqB;YACvB,IAAI,CAACA,mBAAmB,GAAGA;QAC7B,OAAO;YACL,IAAI,CAACA,mBAAmB,GAAG;QAC7B;QAEA,IAAIC,wBAAwB;YAC1B,IAAI,CAACA,sBAAsB,GAAGA;QAChC,OAAO;YACL,IAAI,CAACA,sBAAsB,GAAG;QAChC;IACF;IAEA;;GAEC,GACDP,eAAeU,IAAU,EAMtB;QACD,OAAOV,eAAeU,MAAM;YAC1BC,UAAU;gBAAC,IAAI,CAACT,IAAI;aAAC;YACrBE,YAAY,IAAI,CAACA,UAAU,IAAIQ;YAC/BP,SAAS,IAAI,CAACA,OAAO,IAAIO;YACzBN,qBAAqB,IAAI,CAACA,mBAAmB;YAC7CC,wBAAwB,IAAI,CAACA,sBAAsB;QACrD;IAOF;IAEA;;GAEC,GACD,MAAMM,IACJC,UAME,EACFC,OAAgB,EACD;QACf,MAAM,IAAI,CAACP,MAAM,CAACM,YAAYC;IAChC;AACF"}
1
+ {"version":3,"sources":["../../src/program/Command.ts"],"sourcesContent":["import {\n type Argv,\n parseArguments,\n type OptionsConfig,\n type ParametersConfig,\n type Arguments,\n} from '@jakubmazanec/args';\n\nimport {type CommandAction} from './CommandAction.js';\nimport {type CommandOptions} from './CommandOptions.js';\nimport {type Program} from './Program.js';\n\n/**\n * Command encapsulates piece of a CLI's logic and is executed when {@link Program} instance parses that command from the command line arguments.\n */\nexport class Command<\n A extends string,\n const B extends ParametersConfig | undefined = undefined,\n const C extends OptionsConfig | undefined = undefined,\n D extends boolean = false,\n E extends boolean = false,\n> {\n readonly name: A;\n readonly description?: string;\n readonly parameters: B extends ParametersConfig ? B : null;\n readonly options: C extends OptionsConfig ? C : null;\n readonly allowUnknownOptions: D;\n readonly allowUnknownParameters: E;\n private readonly action: CommandAction<A, B, C, D, E>;\n\n /**\n * @param name Command name, ie. space-separated words consisting only of letters, numbers (but not at the start of the word) or dashes.\n * @param action Function that runs when command is executed.\n * @param options Additionl command options.\n */\n constructor(\n name: A,\n action: CommandAction<A, B, C, D, E>,\n {\n description,\n parameters,\n options,\n allowUnknownOptions,\n allowUnknownParameters,\n }: CommandOptions<NonNullable<B>, NonNullable<C>, D, E>,\n ) {\n this.name = name;\n this.action = action;\n\n if (description) {\n this.description = description;\n }\n\n if (parameters) {\n this.parameters = parameters as B extends ParametersConfig ? B : null;\n } else {\n this.parameters = null as B extends ParametersConfig ? B : null;\n }\n\n if (options) {\n this.options = options as C extends OptionsConfig ? C : null;\n } else {\n this.options = null as C extends OptionsConfig ? C : null;\n }\n\n if (allowUnknownOptions) {\n this.allowUnknownOptions = allowUnknownOptions;\n } else {\n this.allowUnknownOptions = false as D;\n }\n\n if (allowUnknownParameters) {\n this.allowUnknownParameters = allowUnknownParameters;\n } else {\n this.allowUnknownParameters = false as E;\n }\n }\n\n /**\n * Parses a list of command line arguments into an arguments object. Parser is pre-configured based on this command.\n */\n parseArguments(argv: Argv): Arguments<{\n commands: readonly [A];\n parameters: B;\n options: C;\n allowUnknownOptions: D;\n allowUnknownParameters: E;\n }> {\n return parseArguments(argv, {\n commands: [this.name],\n parameters: this.parameters ?? undefined,\n options: this.options ?? undefined,\n allowUnknownOptions: this.allowUnknownOptions,\n allowUnknownParameters: this.allowUnknownParameters,\n } as {\n commands: readonly [A];\n parameters: B;\n options: C;\n allowUnknownOptions: D;\n allowUnknownParameters: E;\n });\n }\n\n /**\n * Executes command action with provided parsed arguments and {@link Program} instance.\n */\n async run(\n parsedArgs: Arguments<{\n commands: readonly [A];\n parameters: B;\n options: C;\n allowUnknownOptions: D;\n allowUnknownParameters: E;\n }>,\n program: Program,\n ): Promise<void> {\n await this.action(parsedArgs, program);\n }\n}\n"],"names":["parseArguments","Command","name","description","parameters","options","allowUnknownOptions","allowUnknownParameters","action","constructor","argv","commands","undefined","run","parsedArgs","program"],"mappings":"AAAA,SAEEA,cAAc,QAIT,qBAAqB;AAM5B;;CAEC,GACD,OAAO,MAAMC;IAOFC,KAAQ;IACRC,YAAqB;IACrBC,WAAkD;IAClDC,QAA4C;IAC5CC,oBAAuB;IACvBC,uBAA0B;IAClBC,OAAqC;IAEtD;;;;GAIC,GACDC,YACEP,IAAO,EACPM,MAAoC,EACpC,EACEL,WAAW,EACXC,UAAU,EACVC,OAAO,EACPC,mBAAmB,EACnBC,sBAAsB,EAC+B,CACvD;QACA,IAAI,CAACL,IAAI,GAAGA;QACZ,IAAI,CAACM,MAAM,GAAGA;QAEd,IAAIL,aAAa;YACf,IAAI,CAACA,WAAW,GAAGA;QACrB;QAEA,IAAIC,YAAY;YACd,IAAI,CAACA,UAAU,GAAGA;QACpB,OAAO;YACL,IAAI,CAACA,UAAU,GAAG;QACpB;QAEA,IAAIC,SAAS;YACX,IAAI,CAACA,OAAO,GAAGA;QACjB,OAAO;YACL,IAAI,CAACA,OAAO,GAAG;QACjB;QAEA,IAAIC,qBAAqB;YACvB,IAAI,CAACA,mBAAmB,GAAGA;QAC7B,OAAO;YACL,IAAI,CAACA,mBAAmB,GAAG;QAC7B;QAEA,IAAIC,wBAAwB;YAC1B,IAAI,CAACA,sBAAsB,GAAGA;QAChC,OAAO;YACL,IAAI,CAACA,sBAAsB,GAAG;QAChC;IACF;IAEA;;GAEC,GACDP,eAAeU,IAAU,EAMtB;QACD,OAAOV,eAAeU,MAAM;YAC1BC,UAAU;gBAAC,IAAI,CAACT,IAAI;aAAC;YACrBE,YAAY,IAAI,CAACA,UAAU,IAAIQ;YAC/BP,SAAS,IAAI,CAACA,OAAO,IAAIO;YACzBN,qBAAqB,IAAI,CAACA,mBAAmB;YAC7CC,wBAAwB,IAAI,CAACA,sBAAsB;QACrD;IAOF;IAEA;;GAEC,GACD,MAAMM,IACJC,UAME,EACFC,OAAgB,EACD;QACf,MAAM,IAAI,CAACP,MAAM,CAACM,YAAYC;IAChC;AACF"}
@@ -2,7 +2,7 @@ import { type ParametersConfig, type Argv, type OptionsConfig } from '@jakubmaza
2
2
  import { type ComponentType } from 'react';
3
3
  import { type FallbackProps } from 'react-error-boundary';
4
4
  import { type Command } from './Command.js';
5
- import { type ProgramOptions } from './ProgramOptions.js';
5
+ import { type ProgramCreateOptions } from './ProgramCreateOptions.js';
6
6
  import { type ProgramStreams } from './ProgramStreams.js';
7
7
  /**
8
8
  * Program manages and executes {@link Command} instances attached to it based on parsing command line arguments.
@@ -10,8 +10,12 @@ import { type ProgramStreams } from './ProgramStreams.js';
10
10
  export declare class Program {
11
11
  /** Program name. */
12
12
  readonly name?: string;
13
+ /** More readable program name. */
14
+ readonly displayName?: string;
13
15
  /** Program version. */
14
16
  readonly version?: string;
17
+ /** Program latest version. */
18
+ readonly latestVersion?: string;
15
19
  /** Program description. */
16
20
  readonly description?: string;
17
21
  /** Program executable name. */
@@ -22,7 +26,8 @@ export declare class Program {
22
26
  readonly streams: ProgramStreams;
23
27
  /** Custom error boundary fallback component */
24
28
  ErrorBoundaryFallbackComponent: ComponentType<FallbackProps>;
25
- constructor({ name, version, description, bin, ErrorBoundaryFallbackComponent }: ProgramOptions);
29
+ private constructor();
30
+ static create({ name, version, description, bin, ErrorBoundaryFallbackComponent, checkForUpdate, }: ProgramCreateOptions): Promise<Program>;
26
31
  /**
27
32
  * Adds command to the program.
28
33
  *
@@ -1,11 +1,17 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any -- needed */ import { ProgramError } from './ProgramError.js';
1
+ /* eslint-disable @typescript-eslint/no-explicit-any -- needed */ import fs from 'fs-extra';
2
+ import fetchLatestVersion from 'latest-version';
3
+ import path from 'path';
4
+ import { ProgramError } from './ProgramError.js';
5
+ import { CACHE_PATH } from '../constants.js';
2
6
  import { Wrapper } from '../ui/internals.js';
3
7
  import { Failure, Help } from '../ui.js';
4
8
  /**
5
9
  * Program manages and executes {@link Command} instances attached to it based on parsing command line arguments.
6
10
  */ export class Program {
7
11
  /** Program name. */ name;
12
+ /** More readable program name. */ displayName;
8
13
  /** Program version. */ version;
14
+ /** Program latest version. */ latestVersion;
9
15
  /** Program description. */ description;
10
16
  /** Program executable name. */ bin;
11
17
  /** Program commands */ commands = [];
@@ -16,13 +22,19 @@ import { Failure, Help } from '../ui.js';
16
22
  stdout: process.stdout
17
23
  };
18
24
  /** Custom error boundary fallback component */ ErrorBoundaryFallbackComponent;
19
- constructor({ name, version, description, bin, ErrorBoundaryFallbackComponent }){
25
+ constructor({ name, displayName, version, latestVersion, description, bin, ErrorBoundaryFallbackComponent }){
20
26
  if (name) {
21
27
  this.name = name;
22
28
  }
29
+ if (displayName) {
30
+ this.displayName = displayName;
31
+ }
23
32
  if (version) {
24
33
  this.version = version;
25
34
  }
35
+ if (latestVersion) {
36
+ this.latestVersion = latestVersion;
37
+ }
26
38
  if (description) {
27
39
  this.description = description;
28
40
  }
@@ -35,6 +47,35 @@ import { Failure, Help } from '../ui.js';
35
47
  this.ErrorBoundaryFallbackComponent = Failure;
36
48
  }
37
49
  }
50
+ static async create({ name, version, description, bin, ErrorBoundaryFallbackComponent, checkForUpdate }) {
51
+ let latestVersion;
52
+ if (checkForUpdate && name) {
53
+ let cachePath = path.join(CACHE_PATH, name, 'latest-version.json');
54
+ let cachedLatestVersion;
55
+ try {
56
+ cachedLatestVersion = await fs.readJson(cachePath);
57
+ } catch {
58
+ // no-op
59
+ }
60
+ // if there is no cache or the cached value is old, refetch and recache
61
+ if (!cachedLatestVersion || Date.now() - cachedLatestVersion.datetime > 86_400_00) {
62
+ latestVersion = await fetchLatestVersion(name);
63
+ await fs.ensureFile(cachePath);
64
+ await fs.writeJson(cachePath, {
65
+ value: latestVersion,
66
+ datetime: Date.now()
67
+ });
68
+ }
69
+ }
70
+ return new this({
71
+ name,
72
+ version,
73
+ latestVersion,
74
+ description,
75
+ bin,
76
+ ErrorBoundaryFallbackComponent
77
+ });
78
+ }
38
79
  /**
39
80
  * Adds command to the program.
40
81
  *
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/program/Program.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any -- needed */\nimport {type ParametersConfig, type Argv, type OptionsConfig} from '@jakubmazanec/args';\nimport {type ComponentType} from 'react';\nimport {type FallbackProps} from 'react-error-boundary';\n\nimport {type Command} from './Command.js';\nimport {ProgramError} from './ProgramError.js';\nimport {type ProgramOptions} from './ProgramOptions.js';\nimport {type ProgramStreams} from './ProgramStreams.js';\nimport {Wrapper} from '../ui/internals.js';\nimport {Failure, Help} from '../ui.js';\n\n/**\n * Program manages and executes {@link Command} instances attached to it based on parsing command line arguments.\n */\nexport class Program {\n /** Program name. */\n readonly name?: string;\n\n /** Program version. */\n readonly version?: string;\n\n /** Program description. */\n readonly description?: string;\n\n /** Program executable name. */\n readonly bin?: string;\n\n /** Program commands */\n readonly commands: Array<\n Command<string, ParametersConfig | undefined, OptionsConfig | undefined, boolean, boolean>\n > = [];\n\n protected isRendering = false;\n\n readonly streams: ProgramStreams = {\n stderr: process.stderr,\n stdin: process.stdin,\n stdout: process.stdout,\n };\n\n /** Custom error boundary fallback component */\n ErrorBoundaryFallbackComponent: ComponentType<FallbackProps>;\n\n constructor({name, version, description, bin, ErrorBoundaryFallbackComponent}: ProgramOptions) {\n if (name) {\n this.name = name;\n }\n\n if (version) {\n this.version = version;\n }\n\n if (description) {\n this.description = description;\n }\n\n if (bin) {\n this.bin = bin;\n }\n\n if (ErrorBoundaryFallbackComponent) {\n this.ErrorBoundaryFallbackComponent = ErrorBoundaryFallbackComponent;\n } else {\n this.ErrorBoundaryFallbackComponent = Failure;\n }\n }\n\n /**\n * Adds command to the program.\n *\n * @param command Command to add.\n * @returns Program instance.\n */\n addCommand(command: Command<any, any, any, any, any>): this {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- it doesn't matter here; `any` is used to make TypeScript happy in this method, but isn't propagated further.\n this.commands.push(command);\n\n return this;\n }\n\n /**\n * Renders a React element using Ink and outputs to the configured streams.\n *\n * @param element The React element to render.\n */\n async renderElement(element: React.ReactElement): Promise<void> {\n if (this.isRendering) {\n throw new ProgramError('NO_NESTED_RENDER');\n } else {\n this.isRendering = true;\n }\n\n let {render} = await import('ink');\n let {createElement} = await import('react');\n let {stdin, stdout, stderr} = this.streams;\n\n try {\n render(createElement(Wrapper, {program: this}, element), {\n exitOnCtrlC: true,\n patchConsole: true,\n stderr,\n stdin,\n stdout,\n });\n } catch (error: unknown) {\n if (error instanceof Error) {\n await this.renderElement(\n createElement(this.ErrorBoundaryFallbackComponent, {error, resetErrorBoundary: () => {}}),\n );\n }\n } finally {\n this.isRendering = false;\n }\n }\n\n /**\n * Runs the program.\n *\n * @param argv Unparsed command line arguments.\n */\n async run(argv: Argv): Promise<void> {\n let hasCommandRun = false;\n\n for (let command of this.commands) {\n let parsedArgs = command.parseArguments(argv);\n\n if (parsedArgs.command) {\n hasCommandRun = true;\n\n if (parsedArgs.errors.length) {\n let {createElement} = await import('react');\n\n for (let error of parsedArgs.errors) {\n process.exitCode = 1;\n\n await this.renderElement(\n createElement(this.ErrorBoundaryFallbackComponent, {\n error,\n resetErrorBoundary: () => {},\n }),\n );\n }\n } else {\n try {\n await command.run(parsedArgs, this);\n } catch (error: unknown) {\n let {createElement} = await import('react');\n\n if (error instanceof Error) {\n process.exitCode = 1;\n\n await this.renderElement(\n createElement(this.ErrorBoundaryFallbackComponent, {\n error,\n resetErrorBoundary: () => {},\n }),\n );\n }\n }\n\n break;\n }\n }\n }\n\n if (!hasCommandRun) {\n let {createElement} = await import('react');\n\n await this.renderElement(createElement(Help, {program: this}));\n }\n }\n}\n"],"names":["ProgramError","Wrapper","Failure","Help","Program","name","version","description","bin","commands","isRendering","streams","stderr","process","stdin","stdout","ErrorBoundaryFallbackComponent","constructor","addCommand","command","push","renderElement","element","render","createElement","program","exitOnCtrlC","patchConsole","error","Error","resetErrorBoundary","run","argv","hasCommandRun","parsedArgs","parseArguments","errors","length","exitCode"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/program/Program.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any -- needed */\nimport {type ParametersConfig, type Argv, type OptionsConfig} from '@jakubmazanec/args';\nimport fs from 'fs-extra';\nimport fetchLatestVersion from 'latest-version';\nimport path from 'path';\nimport {type ComponentType} from 'react';\nimport {type FallbackProps} from 'react-error-boundary';\n\nimport {type Command} from './Command.js';\nimport {type ProgramCreateOptions} from './ProgramCreateOptions.js';\nimport {ProgramError} from './ProgramError.js';\nimport {type ProgramOptions} from './ProgramOptions.js';\nimport {type ProgramStreams} from './ProgramStreams.js';\nimport {CACHE_PATH} from '../constants.js';\nimport {Wrapper} from '../ui/internals.js';\nimport {Failure, Help} from '../ui.js';\n\n/**\n * Program manages and executes {@link Command} instances attached to it based on parsing command line arguments.\n */\nexport class Program {\n /** Program name. */\n readonly name?: string;\n\n /** More readable program name. */\n readonly displayName?: string;\n\n /** Program version. */\n readonly version?: string;\n\n /** Program latest version. */\n readonly latestVersion?: string;\n\n /** Program description. */\n readonly description?: string;\n\n /** Program executable name. */\n readonly bin?: string;\n\n /** Program commands */\n readonly commands: Array<\n Command<string, ParametersConfig | undefined, OptionsConfig | undefined, boolean, boolean>\n > = [];\n\n protected isRendering = false;\n\n readonly streams: ProgramStreams = {\n stderr: process.stderr,\n stdin: process.stdin,\n stdout: process.stdout,\n };\n\n /** Custom error boundary fallback component */\n ErrorBoundaryFallbackComponent: ComponentType<FallbackProps>;\n\n private constructor({\n name,\n displayName,\n version,\n latestVersion,\n description,\n bin,\n ErrorBoundaryFallbackComponent,\n }: ProgramOptions) {\n if (name) {\n this.name = name;\n }\n\n if (displayName) {\n this.displayName = displayName;\n }\n\n if (version) {\n this.version = version;\n }\n\n if (latestVersion) {\n this.latestVersion = latestVersion;\n }\n\n if (description) {\n this.description = description;\n }\n\n if (bin) {\n this.bin = bin;\n }\n\n if (ErrorBoundaryFallbackComponent) {\n this.ErrorBoundaryFallbackComponent = ErrorBoundaryFallbackComponent;\n } else {\n this.ErrorBoundaryFallbackComponent = Failure;\n }\n }\n\n static async create({\n name,\n version,\n description,\n bin,\n ErrorBoundaryFallbackComponent,\n checkForUpdate,\n }: ProgramCreateOptions) {\n let latestVersion: string | undefined;\n\n if (checkForUpdate && name) {\n // first we try to read the cached latest version\n type CachedLatestVersion = {\n value: string;\n datetime: number;\n };\n\n let cachePath = path.join(CACHE_PATH, name, 'latest-version.json');\n let cachedLatestVersion: CachedLatestVersion | undefined;\n\n try {\n cachedLatestVersion = (await fs.readJson(cachePath)) as CachedLatestVersion;\n } catch {\n // no-op\n }\n\n // if there is no cache or the cached value is old, refetch and recache\n if (!cachedLatestVersion || Date.now() - cachedLatestVersion.datetime > 86_400_00) {\n latestVersion = await fetchLatestVersion(name);\n\n await fs.ensureFile(cachePath);\n await fs.writeJson(cachePath, {\n value: latestVersion,\n datetime: Date.now(),\n });\n }\n }\n\n return new this({\n name,\n version,\n latestVersion,\n description,\n bin,\n ErrorBoundaryFallbackComponent,\n });\n }\n\n /**\n * Adds command to the program.\n *\n * @param command Command to add.\n * @returns Program instance.\n */\n addCommand(command: Command<any, any, any, any, any>): this {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- it doesn't matter here; `any` is used to make TypeScript happy in this method, but isn't propagated further.\n this.commands.push(command);\n\n return this;\n }\n\n /**\n * Renders a React element using Ink and outputs to the configured streams.\n *\n * @param element The React element to render.\n */\n async renderElement(element: React.ReactElement): Promise<void> {\n if (this.isRendering) {\n throw new ProgramError('NO_NESTED_RENDER');\n } else {\n this.isRendering = true;\n }\n\n let {render} = await import('ink');\n let {createElement} = await import('react');\n let {stdin, stdout, stderr} = this.streams;\n\n try {\n render(createElement(Wrapper, {program: this}, element), {\n exitOnCtrlC: true,\n patchConsole: true,\n stderr,\n stdin,\n stdout,\n });\n } catch (error: unknown) {\n if (error instanceof Error) {\n await this.renderElement(\n createElement(this.ErrorBoundaryFallbackComponent, {error, resetErrorBoundary: () => {}}),\n );\n }\n } finally {\n this.isRendering = false;\n }\n }\n\n /**\n * Runs the program.\n *\n * @param argv Unparsed command line arguments.\n */\n async run(argv: Argv): Promise<void> {\n let hasCommandRun = false;\n\n for (let command of this.commands) {\n let parsedArgs = command.parseArguments(argv);\n\n if (parsedArgs.command) {\n hasCommandRun = true;\n\n if (parsedArgs.errors.length) {\n let {createElement} = await import('react');\n\n for (let error of parsedArgs.errors) {\n process.exitCode = 1;\n\n await this.renderElement(\n createElement(this.ErrorBoundaryFallbackComponent, {\n error,\n resetErrorBoundary: () => {},\n }),\n );\n }\n } else {\n try {\n await command.run(parsedArgs, this);\n } catch (error: unknown) {\n let {createElement} = await import('react');\n\n if (error instanceof Error) {\n process.exitCode = 1;\n\n await this.renderElement(\n createElement(this.ErrorBoundaryFallbackComponent, {\n error,\n resetErrorBoundary: () => {},\n }),\n );\n }\n }\n\n break;\n }\n }\n }\n\n if (!hasCommandRun) {\n let {createElement} = await import('react');\n\n await this.renderElement(createElement(Help, {program: this}));\n }\n }\n}\n"],"names":["fs","fetchLatestVersion","path","ProgramError","CACHE_PATH","Wrapper","Failure","Help","Program","name","displayName","version","latestVersion","description","bin","commands","isRendering","streams","stderr","process","stdin","stdout","ErrorBoundaryFallbackComponent","create","checkForUpdate","cachePath","join","cachedLatestVersion","readJson","Date","now","datetime","ensureFile","writeJson","value","addCommand","command","push","renderElement","element","render","createElement","program","exitOnCtrlC","patchConsole","error","Error","resetErrorBoundary","run","argv","hasCommandRun","parsedArgs","parseArguments","errors","length","exitCode"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type ComponentType } from 'react';
2
+ import { type FallbackProps } from 'react-error-boundary';
3
+ /**
4
+ * {@link Program.create} options parameter.
5
+ */
6
+ export type ProgramCreateOptions = {
7
+ /** Program name. */
8
+ name?: string | undefined;
9
+ /** More readable program name. */
10
+ displayName?: string | undefined;
11
+ /** Program version. */
12
+ version?: string | undefined;
13
+ /** Program description. */
14
+ description?: string | undefined;
15
+ /** Program executable name. */
16
+ bin?: string | undefined;
17
+ /** Custom error boundary fallback component */
18
+ ErrorBoundaryFallbackComponent?: ComponentType<FallbackProps>;
19
+ /** Perform check whether a newer version of the program is available. */
20
+ checkForUpdate?: boolean;
21
+ };
@@ -0,0 +1,3 @@
1
+ export { };
2
+
3
+ //# sourceMappingURL=ProgramCreateOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/program/ProgramCreateOptions.ts"],"sourcesContent":["import {type ComponentType} from 'react';\nimport {type FallbackProps} from 'react-error-boundary';\n\n/**\n * {@link Program.create} options parameter.\n */\nexport type ProgramCreateOptions = {\n /** Program name. */\n name?: string | undefined;\n\n /** More readable program name. */\n displayName?: string | undefined;\n\n /** Program version. */\n version?: string | undefined;\n\n /** Program description. */\n description?: string | undefined;\n\n /** Program executable name. */\n bin?: string | undefined;\n\n /** Custom error boundary fallback component */\n ErrorBoundaryFallbackComponent?: ComponentType<FallbackProps>;\n\n /** Perform check whether a newer version of the program is available. */\n checkForUpdate?: boolean;\n};\n"],"names":[],"mappings":"AAAA,WA2BE"}
@@ -1,17 +1,23 @@
1
1
  import { type ComponentType } from 'react';
2
2
  import { type FallbackProps } from 'react-error-boundary';
3
3
  /**
4
- * {@link Project.constructor | Program constructor} options parameter.
4
+ * {@link Program.constructor | Program constructor} options parameter.
5
5
  */
6
6
  export type ProgramOptions = {
7
7
  /** Program name. */
8
8
  name?: string | undefined;
9
+ /** More readable program name. */
10
+ displayName?: string | undefined;
9
11
  /** Program version. */
10
12
  version?: string | undefined;
13
+ /** Program latest version. */
14
+ latestVersion?: string | undefined;
11
15
  /** Program description. */
12
16
  description?: string | undefined;
13
17
  /** Program executable name. */
14
18
  bin?: string | undefined;
15
19
  /** Custom error boundary fallback component */
16
- ErrorBoundaryFallbackComponent?: ComponentType<FallbackProps>;
20
+ ErrorBoundaryFallbackComponent?: ComponentType<FallbackProps> | undefined;
21
+ /** Perform check whether a newer version of the program is available. */
22
+ checkForUpdate?: boolean;
17
23
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/program/ProgramOptions.ts"],"sourcesContent":["import {type ComponentType} from 'react';\nimport {type FallbackProps} from 'react-error-boundary';\n\n/**\n * {@link Project.constructor | Program constructor} options parameter.\n */\nexport type ProgramOptions = {\n /** Program name. */\n name?: string | undefined;\n\n /** Program version. */\n version?: string | undefined;\n\n /** Program description. */\n description?: string | undefined;\n\n /** Program executable name. */\n bin?: string | undefined;\n\n /** Custom error boundary fallback component */\n ErrorBoundaryFallbackComponent?: ComponentType<FallbackProps>;\n};\n"],"names":[],"mappings":"AAAA,WAqBE"}
1
+ {"version":3,"sources":["../../src/program/ProgramOptions.ts"],"sourcesContent":["import {type ComponentType} from 'react';\nimport {type FallbackProps} from 'react-error-boundary';\n\n/**\n * {@link Program.constructor | Program constructor} options parameter.\n */\nexport type ProgramOptions = {\n /** Program name. */\n name?: string | undefined;\n\n /** More readable program name. */\n displayName?: string | undefined;\n\n /** Program version. */\n version?: string | undefined;\n\n /** Program latest version. */\n latestVersion?: string | undefined;\n\n /** Program description. */\n description?: string | undefined;\n\n /** Program executable name. */\n bin?: string | undefined;\n\n /** Custom error boundary fallback component */\n ErrorBoundaryFallbackComponent?: ComponentType<FallbackProps> | undefined;\n\n /** Perform check whether a newer version of the program is available. */\n checkForUpdate?: boolean;\n};\n"],"names":[],"mappings":"AAAA,WA8BE"}
package/dist/program.d.ts CHANGED
@@ -4,6 +4,7 @@ export * from './program/CommandArguments.js';
4
4
  export * from './program/CommandOptions.js';
5
5
  export * from './program/getProgramDetail.js';
6
6
  export * from './program/Program.js';
7
+ export * from './program/ProgramCreateOptions.js';
7
8
  export * from './program/ProgramDetail.js';
8
9
  export * from './program/ProgramError.js';
9
10
  export * from './program/ProgramOptions.js';
package/dist/program.js CHANGED
@@ -4,6 +4,7 @@ export * from './program/CommandArguments.js';
4
4
  export * from './program/CommandOptions.js';
5
5
  export * from './program/getProgramDetail.js';
6
6
  export * from './program/Program.js';
7
+ export * from './program/ProgramCreateOptions.js';
7
8
  export * from './program/ProgramDetail.js';
8
9
  export * from './program/ProgramError.js';
9
10
  export * from './program/ProgramOptions.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/program.ts"],"sourcesContent":["export * from './program/Command.js';\nexport * from './program/CommandAction.js';\nexport * from './program/CommandArguments.js';\nexport * from './program/CommandOptions.js';\nexport * from './program/getProgramDetail.js';\nexport * from './program/Program.js';\nexport * from './program/ProgramDetail.js';\nexport * from './program/ProgramError.js';\nexport * from './program/ProgramOptions.js';\nexport * from './program/ProgramStreams.js';\n"],"names":[],"mappings":"AAAA,cAAc,uBAAuB;AACrC,cAAc,6BAA6B;AAC3C,cAAc,gCAAgC;AAC9C,cAAc,8BAA8B;AAC5C,cAAc,gCAAgC;AAC9C,cAAc,uBAAuB;AACrC,cAAc,6BAA6B;AAC3C,cAAc,4BAA4B;AAC1C,cAAc,8BAA8B;AAC5C,cAAc,8BAA8B"}
1
+ {"version":3,"sources":["../src/program.ts"],"sourcesContent":["export * from './program/Command.js';\nexport * from './program/CommandAction.js';\nexport * from './program/CommandArguments.js';\nexport * from './program/CommandOptions.js';\nexport * from './program/getProgramDetail.js';\nexport * from './program/Program.js';\nexport * from './program/ProgramCreateOptions.js';\nexport * from './program/ProgramDetail.js';\nexport * from './program/ProgramError.js';\nexport * from './program/ProgramOptions.js';\nexport * from './program/ProgramStreams.js';\n"],"names":[],"mappings":"AAAA,cAAc,uBAAuB;AACrC,cAAc,6BAA6B;AAC3C,cAAc,gCAAgC;AAC9C,cAAc,8BAA8B;AAC5C,cAAc,gCAAgC;AAC9C,cAAc,uBAAuB;AACrC,cAAc,oCAAoC;AAClD,cAAc,6BAA6B;AAC3C,cAAc,4BAA4B;AAC1C,cAAc,8BAA8B;AAC5C,cAAc,8BAA8B"}
package/dist/ui/Help.js CHANGED
@@ -12,7 +12,7 @@ import * as icons from './icons.js';
12
12
  flexDirection: "column",
13
13
  children: [
14
14
  /*#__PURE__*/ _jsx(Text, {
15
- children: `${chalk[colors.notice](program.name ?? '?')} ${chalk[colors.muted](program.version ?? '?')}`
15
+ children: `${chalk[colors.notice](program.displayName ?? program.name ?? '?')} ${chalk[colors.muted](program.version ?? '?')}`
16
16
  }),
17
17
  /*#__PURE__*/ _jsx(Text, {
18
18
  color: colors.body,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ui/Help.tsx"],"sourcesContent":["import chalk from 'chalk';\nimport {Box, Text, type BoxProps} from 'ink';\n\nimport * as colors from './colors.js';\nimport {Header} from './Header.js';\nimport * as icons from './icons.js';\nimport {type Program} from '../program.js';\n\n/**\n * {@link Help} component props.\n */\nexport type HelpProps = BoxProps & {\n /** {@link Program} instance. */\n program: Program;\n};\n\n/**\n * A component that renders help information.\n */\nexport function Help({program, ...boxProps}: HelpProps) {\n return (\n <Box {...boxProps} flexDirection=\"column\">\n <Text>{`${chalk[colors.notice](program.name ?? '?')} ${chalk[colors.muted](\n program.version ?? '?',\n )}`}</Text>\n <Text color={colors.body}>{program.description}</Text>\n\n {program.commands.length ? (\n <>\n <Header icon={icons.info} marginTop={1}>\n <Text color={colors.highlighted}>Usage</Text>\n </Header>\n {program.commands.map((command) => {\n let line = `${chalk[colors.muted](program.bin ?? '?')} ${command.name}`;\n\n if (command.parameters) {\n if (command.parameters.some((parameterConfig) => !!parameterConfig.required)) {\n line += ` ${chalk[colors.muted]('<parameters>')}`;\n } else {\n line += ` ${chalk[colors.muted]('[<parameters>]')}`;\n }\n }\n\n if (command.options) {\n if (\n Object.entries(command.options).some(([, optionConfig]) => !!optionConfig.required)\n ) {\n line += ` ${chalk[colors.muted]('<options>')}`;\n } else {\n line += ` ${chalk[colors.muted]('[<options>]')}`;\n }\n }\n\n if (command.description) {\n line += ` ${command.description}`;\n }\n\n return (\n <Box key={command.name} marginLeft={2} flexDirection=\"column\">\n <Text>{line}</Text>\n </Box>\n );\n })}\n </>\n ) : null}\n </Box>\n );\n}\n"],"names":["chalk","Box","Text","colors","Header","icons","Help","program","boxProps","flexDirection","notice","name","muted","version","color","body","description","commands","length","icon","info","marginTop","highlighted","map","command","line","bin","parameters","some","parameterConfig","required","options","Object","entries","optionConfig","marginLeft"],"mappings":";AAAA,OAAOA,WAAW,QAAQ;AAC1B,SAAQC,GAAG,EAAEC,IAAI,QAAsB,MAAM;AAE7C,YAAYC,YAAY,cAAc;AACtC,SAAQC,MAAM,QAAO,cAAc;AACnC,YAAYC,WAAW,aAAa;AAWpC;;CAEC,GACD,OAAO,SAASC,KAAK,EAACC,OAAO,EAAE,GAAGC,UAAoB;IACpD,qBACE,MAACP;QAAK,GAAGO,QAAQ;QAAEC,eAAc;;0BAC/B,KAACP;0BAAM,CAAC,EAAEF,KAAK,CAACG,OAAOO,MAAM,CAAC,CAACH,QAAQI,IAAI,IAAI,KAAK,CAAC,EAAEX,KAAK,CAACG,OAAOS,KAAK,CAAC,CACxEL,QAAQM,OAAO,IAAI,KACnB,CAAC;;0BACH,KAACX;gBAAKY,OAAOX,OAAOY,IAAI;0BAAGR,QAAQS,WAAW;;YAE7CT,QAAQU,QAAQ,CAACC,MAAM,iBACtB;;kCACE,KAACd;wBAAOe,MAAMd,MAAMe,IAAI;wBAAEC,WAAW;kCACnC,cAAA,KAACnB;4BAAKY,OAAOX,OAAOmB,WAAW;sCAAE;;;oBAElCf,QAAQU,QAAQ,CAACM,GAAG,CAAC,CAACC;wBACrB,IAAIC,OAAO,CAAC,EAAEzB,KAAK,CAACG,OAAOS,KAAK,CAAC,CAACL,QAAQmB,GAAG,IAAI,KAAK,CAAC,EAAEF,QAAQb,IAAI,CAAC,CAAC;wBAEvE,IAAIa,QAAQG,UAAU,EAAE;4BACtB,IAAIH,QAAQG,UAAU,CAACC,IAAI,CAAC,CAACC,kBAAoB,CAAC,CAACA,gBAAgBC,QAAQ,GAAG;gCAC5EL,QAAQ,CAAC,CAAC,EAAEzB,KAAK,CAACG,OAAOS,KAAK,CAAC,CAAC,gBAAgB,CAAC;4BACnD,OAAO;gCACLa,QAAQ,CAAC,CAAC,EAAEzB,KAAK,CAACG,OAAOS,KAAK,CAAC,CAAC,kBAAkB,CAAC;4BACrD;wBACF;wBAEA,IAAIY,QAAQO,OAAO,EAAE;4BACnB,IACEC,OAAOC,OAAO,CAACT,QAAQO,OAAO,EAAEH,IAAI,CAAC,CAAC,GAAGM,aAAa,GAAK,CAAC,CAACA,aAAaJ,QAAQ,GAClF;gCACAL,QAAQ,CAAC,CAAC,EAAEzB,KAAK,CAACG,OAAOS,KAAK,CAAC,CAAC,aAAa,CAAC;4BAChD,OAAO;gCACLa,QAAQ,CAAC,CAAC,EAAEzB,KAAK,CAACG,OAAOS,KAAK,CAAC,CAAC,eAAe,CAAC;4BAClD;wBACF;wBAEA,IAAIY,QAAQR,WAAW,EAAE;4BACvBS,QAAQ,CAAC,CAAC,EAAED,QAAQR,WAAW,CAAC,CAAC;wBACnC;wBAEA,qBACE,KAACf;4BAAuBkC,YAAY;4BAAG1B,eAAc;sCACnD,cAAA,KAACP;0CAAMuB;;2BADCD,QAAQb,IAAI;oBAI1B;;iBAEA;;;AAGV"}
1
+ {"version":3,"sources":["../../src/ui/Help.tsx"],"sourcesContent":["import chalk from 'chalk';\nimport {Box, Text, type BoxProps} from 'ink';\n\nimport * as colors from './colors.js';\nimport {Header} from './Header.js';\nimport * as icons from './icons.js';\nimport {type Program} from '../program.js';\n\n/**\n * {@link Help} component props.\n */\nexport type HelpProps = BoxProps & {\n /** {@link Program} instance. */\n program: Program;\n};\n\n/**\n * A component that renders help information.\n */\nexport function Help({program, ...boxProps}: HelpProps) {\n return (\n <Box {...boxProps} flexDirection=\"column\">\n <Text>{`${chalk[colors.notice](program.displayName ?? program.name ?? '?')} ${chalk[\n colors.muted\n ](program.version ?? '?')}`}</Text>\n <Text color={colors.body}>{program.description}</Text>\n\n {program.commands.length ? (\n <>\n <Header icon={icons.info} marginTop={1}>\n <Text color={colors.highlighted}>Usage</Text>\n </Header>\n {program.commands.map((command) => {\n let line = `${chalk[colors.muted](program.bin ?? '?')} ${command.name}`;\n\n if (command.parameters) {\n if (command.parameters.some((parameterConfig) => !!parameterConfig.required)) {\n line += ` ${chalk[colors.muted]('<parameters>')}`;\n } else {\n line += ` ${chalk[colors.muted]('[<parameters>]')}`;\n }\n }\n\n if (command.options) {\n if (\n Object.entries(command.options).some(([, optionConfig]) => !!optionConfig.required)\n ) {\n line += ` ${chalk[colors.muted]('<options>')}`;\n } else {\n line += ` ${chalk[colors.muted]('[<options>]')}`;\n }\n }\n\n if (command.description) {\n line += ` ${command.description}`;\n }\n\n return (\n <Box key={command.name} marginLeft={2} flexDirection=\"column\">\n <Text>{line}</Text>\n </Box>\n );\n })}\n </>\n ) : null}\n </Box>\n );\n}\n"],"names":["chalk","Box","Text","colors","Header","icons","Help","program","boxProps","flexDirection","notice","displayName","name","muted","version","color","body","description","commands","length","icon","info","marginTop","highlighted","map","command","line","bin","parameters","some","parameterConfig","required","options","Object","entries","optionConfig","marginLeft"],"mappings":";AAAA,OAAOA,WAAW,QAAQ;AAC1B,SAAQC,GAAG,EAAEC,IAAI,QAAsB,MAAM;AAE7C,YAAYC,YAAY,cAAc;AACtC,SAAQC,MAAM,QAAO,cAAc;AACnC,YAAYC,WAAW,aAAa;AAWpC;;CAEC,GACD,OAAO,SAASC,KAAK,EAACC,OAAO,EAAE,GAAGC,UAAoB;IACpD,qBACE,MAACP;QAAK,GAAGO,QAAQ;QAAEC,eAAc;;0BAC/B,KAACP;0BAAM,CAAC,EAAEF,KAAK,CAACG,OAAOO,MAAM,CAAC,CAACH,QAAQI,WAAW,IAAIJ,QAAQK,IAAI,IAAI,KAAK,CAAC,EAAEZ,KAAK,CACjFG,OAAOU,KAAK,CACb,CAACN,QAAQO,OAAO,IAAI,KAAK,CAAC;;0BAC3B,KAACZ;gBAAKa,OAAOZ,OAAOa,IAAI;0BAAGT,QAAQU,WAAW;;YAE7CV,QAAQW,QAAQ,CAACC,MAAM,iBACtB;;kCACE,KAACf;wBAAOgB,MAAMf,MAAMgB,IAAI;wBAAEC,WAAW;kCACnC,cAAA,KAACpB;4BAAKa,OAAOZ,OAAOoB,WAAW;sCAAE;;;oBAElChB,QAAQW,QAAQ,CAACM,GAAG,CAAC,CAACC;wBACrB,IAAIC,OAAO,CAAC,EAAE1B,KAAK,CAACG,OAAOU,KAAK,CAAC,CAACN,QAAQoB,GAAG,IAAI,KAAK,CAAC,EAAEF,QAAQb,IAAI,CAAC,CAAC;wBAEvE,IAAIa,QAAQG,UAAU,EAAE;4BACtB,IAAIH,QAAQG,UAAU,CAACC,IAAI,CAAC,CAACC,kBAAoB,CAAC,CAACA,gBAAgBC,QAAQ,GAAG;gCAC5EL,QAAQ,CAAC,CAAC,EAAE1B,KAAK,CAACG,OAAOU,KAAK,CAAC,CAAC,gBAAgB,CAAC;4BACnD,OAAO;gCACLa,QAAQ,CAAC,CAAC,EAAE1B,KAAK,CAACG,OAAOU,KAAK,CAAC,CAAC,kBAAkB,CAAC;4BACrD;wBACF;wBAEA,IAAIY,QAAQO,OAAO,EAAE;4BACnB,IACEC,OAAOC,OAAO,CAACT,QAAQO,OAAO,EAAEH,IAAI,CAAC,CAAC,GAAGM,aAAa,GAAK,CAAC,CAACA,aAAaJ,QAAQ,GAClF;gCACAL,QAAQ,CAAC,CAAC,EAAE1B,KAAK,CAACG,OAAOU,KAAK,CAAC,CAAC,aAAa,CAAC;4BAChD,OAAO;gCACLa,QAAQ,CAAC,CAAC,EAAE1B,KAAK,CAACG,OAAOU,KAAK,CAAC,CAAC,eAAe,CAAC;4BAClD;wBACF;wBAEA,IAAIY,QAAQR,WAAW,EAAE;4BACvBS,QAAQ,CAAC,CAAC,EAAED,QAAQR,WAAW,CAAC,CAAC;wBACnC;wBAEA,qBACE,KAAChB;4BAAuBmC,YAAY;4BAAG3B,eAAc;sCACnD,cAAA,KAACP;0CAAMwB;;2BADCD,QAAQb,IAAI;oBAI1B;;iBAEA;;;AAGV"}
@@ -1,8 +1,9 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Box } from 'ink';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Spacer, Text } from 'ink';
3
3
  import { useMemo } from 'react';
4
4
  import { ErrorBoundary } from 'react-error-boundary';
5
5
  import { programContext } from './programContext.js';
6
+ import { highlighted, notice } from '../colors.js';
6
7
  import { Failure } from '../Failure.js';
7
8
  /**
8
9
  * A component that wraps anything {@link Program} tries to render with an error boundary and all neccessary providers.
@@ -14,11 +15,50 @@ import { Failure } from '../Failure.js';
14
15
  ]);
15
16
  return /*#__PURE__*/ _jsx(ErrorBoundary, {
16
17
  FallbackComponent: Failure,
17
- children: /*#__PURE__*/ _jsx(programContext.Provider, {
18
+ children: /*#__PURE__*/ _jsxs(programContext.Provider, {
18
19
  value: programContextValue,
19
- children: /*#__PURE__*/ _jsx(Box, {
20
- children: children
21
- })
20
+ children: [
21
+ program.latestVersion && program.latestVersion !== program.version ? /*#__PURE__*/ _jsxs(Box, {
22
+ children: [
23
+ /*#__PURE__*/ _jsxs(Box, {
24
+ flexDirection: "column",
25
+ borderColor: notice,
26
+ borderStyle: "round",
27
+ margin: 1,
28
+ paddingTop: 1,
29
+ paddingRight: 4,
30
+ paddingBottom: 1,
31
+ paddingLeft: 4,
32
+ children: [
33
+ /*#__PURE__*/ _jsxs(Text, {
34
+ children: [
35
+ "New version ",
36
+ /*#__PURE__*/ _jsx(Text, {
37
+ color: highlighted,
38
+ children: program.latestVersion
39
+ }),
40
+ " is available!"
41
+ ]
42
+ }),
43
+ /*#__PURE__*/ _jsxs(Text, {
44
+ children: [
45
+ "Run ",
46
+ /*#__PURE__*/ _jsx(Text, {
47
+ color: highlighted,
48
+ children: `npm install ${program.name} -D`
49
+ }),
50
+ " to update."
51
+ ]
52
+ })
53
+ ]
54
+ }),
55
+ /*#__PURE__*/ _jsx(Spacer, {})
56
+ ]
57
+ }) : null,
58
+ /*#__PURE__*/ _jsx(Box, {
59
+ children: children
60
+ })
61
+ ]
22
62
  })
23
63
  });
24
64
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/internals/Wrapper.tsx"],"sourcesContent":["import {Box} from 'ink';\nimport {useMemo, type FC, type PropsWithChildren} from 'react';\nimport {ErrorBoundary} from 'react-error-boundary';\n\nimport {programContext} from './programContext.js';\nimport {type Program} from '../../program.js';\nimport {Failure} from '../Failure.js';\n\n/**\n * {@link Wrapper} component props.\n */\nexport type WrapperProps = {\n program: Program;\n};\n\n/**\n * A component that wraps anything {@link Program} tries to render with an error boundary and all neccessary providers.\n */\nexport const Wrapper: FC<PropsWithChildren<WrapperProps>> = ({program, children}) => {\n let programContextValue = useMemo(\n () => ({\n program,\n }),\n [program],\n );\n\n return (\n <ErrorBoundary FallbackComponent={Failure}>\n <programContext.Provider value={programContextValue}>\n <Box>{children}</Box>\n </programContext.Provider>\n </ErrorBoundary>\n );\n};\n"],"names":["Box","useMemo","ErrorBoundary","programContext","Failure","Wrapper","program","children","programContextValue","FallbackComponent","Provider","value"],"mappings":";AAAA,SAAQA,GAAG,QAAO,MAAM;AACxB,SAAQC,OAAO,QAAwC,QAAQ;AAC/D,SAAQC,aAAa,QAAO,uBAAuB;AAEnD,SAAQC,cAAc,QAAO,sBAAsB;AAEnD,SAAQC,OAAO,QAAO,gBAAgB;AAStC;;CAEC,GACD,OAAO,MAAMC,UAA+C,CAAC,EAACC,OAAO,EAAEC,QAAQ,EAAC;IAC9E,IAAIC,sBAAsBP,QACxB,IAAO,CAAA;YACLK;QACF,CAAA,GACA;QAACA;KAAQ;IAGX,qBACE,KAACJ;QAAcO,mBAAmBL;kBAChC,cAAA,KAACD,eAAeO,QAAQ;YAACC,OAAOH;sBAC9B,cAAA,KAACR;0BAAKO;;;;AAId,EAAE"}
1
+ {"version":3,"sources":["../../../src/ui/internals/Wrapper.tsx"],"sourcesContent":["import {Box, Spacer, Text} from 'ink';\nimport {useMemo, type FC, type PropsWithChildren} from 'react';\nimport {ErrorBoundary} from 'react-error-boundary';\n\nimport {programContext} from './programContext.js';\nimport {type Program} from '../../program.js';\nimport {highlighted, notice} from '../colors.js';\nimport {Failure} from '../Failure.js';\n\n/**\n * {@link Wrapper} component props.\n */\nexport type WrapperProps = {\n program: Program;\n};\n\n/**\n * A component that wraps anything {@link Program} tries to render with an error boundary and all neccessary providers.\n */\nexport const Wrapper: FC<PropsWithChildren<WrapperProps>> = ({program, children}) => {\n let programContextValue = useMemo(\n () => ({\n program,\n }),\n [program],\n );\n\n return (\n <ErrorBoundary FallbackComponent={Failure}>\n <programContext.Provider value={programContextValue}>\n {program.latestVersion && program.latestVersion !== program.version ? (\n <Box>\n <Box\n flexDirection=\"column\"\n borderColor={notice}\n borderStyle=\"round\"\n margin={1}\n paddingTop={1}\n paddingRight={4}\n paddingBottom={1}\n paddingLeft={4}\n >\n <Text>\n New version <Text color={highlighted}>{program.latestVersion}</Text> is available!\n </Text>\n <Text>\n Run <Text color={highlighted}>{`npm install ${program.name} -D`}</Text> to update.\n </Text>\n </Box>\n <Spacer />\n </Box>\n ) : null}\n <Box>{children}</Box>\n </programContext.Provider>\n </ErrorBoundary>\n );\n};\n"],"names":["Box","Spacer","Text","useMemo","ErrorBoundary","programContext","highlighted","notice","Failure","Wrapper","program","children","programContextValue","FallbackComponent","Provider","value","latestVersion","version","flexDirection","borderColor","borderStyle","margin","paddingTop","paddingRight","paddingBottom","paddingLeft","color","name"],"mappings":";AAAA,SAAQA,GAAG,EAAEC,MAAM,EAAEC,IAAI,QAAO,MAAM;AACtC,SAAQC,OAAO,QAAwC,QAAQ;AAC/D,SAAQC,aAAa,QAAO,uBAAuB;AAEnD,SAAQC,cAAc,QAAO,sBAAsB;AAEnD,SAAQC,WAAW,EAAEC,MAAM,QAAO,eAAe;AACjD,SAAQC,OAAO,QAAO,gBAAgB;AAStC;;CAEC,GACD,OAAO,MAAMC,UAA+C,CAAC,EAACC,OAAO,EAAEC,QAAQ,EAAC;IAC9E,IAAIC,sBAAsBT,QACxB,IAAO,CAAA;YACLO;QACF,CAAA,GACA;QAACA;KAAQ;IAGX,qBACE,KAACN;QAAcS,mBAAmBL;kBAChC,cAAA,MAACH,eAAeS,QAAQ;YAACC,OAAOH;;gBAC7BF,QAAQM,aAAa,IAAIN,QAAQM,aAAa,KAAKN,QAAQO,OAAO,iBACjE,MAACjB;;sCACC,MAACA;4BACCkB,eAAc;4BACdC,aAAaZ;4BACba,aAAY;4BACZC,QAAQ;4BACRC,YAAY;4BACZC,cAAc;4BACdC,eAAe;4BACfC,aAAa;;8CAEb,MAACvB;;wCAAK;sDACQ,KAACA;4CAAKwB,OAAOpB;sDAAcI,QAAQM,aAAa;;wCAAQ;;;8CAEtE,MAACd;;wCAAK;sDACA,KAACA;4CAAKwB,OAAOpB;sDAAc,CAAC,YAAY,EAAEI,QAAQiB,IAAI,CAAC,GAAG,CAAC;;wCAAQ;;;;;sCAG3E,KAAC1B;;qBAED;8BACJ,KAACD;8BAAKW;;;;;AAId,EAAE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jakubmazanec/cli",
3
- "version": "0.1.3-next.e2c3f48",
3
+ "version": "0.2.0-unstable.fe83933",
4
4
  "description": "TypeScript-first library for building CLIs.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,12 +32,15 @@
32
32
  "@jakubmazanec/ts-utils": "^2.1.0",
33
33
  "@jakubmazanec/zod-utils": "^1.0.0",
34
34
  "chalk": "^5.2.0",
35
+ "fs-extra": "^10.0.1",
36
+ "latest-version": "^7.0.0",
35
37
  "lodash": "^4.17.21",
36
38
  "react-error-boundary": "^4.0.7",
37
39
  "zod": "^3.14.4"
38
40
  },
39
41
  "devDependencies": {
40
42
  "@jest/globals": "^29.5.0",
43
+ "@types/fs-extra": "^9.0.13",
41
44
  "@types/lodash": "^4.14.195",
42
45
  "@types/node": "^16.18.3",
43
46
  "@types/react": "^18.0.26",
package/src/constants.ts CHANGED
@@ -1 +1,9 @@
1
+ import path from 'node:path';
2
+ import url from 'node:url';
3
+
4
+ let directory = path.dirname(url.fileURLToPath(import.meta.url));
5
+
6
+ export const PACKAGE_PATH = path.resolve(directory, '..');
7
+ export const CACHE_PATH = path.resolve(PACKAGE_PATH, 'node_modules/.cache');
8
+
1
9
  export const DELIMITER = '$ ';
@@ -9,7 +9,6 @@ import {
9
9
  import {type CommandAction} from './CommandAction.js';
10
10
  import {type CommandOptions} from './CommandOptions.js';
11
11
  import {type Program} from './Program.js';
12
- // import {type ProgramCommand} from './ProgramCommand.js';
13
12
 
14
13
  /**
15
14
  * Command encapsulates piece of a CLI's logic and is executed when {@link Program} instance parses that command from the command line arguments.
@@ -1,12 +1,17 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any -- needed */
2
2
  import {type ParametersConfig, type Argv, type OptionsConfig} from '@jakubmazanec/args';
3
+ import fs from 'fs-extra';
4
+ import fetchLatestVersion from 'latest-version';
5
+ import path from 'path';
3
6
  import {type ComponentType} from 'react';
4
7
  import {type FallbackProps} from 'react-error-boundary';
5
8
 
6
9
  import {type Command} from './Command.js';
10
+ import {type ProgramCreateOptions} from './ProgramCreateOptions.js';
7
11
  import {ProgramError} from './ProgramError.js';
8
12
  import {type ProgramOptions} from './ProgramOptions.js';
9
13
  import {type ProgramStreams} from './ProgramStreams.js';
14
+ import {CACHE_PATH} from '../constants.js';
10
15
  import {Wrapper} from '../ui/internals.js';
11
16
  import {Failure, Help} from '../ui.js';
12
17
 
@@ -17,9 +22,15 @@ export class Program {
17
22
  /** Program name. */
18
23
  readonly name?: string;
19
24
 
25
+ /** More readable program name. */
26
+ readonly displayName?: string;
27
+
20
28
  /** Program version. */
21
29
  readonly version?: string;
22
30
 
31
+ /** Program latest version. */
32
+ readonly latestVersion?: string;
33
+
23
34
  /** Program description. */
24
35
  readonly description?: string;
25
36
 
@@ -42,15 +53,31 @@ export class Program {
42
53
  /** Custom error boundary fallback component */
43
54
  ErrorBoundaryFallbackComponent: ComponentType<FallbackProps>;
44
55
 
45
- constructor({name, version, description, bin, ErrorBoundaryFallbackComponent}: ProgramOptions) {
56
+ private constructor({
57
+ name,
58
+ displayName,
59
+ version,
60
+ latestVersion,
61
+ description,
62
+ bin,
63
+ ErrorBoundaryFallbackComponent,
64
+ }: ProgramOptions) {
46
65
  if (name) {
47
66
  this.name = name;
48
67
  }
49
68
 
69
+ if (displayName) {
70
+ this.displayName = displayName;
71
+ }
72
+
50
73
  if (version) {
51
74
  this.version = version;
52
75
  }
53
76
 
77
+ if (latestVersion) {
78
+ this.latestVersion = latestVersion;
79
+ }
80
+
54
81
  if (description) {
55
82
  this.description = description;
56
83
  }
@@ -66,6 +93,54 @@ export class Program {
66
93
  }
67
94
  }
68
95
 
96
+ static async create({
97
+ name,
98
+ version,
99
+ description,
100
+ bin,
101
+ ErrorBoundaryFallbackComponent,
102
+ checkForUpdate,
103
+ }: ProgramCreateOptions) {
104
+ let latestVersion: string | undefined;
105
+
106
+ if (checkForUpdate && name) {
107
+ // first we try to read the cached latest version
108
+ type CachedLatestVersion = {
109
+ value: string;
110
+ datetime: number;
111
+ };
112
+
113
+ let cachePath = path.join(CACHE_PATH, name, 'latest-version.json');
114
+ let cachedLatestVersion: CachedLatestVersion | undefined;
115
+
116
+ try {
117
+ cachedLatestVersion = (await fs.readJson(cachePath)) as CachedLatestVersion;
118
+ } catch {
119
+ // no-op
120
+ }
121
+
122
+ // if there is no cache or the cached value is old, refetch and recache
123
+ if (!cachedLatestVersion || Date.now() - cachedLatestVersion.datetime > 86_400_00) {
124
+ latestVersion = await fetchLatestVersion(name);
125
+
126
+ await fs.ensureFile(cachePath);
127
+ await fs.writeJson(cachePath, {
128
+ value: latestVersion,
129
+ datetime: Date.now(),
130
+ });
131
+ }
132
+ }
133
+
134
+ return new this({
135
+ name,
136
+ version,
137
+ latestVersion,
138
+ description,
139
+ bin,
140
+ ErrorBoundaryFallbackComponent,
141
+ });
142
+ }
143
+
69
144
  /**
70
145
  * Adds command to the program.
71
146
  *
@@ -0,0 +1,28 @@
1
+ import {type ComponentType} from 'react';
2
+ import {type FallbackProps} from 'react-error-boundary';
3
+
4
+ /**
5
+ * {@link Program.create} options parameter.
6
+ */
7
+ export type ProgramCreateOptions = {
8
+ /** Program name. */
9
+ name?: string | undefined;
10
+
11
+ /** More readable program name. */
12
+ displayName?: string | undefined;
13
+
14
+ /** Program version. */
15
+ version?: string | undefined;
16
+
17
+ /** Program description. */
18
+ description?: string | undefined;
19
+
20
+ /** Program executable name. */
21
+ bin?: string | undefined;
22
+
23
+ /** Custom error boundary fallback component */
24
+ ErrorBoundaryFallbackComponent?: ComponentType<FallbackProps>;
25
+
26
+ /** Perform check whether a newer version of the program is available. */
27
+ checkForUpdate?: boolean;
28
+ };
@@ -2,15 +2,21 @@ import {type ComponentType} from 'react';
2
2
  import {type FallbackProps} from 'react-error-boundary';
3
3
 
4
4
  /**
5
- * {@link Project.constructor | Program constructor} options parameter.
5
+ * {@link Program.constructor | Program constructor} options parameter.
6
6
  */
7
7
  export type ProgramOptions = {
8
8
  /** Program name. */
9
9
  name?: string | undefined;
10
10
 
11
+ /** More readable program name. */
12
+ displayName?: string | undefined;
13
+
11
14
  /** Program version. */
12
15
  version?: string | undefined;
13
16
 
17
+ /** Program latest version. */
18
+ latestVersion?: string | undefined;
19
+
14
20
  /** Program description. */
15
21
  description?: string | undefined;
16
22
 
@@ -18,5 +24,8 @@ export type ProgramOptions = {
18
24
  bin?: string | undefined;
19
25
 
20
26
  /** Custom error boundary fallback component */
21
- ErrorBoundaryFallbackComponent?: ComponentType<FallbackProps>;
27
+ ErrorBoundaryFallbackComponent?: ComponentType<FallbackProps> | undefined;
28
+
29
+ /** Perform check whether a newer version of the program is available. */
30
+ checkForUpdate?: boolean;
22
31
  };
package/src/program.ts CHANGED
@@ -4,6 +4,7 @@ export * from './program/CommandArguments.js';
4
4
  export * from './program/CommandOptions.js';
5
5
  export * from './program/getProgramDetail.js';
6
6
  export * from './program/Program.js';
7
+ export * from './program/ProgramCreateOptions.js';
7
8
  export * from './program/ProgramDetail.js';
8
9
  export * from './program/ProgramError.js';
9
10
  export * from './program/ProgramOptions.js';
package/src/ui/Help.tsx CHANGED
@@ -20,9 +20,9 @@ export type HelpProps = BoxProps & {
20
20
  export function Help({program, ...boxProps}: HelpProps) {
21
21
  return (
22
22
  <Box {...boxProps} flexDirection="column">
23
- <Text>{`${chalk[colors.notice](program.name ?? '?')} ${chalk[colors.muted](
24
- program.version ?? '?',
25
- )}`}</Text>
23
+ <Text>{`${chalk[colors.notice](program.displayName ?? program.name ?? '?')} ${chalk[
24
+ colors.muted
25
+ ](program.version ?? '?')}`}</Text>
26
26
  <Text color={colors.body}>{program.description}</Text>
27
27
 
28
28
  {program.commands.length ? (
@@ -1,9 +1,10 @@
1
- import {Box} from 'ink';
1
+ import {Box, Spacer, Text} from 'ink';
2
2
  import {useMemo, type FC, type PropsWithChildren} from 'react';
3
3
  import {ErrorBoundary} from 'react-error-boundary';
4
4
 
5
5
  import {programContext} from './programContext.js';
6
6
  import {type Program} from '../../program.js';
7
+ import {highlighted, notice} from '../colors.js';
7
8
  import {Failure} from '../Failure.js';
8
9
 
9
10
  /**
@@ -27,6 +28,28 @@ export const Wrapper: FC<PropsWithChildren<WrapperProps>> = ({program, children}
27
28
  return (
28
29
  <ErrorBoundary FallbackComponent={Failure}>
29
30
  <programContext.Provider value={programContextValue}>
31
+ {program.latestVersion && program.latestVersion !== program.version ? (
32
+ <Box>
33
+ <Box
34
+ flexDirection="column"
35
+ borderColor={notice}
36
+ borderStyle="round"
37
+ margin={1}
38
+ paddingTop={1}
39
+ paddingRight={4}
40
+ paddingBottom={1}
41
+ paddingLeft={4}
42
+ >
43
+ <Text>
44
+ New version <Text color={highlighted}>{program.latestVersion}</Text> is available!
45
+ </Text>
46
+ <Text>
47
+ Run <Text color={highlighted}>{`npm install ${program.name} -D`}</Text> to update.
48
+ </Text>
49
+ </Box>
50
+ <Spacer />
51
+ </Box>
52
+ ) : null}
30
53
  <Box>{children}</Box>
31
54
  </programContext.Provider>
32
55
  </ErrorBoundary>