Frontend uudelleenrakennettu: Astro-komponentit, Wasm pääsäikeessä, ei Workeria

Vanha frontend siirretty temp/. Uusi rakenne:
- StatusBar.astro, Terminal.astro, Editor.astro, Guide.astro
- global.css erillinen
- Wasm pääsäikeessä (ei Worker — yksinkertainen, debugattava)
- Tab-completion, dropdown, projektikortti, Monaco, GUIDE.md
- Ei tokenisointia eikä koodilaboratoriota

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaakko Vanhala
2026-04-09 20:17:39 +03:00
parent e3fdb91ac5
commit a8c4af0975
9617 changed files with 996171 additions and 5349 deletions

View File

@@ -0,0 +1,119 @@
import { del, head, list, put } from "@vercel/blob";
import { defineDriver, normalizeKey, joinKeys, createError } from "./utils/index.mjs";
const DRIVER_NAME = "vercel-blob";
export default defineDriver((opts) => {
const optsBase = normalizeKey(opts?.base);
const r = (...keys) => joinKeys(optsBase, ...keys).replace(/:/g, "/");
const envName = `${opts.envPrefix || "BLOB"}_READ_WRITE_TOKEN`;
const getToken = () => {
if (opts.access !== "public") {
throw createError(DRIVER_NAME, `You must set { access: "public" }`);
}
const token = opts.token || globalThis.process?.env?.[envName];
if (!token) {
throw createError(
DRIVER_NAME,
`Missing token. Set ${envName} env or token config.`
);
}
return token;
};
const get = async (key) => {
const { blobs } = await list({
token: getToken(),
prefix: r(key)
});
const blob = blobs.find((item) => item.pathname === r(key));
return blob;
};
return {
name: DRIVER_NAME,
options: opts,
async hasItem(key) {
const blob = await get(key);
return !!blob;
},
async getItem(key) {
const blob = await get(key);
return blob ? fetch(blob.url).then((res) => res.text()) : null;
},
async getItemRaw(key) {
const blob = await get(key);
return blob ? fetch(blob.url).then((res) => res.arrayBuffer()) : null;
},
async getMeta(key) {
const blob = await get(key);
if (!blob) return null;
const blobHead = await head(blob.url, {
token: getToken()
});
if (!blobHead) return null;
return {
mtime: blobHead.uploadedAt,
...blobHead
};
},
async setItem(key, value, opts2) {
await put(r(key), value, {
access: "public",
addRandomSuffix: false,
token: getToken(),
...opts2
});
},
async setItemRaw(key, value, opts2) {
await put(r(key), value, {
access: "public",
addRandomSuffix: false,
token: getToken(),
...opts2
});
},
async removeItem(key) {
const blob = await get(key);
if (blob) await del(blob.url, { token: getToken() });
},
async getKeys(base) {
const blobs = [];
let cursor = void 0;
do {
const listBlobResult = await list({
token: getToken(),
cursor,
prefix: r(base)
});
cursor = listBlobResult.cursor;
for (const blob of listBlobResult.blobs) {
blobs.push(blob);
}
} while (cursor);
return blobs.map(
(blob) => blob.pathname.replace(
new RegExp(`^${optsBase.replace(/:/g, "/")}/`),
""
)
);
},
async clear(base) {
let cursor = void 0;
const blobs = [];
do {
const listBlobResult = await list({
token: getToken(),
cursor,
prefix: r(base)
});
blobs.push(...listBlobResult.blobs);
cursor = listBlobResult.cursor;
} while (cursor);
if (blobs.length > 0) {
await del(
blobs.map((blob) => blob.url),
{
token: getToken()
}
);
}
}
};
});