Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/code/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
* governing permissions and limitations under the License.
*/
import { Response } from '@adobe/fetch';
import listBranches from './list-branches.js';
import status from './status.js';

/**
* Allowed methods for that handler.
*/
const ALLOWED_METHODS = ['POST'];
const ALLOWED_METHODS = ['GET', 'POST', 'DELETE'];

/**
* Handles the code route
Expand All @@ -29,6 +31,12 @@ export default async function codeHandler(context, info) {
status: 405,
});
}
if (info.method === 'GET') {
if (info.ref === '*') {
return listBranches(context, info);
}
return status(context, info);
}
return new Response('NYI', {
status: 405,
});
Expand Down
59 changes: 59 additions & 0 deletions src/code/info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { fetchS3 } from '@adobe/helix-admin-support';

/**
* Returns the code bus info for the given resource. If the resource is missing, it will not
* have a contentType.
*
* @param {import('../support/AdminContext').AdminContext} context context
* @param {import('../support/RequestInfo').RequestInfo} info request info
*
* @returns {Promise<CodeBusResource>} a resource
*/
export async function getCodeBusInfo(context, info) {
const { attributes: { authInfo, bucketMap: { code } } } = context;
const {
owner, repo, ref, rawPath,
} = info;

if (!authInfo.hasPermissions('code:read')) {
return {
status: 403,
};
}
if (!info.rawPath) {
return {
status: 400,
permissions: authInfo.getPermissions('code:'),
};
}

const key = `${owner}/${repo}/${ref}${info.rawPath}`;
const resp = await fetchS3(context, 'code', key, true);
const ret = {
status: resp.status,
codeBusId: `${code}/${key}`,
permissions: authInfo.getPermissions('code:'),
};
const { GH_RAW_URL = 'https://raw.githubusercontent.com' } = context.env;
if (resp.ok) {
ret.contentType = resp.headers.get('content-type');
ret.lastModified = resp.headers.get('last-modified');
ret.contentLength = resp.headers.get('x-source-content-length') || undefined;
ret.sourceLastModified = resp.headers.get('x-source-last-modified') || undefined;
ret.sourceLocation = `${GH_RAW_URL}/${owner}/${repo}/${ref}${rawPath}`;
} else if (resp.status !== 404) {
ret.error = resp.headers.get('x-error');
}
return ret;
}
41 changes: 41 additions & 0 deletions src/code/list-branches.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { Response } from '@adobe/fetch';
import { HelixStorage } from '@adobe/helix-shared-storage';

/**
* Lists the branches of the repository present in code bus (not github)
*
* @param {import('../support/AdminContext').AdminContext} context context
* @param {import('../support/RequestInfo').RequestInfo} info request info
* @returns {Promise<Response>} response
*/
export default async function listBranches(context, info) {
context.attributes.authInfo.assertPermissions('code:read');

const { owner, repo, path } = info;
const codeBus = HelixStorage.fromContext(context).codeBus();
const branches = await codeBus.listFolders(`${owner}/${repo}/`);
const resp = {
owner,
repo,
branches: branches
.filter((branch) => !branch.endsWith('.helix/'))
.map((branch) => `/code/${branch.substring(0, branch.length - 1)}${path}`),
};

return new Response(JSON.stringify(resp, null, 2), {
headers: {
'content-type': 'application/json',
},
});
}
60 changes: 60 additions & 0 deletions src/code/status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { Response } from '@adobe/fetch';
import { getCodeBusInfo } from './info.js';
import { StatusCodeError } from '../support/StatusCodeError.js';

/**
* Updates a code resource by fetching the content from github and storing it in the code-bus.
*
* @param {import('../support/AdminContext').AdminContext} context context
* @param {import('../support/RequestInfo').RequestInfo} info request info
* @returns {Promise<Response>} response
*/
export default async function status(context, info) {
const {
owner, repo, ref, resourcePath,
} = info;

const codeInfo = await getCodeBusInfo(context, info);
if (codeInfo.status === 404) {
return new Response('', {
status: 404,
});
}
if (codeInfo.status !== 200) {
throw new StatusCodeError(codeInfo.error, codeInfo.status);
}

const resp = {
webPath: info.resourcePath,
resourcePath: info.resourcePath,
code: codeInfo,
live: {
url: info.getLiveUrl(),
},
preview: {
url: info.getPreviewUrl(),
},
edit: {
url: `https://github.com/${owner}/${repo}/edit/${ref}${resourcePath}`,
},
// TODO: should be derived from route
// links: getAPIUrls(ctx, info, 'status', 'preview', 'live', 'code'),
};

return new Response(JSON.stringify(resp, null, 2), {
headers: {
'content-type': 'application/json',
},
});
}
6 changes: 3 additions & 3 deletions src/discover/cdn-identifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ export function generate(config) {
* @param {import('../support/RequestInfo').RequestInfo} info request info
* @returns {Promise<ProductionSite[]>} list of org/site that match
*/
export async function querySiblingSites(ctx, info) {
const { log } = ctx;
export async function querySiblingSites(context, info) {
const { log } = context;
const { owner, repo } = info;
const codeBusId = `${owner}/${repo}`;

const inventory = new Inventory(log, HelixStorage.fromContext(ctx).contentBus());
const inventory = new Inventory(log, HelixStorage.fromContext(context).contentBus());
if (!await inventory.load()) {
log.warn('Inventory not available');
return [];
Expand Down
4 changes: 3 additions & 1 deletion src/status/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import { Response } from '@adobe/fetch';
import { cleanupHeaderValue } from '@adobe/helix-shared-utils';
import { AccessDeniedError } from '../auth/AccessDeniedError.js';
import { LOGOUT_PATH } from '../auth/support.js';
import { getCodeBusInfo } from '../code/info.js';
import getLiveInfo from '../live/info.js';
import getPreviewInfo from '../preview/info.js';
import web2edit from '../lookup/web2edit.js';
import edit2web from '../lookup/edit2web.js';
import getPreviewInfo from '../preview/info.js';

/**
* Handles GET status.
Expand Down Expand Up @@ -112,6 +113,7 @@ export default async function status(context, info) {
live: await getLiveInfo(context, info),
preview: await getPreviewInfo(context, info),
edit,
code: await getCodeBusInfo(context, info),
// TODO links: getAPIUrls(context, info, 'status', 'preview', 'live', 'code'),
};

Expand Down
15 changes: 14 additions & 1 deletion test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ describe('Index Tests', () => {
nock.siteConfig(SITE_CONFIG, { org: 'owner', site: 'repo' });
nock.orgConfig(ORG_CONFIG, { org: 'owner' });

const result = await main(new Request('https://localhost/'), {
const result = await main(new Request('https://localhost/', {
method: 'PUT',
}), {
pathInfo: {
suffix: '/owner/sites/repo/code/main/',
},
Expand Down Expand Up @@ -125,6 +127,9 @@ describe('Index Tests', () => {
.reply(200, '', { 'last-modified': 'Thu, 08 Jul 2021 09:04:16 GMT' })
.getObject('/preview/redirects.json')
.reply(404);
nock.code()
.head('/document')
.reply(404);

const result = await main(new Request('https://localhost/'), {
pathInfo: {
Expand All @@ -139,6 +144,14 @@ describe('Index Tests', () => {
});
assert.strictEqual(result.status, 200);
assert.deepStrictEqual(await result.json(), {
code: {
codeBusId: 'helix-code-bus/owner/repo/main/document',
permissions: [
'read',
'write',
],
status: 404,
},
edit: {},
live: {
contentBusId: 'helix-content-bus/853bced1f82a05e9d27a8f63ecac59e70d9c14680dc5e417429f65e988f/live/document.md',
Expand Down
29 changes: 29 additions & 0 deletions test/status/handler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ describe('Status Tests', () => {

assert.strictEqual(result.status, 200);
assert.deepStrictEqual(await result.json(), {
code: {
status: 403,
},
edit: {
status: 403,
},
Expand Down Expand Up @@ -137,6 +140,11 @@ describe('Status Tests', () => {
modifiedTime: 'Tue, 15 Jun 2021 03:54:28 GMT',
}], '1BHM3lyqi0bEeaBZho8UD328oFsmsisyJ');

// getCodeBusInfo
nock.code()
.head('/folder/page')
.reply(404);

// getContentBusInfo (preview/live)
nock.content()
.head('/preview/folder/page.md')
Expand Down Expand Up @@ -200,6 +208,14 @@ describe('Status Tests', () => {
sourceLocation: 'gdrive:1LSIpJMKoYeVn8-o4c2okZ6x0EwdGKtgOEkaxbnM8nZ4',
status: 200,
},
code: {
codeBusId: 'helix-code-bus/owner/repo/main/folder/page',
permissions: [
'read',
'write',
],
status: 404,
},
});
});

Expand All @@ -222,6 +238,11 @@ describe('Status Tests', () => {
modifiedTime: 'Tue, 15 Jun 2021 03:54:28 GMT',
});

// getCodeBusInfo
nock.code()
.head('/')
.reply(404);

// getContentBusInfo (preview/live)
nock.content()
.head('/preview/index.md')
Expand All @@ -238,6 +259,14 @@ describe('Status Tests', () => {
});
assert.strictEqual(result.status, 200);
assert.deepStrictEqual(await result.json(), {
code: {
codeBusId: 'helix-code-bus/owner/repo/main/',
permissions: [
'read',
'write',
],
status: 404,
},
edit: {
contentType: 'application/vnd.google-apps.document',
folders: [
Expand Down
5 changes: 5 additions & 0 deletions test/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ export function Nock() {
return scope;
};

nocker.code = () => {
const { code: { owner, repo } } = SITE_CONFIG;
return nocker.s3('helix-code-bus', `${owner}/${repo}/main`);
};

nocker.content = (contentBusId) => nocker.s3('helix-content-bus', contentBusId ?? SITE_CONFIG.content.contentBusId);

nocker.media = (contentBusId) => nocker.s3('helix-media-bus', contentBusId ?? SITE_CONFIG.content.contentBusId);
Expand Down