Further iteration
This commit is contained in:
parent
cbc00b73a0
commit
75da8e7ca9
@ -10,6 +10,7 @@ import { path } from "../common/deps.ts";
|
||||
|
||||
Deno.test("Test plug run", async () => {
|
||||
// const tempDir = await Deno.makeTempDir();
|
||||
const tempDbFile = await Deno.makeTempFile({ suffix: ".db" });
|
||||
|
||||
const assetBundle = new AssetBundle(assets);
|
||||
|
||||
@ -26,6 +27,7 @@ Deno.test("Test plug run", async () => {
|
||||
assertEquals(
|
||||
await runPlug(
|
||||
testSpaceFolder,
|
||||
tempDbFile,
|
||||
"test.run",
|
||||
[],
|
||||
assetBundle,
|
||||
@ -35,4 +37,5 @@ Deno.test("Test plug run", async () => {
|
||||
|
||||
// await Deno.remove(tempDir, { recursive: true });
|
||||
esbuild.stop();
|
||||
await Deno.remove(tempDbFile);
|
||||
});
|
||||
|
@ -9,16 +9,13 @@ import { AssetBundlePlugSpacePrimitives } from "../common/spaces/asset_bundle_sp
|
||||
|
||||
export async function runPlug(
|
||||
spacePath: string,
|
||||
dbPath: string,
|
||||
functionName: string | undefined,
|
||||
args: string[] = [],
|
||||
builtinAssetBundle: AssetBundle,
|
||||
indexFirst = false,
|
||||
httpServerPort = 3123,
|
||||
httpHostname = "127.0.0.1",
|
||||
) {
|
||||
spacePath = path.resolve(spacePath);
|
||||
const tempFile = Deno.makeTempFileSync({ suffix: ".db" });
|
||||
console.log("Tempt db file", tempFile);
|
||||
const serverController = new AbortController();
|
||||
const app = new Application();
|
||||
|
||||
@ -27,7 +24,7 @@ export async function runPlug(
|
||||
new DiskSpacePrimitives(spacePath),
|
||||
builtinAssetBundle,
|
||||
),
|
||||
tempFile,
|
||||
dbPath,
|
||||
app,
|
||||
);
|
||||
await serverSystem.init();
|
||||
@ -37,13 +34,6 @@ export async function runPlug(
|
||||
signal: serverController.signal,
|
||||
});
|
||||
|
||||
if (indexFirst) {
|
||||
await serverSystem.system.loadedPlugs.get("index")!.invoke(
|
||||
"reindexSpace",
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
if (functionName) {
|
||||
const [plugName, funcName] = functionName.split(".");
|
||||
|
||||
@ -54,7 +44,6 @@ export async function runPlug(
|
||||
const result = await plug.invoke(funcName, args);
|
||||
await serverSystem.close();
|
||||
serverSystem.denoKv.close();
|
||||
await Deno.remove(tempFile);
|
||||
serverController.abort();
|
||||
return result;
|
||||
} else {
|
||||
|
1
cmd/constants.ts
Normal file
1
cmd/constants.ts
Normal file
@ -0,0 +1 @@
|
||||
export const silverBulletDbFile = ".silverbullet.db";
|
@ -4,14 +4,15 @@ import assets from "../dist/plug_asset_bundle.json" assert {
|
||||
type: "json",
|
||||
};
|
||||
import { AssetBundle } from "../plugos/asset_bundle/bundle.ts";
|
||||
import { silverBulletDbFile } from "./constants.ts";
|
||||
|
||||
export async function plugRunCommand(
|
||||
{
|
||||
noIndex,
|
||||
db,
|
||||
hostname,
|
||||
port,
|
||||
}: {
|
||||
noIndex: boolean;
|
||||
db?: string;
|
||||
hostname?: string;
|
||||
port?: number;
|
||||
},
|
||||
@ -21,14 +22,18 @@ export async function plugRunCommand(
|
||||
) {
|
||||
spacePath = path.resolve(spacePath);
|
||||
console.log("Space path", spacePath);
|
||||
let dbPath = path.resolve(spacePath, silverBulletDbFile);
|
||||
if (db) {
|
||||
dbPath = path.resolve(db);
|
||||
}
|
||||
console.log("Function to run:", functionName, "with arguments", args);
|
||||
try {
|
||||
const result = await runPlug(
|
||||
spacePath,
|
||||
dbPath,
|
||||
functionName,
|
||||
args,
|
||||
new AssetBundle(assets),
|
||||
!noIndex,
|
||||
port,
|
||||
hostname,
|
||||
);
|
||||
|
@ -17,6 +17,7 @@ import { ServerSystem } from "../server/server_system.ts";
|
||||
import { sleep } from "$sb/lib/async.ts";
|
||||
import { SilverBulletHooks } from "../common/manifest.ts";
|
||||
import { System } from "../plugos/system.ts";
|
||||
import { silverBulletDbFile } from "./constants.ts";
|
||||
|
||||
export async function serveCommand(
|
||||
options: {
|
||||
@ -28,7 +29,7 @@ export async function serveCommand(
|
||||
key?: string;
|
||||
reindex?: boolean;
|
||||
db?: string;
|
||||
serverProcessing?: boolean;
|
||||
storeOnly?: boolean;
|
||||
},
|
||||
folder?: string,
|
||||
) {
|
||||
@ -36,11 +37,13 @@ export async function serveCommand(
|
||||
"127.0.0.1";
|
||||
const port = options.port ||
|
||||
(Deno.env.get("SB_PORT") && +Deno.env.get("SB_PORT")!) || 3000;
|
||||
|
||||
let dbFile = options.db || Deno.env.get("SB_DB_FILE") || ".silverbullet.db";
|
||||
const storeOnly = options.storeOnly || Deno.env.get("SB_STORE_ONLY");
|
||||
let dbFile = options.db || Deno.env.get("SB_DB_FILE") || silverBulletDbFile;
|
||||
|
||||
const app = new Application();
|
||||
|
||||
console.log(options);
|
||||
|
||||
if (!folder) {
|
||||
folder = Deno.env.get("SB_FOLDER");
|
||||
if (!folder) {
|
||||
@ -84,7 +87,8 @@ To allow outside connections, pass -L 0.0.0.0 as a flag, and put a TLS terminato
|
||||
);
|
||||
|
||||
let system: System<SilverBulletHooks> | undefined;
|
||||
if (options.serverProcessing) {
|
||||
// system = undefined in storeOnly mode (no PlugOS instance on the server)
|
||||
if (!storeOnly) {
|
||||
// Enable server-side processing
|
||||
dbFile = path.resolve(folder, dbFile);
|
||||
console.log(
|
||||
|
@ -1,97 +0,0 @@
|
||||
import type {
|
||||
ProxyFetchRequest,
|
||||
ProxyFetchResponse,
|
||||
} from "../../common/proxy_fetch.ts";
|
||||
import {
|
||||
base64Decode,
|
||||
base64Encode,
|
||||
} from "../../plugos/asset_bundle/base64.ts";
|
||||
|
||||
async function readStream(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
): Promise<Uint8Array> {
|
||||
const arrays: Uint8Array[] = [];
|
||||
let totalRead = 0;
|
||||
const reader = stream.getReader();
|
||||
while (true) {
|
||||
// The `read()` method returns a promise that
|
||||
// resolves when a value has been received.
|
||||
const { done, value } = await reader.read();
|
||||
// Result objects contain two properties:
|
||||
// `done` - `true` if the stream has already given you all its data.
|
||||
// `value` - Some data. Always `undefined` when `done` is `true`.
|
||||
if (done) {
|
||||
const resultArray = new Uint8Array(totalRead);
|
||||
let offset = 0;
|
||||
for (const array of arrays) {
|
||||
resultArray.set(array, offset);
|
||||
offset += array.length;
|
||||
}
|
||||
return resultArray;
|
||||
}
|
||||
arrays.push(value);
|
||||
totalRead += value.length;
|
||||
}
|
||||
}
|
||||
|
||||
export async function sandboxFetch(
|
||||
reqInfo: RequestInfo,
|
||||
options?: ProxyFetchRequest,
|
||||
): Promise<ProxyFetchResponse> {
|
||||
if (typeof reqInfo !== "string") {
|
||||
const body = new Uint8Array(await reqInfo.arrayBuffer());
|
||||
const encodedBody = body.length > 0 ? base64Encode(body) : undefined;
|
||||
options = {
|
||||
method: reqInfo.method,
|
||||
headers: Object.fromEntries(reqInfo.headers.entries()),
|
||||
base64Body: encodedBody,
|
||||
};
|
||||
reqInfo = reqInfo.url;
|
||||
}
|
||||
// @ts-ignore: monkey patching fetch
|
||||
return syscall("sandboxFetch.fetch", reqInfo, options);
|
||||
}
|
||||
|
||||
async function bodyInitToUint8Array(init: BodyInit): Promise<Uint8Array> {
|
||||
if (init instanceof Blob) {
|
||||
const buffer = await init.arrayBuffer();
|
||||
return new Uint8Array(buffer);
|
||||
} else if (init instanceof ArrayBuffer) {
|
||||
return new Uint8Array(init);
|
||||
} else if (init instanceof ReadableStream) {
|
||||
return readStream(init);
|
||||
} else if (typeof init === "string") {
|
||||
return new TextEncoder().encode(init);
|
||||
} else {
|
||||
throw new Error("Unknown body init type");
|
||||
}
|
||||
}
|
||||
|
||||
export function monkeyPatchFetch() {
|
||||
// @ts-ignore: monkey patching fetch
|
||||
globalThis.nativeFetch = globalThis.fetch;
|
||||
// @ts-ignore: monkey patching fetch
|
||||
globalThis.fetch = async function (
|
||||
reqInfo: RequestInfo,
|
||||
init?: RequestInit,
|
||||
): Promise<Response> {
|
||||
const encodedBody = init && init.body
|
||||
? base64Encode(
|
||||
new Uint8Array(await (new Response(init.body)).arrayBuffer()),
|
||||
)
|
||||
: undefined;
|
||||
// console.log("Encoded this body", encodedBody);
|
||||
const r = await sandboxFetch(
|
||||
reqInfo,
|
||||
init && {
|
||||
method: init.method,
|
||||
headers: init.headers as Record<string, string>,
|
||||
base64Body: encodedBody,
|
||||
},
|
||||
);
|
||||
return new Response(r.base64Body ? base64Decode(r.base64Body) : null, {
|
||||
status: r.status,
|
||||
headers: r.headers,
|
||||
});
|
||||
};
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
// This is the runtime imported from the compiled plug worker code
|
||||
|
||||
import type { ControllerMessage, WorkerMessage } from "./protocol.ts";
|
||||
import type { Manifest } from "../common/manifest.ts";
|
||||
import type {
|
||||
ProxyFetchRequest,
|
||||
ProxyFetchResponse,
|
||||
} from "../common/proxy_fetch.ts";
|
||||
|
||||
declare global {
|
||||
function syscall(name: string, ...args: any[]): Promise<any>;
|
||||
@ -53,7 +55,7 @@ self.syscall = async (name: string, ...args: any[]) => {
|
||||
export function setupMessageListener(
|
||||
// deno-lint-ignore ban-types
|
||||
functionMapping: Record<string, Function>,
|
||||
manifest: Manifest,
|
||||
manifest: any,
|
||||
) {
|
||||
self.addEventListener("message", (event: { data: WorkerMessage }) => {
|
||||
(async () => {
|
||||
@ -81,7 +83,6 @@ export function setupMessageListener(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "sysr":
|
||||
{
|
||||
@ -109,6 +110,72 @@ export function setupMessageListener(
|
||||
});
|
||||
}
|
||||
|
||||
export function base64Decode(s: string): Uint8Array {
|
||||
const binString = atob(s);
|
||||
const len = binString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binString.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
export function base64Encode(buffer: Uint8Array | string): string {
|
||||
if (typeof buffer === "string") {
|
||||
buffer = new TextEncoder().encode(buffer);
|
||||
}
|
||||
let binary = "";
|
||||
const len = buffer.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(buffer[i]);
|
||||
}
|
||||
return btoa(binary);
|
||||
}
|
||||
|
||||
export async function sandboxFetch(
|
||||
reqInfo: RequestInfo,
|
||||
options?: ProxyFetchRequest,
|
||||
): Promise<ProxyFetchResponse> {
|
||||
if (typeof reqInfo !== "string") {
|
||||
const body = new Uint8Array(await reqInfo.arrayBuffer());
|
||||
const encodedBody = body.length > 0 ? base64Encode(body) : undefined;
|
||||
options = {
|
||||
method: reqInfo.method,
|
||||
headers: Object.fromEntries(reqInfo.headers.entries()),
|
||||
base64Body: encodedBody,
|
||||
};
|
||||
reqInfo = reqInfo.url;
|
||||
}
|
||||
return syscall("sandboxFetch.fetch", reqInfo, options);
|
||||
}
|
||||
|
||||
// Monkey patch fetch()
|
||||
import { monkeyPatchFetch } from "../plug-api/plugos-syscall/fetch.ts";
|
||||
|
||||
export function monkeyPatchFetch() {
|
||||
globalThis.nativeFetch = globalThis.fetch;
|
||||
// @ts-ignore: monkey patching fetch
|
||||
globalThis.fetch = async function (
|
||||
reqInfo: RequestInfo,
|
||||
init?: RequestInit,
|
||||
): Promise<Response> {
|
||||
const encodedBody = init && init.body
|
||||
? base64Encode(
|
||||
new Uint8Array(await (new Response(init.body)).arrayBuffer()),
|
||||
)
|
||||
: undefined;
|
||||
const r = await sandboxFetch(
|
||||
reqInfo,
|
||||
init && {
|
||||
method: init.method,
|
||||
headers: init.headers as Record<string, string>,
|
||||
base64Body: encodedBody,
|
||||
},
|
||||
);
|
||||
return new Response(r.base64Body ? base64Decode(r.base64Body) : null, {
|
||||
status: r.status,
|
||||
headers: r.headers,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
monkeyPatchFetch();
|
||||
|
@ -104,7 +104,6 @@ functions:
|
||||
command:
|
||||
name: "Text: Strikethrough"
|
||||
key: "Ctrl-Shift-s"
|
||||
mac: "Cmd-Shift-s"
|
||||
wrapper: "~~"
|
||||
marker:
|
||||
path: ./text.ts:wrapSelection
|
||||
|
@ -45,9 +45,7 @@ export async function renamePageCommand(cmdDef: any) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
const updatedReferences = await renamePage(oldName, newName);
|
||||
console.log("Navigating to new page");
|
||||
await editor.navigate(newName, 0, true);
|
||||
const updatedReferences = await renamePage(oldName, newName, true);
|
||||
|
||||
await editor.flashNotification(
|
||||
`Renamed page, and updated ${updatedReferences} references`,
|
||||
@ -57,12 +55,21 @@ export async function renamePageCommand(cmdDef: any) {
|
||||
}
|
||||
}
|
||||
|
||||
async function renamePage(oldName: string, newName: string): Promise<number> {
|
||||
async function renamePage(
|
||||
oldName: string,
|
||||
newName: string,
|
||||
navigateThere = false,
|
||||
): Promise<number> {
|
||||
const text = await space.readPage(oldName);
|
||||
|
||||
console.log("Writing new page to space");
|
||||
const newPageMeta = await space.writePage(newName, text);
|
||||
|
||||
if (navigateThere) {
|
||||
console.log("Navigating to new page");
|
||||
await editor.navigate(newName, 0, true);
|
||||
}
|
||||
|
||||
const pagesToUpdate = await getBackLinks(oldName);
|
||||
console.log("All pages containing backlinks", pagesToUpdate);
|
||||
|
||||
|
@ -70,8 +70,8 @@ export class HttpServer {
|
||||
this.options.pagesPath.replaceAll("\\", "\\\\"),
|
||||
// );
|
||||
).replaceAll(
|
||||
"{{SUPPORT_ONLINE_MODE}}",
|
||||
this.system ? "true" : "false",
|
||||
"{{SYNC_ONLY}}",
|
||||
this.system ? "false" : "true",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -45,11 +45,11 @@ await new Command()
|
||||
"Path to TLS key",
|
||||
)
|
||||
.option(
|
||||
"--no-server-processing [type:boolean]",
|
||||
"Disable online mode (no server-side processing)",
|
||||
"--store-only",
|
||||
"Run the server as a pure space (file) store only without any backend processing (this disables 'server mode' in the client)",
|
||||
)
|
||||
.option(
|
||||
"--reindex [type:boolean]",
|
||||
"--reindex",
|
||||
"Reindex space on startup",
|
||||
)
|
||||
.option(
|
||||
@ -60,8 +60,8 @@ await new Command()
|
||||
// plug:compile
|
||||
.command("plug:compile", "Bundle (compile) one or more plug manifests")
|
||||
.arguments("<...name.plug.yaml:string>")
|
||||
.option("--debug [type:boolean]", "Do not minifiy code", { default: false })
|
||||
.option("--info [type:boolean]", "Print out size info per function", {
|
||||
.option("--debug", "Do not minifiy code", { default: false })
|
||||
.option("--info", "Print out size info per function", {
|
||||
default: false,
|
||||
})
|
||||
.option("--watch, -w [type:boolean]", "Watch for changes and rebuild", {
|
||||
@ -78,14 +78,15 @@ await new Command()
|
||||
// plug:run
|
||||
.command("plug:run", "Run a PlugOS function from the CLI")
|
||||
.arguments("<spacePath> [function] [...args:string]")
|
||||
.option("--noIndex [type:boolean]", "Do not run a full space index first", {
|
||||
default: false,
|
||||
})
|
||||
.option(
|
||||
"--hostname, -L <hostname:string>",
|
||||
"Hostname or address to listen on",
|
||||
)
|
||||
.option("-p, --port <port:number>", "Port to listen on")
|
||||
.option(
|
||||
"--db <db:string>",
|
||||
"Path to database file",
|
||||
)
|
||||
.action(plugRunCommand)
|
||||
.command("user:add", "Add a new user to an authentication file")
|
||||
.arguments("[username:string]")
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { safeRun } from "../common/util.ts";
|
||||
import { Client } from "./client.ts";
|
||||
|
||||
const syncMode = window.silverBulletConfig.supportOnlineMode !== "true" ||
|
||||
const syncMode = window.silverBulletConfig.syncOnly ||
|
||||
!!localStorage.getItem("syncMode");
|
||||
|
||||
safeRun(async () => {
|
||||
|
@ -52,7 +52,7 @@ declare global {
|
||||
// Injected via index.html
|
||||
silverBulletConfig: {
|
||||
spaceFolderPath: string;
|
||||
supportOnlineMode: string;
|
||||
syncOnly: boolean;
|
||||
};
|
||||
client: Client;
|
||||
}
|
||||
|
@ -46,8 +46,6 @@ export function TopBar({
|
||||
lhs?: ComponentChildren;
|
||||
rhs?: ComponentChildren;
|
||||
}) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Another one of my less proud moments:
|
||||
// Somehow I cannot seem to proerply limit the width of the page name, so I'm doing
|
||||
// it this way. If you have a better way to do this, please let me know!
|
||||
@ -66,7 +64,7 @@ export function TopBar({
|
||||
|
||||
// Then calculate a new width
|
||||
currentPageElement.style.width = `${
|
||||
Math.min(editorWidth - 150, innerDiv.clientWidth - 150)
|
||||
Math.min(editorWidth - 170, innerDiv.clientWidth - 170)
|
||||
}px`;
|
||||
}
|
||||
}
|
||||
|
@ -204,7 +204,8 @@ export class MainUI {
|
||||
editor.focus();
|
||||
}}
|
||||
actionButtons={[
|
||||
...window.silverBulletConfig.supportOnlineMode === "true"
|
||||
...!window.silverBulletConfig.syncOnly
|
||||
// If we support syncOnly, don't show this toggle button
|
||||
? [{
|
||||
icon: RefreshCwIcon,
|
||||
description: this.editor.syncMode
|
||||
|
@ -35,13 +35,13 @@
|
||||
window.silverBulletConfig = {
|
||||
// These {{VARIABLES}} are replaced by http_server.ts
|
||||
spaceFolderPath: "{{SPACE_PATH}}",
|
||||
supportOnlineMode: "{{SUPPORT_ONLINE_MODE}}",
|
||||
syncOnly: "{{SYNC_ONLY}}" === "true",
|
||||
};
|
||||
// But in case these variables aren't replaced by the server, fall back fully static mode (no sync)
|
||||
// But in case these variables aren't replaced by the server, fall back sync only mode
|
||||
if (window.silverBulletConfig.spaceFolderPath.includes("{{")) {
|
||||
window.silverBulletConfig = {
|
||||
spaceFolderPath: "",
|
||||
supportOnlineMode: false,
|
||||
syncOnly: true,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
@ -121,7 +121,9 @@ body {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: -6px;
|
||||
padding-right: 6px;
|
||||
padding: 4px;
|
||||
background-color: var(--top-background-color);
|
||||
margin-right: -2px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
|
@ -130,6 +130,7 @@ You can configure SB with environment variables instead of flags as well. The fo
|
||||
* `SB_PORT`: Sets the port to listen to, e.g. `SB_PORT=1234`
|
||||
* `SB_FOLDER`: Sets the folder to expose, e.g. `SB_FOLDER=/space`
|
||||
* `SB_AUTH`: Loads an [[Authentication]] database from a (JSON encoded) string, e.g. `SB_AUTH=$(cat /path/to/.auth.json)`
|
||||
* `SB_STORE_ONLY`: Runs the server in a "dumb" space store only mode (not indexing content or keeping other state), e.g. `SB_STORE_ONLY=1`
|
||||
|
||||
## Using Authelia
|
||||
You need to adjust a few configuration options in [[Authelia]] in order for SilverBullet to work as intended.
|
||||
|
Loading…
Reference in New Issue
Block a user