init
This commit is contained in:
commit
a4dbedcd69
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal 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
9
LICENSE
Normal 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
11
README.md
Normal 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
149
app.ts
Normal 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
36
gitea.ts
Normal 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
2338
gitea_api.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user