@forwardimpact/libutil 0.1.72 → 0.1.74

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.
@@ -16,7 +16,7 @@ const definition = {
16
16
  name: "fit-download-bundle",
17
17
  version: VERSION,
18
18
  description: "Download generated code bundle from remote storage",
19
- options: {
19
+ globalOptions: {
20
20
  help: { type: "boolean", short: "h", description: "Show this help" },
21
21
  version: { type: "boolean", description: "Show version" },
22
22
  json: { type: "boolean", description: "Output help as JSON" },
@@ -13,7 +13,7 @@ const definition = {
13
13
  version: VERSION,
14
14
  description: "Count tokens in text",
15
15
  usage: "fit-tiktoken <text>\n echo 'text' | fit-tiktoken",
16
- options: {
16
+ globalOptions: {
17
17
  help: { type: "boolean", short: "h", description: "Show this help" },
18
18
  version: { type: "boolean", description: "Show version" },
19
19
  json: { type: "boolean", description: "Output help as JSON" },
package/package.json CHANGED
@@ -1,15 +1,25 @@
1
1
  {
2
2
  "name": "@forwardimpact/libutil",
3
- "version": "0.1.72",
3
+ "version": "0.1.74",
4
4
  "description": "Utility functions and utilities for Guide",
5
5
  "license": "Apache-2.0",
6
6
  "author": "D. Olsson <hi@senzilla.io>",
7
7
  "type": "module",
8
- "main": "index.js",
8
+ "main": "./src/index.js",
9
+ "exports": {
10
+ ".": "./src/index.js",
11
+ "./bin/fit-download-bundle.js": "./bin/fit-download-bundle.js",
12
+ "./bin/fit-tiktoken.js": "./bin/fit-tiktoken.js"
13
+ },
9
14
  "bin": {
10
15
  "fit-download-bundle": "./bin/fit-download-bundle.js",
11
16
  "fit-tiktoken": "./bin/fit-tiktoken.js"
12
17
  },
18
+ "files": [
19
+ "src/**/*.js",
20
+ "bin/**/*.js",
21
+ "README.md"
22
+ ],
13
23
  "engines": {
14
24
  "bun": ">=1.2.0",
15
25
  "node": ">=18.0.0"
@@ -114,7 +114,7 @@ export class Finder {
114
114
  */
115
115
  findGeneratedPath(projectRoot, packageName) {
116
116
  const packagePath = this.findPackagePath(projectRoot, packageName);
117
- return path.join(packagePath, "generated");
117
+ return path.join(packagePath, "src", "generated");
118
118
  }
119
119
 
120
120
  /**
@@ -139,6 +139,9 @@ export class Finder {
139
139
  // Target doesn't exist, which is fine
140
140
  }
141
141
 
142
+ // Ensure the target's parent directory exists before symlinking
143
+ await fsAsync.mkdir(path.dirname(targetPath), { recursive: true });
144
+
142
145
  // Create the symlink
143
146
  await fsAsync.symlink(sourcePath, targetPath, "dir");
144
147
  this.#logger.debug("Finder", "Created symlink", {
@@ -1,223 +0,0 @@
1
- import { strict as assert } from "node:assert";
2
- import { test, describe, beforeEach, mock } from "node:test";
3
-
4
- import { createSilentLogger } from "@forwardimpact/libharness";
5
-
6
- import { BundleDownloader } from "../downloader.js";
7
-
8
- describe("BundleDownloader", () => {
9
- let mockStorageFactory;
10
- let mockExtractor;
11
- let mockLogger;
12
- let mockFinder;
13
- let mockProcess;
14
- let mockLocalStorage;
15
- let mockRemoteStorage;
16
-
17
- beforeEach(() => {
18
- mockLocalStorage = {
19
- ensureBucket: async () => {},
20
- put: async () => {},
21
- delete: async () => {},
22
- path: (key = ".") => `/local/path/${key}`,
23
- };
24
-
25
- mockRemoteStorage = {
26
- exists: async () => true,
27
- get: async () => Buffer.from("bundle data"),
28
- };
29
-
30
- mockStorageFactory = (prefix, type) => {
31
- return type === "local" ? mockLocalStorage : mockRemoteStorage;
32
- };
33
-
34
- mockExtractor = {
35
- extract: mock.fn(async () => {}),
36
- };
37
- mockLogger = createSilentLogger();
38
- mockFinder = {
39
- createPackageSymlinks: mock.fn(async () => {}),
40
- };
41
- mockProcess = {
42
- env: { STORAGE_TYPE: "s3" },
43
- };
44
- });
45
-
46
- test("constructor validates required dependencies", () => {
47
- assert.throws(
48
- () =>
49
- new BundleDownloader(
50
- null,
51
- mockFinder,
52
- mockLogger,
53
- mockExtractor,
54
- mockProcess,
55
- ),
56
- /createStorageFn is required/,
57
- );
58
-
59
- assert.throws(
60
- () =>
61
- new BundleDownloader(
62
- mockStorageFactory,
63
- null,
64
- mockLogger,
65
- mockExtractor,
66
- mockProcess,
67
- ),
68
- /finder is required/,
69
- );
70
-
71
- assert.throws(
72
- () =>
73
- new BundleDownloader(
74
- mockStorageFactory,
75
- mockFinder,
76
- null,
77
- mockExtractor,
78
- mockProcess,
79
- ),
80
- /logger is required/,
81
- );
82
-
83
- assert.throws(
84
- () =>
85
- new BundleDownloader(
86
- mockStorageFactory,
87
- mockFinder,
88
- mockLogger,
89
- null,
90
- mockProcess,
91
- ),
92
- /extractor is required/,
93
- );
94
-
95
- assert.throws(
96
- () =>
97
- new BundleDownloader(
98
- mockStorageFactory,
99
- mockFinder,
100
- mockLogger,
101
- mockExtractor,
102
- null,
103
- ),
104
- /process is required/,
105
- );
106
- });
107
-
108
- test("downloads and extracts bundle when it exists", async () => {
109
- const operations = [];
110
- mockLocalStorage.put = async (key, data) => {
111
- operations.push({ op: "put", key, hasData: !!data });
112
- };
113
- mockLocalStorage.delete = async (key) => {
114
- operations.push({ op: "delete", key });
115
- };
116
- mockExtractor.extract = async (sourcePath, targetPath) => {
117
- operations.push({ op: "extract", sourcePath, targetPath });
118
- };
119
-
120
- const download = new BundleDownloader(
121
- mockStorageFactory,
122
- mockFinder,
123
- mockLogger,
124
- mockExtractor,
125
- mockProcess,
126
- );
127
- await download.initialize();
128
- await download.download();
129
-
130
- assert.strictEqual(operations.length, 3);
131
- assert.deepStrictEqual(operations[0], {
132
- op: "put",
133
- key: "bundle.tar.gz",
134
- hasData: true,
135
- });
136
- assert.strictEqual(operations[1].op, "extract");
137
- assert.strictEqual(operations[1].sourcePath, "/local/path/bundle.tar.gz");
138
- assert.strictEqual(operations[1].targetPath, "/local/path/.");
139
- assert.deepStrictEqual(operations[2], {
140
- op: "delete",
141
- key: "bundle.tar.gz",
142
- });
143
- });
144
-
145
- test("throws error when bundle does not exist", async () => {
146
- mockRemoteStorage.exists = async () => false;
147
-
148
- const download = new BundleDownloader(
149
- mockStorageFactory,
150
- mockFinder,
151
- mockLogger,
152
- mockExtractor,
153
- mockProcess,
154
- );
155
- await download.initialize();
156
-
157
- await assert.rejects(() => download.download(), {
158
- message: /Bundle not found/,
159
- });
160
- });
161
-
162
- test("initializes storage instances correctly", async () => {
163
- const ensureBucketCalls = [];
164
- mockLocalStorage.ensureBucket = async () => {
165
- ensureBucketCalls.push("generated");
166
- };
167
-
168
- const download = new BundleDownloader(
169
- mockStorageFactory,
170
- mockFinder,
171
- mockLogger,
172
- mockExtractor,
173
- mockProcess,
174
- );
175
- await download.initialize();
176
-
177
- assert.strictEqual(ensureBucketCalls.length, 1);
178
- assert.strictEqual(ensureBucketCalls[0], "generated");
179
-
180
- // Should have called createPackageSymlinks
181
- assert.strictEqual(mockFinder.createPackageSymlinks.mock.calls.length, 1);
182
- assert.strictEqual(
183
- mockFinder.createPackageSymlinks.mock.calls[0].arguments[0],
184
- "/local/path/.",
185
- );
186
- });
187
-
188
- test("skips download when STORAGE_TYPE is local", async () => {
189
- mockProcess.env.STORAGE_TYPE = "local";
190
- mockRemoteStorage.exists = mock.fn(async () => true);
191
-
192
- const download = new BundleDownloader(
193
- mockStorageFactory,
194
- mockFinder,
195
- mockLogger,
196
- mockExtractor,
197
- mockProcess,
198
- );
199
- await download.initialize();
200
- await download.download();
201
-
202
- // Should not have called remote storage
203
- assert.strictEqual(mockRemoteStorage.exists.mock.calls.length, 0);
204
- });
205
-
206
- test("downloads when STORAGE_TYPE is s3", async () => {
207
- mockProcess.env.STORAGE_TYPE = "s3";
208
- mockRemoteStorage.exists = mock.fn(async () => true);
209
-
210
- const download = new BundleDownloader(
211
- mockStorageFactory,
212
- mockFinder,
213
- mockLogger,
214
- mockExtractor,
215
- mockProcess,
216
- );
217
- await download.initialize();
218
- await download.download();
219
-
220
- // Should have called remote storage
221
- assert.strictEqual(mockRemoteStorage.exists.mock.calls.length, 1);
222
- });
223
- });
@@ -1,338 +0,0 @@
1
- import { test, describe, beforeEach, afterEach } from "node:test";
2
- import assert from "node:assert";
3
- import fs from "fs";
4
- import fsPromises from "fs/promises";
5
- import path from "path";
6
- import { fileURLToPath } from "url";
7
-
8
- // Module under test
9
- import { TarExtractor, ZipExtractor } from "../extractor.js";
10
-
11
- describe("TarExtractor", () => {
12
- let extractor;
13
- let tempDir;
14
- let fixturesDir;
15
-
16
- beforeEach(() => {
17
- extractor = new TarExtractor(fsPromises, path);
18
-
19
- // Setup directories
20
- const __filename = fileURLToPath(import.meta.url);
21
- const __dirname = path.dirname(__filename);
22
- fixturesDir = path.join(__dirname, "fixtures");
23
- tempDir = path.join(__dirname, ".tmp-extractor-test");
24
-
25
- // Clean up any existing temp directory
26
- if (fs.existsSync(tempDir)) {
27
- fs.rmSync(tempDir, { recursive: true, force: true });
28
- }
29
- fs.mkdirSync(tempDir, { recursive: true });
30
- });
31
-
32
- afterEach(() => {
33
- // Clean up temp directory
34
- if (fs.existsSync(tempDir)) {
35
- fs.rmSync(tempDir, { recursive: true, force: true });
36
- }
37
- });
38
-
39
- describe("constructor", () => {
40
- test("creates TarExtractor with fs and path", () => {
41
- const extractor = new TarExtractor(fsPromises, path);
42
-
43
- assert.ok(extractor instanceof TarExtractor);
44
- });
45
-
46
- test("validates fs parameter", () => {
47
- assert.throws(() => new TarExtractor(), {
48
- message: /fs dependency is required/,
49
- });
50
- assert.throws(() => new TarExtractor(null, path), {
51
- message: /fs dependency is required/,
52
- });
53
- });
54
-
55
- test("validates path parameter", () => {
56
- assert.throws(() => new TarExtractor(fsPromises), {
57
- message: /path dependency is required/,
58
- });
59
- assert.throws(() => new TarExtractor(fsPromises, null), {
60
- message: /path dependency is required/,
61
- });
62
- });
63
- });
64
-
65
- describe("extract", () => {
66
- test("extracts tar.gz archive to specified directory", async () => {
67
- const tarPath = path.join(fixturesDir, "sample.tar.gz");
68
- const outputDir = path.join(tempDir, "tar-output");
69
-
70
- await extractor.extract(tarPath, outputDir);
71
-
72
- // Verify extracted files exist
73
- assert.ok(fs.existsSync(path.join(outputDir, "test.txt")));
74
- assert.ok(fs.existsSync(path.join(outputDir, "nested.txt")));
75
- assert.ok(fs.existsSync(path.join(outputDir, "subdir", "deep.txt")));
76
- });
77
-
78
- test("extracts files with correct content", async () => {
79
- const tarPath = path.join(fixturesDir, "sample.tar.gz");
80
- const outputDir = path.join(tempDir, "tar-content");
81
-
82
- await extractor.extract(tarPath, outputDir);
83
-
84
- // Verify file contents
85
- const testContent = await fsPromises.readFile(
86
- path.join(outputDir, "test.txt"),
87
- "utf8",
88
- );
89
- assert.strictEqual(testContent.trim(), "Hello from root file");
90
-
91
- const nestedContent = await fsPromises.readFile(
92
- path.join(outputDir, "nested.txt"),
93
- "utf8",
94
- );
95
- assert.strictEqual(nestedContent.trim(), "Nested content");
96
-
97
- const deepContent = await fsPromises.readFile(
98
- path.join(outputDir, "subdir", "deep.txt"),
99
- "utf8",
100
- );
101
- assert.strictEqual(deepContent.trim(), "Deep file content");
102
- });
103
-
104
- test("creates nested directory structure", async () => {
105
- const tarPath = path.join(fixturesDir, "sample.tar.gz");
106
- const outputDir = path.join(tempDir, "tar-structure");
107
-
108
- await extractor.extract(tarPath, outputDir);
109
-
110
- // Verify directory structure
111
- const subdirPath = path.join(outputDir, "subdir");
112
- assert.ok(fs.existsSync(subdirPath));
113
-
114
- const stats = await fsPromises.stat(subdirPath);
115
- assert.ok(stats.isDirectory());
116
- });
117
-
118
- test("handles non-existent output directory", async () => {
119
- const tarPath = path.join(fixturesDir, "sample.tar.gz");
120
- const outputDir = path.join(tempDir, "deep", "nested", "tar-output");
121
-
122
- // Should not throw - should create directory structure
123
- await extractor.extract(tarPath, outputDir);
124
-
125
- assert.ok(fs.existsSync(path.join(outputDir, "test.txt")));
126
- });
127
- });
128
- });
129
-
130
- describe("ZipExtractor", () => {
131
- let extractor;
132
- let tempDir;
133
- let fixturesDir;
134
-
135
- beforeEach(() => {
136
- extractor = new ZipExtractor(fsPromises, path);
137
-
138
- // Setup directories
139
- const __filename = fileURLToPath(import.meta.url);
140
- const __dirname = path.dirname(__filename);
141
- fixturesDir = path.join(__dirname, "fixtures");
142
- tempDir = path.join(__dirname, ".tmp-extractor-test");
143
-
144
- // Clean up any existing temp directory
145
- if (fs.existsSync(tempDir)) {
146
- fs.rmSync(tempDir, { recursive: true, force: true });
147
- }
148
- fs.mkdirSync(tempDir, { recursive: true });
149
- });
150
-
151
- afterEach(() => {
152
- // Clean up temp directory
153
- if (fs.existsSync(tempDir)) {
154
- fs.rmSync(tempDir, { recursive: true, force: true });
155
- }
156
- });
157
-
158
- describe("constructor", () => {
159
- test("creates ZipExtractor with fs and path", () => {
160
- const extractor = new ZipExtractor(fsPromises, path);
161
-
162
- assert.ok(extractor instanceof ZipExtractor);
163
- });
164
-
165
- test("validates fs parameter", () => {
166
- assert.throws(() => new ZipExtractor(), {
167
- message: /fs dependency is required/,
168
- });
169
- assert.throws(() => new ZipExtractor(null, path), {
170
- message: /fs dependency is required/,
171
- });
172
- });
173
-
174
- test("validates path parameter", () => {
175
- assert.throws(() => new ZipExtractor(fsPromises), {
176
- message: /path dependency is required/,
177
- });
178
- assert.throws(() => new ZipExtractor(fsPromises, null), {
179
- message: /path dependency is required/,
180
- });
181
- });
182
- });
183
-
184
- describe("extract", () => {
185
- test("extracts zip archive to specified directory", async () => {
186
- const zipPath = path.join(fixturesDir, "sample.zip");
187
- const outputDir = path.join(tempDir, "zip-output");
188
-
189
- await extractor.extract(zipPath, outputDir);
190
-
191
- // Verify extracted files exist
192
- assert.ok(fs.existsSync(path.join(outputDir, "test.txt")));
193
- assert.ok(fs.existsSync(path.join(outputDir, "nested.txt")));
194
- assert.ok(fs.existsSync(path.join(outputDir, "subdir", "deep.txt")));
195
- });
196
-
197
- test("extracts files with correct content", async () => {
198
- const zipPath = path.join(fixturesDir, "sample.zip");
199
- const outputDir = path.join(tempDir, "zip-content");
200
-
201
- await extractor.extract(zipPath, outputDir);
202
-
203
- // Verify file contents
204
- const testContent = await fsPromises.readFile(
205
- path.join(outputDir, "test.txt"),
206
- "utf8",
207
- );
208
- assert.strictEqual(testContent.trim(), "Hello from root file");
209
-
210
- const nestedContent = await fsPromises.readFile(
211
- path.join(outputDir, "nested.txt"),
212
- "utf8",
213
- );
214
- assert.strictEqual(nestedContent.trim(), "Nested content");
215
-
216
- const deepContent = await fsPromises.readFile(
217
- path.join(outputDir, "subdir", "deep.txt"),
218
- "utf8",
219
- );
220
- assert.strictEqual(deepContent.trim(), "Deep file content");
221
- });
222
-
223
- test("creates nested directory structure", async () => {
224
- const zipPath = path.join(fixturesDir, "sample.zip");
225
- const outputDir = path.join(tempDir, "zip-structure");
226
-
227
- await extractor.extract(zipPath, outputDir);
228
-
229
- // Verify directory structure
230
- const subdirPath = path.join(outputDir, "subdir");
231
- assert.ok(fs.existsSync(subdirPath));
232
-
233
- const stats = await fsPromises.stat(subdirPath);
234
- assert.ok(stats.isDirectory());
235
- });
236
-
237
- test("handles non-existent output directory", async () => {
238
- const zipPath = path.join(fixturesDir, "sample.zip");
239
- const outputDir = path.join(tempDir, "deep", "nested", "zip-output");
240
-
241
- // Should not throw - should create directory structure
242
- await extractor.extract(zipPath, outputDir);
243
-
244
- assert.ok(fs.existsSync(path.join(outputDir, "test.txt")));
245
- });
246
-
247
- test("throws error for invalid zip file", async () => {
248
- const invalidZipPath = path.join(tempDir, "invalid.zip");
249
-
250
- // Create an invalid zip file (just some random bytes)
251
- await fsPromises.writeFile(invalidZipPath, Buffer.from([0, 1, 2, 3, 4]));
252
-
253
- const outputDir = path.join(tempDir, "invalid-output");
254
-
255
- await assert.rejects(
256
- async () => {
257
- await extractor.extract(invalidZipPath, outputDir);
258
- },
259
- {
260
- message: /Invalid ZIP file/,
261
- },
262
- );
263
- });
264
- });
265
-
266
- describe("format comparison", () => {
267
- test("both extractors produce identical file content", async () => {
268
- const tarPath = path.join(fixturesDir, "sample.tar.gz");
269
- const zipPath = path.join(fixturesDir, "sample.zip");
270
- const tarOutput = path.join(tempDir, "tar-compare");
271
- const zipOutput = path.join(tempDir, "zip-compare");
272
-
273
- const tarExtractor = new TarExtractor(fsPromises, path);
274
- const zipExtractor = new ZipExtractor(fsPromises, path);
275
-
276
- await tarExtractor.extract(tarPath, tarOutput);
277
- await zipExtractor.extract(zipPath, zipOutput);
278
-
279
- // Compare file contents
280
- const tarTest = await fsPromises.readFile(
281
- path.join(tarOutput, "test.txt"),
282
- "utf8",
283
- );
284
- const zipTest = await fsPromises.readFile(
285
- path.join(zipOutput, "test.txt"),
286
- "utf8",
287
- );
288
- assert.strictEqual(tarTest, zipTest);
289
-
290
- const tarNested = await fsPromises.readFile(
291
- path.join(tarOutput, "nested.txt"),
292
- "utf8",
293
- );
294
- const zipNested = await fsPromises.readFile(
295
- path.join(zipOutput, "nested.txt"),
296
- "utf8",
297
- );
298
- assert.strictEqual(tarNested, zipNested);
299
-
300
- const tarDeep = await fsPromises.readFile(
301
- path.join(tarOutput, "subdir", "deep.txt"),
302
- "utf8",
303
- );
304
- const zipDeep = await fsPromises.readFile(
305
- path.join(zipOutput, "subdir", "deep.txt"),
306
- "utf8",
307
- );
308
- assert.strictEqual(tarDeep, zipDeep);
309
- });
310
-
311
- test("both extractors create identical directory structure", async () => {
312
- const tarPath = path.join(fixturesDir, "sample.tar.gz");
313
- const zipPath = path.join(fixturesDir, "sample.zip");
314
- const tarOutput = path.join(tempDir, "tar-struct");
315
- const zipOutput = path.join(tempDir, "zip-struct");
316
-
317
- const tarExtractor = new TarExtractor(fsPromises, path);
318
- const zipExtractor = new ZipExtractor(fsPromises, path);
319
-
320
- await tarExtractor.extract(tarPath, tarOutput);
321
- await zipExtractor.extract(zipPath, zipOutput);
322
-
323
- // Check that both have the subdir
324
- assert.ok(fs.existsSync(path.join(tarOutput, "subdir")));
325
- assert.ok(fs.existsSync(path.join(zipOutput, "subdir")));
326
-
327
- // Check that both subdirs are directories
328
- const tarSubdirStats = await fsPromises.stat(
329
- path.join(tarOutput, "subdir"),
330
- );
331
- const zipSubdirStats = await fsPromises.stat(
332
- path.join(zipOutput, "subdir"),
333
- );
334
- assert.ok(tarSubdirStats.isDirectory());
335
- assert.ok(zipSubdirStats.isDirectory());
336
- });
337
- });
338
- });