resolve-everything 0.1.1 → 0.1.2

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/.node-version ADDED
@@ -0,0 +1 @@
1
+ v18.18.0
package/.npm-version ADDED
@@ -0,0 +1 @@
1
+ 9.8.1
package/README.md CHANGED
@@ -45,17 +45,20 @@ which outputs JSON shaped like:
45
45
 
46
46
  - `--only-entrypoint` (boolean): if true, only the imports/requires in the entrypoint file will be resolved, and no other files will be walked over.
47
47
 
48
- - `--resolver` (path): module which exports a JS function that locates modules. Uses the same resolver format as [kame](https://github.com/suchipi/kame#config-hell).
48
+ - `--sort` (boolean): if true, results will be sorted lexicographically. If you want results to be stable, you should enable this, as the order is non-deterministic otherwise (because we crawl the filesystem concurrently).
49
+
50
+ - `--resolver` (path): module which exports a JS function that locates modules. Uses a resolver format compatible with [kame](https://github.com/suchipi/kame#config-hell), but with an added optional `async` property.
49
51
 
50
52
  - `--full-errors` (boolean): If reading, parsing, traversal, or resolution errors occur, print as much error information as possible.
51
53
 
52
54
  ### Node API
53
55
 
54
56
  ```ts
55
- import { walk } from "resolve-everything";
57
+ import { walk, walkAsync } from "resolve-everything";
56
58
 
57
59
  const entrypoint = "/home/someone/some-file.js";
58
60
  const { errors, modules } = walk(entrypoint /* , options */);
61
+ // or you could do: await walkAsync(entrypoint, /* options */);
59
62
 
60
63
  // modules is a Map<string, { id: string, requests: Map<string, string> }>
61
64
  ```
@@ -78,12 +81,24 @@ export type WalkOptions = {
78
81
  */
79
82
  onlyEntrypoint?: boolean;
80
83
 
84
+ /**
85
+ * If true, results will be sorted lexicographically. If you want results to
86
+ * be stable when using `walkSync`, you should enable this, as the order is
87
+ * non-deterministic otherwise (because we walk the filesystem concurrently).
88
+ */
89
+ sort?: boolean;
90
+
81
91
  /**
82
92
  * A function which translates the string-part of an import/require into the
83
93
  * absolute path to the file on disk, OR, if there is no file for that module
84
94
  * (ie. it's a node builtin), a string starting with "external:".
95
+ *
96
+ * The 'async' property is optional and will only be used by `walkAsync`.
97
+ * If not present, the normal synchronous resolver will be used.
85
98
  */
86
- resolver?: (id: string, fromFilePath: string) => string;
99
+ resolver?: ((id: string, fromFilePath: string) => string) & {
100
+ async?: (id: string, fromFilePath: string) => Promise<string>;
101
+ };
87
102
  };
88
103
  ```
89
104
 
package/dist/cli.js CHANGED
@@ -15,9 +15,10 @@ const _1 = require(".");
15
15
  skip: clefairy_1.optionalString,
16
16
  json: clefairy_1.optionalBoolean,
17
17
  onlyEntrypoint: clefairy_1.optionalBoolean,
18
+ sort: clefairy_1.optionalBoolean,
18
19
  help: clefairy_1.optionalBoolean,
19
20
  h: clefairy_1.optionalBoolean,
20
- }, async ({ entrypoint, resolver, fullErrors, skip, json, onlyEntrypoint, help, h, }) => {
21
+ }, async ({ entrypoint, resolver, fullErrors, skip, json, onlyEntrypoint, sort, help, h, }) => {
21
22
  if (help || h) {
22
23
  const helpText = require("./help-text").default;
23
24
  console.log(helpText);
@@ -30,6 +31,7 @@ const _1 = require(".");
30
31
  throw new Error("No such file: " + entrypoint);
31
32
  }
32
33
  const walkOptions = {
34
+ sort,
33
35
  onError: (err) => {
34
36
  if (!fullErrors && /node_modules/.test(err.filename || "")) {
35
37
  // Don't report resolution errors under node_modules dirs because
@@ -86,7 +88,7 @@ const _1 = require(".");
86
88
  throw new Error(`Resolver at ${JSON.stringify(resolver)} didn't export a 'resolve' function.`);
87
89
  }
88
90
  }
89
- const result = (0, _1.walk)(entrypoint, walkOptions);
91
+ const result = await (0, _1.walkAsync)(entrypoint, walkOptions);
90
92
  let toPrint = (0, _1.serialize)(result.modules);
91
93
  if (onlyEntrypoint) {
92
94
  toPrint = toPrint[entrypoint];
@@ -1 +1,3 @@
1
- export declare function defaultResolver(id: string, fromFilePath: string): string;
1
+ export declare const defaultResolver: ((id: string, fromFilePath: string) => string) & {
2
+ async: (id: string, fromFilePath: string) => Promise<string>;
3
+ };
@@ -6,16 +6,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.defaultResolver = void 0;
7
7
  const node_module_1 = __importDefault(require("node:module"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
+ const node_util_1 = __importDefault(require("node:util"));
9
10
  const resolve_1 = __importDefault(require("resolve"));
10
11
  if (!Array.isArray(node_module_1.default.builtinModules)) {
11
12
  throw new Error("your node version seriously way too old bruh");
12
13
  }
13
14
  const allBuiltins = new Set(node_module_1.default.builtinModules);
14
- // This is (more or less) a direct copy-paste of kame's default resolver.
15
- // We copy-paste instead of depending on it directly, though, because kame has a LOT of deps and we just need this piece
15
+ const resolveAsync = node_util_1.default.promisify(resolve_1.default);
16
+ // This is (more or less) a direct copy-paste of kame's default resolver, with
17
+ // async added.
16
18
  //
17
- // // https://github.com/suchipi/kame/blob/ae317caf325d5cbe4925fe30273159b6c04651a6/src/default-resolver.ts#L10
18
- function defaultResolver(id, fromFilePath) {
19
+ // Part of why we copy-paste it instead of depending on it directly is because
20
+ // kame has a LOT of deps and we just need this piece
21
+ //
22
+ // https://github.com/suchipi/kame/blob/ae317caf325d5cbe4925fe30273159b6c04651a6/src/default-resolver.ts#L10
23
+ exports.defaultResolver = Object.assign(function defaultResolver(id, fromFilePath) {
19
24
  if (id.includes(":") && !id.startsWith("file:")) {
20
25
  return "external:" + id;
21
26
  }
@@ -40,5 +45,31 @@ function defaultResolver(id, fromFilePath) {
40
45
  return "external:" + result;
41
46
  }
42
47
  return result;
43
- }
44
- exports.defaultResolver = defaultResolver;
48
+ }, {
49
+ async: async function defaultResolverAsync(id, fromFilePath) {
50
+ if (id.includes(":") && !id.startsWith("file:")) {
51
+ return "external:" + id;
52
+ }
53
+ if (allBuiltins.has(id.split("/")[0])) {
54
+ return "external:" + id;
55
+ }
56
+ const result = await resolveAsync(id, {
57
+ basedir: node_path_1.default.dirname(fromFilePath),
58
+ preserveSymlinks: false,
59
+ extensions: [
60
+ ".js",
61
+ ".json",
62
+ ".mjs",
63
+ ".jsx",
64
+ ".ts",
65
+ ".tsx",
66
+ ".node",
67
+ ".wasm",
68
+ ],
69
+ });
70
+ if (result.endsWith(".node") || result.endsWith(".wasm")) {
71
+ return "external:" + result;
72
+ }
73
+ return result;
74
+ },
75
+ });
package/dist/help-text.js CHANGED
@@ -60,11 +60,23 @@ ${opt("--only-entrypoint")} (boolean):
60
60
  If true, only the imports/requires in the entrypoint file will be resolved,
61
61
  and no other files will be walked over.
62
62
 
63
+
64
+ ${opt("--sort")} (boolean):
65
+ If true, results will be sorted lexicographically. If you want results to be
66
+ stable, you should enable this, as the order is non-deterministic otherwise
67
+ (because we crawl the filesystem concurrently).
68
+
63
69
  ${opt("--resolver")} (path):
64
- Module which exports a JS function that locates modules. Uses the same
65
- resolver format as https://github.com/suchipi/kame, namely:
70
+ Module which exports a JS function that locates modules. Uses a resolver
71
+ format compatible with https://github.com/suchipi/kame, namely:
72
+
73
+ export const resolve: {
74
+ (id: string, fromFilePath: string): string;
75
+ async?: (id: string, fromFilePath: string) => Promise<string>;
76
+ }
66
77
 
67
- export function resolve(id: string, fromFilePath: string): string;
78
+ The 'async' property is optional; if not present, the normal synchronous
79
+ resolver will be used.
68
80
 
69
81
  ${opt("--full-errors")} (boolean):
70
82
  If reading, parsing, traversal, or resolution errors occur, print as much
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { walk, type WalkOptions } from "./walk";
1
+ export { walk, walkAsync, type WalkOptions } from "./walk";
2
2
  export type { ErrorReport as ReportedError, ResolverFunction } from "./types";
3
3
  export { defaultResolver } from "./default-resolver";
4
4
  export { Walker, type WalkerOptions } from "./walker";
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.serialize = exports.Module = exports.Walker = exports.defaultResolver = exports.walk = void 0;
3
+ exports.serialize = exports.Module = exports.Walker = exports.defaultResolver = exports.walkAsync = exports.walk = void 0;
4
4
  var walk_1 = require("./walk");
5
5
  Object.defineProperty(exports, "walk", { enumerable: true, get: function () { return walk_1.walk; } });
6
+ Object.defineProperty(exports, "walkAsync", { enumerable: true, get: function () { return walk_1.walkAsync; } });
6
7
  var default_resolver_1 = require("./default-resolver");
7
8
  Object.defineProperty(exports, "defaultResolver", { enumerable: true, get: function () { return default_resolver_1.defaultResolver; } });
8
9
  var walker_1 = require("./walker");
package/dist/module.d.ts CHANGED
@@ -5,7 +5,10 @@ export declare class Module {
5
5
  requests: Map<string, string>;
6
6
  constructor(id: string);
7
7
  read(): string | null;
8
+ readAsync(): Promise<string | null>;
8
9
  parse(code: string): ee.types.File;
9
10
  getRequests(ast: ee.types.File): Array<string>;
10
11
  resolve(request: string, resolver: ResolverFunction): string;
12
+ resolveAsync(request: string, resolver: ResolverFunction): Promise<string>;
13
+ get dependencies(): Set<string>;
11
14
  }
package/dist/module.js CHANGED
@@ -51,6 +51,18 @@ class Module {
51
51
  debug_logger_1.debugLogger.returns("Module.read ->", code);
52
52
  return code;
53
53
  }
54
+ async readAsync() {
55
+ debug_logger_1.debugLogger.summary("Module.readAsync", this.id);
56
+ if (!/\.[cm]?[tj]sx?$/.test(this.id)) {
57
+ debug_logger_1.debugLogger.summary("doesn't appear to be js/ts:", this.id);
58
+ debug_logger_1.debugLogger.returns("Module.readAsync -> null");
59
+ return null;
60
+ }
61
+ const code = await fs.promises.readFile(this.id, "utf-8");
62
+ debug_logger_1.debugLogger.fileContent(code);
63
+ debug_logger_1.debugLogger.returns("Module.readAsync ->", code);
64
+ return code;
65
+ }
54
66
  parse(code) {
55
67
  debug_logger_1.debugLogger.summary("Module.parse", this.id);
56
68
  debug_logger_1.debugLogger.args("Module.parse", code);
@@ -122,5 +134,23 @@ class Module {
122
134
  debug_logger_1.debugLogger.returns("Module.resolve ->", resolved);
123
135
  return resolved;
124
136
  }
137
+ async resolveAsync(request, resolver) {
138
+ debug_logger_1.debugLogger.summary("Module.resolveAsync", this.id, request);
139
+ debug_logger_1.debugLogger.args("Module.resolveAsync", request, resolver);
140
+ let resolved;
141
+ if (resolver.async == null) {
142
+ debug_logger_1.debugLogger.summary("Module.resolveAsync falling back to sync as user-provided resolver doesn't have an 'async' property");
143
+ resolved = resolver(request, this.id);
144
+ }
145
+ else {
146
+ resolved = await resolver.async(request, this.id);
147
+ }
148
+ this.requests.set(request, resolved);
149
+ debug_logger_1.debugLogger.returns("Module.resolveAsync ->", resolved);
150
+ return resolved;
151
+ }
152
+ get dependencies() {
153
+ return new Set(this.requests.values());
154
+ }
125
155
  }
126
156
  exports.Module = Module;
package/dist/types.d.ts CHANGED
@@ -4,4 +4,7 @@ export type ErrorReport = {
4
4
  stage: "read" | "parse" | "getRequests" | "resolve";
5
5
  error: Error;
6
6
  };
7
- export type ResolverFunction = (id: string, fromFilePath: string) => string;
7
+ export type ResolverFunction = {
8
+ (id: string, fromFilePath: string): string;
9
+ async?(id: string, fromFilePath: string): Promise<string>;
10
+ };
package/dist/walk.d.ts CHANGED
@@ -1,12 +1,40 @@
1
1
  import type { Module } from "./module";
2
2
  import type { ErrorReport, ResolverFunction } from "./types";
3
3
  export type WalkOptions = {
4
+ /**
5
+ * A function to call whenever an error occurs.
6
+ */
4
7
  onError?: (error: ErrorReport) => void;
8
+ /**
9
+ * A function which translates the string-part of an import/require into the
10
+ * absolute path to the file on disk, OR, if there is no file for that module
11
+ * (ie. it's a node builtin), a string starting with "external:".
12
+ */
5
13
  resolver?: ResolverFunction;
14
+ /**
15
+ * Which files not to parse and find requires/imports in.
16
+ *
17
+ * Defaults to `/node_modules/`.
18
+ * Specify `null` to include node_modules.
19
+ */
6
20
  skip?: RegExp | null;
21
+ /**
22
+ * If true, only the imports/requires in the entrypoint file will be
23
+ * resolved, and no other files will be walked over.
24
+ */
7
25
  onlyEntrypoint?: boolean;
26
+ /**
27
+ * If true, results will be sorted lexicographically. If you want results to
28
+ * be stable when using `walkSync`, you should enable this, as the order is
29
+ * non-deterministic otherwise (because we walk the filesystem concurrently).
30
+ */
31
+ sort?: boolean;
8
32
  };
9
33
  export declare function walk(entrypoint: string, options?: WalkOptions): {
10
34
  errors: ErrorReport[];
11
35
  modules: Map<string, Module>;
12
36
  };
37
+ export declare function walkAsync(entrypoint: string, options?: WalkOptions): Promise<{
38
+ errors: ErrorReport[];
39
+ modules: Map<string, Module>;
40
+ }>;
package/dist/walk.js CHANGED
@@ -23,14 +23,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.walk = void 0;
26
+ exports.walkAsync = exports.walk = void 0;
27
27
  const path = __importStar(require("node:path"));
28
28
  const walker_1 = require("./walker");
29
29
  const debug_logger_1 = require("./debug-logger");
30
30
  const default_resolver_1 = require("./default-resolver");
31
- function walk(entrypoint, options) {
32
- debug_logger_1.debugLogger.summary("walk", entrypoint);
33
- debug_logger_1.debugLogger.args("walk", entrypoint, options);
31
+ function makeWalker(entrypoint, options) {
34
32
  if (!path.isAbsolute(entrypoint)) {
35
33
  throw new Error(`entrypoint must be an absolute path. received: ${entrypoint}`);
36
34
  }
@@ -45,6 +43,15 @@ function walk(entrypoint, options) {
45
43
  if (options?.onError) {
46
44
  walker.on("error", options.onError);
47
45
  }
46
+ return walker;
47
+ }
48
+ function walk(entrypoint, options) {
49
+ debug_logger_1.debugLogger.summary("walk", entrypoint);
50
+ debug_logger_1.debugLogger.args("walk", entrypoint, options);
51
+ const walker = makeWalker(entrypoint, options);
52
+ if (options?.sort) {
53
+ walker.sort();
54
+ }
48
55
  const { errors } = walker.walk();
49
56
  const modules = walker.modules;
50
57
  const ret = {
@@ -55,3 +62,20 @@ function walk(entrypoint, options) {
55
62
  return ret;
56
63
  }
57
64
  exports.walk = walk;
65
+ async function walkAsync(entrypoint, options) {
66
+ debug_logger_1.debugLogger.summary("walkAsync", entrypoint);
67
+ debug_logger_1.debugLogger.args("walkAsync", entrypoint, options);
68
+ const walker = makeWalker(entrypoint, options);
69
+ const { errors } = await walker.walkAsync();
70
+ if (options?.sort) {
71
+ walker.sort();
72
+ }
73
+ const modules = walker.modules;
74
+ const ret = {
75
+ errors,
76
+ modules,
77
+ };
78
+ debug_logger_1.debugLogger.returns("walkAsync ->", ret);
79
+ return ret;
80
+ }
81
+ exports.walkAsync = walkAsync;
package/dist/walker.d.ts CHANGED
@@ -15,4 +15,8 @@ export declare class Walker extends EventEmitter {
15
15
  walk(): {
16
16
  errors: Array<ErrorReport>;
17
17
  };
18
+ walkAsync(): Promise<{
19
+ errors: Array<ErrorReport>;
20
+ }>;
21
+ sort(): void;
18
22
  }
package/dist/walker.js CHANGED
@@ -26,6 +26,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.Walker = void 0;
27
27
  const path = __importStar(require("node:path"));
28
28
  const node_events_1 = require("node:events");
29
+ const parallel_park_1 = require("parallel-park");
29
30
  const module_1 = require("./module");
30
31
  const debug_logger_1 = require("./debug-logger");
31
32
  class Walker extends node_events_1.EventEmitter {
@@ -44,12 +45,10 @@ class Walker extends node_events_1.EventEmitter {
44
45
  let filename;
45
46
  const errors = [];
46
47
  let errorStage = "read";
47
- let errorRequest = undefined;
48
- const reportError = (error) => {
48
+ const reportError = (error, request) => {
49
49
  const report = {
50
50
  filename,
51
51
  stage: errorStage,
52
- request: errorRequest,
53
52
  error,
54
53
  };
55
54
  debug_logger_1.debugLogger.summary("error reported:", report);
@@ -76,9 +75,7 @@ class Walker extends node_events_1.EventEmitter {
76
75
  errorStage = "getRequests";
77
76
  const requests = mod.getRequests(ast);
78
77
  errorStage = "resolve";
79
- errorRequest = undefined;
80
78
  for (const request of requests) {
81
- errorRequest = request;
82
79
  try {
83
80
  const target = mod.resolve(request, this._options.resolver);
84
81
  if (target.startsWith("external:")) {
@@ -94,10 +91,9 @@ class Walker extends node_events_1.EventEmitter {
94
91
  }
95
92
  }
96
93
  catch (error) {
97
- reportError(error);
94
+ reportError(error, request);
98
95
  }
99
96
  }
100
- errorRequest = undefined;
101
97
  }
102
98
  catch (error) {
103
99
  reportError(error);
@@ -110,5 +106,90 @@ class Walker extends node_events_1.EventEmitter {
110
106
  debug_logger_1.debugLogger.returns("Walker.walk ->", ret);
111
107
  return ret;
112
108
  }
109
+ async walkAsync() {
110
+ debug_logger_1.debugLogger.summary("Walker.walkAsync");
111
+ const filesToProcess = [this.entrypoint];
112
+ let filename;
113
+ const errors = [];
114
+ let errorStage = "read";
115
+ const reportError = (error, request) => {
116
+ const report = {
117
+ filename,
118
+ stage: errorStage,
119
+ request,
120
+ error,
121
+ };
122
+ debug_logger_1.debugLogger.summary("error reported:", report);
123
+ this.emit("error", report);
124
+ errors.push(report);
125
+ };
126
+ while ((filename = filesToProcess.shift())) {
127
+ debug_logger_1.debugLogger.summary("Walker.walkAsync -> processing", filename);
128
+ this.emit("processing", filename);
129
+ if (this.modules.has(filename)) {
130
+ // already processed
131
+ continue;
132
+ }
133
+ const mod = new module_1.Module(filename);
134
+ this.modules.set(filename, mod);
135
+ try {
136
+ const code = await mod.readAsync();
137
+ if (code == null) {
138
+ // not a js/ts file
139
+ continue;
140
+ }
141
+ errorStage = "parse";
142
+ const ast = mod.parse(code);
143
+ errorStage = "getRequests";
144
+ const requests = mod.getRequests(ast);
145
+ errorStage = "resolve";
146
+ await (0, parallel_park_1.runJobs)(requests, async (request) => {
147
+ try {
148
+ const target = await mod.resolveAsync(request, this._options.resolver);
149
+ if (target.startsWith("external:")) {
150
+ return;
151
+ }
152
+ if (this._options.skip != null && this._options.skip.test(target)) {
153
+ return;
154
+ }
155
+ if (!this.modules.has(target)) {
156
+ debug_logger_1.debugLogger.summary("Walker.walkAsync -> queueing", target);
157
+ this.emit("queueing", target);
158
+ filesToProcess.push(target);
159
+ }
160
+ }
161
+ catch (error) {
162
+ reportError(error, request);
163
+ }
164
+ });
165
+ }
166
+ catch (error) {
167
+ reportError(error);
168
+ }
169
+ if (this._options.onlyEntrypoint) {
170
+ break;
171
+ }
172
+ }
173
+ const ret = { errors };
174
+ debug_logger_1.debugLogger.returns("Walker.walkAsync ->", ret);
175
+ return ret;
176
+ }
177
+ sort() {
178
+ const newModules = new Map();
179
+ const keys = Array.from(this.modules.keys());
180
+ keys.sort();
181
+ for (const key of keys) {
182
+ const mod = this.modules.get(key);
183
+ const newRequests = new Map();
184
+ const reqKeys = Array.from(mod.requests.keys());
185
+ reqKeys.sort();
186
+ for (const reqKey of reqKeys) {
187
+ newRequests.set(reqKey, mod.requests.get(reqKey));
188
+ }
189
+ mod.requests = newRequests;
190
+ newModules.set(key, mod);
191
+ }
192
+ this.modules = newModules;
193
+ }
113
194
  }
114
195
  exports.Walker = Walker;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resolve-everything",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "walk your project's import/require tree and print all the relationships",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,6 +37,7 @@
37
37
  "debug": "^4.3.4",
38
38
  "equivalent-exchange": "^1.10.0",
39
39
  "kleur": "^4.1.5",
40
+ "parallel-park": "^0.2.0",
40
41
  "pretty-print-ast": "^1.0.1",
41
42
  "resolve": "^1.22.8"
42
43
  },