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
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:
parent
c42e4e9bd8
commit
cebf4d0a9d
17
nx.json
17
nx.json
@ -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",
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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",
|
||||
|
@ -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',
|
||||
{
|
||||
|
@ -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",
|
||||
|
220
packages/core/src/backends/git/implementation.tsx
Normal file
220
packages/core/src/backends/git/implementation.tsx
Normal 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;
|
||||
}
|
3
packages/core/src/backends/git/index.ts
Normal file
3
packages/core/src/backends/git/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import GitProxyBackEndGenerator from './implementation';
|
||||
|
||||
export default GitProxyBackEndGenerator;
|
@ -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 } = {},
|
||||
|
@ -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: {
|
||||
|
@ -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: {
|
||||
|
@ -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';
|
||||
|
@ -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}`}
|
||||
>
|
||||
|
@ -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);
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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',
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user