@jango-blockchained/hoox-cli 0.5.2 → 0.5.6

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 (114) hide show
  1. package/bin/hoox.js +3 -4
  2. package/dist/index.js +32545 -0
  3. package/package.json +10 -15
  4. package/src/commands/check/check-command.test.ts +0 -476
  5. package/src/commands/check/check-command.ts +0 -1087
  6. package/src/commands/check/index.ts +0 -10
  7. package/src/commands/check/prerequisites-command.test.ts +0 -19
  8. package/src/commands/check/prerequisites-command.ts +0 -97
  9. package/src/commands/check/types.ts +0 -103
  10. package/src/commands/clone/clone-command.test.ts +0 -440
  11. package/src/commands/clone/clone-command.ts +0 -431
  12. package/src/commands/clone/index.ts +0 -1
  13. package/src/commands/config/config-command.test.ts +0 -583
  14. package/src/commands/config/config-command.ts +0 -896
  15. package/src/commands/config/env-command.test.ts +0 -43
  16. package/src/commands/config/env-command.ts +0 -292
  17. package/src/commands/config/index.ts +0 -3
  18. package/src/commands/config/kv-command.test.ts +0 -14
  19. package/src/commands/config/kv-command.ts +0 -290
  20. package/src/commands/dashboard/dashboard-command.test.ts +0 -47
  21. package/src/commands/dashboard/dashboard-command.ts +0 -130
  22. package/src/commands/dashboard/index.ts +0 -1
  23. package/src/commands/db/db-command.test.ts +0 -21
  24. package/src/commands/db/db-command.ts +0 -311
  25. package/src/commands/db/index.ts +0 -1
  26. package/src/commands/deploy/deploy-command.test.ts +0 -324
  27. package/src/commands/deploy/deploy-command.ts +0 -1054
  28. package/src/commands/deploy/index.ts +0 -2
  29. package/src/commands/deploy/telegram-service.ts +0 -76
  30. package/src/commands/deploy/types.ts +0 -34
  31. package/src/commands/dev/dev-command.test.ts +0 -405
  32. package/src/commands/dev/dev-command.ts +0 -398
  33. package/src/commands/dev/index.ts +0 -1
  34. package/src/commands/disclaimer/disclaimer-command.ts +0 -21
  35. package/src/commands/disclaimer/index.ts +0 -1
  36. package/src/commands/infra/index.ts +0 -5
  37. package/src/commands/infra/infra-command.test.ts +0 -719
  38. package/src/commands/infra/infra-command.ts +0 -974
  39. package/src/commands/infra/types.ts +0 -23
  40. package/src/commands/init/__tests__/cli-provisioner.test.ts +0 -17
  41. package/src/commands/init/cli-provisioner.ts +0 -71
  42. package/src/commands/init/index.ts +0 -1
  43. package/src/commands/init/init-command.test.ts +0 -854
  44. package/src/commands/init/init-command.ts +0 -597
  45. package/src/commands/init/types.ts +0 -23
  46. package/src/commands/logs/index.ts +0 -1
  47. package/src/commands/logs/logs-command.test.ts +0 -293
  48. package/src/commands/logs/logs-command.ts +0 -452
  49. package/src/commands/monitor/index.ts +0 -2
  50. package/src/commands/monitor/monitor-command.test.ts +0 -271
  51. package/src/commands/monitor/monitor-command.ts +0 -269
  52. package/src/commands/monitor/monitor-service.ts +0 -65
  53. package/src/commands/monitor/types.ts +0 -13
  54. package/src/commands/repair/index.ts +0 -2
  55. package/src/commands/repair/repair-command.test.ts +0 -243
  56. package/src/commands/repair/repair-command.ts +0 -220
  57. package/src/commands/repair/repair-service.ts +0 -164
  58. package/src/commands/repair/types.ts +0 -13
  59. package/src/commands/schema/index.ts +0 -1
  60. package/src/commands/schema/schema-command.ts +0 -137
  61. package/src/commands/test/index.ts +0 -2
  62. package/src/commands/test/test-command.test.ts +0 -318
  63. package/src/commands/test/test-command.ts +0 -406
  64. package/src/commands/tui/index.ts +0 -1
  65. package/src/commands/tui/tui-command.ts +0 -87
  66. package/src/commands/update/index.ts +0 -1
  67. package/src/commands/update/update-command.ts +0 -109
  68. package/src/commands/waf/index.ts +0 -2
  69. package/src/commands/waf/types.ts +0 -48
  70. package/src/commands/waf/waf-command.test.ts +0 -506
  71. package/src/commands/waf/waf-command.ts +0 -545
  72. package/src/index.ts +0 -261
  73. package/src/services/cloudflare/cloudflare-service.test.ts +0 -658
  74. package/src/services/cloudflare/cloudflare-service.ts +0 -433
  75. package/src/services/cloudflare/index.ts +0 -2
  76. package/src/services/cloudflare/types.ts +0 -30
  77. package/src/services/config/config-service.test.ts +0 -395
  78. package/src/services/config/config-service.ts +0 -202
  79. package/src/services/config/index.ts +0 -9
  80. package/src/services/config/types.ts +0 -66
  81. package/src/services/db/db-service.test.ts +0 -63
  82. package/src/services/db/db-service.ts +0 -131
  83. package/src/services/db/index.ts +0 -1
  84. package/src/services/docker/docker-service.ts +0 -155
  85. package/src/services/docker/index.ts +0 -1
  86. package/src/services/env/env-service.test.ts +0 -259
  87. package/src/services/env/env-service.ts +0 -372
  88. package/src/services/env/index.ts +0 -1
  89. package/src/services/kv/index.ts +0 -1
  90. package/src/services/kv/kv-sync-service.test.ts +0 -38
  91. package/src/services/kv/kv-sync-service.ts +0 -243
  92. package/src/services/prerequisites/index.ts +0 -1
  93. package/src/services/prerequisites/prerequisites-service.test.ts +0 -89
  94. package/src/services/prerequisites/prerequisites-service.ts +0 -377
  95. package/src/services/prerequisites/types.ts +0 -48
  96. package/src/services/schema/index.ts +0 -1
  97. package/src/services/schema/schema-service.ts +0 -99
  98. package/src/services/secrets/index.ts +0 -11
  99. package/src/services/secrets/secrets-service.test.ts +0 -502
  100. package/src/services/secrets/secrets-service.ts +0 -287
  101. package/src/services/secrets/types.ts +0 -50
  102. package/src/services/update/index.ts +0 -2
  103. package/src/services/update/update-service.test.ts +0 -76
  104. package/src/services/update/update-service.ts +0 -193
  105. package/src/ui/banner.ts +0 -141
  106. package/src/ui/index.ts +0 -8
  107. package/src/ui/menu.ts +0 -490
  108. package/src/utils/error-handler.ts +0 -62
  109. package/src/utils/errors.test.ts +0 -69
  110. package/src/utils/errors.ts +0 -24
  111. package/src/utils/formatters.test.ts +0 -180
  112. package/src/utils/formatters.ts +0 -269
  113. package/src/utils/git.ts +0 -134
  114. package/src/utils/theme.ts +0 -92
@@ -1,69 +0,0 @@
1
- import { describe, it, expect } from "bun:test";
2
- import { CLIError, ExitCode } from "./errors.js";
3
-
4
- // CommandItem is defined in errors.js and re-exported; avoid redeclare
5
-
6
- describe("ExitCode", () => {
7
- it("has correct numeric values", () => {
8
- expect(ExitCode.SUCCESS).toBe(0);
9
- expect(ExitCode.ERROR).toBe(1);
10
- expect(ExitCode.INVALID_USAGE).toBe(2);
11
- expect(ExitCode.INFRA_UNAVAILABLE).toBe(3);
12
- });
13
-
14
- it("is a numeric enum with expected values", () => {
15
- expect(ExitCode.SUCCESS).toBe(0);
16
- expect(ExitCode.ERROR).toBe(1);
17
- expect(ExitCode.INVALID_USAGE).toBe(2);
18
- expect(ExitCode.INFRA_UNAVAILABLE).toBe(3);
19
- // All values are distinct
20
- const values = [
21
- ExitCode.SUCCESS,
22
- ExitCode.ERROR,
23
- ExitCode.INVALID_USAGE,
24
- ExitCode.INFRA_UNAVAILABLE,
25
- ];
26
- expect(new Set(values).size).toBe(4);
27
- });
28
- });
29
-
30
- describe("CLIError", () => {
31
- it("constructs with message and defaults", () => {
32
- const err = new CLIError("something went wrong");
33
- expect(err).toBeInstanceOf(Error);
34
- expect(err).toBeInstanceOf(CLIError);
35
- expect(err.name).toBe("CLIError");
36
- expect(err.message).toBe("something went wrong");
37
- expect(err.code).toBe(ExitCode.ERROR);
38
- expect(err.details).toBeUndefined();
39
- expect(err.recoverable).toBe(false);
40
- });
41
-
42
- it("constructs with all fields", () => {
43
- const err = new CLIError(
44
- "invalid input",
45
- ExitCode.INVALID_USAGE,
46
- "Expected --name to be a non-empty string",
47
- true
48
- );
49
- expect(err.message).toBe("invalid input");
50
- expect(err.code).toBe(ExitCode.INVALID_USAGE);
51
- expect(err.details).toBe("Expected --name to be a non-empty string");
52
- expect(err.recoverable).toBe(true);
53
- });
54
-
55
- it("supports INFRA_UNAVAILABLE code", () => {
56
- const err = new CLIError(
57
- "Cloudflare API unreachable",
58
- ExitCode.INFRA_UNAVAILABLE,
59
- "Check your network or try again later"
60
- );
61
- expect(err.code).toBe(3);
62
- expect(err.recoverable).toBe(false);
63
- });
64
-
65
- it("has a string representation that includes the name", () => {
66
- const err = new CLIError("fail");
67
- expect(err.toString()).toBe("CLIError: fail");
68
- });
69
- });
@@ -1,24 +0,0 @@
1
- /**
2
- * CLIError — structured error class for the Hoox CLI.
3
- * Provides exit codes, optional details, and recoverable flag.
4
- */
5
-
6
- export const enum ExitCode {
7
- SUCCESS = 0,
8
- ERROR = 1,
9
- INVALID_USAGE = 2,
10
- INFRA_UNAVAILABLE = 3,
11
- CommandFailed = -1,
12
- }
13
-
14
- export class CLIError extends Error {
15
- constructor(
16
- message: string,
17
- public readonly code: ExitCode = ExitCode.ERROR,
18
- public readonly details?: string,
19
- public readonly recoverable = false
20
- ) {
21
- super(message);
22
- this.name = "CLIError";
23
- }
24
- }
@@ -1,180 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test";
2
- import {
3
- formatSuccess,
4
- formatError,
5
- formatTable,
6
- formatJson,
7
- formatKeyValue,
8
- } from "./formatters.js";
9
- import { CLIError, ExitCode } from "./errors.js";
10
-
11
- /**
12
- * Helper: captures everything written to process.stdout.write into a string.
13
- * Returns a cleanup function that restores the original write.
14
- */
15
- function captureStdout(): { output: () => string; restore: () => void } {
16
- const chunks: string[] = [];
17
- const originalWrite = process.stdout.write.bind(process.stdout);
18
- const writeMock = mock((chunk: string | Buffer) => {
19
- chunks.push(typeof chunk === "string" ? chunk : chunk.toString());
20
- return true;
21
- });
22
- process.stdout.write = writeMock as unknown as typeof process.stdout.write;
23
-
24
- return {
25
- output: () => chunks.join(""),
26
- restore: () => {
27
- process.stdout.write = originalWrite;
28
- },
29
- };
30
- }
31
-
32
- describe("formatSuccess", () => {
33
- let capture: ReturnType<typeof captureStdout>;
34
-
35
- beforeEach(() => {
36
- capture = captureStdout();
37
- });
38
-
39
- afterEach(() => {
40
- capture.restore();
41
- });
42
-
43
- it("outputs human-formatted success", () => {
44
- formatSuccess("Deploy complete");
45
- const out = capture.output();
46
- expect(out).toContain("✓");
47
-
48
- // Check status indicator is present (just the checkmark)
49
- expect(out).toContain("✓");
50
- expect(out).not.toContain("✗");
51
- });
52
- });
53
-
54
- describe("formatTable", () => {
55
- let capture: ReturnType<typeof captureStdout>;
56
-
57
- beforeEach(() => {
58
- capture = captureStdout();
59
- });
60
-
61
- afterEach(() => {
62
- capture.restore();
63
- });
64
-
65
- const sampleRows = [
66
- { name: "alpha", status: "ok" },
67
- { name: "beta-long", status: "error" },
68
- ];
69
-
70
- it("outputs human-formatted table with box drawing", () => {
71
- formatTable(sampleRows);
72
- const out = capture.output();
73
- expect(out).toContain("┌");
74
- expect(out).toContain("┬");
75
- expect(out).toContain("┐");
76
- expect(out).toContain("├");
77
- expect(out).toContain("┼");
78
- expect(out).toContain("┤");
79
- expect(out).toContain("└");
80
- expect(out).toContain("┴");
81
- expect(out).toContain("┘");
82
- expect(out).toContain("alpha");
83
- expect(out).toContain("beta-long");
84
- expect(out).toContain("ok");
85
- expect(out).toContain("error");
86
- });
87
-
88
- it("outputs JSON array when json=true", () => {
89
- formatTable(sampleRows, { json: true });
90
- const out = capture.output();
91
- const parsed = JSON.parse(out);
92
- expect(parsed).toEqual(sampleRows);
93
- });
94
-
95
- it("outputs nothing when quiet=true", () => {
96
- formatTable(sampleRows, { quiet: true });
97
- expect(capture.output()).toBe("");
98
- });
99
-
100
- it("outputs (empty) for empty rows", () => {
101
- formatTable([]);
102
- const out = capture.output();
103
- expect(out).toContain("(empty)");
104
- expect(out).not.toContain("┌");
105
- });
106
- });
107
-
108
- describe("formatJson", () => {
109
- let capture: ReturnType<typeof captureStdout>;
110
-
111
- beforeEach(() => {
112
- capture = captureStdout();
113
- });
114
-
115
- afterEach(() => {
116
- capture.restore();
117
- });
118
-
119
- it("outputs pretty JSON by default", () => {
120
- formatJson({ key: "value" });
121
- const out = capture.output();
122
- const parsed = JSON.parse(out);
123
- expect(parsed).toEqual({ key: "value" });
124
- // Pretty-printed: contains newlines and indentation
125
- expect(out).toContain("\n");
126
- expect(out).toContain('"key"');
127
- });
128
-
129
- it("outputs compact JSON when quiet=true", () => {
130
- formatJson({ key: "value" }, { quiet: true });
131
- const out = capture.output();
132
- const parsed = JSON.parse(out);
133
- expect(parsed).toEqual({ key: "value" });
134
- // Compact: single line of content (console.log appends newline)
135
- expect(out.trim()).not.toContain("\n");
136
- });
137
-
138
- it("outputs pretty JSON when json=true", () => {
139
- formatJson({ key: "value" }, { json: true });
140
- const out = capture.output();
141
- expect(out).toContain("\n");
142
- });
143
- });
144
-
145
- describe("formatKeyValue", () => {
146
- let capture: ReturnType<typeof captureStdout>;
147
-
148
- beforeEach(() => {
149
- capture = captureStdout();
150
- });
151
-
152
- afterEach(() => {
153
- capture.restore();
154
- });
155
-
156
- const pairs = { Name: "alpha", Status: "running", Region: "us-east" };
157
-
158
- it("outputs human-formatted key-value pairs", () => {
159
- formatKeyValue(pairs);
160
- const out = capture.output();
161
- expect(out).toContain("Name");
162
- expect(out).toContain("alpha");
163
- expect(out).toContain("Status");
164
- expect(out).toContain("running");
165
- expect(out).toContain("Region");
166
- expect(out).toContain("us-east");
167
- });
168
-
169
- it("outputs JSON object when json=true", () => {
170
- formatKeyValue(pairs, { json: true });
171
- const out = capture.output();
172
- const parsed = JSON.parse(out);
173
- expect(parsed).toEqual(pairs);
174
- });
175
-
176
- it("outputs nothing when quiet=true", () => {
177
- formatKeyValue(pairs, { quiet: true });
178
- expect(capture.output()).toBe("");
179
- });
180
- });
@@ -1,269 +0,0 @@
1
- /**
2
- * Output formatters for the Hoox CLI.
3
- * Each function respects --json / --quiet flags passed via FormatOptions.
4
- * Human-readable mode uses ansis styling from the theme.
5
- */
6
-
7
- import { CLIError, ExitCode } from "./errors.js";
8
- import { theme, icons, stripAnsi, hr } from "./theme.js";
9
- import { Command } from "commander";
10
-
11
- export interface FormatOptions {
12
- json?: boolean;
13
- quiet?: boolean;
14
- }
15
-
16
- // ── Progress bar ──────────────────────────────────────────────────
17
-
18
- const PROGRESS_BAR_WIDTH = 30;
19
-
20
- /**
21
- * Render an inline progress bar string.
22
- * @param current Current step (0-based)
23
- * @param total Total steps
24
- * @param opts Width override
25
- */
26
- export function renderProgressBar(
27
- current: number,
28
- total: number,
29
- opts?: { width?: number }
30
- ): string {
31
- const width = opts?.width ?? PROGRESS_BAR_WIDTH;
32
- const ratio = total > 0 ? Math.min(current / total, 1) : 0;
33
- const filled = Math.round(width * ratio);
34
- const empty = width - filled;
35
- const pct = Math.round(ratio * 100);
36
-
37
- const bar =
38
- theme.accent("[") +
39
- theme.success("█".repeat(filled)) +
40
- theme.dim("░".repeat(empty)) +
41
- theme.accent("]") +
42
- ` ${pct}%`;
43
-
44
- return bar;
45
- }
46
-
47
- /**
48
- * Render a multi-line progress display for sequential steps.
49
- * Returns lines suitable for overwriting via \r or manual clear.
50
- */
51
- export function renderStepProgress(
52
- steps: Array<{
53
- name: string;
54
- status: "pending" | "running" | "done" | "failed";
55
- }>
56
- ): string {
57
- const iconMap: Record<string, string> = {
58
- pending: theme.dim("○"),
59
- running: theme.info("◌"),
60
- done: theme.success("●"),
61
- failed: theme.error("✗"),
62
- };
63
-
64
- return steps
65
- .map((s) => ` ${iconMap[s.status]} ${theme.bold(s.name)}`)
66
- .join("\n");
67
- }
68
-
69
- // ── Basic output ──────────────────────────────────────────────────
70
-
71
- /**
72
- * Output a success message.
73
- * - JSON mode: {"success":true,"message":"..."}
74
- * - Quiet mode: prints nothing
75
- * - Human mode: green "✓ message"
76
- */
77
- export function formatSuccess(message: string, opts?: FormatOptions): void {
78
- if (opts?.quiet) return;
79
-
80
- if (opts?.json) {
81
- process.stdout.write(JSON.stringify({ success: true, message }) + "\n");
82
- return;
83
- }
84
-
85
- process.stdout.write(`${theme.success(icons.success)} ${message}\n`);
86
- }
87
-
88
- /**
89
- * Output an error message.
90
- * - JSON mode: {"success":false,"error":"...","code":1,"details":"..."}
91
- * - Quiet mode: prints only the error message (no icon)
92
- * - Human mode: red "✗ message" + optional details indented
93
- */
94
- export function formatError(error: Error | string, opts?: FormatOptions): void {
95
- const message = typeof error === "string" ? error : error.message;
96
- const cliError = error instanceof CLIError ? error : null;
97
-
98
- if (opts?.json) {
99
- const output: Record<string, unknown> = {
100
- success: false,
101
- error: message,
102
- code: cliError?.code ?? ExitCode.ERROR,
103
- };
104
- if (cliError?.details) {
105
- output.details = cliError.details;
106
- }
107
- process.stdout.write(JSON.stringify(output) + "\n");
108
- return;
109
- }
110
-
111
- if (opts?.quiet) {
112
- process.stdout.write(`${message}\n`);
113
- return;
114
- }
115
-
116
- process.stdout.write(`${theme.error(icons.error)} ${message}\n`);
117
-
118
- if (cliError?.details) {
119
- process.stdout.write(` ${theme.dim(cliError.details)}\n`);
120
- }
121
- }
122
-
123
- /**
124
- * Output tabular data with enhanced box-drawing borders.
125
- * - JSON mode: JSON array of objects
126
- * - Quiet mode: prints nothing
127
- * - Human mode: themed box-drawn table with column headers
128
- */
129
- export function formatTable(
130
- rows: Record<string, string>[],
131
- opts?: FormatOptions
132
- ): void {
133
- if (opts?.quiet) return;
134
-
135
- if (opts?.json) {
136
- process.stdout.write(JSON.stringify(rows, null, 2) + "\n");
137
- return;
138
- }
139
-
140
- if (rows.length === 0) {
141
- process.stdout.write(`${theme.dim("(empty)")}\n`);
142
- return;
143
- }
144
-
145
- // Collect all keys in display order (first row defines column order)
146
- const keys = Object.keys(rows[0]);
147
-
148
- // Calculate column widths
149
- const widths: Record<string, number> = {};
150
- for (const key of keys) {
151
- widths[key] = stripAnsi(key).length;
152
- for (const row of rows) {
153
- const value = row[key] ?? "";
154
- const stripped = stripAnsi(value).length;
155
- widths[key] = Math.max(widths[key], stripped);
156
- }
157
- }
158
-
159
- // Build border segments
160
- const topParts = keys.map((k) => "─".repeat(widths[k] + 2));
161
- const topBorder = `┌${topParts.join("┬")}┐`;
162
- const sepBorder = `├${topParts.join("┼")}┤`;
163
- const botBorder = `└${topParts.join("┴")}┘`;
164
-
165
- // Header row with themed styling
166
- const headerCells = keys.map((k) =>
167
- theme.bold(k.padEnd(widths[k])).toString()
168
- );
169
- const headerRow = `│ ${headerCells.join(" │ ")} │`;
170
-
171
- // Data rows
172
- const dataRows = rows.map((row) => {
173
- const cells = keys.map((k) => {
174
- const value = row[k] ?? "";
175
- return value.padEnd(widths[k]);
176
- });
177
- return `│ ${cells.join(" │ ")} │`;
178
- });
179
-
180
- process.stdout.write(
181
- `${theme.dim(topBorder)}\n` +
182
- `${headerRow}\n` +
183
- `${theme.dim(sepBorder)}\n` +
184
- `${dataRows.join("\n")}\n` +
185
- `${theme.dim(botBorder)}\n`
186
- );
187
- }
188
-
189
- /**
190
- * Output arbitrary data as JSON.
191
- * - JSON / Quiet mode: JSON stringified (compact or pretty)
192
- * - Human mode: JSON with 2-space indent
193
- */
194
- export function formatJson(data: unknown, opts?: FormatOptions): void {
195
- const json = opts?.quiet
196
- ? JSON.stringify(data)
197
- : JSON.stringify(data, null, 2);
198
-
199
- process.stdout.write(`${json}\n`);
200
- }
201
-
202
- /**
203
- * Output key-value pairs.
204
- * - JSON mode: JSON object
205
- * - Quiet mode: prints nothing
206
- * - Human mode: "label: value" with themed labels
207
- */
208
- export function formatKeyValue(
209
- pairs: Record<string, string>,
210
- opts?: FormatOptions
211
- ): void {
212
- if (opts?.quiet) return;
213
-
214
- if (opts?.json) {
215
- process.stdout.write(JSON.stringify(pairs, null, 2) + "\n");
216
- return;
217
- }
218
-
219
- const maxKeyLen = Math.max(
220
- ...Object.keys(pairs).map((k) => stripAnsi(k).length)
221
- );
222
-
223
- for (const [key, value] of Object.entries(pairs)) {
224
- const paddedKey = key.padEnd(maxKeyLen);
225
- process.stdout.write(
226
- ` ${theme.key(paddedKey)} ${theme.dim(":")} ${value}\n`
227
- );
228
- }
229
- }
230
-
231
- /**
232
- * Output a section header with decorative separator.
233
- */
234
- export function formatHeader(text: string, opts?: FormatOptions): void {
235
- if (opts?.quiet) return;
236
- if (opts?.json) return;
237
-
238
- process.stdout.write(`\n${theme.heading(text)}\n`);
239
- process.stdout.write(`${hr()}\n`);
240
- }
241
-
242
- /**
243
- * Output a bullet-point list with optional icon per item.
244
- */
245
- export function formatList(
246
- items: string[],
247
- opts?: { icon?: string; json?: boolean; quiet?: boolean }
248
- ): void {
249
- if (opts?.quiet) return;
250
-
251
- if (opts?.json) {
252
- process.stdout.write(JSON.stringify(items, null, 2) + "\n");
253
- return;
254
- }
255
-
256
- const icon = opts?.icon ?? icons.arrow;
257
- for (const item of items) {
258
- process.stdout.write(` ${icon} ${item}\n`);
259
- }
260
- }
261
-
262
- /**
263
- * Build the format options for output, reading global --json / --quiet flags.
264
- * Uses `optsWithGlobals()` to include options inherited from the top-level program.
265
- */
266
- export function getFormatOptions(cmd: Command): FormatOptions {
267
- const opts = cmd.optsWithGlobals();
268
- return { json: Boolean(opts.json), quiet: Boolean(opts.quiet) };
269
- }
package/src/utils/git.ts DELETED
@@ -1,134 +0,0 @@
1
- /**
2
- * Shared git utilities for Hoox CLI.
3
- *
4
- * All functions use Bun.spawn for asynchronous git operations.
5
- * Bun is a global in the Bun runtime — no import needed.
6
- */
7
-
8
- // ---------------------------------------------------------------------------
9
- // Repository and working tree checks
10
- // ---------------------------------------------------------------------------
11
-
12
- /**
13
- * Check if `cwd` is inside a git work-tree.
14
- */
15
- export async function isGitRepo(cwd: string): Promise<boolean> {
16
- const proc = Bun.spawn(["git", "rev-parse", "--is-inside-work-tree"], {
17
- cwd,
18
- stdout: "pipe",
19
- stderr: "pipe",
20
- });
21
- const exitCode = await proc.exited;
22
- return exitCode === 0;
23
- }
24
-
25
- /**
26
- * Check whether a path inside `cwd` is a registered git submodule.
27
- */
28
- export async function isSubmodule(
29
- cwd: string,
30
- submodulePath: string
31
- ): Promise<boolean> {
32
- const proc = Bun.spawn(["git", "submodule", "status", "--", submodulePath], {
33
- cwd,
34
- stdout: "pipe",
35
- stderr: "pipe",
36
- });
37
- const exitCode = await proc.exited;
38
- if (exitCode !== 0) return false;
39
- const out = (await new Response(proc.stdout).text()).trim();
40
- return out.length > 0;
41
- }
42
-
43
- /**
44
- * Check if a specific file inside `dir` is tracked by git.
45
- */
46
- export async function isGitTracked(
47
- dir: string,
48
- filename: string
49
- ): Promise<boolean> {
50
- try {
51
- const proc = Bun.spawn(["git", "ls-files", "--error-unmatch", filename], {
52
- cwd: dir,
53
- stdout: "pipe",
54
- stderr: "pipe",
55
- });
56
- const exitCode = await proc.exited;
57
- return exitCode === 0;
58
- } catch {
59
- return false;
60
- }
61
- }
62
-
63
- // ---------------------------------------------------------------------------
64
- // Git operations
65
- // ---------------------------------------------------------------------------
66
-
67
- /**
68
- * Run `git pull --ff-only` inside `cwd`.
69
- */
70
- export async function gitPull(cwd: string): Promise<string> {
71
- const proc = Bun.spawn(["git", "pull", "--ff-only"], {
72
- cwd,
73
- stdout: "pipe",
74
- stderr: "pipe",
75
- });
76
- const exitCode = await proc.exited;
77
- const stdout = await new Response(proc.stdout).text();
78
- const stderr = await new Response(proc.stderr).text();
79
-
80
- if (exitCode !== 0) {
81
- throw new Error(stderr.trim() || `git pull failed (exit ${exitCode})`);
82
- }
83
- return stdout.trim();
84
- }
85
-
86
- /**
87
- * Run `git submodule update --remote --init` for a specific submodule path.
88
- */
89
- export async function gitSubmoduleUpdate(
90
- cwd: string,
91
- submodulePath: string
92
- ): Promise<string> {
93
- const proc = Bun.spawn(
94
- ["git", "submodule", "update", "--remote", "--init", "--", submodulePath],
95
- {
96
- cwd,
97
- stdout: "pipe",
98
- stderr: "pipe",
99
- }
100
- );
101
- const exitCode = await proc.exited;
102
- const stdout = await new Response(proc.stdout).text();
103
- const stderr = await new Response(proc.stderr).text();
104
-
105
- if (exitCode !== 0) {
106
- throw new Error(
107
- stderr.trim() || `git submodule update failed (exit ${exitCode})`
108
- );
109
- }
110
- return stdout.trim();
111
- }
112
-
113
- /**
114
- * Run `git rm --cached` to remove a file from git tracking while keeping it
115
- * on disk.
116
- */
117
- export async function gitUntrackFile(
118
- dir: string,
119
- filename: string
120
- ): Promise<void> {
121
- await new Promise<void>((resolve, reject) => {
122
- const proc = Bun.spawn(["git", "rm", "--cached", filename], {
123
- cwd: dir,
124
- stdout: "pipe",
125
- stderr: "pipe",
126
- });
127
- proc.exited
128
- .then((code: number) => {
129
- if (code === 0) resolve();
130
- else reject(new Error(`git rm --cached failed with code ${code}`));
131
- })
132
- .catch(reject);
133
- });
134
- }