Skip to content

Commit

Permalink
Add elm-watch-lib
Browse files Browse the repository at this point in the history
  • Loading branch information
lydell committed Jan 9, 2025
1 parent 371cb1d commit 0d34a7e
Show file tree
Hide file tree
Showing 17 changed files with 456 additions and 65 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.cache
build
build-*
coverage
dist
elm-stuff
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.cache
build
build-*
coverage
dist
elm-stuff
Expand Down
197 changes: 197 additions & 0 deletions elm-watch-lib.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// This file contains manually copy-pasted-and-adjusted types for `elm-watch-lib.ts`.

import type { DecoderError } from "tiny-decoders";

export function readSourceDirectories(elmJsonPath: string): ElmJsonParseResult;

export function walkImports(
sourceDirectories: NonEmptyArray<string>,
inputRealPaths: NonEmptyArray<string>,
): WalkImportsResult;

export function inject(compilationMode: CompilationMode, code: string): string;

export function elmMake(options: {
elmJsonPath: string;
compilationMode: CompilationMode;
inputs: NonEmptyArray<string>;
outputPath: "/dev/null" | string;
env: Env;
}): {
promise: Promise<RunElmMakeResult>;
kill: (options: { force: boolean }) => void;
};

export type NonEmptyArray<T> = [T, ...Array<T>];

export type Env = Record<string, string | undefined>;

export type CompilationMode = "debug" | "standard" | "optimize";

export type ElmJsonParseResult =
| ElmJsonParseError
| {
tag: "Parsed";
sourceDirectories: NonEmptyArray<string>;
};

export type ElmJsonParseError =
| {
tag: "ElmJsonDecodeError";
elmJsonPath: string;
error: DecoderError;
}
| {
tag: "ElmJsonReadError";
elmJsonPath: string;
error: Error;
};

export type WalkImportsResult =
| WalkImportsError
| {
tag: "Success";
allRelatedElmFilePaths: Set<string>;
};

export type WalkImportsError = {
tag: "ImportWalkerFileSystemError";
error: NodeJS.ErrnoException;
relatedElmFilePathsUntilError: Set<string>;
};

export type RunElmMakeResult =
| RunElmMakeError
| { tag: "Killed" }
| { tag: "Success" };

export type RunElmMakeError =
| {
tag: "ElmMakeCrashError";
beforeError: ElmMakeCrashBeforeError;
error: string;
command: Command;
}
| {
tag: "ElmMakeError";
error: ElmMakeError;
extraError: string | undefined;
}
| {
tag: "ElmMakeJsonParseError";
error: DecoderError;
errorFilePath: ErrorFilePath;
command: Command;
}
| {
tag: "ElmNotFoundError";
command: Command;
}
| {
tag: "OtherSpawnError";
error: Error;
command: Command;
}
| {
tag: "UnexpectedElmMakeOutput";
exitReason: ExitReason;
stdout: string;
stderr: string;
command: Command;
};

export type ElmMakeCrashBeforeError =
| {
tag: "Json";
length: number;
}
| {
tag: "Text";
text: string;
};

export type ErrorFilePath =
| {
tag: "AbsolutePath";
theAbsolutePath: string;
}
| {
tag: "ErrorFileBadContent";
content: string;
}
| {
tag: "WritingErrorFileFailed";
error: Error;
attemptedPath: string;
};

export type ExitReason =
| {
tag: "ExitCode";
exitCode: number;
}
| {
tag: "Signal";
signal: NodeJS.Signals;
}
| {
tag: "Unknown";
};

export type Command = {
command: string;
args: Array<string>;
options: {
cwd: string;
env: Env;
};
stdin?: Buffer | string;
};

type ElmMakeError =
| {
type: "error";
path: "elm.json" | null;
title: string;
message: Array<string | StyledText>;
}
| {
type: "compile-errors";
errors: NonEmptyArray<{
path: string;
name: string;
problems: NonEmptyArray<{
title: string;
region: {
start: { line: number; column: number };
end: { line: number; column: number };
};
message: Array<string | StyledText>;
}>;
}>;
};

export type StyledText = {
bold: boolean;
underline: boolean;
color: Color | null;
string: string;
};

export type Color =
| "red"
| "RED"
| "magenta"
| "MAGENTA"
| "yellow"
| "YELLOW"
| "green"
| "GREEN"
| "cyan"
| "CYAN"
| "blue"
| "BLUE"
| "black"
| "BLACK"
| "white"
| "WHITE";
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ export default typescriptEslint.config(
{
ignores: [
"build",
"build-*",
"coverage",
"dist",
"example",
Expand Down
14 changes: 14 additions & 0 deletions package-elm-watch-lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "elm-watch-lib",
"version": "1.0.0",
"author": "Simon Lydell",
"license": "MIT",
"description": "Parts of elm-watch as a library for use by other tools.",
"repository": "lydell/elm-watch",
"type": "module",
"exports": "./index.js",
"dependencies": {
"cross-spawn": "*",
"tiny-decoders": "*"
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"install-test-elm-dependencies": "tsx scripts/InstallTestDependencies.ts",
"pretest": "run-pty --auto-exit % prettier --check . % eslint . --report-unused-disable-directives % tsc % tsx scripts/EnsureTestDependencies.ts % npm run build",
"test": "vitest run --coverage",
"build": "tsx scripts/Build.ts"
"build": "tsx scripts/Build.ts && tsx scripts/BuildElmWatchLib.ts"
},
"devDependencies": {
"@types/cross-spawn": "6.0.6",
Expand Down
27 changes: 9 additions & 18 deletions scripts/BenchmarkImportWalker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
AbsolutePath,
Cwd,
ElmJsonPath,
InputPath,
markAsAbsolutePath,
markAsCwd,
markAsElmJsonPath,
Expand All @@ -33,22 +32,17 @@ function run(args: Array<string>): void {

const cwd: Cwd = markAsCwd(markAsAbsolutePath(process.cwd()));

const inputPaths: NonEmptyArray<InputPath> = mapNonEmptyArray(
const inputRealPaths: NonEmptyArray<AbsolutePath> = mapNonEmptyArray(
args,
(elmFilePathRaw) => ({
tag: "InputPath",
theInputPath: absolutePathFromString(cwd, elmFilePathRaw),
originalString: elmFilePathRaw,
realpath: absolutePathFromString(cwd, elmFilePathRaw),
}),
(elmFilePathRaw) => absolutePathFromString(cwd, elmFilePathRaw),
);

const elmJsonPathsRaw = new Set<AbsolutePath>(
mapNonEmptyArray(
inputPaths,
(inputPath) =>
findClosest("elm.json", absoluteDirname(inputPath.theInputPath)) ??
inputPath.theInputPath,
inputRealPaths,
(inputRealPath) =>
findClosest("elm.json", absoluteDirname(inputRealPath)) ??
inputRealPath,
),
);

Expand All @@ -57,7 +51,7 @@ function run(args: Array<string>): void {
if (uniqueElmJsonPathRaw === undefined) {
console.error(
"Could not find (a unique) elm.json for all of the input paths:",
inputPaths,
inputRealPaths,
);
process.exit(1);
}
Expand All @@ -81,13 +75,10 @@ function run(args: Array<string>): void {
// Keep going.
}

console.log(
"Elm file(s):",
mapNonEmptyArray(inputPaths, (inputPath) => inputPath.theInputPath),
);
console.log("Elm file(s):", inputRealPaths);
console.log("elm.json:", elmJsonPath);
console.time("Run");
const result = walkImports(elmJsonResult.sourceDirectories, inputPaths);
const result = walkImports(elmJsonResult.sourceDirectories, inputRealPaths);
console.timeEnd("Run");
switch (result.tag) {
case "Success":
Expand Down
4 changes: 2 additions & 2 deletions scripts/Build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const DIR = path.dirname(import.meta.dirname);
const BUILD = path.join(DIR, "build");
const CLIENT_DIR = path.join(DIR, "client");

function readPackage<T extends Record<string, unknown>>(
export function readPackage<T extends Record<string, unknown>>(
name: string,
codec: Codec.Codec<T>,
): T & { raw: Record<string, unknown> } {
Expand Down Expand Up @@ -35,7 +35,7 @@ const PACKAGE_REAL = readPackage(
Codec.fields({ version: Codec.string }),
);

type FileToCopy = {
export type FileToCopy = {
src: string;
dest?: string;
transform?: (content: string) => string;
Expand Down
Loading

0 comments on commit 0d34a7e

Please sign in to comment.