@ryanatkn/gro 0.119.1 → 0.120.1

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.
Files changed (104) hide show
  1. package/README.md +3 -2
  2. package/dist/changelog.d.ts +1 -0
  3. package/dist/changeset.task.js +4 -5
  4. package/dist/clean_fs.d.ts +1 -2
  5. package/dist/clean_fs.js +3 -2
  6. package/dist/cli.d.ts +1 -2
  7. package/dist/cli.js +2 -2
  8. package/dist/config.d.ts +14 -1
  9. package/dist/config.js +17 -5
  10. package/dist/config.test.js +50 -1
  11. package/dist/deploy.task.js +8 -7
  12. package/dist/docs/README.gen.md.js +3 -3
  13. package/dist/docs/config.md +22 -6
  14. package/dist/docs/gen.md +2 -1
  15. package/dist/docs/task.md +4 -3
  16. package/dist/docs/tasks.gen.md.js +17 -10
  17. package/dist/docs/tasks.md +1 -0
  18. package/dist/env.d.ts +2 -3
  19. package/dist/env.js +2 -2
  20. package/dist/esbuild_helpers.d.ts +2 -1
  21. package/dist/esbuild_plugin_external_worker.d.ts +0 -1
  22. package/dist/esbuild_plugin_external_worker.js +7 -7
  23. package/dist/esbuild_plugin_svelte.d.ts +0 -1
  24. package/dist/esbuild_plugin_sveltekit_local_imports.js +2 -2
  25. package/dist/format_directory.d.ts +1 -0
  26. package/dist/fs.d.ts +1 -3
  27. package/dist/fs.js +3 -13
  28. package/dist/fs.test.js +5 -5
  29. package/dist/gen.d.ts +66 -13
  30. package/dist/gen.js +78 -9
  31. package/dist/gen.task.d.ts +3 -0
  32. package/dist/gen.task.js +24 -19
  33. package/dist/gen.test.js +48 -35
  34. package/dist/git.d.ts +0 -1
  35. package/dist/git.js +3 -3
  36. package/dist/gro_helpers.js +4 -4
  37. package/dist/gro_plugin_gen.js +7 -7
  38. package/dist/gro_plugin_server.d.ts +2 -2
  39. package/dist/gro_plugin_server.js +5 -5
  40. package/dist/gro_plugin_sveltekit_app.js +8 -8
  41. package/dist/hash.d.ts +1 -2
  42. package/dist/input_path.d.ts +34 -16
  43. package/dist/input_path.js +119 -67
  44. package/dist/input_path.test.js +150 -46
  45. package/dist/invoke_task.d.ts +2 -1
  46. package/dist/invoke_task.js +33 -79
  47. package/dist/loader.d.ts +0 -1
  48. package/dist/loader.js +4 -4
  49. package/dist/modules.d.ts +17 -36
  50. package/dist/modules.js +29 -68
  51. package/dist/modules.test.js +19 -143
  52. package/dist/package.d.ts +11 -20
  53. package/dist/package.js +61 -64
  54. package/dist/package_json.d.ts +1 -0
  55. package/dist/package_json.js +1 -1
  56. package/dist/package_meta.d.ts +1 -1
  57. package/dist/path.d.ts +12 -8
  58. package/dist/path.js +0 -6
  59. package/dist/paths.d.ts +7 -12
  60. package/dist/paths.js +11 -34
  61. package/dist/paths.test.js +17 -15
  62. package/dist/plugin.d.ts +1 -1
  63. package/dist/publish.task.js +3 -3
  64. package/dist/resolve.task.d.ts +11 -0
  65. package/dist/resolve.task.js +24 -0
  66. package/dist/resolve_node_specifier.d.ts +2 -2
  67. package/dist/resolve_node_specifier.js +3 -3
  68. package/dist/resolve_specifier.d.ts +2 -1
  69. package/dist/resolve_specifier.js +16 -16
  70. package/dist/resolve_specifier.test.js +9 -9
  71. package/dist/run.task.js +2 -2
  72. package/dist/run_gen.d.ts +4 -4
  73. package/dist/run_gen.js +9 -16
  74. package/dist/run_gen.test.js +10 -15
  75. package/dist/run_task.d.ts +2 -1
  76. package/dist/run_task.js +2 -0
  77. package/dist/search_fs.d.ts +20 -7
  78. package/dist/search_fs.js +40 -18
  79. package/dist/search_fs.test.js +9 -11
  80. package/dist/src_json.js +2 -2
  81. package/dist/sveltekit_config.d.ts +0 -1
  82. package/dist/sveltekit_helpers.js +4 -4
  83. package/dist/sveltekit_shim_app.d.ts +1 -1
  84. package/dist/sveltekit_shim_app_forms.d.ts +0 -1
  85. package/dist/sveltekit_shim_app_navigation.d.ts +0 -1
  86. package/dist/sveltekit_shim_app_paths.d.ts +0 -1
  87. package/dist/sveltekit_shim_app_stores.d.ts +0 -1
  88. package/dist/sveltekit_shim_env.d.ts +1 -1
  89. package/dist/task.d.ts +65 -1
  90. package/dist/task.js +86 -13
  91. package/dist/task.test.js +25 -11
  92. package/dist/task_logging.d.ts +3 -6
  93. package/dist/task_logging.js +18 -36
  94. package/dist/watch_dir.d.ts +2 -2
  95. package/dist/watch_dir.js +14 -16
  96. package/package.json +18 -23
  97. package/dist/gen_module.d.ts +0 -34
  98. package/dist/gen_module.js +0 -32
  99. package/dist/gen_module.test.d.ts +0 -1
  100. package/dist/gen_module.test.js +0 -30
  101. package/dist/task_module.d.ts +0 -15
  102. package/dist/task_module.js +0 -18
  103. package/dist/task_module.test.d.ts +0 -1
  104. package/dist/task_module.test.js +0 -67
package/dist/search_fs.js CHANGED
@@ -1,22 +1,44 @@
1
- import glob from 'tiny-glob';
2
- import { stat } from 'node:fs/promises';
3
- import { sort_map, compare_simple_map_entries } from '@ryanatkn/belt/map.js';
4
- import { strip_end, strip_start } from '@ryanatkn/belt/string.js';
5
1
  import { EMPTY_OBJECT } from '@ryanatkn/belt/object.js';
6
- import { exists } from './fs.js';
2
+ import { to_array } from '@ryanatkn/belt/array.js';
3
+ import { ensure_end } from '@ryanatkn/belt/string.js';
4
+ import { isAbsolute, join } from 'node:path';
5
+ import { existsSync, readdirSync } from 'node:fs';
7
6
  export const search_fs = async (dir, options = EMPTY_OBJECT) => {
8
- const { filter, sort = compare_simple_map_entries, files_only = true } = options;
9
- const final_dir = strip_end(dir, '/');
10
- if (!(await exists(final_dir)))
11
- return new Map();
12
- const globbed = await glob(final_dir + '/**/*', { absolute: true, filesOnly: files_only });
13
- const paths = new Map();
14
- await Promise.all(globbed.map(async (g) => {
15
- const path = strip_start(g, final_dir + '/');
16
- const stats = await stat(g);
17
- if (!filter || stats.isDirectory() || filter(path, stats)) {
18
- paths.set(path, stats);
7
+ const { filter, file_filter, sort = default_sort, include_directories = false, cwd = process.cwd(), } = options;
8
+ const final_dir = ensure_end(cwd && !isAbsolute(dir) ? join(cwd, dir) : dir, '/');
9
+ const filters = !filter || (Array.isArray(filter) && !filter.length) ? undefined : to_array(filter);
10
+ const file_filters = !file_filter || (Array.isArray(file_filter) && !file_filter.length)
11
+ ? undefined
12
+ : to_array(file_filter);
13
+ if (!existsSync(final_dir))
14
+ return [];
15
+ const paths = [];
16
+ crawl(final_dir, paths, filters, file_filters, include_directories, null);
17
+ return sort ? paths.sort(typeof sort === 'boolean' ? default_sort : sort) : paths;
18
+ };
19
+ const default_sort = (a, b) => a.path.localeCompare(b.path);
20
+ const crawl = (dir, paths, filters, file_filter, include_directories, base_dir) => {
21
+ // This sync version is significantly faster than using the `fs/promises` version -
22
+ // it doesn't parallelize but that's not the common case in Gro.
23
+ const dirents = readdirSync(dir, { withFileTypes: true });
24
+ for (const dirent of dirents) {
25
+ const { name, parentPath } = dirent;
26
+ const is_directory = dirent.isDirectory();
27
+ const id = parentPath + name;
28
+ const include = !filters || filters.every((f) => f(id, is_directory));
29
+ if (include) {
30
+ const path = base_dir === null ? name : base_dir + '/' + name;
31
+ if (is_directory) {
32
+ const dir_id = id + '/';
33
+ if (include_directories) {
34
+ paths.push({ path, id: dir_id, is_directory: true });
35
+ }
36
+ crawl(dir_id, paths, filters, file_filter, include_directories, path);
37
+ }
38
+ else if (!file_filter || file_filter.every((f) => f(id))) {
39
+ paths.push({ path, id, is_directory: false });
40
+ }
19
41
  }
20
- }));
21
- return sort ? sort_map(paths, sort) : paths;
42
+ }
43
+ return paths;
22
44
  };
@@ -1,10 +1,8 @@
1
- import { suite } from 'uvu';
1
+ import { test } from 'uvu';
2
2
  import * as assert from 'uvu/assert';
3
+ import { resolve } from 'node:path';
3
4
  import { search_fs } from './search_fs.js';
4
- import { paths } from './paths.js';
5
- /* test__search_fs */
6
- const test__search_fs = suite('search_fs');
7
- test__search_fs('basic behavior', async () => {
5
+ test('search_fs basic behavior', async () => {
8
6
  const ignored_path = 'test1.foo.ts';
9
7
  let has_ignored_path = false;
10
8
  const result = await search_fs('./src/fixtures', {
@@ -13,10 +11,10 @@ test__search_fs('basic behavior', async () => {
13
11
  has_ignored_path = path.endsWith(ignored_path);
14
12
  return !path.endsWith(ignored_path);
15
13
  },
16
- sort: (a, b) => -a[0].localeCompare(b[0]),
14
+ sort: (a, b) => a.path.localeCompare(b.path) * -1,
17
15
  });
18
16
  assert.ok(has_ignored_path); // makes sure the test isn't wrong
19
- assert.equal(Array.from(result.keys()), [
17
+ const expected_files = [
20
18
  'test2.foo.ts',
21
19
  'test_ts.ts',
22
20
  'test_task_module.task_fixture.ts',
@@ -43,8 +41,8 @@ test__search_fs('basic behavior', async () => {
43
41
  'baz1/test1.baz.ts',
44
42
  'bar2/test2.bar.ts',
45
43
  'bar1/test1.bar.ts',
46
- ].map((f) => paths.source + 'fixtures/' + f));
44
+ ];
45
+ assert.equal(result.map((f) => f.path), expected_files);
46
+ assert.equal(result.map((f) => f.id), expected_files.map((f) => resolve(`src/fixtures/${f}`)));
47
47
  });
48
- // TODO more tests
49
- test__search_fs.run();
50
- /* test__search_fs */
48
+ test.run();
package/dist/src_json.js CHANGED
@@ -2,8 +2,8 @@ import { z } from 'zod';
2
2
  import { join } from 'node:path';
3
3
  import { strip_start } from '@ryanatkn/belt/string.js';
4
4
  import { Project } from 'ts-morph';
5
+ import { existsSync } from 'node:fs';
5
6
  import { paths, replace_extension } from './paths.js';
6
- import { exists } from './fs.js';
7
7
  import { transform_empty_object_to_undefined, } from './package_json.js';
8
8
  export const Src_Module_Declaration = z
9
9
  .object({
@@ -60,7 +60,7 @@ export const to_src_modules = async (exports, log, lib_path = paths.lib) => {
60
60
  return [k, src_module];
61
61
  }
62
62
  const source_file_id = join(lib_path, source_file_path);
63
- if (!(await exists(source_file_id))) {
63
+ if (!existsSync(source_file_id)) {
64
64
  log?.warn('failed to infer source file from export path', k, '- the inferred file', source_file_id, 'does not exist');
65
65
  return null;
66
66
  }
@@ -1,4 +1,3 @@
1
- /// <reference types="svelte" />
2
1
  import type { Config as SveltekitConfig } from '@sveltejs/kit';
3
2
  import type { CompileOptions, PreprocessorGroup, ModuleCompileOptions } from 'svelte/compiler';
4
3
  /**
@@ -1,5 +1,5 @@
1
+ import { existsSync } from 'node:fs';
1
2
  import { Package_Json, load_package_json } from './package_json.js';
2
- import { exists } from './fs.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
5
  import { find_cli, spawn_cli } from './cli.js';
@@ -8,7 +8,7 @@ export const SVELTEKIT_CLI = 'svelte-kit';
8
8
  export const SVELTE_PACKAGE_CLI = 'svelte-package';
9
9
  export const SVELTE_PACKAGE_DEP_NAME = '@sveltejs/package';
10
10
  export const has_sveltekit_app = async () => {
11
- if (!(await exists(SVELTEKIT_CONFIG_FILENAME))) {
11
+ if (!existsSync(SVELTEKIT_CONFIG_FILENAME)) {
12
12
  return { ok: false, message: `no SvelteKit config found at ${SVELTEKIT_CONFIG_FILENAME}` };
13
13
  }
14
14
  // TODO check for routes?
@@ -19,7 +19,7 @@ export const has_sveltekit_library = async (package_json, sveltekit_config = sve
19
19
  if (!has_sveltekit_app_result.ok) {
20
20
  return has_sveltekit_app_result;
21
21
  }
22
- if (!(await exists(sveltekit_config.lib_path))) {
22
+ if (!existsSync(sveltekit_config.lib_path)) {
23
23
  return { ok: false, message: `no SvelteKit lib directory found at ${sveltekit_config.lib_path}` };
24
24
  }
25
25
  const pkg = package_json ?? (await load_package_json());
@@ -44,7 +44,7 @@ export const sveltekit_sync = async () => {
44
44
  * If the SvelteKit CLI is found and its `.svelte-kit` directory is not, run `svelte-kit sync`.
45
45
  */
46
46
  export const sveltekit_sync_if_obviously_needed = async () => {
47
- if (await exists(SVELTEKIT_DEV_DIRNAME)) {
47
+ if (existsSync(SVELTEKIT_DEV_DIRNAME)) {
48
48
  return;
49
49
  }
50
50
  if (!(await find_cli(SVELTEKIT_CLI))) {
@@ -6,5 +6,5 @@ export declare const sveltekit_shim_app_environment_matcher: RegExp;
6
6
  * @see https://kit.svelte.dev/docs/modules
7
7
  */
8
8
  export declare const sveltekit_shim_app_specifiers: Map<string, string>;
9
- export declare const render_sveltekit_shim_app_paths: (base_url?: Parsed_Sveltekit_Config['base_url'], assets_url?: Parsed_Sveltekit_Config['assets_url']) => string;
9
+ export declare const render_sveltekit_shim_app_paths: (base_url?: Parsed_Sveltekit_Config["base_url"], assets_url?: Parsed_Sveltekit_Config["assets_url"]) => string;
10
10
  export declare const render_sveltekit_shim_app_environment: (dev: boolean) => string;
@@ -1,4 +1,3 @@
1
- /// <reference types="@sveltejs/kit" />
2
1
  import type { applyAction as base_applyAction, deserialize as base_deserialize, enhance as base_enhance } from '$app/forms';
3
2
  export declare const applyAction: typeof base_applyAction;
4
3
  export declare const deserialize: typeof base_deserialize;
@@ -1,4 +1,3 @@
1
- /// <reference types="@sveltejs/kit" />
2
1
  import type { afterNavigate as base_afterNavigate, beforeNavigate as base_beforeNavigate, disableScrollHandling as base_disableScrollHandling, goto as base_goto, invalidate as base_invalidate, invalidateAll as base_invalidateAll, preloadCode as base_preloadCode, preloadData as base_preloadData } from '$app/navigation';
3
2
  export declare const afterNavigate: typeof base_afterNavigate;
4
3
  export declare const beforeNavigate: typeof base_beforeNavigate;
@@ -1,4 +1,3 @@
1
- /// <reference types="@sveltejs/kit" />
2
1
  /**
3
2
  * This file is created dynamically by `render_sveltekit_shim_app_paths`
4
3
  * but exists here for the sake of the Node loader.
@@ -1,4 +1,3 @@
1
- /// <reference types="@sveltejs/kit" />
2
1
  import type { getStores as base_getStores, navigating as base_navigating, page as base_page, updated as base_updated } from '$app/stores';
3
2
  export declare const getStores: typeof base_getStores;
4
3
  export declare const navigating: typeof base_navigating;
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Generates a module shim for SvelteKit's `$env` imports.
3
3
  */
4
- export declare const render_env_shim_module: (dev: boolean, mode: 'static' | 'dynamic', visibility: 'public' | 'private', public_prefix?: string, private_prefix?: string, env_dir?: string, env_files?: string[], ambient_env?: Record<string, string | undefined>) => Promise<string>;
4
+ export declare const render_env_shim_module: (dev: boolean, mode: "static" | "dynamic", visibility: "public" | "private", public_prefix?: string, private_prefix?: string, env_dir?: string, env_files?: string[], ambient_env?: Record<string, string | undefined>) => Promise<string>;
package/dist/task.d.ts CHANGED
@@ -1,8 +1,13 @@
1
1
  import type { Logger } from '@ryanatkn/belt/log.js';
2
2
  import type { z } from 'zod';
3
3
  import type { Timings } from '@ryanatkn/belt/timings.js';
4
+ import type { Result } from '@ryanatkn/belt/result.js';
4
5
  import type { Args } from './args.js';
6
+ import type { Path_Id } from './path.js';
5
7
  import type { Gro_Config } from './config.js';
8
+ import type { Parsed_Sveltekit_Config } from './sveltekit_config.js';
9
+ import { type Input_Path, type Resolved_Input_File, type Resolved_Input_Path } from './input_path.js';
10
+ import { type Load_Modules_Failure, type Module_Meta } from './modules.js';
6
11
  export interface Task<T_Args = Args, // same as `z.infer<typeof Args>`
7
12
  T_Args_Schema extends z.ZodType = z.ZodType, T_Return = unknown> {
8
13
  run: (ctx: Task_Context<T_Args>) => Promise<T_Return>;
@@ -12,14 +17,16 @@ T_Args_Schema extends z.ZodType = z.ZodType, T_Return = unknown> {
12
17
  export interface Task_Context<T_Args = object> {
13
18
  args: T_Args;
14
19
  config: Gro_Config;
20
+ sveltekit_config: Parsed_Sveltekit_Config;
15
21
  log: Logger;
16
22
  timings: Timings;
17
23
  invoke_task: (task_name: string, args?: Args, config?: Gro_Config) => Promise<void>;
18
24
  }
19
25
  export declare const TASK_FILE_SUFFIX_TS = ".task.ts";
20
26
  export declare const TASK_FILE_SUFFIX_JS = ".task.js";
27
+ export declare const TASK_FILE_SUFFIXES: string[];
21
28
  export declare const is_task_path: (path: string) => boolean;
22
- export declare const to_task_name: (id: Flavored<string, "Source_Id">, task_root_paths: string[]) => string;
29
+ export declare const to_task_name: (id: Path_Id, task_root_dir: Path_Id) => string;
23
30
  /**
24
31
  * This is used by tasks to signal a known failure.
25
32
  * It's useful for cleaning up logging because
@@ -27,3 +34,60 @@ export declare const to_task_name: (id: Flavored<string, "Source_Id">, task_root
27
34
  */
28
35
  export declare class Task_Error extends Error {
29
36
  }
37
+ export interface Found_Task {
38
+ input_path: Input_Path;
39
+ id: Path_Id;
40
+ task_root_dir: Path_Id;
41
+ }
42
+ export interface Found_Tasks {
43
+ resolved_input_files: Resolved_Input_File[];
44
+ resolved_input_files_by_input_path: Map<Input_Path, Resolved_Input_File[]>;
45
+ resolved_input_files_by_root_dir: Map<Path_Id, Resolved_Input_File[]>;
46
+ resolved_input_paths: Resolved_Input_Path[];
47
+ resolved_input_paths_by_input_path: Map<Input_Path, Resolved_Input_Path[]>;
48
+ input_paths: Input_Path[];
49
+ task_root_dirs: Path_Id[];
50
+ }
51
+ export type Find_Tasks_Result = Result<{
52
+ value: Found_Tasks;
53
+ }, Find_Modules_Failure>;
54
+ export type Find_Modules_Failure = {
55
+ type: 'unmapped_input_paths';
56
+ unmapped_input_paths: Input_Path[];
57
+ resolved_input_paths: Resolved_Input_Path[];
58
+ resolved_input_paths_by_input_path: Map<Input_Path, Resolved_Input_Path[]>;
59
+ input_paths: Input_Path[];
60
+ task_root_dirs: Path_Id[];
61
+ reasons: string[];
62
+ } | {
63
+ type: 'input_directories_with_no_files';
64
+ input_directories_with_no_files: Resolved_Input_Path[];
65
+ resolved_input_files: Resolved_Input_File[];
66
+ resolved_input_files_by_input_path: Map<Input_Path, Resolved_Input_File[]>;
67
+ resolved_input_files_by_root_dir: Map<Path_Id, Resolved_Input_File[]>;
68
+ resolved_input_paths: Resolved_Input_Path[];
69
+ resolved_input_paths_by_input_path: Map<Input_Path, Resolved_Input_Path[]>;
70
+ input_paths: Input_Path[];
71
+ task_root_dirs: Path_Id[];
72
+ reasons: string[];
73
+ };
74
+ /**
75
+ * Finds modules from input paths. (see `src/lib/input_path.ts` for more)
76
+ */
77
+ export declare const find_tasks: (input_paths: Input_Path[], task_root_dirs: Path_Id[], config: Gro_Config, timings?: any) => Promise<Find_Tasks_Result>;
78
+ export interface Loaded_Tasks {
79
+ modules: Task_Module_Meta[];
80
+ found_tasks: Found_Tasks;
81
+ }
82
+ export interface Task_Module {
83
+ task: Task;
84
+ }
85
+ export interface Task_Module_Meta extends Module_Meta<Task_Module> {
86
+ name: string;
87
+ }
88
+ export type Load_Tasks_Result = Result<{
89
+ value: Loaded_Tasks;
90
+ }, Load_Tasks_Failure>;
91
+ export type Load_Tasks_Failure = Load_Modules_Failure<Task_Module_Meta>;
92
+ export declare const load_tasks: (found_tasks: Found_Tasks) => Promise<Load_Tasks_Result>;
93
+ export declare const validate_task_module: (mod: Record<string, any>) => mod is Task_Module;
package/dist/task.js CHANGED
@@ -1,21 +1,21 @@
1
1
  import { strip_end, strip_start } from '@ryanatkn/belt/string.js';
2
+ import { red } from 'kleur/colors';
3
+ import { resolve_input_files, resolve_input_paths, } from './input_path.js';
4
+ import { print_path } from './paths.js';
5
+ import { search_fs } from './search_fs.js';
6
+ import { load_modules } from './modules.js';
2
7
  export const TASK_FILE_SUFFIX_TS = '.task.ts';
3
8
  export const TASK_FILE_SUFFIX_JS = '.task.js';
9
+ export const TASK_FILE_SUFFIXES = [TASK_FILE_SUFFIX_TS, TASK_FILE_SUFFIX_JS]; // TODO from `Gro_Config`, but needs to be used everywhere the constants are
4
10
  export const is_task_path = (path) => path.endsWith(TASK_FILE_SUFFIX_TS) || path.endsWith(TASK_FILE_SUFFIX_JS);
5
- export const to_task_name = (id, task_root_paths) => {
6
- // If the id is in any of the task root paths, use the longest available match and strip it.
7
- // This is convoluted because we're not tracking which root path was resolved against the id.
8
- // TODO improve the data flow of id from task root path so this is unnecessary
9
- let longest_matching_path = '';
10
- for (const path of task_root_paths) {
11
- if (id.startsWith(path) && path.length > longest_matching_path.length) {
12
- longest_matching_path = path;
13
- }
14
- }
15
- const task_name = longest_matching_path
16
- ? strip_start(strip_start(id, longest_matching_path), '/')
11
+ export const to_task_name = (id, task_root_dir) => {
12
+ let task_name = id.startsWith(task_root_dir)
13
+ ? strip_start(strip_start(id, task_root_dir), '/')
17
14
  : id;
18
- return strip_end(strip_end(task_name, TASK_FILE_SUFFIX_TS), TASK_FILE_SUFFIX_JS);
15
+ for (const suffix of TASK_FILE_SUFFIXES) {
16
+ task_name = strip_end(task_name, suffix);
17
+ }
18
+ return task_name;
19
19
  };
20
20
  /**
21
21
  * This is used by tasks to signal a known failure.
@@ -24,3 +24,76 @@ export const to_task_name = (id, task_root_paths) => {
24
24
  */
25
25
  export class Task_Error extends Error {
26
26
  }
27
+ /**
28
+ * Finds modules from input paths. (see `src/lib/input_path.ts` for more)
29
+ */
30
+ export const find_tasks = async (input_paths, task_root_dirs, config, timings) => {
31
+ // Check which extension variation works - if it's a directory, prefer others first!
32
+ const timing_to_resolve_input_paths = timings?.start('resolve input paths');
33
+ const { resolved_input_paths, resolved_input_paths_by_input_path, unmapped_input_paths } = await resolve_input_paths(input_paths, task_root_dirs, TASK_FILE_SUFFIXES);
34
+ timing_to_resolve_input_paths?.();
35
+ // Error if any input path could not be mapped.
36
+ if (unmapped_input_paths.length) {
37
+ return {
38
+ ok: false,
39
+ type: 'unmapped_input_paths',
40
+ unmapped_input_paths,
41
+ resolved_input_paths,
42
+ resolved_input_paths_by_input_path,
43
+ input_paths,
44
+ task_root_dirs,
45
+ reasons: unmapped_input_paths.map((input_path) => red(`Input path ${print_path(input_path)} cannot be mapped to a file or directory.`)),
46
+ };
47
+ }
48
+ // Find all of the files for any directories.
49
+ const timing_to_resolve_input_files = timings?.start('resolve input files');
50
+ const { resolved_input_files, resolved_input_files_by_input_path, resolved_input_files_by_root_dir, input_directories_with_no_files, } = await resolve_input_files(resolved_input_paths, (id, options) => search_fs(id, {
51
+ ...options,
52
+ filter: config.search_filters,
53
+ file_filter: (p) => TASK_FILE_SUFFIXES.some((s) => p.endsWith(s)),
54
+ }));
55
+ timing_to_resolve_input_files?.();
56
+ // Error if any input path has no files. (means we have an empty directory)
57
+ if (input_directories_with_no_files.length) {
58
+ return {
59
+ ok: false,
60
+ type: 'input_directories_with_no_files',
61
+ input_directories_with_no_files,
62
+ resolved_input_files,
63
+ resolved_input_files_by_input_path,
64
+ resolved_input_files_by_root_dir,
65
+ resolved_input_paths,
66
+ resolved_input_paths_by_input_path,
67
+ input_paths,
68
+ task_root_dirs,
69
+ reasons: input_directories_with_no_files.map(({ input_path }) => red(`Input directory contains no matching files: ${print_path(input_path)}`)),
70
+ };
71
+ }
72
+ return {
73
+ ok: true,
74
+ value: {
75
+ resolved_input_files,
76
+ resolved_input_files_by_input_path,
77
+ resolved_input_files_by_root_dir,
78
+ resolved_input_paths,
79
+ resolved_input_paths_by_input_path,
80
+ input_paths,
81
+ task_root_dirs,
82
+ },
83
+ };
84
+ };
85
+ export const load_tasks = async (found_tasks) => {
86
+ const loaded_modules = await load_modules(found_tasks.resolved_input_files, validate_task_module, (resolved_input_file, mod) => ({
87
+ id: resolved_input_file.id,
88
+ mod,
89
+ name: to_task_name(resolved_input_file.id, resolved_input_file.resolved_input_path.root_dir),
90
+ }));
91
+ if (!loaded_modules.ok) {
92
+ return loaded_modules;
93
+ }
94
+ return {
95
+ ok: true,
96
+ value: { modules: loaded_modules.modules, found_tasks },
97
+ };
98
+ };
99
+ export const validate_task_module = (mod) => !!mod.task && typeof mod.task.run === 'function';
package/dist/task.test.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { test } from 'uvu';
2
2
  import * as assert from 'uvu/assert';
3
3
  import { resolve } from 'node:path';
4
- import { is_task_path, to_task_name } from './task.js';
4
+ import { is_task_path, to_task_name, validate_task_module, find_tasks, load_tasks } from './task.js';
5
+ import * as actual_test_task_module from './test.task.js';
6
+ import { create_empty_config } from './config.js';
5
7
  test('is_task_path basic behavior', () => {
6
8
  assert.ok(is_task_path('foo.task.ts'));
7
9
  assert.ok(is_task_path('foo.task.js'));
@@ -10,15 +12,27 @@ test('is_task_path basic behavior', () => {
10
12
  assert.ok(!is_task_path('bar/baz/foo.ts'));
11
13
  });
12
14
  test('to_task_name basic behavior', () => {
13
- assert.is(to_task_name('foo.task.ts', []), 'foo');
14
- assert.is(to_task_name('bar/baz/foo.task.ts', []), 'bar/baz/foo');
15
- assert.is(to_task_name('a/b/c/foo.task.ts', ['a/b/c', 'a']), 'foo');
16
- assert.is(to_task_name('a/b/c/foo.task.ts', ['a']), 'b/c/foo');
17
- assert.is(to_task_name('a/b/c/foo.task.ts', ['a/b', 'a']), 'c/foo');
18
- assert.is(to_task_name(resolve('node_modules/@ryanatkn/gro/dist/foo'), [
19
- resolve('./'),
20
- resolve('node_modules/@ryanatkn/gro/dist/'),
21
- ]), 'foo', 'resolves to node_modules before the cwd');
22
- assert.is(to_task_name(resolve('a/b'), [resolve('b')]), resolve('a/b'), 'falls back to the id when unresolved');
15
+ assert.is(to_task_name('foo.task.ts', process.cwd()), 'foo');
16
+ assert.is(to_task_name('bar/baz/foo.task.ts', process.cwd()), 'bar/baz/foo');
17
+ assert.is(to_task_name('a/b/c/foo.task.ts', 'a/b/c'), 'foo');
18
+ assert.is(to_task_name('a/b/c/foo.task.ts', 'a'), 'b/c/foo');
19
+ assert.is(to_task_name('a/b/c/foo.task.ts', 'a/b'), 'c/foo');
20
+ assert.is(to_task_name(resolve('a/b'), resolve('b')), resolve('a/b'), 'falls back to the id when unresolved');
21
+ });
22
+ // TODO if we import directly, svelte-package generates types in `src/fixtures`
23
+ const test_task_module = await import('../fixtures/' + 'test_task_module.task_fixture'); // eslint-disable-line no-useless-concat
24
+ const test_invalid_task_module = await import('../fixtures/' + 'test_invalid_task_module.js'); // eslint-disable-line no-useless-concat
25
+ test('validate_task_module basic behavior', () => {
26
+ assert.ok(validate_task_module(test_task_module));
27
+ assert.ok(!validate_task_module(test_invalid_task_module));
28
+ assert.ok(!validate_task_module({ task: { run: {} } }));
29
+ });
30
+ test('load_tasks basic behavior', async () => {
31
+ const found = await find_tasks([resolve('src/lib/test'), resolve('src/lib/test.task.ts')], [resolve('src/lib')], create_empty_config());
32
+ assert.ok(found.ok);
33
+ const result = await load_tasks(found.value);
34
+ assert.ok(result.ok);
35
+ assert.is(result.value.modules.length, 1);
36
+ assert.is(result.value.modules[0].mod, actual_test_task_module);
23
37
  });
24
38
  test.run();
@@ -1,8 +1,5 @@
1
- import { type Find_Modules_Result } from './modules.js';
2
- import { type Task_Module_Meta } from './task_module.js';
3
- import { type Input_Path } from './input_path.js';
4
- import { type Source_Id } from './paths.js';
5
- export declare const log_tasks: (log: Logger, dir_label: string, source_ids_by_input_path: Map<Input_Path, Source_Id[]>, task_root_paths: string[], log_intro?: boolean) => Promise<void>;
6
- export declare const log_gro_package_tasks: (input_path: Flavored<string, "Input_Path">, task_root_paths: string[], log: Logger) => Promise<Find_Modules_Result>;
1
+ import type { Logger } from '@ryanatkn/belt/log.js';
2
+ import type { Loaded_Tasks, Task_Module_Meta } from './task.js';
3
+ export declare const log_tasks: (log: Logger, loaded_tasks: Loaded_Tasks, log_intro?: boolean) => Promise<void>;
7
4
  export declare const log_error_reasons: (log: Logger, reasons: string[]) => void;
8
5
  export declare const log_task_help: (log: Logger, meta: Task_Module_Meta) => void;
@@ -2,46 +2,28 @@ import { cyan, gray, green, red } from 'kleur/colors';
2
2
  import { plural } from '@ryanatkn/belt/string.js';
3
3
  import { print_value } from '@ryanatkn/belt/print.js';
4
4
  import { ZodFirstPartyTypeKind } from 'zod';
5
- import { find_modules, load_modules } from './modules.js';
6
- import { load_task_module } from './task_module.js';
7
- import { to_gro_input_path } from './input_path.js';
8
- import { print_path_or_gro_path } from './paths.js';
9
- import { is_task_path } from './task.js';
10
- import { search_fs } from './search_fs.js';
11
- export const log_tasks = async (log, dir_label, source_ids_by_input_path, task_root_paths, log_intro = true) => {
12
- const source_ids = Array.from(source_ids_by_input_path.values()).flat();
13
- if (source_ids.length) {
14
- // Load all of the tasks so we can log their summary, and args for the `--help` flag.
15
- const load_modules_result = await load_modules(source_ids_by_input_path, (id) => load_task_module(id, task_root_paths));
16
- if (!load_modules_result.ok) {
17
- log_error_reasons(log, load_modules_result.reasons);
18
- process.exit(1);
19
- }
20
- const logged = [
21
- `${log_intro ? '\n\n' : ''}${source_ids.length} task${plural(source_ids.length)} in ${dir_label}:\n`,
22
- ];
23
- if (log_intro) {
24
- logged.unshift(`\n\n${gray('Run a task:')} gro [name]`, `\n${gray('View help:')} gro [name] --help`);
5
+ import { print_path } from './paths.js';
6
+ export const log_tasks = async (log, loaded_tasks, log_intro = true) => {
7
+ const { modules, found_tasks } = loaded_tasks;
8
+ const { resolved_input_files_by_root_dir } = found_tasks;
9
+ const logged = [];
10
+ if (log_intro) {
11
+ logged.unshift(`\n\n${gray('Run a task:')} gro [name]`, `\n${gray('View help:')} gro [name] --help`);
12
+ }
13
+ for (const [root_dir, resolved_input_files] of resolved_input_files_by_root_dir) {
14
+ const dir_label = print_path(root_dir);
15
+ if (!resolved_input_files.length) {
16
+ log.info(`No tasks found in ${dir_label}.`);
17
+ continue;
25
18
  }
26
- const longest_task_name = to_max_length(load_modules_result.modules, (m) => m.name);
27
- for (const meta of load_modules_result.modules) {
19
+ logged.push(`${log_intro ? '\n\n' : ''}${resolved_input_files.length} task${plural(resolved_input_files.length)} in ${dir_label}:\n`);
20
+ const longest_task_name = to_max_length(modules, (m) => m.name);
21
+ for (const resolved_input_file of resolved_input_files) {
22
+ const meta = modules.find((m) => m.id === resolved_input_file.id);
28
23
  logged.push('\n' + cyan(pad(meta.name, longest_task_name)), ' ', meta.mod.task.summary || '');
29
24
  }
30
- log[log_intro ? 'info' : 'plain'](logged.join('') + '\n');
31
- }
32
- else {
33
- log.info(`No tasks found in ${dir_label}.`);
34
- }
35
- };
36
- export const log_gro_package_tasks = async (input_path, task_root_paths, log) => {
37
- const gro_dir_input_path = to_gro_input_path(input_path);
38
- const gro_dir_find_modules_result = await find_modules([gro_dir_input_path], (id) => search_fs(id, { filter: (path) => is_task_path(path) }));
39
- if (gro_dir_find_modules_result.ok) {
40
- const gro_path_data = gro_dir_find_modules_result.source_id_path_data_by_input_path.get(gro_dir_input_path);
41
- // Log the Gro matches.
42
- await log_tasks(log, print_path_or_gro_path(gro_path_data.id), gro_dir_find_modules_result.source_ids_by_input_path, task_root_paths);
43
25
  }
44
- return gro_dir_find_modules_result;
26
+ log[log_intro ? 'info' : 'plain'](logged.join('') + '\n');
45
27
  };
46
28
  export const log_error_reasons = (log, reasons) => {
47
29
  for (const reason of reasons) {
@@ -1,5 +1,5 @@
1
1
  import { type WatchOptions } from 'chokidar';
2
- import type { Path_Stats, Path_Filter } from './path.js';
2
+ import type { Path_Filter } from './path.js';
3
3
  export interface Watch_Node_Fs {
4
4
  init: () => Promise<void>;
5
5
  close: () => Promise<void>;
@@ -7,7 +7,7 @@ export interface Watch_Node_Fs {
7
7
  export interface Watcher_Change {
8
8
  type: Watcher_Change_Type;
9
9
  path: string;
10
- stats: Path_Stats;
10
+ is_directory: boolean;
11
11
  }
12
12
  export type Watcher_Change_Type = 'create' | 'update' | 'delete';
13
13
  export interface Watcher_Change_Callback {
package/dist/watch_dir.js CHANGED
@@ -1,8 +1,6 @@
1
1
  import chokidar, {} from 'chokidar';
2
- import { stat } from 'node:fs/promises';
3
2
  import { relative } from 'node:path';
4
- const FILE_STATS = { isDirectory: () => false };
5
- const DIR_STATS = { isDirectory: () => true };
3
+ import { statSync } from 'node:fs';
6
4
  /**
7
5
  * Watch for changes on the filesystem using chokidar.
8
6
  */
@@ -12,37 +10,37 @@ export const watch_dir = ({ dir, on_change, filter, absolute = true, chokidar: c
12
10
  init: async () => {
13
11
  watcher = chokidar.watch(dir, chokidar_options);
14
12
  watcher.on('add', async (path, s) => {
15
- const stats = s || (await stat(path));
13
+ const stats = s || statSync(path);
16
14
  const final_path = absolute ? path : relative(dir, path);
17
- if (filter && !filter(final_path, stats))
15
+ if (filter && !filter(final_path, stats.isDirectory()))
18
16
  return;
19
- on_change({ type: 'create', path: final_path, stats });
17
+ on_change({ type: 'create', path: final_path, is_directory: stats.isDirectory() });
20
18
  });
21
19
  watcher.on('addDir', async (path, s) => {
22
- const stats = s || (await stat(path));
20
+ const stats = s || statSync(path);
23
21
  const final_path = absolute ? path : relative(dir, path);
24
- if (filter && !filter(final_path, stats))
22
+ if (filter && !filter(final_path, stats.isDirectory()))
25
23
  return;
26
- on_change({ type: 'create', path: final_path, stats });
24
+ on_change({ type: 'create', path: final_path, is_directory: stats.isDirectory() });
27
25
  });
28
26
  watcher.on('change', async (path, s) => {
29
- const stats = s || (await stat(path));
27
+ const stats = s || statSync(path);
30
28
  const final_path = absolute ? path : relative(dir, path);
31
- if (filter && !filter(final_path, stats))
29
+ if (filter && !filter(final_path, stats.isDirectory()))
32
30
  return;
33
- on_change({ type: 'update', path: final_path, stats });
31
+ on_change({ type: 'update', path: final_path, is_directory: stats.isDirectory() });
34
32
  });
35
33
  watcher.on('unlink', (path) => {
36
34
  const final_path = absolute ? path : relative(dir, path);
37
- if (filter && !filter(final_path, FILE_STATS))
35
+ if (filter && !filter(final_path, false))
38
36
  return;
39
- on_change({ type: 'delete', path: final_path, stats: FILE_STATS });
37
+ on_change({ type: 'delete', path: final_path, is_directory: false });
40
38
  });
41
39
  watcher.on('unlinkDir', (path) => {
42
40
  const final_path = absolute ? path : relative(dir, path);
43
- if (filter && !filter(final_path, DIR_STATS))
41
+ if (filter && !filter(final_path, true))
44
42
  return;
45
- on_change({ type: 'delete', path: final_path, stats: DIR_STATS });
43
+ on_change({ type: 'delete', path: final_path, is_directory: true });
46
44
  });
47
45
  // wait until ready
48
46
  let resolve;