Allow to serve raw TS
This commit is contained in:
parent
1b37c64ef8
commit
45fb0df4b6
8 changed files with 206 additions and 78 deletions
22
README.md
22
README.md
|
@ -1,3 +1,25 @@
|
|||
# typescript/bundler
|
||||
|
||||
[![Build Status](https://thunderk.visualstudio.com/typescript/_apis/build/status/bundler?branchName=master)](https://dev.azure.com/thunderk/typescript/_build?pipelineNameFilter=bundler)
|
||||
|
||||
## About
|
||||
|
||||
On-the-fly bundler for typescript libraries hosted on Gitea or compatible
|
||||
|
||||
## How to run
|
||||
|
||||
```shell
|
||||
deno run --allow-run=deno --allow-net=0.0.0.0,yourgithosting.net server.ts yourgithosting.net/namespace [port]
|
||||
```
|
||||
|
||||
You can now get raw typescript files (usable by Deno):
|
||||
|
||||
`http://localhost:8000/libname@1.0.0/path/file.ts`
|
||||
|
||||
Or get a bundled JS version:
|
||||
|
||||
`http://localhost:8000/libname@1.0.0/path/file.js`
|
||||
|
||||
If the version is not specified, the master branch is used instead:
|
||||
|
||||
`http://localhost:8000/libname/path/file.js`
|
||||
|
|
1
config/run.flags
Normal file
1
config/run.flags
Normal file
|
@ -0,0 +1 @@
|
|||
--allow-run=deno --allow-net=0.0.0.0,code.thunderk.net
|
3
doc/about.md
Normal file
3
doc/about.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## About
|
||||
|
||||
On-the-fly bundler for typescript libraries hosted on Gitea or compatible
|
2
doc/index
Normal file
2
doc/index
Normal file
|
@ -0,0 +1,2 @@
|
|||
about
|
||||
run
|
17
doc/run.md
Normal file
17
doc/run.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
## How to run
|
||||
|
||||
```shell
|
||||
deno run --allow-run=deno --allow-net=0.0.0.0,yourgithosting.net server.ts yourgithosting.net/namespace [port]
|
||||
```
|
||||
|
||||
You can now get raw typescript files (usable by Deno):
|
||||
|
||||
`http://localhost:8000/libname@1.0.0/path/file.ts`
|
||||
|
||||
Or get a bundled JS version:
|
||||
|
||||
`http://localhost:8000/libname@1.0.0/path/file.js`
|
||||
|
||||
If the version is not specified, the master branch is used instead:
|
||||
|
||||
`http://localhost:8000/libname/path/file.js`
|
19
run
Executable file
19
run
Executable file
|
@ -0,0 +1,19 @@
|
|||
#!/bin/sh
|
||||
# Simplified run tool for deno commands
|
||||
|
||||
if test $# -eq 0
|
||||
then
|
||||
echo "Usage: $0 [file or command]"
|
||||
exit 1
|
||||
elif echo $1 | grep -q '.*.ts'
|
||||
then
|
||||
denocmd=run
|
||||
denoargs=$1
|
||||
shift
|
||||
else
|
||||
denocmd=$1
|
||||
shift
|
||||
fi
|
||||
|
||||
denoargs="$(cat config/$denocmd.flags 2> /dev/null) $denoargs $@"
|
||||
exec deno $denocmd $denoargs
|
144
server.test.ts
144
server.test.ts
|
@ -1,73 +1,95 @@
|
|||
import {
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
mockfn,
|
||||
} from "https://code.thunderk.net/typescript/devtools/raw/1.2.2/testing.ts";
|
||||
import { processRequest } from "./server.ts";
|
||||
} from "https://code.thunderk.net/typescript/devtools/raw/1.3.0/testing.ts";
|
||||
import { processRequest, Response } from "./server.ts";
|
||||
|
||||
it("serveBundles standard", async () => {
|
||||
const mock_run = mockfn(() => {
|
||||
return {
|
||||
output: () => Promise.resolve(new TextEncoder().encode("abc")),
|
||||
status: () => Promise.resolve({ code: 0, success: true }),
|
||||
} as any;
|
||||
describe("serveBundles", () => {
|
||||
it("calls deno bundle if asking for js", async () => {
|
||||
const run = mockfn(() => {
|
||||
return {
|
||||
output: () => Promise.resolve(new TextEncoder().encode("abc")),
|
||||
status: () => Promise.resolve({ code: 0, success: true }),
|
||||
} as any;
|
||||
});
|
||||
const [response, _] = await call("/greatlib@1.0.0/reader/file.js", { run });
|
||||
expect(response).toEqual({ body: new TextEncoder().encode("abc") });
|
||||
expect(run).toHaveBeenCalledTimes(1);
|
||||
expect(run).toHaveBeenCalledWith({
|
||||
cmd: [
|
||||
"deno",
|
||||
"bundle",
|
||||
"https://git.example.com/libs/greatlib/raw/1.0.0/reader/file.ts",
|
||||
],
|
||||
stdout: "piped",
|
||||
});
|
||||
});
|
||||
|
||||
const response = await processRequest(
|
||||
{ url: "/greatlib/1.0.0/reader/" } as any,
|
||||
mock_run,
|
||||
);
|
||||
expect(response).toEqual({ body: new TextEncoder().encode("abc") });
|
||||
expect(mock_run).toHaveBeenCalledTimes(1);
|
||||
expect(mock_run).toHaveBeenCalledWith({
|
||||
cmd: [
|
||||
"deno",
|
||||
"bundle",
|
||||
"https://code.thunderk.net/typescript/greatlib/raw/1.0.0/reader.ts",
|
||||
],
|
||||
stdout: "piped",
|
||||
it("redirects to raw file if asking for anything other than js", async () => {
|
||||
const [response, run] = await call("/greatlib@1.0.0/reader/file.ts");
|
||||
expect(response).toEqual({
|
||||
status: 301,
|
||||
headers: new Headers({
|
||||
Location:
|
||||
"https://git.example.com/libs/greatlib/raw/1.0.0/reader/file.ts",
|
||||
}),
|
||||
});
|
||||
expect(run).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles bad method", async () => {
|
||||
const [response, run] = await call("/greatlib@1.0.0/reader/file.ts", {
|
||||
method: "POST",
|
||||
});
|
||||
expect(response).toEqual({ status: 405 });
|
||||
expect(run).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles bad path", async () => {
|
||||
const [response, run] = await call("/greatlib@1.0.0/reader{}.ts");
|
||||
expect(response).toEqual(
|
||||
{
|
||||
status: 400,
|
||||
body:
|
||||
'console.error("bundler error - Invalid path https://git.example.com/libs/greatlib/raw/1.0.0/reader{}.ts");',
|
||||
},
|
||||
);
|
||||
expect(run).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles bundle failure", async () => {
|
||||
const run = mockfn(() => {
|
||||
return {
|
||||
output: () => Promise.resolve(undefined),
|
||||
status: () => Promise.resolve({ code: 1, success: false }),
|
||||
} as any;
|
||||
});
|
||||
const [response, _] = await call("/great_lib@1.0.0-dev1/reader.js", {
|
||||
run,
|
||||
});
|
||||
expect(response).toEqual(
|
||||
{
|
||||
status: 500,
|
||||
body:
|
||||
'console.error("bundler error - Failed to bundle https://git.example.com/libs/great_lib/raw/1.0.0-dev1/reader.ts");',
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("serveBundles bad path", async () => {
|
||||
const mock_run = mockfn(() => {
|
||||
return {
|
||||
output: () => Promise.resolve(new TextEncoder().encode("abc")),
|
||||
status: () => Promise.resolve({ code: 0, success: true }),
|
||||
} as any;
|
||||
});
|
||||
|
||||
type CallOptions = { method: string; run: ReturnType<typeof mockfn> };
|
||||
async function call(
|
||||
url: string,
|
||||
options: Partial<CallOptions> = {},
|
||||
): Promise<[Response, ReturnType<typeof mockfn>]> {
|
||||
const method = options.method ?? "GET";
|
||||
const run = options.run ?? mockfn();
|
||||
const response = await processRequest(
|
||||
{ url: "/greatlib/1.0.0/reader{}/" } as any,
|
||||
mock_run,
|
||||
{ method, url } as any,
|
||||
"git.example.com/libs",
|
||||
run,
|
||||
);
|
||||
expect(response).toEqual(
|
||||
{
|
||||
status: 400,
|
||||
body:
|
||||
'console.error("bundler error - Invalid path https://code.thunderk.net/typescript/greatlib/raw/1.0.0/reader{}.ts");',
|
||||
},
|
||||
);
|
||||
expect(mock_run).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("serveBundles bundle fail", async () => {
|
||||
const mock_run = mockfn(() => {
|
||||
return {
|
||||
output: () => Promise.resolve(undefined),
|
||||
status: () => Promise.resolve({ code: 1, success: false }),
|
||||
} as any;
|
||||
});
|
||||
|
||||
const response = await processRequest(
|
||||
{ url: "/great_lib/1.0.0-dev1/reader/" } as any,
|
||||
mock_run,
|
||||
);
|
||||
expect(response).toEqual(
|
||||
{
|
||||
status: 500,
|
||||
body:
|
||||
'console.error("bundler error - Failed to bundle https://code.thunderk.net/typescript/great_lib/raw/1.0.0-dev1/reader.ts");',
|
||||
},
|
||||
);
|
||||
});
|
||||
return [response, run];
|
||||
}
|
||||
|
|
76
server.ts
76
server.ts
|
@ -1,30 +1,51 @@
|
|||
#!/usr/bin/env -S deno run --allow-run --allow-net
|
||||
#!./run
|
||||
// Automated bundle server
|
||||
|
||||
import { bool } from "https://code.thunderk.net/typescript/functional/raw/1.0.0/all.ts";
|
||||
import {
|
||||
Response,
|
||||
serve,
|
||||
ServerRequest,
|
||||
} from "https://deno.land/std@0.79.0/http/server.ts";
|
||||
} from "https://deno.land/std@0.103.0/http/server.ts";
|
||||
|
||||
export async function processRequest(
|
||||
req: ServerRequest,
|
||||
hostpath: string,
|
||||
runner = Deno.run,
|
||||
): Promise<Response> {
|
||||
const params = req.url.split("/").filter(bool);
|
||||
const lib = params[0] || "all";
|
||||
const version = params[1] || "master";
|
||||
const file = params.length > 2 ? params.slice(2).join("/") : "all";
|
||||
const path =
|
||||
`https://code.thunderk.net/typescript/${lib}/raw/${version}/${file}.ts`;
|
||||
if (!path.match(/^[a-z0-9\/\.\-:_]+$/)) {
|
||||
if (req.method != "GET") {
|
||||
return { status: 405 };
|
||||
}
|
||||
|
||||
const params = req.url.split("/").filter((x) => !!x);
|
||||
if (params.length < 2) {
|
||||
return { status: 404 };
|
||||
}
|
||||
|
||||
const [lib, version] = params[0].split("@", 2);
|
||||
const file = params.slice(1).join("/");
|
||||
const branch = version || "master";
|
||||
const path = `https://${hostpath}/${lib}/raw/${branch}/${file}`;
|
||||
if (!isName(lib) || !isName(branch) || !isPath(file)) {
|
||||
return {
|
||||
status: 400,
|
||||
body: `console.error("bundler error - Invalid path ${path}");`,
|
||||
};
|
||||
}
|
||||
|
||||
if (path.endsWith(".js")) {
|
||||
return await bundle(path.slice(0, -3) + ".ts", runner);
|
||||
} else {
|
||||
return {
|
||||
status: 301,
|
||||
headers: new Headers({ Location: path }),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function bundle(
|
||||
path: string,
|
||||
runner: typeof Deno.run,
|
||||
): Promise<Response> {
|
||||
const process = runner({
|
||||
cmd: ["deno", "bundle", path],
|
||||
stdout: "piped",
|
||||
|
@ -41,16 +62,37 @@ export async function processRequest(
|
|||
}
|
||||
}
|
||||
|
||||
export async function serveBundles() {
|
||||
const listen = { hostname: "0.0.0.0", port: 8000 };
|
||||
export async function serveBundles(hostpath: string, port: number) {
|
||||
const listen = { hostname: "0.0.0.0", port };
|
||||
const server = serve(listen);
|
||||
console.log(`Serving bundles on ${listen.hostname}:${listen.port} ...`);
|
||||
console.log(
|
||||
`Serving ${hostpath} bundles on ${listen.hostname}:${listen.port} ...`,
|
||||
);
|
||||
for await (const req of server) {
|
||||
const response = await processRequest(req);
|
||||
await req.respond(response);
|
||||
try {
|
||||
const response = await processRequest(req, hostpath);
|
||||
await req.respond(response);
|
||||
} catch (err) {
|
||||
// console.error(err);
|
||||
await req.respond({ status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
await serveBundles();
|
||||
function isName(input: string): boolean {
|
||||
return !!input.match(/^[a-zA-Z0-9_\-\.]+$/);
|
||||
}
|
||||
|
||||
function isPath(input: string): boolean {
|
||||
return !input.split("/").some((part) => !isName(part));
|
||||
}
|
||||
|
||||
export type { Response };
|
||||
|
||||
if (import.meta.main) {
|
||||
if (Deno.args.length >= 1 && Deno.args.length <= 2) {
|
||||
await serveBundles(Deno.args[0], parseInt(Deno.args[1] || "8000"));
|
||||
} else {
|
||||
console.error("Usage: server.ts yourgithosting.net/namespace [port]");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue