@forgecart/cli 2.202609301225.0 → 2.202610020334.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 (43) hide show
  1. package/dist/src/cli.d.ts +10 -3
  2. package/dist/src/cli.js +34 -7
  3. package/dist/src/cli.js.map +1 -1
  4. package/dist/src/commands/init.d.ts +7 -3
  5. package/dist/src/commands/init.js +4 -2
  6. package/dist/src/commands/init.js.map +1 -1
  7. package/dist/src/exit-ladder.d.ts +25 -0
  8. package/dist/src/exit-ladder.js +32 -0
  9. package/dist/src/exit-ladder.js.map +1 -0
  10. package/dist/src/question/given-answers.d.ts +42 -0
  11. package/dist/src/question/given-answers.js +154 -0
  12. package/dist/src/question/given-answers.js.map +1 -0
  13. package/dist/src/question/prompt-arm.d.ts +21 -0
  14. package/dist/src/question/prompt-arm.js +263 -0
  15. package/dist/src/question/prompt-arm.js.map +1 -0
  16. package/dist/src/question/question-prompt.adapter.d.ts +36 -0
  17. package/dist/src/question/question-prompt.adapter.js +81 -0
  18. package/dist/src/question/question-prompt.adapter.js.map +1 -0
  19. package/dist/src/question/question-unanswered-refusal.d.ts +15 -0
  20. package/dist/src/question/question-unanswered-refusal.js +35 -0
  21. package/dist/src/question/question-unanswered-refusal.js.map +1 -0
  22. package/dist/src/question/terminal-question-session.d.ts +36 -0
  23. package/dist/src/question/terminal-question-session.js +126 -0
  24. package/dist/src/question/terminal-question-session.js.map +1 -0
  25. package/dist/src/question/terminal-screen.d.ts +26 -0
  26. package/dist/src/question/terminal-screen.js +68 -0
  27. package/dist/src/question/terminal-screen.js.map +1 -0
  28. package/dist/src/question/type/question-prompt.input.d.ts +20 -0
  29. package/dist/src/question/type/question-prompt.input.js +2 -0
  30. package/dist/src/question/type/question-prompt.input.js.map +1 -0
  31. package/dist/src/question/type/question-prompt.types.d.ts +104 -0
  32. package/dist/src/question/type/question-prompt.types.js +2 -0
  33. package/dist/src/question/type/question-prompt.types.js.map +1 -0
  34. package/dist/src/question/util/answer-flag.util.d.ts +12 -0
  35. package/dist/src/question/util/answer-flag.util.js +22 -0
  36. package/dist/src/question/util/answer-flag.util.js.map +1 -0
  37. package/dist/src/question/util/question-frame.util.d.ts +22 -0
  38. package/dist/src/question/util/question-frame.util.js +49 -0
  39. package/dist/src/question/util/question-frame.util.js.map +1 -0
  40. package/dist/src/question/util/terminal-text.util.d.ts +7 -0
  41. package/dist/src/question/util/terminal-text.util.js +15 -0
  42. package/dist/src/question/util/terminal-text.util.js.map +1 -0
  43. package/package.json +1 -1
@@ -0,0 +1,68 @@
1
+ import { clearScreenDown, cursorTo, moveCursor } from 'node:readline';
2
+ import { displayWidth } from './util/terminal-text.util.js';
3
+ const HIDE_CURSOR = '\u001B[?25l';
4
+ const SHOW_CURSOR = '\u001B[?25h';
5
+ /** The width drawn for a terminal that reports none. */
6
+ const FALLBACK_COLUMNS = 80;
7
+ /**
8
+ * The block of terminal rows a prompt owns. Each frame erases the one before it
9
+ * and is drawn in its place, so a prompt redraws without scrolling; rows are
10
+ * counted as the terminal wraps them, so a long line still erases whole.
11
+ */
12
+ export class TerminalScreen {
13
+ display;
14
+ /** Rows the drawn block covers. */
15
+ blockRows = 0;
16
+ /** The row of the block the cursor rests on — `blockRows` when it rests below it. */
17
+ cursorRow = 0;
18
+ constructor(display) {
19
+ this.display = display;
20
+ }
21
+ /** The terminal's width in cells. */
22
+ get columns() {
23
+ const { columns } = this.display;
24
+ if (columns === undefined || columns <= 0)
25
+ return FALLBACK_COLUMNS;
26
+ return columns;
27
+ }
28
+ /** Replace the drawn block with `frame`. */
29
+ draw(frame) {
30
+ const rows = frame.lines.map((line) => this.rowsOf(line));
31
+ moveCursor(this.display, 0, -this.cursorRow);
32
+ cursorTo(this.display, 0);
33
+ clearScreenDown(this.display);
34
+ this.display.write(frame.lines.map((line) => `${line}\n`).join(''));
35
+ this.blockRows = rows.reduce((total, count) => total + count, 0);
36
+ this.rest(frame.cursor, rows);
37
+ }
38
+ /** Leave the drawn block on screen; whatever is written next starts on the row below it. */
39
+ release() {
40
+ moveCursor(this.display, 0, this.blockRows - this.cursorRow);
41
+ cursorTo(this.display, 0);
42
+ this.blockRows = 0;
43
+ this.cursorRow = 0;
44
+ }
45
+ /** Hand the terminal its cursor back. */
46
+ restoreCursor() {
47
+ this.display.write(SHOW_CURSOR);
48
+ }
49
+ /** Put the cursor where the frame asks, or hide it below the block. */
50
+ rest(cursor, rows) {
51
+ if (cursor === undefined) {
52
+ this.display.write(HIDE_CURSOR);
53
+ this.cursorRow = this.blockRows;
54
+ return;
55
+ }
56
+ const above = rows.slice(0, cursor.line).reduce((total, count) => total + count, 0);
57
+ const row = above + Math.floor(cursor.column / this.columns);
58
+ moveCursor(this.display, 0, row - this.blockRows);
59
+ cursorTo(this.display, cursor.column % this.columns);
60
+ this.display.write(SHOW_CURSOR);
61
+ this.cursorRow = row;
62
+ }
63
+ /** The rows one line takes once the terminal wraps it — an empty line still takes one. */
64
+ rowsOf(line) {
65
+ return Math.max(1, Math.ceil(displayWidth(line) / this.columns));
66
+ }
67
+ }
68
+ //# sourceMappingURL=terminal-screen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-screen.js","sourceRoot":"","sources":["../../../src/question/terminal-screen.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The three flags a questioning `forgecart` command carries (#2771), as
3
+ * commander parses them. Between them they answer every question a run cannot
4
+ * ask on a terminal.
5
+ */
6
+ export interface QuestionPromptInput {
7
+ /**
8
+ * `--answer <question>=<answer>`, once per question: the question's id or its
9
+ * stable key, then an option id, comma-separated option ids for a
10
+ * multi-select, or the text verbatim. The last one given for a question wins.
11
+ */
12
+ answer?: string[];
13
+ /**
14
+ * `--answers-file <path>`: a JSON object of question id or stable key to an
15
+ * option id, a multi-select's option ids, or the text.
16
+ */
17
+ answersFile?: string;
18
+ /** `--yes`: every question that declares a default takes it, `defaulted: true`. */
19
+ yes?: boolean;
20
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=question-prompt.input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"question-prompt.input.js","sourceRoot":"","sources":["../../../../src/question/type/question-prompt.input.ts"],"names":[],"mappings":""}
@@ -0,0 +1,104 @@
1
+ import type { Readable, Writable } from 'node:stream';
2
+ import type { PromptArm } from '../prompt-arm.js';
3
+ /** One option of a {@link CliQuestion} — launch#61's `AskUserOption`. */
4
+ export interface CliQuestionOption {
5
+ id: string;
6
+ label: string;
7
+ /** The consequence of choosing it, printed under its label. */
8
+ hint?: string;
9
+ }
10
+ /**
11
+ * What a question takes — launch#61's `AskUserInput['kind']`: one of its
12
+ * options, free text, or `both` — the options plus "Other…" for free text.
13
+ */
14
+ export type CliQuestionKind = 'choice' | 'text' | 'both';
15
+ /**
16
+ * A question a `forgecart` command asks (#2771): launch#61's `AskUser` input —
17
+ * so that epic's `AgentQuestion` maps onto the same adapter — plus the two
18
+ * names an answer reaches it by.
19
+ */
20
+ export interface CliQuestion {
21
+ /**
22
+ * This asking's id: printed in every prompt and named by every refusal. A
23
+ * fresh run mints a fresh one.
24
+ */
25
+ id: string;
26
+ /** The question's stable name (`ui-library`): it answers the question on every run. */
27
+ key: string;
28
+ question: string;
29
+ kind: CliQuestionKind;
30
+ /** The choices — empty for a `text` question. */
31
+ options: readonly CliQuestionOption[];
32
+ allowMultiple: boolean;
33
+ /** An example answer, printed once above the free-text buffer. */
34
+ placeholder?: string;
35
+ defaultOptionId?: string;
36
+ }
37
+ /**
38
+ * An answer: the payload launch#61's answer mutation writes, plus the question
39
+ * it answers.
40
+ */
41
+ export interface QuestionPromptAnswer {
42
+ questionId: string;
43
+ optionIds?: string[];
44
+ text?: string;
45
+ /** Set by Esc and by `--yes`, never by a choice — so a default reads as one. */
46
+ defaulted?: boolean;
47
+ }
48
+ /**
49
+ * The `--answers-file` document: a question's id or stable key to an option id
50
+ * or the text, or to a multi-select's option ids. The id answers exactly one
51
+ * asking; the key answers the same question on every run.
52
+ */
53
+ export type QuestionAnswersFile = Record<string, string | string[]>;
54
+ /** One `--answer` value, split at its first `=`. */
55
+ export interface AnswerFlag {
56
+ /** The question's id or stable key. */
57
+ question: string;
58
+ /** An option id, comma-separated option ids, or the text. */
59
+ answer: string;
60
+ }
61
+ /** The keyboard a prompt reads — stdin, or a stream standing in for a terminal's. */
62
+ export type TerminalKeyboard = Readable & {
63
+ isTTY?: boolean;
64
+ setRawMode?: (mode: boolean) => unknown;
65
+ };
66
+ /** The screen a prompt draws on — stdout, or a stream standing in for a terminal's. */
67
+ export type TerminalDisplay = Writable & {
68
+ isTTY?: boolean;
69
+ columns?: number;
70
+ };
71
+ /** The terminal questions are asked on: `process.stdin` and `process.stdout` unless given. */
72
+ export interface QuestionPromptIo {
73
+ input: TerminalKeyboard;
74
+ output: TerminalDisplay;
75
+ }
76
+ /** Asks a set of questions and resolves their answers, in question order. */
77
+ export interface QuestionPrompt {
78
+ ask(questions: readonly CliQuestion[]): Promise<QuestionPromptAnswer[]>;
79
+ }
80
+ /** Where the terminal cursor rests in a frame: a line of it, and a cell of that line. */
81
+ export interface FrameCursor {
82
+ line: number;
83
+ column: number;
84
+ }
85
+ /** One frame of a prompt: its lines, and the cursor — hidden when the frame has none. */
86
+ export interface PromptFrame {
87
+ lines: readonly string[];
88
+ cursor?: FrameCursor;
89
+ }
90
+ /** Where one keypress takes a prompt arm. */
91
+ export type ArmStep = {
92
+ kind: 'ignore';
93
+ } | {
94
+ kind: 'redraw';
95
+ } | {
96
+ kind: 'refuse';
97
+ reason: string;
98
+ } | {
99
+ kind: 'open';
100
+ arm: PromptArm;
101
+ } | {
102
+ kind: 'answer';
103
+ answer: QuestionPromptAnswer;
104
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=question-prompt.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"question-prompt.types.js","sourceRoot":"","sources":["../../../../src/question/type/question-prompt.types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import type { AnswerFlag } from '../type/question-prompt.types.js';
2
+ /**
3
+ * Split one `--answer` value at its first `=`: the question's id or stable key
4
+ * before it, the answer after it — which may be empty (a multi-select's empty
5
+ * set) and may itself hold `=`.
6
+ */
7
+ export declare function parseAnswerFlag(raw: string): AnswerFlag;
8
+ /**
9
+ * Commander's accumulator for the repeatable `--answer`: every value is
10
+ * checked for its shape as the command line is parsed, before any action runs.
11
+ */
12
+ export declare function collectAnswerFlag(raw: string, previous: readonly string[] | undefined): string[];
@@ -0,0 +1,22 @@
1
+ import { InvalidArgumentError } from 'commander';
2
+ /**
3
+ * Split one `--answer` value at its first `=`: the question's id or stable key
4
+ * before it, the answer after it — which may be empty (a multi-select's empty
5
+ * set) and may itself hold `=`.
6
+ */
7
+ export function parseAnswerFlag(raw) {
8
+ const separator = raw.indexOf('=');
9
+ if (separator <= 0) {
10
+ throw new InvalidArgumentError(`--answer "${raw}" is not <question>=<answer>: the question's id or key, then its option id(s) or text.`);
11
+ }
12
+ return { question: raw.slice(0, separator), answer: raw.slice(separator + 1) };
13
+ }
14
+ /**
15
+ * Commander's accumulator for the repeatable `--answer`: every value is
16
+ * checked for its shape as the command line is parsed, before any action runs.
17
+ */
18
+ export function collectAnswerFlag(raw, previous) {
19
+ parseAnswerFlag(raw);
20
+ return [...(previous ?? []), raw];
21
+ }
22
+ //# sourceMappingURL=answer-flag.util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"answer-flag.util.js","sourceRoot":"","sources":["../../../../src/question/util/answer-flag.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAIjD;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,oBAAoB,CAC5B,aAAa,GAAG,wFAAwF,CACzG,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,EAAE,CAAC;AACjF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,QAAuC;IACpF,eAAe,CAAC,GAAG,CAAC,CAAC;IACrB,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { CliQuestion, QuestionPromptAnswer } from '../type/question-prompt.types.js';
2
+ /** The gutter a free-text buffer is drawn behind (record `prompt?case=multiline`). */
3
+ export declare const GUTTER = "\u2502";
4
+ /**
5
+ * A question's opening line, `? <question> (<id>)`, or — when that does not
6
+ * fit the terminal — the id on a line of its own beneath it. Every prompt
7
+ * prints the id: it is the name `--answer` takes.
8
+ */
9
+ export declare function questionHeader(question: CliQuestion, columns: number): string[];
10
+ /** One buffer line behind the gutter; an empty line is the bare gutter. */
11
+ export declare function gutterLine(text: string): string;
12
+ /**
13
+ * The answer Esc and `--yes` give: the declared default, `defaulted: true` —
14
+ * none for a question that declares no default.
15
+ */
16
+ export declare function defaultAnswer(question: CliQuestion): QuestionPromptAnswer | undefined;
17
+ /**
18
+ * The lines an answered prompt collapses to: its header, the options chosen and
19
+ * the text typed — so the screen keeps the answer that was sent, not the last
20
+ * position of a cursor.
21
+ */
22
+ export declare function answeredLines(question: CliQuestion, answer: QuestionPromptAnswer, columns: number): string[];
@@ -0,0 +1,49 @@
1
+ import { displayWidth } from './terminal-text.util.js';
2
+ /** The gutter a free-text buffer is drawn behind (record `prompt?case=multiline`). */
3
+ export const GUTTER = '│';
4
+ /**
5
+ * A question's opening line, `? <question> (<id>)`, or — when that does not
6
+ * fit the terminal — the id on a line of its own beneath it. Every prompt
7
+ * prints the id: it is the name `--answer` takes.
8
+ */
9
+ export function questionHeader(question, columns) {
10
+ const line = `? ${question.question} (${question.id})`;
11
+ if (displayWidth(line) <= columns)
12
+ return [line];
13
+ return [`? ${question.question}`, ` (${question.id})`];
14
+ }
15
+ /** One buffer line behind the gutter; an empty line is the bare gutter. */
16
+ export function gutterLine(text) {
17
+ return text === '' ? GUTTER : `${GUTTER} ${text}`;
18
+ }
19
+ /**
20
+ * The answer Esc and `--yes` give: the declared default, `defaulted: true` —
21
+ * none for a question that declares no default.
22
+ */
23
+ export function defaultAnswer(question) {
24
+ const { defaultOptionId } = question;
25
+ if (defaultOptionId === undefined)
26
+ return;
27
+ return { questionId: question.id, optionIds: [defaultOptionId], defaulted: true };
28
+ }
29
+ /**
30
+ * The lines an answered prompt collapses to: its header, the options chosen and
31
+ * the text typed — so the screen keeps the answer that was sent, not the last
32
+ * position of a cursor.
33
+ */
34
+ export function answeredLines(question, answer, columns) {
35
+ const text = answer.text?.split('\n').map(gutterLine) ?? [];
36
+ return [...questionHeader(question, columns), ...chosenLines(question, answer), ...text];
37
+ }
38
+ function chosenLines(question, answer) {
39
+ const { optionIds } = answer;
40
+ if (optionIds === undefined)
41
+ return [];
42
+ if (optionIds.length === 0 && answer.text !== undefined)
43
+ return [];
44
+ const labels = optionIds.map((id) => question.options.find((option) => option.id === id)?.label ?? id);
45
+ const chosen = labels.length > 0 ? labels.join(', ') : '(none)';
46
+ const defaulted = answer.defaulted === true ? ' (default)' : '';
47
+ return [` ${chosen}${defaulted}`];
48
+ }
49
+ //# sourceMappingURL=question-frame.util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"question-frame.util.js","sourceRoot":"","sources":["../../../../src/question/util/question-frame.util.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,sFAAsF;AACtF,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAC;AAE1B;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,QAAqB,EAAE,OAAe;IACnE,MAAM,IAAI,GAAG,KAAK,QAAQ,CAAC,QAAQ,OAAO,QAAQ,CAAC,EAAE,GAAG,CAAC;IACzD,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,QAAQ,CAAC,QAAQ,EAAE,EAAE,MAAM,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,CAAC;AACpD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,QAAqB;IACjD,MAAM,EAAE,eAAe,EAAE,GAAG,QAAQ,CAAC;IACrC,IAAI,eAAe,KAAK,SAAS;QAAE,OAAO;IAC1C,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,eAAe,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACpF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,QAAqB,EACrB,MAA4B,EAC5B,OAAe;IAEf,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;IAC5D,OAAO,CAAC,GAAG,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,GAAG,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,WAAW,CAAC,QAAqB,EAAE,MAA4B;IACtE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAC7B,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACvC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnE,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAC1B,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,EAAE,CACzE,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAChE,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,OAAO,CAAC,KAAK,MAAM,GAAG,SAAS,EAAE,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The cells `text` takes on a terminal, counted one per grapheme — so an
3
+ * accent or an emoji sequence typed as several code points still counts once.
4
+ */
5
+ export declare function displayWidth(text: string): number;
6
+ /** `text` without its last grapheme — what Backspace leaves of a line. */
7
+ export declare function withoutLastGrapheme(text: string): string;
@@ -0,0 +1,15 @@
1
+ /** Splits text the way a reader sees it: one segment per user-perceived character. */
2
+ const GRAPHEMES = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
3
+ /**
4
+ * The cells `text` takes on a terminal, counted one per grapheme — so an
5
+ * accent or an emoji sequence typed as several code points still counts once.
6
+ */
7
+ export function displayWidth(text) {
8
+ return [...GRAPHEMES.segment(text)].length;
9
+ }
10
+ /** `text` without its last grapheme — what Backspace leaves of a line. */
11
+ export function withoutLastGrapheme(text) {
12
+ const last = [...GRAPHEMES.segment(text)].at(-1);
13
+ return last === undefined ? text : text.slice(0, last.index);
14
+ }
15
+ //# sourceMappingURL=terminal-text.util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-text.util.js","sourceRoot":"","sources":["../../../../src/question/util/terminal-text.util.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;AAE7E;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;AAC7C,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,IAAI,GAAG,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgecart/cli",
3
- "version": "2.202609301225.0",
3
+ "version": "2.202610020334.0",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding and operating ForgeCart channel storefronts",
6
6
  "bin": {