Compare commits

..
5 Commits
Author SHA1 Message Date
polv 9f88b6d93c remove csp and assetProtocol, escape problem in production that don't appear in dev 2026-03-22 04:21:37 +07:00
polv a26cfceaf4 fix css preserve color images 2026-03-22 02:00:52 +07:00
polv 4b6101b019 quick fix worker auth 2026-03-22 00:53:11 +07:00
polv 904cdd0413 check images and dark mode 2026-03-22 00:41:56 +07:00
polv 978ebfa75f cloudflare worker for zip storage 2026-03-21 22:11:52 +07:00
27 changed files with 13729 additions and 85 deletions

No files matched your search

Generated
-7
View File
@@ -1981,12 +1981,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "http-range"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]]
name = "httparse"
version = "1.10.1"
@@ -4843,7 +4837,6 @@ dependencies = [
"gtk",
"heck 0.5.0",
"http",
"http-range",
"jni",
"libc",
"log",
+3
View File
@@ -15,6 +15,9 @@
{
"path": "src-tauri",
},
{
"path": "storage-worker",
},
],
"settings": {
// "importSorter.generalConfiguration.sortOnBeforeSave": true,
+1042 -5
View File
File diff suppressed because it is too large. Load diff
+50 -22
View File
@@ -113,6 +113,8 @@ impl YomitanWriter {
installed_at TEXT NOT NULL DEFAULT (datetime('now')),
bundle_name TEXT, -- custom
lang TEXT NOT NULL, -- custom
asset_count INTEGER, -- custom
source TEXT, -- custom
UNIQUE (title, revision)
);
CREATE TABLE IF NOT EXISTS glossaries ( -- not used. moved to a new file
@@ -186,7 +188,7 @@ impl YomitanWriter {
self.ensure_db_attached(DbChild::YomitanGlossary)?;
self.ensure_db_attached(DbChild::Search)?;
let schema_version = "2026-03-08";
let schema_version = "2026-03-21";
let current_schema_version = {
let mut stmt = self
.conn
@@ -275,6 +277,15 @@ impl YomitanWriter {
)?;
}
if v < "2026-03-21" {
self.conn.execute_batch(
"
ALTER TABLE dictionaries ADD COLUMN asset_count INTEGER;
ALTER TABLE dictionaries ADD COLUMN source TEXT;
",
)?;
}
if v < schema_version {
self.conn
.prepare("UPDATE schema_meta SET value = ?2 WHERE key = ?1")?
@@ -454,25 +465,38 @@ impl YomitanWriter {
let cursor = Cursor::new(zip_file.bytes()?);
let mut archive = ZipArchive::new(cursor)?;
let mut asset_count = 0;
for i in 0..archive.len() {
// Metadata — borrow ends before by_index_raw
let (name, is_dir) = {
if let (Some(asset_name), is_dir) = {
let f = archive.by_index(i)?;
(f.name().to_owned(), f.is_dir())
};
(f.enclosed_name(), f.is_dir())
} {
if is_dir {
continue;
}
if !name.starts_with("assets") || is_dir {
continue;
if asset_name.parent().is_none() {
continue;
}
match asset_name.extension().and_then(|e| e.to_str()) {
Some("json") => continue,
_ => {}
}
let out_path = asset_dir.join(asset_name);
if let Some(parent) = out_path.parent() {
create_dir_all(parent)?;
}
let mut entry = archive.by_index(i)?;
let mut out = File::create(&out_path)?;
io::copy(&mut entry, &mut out)?; // writes plain bytes
asset_count += 1;
}
let out_path = asset_dir.join(name);
if let Some(parent) = out_path.parent() {
create_dir_all(parent)?;
}
let mut entry = archive.by_index(i)?;
let mut out = File::create(&out_path)?;
io::copy(&mut entry, &mut out)?; // writes plain bytes
}
let index_file = match archive.by_name("index.json") {
@@ -482,7 +506,6 @@ impl YomitanWriter {
serde_json::from_str::<Value>(&s)?
}
Err(e) => {
// TODO: proper error handling
eprintln!("{}", e);
return Ok(YomitanZipImportResult {
exists: false,
@@ -525,17 +548,21 @@ impl YomitanWriter {
.to_string();
{
let title: &str = &title;
// Skip if already installed
// Skip if already installed or older revision
let mut stmt = self
.conn
.prepare("SELECT (revision < ?2) FROM dictionaries WHERE title = ?1")?;
let mut rows = stmt.query(params![title, revision])?;
.prepare("SELECT (revision < ?2) FROM dictionaries WHERE bundle_name = ?1")?;
let mut rows = stmt.query(params![bundle_name, revision])?;
if let Some(row) = rows.next()? {
let is_outdated: bool = row.get(0)?;
if is_outdated {
self.drop_dictionary(bundle_name, lang)?;
} else {
self.conn.execute(
"UPDATE dictionaries SET asset_count = ?2 WHERE bundle_name = ?1",
params![bundle_name, asset_count],
)?;
return Ok(YomitanZipImportResult {
exists: true,
load: false,
@@ -551,8 +578,8 @@ impl YomitanWriter {
let tx = self.conn.transaction()?;
tx.execute(
"INSERT INTO dictionaries
(title, revision, author, url, description, lang, bundle_name) VALUES
( ?1, ?2, ?3, ?4, ?5, ?6, ?7)",
(title, revision, author, url, description, lang, bundle_name, asset_count) VALUES
( ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
params![
title,
revision,
@@ -561,6 +588,7 @@ impl YomitanWriter {
index_file.get("description").and_then(Value::as_str),
lang,
bundle_name,
asset_count,
],
)?;
+11 -7
View File
@@ -188,12 +188,21 @@ impl AppService {
let mut new_dicts: Vec<String> = vec![];
let mut to_be_removed_dicts: Vec<String> = vec![];
{
let mut writer = YomitanWriter::new(self.db.dir.clone())?;
writer.create_schema(&import_callback)?;
}
let db_path = self.db.dir.join(DBFILE[DbChild::Yomitan]);
if db_path.exists() {
let conn = Connection::open(&db_path)?;
let mut stmt =
conn.prepare("SELECT DISTINCT bundle_name FROM dictionaries WHERE lang = ?1")?;
let mut stmt = conn.prepare(
"
SELECT DISTINCT bundle_name FROM dictionaries
WHERE lang = ?1 AND asset_count IS NOT NULL
",
)?;
let mut rows = stmt.query([lang])?;
let mut db_zip_list: HashSet<String> = HashSet::new();
@@ -228,11 +237,6 @@ impl AppService {
to_be_removed_dicts: to_be_removed_dicts.clone(),
});
{
let mut writer = YomitanWriter::new(self.db.dir.clone())?;
writer.create_schema(&import_callback)?;
}
{
let conn = Connection::open(&db_path)?;
for z in to_be_removed_dicts.clone().iter() {
+14
View File
@@ -43,4 +43,18 @@ body {
html {
height: 100vh;
overflow-y: hidden;
}
html.dark .glossaries {
span {
word-break: keep-all;
}
img[src$=".svg"], img[src^="data:image/svg"] {
filter: invert(1) hue-rotate(180deg);
}
rt {
font-size: 50%;
}
}
+32 -22
View File
@@ -1,13 +1,12 @@
import { createElement as h } from "react";
import { createElement as h, useEffect, useState } from "react";
import { BaseDirectory, readFile } from "@tauri-apps/plugin-fs";
export default function Glossary({
glossary,
yomitanURL,
maxLength,
onTermClicked,
}: {
glossary: any;
yomitanURL: string;
maxLength?: number;
onTermClicked: (t: string) => void;
}) {
@@ -31,17 +30,14 @@ export default function Glossary({
// GlossaryImage
if ("type" in glossary && glossary.type === "image") {
return <GlossaryImage img={glossary} yomitanURL={yomitanURL} />;
return <GlossaryImage img={glossary} />;
}
// GlossaryStructuredContent
if ("type" in glossary && glossary.type === "structured-content") {
const sc = glossary;
return (
<StructuredContent
node={sc.content}
{...{ yomitanURL, maxLength, onTermClicked }}
/>
<StructuredContent node={sc.content} {...{ maxLength, onTermClicked }} />
);
}
@@ -54,17 +50,37 @@ export default function Glossary({
return null;
}
function GlossaryImage({ img, yomitanURL }: { img: any; yomitanURL: string }) {
function GlossaryImage({ img }: { img: any }) {
const [src, set_src] = useState(img.path);
useEffect(() => {
readFile("yomitan/" + img.path, {
baseDir: BaseDirectory.AppData,
}).then((b) => {
const r = new FileReader();
r.onload = () => {
const dataSrc = r.result as string;
const ext = img.path.split(".").pop();
set_src(
dataSrc.replace(
"data:application/octet-stream",
"data:image/" + (ext === "svg" ? "svg+xml" : ext),
),
);
};
r.readAsDataURL(new File([b], img.path));
});
});
return (
<img
src={`${yomitanURL || ""}/${img.path}`}
width={img.width}
height={img.height}
className="glossary-image"
src={src}
title={img.title}
alt={img.alt}
style={{
imageRendering: img.imageRendering || "auto",
width: `${img.width}em`,
width: `${img.width}em`, // unit seems to be em, not px
height: `${img.height}em`,
}}
/>
@@ -73,12 +89,10 @@ function GlossaryImage({ img, yomitanURL }: { img: any; yomitanURL: string }) {
function StructuredContent({
node,
yomitanURL,
maxLength,
onTermClicked,
}: {
node: any;
yomitanURL: string;
maxLength?: number;
onTermClicked: (t: string) => void;
}): any {
@@ -103,7 +117,7 @@ function StructuredContent({
<StructuredContent
key={i}
node={n}
{...{ yomitanURL, maxLength, onTermClicked }}
{...{ maxLength, onTermClicked }}
/>
))}
</>
@@ -120,7 +134,7 @@ function StructuredContent({
// Image
if (tag === "img") {
return <GlossaryImage img={node} yomitanURL={yomitanURL} />;
return <GlossaryImage img={node} />;
}
// Link
@@ -145,10 +159,7 @@ function StructuredContent({
}}
>
{content ? (
<StructuredContent
node={content}
{...{ yomitanURL, maxLength, onTermClicked }}
/>
<StructuredContent node={content} {...{ maxLength, onTermClicked }} />
) : null}
</a>
);
@@ -162,7 +173,6 @@ function StructuredContent({
{ style: block.style, title: block.title, open: block.open },
StructuredContent({
node: block.content,
yomitanURL,
maxLength,
onTermClicked,
}),
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from "react";
import { Dialog, Portal } from "@chakra-ui/react";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { fetch } from "@tauri-apps/plugin-http";
import { supabase } from "../lib/supabaseClient";
@@ -13,6 +14,13 @@ interface YomitanDictEntry {
lang: string;
}
interface YomitanStorageWorkerDictEntry {
key: string;
size: number;
uploaded: string;
url: string;
}
interface YomitanDownloadDictEntry {
url: string;
filepath: string;
@@ -119,6 +127,53 @@ function LoadingDialog() {
});
}
}
const LAST_API_CALL_KEY = "LAST_API_CALL_KEY_storageWorkerURL";
const LAST_API_JSON_KEY = "LAST_API_JSON_KEY_storageWorkerURL";
let entries: YomitanStorageWorkerDictEntry[] = [];
const lastCall = localStorage.getItem(LAST_API_CALL_KEY);
const lastJSON = localStorage.getItem(LAST_API_JSON_KEY);
if (lastJSON && lastCall) {
const d = new Date(lastCall);
d.setDate(d.getDate() + 1);
if (d > new Date()) {
entries = JSON.parse(lastJSON);
}
}
const prefix = `${lang}/`;
try {
if (!entries.length) {
const { VITE_STORAGE_WORKER_URL, VITE_STORAGE_WORKER_TOKEN } =
import.meta.env;
console.log(`accessing ${VITE_STORAGE_WORKER_URL}`);
entries = await fetch(VITE_STORAGE_WORKER_URL + "/files", {
headers: {
Authorization: `Bearer ${VITE_STORAGE_WORKER_TOKEN}`,
},
}).then((r) => r.json());
localStorage.setItem(LAST_API_CALL_KEY, new Date().toISOString());
localStorage.setItem(LAST_API_JSON_KEY, JSON.stringify(entries));
}
} catch (e) {
console.error(e);
entries = JSON.parse(lastJSON || "[]");
}
dicts.push(
...entries
.filter((o) => o.key.startsWith(prefix))
.map((o) => ({
filepath: `yomitan/${lang}/${o.key.substring(prefix.length)}`,
url: o.url,
})),
);
}
await invoke("init_yomitan", { dicts, lang });
+3 -14
View File
@@ -18,8 +18,7 @@ import {
Stack,
Switch,
} from "@chakra-ui/react";
import { convertFileSrc, invoke } from "@tauri-apps/api/core";
import { appDataDir, join } from "@tauri-apps/api/path";
import { invoke } from "@tauri-apps/api/core";
import { readText } from "@tauri-apps/plugin-clipboard-manager";
import Glossary from "../components/Glossary";
@@ -55,13 +54,6 @@ function SearchPage() {
const [furigana, set_furigana] = useState("");
const [isAutoKana, set_isAutoKana] = useState(true);
const [isScrollEnd, set_isScrollEnd] = useState(false);
const [yomitanURL, set_yomitanURL] = useState("");
useEffect(() => {
(async () => {
set_yomitanURL(convertFileSrc(await join(await appDataDir(), "yomitan")));
})();
});
const nSearch = useRef(0);
const searchboxRef = useRef<HTMLInputElement | null>(null);
@@ -412,6 +404,7 @@ function SearchPage() {
</Card.Header>
<Card.Body>
<div
className="glossaries"
style={{
display: "flex",
flexDirection: "column",
@@ -419,11 +412,7 @@ function SearchPage() {
}}
>
{JSON.parse(glossary_json).map((g: any, i: number) => (
<Glossary
key={i}
glossary={g}
{...{ yomitanURL, onTermClicked }}
/>
<Glossary key={i} glossary={g} {...{ onTermClicked }} />
))}
</div>
</Card.Body>
+1 -1
View File
@@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = ["protocol-asset"] }
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
cjdic2-core = { path = "../src-core" }
tauri-plugin-http = "2"
+11 -1
View File
@@ -8,8 +8,18 @@
"permissions": [
"core:default",
"opener:default",
"http:default",
{
"identifier": "http:default",
"allow": [
{ "url": "https://api.github.com/repos/*/releases/latest" },
{ "url": "https://storage-worker.*.workers.dev" }
]
},
"fs:default",
{
"identifier": "fs:allow-appdata-read",
"allow": [{ "path": "$APPDATA/yomitan/**" }]
},
"clipboard-manager:allow-read-text"
]
}
+2 -6
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "cjdic2",
"version": "0.1.2",
"version": "0.1.3",
"identifier": "cc.polv.cjdic2",
"build": {
"beforeDevCommand": "pnpm dev",
@@ -19,11 +19,7 @@
}
],
"security": {
"csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost",
"assetProtocol": {
"enable": true,
"scope": ["$APPDATA/yomitan/**"]
}
"csp": null
}
},
"bundle": {
+12
View File
@@ -0,0 +1,12 @@
# http://editorconfig.org
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.yml]
indent_style = space
+167
View File
@@ -0,0 +1,167 @@
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
\*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
\*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
\*.tgz
# Yarn Integrity file
.yarn-integrity
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*
# wrangler project
.dev.vars*
!.dev.vars.example
.env*
!.env.example
.wrangler/
+6
View File
@@ -0,0 +1,6 @@
{
"printWidth": 140,
"singleQuote": true,
"semi": true,
"useTabs": true
}
+5
View File
@@ -0,0 +1,5 @@
{
"files.associations": {
"wrangler.json": "jsonc"
}
}
+34
View File
@@ -0,0 +1,34 @@
# Cloudflare Workers
STOP. Your knowledge of Cloudflare Workers APIs and limits may be outdated. Always retrieve current documentation before any Workers, KV, R2, D1, Durable Objects, Queues, Vectorize, AI, or Agents SDK task.
## Docs
- https://developers.cloudflare.com/workers/
- MCP: `https://docs.mcp.cloudflare.com/mcp`
For all limits and quotas, retrieve from the product's `/platform/limits/` page. eg. `/workers/platform/limits`
## Commands
| Command | Purpose |
|---------|---------|
| `npx wrangler dev` | Local development |
| `npx wrangler deploy` | Deploy to Cloudflare |
| `npx wrangler types` | Generate TypeScript types |
Run `wrangler types` after changing bindings in wrangler.jsonc.
## Node.js Compatibility
https://developers.cloudflare.com/workers/runtime-apis/nodejs/
## Errors
- **Error 1102** (CPU/Memory exceeded): Retrieve limits from `/workers/platform/limits/`
- **All errors**: https://developers.cloudflare.com/workers/observability/errors/
## Product Docs
Retrieve API references and limits from:
`/kv/` · `/r2/` · `/d1/` · `/durable-objects/` · `/queues/` · `/vectorize/` · `/workers-ai/` · `/agents/`
+3
View File
@@ -0,0 +1,3 @@
interface Env {
YOMITAN_STORAGE_API_TOKEN: string;
}
+19
View File
@@ -0,0 +1,19 @@
{
"name": "storage-worker",
"version": "0.0.0",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"test": "vitest",
"cf-typegen": "wrangler types"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.12.4",
"@types/node": "^24.10.13",
"typescript": "^5.5.2",
"vitest": "~3.2.0",
"wrangler": "^4.76.0"
}
}
+50
View File
@@ -0,0 +1,50 @@
/**
* Welcome to Cloudflare Workers! This is your first worker.
*
* - Run `npm run dev` in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run `npm run deploy` to publish your worker
*
* Bind resources to your worker in `wrangler.jsonc`. After adding bindings, a type definition for the
* `Env` object can be regenerated with `npm run cf-typegen`.
*
* Learn more at https://developers.cloudflare.com/workers/
*/
export default {
async fetch(request, env) {
const url = new URL(request.url);
// List files: GET /files
if (url.pathname === '/files') {
// Auth check first
const auth = request.headers.get("Authorization");
if (auth !== `Bearer ${env.YOMITAN_STORAGE_API_TOKEN}`) {
return new Response("Unauthorized", { status: 401 });
}
const list = await env.BUCKET.list();
const files = list.objects
.filter((o) => o.size)
.map((o) => ({
key: o.key,
size: o.size,
uploaded: o.uploaded,
url: `${url.origin}/download/${o.key}`,
}));
return Response.json(files);
}
// Download: GET /download/filename.zip
if (url.pathname.startsWith('/download/')) {
const key = url.pathname.replace('/download/', '');
const o = await env.BUCKET.get(key);
if (!o || !o.size) return new Response('Not found', { status: 404 });
return new Response(o.body, {
headers: { 'Content-Disposition': `attachment; filename="${key}"` },
});
}
return new Response('Not found', { status: 404 });
},
} satisfies ExportedHandler<Env>;
+3
View File
@@ -0,0 +1,3 @@
declare module 'cloudflare:test' {
interface ProvidedEnv extends Env {}
}
+24
View File
@@ -0,0 +1,24 @@
import { env, createExecutionContext, waitOnExecutionContext, SELF } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';
import worker from '../src/index';
// For now, you'll need to do something like this to get a correctly-typed
// `Request` to pass to `worker.fetch()`.
const IncomingRequest = Request<unknown, IncomingRequestCfProperties>;
describe('Hello World worker', () => {
it('responds with Hello World! (unit style)', async () => {
const request = new IncomingRequest('http://example.com');
// Create an empty context to pass to `worker.fetch()`.
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
// Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions
await waitOnExecutionContext(ctx);
expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`);
});
it('responds with Hello World! (integration style)', async () => {
const response = await SELF.fetch('https://example.com');
expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`);
});
});
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["@cloudflare/vitest-pool-workers"]
},
"include": ["./**/*.ts", "../worker-configuration.d.ts"],
"exclude": []
}
+47
View File
@@ -0,0 +1,47 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"target": "es2024",
/* Specify a set of bundled library declaration files that describe the target runtime environment. */
"lib": ["es2024"],
/* Specify what JSX code is generated. */
"jsx": "react-jsx",
/* Specify what module code is generated. */
"module": "es2022",
/* Specify how TypeScript looks up a file from a given module specifier. */
"moduleResolution": "Bundler",
/* Enable importing .json files */
"resolveJsonModule": true,
/* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
"allowJs": true,
/* Enable error reporting in type-checked JavaScript files. */
"checkJs": false,
/* Disable emitting files from a compilation. */
"noEmit": true,
/* Ensure that each file can be safely transpiled without relying on other imports. */
"isolatedModules": true,
/* Allow 'import x from y' when a module doesn't have a default export. */
"allowSyntheticDefaultImports": true,
/* Ensure that casing is correct in imports. */
"forceConsistentCasingInFileNames": true,
/* Enable all strict type-checking options. */
"strict": true,
/* Skip type checking all .d.ts files. */
"skipLibCheck": true,
"types": [
"./worker-configuration.d.ts",
"./env.d.ts",
"node"
]
},
"exclude": ["test"],
"include": ["worker-configuration.d.ts", "src/**/*.ts"]
}
+11
View File
@@ -0,0 +1,11 @@
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.jsonc' },
},
},
},
});
File diff suppressed because it is too large. Load diff
+48
View File
@@ -0,0 +1,48 @@
/**
* For more details on how to configure Wrangler, refer to:
* https://developers.cloudflare.com/workers/wrangler/configuration/
*/
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "storage-worker",
"main": "src/index.ts",
"compatibility_date": "2025-09-27",
"observability": {
"enabled": true,
},
"compatibility_flags": ["nodejs_compat"],
/**
* Smart Placement
* https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
*/
// "placement": { "mode": "smart" }
/**
* Bindings
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
* databases, object storage, AI inference, real-time communication and more.
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
*/
"r2_buckets": [
{
"binding": "BUCKET",
"bucket_name": "yomitan",
},
],
/**
* Environment Variables
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
* Note: Use secrets to store sensitive data.
* https://developers.cloudflare.com/workers/configuration/secrets/
*/
// "vars": { "MY_VARIABLE": "production_value" }
/**
* Static Assets
* https://developers.cloudflare.com/workers/static-assets/binding/
*/
// "assets": { "directory": "./public/", "binding": "ASSETS" }
/**
* Service Bindings (communicate between multiple Workers)
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
*/
// "services": [ { "binding": "MY_SERVICE", "service": "my-service" } ]
}