@indigoai-us/hq-cloud 5.1.0 → 5.1.9
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 +134 -0
- package/dist/bin/sync-runner.d.ts.map +1 -0
- package/dist/bin/sync-runner.js +360 -0
- package/dist/bin/sync-runner.js.map +1 -0
- package/dist/bin/sync-runner.test.d.ts +10 -0
- package/dist/bin/sync-runner.test.d.ts.map +1 -0
- package/dist/bin/sync-runner.test.js +648 -0
- package/dist/bin/sync-runner.test.js.map +1 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/share.js +2 -2
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +9 -1
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +28 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +33 -10
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +15 -4
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/cognito-auth.d.ts.map +1 -1
- package/dist/cognito-auth.js +19 -1
- package/dist/cognito-auth.js.map +1 -1
- package/dist/cognito-auth.test.d.ts +9 -0
- package/dist/cognito-auth.test.d.ts.map +1 -0
- package/dist/cognito-auth.test.js +113 -0
- package/dist/cognito-auth.test.js.map +1 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -0
- package/dist/context.js.map +1 -1
- package/dist/daemon-worker.d.ts +6 -1
- package/dist/daemon-worker.d.ts.map +1 -1
- package/dist/daemon-worker.js +12 -16
- package/dist/daemon-worker.js.map +1 -1
- package/dist/daemon.d.ts +2 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +2 -0
- package/dist/daemon.js.map +1 -1
- package/dist/ignore.d.ts +13 -2
- package/dist/ignore.d.ts.map +1 -1
- package/dist/ignore.js +69 -12
- package/dist/ignore.js.map +1 -1
- package/dist/index.d.ts +24 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -134
- package/dist/index.js.map +1 -1
- package/dist/journal.d.ts +20 -4
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +45 -8
- package/dist/journal.js.map +1 -1
- package/dist/journal.test.d.ts +9 -0
- package/dist/journal.test.d.ts.map +1 -0
- package/dist/journal.test.js +114 -0
- package/dist/journal.test.js.map +1 -0
- package/dist/s3.d.ts +18 -6
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +57 -56
- package/dist/s3.js.map +1 -1
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vault-client.d.ts +59 -0
- package/dist/vault-client.d.ts.map +1 -1
- package/dist/vault-client.js +72 -0
- package/dist/vault-client.js.map +1 -1
- package/dist/vault-client.test.js +160 -0
- package/dist/vault-client.test.js.map +1 -1
- package/dist/watcher.d.ts +7 -1
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +11 -5
- package/dist/watcher.js.map +1 -1
- package/package.json +15 -3
- package/src/bin/sync-runner.test.ts +804 -0
- package/src/bin/sync-runner.ts +499 -0
- package/src/cli/accept.ts +97 -0
- package/src/cli/conflict.ts +119 -0
- package/src/cli/index.ts +25 -0
- package/src/cli/invite.test.ts +247 -0
- package/src/cli/invite.ts +180 -0
- package/src/cli/promote.ts +123 -0
- package/src/cli/share.test.ts +155 -0
- package/src/cli/share.ts +212 -0
- package/src/cli/sync.test.ts +225 -0
- package/src/cli/sync.ts +225 -0
- package/src/cognito-auth.test.ts +156 -0
- package/src/cognito-auth.ts +18 -1
- package/src/context.test.ts +202 -0
- package/src/context.ts +178 -0
- package/src/daemon-worker.ts +13 -19
- package/src/daemon.ts +2 -0
- package/src/ignore.ts +76 -12
- package/src/index.ts +94 -165
- package/src/journal.test.ts +146 -0
- package/src/journal.ts +53 -11
- package/src/s3.ts +76 -66
- package/src/types.ts +37 -0
- package/src/vault-client.test.ts +563 -0
- package/src/vault-client.ts +478 -0
- package/src/watcher.ts +12 -5
- package/test/invite-flow.integration.test.ts +244 -0
- package/test/share-sync.integration.test.ts +210 -0
package/dist/index.js
CHANGED
|
@@ -1,138 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @indigoai-us/hq-cloud — public API
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* VLT-5: Entity-aware sync engine. Operations resolve their target bucket
|
|
5
|
+
* and credentials from the vault-service entity registry + STS vending.
|
|
4
6
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
console.log(" Setting up IndigoAI cloud sync...");
|
|
24
|
-
const creds = await authenticate();
|
|
25
|
-
console.log(` ✓ Authenticated as ${creds.userId}`);
|
|
26
|
-
console.log(` ✓ Bucket: ${creds.bucket}`);
|
|
27
|
-
console.log(` ✓ Region: ${creds.region}`);
|
|
28
|
-
console.log();
|
|
29
|
-
console.log(" Run 'hq sync start' to begin syncing.");
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Start the background sync daemon
|
|
33
|
-
*/
|
|
34
|
-
export async function startDaemon(hqRoot) {
|
|
35
|
-
if (!hasCredentials()) {
|
|
36
|
-
throw new Error("Not authenticated. Run 'hq sync init' first.");
|
|
37
|
-
}
|
|
38
|
-
_startDaemon(hqRoot);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Stop the background sync daemon
|
|
42
|
-
*/
|
|
43
|
-
export async function stopDaemon(hqRoot) {
|
|
44
|
-
_stopDaemon(hqRoot);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Get current sync status
|
|
48
|
-
*/
|
|
49
|
-
export async function getStatus(hqRoot) {
|
|
50
|
-
const journal = readJournal(hqRoot);
|
|
51
|
-
const creds = readCredentials();
|
|
52
|
-
const running = isDaemonRunning(hqRoot);
|
|
53
|
-
const errors = [];
|
|
54
|
-
if (!creds) {
|
|
55
|
-
errors.push("Not authenticated — run 'hq sync init'");
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
running,
|
|
59
|
-
lastSync: journal.lastSync || null,
|
|
60
|
-
fileCount: Object.keys(journal.files).length,
|
|
61
|
-
bucket: creds?.bucket || null,
|
|
62
|
-
errors,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Force push all local files to S3
|
|
67
|
-
*/
|
|
68
|
-
export async function pushAll(hqRoot) {
|
|
69
|
-
const shouldSync = createIgnoreFilter(hqRoot);
|
|
70
|
-
const journal = readJournal(hqRoot);
|
|
71
|
-
let filesUploaded = 0;
|
|
72
|
-
let bytesUploaded = 0;
|
|
73
|
-
const files = walkDir(hqRoot, hqRoot, shouldSync);
|
|
74
|
-
for (const { absolutePath, relativePath } of files) {
|
|
75
|
-
if (!isWithinSizeLimit(absolutePath))
|
|
76
|
-
continue;
|
|
77
|
-
try {
|
|
78
|
-
const hash = hashFile(absolutePath);
|
|
79
|
-
const stat = fs.statSync(absolutePath);
|
|
80
|
-
await uploadFile(absolutePath, relativePath);
|
|
81
|
-
updateEntry(journal, relativePath, hash, stat.size, "up");
|
|
82
|
-
filesUploaded++;
|
|
83
|
-
bytesUploaded += stat.size;
|
|
84
|
-
}
|
|
85
|
-
catch (err) {
|
|
86
|
-
console.error(` Failed: ${relativePath} — ${err instanceof Error ? err.message : err}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
writeJournal(hqRoot, journal);
|
|
90
|
-
return { filesUploaded, bytesUploaded };
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Force pull all remote files to local
|
|
94
|
-
*/
|
|
95
|
-
export async function pullAll(hqRoot) {
|
|
96
|
-
const journal = readJournal(hqRoot);
|
|
97
|
-
let filesDownloaded = 0;
|
|
98
|
-
let bytesDownloaded = 0;
|
|
99
|
-
const remoteFiles = await listRemoteFiles();
|
|
100
|
-
for (const file of remoteFiles) {
|
|
101
|
-
try {
|
|
102
|
-
const localPath = path.join(hqRoot, file.relativePath);
|
|
103
|
-
await downloadFile(file.relativePath, localPath);
|
|
104
|
-
const hash = hashFile(localPath);
|
|
105
|
-
updateEntry(journal, file.relativePath, hash, file.size, "down");
|
|
106
|
-
filesDownloaded++;
|
|
107
|
-
bytesDownloaded += file.size;
|
|
108
|
-
}
|
|
109
|
-
catch (err) {
|
|
110
|
-
console.error(` Failed: ${file.relativePath} — ${err instanceof Error ? err.message : err}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
writeJournal(hqRoot, journal);
|
|
114
|
-
return { filesDownloaded, bytesDownloaded };
|
|
115
|
-
}
|
|
116
|
-
// Helper: recursively walk a directory
|
|
117
|
-
function walkDir(dir, root, filter) {
|
|
118
|
-
const results = [];
|
|
119
|
-
if (!fs.existsSync(dir))
|
|
120
|
-
return results;
|
|
121
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
122
|
-
for (const entry of entries) {
|
|
123
|
-
const absolutePath = path.join(dir, entry.name);
|
|
124
|
-
if (!filter(absolutePath))
|
|
125
|
-
continue;
|
|
126
|
-
if (entry.isDirectory()) {
|
|
127
|
-
results.push(...walkDir(absolutePath, root, filter));
|
|
128
|
-
}
|
|
129
|
-
else if (entry.isFile()) {
|
|
130
|
-
results.push({
|
|
131
|
-
absolutePath,
|
|
132
|
-
relativePath: path.relative(root, absolutePath),
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return results;
|
|
137
|
-
}
|
|
7
|
+
export { resolveEntityContext, refreshEntityContext, clearContextCache, isExpiringSoon, } from "./context.js";
|
|
8
|
+
export { uploadFile, downloadFile, listRemoteFiles, deleteRemoteFile, headRemoteFile, } from "./s3.js";
|
|
9
|
+
export { readJournal, writeJournal, hashFile, updateEntry, getEntry, removeEntry, getJournalPath, } from "./journal.js";
|
|
10
|
+
export { createIgnoreFilter, isWithinSizeLimit, } from "./ignore.js";
|
|
11
|
+
// Cognito browser-OAuth (VLT-9)
|
|
12
|
+
export { browserLogin, refreshTokens, loadCachedTokens, saveCachedTokens, clearCachedTokens, isExpiring, getValidAccessToken, CognitoAuthError, } from "./cognito-auth.js";
|
|
13
|
+
// VaultClient SDK (VLT-7)
|
|
14
|
+
export { VaultClient } from "./vault-client.js";
|
|
15
|
+
export { VaultClientError, VaultAuthError, VaultPermissionDeniedError, VaultNotFoundError, VaultConflictError, } from "./vault-client.js";
|
|
16
|
+
// CLI commands
|
|
17
|
+
export { share, sync } from "./cli/index.js";
|
|
18
|
+
export { resolveConflict, showDiff } from "./cli/index.js";
|
|
19
|
+
// Membership CLI commands (VLT-7)
|
|
20
|
+
export { invite, listInvites, revokeInvite } from "./cli/index.js";
|
|
21
|
+
export { accept, parseToken } from "./cli/index.js";
|
|
22
|
+
export { promote } from "./cli/index.js";
|
|
138
23
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,UAAU,EACV,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,cAAc,GACf,MAAM,SAAS,CAAC;AAIjB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,WAAW,EACX,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAErB,gCAAgC;AAChC,OAAO,EACL,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAG3B,0BAA0B;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,0BAA0B,EAC1B,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAwB3B,eAAe;AACf,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG3D,kCAAkC;AAClC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/journal.d.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sync journal — tracks file state
|
|
2
|
+
* Sync journal — tracks per-file state (hash, size, last-synced direction) so
|
|
3
|
+
* sync/share can detect local edits that would be clobbered by a blind pull.
|
|
4
|
+
*
|
|
5
|
+
* ADR-0001 Phase 5: the journal is sharded by company slug and lives in
|
|
6
|
+
* `~/.hq/`, not inside the HQ content root. One monolithic journal per HQ
|
|
7
|
+
* install conflates state across companies and forces every runner to
|
|
8
|
+
* serialize through the same file — splitting it lets `hq-sync-runner
|
|
9
|
+
* --companies` fan out without contention, and a corrupted shard only affects
|
|
10
|
+
* one company.
|
|
11
|
+
*
|
|
12
|
+
* Path: `{stateDir}/sync-journal.{slug}.json`, where `stateDir` resolves to
|
|
13
|
+
* `HQ_STATE_DIR` (if set) or `~/.hq`.
|
|
3
14
|
*/
|
|
4
15
|
import type { SyncJournal, JournalEntry } from "./types.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Where per-company journals are stored. Honors `HQ_STATE_DIR` for tests and
|
|
18
|
+
* non-standard installs; otherwise falls back to `~/.hq`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getStateDir(): string;
|
|
21
|
+
export declare function getJournalPath(slug: string): string;
|
|
22
|
+
export declare function readJournal(slug: string): SyncJournal;
|
|
23
|
+
export declare function writeJournal(slug: string, journal: SyncJournal): void;
|
|
8
24
|
export declare function hashFile(filePath: string): string;
|
|
9
25
|
export declare function updateEntry(journal: SyncJournal, relativePath: string, hash: string, size: number, direction: "up" | "down"): void;
|
|
10
26
|
export declare function getEntry(journal: SyncJournal, relativePath: string): JournalEntry | undefined;
|
package/dist/journal.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK5D;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAmBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAOrD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAIrE;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,IAAI,GAAG,MAAM,GACvB,IAAI,CAQN;AAED,wBAAgB,QAAQ,CACtB,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,MAAM,GACnB,YAAY,GAAG,SAAS,CAE1B;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,MAAM,GACnB,IAAI,CAEN"}
|
package/dist/journal.js
CHANGED
|
@@ -1,23 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sync journal — tracks file state
|
|
2
|
+
* Sync journal — tracks per-file state (hash, size, last-synced direction) so
|
|
3
|
+
* sync/share can detect local edits that would be clobbered by a blind pull.
|
|
4
|
+
*
|
|
5
|
+
* ADR-0001 Phase 5: the journal is sharded by company slug and lives in
|
|
6
|
+
* `~/.hq/`, not inside the HQ content root. One monolithic journal per HQ
|
|
7
|
+
* install conflates state across companies and forces every runner to
|
|
8
|
+
* serialize through the same file — splitting it lets `hq-sync-runner
|
|
9
|
+
* --companies` fan out without contention, and a corrupted shard only affects
|
|
10
|
+
* one company.
|
|
11
|
+
*
|
|
12
|
+
* Path: `{stateDir}/sync-journal.{slug}.json`, where `stateDir` resolves to
|
|
13
|
+
* `HQ_STATE_DIR` (if set) or `~/.hq`.
|
|
3
14
|
*/
|
|
4
15
|
import * as fs from "fs";
|
|
16
|
+
import * as os from "os";
|
|
5
17
|
import * as path from "path";
|
|
6
18
|
import * as crypto from "crypto";
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
19
|
+
const JOURNAL_FILE_PREFIX = "sync-journal.";
|
|
20
|
+
const JOURNAL_FILE_SUFFIX = ".json";
|
|
21
|
+
/**
|
|
22
|
+
* Where per-company journals are stored. Honors `HQ_STATE_DIR` for tests and
|
|
23
|
+
* non-standard installs; otherwise falls back to `~/.hq`.
|
|
24
|
+
*/
|
|
25
|
+
export function getStateDir() {
|
|
26
|
+
return process.env.HQ_STATE_DIR ?? path.join(os.homedir(), ".hq");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Filename-safe form of a slug. Slugs from vault-service are already
|
|
30
|
+
* URL-safe, but this guards against paths, dots, or anything the filesystem
|
|
31
|
+
* might interpret. Empty-or-invalid slugs throw rather than silently writing
|
|
32
|
+
* to a shared "sync-journal..json" file.
|
|
33
|
+
*/
|
|
34
|
+
function sanitizeSlug(slug) {
|
|
35
|
+
if (!slug) {
|
|
36
|
+
throw new Error("journal: slug is required (empty or undefined)");
|
|
37
|
+
}
|
|
38
|
+
const cleaned = slug.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
39
|
+
if (!cleaned || /^[_-]+$/.test(cleaned)) {
|
|
40
|
+
throw new Error(`journal: slug "${slug}" sanitizes to an empty identifier`);
|
|
41
|
+
}
|
|
42
|
+
return cleaned;
|
|
43
|
+
}
|
|
44
|
+
export function getJournalPath(slug) {
|
|
45
|
+
return path.join(getStateDir(), `${JOURNAL_FILE_PREFIX}${sanitizeSlug(slug)}${JOURNAL_FILE_SUFFIX}`);
|
|
10
46
|
}
|
|
11
|
-
export function readJournal(
|
|
12
|
-
const journalPath = getJournalPath(
|
|
47
|
+
export function readJournal(slug) {
|
|
48
|
+
const journalPath = getJournalPath(slug);
|
|
13
49
|
if (fs.existsSync(journalPath)) {
|
|
14
50
|
const content = fs.readFileSync(journalPath, "utf-8");
|
|
15
51
|
return JSON.parse(content);
|
|
16
52
|
}
|
|
17
53
|
return { version: "1", lastSync: "", files: {} };
|
|
18
54
|
}
|
|
19
|
-
export function writeJournal(
|
|
20
|
-
const journalPath = getJournalPath(
|
|
55
|
+
export function writeJournal(slug, journal) {
|
|
56
|
+
const journalPath = getJournalPath(slug);
|
|
57
|
+
fs.mkdirSync(path.dirname(journalPath), { recursive: true });
|
|
21
58
|
fs.writeFileSync(journalPath, JSON.stringify(journal, null, 2));
|
|
22
59
|
}
|
|
23
60
|
export function hashFile(filePath) {
|
package/dist/journal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the sync journal (ADR-0001 Phase 5).
|
|
3
|
+
*
|
|
4
|
+
* Verifies per-company isolation, HQ_STATE_DIR override, and filename
|
|
5
|
+
* sanitization — all behaviors that the pre-Phase-5 monolithic journal
|
|
6
|
+
* didn't need.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=journal.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.test.d.ts","sourceRoot":"","sources":["../src/journal.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the sync journal (ADR-0001 Phase 5).
|
|
3
|
+
*
|
|
4
|
+
* Verifies per-company isolation, HQ_STATE_DIR override, and filename
|
|
5
|
+
* sanitization — all behaviors that the pre-Phase-5 monolithic journal
|
|
6
|
+
* didn't need.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as os from "os";
|
|
11
|
+
import * as path from "path";
|
|
12
|
+
import { getJournalPath, getStateDir, readJournal, writeJournal, updateEntry, } from "./journal.js";
|
|
13
|
+
describe("journal", () => {
|
|
14
|
+
let stateDir;
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-journal-test-"));
|
|
17
|
+
process.env.HQ_STATE_DIR = stateDir;
|
|
18
|
+
});
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
fs.rmSync(stateDir, { recursive: true, force: true });
|
|
21
|
+
delete process.env.HQ_STATE_DIR;
|
|
22
|
+
});
|
|
23
|
+
describe("getStateDir", () => {
|
|
24
|
+
it("honors HQ_STATE_DIR env var", () => {
|
|
25
|
+
expect(getStateDir()).toBe(stateDir);
|
|
26
|
+
});
|
|
27
|
+
it("falls back to ~/.hq when env var unset", () => {
|
|
28
|
+
delete process.env.HQ_STATE_DIR;
|
|
29
|
+
expect(getStateDir()).toBe(path.join(os.homedir(), ".hq"));
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe("getJournalPath", () => {
|
|
33
|
+
it("produces a per-slug filename", () => {
|
|
34
|
+
expect(getJournalPath("indigo")).toBe(path.join(stateDir, "sync-journal.indigo.json"));
|
|
35
|
+
});
|
|
36
|
+
it("isolates different slugs into different files", () => {
|
|
37
|
+
expect(getJournalPath("indigo")).not.toBe(getJournalPath("brandstage"));
|
|
38
|
+
});
|
|
39
|
+
it("sanitizes path-unsafe characters", () => {
|
|
40
|
+
expect(getJournalPath("foo/bar")).toBe(path.join(stateDir, "sync-journal.foo_bar.json"));
|
|
41
|
+
expect(getJournalPath("../escape")).toBe(path.join(stateDir, "sync-journal.___escape.json"));
|
|
42
|
+
});
|
|
43
|
+
it("throws on empty slug", () => {
|
|
44
|
+
expect(() => getJournalPath("")).toThrow(/slug is required/);
|
|
45
|
+
});
|
|
46
|
+
it("throws on slug that sanitizes to empty", () => {
|
|
47
|
+
expect(() => getJournalPath("///")).toThrow(/empty identifier/);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe("readJournal", () => {
|
|
51
|
+
it("returns an empty journal when the file doesn't exist", () => {
|
|
52
|
+
const j = readJournal("indigo");
|
|
53
|
+
expect(j.version).toBe("1");
|
|
54
|
+
expect(j.files).toEqual({});
|
|
55
|
+
expect(j.lastSync).toBe("");
|
|
56
|
+
});
|
|
57
|
+
it("reads a journal written with writeJournal", () => {
|
|
58
|
+
const original = {
|
|
59
|
+
version: "1",
|
|
60
|
+
lastSync: "2026-04-19T00:00:00.000Z",
|
|
61
|
+
files: {
|
|
62
|
+
"docs/handoff.md": {
|
|
63
|
+
hash: "abc123",
|
|
64
|
+
size: 42,
|
|
65
|
+
syncedAt: "2026-04-19T00:00:00.000Z",
|
|
66
|
+
direction: "down",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
writeJournal("indigo", original);
|
|
71
|
+
const roundTripped = readJournal("indigo");
|
|
72
|
+
expect(roundTripped).toEqual(original);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe("writeJournal", () => {
|
|
76
|
+
it("creates the state directory if it doesn't exist", () => {
|
|
77
|
+
const nestedDir = path.join(stateDir, "nested", "deep");
|
|
78
|
+
process.env.HQ_STATE_DIR = nestedDir;
|
|
79
|
+
expect(fs.existsSync(nestedDir)).toBe(false);
|
|
80
|
+
writeJournal("indigo", { version: "1", lastSync: "", files: {} });
|
|
81
|
+
expect(fs.existsSync(nestedDir)).toBe(true);
|
|
82
|
+
expect(fs.existsSync(path.join(nestedDir, "sync-journal.indigo.json"))).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
it("keeps per-company journals independent", () => {
|
|
85
|
+
writeJournal("indigo", {
|
|
86
|
+
version: "1",
|
|
87
|
+
lastSync: "",
|
|
88
|
+
files: { "a.md": { hash: "1", size: 1, syncedAt: "", direction: "up" } },
|
|
89
|
+
});
|
|
90
|
+
writeJournal("brandstage", {
|
|
91
|
+
version: "1",
|
|
92
|
+
lastSync: "",
|
|
93
|
+
files: { "b.md": { hash: "2", size: 2, syncedAt: "", direction: "up" } },
|
|
94
|
+
});
|
|
95
|
+
const indigo = readJournal("indigo");
|
|
96
|
+
const brandstage = readJournal("brandstage");
|
|
97
|
+
expect(indigo.files).toHaveProperty("a.md");
|
|
98
|
+
expect(indigo.files).not.toHaveProperty("b.md");
|
|
99
|
+
expect(brandstage.files).toHaveProperty("b.md");
|
|
100
|
+
expect(brandstage.files).not.toHaveProperty("a.md");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe("updateEntry", () => {
|
|
104
|
+
it("stamps lastSync and the per-file syncedAt", () => {
|
|
105
|
+
const j = { version: "1", lastSync: "", files: {} };
|
|
106
|
+
updateEntry(j, "foo.md", "hash", 10, "up");
|
|
107
|
+
expect(j.files["foo.md"]?.hash).toBe("hash");
|
|
108
|
+
expect(j.files["foo.md"]?.direction).toBe("up");
|
|
109
|
+
expect(j.lastSync).not.toBe("");
|
|
110
|
+
expect(j.files["foo.md"]?.syncedAt).not.toBe("");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
//# sourceMappingURL=journal.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.test.js","sourceRoot":"","sources":["../src/journal.test.ts"],"names":[],"mappings":"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"}
|
package/dist/s3.d.ts
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* S3 operations — upload, download, list, delete
|
|
2
|
+
* S3 operations — upload, download, list, delete.
|
|
3
|
+
*
|
|
4
|
+
* VLT-5: All operations now accept an EntityContext (entity-aware bucket +
|
|
5
|
+
* STS-scoped credentials) instead of reading static env config. The caller
|
|
6
|
+
* is responsible for resolving the context via resolveEntityContext().
|
|
3
7
|
*/
|
|
4
|
-
|
|
5
|
-
export declare function
|
|
8
|
+
import type { EntityContext } from "./types.js";
|
|
9
|
+
export declare function uploadFile(ctx: EntityContext, localPath: string, key: string): Promise<void>;
|
|
10
|
+
export declare function downloadFile(ctx: EntityContext, key: string, localPath: string): Promise<void>;
|
|
6
11
|
export interface RemoteFile {
|
|
7
12
|
key: string;
|
|
8
|
-
relativePath: string;
|
|
9
13
|
size: number;
|
|
10
14
|
lastModified: Date;
|
|
11
15
|
etag: string;
|
|
12
16
|
}
|
|
13
|
-
export declare function listRemoteFiles(): Promise<RemoteFile[]>;
|
|
14
|
-
export declare function deleteRemoteFile(
|
|
17
|
+
export declare function listRemoteFiles(ctx: EntityContext, prefix?: string): Promise<RemoteFile[]>;
|
|
18
|
+
export declare function deleteRemoteFile(ctx: EntityContext, key: string): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Check if a remote key exists and return its metadata.
|
|
21
|
+
*/
|
|
22
|
+
export declare function headRemoteFile(ctx: EntityContext, key: string): Promise<{
|
|
23
|
+
lastModified: Date;
|
|
24
|
+
etag: string;
|
|
25
|
+
size: number;
|
|
26
|
+
} | null>;
|
|
15
27
|
//# sourceMappingURL=s3.d.ts.map
|
package/dist/s3.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../src/s3.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../src/s3.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAkBhD,wBAAsB,UAAU,CAC9B,GAAG,EAAE,aAAa,EAClB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,IAAI,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,aAAa,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,UAAU,EAAE,CAAC,CA6BvB;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAoBpE"}
|
package/dist/s3.js
CHANGED
|
@@ -1,59 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* S3 operations — upload, download, list, delete
|
|
2
|
+
* S3 operations — upload, download, list, delete.
|
|
3
|
+
*
|
|
4
|
+
* VLT-5: All operations now accept an EntityContext (entity-aware bucket +
|
|
5
|
+
* STS-scoped credentials) instead of reading static env config. The caller
|
|
6
|
+
* is responsible for resolving the context via resolveEntityContext().
|
|
3
7
|
*/
|
|
4
8
|
import * as fs from "fs";
|
|
5
9
|
import * as path from "path";
|
|
6
|
-
import { S3Client, PutObjectCommand, GetObjectCommand, ListObjectsV2Command, DeleteObjectCommand, } from "@aws-sdk/client-s3";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
let creds = readCredentials();
|
|
22
|
-
if (!creds) {
|
|
23
|
-
throw new Error("Not authenticated. Run 'hq sync init' first.");
|
|
24
|
-
}
|
|
25
|
-
// Refresh if expired or missing access key
|
|
26
|
-
if (!creds.accessKeyId || (creds.expiration && new Date(creds.expiration) < new Date())) {
|
|
27
|
-
creds = await refreshAwsCredentials(creds);
|
|
28
|
-
}
|
|
29
|
-
if (!s3Client) {
|
|
30
|
-
s3Client = new S3Client({
|
|
31
|
-
region: creds.region,
|
|
32
|
-
credentials: {
|
|
33
|
-
accessKeyId: creds.accessKeyId,
|
|
34
|
-
secretAccessKey: creds.secretAccessKey,
|
|
35
|
-
sessionToken: creds.sessionToken,
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
return { client: s3Client, config: getConfig(creds) };
|
|
10
|
+
import { S3Client, PutObjectCommand, GetObjectCommand, ListObjectsV2Command, DeleteObjectCommand, HeadObjectCommand, } from "@aws-sdk/client-s3";
|
|
11
|
+
/**
|
|
12
|
+
* Build an S3Client from an EntityContext's STS-scoped credentials.
|
|
13
|
+
* A new client is created each time to ensure fresh credentials are used
|
|
14
|
+
* (the caller handles caching/refresh at the EntityContext level).
|
|
15
|
+
*/
|
|
16
|
+
function buildClient(ctx) {
|
|
17
|
+
return new S3Client({
|
|
18
|
+
region: ctx.region,
|
|
19
|
+
credentials: {
|
|
20
|
+
accessKeyId: ctx.credentials.accessKeyId,
|
|
21
|
+
secretAccessKey: ctx.credentials.secretAccessKey,
|
|
22
|
+
sessionToken: ctx.credentials.sessionToken,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
40
25
|
}
|
|
41
|
-
export async function uploadFile(localPath,
|
|
42
|
-
const
|
|
43
|
-
const key = `${config.prefix}${relativePath}`;
|
|
26
|
+
export async function uploadFile(ctx, localPath, key) {
|
|
27
|
+
const client = buildClient(ctx);
|
|
44
28
|
const body = fs.readFileSync(localPath);
|
|
45
29
|
await client.send(new PutObjectCommand({
|
|
46
|
-
Bucket:
|
|
30
|
+
Bucket: ctx.bucketName,
|
|
47
31
|
Key: key,
|
|
48
32
|
Body: body,
|
|
49
|
-
ContentType: getMimeType(
|
|
33
|
+
ContentType: getMimeType(key),
|
|
50
34
|
}));
|
|
51
35
|
}
|
|
52
|
-
export async function downloadFile(
|
|
53
|
-
const
|
|
54
|
-
const key = `${config.prefix}${relativePath}`;
|
|
36
|
+
export async function downloadFile(ctx, key, localPath) {
|
|
37
|
+
const client = buildClient(ctx);
|
|
55
38
|
const response = await client.send(new GetObjectCommand({
|
|
56
|
-
Bucket:
|
|
39
|
+
Bucket: ctx.bucketName,
|
|
57
40
|
Key: key,
|
|
58
41
|
}));
|
|
59
42
|
if (!response.Body) {
|
|
@@ -70,25 +53,21 @@ export async function downloadFile(relativePath, localPath) {
|
|
|
70
53
|
}
|
|
71
54
|
fs.writeFileSync(localPath, Buffer.concat(chunks));
|
|
72
55
|
}
|
|
73
|
-
export async function listRemoteFiles() {
|
|
74
|
-
const
|
|
56
|
+
export async function listRemoteFiles(ctx, prefix) {
|
|
57
|
+
const client = buildClient(ctx);
|
|
75
58
|
const files = [];
|
|
76
59
|
let continuationToken;
|
|
77
60
|
do {
|
|
78
61
|
const response = await client.send(new ListObjectsV2Command({
|
|
79
|
-
Bucket:
|
|
80
|
-
Prefix:
|
|
62
|
+
Bucket: ctx.bucketName,
|
|
63
|
+
Prefix: prefix,
|
|
81
64
|
ContinuationToken: continuationToken,
|
|
82
65
|
}));
|
|
83
66
|
for (const obj of response.Contents || []) {
|
|
84
67
|
if (!obj.Key || !obj.Size)
|
|
85
68
|
continue;
|
|
86
|
-
const relativePath = obj.Key.replace(config.prefix, "");
|
|
87
|
-
if (!relativePath)
|
|
88
|
-
continue;
|
|
89
69
|
files.push({
|
|
90
70
|
key: obj.Key,
|
|
91
|
-
relativePath,
|
|
92
71
|
size: obj.Size,
|
|
93
72
|
lastModified: obj.LastModified || new Date(),
|
|
94
73
|
etag: obj.ETag || "",
|
|
@@ -98,14 +77,36 @@ export async function listRemoteFiles() {
|
|
|
98
77
|
} while (continuationToken);
|
|
99
78
|
return files;
|
|
100
79
|
}
|
|
101
|
-
export async function deleteRemoteFile(
|
|
102
|
-
const
|
|
103
|
-
const key = `${config.prefix}${relativePath}`;
|
|
80
|
+
export async function deleteRemoteFile(ctx, key) {
|
|
81
|
+
const client = buildClient(ctx);
|
|
104
82
|
await client.send(new DeleteObjectCommand({
|
|
105
|
-
Bucket:
|
|
83
|
+
Bucket: ctx.bucketName,
|
|
106
84
|
Key: key,
|
|
107
85
|
}));
|
|
108
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if a remote key exists and return its metadata.
|
|
89
|
+
*/
|
|
90
|
+
export async function headRemoteFile(ctx, key) {
|
|
91
|
+
const client = buildClient(ctx);
|
|
92
|
+
try {
|
|
93
|
+
const response = await client.send(new HeadObjectCommand({
|
|
94
|
+
Bucket: ctx.bucketName,
|
|
95
|
+
Key: key,
|
|
96
|
+
}));
|
|
97
|
+
return {
|
|
98
|
+
lastModified: response.LastModified || new Date(),
|
|
99
|
+
etag: response.ETag || "",
|
|
100
|
+
size: response.ContentLength || 0,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
if (err && typeof err === "object" && "name" in err && err.name === "NotFound") {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
109
110
|
function getMimeType(filePath) {
|
|
110
111
|
const ext = path.extname(filePath).toLowerCase();
|
|
111
112
|
const mimeTypes = {
|