mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 03:18:53 +00:00
Fixes ENG-21287
Build times, from `bun run build && echo '//' >> src/main.zig && time
bun run build`
|Platform|0.14.1|0.15.2|Speedup|
|-|-|-|-|
|macos debug asan|126.90s|106.27s|1.19x|
|macos debug noasan|60.62s|50.85s|1.19x|
|linux debug asan|292.77s|241.45s|1.21x|
|linux debug noasan|146.58s|130.94s|1.12x|
|linux debug use_llvm=false|n/a|78.27s|1.87x|
|windows debug asan|177.13s|142.55s|1.24x|
Runtime performance:
- next build memory usage may have gone up by 5%. Otherwise seems the
same. Some code with writers may have gotten slower, especially one
instance of a counting writer and a few instances of unbuffered writers
that now have vtable overhead.
- File size reduced by 800kb (from 100.2mb to 99.4mb)
Improvements:
- `@export` hack is no longer needed for watch
- native x86_64 backend for linux builds faster. to use it, set use_llvm
false and no_link_obj false. also set `ASAN_OPTIONS=detect_leaks=0`
otherwise it will spam the output with tens of thousands of lines of
debug info errors. may need to use the zig lldb fork for debugging.
- zig test-obj, which we will be able to use for zig unit tests
Still an issue:
- false 'dependency loop' errors remain in watch mode
- watch mode crashes observed
Follow-up:
- [ ] search `comptime Writer: type` and `comptime W: type` and remove
- [ ] remove format_mode in our zig fork
- [ ] remove deprecated.zig autoFormatLabelFallback
- [ ] remove deprecated.zig autoFormatLabel
- [ ] remove deprecated.BufferedWriter and BufferedReader
- [ ] remove override_no_export_cpp_apis as it is no longer needed
- [ ] css Parser(W) -> Parser, and remove all the comptime writer: type
params
- [ ] remove deprecated writer fully
Files that add lines:
```
649 src/deprecated.zig
167 scripts/pack-codegen-for-zig-team.ts
54 scripts/cleartrace-impl.js
46 scripts/cleartrace.ts
43 src/windows.zig
18 src/fs.zig
17 src/bun.js/ConsoleObject.zig
16 src/output.zig
12 src/bun.js/test/debug.zig
12 src/bun.js/node/node_fs.zig
8 src/env_loader.zig
7 src/css/printer.zig
7 src/cli/init_command.zig
7 src/bun.js/node.zig
6 src/string/escapeRegExp.zig
6 src/install/PnpmMatcher.zig
5 src/bun.js/webcore/Blob.zig
4 src/crash_handler.zig
4 src/bun.zig
3 src/install/lockfile/bun.lock.zig
3 src/cli/update_interactive_command.zig
3 src/cli/pack_command.zig
3 build.zig
2 src/Progress.zig
2 src/install/lockfile/lockfile_json_stringify_for_debugging.zig
2 src/css/small_list.zig
2 src/bun.js/webcore/prompt.zig
1 test/internal/ban-words.test.ts
1 test/internal/ban-limits.json
1 src/watcher/WatcherTrace.zig
1 src/transpiler.zig
1 src/shell/builtin/cp.zig
1 src/js_printer.zig
1 src/io/PipeReader.zig
1 src/install/bin.zig
1 src/css/selectors/selector.zig
1 src/cli/run_command.zig
1 src/bun.js/RuntimeTranspilerStore.zig
1 src/bun.js/bindings/JSRef.zig
1 src/bake/DevServer.zig
```
Files that remove lines:
```
-1 src/test/recover.zig
-1 src/sql/postgres/SocketMonitor.zig
-1 src/sql/mysql/MySQLRequestQueue.zig
-1 src/sourcemap/CodeCoverage.zig
-1 src/css/values/color_js.zig
-1 src/compile_target.zig
-1 src/bundler/linker_context/convertStmtsForChunk.zig
-1 src/bundler/bundle_v2.zig
-1 src/bun.js/webcore/blob/read_file.zig
-1 src/ast/base.zig
-2 src/sql/postgres/protocol/ArrayList.zig
-2 src/shell/builtin/mkdir.zig
-2 src/install/PackageManager/patchPackage.zig
-2 src/install/PackageManager/PackageManagerDirectories.zig
-2 src/fmt.zig
-2 src/css/declaration.zig
-2 src/css/css_parser.zig
-2 src/collections/baby_list.zig
-2 src/bun.js/bindings/ZigStackFrame.zig
-2 src/ast/E.zig
-3 src/StandaloneModuleGraph.zig
-3 src/deps/picohttp.zig
-3 src/deps/libuv.zig
-3 src/btjs.zig
-4 src/threading/Futex.zig
-4 src/shell/builtin/touch.zig
-4 src/meta.zig
-4 src/install/lockfile.zig
-4 src/css/selectors/parser.zig
-5 src/shell/interpreter.zig
-5 src/css/error.zig
-5 src/bun.js/web_worker.zig
-5 src/bun.js.zig
-6 src/cli/test_command.zig
-6 src/bun.js/VirtualMachine.zig
-6 src/bun.js/uuid.zig
-6 src/bun.js/bindings/JSValue.zig
-9 src/bun.js/test/pretty_format.zig
-9 src/bun.js/api/BunObject.zig
-14 src/install/install_binding.zig
-14 src/fd.zig
-14 src/bun.js/node/path.zig
-14 scripts/pack-codegen-for-zig-team.sh
-17 src/bun.js/test/diff_format.zig
```
`git diff --numstat origin/main...HEAD | awk '{ print ($1-$2)"\t"$3 }' |
sort -rn`
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Meghan Denny <meghan@bun.com>
Co-authored-by: tayor.fish <contact@taylor.fish>
411 lines
16 KiB
Zig
411 lines
16 KiB
Zig
pub fn view(allocator: std.mem.Allocator, manager: *PackageManager, spec_: string, property_path: ?string, json_output: bool) !void {
|
|
const name, var version = bun.install.Dependency.splitNameAndVersionOrLatest(brk: {
|
|
// Extremely best effort.
|
|
if (bun.strings.eqlComptime(spec_, ".") or bun.strings.eqlComptime(spec_, "")) {
|
|
if (bun.strings.isNPMPackageName(manager.root_package_json_name_at_time_of_init)) {
|
|
break :brk manager.root_package_json_name_at_time_of_init;
|
|
}
|
|
|
|
// Try our best to get the package.json name they meant
|
|
if (manager.root_dir.hasComptimeQuery("package.json")) from_package_json: {
|
|
if (manager.root_dir.fd.isValid()) {
|
|
switch (bun.sys.File.readFrom(manager.root_dir.fd, "package.json", allocator)) {
|
|
.err => {},
|
|
.result => |str| {
|
|
const source = &logger.Source.initPathString("package.json", str);
|
|
var log = logger.Log.init(allocator);
|
|
const json = JSON.parse(source, &log, allocator, false) catch break :from_package_json;
|
|
if (json.getStringCloned(allocator, "name") catch null) |name| {
|
|
if (name.len > 0) {
|
|
break :brk name;
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
break :brk std.fs.path.basename(bun.fs.FileSystem.instance.top_level_dir);
|
|
}
|
|
|
|
break :brk spec_;
|
|
});
|
|
|
|
const scope = manager.scopeForPackageName(name);
|
|
|
|
var url_buf: bun.PathBuffer = undefined;
|
|
const encoded_name = try std.fmt.bufPrint(&url_buf, "{f}", .{bun.fmt.dependencyUrl(name)});
|
|
var path_buf: bun.PathBuffer = undefined;
|
|
// Always fetch the full registry manifest, not a specific version
|
|
const url = URL.parse(try std.fmt.bufPrint(&path_buf, "{s}/{s}", .{
|
|
strings.withoutTrailingSlash(scope.url.href),
|
|
encoded_name,
|
|
}));
|
|
|
|
var headers: http.HeaderBuilder = .{};
|
|
headers.count("Accept", "application/json");
|
|
if (scope.token.len > 0) {
|
|
headers.count("Authorization", "");
|
|
headers.content.cap += "Bearer ".len + scope.token.len;
|
|
} else if (scope.auth.len > 0) {
|
|
headers.count("Authorization", "");
|
|
headers.content.cap += "Basic ".len + scope.auth.len;
|
|
}
|
|
try headers.allocate(allocator);
|
|
headers.append("Accept", "application/json");
|
|
if (scope.token.len > 0) {
|
|
headers.appendFmt("Authorization", "Bearer {s}", .{scope.token});
|
|
} else if (scope.auth.len > 0) {
|
|
headers.appendFmt("Authorization", "Basic {s}", .{scope.auth});
|
|
}
|
|
|
|
var response_buf = try MutableString.init(allocator, 2048);
|
|
var req = http.AsyncHTTP.initSync(
|
|
allocator,
|
|
.GET,
|
|
url,
|
|
headers.entries,
|
|
headers.content.ptr.?[0..headers.content.len],
|
|
&response_buf,
|
|
"",
|
|
manager.httpProxy(url),
|
|
null,
|
|
.follow,
|
|
);
|
|
req.client.flags.reject_unauthorized = manager.tlsRejectUnauthorized();
|
|
|
|
const res = req.sendSync() catch |err| {
|
|
Output.err(err, "view request failed to send", .{});
|
|
Global.crash();
|
|
};
|
|
|
|
if (res.status_code >= 400) {
|
|
try @import("../install/npm.zig").responseError(allocator, &req, &res, .{ name, version }, &response_buf, false);
|
|
}
|
|
|
|
var log = logger.Log.init(allocator);
|
|
const source = &logger.Source.initPathString("view.json", response_buf.list.items);
|
|
var json = JSON.parseUTF8(source, &log, allocator) catch |err| {
|
|
Output.err(err, "failed to parse response body as JSON", .{});
|
|
Global.crash();
|
|
};
|
|
if (log.errors > 0) {
|
|
try log.print(Output.errorWriter());
|
|
Global.crash();
|
|
}
|
|
|
|
// Parse the existing JSON response into a PackageManifest using the now-public parse function
|
|
const parsed_manifest = @import("../install/npm.zig").PackageManifest.parse(
|
|
allocator,
|
|
scope,
|
|
&log,
|
|
response_buf.list.items,
|
|
name,
|
|
"", // last_modified (not needed for view)
|
|
"", // etag (not needed for view)
|
|
0, // public_max_age (not needed for view)
|
|
true, // is_extended_manifest (view uses application/json Accept header)
|
|
) catch |err| {
|
|
Output.err(err, "failed to parse package manifest", .{});
|
|
Global.exit(1);
|
|
} orelse {
|
|
Output.errGeneric("failed to parse package manifest", .{});
|
|
Global.crash();
|
|
};
|
|
|
|
// Now use the existing version resolution logic from outdated_command
|
|
var manifest = json;
|
|
|
|
var versions_len: usize = 1;
|
|
|
|
version, manifest = brk: {
|
|
if (json.getObject("versions")) |versions_obj| from_versions: {
|
|
// Find the version string from JSON that matches the resolved version
|
|
const versions = versions_obj.data.e_object.properties.slice();
|
|
versions_len = versions.len;
|
|
|
|
const wanted_version: Semver.Version = brk2: {
|
|
// First try dist-tag lookup (like "latest", "beta", etc.)
|
|
if (parsed_manifest.findByDistTag(version)) |result| {
|
|
break :brk2 result.version;
|
|
} else {
|
|
// Parse as semver query and find best version - exactly like outdated_command.zig line 325
|
|
const sliced_literal = Semver.SlicedString.init(version, version);
|
|
const query = try Semver.Query.parse(allocator, version, sliced_literal);
|
|
defer query.deinit();
|
|
// Use the same pattern as outdated_command: findBestVersion(query.head, string_buf)
|
|
if (parsed_manifest.findBestVersion(query, parsed_manifest.string_buf)) |result| {
|
|
break :brk2 result.version;
|
|
}
|
|
}
|
|
|
|
break :from_versions;
|
|
};
|
|
|
|
for (versions) |*prop| {
|
|
if (prop.key == null) continue;
|
|
const version_str = prop.key.?.asString(allocator) orelse continue;
|
|
const sliced_version = Semver.SlicedString.init(version_str, version_str);
|
|
const parsed_version = Semver.Version.parse(sliced_version);
|
|
if (parsed_version.valid and parsed_version.version.max().eql(wanted_version)) {
|
|
break :brk .{ version_str, prop.value.? };
|
|
}
|
|
}
|
|
}
|
|
|
|
if (json_output) {
|
|
Output.print("{{ \"error\": \"No matching version found\", \"version\": {f} }}\n", .{
|
|
bun.fmt.formatJSONStringUTF8(spec_, .{
|
|
.quote = true,
|
|
}),
|
|
});
|
|
Output.flush();
|
|
} else {
|
|
Output.errGeneric("No version of <b>{f}<r> satisfying <b>{f}<r> found", .{
|
|
bun.fmt.quote(name),
|
|
bun.fmt.quote(version),
|
|
});
|
|
|
|
const max_versions_to_display = 5;
|
|
|
|
const start_index = parsed_manifest.versions.len -| max_versions_to_display;
|
|
var versions_to_display = parsed_manifest.versions[start_index..];
|
|
versions_to_display = versions_to_display[0..@min(versions_to_display.len, max_versions_to_display)];
|
|
if (versions_to_display.len > 0) {
|
|
Output.prettyErrorln("\nRecent versions:<r>", .{});
|
|
for (versions_to_display) |*v| {
|
|
Output.prettyErrorln("<d>-<r> {f}", .{v.fmt(parsed_manifest.string_buf)});
|
|
}
|
|
|
|
if (start_index > 0) {
|
|
Output.prettyErrorln(" <d>... and {d} more<r>", .{start_index});
|
|
}
|
|
}
|
|
}
|
|
Global.exit(1);
|
|
};
|
|
|
|
// Treat versions specially because npm does some normalization on there.
|
|
if (json.getObject("versions")) |versions_object| {
|
|
const keys = try allocator.alloc(bun.ast.Expr, versions_object.data.e_object.properties.len);
|
|
for (versions_object.data.e_object.properties.slice(), keys) |*prop, *key| {
|
|
key.* = prop.key.?;
|
|
}
|
|
const versions_array = bun.ast.Expr.init(
|
|
bun.ast.E.Array,
|
|
bun.ast.E.Array{
|
|
.items = .fromOwnedSlice(keys),
|
|
},
|
|
.{ .start = -1 },
|
|
);
|
|
try manifest.set(allocator, "versions", versions_array);
|
|
}
|
|
|
|
// Handle property lookup if specified
|
|
if (property_path) |prop_path| {
|
|
|
|
// This is similar to what npm does.
|
|
// `bun pm view react version ` => 1.2.3
|
|
// `bun pm view react versions` => ['1.2.3', '1.2.4', '1.2.5']
|
|
if (manifest.getPathMayBeIndex(prop_path) orelse json.getPathMayBeIndex(prop_path)) |value| {
|
|
if (value.data == .e_string) {
|
|
const slice = value.data.e_string.slice(allocator);
|
|
if (json_output) {
|
|
Output.println("{f}", .{bun.fmt.formatJSONStringUTF8(slice, .{})});
|
|
} else {
|
|
Output.println("{s}", .{slice});
|
|
}
|
|
Output.flush();
|
|
return;
|
|
}
|
|
|
|
const JSPrinter = bun.js_printer;
|
|
var buffer_writer = JSPrinter.BufferWriter.init(bun.default_allocator);
|
|
buffer_writer.append_newline = true;
|
|
var package_json_writer = JSPrinter.BufferPrinter.init(buffer_writer);
|
|
_ = try bun.js_printer.printJSON(
|
|
@TypeOf(&package_json_writer),
|
|
&package_json_writer,
|
|
value,
|
|
source,
|
|
.{
|
|
.mangled_props = null,
|
|
},
|
|
);
|
|
Output.print("{s}", .{package_json_writer.ctx.getWritten()});
|
|
Output.flush();
|
|
Global.exit(0);
|
|
} else {
|
|
if (json_output) {
|
|
Output.print("{{ \"error\": \"Property not found\", \"version\": {f}, \"property\": {f} }}\n", .{
|
|
bun.fmt.formatJSONStringUTF8(spec_, .{
|
|
.quote = true,
|
|
}),
|
|
bun.fmt.formatJSONStringUTF8(prop_path, .{
|
|
.quote = true,
|
|
}),
|
|
});
|
|
Output.flush();
|
|
} else {
|
|
Output.errGeneric("Property <b>{s}<r> not found", .{prop_path});
|
|
}
|
|
}
|
|
Global.exit(1);
|
|
}
|
|
|
|
if (json_output) {
|
|
// Output formatted JSON using JSPrinter
|
|
const JSPrinter = bun.js_printer;
|
|
var buffer_writer = JSPrinter.BufferWriter.init(bun.default_allocator);
|
|
buffer_writer.append_newline = true;
|
|
var package_json_writer = JSPrinter.BufferPrinter.init(buffer_writer);
|
|
_ = try bun.js_printer.printJSON(
|
|
@TypeOf(&package_json_writer),
|
|
&package_json_writer,
|
|
manifest,
|
|
source,
|
|
.{
|
|
.mangled_props = null,
|
|
.indent = .{
|
|
.count = 2,
|
|
},
|
|
},
|
|
);
|
|
Output.print("{s}", .{package_json_writer.ctx.getWritten()});
|
|
Output.flush();
|
|
return;
|
|
}
|
|
|
|
const pkg_name = manifest.getStringCloned(allocator, "name") catch null orelse name;
|
|
const pkg_version = manifest.getStringCloned(allocator, "version") catch null orelse version;
|
|
const license = manifest.getStringCloned(allocator, "license") catch null orelse "";
|
|
var dep_count: usize = 0;
|
|
const dependencies_object = manifest.getObject("dependencies");
|
|
if (dependencies_object) |*deps| {
|
|
dep_count = deps.data.e_object.properties.len;
|
|
}
|
|
|
|
Output.prettyln("<b><blue><u>{s}<r><d>@<r><blue><b><u>{s}<r> <d>|<r> <cyan>{s}<r> <d>|<r> deps<d>:<r> {d} <d>|<r> versions<d>:<r> {d}", .{
|
|
pkg_name,
|
|
pkg_version,
|
|
license,
|
|
dep_count,
|
|
versions_len,
|
|
});
|
|
|
|
// Get description and homepage from the top-level package manifest, not the version-specific one
|
|
if (json.getStringCloned(allocator, "description") catch null) |desc| {
|
|
Output.prettyln("{s}", .{desc});
|
|
}
|
|
if (json.getStringCloned(allocator, "homepage") catch null) |hp| {
|
|
Output.prettyln("<blue>{s}<r>", .{hp});
|
|
}
|
|
|
|
if (json.getArray("keywords")) |arr| {
|
|
var keywords = try MutableString.init(allocator, 64);
|
|
var iter = arr;
|
|
var first = true;
|
|
while (iter.next()) |kw_expr| {
|
|
if (kw_expr.asString(allocator)) |kw| {
|
|
if (!first) try keywords.appendSlice(", ") else first = false;
|
|
try keywords.appendSlice(kw);
|
|
}
|
|
}
|
|
if (keywords.list.items.len > 0) {
|
|
Output.prettyln("<d>keywords:<r> {s}", .{keywords.list.items});
|
|
}
|
|
}
|
|
|
|
// Display dependencies if they exist
|
|
if (dependencies_object) |*deps| {
|
|
const dependencies = deps.data.e_object.properties.slice();
|
|
if (dependencies.len > 0) {
|
|
Output.prettyln("\n<b>dependencies<r><d> ({d}):<r>", .{dependencies.len});
|
|
}
|
|
|
|
for (dependencies) |prop| {
|
|
if (prop.key == null or prop.value == null) continue;
|
|
const dep_name = prop.key.?.asString(allocator) orelse continue;
|
|
const dep_version = prop.value.?.asString(allocator) orelse continue;
|
|
Output.prettyln("- <cyan>{s}<r><d>:<r> {s}", .{ dep_name, dep_version });
|
|
}
|
|
}
|
|
|
|
if (manifest.getObject("dist")) |dist| {
|
|
Output.prettyln("\n<d><r><b>dist<r>", .{});
|
|
if (dist.getStringCloned(allocator, "tarball") catch null) |t| {
|
|
Output.prettyln(" <d>.<r>tarball<d>:<r> {s}", .{t});
|
|
}
|
|
if (dist.getStringCloned(allocator, "shasum") catch null) |s| {
|
|
Output.prettyln(" <d>.<r>shasum<r><d>:<r> <green>{s}<r>", .{s});
|
|
}
|
|
if (dist.getStringCloned(allocator, "integrity") catch null) |i| {
|
|
Output.prettyln(" <d>.<r>integrity<r><d>:<r> <green>{s}<r>", .{i});
|
|
}
|
|
if (dist.getNumber("unpackedSize")) |u| {
|
|
Output.prettyln(" <d>.<r>unpackedSize<r><d>:<r> <blue>{f}<r>", .{bun.fmt.size(@as(u64, @intFromFloat(u[0])), .{})});
|
|
}
|
|
}
|
|
|
|
if (json.getObject("dist-tags")) |tags_obj| {
|
|
Output.prettyln("\n<b>dist-tags<r><d>:<r>", .{});
|
|
for (tags_obj.data.e_object.properties.slice()) |prop| {
|
|
if (prop.key == null or prop.value == null) continue;
|
|
const tagname_expr = prop.key.?;
|
|
const val_expr = prop.value.?;
|
|
if (tagname_expr.asString(allocator)) |tag| {
|
|
if (val_expr.asString(allocator)) |val| {
|
|
if (strings.eqlComptime(tag, "latest")) {
|
|
Output.prettyln("<cyan>{s}<r><d>:<r> {s}", .{ tag, val });
|
|
} else if (strings.eqlComptime(tag, "beta")) {
|
|
Output.prettyln("<blue>{s}<r><d>:<r> {s}", .{ tag, val });
|
|
} else {
|
|
Output.prettyln("<magenta>{s}<r><d>:<r> {s}", .{ tag, val });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (json.getArray("maintainers")) |maint_iter| {
|
|
Output.prettyln("\nmaintainers<r><d>:<r>", .{});
|
|
var iter = maint_iter;
|
|
while (iter.next()) |m| {
|
|
const nm = m.getStringCloned(allocator, "name") catch null orelse "";
|
|
const em = m.getStringCloned(allocator, "email") catch null orelse "";
|
|
if (em.len > 0) {
|
|
Output.prettyln("<d>-<r> {s} <d>\\<{s}\\><r>", .{ nm, em });
|
|
} else if (nm.len > 0) {
|
|
Output.prettyln("<d>-<r> {s}", .{nm});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add published date information
|
|
if (json.getObject("time")) |time_obj| {
|
|
// TODO: use a relative time formatter
|
|
if (time_obj.getStringCloned(allocator, pkg_version) catch null) |published_time| {
|
|
Output.prettyln("\n<b>Published<r><d>:<r> {s}", .{published_time});
|
|
} else if (time_obj.getStringCloned(allocator, "modified") catch null) |modified_time| {
|
|
Output.prettyln("\n<b>Published<r><d>:<r> {s}", .{modified_time});
|
|
}
|
|
}
|
|
}
|
|
|
|
const string = []const u8;
|
|
|
|
const std = @import("std");
|
|
const PackageManager = @import("../install/install.zig").PackageManager;
|
|
const PackageManifest = @import("../install/npm.zig").PackageManifest;
|
|
const URL = @import("../url.zig").URL;
|
|
|
|
const bun = @import("bun");
|
|
const Global = bun.Global;
|
|
const JSON = bun.json;
|
|
const MutableString = bun.MutableString;
|
|
const Output = bun.Output;
|
|
const Semver = bun.Semver;
|
|
const http = bun.http;
|
|
const logger = bun.logger;
|
|
const strings = bun.strings;
|