Compare commits

81 Commits

Author SHA1 Message Date
a0fef1ef76 Improve upgrade script
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-28 12:20:30 +02:00
3cda4ee8c8 Improve upgrade script
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-28 12:16:50 +02:00
e5829d9a4f Add script running abilities
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-28 11:47:52 +02:00
3efe4fc34e Add dpm alias 2021-04-28 11:19:29 +02:00
22a447604b Update to work with newer deno versions
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-15 17:20:09 +02:00
9dfb8d65d3 Add support for intellisense imports for the vscode extension
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-15 02:32:51 +01:00
6bc090e51b Fix dockerfile
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-11 15:38:44 +01:00
965ca33d33 Improve startup speed
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-11 15:37:06 +01:00
0202946813 Implement fragment support
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-11 15:32:47 +01:00
53a11eccf6 Fix tsconfig wrong path
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-11 15:24:00 +01:00
325c1a4d7d Add tsconfig to docker
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-11 15:20:37 +01:00
c718e8898d Change import
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-11 15:10:43 +01:00
6fe3ddbd37 Many improvements
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-11 15:06:02 +01:00
79bcef0698 Remove unused console output 2021-01-11 15:05:03 +01:00
2af5d4f823 Move to own JSX implementation
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-11 14:55:54 +01:00
7c1166bf87 Update dependencies
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-04 22:57:14 +01:00
639287663d Remove useless log output
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-27 03:23:49 +01:00
fbbb66d5af Add option to add tracking to denreg
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-27 03:16:57 +01:00
df1f4965ad Switch src image to new registry
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-19 19:06:29 +01:00
10930db13d Change registry
Some checks failed
continuous-integration/drone/push Build is failing
2020-12-19 19:02:24 +01:00
6b88be1b18 Add content type to registry using filenames
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-21 02:40:55 +02:00
c9bd5c7d18 Adding support for readme field in meta.json to registry
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-19 21:10:53 +02:00
d1244ac0a7 A few SEO
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-14 22:34:57 +02:00
e56d8b4548 Add prism.css to public folder instead of unpkg
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-14 22:20:50 +02:00
ed1ce0cc0f Improving update support
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-14 22:04:18 +02:00
252bf4aac3 Remove logging output
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-14 21:47:09 +02:00
a3a7370be0 Add log
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-14 17:51:49 +02:00
10a7c26642 Configurable region
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-14 17:44:36 +02:00
7e821f9771 Updating dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-14 17:07:51 +02:00
1b7c4847dc Add s3 error output
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-14 16:48:27 +02:00
2194bf199e Add debug output
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-14 16:34:26 +02:00
f3f6f0d7bc Add ability to add root folder to meta.json (only affects upload) 2020-10-14 16:34:18 +02:00
13bc1b07cd Fixing dockerfile
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-14 02:58:00 +02:00
1b2d85eeef Adding hotfixes for packages
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-14 02:56:11 +02:00
46d8f8b289 Adding basic file browsing support
Some checks failed
continuous-integration/drone/push Build is failing
2020-10-14 02:52:02 +02:00
78c40e4819 [CLI] Add help message when no subcommand is specified
All checks were successful
continuous-integration/drone/push Build is passing
2020-09-15 11:40:25 +02:00
6bfcb3642d Publishing new version of CLI
All checks were successful
continuous-integration/drone/push Build is passing
2020-09-15 11:35:10 +02:00
66b430ac90 Improving error messages 2020-09-14 23:47:48 +02:00
dd1d8e0947 Fix some scroll issues on mobile
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-21 00:31:17 +02:00
c783583898 Fix version import
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-16 19:41:28 +02:00
2ee628544e Add automatic version reading
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-16 19:37:09 +02:00
c78f6822e1 Add upgrade command 2020-08-16 19:28:27 +02:00
438cee6bed Upgrading deps 2020-08-16 19:28:12 +02:00
ac73b62df2 Add missing hook
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-16 19:21:17 +02:00
7abe63429f Add hooks
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-16 19:20:37 +02:00
775eca793b Bumping version
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-16 12:12:39 +02:00
ccc48b0480 Add dry run upload and adding additional verbose output
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-16 11:39:56 +02:00
a436a4ecb0 Fix bug with wrong paths in windows
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-16 11:39:11 +02:00
7da72872ba Switching to owner instead of author
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-03 09:38:01 +02:00
93984f211b Reverse order of items on index page
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 23:52:42 +02:00
14171ea24b Removing debug output
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 23:49:44 +02:00
1c67cb22c5 Fixing CLI tar package source
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 23:49:14 +02:00
936cf1ec52 Updating CLI to new deno std
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 23:44:40 +02:00
6453137208 Updating to new deno std
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 23:43:17 +02:00
0d55b09677 Add vscode debug configuration
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 23:41:05 +02:00
a376510768 Fix version string of CLI
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 23:40:24 +02:00
bcb3e16f9d Some improvements for the CLI
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 23:40:12 +02:00
d56f0bcbea Add deprecate command to CLI
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 23:37:59 +02:00
c358486bd8 Deprecating JSX package
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 23:37:27 +02:00
4db95143a0 switching back to deno std tar
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 23:37:16 +02:00
54adb4a7fe Fix bug making package uploads impossible
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 23:34:51 +02:00
2d6c62a791 Fixing path extraction
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 22:39:05 +02:00
4aaee3e419 Working toward an openly accessable registry
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 22:33:18 +02:00
855034f14d Add missing await
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 16:54:40 +02:00
c2b7e8c53e Add missing type annotations
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-02 16:43:33 +02:00
f69e40ae9e Fixing cache control
Some checks failed
continuous-integration/drone/push Build is failing
2020-08-02 16:42:05 +02:00
cece103504 Enable E-Tags for GUI and RAW 2020-08-02 16:33:41 +02:00
5337b44b11 Add package deprecation
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-01 00:15:10 +02:00
81213a9aba Remove useless ul from menu
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-01 00:04:30 +02:00
9f9b390989 Remove render debugging
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-01 00:03:23 +02:00
dce66274be Fix bug where a reupload does not change the author or the description
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-31 23:58:36 +02:00
085b3f5773 Switching to jsx-html branch which includes a fix not yet available in the original repo
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-31 23:47:45 +02:00
e9c42b0b0e Optimizing dockerfile
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-31 20:39:58 +02:00
5e9139e861 Making jsx-html fixed version
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-31 20:32:32 +02:00
d2e2b49c46 Optimize dependency import
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-31 20:22:09 +02:00
ef4ce98ebb Adding better output of publish command
Some checks failed
continuous-integration/drone/push Build is failing
2020-07-31 20:18:28 +02:00
7fcdf2c383 Further progress on registry UI.
Some checks failed
continuous-integration/drone/push Build is failing
Add package README view support and style adjustments
2020-07-31 20:16:12 +02:00
0bee324519 QOL improvements or CLI
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-31 19:45:14 +02:00
ebc6c83f29 Adding readme to tar
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-31 19:35:18 +02:00
6a4d1c3b22 Start of UI work
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-29 20:47:36 +02:00
7a88c636fd Remove importmap.json from default
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-29 11:27:07 +02:00
102 changed files with 4402 additions and 1045 deletions

View File

@ -12,7 +12,7 @@ steps:
from_secret: docker_password
auto_tag: true
context: registry
repo: hibas123.azurecr.io/denreg
registry: hibas123.azurecr.io
repo: docker.hibas123.de/denreg
registry: docker.hibas123.de
dockerfile: registry/Dockerfile
debug: false

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
test.ini

21
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,21 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Registry",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}/registry",
"runtimeExecutable": "deno",
"runtimeArgs": [
"run",
"--inspect-brk",
"-A",
"--unstable",
"src/registry.ts"
],
"port": 9229,
"outputCapture": "std"
}
]
}

View File

@ -1,3 +1,8 @@
{
"deno.enable": true
}
"deno.enable": true,
"deno.unstable": true,
"debug.javascript.usePreview": false,
"deno.import_intellisense_origins": {
"https://deno.land": true
}
}

22
cli/README.md Normal file
View File

@ -0,0 +1,22 @@
A CLI for the denreg registry.
## Installation
Recommended way for installation is
`deno install -A --unstable https://deno.hibas123.de/raw/@denreg-cli/denreg.ts`
Since the cli requires access to:
- reading and writing files
- access to environment variables
- network access
- run programs
The -A flag is the easiest way to install. You can however manually grant the required permissions.
| permission | reason |
| ------------- | ------------------------------------------------------------------------------------------------- |
| --allow-read | Read configuration files (~/.denreg) as well as your project files on publish |
| --allow-write | Write configuration file while using setup and init as well as during publish for temporary files |
| --allow-net | Access to network for uploading to the registry (can be exclusive to the registry) |
| --allow-env | Required to get config file path relative to user home directory |
| --allow-run | Required to run hook scripts |

View File

@ -1,6 +1,6 @@
import { Colors } from "../deps.ts";
import { getMeta, setMeta } from "../global.ts";
import { getMeta, setMeta, log } from "../global.ts";
export default async function bump(
options: any,
@ -26,6 +26,7 @@ export default async function bump(
default:
throw new Error("type must be either major, minor or patch");
}
log(major, minor, patch);
const newVersion = [major, minor, patch].join(".");
console.log(
"Bumping version from",

20
cli/commands/deprecate.ts Normal file
View File

@ -0,0 +1,20 @@
import { Colors, Cliffy } from "../deps.ts";
import { getMeta, setMeta, log } from "../global.ts";
export default async function deprecate(options: any) {
const meta = await getMeta();
const res = await Cliffy.Confirm.prompt(
"Are you sure you want to deprecat this package?"
);
if (res) {
meta.deprecated = true;
console.log(Colors.yellow("Package marked as deprecated!"));
} else {
console.log("Deprecation canceled");
}
await setMeta(meta);
}

View File

@ -1,4 +1,4 @@
import { Cliffy, Path, FS, Compress, Base64 } from "../deps.ts";
import { Cliffy, Path, FS } from "../deps.ts";
import {
getMeta,
setMeta,
@ -18,7 +18,7 @@ export default async function init() {
description: "",
author: getConfig("author"),
contributors: [],
files: ["**/*.ts", "**/*.js", "importmap.json"],
files: ["**/*.ts", "**/*.js", "README.md"],
...existing,
};
@ -35,6 +35,19 @@ export default async function init() {
message: "Who's the author of your package?",
default: meta.author,
});
if (!(await FS.exists("README.md"))) {
const res = await Cliffy.Confirm.prompt({
message: "Autogenerate README?",
default: true,
});
if (res) {
await Deno.writeFile(
"README.md",
new TextEncoder().encode(meta.description || "")
);
}
}
}
await setMeta(meta);

View File

@ -1,8 +1,20 @@
import { Colors, Path, FS, Compress, Base64 } from "../deps.ts";
import { getMeta, IMeta, log, getConfig } from "../global.ts";
import { runHooks } from "../helper/run_script.ts";
import { ServerError } from "../helper/server_error.ts";
import { checkPermOrExit } from "../helper/permission.ts";
export default async function publish() {
const meta: IMeta = await getMeta();
export default async function publish(options: { dry: boolean }) {
const originalMeta = await getMeta();
if (originalMeta.hooks) {
log("Running prepublish hooks");
await runHooks(originalMeta.hooks.prepublish);
}
const meta = await getMeta();
//TODO: Output Diff between original and result meta
if (!meta.name) throw new Error("name is not set in meta.json");
if (!meta.version) throw new Error("version is not set in meta.json");
@ -10,10 +22,10 @@ export default async function publish() {
throw new Error("files is not set or empty in meta.json");
const tmpDir = await Deno.makeTempDir();
const packedFile = await Deno.makeTempFile();
const packedFile = (await Deno.makeTempFile()) + ".tar";
try {
const walker = FS.walk(".", {
const walker = FS.walk(meta.root || ".", {
includeDirs: false,
includeFiles: true,
match: meta.files.map((file) => Path.globToRegExp(file)),
@ -21,19 +33,21 @@ export default async function publish() {
log("Copying files to package to", tmpDir);
const copy = async (path: string) => {
const dest = Path.join(tmpDir, path);
const copy = async (path: string, abs?: boolean) => {
const relative = abs ? path : Path.relative(meta.root || ".", path);
log("Adding file:", path, "as", relative);
const dest = Path.join(tmpDir, relative);
await FS.ensureDir(Path.dirname(dest));
await FS.copy(path, dest);
};
await copy("meta.json");
await copy("meta.json", true);
for await (const file of walker) {
await copy(file.path);
}
log("Compressing file");
log("Compressing files into", packedFile);
await Compress.Tar.compress(tmpDir, packedFile, {
excludeSrc: true,
@ -42,30 +56,52 @@ export default async function publish() {
const url = new URL(getConfig("registry"));
url.pathname = "/api/package/" + meta.name;
log("Uploading new package version");
console.log(
"Pushing version",
Colors.blue(meta.version),
"to repository"
);
const res = await fetch(url, {
method: "POST",
body: await Deno.readFile(packedFile),
headers: {
Authorization:
"Basic " +
Base64.encode(
getConfig("username") + ":" + getConfig("password")
),
},
}).then((res) => (res.status === 200 ? res.json() : res.statusText));
if (!options.dry) {
await checkPermOrExit("net", "Net permission required for publishing");
log("Upload finished. Result:", res);
if (typeof res === "string" || res.error) {
console.log(
Colors.red("Error: " + (typeof res == "string" ? res : res.error))
);
log("Uploading new package version");
await fetch(url, {
method: "POST",
body: await Deno.readFile(packedFile),
headers: {
Authorization:
"Basic " +
Base64.encode(
getConfig("username") + ":" + getConfig("password")
),
},
})
.then((res) =>
res.status === 200
? res.json()
: Promise.reject(new ServerError(res.statusText))
)
.then((res) => {
if (!res.success) {
throw new ServerError(res.message);
} else {
console.log(Colors.green("Upload successfull"));
}
})
.catch((err) => {
//import { ServerError } from "../helper/server_error.ts";
if (err instanceof ServerError)
console.log(Colors.red("Server Error: " + err.message));
else console.log(Colors.red("Error: " + err.message));
});
} else {
if (res.success) {
console.log(Colors.green("Upload successfull"));
}
console.log(Colors.yellow("Dry run. Skipping upload"));
}
if (meta.hooks) {
log("Running postpublish hooks");
await runHooks(meta.hooks.postpublish);
}
} finally {
await Deno.remove(tmpDir, { recursive: true });

18
cli/commands/run.ts Normal file
View File

@ -0,0 +1,18 @@
import { Colors, Path, FS, Compress, Base64 } from "../deps.ts";
import { getMeta, IMeta, log, getConfig } from "../global.ts";
import { ServerError } from "../helper/server_error.ts";
import { runScript } from "../helper/run_script.ts";
export default async function run(options: {}, name: string) {
const { scripts } = await getMeta();
if (!scripts || !scripts[name]) {
console.log(Colors.bold(Colors.red("Script not found:")), name);
} else {
let script = scripts[name];
if (!Array.isArray(script)) script = [script];
for (const s of script) {
await runScript(s);
}
}
}

64
cli/commands/upgrade.ts Normal file
View File

@ -0,0 +1,64 @@
import { Cliffy, Colors } from "../deps.ts";
import { version } from "../version.ts";
import { requestPermOrExit } from "../helper/permission.ts";
export default async function upgrade() {
await requestPermOrExit(
"net",
"Net permission required to fetch new version"
);
const meta = await fetch(
"https://deno.hibas123.de/raw/@denreg-cli/meta.json"
).then((e) => e.json());
if (meta.version === version) {
const res = await Cliffy.Confirm.prompt({
message: Colors.yellow("No update available!") + " Continue?",
default: false,
});
if (!res) return;
}
const res = await Cliffy.Confirm.prompt({
message: "Are you sure you want to upgrade?",
default: true,
});
if (res) {
const cmd_base = ["deno", "install", "-A", "--unstable", "-f"];
const cmd1 = [
...cmd_base,
`https://deno.hibas123.de/raw/@denreg-cli@${meta.version}/denreg.ts`,
];
const cmd2 = [
...cmd_base,
`https://deno.hibas123.de/raw/@denreg-cli@${meta.version}/dpm.ts`,
];
await requestPermOrExit(
"run",
"Run permission required to install new version. Or run it manually: " +
cmd1.join(" ")
);
const process1 = Deno.run({
cmd: cmd1,
});
const s1 = await process1.status();
if (!s1) {
console.log(Colors.red("Upgrade failed!"));
}
const process2 = Deno.run({
cmd: cmd2,
});
const s2 = await process2.status();
if (!s2) {
console.log(Colors.red("Upgrade failed!"));
}
}
}

View File

@ -1,13 +1,20 @@
import { Ini, Cliffy, Compress, Base64 } from "./deps.ts";
import * as Colors from "https://deno.land/std@0.62.0/fmt/colors.ts";
import * as Path from "https://deno.land/std@0.62.0/path/mod.ts";
import * as FS from "https://deno.land/std@0.62.0/fs/mod.ts";
import { init } from "./global.ts";
import { Cliffy, Path, Colors } from "./deps.ts";
import { checkPermOrExit } from "./helper/permission.ts";
await checkPermOrExit("env", "Requires --allow-env");
await checkPermOrExit("read", "Requires --allow-read");
await checkPermOrExit("write", "Requires --allow-write");
import { init } from "./global.ts";
import { version } from "./version.ts";
import setupCMD from "./commands/setup.ts";
import initCMD from "./commands/init.ts";
import bumpCMD from "./commands/bump.ts";
import publishCMD from "./commands/publish.ts";
import deprecateCMD from "./commands/deprecate.ts";
import upgradeCMD from "./commands/upgrade.ts";
import runCMD from "./commands/run.ts";
const HOME_FOLDER = Deno.env.get("HOME") || Deno.env.get("USERPROFILE") || "";
@ -25,10 +32,9 @@ const commandWrapper = (cmd: CommandHandler) => {
opts = params;
};
};
const flags = await new Cliffy.Command()
.name("denreg")
.version("0.0.1")
.version(version)
.description("CLI for the Open Source DenReg package registry")
.option("-i, --interactive [interactive:boolean]", "Interactive mode", {
default: true,
@ -38,7 +44,7 @@ const flags = await new Cliffy.Command()
default: Path.resolve(HOME_FOLDER, ".denreg"),
global: true,
})
.option("-v, --verbose", "Verbose", {
.option("-v, --verbose [verbose:boolean]", "Verbose", {
default: false,
global: true,
})
@ -53,6 +59,9 @@ const flags = await new Cliffy.Command()
new Cliffy.Command()
.description("Upload package")
.action(commandWrapper(publishCMD))
.option("-d, --dry [dry:boolean]", "Dry run", {
default: false,
})
)
.command(
"init",
@ -68,230 +77,38 @@ const flags = await new Cliffy.Command()
.description("Change package version")
.action(commandWrapper(bumpCMD))
)
.command(
"deprecate",
new Cliffy.Command()
.description("Deprecate package")
.action(commandWrapper(deprecateCMD))
)
.command(
"upgrade",
new Cliffy.Command()
.description("Upgrade to latest version of denreg cli")
.action(commandWrapper(upgradeCMD))
)
.command(
"run",
new Cliffy.Command()
.arguments("<name>")
// .complete("name", ()=>) //TODO: add autocomplete?
.description("Run script from meta.json")
.action(commandWrapper(runCMD))
)
.command("completions", new Cliffy.CompletionsCommand())
.command("help", new Cliffy.HelpCommand().global())
.parse(Deno.args);
await init(flags.options);
if (command) {
await Promise.resolve((command as CommandHandler)(...opts));
try {
await Promise.resolve((command as CommandHandler)(...opts));
} catch (err) {
console.log(Colors.bold(Colors.red("An error occured:")), err.message);
}
} else {
flags.cmd.showHelp();
}
// function log(...args: any[]) {
// if (flags.options.verbose) console.log(...args);
// }
// const CONFIG_LOCATION = flags.options.config;
// function loadConfigSync() {
// try {
// const data = Deno.readFileSync(CONFIG_LOCATION);
// return Ini.decode(new TextDecoder().decode(data));
// } catch (err) {
// return {};
// }
// }
// const config = loadConfigSync();
// const { username, password, registry } = config;
// async function setConfig(name: string, value: string) {
// config[name] = value;
// const data = Ini.encode(config);
// await Deno.writeFile(CONFIG_LOCATION, new TextEncoder().encode(data), {
// create: true,
// });
// }
// async function setup() {
// const registry = await Cliffy.Input.prompt({
// message: "What's your registry?",
// default: config.registry,
// });
// const username = await Cliffy.Input.prompt({
// message: "What's your username?",
// default: config.username,
// });
// const password = await Cliffy.Secret.prompt({
// message: "What's your password?",
// hidden: true,
// default: config.password,
// });
// const author = await Cliffy.Input.prompt({
// message: "Who are you? (optional) Name <email@example.com>",
// default: config.author,
// });
// await setConfig("registry", registry);
// await setConfig("username", username);
// await setConfig("password", password);
// await setConfig("author", author);
// }
// interface IMeta {
// name: string;
// version: string;
// description?: string;
// author?: string;
// contributors?: string[];
// files: string[];
// }
// async function init() {
// let existing = {};
// try {
// existing = await _getMeta();
// } catch (err) {}
// let meta: IMeta = {
// name: Path.basename(Deno.cwd()).toLowerCase().replace(/\s+/g, "-"),
// version: "0.0.1",
// description: "",
// author: config.author,
// contributors: [],
// files: ["**/*.ts", "**/*.js", "importmap.json"],
// ...existing,
// };
// if (flags.options.interactive) {
// meta.name = await Cliffy.Input.prompt({
// message: "What's the name of your package?",
// default: meta.name,
// });
// meta.description = await Cliffy.Input.prompt({
// message: "What's the description of your package?",
// default: meta.description,
// });
// meta.author = await Cliffy.Input.prompt({
// message: "Who's the author of your package?",
// default: meta.author,
// });
// }
// await _setMeta(meta);
// }
// async function bump(options: any, type: "minor" | "major" | "patch") {
// const meta = await _getMeta();
// let [major = 0, minor = 0, patch = 0] = meta.version.split(".").map(Number);
// switch (type) {
// case "major":
// major++;
// break;
// case "minor":
// minor++;
// break;
// case "patch":
// patch++;
// break;
// default:
// throw new Error("type must be either major, minor or patch");
// }
// const newVersion = [major, minor, patch].join(".");
// console.log(
// "Bumping version from",
// Colors.blue(meta.version),
// "to",
// Colors.blue(newVersion)
// );
// meta.version = newVersion;
// await _setMeta(meta);
// }
// async function uploadPackage() {
// const meta: IMeta = await _getMeta();
// if (!meta.name) throw new Error("name is not set in meta.json");
// if (!meta.version) throw new Error("version is not set in meta.json");
// if (!meta.files || !Array.isArray(meta.files) || meta.files.length <= 0)
// throw new Error("files is not set or empty in meta.json");
// const tmpDir = await Deno.makeTempDir();
// const packedFile = await Deno.makeTempFile();
// try {
// const walker = FS.walk(".", {
// includeDirs: false,
// includeFiles: true,
// match: meta.files.map((file) => Path.globToRegExp(file)),
// });
// log("Copying files to package to", tmpDir);
// const copy = async (path: string) => {
// const dest = Path.join(tmpDir, path);
// await FS.ensureDir(Path.dirname(dest));
// await FS.copy(path, dest);
// };
// await copy("meta.json");
// for await (const file of walker) {
// await copy(file.path);
// }
// log("Compressing file");
// await Compress.Tar.compress(tmpDir, packedFile, {
// excludeSrc: true,
// });
// const url = new URL(config.registry);
// url.pathname = "/api/package/" + meta.name;
// log("Uploading new package version");
// const res = await fetch(url, {
// method: "POST",
// body: await Deno.readFile(packedFile),
// headers: {
// Authorization:
// "Basic " +
// Base64.encode(config.username + ":" + config.password),
// },
// }).then((res) => (res.status === 200 ? res.json() : res.statusText));
// log("Upload finished. Result:", res);
// if (typeof res === "string" || res.error) {
// console.log(
// Colors.red("Error: " + (typeof res == "string" ? res : res.error))
// );
// } else {
// if (res.success) {
// console.log(Colors.green("Upload successfull"));
// }
// }
// } finally {
// await Deno.remove(tmpDir, { recursive: true });
// await Deno.remove(packedFile);
// }
// }
// async function _getMeta(): Promise<IMeta> {
// log("Reading meta.json");
// return (await FS.readJson("meta.json")) as IMeta;
// }
// async function _setMeta(meta: IMeta): Promise<void> {
// log("Saving meta.json");
// return FS.writeJson("meta.json", meta, {
// spaces: " ",
// });
// }
// if (!username || !password || !registry) {
// if (!flags.options.interactive) {
// console.error(
// Colors.red("Run setup or set necessary value in " + CONFIG_LOCATION)
// );
// } else {
// log("Running setup");
// await setup();
// }
// }

View File

@ -1,7 +1,7 @@
export * as Compress from "https://git.stamm.me/Deno/DenReg/raw/branch/master/tar/mod.ts";
export * as Ini from "https://deno.land/x/ini/mod.ts";
export * as Base64 from "https://deno.land/std@0.62.0/encoding/base64.ts";
export * as Cliffy from "https://deno.land/x/cliffy/mod.ts";
export * as FS from "https://deno.land/std@0.62.0/fs/mod.ts";
export * as Colors from "https://deno.land/std@0.62.0/fmt/colors.ts";
export * as Path from "https://deno.land/std@0.62.0/path/mod.ts";
export * as Compress from "https://deno.hibas123.de/raw/@denreg-tar/mod.ts";
export * as Ini from "https://deno.land/x/ini@v2.1.0/mod.ts";
export * as Cliffy from "https://deno.land/x/cliffy@v0.18.2/mod.ts";
export * as Base64 from "https://deno.land/std@0.95.0/encoding/base64.ts";
export * as FS from "https://deno.land/std@0.95.0/fs/mod.ts";
export * as Colors from "https://deno.land/std@0.95.0/fmt/colors.ts";
export * as Path from "https://deno.land/std@0.95.0/path/mod.ts";

1
cli/dpm.ts Normal file
View File

@ -0,0 +1 @@
import "./denreg.ts";

View File

@ -1,4 +1,4 @@
import { Ini, Cliffy, Compress, Base64, FS, Colors } from "./deps.ts";
import { Ini, FS, Colors } from "./deps.ts";
import setupCMD from "./commands/setup.ts";
export interface IMeta {
@ -7,7 +7,16 @@ export interface IMeta {
description?: string;
author?: string;
contributors?: string[];
deprecated?: boolean;
files: string[];
root?: string;
hooks?: {
prepublish?: string | string[];
postpublish?: string | string[];
};
scripts?: {
[key: string]: string | string[];
};
}
let verbose = false;
@ -36,16 +45,18 @@ export async function setConfig(name: string, value: string) {
});
}
const readJson = (name: string) => Deno.readTextFile(name).then(JSON.parse);
const writeJson = (name: string, value: any, spaces?: string) =>
Deno.writeTextFile(name, JSON.stringify(value, null, spaces));
export async function getMeta() {
log("Reading meta.json");
return (await FS.readJson("meta.json")) as IMeta;
return (await readJson("meta.json")) as IMeta;
}
export async function setMeta(meta: IMeta): Promise<void> {
log("Saving meta.json");
return FS.writeJson("meta.json", meta, {
spaces: " ",
});
return writeJson("meta.json", meta, " ");
}
let interactive = true;

26
cli/helper/permission.ts Normal file
View File

@ -0,0 +1,26 @@
import { Colors } from "../deps.ts";
export const checkPermOrExit = (name: string, err: string) =>
Deno.permissions.query({ name: name as any }).then((res) => {
if (res.state !== "granted") {
console.log(Colors.bold(Colors.red(err)));
Deno.exit(1);
}
});
export const requestPermOrExit = (name: string, err: string) => {
Deno.permissions
.query({ name: name as any })
.then((res) => {
if (res.state === "prompt") {
return Deno.permissions.request({ name: name as any });
}
return res;
})
.then((res) => {
if (res.state !== "granted") {
console.log(Colors.bold(Colors.red(err)));
Deno.exit(1);
}
});
};

51
cli/helper/run_script.ts Normal file
View File

@ -0,0 +1,51 @@
import { Colors } from "../deps.ts";
import { checkPermOrExit } from "../helper/permission.ts";
export async function runScript(script: string) {
await checkPermOrExit(
"run",
"Requires --allow-run to run scripts and hooks"
);
console.log(Colors.bold(Colors.blue("Running script:")), script);
const runPerm = await Deno.permissions.query({
name: "run",
});
if (runPerm.state !== "granted") {
console.log(
Colors.red("Missing --allow-run permission. Cannot run hooks!")
);
throw new Error("Missing --allow-run permission. Cannot run hooks!");
}
const process = Deno.run({
cmd: ["deno", "run", "-A", "--unstable", script],
});
const status = await process.status();
console.log(Colors.bold(Colors.blue("Finished script:")), script);
if (!status.success) {
throw new Error(
"A script did not complete sucessfully. This is not a issue of denreg!"
);
}
}
export async function runHooks(hooks: undefined | string | string[]) {
if (!hooks) return;
if (typeof hooks === "string") {
hooks = [hooks];
}
for (const hook of hooks) {
try {
await runScript(hook);
} catch (err) {
throw new Error(
"A hook did not complete sucessfully. This is not a issue of denreg!"
);
}
}
}

View File

@ -0,0 +1,5 @@
export class ServerError extends Error {
constructor(message: string) {
super(message);
}
}

View File

@ -1,11 +1,18 @@
{
"name": "@denreg-cli",
"version": "0.1.2",
"version": "0.3.3",
"description": "CLI for the DenReg package registry",
"author": "Fabian Stamm <dev@fabianstamm.de>",
"contributors": [],
"files": [
"**/*.ts",
"**/*.js"
]
"**/*.js",
"README.md"
],
"scripts": {
"test": "version.ts"
},
"hooks": {
"prepublish": "pre.ts"
}
}

8
cli/pre.ts Normal file
View File

@ -0,0 +1,8 @@
import { FS } from "./deps.ts";
const meta = (await Deno.readTextFile("./meta.json").then(JSON.parse)) as any;
await Deno.writeTextFile(
"version.ts",
`export const version = "${meta.version}"`
);

0
cli/test.ts Normal file
View File

1
cli/version.ts Normal file
View File

@ -0,0 +1 @@
export const version = "0.3.3"

14
jsx/meta.json Normal file
View File

@ -0,0 +1,14 @@
{
"name": "@denreg-jsx",
"version": "0.1.2",
"description": "Denreg JSX renderer",
"author": "Fabian Stamm <dev@fabianstamm.de>",
"contributors": [],
"deprecated": false,
"files": [
"**/*.ts",
"**/*.js",
"tsconfig.json",
"README.md"
]
}

145
jsx/mod.ts Normal file
View File

@ -0,0 +1,145 @@
import "./types.ts";
const Fragment = Symbol("fragment");
declare namespace JSX {
interface Element {}
interface IntrinsicElements {
div: any;
}
}
export type Element = {
component: Component | string | typeof Fragment;
props: any;
children: any[];
};
export type ComponentRetElm = Element | Element[];
export type Component = (
props: any,
children: any
) => ComponentRetElm | Promise<ComponentRetElm>;
export function h(
component: string | Component,
props: any,
...children: Element[]
): Element {
return {
component,
props,
children,
};
}
const createElement = h;
export { Fragment, createElement };
export async function renderSSR(element: Element | string): Promise<string> {
if (typeof element === "string") return element;
else if (typeof element.component === "string")
return await renderHTML(element as Element);
else if (
typeof element.component === "function" ||
element.component === Fragment
)
return await renderCustom(element as Element);
console.warn("renderSSR: invalid element", element);
return "";
}
const selfClosing = new Set([
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
"link",
"meta",
"param",
"source",
"track",
"wbr",
]);
function flatDeep(arr: any, d = Infinity): any[] {
if (Array.isArray(arr) && d >= 0) {
let res = [];
for (const val of arr) {
const v = flatDeep(val, d - 1);
res.push(...v);
}
return res;
}
return [arr];
}
function cleanChildren(children: any) {
return flatDeep(children).filter((e) => !!e);
}
async function renderHTML(element: Element) {
if (typeof element.component !== "string")
throw new Error("Internal consistency error");
let props = "";
for (const key in element.props) {
if (key == "innerHTML") continue;
props += `${key}="${element.props[key] || ""}" `;
}
const tag = element.component;
if (selfClosing.has(element.component)) {
return `<${tag} ${props}/>`;
} else {
let inner = "";
if (element.props && element.props["innerHTML"]) {
inner = element.props["innerHTML"];
} else {
const children = cleanChildren(element.children);
inner = (
await Promise.all(children.map((child) => renderSSR(child)))
).join("");
}
return `<${tag} ${props}>${inner || ""}</${tag}>`;
}
}
async function renderCustom(element: Element) {
if (typeof element.component === "string")
throw new Error("Internal consistency error");
if (element.component === Fragment) {
const ch = (
await Promise.all(
cleanChildren(element.children).map((child) => renderSSR(child))
)
).join("");
return ch;
} else {
const res = await Promise.resolve(
element.component(
{
...element.props,
children: element.children,
},
element.children
)
);
const ch = (
await Promise.all(cleanChildren(res).map((child) => renderSSR(child)))
).join("");
return ch;
}
}

7
jsx/tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext", "deno.ns", "deno.unstable"],
"jsxFactory": "h",
"strictPropertyInitialization": false
}
}

11
jsx/types.ts Normal file
View File

@ -0,0 +1,11 @@
declare namespace JSX {
interface IntrinsicElements {
[elemName: string]: any;
}
}
declare namespace JSX {
interface ElementClass {
render: any;
}
}

14
markdown/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Deno",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "deno",
"runtimeArgs": ["run", "--inspect", "-A", "app.ts"],
"port": 9229
}
]
}

9
markdown/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"deno.enable": true,
"[typescript]": {
"editor.defaultFormatter": "axetroy.vscode-deno"
},
"[typescriptreact]": {
"editor.defaultFormatter": "axetroy.vscode-deno"
}
}

21
markdown/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Eivind Furuberg
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.

62
markdown/README.md Normal file
View File

@ -0,0 +1,62 @@
# markdown
Deno Markdown module forked from https://github.com/ts-stack/markdown/tree/bb47aa8e625e89e6aa84f49a98536a3089dee831
### Example usage
Simple md2html.ts script:
```typescript
import { Marked } from "./mod.ts";
const decoder = new TextDecoder("utf-8");
const filename = Deno.args[0];
const markdown = decoder.decode(await Deno.readFile(filename));
const markup = Marked.parse(markdown);
console.log(markup.content);
console.log(JSON.stringify(markup.meta))
```
Now running:
```bash
deno run --allow-read md2html.ts example.md > example.html
```
Will output:
```html
<h1 id="hello-world">Hello World</h1>
<h2 id="this-an-example-for-md2html-ts-">
This an example for <code>md2html.ts</code>
</h2>
<p>A small paragraph that will become a <code>&lt;p&gt;</code> tag</p>
<hr />
<p>Code Block (md2html.ts)</p>
<pre><code class="lang-typescript">import { Marked } from &quot;./mod.ts&quot;;
const decoder = new TextDecoder("utf-8");
const filename = Deno.args[0];
const markdown = decoder.decode(await Deno.readFile(filename));
const markup = Marked.parse(markdown);
console.log(markup.content);
console.log(JSON.stringify(markup.meta))
</code></pre>
<p>
This module is forked from
<a
href="https://github.com/ts-stack/markdown/tree/bb47aa8e625e89e6aa84f49a98536a3089dee831"
>ts-stack/markdown</a
>
</p>
<p>Made for Deno <img src="https://deno.land/logo.svg" alt="deno-logo" /></p>
{"title":"Hello world!","subtitle":"Front-matter is supported!","boolean":true,"list-example":["this","is",{"a":"list"}]}
```
---
### Notes
I had to do some changes to the source code to make the compiler happy, mostly fixes for things that were uninitialized and possibly null or undefined

BIN
markdown/example.html Normal file

Binary file not shown.

34
markdown/example.md Normal file
View File

@ -0,0 +1,34 @@
---
title : Hello world!
subtitle : Front-matter is supported!
boolean: true
list-example:
- this
- is
- a: list
---
# Hello World
## This an example for `md2html.ts`
A small paragraph that will become a `<p>` tag
---
Code Block (md2html.ts)
```typescript
import { Marked } from "./mod.ts";
const decoder = new TextDecoder("utf-8");
const filename = Deno.args[0];
const markdown = decoder.decode(await Deno.readFile(filename));
const markup = Marked.parse(markdown);
console.log(markup.content);
console.log(JSON.stringify(markup.meta));
```
This module is forked from [ts-stack/markdown](https://github.com/ts-stack/markdown/tree/bb47aa8e625e89e6aa84f49a98536a3089dee831)
Made for Deno
![deno-logo](https://deno.land/logo.svg)

8
markdown/md2html.ts Normal file
View File

@ -0,0 +1,8 @@
import { Marked } from "./mod.ts";
const decoder = new TextDecoder("utf-8");
const filename = Deno.args[0];
const markdown = decoder.decode(await Deno.readFile(filename));
const markup = Marked.parse(markdown);
console.log(markup.content);
console.log(JSON.stringify(markup.meta))

12
markdown/meta.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "markdown",
"version": "0.0.1",
"description": "",
"author": "Fabian Stamm <dev@fabianstamm.de>",
"contributors": [],
"files": [
"**/*.ts",
"**/*.js",
"README.md"
]
}

8
markdown/mod.ts Normal file
View File

@ -0,0 +1,8 @@
export * from "./src/block-lexer.ts";
export * from "./src/helpers.ts";
export * from "./src/inline-lexer.ts";
export * from "./src/interfaces.ts";
export * from "./src/marked.ts";
export * from "./src/parser.ts";
export * from "./src/renderer.ts";
export * from "./src/extend-regexp.ts";

520
markdown/src/block-lexer.ts Normal file
View File

@ -0,0 +1,520 @@
/**
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import { ExtendRegexp } from "./extend-regexp.ts";
import {
Align,
LexerReturns,
Links,
MarkedOptions,
RulesBlockBase,
RulesBlockGfm,
RulesBlockTables,
Token,
TokenType,
Obj
} from "./interfaces.ts";
import { Marked } from "./marked.ts";
import { load } from "https://deno.land/std/encoding/_yaml/loader/loader.ts";
export class BlockLexer<T extends typeof BlockLexer> {
static simpleRules: RegExp[] = [];
protected static rulesBase: RulesBlockBase;
/**
* GFM Block Grammar.
*/
protected static rulesGfm: RulesBlockGfm;
/**
* GFM + Tables Block Grammar.
*/
protected static rulesTables: RulesBlockTables;
protected rules!: RulesBlockBase | RulesBlockGfm | RulesBlockTables;
protected options: MarkedOptions;
protected links: Links = {};
protected tokens: Token[] = [];
protected frontmatter: Obj = {};
protected hasRulesGfm!: boolean;
protected hasRulesTables!: boolean;
constructor(protected staticThis: typeof BlockLexer, options?: object) {
this.options = options || Marked.options;
this.setRules();
}
/**
* Accepts Markdown text and returns object with tokens and links.
*
* @param src String of markdown source to be compiled.
* @param options Hash of options.
*/
static lex(
src: string,
options?: MarkedOptions,
top?: boolean,
isBlockQuote?: boolean,
): LexerReturns {
const lexer = new this(this, options);
return lexer.getTokens(src, top, isBlockQuote);
}
protected static getRulesBase(): RulesBlockBase {
if (this.rulesBase) {
return this.rulesBase;
}
const base: RulesBlockBase = {
newline: /^\n+/,
code: /^( {4}[^\n]+\n*)+/,
hr: /^( *[-*_]){3,} *(?:\n+|$)/,
heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
html:
/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
paragraph:
/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
text: /^[^\n]+/,
bullet: /(?:[*+-]|\d+\.)/,
item: /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,
};
base.item = new ExtendRegexp(base.item, "gm").setGroup(/bull/g, base.bullet)
.getRegexp();
base.list = new ExtendRegexp(base.list)
.setGroup(/bull/g, base.bullet)
.setGroup("hr", "\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")
.setGroup("def", "\\n+(?=" + base.def.source + ")")
.getRegexp();
const tag = "(?!(?:" +
"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code" +
"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo" +
"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";
base.html = new ExtendRegexp(base.html)
.setGroup("comment", /<!--[\s\S]*?-->/)
.setGroup("closed", /<(tag)[\s\S]+?<\/\1>/)
.setGroup("closing", /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
.setGroup(/tag/g, tag)
.getRegexp();
base.paragraph = new ExtendRegexp(base.paragraph)
.setGroup("hr", base.hr)
.setGroup("heading", base.heading)
.setGroup("lheading", base.lheading)
.setGroup("blockquote", base.blockquote)
.setGroup("tag", "<" + tag)
.setGroup("def", base.def)
.getRegexp();
return (this.rulesBase = base);
}
protected static getRulesGfm(): RulesBlockGfm {
if (this.rulesGfm) {
return this.rulesGfm;
}
const base = this.getRulesBase();
const gfm: RulesBlockGfm = {
...base,
...{
fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,
paragraph: /^/,
heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/,
},
};
const group1 = gfm.fences.source.replace("\\1", "\\2");
const group2 = base.list.source.replace("\\1", "\\3");
gfm.paragraph = new ExtendRegexp(base.paragraph).setGroup(
"(?!",
`(?!${group1}|${group2}|`,
).getRegexp();
return (this.rulesGfm = gfm);
}
protected static getRulesTable(): RulesBlockTables {
if (this.rulesTables) {
return this.rulesTables;
}
return (this.rulesTables = {
...this.getRulesGfm(),
...{
nptable:
/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/,
},
});
}
protected setRules() {
if (this.options.gfm) {
if (this.options.tables) {
this.rules = this.staticThis.getRulesTable();
} else {
this.rules = this.staticThis.getRulesGfm();
}
} else {
this.rules = this.staticThis.getRulesBase();
}
this.hasRulesGfm = (this.rules as RulesBlockGfm).fences !== undefined;
this.hasRulesTables = (this.rules as RulesBlockTables).table !== undefined;
}
/**
* Lexing.
*/
protected getTokens(
src: string,
top?: boolean,
isBlockQuote?: boolean,
): LexerReturns {
let nextPart = src;
let execArr, fmArr: RegExpExecArray | null;
mainLoop:
while (nextPart) {
// newline
if ((execArr = this.rules.newline.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
if (execArr[0].length > 1) {
this.tokens.push({ type: TokenType.space });
}
}
// code
if ((execArr = this.rules.code.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
const code = execArr[0].replace(/^ {4}/gm, "");
this.tokens.push({
type: TokenType.code,
text: !this.options.pedantic ? code.replace(/\n+$/, "") : code,
});
continue;
}
// fences code (gfm)
if (
this.hasRulesGfm &&
(execArr = (this.rules as RulesBlockGfm).fences.exec(nextPart))
) {
nextPart = nextPart.substring(execArr[0].length);
this.tokens.push({
type: TokenType.code,
lang: execArr[2],
text: execArr[3] || "",
});
continue;
}
// heading
if ((execArr = this.rules.heading.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
this.tokens.push({
type: TokenType.heading,
depth: execArr[1].length,
text: execArr[2],
});
continue;
}
// table no leading pipe (gfm)
if (
top && this.hasRulesTables &&
(execArr = (this.rules as RulesBlockTables).nptable.exec(nextPart))
) {
nextPart = nextPart.substring(execArr[0].length);
const item: Token = {
type: TokenType.table,
header: execArr[1].replace(/^ *| *\| *$/g, "").split(/ *\| */),
align: execArr[2].replace(/^ *|\| *$/g, "").split(
/ *\| */,
) as Align[],
cells: [],
};
if (!item.align) throw ReferenceError;
for (let i = 0; i < item.align.length; i++) {
if (/^ *-+: *$/.test(item.align[i])) {
item.align[i] = "right";
} else if (/^ *:-+: *$/.test(item.align[i])) {
item.align[i] = "center";
} else if (/^ *:-+ *$/.test(item.align[i])) {
item.align[i] = "left";
} else {
item.align[i] = "";
}
}
const td: string[] = execArr[3].replace(/\n$/, "").split("\n");
if (!item.cells) throw ReferenceError;
for (let i = 0; i < td.length; i++) {
item.cells[i] = td[i].split(/ *\| */);
}
this.tokens.push(item);
continue;
}
// lheading
if ((execArr = this.rules.lheading.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
this.tokens.push({
type: TokenType.heading,
depth: execArr[2] === "=" ? 1 : 2,
text: execArr[1],
});
continue;
}
// hr
if ((execArr = this.rules.hr.exec(nextPart))) {
// Checks if the previous string contains a content.
if ((this.tokens.length == 0) || (this.tokens.every(object => object.type == TokenType.space))) {
// Grabs front-matter data and parse it into Javascript object.
if (fmArr = /^(?:\-\-\-)(.*?)(?:\-\-\-|\.\.\.)/s.exec(nextPart)) {
nextPart = nextPart.substring(fmArr[0].length);
this.frontmatter = <Obj> load(fmArr[1]);
}
continue;
} else {
nextPart = nextPart.substring(execArr[0].length);
this.tokens.push({ type: TokenType.hr });
continue;
}
}
// blockquote
if ((execArr = this.rules.blockquote.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
this.tokens.push({ type: TokenType.blockquoteStart });
const str = execArr[0].replace(/^ *> ?/gm, "");
// Pass `top` to keep the current
// "toplevel" state. This is exactly
// how markdown.pl works.
this.getTokens(str);
this.tokens.push({ type: TokenType.blockquoteEnd });
continue;
}
// list
if ((execArr = this.rules.list.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
const bull: string = execArr[2];
this.tokens.push(
{ type: TokenType.listStart, ordered: bull.length > 1 },
);
// Get each top-level item.
const str = execArr[0].match(this.rules.item) || "";
const length = str.length;
let next = false;
let space: number;
let blockBullet: string;
let loose: boolean;
for (let i = 0; i < length; i++) {
let item = str[i];
// Remove the list item's bullet so it is seen as the next token.
space = item.length;
item = item.replace(/^ *([*+-]|\d+\.) +/, "");
// Outdent whatever the list item contains. Hacky.
if (item.indexOf("\n ") !== -1) {
space -= item.length;
item = !this.options.pedantic
? item.replace(new RegExp("^ {1," + space + "}", "gm"), "")
: item.replace(/^ {1,4}/gm, "");
}
// Determine whether the next list item belongs here.
// Backpedal if it does not belong in this list.
if (this.options.smartLists && i !== length - 1) {
const bb = this.staticThis.getRulesBase().bullet.exec(str[i + 1]);
blockBullet = bb ? bb[0] : "";
if (
bull !== blockBullet &&
!(bull.length > 1 && blockBullet.length > 1)
) {
nextPart = (str.slice(i + 1) as string[]).join("\n") + nextPart;
i = length - 1;
}
}
// Determine whether item is loose or not.
// Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
// for discount behavior.
loose = next || /\n\n(?!\s*$)/.test(item);
if (i !== length - 1) {
next = item.charAt(item.length - 1) === "\n";
if (!loose) {
loose = next;
}
}
this.tokens.push(
{
type: loose ? TokenType.looseItemStart : TokenType.listItemStart,
},
);
// Recurse.
this.getTokens(item, false, isBlockQuote);
this.tokens.push({ type: TokenType.listItemEnd });
}
this.tokens.push({ type: TokenType.listEnd });
continue;
}
// html
if ((execArr = this.rules.html.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
const attr = execArr[1];
const isPre = attr === "pre" || attr === "script" || attr === "style";
this.tokens.push({
type: this.options.sanitize ? TokenType.paragraph : TokenType.html,
pre: !this.options.sanitizer && isPre,
text: execArr[0],
});
continue;
}
// def
if (top && (execArr = this.rules.def.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
this.links[execArr[1].toLowerCase()] = {
href: execArr[2],
title: execArr[3],
};
continue;
}
// table (gfm)
if (
top && this.hasRulesTables &&
(execArr = (this.rules as RulesBlockTables).table.exec(nextPart))
) {
nextPart = nextPart.substring(execArr[0].length);
const item: Token = {
type: TokenType.table,
header: execArr[1].replace(/^ *| *\| *$/g, "").split(/ *\| */),
align: execArr[2].replace(/^ *|\| *$/g, "").split(
/ *\| */,
) as Align[],
cells: [],
};
if (!item.align) throw ReferenceError;
for (let i = 0; i < item.align.length; i++) {
if (/^ *-+: *$/.test(item.align[i])) {
item.align[i] = "right";
} else if (/^ *:-+: *$/.test(item.align[i])) {
item.align[i] = "center";
} else if (/^ *:-+ *$/.test(item.align[i])) {
item.align[i] = "left";
} else {
item.align[i] = "";
}
}
const td = execArr[3].replace(/(?: *\| *)?\n$/, "").split("\n");
if (!item.cells) throw ReferenceError;
for (let i = 0; i < td.length; i++) {
item.cells[i] = td[i].replace(/^ *\| *| *\| *$/g, "").split(/ *\| */);
}
this.tokens.push(item);
continue;
}
// simple rules
if (this.staticThis.simpleRules.length) {
const simpleRules = this.staticThis.simpleRules;
for (let i = 0; i < simpleRules.length; i++) {
if ((execArr = simpleRules[i].exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
const type = "simpleRule" + (i + 1);
this.tokens.push({ type, execArr });
continue mainLoop;
}
}
}
// top-level paragraph
if (top && (execArr = this.rules.paragraph.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
if (execArr[1].slice(-1) === "\n") {
this.tokens.push({
type: TokenType.paragraph,
text: execArr[1].slice(0, -1),
});
} else {
this.tokens.push({
type: this.tokens.length > 0 ? TokenType.paragraph : TokenType.text,
text: execArr[1],
});
}
continue;
}
// text
// Top-level should never reach here.
if ((execArr = this.rules.text.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
this.tokens.push({ type: TokenType.text, text: execArr[0] });
continue;
}
if (nextPart) {
throw new Error(
"Infinite loop on byte: " + nextPart.charCodeAt(0) +
`, near text '${nextPart.slice(0, 30)}...'`,
);
}
}
return { tokens: this.tokens, links: this.links, meta: this.frontmatter };
}
}

View File

@ -0,0 +1,43 @@
/*
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
export class ExtendRegexp {
private source: string;
private flags: string;
constructor(regex: RegExp, flags: string = "") {
this.source = regex.source;
this.flags = flags;
}
/**
* Extend regular expression.
*
* @param groupName Regular expression for search a group name.
* @param groupRegexp Regular expression of named group.
*/
setGroup(groupName: RegExp | string, groupRegexp: RegExp | string): this {
let newRegexp: string = typeof groupRegexp == "string"
? groupRegexp
: groupRegexp.source;
newRegexp = newRegexp.replace(/(^|[^\[])\^/g, "$1");
// Extend regexp.
this.source = this.source.replace(groupName, newRegexp);
return this;
}
/**
* Returns a result of extending a regular expression.
*/
getRegexp(): RegExp {
return new RegExp(this.source, this.flags);
}
}

64
markdown/src/helpers.ts Normal file
View File

@ -0,0 +1,64 @@
/**
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import type { Replacements } from "./interfaces.ts";
const escapeTest = /[&<>"']/;
const escapeReplace = /[&<>"']/g;
const replacements: Replacements = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
// tslint:disable-next-line:quotemark
"'": "&#39;",
};
const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
export function escape(html: string, encode?: boolean) {
if (encode) {
if (escapeTest.test(html)) {
return html.replace(escapeReplace, (ch: string) => replacements[ch]);
}
} else {
if (escapeTestNoEncode.test(html)) {
return html.replace(
escapeReplaceNoEncode,
(ch: string) => replacements[ch]
);
}
}
return html;
}
export function unescape(html: string) {
// Explicitly match decimal, hex, and named HTML entities
return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi, function (
_,
n
) {
n = n.toLowerCase();
if (n === "colon") {
return ":";
}
if (n.charAt(0) === "#") {
return n.charAt(1) === "x"
? String.fromCharCode(parseInt(n.substring(2), 16))
: String.fromCharCode(+n.substring(1));
}
return "";
});
}

View File

@ -0,0 +1,419 @@
/**
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import { ExtendRegexp } from "./extend-regexp.ts";
import type {
Link,
Links,
MarkedOptions,
RulesInlineBase,
RulesInlineBreaks,
RulesInlineCallback,
RulesInlineGfm,
RulesInlinePedantic,
} from "./interfaces.ts";
import { Marked } from "./marked.ts";
import { Renderer } from "./renderer.ts";
/**
* Inline Lexer & Compiler.
*/
export class InlineLexer {
protected static rulesBase: RulesInlineBase;
/**
* Pedantic Inline Grammar.
*/
protected static rulesPedantic: RulesInlinePedantic;
/**
* GFM Inline Grammar
*/
protected static rulesGfm: RulesInlineGfm;
/**
* GFM + Line Breaks Inline Grammar.
*/
protected static rulesBreaks: RulesInlineBreaks;
protected rules!:
| RulesInlineBase
| RulesInlinePedantic
| RulesInlineGfm
| RulesInlineBreaks;
protected renderer: Renderer;
protected inLink!: boolean;
protected hasRulesGfm!: boolean;
protected ruleCallbacks!: RulesInlineCallback[];
constructor(
protected staticThis: typeof InlineLexer,
protected links: Links,
protected options: MarkedOptions = Marked.options,
renderer?: Renderer
) {
this.renderer =
renderer || this.options.renderer || new Renderer(this.options);
if (!this.links) {
throw new Error(`InlineLexer requires 'links' parameter.`);
}
this.setRules();
}
/**
* Static Lexing/Compiling Method.
*/
static output(src: string, links: Links, options: MarkedOptions): string {
const inlineLexer = new this(this, links, options);
return inlineLexer.output(src);
}
protected static getRulesBase(): RulesInlineBase {
if (this.rulesBase) {
return this.rulesBase;
}
/**
* Inline-Level Grammar.
*/
const base: RulesInlineBase = {
escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
autolink: /^<([^ <>]+(@|:\/)[^ <>]+)>/,
tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^<'">])*?>/,
link: /^!?\[(inside)\]\(href\)/,
reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
code: /^(`+)([\s\S]*?[^`])\1(?!`)/,
br: /^ {2,}\n(?!\s*$)/,
text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/,
_inside: /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/,
_href: /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/,
};
base.link = new ExtendRegexp(base.link)
.setGroup("inside", base._inside)
.setGroup("href", base._href)
.getRegexp();
base.reflink = new ExtendRegexp(base.reflink)
.setGroup("inside", base._inside)
.getRegexp();
return (this.rulesBase = base);
}
protected static getRulesPedantic(): RulesInlinePedantic {
if (this.rulesPedantic) {
return this.rulesPedantic;
}
return (this.rulesPedantic = {
...this.getRulesBase(),
...{
strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,
},
});
}
protected static getRulesGfm(): RulesInlineGfm {
if (this.rulesGfm) {
return this.rulesGfm;
}
const base = this.getRulesBase();
const escape = new ExtendRegexp(base.escape)
.setGroup("])", "~|])")
.getRegexp();
const text = new ExtendRegexp(base.text)
.setGroup("]|", "~]|")
.setGroup("|", "|https?://|")
.getRegexp();
return (this.rulesGfm = {
...base,
...{
escape,
url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
del: /^~~(?=\S)([\s\S]*?\S)~~/,
text,
},
});
}
protected static getRulesBreaks(): RulesInlineBreaks {
if (this.rulesBreaks) {
return this.rulesBreaks;
}
const inline = this.getRulesGfm();
const gfm = this.getRulesGfm();
return (this.rulesBreaks = {
...gfm,
...{
br: new ExtendRegexp(inline.br).setGroup("{2,}", "*").getRegexp(),
text: new ExtendRegexp(gfm.text).setGroup("{2,}", "*").getRegexp(),
},
});
}
protected setRules() {
if (this.options.gfm) {
if (this.options.breaks) {
this.rules = this.staticThis.getRulesBreaks();
} else {
this.rules = this.staticThis.getRulesGfm();
}
} else if (this.options.pedantic) {
this.rules = this.staticThis.getRulesPedantic();
} else {
this.rules = this.staticThis.getRulesBase();
}
this.hasRulesGfm = (this.rules as RulesInlineGfm).url !== undefined;
}
/**
* Lexing/Compiling.
*/
output(nextPart: string): string {
nextPart = nextPart;
let execArr: RegExpExecArray | null;
let out = "";
while (nextPart) {
// escape
if ((execArr = this.rules.escape.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
out += execArr[1];
continue;
}
// autolink
if ((execArr = this.rules.autolink.exec(nextPart))) {
let text: string;
let href: string;
nextPart = nextPart.substring(execArr[0].length);
if (!this.options.escape) throw ReferenceError;
if (execArr[2] === "@") {
text = this.options.escape(
execArr[1].charAt(6) === ":"
? this.mangle(execArr[1].substring(7))
: this.mangle(execArr[1])
);
href = this.mangle("mailto:") + text;
} else {
text = this.options.escape(execArr[1]);
href = text;
}
out += this.renderer.link(href, "", text);
continue;
}
// url (gfm)
if (
!this.inLink &&
this.hasRulesGfm &&
(execArr = (this.rules as RulesInlineGfm).url.exec(nextPart))
) {
if (!this.options.escape) throw ReferenceError;
let text: string;
let href: string;
nextPart = nextPart.substring(execArr[0].length);
text = this.options.escape(execArr[1]);
href = text;
out += this.renderer.link(href, "", text);
continue;
}
// tag
if ((execArr = this.rules.tag.exec(nextPart))) {
if (!this.inLink && /^<a /i.test(execArr[0])) {
this.inLink = true;
} else if (this.inLink && /^<\/a>/i.test(execArr[0])) {
this.inLink = false;
}
nextPart = nextPart.substring(execArr[0].length);
if (!this.options.escape) throw ReferenceError;
out += this.options.sanitize
? this.options.sanitizer
? this.options.sanitizer(execArr[0])
: this.options.escape(execArr[0])
: execArr[0];
continue;
}
// link
if ((execArr = this.rules.link.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
this.inLink = true;
out += this.outputLink(execArr, {
href: execArr[2],
title: execArr[3],
});
this.inLink = false;
continue;
}
// reflink, nolink
if (
(execArr = this.rules.reflink.exec(nextPart)) ||
(execArr = this.rules.nolink.exec(nextPart))
) {
nextPart = nextPart.substring(execArr[0].length);
const keyLink = (execArr[2] || execArr[1]).replace(/\s+/g, " ");
const link = this.links[keyLink.toLowerCase()];
if (!link || !link.href) {
out += execArr[0].charAt(0);
nextPart = execArr[0].substring(1) + nextPart;
continue;
}
this.inLink = true;
out += this.outputLink(execArr, link);
this.inLink = false;
continue;
}
// strong
if ((execArr = this.rules.strong.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
out += this.renderer.strong(this.output(execArr[2] || execArr[1]));
continue;
}
// em
if ((execArr = this.rules.em.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
out += this.renderer.em(this.output(execArr[2] || execArr[1]));
continue;
}
// code
if ((execArr = this.rules.code.exec(nextPart))) {
if (!this.options.escape) throw ReferenceError;
nextPart = nextPart.substring(execArr[0].length);
out += this.renderer.codespan(
this.options.escape(execArr[2].trim(), true)
);
continue;
}
// br
if ((execArr = this.rules.br.exec(nextPart))) {
nextPart = nextPart.substring(execArr[0].length);
out += this.renderer.br();
continue;
}
// del (gfm)
if (
this.hasRulesGfm &&
(execArr = (this.rules as RulesInlineGfm).del.exec(nextPart))
) {
nextPart = nextPart.substring(execArr[0].length);
out += this.renderer.del(this.output(execArr[1]));
continue;
}
// text
if ((execArr = this.rules.text.exec(nextPart))) {
if (!this.options.escape) throw ReferenceError;
nextPart = nextPart.substring(execArr[0].length);
out += this.renderer.text(
this.options.escape(this.smartypants(execArr[0]))
);
continue;
}
if (nextPart) {
throw new Error("Infinite loop on byte: " + nextPart.charCodeAt(0));
}
}
return out;
}
/**
* Compile Link.
*/
protected outputLink(execArr: RegExpExecArray, link: Link) {
if (!this.options.escape) throw ReferenceError;
const href = this.options.escape(link.href);
const title = link.title ? this.options.escape(link.title) : null;
return execArr[0].charAt(0) !== "!"
? this.renderer.link(href, title || "", this.output(execArr[1]))
: this.renderer.image(href, title || "", this.options.escape(execArr[1]));
}
/**
* Smartypants Transformations.
*/
protected smartypants(text: string) {
if (!this.options.smartypants) {
return text;
}
return (
text
// em-dashes
.replace(/---/g, "\u2014")
// en-dashes
.replace(/--/g, "\u2013")
// opening singles
.replace(/(^|[-\u2014/(\[{"\s])'/g, "$1\u2018")
// closing singles & apostrophes
.replace(/'/g, "\u2019")
// opening doubles
.replace(/(^|[-\u2014/(\[{\u2018\s])"/g, "$1\u201c")
// closing doubles
.replace(/"/g, "\u201d")
// ellipses
.replace(/\.{3}/g, "\u2026")
);
}
/**
* Mangle Links.
*/
protected mangle(text: string) {
if (!this.options.mangle) {
return text;
}
let out = "";
const length = text.length;
for (let i = 0; i < length; i++) {
let str: string = "";
if (Math.random() > 0.5) {
str = "x" + text.charCodeAt(i).toString(16);
}
out += "&#" + str + ";";
}
return out;
}
}

196
markdown/src/interfaces.ts Normal file
View File

@ -0,0 +1,196 @@
/**
* @license
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import { escape, unescape } from "./helpers.ts";
import type { Renderer } from "./renderer.ts";
export interface Obj {
[key: string]: any;
}
export interface RulesBlockBase {
newline: RegExp;
code: RegExp;
hr: RegExp;
heading: RegExp;
lheading: RegExp;
blockquote: RegExp;
list: RegExp;
html: RegExp;
def: RegExp;
paragraph: RegExp;
text: RegExp;
bullet: RegExp;
/**
* List item (<li>).
*/
item: RegExp;
}
export interface RulesBlockGfm extends RulesBlockBase {
fences: RegExp;
}
export interface RulesBlockTables extends RulesBlockGfm {
nptable: RegExp;
table: RegExp;
}
export interface Link {
href: string;
title: string;
}
export interface Links {
[key: string]: Link;
}
export enum TokenType {
space = 1,
text,
paragraph,
heading,
listStart,
listEnd,
looseItemStart,
looseItemEnd,
listItemStart,
listItemEnd,
blockquoteStart,
blockquoteEnd,
code,
table,
html,
hr,
}
export type Align = "center" | "left" | "right" | "";
export interface Token {
type: number | string;
text?: string;
lang?: string;
depth?: number;
header?: string[];
align?: Align[];
cells?: string[][];
ordered?: boolean;
pre?: boolean;
escaped?: boolean;
execArr?: RegExpExecArray;
/**
* Used for debugging. Identifies the line number in the resulting HTML file.
*/
line?: number;
}
export interface RulesInlineBase {
escape: RegExp;
autolink: RegExp;
tag: RegExp;
link: RegExp;
reflink: RegExp;
nolink: RegExp;
strong: RegExp;
em: RegExp;
code: RegExp;
br: RegExp;
text: RegExp;
_inside: RegExp;
_href: RegExp;
}
export interface RulesInlinePedantic extends RulesInlineBase {}
/**
* GFM Inline Grammar
*/
export interface RulesInlineGfm extends RulesInlineBase {
url: RegExp;
del: RegExp;
}
export interface RulesInlineBreaks extends RulesInlineGfm {}
export class MarkedOptions {
gfm?: boolean = true;
tables?: boolean = true;
breaks?: boolean = false;
pedantic?: boolean = false;
sanitize?: boolean = false;
sanitizer?: (text: string) => string;
mangle?: boolean = false;
smartLists?: boolean = false;
silent?: boolean = false;
/**
* @param code The section of code to pass to the highlighter.
* @param lang The programming language specified in the code block.
*/
highlight?: (code: string, lang?: string) => string;
langPrefix?: string = "lang-";
smartypants?: boolean = false;
headerPrefix?: string = "";
/**
* An object containing functions to render tokens to HTML. Default: `new Renderer()`
*/
renderer?: Renderer;
/**
* Self-close the tags for void elements (&lt;br/&gt;, &lt;img/&gt;, etc.)
* with a "/" as required by XHTML.
*/
xhtml?: boolean = false;
/**
* The function that will be using to escape HTML entities.
* By default using inner helper.
*/
escape?: (html: string, encode?: boolean) => string = escape;
/**
* The function that will be using to unescape HTML entities.
* By default using inner helper.
*/
unescape?: (html: string) => string = unescape;
/**
* If set to `true`, an inline text will not be taken in paragraph.
*
* ```ts
* // isNoP == false
* Marked.parse('some text'); // returns '<p>some text</p>'
*
* Marked.setOptions({isNoP: true});
*
* Marked.parse('some text'); // returns 'some text'
* ```
*/
isNoP?: boolean;
}
export interface LexerReturns {
tokens: Token[];
links: Links;
meta: Obj;
}
export interface Parsed {
content: string;
meta: Obj;
}
export interface DebugReturns extends LexerReturns {
result: string;
}
export interface Replacements {
[key: string]: string;
}
export interface RulesInlineCallback {
regexp?: RegExp;
condition(): RegExp;
tokenize(execArr: RegExpExecArray): void;
}
export type SimpleRenderer = (execArr?: RegExpExecArray) => string;

154
markdown/src/marked.ts Normal file
View File

@ -0,0 +1,154 @@
/**
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import { BlockLexer } from "./block-lexer.ts";
import {
DebugReturns,
LexerReturns,
Links,
MarkedOptions,
SimpleRenderer,
Token,
TokenType,
Parsed
} from "./interfaces.ts";
import { Parser } from "./parser.ts";
export class Marked {
static options = new MarkedOptions();
protected static simpleRenderers: SimpleRenderer[] = [];
protected static parsed: Parsed = {
content: "",
meta: {},
};
/**
* Merges the default options with options that will be set.
*
* @param options Hash of options.
*/
static setOptions(options: MarkedOptions) {
Object.assign(this.options, options);
return this;
}
/**
* Setting simple block rule.
*/
static setBlockRule(regexp: RegExp, renderer: SimpleRenderer = () => "") {
BlockLexer.simpleRules.push(regexp);
this.simpleRenderers.push(renderer);
return this;
}
/**
* Accepts Markdown text and returns an object containing HTML and metadata.
*
* @param src String of markdown source to be compiled.
* @param options Hash of options. They replace, but do not merge with the default options.
* If you want the merging, you can to do this via `Marked.setOptions()`.
*/
static parse(src: string, options: MarkedOptions = this.options): Parsed {
try {
const { tokens, links, meta } = this.callBlockLexer(src, options);
this.parsed.content = this.callParser(tokens, links, options);
this.parsed.meta = meta;
return this.parsed;
} catch (e) {
this.parsed.content = this.callMe(e);
return this.parsed;
}
}
/**
* Accepts Markdown text and returns object with text in HTML format,
* tokens and links from `BlockLexer.parser()`.
*
* @param src String of markdown source to be compiled.
* @param options Hash of options. They replace, but do not merge with the default options.
* If you want the merging, you can to do this via `Marked.setOptions()`.
*/
static debug(
src: string,
options: MarkedOptions = this.options,
): DebugReturns {
const { tokens, links, meta } = this.callBlockLexer(src, options);
let origin = tokens.slice();
const parser = new Parser(options);
parser.simpleRenderers = this.simpleRenderers;
const result = parser.debug(links, tokens);
/**
* Translates a token type into a readable form,
* and moves `line` field to a first place in a token object.
*/
origin = origin.map((token) => {
token.type = (TokenType as any)[token.type] || token.type;
const line = token.line;
delete token.line;
if (line) {
return { ...{ line }, ...token };
} else {
return token;
}
});
return { tokens: origin, links, meta, result};
}
protected static callBlockLexer(
src: string = "",
options?: MarkedOptions,
): LexerReturns {
if (typeof src != "string") {
throw new Error(
`Expected that the 'src' parameter would have a 'string' type, got '${typeof src}'`,
);
}
// Preprocessing.
src = src
.replace(/\r\n|\r/g, "\n")
.replace(/\t/g, " ")
.replace(/\u00a0/g, " ")
.replace(/\u2424/g, "\n")
.replace(/^ +$/gm, "");
return BlockLexer.lex(src, options, true);
}
protected static callParser(
tokens: Token[],
links: Links,
options?: MarkedOptions,
): string {
if (this.simpleRenderers.length) {
const parser = new Parser(options);
parser.simpleRenderers = this.simpleRenderers;
return parser.parse(links, tokens);
} else {
return Parser.parse(tokens, links, options);
}
}
protected static callMe(err: Error) {
err.message +=
"\nPlease report this to https://github.com/ts-stack/markdown";
if (this.options.silent && this.options.escape) {
return "<p>An error occured:</p><pre>" +
this.options.escape(err.message + "", true) + "</pre>";
}
throw err;
}
}

247
markdown/src/parser.ts Normal file
View File

@ -0,0 +1,247 @@
/**
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import { InlineLexer } from "./inline-lexer.ts";
import {
Links,
MarkedOptions,
SimpleRenderer,
Token,
TokenType,
} from "./interfaces.ts";
import { Marked } from "./marked.ts";
import { Renderer } from "./renderer.ts";
/**
* Parsing & Compiling.
*/
export class Parser {
simpleRenderers: SimpleRenderer[] = [];
protected tokens: Token[];
protected token: Token | undefined;
protected inlineLexer!: InlineLexer;
protected options: MarkedOptions;
protected renderer: Renderer;
protected line: number = 0;
constructor(options?: MarkedOptions) {
this.tokens = [];
this.token = undefined;
this.options = options || Marked.options;
this.renderer = this.options.renderer || new Renderer(this.options);
}
static parse(tokens: Token[], links: Links, options?: MarkedOptions): string {
const parser = new this(options);
return parser.parse(links, tokens);
}
parse(links: Links, tokens: Token[]) {
this.inlineLexer = new InlineLexer(
InlineLexer,
links,
this.options,
this.renderer,
);
this.tokens = tokens.reverse();
let out = "";
while (this.next()) {
out += this.tok();
}
return out;
}
debug(links: Links, tokens: Token[]) {
this.inlineLexer = new InlineLexer(
InlineLexer,
links,
this.options,
this.renderer,
);
this.tokens = tokens.reverse();
let out = "";
while (this.next()) {
const outToken: string = this.tok() || "";
if (!this.token) throw ReferenceError;
this.token.line = this.line += outToken.split("\n").length - 1;
out += outToken;
}
return out;
}
protected next() {
return (this.token = this.tokens.pop());
}
protected getNextElement() {
return this.tokens[this.tokens.length - 1];
}
protected parseText() {
if (!this.token) throw ReferenceError;
let body = this.token.text;
let nextElement: Token;
while (
(nextElement = this.getNextElement()) &&
nextElement.type == TokenType.text
) {
body += "\n" + this.next()?.text;
}
return this.inlineLexer.output(body || "");
}
protected tok() {
if (!this.token) throw ReferenceError;
switch (this.token.type) {
case TokenType.space: {
return "";
}
case TokenType.paragraph: {
return this.renderer.paragraph(
this.inlineLexer.output(this.token.text || ""),
);
}
case TokenType.text: {
if (this.options.isNoP) {
return this.parseText();
} else {
return this.renderer.paragraph(this.parseText());
}
}
case TokenType.heading: {
return this.renderer.heading(
this.inlineLexer.output(this.token.text || ""),
this.token.depth || 0,
this.token.text || "",
);
}
case TokenType.listStart: {
let body = "";
const ordered = this.token.ordered;
while (this.next()?.type != TokenType.listEnd) {
body += this.tok();
}
return this.renderer.list(body, ordered);
}
case TokenType.listItemStart: {
let body = "";
while (this.next()?.type != TokenType.listItemEnd) {
body += this.token.type == (TokenType.text as any)
? this.parseText()
: this.tok();
}
return this.renderer.listitem(body);
}
case TokenType.looseItemStart: {
let body = "";
while (this.next()?.type != TokenType.listItemEnd) {
body += this.tok();
}
return this.renderer.listitem(body);
}
case TokenType.code: {
return this.renderer.code(
this.token.text || "",
this.token.lang,
this.token.escaped,
);
}
case TokenType.table: {
let header = "";
let body = "";
let cell;
if (
!this.token || !this.token.header || !this.token.align ||
!this.token.cells
) {
throw ReferenceError;
}
// header
cell = "";
for (let i = 0; i < this.token.header.length; i++) {
const flags = { header: true, align: this.token.align[i] };
const out = this.inlineLexer.output(this.token.header[i]);
cell += this.renderer.tablecell(out, flags);
}
header += this.renderer.tablerow(cell);
for (const row of this.token.cells) {
cell = "";
for (let j = 0; j < row.length; j++) {
cell += this.renderer.tablecell(this.inlineLexer.output(row[j]), {
header: false,
align: this.token.align[j],
});
}
body += this.renderer.tablerow(cell);
}
return this.renderer.table(header, body);
}
case TokenType.blockquoteStart: {
let body = "";
while (this.next()?.type != TokenType.blockquoteEnd) {
body += this.tok();
}
return this.renderer.blockquote(body);
}
case TokenType.hr: {
return this.renderer.hr();
}
case TokenType.html: {
const html = !this.token.pre && !this.options.pedantic
? this.inlineLexer.output(this.token.text || "")
: this.token.text;
return this.renderer.html(html || "");
}
default: {
if (this.simpleRenderers.length) {
for (let i = 0; i < this.simpleRenderers.length; i++) {
if (this.token.type == "simpleRule" + (i + 1)) {
return this.simpleRenderers[i].call(
this.renderer,
this.token.execArr,
);
}
}
}
const errMsg = `Token with "${this.token.type}" type was not found.`;
if (this.options.silent) {
console.log(errMsg);
} else {
throw new Error(errMsg);
}
}
}
}
}

179
markdown/src/renderer.ts Normal file
View File

@ -0,0 +1,179 @@
/**
* @license
*
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
* https://github.com/ts-stack/markdown
*/
import type { Align, MarkedOptions } from "./interfaces.ts";
import { Marked } from "./marked.ts";
export class Renderer {
protected options: MarkedOptions;
constructor(options?: MarkedOptions) {
this.options = options || Marked.options;
}
code(code: string, lang?: string, escaped?: boolean): string {
if (this.options.highlight) {
const out = this.options.highlight(code, lang);
if (out != null && out !== code) {
escaped = true;
code = out;
}
}
if (!this.options.escape) throw ReferenceError;
if (!lang) {
return (
"\n<pre><code>" +
(escaped ? code : this.options.escape(code, true)) +
"\n</code></pre>\n"
);
}
return (
'\n<pre><code class="' +
this.options.langPrefix +
this.options.escape(lang, true) +
'">' +
(escaped ? code : this.options.escape(code, true)) +
"\n</code></pre>\n"
);
}
blockquote(quote: string): string {
return "<blockquote>\n" + quote + "</blockquote>\n";
}
html(html: string): string {
return html;
}
heading(text: string, level: number, raw: string): string {
const id: string =
this.options.headerPrefix + raw.toLowerCase().replace(/[^\w]+/g, "-");
return `<h${level} id="${id}">${text}</h${level}>\n`;
}
hr(): string {
return this.options.xhtml ? "<hr/>\n" : "<hr>\n";
}
list(body: string, ordered?: boolean): string {
const type = ordered ? "ol" : "ul";
return `\n<${type}>\n${body}</${type}>\n`;
}
listitem(text: string): string {
return "<li>" + text + "</li>\n";
}
paragraph(text: string): string {
return "<p>" + text + "</p>\n";
}
table(header: string, body: string): string {
return `
<table>
<thead>
${header}</thead>
<tbody>
${body}</tbody>
</table>
`;
}
tablerow(content: string): string {
return "<tr>\n" + content + "</tr>\n";
}
tablecell(
content: string,
flags: { header?: boolean; align?: Align }
): string {
const type = flags.header ? "th" : "td";
const tag = flags.align
? "<" + type + ' style="text-align:' + flags.align + '">'
: "<" + type + ">";
return tag + content + "</" + type + ">\n";
}
// *** Inline level renderer methods. ***
strong(text: string): string {
return "<strong>" + text + "</strong>";
}
em(text: string): string {
return "<em>" + text + "</em>";
}
codespan(text: string): string {
return "<code>" + text + "</code>";
}
br(): string {
return this.options.xhtml ? "<br/>" : "<br>";
}
del(text: string): string {
return "<del>" + text + "</del>";
}
link(href: string, title: string, text: string): string {
if (this.options.sanitize) {
let prot: string;
if (!this.options.unescape) throw ReferenceError;
try {
prot = decodeURIComponent(this.options.unescape(href))
.replace(/[^\w:]/g, "")
.toLowerCase();
} catch (e) {
return text;
}
if (
prot.indexOf("javascript:") === 0 ||
prot.indexOf("vbscript:") === 0 ||
prot.indexOf("data:") === 0
) {
return text;
}
}
let out = '<a href="' + href + '"';
if (title) {
out += ' title="' + title + '"';
}
out += ">" + text + "</a>";
return out;
}
image(href: string, title: string, text: string): string {
let out = '<img src="' + href + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
out += this.options.xhtml ? "/>" : ">";
return out;
}
text(text: string): string {
return text;
}
}

View File

@ -1,33 +1,11 @@
FROM debian:bullseye-slim AS builder
ENV DENO_VERSION=1.2.1
WORKDIR /build
RUN apt-get update && apt-get install -y curl zip
RUN curl -fsSL https://github.com/denoland/deno/releases/download/v${DENO_VERSION}/deno-x86_64-unknown-linux-gnu.zip --output deno.zip
RUN unzip deno.zip
RUN rm deno.zip
RUN chmod 777 deno
RUN chmod +x deno
RUN mv deno /usr/bin/deno
FROM debian:bullseye-slim
COPY --from=builder /usr/bin/deno /usr/bin/deno
RUN ls /usr/bin/deno
FROM docker.hibas123.de/deno
WORKDIR /app
COPY src/deps.ts /app/src/deps.ts
RUN /usr/bin/deno cache --unstable src/deps.ts
ADD src /app/src
ADD public /app/public
ADD tsconfig.json /app/
RUN /usr/bin/deno cache --unstable --config /app/tsconfig.json src/registry.ts
RUN /usr/bin/deno cache --unstable src/registry.ts
VOLUME [ "/data" ]
ENTRYPOINT [ "/usr/bin/deno", "run", "-A", "--unstable", "/app/src/registry.ts" ]
VOLUME [ "/app/data" ]
ENTRYPOINT [ "/usr/bin/deno", "run", "-A", "--unstable", "--config", "/app/tsconfig.json", "/app/src/registry.ts" ]

BIN
registry/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

11
registry/favicon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/public/ms-icon-70x70.png"/><square150x150logo src="/public/ms-icon-150x150.png"/><square310x310logo src="/public/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

BIN
registry/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

1
registry/public/file.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>

After

Width:  |  Height:  |  Size: 337 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-folder"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>

After

Width:  |  Height:  |  Size: 311 B

View File

@ -0,0 +1,41 @@
{
"name": "Deno package Registry",
"short_name": "DenReg",
"description": "A deno package registry, that can be self hosted easily",
"start_url": "/",
"scope": "/",
"theme_color": "#ffffff",
"display": "standalone",
"icons": [
{
"src": "/public/favicon-32x32.png",
"sizes": "32x32",
"type": "image/png",
"density": "0.75"
},
{
"src": "/public/apple-icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"density": "1.5"
},
{
"src": "/public/favicon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"density": "2.0"
},
{
"src": "/public/apple-icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"density": "3.0"
},
{
"src": "/public/android-icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"density": "4.0"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

2
registry/public/paper.min.css vendored Normal file

File diff suppressed because one or more lines are too long

218
registry/public/prism.css Normal file
View File

@ -0,0 +1,218 @@
/* PrismJS 1.22.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+abap+abnf+actionscript+ada+agda+al+antlr4+apacheconf+apl+applescript+aql+arduino+arff+asciidoc+aspnet+asm6502+autohotkey+autoit+bash+basic+batch+bbcode+birb+bison+bnf+brainfuck+brightscript+bro+bsl+c+csharp+cpp+cil+clojure+cmake+coffeescript+concurnas+csp+crystal+css-extras+cypher+d+dart+dax+dhall+diff+django+dns-zone-file+docker+ebnf+editorconfig+eiffel+ejs+elixir+elm+etlua+erb+erlang+excel-formula+fsharp+factor+firestore-security-rules+flow+fortran+ftl+gml+gcode+gdscript+gedcom+gherkin+git+glsl+go+graphql+groovy+haml+handlebars+haskell+haxe+hcl+hlsl+http+hpkp+hsts+ichigojam+icon+ignore+inform7+ini+io+j+java+javadoc+javadoclike+javastacktrace+jolie+jq+jsdoc+js-extras+json+json5+jsonp+jsstacktrace+js-templates+julia+keyman+kotlin+latex+latte+less+lilypond+liquid+lisp+livescript+llvm+lolcode+lua+makefile+markdown+markup-templating+matlab+mel+mizar+mongodb+monkey+moonscript+n1ql+n4js+nand2tetris-hdl+naniscript+nasm+neon+nginx+nim+nix+nsis+objectivec+ocaml+opencl+oz+parigp+parser+pascal+pascaligo+pcaxis+peoplecode+perl+php+phpdoc+php-extras+plsql+powerquery+powershell+processing+prolog+properties+protobuf+pug+puppet+pure+purebasic+purescript+python+q+qml+qore+r+racket+jsx+tsx+reason+regex+renpy+rest+rip+roboconf+robotframework+ruby+rust+sas+sass+scss+scala+scheme+shell-session+smali+smalltalk+smarty+sml+solidity+solution-file+soy+sparql+splunk-spl+sqf+sql+stan+iecst+stylus+swift+t4-templating+t4-cs+t4-vb+tap+tcl+tt2+textile+toml+turtle+twig+typescript+typoscript+unrealscript+vala+vbnet+velocity+verilog+vhdl+vim+visual-basic+warpscript+wasm+wiki+xeora+xml-doc+xojo+xquery+yaml+yang+zig&plugins=line-numbers+inline-color */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.token.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
/* This background color was intended by the author of this theme. */
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
pre[class*="language-"].line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
pre[class*="language-"].line-numbers > code {
position: relative;
white-space: inherit;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid #999;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}
span.inline-color-wrapper {
/*
* The background image is the following SVG inline in base 64:
*
* <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2">
* <path fill="gray" d="M0 0h2v2H0z"/>
* <path fill="white" d="M0 0h1v1H0zM1 1h1v1H1z"/>
* </svg>
*
* SVG-inlining explained:
* https://stackoverflow.com/a/21626701/7595472
*/
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyIDIiPjxwYXRoIGZpbGw9ImdyYXkiIGQ9Ik0wIDBoMnYySDB6Ii8+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0wIDBoMXYxSDB6TTEgMWgxdjFIMXoiLz48L3N2Zz4=");
/* This is to prevent visual glitches where one pixel from the repeating pattern could be seen. */
background-position: center;
background-size: 110%;
display: inline-block;
height: 1.333ch;
width: 1.333ch;
margin: 0 .333ch;
box-sizing: border-box;
border: 1px solid white;
outline: 1px solid rgba(0,0,0,.5);
overflow: hidden;
}
span.inline-color {
display: block;
/* To prevent visual glitches again */
height: 120%;
width: 120%;
}

View File

@ -0,0 +1,6 @@
await fetch("https://unpkg.com/papercss/dist/paper.min.css")
.then((res) => res.arrayBuffer())
.then((data) =>
Deno.writeFile("./public/paper.min.css", new Uint8Array(data))
)
.catch(console.error);

View File

@ -14,6 +14,8 @@ const config =
) || {};
if (!config.user) config.user = {};
if (!config.web) config.web = {};
if (!config.general) config.general = {};
const env = Deno.env.toObject();
@ -40,11 +42,30 @@ for (const key in env) {
case "S3_SECRET":
config.s3 = { ...(config.s3 || {}), secretKey: env[key] };
break;
case "S3_REGION":
config.s3 = { ...(config.s3 || {}), region: env[key] };
break;
case "WEB_URL":
config.web.url = env[key];
break;
case "WEB_TRACKING":
config.web.tracking = env[key];
break;
case "GENERAL_DEV":
config.general.dev = env[key] === "true";
}
}
}
}
if (config.general.dev) {
console.warn("Dev mode active!!!");
}
if (!config.web.url) {
console.error("The web.url configuration has to be set!");
}
console.log("Known users:", Object.keys(config.user));
export default config;

View File

@ -4,12 +4,29 @@ await FS.ensureDir("./data");
export interface IPackage {
name: string;
owner: string;
description: string;
versions: string[];
deprecated: boolean;
readme: string;
}
const db = new Datastore<IPackage>({
filename: "data/db.json",
autoload: true,
});
export interface IApiKey {
user: string;
key: string;
createdAt: Date;
lastAccess?: Date;
lastIP?: string;
}
const db = {
package: new Datastore<IPackage>({
filename: "data/packages.json",
autoload: true,
}),
api_key: new Datastore<IApiKey>({
filename: "data/api_keys.json",
}),
};
export default db;

View File

@ -1,18 +1,30 @@
// export { MongoClient, ObjectId } from "https://deno.land/x/mongo@v0.9.1/mod.ts";
// @deno-types="./types/hotfix.d.ts"
export * as S3 from "https://deno.land/x/s3/mod.ts";
export * as S3 from "https://deno.land/x/s3@0.3.0/mod.ts";
export { S3Error } from "https://deno.land/x/s3@0.3.0/src/error.ts";
export * as Ini from "https://deno.land/x/ini/mod.ts";
export * as Ini from "https://deno.hibas123.de/raw/ini@0.0.3/mod.ts";
export * as ABC from "https://deno.land/x/abc@v1/mod.ts";
export * as CorsMW from "https://deno.land/x/abc@v1/middleware/cors.ts";
export * as LoggerMW from "https://deno.land/x/abc@v1/middleware/logger.ts";
export * as ABC from "https://deno.land/x/abc@v1.2.4/mod.ts";
export * as CorsMW from "https://deno.land/x/abc@v1.2.4/middleware/cors.ts";
export * as LoggerMW from "https://deno.land/x/abc@v1.2.4/middleware/logger.ts";
export * as Path from "https://deno.land/std@0.62.0/path/mod.ts";
export * as FS from "https://deno.land/std@0.62.0/fs/mod.ts";
export * as Path from "https://deno.land/std@0.83.0/path/mod.ts";
export * as FS from "https://deno.land/std@0.83.0/fs/mod.ts";
export * as Base64 from "https://deno.land/std@0.83.0/encoding/base64.ts";
export * as Hash from "https://deno.land/std@0.83.0/hash/mod.ts";
export * as Colors from "https://deno.land/std@0.83.0/fmt/colors.ts";
export * as Compress from "https://git.stamm.me/Deno/DenReg/raw/branch/master/tar/mod.ts";
export { default as Prism } from "https://cdn.skypack.dev/prismjs";
export { Marked } from "https://deno.hibas123.de/raw/markdown/mod.ts";
import DS from "https://raw.githubusercontent.com/hibas123/dndb/master/mod.ts";
import * as Pico from "https://deno.hibas123.de/raw/@denreg-jsx@0.1.2/mod.ts";
export { Pico };
export const Datastore = DS;

View File

@ -1,4 +1,5 @@
import { ABC, CorsMW, LoggerMW } from "./deps.ts";
// /// <reference path="./types/jsx.d.ts" />
import { ABC, CorsMW, LoggerMW, Path } from "./deps.ts";
import config from "./config.ts";
const port = config?.api?.port || 8000;
@ -7,11 +8,55 @@ const app = new ABC.Application();
app.use(LoggerMW.logger({}));
app.use(CorsMW.cors({}));
// app.static("/public", "./public");
const MIMEDB = {
".css": "text/css",
".json": "application/json",
".js": "application/javascript",
".gz": "applcation/gzip",
".ico": "image/x-icon",
".png": "image/png",
".svg": "image/svg+xml",
".xml": "application/xml",
};
app.get("/public/*path", async (ctx) => {
const filePath = Path.join("./public", ctx.params.path);
console.log(filePath);
ctx.blob(
await Deno.open(filePath, { read: true }),
(MIMEDB as any)[Path.extname(filePath)]
);
});
import api from "./http/api.ts";
api(app.group("api"));
api(app.group("/api"));
import raw from "./http/raw.ts";
raw(app.group("raw"));
raw(app.group("/raw"));
import intellisense from "./http/intellisense.ts";
intellisense(app.group("/.well-known"));
import view from "./http/views.ts";
view(app);
// function logNode(router: Node, indent = 0) {
// trees.map((tree) => {
// console.log("Path:", tree.path);
// tree.children?.forEach((node) => logNode(node, indent + 3));
// });
// }
// const trees = Object.values(app.router) as Node[];
// trees.forEach(logNode);
import render from "./renderer.tsx";
app.renderer = {
render: render,
};
app.start({ port });
console.log("Running server at http://0.0.0.0:" + port);

View File

@ -1,30 +1,118 @@
import { ABC, Path, Compress, FS } from "../deps.ts";
import { ABC, Path, Compress, FS, Colors, S3Error } from "../deps.ts";
import bucket from "../s3.ts";
import { isValidPackageName, basicauth, isValidFullVersion } from "../utils.ts";
import {
isValidPackageName,
basicauth,
isValidFullVersion,
getAbsolutePackageVersion,
getBucketFilePath,
} from "../utils.ts";
import db, { IPackage } from "../db.ts";
import { v4 } from "https://deno.land/std/uuid/mod.ts";
export default function api(g: ABC.Group) {
g.get("/", (ctx) => {
return { version: "1" };
const cacheControl = (next: ABC.HandlerFunc) => (ctx: ABC.Context) => {
ctx.response.headers.set("cache-control", "private");
return next(ctx);
};
g.get(
"/",
(ctx) => {
return { version: "1" };
},
cacheControl
);
g.get("/module", async (ctx) => {
return db.package.find({}).then((res) => res.map((e) => e.name));
});
g.post("/package/:name", uploadPackage, basicauth("api"));
g.get("/module/:module", async (ctx) => {
const module = await db.package.findOne({ name: ctx.params.module });
if (!module) {
ctx.response.status = 404;
return "// Not found";
} else {
return module.versions;
}
});
g.get("/module/:module/v/:version", async (ctx) => {
const module = await db.package.findOne({ name: ctx.params.module });
if (!module) {
ctx.response.status = 404;
return "// Not found";
} else {
let version = getAbsolutePackageVersion(
module,
ctx.params.version
) as string;
if (!version) {
ctx.response.status = 404;
return "// Not found";
}
const bucketPath = await getBucketFilePath(module.name, version, "/");
const filesItr = bucket.listAllObjects({
batchSize: 100,
prefix: bucketPath,
});
const allowedExts = new Set(
(ctx.queryParams.ext || "js|ts").split("|").map((e) => "." + e)
);
let files: string[] = [];
for await (let file of filesItr) {
const relPath = Path.posix.relative(bucketPath, file.key || "");
const ext = Path.extname(relPath);
if (allowedExts.has(ext)) files.push(relPath);
}
return files;
}
});
// g.post("/getapikey", getApiKey, basicauth("api"));
g.post("/package/:name", uploadPackage, cacheControl, basicauth("api"));
g.post("/module/:name", uploadPackage, cacheControl, basicauth("api")); //Switch no module instead of package
}
// async function getApiKey(ctx: ABC.Context) {
// const key = v4.generate();
// await db.api_key.insert({
// user: ctx.customContext.user,
// key,
// createdAt: new Date(),
// lastAccess: undefined,
// lastIP: undefined,
// });
// return {
// key,
// };
// }
async function uploadPackage(ctx: ABC.Context) {
const reqId = v4.generate();
const filename = "./tmp/" + reqId + ".tar";
const folder = "./tmp/" + reqId;
try {
const packageName = ctx.params.name;
const packageName = ctx.params.name.toLowerCase();
if (!isValidPackageName(packageName))
throw new Error("Invalid package name");
if (!isValidPackageName(packageName)) {
return {
success: false,
message: "Invalid package name",
};
}
console.log("Writing body to tmp file:", filename);
const file = await Deno.open(filename, {
@ -53,7 +141,10 @@ async function uploadPackage(ctx: ABC.Context) {
console.log("Checking meta.json");
if (!meta?.version) {
throw new Error("No version available in meta.json");
return {
success: false,
message: "No version available in meta.json",
};
}
const packageVersion = meta.version;
@ -61,26 +152,39 @@ async function uploadPackage(ctx: ABC.Context) {
console.log("Checking correct version");
if (!isValidFullVersion(packageVersion)) {
throw new Error("Invalid version. Version must be in format: 0.0.0");
return {
success: false,
message: "Invalid version. Version must be in format: 0.0.0",
};
return;
}
console.log("Checking for previous uploads");
let packageMeta = await db.findOne({ name: packageName });
let packageMeta = await db.package.findOne({ name: packageName });
console.log(meta, packageMeta);
if (!packageMeta) {
packageMeta = {
name: packageName,
owner: ctx.customContext.user,
description: meta.description,
deprecated: false,
readme: meta.readme,
versions: [],
};
await db.insert(packageMeta);
await db.package.insert(packageMeta);
}
console.log("Check if version was uploaded before");
if (packageMeta.versions.find((e) => e === meta.version)) {
throw new Error("Version was already uploaded!");
return {
success: false,
message: "Version was already uploaded!",
};
}
const bucketBase = "packages/" + packageName + "/" + packageVersion + "/";
@ -93,31 +197,40 @@ async function uploadPackage(ctx: ABC.Context) {
});
for await (const file of walker) {
const relative = Path.relative(folder, file.path);
const relative = Path.relative(folder, file.path).replace("\\", "/");
console.log("Normalised path:", relative.replace("\\", "/"));
const bucketPath = (bucketBase + relative).replace(/@/g, "§");
console.log("Uploading file:", file.path, bucketPath, bucketBase);
await bucket.putObject(
bucketPath,
await Deno.readAll(await Deno.open(file.path)),
{}
);
const body = await Deno.readAll(await Deno.open(file.path));
console.log("Put Object", bucketPath, body.byteLength);
await bucket.putObject(bucketPath, body, {});
}
console.log("Setting new live version");
//TODO: Better option, since this could error whith multiple upload to the same package
await db.update(
//TODO: Better option, since this could error whith multiple uploads to the same package at the same time
await db.package.update(
{ name: packageName },
{
$set: { versions: [...packageMeta.versions, packageVersion] },
$set: {
versions: [...packageMeta.versions, packageVersion],
description: meta.description || packageMeta.description,
deprecated: meta.deprecated === true,
},
}
);
console.log("Finished successfully");
return {
success: true,
};
} catch (err) {
console.error("Error while processing newly uploaded package");
console.error(
Colors.red("Error while processing newly uploaded package")
);
console.error(err);
if (err instanceof S3Error) console.log(err.response);
return {
success: false,
message: err.message,
@ -127,7 +240,4 @@ async function uploadPackage(ctx: ABC.Context) {
await Deno.remove(filename).catch(console.error);
await Deno.remove(folder, { recursive: true }).catch(console.error);
}
return {
success: true,
};
}

View File

@ -0,0 +1,43 @@
import { ABC } from "../deps.ts";
import config from "../config.ts";
export default function raw(g: ABC.Group) {
g.get("/deno-import-intellisense.json", (ctx) => {
return {
version: 1,
registries: [
{
schema: "/raw/:module([@]*[a-z0-9\\-\\_]*)@:version?/:path*",
variables: [
{
key: "module",
url: `${config.web.url}/api/module`,
},
{
key: "version",
url: `${config.web.url}/api/module/\${module}`,
},
{
key: "path",
url: `${config.web.url}/api/module/\${module}/v/\${{version}}`,
},
],
},
{
schema: "/raw/:module([@]*[a-z0-9\\-\\_]*)/:path*",
variables: [
{
key: "module",
url: `${config.web.url}/api/module`,
},
{
key: "path",
url: `${config.web.url}/api/module/\${module}/v/latest`,
},
],
},
],
};
});
}

View File

@ -1,10 +1,15 @@
import { ABC } from "../deps.ts";
import { Path } from "../deps.ts";
import type { ABC } from "../deps.ts";
import {
extractPackagePath,
getAbsolutePackageVersion,
getFile,
getContentType,
} from "../utils.ts";
import db from "../db.ts";
import { sortVersions, extractPackagePath } from "../utils.ts";
import db, { IPackage } from "../db.ts";
import bucket from "../s3.ts";
const MAX_FIXED_CACHE_AGE = 60 * 60 * 24 * 365;
const MAX_FLOATING_CACHE_AGE = 60 * 30;
export default function raw(g: ABC.Group) {
g.get("/:package/*path", async (ctx) => {
@ -13,41 +18,45 @@ export default function raw(g: ABC.Group) {
ctx.params.package
);
const meta = await db.findOne({ name: packageName });
console.log(packageName, await db.findOne({ name: packageName }));
const pkg = await db.package.findOne({ name: packageName });
packageVersion = getAbsolutePackageVersion(pkg, packageVersion);
const E404 = () => {
ctx.response.status = 404;
ctx.response.body = "Not found!";
throw new Error("Not found!");
ctx.response.body = "// Not found!";
};
if (!meta || meta.versions.length < 1) return E404();
const filepath = ctx.params.path;
const result = await getFile(packageName, packageVersion, filepath);
const versions = meta.versions.sort(sortVersions).reverse();
if (!packageVersion) {
packageVersion = versions[0];
} else {
const v = versions.filter((e) =>
e.startsWith(packageVersion as string)
);
if (v.length < 1) return E404();
packageVersion = v[0];
if (filepath.endsWith(".js")) {
const tsFile = filepath.substr(0, filepath.length - 3) + ".d.ts";
const tsResult = await getFile(packageName, packageVersion, tsFile);
if (tsResult) {
ctx.response.headers.set(
"X-TypeScript-Types",
"./" + Path.posix.basename(tsFile)
);
}
}
const bucketPath = (
"packages/" +
packageName +
"/" +
packageVersion +
"/" +
ctx.params.path
).replace(/@/g, "§");
if (packageVersion && result) {
ctx.response.headers.set(
"cache-control",
"public, max-age=" + MAX_FIXED_CACHE_AGE
);
} else {
ctx.response.headers.set(
"cache-control",
"no-cache, max-age=" + MAX_FLOATING_CACHE_AGE
);
}
console.log("Getting file from:", bucketPath);
if (!result) return E404();
return (await bucket.getObject(bucketPath))?.body;
ctx.response.headers.set("e-tag", result.etag);
let contentType = getContentType(filepath);
if (contentType) ctx.response.headers.set("Content-Type", contentType);
return result.data;
});
}

153
registry/src/http/views.ts Normal file
View File

@ -0,0 +1,153 @@
import type { ABC } from "../deps.ts";
import {
basicauth,
extractPackagePath,
getBucketFilePath,
getFile,
getAbsolutePackageVersion,
sortVersions,
} from "../utils.ts";
import { Hash, Path } from "../deps.ts";
import db, { IPackage } from "../db.ts";
import bucket from "../s3.ts";
const MAX_CACHE_AGE = 60 * 30; // 30 Minutes
const CACHE_CONTROL = "public, max-age=" + MAX_CACHE_AGE;
export default function views(g: ABC.Application) {
g.get("/", async (ctx) => {
ctx.response.headers.set("cache-control", CACHE_CONTROL);
const search = ctx.queryParams.q;
let packages: IPackage[] = [];
if (search && search !== "") {
packages = await db.package.find({
name: RegExp(`${search}`),
});
} else {
packages = await db.package.find({});
}
await ctx.render("index", {
packages: packages.reverse(),
search,
});
const etag =
"W/" +
Hash.createHash("sha3-256")
.update(
packages
.map((e) => {
const sorted = e.versions.sort(sortVersions).reverse();
return e.name + sorted[0];
})
.join(":")
)
.toString("base64");
ctx.response.headers.set("cache-control", CACHE_CONTROL);
ctx.response.headers.set("E-Tag", etag);
});
g.get("/package/:package", async (ctx) => {
let [packageName, packageVersion] = extractPackagePath(
ctx.params.package
);
const pkg = await db.package.findOne({ name: packageName });
const etag =
"W/" +
Hash.createHash("sha3-256")
.update(`${packageName}:${packageVersion}`)
.toString("base64");
await ctx.render("package", { pkg, version: packageVersion });
ctx.response.headers.set("cache-control", CACHE_CONTROL);
ctx.response.headers.set("E-Tag", etag);
});
async function handleBrowse(ctx: ABC.Context) {
const E404 = () => {
ctx.response.status = 404;
ctx.response.body = "// Not found!";
};
let [packageName, packageVersion] = extractPackagePath(
ctx.params.package
);
const pkg = await db.package.findOne({ name: packageName });
packageVersion = getAbsolutePackageVersion(pkg, packageVersion);
if (!packageVersion) return E404();
const path = ctx.params.path || "";
const fileContent = await getFile(packageName, packageVersion, path);
if (fileContent) {
await ctx.render("browse_file", {
pkg,
version: packageVersion,
content: new TextDecoder().decode(fileContent.data),
ext: Path.extname(path),
path: `${packageName}@${packageVersion}/${path}`,
});
} else {
const bucketPath = await getBucketFilePath(
packageName,
packageVersion,
path
);
if (!bucketPath) return E404();
console.log(bucketPath);
const filesItr = bucket.listAllObjects({
batchSize: 100,
prefix: bucketPath,
// delimiter: "/",
});
let files: { name: string; size: number }[] = [];
let directories: Set<string> = new Set();
let readme: string | null = null;
for await (let file of filesItr) {
const relPath = Path.posix.relative(bucketPath, file.key || "");
if (relPath.indexOf("/") >= 0) {
directories.add(relPath.split("/")[0]);
} else {
files.push({ name: relPath, size: file.size || -1 });
if (relPath.toLowerCase() === "readme.md") {
let readmeCont = await getFile(
packageName,
packageVersion,
Path.posix.join(path, relPath)
);
if (readmeCont) {
readme = new TextDecoder().decode(readmeCont?.data);
}
}
}
}
await ctx.render("browse_folder", {
pkg,
version: packageVersion,
readme,
files,
directories: Array.from(directories.values()).map((e) => ({
name: e,
})),
path: `${packageName}@${packageVersion}/${path}`,
});
}
}
g.get("/browse/:package", handleBrowse);
g.get("/browse/:package/*path", handleBrowse);
}

77
registry/src/renderer.tsx Normal file
View File

@ -0,0 +1,77 @@
// / <reference path="./types/jsx.d.ts" />
import { Pico } from "./deps.ts";
import config from "./config.ts";
const React = {
createElement: Pico.h.bind(Pico),
};
class StringReader implements Deno.Reader {
private data: Uint8Array;
private offset = 0;
constructor(text: string) {
this.data = new TextEncoder().encode(text);
}
async read(p: Uint8Array): Promise<number | null> {
if (this.offset >= this.data.byteLength) return null;
const forLength = Math.min(p.length, this.data.length - this.offset);
for (let i = 0; i < forLength; i++) {
p[i] = this.data[i + this.offset];
}
this.offset += forLength;
return forLength;
}
}
type ELM = any;
type Component = () => ELM;
const componentCache = new Map<string, Component>();
async function loadComponent(name: string) {
let mod = componentCache.get(name);
if (!mod || config.general.dev) {
mod = (await import(`./views/${name}.tsx`)).default;
if (!mod) throw new Error("Invalid component " + name);
componentCache.set(name, mod);
}
return mod;
}
Promise.resolve().then(async () => {
console.log("[PRECACHE] Start loading pages");
await loadComponent("index");
await loadComponent("package");
await loadComponent("browse_folder");
await loadComponent("browse_file");
console.log("[PRECACHE] Finished loading pages");
});
// import index from "./views/index.tsx";
// componentCache.set("index", index as Component);
// import pkg from "./views/package.tsx";
// componentCache.set("package", pkg as Component);
// import browse_folder from "./views/browse_folder.tsx";
// componentCache.set("browse_folder", browse_folder as Component);
// import browse_file from "./views/browse_file.tsx";
// componentCache.set("browse_file", browse_file as Component);
export default async function render(
name: string,
data: any
): Promise<Deno.Reader> {
const Component = await loadComponent(name);
//@ts-ignore
const res = await Pico.renderSSR(<Component {...data} />);
return new StringReader("<!DOCTYPE html>\n" + res);
}

View File

@ -5,12 +5,26 @@ if (!config.s3) {
throw new Error("Config is missing [s3] section!");
}
const bucket = new S3.S3Bucket({
if (!config.s3.endpoint) {
throw new Error("Config is missing s3.endpoint!");
}
if (!config.s3.accessKey) {
throw new Error("Config is missing s3.accessKey!");
}
if (!config.s3.secretKey) {
throw new Error("Config is missing s3.secretKey!");
}
const s3config: S3.S3BucketConfig = {
bucket: config.s3.bucket || "deno-registry",
endpointURL: config.s3.endpoint,
accessKeyID: config.s3.accessKey,
secretKey: config.s3.secretKey,
region: config?.s3?.region || "us-east-1",
});
};
const bucket = new S3.S3Bucket(s3config);
export default bucket;

3
registry/src/test.ts Normal file
View File

@ -0,0 +1,3 @@
import * as Prism from "./vendor/prism/prism.js";
console.log((window as any).Prism);

4
registry/src/types/hotfix.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
// fixes an issue in std@0.80.0
interface ReadableStream<R> {
getIterator(): any;
}

View File

@ -1,5 +1,4 @@
import { ABC } from "./deps.ts";
import { decode } from "https://deno.land/std/encoding/base64.ts";
import { ABC, Base64, Path } from "./deps.ts";
import config from "./config.ts";
const packageNameRegex = /^[@]?[a-zA-Z][\d\w\-\_]*$/g.compile();
@ -42,11 +41,13 @@ export const basicauth = (realm: string) => (next: ABC.HandlerFunc) => (
if (value && value.toLowerCase().startsWith("basic ")) {
const credentials = value.slice(6);
const [username, passwd] = new TextDecoder()
.decode(decode(credentials))
.decode(Base64.decode(credentials))
.split(":", 2);
if (config?.user[username]?.password === passwd) {
console.log("User authenticated!");
if (!ctx.customContext) ctx.customContext = {};
ctx.customContext.user = username;
return next(ctx);
}
}
@ -62,6 +63,8 @@ export const basicauth = (realm: string) => (next: ABC.HandlerFunc) => (
export function extractPackagePath(path: string): [string, string | undefined] {
let packageName = "";
path = path.toLowerCase();
if (path.startsWith("@")) {
packageName = "@";
path = path.slice(1);
@ -71,7 +74,7 @@ export function extractPackagePath(path: string): [string, string | undefined] {
if (parts.length > 2) throw new Error("Invalid package name!");
packageName += parts[0];
let packageVersion: string | undefined = path[1];
let packageVersion: string | undefined = parts[1];
if (!isValidPackageName(packageName))
throw new Error("Invalid package name!");
@ -79,8 +82,92 @@ export function extractPackagePath(path: string): [string, string | undefined] {
if (packageVersion !== "") {
if (!isValidVersion(packageVersion))
throw new Error("Invalid package version!");
else packageVersion = undefined;
}
return [packageName, packageVersion];
}
import type { IPackage } from "./db.ts";
import bucket from "./s3.ts";
export function getAbsolutePackageVersion(
pkg?: IPackage | null,
version?: string
) {
if (!pkg || pkg.versions.length < 1) return undefined;
const versions = pkg.versions.sort(sortVersions).reverse();
if (!version || version === "latest") {
version = versions[0];
} else {
const v = versions.filter((e) => e.startsWith(version as string));
if (v.length < 1) return undefined;
version = v[0];
}
return version;
}
export async function getBucketFilePath(
pkgName: string,
version: string,
file: string
) {
if (file.startsWith("/")) {
file = file.substr(1);
}
const bucketPath = (
"packages/" +
pkgName +
"/" +
version +
"/" +
file
).replace(/@/g, "§");
return bucketPath;
}
export async function getFile(
pkgName: string,
version: string | undefined,
file: string
): Promise<{ etag: string; data: Uint8Array } | null | undefined> {
if (!version) return undefined;
const bucketPath = await getBucketFilePath(pkgName, version, file);
if (!bucketPath) return null;
console.log("Getting file from:", bucketPath);
try {
const res = await bucket.getObject(bucketPath);
if (!res || res.body.byteLength === 0) return undefined;
return {
etag: res.etag,
data: res.body,
};
} catch (err) {
const msg = err.message as string;
if (msg.indexOf("404") >= 0) return null;
throw err;
}
}
const exts = new Map<string, string>();
exts.set(".js", "text/javascript");
exts.set(".mjs", "text/javascript");
exts.set(".json", "application/json");
exts.set(".jsonld", "application/ld+json");
exts.set(".css", "text/css");
exts.set(".html", "text/html");
exts.set(".htm", "text/html");
export function getContentType(filename: string) {
const ext = Path.extname(filename);
return exts.get(ext);
}

7
registry/src/vendor/prism.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import "./prism/prism.js";
const Prism = (window as any).Prism;
delete (window as any).Prism;
export default Prism;

239
registry/src/vendor/prism/prism.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,116 @@
import { Pico } from "../deps.ts";
import config from "../config.ts";
const styles = new TextDecoder().decode(
Deno.readFileSync("src/views/styles.css")
);
// href="https://unpkg.com/papercss@1.6.1/dist/paper.min.css"
export default function Base(p: any, children: any[]) {
const title = p.title || "DenReg";
return (
<html lang="en">
<head>
<meta charset="UTF-8"></meta>
{/* <link
rel="stylesheet"
href="https://deno.hibas123.de/raw/@hibas123-theme@2.0.2/out/base.css"
/> */}
<link rel="stylesheet" href="/public/paper.min.css" />
<link
rel="apple-touch-icon"
sizes="57x57"
href="/public/apple-icon-57x57.png"
/>
<link
rel="apple-touch-icon"
sizes="60x60"
href="/public/apple-icon-60x60.png"
/>
<link
rel="apple-touch-icon"
sizes="72x72"
href="/public/apple-icon-72x72.png"
/>
<link
rel="apple-touch-icon"
sizes="76x76"
href="/public/apple-icon-76x76.png"
/>
<link
rel="apple-touch-icon"
sizes="114x114"
href="/public/apple-icon-114x114.png"
/>
<link
rel="apple-touch-icon"
sizes="120x120"
href="/public/apple-icon-120x120.png"
/>
<link
rel="apple-touch-icon"
sizes="144x144"
href="/public/apple-icon-144x144.png"
/>
<link
rel="apple-touch-icon"
sizes="152x152"
href="/public/apple-icon-152x152.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/public/apple-icon-180x180.png"
/>
<link
rel="icon"
type="image/png"
sizes="192x192"
href="/public/android-icon-192x192.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/public/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="96x96"
href="/public/favicon-96x96.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/public/favicon-16x16.png"
/>
<link rel="manifest" href="/public/manifest.json" />
<meta name="msapplication-TileColor" content="#ffffff" />
<meta
name="msapplication-TileImage"
content="/public/ms-icon-144x144.png"
/>
<meta name="theme-color" content="#ffffff"></meta>
<link href="/public/prism.css" rel="stylesheet" />
<style innerHTML={styles}></style>
<title>{title}</title>
<meta
name="Description"
content="Deno package registry. Includes version management, deprecation of packages as well as a CDN for distributing deno packages."
/>
<meta
name="viewport"
content="width=device-width,initial-scale=1"
/>
</head>
<body class="site">
{config.web.tracking && (
<tracking innerHTML={config.web.tracking}></tracking>
)}
{children}
</body>
</html>
);
}

View File

@ -0,0 +1,112 @@
import { Pico, Marked } from "../deps.ts";
import type { IPackage } from "../db.ts";
import { sortVersions } from "../utils.ts";
import Prism from "../vendor/prism.ts";
interface IEntryParams {
name: string;
size?: number;
directory?: true;
}
export function Entry({ name, size, directory }: IEntryParams) {
return (
<a class="browse-list-item" href={"./" + name + (directory ? "/" : "")}>
<img src={directory ? "/public/folder.svg" : "/public/file.svg"} />
<span>{name}</span>
{size && <span class="browse-list-item-size">{size}</span>}
</a>
);
}
interface IEntryListParams {
files: { name: string; size: number }[];
directories: { name: string }[];
}
export function EntryList({ directories, files }: IEntryListParams) {
return (
<div class="card" style="padding: 1rem;">
<div>
{directories.map((e) => (
<Entry name={e.name} directory />
))}
{files.map((e) => (
<Entry name={e.name} size={e.size} />
))}
</div>
</div>
);
}
interface IRenderFileInterface {
content: string;
ext: string;
}
const languages: { [key: string]: string } = {
js: "javascript",
cjs: "javascript",
mjs: "javascript",
ts: "typescript",
c: "clike",
svelte: "html",
cs: "csharp",
hb: "handlebars",
ps: "powershell",
sh: "bash",
bat: "batch",
yml: "yaml",
};
export function RenderFile({ content, ext }: IRenderFileInterface) {
if (ext === ".md") {
content = Marked.parse(content).content;
return (
<div
class="card browse-code-block"
style="margin-top: 1rem; padding: 1rem;"
innerHTML={content}
/>
);
} else {
let lang = languages[ext.replace(".", "")] || ext.replace(".", "");
if (Prism.languages[lang]) {
content = Prism.highlight(content, Prism.languages[lang], lang);
}
return <pre innerHTML={content} />;
}
}
interface IBrowseHeaderParams {
pkg: IPackage;
version?: string;
path: string;
}
export function BrowseHeader({ pkg, version, path }: IBrowseHeaderParams) {
return (
<>
<div style="display: flex;">
<div>
<h2 style="margin-bottom: 0">Browse: {pkg.name}</h2>
<h4 class="text-muted" style="margin-top: 0; margin-left: .5rem">
By {pkg.owner}
</h4>
</div>
<div>
Version:
<select>
{pkg.versions.sort(sortVersions).map((v) => (
<option selected={version === v}>{v}</option>
))}
</select>
</div>
</div>
<div class="browse-path">{path}</div>
</>
);
}

View File

@ -0,0 +1,29 @@
import { Pico } from "../deps.ts";
export function Main(a: any, children: any) {
return (
<div style="grid-area: main">
<div class="paper">{children}</div>
</div>
);
}
export function Menu({}: any, children: any) {
return (
<div style="grid-area: menu">
<div class="paper">
{/* <div class="row flex-right">
<button class="sm-4">Login</button>
<button class="sm-4">SignUp</button>
</div> */}
<h3 class="sidebar-title" style="text-align:center">
<a href="/" style="all:inherit;">
DenReg
</a>
</h3>
{children}
</div>
</div>
);
}

View File

@ -0,0 +1,20 @@
// /// <reference path="../types/jsx.d.ts" />
import { Pico, Marked } from "../deps.ts";
import type { IPackage } from "../db.ts";
export default async function index({
pkg,
version,
}: {
pkg: IPackage;
version?: string;
}) {
return (
<>
<h2 style="margin-bottom: 0">Package: {pkg.name}</h2>
<h4 class="text-muted" style="margin-top: 0; margin-left: .5rem">
By {pkg.owner}
</h4>
</>
);
}

View File

@ -0,0 +1,37 @@
import { Pico } from "../deps.ts";
import Base from "./_base.tsx";
import type { IPackage } from "../db.ts";
import { Main, Menu } from "./_default.tsx";
import { RenderFile, EntryList, BrowseHeader } from "./_browse.tsx";
export default async function index({
pkg,
version,
content,
ext,
path,
}: {
pkg: IPackage;
version?: string;
content: string;
ext: string;
path: string;
}) {
if (!pkg)
return (
<Base>
<h1>Not found</h1>
</Base>
);
return (
<Base title={"DenReg - " + pkg.name}>
<Main>
<BrowseHeader pkg={pkg} version={version} path={path} />
<RenderFile content={content} ext={ext} />
</Main>
<Menu></Menu>
</Base>
);
}

View File

@ -0,0 +1,42 @@
import { Pico } from "../deps.ts";
import Base from "./_base.tsx";
import type { IPackage } from "../db.ts";
import { Main, Menu } from "./_default.tsx";
import { RenderFile, EntryList, BrowseHeader } from "./_browse.tsx";
export default async function index({
pkg,
version,
files,
directories,
readme,
path,
}: {
pkg: IPackage;
version?: string;
files: { name: string; size: number }[];
directories: { name: string }[];
readme?: string;
path: string;
}) {
if (!pkg)
return (
<Base>
<h1>Not found</h1>
</Base>
);
return (
<Base title={"DenReg - " + pkg.name}>
<Main>
<BrowseHeader pkg={pkg} version={version} path={path} />
<EntryList directories={directories} files={files} />
{readme && <RenderFile content={readme} ext={".md"} />}
</Main>
<Menu></Menu>
</Base>
);
}

View File

@ -0,0 +1,68 @@
import { Pico } from "../deps.ts";
import Base from "./_base.tsx";
import type { IPackage } from "../db.ts";
import { sortVersions } from "../utils.ts";
function Package({ pkg }: { pkg: IPackage }) {
const { name, versions, owner, description, deprecated } = pkg;
const sorted = versions.sort(sortVersions).reverse();
return (
<div
class="card package-list-margin"
onClick={"window.location.href = '/package/" + name + "'"}
>
<div class="card-body">
<h4 class="card-title">
{name} <span class="badge">{sorted[0]}</span>
{deprecated && <span class="badge warning">deprecated</span>}
</h4>
<h5 class="card-subtitle">By {owner}</h5>
<div class="card-text">
{/* {versions.map((version) => (
<li>{version}</li>
))} */}
{description}
</div>
</div>
</div>
);
}
import { Main, Menu } from "./_default.tsx";
export default async function index({
packages,
search,
}: {
packages: IPackage[];
search: string;
}) {
return (
<Base>
<Main>
<form method="GET" action="./">
<div class="form-group package-list-margin">
{/* <label for="searchInput">Search</label> */}
<div style="display:flex">
<input
placeholder="Search..."
class="input-block"
type="text"
id="searchInput"
name="q"
value={search}
/>
<button>Search</button>
</div>
</div>
</form>
{packages.map((pkg) => (
<Package pkg={pkg} />
))}
</Main>
<Menu></Menu>
</Base>
);
}

View File

@ -0,0 +1,97 @@
import { Pico, Marked } from "../deps.ts";
import Base from "./_base.tsx";
import type { IPackage } from "../db.ts";
import { sortVersions, getFile, getAbsolutePackageVersion } from "../utils.ts";
import PkgHeader from "./_pkgheader.tsx";
// function Package({ pkg }: { pkg: IPackage }) {
// const { name, versions, author } = pkg;
// const sorted = versions.sort(sortVersions);
// return (
// <div
// class="card margin"
// onClick={"window.location.href = '/package/" + name + "'"}
// >
// <div class="card-body">
// <h4 class="card-title">{name}</h4>
// <ul class="card-text">
// {versions.map((version) => (
// <li>{version}</li>
// ))}
// {author} {sorted[0]}
// </ul>
// </div>
// </div>
// );
// }
import { Main, Menu } from "./_default.tsx";
export default async function index({
pkg,
version,
}: {
pkg: IPackage;
version?: string;
}) {
if (!pkg)
return (
<Base>
<h1>Not found</h1>
</Base>
);
version = getAbsolutePackageVersion(pkg, version);
const readmeContent = await getFile(
pkg.name,
version,
pkg.readme || "README.md"
).then((res) => {
if (res)
return Marked.parse(new TextDecoder().decode(res.data))
.content as string;
else return undefined;
});
return (
<Base title={"DenReg - " + pkg.name}>
<Main>
<PkgHeader pkg={pkg} version={version} />
<div class="tabs">
<input id="tab1" type="radio" name="tabs" checked />
<label for="tab1">Readme</label>
<input id="tab2" type="radio" name="tabs" />
<label for="tab2">Versions</label>
<div class="content" id="content1">
{readmeContent !== undefined ? (
<div
style="overflow-x: hidden"
innerHTML={readmeContent}
/>
) : (
<div class="alert alert-warning">No README.md found!</div>
)}
</div>
<div class="content" id="content2">
<ul>
{pkg.versions.sort(sortVersions).map((version) => (
<li>
<a href={`./${pkg.name}@${version}`}>{version}</a>
</li>
))}
</ul>
</div>
</div>
</Main>
<Menu>
<a href={`/browse/${pkg.name}${version ? "@" + version : ""}/`}>
Browse Files
</a>
</Menu>
</Base>
);
}

View File

@ -0,0 +1,60 @@
.site {
display: grid;
grid-template-areas: "main menu";
grid-template-columns: 2fr 1fr;
gap: 1rem;
padding-left: 1rem;
padding-right: 1rem;
}
.package-list-margin {
margin: 1rem;
}
code {
overflow-x: auto;
}
@media only screen and (max-width: 64rem) {
.site {
grid-template-columns: 1fr;
grid-template-areas:
"menu"
"main";
}
.package-list-margin {
margin: 1rem 0;
}
}
.browse-list-item {
display: flex;
margin: 0.2rem;
text-decoration: none;
color: unset;
background-image: unset;
/* flex */
}
.browse-list-item:hover {
background-color: var(--muted-light);
}
.browse-list-item > img {
height: 1rem;
width: auto;
border: none !important;
margin-right: 1rem;
}
.browse-list-item-size {
margin-left: auto;
}
.browse-path {
margin: 1rem;
font-size: 1.2rem;
font-weight: bold;
}

10
registry/tsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext", "deno.ns", "deno.unstable"],
"jsx": "react",
"jsxFactory": "Pico.h",
"jsxFragmentFactory": "Pico.Fragment",
"noImplicitAny": true,
"strictPropertyInitialization": false
}
}

11
tar/README.md Normal file
View File

@ -0,0 +1,11 @@
Helper functions for packing and unpacking tarfiles.
## Usage
```typescript
import { Tar } from "https://deno.hibas123.de/raw/@denreg-tar";
// The first parameter can be a file or a folder
await Tar.compress("test.txt", "test.tar");
await Tar.decompress("test.tar", ".");
```

View File

@ -1,12 +1,8 @@
{
"name": "@denreg-tar",
"version": "0.0.1",
"version": "0.2.2",
"description": "Pack and Unpack tar files",
"author": "Fabian Stamm <dev@fabianstamm.de>",
"contributors": [],
"files": [
"**/*.ts",
"**/*.js",
"importmap.json"
]
}
"files": ["**/*.ts", "**/*.js", "README.md"]
}

View File

@ -1,6 +1,6 @@
export * as Tar from "https://deno.land/std@0.62.0/archive/tar.ts";
export * as Path from "https://deno.land/std@0.62.0/path/mod.ts";
export * as FS from "https://deno.land/std@0.62.0/fs/mod.ts";
export * as IO_Readers from "https://deno.land/std@0.62.0/io/readers.ts";
export * as IO_BufIO from "https://deno.land/std@0.62.0/io/bufio.ts";
export * as Assert from "https://deno.land/std@0.62.0/_util/assert.ts";
export * as Tar from "https://deno.land/std@0.63.0/archive/tar.ts";
export * as Path from "https://deno.land/std@0.63.0/path/mod.ts";
export * as FS from "https://deno.land/std@0.63.0/fs/mod.ts";
export * as IO_Readers from "https://deno.land/std@0.63.0/io/readers.ts";
export * as IO_BufIO from "https://deno.land/std@0.63.0/io/bufio.ts";
export * as Assert from "https://deno.land/std@0.63.0/_util/assert.ts";

Some files were not shown because too many files have changed in this diff Show More