@indigoai-us/hq-cloud 5.11.3 → 5.13.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.
- package/dist/bin/sync-runner.d.ts +11 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +71 -4
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +124 -0
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/share.d.ts +17 -0
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +11 -3
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +125 -0
- package/dist/cli/share.test.js.map +1 -1
- package/dist/ignore.d.ts.map +1 -1
- package/dist/ignore.js +5 -1
- package/dist/ignore.js.map +1 -1
- package/dist/ignore.test.js +8 -3
- package/dist/ignore.test.js.map +1 -1
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +7 -2
- package/dist/s3.js.map +1 -1
- package/dist/s3.test.js +45 -1
- package/dist/s3.test.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/sync-runner.test.ts +158 -0
- package/src/bin/sync-runner.ts +72 -4
- package/src/cli/share.test.ts +146 -0
- package/src/cli/share.ts +28 -3
- package/src/ignore.test.ts +8 -3
- package/src/ignore.ts +5 -1
- package/src/s3.test.ts +53 -1
- package/src/s3.ts +7 -2
package/src/s3.test.ts
CHANGED
|
@@ -15,6 +15,11 @@ import * as path from "path";
|
|
|
15
15
|
// in beforeEach so per-test assertions don't leak from neighbours.
|
|
16
16
|
const sentCommands: Array<{ name: string; input: Record<string, unknown> }> = [];
|
|
17
17
|
|
|
18
|
+
// Per-test override for the ListObjectsV2Command response. Tests of
|
|
19
|
+
// `listRemoteFiles` push the Contents shape they want returned; the default
|
|
20
|
+
// is an empty bucket. Reset in beforeEach so cross-test leakage is impossible.
|
|
21
|
+
let nextListObjectsResponse: Record<string, unknown> = { Contents: [] };
|
|
22
|
+
|
|
18
23
|
vi.mock("@aws-sdk/client-s3", () => {
|
|
19
24
|
class FakeS3Client {
|
|
20
25
|
async send(command: { constructor: { name: string }; input: Record<string, unknown> }): Promise<Record<string, unknown>> {
|
|
@@ -27,6 +32,9 @@ vi.mock("@aws-sdk/client-s3", () => {
|
|
|
27
32
|
if (command.constructor.name === "PutObjectCommand") {
|
|
28
33
|
return { ETag: '"fake-etag"' };
|
|
29
34
|
}
|
|
35
|
+
if (command.constructor.name === "ListObjectsV2Command") {
|
|
36
|
+
return nextListObjectsResponse;
|
|
37
|
+
}
|
|
30
38
|
return {};
|
|
31
39
|
}
|
|
32
40
|
}
|
|
@@ -58,7 +66,7 @@ vi.mock("@aws-sdk/client-s3", () => {
|
|
|
58
66
|
};
|
|
59
67
|
});
|
|
60
68
|
|
|
61
|
-
import { uploadFile } from "./s3.js";
|
|
69
|
+
import { uploadFile, listRemoteFiles } from "./s3.js";
|
|
62
70
|
import type { EntityContext } from "./types.js";
|
|
63
71
|
|
|
64
72
|
function makeCtx(): EntityContext {
|
|
@@ -164,3 +172,47 @@ describe("uploadFile", () => {
|
|
|
164
172
|
expect(meta["created-by-sub"]).toBeUndefined();
|
|
165
173
|
});
|
|
166
174
|
});
|
|
175
|
+
|
|
176
|
+
describe("listRemoteFiles", () => {
|
|
177
|
+
beforeEach(() => {
|
|
178
|
+
sentCommands.length = 0;
|
|
179
|
+
nextListObjectsResponse = { Contents: [] };
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Regression: pre-fix, `if (!obj.Key || !obj.Size) continue;` skipped every
|
|
183
|
+
// 0-byte object on the listing. The user's personal vault stored
|
|
184
|
+
// `projects/.gitkeep` (a 0-byte placeholder) and the sync pull plan never
|
|
185
|
+
// saw it — the file existed on S3 but was invisible to the runner, so the
|
|
186
|
+
// box never created `projects/` on first pull. The fix narrows the guard
|
|
187
|
+
// to `if (!obj.Key) continue;` so Size === 0 passes through with its real
|
|
188
|
+
// value (handled `?? 0` to keep TS Size?: number happy).
|
|
189
|
+
it("includes 0-byte objects in the result (no implicit skip-on-empty)", async () => {
|
|
190
|
+
nextListObjectsResponse = {
|
|
191
|
+
Contents: [
|
|
192
|
+
{ Key: "projects/.gitkeep", Size: 0, LastModified: new Date(), ETag: '"d41d8cd98f00b204e9800998ecf8427e"' },
|
|
193
|
+
{ Key: "AGENTS.md", Size: 100, LastModified: new Date(), ETag: '"e1"' },
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const files = await listRemoteFiles(makeCtx());
|
|
198
|
+
|
|
199
|
+
expect(files).toHaveLength(2);
|
|
200
|
+
const gitkeep = files.find((f) => f.key === "projects/.gitkeep");
|
|
201
|
+
expect(gitkeep).toBeDefined();
|
|
202
|
+
expect(gitkeep!.size).toBe(0);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("still skips Contents entries with no Key (defensive against malformed responses)", async () => {
|
|
206
|
+
nextListObjectsResponse = {
|
|
207
|
+
Contents: [
|
|
208
|
+
{ Size: 100, LastModified: new Date(), ETag: '"e1"' }, // no Key
|
|
209
|
+
{ Key: "real.md", Size: 100, LastModified: new Date(), ETag: '"e2"' },
|
|
210
|
+
],
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const files = await listRemoteFiles(makeCtx());
|
|
214
|
+
|
|
215
|
+
expect(files).toHaveLength(1);
|
|
216
|
+
expect(files[0].key).toBe("real.md");
|
|
217
|
+
});
|
|
218
|
+
});
|
package/src/s3.ts
CHANGED
|
@@ -173,11 +173,16 @@ export async function listRemoteFiles(
|
|
|
173
173
|
);
|
|
174
174
|
|
|
175
175
|
for (const obj of response.Contents || []) {
|
|
176
|
-
|
|
176
|
+
// Pre-fix this guard was `!obj.Key || !obj.Size`. The `!obj.Size` test
|
|
177
|
+
// is truthy when Size === 0 (a real 0-byte object like `.gitkeep`),
|
|
178
|
+
// silently filtering legitimate placeholder files out of every pull
|
|
179
|
+
// plan. Narrow the guard to "no key" only; surface real 0-byte
|
|
180
|
+
// objects to the planner.
|
|
181
|
+
if (!obj.Key) continue;
|
|
177
182
|
|
|
178
183
|
files.push({
|
|
179
184
|
key: obj.Key,
|
|
180
|
-
size: obj.Size,
|
|
185
|
+
size: obj.Size ?? 0,
|
|
181
186
|
lastModified: obj.LastModified || new Date(),
|
|
182
187
|
etag: obj.ETag || "",
|
|
183
188
|
});
|