This commit is contained in:
monoid 2023-03-24 01:40:08 +09:00
commit a4dbedcd69
6 changed files with 2553 additions and 0 deletions

10
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"deno.enable": true,
"deno.unstable": true,
"deno.suggest.imports.hosts": {
"https://deno.land": true,
"https://x.nest.land": true,
"https://crux.land": true,
"http://localhost:9999": true
}
}

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2023 monoid
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# Simple deno import intellisense proxy for gitea
This is a simple proxy server for gitea. It is used to proxy requests to gitea from a subpath.
It implements import completion for deno.
Details can be found in the [deno docs](https://deno.land/manual@v1.32.0/advanced/language_server/imports).
## Usage
```bash
deno run --allow-net app.ts
```

149
app.ts Normal file
View File

@ -0,0 +1,149 @@
import { Application, Router } from "https://deno.land/x/oak@v12.1.0/mod.ts";
import {
searchRepositoryWithTopic,
getRepositoryTags,
getRepositoryContent
} from "./gitea.ts";
import { ContentsResponse } from "./gitea_api.d.ts";
import { Command } from "https://deno.land/x/cliffy@v0.25.7/mod.ts";
// import { load } from "https://deno.land/std@0.181.0/dotenv/mod.ts";
// const env = await load();
const app = new Application();
const router = new Router();
const RelativeTopic = "denolib";
export interface CompletionList {
/** The list (or partial list) of completion items. */
items: string[];
/** If the list is a partial list, and further queries to the endpoint will
* change the items, set `isIncomplete` to `true`. */
isIncomplete?: boolean;
/** If one of the items in the list should be preselected (the default
* suggestion), then set the value of `preselect` to the value of the item. */
preselect?: string;
}
router.get("/.well-known/deno-import-intellisense.json", (ctx) => {
console.log("get /.well-known/deno-import-intellisense.json");
ctx.response.type = "application/json";
ctx.response.body = {
"version": 2,
"registries": [
{
"schema": "/:package([a-z0-9_]*@[a-z0-9_]*)/:version?/:path*",
"variables": [
{
"key": "package",
// "documentation": "/docs/packages/${package}",
"url": "/packages/${package}"
},
{
"key": "version",
"url": "/packages/${package}/versions"
},
{
"key": "path",
// "documentation": "/docs/packages/${package}/${{version}}/paths/${path}",
"url": "/packages/${package}/${{version}}/paths/${path}"
}
]
}
]
};
});
router.get("/packages/:package", async (ctx) => {
const packageName = ctx.params.package;
console.log(`searchRepositoryWithTopic: ${packageName}`);
const repositories = await searchRepositoryWithTopic(RelativeTopic);
const repo_name = repositories.data?.map((repo) => repo.full_name)
.filter(x => x !== undefined)
.map(x=> x?.replace("/","@")) ?? [];
const completionList: CompletionList = {
items: repo_name as string[],
isIncomplete: true, // TODO: check if there are more than max results
preselect: repo_name[0]
};
ctx.response.type = "application/json";
ctx.response.body = completionList;
});
router.get("/packages/:package/versions", async (ctx) => {
const packageName = ctx.params.package;
const [owner, repo] = packageName.split("@");
console.log(`getTags: owner: ${owner}, repo: ${repo}`);
const tags = await getRepositoryTags(owner, repo);
const candidate = ["main", ...tags.map((tag) => tag.name) as string[]]
const completionList: CompletionList = {
items: candidate,
isIncomplete: false,
preselect: candidate[0]
};
ctx.response.type = "application/json";
ctx.response.body = completionList;
});
router.get("/packages/:package/:version/paths/:path*", async (ctx) => {
const packageName = ctx.params.package;
const version = ctx.params.version;
const path = ctx.params.path;
const [owner, repo] = packageName.split("@");
console.log(`getFilesEntry: owner: ${owner}, repo: ${repo}, path: ${path}, version: ${version}`);
const entries = await getRepositoryContent(owner, repo, path ?? "", version) as ContentsResponse[];
const completionList: CompletionList = {
items: entries.map((entry) => entry.name) as string[],
isIncomplete: false,
preselect: entries[0].name
};
ctx.response.type = "application/json";
ctx.response.body = completionList;
});
router.get("/:package([a-z0-9_]*@[a-z0-9_]*)/:version?/:path*", async (ctx) => {
const packageName = ctx.params.package;
const [owner, repo] = packageName.split("@");
const version = ctx.params.version;
const path = ctx.params.path;
console.log(`getFiles: owner: ${owner}, repo: ${repo}, path: ${path}, version: ${version}`);
const entries = await getRepositoryContent(owner, repo, path ?? "", version);
if (entries instanceof Array) {
ctx.response.type = "application/json";
ctx.response.body = entries;
}
else {
if ("errors" in entries){
ctx.throw(404);
}
// TODO: check if the file is text file or not (e.g. image)
ctx.response.type = "text/plain";
ctx.response.body = atob(entries.content ?? "");
}
});
app.use(router.routes());
app.use(router.allowedMethods());
//app.use(async (ctx, next) => {
// ctx.throw(404);
// //ctx.response.status = 404;
// //ctx.response.body = "Not Found";
// //await next();
//});
app.addEventListener("listen", ({ hostname, port, secure }) => {
console.log(`🚀 Listening on: ${secure ? "https://" : "http://"}${hostname ?? "localhost"}:${port}`);
});
if (import.meta.main) {
const cmd = new Command()
.version("0.1.0")
.description("Simple Deno import intellisense proxy server for Gitea")
.option("-p, --port <port:number>", "Port number to listen on", { default: 9999 })
.action(async ({ port }) => {
await app.listen({ port: port });
});
await cmd.parse(Deno.args);
}

36
gitea.ts Normal file
View File

@ -0,0 +1,36 @@
import { SearchResults, Tag, ContentsResponse } from "./gitea_api.d.ts";
const ENDPOINT_URL = "https://git.prelude.duckdns.org/api/v1/";
export async function searchRepositoryWithTopic(topic: string): Promise<SearchResults> {
const url = new URL(ENDPOINT_URL+ "repos/search");
url.searchParams.append("q", topic);
url.searchParams.append("topic", "true");
const response = await fetch(url);
const data = await response.json();
return data;
}
export async function getRepositoryTags(owner:string,
repo:string): Promise<Tag[]>{
const url = new URL(ENDPOINT_URL+ "repos/"+owner+"/"+repo+"/tags");
const response = await fetch(url);
const data = await response.json();
return data;
}
export async function getRepositoryContent(owner:string,
repo:string, path:string, ref:string): Promise<ContentsResponse[] | ContentsResponse>{
const url = new URL(ENDPOINT_URL+ "repos/"+owner+"/"+repo+"/contents/"+path);
url.searchParams.append("ref", ref);
const response = await fetch(url);
const data = await response.json();
return data;
}
if (import.meta.main) {
const results = await searchRepositoryWithTopic("deno");
console.log(results.data?.map((repo) => repo.full_name));
const s = await getRepositoryContent("monoid", "script", "", "");
console.log((s as ContentsResponse[]).map((x) => x.name));
}

2338
gitea_api.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff