Skip to content

Commit 0d17843

Browse files
authored
Fix bun run folder (#15117)
Co-authored-by: pfgithub <[email protected]>
1 parent 288f256 commit 0d17843

File tree

24 files changed

+762
-541
lines changed

24 files changed

+762
-541
lines changed

docs/api/transpiler.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ Each import in the `imports` array has a `path` and `kind`. Bun categories impor
137137
- `import-rule`: `@import 'foo.css'`
138138
- `url-token`: `url('./foo.png')`
139139
<!-- - `internal`: `import {foo} from 'bun:internal'`
140-
- `entry-point`: `import {foo} from 'bun:entry'` -->
140+
- `entry-point-build`: `import {foo} from 'bun:entry'`
141+
- `entry-point-run`: `bun ./mymodule` -->
141142

142143
## `.scanImports()`
143144

@@ -267,7 +268,8 @@ type Import = {
267268
// The import was injected by Bun
268269
| "internal" 
269270
// Entry point (not common)
270-
| "entry-point"
271+
| "entry-point-build"
272+
| "entry-point-run"
271273
}
272274

273275
const transpiler = new Bun.Transpiler({ loader: "jsx" });

docs/bundler/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,8 @@ export type BuildManifest = {
533533
};
534534
535535
export type ImportKind =
536-
| "entry-point"
536+
| "entry-point-build"
537+
| "entry-point-run"
537538
| "import-statement"
538539
| "require-call"
539540
| "dynamic-import"

docs/cli/run.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,13 @@ $ bun run clean
106106
Done.
107107
```
108108

109-
Bun executes the script command in a subshell. It checks for the following shells in order, using the first one it finds: `bash`, `sh`, `zsh`.
109+
Bun executes the script command in a subshell. On Linux & macOS, it checks for the following shells in order, using the first one it finds: `bash`, `sh`, `zsh`. On windows, it uses [bun shell](https://bun.sh/docs/runtime/shell) to support bash-like syntax and many common commands.
110110

111111
{% callout %}
112112
⚡️ The startup time for `npm run` on Linux is roughly 170ms; with Bun it is `6ms`.
113113
{% /callout %}
114114

115-
If there is a name conflict between a `package.json` script and a built-in `bun` command (`install`, `dev`, `upgrade`, etc.) Bun's built-in command takes precedence. In this case, use the more explicit `bun run` command to execute your package script.
115+
Scripts can also be run with the shorter command `bun <script>`, however if there is a built-in bun command with the same name, the built-in command takes precedence. In this case, use the more explicit `bun run <script>` command to execute your package script.
116116

117117
```bash
118118
$ bun run dev
@@ -194,3 +194,14 @@ $ bun --smol run index.tsx
194194
```
195195

196196
This causes the garbage collector to run more frequently, which can slow down execution. However, it can be useful in environments with limited memory. Bun automatically adjusts the garbage collector's heap size based on the available memory (accounting for cgroups and other memory limits) with and without the `--smol` flag, so this is mostly useful for cases where you want to make the heap size grow more slowly.
197+
198+
## Resolution order
199+
200+
Absolute paths and paths starting with `./` or `.\\` are always executed as source files. Unless using `bun run`, running a file with an allowed extension will prefer the file over a package.json script.
201+
202+
When there is a package.json script and a file with the same name, `bun run` prioritizes the package.json script. The full resolution order is:
203+
204+
1. package.json scripts, eg `bun run build`
205+
2. Source files, eg `bun run src/main.js`
206+
3. Binaries from project packages, eg `bun add eslint && bun run eslint`
207+
4. (`bun run` only) System commands, eg `bun run ls`

packages/bun-polyfills/src/modules/bun/transpiler.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import $ from 'chalk';
66
await init();
77

88
enum InternalImportKind {
9-
'entry-point' = 1, // entry_point
10-
'import-statement' = 2, // stmt
11-
'require-call' = 3, // require
12-
'dynamic-import' = 4, // dynamic
13-
'require-resolve' = 5, // require_resolve
14-
'import-rule' = 6, // at
15-
'url-token' = 7, // url
16-
'internal' = 8, // internal
9+
'entry-point-run' = 1, // entry_point_run
10+
'entry-point-build' = 2, // entry_point_build
11+
'import-statement' = 3, // stmt
12+
'require-call' = 4, // require
13+
'dynamic-import' = 5, // dynamic
14+
'require-resolve' = 6, // require_resolve
15+
'import-rule' = 7, // at
16+
'url-token' = 8, // url
17+
'internal' = 9, // internal
1718
}
1819

1920
export type ScanImportsEntry = {

packages/bun-types/bun.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2278,7 +2278,8 @@ declare module "bun" {
22782278
| "import-rule"
22792279
| "url-token"
22802280
| "internal"
2281-
| "entry-point";
2281+
| "entry-point-run"
2282+
| "entry-point-build";
22822283

22832284
interface Import {
22842285
path: string;

src/bake/production.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
109109
unresolved_config_entry_point = try std.fmt.allocPrint(ctx.allocator, "./{s}", .{unresolved_config_entry_point});
110110
}
111111

112-
const config_entry_point = b.resolver.resolve(cwd, unresolved_config_entry_point, .entry_point) catch |err| {
112+
const config_entry_point = b.resolver.resolve(cwd, unresolved_config_entry_point, .entry_point_build) catch |err| {
113113
if (err == error.ModuleNotFound) {
114114
if (ctx.args.entry_points.len == 0) {
115115
// Onboarding message

src/bun.js/bindings/bindings.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3266,7 +3266,7 @@ void JSC__JSPromise__resolve(JSC__JSPromise* arg0, JSC__JSGlobalObject* arg1,
32663266
ASSERT_WITH_MESSAGE(arg0->inherits<JSC::JSPromise>(), "Argument is not a promise");
32673267
ASSERT_WITH_MESSAGE(arg0->status(arg0->vm()) == JSC::JSPromise::Status::Pending, "Promise is already resolved or rejected");
32683268
ASSERT(!target.isEmpty());
3269-
ASSERT_WITH_MESSAGE(arg0 != target, "Promise cannot be resoled to itself");
3269+
ASSERT_WITH_MESSAGE(arg0 != target, "Promise cannot be resolved to itself");
32703270

32713271
// Note: the Promise can be another promise. Since we go through the generic promise resolve codepath.
32723272
arg0->resolve(arg1, JSC::JSValue::decode(JSValue2));

src/bundler/bundle_v2.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2029,7 +2029,7 @@ pub const BundleV2 = struct {
20292029
// When it's not a file, this is an error and we should report it.
20302030
//
20312031
// We have no way of loading non-files.
2032-
if (resolve.import_record.kind == .entry_point or resolve.import_record.importer_source_index == null) {
2032+
if (resolve.import_record.kind == .entry_point_build or resolve.import_record.importer_source_index == null) {
20332033
log.addErrorFmt(null, Logger.Loc.Empty, bun.default_allocator, "Module not found {} in namespace {}", .{
20342034
bun.fmt.quote(resolve.import_record.specifier),
20352035
bun.fmt.quote(resolve.import_record.namespace),

src/cli.zig

Lines changed: 5 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,8 +1436,6 @@ pub var pretend_to_be_node = false;
14361436
pub var is_bunx_exe = false;
14371437

14381438
pub const Command = struct {
1439-
var script_name_buf: bun.PathBuffer = undefined;
1440-
14411439
pub fn get() Context {
14421440
return global_cli_ctx;
14431441
}
@@ -2148,6 +2146,7 @@ pub const Command = struct {
21482146
.RunCommand => {
21492147
if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .RunCommand) unreachable;
21502148
const ctx = try Command.init(allocator, log, .RunCommand);
2149+
ctx.args.target = .bun;
21512150

21522151
if (ctx.filters.len > 0) {
21532152
FilterRun.runScriptsWithFilter(ctx) catch |err| {
@@ -2157,7 +2156,7 @@ pub const Command = struct {
21572156
}
21582157

21592158
if (ctx.positionals.len > 0) {
2160-
if (try RunCommand.exec(ctx, false, true, false)) {
2159+
if (try RunCommand.exec(ctx, .{ .bin_dirs_only = false, .log_errors = true, .allow_fast_run_for_extensions = false })) {
21612160
return;
21622161
}
21632162

@@ -2190,6 +2189,7 @@ pub const Command = struct {
21902189
},
21912190
}
21922191
};
2192+
ctx.args.target = .bun;
21932193

21942194
if (ctx.filters.len > 0) {
21952195
FilterRun.runScriptsWithFilter(ctx) catch |err| {
@@ -2240,91 +2240,16 @@ pub const Command = struct {
22402240
}
22412241
}
22422242

2243-
var was_js_like = false;
2244-
// If we start bun with:
2245-
// 1. `bun foo.js`, assume it's a JavaScript file.
2246-
// 2. `bun /absolute/path/to/bin/foo` assume its a JavaScript file.
2247-
// ^ no file extension
2248-
//
2249-
// #!/usr/bin/env bun
2250-
// will pass us an absolute path to the script.
2251-
// This means a non-standard file extension will not work, but that is better than the current state
2252-
// which is file extension-less doesn't work
2253-
const default_loader = options.defaultLoaders.get(extension) orelse brk: {
2254-
if (extension.len == 0 and ctx.args.entry_points.len > 0 and ctx.args.entry_points[0].len > 0 and std.fs.path.isAbsolute(ctx.args.entry_points[0])) {
2255-
break :brk options.Loader.js;
2256-
}
2257-
2258-
if (extension.len > 0) {
2259-
if (strings.endsWithComptime(ctx.args.entry_points[0], ".sh")) {
2260-
break :brk options.Loader.bunsh;
2261-
}
2262-
2263-
if (!ctx.debug.loaded_bunfig) {
2264-
try bun.CLI.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", ctx, .RunCommand);
2265-
}
2266-
2267-
if (ctx.preloads.len > 0)
2268-
break :brk options.Loader.js;
2269-
}
2270-
2271-
break :brk null;
2272-
};
2273-
2274-
const force_using_bun = ctx.debug.run_in_bun;
2275-
var did_check = false;
2276-
if (default_loader) |loader| {
2277-
if (loader.canBeRunByBun()) {
2278-
was_js_like = true;
2279-
if (maybeOpenWithBunJS(ctx)) {
2280-
return;
2281-
}
2282-
did_check = true;
2283-
}
2284-
}
2285-
2286-
if (force_using_bun and !did_check) {
2287-
if (maybeOpenWithBunJS(ctx)) {
2288-
return;
2289-
}
2290-
}
2291-
2292-
if (ctx.positionals.len > 0 and extension.len == 0) {
2243+
if (ctx.positionals.len > 0) {
22932244
if (ctx.filters.len > 0) {
22942245
Output.prettyln("<r><yellow>warn<r>: Filters are ignored for auto command", .{});
22952246
}
2296-
if (try RunCommand.exec(ctx, true, false, true)) {
2247+
if (try RunCommand.exec(ctx, .{ .bin_dirs_only = true, .log_errors = !ctx.runtime_options.if_present, .allow_fast_run_for_extensions = true })) {
22972248
return;
22982249
}
2299-
2300-
Output.prettyErrorln("<r><red>error<r><d>:<r> <b>Script not found \"{s}\"<r>", .{
2301-
ctx.positionals[0],
2302-
});
2303-
2304-
Global.exit(1);
2305-
}
2306-
2307-
if (ctx.runtime_options.if_present) {
23082250
return;
23092251
}
23102252

2311-
if (was_js_like) {
2312-
Output.prettyErrorln("<r><red>error<r><d>:<r> <b>Module not found \"{s}\"<r>", .{
2313-
ctx.positionals[0],
2314-
});
2315-
Global.exit(1);
2316-
} else if (ctx.positionals.len > 0) {
2317-
Output.prettyErrorln("<r><red>error<r><d>:<r> <b>File not found: \"{s}\"<r>", .{
2318-
ctx.positionals[0],
2319-
});
2320-
Global.exit(1);
2321-
}
2322-
2323-
// if we get here, the command was not parsed
2324-
// or the user just ran `bun` with no arguments
2325-
if (ctx.positionals.len > 0) {
2326-
Output.warn("failed to parse command\n", .{});
2327-
}
23282253
Output.flush();
23292254
try HelpCommand.exec(allocator);
23302255
},
@@ -2338,94 +2263,6 @@ pub const Command = struct {
23382263
}
23392264
}
23402265

2341-
fn maybeOpenWithBunJS(ctx: Command.Context) bool {
2342-
if (ctx.args.entry_points.len == 0)
2343-
return false;
2344-
2345-
const script_name_to_search = ctx.args.entry_points[0];
2346-
2347-
var absolute_script_path: ?string = null;
2348-
2349-
// TODO: optimize this pass for Windows. we can make better use of system apis available
2350-
var file_path = script_name_to_search;
2351-
{
2352-
const file = bun.toLibUVOwnedFD(((brk: {
2353-
if (std.fs.path.isAbsolute(script_name_to_search)) {
2354-
var win_resolver = resolve_path.PosixToWinNormalizer{};
2355-
var resolved = win_resolver.resolveCWD(script_name_to_search) catch @panic("Could not resolve path");
2356-
if (comptime Environment.isWindows) {
2357-
resolved = resolve_path.normalizeString(resolved, false, .windows);
2358-
}
2359-
break :brk bun.openFile(
2360-
resolved,
2361-
.{ .mode = .read_only },
2362-
);
2363-
} else if (!strings.hasPrefix(script_name_to_search, "..") and script_name_to_search[0] != '~') {
2364-
const file_pathZ = brk2: {
2365-
@memcpy(script_name_buf[0..file_path.len], file_path);
2366-
script_name_buf[file_path.len] = 0;
2367-
break :brk2 script_name_buf[0..file_path.len :0];
2368-
};
2369-
2370-
break :brk bun.openFileZ(file_pathZ, .{ .mode = .read_only });
2371-
} else {
2372-
var path_buf: bun.PathBuffer = undefined;
2373-
const cwd = bun.getcwd(&path_buf) catch return false;
2374-
path_buf[cwd.len] = std.fs.path.sep;
2375-
var parts = [_]string{script_name_to_search};
2376-
file_path = resolve_path.joinAbsStringBuf(
2377-
path_buf[0 .. cwd.len + 1],
2378-
&script_name_buf,
2379-
&parts,
2380-
.auto,
2381-
);
2382-
if (file_path.len == 0) return false;
2383-
script_name_buf[file_path.len] = 0;
2384-
const file_pathZ = script_name_buf[0..file_path.len :0];
2385-
break :brk bun.openFileZ(file_pathZ, .{ .mode = .read_only });
2386-
}
2387-
}) catch return false).handle) catch return false;
2388-
defer _ = bun.sys.close(file);
2389-
2390-
switch (bun.sys.fstat(file)) {
2391-
.result => |stat| {
2392-
// directories cannot be run. if only there was a faster way to check this
2393-
if (bun.S.ISDIR(@intCast(stat.mode))) return false;
2394-
},
2395-
.err => return false,
2396-
}
2397-
2398-
Global.configureAllocator(.{ .long_running = true });
2399-
2400-
absolute_script_path = brk: {
2401-
if (comptime !Environment.isWindows) break :brk bun.getFdPath(file, &script_name_buf) catch return false;
2402-
2403-
var fd_path_buf: bun.PathBuffer = undefined;
2404-
break :brk bun.getFdPath(file, &fd_path_buf) catch return false;
2405-
};
2406-
}
2407-
2408-
if (!ctx.debug.loaded_bunfig) {
2409-
bun.CLI.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", ctx, .RunCommand) catch {};
2410-
}
2411-
2412-
BunJS.Run.boot(
2413-
ctx,
2414-
absolute_script_path.?,
2415-
) catch |err| {
2416-
bun.handleErrorReturnTrace(err, @errorReturnTrace());
2417-
2418-
ctx.log.print(Output.errorWriter()) catch {};
2419-
2420-
Output.prettyErrorln("<r><red>error<r>: Failed to run <b>{s}<r> due to error <b>{s}<r>", .{
2421-
std.fs.path.basename(file_path),
2422-
@errorName(err),
2423-
});
2424-
Global.exit(1);
2425-
};
2426-
return true;
2427-
}
2428-
24292266
pub const Tag = enum {
24302267
AddCommand,
24312268
AutoCommand,

src/cli/exec_command.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub const ExecCommand = struct {
3939
};
4040
const script_path = bun.path.join(parts, .auto);
4141

42-
const code = bun.shell.Interpreter.initAndRunFromSource(ctx, mini, script_path, script) catch |err| {
42+
const code = bun.shell.Interpreter.initAndRunFromSource(ctx, mini, script_path, script, null) catch |err| {
4343
Output.err(err, "failed to run script <b>{s}<r>", .{script_path});
4444
Global.exit(1);
4545
};

0 commit comments

Comments
 (0)