Skip to content

Commit b6ca41c

Browse files
committed
fix: auth and unpublish
1 parent 727869a commit b6ca41c

3 files changed

Lines changed: 124 additions & 8 deletions

File tree

src/eds-admin/client.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,27 @@ export class EDSAdminClient {
2727
return stripped.startsWith('/') ? stripped.slice(1) : stripped;
2828
}
2929

30-
private authHeaders(): Headers {
30+
private authHeaders(includeContentSource = true): Headers {
3131
const headers = new Headers();
3232
headers.set('Authorization', `Bearer ${this.apiToken}`);
33-
headers.set('x-content-source-authorization', `Bearer ${this.apiToken}`);
33+
if (includeContentSource) {
34+
headers.set('x-content-source-authorization', `Bearer ${this.apiToken}`);
35+
}
3436
return headers;
3537
}
3638

37-
private async post(endpoint: string): Promise<Response> {
39+
private async request(method: 'POST' | 'DELETE', endpoint: string, includeContentSource = true): Promise<Response> {
3840
const url = `https://admin.hlx.page${endpoint}`;
39-
const headers = this.authHeaders();
40-
console.log(`EDS Admin API Call: POST ${url}`);
41+
const headers = this.authHeaders(includeContentSource);
42+
console.log(`EDS Admin API Call: ${method} ${url}`);
4143

4244
const controller = new AbortController();
4345
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
4446
const startTime = Date.now();
4547

4648
try {
4749
const response = await fetch(url, {
48-
method: 'POST',
50+
method,
4951
headers,
5052
signal: controller.signal,
5153
});
@@ -101,7 +103,17 @@ export class EDSAdminClient {
101103
*/
102104
async preview(owner: string, repo: string, path: string): Promise<EDSOperationResult> {
103105
const normPath = this.normalisePath(path);
104-
const response = await this.post(`/preview/${owner}/${repo}/main/${normPath}`);
106+
const response = await this.request('POST', `/preview/${owner}/${repo}/main/${normPath}`);
107+
return this.parseResponse(response, 'preview');
108+
}
109+
110+
/**
111+
* Remove the given page from the preview environment.
112+
* DELETE https://admin.hlx.page/preview/{owner}/{repo}/main/{path}
113+
*/
114+
async unpreview(owner: string, repo: string, path: string): Promise<EDSOperationResult> {
115+
const normPath = this.normalisePath(path);
116+
const response = await this.request('DELETE', `/preview/${owner}/${repo}/main/${normPath}`, false);
105117
return this.parseResponse(response, 'preview');
106118
}
107119

@@ -111,7 +123,17 @@ export class EDSAdminClient {
111123
*/
112124
async publishLive(owner: string, repo: string, path: string): Promise<EDSOperationResult> {
113125
const normPath = this.normalisePath(path);
114-
const response = await this.post(`/live/${owner}/${repo}/main/${normPath}`);
126+
const response = await this.request('POST', `/live/${owner}/${repo}/main/${normPath}`, false);
127+
return this.parseResponse(response, 'live');
128+
}
129+
130+
/**
131+
* Unpublish the given page from the live environment.
132+
* DELETE https://admin.hlx.page/live/{owner}/{repo}/main/{path}
133+
*/
134+
async unpublishLive(owner: string, repo: string, path: string): Promise<EDSOperationResult> {
135+
const normPath = this.normalisePath(path);
136+
const response = await this.request('DELETE', `/live/${owner}/${repo}/main/${normPath}`, false);
115137
return this.parseResponse(response, 'live');
116138
}
117139
}

src/tools/tools.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,5 +450,43 @@ export function createEDSTools(client: EDSAdminClient) {
450450
},
451451
});
452452

453+
tools.content_unpreview = tool({
454+
description:
455+
'Remove a page from the EDS (Edge Delivery Services) preview environment. '
456+
+ 'Use this to retract a page from preview without affecting the live site.',
457+
inputSchema: z.object({
458+
org: z.string().describe('Organization name (owner)'),
459+
repo: z.string().describe('Repository / site name'),
460+
path: z.string().describe('Page path (e.g. "/docs/index" or "/docs/index.html" — .html will be stripped)'),
461+
}),
462+
execute: async ({ org, repo, path }): Promise<EDSOperationResult | EDSToolError> => {
463+
try {
464+
return await client.unpreview(org, repo, path);
465+
} catch (e) {
466+
if (isAPIError(e)) return { error: e.message, status: e.status };
467+
return { error: String(e) };
468+
}
469+
},
470+
});
471+
472+
tools.content_unpublish = tool({
473+
description:
474+
'Unpublish a page from the EDS (Edge Delivery Services) live environment. '
475+
+ 'Removes the page from the live site without deleting the source content.',
476+
inputSchema: z.object({
477+
org: z.string().describe('Organization name (owner)'),
478+
repo: z.string().describe('Repository / site name'),
479+
path: z.string().describe('Page path (e.g. "/docs/index" or "/docs/index.html" — .html will be stripped)'),
480+
}),
481+
execute: async ({ org, repo, path }): Promise<EDSOperationResult | EDSToolError> => {
482+
try {
483+
return await client.unpublishLive(org, repo, path);
484+
} catch (e) {
485+
if (isAPIError(e)) return { error: e.message, status: e.status };
486+
return { error: String(e) };
487+
}
488+
},
489+
});
490+
453491
return tools;
454492
}

test/eds-tools.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import { createDATools, createEDSTools } from '../src/tools/tools';
88
function makeEdsClient(overrides: Partial<EDSAdminClient> = {}): EDSAdminClient {
99
return {
1010
preview: vi.fn().mockResolvedValue({ status: 200, path: '/docs/index', url: 'https://main--repo--org.hlx.page/docs/index' }),
11+
unpreview: vi.fn().mockResolvedValue({ status: 200, path: '/docs/index' }),
1112
publishLive: vi.fn().mockResolvedValue({ status: 200, path: '/docs/index', url: 'https://main--repo--org.hlx.live/docs/index' }),
13+
unpublishLive: vi.fn().mockResolvedValue({ status: 200, path: '/docs/index' }),
1214
...overrides,
1315
} as unknown as EDSAdminClient;
1416
}
@@ -85,6 +87,60 @@ describe('eds_publish tool', () => {
8587
});
8688
});
8789

90+
describe('content_unpreview tool', () => {
91+
it('is registered when edsClient is provided', () => {
92+
const tools = createEDSTools(makeEdsClient());
93+
expect(tools).toHaveProperty('content_unpreview');
94+
});
95+
96+
it('calls edsClient.unpreview() and returns result', async () => {
97+
const edsClient = makeEdsClient();
98+
const tools = createEDSTools(edsClient);
99+
100+
const result = await tools.content_unpreview.execute({ org: 'o', repo: 'r', path: '/docs/index' }, {} as any);
101+
102+
expect(edsClient.unpreview).toHaveBeenCalledWith('o', 'r', '/docs/index');
103+
expect(result).toMatchObject({ status: 200, path: '/docs/index' });
104+
});
105+
106+
it('returns { error, status } when unpreview throws', async () => {
107+
const edsClient = makeEdsClient({
108+
unpreview: vi.fn().mockRejectedValue({ status: 404, message: 'Not Found' }),
109+
});
110+
const tools = createEDSTools(edsClient);
111+
112+
const result = await tools.content_unpreview.execute({ org: 'o', repo: 'r', path: '/p' }, {} as any);
113+
expect(result).toMatchObject({ error: 'Not Found', status: 404 });
114+
});
115+
});
116+
117+
describe('content_unpublish tool', () => {
118+
it('is registered when edsClient is provided', () => {
119+
const tools = createEDSTools(makeEdsClient());
120+
expect(tools).toHaveProperty('content_unpublish');
121+
});
122+
123+
it('calls edsClient.unpublishLive() and returns result', async () => {
124+
const edsClient = makeEdsClient();
125+
const tools = createEDSTools(edsClient);
126+
127+
const result = await tools.content_unpublish.execute({ org: 'o', repo: 'r', path: '/docs/index' }, {} as any);
128+
129+
expect(edsClient.unpublishLive).toHaveBeenCalledWith('o', 'r', '/docs/index');
130+
expect(result).toMatchObject({ status: 200, path: '/docs/index' });
131+
});
132+
133+
it('returns { error, status } when unpublishLive throws', async () => {
134+
const edsClient = makeEdsClient({
135+
unpublishLive: vi.fn().mockRejectedValue({ status: 502, message: 'Live error' }),
136+
});
137+
const tools = createEDSTools(edsClient);
138+
139+
const result = await tools.content_unpublish.execute({ org: 'o', repo: 'r', path: '/p' }, {} as any);
140+
expect(result).toMatchObject({ error: 'Live error', status: 502 });
141+
});
142+
});
143+
88144
describe('DA tools still registered when client provided', () => {
89145
it('da_list_sources present when daClient is provided', () => {
90146
// Minimal mock for DAAdminClient

0 commit comments

Comments
 (0)