From 8583394481230dc177ed27ec4537055187cbf2ae Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 15 Jan 2024 19:03:59 +0100 Subject: [PATCH 01/13] fix: prototype pollution in deserializer (#2255) This is not exploitable in practice unless a user maliciously crafts serialized values in __FRSH_STATE, because `serializer()` never outputs serialized representation that would be vulnerable to prototype pollution. But hey, defense in depth. --- src/runtime/deserializer.ts | 34 +++++++++++++++++++++++----------- src/server/serializer_test.ts | 20 +++++++++++++++++++- tests/deps.ts | 1 + 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/runtime/deserializer.ts b/src/runtime/deserializer.ts index f82444b58b5..26b04890ed3 100644 --- a/src/runtime/deserializer.ts +++ b/src/runtime/deserializer.ts @@ -1,6 +1,3 @@ -// Run `deno run -A npm:esbuild --minify src/runtime/deserializer.ts` to minify -// this file. It is embedded into src/server/deserializer_code.ts. - export const KEY = "_f"; interface Signal { @@ -18,6 +15,20 @@ function b64decode(b64: string): Uint8Array { return bytes; } +const INVALID_REFERENCE_ERROR = "Invalid reference"; + +function getPropertyFromPath(o: object, path: string[]): object { + for (const key of path) { + if (key === null) continue; + if (key !== "value" && !Object.hasOwn(o, key)) { + throw new Error(INVALID_REFERENCE_ERROR); + } + // deno-lint-ignore no-explicit-any + o = (o as any)[key]; + } + return o; +} + export function deserialize( str: string, signal?: (a: T) => Signal, @@ -44,19 +55,20 @@ export function deserialize( } return value; } - const { v, r } = JSON.parse(str, reviver); const references = (r ?? []) as [string[], ...string[][]][]; for (const [targetPath, ...refPaths] of references) { - const target = targetPath.reduce((o, k) => k === null ? o : o[k], v); + const target = getPropertyFromPath(v, targetPath); for (const refPath of refPaths) { - if (refPath.length === 0) throw new Error("Invalid reference"); + if (refPath.length === 0) throw new Error(INVALID_REFERENCE_ERROR); // set the reference to the target object - const parent = refPath.slice(0, -1).reduce( - (o, k) => k === null ? o : o[k], - v, - ); - parent[refPath[refPath.length - 1]!] = target; + const parent = getPropertyFromPath(v, refPath.slice(0, -1)); + const key = refPath[refPath.length - 1]!; + if (key !== "value" && !Object.hasOwn(parent, key)) { + throw new Error(INVALID_REFERENCE_ERROR); + } + // deno-lint-ignore no-explicit-any + (parent as any)[key] = target; } } return v; diff --git a/src/server/serializer_test.ts b/src/server/serializer_test.ts index eca080a4865..6469e036a96 100644 --- a/src/server/serializer_test.ts +++ b/src/server/serializer_test.ts @@ -1,7 +1,12 @@ // deno-lint-ignore-file no-explicit-any import { serialize } from "./serializer.ts"; -import { assert, assertEquals, assertSnapshot } from "../../tests/deps.ts"; +import { + assert, + assertEquals, + assertSnapshot, + assertThrows, +} from "../../tests/deps.ts"; import { deserialize, KEY } from "../runtime/deserializer.ts"; import { signal } from "@preact/signals-core"; import { signal as signal130 } from "@preact/signals-core@1.3.0"; @@ -213,3 +218,16 @@ Deno.test("serializer - multiple reference in signal", async (t) => { assertEquals(deserialized.s.peek(), inner); assertEquals(deserialized.inner, inner); }); + +Deno.test("deserializer - no prototype pollution with manual input", () => { + const serialized = String.raw`{ + "v": { + "*": ["onerror"] + }, + "r": [ + [["*"], ["constructor", "prototype", "*"]] + ] + }`; + assertThrows(() => deserialize(serialized, signal)); + assertEquals({}.constructor.prototype["*"], undefined); +}); diff --git a/tests/deps.ts b/tests/deps.ts index a7dce31de62..9d7199350cf 100644 --- a/tests/deps.ts +++ b/tests/deps.ts @@ -14,6 +14,7 @@ export { assertNotMatch, assertRejects, assertStringIncludes, + assertThrows, } from "https://deno.land/std@0.211.0/assert/mod.ts"; export { assertSnapshot } from "https://deno.land/std@0.211.0/testing/snapshot.ts"; export { From 9f81806c6eef1071623e877f0df041392f112644 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Mon, 15 Jan 2024 19:41:12 +0100 Subject: [PATCH 02/13] fix: `__FRSH_STATE` potentially being overwritten by user code (#2256) As outlined in https://github.com/denoland/fresh/issues/2254 it was possible to overwrite the script tag that is used by Fresh to pass island props from the server to the client. This could be done by injecting raw HTML via `dangerouslySetInnerHTML` and simply using the same id that Fresh was using. With this PR we generate a unique id per render that cannot be guessed anymore. It's composed of `__FRSH_STATE_` --- src/runtime/entrypoints/main.ts | 5 +++-- src/server/context.ts | 7 ++++--- src/server/render.ts | 5 +++-- src/server/rendering/fresh_tags.tsx | 12 +++++++---- src/server/rendering/state.ts | 3 +++ tests/fixture/fresh.gen.ts | 4 ++++ tests/fixture/islands/DangerousIsland.tsx | 10 ++++++++++ tests/fixture/routes/spoof_state.tsx | 5 +++++ tests/fixture_partials/fresh.gen.ts | 6 ++++++ .../islands/DangerousIsland.tsx | 10 ++++++++++ .../routes/spoof_state/index.tsx | 12 +++++++++++ .../routes/spoof_state/partial.tsx | 11 ++++++++++ tests/main_test.ts | 17 +++++++++++++++- tests/partials_test.ts | 20 +++++++++++++++++++ tests/render_test.ts | 4 ++-- 15 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 tests/fixture/islands/DangerousIsland.tsx create mode 100644 tests/fixture/routes/spoof_state.tsx create mode 100644 tests/fixture_partials/islands/DangerousIsland.tsx create mode 100644 tests/fixture_partials/routes/spoof_state/index.tsx create mode 100644 tests/fixture_partials/routes/spoof_state/partial.tsx diff --git a/src/runtime/entrypoints/main.ts b/src/runtime/entrypoints/main.ts index de401bfb905..8cf9e4119cb 100644 --- a/src/runtime/entrypoints/main.ts +++ b/src/runtime/entrypoints/main.ts @@ -640,6 +640,7 @@ class NoPartialsError extends Error {} */ export async function applyPartials(res: Response): Promise { const contentType = res.headers.get("Content-Type"); + const uuid = res.headers.get("X-Fresh-UUID"); if (contentType !== "text/html; charset=utf-8") { throw new Error(partialErrorMessage); } @@ -652,7 +653,7 @@ export async function applyPartials(res: Response): Promise { // Preload all islands because they need to be available synchronously // for rendering later const islands: IslandRegistry = {}; - const dataRaw = doc.getElementById("__FRSH_PARTIAL_DATA")!; + const dataRaw = doc.getElementById(`__FRSH_PARTIAL_DATA_${uuid}`)!; let data: { islands: Record; signals: string | null; @@ -669,7 +670,7 @@ export async function applyPartials(res: Response): Promise { ); } - const stateDom = doc.getElementById("__FRSH_STATE")?.textContent; + const stateDom = doc.getElementById(`__FRSH_STATE_${uuid}`)?.textContent; let state: SerializedState = [[], []]; // Load all dependencies diff --git a/src/server/context.ts b/src/server/context.ts index aafdc355d04..f76fc97ab4a 100644 --- a/src/server/context.ts +++ b/src/server/context.ts @@ -669,7 +669,7 @@ const createRenderNotFound = ( return async (req, ctx) => { const notFound = extractResult.notFound; if (!notFound.component) { - return sendResponse(["Not found.", undefined], { + return sendResponse(["Not found.", "", undefined], { status: STATUS_CODE.NotFound, isDev: dev, statusText: undefined, @@ -771,7 +771,7 @@ function collectEntrypoints( } function sendResponse( - resp: [string, ContentSecurityPolicy | undefined], + resp: [string, string, ContentSecurityPolicy | undefined], options: { status: number; statusText: string | undefined; @@ -779,11 +779,12 @@ function sendResponse( isDev: boolean; }, ) { + const [body, uuid, csp] = resp; const headers: Record = { "content-type": "text/html; charset=utf-8", + "x-fresh-uuid": uuid, }; - const [body, csp] = resp; if (csp) { if (options.isDev) { csp.directives.connectSrc = [ diff --git a/src/server/render.ts b/src/server/render.ts index 71d8d77868d..86a269c47a3 100644 --- a/src/server/render.ts +++ b/src/server/render.ts @@ -122,7 +122,7 @@ export function checkAsyncComponent( */ export async function render( opts: RenderOptions, -): Promise<[string, ContentSecurityPolicy | undefined] | Response> { +): Promise<[string, string, ContentSecurityPolicy | undefined] | Response> { const component = opts.route.component; // Only inherit layouts up to the nearest root layout. @@ -242,6 +242,7 @@ export async function render( // ensures that each render request is associated with the same // data. const renderState = new RenderState( + crypto.randomUUID(), { url, route: opts.route.pattern, @@ -393,5 +394,5 @@ export async function render( moduleScripts: result.moduleScripts, lang: ctx.lang, }); - return [html, csp]; + return [html, renderState.renderUuid, csp]; } diff --git a/src/server/rendering/fresh_tags.tsx b/src/server/rendering/fresh_tags.tsx index bf0f41cf4e4..baad2a7d36d 100644 --- a/src/server/rendering/fresh_tags.tsx +++ b/src/server/rendering/fresh_tags.tsx @@ -72,16 +72,20 @@ export function renderFreshTags( // The inline script that will hydrate the page. let script = ""; - // Serialize the state into the `; + ``; hasSignals = res.hasSignals; requiresDeserializer = res.requiresDeserializer; @@ -94,7 +98,7 @@ export function renderFreshTags( const url = addImport("signals.js"); script += `import { signal } from "${url}";`; } - script += `const ST = document.getElementById("__FRSH_STATE").textContent;`; + script += `const ST = document.getElementById("${stateId}").textContent;`; script += `const STATE = `; if (res.requiresDeserializer) { if (res.hasSignals) { @@ -166,7 +170,7 @@ export function renderFreshTags( ); const nonce = renderState.csp ? ` nonce="${renderState.getNonce()}` : ""; opts.bodyHtml += - ``; + ``; } if (script !== "") { opts.bodyHtml += diff --git a/src/server/rendering/state.ts b/src/server/rendering/state.ts index 2143c66e636..82e3a9225ec 100644 --- a/src/server/rendering/state.ts +++ b/src/server/rendering/state.ts @@ -16,6 +16,7 @@ export interface RenderStateRouteOptions { } export class RenderState { + readonly renderUuid: string; // deno-lint-ignore no-explicit-any componentStack: any[]; renderingUserTemplate = false; @@ -48,12 +49,14 @@ export class RenderState { basePath: string; constructor( + renderUuid: string, routeOptions: RenderStateRouteOptions, // deno-lint-ignore no-explicit-any componentStack: any[], csp?: ContentSecurityPolicy, error?: unknown, ) { + this.renderUuid = renderUuid; this.routeOptions = routeOptions; this.csp = csp; this.componentStack = componentStack; diff --git a/tests/fixture/fresh.gen.ts b/tests/fixture/fresh.gen.ts index bc2a68049d4..e60cd48d357 100644 --- a/tests/fixture/fresh.gen.ts +++ b/tests/fixture/fresh.gen.ts @@ -74,6 +74,7 @@ import * as $route_groups_bar_boof_index from "./routes/route-groups/(bar)/boof/ import * as $route_groups_foo_layout from "./routes/route-groups/(foo)/_layout.tsx"; import * as $route_groups_foo_index from "./routes/route-groups/(foo)/index.tsx"; import * as $signal_shared from "./routes/signal_shared.tsx"; +import * as $spoof_state from "./routes/spoof_state.tsx"; import * as $state_in_props_middleware from "./routes/state-in-props/_middleware.ts"; import * as $state_in_props_index from "./routes/state-in-props/index.tsx"; import * as $state_middleware_middleware from "./routes/state-middleware/_middleware.ts"; @@ -85,6 +86,7 @@ import * as $std from "./routes/std.tsx"; import * as $umlaut_äöüß from "./routes/umlaut-äöüß.tsx"; import * as $wildcard from "./routes/wildcard.tsx"; import * as $Counter from "./islands/Counter.tsx"; +import * as $DangerousIsland from "./islands/DangerousIsland.tsx"; import * as $Foo_Bar from "./islands/Foo.Bar.tsx"; import * as $FormIsland from "./islands/FormIsland.tsx"; import * as $Greeter from "./islands/Greeter.tsx"; @@ -194,6 +196,7 @@ const manifest = { "./routes/route-groups/(foo)/_layout.tsx": $route_groups_foo_layout, "./routes/route-groups/(foo)/index.tsx": $route_groups_foo_index, "./routes/signal_shared.tsx": $signal_shared, + "./routes/spoof_state.tsx": $spoof_state, "./routes/state-in-props/_middleware.ts": $state_in_props_middleware, "./routes/state-in-props/index.tsx": $state_in_props_index, "./routes/state-middleware/_middleware.ts": $state_middleware_middleware, @@ -208,6 +211,7 @@ const manifest = { }, islands: { "./islands/Counter.tsx": $Counter, + "./islands/DangerousIsland.tsx": $DangerousIsland, "./islands/Foo.Bar.tsx": $Foo_Bar, "./islands/FormIsland.tsx": $FormIsland, "./islands/Greeter.tsx": $Greeter, diff --git a/tests/fixture/islands/DangerousIsland.tsx b/tests/fixture/islands/DangerousIsland.tsx new file mode 100644 index 00000000000..287c481e0a6 --- /dev/null +++ b/tests/fixture/islands/DangerousIsland.tsx @@ -0,0 +1,10 @@ +import { useEffect, useState } from "preact/hooks"; + +export default function RawIsland(props: { raw: string }) { + const [css, set] = useState(""); + useEffect(() => { + set("raw_ready"); + }, []); + + return
; +} diff --git a/tests/fixture/routes/spoof_state.tsx b/tests/fixture/routes/spoof_state.tsx new file mode 100644 index 00000000000..0399ca217c4 --- /dev/null +++ b/tests/fixture/routes/spoof_state.tsx @@ -0,0 +1,5 @@ +import DangerousIsland from "../islands/DangerousIsland.tsx"; + +export default function SerializePrototype() { + return {.invalid.json}`} />; +} diff --git a/tests/fixture_partials/fresh.gen.ts b/tests/fixture_partials/fresh.gen.ts index d97abc562e9..ec5922e4450 100644 --- a/tests/fixture_partials/fresh.gen.ts +++ b/tests/fixture_partials/fresh.gen.ts @@ -117,9 +117,12 @@ import * as $relative_link_index from "./routes/relative_link/index.tsx"; import * as $scroll_restoration_index from "./routes/scroll_restoration/index.tsx"; import * as $scroll_restoration_injected from "./routes/scroll_restoration/injected.tsx"; import * as $scroll_restoration_update from "./routes/scroll_restoration/update.tsx"; +import * as $spoof_state_index from "./routes/spoof_state/index.tsx"; +import * as $spoof_state_partial from "./routes/spoof_state/partial.tsx"; import * as $Counter from "./islands/Counter.tsx"; import * as $CounterA from "./islands/CounterA.tsx"; import * as $CounterB from "./islands/CounterB.tsx"; +import * as $DangerousIsland from "./islands/DangerousIsland.tsx"; import * as $Fader from "./islands/Fader.tsx"; import * as $InvalidSlot from "./islands/InvalidSlot.tsx"; import * as $KeyExplorer from "./islands/KeyExplorer.tsx"; @@ -262,11 +265,14 @@ const manifest = { "./routes/scroll_restoration/index.tsx": $scroll_restoration_index, "./routes/scroll_restoration/injected.tsx": $scroll_restoration_injected, "./routes/scroll_restoration/update.tsx": $scroll_restoration_update, + "./routes/spoof_state/index.tsx": $spoof_state_index, + "./routes/spoof_state/partial.tsx": $spoof_state_partial, }, islands: { "./islands/Counter.tsx": $Counter, "./islands/CounterA.tsx": $CounterA, "./islands/CounterB.tsx": $CounterB, + "./islands/DangerousIsland.tsx": $DangerousIsland, "./islands/Fader.tsx": $Fader, "./islands/InvalidSlot.tsx": $InvalidSlot, "./islands/KeyExplorer.tsx": $KeyExplorer, diff --git a/tests/fixture_partials/islands/DangerousIsland.tsx b/tests/fixture_partials/islands/DangerousIsland.tsx new file mode 100644 index 00000000000..d2fd6cc6828 --- /dev/null +++ b/tests/fixture_partials/islands/DangerousIsland.tsx @@ -0,0 +1,10 @@ +import { useEffect, useState } from "preact/hooks"; + +export default function DangerousIsland(props: { raw: string }) { + const [css, set] = useState(""); + useEffect(() => { + set("raw_ready"); + }, []); + + return
; +} diff --git a/tests/fixture_partials/routes/spoof_state/index.tsx b/tests/fixture_partials/routes/spoof_state/index.tsx new file mode 100644 index 00000000000..0ee8e71bce9 --- /dev/null +++ b/tests/fixture_partials/routes/spoof_state/index.tsx @@ -0,0 +1,12 @@ +import { Partial } from "$fresh/runtime.ts"; + +export default function SerializePrototype() { + return ( +
+ +

initial

+
+ Update +
+ ); +} diff --git a/tests/fixture_partials/routes/spoof_state/partial.tsx b/tests/fixture_partials/routes/spoof_state/partial.tsx new file mode 100644 index 00000000000..8884e759a85 --- /dev/null +++ b/tests/fixture_partials/routes/spoof_state/partial.tsx @@ -0,0 +1,11 @@ +import { Partial } from "$fresh/runtime.ts"; +import DangerousIsland from "../../islands/DangerousIsland.tsx"; + +export default function Res() { + return ( + + {.invalid.json}`} /> +

partial

+
+ ); +} diff --git a/tests/main_test.ts b/tests/main_test.ts index 6be9bdf5a78..252ac46b25f 100644 --- a/tests/main_test.ts +++ b/tests/main_test.ts @@ -949,7 +949,7 @@ Deno.test("Adds nonce to inline scripts", async () => { await withFakeServe("./tests/fixture/main.ts", async (server) => { const doc = await server.getHtml(`/nonce_inline`); - const stateScript = doc.querySelector("#__FRSH_STATE")!; + const stateScript = doc.querySelector("[id^=__FRSH_STATE]")!; const nonce = stateScript.getAttribute("nonce")!; const el = doc.querySelector("#inline-script")!; @@ -1220,3 +1220,18 @@ Deno.test("empty string fallback for optional params", async () => { assertEquals(data, { path: "foo", version: "" }); }); }); + +// See https://github.com/denoland/fresh/issues/2254 +Deno.test("should not be able to override __FRSH_STATE", async () => { + await withPageName("./tests/fixture/main.ts", async (page, address) => { + let didError = false; + page.on("pageerror", (ev) => { + didError = true; + console.log(ev); + }); + await page.goto(`${address}/spoof_state`); + await page.waitForSelector(".raw_ready"); + + assert(!didError); + }); +}); diff --git a/tests/partials_test.ts b/tests/partials_test.ts index 8d6f03f0d4f..d1d34d09496 100644 --- a/tests/partials_test.ts +++ b/tests/partials_test.ts @@ -1569,3 +1569,23 @@ Deno.test("render partial without title", async () => { }, ); }); + +// See https://github.com/denoland/fresh/issues/2254 +Deno.test("should not be able to override __FRSH_STATE", async () => { + await withPageName( + "./tests/fixture_partials/main.ts", + async (page, address) => { + let didError = false; + page.on("pageerror", (ev) => { + didError = true; + console.log(ev); + }); + await page.goto(`${address}/spoof_state`); + + await page.click("a"); + await page.waitForSelector(".raw_ready"); + + assert(!didError); + }, + ); +}); diff --git a/tests/render_test.ts b/tests/render_test.ts index 067a745973f..66356b65aa2 100644 --- a/tests/render_test.ts +++ b/tests/render_test.ts @@ -18,8 +18,8 @@ Deno.test("doesn't leak data across renderers", async () => { const resp = await handler(req); const doc = parseHtml(await resp.text()); - assertSelector(doc, "#__FRSH_STATE"); - const text = doc.querySelector("#__FRSH_STATE")?.textContent!; + assertSelector(doc, "[id^=__FRSH_STATE]"); + const text = doc.querySelector("[id^=__FRSH_STATE]")?.textContent!; const json = JSON.parse(text); assertEquals(json, { "v": [[{ "site": name }], []] }); } From 3d30930d739d2ff88cb3464c0cacd207198735ce Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Mon, 15 Jan 2024 19:43:28 +0100 Subject: [PATCH 03/13] chore: release 1.6.3 (#2257) --- versions.json | 1 + 1 file changed, 1 insertion(+) diff --git a/versions.json b/versions.json index 500653e006e..31e1adb3f8f 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,5 @@ [ + "1.6.3", "1.6.2", "1.6.1", "1.6.0", From 5268aaa6a5691a2578d109e5957194a04c8b0076 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Tue, 16 Jan 2024 15:41:20 +0100 Subject: [PATCH 04/13] chore: simplify deno check:types command (#2259) --- deno.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno.json b/deno.json index 6cf330a1bff..99dd5de53e9 100644 --- a/deno.json +++ b/deno.json @@ -5,7 +5,7 @@ "fixture": "deno run -A --watch=static/,routes/ tests/fixture/dev.ts", "www": "deno task --cwd=www start", "screenshot": "deno run -A www/utils/screenshot.ts", - "check:types": "deno check **/*.ts && deno check **/*.tsx", + "check:types": "deno check **/*.ts **/*.tsx", "ok": "deno fmt --check && deno lint && deno task check:types && deno task test", "install-puppeteer": "PUPPETEER_PRODUCT=chrome deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts && PUPPETEER_PRODUCT=firefox deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts", "test:www": "deno test -A tests/www/", From 099aa051be3ee5440f7d77313a47a46718dfb00c Mon Sep 17 00:00:00 2001 From: Reed von Redwitz Date: Wed, 17 Jan 2024 16:35:12 +0100 Subject: [PATCH 05/13] Revert "chore: simplify deno check:types command" (#2262) Reverts denoland/fresh#2259 Windows is failing with this change: * my PR https://github.com/denoland/fresh/actions/runs/7556810357/job/20574665605?pr=2261 * your PR https://github.com/denoland/fresh/actions/runs/7543217433/job/20533721591 * the commit from merging your PR https://github.com/denoland/fresh/actions/runs/7543221780/job/20533736560 ``` Run deno task check:types Task check:types deno check **/*.ts **/*.tsx Error launching 'deno': The filename or extension is too long. (os error 206) Error: Process completed with exit code 1. ``` --- deno.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno.json b/deno.json index 99dd5de53e9..6cf330a1bff 100644 --- a/deno.json +++ b/deno.json @@ -5,7 +5,7 @@ "fixture": "deno run -A --watch=static/,routes/ tests/fixture/dev.ts", "www": "deno task --cwd=www start", "screenshot": "deno run -A www/utils/screenshot.ts", - "check:types": "deno check **/*.ts **/*.tsx", + "check:types": "deno check **/*.ts && deno check **/*.tsx", "ok": "deno fmt --check && deno lint && deno task check:types && deno task test", "install-puppeteer": "PUPPETEER_PRODUCT=chrome deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts && PUPPETEER_PRODUCT=firefox deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts", "test:www": "deno test -A tests/www/", From 4bd556970af1e2d6227bb3b569a525cd9a8d52c5 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 22 Jan 2024 14:01:42 +0100 Subject: [PATCH 06/13] chore: update esbuild deno loader (#2270) This fixes a bunch of bugs related to `npm:` imports. --- src/build/deps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build/deps.ts b/src/build/deps.ts index 07d31d90979..b475580ea79 100644 --- a/src/build/deps.ts +++ b/src/build/deps.ts @@ -6,5 +6,5 @@ export { toFileUrl, } from "https://deno.land/std@0.211.0/path/mod.ts"; export { escape as regexpEscape } from "https://deno.land/std@0.211.0/regexp/escape.ts"; -export { denoPlugins } from "https://deno.land/x/esbuild_deno_loader@0.8.3/mod.ts"; +export { denoPlugins } from "https://deno.land/x/esbuild_deno_loader@0.8.4/mod.ts"; export { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; From a83765efaa060789d5e2df28151637e7f5533211 Mon Sep 17 00:00:00 2001 From: Reed von Redwitz Date: Tue, 23 Jan 2024 21:51:38 +0100 Subject: [PATCH 07/13] fix: no-window lint error (#2272) Latest PR runs are failing with linting errors, e.g. https://github.com/denoland/fresh/actions/runs/7625625084/job/20770308634?pr=2266 Maybe this shouldn't be happening like this right now, but we'll still need to clean these up eventually. --- src/runtime/entrypoints/main.ts | 8 +++++--- src/runtime/polyfills.ts | 2 ++ tests/base_path_test.ts | 2 +- tests/partials_test.ts | 6 +++--- tests/server_components_test.ts | 2 +- tests/test_utils.ts | 2 +- www/islands/LemonDrop.tsx | 4 +++- 7 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/runtime/entrypoints/main.ts b/src/runtime/entrypoints/main.ts index 8cf9e4119cb..00f1797e3f0 100644 --- a/src/runtime/entrypoints/main.ts +++ b/src/runtime/entrypoints/main.ts @@ -1,3 +1,4 @@ +// deno-lint-ignore-file ban-unknown-rule-code ban-unused-ignore import "../polyfills.ts"; import { Component, @@ -111,6 +112,7 @@ export function revive( ); }; + // deno-lint-ignore no-window "scheduler" in window // `scheduler.postTask` is async but that can easily // fire in the background. We don't want waiting for @@ -892,11 +894,11 @@ function maybeUpdateHistory(nextUrl: URL) { // Only add history entry when URL is new. Still apply // the partials because sometimes users click a link to // "refresh" the current page. - if (nextUrl.href !== window.location.href) { + if (nextUrl.href !== globalThis.location.href) { const state: FreshHistoryState = { index, - scrollX: window.scrollX, - scrollY: window.scrollY, + scrollX: globalThis.scrollX, + scrollY: globalThis.scrollY, }; // Store current scroll position diff --git a/src/runtime/polyfills.ts b/src/runtime/polyfills.ts index 948fa5f1863..1c6e5071f73 100644 --- a/src/runtime/polyfills.ts +++ b/src/runtime/polyfills.ts @@ -1,5 +1,7 @@ +// deno-lint-ignore-file ban-unknown-rule-code ban-unused-ignore // Polyfill for old safari versions if (typeof globalThis === "undefined") { // @ts-ignore polyfill + // deno-lint-ignore no-window window.globalThis = window; } diff --git a/tests/base_path_test.ts b/tests/base_path_test.ts index 8cc93f1ca0e..20533552250 100644 --- a/tests/base_path_test.ts +++ b/tests/base_path_test.ts @@ -116,7 +116,7 @@ Deno.test("rewrites root relative URLs in HTML", async () => { const style = await page.$eval( ".foo", - (el) => window.getComputedStyle(el).color, + (el) => globalThis.getComputedStyle(el).color, ); assertMatch( style, diff --git a/tests/partials_test.ts b/tests/partials_test.ts index d1d34d09496..34b80ef0c28 100644 --- a/tests/partials_test.ts +++ b/tests/partials_test.ts @@ -1204,7 +1204,7 @@ Deno.test("fragment navigation should not scroll to top", async () => { await page.click("a"); await page.waitForFunction(() => location.hash === "#foo"); - const scroll = await page.evaluate(() => window.scrollY); + const scroll = await page.evaluate(() => globalThis.scrollY); assert(scroll > 0, `Did not scroll to fragment`); }, ); @@ -1332,12 +1332,12 @@ Deno.test("merges content", async () => { assertMetaContent(doc, "og:bar", "og value bar"); const color = await page.$eval("h1", (el) => { - return window.getComputedStyle(el).color; + return globalThis.getComputedStyle(el).color; }); assertEquals(color, "rgb(255, 0, 0)"); const textColor = await page.$eval("p", (el) => { - return window.getComputedStyle(el).color; + return globalThis.getComputedStyle(el).color; }); assertEquals(textColor, "rgb(0, 128, 0)"); }, diff --git a/tests/server_components_test.ts b/tests/server_components_test.ts index 330a632a2d2..a32cac6539a 100644 --- a/tests/server_components_test.ts +++ b/tests/server_components_test.ts @@ -157,7 +157,7 @@ Deno.test({ // Check that CSS was applied accordingly const color = await page.$eval("h1", (el) => { - return window.getComputedStyle(el).color; + return globalThis.getComputedStyle(el).color; }); assertEquals(color, "rgb(220, 38, 38)"); }, diff --git a/tests/test_utils.ts b/tests/test_utils.ts index a3ffdb539c4..79d50ea8668 100644 --- a/tests/test_utils.ts +++ b/tests/test_utils.ts @@ -459,7 +459,7 @@ export async function waitForStyle( (s, n, v) => { const el = document.querySelector(s); if (!el) return false; - return window.getComputedStyle(el)[n] === v; + return globalThis.getComputedStyle(el)[n] === v; }, selector, name, diff --git a/www/islands/LemonDrop.tsx b/www/islands/LemonDrop.tsx index 456f0ea7803..e54ecfaa31d 100644 --- a/www/islands/LemonDrop.tsx +++ b/www/islands/LemonDrop.tsx @@ -74,7 +74,9 @@ function LemonDrop() { }, [width.value]); useEffect(() => { - const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); + const mediaQuery = globalThis.matchMedia( + "(prefers-reduced-motion: reduce)", + ); if (mediaQuery.matches) { return; } From 80b420732652e808de4ebb161282b9da7f10e557 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:52:02 +0100 Subject: [PATCH 08/13] chore(deps): bump actions/cache from 3 to 4 (#2271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
Release notes

Sourced from actions/cache's releases.

v4.0.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/cache/compare/v3...v4.0.0

v3.3.3

What's Changed

New Contributors

Full Changelog: https://github.com/actions/cache/compare/v3...v3.3.3

v3.3.2

What's Changed

New Contributors

Full Changelog: https://github.com/actions/cache/compare/v3...v3.3.2

v3.3.1

What's Changed

Full Changelog: https://github.com/actions/cache/compare/v3...v3.3.1

v3.3.0

What's Changed

... (truncated)

Changelog

Sourced from actions/cache's changelog.

Releases

3.0.0

  • Updated minimum runner version support from node 12 -> node 16

3.0.1

  • Added support for caching from GHES 3.5.
  • Fixed download issue for files > 2GB during restore.

3.0.2

  • Added support for dynamic cache size cap on GHES.

3.0.3

  • Fixed avoiding empty cache save when no files are available for caching. (issue)

3.0.4

  • Fixed tar creation error while trying to create tar with path as ~/ home folder on ubuntu-latest. (issue)

3.0.5

  • Removed error handling by consuming actions/cache 3.0 toolkit, Now cache server error handling will be done by toolkit. (PR)

3.0.6

  • Fixed #809 - zstd -d: no such file or directory error
  • Fixed #833 - cache doesn't work with github workspace directory

3.0.7

  • Fixed #810 - download stuck issue. A new timeout is introduced in the download process to abort the download if it gets stuck and doesn't finish within an hour.

3.0.8

  • Fix zstd not working for windows on gnu tar in issues #888 and #891.
  • Allowing users to provide a custom timeout as input for aborting download of a cache segment using an environment variable SEGMENT_DOWNLOAD_TIMEOUT_MINS. Default is 60 minutes.

3.0.9

  • Enhanced the warning message for cache unavailablity in case of GHES.

3.0.10

  • Fix a bug with sorting inputs.
  • Update definition for restore-keys in README.md

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/cache&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13565497377..9a683573d6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: uses: crate-ci/typos@master - name: Cache dependencies and Chrome - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ${{ matrix.cache_path }}deps From 34c875c71cc5c25a1e355bee6fcdf798099f38a9 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Tue, 23 Jan 2024 21:52:30 +0100 Subject: [PATCH 09/13] chore: update esbuild_deno_loader to 0.8.5 (#2273) --- deno.json | 3 +++ src/build/deps.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/deno.json b/deno.json index 6cf330a1bff..859483eb6ab 100644 --- a/deno.json +++ b/deno.json @@ -19,5 +19,8 @@ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" + }, + "lint": { + "rules": { "exclude": ["no-window"] } } } diff --git a/src/build/deps.ts b/src/build/deps.ts index b475580ea79..8931a146e92 100644 --- a/src/build/deps.ts +++ b/src/build/deps.ts @@ -6,5 +6,5 @@ export { toFileUrl, } from "https://deno.land/std@0.211.0/path/mod.ts"; export { escape as regexpEscape } from "https://deno.land/std@0.211.0/regexp/escape.ts"; -export { denoPlugins } from "https://deno.land/x/esbuild_deno_loader@0.8.4/mod.ts"; +export { denoPlugins } from "https://deno.land/x/esbuild_deno_loader@0.8.5/mod.ts"; export { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; From 3f28cc8977229f48285337b8e61f7da09e339354 Mon Sep 17 00:00:00 2001 From: Reed von Redwitz Date: Mon, 29 Jan 2024 14:10:28 +0100 Subject: [PATCH 10/13] chore: upgrade tailwind to 3.4.1 (#2287) --- .vscode/import_map.json | 6 +++--- docs/latest/examples/migrating-to-tailwind.md | 6 +++--- plugins/tailwind.ts | 2 +- plugins/tailwind/compiler.ts | 6 +++--- src/dev/imports.ts | 2 +- tests/fixture_base_path/deno.json | 6 +++--- tests/fixture_base_path_build/deno.json | 2 +- tests/fixture_base_path_config/deno.json | 2 +- tests/fixture_tailwind/deno.json | 2 +- tests/fixture_tailwind_build/deno.json | 2 +- tests/fixture_tailwind_build_2/deno.json | 2 +- tests/fixture_tailwind_config/deno.json | 2 +- www/deno.json | 6 +++--- 13 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.vscode/import_map.json b/.vscode/import_map.json index 60e08d9425a..b3854afd917 100644 --- a/.vscode/import_map.json +++ b/.vscode/import_map.json @@ -17,8 +17,8 @@ "$marked-mangle": "https://esm.sh/marked-mangle@1.0.1", "$fresh-testing-library": "https://deno.land/x/fresh_testing_library@0.11.1/mod.ts", "$fresh-testing-library/": "https://deno.land/x/fresh_testing_library@0.11.1/", - "tailwindcss": "npm:tailwindcss@3.3.5", - "tailwindcss/": "npm:/tailwindcss@3.3.5/", - "tailwindcss/plugin": "npm:/tailwindcss@3.3.5/plugin.js" + "tailwindcss": "npm:tailwindcss@3.4.1", + "tailwindcss/": "npm:/tailwindcss@3.4.1/", + "tailwindcss/plugin": "npm:/tailwindcss@3.4.1/plugin.js" } } diff --git a/docs/latest/examples/migrating-to-tailwind.md b/docs/latest/examples/migrating-to-tailwind.md index e0db41b70a6..e4ac0854d27 100644 --- a/docs/latest/examples/migrating-to-tailwind.md +++ b/docs/latest/examples/migrating-to-tailwind.md @@ -87,9 +87,9 @@ export default { "preact/": "https://esm.sh/preact@10.19.2/", - "twind": "https://esm.sh/twind@0.16.19", - "twind/": "https://esm.sh/twind@0.16.19/", -+ "tailwindcss": "npm:tailwindcss@3.3.5", -+ "tailwindcss/": "npm:/tailwindcss@3.3.5/", -+ "tailwindcss/plugin": "npm:/tailwindcss@3.3.5/plugin.js" ++ "tailwindcss": "npm:tailwindcss@3.4.1", ++ "tailwindcss/": "npm:/tailwindcss@3.4.1/", ++ "tailwindcss/plugin": "npm:/tailwindcss@3.4.1/plugin.js" } } ``` diff --git a/plugins/tailwind.ts b/plugins/tailwind.ts index c0aaca417d1..8383d7debed 100644 --- a/plugins/tailwind.ts +++ b/plugins/tailwind.ts @@ -1,5 +1,5 @@ import { Plugin, PluginMiddleware, ResolvedFreshConfig } from "../server.ts"; -import type postcss from "npm:postcss@8.4.31"; +import type postcss from "npm:postcss@8.4.33"; import * as path from "https://deno.land/std@0.207.0/path/mod.ts"; import { walk } from "https://deno.land/std@0.207.0/fs/walk.ts"; import { TailwindPluginOptions } from "./tailwind/types.ts"; diff --git a/plugins/tailwind/compiler.ts b/plugins/tailwind/compiler.ts index 62af242edce..f75500def28 100644 --- a/plugins/tailwind/compiler.ts +++ b/plugins/tailwind/compiler.ts @@ -1,8 +1,8 @@ import { ResolvedFreshConfig } from "../../server.ts"; import tailwindCss, { Config } from "tailwindcss"; -import postcss from "npm:postcss@8.4.31"; -import cssnano from "npm:cssnano@6.0.1"; -import autoprefixer from "npm:autoprefixer@10.4.16"; +import postcss from "npm:postcss@8.4.33"; +import cssnano from "npm:cssnano@6.0.3"; +import autoprefixer from "npm:autoprefixer@10.4.17"; import * as path from "https://deno.land/std@0.207.0/path/mod.ts"; import { TailwindPluginOptions } from "./types.ts"; diff --git a/src/dev/imports.ts b/src/dev/imports.ts index 3a3ccf5ed77..514c1a41197 100644 --- a/src/dev/imports.ts +++ b/src/dev/imports.ts @@ -3,7 +3,7 @@ export const RECOMMENDED_PREACT_SIGNALS_VERSION = "1.2.1"; export const RECOMMENDED_PREACT_SIGNALS_CORE_VERSION = "1.5.0"; export const RECOMMENDED_TWIND_VERSION = "0.16.19"; export const RECOMMENDED_STD_VERSION = "0.211.0"; -export const RECOMMENDED_TAILIWIND_VERSION = "3.3.5"; +export const RECOMMENDED_TAILIWIND_VERSION = "3.4.1"; export function freshImports(imports: Record) { imports["$fresh/"] = new URL("../../", import.meta.url).href; diff --git a/tests/fixture_base_path/deno.json b/tests/fixture_base_path/deno.json index 7802f6a00ee..8c3bdc24237 100644 --- a/tests/fixture_base_path/deno.json +++ b/tests/fixture_base_path/deno.json @@ -10,9 +10,9 @@ "@preact/signals": "https://esm.sh/*@preact/signals@1.2.2", "@preact/signals-core": "https://esm.sh/@preact/signals-core@1.5.0", "preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.1.0", - "tailwindcss": "npm:tailwindcss@3.3.5", - "tailwindcss/": "npm:/tailwindcss@3.3.5/", - "tailwindcss/plugin": "npm:/tailwindcss@3.3.5/plugin.js" + "tailwindcss": "npm:tailwindcss@3.4.1", + "tailwindcss/": "npm:/tailwindcss@3.4.1/", + "tailwindcss/plugin": "npm:/tailwindcss@3.4.1/plugin.js" }, "compilerOptions": { "jsx": "react-jsx", diff --git a/tests/fixture_base_path_build/deno.json b/tests/fixture_base_path_build/deno.json index d9ccbefafc5..6e495542eda 100644 --- a/tests/fixture_base_path_build/deno.json +++ b/tests/fixture_base_path_build/deno.json @@ -4,7 +4,7 @@ "$fresh/": "../../", "preact": "https://esm.sh/preact@10.15.1", "preact/": "https://esm.sh/preact@10.15.1/", - "tailwindcss": "npm:tailwindcss@3.3.5" + "tailwindcss": "npm:tailwindcss@3.4.1" }, "compilerOptions": { "jsx": "react-jsx", diff --git a/tests/fixture_base_path_config/deno.json b/tests/fixture_base_path_config/deno.json index d9ccbefafc5..6e495542eda 100644 --- a/tests/fixture_base_path_config/deno.json +++ b/tests/fixture_base_path_config/deno.json @@ -4,7 +4,7 @@ "$fresh/": "../../", "preact": "https://esm.sh/preact@10.15.1", "preact/": "https://esm.sh/preact@10.15.1/", - "tailwindcss": "npm:tailwindcss@3.3.5" + "tailwindcss": "npm:tailwindcss@3.4.1" }, "compilerOptions": { "jsx": "react-jsx", diff --git a/tests/fixture_tailwind/deno.json b/tests/fixture_tailwind/deno.json index d9ccbefafc5..6e495542eda 100644 --- a/tests/fixture_tailwind/deno.json +++ b/tests/fixture_tailwind/deno.json @@ -4,7 +4,7 @@ "$fresh/": "../../", "preact": "https://esm.sh/preact@10.15.1", "preact/": "https://esm.sh/preact@10.15.1/", - "tailwindcss": "npm:tailwindcss@3.3.5" + "tailwindcss": "npm:tailwindcss@3.4.1" }, "compilerOptions": { "jsx": "react-jsx", diff --git a/tests/fixture_tailwind_build/deno.json b/tests/fixture_tailwind_build/deno.json index d9ccbefafc5..6e495542eda 100644 --- a/tests/fixture_tailwind_build/deno.json +++ b/tests/fixture_tailwind_build/deno.json @@ -4,7 +4,7 @@ "$fresh/": "../../", "preact": "https://esm.sh/preact@10.15.1", "preact/": "https://esm.sh/preact@10.15.1/", - "tailwindcss": "npm:tailwindcss@3.3.5" + "tailwindcss": "npm:tailwindcss@3.4.1" }, "compilerOptions": { "jsx": "react-jsx", diff --git a/tests/fixture_tailwind_build_2/deno.json b/tests/fixture_tailwind_build_2/deno.json index d9ccbefafc5..6e495542eda 100644 --- a/tests/fixture_tailwind_build_2/deno.json +++ b/tests/fixture_tailwind_build_2/deno.json @@ -4,7 +4,7 @@ "$fresh/": "../../", "preact": "https://esm.sh/preact@10.15.1", "preact/": "https://esm.sh/preact@10.15.1/", - "tailwindcss": "npm:tailwindcss@3.3.5" + "tailwindcss": "npm:tailwindcss@3.4.1" }, "compilerOptions": { "jsx": "react-jsx", diff --git a/tests/fixture_tailwind_config/deno.json b/tests/fixture_tailwind_config/deno.json index d9ccbefafc5..6e495542eda 100644 --- a/tests/fixture_tailwind_config/deno.json +++ b/tests/fixture_tailwind_config/deno.json @@ -4,7 +4,7 @@ "$fresh/": "../../", "preact": "https://esm.sh/preact@10.15.1", "preact/": "https://esm.sh/preact@10.15.1/", - "tailwindcss": "npm:tailwindcss@3.3.5" + "tailwindcss": "npm:tailwindcss@3.4.1" }, "compilerOptions": { "jsx": "react-jsx", diff --git a/www/deno.json b/www/deno.json index 273711ff8d3..555a1c2c9e5 100644 --- a/www/deno.json +++ b/www/deno.json @@ -17,9 +17,9 @@ "$marked-mangle": "https://esm.sh/marked-mangle@1.0.1", "$fresh-testing-library": "https://deno.land/x/fresh_testing_library@0.8.0/mod.ts", "$fresh-testing-library/": "https://deno.land/x/fresh_testing_library@0.8.0/", - "tailwindcss": "npm:tailwindcss@3.3.5", - "tailwindcss/": "npm:/tailwindcss@3.3.5/", - "tailwindcss/plugin": "npm:/tailwindcss@3.3.5/plugin.js" + "tailwindcss": "npm:tailwindcss@3.4.1", + "tailwindcss/": "npm:/tailwindcss@3.4.1/", + "tailwindcss/plugin": "npm:/tailwindcss@3.4.1/plugin.js" }, "compilerOptions": { "jsx": "react-jsx", From 2652b85532b24802c4c65164ab815b810eda5dc7 Mon Sep 17 00:00:00 2001 From: Benjamin Jesuiter Date: Mon, 29 Jan 2024 14:13:32 +0100 Subject: [PATCH 11/13] fix: GH actions warning by upgrading to v4 #2285 (#2286) I tested the change in my own repo, the warning on github disapears for the "actions/checkout" action. It is still there for the denoland/setup-deno action, but an upgrade PR is already ongoing for this action, see: https://github.com/denoland/setup-deno/pull/56 --- docs/latest/concepts/ahead-of-time-builds.md | 2 +- src/dev/imports.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/latest/concepts/ahead-of-time-builds.md b/docs/latest/concepts/ahead-of-time-builds.md index ee0aeb645fe..42ad614db83 100644 --- a/docs/latest/concepts/ahead-of-time-builds.md +++ b/docs/latest/concepts/ahead-of-time-builds.md @@ -81,7 +81,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Deno uses: denoland/setup-deno@v1 diff --git a/src/dev/imports.ts b/src/dev/imports.ts index 514c1a41197..5a1d967ea22 100644 --- a/src/dev/imports.ts +++ b/src/dev/imports.ts @@ -50,7 +50,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Deno uses: denoland/setup-deno@v1 From dfabf5035e2abe7a30c440b8771b6011a8b0f228 Mon Sep 17 00:00:00 2001 From: Aarvin R <144612896+aarvinr@users.noreply.github.com> Date: Mon, 29 Jan 2024 05:19:09 -0800 Subject: [PATCH 12/13] docs: add link to deno (#2281) This pull request replaces a code snippet mentioning Deno (`deno`) with a link to deno.com ([deno](https://deno.com/)), and will close #2280. --- docs/latest/introduction/index.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/latest/introduction/index.md b/docs/latest/introduction/index.md index 4c6874491d3..87960c26c31 100644 --- a/docs/latest/introduction/index.md +++ b/docs/latest/introduction/index.md @@ -24,9 +24,9 @@ necessary transpilation of TypeScript or JSX to plain JavaScript is done on the fly, just when it is needed. This allows for insanely fast iteration loops and very very fast deployments. -Fresh projects can be deployed manually to any platform with `deno`, but it is -intended to be deployed to an edge runtime like [Deno Deploy][deno-deploy] for -the best experience. +Fresh projects can be deployed manually to any platform with [Deno][deno], but +it is intended to be deployed to an edge runtime like [Deno Deploy][deno-deploy] +for the best experience. Some stand out features: @@ -41,4 +41,5 @@ Some stand out features: - File-system routing à la Next.js [preact]: https://preactjs.com +[deno]: https://deno.com [deno-deploy]: https://deno.com/deploy From d19c22d5921bdf21de9a7d7bc043b8523ac81893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roj=20=5Bro=CB=90=CA=92=5D?= Date: Tue, 30 Jan 2024 14:34:38 +0300 Subject: [PATCH 13/13] feat: add back twind in init wizard (#2290) --- init.ts | 89 +++++++++++++++++++++++++++++++++++-------- src/dev/imports.ts | 12 ++++-- tests/init_test.ts | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 19 deletions(-) diff --git a/init.ts b/init.ts index 2d82fbaef06..9a28f7ce46b 100644 --- a/init.ts +++ b/init.ts @@ -6,6 +6,7 @@ import { dotenvImports, freshImports, tailwindImports, + twindImports, } from "./src/dev/imports.ts"; ensureMinDenoVersion(); @@ -26,22 +27,26 @@ USAGE: OPTIONS: --force Overwrite existing files - --tailwind Setup project to use 'tailwind' for styling - --vscode Setup project for VSCode + --tailwind Use Tailwind for styling + --twind Use Twind for styling + --vscode Setup project for VS Code --docker Setup Project to use Docker `; const CONFIRM_EMPTY_MESSAGE = "The target directory is not empty (files could get overwritten). Do you want to continue anyway?"; -const USE_TAILWIND_MESSAGE = - "Fresh has built in support for styling using Tailwind CSS. Do you want to use this?"; - const USE_VSCODE_MESSAGE = "Do you use VS Code?"; const flags = parse(Deno.args, { - boolean: ["force", "tailwind", "vscode", "docker", "help"], - default: { "force": null, "tailwind": null, "vscode": null, "docker": null }, + boolean: ["force", "tailwind", "twind", "vscode", "docker", "help"], + default: { + force: null, + tailwind: null, + twind: null, + vscode: null, + docker: null, + }, alias: { help: "h", }, @@ -52,6 +57,10 @@ if (flags.help) { Deno.exit(0); } +if (flags.tailwind && flags.twind) { + error("Cannot use Tailwind and Twind at the same time."); +} + console.log(); console.log( colors.bgRgb8( @@ -90,9 +99,25 @@ try { } console.log("%cLet's set up your new Fresh project.\n", "font-weight: bold"); -const useTailwind = flags.tailwind === null - ? confirm(USE_TAILWIND_MESSAGE) - : flags.tailwind; +let useTailwind = flags.tailwind || false; +let useTwind = flags.twind || false; + +if (flags.tailwind == null && flags.twind == null) { + if (confirm("Do you want to use a styling library?")) { + console.log(); + console.log("1. Tailwind"); + console.log("2. Twind"); + switch ( + (prompt("Which styling library do you want to use? [1]") || "1").trim() + ) { + case "2": + useTwind = true; + break; + default: + useTailwind = true; + } + } +} const useVSCode = flags.vscode === null ? confirm(USE_VSCODE_MESSAGE) @@ -322,6 +347,24 @@ if (useTailwind) { ); } +const TWIND_CONFIG_TS = `import { defineConfig, Preset } from "@twind/core"; +import presetTailwind from "@twind/preset-tailwind"; +import presetAutoprefix from "@twind/preset-autoprefix"; + +export default { + ...defineConfig({ + presets: [presetTailwind() as Preset, presetAutoprefix() as Preset], + }), + selfURL: import.meta.url, +}; +`; +if (useTwind) { + await Deno.writeTextFile( + join(resolvedDirectory, "twind.config.ts"), + TWIND_CONFIG_TS, + ); +} + const NO_TAILWIND_STYLES = ` *, *::before, @@ -461,7 +504,7 @@ export default function App({ Component }: PageProps) { ${basename(resolvedDirectory)} - + ${useTwind ? "" : ``} @@ -481,10 +524,12 @@ const TAILWIND_CSS = `@tailwind base; @tailwind utilities;`; const cssStyles = useTailwind ? TAILWIND_CSS : NO_TAILWIND_STYLES; -await Deno.writeTextFile( - join(resolvedDirectory, "static", "styles.css"), - cssStyles, -); +if (!useTwind) { + await Deno.writeTextFile( + join(resolvedDirectory, "static", "styles.css"), + cssStyles, + ); +} const STATIC_LOGO = ` @@ -515,10 +560,19 @@ if (useTailwind) { FRESH_CONFIG_TS += `import tailwind from "$fresh/plugins/tailwind.ts"; `; } +if (useTwind) { + FRESH_CONFIG_TS += `import twind from "$fresh/plugins/twindv1.ts"; +import twindConfig from "./twind.config.ts"; +`; +} FRESH_CONFIG_TS += ` export default defineConfig({${ - useTailwind ? `\n plugins: [tailwind()],\n` : "" + useTailwind + ? `\n plugins: [tailwind()],\n` + : useTwind + ? `\n plugins: [twind(twindConfig)],\n` + : "" }}); `; const CONFIG_TS_PATH = join(resolvedDirectory, "fresh.config.ts"); @@ -592,6 +646,9 @@ if (useTailwind) { // deno-lint-ignore no-explicit-any (config as any).nodeModulesDir = true; } +if (useTwind) { + twindImports(config.imports); +} dotenvImports(config.imports); const DENO_CONFIG = JSON.stringify(config, null, 2) + "\n"; diff --git a/src/dev/imports.ts b/src/dev/imports.ts index 5a1d967ea22..14689364cc2 100644 --- a/src/dev/imports.ts +++ b/src/dev/imports.ts @@ -1,7 +1,9 @@ export const RECOMMENDED_PREACT_VERSION = "10.19.2"; export const RECOMMENDED_PREACT_SIGNALS_VERSION = "1.2.1"; export const RECOMMENDED_PREACT_SIGNALS_CORE_VERSION = "1.5.0"; -export const RECOMMENDED_TWIND_VERSION = "0.16.19"; +export const RECOMMENDED_TWIND_CORE_VERSION = "1.1.3"; +export const RECOMMENDED_TWIND_PRESET_AUTOPREFIX_VERSION = "1.0.7"; +export const RECOMMENDED_TWIND_PRESET_TAILWIND_VERSION = "1.1.4"; export const RECOMMENDED_STD_VERSION = "0.211.0"; export const RECOMMENDED_TAILIWIND_VERSION = "3.4.1"; @@ -16,8 +18,12 @@ export function freshImports(imports: Record) { } export function twindImports(imports: Record) { - imports["twind"] = `https://esm.sh/twind@${RECOMMENDED_TWIND_VERSION}`; - imports["twind/"] = `https://esm.sh/twind@${RECOMMENDED_TWIND_VERSION}/`; + imports["@twind/core"] = + `https://esm.sh/@twind/core@${RECOMMENDED_TWIND_CORE_VERSION}`; + imports["@twind/preset-tailwind"] = + `https://esm.sh/@twind/preset-tailwind@${RECOMMENDED_TWIND_PRESET_TAILWIND_VERSION}/`; + imports["@twind/preset-autoprefix"] = + `https://esm.sh/@twind/preset-autoprefix@${RECOMMENDED_TWIND_PRESET_AUTOPREFIX_VERSION}/`; } export function tailwindImports(imports: Record) { diff --git a/tests/init_test.ts b/tests/init_test.ts index f24447165f5..347e9967542 100644 --- a/tests/init_test.ts +++ b/tests/init_test.ts @@ -242,6 +242,100 @@ Deno.test({ }, }); +Deno.test({ + name: "fresh-init --twind --vscode", + async fn(t) { + // Preparation + const tmpDirName = await Deno.makeTempDir(); + + await t.step("execute init command", async () => { + const cliProcess = new Deno.Command(Deno.execPath(), { + args: [ + "run", + "-A", + "init.ts", + tmpDirName, + "--twind", + "--vscode", + ], + stdin: "null", + stdout: "null", + }); + const { code } = await cliProcess.output(); + assertEquals(code, 0); + }); + + const files = [ + "/README.md", + "/fresh.gen.ts", + "/twind.config.ts", + "/components/Button.tsx", + "/islands/Counter.tsx", + "/main.ts", + "/routes/greet/[name].tsx", + "/routes/api/joke.ts", + "/routes/_app.tsx", + "/routes/index.tsx", + "/static/logo.svg", + "/.vscode/settings.json", + "/.vscode/extensions.json", + "/.gitignore", + ]; + + await t.step("check generated files", async () => { + await assertFileExistence(files, tmpDirName); + }); + + await t.step("start up the server and access the root page", async () => { + const { serverProcess, lines, address } = await startFreshServer({ + args: ["run", "-A", "--check", "main.ts"], + cwd: tmpDirName, + }); + + await delay(100); + + // Access the root page + const res = await fetch(address); + await res.body?.cancel(); + assertEquals(res.status, STATUS_CODE.OK); + + // verify the island is revived. + const browser = await puppeteer.launch({ args: ["--no-sandbox"] }); + const page = await browser.newPage(); + await page.goto(address, { waitUntil: "networkidle2" }); + + const counter = await page.$("body > div > div > div > p"); + let counterValue = await counter?.evaluate((el) => el.textContent); + assertEquals(counterValue, "3"); + + const fontWeight = await counter?.evaluate((el) => + getComputedStyle(el).fontWeight + ); + assertEquals(fontWeight, "400"); + + const buttonPlus = await page.$( + "body > div > div > div > button:nth-child(3)", + ); + await buttonPlus?.click(); + + await waitForText(page, "body > div > div > div > p", "4"); + + counterValue = await counter?.evaluate((el) => el.textContent); + assert(counterValue === "4"); + await page.close(); + await browser.close(); + + serverProcess.kill("SIGTERM"); + await serverProcess.status; + + // Drain the lines stream + for await (const _ of lines) { /* noop */ } + }); + + await retry(() => Deno.remove(tmpDirName, { recursive: true })); + }, +}); + Deno.test("fresh-init error(help)", async function (t) { const includeText = "fresh-init";