diff --git a/.changeset/swap-prettier-for-tsv.md b/.changeset/swap-prettier-for-tsv.md
new file mode 100644
index 0000000000..55d31596e8
--- /dev/null
+++ b/.changeset/swap-prettier-for-tsv.md
@@ -0,0 +1,6 @@
+---
+'@fuzdev/gro': minor
+---
+
+feat: replace Prettier with [`tsv`](https://github.com/fuzdev/tsv)
+
diff --git a/CLAUDE.md b/CLAUDE.md
index 605ee2b05d..49ec1e15c8 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -6,7 +6,7 @@
Gro (`@fuzdev/gro`) is a dev tool for building TypeScript projects with
SvelteKit, providing: convention-based task runner, Node loader for
TypeScript/Svelte/SvelteKit modules, code generation system, plugin
-architecture, and integrations with Vite, esbuild, Vitest, Prettier, ESLint, and
+architecture, and integrations with Vite, esbuild, Vitest, tsv, ESLint, and
Changesets. It's designed around conventions and the filesystem, not
configuration files.
@@ -168,7 +168,7 @@ Naming convention:
Return values:
-- String: default filename, auto-formatted with Prettier
+- String: default filename, auto-formatted with tsv
- Object: `{content, filename?, format?}` for control
- Array: multiple files from one genfile
- `null`: no-op
diff --git a/README.md b/README.md
index 95cea780d2..5dc67e1708 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ It includes:
- codegen by convention with [`gen`](/src/docs/gen.md)
- linting with [ESLint](https://github.com/eslint/eslint)
(I also maintain [`@feltjs/eslint-config`](https://github.com/feltjs/eslint-config))
-- formatting with [Prettier](https://github.com/prettier/prettier)
+- formatting with [`tsv`](https://github.com/fuzdev/tsv)
## docs
@@ -207,10 +207,10 @@ gro lint # eslint
For a usage example see [the `check.yml` CI config](.github/workflows/check.yml).
-Formatting with [`prettier`](https://github.com/prettier/prettier):
+Formatting with [`tsv`](https://github.com/fuzdev/tsv):
```bash
-gro format # format all of the source files using Prettier
+gro format # format all of the source files using tsv
gro format --check # check that all source files are formatted
```
@@ -274,7 +274,7 @@ Gro builds on
[zod](https://github.com/colinhacks/zod) ∙
[@fuzdev/fuz_util](https://github.com/fuzdev/fuz_util) ∙
[ESLint](https://github.com/eslint/eslint) ∙
-[Prettier](https://github.com/prettier/prettier) ∙
+[tsv](https://github.com/fuzdev/tsv) ∙
[svelte-check](https://github.com/sveltejs/language-tools/tree/master/packages/svelte-check) &
[more](package.json)
diff --git a/package-lock.json b/package-lock.json
index 3a576982b3..7895b268bd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,12 +9,11 @@
"version": "0.205.1",
"license": "MIT",
"dependencies": {
+ "@fuzdev/tsv_wasm": "^0.1.0",
"chokidar": "^5.0.0",
"dotenv": "^17.2.3",
"esm-env": "^1.2.2",
"oxc-parser": "^0.99.0",
- "prettier": "^3.7.4",
- "prettier-plugin-svelte": "^3.5.1",
"ts-blank-space": "^0.6.2",
"tslib": "^2.8.1"
},
@@ -942,6 +941,21 @@
}
}
},
+ "node_modules/@fuzdev/tsv_wasm": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@fuzdev/tsv_wasm/-/tsv_wasm-0.1.0.tgz",
+ "integrity": "sha512-hlZxdL1c5jiMyLnMv3NWONxNR64JChNekxlsKEJxXvgr/bmcRsgF1XuyabOTtppW9C+9HUvCpklJgJeU4aZbKw==",
+ "license": "MIT",
+ "bin": {
+ "tsv": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://www.ryanatkn.com/funding"
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
@@ -1012,6 +1026,7 @@
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -1022,6 +1037,7 @@
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -1032,6 +1048,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -1041,12 +1058,14 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -1623,6 +1642,7 @@
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz",
"integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==",
+ "dev": true,
"license": "MIT",
"peerDependencies": {
"acorn": "^8.9.0"
@@ -1772,6 +1792,7 @@
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
@@ -1795,6 +1816,7 @@
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
@@ -1947,7 +1969,7 @@
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz",
"integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2206,6 +2228,7 @@
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -2268,6 +2291,7 @@
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
"integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==",
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">= 0.4"
@@ -2287,6 +2311,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">= 0.4"
@@ -2366,6 +2391,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -2524,6 +2550,7 @@
"version": "5.8.1",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz",
"integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/dotenv": {
@@ -2765,6 +2792,7 @@
"version": "2.2.11",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.11.tgz",
"integrity": "sha512-gPdx+I+BjYEinNMQaBXFjbaJVyoPMU4ZODg5mE+M4DqVG9VusAVHHjcBX+zqyITlI0DIARwDMMzZwAWj36dRoQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
@@ -3036,6 +3064,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.6"
@@ -3408,6 +3437,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/locate-path": {
@@ -3437,6 +3467,7 @@
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
@@ -3819,31 +3850,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/prettier": {
- "version": "3.8.3",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
- "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
- "license": "MIT",
- "bin": {
- "prettier": "bin/prettier.cjs"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/prettier/prettier?sponsor=1"
- }
- },
- "node_modules/prettier-plugin-svelte": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.2.tgz",
- "integrity": "sha512-ItFouLvzSFE3ulNl4DKoWM3BGcbDCNVpIyy/Y3F2gC3aNiGLxtFUdffVqO5Z5hhYG+DFT5KULWaxmeFFpdbvaQ==",
- "license": "MIT",
- "peerDependencies": {
- "prettier": "^3.0.0",
- "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
- }
- },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -4060,6 +4066,7 @@
"version": "5.56.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.1.tgz",
"integrity": "sha512-eArsJmvl3xZVuTYD852PzIEdg2wgDdIZ1NEsIPbzAukHwi284B18No4nK2rCO9AwsWUDza4Cjvmoa4HaojTl5g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
@@ -4614,6 +4621,7 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/zod": {
diff --git a/package.json b/package.json
index 1ba4645210..a9450942ca 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
"bugs": "https://github.com/fuzdev/gro/issues",
"funding": "https://www.ryanatkn.com/funding",
"scripts": {
- "bootstrap": "rm -rf .gro dist && svelte-kit sync && svelte-package && chmod +x ./dist/gro.js && npm link -f",
+ "bootstrap": "rm -rf .gro dist && svelte-kit sync && svelte-package && node -e \"import('./src/lib/dist_rewrite_imports.ts').then((m) => m.rewrite_dist_imports())\" && chmod +x ./dist/gro.js && npm link -f",
"start": "gro dev",
"dev": "gro dev",
"build": "gro build",
@@ -48,12 +48,11 @@
"typescript"
],
"dependencies": {
+ "@fuzdev/tsv_wasm": "^0.1.0",
"chokidar": "^5.0.0",
"dotenv": "^17.2.3",
"esm-env": "^1.2.2",
"oxc-parser": "^0.99.0",
- "prettier": "^3.7.4",
- "prettier-plugin-svelte": "^3.5.1",
"ts-blank-space": "^0.6.2",
"tslib": "^2.8.1"
},
@@ -115,23 +114,6 @@
"zimmerframe": "^1.1.4",
"zod": "^4.3.6"
},
- "prettier": {
- "plugins": [
- "prettier-plugin-svelte"
- ],
- "useTabs": true,
- "printWidth": 100,
- "singleQuote": true,
- "bracketSpacing": false,
- "overrides": [
- {
- "files": "package.json",
- "options": {
- "useTabs": false
- }
- }
- ]
- },
"sideEffects": [
"**/*.css"
],
diff --git a/src/docs/README.md b/src/docs/README.md
index 41123c235b..ce4d907490 100644
--- a/src/docs/README.md
+++ b/src/docs/README.md
@@ -1,5 +1,6 @@
# docs
+
> [gro](/../..) / docs / README.md
- [build](build.md)
diff --git a/src/docs/task.md b/src/docs/task.md
index 6aa2536768..499683ec6e 100644
--- a/src/docs/task.md
+++ b/src/docs/task.md
@@ -291,8 +291,7 @@ Some builtin Gro tasks call external commands like
[`svelte-kit`](https://github.com/sveltejs/kit),
[`vite`](https://github.com/vitejs/vite),
[`vitest`](https://github.com/vitest-dev/vitest),
-[`tsc`](https://github.com/microsoft/typescript),
-and [`prettier`](https://github.com/prettier/prettier).
+and [`tsc`](https://github.com/microsoft/typescript).
Gro supports generic agnostic args forwarding to these tasks via the `--` pattern:
for example, to forward args to `svelte-kit` and `vitest`, no matter which task invokes them,
use `gro taskname --taskname-arg -- vitest --arg1 neat --arg2 22 -- svelte-kit --arg3`.
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index fe28c66b30..7221e208e2 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -19,7 +19,6 @@ export const GRO_DIR = GRO_DIRNAME + '/';
/** @trailing_slash */
export const GRO_DEV_DIR = GRO_DEV_DIRNAME + '/';
export const GRO_CONFIG_FILENAME = 'gro.config.ts';
-export const README_FILENAME = 'README.md';
export const SVELTE_CONFIG_FILENAME = 'svelte.config.js';
export const VITE_CONFIG_FILENAME = 'vite.config.ts';
export const NODE_MODULES_DIRNAME = 'node_modules';
@@ -29,7 +28,6 @@ export const SVELTEKIT_DEV_DIRNAME = '.svelte-kit'; // TODO use Svelte config va
export const SVELTEKIT_BUILD_DIRNAME = 'build';
export const SVELTEKIT_DIST_DIRNAME = 'dist';
export const SVELTEKIT_VITE_CACHE_PATH = NODE_MODULES_DIRNAME + '/.vite';
-export const GITHUB_DIRNAME = '.github';
export const GIT_DIRNAME = '.git';
export const TSCONFIG_FILENAME = 'tsconfig.json';
@@ -47,7 +45,6 @@ export const EVERYTHING_MATCHER = /.*/;
export const JS_CLI_DEFAULT = 'node';
export const PM_CLI_DEFAULT = 'npm';
-export const PRETTIER_CLI_DEFAULT = 'prettier';
export const SVELTEKIT_CLI = 'svelte-kit';
export const SVELTE_CHECK_CLI = 'svelte-check';
export const SVELTE_PACKAGE_CLI = 'svelte-package';
diff --git a/src/lib/filer.ts b/src/lib/filer.ts
index c99f1885bb..e8bf69f3f5 100644
--- a/src/lib/filer.ts
+++ b/src/lib/filer.ts
@@ -449,7 +449,9 @@ export const filter_dependents = (
const dependent_disknode = get_by_id(dependent_id);
if (!dependent_disknode) {
log?.warn(
- `[filer.filter_dependents] dependent source file ${dependent_id} not found for ${current.id}`,
+ `[filer.filter_dependents] dependent source file ${dependent_id} not found for ${
+ current.id
+ }`,
);
continue;
}
diff --git a/src/lib/format.task.ts b/src/lib/format.task.ts
index 9331f62d89..17db74072d 100644
--- a/src/lib/format.task.ts
+++ b/src/lib/format.task.ts
@@ -1,15 +1,12 @@
-import {print_spawn_result} from '@fuzdev/fuz_util/process.ts';
import {z} from 'zod';
-import {to_implicit_forwarded_args} from './args.ts';
-import {PRETTIER_CLI_DEFAULT} from './constants.ts';
import {format_directory} from './format_directory.ts';
import {paths} from './paths.ts';
import {TaskError, type Task} from './task.ts';
/** @nodocs */
export const Args = z.strictObject({
- _: z.array(z.string()).meta({description: 'files or directories to format'}).optional(),
+ _: z.array(z.string()).meta({description: 'files or globs to format'}).optional(),
check: z
.boolean()
.meta({description: 'exit with a nonzero code if any files are unformatted'})
@@ -24,21 +21,15 @@ export const task: Task = {
run: async ({args, log, config}) => {
const {_: patterns, check} = args;
- const format_result = await format_directory(
+ const result = await format_directory(
log,
paths.source,
check,
- undefined,
- undefined,
- undefined,
- config.pm_cli,
- to_implicit_forwarded_args(PRETTIER_CLI_DEFAULT),
+ config.search_filters,
patterns,
);
- if (!format_result.ok) {
- throw new TaskError(
- `Failed ${check ? 'formatting check' : 'to format'}. ${print_spawn_result(format_result)}`,
- );
+ if (!result.ok) {
+ throw new TaskError(`Failed ${check ? 'formatting check' : 'to format'}.`);
}
},
};
diff --git a/src/lib/format_directory.ts b/src/lib/format_directory.ts
index 09cefdb1bd..8a6ac42827 100644
--- a/src/lib/format_directory.ts
+++ b/src/lib/format_directory.ts
@@ -1,65 +1,135 @@
-import {args_serialize, type Args} from '@fuzdev/fuz_util/args.ts';
+import {map_concurrent} from '@fuzdev/fuz_util/async.ts';
+import {fs_search} from '@fuzdev/fuz_util/fs.ts';
import type {Logger} from '@fuzdev/fuz_util/log.ts';
-import type {SpawnResult} from '@fuzdev/fuz_util/process.ts';
+import type {PathFilter} from '@fuzdev/fuz_util/path.ts';
+import {globSync} from 'node:fs';
+import {readFile, writeFile} from 'node:fs/promises';
+import {isAbsolute, join, resolve} from 'node:path';
-import {spawn_cli, to_cli_name, type Cli} from './cli.ts';
import {
- GITHUB_DIRNAME,
- README_FILENAME,
+ GRO_CONFIG_FILENAME,
SVELTE_CONFIG_FILENAME,
- VITE_CONFIG_FILENAME,
TSCONFIG_FILENAME,
- GRO_CONFIG_FILENAME,
- PM_CLI_DEFAULT,
- PRETTIER_CLI_DEFAULT,
+ VITE_CONFIG_FILENAME,
} from './constants.ts';
+import {format_file} from './format_file.ts';
import {paths} from './paths.ts';
-const EXTENSIONS_DEFAULT = 'ts,js,json,svelte,html,css,md,yml';
-const ROOT_PATHS_DEFAULT = `${[
- README_FILENAME,
+/** Matches the file extensions `format_file` knows how to format. */
+const FORMATTABLE_MATCHER = /\.(ts|mts|cts|js|mjs|cjs|svelte|css|json)$/;
+
+/**
+ * Root-level files formatted alongside `paths.source`.
+ * `package.json` is intentionally omitted — `gro sync` owns its serialization
+ * via `package_json_serialize` (2-space, matching the npm convention).
+ */
+const ROOT_FILES_DEFAULT = [
GRO_CONFIG_FILENAME,
SVELTE_CONFIG_FILENAME,
VITE_CONFIG_FILENAME,
TSCONFIG_FILENAME,
- GITHUB_DIRNAME,
-].join(',')}/**/*`;
+];
+
+const FORMAT_CONCURRENCY = 16;
+
+export interface FormatDirectoryResult {
+ ok: boolean;
+ /** The files that were formatted (or, in `check` mode, that need formatting). */
+ formatted: Array;
+ /** Files the formatter rejected (e.g. a syntax error), left untouched. */
+ errored: Array<{id: string; error: unknown}>;
+}
/**
- * Formats files on the filesystem.
- * When `patterns` is provided, formats those specific files/patterns.
- * Otherwise formats `dir` with default extensions, plus root files if `dir` is `paths.source`.
- * This is separated from `./format_file` to avoid importing all of the `prettier` code
- * inside modules that import this one. (which has a nontrivial cost)
+ * Formats files on the filesystem in-process via `format_file`.
+ * When `patterns` is provided, formats those specific files/globs.
+ * Otherwise formats `dir` (recursively, respecting `filter`) plus the root
+ * files when `dir` is `paths.source`.
+ *
+ * Files the formatter rejects (e.g. a syntax error) are reported and fail the
+ * run in both modes rather than being silently skipped.
+ *
+ * This is separated from `./format_file` so modules that only need directory
+ * traversal don't pull in the formatter (which loads the `tsv` WASM module).
+ *
+ * @param check - when `true`, reports unformatted files instead of writing them
+ * @param filter - directory filters (e.g. `config.search_filters`) to skip;
+ * also prunes `patterns` results so an explicit glob can't reach `node_modules`
*/
export const format_directory = async (
log: Logger,
dir: string,
check = false,
- extensions = EXTENSIONS_DEFAULT,
- root_paths = ROOT_PATHS_DEFAULT,
- prettier_cli: string | Cli = PRETTIER_CLI_DEFAULT,
- pm_cli: string = PM_CLI_DEFAULT,
- additional_args?: Args,
+ filter?: PathFilter | Array,
patterns?: Array,
-): Promise => {
- const forwarded_args = {...additional_args};
- if (forwarded_args.check === undefined && forwarded_args.write === undefined) {
- forwarded_args[check ? 'check' : 'write'] = true;
+): Promise => {
+ const file_ids = patterns?.length
+ ? globSync(patterns, to_glob_options(filter))
+ .filter((p) => FORMATTABLE_MATCHER.test(p))
+ .map((p) => (isAbsolute(p) ? p : resolve(p)))
+ : await collect_source_files(dir, filter);
+
+ const formatted: Array = [];
+ const errored: Array<{id: string; error: unknown}> = [];
+ await map_concurrent(file_ids, FORMAT_CONCURRENCY, async (id) => {
+ let content: string;
+ try {
+ content = await readFile(id, 'utf8');
+ } catch (_err) {
+ return; // a default root file that doesn't exist in this project
+ }
+ let next: string;
+ try {
+ next = format_file(content, {filepath: id});
+ } catch (error) {
+ errored.push({id, error});
+ return;
+ }
+ if (next === content) return;
+ formatted.push(id);
+ if (!check) await writeFile(id, next);
+ });
+
+ // `map_concurrent` completes in nondeterministic order; sort for stable output.
+ formatted.sort();
+ errored.sort((a, b) => a.id.localeCompare(b.id));
+
+ if (check && formatted.length) {
+ log.error(`${formatted.length} file(s) need formatting:`);
+ for (const id of formatted) log.error(` ${id}`);
}
- const serialized_args = args_serialize(forwarded_args);
- if (patterns?.length) {
- serialized_args.push(...patterns);
- } else {
- serialized_args.push(`${dir}**/*.{${extensions}}`);
- if (dir === paths.source) {
- serialized_args.push(`${paths.root}{${root_paths}}`);
+ if (errored.length) {
+ log.error(`${errored.length} file(s) could not be formatted:`);
+ for (const {id, error} of errored) {
+ log.error(` ${id}: ${error instanceof Error ? error.message : String(error)}`);
+ }
+ }
+
+ return {ok: errored.length === 0 && (!check || formatted.length === 0), formatted, errored};
+};
+
+/** Builds `globSync` options that prune the same paths `filter` would exclude. */
+const to_glob_options = (
+ filter?: PathFilter | Array,
+): {exclude: (path: string) => boolean} => {
+ const filters = filter && (Array.isArray(filter) ? filter : [filter]);
+ if (!filters?.length) return {exclude: () => false};
+ return {exclude: (path) => filters.some((f) => !f(path, false))};
+};
+
+const collect_source_files = async (
+ dir: string,
+ filter?: PathFilter | Array,
+): Promise> => {
+ const found = await fs_search(dir, {
+ filter,
+ file_filter: (path) => FORMATTABLE_MATCHER.test(path),
+ });
+ const file_ids = found.map((f) => f.id);
+ if (dir === paths.source) {
+ for (const name of ROOT_FILES_DEFAULT) {
+ file_ids.push(join(paths.root, name));
}
}
- const spawned = await spawn_cli(prettier_cli, serialized_args, log);
- if (!spawned)
- throw Error(
- `failed to find \`${to_cli_name(prettier_cli)}\` CLI locally or globally, do you need to run \`${pm_cli} install\`?`,
- );
- return spawned;
+ return file_ids;
};
diff --git a/src/lib/format_file.ts b/src/lib/format_file.ts
index be164166b2..4d602c9c4d 100644
--- a/src/lib/format_file.ts
+++ b/src/lib/format_file.ts
@@ -1,46 +1,84 @@
-import prettier from 'prettier';
+import {format_css, format_svelte, format_typescript} from '@fuzdev/tsv_wasm';
import {extname} from 'node:path';
-import {package_json_load} from './package_json.ts';
+/**
+ * The source languages Gro can format in-process, backed by
+ * `@fuzdev/tsv_wasm` (`typescript`/`svelte`/`css`) plus a builtin `json`
+ * formatter. Anything else passes through unchanged.
+ */
+export type FormatLang = 'typescript' | 'svelte' | 'css' | 'json';
-let cached_base_options: prettier.Options | undefined;
+export interface FormatFileOptions {
+ /** The file path, used to infer the language from its extension. */
+ filepath?: string;
+ /** The language to format as, overriding any inference from `filepath`. */
+ lang?: FormatLang;
+}
/**
- * Formats a file with Prettier.
- * @param content
- * @param options
- * @param base_options - defaults to the cwd's `package.json` `prettier` value
+ * Formats a string of source code in-process.
+ * Passes the input through unchanged when the language is unsupported (an
+ * expected no-op), but throws when the formatter rejects the source — e.g. a
+ * syntax error — so callers can decide whether to log, skip, or fail rather
+ * than silently treating broken input as already-formatted.
+ * @param content - the source to format
+ * @param options - a `filepath` to infer the language, or an explicit `lang`
*/
-export const format_file = async (
- content: string,
- options: prettier.Options,
- base_options: prettier.Options | null | undefined = cached_base_options,
-): Promise => {
- const final_base_options =
- base_options !== undefined
- ? base_options
- : (cached_base_options = (await package_json_load()).prettier as any);
- let final_options = options;
- if (options.filepath && !options.parser) {
- const {filepath, ...rest} = options;
- const parser = infer_parser(filepath);
- if (parser) final_options = {...rest, parser};
+export const format_file = (content: string, options: FormatFileOptions = {}): string => {
+ const lang = options.lang ?? (options.filepath ? infer_lang(options.filepath) : null);
+ switch (lang) {
+ case 'typescript':
+ return format_typescript(content);
+ case 'svelte':
+ return format_svelte(content);
+ case 'css':
+ return format_css(content);
+ case 'json':
+ return format_json(content);
+ default:
+ return content;
}
+};
+
+/**
+ * Formats JSON with tabs.
+ * Passes non-strict JSON (e.g. JSONC like `tsconfig.json` with comments)
+ * through unchanged rather than throwing — unparseable JSON is usually
+ * intentional, not a formatting error.
+ * Forward-compatible with a future `tsv` JSON formatter.
+ */
+const format_json = (content: string): string => {
+ let parsed;
try {
- return await prettier.format(content, {...final_base_options, ...final_options});
+ parsed = JSON.parse(content);
} catch (_err) {
return content;
}
+ return JSON.stringify(parsed, null, '\t') + '\n';
};
-// This is just a simple convenience for callers so they can pass a file path.
-// They can provide the Prettier `options.parser` for custom extensions.
-const infer_parser = (path: string): string | null => {
- const extension = extname(path).substring(1);
- switch (extension) {
- case 'svelte':
- case 'xml': {
- return extension;
+/**
+ * Infers the format language from a file path's extension.
+ * Returns `null` for extensions Gro doesn't format.
+ */
+const infer_lang = (path: string): FormatLang | null => {
+ switch (extname(path).substring(1)) {
+ case 'ts':
+ case 'mts':
+ case 'cts':
+ case 'js':
+ case 'mjs':
+ case 'cjs': {
+ return 'typescript';
+ }
+ case 'svelte': {
+ return 'svelte';
+ }
+ case 'css': {
+ return 'css';
+ }
+ case 'json': {
+ return 'json';
}
default: {
return null;
diff --git a/src/lib/gen.task.ts b/src/lib/gen.task.ts
index 24c14a4e37..2a0b2c1c5a 100644
--- a/src/lib/gen.task.ts
+++ b/src/lib/gen.task.ts
@@ -125,7 +125,12 @@ export const task: Task = {
log.info(
format_gen_output(output_lines) +
- `\n\n\t${new_count} ${st(new_count > 0 ? 'green' : 'gray', 'new')}, ${changed_count} ${st(changed_count > 0 ? 'cyan' : 'gray', 'changed')}, ${unchanged_count} ${st('gray', 'unchanged')}${error_count ? `, ${error_count} ${st('red', 'error' + plural(error_count))}` : ''} from ${gen_results.input_count} input file${plural(gen_results.input_count)}`,
+ `\n\n\t${new_count} ${st(new_count > 0 ? 'green' : 'gray', 'new')}, ${changed_count} ${st(
+ changed_count > 0 ? 'cyan' : 'gray',
+ 'changed',
+ )}, ${unchanged_count} ${st('gray', 'unchanged')}${
+ error_count ? `, ${error_count} ${st('red', 'error' + plural(error_count))}` : ''
+ } from ${gen_results.input_count} input file${plural(gen_results.input_count)}`,
);
if (fail_count) {
@@ -200,7 +205,9 @@ const format_gen_output = (output_lines: Array): string => {
for (const line of output_lines) {
const elapsed_text = line.elapsed.padStart(max_elapsed_length);
const source_text = line.source.padEnd(max_source_length);
- log_result += `\n\t${st(line.status.color, line.status.symbol)} ${elapsed_text} ${source_text} → ${line.target}`;
+ log_result += `\n\t${st(line.status.color, line.status.symbol)} ${elapsed_text} ${
+ source_text
+ } → ${line.target}`;
}
return log_result;
};
diff --git a/src/lib/gen.ts b/src/lib/gen.ts
index db257eefd2..e5a258ac99 100644
--- a/src/lib/gen.ts
+++ b/src/lib/gen.ts
@@ -165,14 +165,18 @@ export const to_output_file_name = (filename: string): string => {
}
if (gen_pattern_index !== parts.lastIndexOf(GEN_FILE_PATTERN_TEXT)) {
throw Error(
- `Invalid gen file name - multiple instances of '${GEN_FILE_PATTERN_TEXT}' found in '${filename}'`,
+ `Invalid gen file name - multiple instances of '${GEN_FILE_PATTERN_TEXT}' found in '${
+ filename
+ }'`,
);
}
if (gen_pattern_index < parts.length - 3) {
// This check is technically unneccessary,
// but ensures a consistent file naming convention.
throw Error(
- `Invalid gen file name - only one additional extension is allowed to follow '${GEN_FILE_PATTERN}' in '${filename}'`,
+ `Invalid gen file name - only one additional extension is allowed to follow '${
+ GEN_FILE_PATTERN
+ }' in '${filename}'`,
);
}
const final_parts: Array = [];
@@ -202,13 +206,13 @@ export type AnalyzedGenResult =
existing_content: string;
is_new: false;
has_changed: boolean;
- }
+ }
| {
file: GenFile;
existing_content: null;
is_new: true;
has_changed: true;
- };
+ };
export const analyze_gen_results = async (
gen_results: GenResults,
@@ -276,7 +280,7 @@ export type FindGenfilesFailure =
unmapped_input_paths: Array;
resolved_input_paths: Array;
reasons: Array;
- }
+ }
| {
type: 'input_directories_with_no_files';
input_directories_with_no_files: Array;
@@ -284,7 +288,7 @@ export type FindGenfilesFailure =
resolved_input_files_by_root_dir: Map>;
resolved_input_paths: Array;
reasons: Array;
- };
+ };
/**
* Finds modules from input paths. (see `input_path.ts` for more)
diff --git a/src/lib/gro_config.ts b/src/lib/gro_config.ts
index 0beb33071b..c93e3e6825 100644
--- a/src/lib/gro_config.ts
+++ b/src/lib/gro_config.ts
@@ -146,9 +146,13 @@ export const SEARCH_EXCLUDER_DEFAULT = new RegExp(
`(${
'(^|/)\\.[^/]+' + // exclude all `.`-prefixed directories
// TODO probably change to `pkg.name` instead of this catch-all (also `gro` below)
- `|(^|/)${NODE_MODULES_DIRNAME}(?!/(@[^/]+/)?gro/${SVELTEKIT_DIST_DIRNAME})` + // exclude `node_modules` unless it's to the Gro directory
+ `|(^|/)${NODE_MODULES_DIRNAME}(?!/(@[^/]+/)?gro/${
+ SVELTEKIT_DIST_DIRNAME
+ })` + // exclude `node_modules` unless it's to the Gro directory
`|(^|/)${SVELTEKIT_BUILD_DIRNAME}` + // exclude the SvelteKit build directory
- `|(^|/)(? 0) {
log.info(
- `[gen] re-running for ${queued_files.size} more queued file${queued_files.size === 1 ? '' : 's'}`,
+ `[gen] re-running for ${queued_files.size} more queued file${
+ queued_files.size === 1 ? '' : 's'
+ }`,
);
setTimeout(flush_gen_queue); // setTimeout is needed bc of throttle behavior
} else {
diff --git a/src/lib/gro_plugin_sveltekit_app.ts b/src/lib/gro_plugin_sveltekit_app.ts
index 0de6eeaaaf..c7a540846a 100644
--- a/src/lib/gro_plugin_sveltekit_app.ts
+++ b/src/lib/gro_plugin_sveltekit_app.ts
@@ -43,7 +43,9 @@ export const gro_plugin_sveltekit_app = ({
const spawned = await spawn_cli(found_vite_cli, serialized_args, log);
if (!spawned?.ok) {
throw new TaskError(
- `${vite_cli} build failed: ${spawned ? spawn_result_to_message(spawned) : 'unknown error'}`,
+ `${vite_cli} build failed: ${
+ spawned ? spawn_result_to_message(spawned) : 'unknown error'
+ }`,
);
}
}
diff --git a/src/lib/input_path.ts b/src/lib/input_path.ts
index 39dd3571a4..99befa7e13 100644
--- a/src/lib/input_path.ts
+++ b/src/lib/input_path.ts
@@ -195,7 +195,12 @@ export const resolve_input_files = async (
let remaining = resolved_input_paths.slice();
const handle_found = (input_path: InputPath, id: PathId) => {
remaining = remaining.filter(
- (r) => !(r.id === id || r.input_path === input_path || r.input_path === id), // `r.input_path === id` may be unnecessary
+ (r) =>
+ !(
+ r.id === id ||
+ r.input_path === input_path ||
+ r.input_path === id
+ ), // `r.input_path === id` may be unnecessary
);
};
diff --git a/src/lib/invoke_task.ts b/src/lib/invoke_task.ts
index e46ef79684..7474ba0207 100644
--- a/src/lib/invoke_task.ts
+++ b/src/lib/invoke_task.ts
@@ -114,7 +114,9 @@ export const invoke_task = async (
if (loaded_tasks.modules.length !== 1) throw Error('expected one loaded task'); // run only one task at a time
const task = loaded_tasks.modules[0]!;
log.info(
- `→ ${st('cyan', task.name)} ${(task.mod.task.summary && st('gray', task.mod.task.summary)) ?? ''}`,
+ `→ ${st('cyan', task.name)} ${
+ (task.mod.task.summary && st('gray', task.mod.task.summary)) ?? ''
+ }`,
);
const timing_to_run_task = timings.start('run task ' + task_name);
diff --git a/src/lib/modules.ts b/src/lib/modules.ts
index ae92d5ddb3..a7a78a5a46 100644
--- a/src/lib/modules.ts
+++ b/src/lib/modules.ts
@@ -22,7 +22,7 @@ export type LoadModuleFailure =
id: PathId;
mod: Record;
validation: string;
- };
+ };
export const load_module = async >(
id: PathId,
diff --git a/src/lib/publish.task.ts b/src/lib/publish.task.ts
index 3020fc82b0..bcd01e818e 100644
--- a/src/lib/publish.task.ts
+++ b/src/lib/publish.task.ts
@@ -214,7 +214,9 @@ export const task: Task = {
const changeset_publish_result = await spawn_cli(found_changeset_cli, ['publish'], log);
if (!changeset_publish_result?.ok) {
throw new TaskError(
- `\`${changeset_cli} publish\` failed - continue manually or try again after running \`git reset --hard\``,
+ `\`${
+ changeset_cli
+ } publish\` failed - continue manually or try again after running \`git reset --hard\``,
);
}
diff --git a/src/lib/reinstall.task.ts b/src/lib/reinstall.task.ts
index 7b91f57c2a..026045ff6f 100644
--- a/src/lib/reinstall.task.ts
+++ b/src/lib/reinstall.task.ts
@@ -20,7 +20,9 @@ export const task: Task = {
// Deleting both the lockfile and node_modules upgrades to the latest minor/patch versions.
await Promise.all([rm(LOCKFILE_FILENAME), rm(NODE_MODULES_DIRNAME, {recursive: true})]);
log.info(
- `running \`${config.pm_cli} install\` after deleting ${LOCKFILE_FILENAME} and ${NODE_MODULES_DIRNAME}, this can take a while...`,
+ `running \`${config.pm_cli} install\` after deleting ${LOCKFILE_FILENAME} and ${
+ NODE_MODULES_DIRNAME
+ }, this can take a while...`,
);
await install_with_cache_healing_or_throw(config.pm_cli, {
log,
diff --git a/src/lib/run_gen.ts b/src/lib/run_gen.ts
index 79ef877d63..ec83794b50 100644
--- a/src/lib/run_gen.ts
+++ b/src/lib/run_gen.ts
@@ -70,12 +70,13 @@ export const run_gen = async (
// Convert the module's return value to a normalized form.
const gen_result = to_gen_result(id, raw_gen_result);
- // Format the files if needed.
+ // Format the files if needed. `format_file` is synchronous and throws
+ // on a real formatting error, so log and fall back to the unformatted file.
const files = format_file
- ? await map_concurrent(gen_result.files, 10, async (file) => {
+ ? gen_result.files.map((file) => {
if (!file.format) return file;
try {
- return {...file, content: await format_file(file.content, {filepath: file.id})};
+ return {...file, content: format_file(file.content, {filepath: file.id})};
} catch (error) {
log.error(
st('red', `Error formatting ${print_path(file.id)} via ${print_path(id)}`),
diff --git a/src/lib/run_task.ts b/src/lib/run_task.ts
index 83674905da..304e816536 100644
--- a/src/lib/run_task.ts
+++ b/src/lib/run_task.ts
@@ -15,12 +15,12 @@ export type RunTaskResult =
| {
ok: true;
output: unknown;
- }
+ }
| {
ok: false;
reason: string;
error: Error;
- };
+ };
export const run_task = async (
task_meta: TaskModuleMeta,
@@ -44,7 +44,9 @@ export const run_task = async (
const parsed = args_parse(unparsed_args, task.Args);
if (!parsed.success) {
throw new TaskError(
- `Failed task args validation for task '${task_meta.name}':\n${z.prettifyError(parsed.error)}`,
+ `Failed task args validation for task '${task_meta.name}':\n${z.prettifyError(
+ parsed.error,
+ )}`,
);
}
args = parsed.data;
diff --git a/src/lib/sveltekit_helpers.ts b/src/lib/sveltekit_helpers.ts
index 724c89cf3e..1394406981 100644
--- a/src/lib/sveltekit_helpers.ts
+++ b/src/lib/sveltekit_helpers.ts
@@ -59,7 +59,9 @@ export const sveltekit_sync = async (
const result = await spawn_cli(sveltekit_cli, ['sync']);
if (!result) {
throw new TaskError(
- `Failed to find SvelteKit CLI \`${to_cli_name(sveltekit_cli)}\`, do you need to run \`${pm_cli} install\`?`,
+ `Failed to find SvelteKit CLI \`${to_cli_name(sveltekit_cli)}\`, do you need to run \`${
+ pm_cli
+ } install\`?`,
);
} else if (!result.ok) {
throw new TaskError(`Failed ${to_cli_name(sveltekit_cli)} sync`);
@@ -164,7 +166,9 @@ export const run_svelte_package = async (
const found_svelte_package_cli = cli === cli_name ? await find_cli(cli) : (cli as Cli);
if (found_svelte_package_cli?.kind !== 'local') {
throw new TaskError(
- `Failed to find SvelteKit packaging CLI \`${cli_name}\`, do you need to run \`${pm_cli} install\`?`,
+ `Failed to find SvelteKit packaging CLI \`${cli_name}\`, do you need to run \`${
+ pm_cli
+ } install\`?`,
);
}
const serialized_args = args_serialize({
diff --git a/src/lib/task.ts b/src/lib/task.ts
index 71300ee4fc..9e68ec5120 100644
--- a/src/lib/task.ts
+++ b/src/lib/task.ts
@@ -23,7 +23,10 @@ import type {Filer} from './filer.ts';
export interface Task<
TArgs = Args,
- TArgsSchema extends z.ZodType = z.ZodType, // TODO improve type? separate input/output?
+ TArgsSchema extends z.ZodType = z.ZodType<
+ Args,
+ Args
+ >, // TODO improve type? separate input/output?
TReturn = unknown,
> {
run: (ctx: TaskContext) => TReturn | Promise; // TODO unused return value
@@ -110,7 +113,7 @@ export type FindModulesFailure =
input_paths: Array;
task_root_dirs: Array;
reasons: Array;
- }
+ }
| {
type: 'input_directories_with_no_files';
input_directories_with_no_files: Array;
@@ -120,7 +123,7 @@ export type FindModulesFailure =
input_paths: Array;
task_root_dirs: Array;
reasons: Array;
- };
+ };
/**
* Finds modules from input paths. (see `input_path.ts` for more)
diff --git a/src/lib/typecheck.task.ts b/src/lib/typecheck.task.ts
index 55198608b7..20fde1fdd8 100644
--- a/src/lib/typecheck.task.ts
+++ b/src/lib/typecheck.task.ts
@@ -75,7 +75,9 @@ export const task: Task = {
}
throw new TaskError(
- `Failed to typecheck because neither \`${svelte_check_cli}\` nor \`${typescript_cli}\` was found`,
+ `Failed to typecheck because neither \`${svelte_check_cli}\` nor \`${
+ typescript_cli
+ }\` was found`,
);
},
};
diff --git a/src/lib/upgrade.task.ts b/src/lib/upgrade.task.ts
index 810ec13bc2..4b9b459f2b 100644
--- a/src/lib/upgrade.task.ts
+++ b/src/lib/upgrade.task.ts
@@ -98,7 +98,9 @@ export const task: Task = {
if (only.length && only.length !== deps.length) {
throw new TaskError(
- `Some deps to upgrade were not found: ${only.filter((o) => !deps.find((d) => d.name === o)).join(', ')}`,
+ `Some deps to upgrade were not found: ${only
+ .filter((o) => !deps.find((d) => d.name === o))
+ .join(', ')}`,
);
}
diff --git a/src/test/fixtures/changelog_cache.json b/src/test/fixtures/changelog_cache.json
index 0182e3788f..c47ba05d6c 100644
--- a/src/test/fixtures/changelog_cache.json
+++ b/src/test/fixtures/changelog_cache.json
@@ -10,7 +10,9 @@
"id": 1573508138,
"html_url": "https://github.com/fuzdev/gro/pull/429",
"number": 429,
- "user": {"login": "ryanatkn"}
+ "user": {
+ "login": "ryanatkn"
+ }
}
],
"etag": "W/\"4912dcf67e17b0ea3e68ca2727dcfbedfd92983b2ff46b8ae58ce2b062a38c45\"",
@@ -28,7 +30,9 @@
"id": 1639829810,
"html_url": "https://github.com/fuzdev/gro/pull/437",
"number": 437,
- "user": {"login": "ryanatkn"}
+ "user": {
+ "login": "ryanatkn"
+ }
}
],
"etag": "W/\"1fdf4fc5a79e58799d9ee4876aa55fa0534950f9cb2a56ced4b95df74879ad9d\"",
@@ -126,7 +130,9 @@
"id": 1593503608,
"html_url": "https://github.com/fuzdev/gro/pull/434",
"number": 434,
- "user": {"login": "ryanatkn"}
+ "user": {
+ "login": "ryanatkn"
+ }
}
],
"etag": "W/\"b26ed2c9b5eb0e59fdb65c82dd7aa053651769f0438e3a1ab107524ace8b706f\"",
diff --git a/src/test/fixtures/test_sveltekit_env_subprocess.ts b/src/test/fixtures/test_sveltekit_env_subprocess.ts
index 1b5b9eadfc..e23c66382e 100644
--- a/src/test/fixtures/test_sveltekit_env_subprocess.ts
+++ b/src/test/fixtures/test_sveltekit_env_subprocess.ts
@@ -22,7 +22,9 @@ async function runTest() {
if (fixture.exported_env_static_public !== VALUE) {
console.error(
- `✗ Static import test failed: expected "${VALUE}", got "${fixture.exported_env_static_public}"`,
+ `✗ Static import test failed: expected "${VALUE}", got "${
+ fixture.exported_env_static_public
+ }"`,
);
process.exit(1);
}
@@ -34,7 +36,9 @@ async function runTest() {
if (!env_mod.PUBLIC_SOME_PUBLIC_ENV_VAR || env_mod.PUBLIC_SOME_PUBLIC_ENV_VAR !== VALUE) {
console.error(
- `✗ Dynamic import test failed: expected "${VALUE}", got "${env_mod.PUBLIC_SOME_PUBLIC_ENV_VAR}"`,
+ `✗ Dynamic import test failed: expected "${VALUE}", got "${
+ env_mod.PUBLIC_SOME_PUBLIC_ENV_VAR
+ }"`,
);
process.exit(1);
}
diff --git a/src/test/format_file.test.ts b/src/test/format_file.test.ts
index 7e73eb7e21..2d4fe1fccb 100644
--- a/src/test/format_file.test.ts
+++ b/src/test/format_file.test.ts
@@ -2,16 +2,43 @@ import {test, expect} from 'vitest';
import {format_file} from '$lib/format_file.ts';
-test('format ts', async () => {
+test('format ts', () => {
const ts_unformatted = 'hey (1)';
const ts_formatted = 'hey(1);\n';
- expect(await format_file(ts_unformatted, {filepath: 'foo.ts'})).toBe(ts_formatted);
- expect(await format_file(ts_unformatted, {parser: 'typescript'})).toBe(ts_formatted);
+ expect(format_file(ts_unformatted, {filepath: 'foo.ts'})).toBe(ts_formatted);
+ expect(format_file(ts_unformatted, {lang: 'typescript'})).toBe(ts_formatted);
});
-test('format svelte', async () => {
+test('format js', () => {
+ expect(format_file('const x=1', {filepath: 'foo.js'})).toBe('const x = 1;\n');
+});
+
+test('format svelte', () => {
const svelte_unformatted = '';
const svelte_formatted = '\n';
- expect(await format_file(svelte_unformatted, {filepath: 'foo.svelte'})).toBe(svelte_formatted);
- expect(await format_file(svelte_unformatted, {parser: 'svelte'})).toBe(svelte_formatted);
+ expect(format_file(svelte_unformatted, {filepath: 'foo.svelte'})).toBe(svelte_formatted);
+ expect(format_file(svelte_unformatted, {lang: 'svelte'})).toBe(svelte_formatted);
+});
+
+test('format css', () => {
+ expect(format_file('a{color:red}', {filepath: 'foo.css'})).toBe('a {\n\tcolor: red;\n}\n');
+});
+
+test('format json with tabs', () => {
+ expect(format_file('{"b":1,"a":2}', {filepath: 'foo.json'})).toBe('{\n\t"b": 1,\n\t"a": 2\n}\n');
+});
+
+test('unsupported extension passes through unchanged', () => {
+ const md = '# hi\n\n\nextra';
+ expect(format_file(md, {filepath: 'foo.md'})).toBe(md);
+ expect(format_file(md)).toBe(md);
+});
+
+test('non-strict json (jsonc) passes through unchanged', () => {
+ const jsonc = '{\n\t// a comment\n\t"a": 1\n}';
+ expect(format_file(jsonc, {filepath: 'tsconfig.json'})).toBe(jsonc);
+});
+
+test('invalid source throws', () => {
+ expect(() => format_file('const = =', {filepath: 'foo.ts'})).toThrow();
});
diff --git a/tsconfig.json b/tsconfig.json
index 0300902f5a..6e255c09cf 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,9 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
- "types": ["@sveltejs/kit"],
+ "types": [
+ "@sveltejs/kit"
+ ],
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,