@indigoai-us/hq-cloud 5.1.9 → 5.1.10
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/auth.js +2 -2
- package/dist/auth.js.map +1 -1
- package/dist/cli/accept.js +2 -2
- package/dist/cli/accept.js.map +1 -1
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +23 -7
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +51 -13
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +6 -1
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +31 -12
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/cognito-auth.d.ts +13 -2
- package/dist/cognito-auth.d.ts.map +1 -1
- package/dist/cognito-auth.js +18 -9
- package/dist/cognito-auth.js.map +1 -1
- package/dist/cognito-auth.test.d.ts +3 -3
- package/dist/cognito-auth.test.js +21 -10
- package/dist/cognito-auth.test.js.map +1 -1
- package/package.json +1 -1
- package/src/auth.ts +2 -2
- package/src/cli/accept.ts +2 -2
- package/src/cli/share.test.ts +59 -13
- package/src/cli/share.ts +25 -6
- package/src/cli/sync.test.ts +33 -12
- package/src/cli/sync.ts +6 -1
- package/src/cognito-auth.test.ts +22 -14
- package/src/cognito-auth.ts +31 -11
- package/test/invite-flow.integration.test.ts +1 -1
package/dist/cognito-auth.js
CHANGED
|
@@ -48,18 +48,19 @@ export function saveCachedTokens(tokens) {
|
|
|
48
48
|
if (!fs.existsSync(HQ_DIR)) {
|
|
49
49
|
fs.mkdirSync(HQ_DIR, { recursive: true, mode: 0o700 });
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
const tmpPath = path.join(HQ_DIR, `.cognito-tokens.json.tmp.${process.pid}`);
|
|
52
|
+
fs.writeFileSync(tmpPath, JSON.stringify(tokens, null, 2), { mode: 0o600 });
|
|
53
|
+
fs.renameSync(tmpPath, TOKEN_FILE);
|
|
52
54
|
}
|
|
53
55
|
export function clearCachedTokens() {
|
|
54
56
|
if (fs.existsSync(TOKEN_FILE))
|
|
55
57
|
fs.unlinkSync(TOKEN_FILE);
|
|
56
58
|
}
|
|
57
59
|
/**
|
|
58
|
-
* Parse `expiresAt` to epoch-ms. Canonical on-disk shape is
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* as "expired" and force a refresh.
|
|
60
|
+
* Parse `expiresAt` to epoch-ms. Canonical on-disk shape is epoch milliseconds
|
|
61
|
+
* (number). Older token files may contain ISO 8601 strings. Accept both for
|
|
62
|
+
* migration safety. Returns null for anything unparseable — callers should
|
|
63
|
+
* treat that as "expired" and force a refresh.
|
|
63
64
|
*/
|
|
64
65
|
function parseExpiresAtMs(raw) {
|
|
65
66
|
if (typeof raw === "number")
|
|
@@ -115,7 +116,9 @@ export async function browserLogin(config) {
|
|
|
115
116
|
const scopes = (config.scopes ?? ["openid", "email", "profile"]).join(" ");
|
|
116
117
|
const { verifier, challenge } = generatePkce();
|
|
117
118
|
const state = base64UrlEncode(crypto.randomBytes(16));
|
|
118
|
-
|
|
119
|
+
// Use `/oauth2/authorize` (not `/login`) so `identity_provider` + `prompt`
|
|
120
|
+
// are honored. `/login` ignores those params and always shows the IdP picker.
|
|
121
|
+
const authUrl = new URL(`${authBaseUrl(config)}/oauth2/authorize`);
|
|
119
122
|
authUrl.searchParams.set("client_id", config.clientId);
|
|
120
123
|
authUrl.searchParams.set("response_type", "code");
|
|
121
124
|
authUrl.searchParams.set("scope", scopes);
|
|
@@ -123,6 +126,12 @@ export async function browserLogin(config) {
|
|
|
123
126
|
authUrl.searchParams.set("code_challenge", challenge);
|
|
124
127
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
125
128
|
authUrl.searchParams.set("state", state);
|
|
129
|
+
if (config.identityProvider) {
|
|
130
|
+
authUrl.searchParams.set("identity_provider", config.identityProvider);
|
|
131
|
+
}
|
|
132
|
+
if (config.prompt) {
|
|
133
|
+
authUrl.searchParams.set("prompt", config.prompt);
|
|
134
|
+
}
|
|
126
135
|
const code = await waitForAuthCode(port, state);
|
|
127
136
|
const tokens = await exchangeCodeForTokens(config, code, verifier, port);
|
|
128
137
|
saveCachedTokens(tokens);
|
|
@@ -222,7 +231,7 @@ async function exchangeCodeForTokens(config, code, verifier, port) {
|
|
|
222
231
|
accessToken: data.access_token,
|
|
223
232
|
idToken: data.id_token,
|
|
224
233
|
refreshToken: data.refresh_token,
|
|
225
|
-
expiresAt:
|
|
234
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
226
235
|
tokenType: "Bearer",
|
|
227
236
|
};
|
|
228
237
|
}
|
|
@@ -251,7 +260,7 @@ export async function refreshTokens(config, currentRefreshToken) {
|
|
|
251
260
|
accessToken: data.access_token,
|
|
252
261
|
idToken: data.id_token,
|
|
253
262
|
refreshToken: data.refresh_token ?? currentRefreshToken,
|
|
254
|
-
expiresAt:
|
|
263
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
255
264
|
tokenType: "Bearer",
|
|
256
265
|
};
|
|
257
266
|
saveCachedTokens(tokens);
|
package/dist/cognito-auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cognito-auth.js","sourceRoot":"","sources":["../src/cognito-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"cognito-auth.js","sourceRoot":"","sources":["../src/cognito-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unit tests for cognito-auth.ts — focus on the `expiresAt` shape contract.
|
|
3
3
|
*
|
|
4
|
-
* Canonical on-disk shape is
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Canonical on-disk shape is epoch milliseconds (number). The reader also
|
|
5
|
+
* tolerates ISO 8601 strings for backward compatibility with pre-migration
|
|
6
|
+
* token files, and fails safe on anything unparseable.
|
|
7
7
|
*/
|
|
8
8
|
export {};
|
|
9
9
|
//# sourceMappingURL=cognito-auth.test.d.ts.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unit tests for cognito-auth.ts — focus on the `expiresAt` shape contract.
|
|
3
3
|
*
|
|
4
|
-
* Canonical on-disk shape is
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Canonical on-disk shape is epoch milliseconds (number). The reader also
|
|
5
|
+
* tolerates ISO 8601 strings for backward compatibility with pre-migration
|
|
6
|
+
* token files, and fails safe on anything unparseable.
|
|
7
7
|
*/
|
|
8
8
|
import * as fs from "fs";
|
|
9
9
|
import * as os from "os";
|
|
@@ -76,10 +76,19 @@ describe("isExpiring — expiresAt shape tolerance", () => {
|
|
|
76
76
|
});
|
|
77
77
|
});
|
|
78
78
|
// ---------------------------------------------------------------------------
|
|
79
|
-
// Round-trip: writers emit
|
|
79
|
+
// Round-trip: writers emit epoch-ms, readers read epoch-ms
|
|
80
80
|
// ---------------------------------------------------------------------------
|
|
81
81
|
describe("expiresAt shape round-trip", () => {
|
|
82
|
-
it("saveCachedTokens + loadCachedTokens preserves
|
|
82
|
+
it("saveCachedTokens + loadCachedTokens preserves epoch-ms number shape", async () => {
|
|
83
|
+
const { saveCachedTokens, loadCachedTokens } = await importModule();
|
|
84
|
+
const epochMs = Date.now() + 3600 * 1000;
|
|
85
|
+
saveCachedTokens({ ...baseTokens, expiresAt: epochMs });
|
|
86
|
+
const loaded = loadCachedTokens();
|
|
87
|
+
expect(loaded).not.toBeNull();
|
|
88
|
+
expect(typeof loaded?.expiresAt).toBe("number");
|
|
89
|
+
expect(loaded?.expiresAt).toBe(epochMs);
|
|
90
|
+
});
|
|
91
|
+
it("saveCachedTokens + loadCachedTokens tolerates legacy ISO string", async () => {
|
|
83
92
|
const { saveCachedTokens, loadCachedTokens } = await importModule();
|
|
84
93
|
const iso = new Date(Date.now() + 3600 * 1000).toISOString();
|
|
85
94
|
saveCachedTokens({ ...baseTokens, expiresAt: iso });
|
|
@@ -87,9 +96,8 @@ describe("expiresAt shape round-trip", () => {
|
|
|
87
96
|
expect(loaded).not.toBeNull();
|
|
88
97
|
expect(typeof loaded?.expiresAt).toBe("string");
|
|
89
98
|
expect(loaded?.expiresAt).toBe(iso);
|
|
90
|
-
expect(loaded?.expiresAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/);
|
|
91
99
|
});
|
|
92
|
-
it("refreshTokens writes
|
|
100
|
+
it("refreshTokens writes epoch milliseconds to cache", async () => {
|
|
93
101
|
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({
|
|
94
102
|
access_token: "new-access",
|
|
95
103
|
id_token: "new-id",
|
|
@@ -98,16 +106,19 @@ describe("expiresAt shape round-trip", () => {
|
|
|
98
106
|
token_type: "Bearer",
|
|
99
107
|
}), { status: 200, headers: { "Content-Type": "application/json" } })));
|
|
100
108
|
const { refreshTokens, loadCachedTokens } = await importModule();
|
|
109
|
+
const before = Date.now();
|
|
101
110
|
const result = await refreshTokens({
|
|
102
111
|
region: "us-east-1",
|
|
103
112
|
userPoolDomain: "hq-vault-dev",
|
|
104
113
|
clientId: "test-client",
|
|
105
114
|
}, "prior-refresh-token");
|
|
106
|
-
|
|
107
|
-
expect(result.expiresAt).
|
|
115
|
+
const after = Date.now();
|
|
116
|
+
expect(typeof result.expiresAt).toBe("number");
|
|
117
|
+
expect(result.expiresAt).toBeGreaterThanOrEqual(before + 3600 * 1000);
|
|
118
|
+
expect(result.expiresAt).toBeLessThanOrEqual(after + 3600 * 1000);
|
|
108
119
|
const onDisk = loadCachedTokens();
|
|
109
120
|
expect(onDisk?.expiresAt).toBe(result.expiresAt);
|
|
110
|
-
expect(typeof onDisk?.expiresAt).toBe("
|
|
121
|
+
expect(typeof onDisk?.expiresAt).toBe("number");
|
|
111
122
|
});
|
|
112
123
|
});
|
|
113
124
|
//# sourceMappingURL=cognito-auth.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cognito-auth.test.js","sourceRoot":"","sources":["../src/cognito-auth.test.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"cognito-auth.test.js","sourceRoot":"","sources":["../src/cognito-auth.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/auth.ts
CHANGED
|
@@ -9,7 +9,7 @@ import * as http from "http";
|
|
|
9
9
|
import open from "open";
|
|
10
10
|
import type { Credentials } from "./types.js";
|
|
11
11
|
|
|
12
|
-
const AUTH_URL = "https://
|
|
12
|
+
const AUTH_URL = "https://example.com/auth";
|
|
13
13
|
const CALLBACK_PORT = 19847;
|
|
14
14
|
const CREDS_DIR = path.join(
|
|
15
15
|
process.env.HOME || process.env.USERPROFILE || "~",
|
|
@@ -116,7 +116,7 @@ export async function authenticate(): Promise<Credentials> {
|
|
|
116
116
|
export async function refreshAwsCredentials(
|
|
117
117
|
creds: Credentials
|
|
118
118
|
): Promise<Credentials> {
|
|
119
|
-
const response = await fetch("https://
|
|
119
|
+
const response = await fetch("https://example.com/api/auth/refresh", {
|
|
120
120
|
method: "POST",
|
|
121
121
|
headers: { "Content-Type": "application/json" },
|
|
122
122
|
body: JSON.stringify({ refreshToken: creds.refreshToken }),
|
package/src/cli/accept.ts
CHANGED
|
@@ -40,8 +40,8 @@ export function parseToken(tokenOrLink: string): string {
|
|
|
40
40
|
return trimmed.slice("hq://accept/".length);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// https://
|
|
44
|
-
const httpsPrefix = "https://
|
|
43
|
+
// https://example.com/accept/<token> (future web route)
|
|
44
|
+
const httpsPrefix = "https://example.com/accept/";
|
|
45
45
|
if (trimmed.startsWith(httpsPrefix)) {
|
|
46
46
|
return trimmed.slice(httpsPrefix.length);
|
|
47
47
|
}
|
package/src/cli/share.test.ts
CHANGED
|
@@ -19,7 +19,7 @@ vi.mock("../s3.js", () => ({
|
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
21
|
import { share } from "./share.js";
|
|
22
|
-
import { headRemoteFile } from "../s3.js";
|
|
22
|
+
import { headRemoteFile, uploadFile } from "../s3.js";
|
|
23
23
|
|
|
24
24
|
const mockConfig: VaultServiceConfig = {
|
|
25
25
|
apiUrl: "https://vault-api.test",
|
|
@@ -82,8 +82,10 @@ describe("share", () => {
|
|
|
82
82
|
delete process.env.HQ_STATE_DIR;
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
it("shares a single file", async () => {
|
|
86
|
-
const
|
|
85
|
+
it("shares a single file keyed relative to the company root", async () => {
|
|
86
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
87
|
+
fs.mkdirSync(companyRoot, { recursive: true });
|
|
88
|
+
const testFile = path.join(companyRoot, "test.md");
|
|
87
89
|
fs.writeFileSync(testFile, "# Hello World");
|
|
88
90
|
|
|
89
91
|
const result = await share({
|
|
@@ -95,15 +97,18 @@ describe("share", () => {
|
|
|
95
97
|
|
|
96
98
|
expect(result.filesUploaded).toBe(1);
|
|
97
99
|
expect(result.aborted).toBe(false);
|
|
100
|
+
// Remote key must be company-relative, not hqRoot-relative
|
|
101
|
+
expect(uploadFile).toHaveBeenCalledWith(expect.anything(), testFile, "test.md");
|
|
98
102
|
});
|
|
99
103
|
|
|
100
104
|
it("respects ignore rules", async () => {
|
|
101
|
-
|
|
102
|
-
fs.
|
|
103
|
-
fs.writeFileSync(path.join(
|
|
105
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
106
|
+
fs.mkdirSync(path.join(companyRoot, ".git"), { recursive: true });
|
|
107
|
+
fs.writeFileSync(path.join(companyRoot, ".git", "config"), "git config");
|
|
108
|
+
fs.writeFileSync(path.join(companyRoot, "readme.md"), "readme");
|
|
104
109
|
|
|
105
110
|
const result = await share({
|
|
106
|
-
paths: [
|
|
111
|
+
paths: [companyRoot],
|
|
107
112
|
company: "acme",
|
|
108
113
|
vaultConfig: mockConfig,
|
|
109
114
|
hqRoot: tmpDir,
|
|
@@ -113,12 +118,13 @@ describe("share", () => {
|
|
|
113
118
|
});
|
|
114
119
|
|
|
115
120
|
it("shares a directory of files", async () => {
|
|
116
|
-
|
|
117
|
-
fs.
|
|
118
|
-
fs.writeFileSync(path.join(
|
|
121
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
122
|
+
fs.mkdirSync(path.join(companyRoot, "docs"), { recursive: true });
|
|
123
|
+
fs.writeFileSync(path.join(companyRoot, "docs", "a.md"), "doc a");
|
|
124
|
+
fs.writeFileSync(path.join(companyRoot, "docs", "b.md"), "doc b");
|
|
119
125
|
|
|
120
126
|
const result = await share({
|
|
121
|
-
paths: [path.join(
|
|
127
|
+
paths: [path.join(companyRoot, "docs")],
|
|
122
128
|
company: "acme",
|
|
123
129
|
vaultConfig: mockConfig,
|
|
124
130
|
hqRoot: tmpDir,
|
|
@@ -127,6 +133,44 @@ describe("share", () => {
|
|
|
127
133
|
expect(result.filesUploaded).toBe(2);
|
|
128
134
|
});
|
|
129
135
|
|
|
136
|
+
it("keys nested paths relative to the company root, not hqRoot", async () => {
|
|
137
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
138
|
+
fs.mkdirSync(path.join(companyRoot, "knowledge"), { recursive: true });
|
|
139
|
+
const nested = path.join(companyRoot, "knowledge", "crawl.json");
|
|
140
|
+
fs.writeFileSync(nested, "{}");
|
|
141
|
+
|
|
142
|
+
await share({
|
|
143
|
+
paths: [nested],
|
|
144
|
+
company: "acme",
|
|
145
|
+
vaultConfig: mockConfig,
|
|
146
|
+
hqRoot: tmpDir,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Key is "knowledge/crawl.json", not "companies/acme/knowledge/crawl.json"
|
|
150
|
+
expect(uploadFile).toHaveBeenCalledWith(expect.anything(), nested, "knowledge/crawl.json");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("skips files outside the company folder with a warning", async () => {
|
|
154
|
+
const warnSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
155
|
+
// File at hqRoot, outside companies/acme/
|
|
156
|
+
const outsideFile = path.join(tmpDir, "stray.md");
|
|
157
|
+
fs.writeFileSync(outsideFile, "stray");
|
|
158
|
+
|
|
159
|
+
const result = await share({
|
|
160
|
+
paths: [outsideFile],
|
|
161
|
+
company: "acme",
|
|
162
|
+
vaultConfig: mockConfig,
|
|
163
|
+
hqRoot: tmpDir,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(result.filesUploaded).toBe(0);
|
|
167
|
+
expect(uploadFile).not.toHaveBeenCalled();
|
|
168
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
169
|
+
expect.stringMatching(/outside company folder/i),
|
|
170
|
+
);
|
|
171
|
+
warnSpy.mockRestore();
|
|
172
|
+
});
|
|
173
|
+
|
|
130
174
|
it("throws when no company specified and no active company", async () => {
|
|
131
175
|
fs.writeFileSync(path.join(tmpDir, "test.md"), "test");
|
|
132
176
|
|
|
@@ -140,12 +184,14 @@ describe("share", () => {
|
|
|
140
184
|
});
|
|
141
185
|
|
|
142
186
|
it("resolves active company from .hq/config.json", async () => {
|
|
187
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
188
|
+
fs.mkdirSync(companyRoot, { recursive: true });
|
|
143
189
|
fs.mkdirSync(path.join(tmpDir, ".hq"), { recursive: true });
|
|
144
190
|
fs.writeFileSync(path.join(tmpDir, ".hq", "config.json"), JSON.stringify({ activeCompany: "acme" }));
|
|
145
|
-
fs.writeFileSync(path.join(
|
|
191
|
+
fs.writeFileSync(path.join(companyRoot, "test.md"), "test");
|
|
146
192
|
|
|
147
193
|
const result = await share({
|
|
148
|
-
paths: [path.join(
|
|
194
|
+
paths: [path.join(companyRoot, "test.md")],
|
|
149
195
|
vaultConfig: mockConfig,
|
|
150
196
|
hqRoot: tmpDir,
|
|
151
197
|
});
|
package/src/cli/share.ts
CHANGED
|
@@ -54,6 +54,10 @@ export async function share(options: ShareOptions): Promise<ShareResult> {
|
|
|
54
54
|
|
|
55
55
|
// Resolve entity context (handles STS vending + caching)
|
|
56
56
|
let ctx = await resolveEntityContext(companyRef, vaultConfig);
|
|
57
|
+
// Remote keys are company-relative; the on-disk scoping prefix is
|
|
58
|
+
// companies/{slug}/. Anything outside this folder gets skipped to avoid
|
|
59
|
+
// leaking cross-company state into the vault.
|
|
60
|
+
const syncRoot = path.join(hqRoot, "companies", ctx.slug);
|
|
57
61
|
const shouldSync = createIgnoreFilter(hqRoot);
|
|
58
62
|
const journal = readJournal(ctx.slug);
|
|
59
63
|
|
|
@@ -62,7 +66,7 @@ export async function share(options: ShareOptions): Promise<ShareResult> {
|
|
|
62
66
|
let filesSkipped = 0;
|
|
63
67
|
|
|
64
68
|
// Collect all files to share
|
|
65
|
-
const filesToShare = collectFiles(paths, hqRoot, shouldSync);
|
|
69
|
+
const filesToShare = collectFiles(paths, hqRoot, syncRoot, shouldSync);
|
|
66
70
|
|
|
67
71
|
for (const { absolutePath, relativePath } of filesToShare) {
|
|
68
72
|
if (!isWithinSizeLimit(absolutePath)) {
|
|
@@ -155,10 +159,15 @@ function resolveActiveCompany(hqRoot: string): string | undefined {
|
|
|
155
159
|
|
|
156
160
|
/**
|
|
157
161
|
* Collect files from paths (expanding directories recursively).
|
|
162
|
+
*
|
|
163
|
+
* Remote S3 keys are computed relative to `syncRoot` (companies/{slug}/), not
|
|
164
|
+
* `hqRoot`. Files outside `syncRoot` are skipped with a warning — sharing
|
|
165
|
+
* anything outside a company's folder would leak state into the wrong vault.
|
|
158
166
|
*/
|
|
159
167
|
function collectFiles(
|
|
160
168
|
paths: string[],
|
|
161
169
|
hqRoot: string,
|
|
170
|
+
syncRoot: string,
|
|
162
171
|
filter: (p: string) => boolean,
|
|
163
172
|
): { absolutePath: string; relativePath: string }[] {
|
|
164
173
|
const results: { absolutePath: string; relativePath: string }[] = [];
|
|
@@ -171,11 +180,16 @@ function collectFiles(
|
|
|
171
180
|
continue;
|
|
172
181
|
}
|
|
173
182
|
|
|
183
|
+
if (!isWithin(syncRoot, absolutePath)) {
|
|
184
|
+
console.error(` Warning: ${p} is outside company folder, skipping.`);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
174
188
|
const stat = fs.statSync(absolutePath);
|
|
175
189
|
if (stat.isDirectory()) {
|
|
176
|
-
results.push(...walkDir(absolutePath,
|
|
190
|
+
results.push(...walkDir(absolutePath, syncRoot, filter));
|
|
177
191
|
} else if (stat.isFile()) {
|
|
178
|
-
const relativePath = path.relative(
|
|
192
|
+
const relativePath = path.relative(syncRoot, absolutePath);
|
|
179
193
|
if (filter(absolutePath)) {
|
|
180
194
|
results.push({ absolutePath, relativePath });
|
|
181
195
|
}
|
|
@@ -187,7 +201,7 @@ function collectFiles(
|
|
|
187
201
|
|
|
188
202
|
function walkDir(
|
|
189
203
|
dir: string,
|
|
190
|
-
|
|
204
|
+
syncRoot: string,
|
|
191
205
|
filter: (p: string) => boolean,
|
|
192
206
|
): { absolutePath: string; relativePath: string }[] {
|
|
193
207
|
const results: { absolutePath: string; relativePath: string }[] = [];
|
|
@@ -199,14 +213,19 @@ function walkDir(
|
|
|
199
213
|
if (!filter(absolutePath)) continue;
|
|
200
214
|
|
|
201
215
|
if (entry.isDirectory()) {
|
|
202
|
-
results.push(...walkDir(absolutePath,
|
|
216
|
+
results.push(...walkDir(absolutePath, syncRoot, filter));
|
|
203
217
|
} else if (entry.isFile()) {
|
|
204
218
|
results.push({
|
|
205
219
|
absolutePath,
|
|
206
|
-
relativePath: path.relative(
|
|
220
|
+
relativePath: path.relative(syncRoot, absolutePath),
|
|
207
221
|
});
|
|
208
222
|
}
|
|
209
223
|
}
|
|
210
224
|
|
|
211
225
|
return results;
|
|
212
226
|
}
|
|
227
|
+
|
|
228
|
+
function isWithin(parent: string, child: string): boolean {
|
|
229
|
+
const rel = path.relative(parent, child);
|
|
230
|
+
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
231
|
+
}
|
package/src/cli/sync.test.ts
CHANGED
|
@@ -61,7 +61,7 @@ const mockVendResponse = {
|
|
|
61
61
|
function setupFetchMock() {
|
|
62
62
|
const fetchMock = vi.fn().mockImplementation(async (url: string) => {
|
|
63
63
|
const urlStr = String(url);
|
|
64
|
-
if (urlStr.includes("/entity/by-slug/")) {
|
|
64
|
+
if (urlStr.includes("/entity/by-slug/") || /\/entity\/cmp_/.test(urlStr)) {
|
|
65
65
|
return { ok: true, status: 200, json: async () => ({ entity: mockEntity }), text: async () => "" };
|
|
66
66
|
}
|
|
67
67
|
if (urlStr.includes("/sts/vend")) {
|
|
@@ -98,7 +98,7 @@ describe("sync", () => {
|
|
|
98
98
|
delete process.env.HQ_STATE_DIR;
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
it("downloads remote files
|
|
101
|
+
it("downloads remote files under companies/{slug}/ so two companies don't collide", async () => {
|
|
102
102
|
const result = await sync({
|
|
103
103
|
company: "acme",
|
|
104
104
|
vaultConfig: mockConfig,
|
|
@@ -107,8 +107,26 @@ describe("sync", () => {
|
|
|
107
107
|
|
|
108
108
|
expect(result.filesDownloaded).toBe(2);
|
|
109
109
|
expect(result.aborted).toBe(false);
|
|
110
|
-
|
|
111
|
-
expect(fs.existsSync(path.join(tmpDir, "
|
|
110
|
+
// Scoped under companies/{slug}/
|
|
111
|
+
expect(fs.existsSync(path.join(tmpDir, "companies", "acme", "docs", "handoff.md"))).toBe(true);
|
|
112
|
+
expect(fs.existsSync(path.join(tmpDir, "companies", "acme", "knowledge", "readme.md"))).toBe(true);
|
|
113
|
+
// NOT at hqRoot (pre-fix behavior would have written here and clobbered across companies)
|
|
114
|
+
expect(fs.existsSync(path.join(tmpDir, "docs", "handoff.md"))).toBe(false);
|
|
115
|
+
expect(fs.existsSync(path.join(tmpDir, "knowledge", "readme.md"))).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("scopes by resolved ctx.slug even when caller passes a UID", async () => {
|
|
119
|
+
// mockEntity.slug is "acme" regardless of the ref used; verify resolved
|
|
120
|
+
// slug drives the local path, not the caller's ref.
|
|
121
|
+
const result = await sync({
|
|
122
|
+
company: "cmp_01ABCDEF",
|
|
123
|
+
vaultConfig: mockConfig,
|
|
124
|
+
hqRoot: tmpDir,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(result.filesDownloaded).toBe(2);
|
|
128
|
+
expect(fs.existsSync(path.join(tmpDir, "companies", "acme", "docs", "handoff.md"))).toBe(true);
|
|
129
|
+
expect(fs.existsSync(path.join(tmpDir, "companies", "cmp_01ABCDEF", "docs", "handoff.md"))).toBe(false);
|
|
112
130
|
});
|
|
113
131
|
|
|
114
132
|
it("throws when no company specified and no active company", async () => {
|
|
@@ -129,8 +147,9 @@ describe("sync", () => {
|
|
|
129
147
|
});
|
|
130
148
|
|
|
131
149
|
it("detects conflicts with local changes and keeps local on --on-conflict keep", async () => {
|
|
132
|
-
|
|
133
|
-
fs.
|
|
150
|
+
const companyDocs = path.join(tmpDir, "companies", "acme", "docs");
|
|
151
|
+
fs.mkdirSync(companyDocs, { recursive: true });
|
|
152
|
+
fs.writeFileSync(path.join(companyDocs, "handoff.md"), "local version");
|
|
134
153
|
|
|
135
154
|
fs.writeFileSync(
|
|
136
155
|
journalPath,
|
|
@@ -157,12 +176,13 @@ describe("sync", () => {
|
|
|
157
176
|
|
|
158
177
|
expect(result.conflicts).toBe(1);
|
|
159
178
|
expect(result.filesSkipped).toBeGreaterThanOrEqual(1);
|
|
160
|
-
expect(fs.readFileSync(path.join(
|
|
179
|
+
expect(fs.readFileSync(path.join(companyDocs, "handoff.md"), "utf-8")).toBe("local version");
|
|
161
180
|
});
|
|
162
181
|
|
|
163
182
|
it("aborts on --on-conflict abort", async () => {
|
|
164
|
-
|
|
165
|
-
fs.
|
|
183
|
+
const companyDocs = path.join(tmpDir, "companies", "acme", "docs");
|
|
184
|
+
fs.mkdirSync(companyDocs, { recursive: true });
|
|
185
|
+
fs.writeFileSync(path.join(companyDocs, "handoff.md"), "local version");
|
|
166
186
|
|
|
167
187
|
fs.writeFileSync(
|
|
168
188
|
journalPath,
|
|
@@ -191,8 +211,9 @@ describe("sync", () => {
|
|
|
191
211
|
});
|
|
192
212
|
|
|
193
213
|
it("overwrites local on --on-conflict overwrite", async () => {
|
|
194
|
-
|
|
195
|
-
fs.
|
|
214
|
+
const companyDocs = path.join(tmpDir, "companies", "acme", "docs");
|
|
215
|
+
fs.mkdirSync(companyDocs, { recursive: true });
|
|
216
|
+
fs.writeFileSync(path.join(companyDocs, "handoff.md"), "local version");
|
|
196
217
|
|
|
197
218
|
fs.writeFileSync(
|
|
198
219
|
journalPath,
|
|
@@ -220,6 +241,6 @@ describe("sync", () => {
|
|
|
220
241
|
expect(result.conflicts).toBe(1);
|
|
221
242
|
expect(result.filesDownloaded).toBeGreaterThanOrEqual(1);
|
|
222
243
|
// File should be overwritten with mock content
|
|
223
|
-
expect(fs.readFileSync(path.join(
|
|
244
|
+
expect(fs.readFileSync(path.join(companyDocs, "handoff.md"), "utf-8")).toBe("mock file content");
|
|
224
245
|
});
|
|
225
246
|
});
|
package/src/cli/sync.ts
CHANGED
|
@@ -74,6 +74,11 @@ export async function sync(options: SyncOptions): Promise<SyncResult> {
|
|
|
74
74
|
|
|
75
75
|
// Resolve entity context
|
|
76
76
|
let ctx = await resolveEntityContext(companyRef, vaultConfig);
|
|
77
|
+
// Every company's files land under companies/{slug}/ so fanning out multiple
|
|
78
|
+
// companies into the same hqRoot doesn't cross-clobber files with overlapping
|
|
79
|
+
// S3 keys (e.g. every company has a .hq/manifest.json). Remote keys stay
|
|
80
|
+
// company-relative; the prefix lives only on disk.
|
|
81
|
+
const companyRoot = path.join(hqRoot, "companies", ctx.slug);
|
|
77
82
|
const shouldSync = createIgnoreFilter(hqRoot);
|
|
78
83
|
const journal = readJournal(ctx.slug);
|
|
79
84
|
|
|
@@ -86,7 +91,7 @@ export async function sync(options: SyncOptions): Promise<SyncResult> {
|
|
|
86
91
|
const remoteFiles = await listRemoteFiles(ctx);
|
|
87
92
|
|
|
88
93
|
for (const remoteFile of remoteFiles) {
|
|
89
|
-
const localPath = path.join(
|
|
94
|
+
const localPath = path.join(companyRoot, remoteFile.key);
|
|
90
95
|
|
|
91
96
|
// Apply ignore rules
|
|
92
97
|
if (!shouldSync(localPath)) {
|