@flowscripter/dynamic-cli-framework 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bun.lock CHANGED
@@ -13,7 +13,7 @@
13
13
  "ts-log": "^2.2.7",
14
14
  },
15
15
  "devDependencies": {
16
- "@types/bun": "^1.2.8",
16
+ "@types/bun": "^1.2.9",
17
17
  },
18
18
  "peerDependencies": {
19
19
  "typescript": "^5.8.3",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  },
23
23
  "packages": {
24
- "@types/bun": ["@types/bun@1.2.8", "", { "dependencies": { "bun-types": "1.2.7" } }, "sha512-t8L1RvJVUghW5V+M/fL3Thbxcs0HwNsXsnTEBEfEVqGteiJToOlZ/fyOEaR1kZsNqnu+3XA4RI/qmnX4w6+S+w=="],
24
+ "@types/bun": ["@types/bun@1.2.9", "", { "dependencies": { "bun-types": "1.2.9" } }, "sha512-epShhLGQYc4Bv/aceHbmBhOz1XgUnuTZgcxjxk+WXwNyDXavv5QHD1QEFV0FwbTSQtNq6g4ZcV6y0vZakTjswg=="],
25
25
 
26
26
  "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
27
27
 
@@ -31,7 +31,7 @@
31
31
 
32
32
  "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
33
33
 
34
- "bun-types": ["bun-types@1.2.7", "", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }, "sha512-P4hHhk7kjF99acXqKvltyuMQ2kf/rzIw3ylEDpCxDS9Xa0X0Yp/gJu/vDCucmWpiur5qJ0lwB2bWzOXa2GlHqA=="],
34
+ "bun-types": ["bun-types@1.2.9", "", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }, "sha512-dk/kOEfQbajENN/D6FyiSgOKEuUi9PWfqKQJEgwKrCMWbjS/S6tEXp178mWvWAcUSYm9ArDlWHZKO3T/4cLXiw=="],
35
35
 
36
36
  "chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
37
37
 
package/package.json CHANGED
@@ -18,12 +18,12 @@
18
18
  ],
19
19
  "module": "index.ts",
20
20
  "type": "module",
21
- "version": "1.1.3",
21
+ "version": "1.1.4",
22
22
  "publishConfig": {
23
23
  "access": "public"
24
24
  },
25
25
  "devDependencies": {
26
- "@types/bun": "^1.2.8"
26
+ "@types/bun": "^1.2.9"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "typescript": "^5.8.3"
@@ -129,11 +129,20 @@ export default interface PrinterService {
129
129
  cyan(message: string): string;
130
130
 
131
131
  /**
132
- * Return the provided message so that it the foreground is green.
132
+ * Return the provided message so that the foreground is green.
133
133
  * Has no effect if {@link colorEnabled} is `false`.
134
134
  */
135
135
  green(message: string): string;
136
136
 
137
+ /**
138
+ * Return the provided message so that the foreground is the specified color.
139
+ * Has no effect if {@link colorEnabled} is `false`.
140
+ *
141
+ * @param message the message to color.
142
+ * @param hexFormattedColor the color to use. This should be a valid hex formatted string e.g. "0xrrggbb".
143
+ */
144
+ color(message: string, hexFormattedColor: string): string;
145
+
137
146
  /**
138
147
  * Print a message on `stdout`.
139
148
  * Will be displayed as primary content if {@link colorEnabled} is `true`.
@@ -3,6 +3,17 @@ import type { LanguageFn as HighlightSyntax } from "highlight.js";
3
3
  export const SYNTAX_HIGHLIGHTER_SERVICE_ID =
4
4
  "@flowscripter/dynamic-cli-framework/syntax-highlighter-service";
5
5
 
6
+ /**
7
+ * Color scheme for syntax highlighting.
8
+ *
9
+ * The keys are the names of the defined [highlight.js scopes](https://highlightjs.readthedocs.io/en/latest/css-classes-reference.html).
10
+ *
11
+ * The values are colors expressed as a hex formatted string e.g. "0xrrggbb".
12
+ *
13
+ * The scheme does not need to list all scopes exhaustively.
14
+ */
15
+ export type ColorScheme = Record<string, string>;
16
+
6
17
  /**
7
18
  * Service providing syntax based color highlighting of text for the CLI using [highlight.js](https://github.com/highlightjs/highlight.js).
8
19
  */
@@ -33,10 +44,15 @@ export default interface SyntaxHighlighterService {
33
44
 
34
45
  /**
35
46
  * Return a syntactically highlighted version of the provided text using the specified syntax.
36
- * The returned text will include appropriate terminal color codes.
47
+ * The returned text will include appropriate color styling. An optional {@link ColorScheme} can be provided.
37
48
  *
38
49
  * @param text the text to highlight.
39
50
  * @param syntaxName the syntax to use.
51
+ * @param colorScheme the color scheme to use.
40
52
  */
41
- highlight(text: string, syntaxName: string): string;
53
+ highlight(
54
+ text: string,
55
+ syntaxName: string,
56
+ colorScheme?: ColorScheme,
57
+ ): string;
42
58
  }
@@ -34,6 +34,16 @@ export default class DefaultAsciiBannerGeneratorService
34
34
  throw new Error(`Font name is not registered: ${name}`);
35
35
  }
36
36
 
37
- return "\n" + await figlet.text(message, { font: name }) + "\n";
37
+ const bannerText = await figlet.text(message, { font: name });
38
+ const lines = bannerText.split("\n");
39
+
40
+ // Remove any whitespace lines from the end
41
+ while (
42
+ (lines.length > 0) && (lines[lines.length - 1].trim().length === 0)
43
+ ) {
44
+ lines.pop();
45
+ }
46
+
47
+ return "\n" + lines.join("\n") + "\n";
38
48
  }
39
49
  }
@@ -35,10 +35,12 @@ export default class BannerServiceProvider implements ServiceProvider {
35
35
  *
36
36
  * @param servicePriority the priority of the service.
37
37
  * @param configurationServiceProvider an optional {@link ConfigurationServiceProvider} to make use of.
38
+ * @param fontName an optional [FIGlet](http://www.figlet.org) font name to use for the banner, defaults to "standard".
38
39
  */
39
40
  public constructor(
40
41
  servicePriority: number,
41
42
  configurationServiceProvider?: ConfigurationServiceProvider,
43
+ private readonly fontName: string = "standard",
42
44
  ) {
43
45
  this.servicePriority = servicePriority;
44
46
  this.#configurationServiceProvider = configurationServiceProvider;
@@ -68,7 +70,7 @@ export default class BannerServiceProvider implements ServiceProvider {
68
70
  const { cliConfig } = context;
69
71
  const bannerText = await asciiBannerGeneratorService.generate(
70
72
  cliConfig.name.toUpperCase(),
71
- "standard",
73
+ this.fontName,
72
74
  );
73
75
 
74
76
  await printerService.info(printerService.blue(bannerText));
@@ -161,6 +161,23 @@ export default class DefaultPrinterService implements PrinterService {
161
161
  return this.#styler.colorText(message, this.#theme[Color.GREEN]);
162
162
  }
163
163
 
164
+ public color(message: string, hexFormattedColor: string): string {
165
+ if (
166
+ (hexFormattedColor.length !== 8) ||
167
+ (!hexFormattedColor.toLowerCase().startsWith("0x"))
168
+ ) {
169
+ throw new Error(`Invalid color: ${hexFormattedColor}`);
170
+ }
171
+
172
+ const colorValue = parseInt(hexFormattedColor, 16);
173
+
174
+ if (isNaN(colorValue) || (colorValue < 0 || colorValue > 0xffffff)) {
175
+ throw new Error(`Invalid color: ${hexFormattedColor}`);
176
+ }
177
+
178
+ return this.#styler.colorText(message, colorValue);
179
+ }
180
+
164
181
  public async debug(message: string, icon?: Icon): Promise<void> {
165
182
  await this.#log(Level.DEBUG, this.secondary(message), icon);
166
183
  }
@@ -2,6 +2,9 @@ import type SyntaxHighlighterService from "../../api/service/core/SyntaxHighligh
2
2
  import type { LanguageFn as HighlightSyntax } from "highlight.js";
3
3
  import { createEmphasize } from "emphasize";
4
4
  import json from "highlight.js/lib/languages/json";
5
+ import { ColorScheme } from "../../api/service/core/SyntaxHighlighterService.ts";
6
+
7
+ type Sheet = Record<string, (string) => string> | undefined;
5
8
 
6
9
  interface Emphasize {
7
10
  listLanguages(): ReadonlyArray<string>;
@@ -9,7 +12,7 @@ interface Emphasize {
9
12
  highlight(
10
13
  name: string,
11
14
  text: string,
12
- sheet: Record<string, (string) => string> | undefined,
15
+ sheet: Sheet,
13
16
  ): { value: string };
14
17
  register(name: string, syntax: HighlightSyntax): void;
15
18
  }
@@ -21,7 +24,8 @@ interface Emphasize {
21
24
  export default class DefaultSyntaxHighlighterService
22
25
  implements SyntaxHighlighterService {
23
26
  colorEnabled = true;
24
- sheet: Record<string, (text: string) => string> | undefined;
27
+ colorFunction: (text: string, hexFormattedColor: string) => string;
28
+
25
29
  emphasize: Emphasize;
26
30
 
27
31
  constructor() {
@@ -33,7 +37,11 @@ export default class DefaultSyntaxHighlighterService
33
37
  return this.emphasize.listLanguages();
34
38
  }
35
39
 
36
- highlight(text: string, syntaxName: string): string {
40
+ highlight(
41
+ text: string,
42
+ syntaxName: string,
43
+ colorScheme?: ColorScheme,
44
+ ): string {
37
45
  const name = syntaxName.toLowerCase();
38
46
  if (!this.emphasize.registered(name)) {
39
47
  throw new Error(`Syntax name is not registered: ${name}`);
@@ -44,7 +52,19 @@ export default class DefaultSyntaxHighlighterService
44
52
  return text;
45
53
  }
46
54
 
47
- return this.emphasize.highlight(name, text, this.sheet).value;
55
+ let sheet: Sheet;
56
+
57
+ // if a color scheme is provided, create a sheet of color functions
58
+ if (colorScheme && this.colorFunction) {
59
+ sheet = {};
60
+ for (const [key, value] of Object.entries(colorScheme)) {
61
+ if (value) {
62
+ sheet[key] = (text: string) => this.colorFunction(text, value);
63
+ }
64
+ }
65
+ }
66
+
67
+ return this.emphasize.highlight(name, text, sheet).value;
48
68
  }
49
69
 
50
70
  registerSyntax(syntaxName: string, syntaxDefinition: HighlightSyntax): void {
@@ -45,20 +45,8 @@ export default class SyntaxHighlighterServiceProvider
45
45
 
46
46
  this.#defaultSyntaxHighlighterService.colorEnabled =
47
47
  printerService.colorEnabled;
48
-
49
- if (this.#defaultSyntaxHighlighterService.sheet === undefined) {
50
- this.#defaultSyntaxHighlighterService.sheet = {
51
- comment: printerService.secondary,
52
- keyword: printerService.primary,
53
- built_in: printerService.primary,
54
- type: printerService.emphasised,
55
- operator: printerService.blue,
56
- punctuation: printerService.green,
57
- literal: printerService.selected,
58
- string: printerService.selected,
59
- number: printerService.selected,
60
- };
61
- }
48
+ this.#defaultSyntaxHighlighterService.colorFunction = printerService.color
49
+ .bind(printerService);
62
50
 
63
51
  return Promise.resolve(undefined);
64
52
  }
@@ -1,26 +1,5 @@
1
1
  // Bun Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`DefaultAsciiBannerGeneratorService Tests Can generate with standard font 1`] = `
4
- "
5
- __
6
- / _| ___ ___
7
- | |_ / _ \\ / _ \\
8
- | _| (_) | (_) |
9
- |_| \\___/ \\___/
10
-
11
- "
12
- `;
13
-
14
- exports[`DefaultAsciiBannerGeneratorService Tests Can generate with newly registered font 1`] = `
15
- "
16
- __
17
- / _|___ ___
18
- | _/ _ \\/ _ \\
19
- |_| \\___/\\___/
20
-
21
- "
22
- `;
23
-
24
3
  exports[`DefaultAsciiBannerGeneratorService tests Can generate with standard font 1`] = `
25
4
  "
26
5
  __
@@ -28,7 +7,6 @@ exports[`DefaultAsciiBannerGeneratorService tests Can generate with standard fon
28
7
  | |_ / _ \\ / _ \\
29
8
  | _| (_) | (_) |
30
9
  |_| \\___/ \\___/
31
-
32
10
  "
33
11
  `;
34
12
 
@@ -38,6 +16,5 @@ exports[`DefaultAsciiBannerGeneratorService tests Can generate with newly regist
38
16
  / _|___ ___
39
17
  | _/ _ \\/ _ \\
40
18
  |_| \\___/\\___/
41
-
42
19
  "
43
20
  `;
@@ -9,6 +9,12 @@ import DefaultAsciiBannerGeneratorService from "../../../src/service/asciiBanner
9
9
  import TtyTerminal from "../../../src/service/printer/terminal/TtyTerminal.ts";
10
10
  import StreamString from "../../fixtures/StreamString.ts";
11
11
  import TtyStyler from "../../../src/service/printer/terminal/TtyStyler.ts";
12
+ import { getConfigurationServiceProvider } from "../../fixtures/ConfigurationServiceProvider.ts";
13
+
14
+ // FIGlet font is converted to a JSON string and embedded in a simple JSON file: `{ "font": "<figlet font definition>" }`
15
+ import smallFont from "../asciiBannerGenerator/small.flf.json" with {
16
+ type: "json",
17
+ };
12
18
 
13
19
  describe("BannerServiceProvider tests", () => {
14
20
  test("BannerServiceProvider provide works", async () => {
@@ -44,6 +50,47 @@ describe("BannerServiceProvider tests", () => {
44
50
 
45
51
  expect(bannerServiceProvider.printBanner).toBeTrue();
46
52
 
53
+ expect(dummyStderr.getString()).toMatchSnapshot();
54
+ });
55
+
56
+ test("BannerServiceProvider with configuration service and custom font name works", async () => {
57
+ const dummyStdout = new StreamString();
58
+ const dummyStderr = new StreamString();
59
+ const printer = new DefaultPrinterService(
60
+ dummyStdout.writableStream,
61
+ dummyStderr.writableStream,
62
+ true,
63
+ true,
64
+ new TtyTerminal(dummyStderr.writeStream),
65
+ new TtyStyler(3),
66
+ );
67
+ printer.colorEnabled = false;
68
+
69
+ const asciiBannerGenerator = new DefaultAsciiBannerGeneratorService();
70
+ const context = new DefaultContext(getCLIConfig("mpeg-sdl-tool"));
71
+
72
+ context.addServiceInstance(PRINTER_SERVICE_ID, printer);
73
+ context.addServiceInstance(
74
+ ASCII_BANNER_GENERATOR_SERVICE_ID,
75
+ asciiBannerGenerator,
76
+ );
77
+
78
+ asciiBannerGenerator.registerFont("small", smallFont.font);
79
+
80
+ const configurationServiceProvider = getConfigurationServiceProvider(
81
+ 100,
82
+ new Map(),
83
+ );
84
+ configurationServiceProvider.configLocation = "config.yaml";
85
+
86
+ const bannerServiceProvider = new BannerServiceProvider(
87
+ 100,
88
+ configurationServiceProvider,
89
+ "small",
90
+ );
91
+ await bannerServiceProvider.initService(context);
92
+ await printer.error("some text\n");
93
+ expect(bannerServiceProvider.printBanner).toBeTrue();
47
94
  expect(dummyStdout.getString()).toMatchSnapshot();
48
95
  });
49
96
  });
@@ -0,0 +1,16 @@
1
+ // Bun Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`BannerServiceProvider tests BannerServiceProvider initService works 1`] = `
4
+ "
5
+ _____ ___ ___
6
+ | ___/ _ \\ / _ \\
7
+ | |_ | | | | | | |
8
+ | _|| |_| | |_| |
9
+ |_| \\___/ \\___/
10
+ bar
11
+ version: foobar
12
+
13
+ "
14
+ `;
15
+
16
+ exports[`BannerServiceProvider tests BannerServiceProvider with configuration service and custom font name works 1`] = `""`;
@@ -1,4 +1,4 @@
1
- import { describe, test } from "bun:test";
1
+ import { describe, expect, test } from "bun:test";
2
2
  import {
3
3
  expectBytesEquals,
4
4
  expectStringEquals,
@@ -110,6 +110,94 @@ describe("DefaultPrinterService tests", () => {
110
110
  );
111
111
  });
112
112
 
113
+ test("Custom color works", async () => {
114
+ const dummyStdout = new StreamString();
115
+ const dummyStderr = new StreamString();
116
+ const printerService = new DefaultPrinterService(
117
+ dummyStdout.writableStream,
118
+ dummyStderr.writableStream,
119
+ true,
120
+ true,
121
+ new TtyTerminal(dummyStderr.writeStream),
122
+ new TtyStyler(3),
123
+ );
124
+ printerService.colorEnabled = true;
125
+ await printerService.info(printerService.color("hello", "0x000000"));
126
+ expectBytesEquals(
127
+ dummyStderr.getString(),
128
+ new Uint8Array([
129
+ 27,
130
+ 91,
131
+ 51,
132
+ 56,
133
+ 59,
134
+ 50,
135
+ 59,
136
+ 49,
137
+ 48,
138
+ 49,
139
+ 59,
140
+ 49,
141
+ 50,
142
+ 51,
143
+ 59,
144
+ 49,
145
+ 51,
146
+ 49,
147
+ 109,
148
+ 27,
149
+ 91,
150
+ 51,
151
+ 56,
152
+ 59,
153
+ 50,
154
+ 59,
155
+ 48,
156
+ 59,
157
+ 48,
158
+ 59,
159
+ 48,
160
+ 109,
161
+ 104,
162
+ 101,
163
+ 108,
164
+ 108,
165
+ 111,
166
+ 27,
167
+ 91,
168
+ 51,
169
+ 57,
170
+ 109,
171
+ 27,
172
+ 91,
173
+ 51,
174
+ 57,
175
+ 109,
176
+ ]),
177
+ );
178
+ });
179
+
180
+ test("Custom color validation works", () => {
181
+ const dummyStdout = new StreamString();
182
+ const dummyStderr = new StreamString();
183
+ const printerService = new DefaultPrinterService(
184
+ dummyStdout.writableStream,
185
+ dummyStderr.writableStream,
186
+ true,
187
+ true,
188
+ new TtyTerminal(dummyStderr.writeStream),
189
+ new TtyStyler(3),
190
+ );
191
+ printerService.colorEnabled = true;
192
+
193
+ expect(() => printerService.color("hello", "foo")).toThrow(
194
+ "Invalid color: foo",
195
+ );
196
+ expect(() => printerService.color("hello", "0x0")).toThrow(
197
+ "Invalid color: 0x0",
198
+ );
199
+ });
200
+
113
201
  test("stdout writable accessible", async () => {
114
202
  const dummyStdout = new StreamString();
115
203
  const dummyStderr = new StreamString();
@@ -2,6 +2,10 @@ import { describe, expect, test } from "bun:test";
2
2
  import { expectBytesEquals } from "../../fixtures/util.ts";
3
3
  import DefaultSyntaxHighlighterService from "../../../src/service/syntaxHighlighter/DefaultSyntaxHighlighterService.ts";
4
4
  import yaml from "highlight.js/lib/languages/yaml";
5
+ import DefaultPrinterService from "../../../src/service/printer/DefaultPrinterService.ts";
6
+ import TtyStyler from "../../../src/service/printer/terminal/TtyStyler.ts";
7
+ import TtyTerminal from "../../../src/service/printer/terminal/TtyTerminal.ts";
8
+ import StreamString from "../../fixtures/StreamString.ts";
5
9
 
6
10
  describe("DefaultSyntaxHighlighterService tests", () => {
7
11
  test("JSON registered by default", () => {
@@ -37,8 +41,6 @@ describe("DefaultSyntaxHighlighterService tests", () => {
37
41
  test("Can highlight with JSON syntax", () => {
38
42
  const syntaxHighlighterService = new DefaultSyntaxHighlighterService();
39
43
 
40
- syntaxHighlighterService.registerSyntax("yaml", yaml);
41
-
42
44
  const highlighted = syntaxHighlighterService.highlight(
43
45
  "{ foo: 1 }",
44
46
  "json",
@@ -70,6 +72,67 @@ describe("DefaultSyntaxHighlighterService tests", () => {
70
72
  );
71
73
  });
72
74
 
75
+ test("Can highlight with JSON syntax and custom color scheme", () => {
76
+ const dummyStdout = new StreamString();
77
+ const dummyStderr = new StreamString();
78
+ const printer = new DefaultPrinterService(
79
+ dummyStdout.writableStream,
80
+ dummyStderr.writableStream,
81
+ true,
82
+ true,
83
+ new TtyTerminal(dummyStderr.writeStream),
84
+ new TtyStyler(3),
85
+ );
86
+ printer.colorEnabled = true;
87
+
88
+ const syntaxHighlighterService = new DefaultSyntaxHighlighterService();
89
+
90
+ syntaxHighlighterService.colorFunction = printer.color.bind(printer);
91
+
92
+ const highlighted = syntaxHighlighterService.highlight(
93
+ "{ foo: 1 }",
94
+ "json",
95
+ {
96
+ "number": "0xFF0000",
97
+ },
98
+ );
99
+ expectBytesEquals(
100
+ highlighted,
101
+ new Uint8Array([
102
+ 123,
103
+ 32,
104
+ 102,
105
+ 111,
106
+ 111,
107
+ 58,
108
+ 32,
109
+ 27,
110
+ 91,
111
+ 51,
112
+ 56,
113
+ 59,
114
+ 50,
115
+ 59,
116
+ 50,
117
+ 53,
118
+ 53,
119
+ 59,
120
+ 48,
121
+ 59,
122
+ 48,
123
+ 109,
124
+ 49,
125
+ 27,
126
+ 91,
127
+ 51,
128
+ 57,
129
+ 109,
130
+ 32,
131
+ 125,
132
+ ]),
133
+ );
134
+ });
135
+
73
136
  test("Can highlight with newly registered syntax", () => {
74
137
  const syntaxHighlighterService = new DefaultSyntaxHighlighterService();
75
138
 
@@ -1,5 +0,0 @@
1
- // Bun Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`BannerServiceProvider Tests BannerServiceProvider initService works 1`] = `""`;
4
-
5
- exports[`BannerServiceProvider tests BannerServiceProvider initService works 1`] = `""`;