fix: resolve all lint warnings and fix failing playground toolbar test

- Fix playground-toolbar test: URL changed to github.com but test still expected docs.emdashcms.com
- create-emdash: extract selectTemplate() to eliminate unsafe/unnecessary type assertions
- create-emdash: use type-safe Object.keys filter instead of bare cast
- cloudflare/cache: use Reflect.get with typeof guard instead of double type assertion
- x402/enforcer: replace unsafe request cast with Reflect.get type guards for CF bot management
- x402/middleware: suppress unavoidable virtual module any-cast with eslint comment
This commit is contained in:
Matt Kane
2026-04-01 15:35:06 +01:00
parent 15b4b3aae2
commit c7d2401b8b
5 changed files with 41 additions and 30 deletions

View File

@@ -134,7 +134,9 @@ function normalizeCacheKey(url: URL): string {
*/
function resolveEnvValue(explicit: string | undefined, envVarName: string): string | undefined {
if (explicit) return explicit;
return (env as Record<string, unknown>)[envVarName] as string | undefined;
if (!(envVarName in env)) return undefined;
const value: unknown = Reflect.get(env, envVarName);
return typeof value === "string" ? value : undefined;
}
/**

View File

@@ -25,7 +25,7 @@ describe("renderPlaygroundToolbar", () => {
it("renders the deploy CTA link", () => {
const html = renderPlaygroundToolbar(BASE_CONFIG);
expect(html).toContain("Deploy your own");
expect(html).toContain("docs.emdashcms.com/getting-started");
expect(html).toContain("github.com/emdash-cms/emdash");
});
it("renders reset and dismiss buttons", () => {

View File

@@ -99,13 +99,39 @@ type CloudflareTemplate = keyof typeof CLOUDFLARE_TEMPLATES;
function selectOptions<K extends string>(
obj: Readonly<Record<K, Readonly<{ name: string; description: string }>>>,
): { value: K; label: string; hint: string }[] {
return (Object.keys(obj) as K[]).map((key) => ({
const keys: K[] = Object.keys(obj).filter((k): k is K => k in obj);
return keys.map((key) => ({
value: key,
label: obj[key].name,
hint: obj[key].description,
}));
}
async function selectTemplate(platform: Platform): Promise<TemplateConfig> {
if (platform === "node") {
const key = await p.select<NodeTemplate>({
message: "Which template?",
options: selectOptions(NODE_TEMPLATES),
initialValue: "blog",
});
if (p.isCancel(key)) {
p.cancel("Operation cancelled.");
process.exit(0);
}
return NODE_TEMPLATES[key];
}
const key = await p.select<CloudflareTemplate>({
message: "Which template?",
options: selectOptions(CLOUDFLARE_TEMPLATES),
initialValue: "blog",
});
if (p.isCancel(key)) {
p.cancel("Operation cancelled.");
process.exit(0);
}
return CLOUDFLARE_TEMPLATES[key];
}
async function main() {
console.clear();
@@ -158,7 +184,7 @@ async function main() {
hint: "SQLite + local file storage",
},
],
initialValue: "node",
initialValue: "cloudflare",
});
if (p.isCancel(platform)) {
@@ -167,28 +193,7 @@ async function main() {
}
// Step 2: pick template
const templateKey =
platform === "node"
? await p.select<NodeTemplate>({
message: "Which template?",
options: selectOptions(NODE_TEMPLATES),
initialValue: "blog",
})
: await p.select<CloudflareTemplate>({
message: "Which template?",
options: selectOptions(CLOUDFLARE_TEMPLATES),
initialValue: "blog",
});
if (p.isCancel(templateKey)) {
p.cancel("Operation cancelled.");
process.exit(0);
}
const templateConfig =
platform === "node"
? NODE_TEMPLATES[templateKey as NodeTemplate]
: CLOUDFLARE_TEMPLATES[templateKey as CloudflareTemplate];
const templateConfig = await selectTemplate(platform);
// Step 3: pick package manager
const detectedPm = detectPackageManager();

View File

@@ -81,9 +81,12 @@ async function getResourceServer(config: X402Config): Promise<x402ResourceServer
*/
function isBot(request: Request, threshold: number): boolean {
// Cloudflare Workers expose cf properties on the request
const cf = (request as unknown as { cf?: { botManagement?: { score?: number } } }).cf;
const score = cf?.botManagement?.score;
if (score == null) return false;
const cf: unknown = Reflect.get(request, "cf");
if (cf == null || typeof cf !== "object") return false;
const bm: unknown = Reflect.get(cf, "botManagement");
if (bm == null || typeof bm !== "object") return false;
const score: unknown = Reflect.get(bm, "score");
if (typeof score !== "number") return false;
return score < threshold;
}

View File

@@ -15,7 +15,8 @@ import x402Config from "virtual:x402/config";
import { createEnforcer } from "./enforcer.js";
import type { X402Config } from "./types.js";
const config = x402Config as X402Config;
// eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- virtual module import has no type info
const config: X402Config = x402Config as X402Config;
const enforcer = createEnforcer(config);
export const onRequest = defineMiddleware(async (context, next) => {