Skip to content

Commit a3027dd

Browse files
mythmonmbostock
andauthored
windows support (#944)
* disable autocrlf * test on windows * cross-spawn * rimraf * use cross-env to run mocha * fix tests with posix paths * one less op * mostly posix Co-authored-by: Mike Bostock <[email protected]> * fix data loader and config tests * more tests * all tests passing * tweak gitattributes * test fixes * .gitattributes, take 3? * fix empty inversion * fix isEmpty logic, again * Update src/files.ts * add rimraf to templates --------- Co-authored-by: Mike Bostock <[email protected]> Co-authored-by: Mike Bostock <[email protected]>
1 parent fcbca53 commit a3027dd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+437
-98
lines changed

.gitattributes

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Ask git to not change line endings (to preserve content hashes).
2+
* -text

.github/workflows/test.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ jobs:
1111
strategy:
1212
matrix:
1313
version: [20, 21]
14-
runs-on: ubuntu-latest
14+
os: [ubuntu-latest, windows-latest]
15+
fail-fast: false
16+
runs-on: ${{ matrix.os }}
1517
steps:
1618
- uses: actions/checkout@v4
1719
- uses: actions/setup-node@v4
@@ -28,7 +30,7 @@ jobs:
2830
- uses: actions/upload-artifact@v3
2931
if: failure()
3032
with:
31-
name: test-output-changes
33+
name: test-output-changes-${{ matrix.os }}-${{ matrix.version }}
3234
path: |
3335
test/output/*-changed.*
3436
test/output/build/*-changed/

bin/observable-init.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
#!/usr/bin/env -S node --no-warnings=ExperimentalWarning
1+
#!/usr/bin/env node
2+
import {fileURLToPath} from "node:url";
3+
import crossSpawn from "cross-spawn";
24

3-
await import("tsx/esm");
4-
await import("./observable.ts");
5+
crossSpawn.sync(
6+
"node",
7+
[
8+
"--no-warnings=ExperimentalWarning",
9+
"--import",
10+
"tsx/esm",
11+
fileURLToPath(import.meta.resolve("./observable.ts")),
12+
...process.argv.slice(2)
13+
],
14+
{stdio: "inherit"}
15+
);

package.json

+8-4
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222
"observable": "bin/observable-init.js"
2323
},
2424
"scripts": {
25-
"dev": "rm -f docs/themes.md docs/theme/*.md && (tsx watch docs/theme/generate-themes.ts & tsx watch --no-warnings=ExperimentalWarning ./bin/observable.ts preview --no-open)",
26-
"build": "yarn rebuild-themes && rm -rf dist && tsx --no-warnings=ExperimentalWarning ./bin/observable.ts build",
25+
"dev": "rimraf --glob docs/themes.md docs/theme/*.md && (tsx watch docs/theme/generate-themes.ts & tsx watch --no-warnings=ExperimentalWarning ./bin/observable.ts preview --no-open)",
26+
"build": "yarn rebuild-themes && rimraf dist && tsx --no-warnings=ExperimentalWarning ./bin/observable.ts build",
2727
"deploy": "yarn rebuild-themes && tsx --no-warnings=ExperimentalWarning ./bin/observable.ts deploy",
28-
"rebuild-themes": "rm -f docs/themes.md docs/theme/*.md && tsx docs/theme/generate-themes.ts",
28+
"rebuild-themes": "rimraf --glob docs/themes.md docs/theme/*.md && tsx docs/theme/generate-themes.ts",
2929
"test": "yarn test:mocha && yarn test:tsc && yarn test:lint && yarn test:prettier",
3030
"test:coverage": "c8 yarn test:mocha",
31-
"test:mocha": "rm -rf test/.observablehq/cache test/input/build/*/.observablehq/cache && OBSERVABLE_TELEMETRY_DISABLE=1 TZ=America/Los_Angeles tsx --no-warnings=ExperimentalWarning ./node_modules/.bin/mocha 'test/**/*-test.*'",
31+
"test:mocha": "rimraf --glob test/.observablehq/cache test/input/build/*/.observablehq/cache && cross-env OBSERVABLE_TELEMETRY_DISABLE=1 TZ=America/Los_Angeles tsx --no-warnings=ExperimentalWarning ./node_modules/mocha/bin/mocha.js 'test/**/*-test.*'",
3232
"test:lint": "eslint src test --max-warnings=0",
3333
"test:prettier": "prettier --check src test",
3434
"test:tsc": "tsc --noEmit",
@@ -53,6 +53,8 @@
5353
"acorn": "^8.11.2",
5454
"acorn-walk": "^8.3.0",
5555
"ci-info": "^4.0.0",
56+
"cross-env": "^7.0.3",
57+
"cross-spawn": "^7.0.3",
5658
"esbuild": "^0.19.8",
5759
"fast-array-diff": "^1.1.0",
5860
"gray-matter": "^4.0.3",
@@ -78,6 +80,7 @@
7880
"ws": "^8.14.2"
7981
},
8082
"devDependencies": {
83+
"@types/cross-spawn": "^6.0.6",
8184
"@types/d3-array": "^3.2.1",
8285
"@types/he": "^1.2.3",
8386
"@types/jsdom": "^21.1.6",
@@ -103,6 +106,7 @@
103106
"fast-deep-equal": "^3.1.3",
104107
"mocha": "^10.2.0",
105108
"prettier": "^3.0.3 <3.1",
109+
"rimraf": "^5.0.5",
106110
"typescript": "^5.2.2",
107111
"undici": "^5.27.2"
108112
},

src/build.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {createHash} from "node:crypto";
22
import {existsSync} from "node:fs";
33
import {access, constants, copyFile, readFile, writeFile} from "node:fs/promises";
4-
import {basename, dirname, extname, join} from "node:path";
4+
import {basename, dirname, extname, join} from "node:path/posix";
55
import type {Config} from "./config.js";
66
import {Loader} from "./dataloader.js";
77
import {CliError, isEnoent} from "./error.js";

src/config.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import {basename, dirname, join} from "node:path";
1+
import op from "node:path";
2+
import {basename, dirname, join} from "node:path/posix";
3+
import {cwd} from "node:process";
4+
import {pathToFileURL} from "node:url";
25
import {visitMarkdownFiles} from "./files.js";
36
import {formatIsoDate, formatLocaleDate} from "./format.js";
47
import {parseMarkdown} from "./markdown.js";
@@ -51,7 +54,7 @@ export interface Config {
5154

5255
export async function readConfig(configPath?: string, root?: string): Promise<Config> {
5356
if (configPath === undefined) return readDefaultConfig(root);
54-
const importPath = join(process.cwd(), root ?? ".", configPath);
57+
const importPath = pathToFileURL(op.join(cwd(), root ?? ".", configPath)).toString();
5558
return normalizeConfig((await import(importPath)).default, root);
5659
}
5760

src/convert.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {existsSync} from "node:fs";
22
import {utimes, writeFile} from "node:fs/promises";
3-
import {join} from "node:path";
3+
import {join} from "node:path/posix";
44
import * as clack from "@clack/prompts";
55
import wrapAnsi from "wrap-ansi";
66
import type {ClackEffects} from "./clack.js";

src/create.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {exec} from "node:child_process";
22
import {accessSync, existsSync, readdirSync, statSync} from "node:fs";
33
import {constants, copyFile, mkdir, readFile, readdir, stat, writeFile} from "node:fs/promises";
4-
import {basename, dirname, join, normalize, resolve} from "node:path";
4+
import op from "node:path";
5+
import {basename, dirname, join, normalize} from "node:path/posix";
56
import {setTimeout as sleep} from "node:timers/promises";
67
import {fileURLToPath} from "node:url";
78
import {promisify} from "node:util";
@@ -91,7 +92,7 @@ export async function create(options = {}, effects: CreateEffects = defaultEffec
9192
const s = clack.spinner();
9293
s.start("Copying template files");
9394
const template = includeSampleFiles ? "default" : "empty";
94-
const templateDir = resolve(fileURLToPath(import.meta.url), "..", "..", "templates", template);
95+
const templateDir = op.resolve(fileURLToPath(import.meta.url), "..", "..", "templates", template);
9596
const runCommand = packageManager === "yarn" ? "yarn" : `${packageManager ?? "npm"} run`;
9697
const installCommand = `${packageManager ?? "npm"} install`;
9798
await effects.sleep(1000);

src/dataloader.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import {spawn} from "node:child_process";
21
import {type WriteStream, createReadStream, existsSync, statSync} from "node:fs";
32
import {mkdir, open, readFile, rename, unlink} from "node:fs/promises";
4-
import {dirname, extname, join} from "node:path";
3+
import {dirname, extname, join} from "node:path/posix";
54
import {createGunzip} from "node:zlib";
5+
import {spawn} from "cross-spawn";
66
import JSZip from "jszip";
77
import {extract} from "tar-stream";
88
import {maybeStat, prepareOutput} from "./files.js";

src/deploy.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {join} from "node:path";
1+
import {join} from "node:path/posix";
22
import * as clack from "@clack/prompts";
33
import wrapAnsi from "wrap-ansi";
44
import type {BuildEffects} from "./build.js";

src/fileWatchers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {type FSWatcher, existsSync, watch} from "node:fs";
2-
import {join} from "node:path";
2+
import {join} from "node:path/posix";
33
import {Loader} from "./dataloader.js";
44
import {isEnoent} from "./error.js";
55
import {maybeStat} from "./files.js";

src/files.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
import {type Stats, existsSync} from "node:fs";
1+
import type {Stats} from "node:fs";
2+
import {existsSync} from "node:fs";
23
import {mkdir, readdir, stat} from "node:fs/promises";
3-
import {dirname, extname, join, normalize, relative} from "node:path";
4+
import op from "node:path";
5+
import {extname, join, normalize, relative, sep} from "node:path/posix";
46
import {cwd} from "node:process";
57
import {fileURLToPath} from "node:url";
68
import {isEnoent} from "./error.js";
79

10+
export function toOsPath(path: string): string {
11+
return path.split(sep).join(op.sep);
12+
}
13+
14+
export function fromOsPath(path: string): string {
15+
return path.split(op.sep).join(sep);
16+
}
17+
818
/**
919
* Returns the relative path from the current working directory to the given
1020
* Framework source file, such as "./src/client/search.js". This is typically
1121
* used to rollup JavaScript and style bundles for built-in modules.
1222
*/
1323
export function getClientPath(entry: string): string {
14-
const path = relative(cwd(), join(dirname(fileURLToPath(import.meta.url)), "..", entry));
24+
const path = fromOsPath(op.relative(cwd(), op.join(fileURLToPath(import.meta.url), "..", "..", entry)));
1525
if (path.endsWith(".js") && !existsSync(path)) {
1626
const tspath = path.slice(0, -".js".length) + ".ts";
1727
if (existsSync(tspath)) return tspath;
@@ -56,7 +66,7 @@ export async function maybeStat(path: string): Promise<Stats | undefined> {
5666

5767
/** Like recursive mkdir, but for the parent of the specified output. */
5868
export async function prepareOutput(outputPath: string): Promise<void> {
59-
const outputDir = dirname(outputPath);
69+
const outputDir = op.dirname(outputPath);
6070
if (outputDir === ".") return;
6171
await mkdir(outputDir, {recursive: true});
6272
}

src/javascript/files.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {extname} from "node:path";
1+
import {extname} from "node:path/posix";
22
import type {CallExpression, MemberExpression, Node} from "acorn";
33
import {ancestor, simple} from "acorn-walk";
44
import {relativePath, resolveLocalPath} from "../path.js";

src/javascript/module.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {createHash} from "node:crypto";
22
import {existsSync, readFileSync, statSync} from "node:fs";
3-
import {join, relative} from "node:path";
3+
import {join, relative} from "node:path/posix";
44
import type {Program} from "acorn";
55
import {Parser} from "acorn";
66
import {Loader} from "../dataloader.js";

src/npm.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {existsSync} from "node:fs";
22
import {mkdir, readFile, readdir, writeFile} from "node:fs/promises";
3-
import {dirname, join} from "node:path";
3+
import {dirname, join} from "node:path/posix";
44
import type {CallExpression} from "acorn";
55
import {Parser} from "acorn";
66
import {simple} from "acorn-walk";

src/observableApiConfig.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import fs from "node:fs/promises";
22
import os from "node:os";
3-
import path from "node:path";
3+
import op from "node:path";
44
import {CliError, isEnoent} from "./error.js";
55

66
export interface ConfigEffects {
@@ -69,7 +69,7 @@ export async function getDeployConfig(
6969
sourceRoot: string,
7070
effects: ConfigEffects = defaultEffects
7171
): Promise<DeployConfig> {
72-
const deployConfigPath = path.join(effects.cwd(), sourceRoot, ".observablehq", "deploy.json");
72+
const deployConfigPath = op.join(effects.cwd(), sourceRoot, ".observablehq", "deploy.json");
7373
let config: object | null = null;
7474
try {
7575
const content = await effects.readFile(deployConfigPath, "utf8");
@@ -93,8 +93,8 @@ export async function setDeployConfig(
9393
newConfig: DeployConfig,
9494
effects: ConfigEffects = defaultEffects
9595
): Promise<void> {
96-
const dir = path.join(effects.cwd(), sourceRoot, ".observablehq");
97-
const deployConfigPath = path.join(dir, "deploy.json");
96+
const dir = op.join(effects.cwd(), sourceRoot, ".observablehq");
97+
const deployConfigPath = op.join(dir, "deploy.json");
9898
const oldConfig = (await getDeployConfig(sourceRoot)) || {};
9999
const merged = {...oldConfig, ...newConfig};
100100
await effects.mkdir(dir, {recursive: true});
@@ -104,13 +104,13 @@ export async function setDeployConfig(
104104
export async function loadUserConfig(
105105
effects: ConfigEffects = defaultEffects
106106
): Promise<{configPath: string; config: UserConfig}> {
107-
const homeConfigPath = path.join(effects.homedir(), userConfigName);
107+
const homeConfigPath = op.join(effects.homedir(), userConfigName);
108108

109109
function* pathsToTry(): Generator<string> {
110-
let cursor = path.resolve(effects.cwd());
110+
let cursor = op.resolve(effects.cwd());
111111
while (true) {
112-
yield path.join(cursor, userConfigName);
113-
const nextCursor = path.dirname(cursor);
112+
yield op.join(cursor, userConfigName);
113+
const nextCursor = op.dirname(cursor);
114114
if (nextCursor === cursor) break;
115115
cursor = nextCursor;
116116
}

src/path.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {dirname, join} from "node:path";
1+
import {dirname, join} from "node:path/posix";
22

33
/**
44
* Returns the normalized relative path from "/file/path/to/a" to

src/preview.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {FSWatcher, WatchEventType} from "node:fs";
44
import {access, constants, readFile, stat} from "node:fs/promises";
55
import {createServer} from "node:http";
66
import type {IncomingMessage, RequestListener, Server, ServerResponse} from "node:http";
7-
import {basename, dirname, extname, join, normalize} from "node:path";
7+
import {basename, dirname, extname, join, normalize} from "node:path/posix";
88
import {difference} from "d3-array";
99
import type {PatchItem} from "fast-array-diff";
1010
import {getPatch} from "fast-array-diff";

src/rollup.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ function importResolve(input: string, root: string, path: string): Plugin {
124124
? {id: relativePath(path, "/_observablehq/stdlib/zip.js"), external: true} // TODO publish to npm
125125
: specifier.startsWith("npm:")
126126
? {id: relativePath(path, await resolveNpmImport(root, specifier.slice("npm:".length))), external: true}
127-
: !isPathImport(specifier) && !BUNDLED_MODULES.includes(specifier) // e.g., inputs.js imports "htl"
127+
: !/^[a-z]:\\/i.test(specifier) && !isPathImport(specifier) && !BUNDLED_MODULES.includes(specifier) // e.g., inputs.js imports "htl"
128128
? {id: relativePath(path, await resolveNpmImport(root, specifier)), external: true}
129129
: null;
130130
}

src/search.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {basename, join} from "node:path";
1+
import {basename, join} from "node:path/posix";
22
import he from "he";
33
import MiniSearch from "minisearch";
44
import type {Config} from "./config.js";

src/telemetry.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {exec} from "node:child_process";
22
import {createHash, randomUUID} from "node:crypto";
33
import {readFile, writeFile} from "node:fs/promises";
4-
import {join} from "node:path";
4+
import {join} from "node:path/posix";
55
import os from "os";
66
import {CliError} from "./error.js";
77
import type {Logger} from "./logger.js";

templates/default/package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"type": "module",
33
"private": true,
44
"scripts": {
5-
"clean": "rm -rf docs/.observablehq/cache",
6-
"build": "rm -rf dist && observable build",
5+
"clean": "rimraf docs/.observablehq/cache",
6+
"build": "rimraf dist && observable build",
77
"dev": "observable preview",
88
"deploy": "observable deploy",
99
"observable": "observable"
@@ -13,6 +13,9 @@
1313
"d3-dsv": "^3.0.1",
1414
"d3-time-format": "^4.1.0"
1515
},
16+
"devDependencies": {
17+
"rimraf": "^5.0.5"
18+
},
1619
"engines": {
1720
"node": ">=20.6"
1821
}

templates/empty/package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"type": "module",
33
"private": true,
44
"scripts": {
5-
"clean": "rm -rf docs/.observablehq/cache",
6-
"build": "rm -rf dist && observable build",
5+
"clean": "rimraf docs/.observablehq/cache",
6+
"build": "rimraf dist && observable build",
77
"dev": "observable preview",
88
"deploy": "observable deploy",
99
"observable": "observable"
@@ -13,6 +13,9 @@
1313
"d3-dsv": "^3.0.1",
1414
"d3-time-format": "^4.1.0"
1515
},
16+
"devDependencies": {
17+
"rimraf": "^5.0.5"
18+
},
1619
"engines": {
1720
"node": ">=20.6"
1821
}

0 commit comments

Comments
 (0)