From 034674c88c5eab59d1cf8883951daa60693fb95c Mon Sep 17 00:00:00 2001 From: Drew Powers <1369770+drwpow@users.noreply.github.com> Date: Wed, 14 Apr 2021 13:21:25 -0600 Subject: [PATCH] Add Windows Support (#93) * Add Windows to test suite * Try implicit URL --- .github/workflows/nodejs.yml | 6 +++--- src/build.ts | 7 ++++--- src/build/bundle.ts | 5 +++-- src/compiler/transform/styles.ts | 3 ++- src/runtime.ts | 11 ++++++----- src/search.ts | 8 +++++--- test/astro-doctype.test.js | 3 ++- test/astro-markdown.test.js | 3 ++- test/astro-styles-ssr.test.js | 12 +++++++++--- test/helpers.js | 3 ++- test/react-component.test.js | 3 ++- test/snowpack-integration.test.js | 11 +++++++---- 12 files changed, 47 insertions(+), 28 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 369d07563..9890f393d 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -4,11 +4,11 @@ on: [push] jobs: test: - runs-on: ubuntu-latest - + runs-on: ${{ matrix.os }} strategy: matrix: - node-version: [14.x] + os: [ubuntu-latest, windows-latest] + node-version: [14.x, 15.x] steps: - uses: actions/checkout@v1 diff --git a/src/build.ts b/src/build.ts index 37ef560d6..51cdc6e56 100644 --- a/src/build.ts +++ b/src/build.ts @@ -4,6 +4,7 @@ import type { LoadResult } from './runtime'; import { existsSync, promises as fsPromises } from 'fs'; import { relative as pathRelative } from 'path'; +import { fileURLToPath } from 'url'; import { fdir } from 'fdir'; import { defaultLogDestination, error } from './logger.js'; import { createRuntime } from './runtime.js'; @@ -22,7 +23,7 @@ async function allPages(root: URL) { const api = new fdir() .filter((p) => /\.(astro|md)$/.test(p)) .withFullPaths() - .crawl(root.pathname); + .crawl(fileURLToPath(root)); const files = await api.withPromise(); return files as string[]; } @@ -47,7 +48,7 @@ async function writeResult(result: LoadResult, outPath: URL, encoding: null | 'u if (result.statusCode === 500 || result.statusCode === 404) { error(logging, 'build', result.error || result.statusCode); } else if (result.statusCode !== 200) { - error(logging, 'build', `Unexpected load result (${result.statusCode}) for ${outPath.pathname}`); + error(logging, 'build', `Unexpected load result (${result.statusCode}) for ${fileURLToPath(outPath)}`); } else { const bytes = result.contents; await writeFilep(outPath, bytes, encoding); @@ -119,7 +120,7 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> { if (existsSync(astroConfig.public)) { const pub = astroConfig.public; - const publicFiles = (await new fdir().withFullPaths().crawl(pub.pathname).withPromise()) as string[]; + const publicFiles = (await new fdir().withFullPaths().crawl(fileURLToPath(pub)).withPromise()) as string[]; for (const filepath of publicFiles) { const fileUrl = new URL(`file://${filepath}`); const rel = pathRelative(pub.pathname, fileUrl.pathname); diff --git a/src/build/bundle.ts b/src/build/bundle.ts index a0f61289e..0a5961986 100644 --- a/src/build/bundle.ts +++ b/src/build/bundle.ts @@ -6,6 +6,7 @@ import type { LogOptions } from '../logger'; import esbuild from 'esbuild'; import { promises as fsPromises } from 'fs'; +import { fileURLToPath } from 'url'; import { parse } from '../parser/index.js'; import { transform } from '../compiler/transform/index.js'; import { convertMdToAstroSource } from '../compiler/index.js'; @@ -93,7 +94,7 @@ export async function collectDynamicImports(filename: URL, { astroConfig, loggin } await transform(ast, { - filename: filename.pathname, + filename: fileURLToPath(filename), fileID: '', compileOptions: { astroConfig, @@ -281,7 +282,7 @@ export async function bundle(imports: Set, { runtime, dist }: BundleOpti const build = await rollup(inputOptions); const outputOptions: OutputOptions = { - dir: dist.pathname, + dir: fileURLToPath(dist), format: 'esm', exports: 'named', entryFileNames(chunk) { diff --git a/src/compiler/transform/styles.ts b/src/compiler/transform/styles.ts index bb07f3267..77eedfa89 100644 --- a/src/compiler/transform/styles.ts +++ b/src/compiler/transform/styles.ts @@ -2,6 +2,7 @@ import crypto from 'crypto'; import fs from 'fs'; import { createRequire } from 'module'; import path from 'path'; +import { fileURLToPath } from 'url'; import autoprefixer from 'autoprefixer'; import postcss, { Plugin } from 'postcss'; import postcssKeyframes from 'postcss-icss-keyframes'; @@ -165,7 +166,7 @@ export default function transformStyles({ compileOptions, filename, fileID }: Tr if (miniCache.tailwindEnabled === undefined) { const tailwindNames = ['tailwind.config.js', 'tailwind.config.mjs']; for (const loc of tailwindNames) { - const tailwindLoc = path.join(compileOptions.astroConfig.projectRoot.pathname, loc); + const tailwindLoc = path.join(fileURLToPath(compileOptions.astroConfig.projectRoot), loc); if (fs.existsSync(tailwindLoc)) { miniCache.tailwindEnabled = true; // Success! We have a Tailwind config file. debug(compileOptions.logging, 'tailwind', 'Found config. Enabling.'); diff --git a/src/runtime.ts b/src/runtime.ts index 583fb97b5..24e186e1c 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -1,3 +1,4 @@ +import { fileURLToPath } from 'url'; import type { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, SnowpackConfig } from 'snowpack'; import type { AstroConfig, CollectionResult, CreateCollection, Params, RuntimeMode } from './@types/astro'; import type { LogOptions } from './logger'; @@ -223,19 +224,19 @@ async function createSnowpack(astroConfig: AstroConfig, env: Record }; const mountOptions = { - [astroRoot.pathname]: '/_astro', - [internalPath.pathname]: '/_astro_internal', + [fileURLToPath(astroRoot)]: '/_astro', + [fileURLToPath(internalPath)]: '/_astro_internal', }; if (existsSync(astroConfig.public)) { - mountOptions[astroConfig.public.pathname] = '/'; + mountOptions[fileURLToPath(astroConfig.public)] = '/'; } const snowpackConfig = await loadConfiguration({ - root: projectRoot.pathname, + root: fileURLToPath(projectRoot), mount: mountOptions, plugins: [ - [new URL('../snowpack-plugin.cjs', import.meta.url).pathname, astroPlugOptions], + [fileURLToPath(new URL('../snowpack-plugin.cjs', import.meta.url)), astroPlugOptions], require.resolve('@snowpack/plugin-sass'), require.resolve('@snowpack/plugin-svelte'), require.resolve('@snowpack/plugin-vue'), diff --git a/src/search.ts b/src/search.ts index 64a9c4782..d4ed73f96 100644 --- a/src/search.ts +++ b/src/search.ts @@ -1,5 +1,6 @@ import { existsSync } from 'fs'; import path from 'path'; +import { fileURLToPath } from 'url'; import { fdir, PathsOutput } from 'fdir'; interface PageLocation { @@ -99,9 +100,10 @@ const crawler = new fdir(); /** load a collection route */ function loadCollection(url: string, astroRoot: URL): { currentPage?: number; location: PageLocation } | undefined { - const pages = (crawler.glob('**/*').crawl(path.join(astroRoot.pathname, 'pages')).sync() as PathsOutput).filter( - (filepath) => filepath.startsWith('$') || filepath.includes('/$') - ); + const pages = (crawler + .glob('**/*') + .crawl(path.join(fileURLToPath(astroRoot), 'pages')) + .sync() as PathsOutput).filter((filepath) => filepath.startsWith('$') || filepath.includes('/$')); for (const pageURL of pages) { const reqURL = new RegExp('^/' + pageURL.replace(/\$([^/]+)\.astro/, '$1') + '/?(.*)'); const match = url.match(reqURL); diff --git a/test/astro-doctype.test.js b/test/astro-doctype.test.js index c71aee1de..b6c37c3d7 100644 --- a/test/astro-doctype.test.js +++ b/test/astro-doctype.test.js @@ -1,3 +1,4 @@ +import { fileURLToPath } from 'url'; import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import { loadConfig } from '../lib/config.js'; @@ -9,7 +10,7 @@ let runtime, setupError; DType.before(async () => { try { - const astroConfig = await loadConfig(new URL('./fixtures/astro-doctype', import.meta.url).pathname); + const astroConfig = await loadConfig(fileURLToPath(new URL('./fixtures/astro-doctype', import.meta.url))); const logging = { level: 'error', diff --git a/test/astro-markdown.test.js b/test/astro-markdown.test.js index 1651a0a6f..4d6a4f438 100644 --- a/test/astro-markdown.test.js +++ b/test/astro-markdown.test.js @@ -1,5 +1,6 @@ import { existsSync, promises as fsPromises } from 'fs'; import { join } from 'path'; +import { fileURLToPath } from 'url'; import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import { createRuntime } from '../lib/runtime.js'; @@ -14,7 +15,7 @@ const Markdown = suite('Astro Markdown'); let runtime, setupError, fixturePath, astroConfig; Markdown.before(async () => { - fixturePath = new URL('./fixtures/astro-markdown', import.meta.url).pathname; + fixturePath = fileURLToPath(new URL('./fixtures/astro-markdown', import.meta.url)); astroConfig = await loadConfig(fixturePath); diff --git a/test/astro-styles-ssr.test.js b/test/astro-styles-ssr.test.js index af3cccd3b..0f113c128 100644 --- a/test/astro-styles-ssr.test.js +++ b/test/astro-styles-ssr.test.js @@ -42,14 +42,20 @@ StylesSSR('Has correct CSS classes', async ({ runtime }) => { const MUST_HAVE_CLASSES = { '#react-css': 'react-title', '#vue-css': 'vue-title', - '#vue-css-modules': '_title_1gi0u_2', // ⚠️ may be flaky + '#vue-css-modules': 'title', // ⚠️ this is the inverse '#vue-scoped': 'vue-title', // also has data-v-* property '#svelte-scoped': 'svelte-title', // also has additional class }; for (const [selector, className] of Object.entries(MUST_HAVE_CLASSES)) { const el = $(selector); - assert.ok(el.attr('class').includes(className)); + if (selector === '#vue-css-modules') { + // this will generate differently on Unix vs Windows. Here we simply test that it has transformed + assert.not.equal(el.attr('class'), 'title'); + } else { + // if this is not a CSS module, it should remain as expected + assert.ok(el.attr('class').includes(className)); + } // add’l test: Vue Scoped styles should have data-v-* attribute if (selector === '#vue-scoped') { @@ -81,7 +87,7 @@ StylesSSR('CSS Module support in .astro', async ({ runtime }) => { }) ); - assert.equal(css, `.wrapper${scopedClass}{margin-left:auto;margin-right:auto;max-width:1200px}`); + assert.match(css, `.wrapper${scopedClass}{margin-left:auto;margin-right:auto;max-width:1200px}`); // test 2: element received .astro-XXXXXX class (this selector will succeed if transformed correctly) const wrapper = $(`.wrapper${scopedClass}`); diff --git a/test/helpers.js b/test/helpers.js index 764208c17..aa2e31714 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -1,3 +1,4 @@ +import { fileURLToPath } from 'url'; import { createRuntime } from '../lib/runtime.js'; import { loadConfig } from '../lib/config.js'; import * as assert from 'uvu/assert'; @@ -6,7 +7,7 @@ export function setup(Suite, fixturePath) { let runtime, setupError; Suite.before(async (context) => { - const astroConfig = await loadConfig(new URL(fixturePath, import.meta.url).pathname); + const astroConfig = await loadConfig(fileURLToPath(new URL(fixturePath, import.meta.url))); const logging = { level: 'error', diff --git a/test/react-component.test.js b/test/react-component.test.js index ea0cb51b7..7de92cb0a 100644 --- a/test/react-component.test.js +++ b/test/react-component.test.js @@ -1,3 +1,4 @@ +import { fileURLToPath } from 'url'; import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import { createRuntime } from '../lib/runtime.js'; @@ -9,7 +10,7 @@ const React = suite('React Components'); let runtime, setupError; React.before(async () => { - const astroConfig = await loadConfig(new URL('./fixtures/react-component', import.meta.url).pathname); + const astroConfig = await loadConfig(fileURLToPath(new URL('./fixtures/react-component', import.meta.url))); const logging = { level: 'error', diff --git a/test/snowpack-integration.test.js b/test/snowpack-integration.test.js index 2583aab5e..9a29bf16e 100644 --- a/test/snowpack-integration.test.js +++ b/test/snowpack-integration.test.js @@ -1,3 +1,4 @@ +import { fileURLToPath } from 'url'; import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import { createRuntime } from '../lib/runtime.js'; @@ -15,9 +16,9 @@ let runtime, cwd, setupError; SnowpackDev.before(async () => { // Bug: Snowpack config is still loaded relative to the current working directory. cwd = process.cwd(); - process.chdir(new URL('../examples/snowpack/', import.meta.url).pathname); + process.chdir(fileURLToPath(new URL('../examples/snowpack/', import.meta.url))); - const astroConfig = await loadConfig(new URL('../examples/snowpack', import.meta.url).pathname); + const astroConfig = await loadConfig(fileURLToPath(new URL('../examples/snowpack', import.meta.url))); const logging = { level: 'error', @@ -53,9 +54,11 @@ async function* allPageFiles(root) { /** create an iterator for all pages and yield the relative paths */ async function* allPages(root) { for await (let fileURL of allPageFiles(root)) { - let bare = fileURL.pathname.replace(/\.(astro|md)$/, '').replace(/index$/, ''); + let bare = fileURLToPath(fileURL) + .replace(/\.(astro|md)$/, '') + .replace(/index$/, ''); - yield '/' + pathRelative(root.pathname, bare); + yield '/' + pathRelative(fileURLToPath(root), bare); } }