extensionless-imports 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mirmaxsudov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,252 @@
1
- # Temporary Holding Version
1
+ # extensionless-imports
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Remove .ts, .tsx, .js, and .jsx suffixes from local JavaScript and
4
+ TypeScript module specifiers without reformatting the rest of the file.
5
+
6
+ - import { Button } from "./Button.tsx";
7
+ + import { Button } from "./Button";
8
+
9
+ The package includes a command-line interface and a small programmatic API.
10
+
11
+ > **Compatibility warning**
12
+ >
13
+ > Native Node.js ESM and browsers generally require explicit extensions for
14
+ > relative imports. Use this package only when your bundler, framework, or
15
+ > module resolver supports extensionless imports. Preview and check modes make
16
+ > it possible to review changes before writing them.
17
+
18
+ ## Installation
19
+
20
+ Install it in a project:
21
+
22
+ npm install --save-dev extensionless-imports
23
+
24
+ Or run it without adding a dependency:
25
+
26
+ npx extensionless-imports src
27
+
28
+ ## CLI
29
+
30
+ Preview changes without modifying files:
31
+
32
+ npx extensionless-imports src test
33
+
34
+ Write changes:
35
+
36
+ npx extensionless-imports --write src test
37
+
38
+ Check in CI and exit with status 1 when changes are required:
39
+
40
+ npx extensionless-imports --check src test
41
+
42
+ Process package subpaths as well as local paths:
43
+
44
+ npx extensionless-imports --write --include-package-subpaths src
45
+
46
+ Ignore files or directories with repeatable glob patterns:
47
+
48
+ npx extensionless-imports --ignore "**/*.test.ts" --ignore "src/generated/**" src
49
+
50
+ Scan only a custom comma-separated set of source extensions:
51
+
52
+ npx extensionless-imports --extensions ts,tsx src
53
+
54
+ Show the active configuration, discovery results, unchanged files, and
55
+ replacement counts:
56
+
57
+ npx extensionless-imports --verbose src
58
+
59
+ Automatically rewrite matching imports whenever a source file is saved:
60
+
61
+ npx extensionless-imports --write --watch src
62
+
63
+ Full usage:
64
+
65
+ Usage: extensionless-imports [options] [files or directories...]
66
+
67
+ Options:
68
+ --write Update files in place
69
+ --check Exit with code 1 when changes are needed
70
+ --include-package-subpaths Also rewrite package subpaths
71
+ --ignore <pattern> Ignore a glob pattern (repeatable)
72
+ --extensions <list> Scan comma-separated extensions
73
+ --verbose Show discovery and processing details
74
+ --watch Rewrite files automatically after save
75
+ -h, --help Show help
76
+ -v, --version Show the package version
77
+
78
+ When no path is supplied, the CLI scans the current directory. It ignores
79
+ .git, node_modules, dist, and coverage directories.
80
+
81
+ Ignore patterns use forward-slash paths relative to the directory where the
82
+ command is run. Quote glob patterns so the shell passes them unchanged.
83
+
84
+ The default scanned extensions are .ts, .tsx, .js, .jsx, .mts, .cts, .mjs,
85
+ and .cjs. The --extensions option changes only which source files are scanned;
86
+ the transformer still removes only .ts, .tsx, .js, and .jsx from imports.
87
+
88
+ ### Watch mode
89
+
90
+ Watch mode runs the normal write pass once, then stays active and processes
91
+ matching files after they are created or saved:
92
+
93
+ npx extensionless-imports --write --watch src
94
+
95
+ The --watch option requires --write and cannot be combined with --check.
96
+ Ignore patterns, extension filters, package-subpath handling, and verbose
97
+ output all apply to watched changes. Press Ctrl+C to stop watching cleanly.
98
+
99
+ The watcher waits briefly for a save to finish and serializes repeated events
100
+ for the same file. This avoids processing incomplete editor writes and
101
+ prevents overlapping rewrites.
102
+
103
+ ### Exit codes
104
+
105
+ | Code | Meaning |
106
+ | ---: | --- |
107
+ | 0 | The command completed successfully |
108
+ | 1 | Check mode found files that require changes |
109
+ | 2 | Arguments, source parsing, or filesystem processing failed |
110
+
111
+ ## API
112
+
113
+ ### stripImportExtensions
114
+
115
+ Transform a source string without accessing the filesystem:
116
+
117
+ import { stripImportExtensions } from "extensionless-imports";
118
+
119
+ const output = stripImportExtensions(
120
+ 'import value from "./value.ts";',
121
+ );
122
+
123
+ // import value from "./value";
124
+
125
+ Package subpaths are unchanged by default. They can be enabled explicitly:
126
+
127
+ stripImportExtensions(source, {
128
+ includePackageSubpaths: true,
129
+ });
130
+
131
+ ### processFile
132
+
133
+ Preview a file:
134
+
135
+ import { processFile } from "extensionless-imports";
136
+
137
+ const result = await processFile("src/example.ts");
138
+
139
+ Write a file:
140
+
141
+ await processFile("src/example.ts", { write: true });
142
+
143
+ The result reports the absolute path, whether the file changed, the number of
144
+ replacements, and whether content was written.
145
+
146
+ The check and write options are mutually exclusive.
147
+
148
+ ### findSourceFiles
149
+
150
+ Discover supported source files:
151
+
152
+ import { findSourceFiles } from "extensionless-imports";
153
+
154
+ const files = await findSourceFiles("src");
155
+
156
+ Additional directory names can be ignored:
157
+
158
+ const files = await findSourceFiles(".", {
159
+ ignoredDirectories: ["generated", "vendor"],
160
+ });
161
+
162
+ Use the same glob and extension filtering available in the CLI:
163
+
164
+ const files = await findSourceFiles(".", {
165
+ baseDirectory: process.cwd(),
166
+ ignorePatterns: ["**/*.test.ts", "src/generated/**"],
167
+ extensions: ["ts", "tsx"],
168
+ });
169
+
170
+ ## Supported syntax
171
+
172
+ The transformer handles statically analyzable module specifiers in:
173
+
174
+ - Default, named, namespace, and type-only imports
175
+ - Side-effect imports
176
+ - Named and star re-exports
177
+ - String-literal dynamic imports
178
+
179
+ Relative imports and the conventional `@/` and `~/` project aliases are
180
+ rewritten by default. For example:
181
+
182
+ ```ts
183
+ import Button from "@/components/Button.tsx";
184
+ import type { User } from "~/types/User.ts";
185
+ ```
186
+
187
+ becomes:
188
+
189
+ ```ts
190
+ import Button from "@/components/Button";
191
+ import type { User } from "~/types/User";
192
+ ```
193
+
194
+ Examples:
195
+
196
+ import value from "./value.ts";
197
+ import type { User } from "./User.ts";
198
+ import "./setup.js";
199
+ export { Button } from "./Button.tsx";
200
+ export * from "./utilities.js";
201
+ const page = import("./Page.jsx");
202
+
203
+ Query strings and fragments are preserved:
204
+
205
+ import raw from "./file.ts?raw";
206
+ // becomes "./file?raw"
207
+
208
+ import preview from "./Component.tsx#preview";
209
+ // becomes "./Component#preview"
210
+
211
+ ## Intentionally unchanged
212
+
213
+ The package does not modify:
214
+
215
+ - Ordinary strings and comments
216
+ - JSON, CSS, images, or other non-code resources
217
+ - Nonliteral dynamic imports
218
+ - URL, node:, and package-import-map specifiers
219
+ - Root-relative browser paths
220
+ - Package imports and package subpaths by default (scoped packages such as
221
+ `@scope/package/file.js` are not mistaken for the `@/` alias)
222
+ - require() calls
223
+
224
+ Package subpaths can be enabled with --include-package-subpaths. This option
225
+ may also match project aliases shaped like package subpaths, so review its
226
+ output before writing.
227
+
228
+ ## Supported source files
229
+
230
+ The scanner reads:
231
+
232
+ - .ts and .tsx
233
+ - .js and .jsx
234
+ - .mts and .cts
235
+ - .mjs and .cjs
236
+
237
+ Only .ts, .tsx, .js, and .jsx suffixes are removed from module specifiers.
238
+
239
+ ## Development
240
+
241
+ npm install
242
+ npm run typecheck
243
+ npm test
244
+ npm run check
245
+ npm pack --dry-run
246
+
247
+ The project uses TypeScript, Vitest, es-module-lexer, and chokidar. Build
248
+ output is generated in dist and is not committed.
249
+
250
+ ## License
251
+
252
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,389 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises";
3
+ import { isAbsolute, relative, resolve } from "node:path";
4
+ import { watch as watchPaths } from "chokidar";
5
+ import { DEFAULT_SOURCE_EXTENSIONS, findSourceFiles, isPathIgnored, processFile, } from "./index.js";
6
+ try {
7
+ const options = parseArguments(process.argv.slice(2));
8
+ if (options.help) {
9
+ printHelp();
10
+ }
11
+ else if (options.version) {
12
+ console.log(await readVersion());
13
+ }
14
+ else {
15
+ await run(options);
16
+ }
17
+ }
18
+ catch (error) {
19
+ console.error(error instanceof Error ? error.message : String(error));
20
+ process.exitCode = 2;
21
+ }
22
+ async function run(options) {
23
+ const inputPaths = (options.paths.length > 0 ? options.paths : ["."]).map((inputPath) => resolve(inputPath));
24
+ const fileSet = new Set();
25
+ const findOptions = {
26
+ baseDirectory: process.cwd(),
27
+ ignorePatterns: options.ignorePatterns,
28
+ };
29
+ if (options.extensions !== undefined) {
30
+ findOptions.extensions = options.extensions;
31
+ }
32
+ if (options.verbose) {
33
+ console.log("mode: " + options.mode);
34
+ console.log("package subpaths: " +
35
+ (options.includePackageSubpaths ? "included" : "excluded"));
36
+ console.log("extensions: " +
37
+ (options.extensions ?? DEFAULT_SOURCE_EXTENSIONS).join(", "));
38
+ console.log("ignore patterns: " +
39
+ (options.ignorePatterns.length > 0
40
+ ? options.ignorePatterns.join(", ")
41
+ : "none"));
42
+ console.log("watch: " + (options.watch ? "enabled" : "disabled"));
43
+ }
44
+ for (const inputPath of inputPaths) {
45
+ if (options.verbose) {
46
+ console.log("scanning " + displayPath(inputPath));
47
+ }
48
+ for (const filePath of await findSourceFiles(inputPath, findOptions)) {
49
+ fileSet.add(filePath);
50
+ }
51
+ }
52
+ const files = [...fileSet].sort((left, right) => left.localeCompare(right));
53
+ let changedCount = 0;
54
+ if (options.verbose) {
55
+ const fileLabel = files.length === 1 ? "file" : "files";
56
+ console.log("found " + files.length + " source " + fileLabel);
57
+ }
58
+ for (const filePath of files) {
59
+ const result = await processFile(filePath, {
60
+ check: options.mode === "check",
61
+ write: options.mode === "write",
62
+ includePackageSubpaths: options.includePackageSubpaths,
63
+ });
64
+ if (!result.changed) {
65
+ if (options.verbose) {
66
+ console.log("unchanged " + displayPath(result.path));
67
+ }
68
+ continue;
69
+ }
70
+ changedCount += 1;
71
+ const action = options.mode === "write" ? "updated" : "would update";
72
+ const details = options.verbose
73
+ ? " (" +
74
+ result.replacements +
75
+ " " +
76
+ (result.replacements === 1 ? "replacement" : "replacements") +
77
+ ")"
78
+ : "";
79
+ console.log(action + " " + displayPath(result.path) + details);
80
+ }
81
+ if (changedCount > 0)
82
+ console.log();
83
+ console.log(summary(options.mode, changedCount, files.length));
84
+ if (options.mode === "check" && changedCount > 0) {
85
+ process.exitCode = 1;
86
+ }
87
+ if (options.watch) {
88
+ await startWatcher(inputPaths, findOptions, options);
89
+ }
90
+ }
91
+ function parseArguments(args) {
92
+ let mode = "preview";
93
+ let modeWasSet = false;
94
+ let includePackageSubpaths = false;
95
+ const ignorePatterns = [];
96
+ let extensions;
97
+ let verbose = false;
98
+ let watch = false;
99
+ let help = false;
100
+ let version = false;
101
+ let positionalOnly = false;
102
+ const paths = [];
103
+ for (let index = 0; index < args.length; index += 1) {
104
+ const argument = args[index];
105
+ if (argument === undefined)
106
+ continue;
107
+ if (positionalOnly) {
108
+ paths.push(argument);
109
+ continue;
110
+ }
111
+ if (argument === "--") {
112
+ positionalOnly = true;
113
+ continue;
114
+ }
115
+ if (argument === "--write") {
116
+ if (modeWasSet && mode !== "write") {
117
+ throw new TypeError("--write and --check cannot be used together");
118
+ }
119
+ mode = "write";
120
+ modeWasSet = true;
121
+ continue;
122
+ }
123
+ if (argument === "--check") {
124
+ if (modeWasSet && mode !== "check") {
125
+ throw new TypeError("--write and --check cannot be used together");
126
+ }
127
+ mode = "check";
128
+ modeWasSet = true;
129
+ continue;
130
+ }
131
+ if (argument === "--include-package-subpaths") {
132
+ includePackageSubpaths = true;
133
+ continue;
134
+ }
135
+ if (argument === "--ignore") {
136
+ ignorePatterns.push(readOptionValue(args, index, "--ignore"));
137
+ index += 1;
138
+ continue;
139
+ }
140
+ if (argument.startsWith("--ignore=")) {
141
+ const pattern = argument.slice("--ignore=".length);
142
+ if (pattern.length === 0) {
143
+ throw new TypeError("--ignore requires a pattern");
144
+ }
145
+ ignorePatterns.push(pattern);
146
+ continue;
147
+ }
148
+ if (argument === "--extensions") {
149
+ const value = readOptionValue(args, index, "--extensions");
150
+ extensions = [
151
+ ...(extensions ?? []),
152
+ ...parseExtensionList(value),
153
+ ];
154
+ index += 1;
155
+ continue;
156
+ }
157
+ if (argument.startsWith("--extensions=")) {
158
+ extensions = [
159
+ ...(extensions ?? []),
160
+ ...parseExtensionList(argument.slice("--extensions=".length)),
161
+ ];
162
+ continue;
163
+ }
164
+ if (argument === "--verbose") {
165
+ verbose = true;
166
+ continue;
167
+ }
168
+ if (argument === "--watch") {
169
+ watch = true;
170
+ continue;
171
+ }
172
+ if (argument === "--help" || argument === "-h") {
173
+ help = true;
174
+ continue;
175
+ }
176
+ if (argument === "--version" || argument === "-v") {
177
+ version = true;
178
+ continue;
179
+ }
180
+ if (argument.startsWith("-")) {
181
+ throw new TypeError("unknown option: " + argument);
182
+ }
183
+ paths.push(argument);
184
+ }
185
+ if (watch && mode !== "write") {
186
+ throw new TypeError("--watch requires --write");
187
+ }
188
+ return {
189
+ mode,
190
+ includePackageSubpaths,
191
+ ignorePatterns,
192
+ extensions,
193
+ verbose,
194
+ watch,
195
+ paths,
196
+ help,
197
+ version,
198
+ };
199
+ }
200
+ async function startWatcher(inputPaths, findOptions, options) {
201
+ const watcher = watchPaths(inputPaths, {
202
+ ignoreInitial: true,
203
+ followSymlinks: false,
204
+ atomic: true,
205
+ awaitWriteFinish: {
206
+ stabilityThreshold: 100,
207
+ pollInterval: 20,
208
+ },
209
+ ignored: (filePath, stats) => hasDefaultIgnoredDirectory(filePath) ||
210
+ isPathIgnored(resolve(filePath), stats?.isDirectory() === true, process.cwd(), options.ignorePatterns),
211
+ });
212
+ const pending = new Map();
213
+ const enqueue = (filePath) => {
214
+ const absolutePath = resolve(filePath);
215
+ const previous = pending.get(absolutePath) ?? Promise.resolve();
216
+ const current = previous
217
+ .catch(() => undefined)
218
+ .then(() => processWatchedFile(absolutePath, findOptions, options));
219
+ pending.set(absolutePath, current);
220
+ void current.finally(() => {
221
+ if (pending.get(absolutePath) === current) {
222
+ pending.delete(absolutePath);
223
+ }
224
+ });
225
+ };
226
+ watcher.on("add", enqueue);
227
+ watcher.on("change", enqueue);
228
+ await waitForWatcherReady(watcher);
229
+ console.log("watching for changes (press Ctrl+C to stop)");
230
+ await waitForWatcherShutdown(watcher, pending);
231
+ }
232
+ async function processWatchedFile(filePath, findOptions, options) {
233
+ try {
234
+ const files = await findSourceFiles(filePath, findOptions);
235
+ if (files.length === 0)
236
+ return;
237
+ const result = await processFile(filePath, {
238
+ write: true,
239
+ includePackageSubpaths: options.includePackageSubpaths,
240
+ });
241
+ if (result.changed) {
242
+ const details = options.verbose
243
+ ? " (" +
244
+ result.replacements +
245
+ " " +
246
+ (result.replacements === 1
247
+ ? "replacement"
248
+ : "replacements") +
249
+ ")"
250
+ : "";
251
+ console.log("updated " + displayPath(result.path) + details);
252
+ }
253
+ else if (options.verbose) {
254
+ console.log("unchanged " + displayPath(result.path));
255
+ }
256
+ }
257
+ catch (error) {
258
+ if (isNodeError(error) && error.code === "ENOENT")
259
+ return;
260
+ console.error("failed " +
261
+ displayPath(filePath) +
262
+ ": " +
263
+ (error instanceof Error ? error.message : String(error)));
264
+ process.exitCode = 2;
265
+ }
266
+ }
267
+ function waitForWatcherReady(watcher) {
268
+ return new Promise((resolveReady, rejectReady) => {
269
+ const onReady = () => {
270
+ watcher.off("error", onError);
271
+ resolveReady();
272
+ };
273
+ const onError = (error) => {
274
+ watcher.off("ready", onReady);
275
+ rejectReady(error);
276
+ };
277
+ watcher.once("ready", onReady);
278
+ watcher.once("error", onError);
279
+ });
280
+ }
281
+ function waitForWatcherShutdown(watcher, pending) {
282
+ return new Promise((resolveShutdown) => {
283
+ let closing = false;
284
+ const cleanup = () => {
285
+ process.off("SIGINT", stop);
286
+ process.off("SIGTERM", stop);
287
+ };
288
+ const stop = () => {
289
+ if (closing)
290
+ return;
291
+ closing = true;
292
+ void (async () => {
293
+ await watcher.close();
294
+ await Promise.allSettled([...pending.values()]);
295
+ cleanup();
296
+ resolveShutdown();
297
+ })();
298
+ };
299
+ const onError = (error) => {
300
+ console.error("watcher error: " +
301
+ (error instanceof Error ? error.message : String(error)));
302
+ process.exitCode = 2;
303
+ stop();
304
+ };
305
+ watcher.on("error", onError);
306
+ process.once("SIGINT", stop);
307
+ process.once("SIGTERM", stop);
308
+ });
309
+ }
310
+ function hasDefaultIgnoredDirectory(filePath) {
311
+ const ignored = new Set([".git", "node_modules", "dist", "coverage"]);
312
+ return relative(process.cwd(), resolve(filePath))
313
+ .split(/[\\/]+/)
314
+ .some((segment) => ignored.has(segment.toLowerCase()));
315
+ }
316
+ function isNodeError(error) {
317
+ return error instanceof Error && "code" in error;
318
+ }
319
+ function readOptionValue(args, optionIndex, optionName) {
320
+ const value = args[optionIndex + 1];
321
+ if (value === undefined || value.startsWith("-")) {
322
+ throw new TypeError(optionName + " requires a value");
323
+ }
324
+ return value;
325
+ }
326
+ function parseExtensionList(value) {
327
+ const extensions = value.split(",").map((extension) => extension.trim());
328
+ if (extensions.length === 0 ||
329
+ extensions.some((extension) => extension.length === 0)) {
330
+ throw new TypeError("--extensions requires a comma-separated list");
331
+ }
332
+ for (const extension of extensions) {
333
+ const withoutDot = extension.startsWith(".")
334
+ ? extension.slice(1)
335
+ : extension;
336
+ if (!/^[a-z0-9]+$/i.test(withoutDot)) {
337
+ throw new TypeError("invalid source extension: " + extension);
338
+ }
339
+ }
340
+ return extensions;
341
+ }
342
+ function summary(mode, changed, total) {
343
+ const fileLabel = total === 1 ? "file" : "files";
344
+ if (mode === "write") {
345
+ return changed + " of " + total + " " + fileLabel + " updated";
346
+ }
347
+ if (mode === "check") {
348
+ const changedLabel = changed === 1 ? "file needs" : "files need";
349
+ return changed + " of " + total + " " + changedLabel + " changes";
350
+ }
351
+ return changed + " of " + total + " " + fileLabel + " would change";
352
+ }
353
+ function displayPath(filePath) {
354
+ const pathFromCwd = relative(process.cwd(), filePath);
355
+ if (pathFromCwd.length > 0 &&
356
+ !pathFromCwd.startsWith("..") &&
357
+ !isAbsolute(pathFromCwd)) {
358
+ return pathFromCwd;
359
+ }
360
+ return filePath;
361
+ }
362
+ async function readVersion() {
363
+ const packageJsonUrl = new URL("../package.json", import.meta.url);
364
+ const packageJson = JSON.parse(await readFile(packageJsonUrl, "utf8"));
365
+ if (typeof packageJson.version !== "string") {
366
+ throw new TypeError("package.json does not contain a valid version");
367
+ }
368
+ return packageJson.version;
369
+ }
370
+ function printHelp() {
371
+ console.log([
372
+ "Usage: extensionless-imports [options] [files or directories...]",
373
+ "",
374
+ "Remove .ts, .tsx, .js, and .jsx suffixes from module specifiers.",
375
+ "Preview mode is used when neither --write nor --check is provided.",
376
+ "",
377
+ "Options:",
378
+ " --write Update files in place",
379
+ " --check Exit with code 1 when changes are needed",
380
+ " --include-package-subpaths Also rewrite package subpaths",
381
+ " --ignore <pattern> Ignore a glob pattern (repeatable)",
382
+ " --extensions <list> Scan comma-separated extensions",
383
+ " --verbose Show discovery and processing details",
384
+ " --watch Rewrite files automatically after save",
385
+ " -h, --help Show this help",
386
+ " -v, --version Show the package version",
387
+ ].join("\n"));
388
+ }
389
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport {readFile} from \"node:fs/promises\";\nimport {isAbsolute, relative, resolve} from \"node:path\";\nimport {watch as watchPaths, type FSWatcher} from \"chokidar\";\n\nimport {\n DEFAULT_SOURCE_EXTENSIONS,\n findSourceFiles,\n isPathIgnored,\n processFile,\n} from \"./index.js\";\nimport type {FindFilesOptions} from \"./types.js\";\n\ntype RunMode = \"preview\" | \"write\" | \"check\";\n\ninterface CliOptions {\n mode: RunMode;\n includePackageSubpaths: boolean;\n ignorePatterns: string[];\n extensions: string[] | undefined;\n verbose: boolean;\n watch: boolean;\n paths: string[];\n help: boolean;\n version: boolean;\n}\n\ntry {\n const options = parseArguments(process.argv.slice(2));\n\n if (options.help) {\n printHelp();\n } else if (options.version) {\n console.log(await readVersion());\n } else {\n await run(options);\n }\n} catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 2;\n}\n\nasync function run(options: CliOptions): Promise<void> {\n const inputPaths = (options.paths.length > 0 ? options.paths : [\".\"]).map(\n (inputPath) => resolve(inputPath),\n );\n const fileSet = new Set<string>();\n const findOptions: FindFilesOptions = {\n baseDirectory: process.cwd(),\n ignorePatterns: options.ignorePatterns,\n };\n\n if (options.extensions !== undefined) {\n findOptions.extensions = options.extensions;\n }\n\n if (options.verbose) {\n console.log(\"mode: \" + options.mode);\n console.log(\n \"package subpaths: \" +\n (options.includePackageSubpaths ? \"included\" : \"excluded\"),\n );\n console.log(\n \"extensions: \" +\n (options.extensions ?? DEFAULT_SOURCE_EXTENSIONS).join(\", \"),\n );\n console.log(\n \"ignore patterns: \" +\n (options.ignorePatterns.length > 0\n ? options.ignorePatterns.join(\", \")\n : \"none\"),\n );\n console.log(\"watch: \" + (options.watch ? \"enabled\" : \"disabled\"));\n }\n\n for (const inputPath of inputPaths) {\n if (options.verbose) {\n console.log(\"scanning \" + displayPath(inputPath));\n }\n\n for (const filePath of await findSourceFiles(inputPath, findOptions)) {\n fileSet.add(filePath);\n }\n }\n\n const files = [...fileSet].sort((left, right) =>\n left.localeCompare(right),\n );\n let changedCount = 0;\n\n if (options.verbose) {\n const fileLabel = files.length === 1 ? \"file\" : \"files\";\n console.log(\"found \" + files.length + \" source \" + fileLabel);\n }\n\n for (const filePath of files) {\n const result = await processFile(filePath, {\n check: options.mode === \"check\",\n write: options.mode === \"write\",\n includePackageSubpaths: options.includePackageSubpaths,\n });\n\n if (!result.changed) {\n if (options.verbose) {\n console.log(\"unchanged \" + displayPath(result.path));\n }\n continue;\n }\n changedCount += 1;\n\n const action = options.mode === \"write\" ? \"updated\" : \"would update\";\n const details = options.verbose\n ? \" (\" +\n result.replacements +\n \" \" +\n (result.replacements === 1 ? \"replacement\" : \"replacements\") +\n \")\"\n : \"\";\n console.log(action + \" \" + displayPath(result.path) + details);\n }\n\n if (changedCount > 0) console.log();\n console.log(summary(options.mode, changedCount, files.length));\n\n if (options.mode === \"check\" && changedCount > 0) {\n process.exitCode = 1;\n }\n\n if (options.watch) {\n await startWatcher(inputPaths, findOptions, options);\n }\n}\n\nfunction parseArguments(args: string[]): CliOptions {\n let mode: RunMode = \"preview\";\n let modeWasSet = false;\n let includePackageSubpaths = false;\n const ignorePatterns: string[] = [];\n let extensions: string[] | undefined;\n let verbose = false;\n let watch = false;\n let help = false;\n let version = false;\n let positionalOnly = false;\n const paths: string[] = [];\n\n for (let index = 0; index < args.length; index += 1) {\n const argument = args[index];\n if (argument === undefined) continue;\n\n if (positionalOnly) {\n paths.push(argument);\n continue;\n }\n\n if (argument === \"--\") {\n positionalOnly = true;\n continue;\n }\n\n if (argument === \"--write\") {\n if (modeWasSet && mode !== \"write\") {\n throw new TypeError(\"--write and --check cannot be used together\");\n }\n mode = \"write\";\n modeWasSet = true;\n continue;\n }\n\n if (argument === \"--check\") {\n if (modeWasSet && mode !== \"check\") {\n throw new TypeError(\"--write and --check cannot be used together\");\n }\n mode = \"check\";\n modeWasSet = true;\n continue;\n }\n\n if (argument === \"--include-package-subpaths\") {\n includePackageSubpaths = true;\n continue;\n }\n\n if (argument === \"--ignore\") {\n ignorePatterns.push(readOptionValue(args, index, \"--ignore\"));\n index += 1;\n continue;\n }\n\n if (argument.startsWith(\"--ignore=\")) {\n const pattern = argument.slice(\"--ignore=\".length);\n if (pattern.length === 0) {\n throw new TypeError(\"--ignore requires a pattern\");\n }\n ignorePatterns.push(pattern);\n continue;\n }\n\n if (argument === \"--extensions\") {\n const value = readOptionValue(args, index, \"--extensions\");\n extensions = [\n ...(extensions ?? []),\n ...parseExtensionList(value),\n ];\n index += 1;\n continue;\n }\n\n if (argument.startsWith(\"--extensions=\")) {\n extensions = [\n ...(extensions ?? []),\n ...parseExtensionList(\n argument.slice(\"--extensions=\".length),\n ),\n ];\n continue;\n }\n\n if (argument === \"--verbose\") {\n verbose = true;\n continue;\n }\n\n if (argument === \"--watch\") {\n watch = true;\n continue;\n }\n\n if (argument === \"--help\" || argument === \"-h\") {\n help = true;\n continue;\n }\n\n if (argument === \"--version\" || argument === \"-v\") {\n version = true;\n continue;\n }\n\n if (argument.startsWith(\"-\")) {\n throw new TypeError(\"unknown option: \" + argument);\n }\n\n paths.push(argument);\n }\n\n if (watch && mode !== \"write\") {\n throw new TypeError(\"--watch requires --write\");\n }\n\n return {\n mode,\n includePackageSubpaths,\n ignorePatterns,\n extensions,\n verbose,\n watch,\n paths,\n help,\n version,\n };\n}\n\nasync function startWatcher(\n inputPaths: string[],\n findOptions: FindFilesOptions,\n options: CliOptions,\n): Promise<void> {\n const watcher = watchPaths(inputPaths, {\n ignoreInitial: true,\n followSymlinks: false,\n atomic: true,\n awaitWriteFinish: {\n stabilityThreshold: 100,\n pollInterval: 20,\n },\n ignored: (filePath, stats) =>\n hasDefaultIgnoredDirectory(filePath) ||\n isPathIgnored(\n resolve(filePath),\n stats?.isDirectory() === true,\n process.cwd(),\n options.ignorePatterns,\n ),\n });\n\n const pending = new Map<string, Promise<void>>();\n\n const enqueue = (filePath: string): void => {\n const absolutePath = resolve(filePath);\n const previous = pending.get(absolutePath) ?? Promise.resolve();\n const current = previous\n .catch(() => undefined)\n .then(() =>\n processWatchedFile(absolutePath, findOptions, options),\n );\n\n pending.set(absolutePath, current);\n void current.finally(() => {\n if (pending.get(absolutePath) === current) {\n pending.delete(absolutePath);\n }\n });\n };\n\n watcher.on(\"add\", enqueue);\n watcher.on(\"change\", enqueue);\n await waitForWatcherReady(watcher);\n\n console.log(\"watching for changes (press Ctrl+C to stop)\");\n await waitForWatcherShutdown(watcher, pending);\n}\n\nasync function processWatchedFile(\n filePath: string,\n findOptions: FindFilesOptions,\n options: CliOptions,\n): Promise<void> {\n try {\n const files = await findSourceFiles(filePath, findOptions);\n if (files.length === 0) return;\n\n const result = await processFile(filePath, {\n write: true,\n includePackageSubpaths: options.includePackageSubpaths,\n });\n\n if (result.changed) {\n const details = options.verbose\n ? \" (\" +\n result.replacements +\n \" \" +\n (result.replacements === 1\n ? \"replacement\"\n : \"replacements\") +\n \")\"\n : \"\";\n console.log(\"updated \" + displayPath(result.path) + details);\n } else if (options.verbose) {\n console.log(\"unchanged \" + displayPath(result.path));\n }\n } catch (error) {\n if (isNodeError(error) && error.code === \"ENOENT\") return;\n\n console.error(\n \"failed \" +\n displayPath(filePath) +\n \": \" +\n (error instanceof Error ? error.message : String(error)),\n );\n process.exitCode = 2;\n }\n}\n\nfunction waitForWatcherReady(watcher: FSWatcher): Promise<void> {\n return new Promise((resolveReady, rejectReady) => {\n const onReady = (): void => {\n watcher.off(\"error\", onError);\n resolveReady();\n };\n const onError = (error: unknown): void => {\n watcher.off(\"ready\", onReady);\n rejectReady(error);\n };\n\n watcher.once(\"ready\", onReady);\n watcher.once(\"error\", onError);\n });\n}\n\nfunction waitForWatcherShutdown(\n watcher: FSWatcher,\n pending: ReadonlyMap<string, Promise<void>>,\n): Promise<void> {\n return new Promise((resolveShutdown) => {\n let closing = false;\n\n const cleanup = (): void => {\n process.off(\"SIGINT\", stop);\n process.off(\"SIGTERM\", stop);\n };\n\n const stop = (): void => {\n if (closing) return;\n closing = true;\n\n void (async () => {\n await watcher.close();\n await Promise.allSettled([...pending.values()]);\n cleanup();\n resolveShutdown();\n })();\n };\n\n const onError = (error: unknown): void => {\n console.error(\n \"watcher error: \" +\n (error instanceof Error ? error.message : String(error)),\n );\n process.exitCode = 2;\n stop();\n };\n\n watcher.on(\"error\", onError);\n process.once(\"SIGINT\", stop);\n process.once(\"SIGTERM\", stop);\n });\n}\n\nfunction hasDefaultIgnoredDirectory(filePath: string): boolean {\n const ignored = new Set([\".git\", \"node_modules\", \"dist\", \"coverage\"]);\n return relative(process.cwd(), resolve(filePath))\n .split(/[\\\\/]+/)\n .some((segment) => ignored.has(segment.toLowerCase()));\n}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error;\n}\n\nfunction readOptionValue(\n args: readonly string[],\n optionIndex: number,\n optionName: string,\n): string {\n const value = args[optionIndex + 1];\n if (value === undefined || value.startsWith(\"-\")) {\n throw new TypeError(optionName + \" requires a value\");\n }\n return value;\n}\n\nfunction parseExtensionList(value: string): string[] {\n const extensions = value.split(\",\").map((extension) => extension.trim());\n if (\n extensions.length === 0 ||\n extensions.some((extension) => extension.length === 0)\n ) {\n throw new TypeError(\"--extensions requires a comma-separated list\");\n }\n\n for (const extension of extensions) {\n const withoutDot = extension.startsWith(\".\")\n ? extension.slice(1)\n : extension;\n if (!/^[a-z0-9]+$/i.test(withoutDot)) {\n throw new TypeError(\"invalid source extension: \" + extension);\n }\n }\n\n return extensions;\n}\n\nfunction summary(mode: RunMode, changed: number, total: number): string {\n const fileLabel = total === 1 ? \"file\" : \"files\";\n\n if (mode === \"write\") {\n return changed + \" of \" + total + \" \" + fileLabel + \" updated\";\n }\n\n if (mode === \"check\") {\n const changedLabel = changed === 1 ? \"file needs\" : \"files need\";\n return changed + \" of \" + total + \" \" + changedLabel + \" changes\";\n }\n\n return changed + \" of \" + total + \" \" + fileLabel + \" would change\";\n}\n\nfunction displayPath(filePath: string): string {\n const pathFromCwd = relative(process.cwd(), filePath);\n if (\n pathFromCwd.length > 0 &&\n !pathFromCwd.startsWith(\"..\") &&\n !isAbsolute(pathFromCwd)\n ) {\n return pathFromCwd;\n }\n\n return filePath;\n}\n\nasync function readVersion(): Promise<string> {\n const packageJsonUrl = new URL(\"../package.json\", import.meta.url);\n const packageJson = JSON.parse(await readFile(packageJsonUrl, \"utf8\")) as {\n version?: unknown;\n };\n\n if (typeof packageJson.version !== \"string\") {\n throw new TypeError(\"package.json does not contain a valid version\");\n }\n\n return packageJson.version;\n}\n\nfunction printHelp(): void {\n console.log(\n [\n \"Usage: extensionless-imports [options] [files or directories...]\",\n \"\",\n \"Remove .ts, .tsx, .js, and .jsx suffixes from module specifiers.\",\n \"Preview mode is used when neither --write nor --check is provided.\",\n \"\",\n \"Options:\",\n \" --write Update files in place\",\n \" --check Exit with code 1 when changes are needed\",\n \" --include-package-subpaths Also rewrite package subpaths\",\n \" --ignore <pattern> Ignore a glob pattern (repeatable)\",\n \" --extensions <list> Scan comma-separated extensions\",\n \" --verbose Show discovery and processing details\",\n \" --watch Rewrite files automatically after save\",\n \" -h, --help Show this help\",\n \" -v, --version Show the package version\",\n ].join(\"\\n\"),\n );\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import type { FindFilesOptions, ProcessFileOptions, ProcessResult, StripOptions } from "./types.js";
2
+ export type { FindFilesOptions, ProcessFileOptions, ProcessResult, StripOptions, } from "./types.js";
3
+ export declare const DEFAULT_SOURCE_EXTENSIONS: readonly string[];
4
+ /**
5
+ * Remove .ts, .tsx, .js, and .jsx suffixes from eligible module specifiers
6
+ * while preserving every other part of the source text.
7
+ */
8
+ export declare function stripImportExtensions(source: string, options?: StripOptions): string;
9
+ /**
10
+ * Inspect or rewrite one source file.
11
+ *
12
+ * Preview is the default. Pass write: true to persist changes. The check and
13
+ * write options are mutually exclusive.
14
+ */
15
+ export declare function processFile(filePath: string, options?: ProcessFileOptions): Promise<ProcessResult>;
16
+ /**
17
+ * Find supported JavaScript and TypeScript source files below a file or
18
+ * directory. Directory symlinks are intentionally not followed.
19
+ */
20
+ export declare function findSourceFiles(inputPath: string, options?: FindFilesOptions): Promise<string[]>;
21
+ /**
22
+ * Test a path against the same cwd-relative glob rules used by file discovery.
23
+ */
24
+ export declare function isPathIgnored(filePath: string, isDirectory: boolean, baseDirectory: string, patterns: readonly string[]): boolean;
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EACR,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,EACb,YAAY,EACf,MAAM,YAAY,CAAC;AAEpB,YAAY,EACR,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,EACb,YAAY,GACf,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EASrD,CAAC;AAsBH;;;GAGG;AACH,wBAAgB,qBAAqB,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,YAAiB,GAC3B,MAAM,CAER;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,kBAAuB,GACjC,OAAO,CAAC,aAAa,CAAC,CAqBxB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,gBAAqB,GAC/B,OAAO,CAAC,MAAM,EAAE,CAAC,CAwBnB;AAmKD;;GAEG;AACH,wBAAgB,aAAa,CACzB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,OAAO,EACpB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC5B,OAAO,CAsBT"}
package/dist/index.js ADDED
@@ -0,0 +1,225 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { chmod, lstat, readFile, readdir, rename, unlink, writeFile, } from "node:fs/promises";
3
+ import { basename, dirname, extname, join, relative, resolve, } from "node:path";
4
+ import { init, parse } from "es-module-lexer";
5
+ import { minimatch } from "minimatch";
6
+ const REMOVABLE_EXTENSION = /\.(?:tsx?|jsx?)(?=[?#]|$)/i;
7
+ export const DEFAULT_SOURCE_EXTENSIONS = Object.freeze([
8
+ ".ts",
9
+ ".tsx",
10
+ ".js",
11
+ ".jsx",
12
+ ".mts",
13
+ ".cts",
14
+ ".mjs",
15
+ ".cjs",
16
+ ]);
17
+ const DEFAULT_IGNORED_DIRECTORIES = new Set([
18
+ ".git",
19
+ "node_modules",
20
+ "dist",
21
+ "coverage",
22
+ ]);
23
+ const URI_SCHEME = /^[a-zA-Z][a-zA-Z\d+.-]*:/;
24
+ await init();
25
+ /**
26
+ * Remove .ts, .tsx, .js, and .jsx suffixes from eligible module specifiers
27
+ * while preserving every other part of the source text.
28
+ */
29
+ export function stripImportExtensions(source, options = {}) {
30
+ return transformSource(source, options).code;
31
+ }
32
+ /**
33
+ * Inspect or rewrite one source file.
34
+ *
35
+ * Preview is the default. Pass write: true to persist changes. The check and
36
+ * write options are mutually exclusive.
37
+ */
38
+ export async function processFile(filePath, options = {}) {
39
+ if (options.check && options.write) {
40
+ throw new TypeError("check and write cannot both be enabled");
41
+ }
42
+ const absolutePath = resolve(filePath);
43
+ const source = await readFile(absolutePath, "utf8");
44
+ const transformed = transformSource(source, options);
45
+ const changed = transformed.code !== source;
46
+ const written = changed && options.write === true;
47
+ if (written) {
48
+ await atomicWrite(absolutePath, transformed.code);
49
+ }
50
+ return {
51
+ path: absolutePath,
52
+ changed,
53
+ replacements: transformed.replacements,
54
+ written,
55
+ };
56
+ }
57
+ /**
58
+ * Find supported JavaScript and TypeScript source files below a file or
59
+ * directory. Directory symlinks are intentionally not followed.
60
+ */
61
+ export async function findSourceFiles(inputPath, options = {}) {
62
+ const absolutePath = resolve(inputPath);
63
+ const baseDirectory = resolve(options.baseDirectory ?? process.cwd());
64
+ const ignoredDirectories = new Set([
65
+ ...DEFAULT_IGNORED_DIRECTORIES,
66
+ ...(options.ignoredDirectories ?? []),
67
+ ].map((name) => name.toLowerCase()));
68
+ const ignorePatterns = options.ignorePatterns ?? [];
69
+ const sourceExtensions = normalizeExtensions(options.extensions ?? DEFAULT_SOURCE_EXTENSIONS);
70
+ const files = [];
71
+ await collectSourceFiles(absolutePath, baseDirectory, ignoredDirectories, ignorePatterns, sourceExtensions, files);
72
+ return files.sort((left, right) => left.localeCompare(right));
73
+ }
74
+ function transformSource(source, options) {
75
+ if (typeof source !== "string")
76
+ throw new TypeError("source must be a string");
77
+ const [imports] = parse(source);
78
+ const edits = [];
79
+ for (const imported of imports) {
80
+ const specifier = imported.specifier;
81
+ if (typeof specifier !== "string" || specifier.length === 0)
82
+ continue;
83
+ if (imported.type === "dynamic" && imported.glob)
84
+ continue;
85
+ if (!REMOVABLE_EXTENSION.test(specifier))
86
+ continue;
87
+ if (!shouldTransformSpecifier(specifier, options))
88
+ continue;
89
+ const rawRange = source.slice(imported.start, imported.end);
90
+ const offset = rawRange.indexOf(specifier);
91
+ if (offset < 0)
92
+ continue;
93
+ const start = imported.start + offset;
94
+ edits.push({
95
+ start,
96
+ end: start + specifier.length,
97
+ value: specifier.replace(REMOVABLE_EXTENSION, ""),
98
+ });
99
+ }
100
+ return {
101
+ code: applyEdits(source, edits),
102
+ replacements: edits.length,
103
+ };
104
+ }
105
+ function shouldTransformSpecifier(specifier, options) {
106
+ if (isRelativeSpecifier(specifier) ||
107
+ isProjectAliasSpecifier(specifier)) {
108
+ return true;
109
+ }
110
+ return (options.includePackageSubpaths === true && isPackageSubpath(specifier));
111
+ }
112
+ function isRelativeSpecifier(specifier) {
113
+ return specifier.startsWith("./") || specifier.startsWith("../");
114
+ }
115
+ function isProjectAliasSpecifier(specifier) {
116
+ return specifier.startsWith("@/") || specifier.startsWith("~/");
117
+ }
118
+ function isPackageSubpath(specifier) {
119
+ if (specifier.startsWith("/") ||
120
+ specifier.charCodeAt(0) === 92 ||
121
+ specifier.startsWith("#") ||
122
+ URI_SCHEME.test(specifier)) {
123
+ return false;
124
+ }
125
+ const path = specifier.split(/[?#]/, 1)[0];
126
+ if (!path)
127
+ return false;
128
+ const segments = path.split("/");
129
+ return path.startsWith("@") ? segments.length >= 3 : segments.length >= 2;
130
+ }
131
+ function applyEdits(source, edits) {
132
+ let output = source;
133
+ for (const edit of edits.sort((left, right) => right.start - left.start)) {
134
+ output =
135
+ output.slice(0, edit.start) + edit.value + output.slice(edit.end);
136
+ }
137
+ return output;
138
+ }
139
+ async function collectSourceFiles(currentPath, baseDirectory, ignoredDirectories, ignorePatterns, sourceExtensions, files) {
140
+ const metadata = await lstat(currentPath);
141
+ if (metadata.isSymbolicLink())
142
+ return;
143
+ if (isPathIgnored(currentPath, metadata.isDirectory(), baseDirectory, ignorePatterns)) {
144
+ return;
145
+ }
146
+ if (metadata.isFile()) {
147
+ if (sourceExtensions.has(extname(currentPath).toLowerCase())) {
148
+ files.push(currentPath);
149
+ }
150
+ return;
151
+ }
152
+ if (!metadata.isDirectory())
153
+ return;
154
+ const entries = await readdir(currentPath, { withFileTypes: true });
155
+ entries.sort((left, right) => left.name.localeCompare(right.name));
156
+ for (const entry of entries) {
157
+ if (entry.isDirectory() &&
158
+ ignoredDirectories.has(entry.name.toLowerCase())) {
159
+ continue;
160
+ }
161
+ if (entry.isSymbolicLink())
162
+ continue;
163
+ await collectSourceFiles(join(currentPath, entry.name), baseDirectory, ignoredDirectories, ignorePatterns, sourceExtensions, files);
164
+ }
165
+ }
166
+ function normalizeExtensions(extensions) {
167
+ if (extensions.length === 0) {
168
+ throw new TypeError("at least one source extension is required");
169
+ }
170
+ const normalized = new Set();
171
+ for (const extension of extensions) {
172
+ const value = extension.trim().toLowerCase();
173
+ const withoutDot = value.startsWith(".") ? value.slice(1) : value;
174
+ if (!/^[a-z0-9]+$/i.test(withoutDot)) {
175
+ throw new TypeError("invalid source extension: " + extension);
176
+ }
177
+ normalized.add("." + withoutDot);
178
+ }
179
+ return normalized;
180
+ }
181
+ /**
182
+ * Test a path against the same cwd-relative glob rules used by file discovery.
183
+ */
184
+ export function isPathIgnored(filePath, isDirectory, baseDirectory, patterns) {
185
+ if (patterns.length === 0)
186
+ return false;
187
+ const relativePath = relative(baseDirectory, filePath)
188
+ .split("\\")
189
+ .join("/");
190
+ const candidate = relativePath.length === 0 ? "." : relativePath;
191
+ const candidates = isDirectory ? [candidate, candidate + "/"] : [candidate];
192
+ return patterns.some((pattern) => {
193
+ if (pattern.length === 0) {
194
+ throw new TypeError("ignore patterns cannot be empty");
195
+ }
196
+ return candidates.some((path) => minimatch(path, pattern, {
197
+ dot: true,
198
+ nocase: process.platform === "win32",
199
+ nonegate: true,
200
+ }));
201
+ });
202
+ }
203
+ async function atomicWrite(filePath, contents) {
204
+ const metadata = await lstat(filePath);
205
+ const temporaryPath = join(dirname(filePath), "." +
206
+ basename(filePath) +
207
+ "." +
208
+ process.pid +
209
+ "." +
210
+ randomUUID() +
211
+ ".tmp");
212
+ try {
213
+ await writeFile(temporaryPath, contents, {
214
+ encoding: "utf8",
215
+ mode: metadata.mode,
216
+ });
217
+ await chmod(temporaryPath, metadata.mode);
218
+ await rename(temporaryPath, filePath);
219
+ }
220
+ catch (error) {
221
+ await unlink(temporaryPath).catch(() => undefined);
222
+ throw error;
223
+ }
224
+ }
225
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import {randomUUID} from \"node:crypto\";\nimport {\n chmod,\n lstat,\n readFile,\n readdir,\n rename,\n unlink,\n writeFile,\n} from \"node:fs/promises\";\nimport {\n basename,\n dirname,\n extname,\n join,\n relative,\n resolve,\n} from \"node:path\";\nimport {init, parse} from \"es-module-lexer\";\nimport {minimatch} from \"minimatch\";\n\nimport type {\n FindFilesOptions,\n ProcessFileOptions,\n ProcessResult,\n StripOptions,\n} from \"./types.js\";\n\nexport type {\n FindFilesOptions,\n ProcessFileOptions,\n ProcessResult,\n StripOptions,\n} from \"./types.js\";\n\nconst REMOVABLE_EXTENSION = /\\.(?:tsx?|jsx?)(?=[?#]|$)/i;\nexport const DEFAULT_SOURCE_EXTENSIONS: readonly string[] = Object.freeze([\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".mts\",\n \".cts\",\n \".mjs\",\n \".cjs\",\n]);\nconst DEFAULT_IGNORED_DIRECTORIES = new Set([\n \".git\",\n \"node_modules\",\n \"dist\",\n \"coverage\",\n]);\nconst URI_SCHEME = /^[a-zA-Z][a-zA-Z\\d+.-]*:/;\n\nawait init();\n\ninterface TextEdit {\n start: number;\n end: number;\n value: string;\n}\n\ninterface TransformResult {\n code: string;\n replacements: number;\n}\n\n/**\n * Remove .ts, .tsx, .js, and .jsx suffixes from eligible module specifiers\n * while preserving every other part of the source text.\n */\nexport function stripImportExtensions(\n source: string,\n options: StripOptions = {},\n): string {\n return transformSource(source, options).code;\n}\n\n/**\n * Inspect or rewrite one source file.\n *\n * Preview is the default. Pass write: true to persist changes. The check and\n * write options are mutually exclusive.\n */\nexport async function processFile(\n filePath: string,\n options: ProcessFileOptions = {},\n): Promise<ProcessResult> {\n if (options.check && options.write) {\n throw new TypeError(\"check and write cannot both be enabled\");\n }\n\n const absolutePath = resolve(filePath);\n const source = await readFile(absolutePath, \"utf8\");\n const transformed = transformSource(source, options);\n const changed = transformed.code !== source;\n const written = changed && options.write === true;\n\n if (written) {\n await atomicWrite(absolutePath, transformed.code);\n }\n\n return {\n path: absolutePath,\n changed,\n replacements: transformed.replacements,\n written,\n };\n}\n\n/**\n * Find supported JavaScript and TypeScript source files below a file or\n * directory. Directory symlinks are intentionally not followed.\n */\nexport async function findSourceFiles(\n inputPath: string,\n options: FindFilesOptions = {},\n): Promise<string[]> {\n const absolutePath = resolve(inputPath);\n const baseDirectory = resolve(options.baseDirectory ?? process.cwd());\n const ignoredDirectories = new Set(\n [\n ...DEFAULT_IGNORED_DIRECTORIES,\n ...(options.ignoredDirectories ?? []),\n ].map((name) => name.toLowerCase()),\n );\n const ignorePatterns = options.ignorePatterns ?? [];\n const sourceExtensions = normalizeExtensions(\n options.extensions ?? DEFAULT_SOURCE_EXTENSIONS,\n );\n const files: string[] = [];\n\n await collectSourceFiles(\n absolutePath,\n baseDirectory,\n ignoredDirectories,\n ignorePatterns,\n sourceExtensions,\n files,\n );\n return files.sort((left, right) => left.localeCompare(right));\n}\n\nfunction transformSource(\n source: string,\n options: StripOptions,\n): TransformResult {\n if (typeof source !== \"string\")\n throw new TypeError(\"source must be a string\");\n\n const [imports] = parse(source);\n const edits: TextEdit[] = [];\n\n for (const imported of imports) {\n const specifier = imported.specifier;\n if (typeof specifier !== \"string\" || specifier.length === 0) continue;\n if (imported.type === \"dynamic\" && imported.glob) continue;\n if (!REMOVABLE_EXTENSION.test(specifier)) continue;\n if (!shouldTransformSpecifier(specifier, options)) continue;\n\n const rawRange = source.slice(imported.start, imported.end);\n const offset = rawRange.indexOf(specifier);\n if (offset < 0) continue;\n\n const start = imported.start + offset;\n edits.push({\n start,\n end: start + specifier.length,\n value: specifier.replace(REMOVABLE_EXTENSION, \"\"),\n });\n }\n\n return {\n code: applyEdits(source, edits),\n replacements: edits.length,\n };\n}\n\nfunction shouldTransformSpecifier(\n specifier: string,\n options: StripOptions,\n): boolean {\n if (\n isRelativeSpecifier(specifier) ||\n isProjectAliasSpecifier(specifier)\n ) {\n return true;\n }\n\n return (\n options.includePackageSubpaths === true && isPackageSubpath(specifier)\n );\n}\n\nfunction isRelativeSpecifier(specifier: string): boolean {\n return specifier.startsWith(\"./\") || specifier.startsWith(\"../\");\n}\n\nfunction isProjectAliasSpecifier(specifier: string): boolean {\n return specifier.startsWith(\"@/\") || specifier.startsWith(\"~/\");\n}\n\nfunction isPackageSubpath(specifier: string): boolean {\n if (\n specifier.startsWith(\"/\") ||\n specifier.charCodeAt(0) === 92 ||\n specifier.startsWith(\"#\") ||\n URI_SCHEME.test(specifier)\n ) {\n return false;\n }\n\n const path = specifier.split(/[?#]/, 1)[0];\n if (!path) return false;\n\n const segments = path.split(\"/\");\n return path.startsWith(\"@\") ? segments.length >= 3 : segments.length >= 2;\n}\n\nfunction applyEdits(source: string, edits: TextEdit[]): string {\n let output = source;\n\n for (const edit of edits.sort((left, right) => right.start - left.start)) {\n output =\n output.slice(0, edit.start) + edit.value + output.slice(edit.end);\n }\n\n return output;\n}\n\nasync function collectSourceFiles(\n currentPath: string,\n baseDirectory: string,\n ignoredDirectories: ReadonlySet<string>,\n ignorePatterns: readonly string[],\n sourceExtensions: ReadonlySet<string>,\n files: string[],\n): Promise<void> {\n const metadata = await lstat(currentPath);\n if (metadata.isSymbolicLink()) return;\n if (\n isPathIgnored(\n currentPath,\n metadata.isDirectory(),\n baseDirectory,\n ignorePatterns,\n )\n ) {\n return;\n }\n\n if (metadata.isFile()) {\n if (sourceExtensions.has(extname(currentPath).toLowerCase())) {\n files.push(currentPath);\n }\n return;\n }\n\n if (!metadata.isDirectory()) return;\n\n const entries = await readdir(currentPath, {withFileTypes: true});\n entries.sort((left, right) => left.name.localeCompare(right.name));\n\n for (const entry of entries) {\n if (\n entry.isDirectory() &&\n ignoredDirectories.has(entry.name.toLowerCase())\n ) {\n continue;\n }\n\n if (entry.isSymbolicLink()) continue;\n await collectSourceFiles(\n join(currentPath, entry.name),\n baseDirectory,\n ignoredDirectories,\n ignorePatterns,\n sourceExtensions,\n files,\n );\n }\n}\n\nfunction normalizeExtensions(extensions: readonly string[]): Set<string> {\n if (extensions.length === 0) {\n throw new TypeError(\"at least one source extension is required\");\n }\n\n const normalized = new Set<string>();\n\n for (const extension of extensions) {\n const value = extension.trim().toLowerCase();\n const withoutDot = value.startsWith(\".\") ? value.slice(1) : value;\n\n if (!/^[a-z0-9]+$/i.test(withoutDot)) {\n throw new TypeError(\"invalid source extension: \" + extension);\n }\n\n normalized.add(\".\" + withoutDot);\n }\n\n return normalized;\n}\n\n/**\n * Test a path against the same cwd-relative glob rules used by file discovery.\n */\nexport function isPathIgnored(\n filePath: string,\n isDirectory: boolean,\n baseDirectory: string,\n patterns: readonly string[],\n): boolean {\n if (patterns.length === 0) return false;\n\n const relativePath = relative(baseDirectory, filePath)\n .split(\"\\\\\")\n .join(\"/\");\n const candidate = relativePath.length === 0 ? \".\" : relativePath;\n const candidates = isDirectory ? [candidate, candidate + \"/\"] : [candidate];\n\n return patterns.some((pattern) => {\n if (pattern.length === 0) {\n throw new TypeError(\"ignore patterns cannot be empty\");\n }\n\n return candidates.some((path) =>\n minimatch(path, pattern, {\n dot: true,\n nocase: process.platform === \"win32\",\n nonegate: true,\n }),\n );\n });\n}\n\nasync function atomicWrite(filePath: string, contents: string): Promise<void> {\n const metadata = await lstat(filePath);\n const temporaryPath = join(\n dirname(filePath),\n \".\" +\n basename(filePath) +\n \".\" +\n process.pid +\n \".\" +\n randomUUID() +\n \".tmp\",\n );\n\n try {\n await writeFile(temporaryPath, contents, {\n encoding: \"utf8\",\n mode: metadata.mode,\n });\n await chmod(temporaryPath, metadata.mode);\n await rename(temporaryPath, filePath);\n } catch (error) {\n await unlink(temporaryPath).catch(() => undefined);\n throw error;\n }\n}\n"]}
@@ -0,0 +1,39 @@
1
+ export interface StripOptions {
2
+ /**
3
+ * Also rewrite package subpaths such as some-package/helper.js.
4
+ *
5
+ * Package subpaths are unchanged by default because their extensions can be
6
+ * part of the package's public API.
7
+ */
8
+ includePackageSubpaths?: boolean;
9
+ }
10
+ export interface ProcessFileOptions extends StripOptions {
11
+ /** Report changes without writing them. */
12
+ check?: boolean;
13
+ /** Persist transformed source code to disk. */
14
+ write?: boolean;
15
+ }
16
+ export interface ProcessResult {
17
+ path: string;
18
+ changed: boolean;
19
+ replacements: number;
20
+ written: boolean;
21
+ }
22
+ export interface FindFilesOptions {
23
+ /** Additional directory names to ignore during recursive discovery. */
24
+ ignoredDirectories?: readonly string[];
25
+ /**
26
+ * Glob patterns matched against forward-slash paths relative to
27
+ * baseDirectory.
28
+ */
29
+ ignorePatterns?: readonly string[];
30
+ /**
31
+ * Source file extensions to scan. Values may include or omit the leading
32
+ * dot. Defaults to the JavaScript and TypeScript extension set exported as
33
+ * DEFAULT_SOURCE_EXTENSIONS.
34
+ */
35
+ extensions?: readonly string[];
36
+ /** Base directory used to resolve ignore patterns. Defaults to cwd. */
37
+ baseDirectory?: string;
38
+ }
39
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IACzB;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACpD,2CAA2C;IAC3C,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC7B,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;OAGG;IACH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEnC;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAE/B,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export interface StripOptions {\n /**\n * Also rewrite package subpaths such as some-package/helper.js.\n *\n * Package subpaths are unchanged by default because their extensions can be\n * part of the package's public API.\n */\n includePackageSubpaths?: boolean;\n}\n\nexport interface ProcessFileOptions extends StripOptions {\n /** Report changes without writing them. */\n check?: boolean;\n\n /** Persist transformed source code to disk. */\n write?: boolean;\n}\n\nexport interface ProcessResult {\n path: string;\n changed: boolean;\n replacements: number;\n written: boolean;\n}\n\nexport interface FindFilesOptions {\n /** Additional directory names to ignore during recursive discovery. */\n ignoredDirectories?: readonly string[];\n\n /**\n * Glob patterns matched against forward-slash paths relative to\n * baseDirectory.\n */\n ignorePatterns?: readonly string[];\n\n /**\n * Source file extensions to scan. Values may include or omit the leading\n * dot. Defaults to the JavaScript and TypeScript extension set exported as\n * DEFAULT_SOURCE_EXTENSIONS.\n */\n extensions?: readonly string[];\n\n /** Base directory used to resolve ignore patterns. Defaults to cwd. */\n baseDirectory?: string;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "extensionless-imports",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Remove JavaScript and TypeScript extensions from module specifiers.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "bin": {
15
+ "extensionless-imports": "./dist/cli.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
24
+ "build": "npm run clean && tsc -p tsconfig.json",
25
+ "typecheck": "tsc --noEmit -p tsconfig.test.json",
26
+ "test": "npm run build && vitest run",
27
+ "check": "npm run typecheck && npm test && publint",
28
+ "prepublishOnly": "npm run check"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/mirmaxsudov/extensionless-imports.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/mirmaxsudov/extensionless-imports/issues"
36
+ },
37
+ "homepage": "https://github.com/mirmaxsudov/extensionless-imports#readme",
38
+ "keywords": [
39
+ "imports",
40
+ "typescript",
41
+ "javascript",
42
+ "codemod",
43
+ "cli"
44
+ ],
45
+ "license": "MIT",
46
+ "engines": {
47
+ "node": ">=20.19.0"
48
+ },
49
+ "dependencies": {
50
+ "chokidar": "^5.0.0",
51
+ "es-module-lexer": "^3.0.2",
52
+ "minimatch": "^10.2.6"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^26.6.3",
56
+ "publint": "^0.3.24",
57
+ "typescript": "^7.0.2",
58
+ "vitest": "^5.0.3"
59
+ }
60
+ }