jamdesk 1.0.14 → 1.0.16
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/__tests__/unit/auth.test.js +52 -21
- package/dist/__tests__/unit/auth.test.js.map +1 -1
- package/dist/__tests__/unit/login.test.js +92 -24
- package/dist/__tests__/unit/login.test.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +12 -4
- package/dist/commands/login.js.map +1 -1
- package/dist/lib/auth.d.ts +1 -2
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +5 -4
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js.map +1 -1
- package/package.json +1 -1
- package/vendored/app/api/og/route.tsx +64 -28
- package/vendored/lib/seo.ts +38 -12
|
@@ -10,7 +10,18 @@ vi.mock('../../lib/config.js', () => ({
|
|
|
10
10
|
// Mock global fetch
|
|
11
11
|
const mockFetch = vi.fn();
|
|
12
12
|
vi.stubGlobal('fetch', mockFetch);
|
|
13
|
-
import { isLoggedIn, getValidToken, storeAuth, clearAuth, apiCall,
|
|
13
|
+
import { isLoggedIn, getValidToken, storeAuth, clearAuth, apiCall, FUNCTIONS_BASE_URL, _resetTokenCache, } from '../../lib/auth.js';
|
|
14
|
+
const TEST_API_KEY = 'test-firebase-api-key';
|
|
15
|
+
function authConfig(overrides = {}) {
|
|
16
|
+
return {
|
|
17
|
+
refreshToken: 'tok',
|
|
18
|
+
email: 'a@b.com',
|
|
19
|
+
uid: '1',
|
|
20
|
+
expiresAt: 0,
|
|
21
|
+
firebaseApiKey: TEST_API_KEY,
|
|
22
|
+
...overrides,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
14
25
|
describe('auth', () => {
|
|
15
26
|
beforeEach(() => {
|
|
16
27
|
vi.clearAllMocks();
|
|
@@ -19,13 +30,13 @@ describe('auth', () => {
|
|
|
19
30
|
});
|
|
20
31
|
describe('isLoggedIn', () => {
|
|
21
32
|
it('returns true when auth config has refreshToken', () => {
|
|
22
|
-
expect(isLoggedIn({ auth:
|
|
33
|
+
expect(isLoggedIn({ auth: authConfig() })).toBe(true);
|
|
23
34
|
});
|
|
24
35
|
it('returns false when auth config is missing', () => {
|
|
25
36
|
expect(isLoggedIn({})).toBe(false);
|
|
26
37
|
});
|
|
27
38
|
it('returns false when refreshToken is empty', () => {
|
|
28
|
-
expect(isLoggedIn({ auth: { refreshToken: ''
|
|
39
|
+
expect(isLoggedIn({ auth: authConfig({ refreshToken: '' }) })).toBe(false);
|
|
29
40
|
});
|
|
30
41
|
});
|
|
31
42
|
describe('getValidToken', () => {
|
|
@@ -33,9 +44,21 @@ describe('auth', () => {
|
|
|
33
44
|
mockLoadConfig.mockResolvedValue({});
|
|
34
45
|
await expect(getValidToken()).rejects.toThrow('Not logged in');
|
|
35
46
|
});
|
|
47
|
+
it('throws when firebaseApiKey is missing', async () => {
|
|
48
|
+
mockLoadConfig.mockResolvedValue({
|
|
49
|
+
auth: authConfig({ firebaseApiKey: undefined }),
|
|
50
|
+
});
|
|
51
|
+
await expect(getValidToken()).rejects.toThrow('Run `jamdesk login` to re-authenticate');
|
|
52
|
+
});
|
|
53
|
+
it('throws when firebaseApiKey is empty string', async () => {
|
|
54
|
+
mockLoadConfig.mockResolvedValue({
|
|
55
|
+
auth: authConfig({ firebaseApiKey: '' }),
|
|
56
|
+
});
|
|
57
|
+
await expect(getValidToken()).rejects.toThrow('Run `jamdesk login` to re-authenticate');
|
|
58
|
+
});
|
|
36
59
|
it('refreshes token via Firebase REST API', async () => {
|
|
37
60
|
mockLoadConfig.mockResolvedValue({
|
|
38
|
-
auth: { refreshToken: 'old-refresh'
|
|
61
|
+
auth: authConfig({ refreshToken: 'old-refresh' }),
|
|
39
62
|
});
|
|
40
63
|
mockFetch.mockResolvedValue({
|
|
41
64
|
ok: true,
|
|
@@ -47,8 +70,8 @@ describe('auth', () => {
|
|
|
47
70
|
});
|
|
48
71
|
const token = await getValidToken();
|
|
49
72
|
expect(token).toBe('new-id-token');
|
|
50
|
-
// Verify fetch was called with
|
|
51
|
-
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining(
|
|
73
|
+
// Verify fetch was called with API key from config
|
|
74
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining(`key=${TEST_API_KEY}`), expect.objectContaining({
|
|
52
75
|
method: 'POST',
|
|
53
76
|
body: expect.stringContaining('refresh_token=old-refresh'),
|
|
54
77
|
}));
|
|
@@ -59,9 +82,26 @@ describe('auth', () => {
|
|
|
59
82
|
}),
|
|
60
83
|
}));
|
|
61
84
|
});
|
|
85
|
+
it('preserves firebaseApiKey after token refresh', async () => {
|
|
86
|
+
mockLoadConfig.mockResolvedValue({
|
|
87
|
+
auth: authConfig({ refreshToken: 'old-refresh' }),
|
|
88
|
+
});
|
|
89
|
+
mockFetch.mockResolvedValue({
|
|
90
|
+
ok: true,
|
|
91
|
+
json: () => Promise.resolve({
|
|
92
|
+
id_token: 'new-id-token',
|
|
93
|
+
refresh_token: 'new-refresh-token',
|
|
94
|
+
expires_in: '3600',
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
await getValidToken();
|
|
98
|
+
// Verify saved config still has firebaseApiKey
|
|
99
|
+
const savedConfig = mockSaveConfig.mock.calls[0][0];
|
|
100
|
+
expect(savedConfig.auth.firebaseApiKey).toBe(TEST_API_KEY);
|
|
101
|
+
});
|
|
62
102
|
it('throws on refresh failure', async () => {
|
|
63
103
|
mockLoadConfig.mockResolvedValue({
|
|
64
|
-
auth: { refreshToken: 'bad-token'
|
|
104
|
+
auth: authConfig({ refreshToken: 'bad-token' }),
|
|
65
105
|
});
|
|
66
106
|
mockFetch.mockResolvedValue({
|
|
67
107
|
ok: false,
|
|
@@ -74,7 +114,7 @@ describe('auth', () => {
|
|
|
74
114
|
});
|
|
75
115
|
it('clears auth on permanent token errors', async () => {
|
|
76
116
|
mockLoadConfig.mockResolvedValue({
|
|
77
|
-
auth: { refreshToken: 'expired'
|
|
117
|
+
auth: authConfig({ refreshToken: 'expired' }),
|
|
78
118
|
});
|
|
79
119
|
mockFetch.mockResolvedValue({
|
|
80
120
|
ok: false,
|
|
@@ -91,7 +131,7 @@ describe('auth', () => {
|
|
|
91
131
|
describe('storeAuth', () => {
|
|
92
132
|
it('saves auth config', async () => {
|
|
93
133
|
mockLoadConfig.mockResolvedValue({ verbose: true });
|
|
94
|
-
const auth = {
|
|
134
|
+
const auth = authConfig({ expiresAt: 100 });
|
|
95
135
|
await storeAuth(auth);
|
|
96
136
|
expect(mockSaveConfig).toHaveBeenCalledWith({
|
|
97
137
|
verbose: true,
|
|
@@ -103,7 +143,7 @@ describe('auth', () => {
|
|
|
103
143
|
it('removes auth from config', async () => {
|
|
104
144
|
mockLoadConfig.mockResolvedValue({
|
|
105
145
|
verbose: true,
|
|
106
|
-
auth: {
|
|
146
|
+
auth: authConfig({ expiresAt: 100 }),
|
|
107
147
|
});
|
|
108
148
|
await clearAuth();
|
|
109
149
|
expect(mockSaveConfig).toHaveBeenCalledWith({ verbose: true });
|
|
@@ -111,10 +151,7 @@ describe('auth', () => {
|
|
|
111
151
|
});
|
|
112
152
|
describe('apiCall', () => {
|
|
113
153
|
it('makes authenticated GET request', async () => {
|
|
114
|
-
|
|
115
|
-
mockLoadConfig.mockResolvedValue({
|
|
116
|
-
auth: { refreshToken: 'tok', email: 'a@b.com', uid: '1', expiresAt: 0 },
|
|
117
|
-
});
|
|
154
|
+
mockLoadConfig.mockResolvedValue({ auth: authConfig() });
|
|
118
155
|
// First fetch: token refresh
|
|
119
156
|
mockFetch.mockResolvedValueOnce({
|
|
120
157
|
ok: true,
|
|
@@ -138,9 +175,7 @@ describe('auth', () => {
|
|
|
138
175
|
expect(apiCallArgs[1].headers.Authorization).toBe('Bearer test-id-token');
|
|
139
176
|
});
|
|
140
177
|
it('makes authenticated POST request with body', async () => {
|
|
141
|
-
mockLoadConfig.mockResolvedValue({
|
|
142
|
-
auth: { refreshToken: 'tok', email: 'a@b.com', uid: '1', expiresAt: 0 },
|
|
143
|
-
});
|
|
178
|
+
mockLoadConfig.mockResolvedValue({ auth: authConfig() });
|
|
144
179
|
mockFetch
|
|
145
180
|
.mockResolvedValueOnce({
|
|
146
181
|
ok: true,
|
|
@@ -157,10 +192,6 @@ describe('auth', () => {
|
|
|
157
192
|
});
|
|
158
193
|
});
|
|
159
194
|
describe('constants', () => {
|
|
160
|
-
it('exports Firebase API key', () => {
|
|
161
|
-
expect(FIREBASE_API_KEY).toBeTruthy();
|
|
162
|
-
expect(FIREBASE_API_KEY).toMatch(/^AIza/);
|
|
163
|
-
});
|
|
164
195
|
it('exports functions base URL', () => {
|
|
165
196
|
expect(FUNCTIONS_BASE_URL).toContain('cloudfunctions.net');
|
|
166
197
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/auth.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE9D,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3D,cAAc,EAAE,EAAE,CAAC,EAAE,EAAE;IACvB,cAAc,EAAE,EAAE,CAAC,EAAE,EAAE;CACxB,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC;IACpC,UAAU,EAAE,cAAc;IAC1B,UAAU,EAAE,cAAc;CAC3B,CAAC,CAAC,CAAC;AAEJ,oBAAoB;AACpB,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AAC1B,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AAElC,OAAO,EACL,UAAU,EACV,aAAa,EACb,SAAS,EACT,SAAS,EACT,OAAO,EACP,
|
|
1
|
+
{"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/auth.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE9D,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3D,cAAc,EAAE,EAAE,CAAC,EAAE,EAAE;IACvB,cAAc,EAAE,EAAE,CAAC,EAAE,EAAE;CACxB,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC;IACpC,UAAU,EAAE,cAAc;IAC1B,UAAU,EAAE,cAAc;CAC3B,CAAC,CAAC,CAAC;AAEJ,oBAAoB;AACpB,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AAC1B,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AAElC,OAAO,EACL,UAAU,EACV,aAAa,EACb,SAAS,EACT,SAAS,EACT,OAAO,EACP,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,YAAY,GAAG,uBAAuB,CAAC;AAE7C,SAAS,UAAU,CAAC,SAAS,GAAG,EAAE;IAChC,OAAO;QACL,YAAY,EAAE,KAAK;QACnB,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,GAAG;QACR,SAAS,EAAE,CAAC;QACZ,cAAc,EAAE,YAAY;QAC5B,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE;IACpB,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,cAAc,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC5C,gBAAgB,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;YACxD,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;YAClD,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;YACzC,cAAc,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;YACrC,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YACrD,cAAc,CAAC,iBAAiB,CAAC;gBAC/B,IAAI,EAAE,UAAU,CAAC,EAAE,cAAc,EAAE,SAAS,EAAE,CAAC;aAChD,CAAC,CAAC;YACH,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC;QAC1F,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;YAC1D,cAAc,CAAC,iBAAiB,CAAC;gBAC/B,IAAI,EAAE,UAAU,CAAC,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;aACzC,CAAC,CAAC;YACH,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC;QAC1F,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YACrD,cAAc,CAAC,iBAAiB,CAAC;gBAC/B,IAAI,EAAE,UAAU,CAAC,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;aAClD,CAAC,CAAC;YAEH,SAAS,CAAC,iBAAiB,CAAC;gBAC1B,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;oBAC1B,QAAQ,EAAE,cAAc;oBACxB,aAAa,EAAE,mBAAmB;oBAClC,UAAU,EAAE,MAAM;iBACnB,CAAC;aACH,CAAC,CAAC;YAEH,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;YACpC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAEnC,mDAAmD;YACnD,MAAM,CAAC,SAAS,CAAC,CAAC,oBAAoB,CACpC,MAAM,CAAC,gBAAgB,CAAC,OAAO,YAAY,EAAE,CAAC,EAC9C,MAAM,CAAC,gBAAgB,CAAC;gBACtB,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,MAAM,CAAC,gBAAgB,CAAC,2BAA2B,CAAC;aAC3D,CAAC,CACH,CAAC;YAEF,iDAAiD;YACjD,MAAM,CAAC,cAAc,CAAC,CAAC,oBAAoB,CACzC,MAAM,CAAC,gBAAgB,CAAC;gBACtB,IAAI,EAAE,MAAM,CAAC,gBAAgB,CAAC;oBAC5B,YAAY,EAAE,mBAAmB;iBAClC,CAAC;aACH,CAAC,CACH,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;YAC5D,cAAc,CAAC,iBAAiB,CAAC;gBAC/B,IAAI,EAAE,UAAU,CAAC,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;aAClD,CAAC,CAAC;YAEH,SAAS,CAAC,iBAAiB,CAAC;gBAC1B,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;oBAC1B,QAAQ,EAAE,cAAc;oBACxB,aAAa,EAAE,mBAAmB;oBAClC,UAAU,EAAE,MAAM;iBACnB,CAAC;aACH,CAAC,CAAC;YAEH,MAAM,aAAa,EAAE,CAAC;YAEtB,+CAA+C;YAC/C,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACpD,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;YACzC,cAAc,CAAC,iBAAiB,CAAC;gBAC/B,IAAI,EAAE,UAAU,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;aAChD,CAAC,CAAC;YAEH,SAAS,CAAC,iBAAiB,CAAC;gBAC1B,EAAE,EAAE,KAAK;gBACT,UAAU,EAAE,aAAa;gBACzB,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;oBAC1B,KAAK,EAAE,EAAE,OAAO,EAAE,uBAAuB,EAAE;iBAC5C,CAAC;aACH,CAAC,CAAC;YAEH,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YACrD,cAAc,CAAC,iBAAiB,CAAC;gBAC/B,IAAI,EAAE,UAAU,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;aAC9C,CAAC,CAAC;YAEH,SAAS,CAAC,iBAAiB,CAAC;gBAC1B,EAAE,EAAE,KAAK;gBACT,UAAU,EAAE,aAAa;gBACzB,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;oBAC1B,KAAK,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE;iBACpC,CAAC;aACH,CAAC,CAAC;YAEH,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAEhD,2BAA2B;YAC3B,MAAM,CAAC,cAAc,CAAC,CAAC,oBAAoB,CACzC,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CACzD,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;QACzB,EAAE,CAAC,mBAAmB,EAAE,KAAK,IAAI,EAAE;YACjC,cAAc,CAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YAEpD,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;YAC5C,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;YAEtB,MAAM,CAAC,cAAc,CAAC,CAAC,oBAAoB,CAAC;gBAC1C,OAAO,EAAE,IAAI;gBACb,IAAI;aACL,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;QACzB,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;YACxC,cAAc,CAAC,iBAAiB,CAAC;gBAC/B,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;aACrC,CAAC,CAAC;YAEH,MAAM,SAAS,EAAE,CAAC;YAElB,MAAM,CAAC,cAAc,CAAC,CAAC,oBAAoB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;QACvB,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;YAC/C,cAAc,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;YAEzD,6BAA6B;YAC7B,SAAS,CAAC,qBAAqB,CAAC;gBAC9B,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;oBAC1B,QAAQ,EAAE,eAAe;oBACzB,aAAa,EAAE,aAAa;oBAC5B,UAAU,EAAE,MAAM;iBACnB,CAAC;aACH,CAAC,CAAC;YAEH,yBAAyB;YACzB,SAAS,CAAC,qBAAqB,CAAC;gBAC9B,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;aAC9C,CAAC,CAAC;YAEH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;YACnD,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAE/B,oCAAoC;YACpC,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;YAC3C,MAAM,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,kBAAkB,mBAAmB,CAAC,CAAC;YACtE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;YAC1D,cAAc,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;YAEzD,SAAS;iBACN,qBAAqB,CAAC;gBACrB,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;aACzF,CAAC;iBACD,qBAAqB,CAAC;gBACrB,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;aAC/C,CAAC,CAAC;YAEL,MAAM,OAAO,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;YAE/D,MAAM,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC3E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;QACzB,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,kBAAkB,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -18,6 +18,26 @@ const { mockOutput } = vi.hoisted(() => ({
|
|
|
18
18
|
vi.mock('../../lib/output.js', () => ({ output: mockOutput }));
|
|
19
19
|
vi.mock('open', () => ({ default: vi.fn() }));
|
|
20
20
|
import { login } from '../../commands/login.js';
|
|
21
|
+
function sendOptions(port) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const req = http.request({
|
|
24
|
+
hostname: '127.0.0.1',
|
|
25
|
+
port,
|
|
26
|
+
path: '/callback',
|
|
27
|
+
method: 'OPTIONS',
|
|
28
|
+
headers: {
|
|
29
|
+
Origin: 'https://dashboard.jamdesk.com',
|
|
30
|
+
'Access-Control-Request-Method': 'POST',
|
|
31
|
+
'Access-Control-Request-Private-Network': 'true',
|
|
32
|
+
},
|
|
33
|
+
}, (res) => {
|
|
34
|
+
res.resume();
|
|
35
|
+
res.on('end', () => resolve({ statusCode: res.statusCode || 0, headers: res.headers }));
|
|
36
|
+
});
|
|
37
|
+
req.on('error', reject);
|
|
38
|
+
req.end();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
21
41
|
function sendCallback(port, body) {
|
|
22
42
|
return new Promise((resolve, reject) => {
|
|
23
43
|
const data = JSON.stringify(body);
|
|
@@ -26,7 +46,11 @@ function sendCallback(port, body) {
|
|
|
26
46
|
port,
|
|
27
47
|
path: '/callback',
|
|
28
48
|
method: 'POST',
|
|
29
|
-
headers: {
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
'Content-Length': Buffer.byteLength(data),
|
|
52
|
+
Origin: 'https://dashboard.jamdesk.com',
|
|
53
|
+
},
|
|
30
54
|
}, (res) => {
|
|
31
55
|
res.resume();
|
|
32
56
|
res.on('end', () => resolve({ statusCode: res.statusCode || 0 }));
|
|
@@ -36,22 +60,32 @@ function sendCallback(port, body) {
|
|
|
36
60
|
req.end();
|
|
37
61
|
});
|
|
38
62
|
}
|
|
63
|
+
/** Start login, extract port and state from the logged URL. */
|
|
64
|
+
async function startLoginAndGetParams() {
|
|
65
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
66
|
+
const loginPromise = login();
|
|
67
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
68
|
+
const consoleSpy = vi.mocked(console.log);
|
|
69
|
+
const urlLog = consoleSpy.mock.calls.find((c) => typeof c[0] === 'string' && c[0].includes('cli-auth'));
|
|
70
|
+
expect(urlLog).toBeTruthy();
|
|
71
|
+
const url = new URL(urlLog[0].trim());
|
|
72
|
+
const port = parseInt(url.searchParams.get('port'), 10);
|
|
73
|
+
const state = url.searchParams.get('state');
|
|
74
|
+
return { loginPromise, port, state };
|
|
75
|
+
}
|
|
76
|
+
/** Send a valid callback to shut down the login server. */
|
|
77
|
+
function shutdownServer(port, state) {
|
|
78
|
+
return sendCallback(port, {
|
|
79
|
+
token: 't', refreshToken: 't', email: 'e@e.com', uid: 'u', state, firebaseApiKey: 'k',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
39
82
|
describe('login', () => {
|
|
40
|
-
let consoleSpy;
|
|
41
83
|
beforeEach(() => {
|
|
42
84
|
vi.clearAllMocks();
|
|
43
85
|
mockStoreAuth.mockResolvedValue(undefined);
|
|
44
|
-
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
45
86
|
});
|
|
46
|
-
it('stores auth on valid callback
|
|
47
|
-
const loginPromise =
|
|
48
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
49
|
-
// Extract port and state from logged URL
|
|
50
|
-
const urlLog = consoleSpy.mock.calls.find((c) => typeof c[0] === 'string' && c[0].includes('cli-auth'));
|
|
51
|
-
expect(urlLog).toBeTruthy();
|
|
52
|
-
const url = new URL(urlLog[0].trim());
|
|
53
|
-
const port = parseInt(url.searchParams.get('port'), 10);
|
|
54
|
-
const state = url.searchParams.get('state');
|
|
87
|
+
it('stores auth with firebaseApiKey on valid callback', async () => {
|
|
88
|
+
const { loginPromise, port, state } = await startLoginAndGetParams();
|
|
55
89
|
expect(port).toBeGreaterThanOrEqual(1024);
|
|
56
90
|
expect(state).toHaveLength(64); // 32 bytes hex
|
|
57
91
|
const response = await sendCallback(port, {
|
|
@@ -60,6 +94,7 @@ describe('login', () => {
|
|
|
60
94
|
email: 'user@test.com',
|
|
61
95
|
uid: 'uid-123',
|
|
62
96
|
state,
|
|
97
|
+
firebaseApiKey: 'AIzaTest123',
|
|
63
98
|
});
|
|
64
99
|
expect(response.statusCode).toBe(200);
|
|
65
100
|
await loginPromise;
|
|
@@ -67,33 +102,66 @@ describe('login', () => {
|
|
|
67
102
|
refreshToken: 'test-refresh',
|
|
68
103
|
email: 'user@test.com',
|
|
69
104
|
uid: 'uid-123',
|
|
105
|
+
firebaseApiKey: 'AIzaTest123',
|
|
70
106
|
}));
|
|
71
107
|
expect(mockOutput.success).toHaveBeenCalledWith('Logged in as user@test.com');
|
|
72
108
|
}, 10000);
|
|
73
109
|
it('rejects callback with wrong state', async () => {
|
|
74
|
-
const loginPromise =
|
|
75
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
76
|
-
const urlLog = consoleSpy.mock.calls.find((c) => typeof c[0] === 'string' && c[0].includes('cli-auth'));
|
|
77
|
-
const url = new URL(urlLog[0].trim());
|
|
78
|
-
const port = parseInt(url.searchParams.get('port'), 10);
|
|
110
|
+
const { loginPromise, port, state } = await startLoginAndGetParams();
|
|
79
111
|
const response = await sendCallback(port, {
|
|
80
112
|
token: 'test',
|
|
81
113
|
refreshToken: 'test',
|
|
82
114
|
email: 'test@test.com',
|
|
83
115
|
uid: 'uid',
|
|
84
116
|
state: 'wrong-state-value',
|
|
117
|
+
firebaseApiKey: 'key',
|
|
85
118
|
});
|
|
86
119
|
expect(response.statusCode).toBe(403);
|
|
87
120
|
expect(mockStoreAuth).not.toHaveBeenCalled();
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
121
|
+
await shutdownServer(port, state);
|
|
122
|
+
await loginPromise;
|
|
123
|
+
}, 10000);
|
|
124
|
+
it('responds to CORS preflight with PNA header', async () => {
|
|
125
|
+
const { loginPromise, port, state } = await startLoginAndGetParams();
|
|
126
|
+
const res = await sendOptions(port);
|
|
127
|
+
expect(res.statusCode).toBe(204);
|
|
128
|
+
expect(res.headers['access-control-allow-private-network']).toBe('true');
|
|
129
|
+
expect(res.headers['access-control-allow-origin']).toBe('https://dashboard.jamdesk.com');
|
|
130
|
+
await shutdownServer(port, state);
|
|
131
|
+
await loginPromise;
|
|
132
|
+
}, 10000);
|
|
133
|
+
it('rejects POST without Origin header', async () => {
|
|
134
|
+
const { loginPromise, port, state } = await startLoginAndGetParams();
|
|
135
|
+
// POST without Origin (non-browser client) should be rejected
|
|
136
|
+
const res = await new Promise((resolve, reject) => {
|
|
137
|
+
const data = JSON.stringify({
|
|
138
|
+
refreshToken: 't', email: 'e@e.com', uid: 'u', state, firebaseApiKey: 'k',
|
|
139
|
+
});
|
|
140
|
+
const req = http.request({
|
|
141
|
+
hostname: '127.0.0.1', port, path: '/callback', method: 'POST',
|
|
142
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) },
|
|
143
|
+
}, (r) => { r.resume(); r.on('end', () => resolve({ statusCode: r.statusCode || 0 })); });
|
|
144
|
+
req.on('error', reject);
|
|
145
|
+
req.write(data);
|
|
146
|
+
req.end();
|
|
147
|
+
});
|
|
148
|
+
expect(res.statusCode).toBe(403);
|
|
149
|
+
await shutdownServer(port, state);
|
|
150
|
+
await loginPromise;
|
|
151
|
+
}, 10000);
|
|
152
|
+
it('rejects callback missing firebaseApiKey', async () => {
|
|
153
|
+
const { loginPromise, port, state } = await startLoginAndGetParams();
|
|
154
|
+
const response = await sendCallback(port, {
|
|
155
|
+
token: 'test-id-token',
|
|
156
|
+
refreshToken: 'test-refresh',
|
|
157
|
+
email: 'user@test.com',
|
|
158
|
+
uid: 'uid-123',
|
|
95
159
|
state,
|
|
160
|
+
// no firebaseApiKey
|
|
96
161
|
});
|
|
162
|
+
expect(response.statusCode).toBe(400);
|
|
163
|
+
expect(mockStoreAuth).not.toHaveBeenCalled();
|
|
164
|
+
await shutdownServer(port, state);
|
|
97
165
|
await loginPromise;
|
|
98
166
|
}, 10000);
|
|
99
167
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/login.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC1C,aAAa,EAAE,EAAE,CAAC,EAAE,EAAE;CACvB,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,CAAC;IAClC,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC,CAAC;AAEJ,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACvC,UAAU,EAAE;QACV,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;QACb,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE;QAChB,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;QACd,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;QACb,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;KACd;CACF,CAAC,CAAC,CAAC;AACJ,EAAE,CAAC,IAAI,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;AAC/D,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AAE9C,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAEhD,SAAS,
|
|
1
|
+
{"version":3,"file":"login.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/login.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAiJ3C"}
|
package/dist/commands/login.js
CHANGED
|
@@ -26,8 +26,15 @@ export async function login() {
|
|
|
26
26
|
}
|
|
27
27
|
}, TIMEOUT_MS);
|
|
28
28
|
const server = http.createServer(async (req, res) => {
|
|
29
|
-
// Validate Origin header (defense-in-depth alongside state parameter)
|
|
29
|
+
// Validate Origin header (defense-in-depth alongside state parameter).
|
|
30
|
+
// OPTIONS preflight may omit Origin in non-browser clients, so only
|
|
31
|
+
// require it on POST where browsers always send it cross-origin.
|
|
30
32
|
const origin = req.headers.origin;
|
|
33
|
+
if (req.method === 'POST' && origin !== ALLOWED_ORIGIN) {
|
|
34
|
+
res.writeHead(403);
|
|
35
|
+
res.end();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
31
38
|
if (origin && origin !== ALLOWED_ORIGIN) {
|
|
32
39
|
res.writeHead(403);
|
|
33
40
|
res.end();
|
|
@@ -37,6 +44,7 @@ export async function login() {
|
|
|
37
44
|
res.setHeader('Access-Control-Allow-Origin', ALLOWED_ORIGIN);
|
|
38
45
|
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
39
46
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
47
|
+
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
|
40
48
|
if (req.method === 'OPTIONS') {
|
|
41
49
|
res.writeHead(204);
|
|
42
50
|
res.end();
|
|
@@ -62,24 +70,24 @@ export async function login() {
|
|
|
62
70
|
}
|
|
63
71
|
try {
|
|
64
72
|
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
65
|
-
const { refreshToken, email, uid, state: receivedState } = body;
|
|
73
|
+
const { refreshToken, email, uid, state: receivedState, firebaseApiKey } = body;
|
|
66
74
|
// Verify state matches (anti-CSRF)
|
|
67
75
|
if (receivedState !== state) {
|
|
68
76
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
69
77
|
res.end(JSON.stringify({ error: 'Invalid state parameter' }));
|
|
70
78
|
return;
|
|
71
79
|
}
|
|
72
|
-
if (!refreshToken || !email || !uid) {
|
|
80
|
+
if (!refreshToken || !email || !uid || !firebaseApiKey) {
|
|
73
81
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
74
82
|
res.end(JSON.stringify({ error: 'Missing required fields' }));
|
|
75
83
|
return;
|
|
76
84
|
}
|
|
77
|
-
// Store auth
|
|
78
85
|
await storeAuth({
|
|
79
86
|
refreshToken,
|
|
80
87
|
email,
|
|
81
88
|
uid,
|
|
82
89
|
expiresAt: Date.now() + 3600 * 1000, // 1 hour
|
|
90
|
+
firebaseApiKey,
|
|
83
91
|
});
|
|
84
92
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
85
93
|
res.end(JSON.stringify({ success: true }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,MAAM,aAAa,GAAG,+BAA+B,CAAC;AACtD,MAAM,cAAc,GAAG,IAAI,CAAC;AAC5B,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,YAAY;AACxC,MAAM,cAAc,GAAG,aAAa,CAAC;AAErC,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAErD,qEAAqE;IACrE,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,IAAI,CAAC;gBACf,MAAM,CAAC,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,QAAQ,CAAC,oCAAoC,EAAE,eAAe,CAAC,CAAC,CAAC;YAC9E,CAAC;QACH,CAAC,EAAE,UAAU,CAAC,CAAC;QAEf,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;YAClD,
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/auth.d.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* Tokens are refreshed automatically via the Firebase REST API.
|
|
6
6
|
*/
|
|
7
7
|
import { type AuthConfig } from './config.js';
|
|
8
|
-
declare const FIREBASE_API_KEY = "AIzaSyC415KAqbWNXXyr80nuNIDf6hM1Sv3rOMU";
|
|
9
8
|
declare const FUNCTIONS_BASE_URL = "https://us-central1-jamdesk-prod.cloudfunctions.net";
|
|
10
9
|
export declare function isLoggedIn(config: {
|
|
11
10
|
auth?: AuthConfig;
|
|
@@ -28,7 +27,7 @@ export declare function apiCall(endpoint: string, options?: {
|
|
|
28
27
|
body?: Record<string, unknown>;
|
|
29
28
|
baseUrl?: string;
|
|
30
29
|
}): Promise<Response>;
|
|
31
|
-
export {
|
|
30
|
+
export { FUNCTIONS_BASE_URL };
|
|
32
31
|
/** Reset in-memory token cache (testing only). */
|
|
33
32
|
export declare function _resetTokenCache(): void;
|
|
34
33
|
//# sourceMappingURL=auth.d.ts.map
|
package/dist/lib/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAA0B,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAA0B,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAEtE,QAAA,MAAM,kBAAkB,wDAAwD,CAAC;AAQjF,wBAAgB,UAAU,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,UAAU,CAAA;CAAE,GAAG,OAAO,CAEjE;AAED;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CA0DrD;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAK/D;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAK/C;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;CACb,GACL,OAAO,CAAC,QAAQ,CAAC,CAmBnB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAE9B,kDAAkD;AAClD,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
|
package/dist/lib/auth.js
CHANGED
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
* Tokens are refreshed automatically via the Firebase REST API.
|
|
6
6
|
*/
|
|
7
7
|
import { loadConfig, saveConfig } from './config.js';
|
|
8
|
-
// Public Firebase config — same values as the dashboard
|
|
9
|
-
const FIREBASE_API_KEY = 'AIzaSyC415KAqbWNXXyr80nuNIDf6hM1Sv3rOMU';
|
|
10
8
|
const FUNCTIONS_BASE_URL = 'https://us-central1-jamdesk-prod.cloudfunctions.net';
|
|
11
9
|
// Token refresh buffer: refresh 5 minutes before expiry
|
|
12
10
|
const REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
@@ -29,8 +27,11 @@ export async function getValidToken() {
|
|
|
29
27
|
if (!config.auth?.refreshToken) {
|
|
30
28
|
throw new Error('Not logged in. Run `jamdesk login` first.');
|
|
31
29
|
}
|
|
30
|
+
if (!config.auth.firebaseApiKey) {
|
|
31
|
+
throw new Error('CLI config updated. Run `jamdesk login` to re-authenticate (one-time setup).');
|
|
32
|
+
}
|
|
32
33
|
// Refresh the token
|
|
33
|
-
const response = await fetch(`https://securetoken.googleapis.com/v1/token?key=${
|
|
34
|
+
const response = await fetch(`https://securetoken.googleapis.com/v1/token?key=${config.auth.firebaseApiKey}`, {
|
|
34
35
|
method: 'POST',
|
|
35
36
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
36
37
|
body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(config.auth.refreshToken)}`,
|
|
@@ -97,7 +98,7 @@ export async function apiCall(endpoint, options = {}) {
|
|
|
97
98
|
throw new Error(`API request failed: ${message}`);
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
|
-
export {
|
|
101
|
+
export { FUNCTIONS_BASE_URL };
|
|
101
102
|
/** Reset in-memory token cache (testing only). */
|
|
102
103
|
export function _resetTokenCache() {
|
|
103
104
|
cachedIdToken = null;
|
package/dist/lib/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAmB,MAAM,aAAa,CAAC;AAEtE,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAmB,MAAM,aAAa,CAAC;AAEtE,MAAM,kBAAkB,GAAG,qDAAqD,CAAC;AAEjF,wDAAwD;AACxD,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAExC,yEAAyE;AACzE,IAAI,aAAa,GAAgD,IAAI,CAAC;AAEtE,MAAM,UAAU,UAAU,CAAC,MAA6B;IACtD,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,qCAAqC;IACrC,IAAI,aAAa,IAAI,aAAa,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,EAAE,CAAC;QAC9E,OAAO,aAAa,CAAC,KAAK,CAAC;IAC7B,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAC;IAClG,CAAC;IAED,oBAAoB;IACpB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAC1B,mDAAmD,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,EAC/E;QACE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI,EAAE,0CAA0C,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE;KAC/F,CACF,CAAC;IAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAEvD,CAAC;QACF,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,EAAE,OAAO,IAAI,QAAQ,CAAC,UAAU,CAAC;QAElE,yCAAyC;QACzC,IAAI,CAAC,eAAe,EAAE,gBAAgB,EAAE,uBAAuB,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACrF,MAAM,SAAS,EAAE,CAAC;QACpB,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,+DAA+D,SAAS,GAAG,CAAC,CAAC;IAC/F,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA2B,CAAC;IAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC9B,MAAM,eAAe,GAAG,IAAI,CAAC,aAAa,CAAC;IAC3C,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAEhD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC;IAEhD,+BAA+B;IAC/B,aAAa,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IAE9C,yCAAyC;IACzC,MAAM,CAAC,IAAI,GAAG;QACZ,GAAG,MAAM,CAAC,IAAI;QACd,YAAY,EAAE,eAAe;QAC7B,SAAS;KACV,CAAC;IACF,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;IAEzB,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAgB;IAC9C,aAAa,GAAG,IAAI,CAAC,CAAC,8CAA8C;IACpE,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,aAAa,GAAG,IAAI,CAAC;IACrB,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,OAAO,MAAM,CAAC,IAAI,CAAC;IACnB,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,QAAgB,EAChB,UAII,EAAE;IAEN,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;IACpC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC1C,MAAM,GAAG,GAAG,GAAG,OAAO,IAAI,kBAAkB,IAAI,QAAQ,EAAE,CAAC;IAE3D,IAAI,CAAC;QACH,OAAO,MAAM,KAAK,CAAC,GAAG,EAAE;YACtB,MAAM,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;YACzC,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,KAAK,EAAE;gBAChC,cAAc,EAAE,kBAAkB;aACnC;YACD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChD,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,8DAA8D;QAC9D,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC;QAC7E,MAAM,IAAI,KAAK,CAAC,uBAAuB,OAAO,EAAE,CAAC,CAAC;IACpD,CAAC;AACH,CAAC;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAE9B,kDAAkD;AAClD,MAAM,UAAU,gBAAgB;IAC9B,aAAa,GAAG,IAAI,CAAC;AACvB,CAAC"}
|
package/dist/lib/config.d.ts
CHANGED
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,eAAO,MAAM,WAAW,QAAqC,CAAC;AAE9D,wBAAsB,UAAU,IAAI,OAAO,CAAC,aAAa,CAAC,CAWzD;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAKrE"}
|
package/dist/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAiB1B,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;AAE9D,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAChC,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACxD,OAAO,KAAK,CAAC,KAAK,CAAC,OAAO,CAAkB,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAqB;IACpD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7C,MAAM,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACvD,yEAAyE;IACzE,MAAM,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jamdesk",
|
|
@@ -3,6 +3,31 @@ import { NextRequest } from 'next/server';
|
|
|
3
3
|
|
|
4
4
|
export const runtime = 'edge';
|
|
5
5
|
|
|
6
|
+
const ACCENT_COLOR = '#8B5CF6';
|
|
7
|
+
const MAX_DESCRIPTION_LENGTH = 120;
|
|
8
|
+
const LONG_TITLE_THRESHOLD = 40;
|
|
9
|
+
const FONT_STACK =
|
|
10
|
+
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
|
11
|
+
const ACCENT_GRADIENT = `linear-gradient(90deg, ${ACCENT_COLOR} 0%, #C4B5FD 50%, #A78BFA 100%)`;
|
|
12
|
+
|
|
13
|
+
function getThemeColors(isDark: boolean) {
|
|
14
|
+
return {
|
|
15
|
+
text: isDark ? '#ffffff' : '#1a1a2e',
|
|
16
|
+
muted: isDark ? '#a0a0b0' : '#666680',
|
|
17
|
+
background: isDark
|
|
18
|
+
? 'linear-gradient(135deg, #1a1a3e 0%, #0f0f23 100%)'
|
|
19
|
+
: 'linear-gradient(135deg, #f8f8fc 0%, #ffffff 100%)',
|
|
20
|
+
badgeBackground: isDark
|
|
21
|
+
? 'rgba(139, 92, 246, 0.15)'
|
|
22
|
+
: 'rgba(139, 92, 246, 0.1)',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function truncateText(text: string, maxLength: number): string {
|
|
27
|
+
if (text.length <= maxLength) return text;
|
|
28
|
+
return text.substring(0, maxLength) + '...';
|
|
29
|
+
}
|
|
30
|
+
|
|
6
31
|
/**
|
|
7
32
|
* OG Image Generator
|
|
8
33
|
*
|
|
@@ -26,18 +51,27 @@ export async function GET(request: NextRequest) {
|
|
|
26
51
|
const description = searchParams.get('description') || '';
|
|
27
52
|
const section = searchParams.get('section') || '';
|
|
28
53
|
const siteName = searchParams.get('siteName') || 'Documentation';
|
|
29
|
-
const
|
|
30
|
-
const
|
|
54
|
+
const logoUrl = searchParams.get('logo') || '';
|
|
55
|
+
const isDark = (searchParams.get('theme') || 'dark') === 'dark';
|
|
31
56
|
|
|
32
|
-
|
|
57
|
+
// Fetch logo and convert to base64 data URI for Satori compatibility
|
|
58
|
+
// (Satori doesn't support webp and can fail on remote URLs)
|
|
59
|
+
let logo = '';
|
|
60
|
+
if (logoUrl) {
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetch(logoUrl);
|
|
63
|
+
if (res.ok) {
|
|
64
|
+
const contentType = res.headers.get('content-type') || 'image/png';
|
|
65
|
+
const buf = await res.arrayBuffer();
|
|
66
|
+
logo = `data:${contentType};base64,${Buffer.from(buf).toString('base64')}`;
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Skip logo on fetch failure
|
|
70
|
+
}
|
|
71
|
+
}
|
|
33
72
|
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
const textColor = isDark ? '#ffffff' : '#1a1a2e';
|
|
37
|
-
const mutedColor = isDark ? '#a0a0b0' : '#666680';
|
|
38
|
-
const accentColor = '#8B5CF6'; // Jamdesk purple
|
|
39
|
-
const gradientStart = isDark ? '#1a1a3e' : '#f8f8fc';
|
|
40
|
-
const gradientEnd = isDark ? '#0f0f23' : '#ffffff';
|
|
73
|
+
const colors = getThemeColors(isDark);
|
|
74
|
+
const titleFontSize = title.length > LONG_TITLE_THRESHOLD ? '52px' : '64px';
|
|
41
75
|
|
|
42
76
|
return new ImageResponse(
|
|
43
77
|
(
|
|
@@ -48,15 +82,16 @@ export async function GET(request: NextRequest) {
|
|
|
48
82
|
display: 'flex',
|
|
49
83
|
flexDirection: 'column',
|
|
50
84
|
padding: '60px',
|
|
51
|
-
background:
|
|
52
|
-
fontFamily:
|
|
85
|
+
background: colors.background,
|
|
86
|
+
fontFamily: FONT_STACK,
|
|
53
87
|
}}
|
|
54
88
|
>
|
|
55
|
-
{/*
|
|
89
|
+
{/* Logo and site name */}
|
|
56
90
|
<div
|
|
57
91
|
style={{
|
|
58
92
|
display: 'flex',
|
|
59
|
-
|
|
93
|
+
flexDirection: 'column',
|
|
94
|
+
alignItems: 'flex-start',
|
|
60
95
|
marginBottom: '40px',
|
|
61
96
|
}}
|
|
62
97
|
>
|
|
@@ -64,10 +99,10 @@ export async function GET(request: NextRequest) {
|
|
|
64
99
|
<img
|
|
65
100
|
src={logo}
|
|
66
101
|
alt=""
|
|
67
|
-
width={
|
|
68
|
-
height={
|
|
102
|
+
width={48}
|
|
103
|
+
height={48}
|
|
69
104
|
style={{
|
|
70
|
-
|
|
105
|
+
marginBottom: '16px',
|
|
71
106
|
borderRadius: '8px',
|
|
72
107
|
}}
|
|
73
108
|
/>
|
|
@@ -76,7 +111,7 @@ export async function GET(request: NextRequest) {
|
|
|
76
111
|
style={{
|
|
77
112
|
fontSize: '24px',
|
|
78
113
|
fontWeight: 600,
|
|
79
|
-
color:
|
|
114
|
+
color: colors.muted,
|
|
80
115
|
textTransform: 'uppercase',
|
|
81
116
|
letterSpacing: '0.05em',
|
|
82
117
|
}}
|
|
@@ -97,9 +132,9 @@ export async function GET(request: NextRequest) {
|
|
|
97
132
|
style={{
|
|
98
133
|
fontSize: '18px',
|
|
99
134
|
fontWeight: 500,
|
|
100
|
-
color:
|
|
135
|
+
color: ACCENT_COLOR,
|
|
101
136
|
padding: '8px 16px',
|
|
102
|
-
background:
|
|
137
|
+
background: colors.badgeBackground,
|
|
103
138
|
borderRadius: '20px',
|
|
104
139
|
}}
|
|
105
140
|
>
|
|
@@ -108,7 +143,7 @@ export async function GET(request: NextRequest) {
|
|
|
108
143
|
</div>
|
|
109
144
|
)}
|
|
110
145
|
|
|
111
|
-
{/*
|
|
146
|
+
{/* Title and description */}
|
|
112
147
|
<div
|
|
113
148
|
style={{
|
|
114
149
|
display: 'flex',
|
|
@@ -119,9 +154,9 @@ export async function GET(request: NextRequest) {
|
|
|
119
154
|
>
|
|
120
155
|
<h1
|
|
121
156
|
style={{
|
|
122
|
-
fontSize:
|
|
157
|
+
fontSize: titleFontSize,
|
|
123
158
|
fontWeight: 700,
|
|
124
|
-
color:
|
|
159
|
+
color: colors.text,
|
|
125
160
|
lineHeight: 1.2,
|
|
126
161
|
margin: 0,
|
|
127
162
|
maxWidth: '900px',
|
|
@@ -134,15 +169,13 @@ export async function GET(request: NextRequest) {
|
|
|
134
169
|
<p
|
|
135
170
|
style={{
|
|
136
171
|
fontSize: '28px',
|
|
137
|
-
color:
|
|
172
|
+
color: colors.muted,
|
|
138
173
|
marginTop: '24px',
|
|
139
174
|
lineHeight: 1.4,
|
|
140
175
|
maxWidth: '800px',
|
|
141
176
|
}}
|
|
142
177
|
>
|
|
143
|
-
{description
|
|
144
|
-
? description.substring(0, 120) + '...'
|
|
145
|
-
: description}
|
|
178
|
+
{truncateText(description, MAX_DESCRIPTION_LENGTH)}
|
|
146
179
|
</p>
|
|
147
180
|
)}
|
|
148
181
|
</div>
|
|
@@ -152,7 +185,7 @@ export async function GET(request: NextRequest) {
|
|
|
152
185
|
style={{
|
|
153
186
|
display: 'flex',
|
|
154
187
|
height: '6px',
|
|
155
|
-
background:
|
|
188
|
+
background: ACCENT_GRADIENT,
|
|
156
189
|
borderRadius: '3px',
|
|
157
190
|
marginTop: '40px',
|
|
158
191
|
}}
|
|
@@ -162,6 +195,9 @@ export async function GET(request: NextRequest) {
|
|
|
162
195
|
{
|
|
163
196
|
width: 1200,
|
|
164
197
|
height: 630,
|
|
198
|
+
headers: {
|
|
199
|
+
'Cache-Control': 'public, s-maxage=86400, stale-while-revalidate=604800',
|
|
200
|
+
},
|
|
165
201
|
}
|
|
166
202
|
);
|
|
167
203
|
}
|
package/vendored/lib/seo.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Metadata } from 'next';
|
|
11
|
-
import type { DocsConfig, Logo, LogoConfig, LanguageConfig, LanguageCode } from './docs-types';
|
|
11
|
+
import type { DocsConfig, Logo, LogoConfig, Favicon, FaviconConfig, LanguageConfig, LanguageCode } from './docs-types';
|
|
12
12
|
import { transformLanguagePath, extractLanguageFromPath, isValidLanguageCode } from './language-utils';
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -23,6 +23,7 @@ function buildOgImageUrl(
|
|
|
23
23
|
description?: string;
|
|
24
24
|
section?: string;
|
|
25
25
|
logo?: Logo;
|
|
26
|
+
favicon?: Favicon;
|
|
26
27
|
theme?: 'light' | 'dark';
|
|
27
28
|
}
|
|
28
29
|
): string {
|
|
@@ -40,16 +41,31 @@ function buildOgImageUrl(
|
|
|
40
41
|
params.set('theme', options.theme);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
//
|
|
44
|
+
// For hostAtDocs sites (baseUrl ends with /docs), use the parent domain with
|
|
45
|
+
// /_jd/og path instead of cross-domain /api/og. LinkedIn's crawler fails to
|
|
46
|
+
// fetch OG images from different domains. The /_jd/og path is proxied through
|
|
47
|
+
// the customer's CF Worker/proxy to the ISR app's /api/og via Next.js rewrite.
|
|
48
|
+
const isHostAtDocs = baseUrl.endsWith('/docs');
|
|
44
49
|
const rootUrl = baseUrl.replace(/\/docs$/, '');
|
|
45
50
|
|
|
46
51
|
// Handle logo - prefer dark variant for OG images (dark background)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
// Satori (@vercel/og) can't render webp, so fall back to favicon if logo is webp
|
|
53
|
+
if (options?.logo || options?.favicon) {
|
|
54
|
+
let logoPath = '';
|
|
55
|
+
if (options?.logo) {
|
|
56
|
+
if (typeof options.logo === 'string') {
|
|
57
|
+
logoPath = options.logo;
|
|
58
|
+
} else {
|
|
59
|
+
logoPath = (options.logo as LogoConfig).dark || (options.logo as LogoConfig).light;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Fall back to favicon when logo is webp (Satori can't decode webp)
|
|
63
|
+
if ((!logoPath || logoPath.endsWith('.webp')) && options?.favicon) {
|
|
64
|
+
if (typeof options.favicon === 'string') {
|
|
65
|
+
logoPath = options.favicon;
|
|
66
|
+
} else {
|
|
67
|
+
logoPath = (options.favicon as FaviconConfig).dark || (options.favicon as FaviconConfig).light;
|
|
68
|
+
}
|
|
53
69
|
}
|
|
54
70
|
// Make logo URL absolute (use root URL since assets are at domain root)
|
|
55
71
|
if (logoPath && !logoPath.startsWith('http')) {
|
|
@@ -60,7 +76,10 @@ function buildOgImageUrl(
|
|
|
60
76
|
}
|
|
61
77
|
}
|
|
62
78
|
|
|
63
|
-
|
|
79
|
+
// hostAtDocs: use /_jd/og (proxied to ISR app's /api/og) for same-domain OG images
|
|
80
|
+
// non-hostAtDocs: use /api/og directly (already same-domain)
|
|
81
|
+
const ogPath = isHostAtDocs ? '/_jd/og' : '/api/og';
|
|
82
|
+
return `${rootUrl}${ogPath}?${params.toString()}`;
|
|
64
83
|
}
|
|
65
84
|
|
|
66
85
|
export interface PageFrontmatter {
|
|
@@ -137,7 +156,7 @@ export function buildSeoMetadata(
|
|
|
137
156
|
frontmatter: PageFrontmatter,
|
|
138
157
|
pagePath: string,
|
|
139
158
|
baseUrl: string,
|
|
140
|
-
languages?: LanguageConfig[]
|
|
159
|
+
languages?: LanguageConfig[],
|
|
141
160
|
): Partial<Metadata> {
|
|
142
161
|
const globalMeta = config.seo?.metatags || {};
|
|
143
162
|
const pageMeta = (frontmatter.seo as Record<string, string>) || {};
|
|
@@ -210,6 +229,7 @@ export function buildSeoMetadata(
|
|
|
210
229
|
description: frontmatter.description,
|
|
211
230
|
section: frontmatter.section,
|
|
212
231
|
logo: config.logo,
|
|
232
|
+
favicon: config.favicon,
|
|
213
233
|
theme: 'dark', // Dark theme looks better for OG images
|
|
214
234
|
}
|
|
215
235
|
);
|
|
@@ -220,13 +240,19 @@ export function buildSeoMetadata(
|
|
|
220
240
|
url: metatags['og:url'] || pageUrl,
|
|
221
241
|
siteName: metatags['og:site_name'] || config.name,
|
|
222
242
|
type: (metatags['og:type'] as 'website' | 'article') || 'website',
|
|
223
|
-
images: [
|
|
243
|
+
images: [{
|
|
244
|
+
url: ogImageUrl,
|
|
245
|
+
width: 1200,
|
|
246
|
+
height: 630,
|
|
247
|
+
type: 'image/png',
|
|
248
|
+
}],
|
|
224
249
|
};
|
|
225
250
|
}
|
|
226
251
|
|
|
227
252
|
// 9. Twitter Cards
|
|
228
253
|
// Auto-generate Twitter card with OG image
|
|
229
|
-
const
|
|
254
|
+
const ogImages = (metadata.openGraph as { images?: Array<{ url: string }> })?.images;
|
|
255
|
+
const twitterImageUrl = metatags['twitter:image'] || ogImages?.[0]?.url;
|
|
230
256
|
metadata.twitter = {
|
|
231
257
|
card: (metatags['twitter:card'] as 'summary' | 'summary_large_image') || 'summary_large_image',
|
|
232
258
|
site: metatags['twitter:site'],
|