@forwardimpact/libutil 0.1.63 → 0.1.65

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,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import { spawn } from "node:child_process";
4
4
  import { createScriptConfig } from "@forwardimpact/libconfig";
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  import { countTokens } from "@forwardimpact/libutil";
3
3
 
4
4
  /**
package/finder.js CHANGED
@@ -47,6 +47,25 @@ export class Finder {
47
47
  return null;
48
48
  }
49
49
 
50
+ /**
51
+ * Resolve a data directory by upward traversal, with HOME fallback.
52
+ * @param {string} baseName - Directory name to find (e.g. "data")
53
+ * @param {string} homeDir - User home directory path
54
+ * @returns {string} Absolute path to found directory
55
+ */
56
+ findData(baseName, homeDir) {
57
+ const cwd = this.#process.cwd();
58
+ const found = this.findUpward(cwd, baseName);
59
+ if (found) return found;
60
+
61
+ const homePath = path.join(homeDir, ".fit", baseName);
62
+ if (fs.existsSync(homePath)) return homePath;
63
+
64
+ throw new Error(
65
+ `No ${baseName} directory found from ${cwd} or ${homePath}.`,
66
+ );
67
+ }
68
+
50
69
  /**
51
70
  * Find the project root directory
52
71
  * @param {string} startPath - Starting directory path
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/libutil",
3
- "version": "0.1.63",
3
+ "version": "0.1.65",
4
4
  "description": "Utility functions and utilities for Guide",
5
5
  "license": "Apache-2.0",
6
6
  "author": "D. Olsson <hi@senzilla.io>",
@@ -11,10 +11,10 @@
11
11
  "fit-tiktoken": "./bin/fit-tiktoken.js"
12
12
  },
13
13
  "engines": {
14
- "node": ">=22.0.0"
14
+ "bun": ">=1.2.0"
15
15
  },
16
16
  "scripts": {
17
- "test": "node --test test/*.test.js"
17
+ "test": "bun run node --test test/*.test.js"
18
18
  },
19
19
  "dependencies": {
20
20
  "@forwardimpact/libtelemetry": "^0.1.22"
@@ -257,6 +257,79 @@ describe("Finder", () => {
257
257
  });
258
258
  });
259
259
 
260
+ describe("findData", () => {
261
+ test("finds data/ in CWD via findUpward", () => {
262
+ const dataDir = path.join(tempDir, "data");
263
+ fs.mkdirSync(dataDir);
264
+
265
+ const cwdFinder = new Finder(fsPromises, mockLogger, {
266
+ cwd: () => tempDir,
267
+ });
268
+ const result = cwdFinder.findData("data", "/nonexistent-home");
269
+
270
+ assert.strictEqual(result, dataDir);
271
+ });
272
+
273
+ test("finds data/ in a parent directory via findUpward", () => {
274
+ const dataDir = path.join(tempDir, "data");
275
+ fs.mkdirSync(dataDir);
276
+ const subDir = path.join(tempDir, "products", "pathway");
277
+ fs.mkdirSync(subDir, { recursive: true });
278
+
279
+ const cwdFinder = new Finder(fsPromises, mockLogger, {
280
+ cwd: () => subDir,
281
+ });
282
+ const result = cwdFinder.findData("data", "/nonexistent-home");
283
+
284
+ assert.strictEqual(result, dataDir);
285
+ });
286
+
287
+ test("falls back to ~/.fit/data/ when CWD traversal fails", () => {
288
+ const fakeHome = path.join(tempDir, "fakehome");
289
+ const homeFitData = path.join(fakeHome, ".fit", "data");
290
+ fs.mkdirSync(homeFitData, { recursive: true });
291
+
292
+ const isolatedDir = path.join(tempDir, "isolated");
293
+ fs.mkdirSync(isolatedDir);
294
+
295
+ const cwdFinder = new Finder(fsPromises, mockLogger, {
296
+ cwd: () => isolatedDir,
297
+ });
298
+ const result = cwdFinder.findData("data", fakeHome);
299
+
300
+ assert.strictEqual(result, homeFitData);
301
+ });
302
+
303
+ test("throws when neither CWD traversal nor HOME fallback finds directory", () => {
304
+ const isolatedDir = path.join(tempDir, "isolated");
305
+ fs.mkdirSync(isolatedDir);
306
+
307
+ const cwdFinder = new Finder(fsPromises, mockLogger, {
308
+ cwd: () => isolatedDir,
309
+ });
310
+
311
+ assert.throws(() => cwdFinder.findData("data", "/nonexistent-home"), {
312
+ message: /No data directory found/,
313
+ });
314
+ });
315
+
316
+ test("CWD takes priority over HOME when both exist", () => {
317
+ const cwdData = path.join(tempDir, "data");
318
+ fs.mkdirSync(cwdData);
319
+
320
+ const fakeHome = path.join(tempDir, "fakehome");
321
+ const homeFitData = path.join(fakeHome, ".fit", "data");
322
+ fs.mkdirSync(homeFitData, { recursive: true });
323
+
324
+ const cwdFinder = new Finder(fsPromises, mockLogger, {
325
+ cwd: () => tempDir,
326
+ });
327
+ const result = cwdFinder.findData("data", fakeHome);
328
+
329
+ assert.strictEqual(result, cwdData);
330
+ });
331
+ });
332
+
260
333
  describe("findPackagePath", () => {
261
334
  test("finds package in local monorepo structure", () => {
262
335
  // Create mock monorepo structure
@@ -97,28 +97,6 @@ describe("Logger", () => {
97
97
  assert.strictEqual(consoleOutput.length, 0);
98
98
  });
99
99
 
100
- test(
101
- "logs message with data object",
102
- { skip: "Future PR will fix this" },
103
- () => {
104
- process.env.DEBUG = "test";
105
- const logger = new Logger("test");
106
-
107
- logger.debug("ProcessMethod", "Processing", {
108
- items: "50/200",
109
- retry: "2/3",
110
- });
111
-
112
- assert.strictEqual(consoleOutput.length, 1);
113
- assert.ok(consoleOutput[0].includes("DEBUG"));
114
- assert.ok(consoleOutput[0].includes("test"));
115
- assert.ok(consoleOutput[0].includes("ProcessMethod"));
116
- assert.ok(consoleOutput[0].includes("Processing"));
117
- assert.ok(consoleOutput[0].includes('items="50/200"'));
118
- assert.ok(consoleOutput[0].includes('retry="2/3"'));
119
- },
120
- );
121
-
122
100
  test("handles empty data object", () => {
123
101
  process.env.DEBUG = "test";
124
102
  const logger = new Logger("test");
@@ -140,87 +118,6 @@ describe("Logger", () => {
140
118
  assert.ok(consoleOutput[0].match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/));
141
119
  });
142
120
 
143
- test(
144
- "extracts trace context from error objects",
145
- { skip: "Future PR will fix this" },
146
- () => {
147
- process.env.DEBUG = "test";
148
- const logger = new Logger("test");
149
-
150
- // Create error with trace context (as added by Tracer)
151
- const error = new Error("Test error");
152
- Object.defineProperties(error, {
153
- trace_id: {
154
- value: "abc123def456",
155
- enumerable: false,
156
- writable: false,
157
- },
158
- span_id: {
159
- value: "789xyz012",
160
- enumerable: false,
161
- writable: false,
162
- },
163
- service_name: {
164
- value: "test-service",
165
- enumerable: false,
166
- writable: false,
167
- },
168
- });
169
-
170
- logger.error("TestMethod", error);
171
-
172
- assert.strictEqual(consoleOutput.length, 1);
173
- assert.ok(consoleOutput[0].includes("ERROR"));
174
- assert.ok(consoleOutput[0].includes("Test error"));
175
- assert.ok(
176
- consoleOutput[0].includes("trace_id=abc123def456"),
177
- "Should include trace_id in structured data",
178
- );
179
- assert.ok(
180
- consoleOutput[0].includes("span_id=789xyz012"),
181
- "Should include span_id in structured data",
182
- );
183
- assert.ok(
184
- consoleOutput[0].includes("service_name=test-service"),
185
- "Should include service_name in structured data",
186
- );
187
- },
188
- );
189
-
190
- test(
191
- "merges trace context with provided attributes",
192
- { skip: "Future PR will fix this" },
193
- () => {
194
- process.env.DEBUG = "test";
195
- const logger = new Logger("test");
196
-
197
- const error = new Error("Test error");
198
- Object.defineProperty(error, "trace_id", {
199
- value: "trace123",
200
- enumerable: false,
201
- writable: false,
202
- });
203
-
204
- logger.error("TestMethod", error, { retry: "1/3", status: "500" });
205
-
206
- assert.strictEqual(consoleOutput.length, 1);
207
- assert.ok(consoleOutput[0].includes("ERROR"));
208
- assert.ok(consoleOutput[0].includes("Test error"));
209
- assert.ok(
210
- consoleOutput[0].includes('trace_id="abc123def456"'),
211
- "Should include trace_id in structured data",
212
- );
213
- assert.ok(
214
- consoleOutput[0].includes('span_id="789xyz012"'),
215
- "Should include span_id in structured data",
216
- );
217
- assert.ok(
218
- consoleOutput[0].includes('service_name="test-service"'),
219
- "Should include service_name in structured data",
220
- );
221
- },
222
- );
223
-
224
121
  test("merges trace context with provided attributes", () => {
225
122
  process.env.DEBUG = "test";
226
123
  const logger = new Logger("test");