@fuzdev/gro 0.192.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/args.d.ts +37 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +102 -0
- package/dist/build.task.d.ts +20 -0
- package/dist/build.task.d.ts.map +1 -0
- package/dist/build.task.js +119 -0
- package/dist/build_cache.d.ts +100 -0
- package/dist/build_cache.d.ts.map +1 -0
- package/dist/build_cache.js +299 -0
- package/dist/changelog.d.ts +11 -0
- package/dist/changelog.d.ts.map +1 -0
- package/dist/changelog.js +47 -0
- package/dist/changeset.task.d.ts +35 -0
- package/dist/changeset.task.d.ts.map +1 -0
- package/dist/changeset.task.js +151 -0
- package/dist/changeset_helpers.d.ts +17 -0
- package/dist/changeset_helpers.d.ts.map +1 -0
- package/dist/changeset_helpers.js +7 -0
- package/dist/check.task.d.ts +28 -0
- package/dist/check.task.d.ts.map +1 -0
- package/dist/check.task.js +104 -0
- package/dist/child_process_logging.d.ts +10 -0
- package/dist/child_process_logging.d.ts.map +1 -0
- package/dist/child_process_logging.js +26 -0
- package/dist/clean.task.d.ts +15 -0
- package/dist/clean.task.d.ts.map +1 -0
- package/dist/clean.task.js +40 -0
- package/dist/clean_fs.d.ts +9 -0
- package/dist/clean_fs.d.ts.map +1 -0
- package/dist/clean_fs.js +28 -0
- package/dist/cli.d.ts +34 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +61 -0
- package/dist/commit.task.d.ts +11 -0
- package/dist/commit.task.d.ts.map +1 -0
- package/dist/commit.task.js +24 -0
- package/dist/constants.d.ts +46 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +52 -0
- package/dist/deploy.task.d.ts +29 -0
- package/dist/deploy.task.d.ts.map +1 -0
- package/dist/deploy.task.js +217 -0
- package/dist/dev.task.d.ts +16 -0
- package/dist/dev.task.d.ts.map +1 -0
- package/dist/dev.task.js +44 -0
- package/dist/disknode.d.ts +23 -0
- package/dist/disknode.d.ts.map +1 -0
- package/dist/disknode.js +1 -0
- package/dist/env.d.ts +11 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +49 -0
- package/dist/esbuild_helpers.d.ts +16 -0
- package/dist/esbuild_helpers.d.ts.map +1 -0
- package/dist/esbuild_helpers.js +36 -0
- package/dist/esbuild_plugin_external_worker.d.ts +23 -0
- package/dist/esbuild_plugin_external_worker.d.ts.map +1 -0
- package/dist/esbuild_plugin_external_worker.js +55 -0
- package/dist/esbuild_plugin_svelte.d.ts +15 -0
- package/dist/esbuild_plugin_svelte.d.ts.map +1 -0
- package/dist/esbuild_plugin_svelte.js +83 -0
- package/dist/esbuild_plugin_sveltekit_local_imports.d.ts +8 -0
- package/dist/esbuild_plugin_sveltekit_local_imports.d.ts.map +1 -0
- package/dist/esbuild_plugin_sveltekit_local_imports.js +30 -0
- package/dist/esbuild_plugin_sveltekit_shim_alias.d.ts +7 -0
- package/dist/esbuild_plugin_sveltekit_shim_alias.d.ts.map +1 -0
- package/dist/esbuild_plugin_sveltekit_shim_alias.js +18 -0
- package/dist/esbuild_plugin_sveltekit_shim_app.d.ts +9 -0
- package/dist/esbuild_plugin_sveltekit_shim_app.d.ts.map +1 -0
- package/dist/esbuild_plugin_sveltekit_shim_app.js +22 -0
- package/dist/esbuild_plugin_sveltekit_shim_env.d.ts +11 -0
- package/dist/esbuild_plugin_sveltekit_shim_env.d.ts.map +1 -0
- package/dist/esbuild_plugin_sveltekit_shim_env.js +18 -0
- package/dist/filer.d.ts +33 -0
- package/dist/filer.d.ts.map +1 -0
- package/dist/filer.js +385 -0
- package/dist/format.task.d.ts +11 -0
- package/dist/format.task.d.ts.map +1 -0
- package/dist/format.task.js +27 -0
- package/dist/format_directory.d.ts +13 -0
- package/dist/format_directory.d.ts.map +1 -0
- package/dist/format_directory.js +40 -0
- package/dist/format_file.d.ts +9 -0
- package/dist/format_file.d.ts.map +1 -0
- package/dist/format_file.js +42 -0
- package/dist/gen.d.ts +142 -0
- package/dist/gen.d.ts.map +1 -0
- package/dist/gen.js +199 -0
- package/dist/gen.task.d.ts +12 -0
- package/dist/gen.task.d.ts.map +1 -0
- package/dist/gen.task.js +149 -0
- package/dist/gen_helpers.d.ts +11 -0
- package/dist/gen_helpers.d.ts.map +1 -0
- package/dist/gen_helpers.js +76 -0
- package/dist/github.d.ts +19 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +33 -0
- package/dist/gro.config.default.d.ts +13 -0
- package/dist/gro.config.default.d.ts.map +1 -0
- package/dist/gro.config.default.js +33 -0
- package/dist/gro.d.ts +3 -0
- package/dist/gro.d.ts.map +1 -0
- package/dist/gro.js +21 -0
- package/dist/gro_config.d.ts +115 -0
- package/dist/gro_config.d.ts.map +1 -0
- package/dist/gro_config.js +114 -0
- package/dist/gro_helpers.d.ts +49 -0
- package/dist/gro_helpers.d.ts.map +1 -0
- package/dist/gro_helpers.js +97 -0
- package/dist/gro_plugin_gen.d.ts +12 -0
- package/dist/gro_plugin_gen.d.ts.map +1 -0
- package/dist/gro_plugin_gen.js +101 -0
- package/dist/gro_plugin_server.d.ts +80 -0
- package/dist/gro_plugin_server.d.ts.map +1 -0
- package/dist/gro_plugin_server.js +167 -0
- package/dist/gro_plugin_sveltekit_app.d.ts +9 -0
- package/dist/gro_plugin_sveltekit_app.d.ts.map +1 -0
- package/dist/gro_plugin_sveltekit_app.js +42 -0
- package/dist/gro_plugin_sveltekit_library.d.ts +16 -0
- package/dist/gro_plugin_sveltekit_library.d.ts.map +1 -0
- package/dist/gro_plugin_sveltekit_library.js +34 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/input_path.d.ts +64 -0
- package/dist/input_path.d.ts.map +1 -0
- package/dist/input_path.js +199 -0
- package/dist/invoke.d.ts +2 -0
- package/dist/invoke.d.ts.map +1 -0
- package/dist/invoke.js +28 -0
- package/dist/invoke_task.d.ts +30 -0
- package/dist/invoke_task.d.ts.map +1 -0
- package/dist/invoke_task.js +104 -0
- package/dist/lint.task.d.ts +11 -0
- package/dist/lint.task.d.ts.map +1 -0
- package/dist/lint.task.js +32 -0
- package/dist/loader.d.ts +6 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +192 -0
- package/dist/module.d.ts +4 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +6 -0
- package/dist/modules.d.ts +36 -0
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +71 -0
- package/dist/package_json.d.ts +32 -0
- package/dist/package_json.d.ts.map +1 -0
- package/dist/package_json.js +178 -0
- package/dist/parse_exports.d.ts +20 -0
- package/dist/parse_exports.d.ts.map +1 -0
- package/dist/parse_exports.js +65 -0
- package/dist/parse_exports_context.d.ts +21 -0
- package/dist/parse_exports_context.d.ts.map +1 -0
- package/dist/parse_exports_context.js +332 -0
- package/dist/parse_imports.d.ts +5 -0
- package/dist/parse_imports.d.ts.map +1 -0
- package/dist/parse_imports.js +140 -0
- package/dist/paths.d.ts +41 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +69 -0
- package/dist/plugin.d.ts +36 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +78 -0
- package/dist/publish.task.d.ts +26 -0
- package/dist/publish.task.d.ts.map +1 -0
- package/dist/publish.task.js +176 -0
- package/dist/register.d.ts +2 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +2 -0
- package/dist/reinstall.task.d.ts +8 -0
- package/dist/reinstall.task.d.ts.map +1 -0
- package/dist/reinstall.task.js +35 -0
- package/dist/release.task.d.ts +8 -0
- package/dist/release.task.d.ts.map +1 -0
- package/dist/release.task.js +20 -0
- package/dist/resolve.task.d.ts +11 -0
- package/dist/resolve.task.d.ts.map +1 -0
- package/dist/resolve.task.js +38 -0
- package/dist/resolve_specifier.d.ts +22 -0
- package/dist/resolve_specifier.d.ts.map +1 -0
- package/dist/resolve_specifier.js +57 -0
- package/dist/run.task.d.ts +16 -0
- package/dist/run.task.d.ts.map +1 -0
- package/dist/run.task.js +52 -0
- package/dist/run_gen.d.ts +10 -0
- package/dist/run_gen.d.ts.map +1 -0
- package/dist/run_gen.js +73 -0
- package/dist/run_task.d.ts +17 -0
- package/dist/run_task.d.ts.map +1 -0
- package/dist/run_task.js +45 -0
- package/dist/source_json.d.ts +7 -0
- package/dist/source_json.d.ts.map +1 -0
- package/dist/source_json.js +145 -0
- package/dist/svelte_config.d.ts +57 -0
- package/dist/svelte_config.d.ts.map +1 -0
- package/dist/svelte_config.js +81 -0
- package/dist/sveltekit_helpers.d.ts +75 -0
- package/dist/sveltekit_helpers.d.ts.map +1 -0
- package/dist/sveltekit_helpers.js +94 -0
- package/dist/sveltekit_shim_app.d.ts +11 -0
- package/dist/sveltekit_shim_app.d.ts.map +1 -0
- package/dist/sveltekit_shim_app.js +31 -0
- package/dist/sveltekit_shim_app_environment.d.ts +13 -0
- package/dist/sveltekit_shim_app_environment.d.ts.map +1 -0
- package/dist/sveltekit_shim_app_environment.js +14 -0
- package/dist/sveltekit_shim_app_forms.d.ts +5 -0
- package/dist/sveltekit_shim_app_forms.d.ts.map +1 -0
- package/dist/sveltekit_shim_app_forms.js +6 -0
- package/dist/sveltekit_shim_app_navigation.d.ts +10 -0
- package/dist/sveltekit_shim_app_navigation.d.ts.map +1 -0
- package/dist/sveltekit_shim_app_navigation.js +11 -0
- package/dist/sveltekit_shim_app_paths.d.ts +17 -0
- package/dist/sveltekit_shim_app_paths.d.ts.map +1 -0
- package/dist/sveltekit_shim_app_paths.js +10 -0
- package/dist/sveltekit_shim_app_state.d.ts +5 -0
- package/dist/sveltekit_shim_app_state.d.ts.map +1 -0
- package/dist/sveltekit_shim_app_state.js +26 -0
- package/dist/sveltekit_shim_env.d.ts +5 -0
- package/dist/sveltekit_shim_env.d.ts.map +1 -0
- package/dist/sveltekit_shim_env.js +23 -0
- package/dist/sync.task.d.ts +16 -0
- package/dist/sync.task.d.ts.map +1 -0
- package/dist/sync.task.js +39 -0
- package/dist/task.d.ts +98 -0
- package/dist/task.d.ts.map +1 -0
- package/dist/task.js +109 -0
- package/dist/task_logging.d.ts +6 -0
- package/dist/task_logging.d.ts.map +1 -0
- package/dist/task_logging.js +201 -0
- package/dist/test.task.d.ts +13 -0
- package/dist/test.task.d.ts.map +1 -0
- package/dist/test.task.js +53 -0
- package/dist/typecheck.task.d.ts +13 -0
- package/dist/typecheck.task.d.ts.map +1 -0
- package/dist/typecheck.task.js +68 -0
- package/dist/upgrade.task.d.ts +20 -0
- package/dist/upgrade.task.d.ts.map +1 -0
- package/dist/upgrade.task.js +111 -0
- package/dist/watch_dir.d.ts +36 -0
- package/dist/watch_dir.d.ts.map +1 -0
- package/dist/watch_dir.js +69 -0
- package/package.json +149 -0
- package/src/lib/args.ts +115 -0
- package/src/lib/build.task.ts +151 -0
- package/src/lib/build_cache.ts +378 -0
- package/src/lib/changelog.ts +69 -0
- package/src/lib/changeset.task.ts +228 -0
- package/src/lib/changeset_helpers.ts +14 -0
- package/src/lib/check.task.ts +132 -0
- package/src/lib/child_process_logging.ts +38 -0
- package/src/lib/clean.task.ts +48 -0
- package/src/lib/clean_fs.ts +54 -0
- package/src/lib/cli.ts +98 -0
- package/src/lib/commit.task.ts +34 -0
- package/src/lib/constants.ts +56 -0
- package/src/lib/deploy.task.ts +287 -0
- package/src/lib/dev.task.ts +52 -0
- package/src/lib/disknode.ts +26 -0
- package/src/lib/env.ts +78 -0
- package/src/lib/esbuild_helpers.ts +49 -0
- package/src/lib/esbuild_plugin_external_worker.ts +94 -0
- package/src/lib/esbuild_plugin_svelte.ts +134 -0
- package/src/lib/esbuild_plugin_sveltekit_local_imports.ts +38 -0
- package/src/lib/esbuild_plugin_sveltekit_shim_alias.ts +27 -0
- package/src/lib/esbuild_plugin_sveltekit_shim_app.ts +42 -0
- package/src/lib/esbuild_plugin_sveltekit_shim_env.ts +47 -0
- package/src/lib/filer.ts +458 -0
- package/src/lib/format.task.ts +44 -0
- package/src/lib/format_directory.ts +65 -0
- package/src/lib/format_file.ts +49 -0
- package/src/lib/gen.task.ts +206 -0
- package/src/lib/gen.ts +406 -0
- package/src/lib/gen_helpers.ts +131 -0
- package/src/lib/github.ts +46 -0
- package/src/lib/gro.config.default.ts +42 -0
- package/src/lib/gro.ts +29 -0
- package/src/lib/gro_config.ts +254 -0
- package/src/lib/gro_helpers.ts +108 -0
- package/src/lib/gro_plugin_gen.ts +149 -0
- package/src/lib/gro_plugin_server.ts +288 -0
- package/src/lib/gro_plugin_sveltekit_app.ts +58 -0
- package/src/lib/gro_plugin_sveltekit_library.ts +63 -0
- package/src/lib/index.ts +8 -0
- package/src/lib/input_path.ts +254 -0
- package/src/lib/invoke.ts +34 -0
- package/src/lib/invoke_task.ts +139 -0
- package/src/lib/lint.task.ts +39 -0
- package/src/lib/loader.ts +229 -0
- package/src/lib/module.ts +13 -0
- package/src/lib/modules.ts +117 -0
- package/src/lib/package_json.ts +255 -0
- package/src/lib/parse_exports.ts +100 -0
- package/src/lib/parse_exports_context.ts +395 -0
- package/src/lib/parse_imports.ts +180 -0
- package/src/lib/paths.ts +111 -0
- package/src/lib/plugin.ts +106 -0
- package/src/lib/publish.task.ts +228 -0
- package/src/lib/register.ts +3 -0
- package/src/lib/reinstall.task.ts +45 -0
- package/src/lib/release.task.ts +26 -0
- package/src/lib/resolve.task.ts +43 -0
- package/src/lib/resolve_specifier.ts +81 -0
- package/src/lib/run.task.ts +65 -0
- package/src/lib/run_gen.ts +110 -0
- package/src/lib/run_task.ts +82 -0
- package/src/lib/source_json.ts +183 -0
- package/src/lib/svelte_config.ts +140 -0
- package/src/lib/sveltekit_helpers.ts +193 -0
- package/src/lib/sveltekit_shim_app.ts +41 -0
- package/src/lib/sveltekit_shim_app_environment.ts +16 -0
- package/src/lib/sveltekit_shim_app_forms.ts +13 -0
- package/src/lib/sveltekit_shim_app_navigation.ts +23 -0
- package/src/lib/sveltekit_shim_app_paths.ts +26 -0
- package/src/lib/sveltekit_shim_app_state.ts +35 -0
- package/src/lib/sveltekit_shim_env.ts +45 -0
- package/src/lib/sync.task.ts +47 -0
- package/src/lib/task.ts +245 -0
- package/src/lib/task_logging.ts +255 -0
- package/src/lib/test.task.ts +63 -0
- package/src/lib/typecheck.task.ts +81 -0
- package/src/lib/upgrade.task.ts +148 -0
- package/src/lib/watch_dir.ts +115 -0
package/src/lib/filer.ts
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import {EMPTY_OBJECT} from '@fuzdev/fuz_util/object.js';
|
|
2
|
+
import {readFile, stat} from 'node:fs/promises';
|
|
3
|
+
import {dirname, resolve} from 'node:path';
|
|
4
|
+
import type {OmitStrict} from '@fuzdev/fuz_util/types.js';
|
|
5
|
+
import {isBuiltin} from 'node:module';
|
|
6
|
+
import {fileURLToPath, pathToFileURL} from 'node:url';
|
|
7
|
+
import {UnreachableError} from '@fuzdev/fuz_util/error.js';
|
|
8
|
+
import type {Logger} from '@fuzdev/fuz_util/log.js';
|
|
9
|
+
import type {PackageJson} from '@fuzdev/fuz_util/package_json.js';
|
|
10
|
+
import type {FileFilter, PathId} from '@fuzdev/fuz_util/path.js';
|
|
11
|
+
import {hash_secure} from '@fuzdev/fuz_util/hash.js';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
watch_dir,
|
|
15
|
+
type WatchNodeFs,
|
|
16
|
+
type WatcherChange,
|
|
17
|
+
type WatchDirOptions,
|
|
18
|
+
type WatcherChangeCallback,
|
|
19
|
+
} from './watch_dir.ts';
|
|
20
|
+
import {paths} from './paths.ts';
|
|
21
|
+
import {parse_imports} from './parse_imports.ts';
|
|
22
|
+
import {resolve_specifier} from './resolve_specifier.ts';
|
|
23
|
+
import {default_svelte_config} from './svelte_config.ts';
|
|
24
|
+
import {map_sveltekit_aliases} from './sveltekit_helpers.ts';
|
|
25
|
+
import {SVELTEKIT_GLOBAL_SPECIFIER} from './constants.ts';
|
|
26
|
+
import type {Disknode} from './disknode.ts';
|
|
27
|
+
|
|
28
|
+
const aliases = Object.entries(default_svelte_config.alias);
|
|
29
|
+
|
|
30
|
+
export type OnFilerChange = (change: WatcherChange, disknode: Disknode) => void;
|
|
31
|
+
|
|
32
|
+
export interface FilerOptions {
|
|
33
|
+
watch_dir?: typeof watch_dir;
|
|
34
|
+
watch_dir_options?: Partial<OmitStrict<WatchDirOptions, 'on_change'>>;
|
|
35
|
+
package_json_cache?: Record<string, PackageJson>;
|
|
36
|
+
log?: Logger;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class Filer {
|
|
40
|
+
readonly root_dir: PathId;
|
|
41
|
+
|
|
42
|
+
// TODO rename everything to `disknode`
|
|
43
|
+
readonly files: Map<PathId, Disknode> = new Map();
|
|
44
|
+
|
|
45
|
+
#watch_dir: typeof watch_dir;
|
|
46
|
+
#watch_dir_options: Partial<WatchDirOptions>;
|
|
47
|
+
|
|
48
|
+
#log?: Logger;
|
|
49
|
+
|
|
50
|
+
#listeners: Set<OnFilerChange> = new Set();
|
|
51
|
+
#watching: WatchNodeFs | undefined;
|
|
52
|
+
#initing: Promise<void> | undefined;
|
|
53
|
+
#closing: Promise<void> | undefined;
|
|
54
|
+
|
|
55
|
+
#change_queue: Array<WatcherChange> = [];
|
|
56
|
+
#processing_promise: Promise<void> | null = null;
|
|
57
|
+
|
|
58
|
+
constructor(options: FilerOptions = EMPTY_OBJECT) {
|
|
59
|
+
this.#watch_dir = options.watch_dir ?? watch_dir;
|
|
60
|
+
this.#watch_dir_options = options.watch_dir_options ?? EMPTY_OBJECT;
|
|
61
|
+
this.root_dir = resolve(options.watch_dir_options?.dir ?? paths.source);
|
|
62
|
+
// TODO for package.json maybe another array of files/dirs to watch to invalidate everything?
|
|
63
|
+
// or instead of that, think of taking an array of config objects that can specify invalidation rules,
|
|
64
|
+
// so package.json would be configured differently than ./src, and we could add a default with
|
|
65
|
+
// package.json/gro.config.ts/tsconfig.json/svelte.config.js/vite.config.ts to invalidate everything
|
|
66
|
+
this.#log = options.log;
|
|
67
|
+
}
|
|
68
|
+
get inited(): boolean {
|
|
69
|
+
return this.#watching !== undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get_by_id = (id: PathId): Disknode | undefined => {
|
|
73
|
+
return this.files.get(id);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
get_or_create = (id: PathId): Disknode => {
|
|
77
|
+
const existing = this.get_by_id(id);
|
|
78
|
+
if (existing) return existing;
|
|
79
|
+
const file: Disknode = {
|
|
80
|
+
id,
|
|
81
|
+
contents: null,
|
|
82
|
+
external: this.#is_external(id), // TODO maybe filter externals by default? the user needs to configure the filer then
|
|
83
|
+
ctime: null,
|
|
84
|
+
mtime: null,
|
|
85
|
+
content_hash: null,
|
|
86
|
+
dependents: new Map(),
|
|
87
|
+
dependencies: new Map(),
|
|
88
|
+
};
|
|
89
|
+
this.files.set(id, file);
|
|
90
|
+
// Defer external file change notification to avoid reentrancy during queue processing
|
|
91
|
+
if (file.external) {
|
|
92
|
+
queueMicrotask(() => {
|
|
93
|
+
this.#on_change({type: 'add', path: file.id, is_directory: false});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return file;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
filter(predicate: (disknode: Disknode) => boolean): Array<Disknode> | null {
|
|
100
|
+
let found: Array<Disknode> | null = null;
|
|
101
|
+
for (const disknode of this.files.values()) {
|
|
102
|
+
if (predicate(disknode)) {
|
|
103
|
+
(found ??= []).push(disknode);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return found;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Initialize the filer to populate files without watching.
|
|
111
|
+
* Safe to call multiple times - subsequent calls are no-ops.
|
|
112
|
+
* Used by gen files to access the file graph.
|
|
113
|
+
*/
|
|
114
|
+
async init(): Promise<void> {
|
|
115
|
+
// if already initing, return the existing promise
|
|
116
|
+
if (this.#initing) return this.#initing;
|
|
117
|
+
|
|
118
|
+
// if already initialized, just ensure ready
|
|
119
|
+
if (this.#watching) {
|
|
120
|
+
return this.#watching.init();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// start new initialization
|
|
124
|
+
this.#initing = this.#init();
|
|
125
|
+
try {
|
|
126
|
+
await this.#initing;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// use shared cleanup logic
|
|
129
|
+
this.#cleanup();
|
|
130
|
+
throw error;
|
|
131
|
+
} finally {
|
|
132
|
+
this.#initing = undefined;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async #init(): Promise<void> {
|
|
137
|
+
const watcher = this.#watch_dir({
|
|
138
|
+
...this.#watch_dir_options,
|
|
139
|
+
dir: this.root_dir,
|
|
140
|
+
on_change: this.#on_change,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
await watcher.init();
|
|
145
|
+
|
|
146
|
+
// Wait for any queued changes from init to be processed
|
|
147
|
+
await this.#drain_queue();
|
|
148
|
+
|
|
149
|
+
// check if close() was called during init
|
|
150
|
+
if (this.#closing) {
|
|
151
|
+
await watcher.close();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// only set after successful init and not closing
|
|
156
|
+
this.#watching = watcher;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
// clean up watcher on error, but don't let close error mask init error
|
|
159
|
+
try {
|
|
160
|
+
await watcher.close();
|
|
161
|
+
} catch {
|
|
162
|
+
// ignore close errors - init error is more important
|
|
163
|
+
}
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async watch(listener: OnFilerChange): Promise<() => void> {
|
|
169
|
+
await this.#add_listener(listener);
|
|
170
|
+
return () => {
|
|
171
|
+
this.#remove_listener(listener);
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Internal cleanup of all state - can be called safely from anywhere
|
|
177
|
+
*/
|
|
178
|
+
#cleanup(): void {
|
|
179
|
+
this.#listeners.clear();
|
|
180
|
+
this.files.clear();
|
|
181
|
+
this.#watching = undefined;
|
|
182
|
+
this.#change_queue = [];
|
|
183
|
+
this.#processing_promise = null;
|
|
184
|
+
// #initing is handled in finally block of init()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
close(): Promise<void> {
|
|
188
|
+
// if already closing, return existing promise
|
|
189
|
+
if (this.#closing) return this.#closing;
|
|
190
|
+
|
|
191
|
+
// if already closed and not initing, nothing to do
|
|
192
|
+
if (!this.#watching && !this.#initing) return Promise.resolve();
|
|
193
|
+
|
|
194
|
+
// start new close operation
|
|
195
|
+
const closing = this.#close();
|
|
196
|
+
this.#closing = closing;
|
|
197
|
+
// Clean up after completion, but don't change the returned promise
|
|
198
|
+
// Use void to ensure we don't accidentally return the .then() promise
|
|
199
|
+
void closing.then(
|
|
200
|
+
() => {
|
|
201
|
+
this.#closing = undefined;
|
|
202
|
+
},
|
|
203
|
+
() => {
|
|
204
|
+
this.#closing = undefined;
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
return this.#closing;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async #close(): Promise<void> {
|
|
211
|
+
// wait for any pending initialization to complete
|
|
212
|
+
if (this.#initing) {
|
|
213
|
+
try {
|
|
214
|
+
await this.#initing;
|
|
215
|
+
} catch {
|
|
216
|
+
// ignore errors during close
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// close watcher if it exists
|
|
221
|
+
if (this.#watching) {
|
|
222
|
+
await this.#watching.close();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// clean up all state
|
|
226
|
+
this.#cleanup();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async #update(id: PathId): Promise<Disknode | null> {
|
|
230
|
+
const file = this.get_or_create(id);
|
|
231
|
+
|
|
232
|
+
let stats: Awaited<ReturnType<typeof stat>> | null = null;
|
|
233
|
+
let new_contents: string | null = null; // TODO need to lazily load contents, probably turn `Disknode` into a class
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
[stats, new_contents] = await Promise.all([stat(id), readFile(id, 'utf8')]);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
239
|
+
// Treat file as deleted/inaccessible for common error codes
|
|
240
|
+
if (code === 'ENOENT' || code === 'EACCES' || code === 'EPERM') {
|
|
241
|
+
// File doesn't exist or is inaccessible, treat as deleted
|
|
242
|
+
} else {
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Compute hash for new contents
|
|
248
|
+
const new_hash = new_contents !== null ? await hash_secure(new_contents) : null;
|
|
249
|
+
|
|
250
|
+
file.ctime = stats?.ctimeMs ?? null;
|
|
251
|
+
file.mtime = stats?.mtimeMs ?? null;
|
|
252
|
+
|
|
253
|
+
// Use hash comparison for change detection (content-based, not mtime-based)
|
|
254
|
+
if (file.content_hash === new_hash) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
file.contents = new_contents;
|
|
259
|
+
file.content_hash = new_hash;
|
|
260
|
+
|
|
261
|
+
const dir = dirname(file.id);
|
|
262
|
+
|
|
263
|
+
const dependencies_before = new Set(file.dependencies.keys());
|
|
264
|
+
const dependencies_removed = new Set(dependencies_before);
|
|
265
|
+
|
|
266
|
+
let imported: Array<string> = [];
|
|
267
|
+
if (file.contents) {
|
|
268
|
+
try {
|
|
269
|
+
imported = parse_imports(file.id, file.contents);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
this.#log?.error('[filer] Failed to parse imports', file.id, error);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
for (const specifier of imported) {
|
|
275
|
+
if (SVELTEKIT_GLOBAL_SPECIFIER.test(specifier)) continue;
|
|
276
|
+
const path = map_sveltekit_aliases(specifier, aliases);
|
|
277
|
+
|
|
278
|
+
let path_id;
|
|
279
|
+
// TODO can we replace `resolve_specifier` with `import.meta.resolve` completely now outside of esbuild plugins?
|
|
280
|
+
if (path[0] === '.' || path[0] === '/') {
|
|
281
|
+
const resolved = await resolve_specifier(path, dir); // eslint-disable-line no-await-in-loop
|
|
282
|
+
path_id = resolved.path_id;
|
|
283
|
+
} else {
|
|
284
|
+
if (isBuiltin(path)) continue;
|
|
285
|
+
const file_url = pathToFileURL(file.id);
|
|
286
|
+
try {
|
|
287
|
+
path_id = fileURLToPath(import.meta.resolve(path, file_url.href));
|
|
288
|
+
} catch (error) {
|
|
289
|
+
// if resolving fails for any reason, just log and ignore it
|
|
290
|
+
this.#log?.error('[filer] failed to resolve path', path, file_url.href, error);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
dependencies_removed.delete(path_id);
|
|
295
|
+
if (!dependencies_before.has(path_id)) {
|
|
296
|
+
const d = this.get_or_create(path_id);
|
|
297
|
+
file.dependencies.set(d.id, d);
|
|
298
|
+
d.dependents.set(file.id, file);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// update any removed dependencies
|
|
303
|
+
for (const dependency_removed of dependencies_removed) {
|
|
304
|
+
file.dependencies.delete(dependency_removed);
|
|
305
|
+
const dependency_removed_file = this.get_or_create(dependency_removed);
|
|
306
|
+
dependency_removed_file.dependents.delete(file.id);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return file;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
#remove(id: PathId): Disknode | null {
|
|
313
|
+
const file = this.get_by_id(id);
|
|
314
|
+
if (!file) return null; // this is safe because the object would exist if any other file referenced it as a dependency or dependent
|
|
315
|
+
|
|
316
|
+
file.contents = null; // clear contents in case it gets re-added later, we want the change to be detected
|
|
317
|
+
file.content_hash = null; // clear hash so re-add detects the change
|
|
318
|
+
|
|
319
|
+
file.dependencies.clear();
|
|
320
|
+
|
|
321
|
+
// keep the file in memory if other files still depend on it
|
|
322
|
+
if (file.dependents.size === 0) {
|
|
323
|
+
this.files.delete(id);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return file;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
#sync_listener_with_files(listener: OnFilerChange): void {
|
|
330
|
+
for (const disknode of this.files.values()) {
|
|
331
|
+
try {
|
|
332
|
+
listener({type: 'add', path: disknode.id, is_directory: false}, disknode);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
this.#log?.error('[filer] Listener error during sync:', error);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
#notify_change(change: WatcherChange, disknode: Disknode): void {
|
|
340
|
+
for (const listener of this.#listeners) {
|
|
341
|
+
try {
|
|
342
|
+
listener(change, disknode);
|
|
343
|
+
} catch (error) {
|
|
344
|
+
this.#log?.error('[filer] Listener error during change notification:', error);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async #add_listener(listener: OnFilerChange): Promise<void> {
|
|
350
|
+
this.#listeners.add(listener);
|
|
351
|
+
|
|
352
|
+
// ensure initialized
|
|
353
|
+
await this.init();
|
|
354
|
+
|
|
355
|
+
// notify of existing files
|
|
356
|
+
this.#sync_listener_with_files(listener);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
#remove_listener(listener: OnFilerChange): void {
|
|
360
|
+
this.#listeners.delete(listener);
|
|
361
|
+
// keep watching active even with no listeners, only close() tears down
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async #drain_queue(): Promise<void> {
|
|
365
|
+
// Wait for queue to be empty and no active processing
|
|
366
|
+
while (this.#change_queue.length > 0 || this.#processing_promise) {
|
|
367
|
+
await this.#process_queue(); // eslint-disable-line no-await-in-loop
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async #process_queue(): Promise<void> {
|
|
372
|
+
// If already processing, return the existing promise
|
|
373
|
+
if (this.#processing_promise) return this.#processing_promise;
|
|
374
|
+
|
|
375
|
+
// Create and track the processing promise
|
|
376
|
+
this.#processing_promise = this.#do_process_queue();
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
await this.#processing_promise;
|
|
380
|
+
} finally {
|
|
381
|
+
this.#processing_promise = null;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async #do_process_queue(): Promise<void> {
|
|
386
|
+
while (this.#change_queue.length > 0) {
|
|
387
|
+
const change = this.#change_queue.shift()!;
|
|
388
|
+
|
|
389
|
+
if (this.#closing) continue; // ignore changes during close
|
|
390
|
+
if (change.is_directory) continue; // TODO manage directories?
|
|
391
|
+
|
|
392
|
+
let disknode: Disknode | null;
|
|
393
|
+
switch (change.type) {
|
|
394
|
+
case 'add':
|
|
395
|
+
case 'update': {
|
|
396
|
+
disknode = await this.#update(change.path); // eslint-disable-line no-await-in-loop
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
case 'delete': {
|
|
400
|
+
disknode = this.#remove(change.path);
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
default:
|
|
404
|
+
throw new UnreachableError(change.type);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (disknode && this.#listeners.size > 0) {
|
|
408
|
+
this.#notify_change(change, disknode);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
#on_change: WatcherChangeCallback = (change) => {
|
|
414
|
+
// Enqueue the change (sync callback from chokidar)
|
|
415
|
+
this.#change_queue.push(change);
|
|
416
|
+
|
|
417
|
+
// Start processing if not already running
|
|
418
|
+
void this.#process_queue();
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
#is_external(id: PathId): boolean {
|
|
422
|
+
const {filter} = this.#watch_dir_options;
|
|
423
|
+
return !id.startsWith(this.root_dir + '/') || (!!filter && !filter(id, false));
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// TODO maybe `Disknode` class?
|
|
428
|
+
export const filter_dependents = (
|
|
429
|
+
disknode: Disknode,
|
|
430
|
+
get_by_id: (id: PathId) => Disknode | undefined,
|
|
431
|
+
filter?: FileFilter,
|
|
432
|
+
results: Set<PathId> = new Set(),
|
|
433
|
+
searched: Set<PathId> = new Set(),
|
|
434
|
+
log?: Logger,
|
|
435
|
+
): Set<PathId> => {
|
|
436
|
+
// Use iterative approach to avoid stack overflow on deep dependency trees
|
|
437
|
+
const stack = [disknode];
|
|
438
|
+
|
|
439
|
+
while (stack.length > 0) {
|
|
440
|
+
const current = stack.pop()!;
|
|
441
|
+
for (const dependent_id of current.dependents.keys()) {
|
|
442
|
+
if (searched.has(dependent_id)) continue;
|
|
443
|
+
searched.add(dependent_id);
|
|
444
|
+
if (!filter || filter(dependent_id)) {
|
|
445
|
+
results.add(dependent_id);
|
|
446
|
+
}
|
|
447
|
+
const dependent_disknode = get_by_id(dependent_id);
|
|
448
|
+
if (!dependent_disknode) {
|
|
449
|
+
log?.warn(
|
|
450
|
+
`[filer.filter_dependents] dependent source file ${dependent_id} not found for ${current.id}`,
|
|
451
|
+
);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
stack.push(dependent_disknode);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return results;
|
|
458
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {print_spawn_result} from '@fuzdev/fuz_util/process.js';
|
|
2
|
+
import {z} from 'zod';
|
|
3
|
+
|
|
4
|
+
import {to_implicit_forwarded_args} from './args.ts';
|
|
5
|
+
import {PRETTIER_CLI_DEFAULT} from './constants.ts';
|
|
6
|
+
import {format_directory} from './format_directory.ts';
|
|
7
|
+
import {paths} from './paths.ts';
|
|
8
|
+
import {TaskError, type Task} from './task.ts';
|
|
9
|
+
|
|
10
|
+
/** @nodocs */
|
|
11
|
+
export const Args = z.strictObject({
|
|
12
|
+
_: z.array(z.string()).meta({description: 'files or directories to format'}).optional(),
|
|
13
|
+
check: z
|
|
14
|
+
.boolean()
|
|
15
|
+
.meta({description: 'exit with a nonzero code if any files are unformatted'})
|
|
16
|
+
.default(false),
|
|
17
|
+
});
|
|
18
|
+
export type Args = z.infer<typeof Args>;
|
|
19
|
+
|
|
20
|
+
/** @nodocs */
|
|
21
|
+
export const task: Task<Args> = {
|
|
22
|
+
summary: 'format source files',
|
|
23
|
+
Args,
|
|
24
|
+
run: async ({args, log, config}) => {
|
|
25
|
+
const {_: patterns, check} = args;
|
|
26
|
+
|
|
27
|
+
const format_result = await format_directory(
|
|
28
|
+
log,
|
|
29
|
+
paths.source,
|
|
30
|
+
check,
|
|
31
|
+
undefined,
|
|
32
|
+
undefined,
|
|
33
|
+
undefined,
|
|
34
|
+
config.pm_cli,
|
|
35
|
+
to_implicit_forwarded_args(PRETTIER_CLI_DEFAULT),
|
|
36
|
+
patterns,
|
|
37
|
+
);
|
|
38
|
+
if (!format_result.ok) {
|
|
39
|
+
throw new TaskError(
|
|
40
|
+
`Failed ${check ? 'formatting check' : 'to format'}. ${print_spawn_result(format_result)}`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {args_serialize, type Args} from '@fuzdev/fuz_util/args.js';
|
|
2
|
+
import type {Logger} from '@fuzdev/fuz_util/log.js';
|
|
3
|
+
import type {SpawnResult} from '@fuzdev/fuz_util/process.js';
|
|
4
|
+
|
|
5
|
+
import {spawn_cli, to_cli_name, type Cli} from './cli.ts';
|
|
6
|
+
import {
|
|
7
|
+
GITHUB_DIRNAME,
|
|
8
|
+
README_FILENAME,
|
|
9
|
+
SVELTE_CONFIG_FILENAME,
|
|
10
|
+
VITE_CONFIG_FILENAME,
|
|
11
|
+
TSCONFIG_FILENAME,
|
|
12
|
+
GRO_CONFIG_FILENAME,
|
|
13
|
+
PM_CLI_DEFAULT,
|
|
14
|
+
PRETTIER_CLI_DEFAULT,
|
|
15
|
+
} from './constants.ts';
|
|
16
|
+
import {paths} from './paths.ts';
|
|
17
|
+
|
|
18
|
+
const EXTENSIONS_DEFAULT = 'ts,js,json,svelte,html,css,md,yml';
|
|
19
|
+
const ROOT_PATHS_DEFAULT = `${[
|
|
20
|
+
README_FILENAME,
|
|
21
|
+
GRO_CONFIG_FILENAME,
|
|
22
|
+
SVELTE_CONFIG_FILENAME,
|
|
23
|
+
VITE_CONFIG_FILENAME,
|
|
24
|
+
TSCONFIG_FILENAME,
|
|
25
|
+
GITHUB_DIRNAME,
|
|
26
|
+
].join(',')}/**/*`;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Formats files on the filesystem.
|
|
30
|
+
* When `patterns` is provided, formats those specific files/patterns.
|
|
31
|
+
* Otherwise formats `dir` with default extensions, plus root files if `dir` is `paths.source`.
|
|
32
|
+
* This is separated from `./format_file` to avoid importing all of the `prettier` code
|
|
33
|
+
* inside modules that import this one. (which has a nontrivial cost)
|
|
34
|
+
*/
|
|
35
|
+
export const format_directory = async (
|
|
36
|
+
log: Logger,
|
|
37
|
+
dir: string,
|
|
38
|
+
check = false,
|
|
39
|
+
extensions = EXTENSIONS_DEFAULT,
|
|
40
|
+
root_paths = ROOT_PATHS_DEFAULT,
|
|
41
|
+
prettier_cli: string | Cli = PRETTIER_CLI_DEFAULT,
|
|
42
|
+
pm_cli: string = PM_CLI_DEFAULT,
|
|
43
|
+
additional_args?: Args,
|
|
44
|
+
patterns?: Array<string>,
|
|
45
|
+
): Promise<SpawnResult> => {
|
|
46
|
+
const forwarded_args = {...additional_args};
|
|
47
|
+
if (forwarded_args.check === undefined && forwarded_args.write === undefined) {
|
|
48
|
+
forwarded_args[check ? 'check' : 'write'] = true;
|
|
49
|
+
}
|
|
50
|
+
const serialized_args = args_serialize(forwarded_args);
|
|
51
|
+
if (patterns?.length) {
|
|
52
|
+
serialized_args.push(...patterns);
|
|
53
|
+
} else {
|
|
54
|
+
serialized_args.push(`${dir}**/*.{${extensions}}`);
|
|
55
|
+
if (dir === paths.source) {
|
|
56
|
+
serialized_args.push(`${paths.root}{${root_paths}}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const spawned = await spawn_cli(prettier_cli, serialized_args, log);
|
|
60
|
+
if (!spawned)
|
|
61
|
+
throw Error(
|
|
62
|
+
`failed to find \`${to_cli_name(prettier_cli)}\` CLI locally or globally, do you need to run \`${pm_cli} install\`?`,
|
|
63
|
+
);
|
|
64
|
+
return spawned;
|
|
65
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import prettier from 'prettier';
|
|
2
|
+
import {extname} from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {package_json_load} from './package_json.ts';
|
|
5
|
+
|
|
6
|
+
let cached_base_options: prettier.Options | undefined;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Formats a file with Prettier.
|
|
10
|
+
* @param content
|
|
11
|
+
* @param options
|
|
12
|
+
* @param base_options - defaults to the the cwd's package.json `prettier` value
|
|
13
|
+
*/
|
|
14
|
+
export const format_file = async (
|
|
15
|
+
content: string,
|
|
16
|
+
options: prettier.Options,
|
|
17
|
+
base_options: prettier.Options | null | undefined = cached_base_options,
|
|
18
|
+
): Promise<string> => {
|
|
19
|
+
const final_base_options =
|
|
20
|
+
base_options !== undefined
|
|
21
|
+
? base_options
|
|
22
|
+
: (cached_base_options = (await package_json_load()).prettier as any);
|
|
23
|
+
let final_options = options;
|
|
24
|
+
if (options.filepath && !options.parser) {
|
|
25
|
+
const {filepath, ...rest} = options;
|
|
26
|
+
const parser = infer_parser(filepath);
|
|
27
|
+
if (parser) final_options = {...rest, parser};
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
return await prettier.format(content, {...final_base_options, ...final_options});
|
|
31
|
+
} catch (_err) {
|
|
32
|
+
return content;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// This is just a simple convenience for callers so they can pass a file path.
|
|
37
|
+
// They can provide the Prettier `options.parser` for custom extensions.
|
|
38
|
+
const infer_parser = (path: string): string | null => {
|
|
39
|
+
const extension = extname(path).substring(1);
|
|
40
|
+
switch (extension) {
|
|
41
|
+
case 'svelte':
|
|
42
|
+
case 'xml': {
|
|
43
|
+
return extension;
|
|
44
|
+
}
|
|
45
|
+
default: {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|