@ryanatkn/gro 0.119.1 → 0.120.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.
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 +77 -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 +85 -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/git.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn, spawn_out } from '@ryanatkn/belt/process.js';
2
2
  import { z } from 'zod';
3
- import { exists } from './fs.js';
3
+ import { existsSync } from 'node:fs';
4
4
  import { to_file_path } from './path.js';
5
5
  // TODO maybe extract to `util-git`
6
6
  export const Git_Origin = z.string().brand('Git_Origin');
@@ -20,7 +20,7 @@ export const git_current_branch_name = async (options) => {
20
20
  */
21
21
  export const git_remote_branch_exists = async (origin = 'origin', branch, options) => {
22
22
  const final_branch = branch ?? (await git_current_branch_name(options));
23
- if (options?.cwd && !(await exists(to_file_path(options.cwd)))) {
23
+ if (options?.cwd && !existsSync(to_file_path(options.cwd))) {
24
24
  return false;
25
25
  }
26
26
  const result = await spawn('git', ['ls-remote', '--exit-code', '--heads', origin, 'refs/heads/' + final_branch], options);
@@ -38,7 +38,7 @@ export const git_remote_branch_exists = async (origin = 'origin', branch, option
38
38
  * @returns a boolean indicating if the local git branch exists
39
39
  */
40
40
  export const git_local_branch_exists = async (branch, options) => {
41
- if (options?.cwd && !(await exists(to_file_path(options.cwd)))) {
41
+ if (options?.cwd && !existsSync(to_file_path(options.cwd))) {
42
42
  return false;
43
43
  }
44
44
  const result = await spawn('git', ['show-ref', '--quiet', 'refs/heads/' + branch], options);
@@ -2,7 +2,7 @@ import { realpath } from 'node:fs/promises';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { spawn } from '@ryanatkn/belt/process.js';
5
- import { exists } from './fs.js';
5
+ import { existsSync } from 'node:fs';
6
6
  import { NODE_MODULES_DIRNAME, SVELTEKIT_DIST_DIRNAME } from './path_constants.js';
7
7
  /*
8
8
 
@@ -47,14 +47,14 @@ export const resolve_gro_module_path = async (path = '') => {
47
47
  const gro_bin_path = resolve(NODE_MODULES_DIRNAME, '.bin/gro');
48
48
  // case 1
49
49
  // Prefer any locally installed version of Gro.
50
- if (await exists(gro_bin_path)) {
50
+ if (existsSync(gro_bin_path)) {
51
51
  return join(await realpath(gro_bin_path), '..', path);
52
52
  }
53
53
  // case 2
54
54
  // If running Gro inside its own repo, require the local dist.
55
55
  // If the local dist is not yet built it will fall back to the global.
56
- if ((await exists(join(SVELTEKIT_DIST_DIRNAME, 'gro.js'))) &&
57
- (await exists(join(SVELTEKIT_DIST_DIRNAME, path)))) {
56
+ if (existsSync(join(SVELTEKIT_DIST_DIRNAME, 'gro.js')) &&
57
+ existsSync(join(SVELTEKIT_DIST_DIRNAME, path))) {
58
58
  return resolve(SVELTEKIT_DIST_DIRNAME, path);
59
59
  }
60
60
  // case 3
@@ -2,8 +2,8 @@
2
2
  // because we no longer have a normal system build - replace with an esbuild plugin
3
3
  // @ts-nocheck
4
4
  import { spawn } from '@ryanatkn/belt/process.js';
5
- import { source_id_to_base_path } from './paths.js';
6
- import { find_gen_modules, is_gen_path } from './gen_module.js';
5
+ import { path_id_to_base_path } from './paths.js';
6
+ import { find_genfiles, is_gen_path } from './gen.js';
7
7
  import { filter_dependents } from './build/source_file.js';
8
8
  import { throttle } from './throttle.js';
9
9
  const FLUSH_DEBOUNCE_DELAY = 500;
@@ -35,7 +35,7 @@ export const plugin = () => {
35
35
  const gen = (files = []) => spawn_cli('gro', ['gen', ...files]);
36
36
  return {
37
37
  name: 'gro_plugin_gen',
38
- setup: async ({ args: { watch }, dev, log }) => {
38
+ setup: async ({ args: { watch }, dev, log, config }) => {
39
39
  // For production builds, we assume `gen` is already fresh,
40
40
  // which should be checked by CI via `gro check` which calls `gro gen --check`.
41
41
  if (!dev)
@@ -44,8 +44,8 @@ export const plugin = () => {
44
44
  // Some parts of the build may have already happened,
45
45
  // making us miss `build` events for gen dependencies,
46
46
  // so we run `gen` here even if it's usually wasteful.
47
- const found = await find_gen_modules();
48
- if (found.ok && found.source_ids_by_input_path.size > 0) {
47
+ const found = await find_genfiles([paths.source], root_dirs, config);
48
+ if (found.ok && found.resolved_input_files_by_input_path.size > 0) {
49
49
  await gen();
50
50
  }
51
51
  // Do we need to just generate everything once and exit?
@@ -61,12 +61,12 @@ export const plugin = () => {
61
61
  // but we probably want to make this an esbuild plugin instead
62
62
  // if (build_config.name !== 'system') return;
63
63
  if (is_gen_path(source_file.id)) {
64
- queue_gen(source_id_to_base_path(source_file.id));
64
+ queue_gen(path_id_to_base_path(source_file.id));
65
65
  }
66
66
  const dependent_gen_file_ids = filter_dependents(source_file, build_config, filer.find_by_id, // cast because we can assume they're all `SourceFile`s
67
67
  is_gen_path);
68
68
  for (const dependent_gen_file_id of dependent_gen_file_ids) {
69
- queue_gen(source_id_to_base_path(dependent_gen_file_id));
69
+ queue_gen(path_id_to_base_path(dependent_gen_file_id));
70
70
  }
71
71
  };
72
72
  filer.on('build', on_filer_build);
@@ -2,8 +2,8 @@ import * as esbuild from 'esbuild';
2
2
  import type { Config as SvelteKitConfig } from '@sveltejs/kit';
3
3
  import type { Result } from '@ryanatkn/belt/result.js';
4
4
  import type { Plugin, Plugin_Context } from './plugin.js';
5
- export declare const SERVER_SOURCE_ID: Flavored<string, "Source_Id">;
6
- export declare const has_server: (path?: Flavored<string, "Source_Id">) => Promise<Result<object, {
5
+ export declare const SERVER_SOURCE_ID: Flavored<string, "Path_Id">;
6
+ export declare const has_server: (path?: Flavored<string, "Path_Id">) => Promise<Result<object, {
7
7
  message: string;
8
8
  }>>;
9
9
  export interface Options {
@@ -3,7 +3,8 @@ import * as esbuild from 'esbuild';
3
3
  import { join, resolve } from 'node:path';
4
4
  import { identity } from '@ryanatkn/belt/function.js';
5
5
  import { strip_before, strip_end } from '@ryanatkn/belt/string.js';
6
- import { base_path_to_source_id, LIB_DIRNAME, paths } from './paths.js';
6
+ import { existsSync } from 'node:fs';
7
+ import { base_path_to_path_id, LIB_DIRNAME, paths } from './paths.js';
7
8
  import { GRO_DEV_DIRNAME, SERVER_DIST_PATH } from './path_constants.js';
8
9
  import { watch_dir } from './watch_dir.js';
9
10
  import { init_sveltekit_config } from './sveltekit_config.js';
@@ -13,14 +14,13 @@ import { print_build_result, to_define_import_meta_env } from './esbuild_helpers
13
14
  import { esbuild_plugin_sveltekit_shim_alias } from './esbuild_plugin_sveltekit_shim_alias.js';
14
15
  import { esbuild_plugin_external_worker } from './esbuild_plugin_external_worker.js';
15
16
  import { esbuild_plugin_sveltekit_local_imports } from './esbuild_plugin_sveltekit_local_imports.js';
16
- import { exists } from './fs.js';
17
17
  import { esbuild_plugin_svelte } from './esbuild_plugin_svelte.js';
18
18
  import { throttle } from './throttle.js';
19
19
  import { sveltekit_config_global } from './sveltekit_config_global.js';
20
20
  // TODO sourcemap as a hoisted option? disable for production by default - or like `outpaths`, passed a `dev` param
21
- export const SERVER_SOURCE_ID = base_path_to_source_id(LIB_DIRNAME + '/server/server.ts');
21
+ export const SERVER_SOURCE_ID = base_path_to_path_id(LIB_DIRNAME + '/server/server.ts');
22
22
  export const has_server = async (path = SERVER_SOURCE_ID) => {
23
- if (!(await exists(path))) {
23
+ if (!existsSync(path)) {
24
24
  return { ok: false, message: `no server file found at ${path}` };
25
25
  }
26
26
  return { ok: true };
@@ -138,7 +138,7 @@ export const gro_plugin_server = ({ entry_points = [SERVER_SOURCE_ID], dir = pro
138
138
  await watcher.init();
139
139
  watcher_ready = true;
140
140
  }
141
- if (!(await exists(server_outpath))) {
141
+ if (!existsSync(server_outpath)) {
142
142
  throw Error(`Node server failed to start due to missing file: ${server_outpath}`);
143
143
  }
144
144
  if (run ?? dev) {
@@ -1,14 +1,15 @@
1
1
  import { spawn_process } from '@ryanatkn/belt/process.js';
2
2
  import { cp, mkdir, rm, writeFile } from 'node:fs/promises';
3
3
  import { dirname, join } from 'node:path';
4
+ import { existsSync } from 'node:fs';
4
5
  import { print_command_args, serialize_args, to_forwarded_args } from './args.js';
5
- import { exists } from './fs.js';
6
6
  import { serialize_package_json, load_package_json } from './package_json.js';
7
7
  import { Task_Error } from './task.js';
8
8
  import { spawn_cli } from './cli.js';
9
9
  import { serialize_src_json, create_src_json } from './src_json.js';
10
10
  import { DEFAULT_EXPORTS_EXCLUDER } from './config.js';
11
11
  import { sveltekit_config_global } from './sveltekit_config_global.js';
12
+ import { SOURCE_DIRNAME } from './path_constants.js';
12
13
  export const gro_plugin_sveltekit_app = ({ host_target = 'github_pages', well_known_package_json, well_known_src_json, well_known_src_files, } = {}) => {
13
14
  let sveltekit_process = null;
14
15
  return {
@@ -64,7 +65,7 @@ export const gro_plugin_sveltekit_app = ({ host_target = 'github_pages', well_kn
64
65
  ? await create_temporarily(join(assets_path, '.well-known/src.json'), serialized_src_json)
65
66
  : null,
66
67
  serialized_src_json && well_known_src_files
67
- ? await copy_temporarily('src', assets_path, '.well-known', well_known_src_files === true
68
+ ? await copy_temporarily(SOURCE_DIRNAME, assets_path, '.well-known', well_known_src_files === true
68
69
  ? (file_path) => !DEFAULT_EXPORTS_EXCLUDER.test(file_path)
69
70
  : well_known_src_files)
70
71
  : null,
@@ -106,7 +107,7 @@ export const gro_plugin_sveltekit_app = ({ host_target = 'github_pages', well_kn
106
107
  const copy_temporarily = async (source_path, dest_dir, dest_base_dir = '', filter) => {
107
108
  const path = join(dest_dir, dest_base_dir, source_path);
108
109
  const dir = dirname(path);
109
- const dir_already_exists = await exists(dir);
110
+ const dir_already_exists = existsSync(dir);
110
111
  let root_created_dir;
111
112
  if (!dir_already_exists) {
112
113
  root_created_dir = await to_root_dir_that_doesnt_exist(dir);
@@ -114,7 +115,7 @@ const copy_temporarily = async (source_path, dest_dir, dest_base_dir = '', filte
114
115
  throw Error();
115
116
  await mkdir(dir, { recursive: true });
116
117
  }
117
- const path_already_exists = await exists(path);
118
+ const path_already_exists = existsSync(path);
118
119
  if (!path_already_exists) {
119
120
  await cp(source_path, path, { recursive: true, filter });
120
121
  }
@@ -138,7 +139,7 @@ const copy_temporarily = async (source_path, dest_dir, dest_base_dir = '', filte
138
139
  */
139
140
  const create_temporarily = async (path, contents) => {
140
141
  const dir = dirname(path);
141
- const dir_already_exists = await exists(dir);
142
+ const dir_already_exists = existsSync(dir);
142
143
  let root_created_dir;
143
144
  if (!dir_already_exists) {
144
145
  root_created_dir = await to_root_dir_that_doesnt_exist(dir);
@@ -146,7 +147,7 @@ const create_temporarily = async (path, contents) => {
146
147
  throw Error();
147
148
  await mkdir(dir, { recursive: true });
148
149
  }
149
- const path_already_exists = await exists(path);
150
+ const path_already_exists = existsSync(path);
150
151
  if (!path_already_exists) {
151
152
  await writeFile(path, contents, 'utf8');
152
153
  }
@@ -170,8 +171,7 @@ const to_root_dir_that_doesnt_exist = async (dir) => {
170
171
  let prev;
171
172
  let d = dir;
172
173
  do {
173
- // eslint-disable-next-line no-await-in-loop
174
- if (await exists(d)) {
174
+ if (existsSync(d)) {
175
175
  return prev;
176
176
  }
177
177
  prev = d;
package/dist/hash.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- /// <reference types="node" />
2
1
  /**
3
2
  * @see https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto
4
3
  */
5
- export declare const to_hash: (data: Buffer, algorithm?: 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512') => Promise<string>;
4
+ export declare const to_hash: (data: Buffer, algorithm?: "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512") => Promise<string>;
@@ -1,7 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import type { Flavored } from '@ryanatkn/belt/types.js';
3
- import { Source_Id } from './paths.js';
4
- import { type Path_Data } from './path.js';
3
+ import type { Path_Id } from './path.js';
5
4
  export declare const Input_Path: z.ZodString;
6
5
  export type Input_Path = Flavored<z.infer<typeof Input_Path>, 'Input_Path'>;
7
6
  export declare const Raw_Input_Path: z.ZodString;
@@ -18,30 +17,49 @@ export type Raw_Input_Path = Flavored<z.infer<typeof Raw_Input_Path>, 'Raw_Input
18
17
  *
19
18
  * Thus, input paths are either absolute or implicitly relative.
20
19
  */
21
- export declare const to_input_path: (raw_input_path: Flavored<string, "Raw_Input_Path">, root_path?: string) => Flavored<string, "Input_Path">;
20
+ export declare const to_input_path: (raw_input_path: Raw_Input_Path, root_path?: string) => Input_Path;
22
21
  export declare const to_input_paths: (raw_input_paths: Raw_Input_Path[], root_path?: string) => Input_Path[];
22
+ export interface Possible_Path {
23
+ id: Path_Id;
24
+ input_path: Input_Path;
25
+ root_dir: Path_Id;
26
+ }
23
27
  /**
24
28
  * Gets a list of possible source ids for each input path with `extensions`,
25
- * duplicating each under `root_dirs`.
26
- * This is first used to fall back to the Gro dir to search for tasks.
27
- * It's the helper used in implementations of `get_possible_source_ids_for_input_path` below.
29
+ * duplicating each under `root_dirs`, without checking the filesystem.
28
30
  */
29
- export declare const get_possible_source_ids: (input_path: Flavored<string, "Input_Path">, extensions: string[], root_dirs: string[]) => Source_Id[];
31
+ export declare const get_possible_paths: (input_path: Input_Path, root_dirs: Path_Id[], extensions: string[]) => Possible_Path[];
32
+ export interface Resolved_Input_Path {
33
+ input_path: Input_Path;
34
+ id: Path_Id;
35
+ is_directory: boolean;
36
+ root_dir: Path_Id;
37
+ }
38
+ export interface Resolved_Input_File {
39
+ id: Path_Id;
40
+ input_path: Input_Path;
41
+ resolved_input_path: Resolved_Input_Path;
42
+ }
43
+ export interface Resolved_Input_Paths {
44
+ resolved_input_paths: Resolved_Input_Path[];
45
+ resolved_input_paths_by_input_path: Map<Input_Path, Resolved_Input_Path[]>;
46
+ possible_paths_by_input_path: Map<Input_Path, Possible_Path[]>;
47
+ unmapped_input_paths: Input_Path[];
48
+ }
30
49
  /**
31
50
  * Gets the path data for each input path, checking the filesystem for the possibilities
32
51
  * and stopping at the first existing file or falling back to the first existing directory.
33
52
  * If none is found for an input path, it's added to `unmapped_input_paths`.
34
53
  */
35
- export declare const load_source_path_data_by_input_path: (input_paths: Input_Path[], get_possible_source_ids_for_input_path?: (input_path: Input_Path) => Source_Id[]) => Promise<{
36
- source_id_path_data_by_input_path: Map<Input_Path, Path_Data>;
37
- unmapped_input_paths: Input_Path[];
38
- }>;
54
+ export declare const resolve_input_paths: (input_paths: Input_Path[], root_dirs: Path_Id[], extensions: string[]) => Promise<Resolved_Input_Paths>;
55
+ export interface Resolved_Input_Files {
56
+ resolved_input_files: Resolved_Input_File[];
57
+ resolved_input_files_by_input_path: Map<Input_Path, Resolved_Input_File[]>;
58
+ resolved_input_files_by_root_dir: Map<Path_Id, Resolved_Input_File[]>;
59
+ input_directories_with_no_files: Resolved_Input_Path[];
60
+ }
39
61
  /**
40
62
  * Finds all of the matching files for the given input paths.
41
63
  * De-dupes source ids.
42
64
  */
43
- export declare const load_source_ids_by_input_path: (source_id_path_data_by_input_path: Map<Input_Path, Path_Data>, custom_search_fs?: (dir: string, options?: import("./search_fs.js").Search_Fs_Options) => Promise<Map<string, import("./path.js").Path_Stats>>) => Promise<{
44
- source_ids_by_input_path: Map<Input_Path, Source_Id[]>;
45
- input_directories_with_no_files: Input_Path[];
46
- }>;
47
- export declare const to_gro_input_path: (input_path: Flavored<string, "Input_Path">) => Flavored<string, "Input_Path">;
65
+ export declare const resolve_input_files: (resolved_input_paths: Resolved_Input_Path[], custom_search_fs?: (dir: string, options?: import("./search_fs.js").Search_Fs_Options) => Promise<import("./path.js").Resolved_Path[]>) => Promise<Resolved_Input_Files>;
@@ -1,10 +1,8 @@
1
- import { isAbsolute, join, resolve } from 'node:path';
1
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
2
+ import { existsSync, statSync } from 'node:fs';
2
3
  import { strip_start } from '@ryanatkn/belt/string.js';
3
- import { stat } from 'node:fs/promises';
4
4
  import { z } from 'zod';
5
- import { GRO_PACKAGE_DIR, GRO_DIST_DIR, paths, Source_Id } from './paths.js';
6
- import { to_path_data } from './path.js';
7
- import { exists } from './fs.js';
5
+ import { GRO_PACKAGE_DIR, GRO_DIST_DIR } from './paths.js';
8
6
  import { search_fs } from './search_fs.js';
9
7
  import { TASK_FILE_SUFFIX_JS } from './task.js';
10
8
  // TODO Flavored doesn't work when used in schemas, use Zod brand instead? problem is ergonomics
@@ -34,124 +32,178 @@ export const to_input_path = (raw_input_path, root_path = process.cwd()) => {
34
32
  export const to_input_paths = (raw_input_paths, root_path) => raw_input_paths.map((p) => to_input_path(p, root_path));
35
33
  /**
36
34
  * Gets a list of possible source ids for each input path with `extensions`,
37
- * duplicating each under `root_dirs`.
38
- * This is first used to fall back to the Gro dir to search for tasks.
39
- * It's the helper used in implementations of `get_possible_source_ids_for_input_path` below.
35
+ * duplicating each under `root_dirs`, without checking the filesystem.
40
36
  */
41
- export const get_possible_source_ids = (input_path, extensions, root_dirs) => {
42
- const possible_source_ids = [];
43
- const add_possible_source_ids = (path) => {
37
+ export const get_possible_paths = (input_path, root_dirs, extensions) => {
38
+ const possible_paths = new Set();
39
+ const add_possible_paths = (path, root_dir) => {
44
40
  // Specifically for paths to the Gro package dist, optimize by only looking for `.task.js`.
45
41
  if (path.startsWith(GRO_DIST_DIR)) {
46
- possible_source_ids.push((path.endsWith('/') || path.endsWith(TASK_FILE_SUFFIX_JS)
47
- ? path
48
- : path + TASK_FILE_SUFFIX_JS));
42
+ possible_paths.add({
43
+ id: (path.endsWith('/') || path.endsWith(TASK_FILE_SUFFIX_JS)
44
+ ? path
45
+ : path + TASK_FILE_SUFFIX_JS),
46
+ input_path,
47
+ root_dir,
48
+ });
49
49
  }
50
50
  else {
51
- possible_source_ids.push(path);
51
+ possible_paths.add({ id: path, input_path, root_dir });
52
52
  if (!path.endsWith('/') && !extensions.some((e) => path.endsWith(e))) {
53
53
  for (const extension of extensions) {
54
- possible_source_ids.push(path + extension);
54
+ possible_paths.add({ id: path + extension, input_path, root_dir });
55
55
  }
56
56
  }
57
57
  }
58
58
  };
59
59
  if (isAbsolute(input_path)) {
60
- add_possible_source_ids(input_path);
60
+ // TODO this is hacky because it's the only place we're using sync fs calls (even if they're faster, it's oddly inconsistent),
61
+ // we probably should just change this function to check the filesystem and not return non-existing paths
62
+ add_possible_paths(input_path, existsSync(input_path) && statSync(input_path).isDirectory()
63
+ ? input_path
64
+ : dirname(input_path));
61
65
  }
62
66
  else {
63
67
  for (const root_dir of root_dirs) {
64
- add_possible_source_ids(join(root_dir, input_path));
68
+ add_possible_paths(join(root_dir, input_path), root_dir);
65
69
  }
66
70
  }
67
- return possible_source_ids;
71
+ return Array.from(possible_paths);
68
72
  };
69
73
  /**
70
74
  * Gets the path data for each input path, checking the filesystem for the possibilities
71
75
  * and stopping at the first existing file or falling back to the first existing directory.
72
76
  * If none is found for an input path, it's added to `unmapped_input_paths`.
73
77
  */
74
- export const load_source_path_data_by_input_path = async (input_paths, get_possible_source_ids_for_input_path) => {
75
- const source_id_path_data_by_input_path = new Map();
78
+ export const resolve_input_paths = async (input_paths, root_dirs, extensions) => {
79
+ const resolved_input_paths = [];
80
+ const possible_paths_by_input_path = new Map();
76
81
  const unmapped_input_paths = [];
77
82
  for (const input_path of input_paths) {
78
- let file_path_data = null;
79
- let dir_path_data = null;
80
- const possible_source_ids = get_possible_source_ids_for_input_path
81
- ? get_possible_source_ids_for_input_path(input_path)
82
- : [input_path];
83
+ let found_file = null;
84
+ let found_dirs = null;
85
+ const possible_paths = get_possible_paths(input_path, root_dirs, extensions);
86
+ possible_paths_by_input_path.set(input_path, possible_paths);
83
87
  // Find the first existing file path or fallback to the first directory path.
84
- for (const possible_source_id of possible_source_ids) {
85
- if (!(await exists(possible_source_id)))
86
- continue; // eslint-disable-line no-await-in-loop
87
- const stats = await stat(possible_source_id); // eslint-disable-line no-await-in-loop
88
+ for (const possible_path of possible_paths) {
89
+ if (!existsSync(possible_path.id))
90
+ continue;
91
+ const stats = statSync(possible_path.id);
88
92
  if (stats.isDirectory()) {
89
- if (dir_path_data)
90
- continue;
91
- dir_path_data = to_path_data(possible_source_id, stats);
93
+ found_dirs ??= [];
94
+ found_dirs.push([{ id: possible_path.id, is_directory: stats.isDirectory() }, possible_path]);
92
95
  }
93
96
  else {
94
- file_path_data = to_path_data(possible_source_id, stats);
97
+ found_file = [{ id: possible_path.id, is_directory: stats.isDirectory() }, possible_path];
95
98
  break;
96
99
  }
97
100
  }
98
- const path_data = file_path_data || dir_path_data;
99
- if (path_data) {
100
- source_id_path_data_by_input_path.set(input_path, path_data);
101
+ if (found_file) {
102
+ resolved_input_paths.push({
103
+ input_path,
104
+ id: found_file[0].id,
105
+ is_directory: found_file[0].is_directory,
106
+ root_dir: found_file[1].root_dir,
107
+ });
108
+ }
109
+ else if (found_dirs) {
110
+ for (const found_dir of found_dirs) {
111
+ resolved_input_paths.push({
112
+ input_path,
113
+ id: found_dir[0].id,
114
+ is_directory: found_dir[0].is_directory,
115
+ root_dir: found_dir[1].root_dir,
116
+ });
117
+ }
101
118
  }
102
119
  else {
103
120
  unmapped_input_paths.push(input_path);
104
121
  }
105
122
  }
106
- return { source_id_path_data_by_input_path, unmapped_input_paths };
123
+ return {
124
+ resolved_input_paths,
125
+ resolved_input_paths_by_input_path: resolved_input_paths.reduce((map, resolved_input_path) => {
126
+ if (map.has(resolved_input_path.input_path)) {
127
+ map.get(resolved_input_path.input_path).push(resolved_input_path);
128
+ }
129
+ else {
130
+ map.set(resolved_input_path.input_path, [resolved_input_path]);
131
+ }
132
+ return map;
133
+ }, new Map()),
134
+ possible_paths_by_input_path,
135
+ unmapped_input_paths,
136
+ };
107
137
  };
108
138
  /**
109
139
  * Finds all of the matching files for the given input paths.
110
140
  * De-dupes source ids.
111
141
  */
112
- export const load_source_ids_by_input_path = async (source_id_path_data_by_input_path, custom_search_fs = search_fs) => {
113
- const source_ids_by_input_path = new Map();
142
+ export const resolve_input_files = async (resolved_input_paths, custom_search_fs = search_fs) => {
143
+ const resolved_input_files = [];
144
+ const resolved_input_files_by_input_path = new Map();
114
145
  const input_directories_with_no_files = [];
115
- const existing_source_ids = new Set();
116
- for (const [input_path, path_data] of source_id_path_data_by_input_path) {
117
- const { id } = path_data;
118
- if (path_data.isDirectory) {
119
- const files = await custom_search_fs(id, { files_only: false }); // eslint-disable-line no-await-in-loop
120
- if (files.size) {
121
- const source_ids = [];
146
+ const existing_path_ids = new Set();
147
+ // TODO parallelize but would need to de-dupe and retain order
148
+ for (const resolved_input_path of resolved_input_paths) {
149
+ const { input_path, id, is_directory } = resolved_input_path;
150
+ if (is_directory) {
151
+ const files = await custom_search_fs(id, { include_directories: true }); // eslint-disable-line no-await-in-loop
152
+ if (files.length) {
153
+ const path_ids = [];
122
154
  let has_files = false;
123
- for (const [path, stats] of files) {
124
- if (stats.isDirectory())
155
+ for (const { path, is_directory } of files) {
156
+ if (is_directory)
125
157
  continue;
126
158
  has_files = true;
127
- const source_id = join(id, path);
128
- if (!existing_source_ids.has(source_id)) {
129
- existing_source_ids.add(source_id);
130
- source_ids.push(source_id);
159
+ const path_id = join(id, path);
160
+ if (!existing_path_ids.has(path_id)) {
161
+ existing_path_ids.add(path_id);
162
+ path_ids.push(path_id);
131
163
  }
132
164
  }
133
- if (source_ids.length) {
134
- source_ids_by_input_path.set(input_path, source_ids);
165
+ if (path_ids.length) {
166
+ const resolved_input_files_for_input_path = [];
167
+ for (const path_id of path_ids) {
168
+ const resolved_input_file = {
169
+ id: path_id,
170
+ input_path,
171
+ resolved_input_path,
172
+ };
173
+ resolved_input_files.push(resolved_input_file);
174
+ resolved_input_files_for_input_path.push(resolved_input_file);
175
+ }
176
+ resolved_input_files_by_input_path.set(input_path, resolved_input_files_for_input_path);
135
177
  }
136
178
  if (!has_files) {
137
- input_directories_with_no_files.push(input_path);
179
+ input_directories_with_no_files.push(resolved_input_path);
138
180
  }
139
181
  // do callers ever need `input_directories_with_duplicate_files`?
140
182
  }
141
183
  else {
142
- input_directories_with_no_files.push(input_path);
184
+ input_directories_with_no_files.push(resolved_input_path);
143
185
  }
144
186
  }
145
- else if (!existing_source_ids.has(id)) {
146
- existing_source_ids.add(id);
147
- source_ids_by_input_path.set(input_path, [id]);
187
+ else if (!existing_path_ids.has(id)) {
188
+ existing_path_ids.add(id);
189
+ const resolved_input_file = { id, input_path, resolved_input_path };
190
+ resolved_input_files.push(resolved_input_file);
191
+ resolved_input_files_by_input_path.set(input_path, [resolved_input_file]);
148
192
  }
149
193
  }
150
- return { source_ids_by_input_path, input_directories_with_no_files };
151
- };
152
- // TODO I don't think this is valid any more, we shouldn't transform absolute paths like this,
153
- // the searching should happen with the input paths
154
- export const to_gro_input_path = (input_path) => {
155
- const base_path = input_path === paths.lib.slice(0, -1) ? '' : strip_start(input_path, paths.lib);
156
- return GRO_DIST_DIR + base_path;
194
+ return {
195
+ resolved_input_files,
196
+ resolved_input_files_by_input_path,
197
+ resolved_input_files_by_root_dir: resolved_input_files.reduce((map, resolved_input_file) => {
198
+ const { root_dir } = resolved_input_file.resolved_input_path;
199
+ if (map.has(root_dir)) {
200
+ map.get(root_dir).push(resolved_input_file);
201
+ }
202
+ else {
203
+ map.set(root_dir, [resolved_input_file]);
204
+ }
205
+ return map;
206
+ }, new Map()),
207
+ input_directories_with_no_files,
208
+ };
157
209
  };