feat(backend): Add Gitea proxy backend and LFS support for Gitea backend
Some checks failed
Build / build (push) Waiting to run
Build / lint (push) Waiting to run
Build / test (push) Waiting to run
Build / integration_tests (push) Waiting to run
Cypress Tests / cypress-run (1) (push) Waiting to run
Cypress Tests / cypress-run (2) (push) Waiting to run
CodeQL / Analyze (javascript) (push) Has been cancelled

This commit is contained in:
Denys Konovalov 2024-12-28 11:41:05 +01:00
parent c42e4e9bd8
commit cebf4d0a9d
Signed by: Denys Konovalov
GPG Key ID: 0037E1B0E33BD2C9
17 changed files with 2530 additions and 951 deletions

17
nx.json
View File

@ -1,25 +1,16 @@
{
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": [
"build",
"test"
]
}
}
},
"targetDefaults": {
"build": {
"dependsOn": [
"^build"
]
],
"cache": true
},
"dev": {
"dependsOn": [
"^build"
]
],
"cache": true
}
},
"$schema": "./node_modules/nx/schemas/nx-schema.json",

View File

@ -31,15 +31,15 @@
"@simonsmith/cypress-image-snapshot": "9.0.1",
"@types/mocha": "10.0.6",
"all-contributors-cli": "6.26.1",
"cypress": "13.17.0",
"cypress-plugin-tab": "1.0.5",
"cypress-real-events": "1.11.0",
"cypress": "13.6.2",
"execa": "8.0.1",
"husky": "8.0.3",
"lerna": "8.0.1",
"lerna": "8.1.9",
"lint-staged": "15.2.0",
"react-dom": "18.2.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"rehype": "13.0.1",
"unist-util-visit": "5.0.0"
},

View File

@ -48,7 +48,7 @@
},
"devDependencies": {
"@babel/cli": "7.23.4",
"@babel/core": "7.23.7",
"@babel/core": "7.26.0",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-export-default-from": "7.23.3",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
@ -57,7 +57,7 @@
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.23.7",
"@babel/preset-react": "7.23.3",
"@babel/preset-react": "7.26.3",
"@babel/preset-typescript": "7.23.3",
"@types/node": "18.19.4",
"@types/react": "18.2.46",
@ -69,7 +69,7 @@
"babel-loader": "9.1.3",
"babel-plugin-inline-json-import": "0.3.2",
"babel-plugin-inline-react-svg": "2.0.2",
"babel-plugin-lodash": "3.3.4",
"@sigmacomputing/babel-plugin-lodash": "3.3.5",
"babel-plugin-transform-builtin-extend": "1.1.2",
"babel-plugin-transform-define": "2.1.4",
"babel-plugin-transform-export-extensions": "6.22.0",

View File

@ -8,7 +8,7 @@ const isESM = process.env.NODE_ENV === 'esm';
console.info('Build Package:', path.basename(process.cwd()));
const defaultPlugins = [
'lodash',
'@sigmacomputing/babel-plugin-lodash',
[
'babel-plugin-transform-builtin-extend',
{

View File

@ -69,9 +69,9 @@
"@mdx-js/mdx": "3.0.0",
"@mdx-js/react": "3.0.0",
"@mui/base": "5.0.0-beta.30",
"@mui/material": "5.15.3",
"@mui/system": "5.15.3",
"@mui/x-date-pickers": "7.3.1",
"@mui/material": "6.3.0",
"@mui/system": "6.3.0",
"@mui/x-date-pickers": "7.23.3",
"@reduxjs/toolkit": "1.9.7",
"@styled-icons/bootstrap": "10.47.0",
"@styled-icons/fa-brands": "10.47.0",
@ -88,7 +88,7 @@
"@udecode/plate-serializer-md": "23.7.4",
"@uiw/codemirror-extensions-langs": "4.19.16",
"@uiw/react-codemirror": "4.19.16",
"ajv": "8.12.0",
"ajv": "8.17.1",
"ajv-errors": "3.0.0",
"ajv-keywords": "5.1.0",
"buffer": "6.0.3",
@ -166,7 +166,7 @@
"symbol-observable": "4.0.0",
"unified": "11.0.4",
"unist-util-visit": "5.0.0",
"url": "0.11.3",
"url": "0.11.4",
"url-join": "5.0.0",
"uuid": "9.0.1",
"validate-color": "2.2.4",
@ -174,21 +174,23 @@
"vfile-message": "4.0.2",
"vfile-statistics": "3.0.0",
"what-the-diff": "0.6.0",
"yaml": "2.3.4"
"yaml": "2.3.4",
"isomorphic-git": "1.27.2",
"@isomorphic-git/lightning-fs": "4.6.0"
},
"devDependencies": {
"@babel/cli": "7.23.4",
"@babel/core": "7.23.7",
"@babel/cli": "7.26.4",
"@babel/core": "7.26.0",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-export-default-from": "7.23.3",
"@babel/plugin-proposal-export-default-from": "7.25.9",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-numeric-separator": "7.18.6",
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.23.7",
"@babel/preset-react": "7.23.3",
"@babel/preset-typescript": "7.23.3",
"@babel/preset-env": "7.26.0",
"@babel/preset-react": "7.26.3",
"@babel/preset-typescript": "7.26.0",
"@iarna/toml": "2.2.5",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
"@simbathesailor/use-what-changed": "2.0.0",
@ -220,7 +222,7 @@
"babel-loader": "9.1.3",
"babel-plugin-inline-json-import": "0.3.2",
"babel-plugin-inline-react-svg": "2.0.2",
"babel-plugin-lodash": "3.3.4",
"@sigmacomputing/babel-plugin-lodash": "3.3.5",
"babel-plugin-transform-builtin-extend": "1.1.2",
"babel-plugin-transform-define": "2.1.4",
"babel-plugin-transform-export-extensions": "6.22.0",

View File

@ -0,0 +1,220 @@
import FS from '@isomorphic-git/lightning-fs';
import * as git from 'isomorphic-git';
import http from 'isomorphic-git/http/web';
import type { WorkflowStatus } from '@staticcms/core/constants/publishModes';
import type { AssetProxy } from '@staticcms/core/valueObjects';
import type {
Backend,
BackendClass,
BackendEntry,
Config,
ConfigWithDefaults,
Credentials,
Cursor,
DisplayURL,
ImplementationEntry,
ImplementationFile,
PersistOptions,
User,
} from '@staticcms/core';
const dir = '/repo';
let singleton: Promise<string[]>;
function determineRepositoryURL(backend: Backend): string {
const name = backend.name;
if (name.startsWith('github')) {
return `https://github.com/${backend.repo}.git`;
} else if (name.startsWith('gitlab')) {
return `https://gitlab.com/${backend.repo}.git`;
} else if (name.startsWith('gitea')) {
return `${backend.base_url}/${backend.repo}.git`;
} else if (name.startsWith('git-gateway')) {
if (!backend.repo) {
throw new Error(
"Repository URL mandatory for 'git-gateway' configuration (https://[...]/repoName.git)",
);
}
return backend.repo!;
}
throw new Error("Can't determine repository URL");
}
export default function GitProxyBackEndGenerator(
T: new (config: ConfigWithDefaults, options?: Object) => BackendClass,
) {
class GitProxyBackend implements BackendClass {
backend: BackendClass;
config: Config;
fs: FS;
pfs: FS.PromisifiedFS;
repositoryUrl: string;
repository: Promise<string[]>;
constructor(config: ConfigWithDefaults, options = {}) {
this.backend = new T(config, options);
this.config = config;
this.fs = new FS('decapfs');
this.pfs = this.fs.promises;
this.repositoryUrl = determineRepositoryURL(config.backend);
if (!singleton) {
singleton = this.getRepository();
}
this.repository = singleton;
}
async getRepository() {
const branch = this.config.backend.branch || 'main';
try {
await this.pfs.stat(dir);
} catch (e) {
await this.pfs.mkdir(dir);
await git.init({
fs: this.fs,
dir,
defaultBranch: branch,
});
}
await git.addRemote({
fs: this.fs,
dir,
url: this.repositoryUrl,
remote: 'origin',
force: true,
});
await git.fetch({
fs: this.fs,
http,
dir,
remote: 'origin',
ref: branch,
singleBranch: true,
depth: 1,
});
await git.checkout({
fs: this.fs,
dir,
ref: branch,
force: true,
track: false,
});
return this.pfs.readdir(dir);
}
isGitBackend() {
return true;
}
async entriesByFolder(folder: string, extension: string) {
try {
await this.repository;
const files = await this.pfs.readdir(`${dir}/${folder}`);
const relevantFiles = files.filter((name: string) => name.endsWith(extension));
return Promise.all(
relevantFiles.map(async (filename: string) => {
const path = `${folder}/${filename}`;
const fullPath = `${dir}/${path}`;
let data = await this.pfs.readFile(fullPath, 'utf8');
if (data instanceof Uint8Array) {
data = new TextDecoder().decode(data);
}
return {
data,
file: { path, id: path },
};
}),
);
} catch {
return [];
}
}
async getEntry(path: string) {
await this.repository;
let data = await this.pfs.readFile(`${dir}/${path}`, 'utf8');
if (data instanceof Uint8Array) {
data = new TextDecoder().decode(data);
}
return {
file: { path, id: null },
data,
};
}
status() {
return this.backend.status();
}
authComponent() {
return this.backend.authComponent();
}
restoreUser(user: User) {
return this.backend.restoreUser(user);
}
authenticate(credentials: Credentials) {
return this.backend.authenticate(credentials);
}
logout() {
return this.backend.logout();
}
getToken() {
return this.backend.getToken();
}
traverseCursor(cursor: Cursor, action: string) {
return this.backend.traverseCursor!(cursor, action);
}
entriesByFiles(files: ImplementationFile[]) {
return this.backend.entriesByFiles(files);
}
unpublishedEntries() {
return this.backend.unpublishedEntries();
}
unpublishedEntry(args: { id?: string; collection?: string; slug?: string }) {
return this.backend.unpublishedEntry(args);
}
unpublishedEntryDataFile(collection: string, slug: string, path: string, id: string) {
return this.backend.unpublishedEntryDataFile(collection, slug, path, id);
}
unpublishedEntryMediaFile(collection: string, slug: string, path: string, id: string) {
return this.backend.unpublishedEntryMediaFile(collection, slug, path, id);
}
deleteUnpublishedEntry(collection: string, slug: string) {
return this.backend.deleteUnpublishedEntry(collection, slug);
}
persistEntry(entry: BackendEntry, opts: PersistOptions) {
return this.backend.persistEntry(entry, opts);
}
updateUnpublishedEntryStatus(collection: string, slug: string, newStatus: WorkflowStatus) {
return this.backend.updateUnpublishedEntryStatus(collection, slug, newStatus);
}
publishUnpublishedEntry(collection: string, slug: string) {
return this.backend.publishUnpublishedEntry(collection, slug);
}
getMedia(folder?: string, folderSupport?: boolean) {
return this.backend.getMedia(folder, folderSupport);
}
getMediaFile(path: string) {
return this.backend.getMediaFile(path);
}
getMediaDisplayURL(displayURL: DisplayURL): Promise<string> {
return this.backend.getMediaDisplayURL(displayURL);
}
persistMedia(file: AssetProxy, opts: PersistOptions) {
return this.backend.persistMedia(file, opts);
}
deleteFiles(paths: string[], commitMessage: string) {
return this.backend.deleteFiles(paths, commitMessage);
}
getDeployPreview(collectionName: string, slug: string) {
return this.backend.getDeployPreview(collectionName, slug);
}
allEntriesByFolder(
folder: string,
extension: string,
depth: number,
pathRegex?: RegExp,
): Promise<ImplementationEntry[]> {
return this.backend.allEntriesByFolder(folder, extension, depth, pathRegex);
}
}
return GitProxyBackend;
}

View File

@ -0,0 +1,3 @@
import GitProxyBackEndGenerator from './implementation';
export default GitProxyBackEndGenerator;

View File

@ -273,11 +273,13 @@ export default class API {
parseText?: boolean;
} = {},
) {
if (!parseText) {
return await this.fetchMediaContent(path, branch, repoURL);
}
if (!sha) {
sha = await this.getFileSha(path, { repoURL, branch });
}
const content = await this.fetchBlobContent({ sha: sha as string, repoURL, parseText });
return content;
return await this.fetchBlobContent({ sha: sha as string, repoURL, parseText });
}
async readFileMetadata(path: string, sha: string | null | undefined) {
@ -323,6 +325,16 @@ export default class API {
}
}
async fetchMediaContent(path: string, branch: string, repoURL: string) {
return await this.request(
`${repoURL}/media/${path}??ref=${branch}`,
{
cache: 'force-cache',
},
response => response.blob(),
);
}
async listFiles(
path: string,
{ repoURL = this.repoURL, branch = this.branch, depth = 1 } = {},

View File

@ -372,11 +372,11 @@ export default class Gitea implements BackendClass {
break;
}
case 'next': {
result = this.getCursorAndFiles(files, (meta?.['page'] as number) + 1 ?? 1);
result = this.getCursorAndFiles(files, ((meta?.['page'] as number) ?? 1) + 1);
break;
}
case 'prev': {
result = this.getCursorAndFiles(files, (meta?.['page'] as number) - 1 ?? 1);
result = this.getCursorAndFiles(files, ((meta?.['page'] as number) ?? 1) - 1);
break;
}
default: {

View File

@ -526,11 +526,11 @@ export default class GitHub implements BackendClass {
break;
}
case 'next': {
result = this.getCursorAndFiles(files, (meta?.['page'] as number) + 1 ?? 1);
result = this.getCursorAndFiles(files, ((meta?.['page'] as number) ?? 1) + 1);
break;
}
case 'prev': {
result = this.getCursorAndFiles(files, (meta?.['page'] as number) - 1 ?? 1);
result = this.getCursorAndFiles(files, ((meta?.['page'] as number) ?? 1) - 1);
break;
}
default: {

View File

@ -5,3 +5,4 @@ export { GitLabBackend } from './gitlab';
export { GiteaBackend } from './gitea';
export { ProxyBackend } from './proxy';
export { TestBackend } from './test';
export { default as GitProxyBackendGenerator } from './git';

View File

@ -247,8 +247,8 @@ const Autocomplete: FC<AutocompleteProps> = ({
return (
<li
key={index}
{...getOptionProps({ option: option as Option, index })}
key={index}
className={classNames(classes.option, selected && classes['option-selected'])}
data-testid={`autocomplete-option-${optionValue}`}
>

View File

@ -4,6 +4,7 @@ import {
GitGatewayBackend,
GitHubBackend,
GitLabBackend,
GitProxyBackendGenerator,
ProxyBackend,
TestBackend,
} from './backends';
@ -35,6 +36,7 @@ export default function addExtensions() {
registerBackend('github', GitHubBackend);
registerBackend('gitlab', GitLabBackend);
registerBackend('gitea', GiteaBackend);
registerBackend('gitea-large', GitProxyBackendGenerator(GiteaBackend));
registerBackend('bitbucket', BitbucketBackend);
registerBackend('test-repo', TestBackend);
registerBackend('proxy', ProxyBackend);

View File

@ -16,7 +16,7 @@
},
"devDependencies": {
"@babel/cli": "7.23.4",
"@babel/core": "7.23.7",
"@babel/core": "7.26.0",
"@babel/plugin-syntax-flow": "7.23.3",
"@babel/plugin-transform-react-jsx": "7.23.4",
"@vitejs/plugin-react": "4.2.1",

View File

@ -33,7 +33,7 @@
"yaml": "2.3.4"
},
"devDependencies": {
"@babel/core": "7.23.7",
"@babel/core": "7.26.0",
"@babel/eslint-parser": "7.23.3",
"@emotion/eslint-plugin": "11.11.0",
"@next/bundle-analyzer": "14.0.4",

View File

@ -8,7 +8,7 @@ const isESM = process.env.NODE_ENV === 'esm';
console.info('Build Package:', path.basename(process.cwd()));
const defaultPlugins = [
'lodash',
'@sigmacomputing/babel-plugin-lodash',
[
'babel-plugin-transform-builtin-extend',
{

3164
yarn.lock

File diff suppressed because it is too large Load Diff