@crewhaus/slash-commands 0.1.1 → 0.1.2

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 (2) hide show
  1. package/package.json +6 -11
  2. package/src/index.test.ts +65 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/slash-commands",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Markdown-templated slash commands with $ARGUMENTS substitution",
6
6
  "main": "src/index.ts",
@@ -12,14 +12,14 @@
12
12
  "test": "bun test src"
13
13
  },
14
14
  "dependencies": {
15
- "@crewhaus/errors": "0.1.1",
15
+ "@crewhaus/errors": "0.1.2",
16
16
  "yaml": "^2.6.0"
17
17
  },
18
18
  "license": "Apache-2.0",
19
19
  "author": {
20
20
  "name": "Max Meier",
21
- "email": "max@studiomax.io",
22
- "url": "https://studiomax.io"
21
+ "email": "max@crewhaus.ai",
22
+ "url": "https://crewhaus.ai"
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
@@ -31,12 +31,7 @@
31
31
  "url": "https://github.com/crewhaus/factory/issues"
32
32
  },
33
33
  "publishConfig": {
34
- "access": "restricted"
34
+ "access": "public"
35
35
  },
36
- "files": [
37
- "src",
38
- "README.md",
39
- "LICENSE",
40
- "NOTICE"
41
- ]
36
+ "files": ["src", "README.md", "LICENSE", "NOTICE"]
42
37
  }
package/src/index.test.ts CHANGED
@@ -1,8 +1,22 @@
1
- import { describe, expect, test } from "bun:test";
1
+ import { afterEach, describe, expect, mock, test } from "bun:test";
2
2
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
- import { type SlashCommand, expand, loadCommands, parseCommandFile } from "./index";
5
+ import {
6
+ type SlashCommand,
7
+ SlashCommandError,
8
+ expand,
9
+ loadCommands,
10
+ parseCommandFile,
11
+ } from "./index";
12
+
13
+ // Snapshot the genuine node:fs module before any mock so passthroughs hold the
14
+ // real implementations (Bun mutates the live namespace in place when mocking).
15
+ const REAL_FS = { ...(await import("node:fs")) };
16
+ const realReadFileSync = REAL_FS.readFileSync;
17
+ const restoreRealFs = () => {
18
+ mock.module("node:fs", () => ({ ...REAL_FS }));
19
+ };
6
20
 
7
21
  function withTempCwd(
8
22
  setup: (cwd: string) => void,
@@ -113,6 +127,55 @@ describe("loadCommands", () => {
113
127
  });
114
128
  });
115
129
 
130
+ describe("loadCommands — error wrapping (SlashCommandError)", () => {
131
+ afterEach(() => {
132
+ restoreRealFs();
133
+ });
134
+
135
+ test("a readFileSync failure is wrapped in a SlashCommandError", async () => {
136
+ await withTempCwd(
137
+ (cwd) => {
138
+ writeCommand(cwd, "boom", "body");
139
+ },
140
+ async (cwd) => {
141
+ const mdFile = join(cwd, ".crewhaus", "commands", "boom.md");
142
+ // statSync passes (real file), but the read fails — exercises the
143
+ // readFileSync catch and the SlashCommandError constructor.
144
+ mock.module("node:fs", () => ({
145
+ ...REAL_FS,
146
+ readFileSync: (p: Parameters<typeof realReadFileSync>[0], ...rest: unknown[]) => {
147
+ if (typeof p === "string" && p === mdFile) {
148
+ throw new Error("EACCES: permission denied");
149
+ }
150
+ // biome-ignore lint/suspicious/noExplicitAny: passthrough shim
151
+ return (realReadFileSync as any)(p, ...rest);
152
+ },
153
+ }));
154
+
155
+ const err = await loadCommands({ cwd }).then(
156
+ () => null,
157
+ (e) => e,
158
+ );
159
+ expect(err).toBeInstanceOf(SlashCommandError);
160
+ expect((err as SlashCommandError).name).toBe("SlashCommandError");
161
+ expect((err as Error).message).toContain("failed to read");
162
+ expect((err as Error).message).toContain("EACCES");
163
+ // The original error is threaded through as `cause`.
164
+ expect((err as SlashCommandError).cause).toBeInstanceOf(Error);
165
+ },
166
+ );
167
+ });
168
+
169
+ test("SlashCommandError is a config-kind CrewhausError carrying its cause", () => {
170
+ const cause = new Error("root");
171
+ const err = new SlashCommandError("nope", cause);
172
+ expect(err).toBeInstanceOf(SlashCommandError);
173
+ expect(err.name).toBe("SlashCommandError");
174
+ expect(err.message).toBe("nope");
175
+ expect(err.cause).toBe(cause);
176
+ });
177
+ });
178
+
116
179
  describe("expand — non-slash and unknown", () => {
117
180
  test("non-slash input passes through unchanged", () => {
118
181
  const cmds = new Map<string, SlashCommand>([["x", { name: "x", body: "B", filePath: "/x" }]]);