@ryanatkn/gro 0.124.0 → 0.125.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.
@@ -1,8 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { join } from 'node:path';
3
3
  import { readFile, writeFile } from 'node:fs/promises';
4
- import { plural } from '@ryanatkn/belt/string.js';
4
+ import { count_graphemes, plural } from '@ryanatkn/belt/string.js';
5
5
  import { strip_end } from '@ryanatkn/belt/string.js';
6
+ import { red } from 'kleur/colors';
6
7
  import { paths, gro_paths, IS_THIS_GRO, replace_extension } from './paths.js';
7
8
  import { SVELTEKIT_DIST_DIRNAME } from './path_constants.js';
8
9
  import { search_fs } from './search_fs.js';
@@ -51,25 +52,33 @@ export const Package_Json_Exports = z.record(z.union([z.string(), z.record(z.str
51
52
  /**
52
53
  * @see https://docs.npmjs.com/cli/v10/configuring-npm/package-json
53
54
  */
54
- export const Package_Json = z.intersection(z.record(z.unknown()), z
55
+ export const Package_Json = z
55
56
  .object({
56
57
  // according to the npm docs, `name` and `version` are the only required properties
57
58
  name: z.string(),
58
59
  version: z.string(),
59
- // Gro extensions
60
+ private: z.boolean({ description: 'disallow npm publish' }).optional(),
60
61
  public: z
61
62
  .boolean({
62
63
  description: 'a Gro extension that enables publishing `.well-known/package.json` and `.well-known/src`',
63
64
  })
64
65
  .optional(),
65
- icon: z.string({ description: 'a Gro extension' }).optional(), // TODO maybe base64 favicon?
66
- private: z.boolean({ description: 'disallow npm publish' }).optional(),
67
66
  description: z.string().optional(),
68
- motto: z.string().optional(),
67
+ motto: z
68
+ .string({ description: "a Gro extension that's a short phrase that represents this project" })
69
+ .optional(),
70
+ // TODO icon/favicon/logo that can point to a URL as an alternative to `<link rel="icon"`?
71
+ glyph: z
72
+ .string({
73
+ description: "a Gro extension that's a single unicode character that represents this project",
74
+ })
75
+ .refine((v) => count_graphemes(v) === 1, 'must be a single unicode character')
76
+ .optional(),
69
77
  license: z.string().optional(),
78
+ scripts: z.record(z.string()).optional(),
70
79
  homepage: Url.optional(),
71
- repository: z.union([z.string(), Url, Package_Json_Repository]).optional(),
72
80
  author: z.union([z.string(), Package_Json_Author.optional()]),
81
+ repository: z.union([z.string(), Url, Package_Json_Repository]).optional(),
73
82
  contributors: z.array(z.union([z.string(), Package_Json_Author])).optional(),
74
83
  bugs: z
75
84
  .union([z.string(), z.object({ url: Url.optional(), email: Email.optional() }).passthrough()])
@@ -78,20 +87,20 @@ export const Package_Json = z.intersection(z.record(z.unknown()), z
78
87
  .union([Url, Package_Json_Funding, z.array(z.union([Url, Package_Json_Funding]))])
79
88
  .optional(),
80
89
  keywords: z.array(z.string()).optional(),
81
- scripts: z.record(z.string()).optional(),
82
- bin: z.record(z.string()).optional(),
83
- files: z.array(z.string()).optional(),
84
- exports: Package_Json_Exports.transform(transform_empty_object_to_undefined).optional(),
90
+ type: z.string().optional(),
91
+ engines: z.record(z.string()).optional(),
92
+ os: z.array(z.string()).optional(),
93
+ cpu: z.array(z.string()).optional(),
85
94
  dependencies: z.record(z.string()).optional(),
86
95
  devDependencies: z.record(z.string()).optional(),
87
96
  peerDependencies: z.record(z.string()).optional(),
88
97
  peerDependenciesMeta: z.record(z.record(z.string())).optional(),
89
98
  optionalDependencies: z.record(z.string()).optional(),
90
- engines: z.record(z.string()).optional(),
91
- os: z.array(z.string()).optional(),
92
- cpu: z.array(z.string()).optional(),
99
+ bin: z.record(z.string()).optional(),
100
+ files: z.array(z.string()).optional(),
101
+ exports: Package_Json_Exports.transform(transform_empty_object_to_undefined).optional(),
93
102
  })
94
- .passthrough());
103
+ .passthrough();
95
104
  export const EMPTY_PACKAGE_JSON = { name: '', version: '' };
96
105
  export const load_package_json = async (dir = IS_THIS_GRO ? gro_paths.root : paths.root, cache) => {
97
106
  let package_json;
@@ -104,6 +113,7 @@ export const load_package_json = async (dir = IS_THIS_GRO ? gro_paths.root : pat
104
113
  catch (err) {
105
114
  return EMPTY_PACKAGE_JSON;
106
115
  }
116
+ package_json = parse_package_json(Package_Json, package_json);
107
117
  if (cache)
108
118
  cache[dir] = package_json;
109
119
  return package_json;
@@ -117,7 +127,7 @@ export const sync_package_json = async (map_package_json, log, check = false, di
117
127
  package_json.exports = exports;
118
128
  }
119
129
  const mapped = await map_package_json(package_json);
120
- return mapped ? Package_Json.parse(mapped) : mapped;
130
+ return mapped ? parse_package_json(Package_Json, mapped) : mapped;
121
131
  }, !check);
122
132
  const exports_count = updated.changed && updated.package_json?.exports
123
133
  ? Object.keys(updated.package_json.exports).length
@@ -133,10 +143,7 @@ const load_package_json_contents = (dir) => readFile(join(dir, 'package.json'),
133
143
  export const write_package_json = async (serialized_package_json) => {
134
144
  await writeFile(join(paths.root, 'package.json'), serialized_package_json);
135
145
  };
136
- export const serialize_package_json = (package_json) => {
137
- Package_Json.parse(package_json);
138
- return JSON.stringify(package_json, null, 2) + '\n';
139
- };
146
+ export const serialize_package_json = (package_json) => JSON.stringify(parse_package_json(Package_Json, package_json), null, 2) + '\n';
140
147
  /**
141
148
  * Updates package.json. Writes to the filesystem only when contents change.
142
149
  */
@@ -173,30 +180,30 @@ export const to_package_exports = (paths) => {
173
180
  else if (path.endsWith('.json.d.ts')) {
174
181
  const json_path = path.substring(0, path.length - 5);
175
182
  exports['./' + json_path] = {
176
- default: IMPORT_PREFIX + json_path, // assuming a matching json file
177
183
  types: IMPORT_PREFIX + path,
184
+ default: IMPORT_PREFIX + json_path, // assuming a matching json file
178
185
  };
179
186
  }
180
187
  else if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {
181
188
  const js_path = replace_extension(path, '.js');
182
189
  const key = is_index(path) ? '.' : './' + js_path;
183
190
  exports[key] = {
184
- default: IMPORT_PREFIX + js_path,
185
191
  types: IMPORT_PREFIX + replace_extension(path, '.d.ts'),
192
+ default: IMPORT_PREFIX + js_path,
186
193
  };
187
194
  }
188
195
  else if (path.endsWith('.js')) {
189
196
  const key = is_index(path) ? '.' : './' + path;
190
197
  exports[key] = {
191
- default: IMPORT_PREFIX + path,
192
198
  types: IMPORT_PREFIX + replace_extension(path, '.d.ts'), // assuming JSDoc types
199
+ default: IMPORT_PREFIX + path,
193
200
  };
194
201
  }
195
202
  else if (path.endsWith('.svelte')) {
196
203
  exports['./' + path] = {
204
+ types: IMPORT_PREFIX + path + '.d.ts',
197
205
  svelte: IMPORT_PREFIX + path,
198
206
  default: IMPORT_PREFIX + path, // needed for loader imports
199
- types: IMPORT_PREFIX + path + '.d.ts',
200
207
  };
201
208
  }
202
209
  else {
@@ -205,7 +212,7 @@ export const to_package_exports = (paths) => {
205
212
  };
206
213
  }
207
214
  }
208
- return Package_Json_Exports.parse(exports);
215
+ return parse_or_throw_formatted_error('package.json#exports', Package_Json_Exports, exports);
209
216
  };
210
217
  const IMPORT_PREFIX = './' + SVELTEKIT_DIST_DIRNAME + '/';
211
218
  export const parse_repo_url = (package_json) => {
@@ -225,3 +232,23 @@ export const parse_repo_url = (package_json) => {
225
232
  const [, owner, repo] = parsed_repo_url;
226
233
  return { owner, repo };
227
234
  };
235
+ /**
236
+ * Parses a `Package_Json` object but preserves the order of the original keys.
237
+ */
238
+ const parse_package_json = (schema, value) => {
239
+ const parsed = parse_or_throw_formatted_error('package.json', schema, value);
240
+ const keys = Object.keys(value);
241
+ return Object.fromEntries(Object.entries(parsed).sort(([a], [b]) => keys.indexOf(a) - keys.indexOf(b)));
242
+ };
243
+ // TODO maybe extract to zod helpers? see also everything in `task_logging.ts`
244
+ const parse_or_throw_formatted_error = (name, schema, value) => {
245
+ const parsed = schema.safeParse(value);
246
+ if (!parsed.success) {
247
+ let msg = red(`Failed to parse ${name}:\n`);
248
+ for (const issue of parsed.error.issues) {
249
+ msg += red(`\n\t"${issue.path}" ${issue.message}\n`);
250
+ }
251
+ throw Error(msg);
252
+ }
253
+ return parsed.data;
254
+ };
package/dist/path.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import type { Flavored } from '@ryanatkn/belt/types.js';
2
+ /**
3
+ * An absolute path on the filesystem. Named "id" to be consistent with Rollup.
4
+ */
2
5
  export type Path_Id = Flavored<string, 'Path_Id'>;
3
6
  export interface Path_Info {
4
7
  id: Path_Id;
@@ -13,12 +13,14 @@ export declare const Args: z.ZodObject<{
13
13
  'no-build': z.ZodDefault<z.ZodBoolean>;
14
14
  pull: z.ZodDefault<z.ZodBoolean>;
15
15
  'no-pull': z.ZodDefault<z.ZodBoolean>;
16
+ changeset_cli: z.ZodDefault<z.ZodString>;
16
17
  }, "strict", z.ZodTypeAny, {
17
18
  build: boolean;
18
19
  origin: string & z.BRAND<"Git_Origin">;
19
20
  pull: boolean;
20
21
  branch: string & z.BRAND<"Git_Branch">;
21
22
  changelog: string;
23
+ changeset_cli: string;
22
24
  check: boolean;
23
25
  dry: boolean;
24
26
  'no-build': boolean;
@@ -32,6 +34,7 @@ export declare const Args: z.ZodObject<{
32
34
  pull?: boolean | undefined;
33
35
  branch?: string | undefined;
34
36
  changelog?: string | undefined;
37
+ changeset_cli?: string | undefined;
35
38
  check?: boolean | undefined;
36
39
  dry?: boolean | undefined;
37
40
  'no-build'?: boolean | undefined;
@@ -10,6 +10,7 @@ import { has_sveltekit_library } from './sveltekit_helpers.js';
10
10
  import { update_changelog } from './changelog.js';
11
11
  import { load_from_env } from './env.js';
12
12
  import { Git_Branch, Git_Origin, git_check_clean_workspace, git_checkout, git_fetch, git_pull, } from './git.js';
13
+ import { CHANGESET_CLI } from './changeset_helpers.js';
13
14
  // publish.task.ts
14
15
  // - usage: `gro publish patch`
15
16
  // - forwards args to `npm version`: https://docs.npmjs.com/v6/commands/npm-version
@@ -40,13 +41,14 @@ export const Args = z
40
41
  'no-build': z.boolean({ description: 'opt out of building' }).default(false),
41
42
  pull: z.boolean({ description: 'dual of no-pull' }).default(true),
42
43
  'no-pull': z.boolean({ description: 'opt out of git pull' }).default(false),
44
+ changeset_cli: z.string({ description: 'the changeset CLI to use' }).default(CHANGESET_CLI),
43
45
  })
44
46
  .strict();
45
47
  export const task = {
46
48
  summary: 'bump version, publish to npm, and git push',
47
49
  Args,
48
50
  run: async ({ args, log, invoke_task }) => {
49
- const { branch, origin, changelog, preserve_changelog, dry, check, build, pull, optional } = args;
51
+ const { branch, origin, changelog, preserve_changelog, dry, check, build, pull, optional, changeset_cli, } = args;
50
52
  if (dry) {
51
53
  log.info(green('dry run!'));
52
54
  }
@@ -59,7 +61,8 @@ export const task = {
59
61
  await spawn('npm', ['run', 'build']);
60
62
  }
61
63
  const changelog_exists = existsSync(changelog);
62
- if (!(await find_cli('changeset'))) {
64
+ const found_changeset_cli = await find_cli(changeset_cli);
65
+ if (!found_changeset_cli) {
63
66
  throw new Task_Error('changeset command not found, install @changesets/cli locally or globally');
64
67
  }
65
68
  // Make sure we're on the right branch:
@@ -96,7 +99,7 @@ export const task = {
96
99
  ' or an object with the `url` key');
97
100
  }
98
101
  // This is the first line that alters the repo.
99
- const npmVersionResult = await spawn_cli('changeset', ['version']);
102
+ const npmVersionResult = await spawn_cli(found_changeset_cli, ['version'], log);
100
103
  if (!npmVersionResult?.ok) {
101
104
  throw Error('npm version failed: no commits were made: see the error above');
102
105
  }
@@ -116,7 +119,7 @@ export const task = {
116
119
  return; // exit gracefully
117
120
  }
118
121
  else {
119
- throw new Task_Error('changeset version failed: are there any changes?');
122
+ throw new Task_Error(`\`${changeset_cli} version\` failed: are there any changes?`);
120
123
  }
121
124
  }
122
125
  }
@@ -128,9 +131,9 @@ export const task = {
128
131
  log.info(green('dry run complete!'));
129
132
  return;
130
133
  }
131
- const npm_publish_result = await spawn_cli('changeset', ['publish']);
134
+ const npm_publish_result = await spawn_cli(found_changeset_cli, ['publish'], log);
132
135
  if (!npm_publish_result?.ok) {
133
- throw new Task_Error('changeset publish failed - revert the version tag or run it again manually');
136
+ throw new Task_Error(`\`${changeset_cli} publish\` failed - revert the version tag or run it again manually`);
134
137
  }
135
138
  if (!changelog_exists && existsSync(changelog)) {
136
139
  await spawn('git', ['add', changelog]);
@@ -3,11 +3,11 @@ export interface Search_Fs_Options {
3
3
  /**
4
4
  * One or more filter functions, any of which can short-circuit the search by returning `false`.
5
5
  */
6
- filter?: Path_Filter | Path_Filter[] | null;
6
+ filter?: Path_Filter | Path_Filter[];
7
7
  /**
8
8
  * An array of file suffixes to include.
9
9
  */
10
- file_filter?: File_Filter | File_Filter[] | null;
10
+ file_filter?: File_Filter | File_Filter[];
11
11
  /**
12
12
  * Pass `null` or `false` to speed things up at the cost of volatile ordering.
13
13
  */
@@ -1,17 +1,20 @@
1
1
  import type { Result } from '@ryanatkn/belt/result.js';
2
2
  import { Package_Json } from './package_json.js';
3
3
  import type { Parsed_Sveltekit_Config } from './sveltekit_config.js';
4
+ import { type Cli } from './cli.js';
4
5
  export declare const SVELTEKIT_CLI = "svelte-kit";
6
+ export declare const SVELTE_CHECK_CLI = "svelte-check";
5
7
  export declare const SVELTE_PACKAGE_CLI = "svelte-package";
6
8
  export declare const SVELTE_PACKAGE_DEP_NAME = "@sveltejs/package";
9
+ export declare const VITE_CLI = "vite";
7
10
  export declare const has_sveltekit_app: () => Promise<Result<object, {
8
11
  message: string;
9
12
  }>>;
10
13
  export declare const has_sveltekit_library: (package_json?: Package_Json, sveltekit_config?: Parsed_Sveltekit_Config) => Promise<Result<object, {
11
14
  message: string;
12
15
  }>>;
13
- export declare const sveltekit_sync: () => Promise<void>;
16
+ export declare const sveltekit_sync: (sveltekit_cli?: string | Cli) => Promise<void>;
14
17
  /**
15
18
  * If the SvelteKit CLI is found and its `.svelte-kit` directory is not, run `svelte-kit sync`.
16
19
  */
17
- export declare const sveltekit_sync_if_obviously_needed: () => Promise<void>;
20
+ export declare const sveltekit_sync_if_obviously_needed: (sveltekit_cli?: string | Cli) => Promise<void>;
@@ -2,11 +2,13 @@ import { existsSync } from 'node:fs';
2
2
  import { Package_Json, load_package_json } from './package_json.js';
3
3
  import { sveltekit_config_global } from './sveltekit_config_global.js';
4
4
  import { SVELTEKIT_CONFIG_FILENAME, SVELTEKIT_DEV_DIRNAME } from './path_constants.js';
5
- import { find_cli, spawn_cli } from './cli.js';
5
+ import { find_cli, spawn_cli, to_cli_name } from './cli.js';
6
6
  import { Task_Error } from './task.js';
7
7
  export const SVELTEKIT_CLI = 'svelte-kit';
8
+ export const SVELTE_CHECK_CLI = 'svelte-check';
8
9
  export const SVELTE_PACKAGE_CLI = 'svelte-package';
9
10
  export const SVELTE_PACKAGE_DEP_NAME = '@sveltejs/package';
11
+ export const VITE_CLI = 'vite';
10
12
  export const has_sveltekit_app = async () => {
11
13
  if (!existsSync(SVELTEKIT_CONFIG_FILENAME)) {
12
14
  return { ok: false, message: `no SvelteKit config found at ${SVELTEKIT_CONFIG_FILENAME}` };
@@ -31,24 +33,25 @@ export const has_sveltekit_library = async (package_json, sveltekit_config = sve
31
33
  }
32
34
  return { ok: true };
33
35
  };
34
- export const sveltekit_sync = async () => {
35
- if (!(await find_cli(SVELTEKIT_CLI))) {
36
- throw new Task_Error(`Failed to find ${SVELTEKIT_CLI} CLI - do you need to run \`npm i\`?`);
36
+ export const sveltekit_sync = async (sveltekit_cli = SVELTEKIT_CLI) => {
37
+ const result = await spawn_cli(sveltekit_cli, ['sync']);
38
+ if (!result) {
39
+ throw new Task_Error(`Failed to find SvelteKit CLI \`${to_cli_name(sveltekit_cli)}\`, do you need to run \`npm i\`?`);
37
40
  }
38
- const result = await spawn_cli(SVELTEKIT_CLI, ['sync']);
39
- if (!result?.ok) {
40
- throw new Task_Error(`Failed ${SVELTEKIT_CLI} sync`);
41
+ else if (!result.ok) {
42
+ throw new Task_Error(`Failed ${to_cli_name(sveltekit_cli)} sync`);
41
43
  }
42
44
  };
43
45
  /**
44
46
  * If the SvelteKit CLI is found and its `.svelte-kit` directory is not, run `svelte-kit sync`.
45
47
  */
46
- export const sveltekit_sync_if_obviously_needed = async () => {
48
+ export const sveltekit_sync_if_obviously_needed = async (sveltekit_cli = SVELTEKIT_CLI) => {
47
49
  if (existsSync(SVELTEKIT_DEV_DIRNAME)) {
48
50
  return;
49
51
  }
50
- if (!(await find_cli(SVELTEKIT_CLI))) {
52
+ const found_sveltekit_cli = typeof sveltekit_cli === 'string' ? await find_cli(sveltekit_cli) : sveltekit_cli;
53
+ if (!found_sveltekit_cli) {
51
54
  return;
52
55
  }
53
- return sveltekit_sync();
56
+ return sveltekit_sync(found_sveltekit_cli);
54
57
  };
package/dist/test.task.js CHANGED
@@ -24,8 +24,7 @@ export const task = {
24
24
  log.warn(yellow('uvu is not installed, skipping tests'));
25
25
  return;
26
26
  }
27
- const { run } = await import('uvu/run');
28
- const { parse } = await import('uvu/parse');
27
+ const [{ run }, { parse }] = await Promise.all([import('uvu/run'), import('uvu/parse')]);
29
28
  // uvu doesn't work with esm loaders and TypeScript files,
30
29
  // so we use its `parse` and `run` APIs directly instead of its CLI.
31
30
  // To avoid surprises, we allow any number of patterns in the rest args,
@@ -1,5 +1,14 @@
1
1
  import { z } from 'zod';
2
2
  import { type Task } from './task.js';
3
- export declare const Args: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
3
+ export declare const Args: z.ZodObject<{
4
+ svelte_check_cli: z.ZodDefault<z.ZodString>;
5
+ typescript_cli: z.ZodDefault<z.ZodString>;
6
+ }, "strict", z.ZodTypeAny, {
7
+ svelte_check_cli: string;
8
+ typescript_cli: string;
9
+ }, {
10
+ svelte_check_cli?: string | undefined;
11
+ typescript_cli?: string | undefined;
12
+ }>;
4
13
  export type Args = z.infer<typeof Args>;
5
14
  export declare const task: Task<Args>;
@@ -1,38 +1,48 @@
1
1
  import { print_spawn_result } from '@ryanatkn/belt/process.js';
2
2
  import { z } from 'zod';
3
3
  import { Task_Error } from './task.js';
4
- import { print_command_args, serialize_args, to_forwarded_args } from './args.js';
4
+ import { serialize_args, to_forwarded_args } from './args.js';
5
5
  import { find_cli, spawn_cli } from './cli.js';
6
- import { sveltekit_sync } from './sveltekit_helpers.js';
7
- export const Args = z.object({}).strict();
6
+ import { SVELTE_CHECK_CLI, sveltekit_sync } from './sveltekit_helpers.js';
7
+ export const Args = z
8
+ .object({
9
+ svelte_check_cli: z
10
+ .string({ description: 'the svelte-check CLI to use' })
11
+ .default(SVELTE_CHECK_CLI),
12
+ typescript_cli: z
13
+ .string({ description: 'the TypeScript CLI to use as a fallback to svelte-check' })
14
+ .default('tsc'),
15
+ })
16
+ .strict();
8
17
  export const task = {
9
18
  summary: 'run tsc on the project without emitting any files',
10
19
  Args,
11
- run: async ({ log }) => {
20
+ run: async ({ args, log }) => {
21
+ const { svelte_check_cli, typescript_cli } = args;
12
22
  await sveltekit_sync();
13
- if (await find_cli('svelte-check')) {
14
- // svelte-check
15
- const serialized = serialize_args(to_forwarded_args('svelte-check'));
16
- log.info(print_command_args(['svelte-check'].concat(serialized)));
17
- const svelte_check_result = await spawn_cli('svelte-check', serialized);
23
+ // Prefer svelte-check if available.
24
+ const found_svelte_check_cli = await find_cli(svelte_check_cli);
25
+ if (found_svelte_check_cli) {
26
+ const serialized = serialize_args(to_forwarded_args(svelte_check_cli));
27
+ const svelte_check_result = await spawn_cli(found_svelte_check_cli, serialized, log);
18
28
  if (!svelte_check_result?.ok) {
19
29
  throw new Task_Error(`Failed to typecheck. ${print_spawn_result(svelte_check_result)}`);
20
30
  }
31
+ return;
21
32
  }
22
- else if (await find_cli('tsc')) {
23
- // tsc
24
- const forwarded = to_forwarded_args('tsc');
33
+ // Fall back to tsc.
34
+ const found_typescript_cli = await find_cli(typescript_cli);
35
+ if (found_typescript_cli) {
36
+ const forwarded = to_forwarded_args(typescript_cli);
25
37
  if (!forwarded.noEmit)
26
38
  forwarded.noEmit = true;
27
39
  const serialized = serialize_args(forwarded);
28
- log.info(print_command_args(['tsc'].concat(serialized)));
29
- const svelte_check_result = await spawn_cli('tsc', serialized);
40
+ const svelte_check_result = await spawn_cli(found_typescript_cli, serialized, log);
30
41
  if (!svelte_check_result?.ok) {
31
42
  throw new Task_Error(`Failed to typecheck. ${print_spawn_result(svelte_check_result)}`);
32
43
  }
44
+ return;
33
45
  }
34
- else {
35
- throw new Task_Error(`Failed to typecheck because neither tsc nor svelte-check was found`);
36
- }
46
+ throw new Task_Error(`Failed to typecheck because neither \`${svelte_check_cli}\` nor \`${typescript_cli}\` was found`);
37
47
  },
38
48
  };