Skip to content

Commit

Permalink
support bun install --filter <pattern> (oven-sh#16093)
Browse files Browse the repository at this point in the history
  • Loading branch information
dylan-conway authored and probably-neb committed Jan 7, 2025
1 parent 24f1dee commit 426ae53
Show file tree
Hide file tree
Showing 17 changed files with 561 additions and 63 deletions.
2 changes: 1 addition & 1 deletion src/bun.js/api/glob.zig
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ pub fn match(this: *Glob, globalThis: *JSGlobalObject, callframe: *JSC.CallFrame
var str = str_arg.toSlice(globalThis, arena.allocator());
defer str.deinit();

if (this.is_ascii and isAllAscii(str.slice())) return JSC.JSValue.jsBoolean(globImpl.Ascii.match(this.pattern, str.slice()));
if (this.is_ascii and isAllAscii(str.slice())) return JSC.JSValue.jsBoolean(globImpl.Ascii.match(this.pattern, str.slice()).matches());

const codepoints = codepoints: {
if (this.pattern_codepoints) |cp| break :codepoints cp.items[0..];
Expand Down
36 changes: 6 additions & 30 deletions src/cli/outdated_command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const FileSystem = bun.fs.FileSystem;
const path = bun.path;
const glob = bun.glob;
const Table = bun.fmt.Table;
const WorkspaceFilter = PackageManager.WorkspaceFilter;
const OOM = bun.OOM;

pub const OutdatedCommand = struct {
pub fn exec(ctx: Command.Context) !void {
Expand Down Expand Up @@ -138,7 +140,7 @@ pub const OutdatedCommand = struct {
original_cwd: string,
manager: *PackageManager,
filters: []const string,
) error{OutOfMemory}![]const PackageID {
) OOM![]const PackageID {
const lockfile = manager.lockfile;
const packages = lockfile.packages.slice();
const pkg_names = packages.items(.name);
Expand All @@ -152,36 +154,10 @@ pub const OutdatedCommand = struct {
}

const converted_filters = converted_filters: {
const buf = try allocator.alloc(FilterType, filters.len);
const buf = try allocator.alloc(WorkspaceFilter, filters.len);
var path_buf: bun.PathBuffer = undefined;
for (filters, buf) |filter, *converted| {
if ((filter.len == 1 and filter[0] == '*') or strings.eqlComptime(filter, "**")) {
converted.* = .all;
continue;
}

const is_path = filter.len > 0 and filter[0] == '.';

const joined_filter = if (is_path)
strings.withoutTrailingSlash(path.joinAbsString(original_cwd, &[_]string{filter}, .posix))
else
filter;

if (joined_filter.len == 0) {
converted.* = FilterType.init(&.{}, is_path);
continue;
}

const length = bun.simdutf.length.utf32.from.utf8.le(joined_filter);
const convert_buf = try allocator.alloc(u32, length);

const convert_result = bun.simdutf.convert.utf8.to.utf32.with_errors.le(joined_filter, convert_buf);
if (!convert_result.isSuccessful()) {
// nothing would match
converted.* = FilterType.init(&.{}, false);
continue;
}

converted.* = FilterType.init(convert_buf[0..convert_result.count], is_path);
converted.* = try WorkspaceFilter.init(allocator, filter, original_cwd, &path_buf);
}
break :converted_filters buf;
};
Expand Down
2 changes: 1 addition & 1 deletion src/glob/GlobWalker.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1358,7 +1358,7 @@ pub fn GlobWalker_(
return GlobAscii.match(
pattern_component.patternSlice(this.pattern),
filepath,
);
).matches();
}
const codepoints = this.componentStringUnicode(pattern_component);
return matchImpl(
Expand Down
32 changes: 22 additions & 10 deletions src/glob/ascii.zig
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,18 @@ pub fn valid_glob_indices(glob: []const u8, indices: std.ArrayList(BraceIndex))
}
}

pub const MatchResult = enum {
no_match,
match,

negate_no_match,
negate_match,

pub fn matches(this: MatchResult) bool {
return this == .match or this == .negate_match;
}
};

/// This function checks returns a boolean value if the pathname `path` matches
/// the pattern `glob`.
///
Expand Down Expand Up @@ -208,7 +220,7 @@ pub fn valid_glob_indices(glob: []const u8, indices: std.ArrayList(BraceIndex))
/// Multiple "!" characters negate the pattern multiple times.
/// "\"
/// Used to escape any of the special characters above.
pub fn match(glob: []const u8, path: []const u8) bool {
pub fn match(glob: []const u8, path: []const u8) MatchResult {
// This algorithm is based on https://research.swtch.com/glob
var state = State{};
// Store the state when we see an opening '{' brace in a stack.
Expand Down Expand Up @@ -290,7 +302,7 @@ pub fn match(glob: []const u8, path: []const u8) bool {
(glob[state.glob_index] == ',' or glob[state.glob_index] == '}'))
{
if (state.skipBraces(glob, false) == .Invalid)
return false; // invalid pattern!
return .no_match; // invalid pattern!
}

continue;
Expand Down Expand Up @@ -321,7 +333,7 @@ pub fn match(glob: []const u8, path: []const u8) bool {
while (state.glob_index < glob.len and (first or glob[state.glob_index] != ']')) {
var low = glob[state.glob_index];
if (!unescape(&low, glob, &state.glob_index))
return false; // Invalid pattern
return .no_match; // Invalid pattern
state.glob_index += 1;

// If there is a - and the following character is not ],
Expand All @@ -332,7 +344,7 @@ pub fn match(glob: []const u8, path: []const u8) bool {
state.glob_index += 1;
var h = glob[state.glob_index];
if (!unescape(&h, glob, &state.glob_index))
return false; // Invalid pattern!
return .no_match; // Invalid pattern!
state.glob_index += 1;
break :blk h;
} else low;
Expand All @@ -342,7 +354,7 @@ pub fn match(glob: []const u8, path: []const u8) bool {
first = false;
}
if (state.glob_index >= glob.len)
return false; // Invalid pattern!
return .no_match; // Invalid pattern!
state.glob_index += 1;
if (is_match != class_negated) {
state.path_index += 1;
Expand All @@ -351,7 +363,7 @@ pub fn match(glob: []const u8, path: []const u8) bool {
},
'{' => if (state.path_index < path.len) {
if (brace_stack.len >= brace_stack.stack.len)
return false; // Invalid pattern! Too many nested braces.
return .no_match; // Invalid pattern! Too many nested braces.

// Push old state to the stack, and reset current state.
state = brace_stack.push(&state);
Expand Down Expand Up @@ -380,7 +392,7 @@ pub fn match(glob: []const u8, path: []const u8) bool {
var cc = c;
// Match escaped characters as literals.
if (!unescape(&cc, glob, &state.glob_index))
return false; // Invalid pattern;
return .no_match; // Invalid pattern;

const is_match = if (cc == '/')
isSeparator(path[state.path_index])
Expand Down Expand Up @@ -416,7 +428,7 @@ pub fn match(glob: []const u8, path: []const u8) bool {
if (brace_stack.len > 0) {
// If in braces, find next option and reset path to index where we saw the '{'
switch (state.skipBraces(glob, true)) {
.Invalid => return false,
.Invalid => return .no_match,
.Comma => {
state.path_index = brace_stack.last().path_index;
continue;
Expand All @@ -440,10 +452,10 @@ pub fn match(glob: []const u8, path: []const u8) bool {
}
}

return negated;
return if (negated) .negate_match else .no_match;
}

return !negated;
return if (!negated) .match else .negate_no_match;
}

inline fn isSeparator(c: u8) bool {
Expand Down
13 changes: 12 additions & 1 deletion src/install/bun.lock.zig
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,17 @@ pub const Stringifier = struct {
// need a way to detect new/deleted workspaces
if (pkg_id == 0) {
try writer.writeAll("\"\": {");
const root_name = pkg_names[0].slice(buf);
if (root_name.len > 0) {
try writer.writeByte('\n');
try incIndent(writer, indent);
try writer.print("\"name\": {}", .{
bun.fmt.formatJSONStringUTF8(root_name, .{}),
});

// TODO(dylan-conway) should we save version?
any = true;
}
} else {
try writer.print("{}: {{", .{
bun.fmt.formatJSONStringUTF8(res.slice(buf), .{}),
Expand Down Expand Up @@ -1625,7 +1636,7 @@ pub fn parseIntoBinaryLockfile(
}
}

lockfile.hoist(log, .resolvable, {}) catch |err| {
lockfile.resolve(log) catch |err| {
switch (err) {
error.OutOfMemory => |oom| return oom,
else => {
Expand Down
83 changes: 76 additions & 7 deletions src/install/install.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2772,6 +2772,67 @@ pub const PackageManager = struct {
last_reported_slow_lifecycle_script_at: u64 = 0,
cached_tick_for_slow_lifecycle_script_logging: u64 = 0,

pub const WorkspaceFilter = union(enum) {
all,
name: []const u32,
path: []const u32,

pub fn init(allocator: std.mem.Allocator, input: string, cwd: string, path_buf: []u8) OOM!WorkspaceFilter {
if ((input.len == 1 and input[0] == '*') or strings.eqlComptime(input, "**")) {
return .all;
}

var remain = input;

var prepend_negate = false;
while (remain.len > 0 and remain[0] == '!') {
prepend_negate = !prepend_negate;
remain = remain[1..];
}

const is_path = remain.len > 0 and remain[0] == '.';

const filter = if (is_path)
strings.withoutTrailingSlash(bun.path.joinAbsStringBuf(cwd, path_buf, &.{remain}, .posix))
else
remain;

if (filter.len == 0) {
// won't match anything
return .{ .path = &.{} };
}

// TODO(dylan-conway): finish encoding agnostic glob matcher so we don't
// need to convert
const len = bun.simdutf.length.utf32.from.utf8.le(filter) + @intFromBool(prepend_negate);
const buf = try allocator.alloc(u32, len);

const result = bun.simdutf.convert.utf8.to.utf32.with_errors.le(filter, buf[@intFromBool(prepend_negate)..]);
if (!result.isSuccessful()) {
// won't match anything
return .{ .path = &.{} };
}

if (prepend_negate) {
buf[0] = '!';
}

const pattern = buf[0..len];

return if (is_path)
.{ .path = pattern }
else
.{ .name = pattern };
}

pub fn deinit(this: WorkspaceFilter, allocator: std.mem.Allocator) void {
switch (this) {
.path, .name => |pattern| allocator.free(pattern),
.all => {},
}
}
};

pub fn reportSlowLifecycleScripts(this: *PackageManager, log_level: Options.LogLevel) void {
if (log_level == .silent) return;
if (bun.getRuntimeFeatureFlag("BUN_DISABLE_SLOW_LIFECYCLE_SCRIPT_LOGGING")) {
Expand Down Expand Up @@ -8559,6 +8620,7 @@ pub const PackageManager = struct {
pub fn supportsWorkspaceFiltering(this: Subcommand) bool {
return switch (this) {
.outdated => true,
.install => true,
// .pack => true,
else => false,
};
Expand Down Expand Up @@ -9366,7 +9428,7 @@ pub const PackageManager = struct {
} else {
// bun link lodash
switch (manager.options.log_level) {
inline else => |log_level| try manager.updatePackageJSONAndInstallWithManager(ctx, log_level),
inline else => |log_level| try manager.updatePackageJSONAndInstallWithManager(ctx, original_cwd, log_level),
}
}
}
Expand Down Expand Up @@ -9539,6 +9601,7 @@ pub const PackageManager = struct {
clap.parseParam("-D, --development") catch unreachable,
clap.parseParam("--optional Add dependency to \"optionalDependencies\"") catch unreachable,
clap.parseParam("-E, --exact Add the exact version instead of the ^range") catch unreachable,
clap.parseParam("--filter <STR>... Install packages for the matching workspaces") catch unreachable,
clap.parseParam("<POS> ... ") catch unreachable,
});

Expand Down Expand Up @@ -10468,7 +10531,7 @@ pub const PackageManager = struct {
}

switch (manager.options.log_level) {
inline else => |log_level| try manager.updatePackageJSONAndInstallWithManager(ctx, log_level),
inline else => |log_level| try manager.updatePackageJSONAndInstallWithManager(ctx, original_cwd, log_level),
}

if (manager.options.patch_features == .patch) {
Expand Down Expand Up @@ -10599,6 +10662,7 @@ pub const PackageManager = struct {
fn updatePackageJSONAndInstallWithManager(
manager: *PackageManager,
ctx: Command.Context,
original_cwd: string,
comptime log_level: Options.LogLevel,
) !void {
var update_requests = UpdateRequest.Array.initCapacity(manager.allocator, 64) catch bun.outOfMemory();
Expand Down Expand Up @@ -10632,6 +10696,7 @@ pub const PackageManager = struct {
ctx,
updates,
manager.subcommand,
original_cwd,
log_level,
);
}
Expand All @@ -10641,6 +10706,7 @@ pub const PackageManager = struct {
ctx: Command.Context,
updates: []UpdateRequest,
subcommand: Subcommand,
original_cwd: string,
comptime log_level: Options.LogLevel,
) !void {
if (manager.log.errors > 0) {
Expand Down Expand Up @@ -10906,7 +10972,7 @@ pub const PackageManager = struct {
break :brk .{ root_package_json.source.contents, root_package_json_path_buf[0..root_package_json_path.len :0] };
};

try manager.installWithManager(ctx, root_package_json_source, log_level);
try manager.installWithManager(ctx, root_package_json_source, original_cwd, log_level);

if (subcommand == .update or subcommand == .add or subcommand == .link) {
for (updates) |request| {
Expand Down Expand Up @@ -12175,7 +12241,7 @@ pub const PackageManager = struct {

// TODO(dylan-conway): print `bun install <version>` or `bun add <version>` before logs from `init`.
// and cleanup install/add subcommand usage
var manager, _ = try init(ctx, cli, .install);
var manager, const original_cwd = try init(ctx, cli, .install);

// switch to `bun add <package>`
if (subcommand == .add) {
Expand All @@ -12185,7 +12251,7 @@ pub const PackageManager = struct {
Output.flush();
}
return try switch (manager.options.log_level) {
inline else => |log_level| manager.updatePackageJSONAndInstallWithManager(ctx, log_level),
inline else => |log_level| manager.updatePackageJSONAndInstallWithManager(ctx, original_cwd, log_level),
};
}

Expand All @@ -12203,7 +12269,7 @@ pub const PackageManager = struct {
};

try switch (manager.options.log_level) {
inline else => |log_level| manager.installWithManager(ctx, package_json_contents, log_level),
inline else => |log_level| manager.installWithManager(ctx, package_json_contents, original_cwd, log_level),
};

if (manager.any_failed_to_install) {
Expand Down Expand Up @@ -13837,12 +13903,13 @@ pub const PackageManager = struct {
pub fn installPackages(
this: *PackageManager,
ctx: Command.Context,
original_cwd: string,
comptime log_level: PackageManager.Options.LogLevel,
) !PackageInstall.Summary {
const original_trees = this.lockfile.buffers.trees;
const original_tree_dep_ids = this.lockfile.buffers.hoisted_dependencies;

try this.lockfile.hoist(this.log, .filter, this);
try this.lockfile.filter(this.log, this, original_cwd);

defer {
this.lockfile.buffers.trees = original_trees;
Expand Down Expand Up @@ -14306,6 +14373,7 @@ pub const PackageManager = struct {
manager: *PackageManager,
ctx: Command.Context,
root_package_json_contents: string,
original_cwd: string,
comptime log_level: Options.LogLevel,
) !void {

Expand Down Expand Up @@ -14963,6 +15031,7 @@ pub const PackageManager = struct {
if (manager.options.do.install_packages) {
install_summary = try manager.installPackages(
ctx,
original_cwd,
log_level,
);
}
Expand Down
Loading

0 comments on commit 426ae53

Please sign in to comment.