From f374ae6db1bffef2a2160921399ee5f810955d19 Mon Sep 17 00:00:00 2001 From: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> Date: Wed, 2 Oct 2024 20:47:22 -0700 Subject: [PATCH] add `bun publish` (#14215) --- docs/install/workspaces.md | 6 +- .../archive_write_add_filter_gzip.c.patch | 58 + src/StandaloneModuleGraph.zig | 2 +- src/bun.js/ConsoleObject.zig | 2 +- src/bun.js/javascript.zig | 4 +- src/bun.js/module_loader.zig | 6 +- src/bun.js/test/pretty_format.zig | 2 +- src/bun.zig | 15 +- src/bundler.zig | 4 +- src/bunfig.zig | 2 +- src/cache.zig | 8 +- src/ci_info.zig | 421 +++++++ src/cli.zig | 76 +- src/cli/bunx_command.zig | 2 +- src/cli/create_command.zig | 30 +- src/cli/filter_arg.zig | 4 +- src/cli/init_command.zig | 11 +- src/cli/pack_command.zig | 566 ++++++--- src/cli/package_manager_command.zig | 2 +- src/cli/pm_trusted_command.zig | 2 +- src/cli/publish_command.zig | 1074 +++++++++++++++++ src/cli/upgrade_command.zig | 8 +- src/compile_target.zig | 6 +- src/defines.zig | 2 +- src/deps/picohttp.zig | 4 +- src/exact_size_matcher.zig | 2 +- src/fmt.zig | 107 ++ src/http.zig | 47 +- src/ini.zig | 27 +- src/install/dependency.zig | 19 + src/install/extract_tarball.zig | 6 +- src/install/install.zig | 213 +++- src/install/lockfile.zig | 14 +- src/install/migration.zig | 2 +- src/install/npm.zig | 2 +- src/js_ast.zig | 78 +- src/js_printer.zig | 25 - src/json_parser.zig | 24 +- src/libarchive/libarchive-bindings.zig | 1019 ++++++++++++---- src/libarchive/libarchive.zig | 275 +---- src/open.zig | 2 +- src/sourcemap/sourcemap.zig | 2 +- src/sql/postgres.zig | 2 +- src/string_immutable.zig | 102 ++ src/string_mutable.zig | 13 +- test/bun.lockb | Bin 362754 -> 370290 bytes test/cli/install/bun-pack.test.ts | 41 +- .../registry/bun-install-registry.test.ts | 443 ++++++- test/cli/install/registry/verdaccio.yaml | 7 +- test/harness.ts | 24 + test/package.json | 2 +- 51 files changed, 3911 insertions(+), 904 deletions(-) create mode 100644 patches/libarchive/archive_write_add_filter_gzip.c.patch create mode 100644 src/ci_info.zig create mode 100644 src/cli/publish_command.zig diff --git a/docs/install/workspaces.md b/docs/install/workspaces.md index dc3219679c..64d2445132 100644 --- a/docs/install/workspaces.md +++ b/docs/install/workspaces.md @@ -41,7 +41,7 @@ In the root `package.json`, the `"workspaces"` key is used to indicate which sub **Glob support** — Bun supports full glob syntax in `"workspaces"` (see [here](https://bun.sh/docs/api/glob#supported-glob-patterns) for a comprehensive list of supported syntax), _except_ for exclusions (e.g. `!**/excluded/**`), which are not implemented yet. {% /callout %} -Each workspace has it's own `package.json` When referencing other packages in the monorepo, use `"workspace:*"` as the version field in your `package.json`. +Each workspace has it's own `package.json`. When referencing other packages in the monorepo, semver or workspace protocols (e.g. `workspace:*`) can be used as the version field in your `package.json`. ```json { @@ -53,10 +53,6 @@ Each workspace has it's own `package.json` When referencing other packages in th } ``` -{% callout %} -**Version support** — Bun supports simple `workspace:*` versions in `"dependencies"`. Full version syntax (e.g. `workspace:^*`) is not yet supported. -{% /callout %} - Workspaces have a couple major benefits. - **Code can be split into logical parts.** If one package relies on another, you can simply add it as a dependency in `package.json`. If package `b` depends on `a`, `bun install` will install your local `packages/a` directory into `node_modules` instead of downloading it from the npm registry. diff --git a/patches/libarchive/archive_write_add_filter_gzip.c.patch b/patches/libarchive/archive_write_add_filter_gzip.c.patch new file mode 100644 index 0000000000..bdbdf68f21 --- /dev/null +++ b/patches/libarchive/archive_write_add_filter_gzip.c.patch @@ -0,0 +1,58 @@ +--- a/libarchive/archive_write_add_filter_gzip.c ++++ b/libarchive/archive_write_add_filter_gzip.c +@@ -58,6 +58,7 @@ archive_write_set_compression_gzip(struct archive *a) + struct private_data { + int compression_level; + int timestamp; ++ unsigned char os; + #ifdef HAVE_ZLIB_H + z_stream stream; + int64_t total_in; +@@ -106,6 +107,7 @@ archive_write_add_filter_gzip(struct archive *_a) + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } ++ data->os = 3; /* default Unix */ + f->data = data; + f->open = &archive_compressor_gzip_open; + f->options = &archive_compressor_gzip_options; +@@ -166,6 +168,30 @@ archive_compressor_gzip_options(struct archive_write_filter *f, const char *key, + return (ARCHIVE_OK); + } + ++ if (strcmp(key, "os") == 0) { ++ if (value == NULL) ++ return (ARCHIVE_WARN); ++ ++ if (strcmp(value, "FAT") == 0) data->os = 0; ++ else if (strcmp(value, "Amiga") == 0) data->os = 1; ++ else if (strcmp(value, "VMS") == 0 || strcmp(value, "OpenVMS") == 0) data->os = 2; ++ else if (strcmp(value, "Unix") == 0) data->os = 3; ++ else if (strcmp(value, "VM") == 0 || strcmp(value, "VM/CMS") == 0) data->os = 4; ++ else if (strcmp(value, "Atari TOS") == 0) data->os = 5; ++ else if (strcmp(value, "HPFS") == 0) data->os = 6; ++ else if (strcmp(value, "Macintosh") == 0) data->os = 7; ++ else if (strcmp(value, "Z-System") == 0) data->os = 8; ++ else if (strcmp(value, "CP/M") == 0) data->os = 9; ++ else if (strcmp(value, "TOPS-20") == 0) data->os = 10; ++ else if (strcmp(value, "NTFS") == 0) data->os = 11; ++ else if (strcmp(value, "QDOS") == 0) data->os = 12; ++ else if (strcmp(value, "Acorn RISCOS") == 0) data->os = 13; ++ else if (strcmp(value, "Unknown") == 0) data->os = 255; ++ else return (ARCHIVE_WARN); ++ ++ return (ARCHIVE_OK); ++ } ++ + /* Note: The "warn" return is just to inform the options + * supervisor that we didn't handle it. It will generate + * a suitable error if no one used this option. */ +@@ -226,7 +252,7 @@ archive_compressor_gzip_open(struct archive_write_filter *f) + data->compressed[8] = 4; + else + data->compressed[8] = 0; +- data->compressed[9] = 3; /* OS=Unix */ ++ data->compressed[9] = data->os; + data->stream.next_out += 10; + data->stream.avail_out -= 10; + diff --git a/src/StandaloneModuleGraph.zig b/src/StandaloneModuleGraph.zig index 00be32eda7..a58989280f 100644 --- a/src/StandaloneModuleGraph.zig +++ b/src/StandaloneModuleGraph.zig @@ -1039,7 +1039,7 @@ pub const StandaloneModuleGraph = struct { bun.JSAst.Expr.Data.Store.reset(); bun.JSAst.Stmt.Data.Store.reset(); } - var json = bun.JSON.ParseJSON(&json_src, &log, arena, false) catch + var json = bun.JSON.parse(&json_src, &log, arena, false) catch return error.InvalidSourceMap; const mappings_str = json.get("mappings") orelse diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index 58141dec83..b58cee7c92 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/ConsoleObject.zig @@ -1778,7 +1778,7 @@ pub const Formatter = struct { writer.print( comptime Output.prettyFmt("{s}: ", enable_ansi_colors), - .{JSPrinter.formatJSONString(key.slice())}, + .{bun.fmt.formatJSONString(key.slice())}, ); } } else if (Environment.isDebug and is_private_symbol) { diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index dab8a72585..b5d4c5bea6 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -2666,7 +2666,7 @@ pub const VirtualMachine = struct { "{s} resolving preload {}", .{ @errorName(e), - js_printer.formatJSONString(preload), + bun.fmt.formatJSONString(preload), }, ) catch unreachable; return e; @@ -2678,7 +2678,7 @@ pub const VirtualMachine = struct { this.allocator, "preload not found {}", .{ - js_printer.formatJSONString(preload), + bun.fmt.formatJSONString(preload), }, ) catch unreachable; return error.ModuleNotFound; diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index 1d9d176bc6..8e0d014928 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -184,9 +184,9 @@ fn dumpSourceStringFailiable(vm: *VirtualMachine, specifier: string, written: [] \\ "mappings": "{}" \\}} , .{ - js_printer.formatJSONStringUTF8(std.fs.path.basename(specifier)), - js_printer.formatJSONStringUTF8(specifier), - js_printer.formatJSONStringUTF8(source_file), + bun.fmt.formatJSONStringUTF8(std.fs.path.basename(specifier)), + bun.fmt.formatJSONStringUTF8(specifier), + bun.fmt.formatJSONStringUTF8(source_file), mappings.formatVLQs(), }); try bufw.flush(); diff --git a/src/bun.js/test/pretty_format.zig b/src/bun.js/test/pretty_format.zig index 3b2720b995..501709db4f 100644 --- a/src/bun.js/test/pretty_format.zig +++ b/src/bun.js/test/pretty_format.zig @@ -861,7 +861,7 @@ pub const JestPrettyFormat = struct { writer.print( comptime Output.prettyFmt("{s}: ", enable_ansi_colors), - .{JSPrinter.formatJSONString(key.slice())}, + .{bun.fmt.formatJSONString(key.slice())}, ); } } else { diff --git a/src/bun.zig b/src/bun.zig index 8f979c5196..2cda4881b5 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -96,6 +96,8 @@ pub const JSError = error{ JSError, }; +pub const detectCI = @import("./ci_info.zig").detectCI; + pub const C = @import("root").C; pub const sha = @import("./sha.zig"); pub const FeatureFlags = @import("feature_flags.zig"); @@ -299,6 +301,8 @@ pub fn platformIOVecToSlice(iovec: PlatformIOVec) []u8 { return iovec.base[0..iovec.len]; } +pub const libarchive = @import("./libarchive/libarchive.zig"); + pub const StringTypes = @import("string_types.zig"); pub const stringZ = StringTypes.stringZ; pub const string = StringTypes.string; @@ -2231,9 +2235,12 @@ pub const Stat = if (Environment.isWindows) windows.libuv.uv_stat_t else std.pos pub var argv: [][:0]const u8 = &[_][:0]const u8{}; pub fn initArgv(allocator: std.mem.Allocator) !void { - if (comptime !Environment.isWindows) { - argv = try std.process.argsAlloc(allocator); - } else { + if (comptime Environment.isPosix) { + argv = try allocator.alloc([:0]const u8, std.os.argv.len); + for (0..argv.len) |i| { + argv[i] = std.mem.sliceTo(std.os.argv[i], 0); + } + } else if (comptime Environment.isWindows) { // Zig's implementation of `std.process.argsAlloc()`on Windows platforms // is not reliable, specifically the way it splits the command line string. // @@ -2283,6 +2290,8 @@ pub fn initArgv(allocator: std.mem.Allocator) !void { } argv = out_argv; + } else { + argv = try std.process.argsAlloc(allocator); } } diff --git a/src/bundler.zig b/src/bundler.zig index a729e3d19d..dc9b55abe8 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -1455,9 +1455,9 @@ pub const Bundler = struct { // We allow importing tsconfig.*.json or jsconfig.*.json with comments // These files implicitly become JSONC files, which aligns with the behavior of text editors. if (source.path.isJSONCFile()) - json_parser.ParseTSConfig(&source, bundler.log, allocator, false) catch return null + json_parser.parseTSConfig(&source, bundler.log, allocator, false) catch return null else - json_parser.ParseJSON(&source, bundler.log, allocator, false) catch return null + json_parser.parse(&source, bundler.log, allocator, false) catch return null else if (kind == .toml) TOML.parse(&source, bundler.log, allocator) catch return null else diff --git a/src/bunfig.zig b/src/bunfig.zig index 6ad52eac40..f141edcd2f 100644 --- a/src/bunfig.zig +++ b/src/bunfig.zig @@ -785,7 +785,7 @@ pub const Bunfig = struct { ctx.log.addErrorFmt(&source, logger.Loc.Empty, allocator, "Failed to parse", .{}) catch unreachable; } return err; - } else JSONParser.ParseTSConfig(&source, ctx.log, allocator, true) catch |err| { + } else JSONParser.parseTSConfig(&source, ctx.log, allocator, true) catch |err| { if (ctx.log.errors + ctx.log.warnings == log_count) { ctx.log.addErrorFmt(&source, logger.Loc.Empty, allocator, "Failed to parse", .{}) catch unreachable; } diff --git a/src/cache.zig b/src/cache.zig index 6934a8aa6e..4ea8616cac 100644 --- a/src/cache.zig +++ b/src/cache.zig @@ -308,17 +308,17 @@ pub const Json = struct { // They are JSON files with comments and trailing commas. // Sometimes tooling expects this to work. if (source.path.isJSONCFile()) { - return try parse(cache, log, source, allocator, json_parser.ParseTSConfig, true); + return try parse(cache, log, source, allocator, json_parser.parseTSConfig, true); } - return try parse(cache, log, source, allocator, json_parser.ParseJSON, false); + return try parse(cache, log, source, allocator, json_parser.parse, false); } pub fn parsePackageJSON(cache: *@This(), log: *logger.Log, source: logger.Source, allocator: std.mem.Allocator, comptime force_utf8: bool) anyerror!?js_ast.Expr { - return try parse(cache, log, source, allocator, json_parser.ParseTSConfig, force_utf8); + return try parse(cache, log, source, allocator, json_parser.parseTSConfig, force_utf8); } pub fn parseTSConfig(cache: *@This(), log: *logger.Log, source: logger.Source, allocator: std.mem.Allocator) anyerror!?js_ast.Expr { - return try parse(cache, log, source, allocator, json_parser.ParseTSConfig, true); + return try parse(cache, log, source, allocator, json_parser.parseTSConfig, true); } }; diff --git a/src/ci_info.zig b/src/ci_info.zig new file mode 100644 index 0000000000..ed88efc291 --- /dev/null +++ b/src/ci_info.zig @@ -0,0 +1,421 @@ +// A modified port of ci-info@4.0.0 (https://github.com/watson/ci-info) +// Only gets the CI name, `isPR` is not implemented. + +// Names are changed to match what `npm publish` uses +// https://github.com/npm/cli/blob/63d6a732c3c0e9c19fd4d147eaa5cc27c29b168d/workspaces/config/lib/definitions/definitions.js#L2129 +// `name.toLowerCase().split(' ').join('-')` + +const std = @import("std"); +const bun = @import("root").bun; +const strings = bun.strings; + +var ci_name: ?[]const u8 = null; + +pub fn detectCI() ?[]const u8 { + const ci = ci_name orelse ci_name: { + CI.once.call(); + break :ci_name ci_name.?; + }; + + return if (ci.len == 0) null else ci; +} + +const CI = enum { + @"agola-ci", + appcircle, + appveyor, + @"aws-codebuild", + @"azure-pipelines", + bamboo, + @"bitbucket-pipelines", + bitrise, + buddy, + buildkite, + circleci, + @"cirrus-ci", + codefresh, + codemagic, + codeship, + drone, + dsari, + earthly, + @"expo-application-services", + gerrit, + @"gitea-actions", + @"github-actions", + @"gitlab-ci", + gocd, + @"google-cloud-build", + @"harness-ci", + // heroku, + hudson, + jenkins, + layerci, + @"magnum-ci", + @"netlify-ci", + nevercode, + prow, + releasehub, + render, + @"sail-ci", + screwdriver, + semaphore, + sourcehut, + @"strider-cd", + taskcluster, + teamcity, + @"travis-ci", + vela, + vercel, + @"visual-studio-app-center", + woodpecker, + @"xcode-cloud", + @"xcode-server", + + pub var once = std.once(struct { + pub fn once() void { + var name: []const u8 = ""; + defer ci_name = name; + + if (bun.getenvZ("CI")) |ci| { + if (strings.eqlComptime(ci, "false")) { + return; + } + } + + // Special case Heroku + if (bun.getenvZ("NODE")) |node| { + if (strings.containsComptime(node, "/app/.heroku/node/bin/node")) { + name = "heroku"; + return; + } + } + + ci: for (CI.array.values, 0..) |item, i| { + const any, const pairs = item; + + pairs: for (pairs) |pair| { + const key, const value = pair; + + if (bun.getenvZ(key)) |env| { + if (value.len == 0 or bun.strings.eqlLong(env, value, true)) { + if (!any) continue :pairs; + + name = @tagName(Array.Indexer.keyForIndex(i)); + return; + } + } + + if (!any) continue :ci; + } + + if (!any) { + name = @tagName(Array.Indexer.keyForIndex(i)); + return; + } + } + } + }.once); + + pub const Array = std.EnumArray(CI, struct { bool, []const [2][:0]const u8 }); + + pub const array = Array.init(.{ + .@"agola-ci" = .{ + false, + &.{ + .{ "AGOLA_GIT_REF", "" }, + }, + }, + .appcircle = .{ + false, + &.{ + .{ "AC_APPCIRCLE", "" }, + }, + }, + .appveyor = .{ + false, + &.{ + .{ "APPVEYOR", "" }, + }, + }, + .@"aws-codebuild" = .{ + false, + &.{ + .{ "CODEBUILD_BUILD_ARN", "" }, + }, + }, + .@"azure-pipelines" = .{ + false, + &.{ + .{ "TF_BUILD", "" }, + }, + }, + .bamboo = .{ + false, + &.{ + .{ "bamboo_planKey", "" }, + }, + }, + .@"bitbucket-pipelines" = .{ + false, + &.{ + .{ "BITBUCKET_COMMIT", "" }, + }, + }, + .bitrise = .{ + false, + &.{ + .{ "BITRISE_IO", "" }, + }, + }, + .buddy = .{ + false, + &.{ + .{ "BUDDY_WORKSPACE_ID", "" }, + }, + }, + .buildkite = .{ + false, + &.{ + .{ "BUILDKITE", "" }, + }, + }, + .circleci = .{ + false, + &.{ + .{ "CIRCLECI", "" }, + }, + }, + .@"cirrus-ci" = .{ + false, + &.{ + .{ "CIRRUS_CI", "" }, + }, + }, + .codefresh = .{ + false, + &.{ + .{ "CF_BUILD_ID", "" }, + }, + }, + .codemagic = .{ + false, + &.{ + .{ "CM_BUILD_ID", "" }, + }, + }, + .codeship = .{ + false, + &.{ + .{ "CI_NAME", "codeship" }, + }, + }, + .drone = .{ + false, + &.{ + .{ "DRONE", "" }, + }, + }, + .dsari = .{ + false, + &.{ + .{ "DSARI", "" }, + }, + }, + .earthly = .{ + false, + &.{ + .{ "EARTHLY_CI", "" }, + }, + }, + .@"expo-application-services" = .{ + false, + &.{ + .{ "EAS_BUILD", "" }, + }, + }, + .gerrit = .{ + false, + &.{ + .{ "GERRIT_PROJECT", "" }, + }, + }, + .@"gitea-actions" = .{ + false, + &.{ + .{ "GITEA_ACTIONS", "" }, + }, + }, + .@"github-actions" = .{ + false, + &.{ + .{ "GITHUB_ACTIONS", "" }, + }, + }, + .@"gitlab-ci" = .{ + false, + &.{ + .{ "GITLAB_CI", "" }, + }, + }, + .gocd = .{ + false, + &.{ + .{ "GO_PIPELINE_LABEL", "" }, + }, + }, + .@"google-cloud-build" = .{ + false, + &.{ + .{ "BUILDER_OUTPUT", "" }, + }, + }, + .@"harness-ci" = .{ + false, + &.{ + .{ "HARNESS_BUILD_ID", "" }, + }, + }, + .hudson = .{ + false, + &.{ + .{ "HUDSON_URL", "" }, + }, + }, + .jenkins = .{ + false, + &.{ + .{ "JENKINS_URL", "" }, + .{ "BUILD_ID", "" }, + }, + }, + .layerci = .{ + false, + &.{ + .{ "LAYERCI", "" }, + }, + }, + .@"magnum-ci" = .{ + false, + &.{ + .{ "MAGNUM", "" }, + }, + }, + .@"netlify-ci" = .{ + false, + &.{ + .{ "NETLIFY", "" }, + }, + }, + .nevercode = .{ + false, + &.{ + .{ "NEVERCODE", "" }, + }, + }, + .prow = .{ + false, + &.{ + .{ "PROW_JOB_ID", "" }, + }, + }, + .releasehub = .{ + false, + &.{ + .{ "RELEASE_BUILD_ID", "" }, + }, + }, + .render = .{ + false, + &.{ + .{ "RENDER", "" }, + }, + }, + .@"sail-ci" = .{ + false, + &.{ + .{ "SAILCI", "" }, + }, + }, + .screwdriver = .{ + false, + &.{ + .{ "SCREWDRIVER", "" }, + }, + }, + .semaphore = .{ + false, + &.{ + .{ "SEMAPHORE", "" }, + }, + }, + .sourcehut = .{ + false, + &.{ + .{ "CI_NAME", "sourcehut" }, + }, + }, + .@"strider-cd" = .{ + false, + &.{ + .{ "STRIDER", "" }, + }, + }, + .taskcluster = .{ + false, + &.{ + .{ "TASK_ID", "" }, + .{ "RUN_ID", "" }, + }, + }, + .teamcity = .{ + false, + &.{ + .{ "TEAMCITY_VERSION", "" }, + }, + }, + .@"travis-ci" = .{ + false, + &.{ + .{ "TRAVIS", "" }, + }, + }, + .vela = .{ + false, + &.{ + .{ "VELA", "" }, + }, + }, + .vercel = .{ + true, + &.{ + .{ "NOW_BUILDER", "" }, + .{ "VERCEL", "" }, + }, + }, + .@"visual-studio-app-center" = .{ + false, + &.{ + .{ "APPCENTER_BUILD_ID", "" }, + }, + }, + .woodpecker = .{ + false, + &.{ + .{ "CI", "woodpecker" }, + }, + }, + .@"xcode-cloud" = .{ + false, + &.{ + .{ "CI_XCODE_PROJECT", "" }, + }, + }, + .@"xcode-server" = .{ + false, + &.{ + .{ "XCS", "" }, + }, + }, + }); +}; diff --git a/src/cli.zig b/src/cli.zig index 6e30b2b95c..a3395f7ca1 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -44,6 +44,7 @@ const MacroMap = @import("./resolver/package_json.zig").MacroMap; const TestCommand = @import("./cli/test_command.zig").TestCommand; pub var start_time: i128 = undefined; const Bunfig = @import("./bunfig.zig").Bunfig; +const OOM = bun.OOM; pub const Cli = struct { pub const CompileTarget = @import("./compile_target.zig"); @@ -116,6 +117,9 @@ pub const ExecCommand = @import("./cli/exec_command.zig").ExecCommand; pub const PatchCommand = @import("./cli/patch_command.zig").PatchCommand; pub const PatchCommitCommand = @import("./cli/patch_commit_command.zig").PatchCommitCommand; pub const OutdatedCommand = @import("./cli/outdated_command.zig").OutdatedCommand; +pub const PublishCommand = @import("./cli/publish_command.zig").PublishCommand; +pub const PackCommand = @import("./cli/pack_command.zig").PackCommand; +pub const InitCommand = @import("./cli/init_command.zig").InitCommand; pub const Arguments = struct { pub fn loader_resolver(in: string) !Api.Loader { @@ -327,14 +331,25 @@ pub const Arguments = struct { return null; } - pub fn loadConfig(allocator: std.mem.Allocator, user_config_path_: ?string, ctx: Command.Context, comptime cmd: Command.Tag) !void { + pub fn loadConfig(allocator: std.mem.Allocator, user_config_path_: ?string, ctx: Command.Context, comptime cmd: Command.Tag) OOM!void { var config_buf: bun.PathBuffer = undefined; if (comptime cmd.readGlobalConfig()) { if (!ctx.has_loaded_global_config) { ctx.has_loaded_global_config = true; if (getHomeConfigPath(&config_buf)) |path| { - try loadConfigPath(allocator, true, path, ctx, comptime cmd); + loadConfigPath(allocator, true, path, ctx, comptime cmd) catch |err| { + if (ctx.log.hasAny()) { + switch (Output.enable_ansi_colors) { + inline else => |enable_ansi_colors| { + ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; + }, + } + } + if (ctx.log.hasAny()) Output.printError("\n", .{}); + Output.err(err, "failed to load bunfig", .{}); + Global.crash(); + }; } } } @@ -382,7 +397,18 @@ pub const Arguments = struct { config_path = config_buf[0..config_path_.len :0]; } - try loadConfigPath(allocator, auto_loaded, config_path, ctx, comptime cmd); + loadConfigPath(allocator, auto_loaded, config_path, ctx, comptime cmd) catch |err| { + if (ctx.log.hasAny()) { + switch (Output.enable_ansi_colors) { + inline else => |enable_ansi_colors| { + ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; + }, + } + } + if (ctx.log.hasAny()) Output.printError("\n", .{}); + Output.err(err, "failed to load bunfig", .{}); + Global.crash(); + }; } pub fn loadConfigWithCmdArgs( @@ -390,7 +416,7 @@ pub const Arguments = struct { allocator: std.mem.Allocator, args: clap.Args(clap.Help, cmd.params()), ctx: Command.Context, - ) !void { + ) OOM!void { return try loadConfig(allocator, args.option("--config"), ctx, comptime cmd); } @@ -1068,7 +1094,6 @@ const AutoCommand = struct { try HelpCommand.execWithReason(allocator, .invalid_command); } }; -const InitCommand = @import("./cli/init_command.zig").InitCommand; pub const HelpCommand = struct { pub fn exec(allocator: std.mem.Allocator) !void { @@ -1214,9 +1239,9 @@ pub const HelpCommand = struct { printWithReason(reason, false); if (reason == .invalid_command) { - std.process.exit(1); + Global.exit(1); } - std.process.exit(0); + Global.exit(0); } }; @@ -1393,7 +1418,7 @@ pub const Command = struct { // std.process.args allocates! const ArgsIterator = struct { - buf: [][:0]const u8 = undefined, + buf: [][:0]const u8, i: u32 = 0, pub fn next(this: *ArgsIterator) ?[]const u8 { @@ -1456,7 +1481,7 @@ pub const Command = struct { } const first_arg_name = next_arg; - const RootCommandMatcher = strings.ExactSizeMatcher(16); + const RootCommandMatcher = strings.ExactSizeMatcher(12); return switch (RootCommandMatcher.match(first_arg_name)) { RootCommandMatcher.case("init") => .InitCommand, @@ -1505,6 +1530,7 @@ pub const Command = struct { RootCommandMatcher.case("exec") => .ExecCommand, RootCommandMatcher.case("outdated") => .OutdatedCommand, + RootCommandMatcher.case("publish") => .PublishCommand, // These are reserved for future use by Bun, so that someone // doing `bun deploy` to run a script doesn't accidentally break @@ -1518,7 +1544,6 @@ pub const Command = struct { RootCommandMatcher.case("login") => .ReservedCommand, RootCommandMatcher.case("logout") => .ReservedCommand, RootCommandMatcher.case("whoami") => .ReservedCommand, - RootCommandMatcher.case("publish") => .ReservedCommand, RootCommandMatcher.case("prune") => .ReservedCommand, RootCommandMatcher.case("list") => .ReservedCommand, RootCommandMatcher.case("why") => .ReservedCommand, @@ -1648,6 +1673,13 @@ pub const Command = struct { try OutdatedCommand.exec(ctx); return; }, + .PublishCommand => { + if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .PublishCommand) unreachable; + const ctx = try Command.init(allocator, log, .PublishCommand); + + try PublishCommand.exec(ctx); + return; + }, .BunxCommand => { if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .BunxCommand) unreachable; const ctx = try Command.init(allocator, log, .BunxCommand); @@ -1690,6 +1722,20 @@ pub const Command = struct { if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .PackageManagerCommand) unreachable; const ctx = try Command.init(allocator, log, .PackageManagerCommand); + // const maybe_subcommand, const maybe_arg = PackageManagerCommand.which(command_index); + // if (maybe_subcommand) |subcommand| { + // return switch (subcommand) { + // inline else => |tag| try PackageManagerCommand.exec(ctx, tag), + // }; + // } + + // PackageManagerCommand.printHelp(); + + // if (maybe_arg) |arg| { + // Output.errGeneric("\"{s}\" unknown command", .{arg}); + // Global.crash(); + // } + try PackageManagerCommand.exec(ctx); return; }, @@ -2216,6 +2262,7 @@ pub const Command = struct { PatchCommand, PatchCommitCommand, OutdatedCommand, + PublishCommand, /// Used by crash reports. /// @@ -2248,6 +2295,7 @@ pub const Command = struct { .PatchCommand => 'x', .PatchCommitCommand => 'z', .OutdatedCommand => 'o', + .PublishCommand => 'k', }; } @@ -2471,9 +2519,10 @@ pub const Command = struct { , .{}); Output.flush(); }, - .OutdatedCommand => { + .OutdatedCommand, .PublishCommand => { Install.PackageManager.CommandLineArguments.printHelp(switch (cmd) { .OutdatedCommand => .outdated, + .PublishCommand => .publish, }); }, else => { @@ -2493,6 +2542,7 @@ pub const Command = struct { .PatchCommand, .PatchCommitCommand, .OutdatedCommand, + .PublishCommand, => true, else => false, }; @@ -2511,6 +2561,7 @@ pub const Command = struct { .PatchCommand, .PatchCommitCommand, .OutdatedCommand, + .PublishCommand, => true, else => false, }; @@ -2531,6 +2582,7 @@ pub const Command = struct { .RunCommand = true, .RunAsNodeCommand = true, .OutdatedCommand = true, + .PublishCommand = true, }); pub const always_loads_config: std.EnumArray(Tag, bool) = std.EnumArray(Tag, bool).initDefault(false, .{ @@ -2545,6 +2597,7 @@ pub const Command = struct { .PackageManagerCommand = true, .BunxCommand = true, .OutdatedCommand = true, + .PublishCommand = true, }); pub const uses_global_options: std.EnumArray(Tag, bool) = std.EnumArray(Tag, bool).initDefault(true, .{ @@ -2560,6 +2613,7 @@ pub const Command = struct { .UnlinkCommand = false, .BunxCommand = false, .OutdatedCommand = false, + .PublishCommand = false, }); }; }; diff --git a/src/cli/bunx_command.zig b/src/cli/bunx_command.zig index c0bb2fa7f7..21ce662274 100644 --- a/src/cli/bunx_command.zig +++ b/src/cli/bunx_command.zig @@ -82,7 +82,7 @@ pub const BunxCommand = struct { bun.JSAst.Expr.Data.Store.create(); bun.JSAst.Stmt.Data.Store.create(); - const expr = try bun.JSON.ParsePackageJSONUTF8(&source, bundler.log, bundler.allocator); + const expr = try bun.JSON.parsePackageJSONUTF8(&source, bundler.log, bundler.allocator); // choose the first package that fits if (expr.get("bin")) |bin_expr| { diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index 7b7bf50c7c..d45a8b0408 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -31,8 +31,8 @@ const fs = @import("../fs.zig"); const URL = @import("../url.zig").URL; const HTTP = bun.http; -const ParseJSON = @import("../json_parser.zig").ParseJSONUTF8; -const Archive = @import("../libarchive/libarchive.zig").Archive; +const JSON = bun.JSON; +const Archiver = bun.libarchive.Archiver; const Zlib = @import("../zlib.zig"); const JSPrinter = bun.js_printer; const DotEnv = @import("../env_loader.zig"); @@ -377,19 +377,19 @@ pub const CreateCommand = struct { progress.refresh(); - var pluckers: [1]Archive.Plucker = if (!create_options.skip_package_json) - [1]Archive.Plucker{try Archive.Plucker.init(comptime strings.literal(bun.OSPathChar, "package.json"), 2048, ctx.allocator)} + var pluckers: [1]Archiver.Plucker = if (!create_options.skip_package_json) + [1]Archiver.Plucker{try Archiver.Plucker.init(comptime strings.literal(bun.OSPathChar, "package.json"), 2048, ctx.allocator)} else - [1]Archive.Plucker{undefined}; + [1]Archiver.Plucker{undefined}; - var archive_context = Archive.Context{ + var archive_context = Archiver.Context{ .pluckers = pluckers[0..@as(usize, @intCast(@intFromBool(!create_options.skip_package_json)))], .all_files = undefined, .overwrite_list = bun.StringArrayHashMap(void).init(ctx.allocator), }; if (!create_options.overwrite) { - try Archive.getOverwritingFileList( + try Archiver.getOverwritingFileList( tarball_buf_list.items, destination, &archive_context, @@ -427,7 +427,7 @@ pub const CreateCommand = struct { } } - _ = try Archive.extractToDisk( + _ = try Archiver.extractToDisk( tarball_buf_list.items, destination, &archive_context, @@ -701,7 +701,7 @@ pub const CreateCommand = struct { var source = logger.Source.initPathString("package.json", package_json_contents.list.items); - var package_json_expr = ParseJSON(&source, ctx.log, ctx.allocator) catch { + var package_json_expr = JSON.parseUTF8(&source, ctx.log, ctx.allocator) catch { package_json_file = null; break :process_package_json; }; @@ -1983,7 +1983,7 @@ pub const Example = struct { async_http.client.progress_node = progress; async_http.client.flags.reject_unauthorized = env_loader.getTLSRejectUnauthorized(); - const response = try async_http.sendSync(true); + const response = try async_http.sendSync(); switch (response.status_code) { 404 => return error.GitHubRepositoryNotFound, @@ -2060,7 +2060,7 @@ pub const Example = struct { async_http.client.progress_node = progress; async_http.client.flags.reject_unauthorized = env_loader.getTLSRejectUnauthorized(); - var response = try async_http.sendSync(true); + var response = try async_http.sendSync(); switch (response.status_code) { 404 => return error.ExampleNotFound, @@ -2075,7 +2075,7 @@ pub const Example = struct { refresher.refresh(); initializeStore(); var source = logger.Source.initPathString("package.json", mutable.list.items); - var expr = ParseJSON(&source, ctx.log, ctx.allocator) catch |err| { + var expr = JSON.parseUTF8(&source, ctx.log, ctx.allocator) catch |err| { progress.end(); refresher.refresh(); @@ -2151,7 +2151,7 @@ pub const Example = struct { refresher.maybeRefresh(); - response = try async_http.sendSync(true); + response = try async_http.sendSync(); refresher.maybeRefresh(); @@ -2194,7 +2194,7 @@ pub const Example = struct { async_http.client.progress_node = progress_node; } - const response = async_http.sendSync(true) catch |err| { + const response = async_http.sendSync() catch |err| { switch (err) { error.WouldBlock => { Output.prettyErrorln("Request timed out while trying to fetch examples list. Please try again", .{}); @@ -2214,7 +2214,7 @@ pub const Example = struct { initializeStore(); var source = logger.Source.initPathString("examples.json", mutable.list.items); - const examples_object = ParseJSON(&source, ctx.log, ctx.allocator) catch |err| { + const examples_object = JSON.parseUTF8(&source, ctx.log, ctx.allocator) catch |err| { if (ctx.log.errors > 0) { if (Output.enable_ansi_colors) { try ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true); diff --git a/src/cli/filter_arg.zig b/src/cli/filter_arg.zig index 297233df3a..99031058d5 100644 --- a/src/cli/filter_arg.zig +++ b/src/cli/filter_arg.zig @@ -5,7 +5,7 @@ const string = bun.string; const Output = bun.Output; const Global = bun.Global; const strings = bun.strings; -const json_parser = bun.JSON; +const JSON = bun.JSON; const Glob = @import("../glob.zig"); const Package = @import("../install/lockfile.zig").Package; @@ -65,7 +65,7 @@ pub fn getCandidatePackagePatterns(allocator: std.mem.Allocator, log: *bun.logge }; defer allocator.free(json_source.contents); - const json = try json_parser.ParsePackageJSONUTF8(&json_source, log, allocator); + const json = try JSON.parsePackageJSONUTF8(&json_source, log, allocator); const prop = json.asProperty("workspaces") orelse continue; diff --git a/src/cli/init_command.zig b/src/cli/init_command.zig index 35d22af2fe..16d4d407a7 100644 --- a/src/cli/init_command.zig +++ b/src/cli/init_command.zig @@ -12,7 +12,7 @@ const std = @import("std"); const open = @import("../open.zig"); const CLI = @import("../cli.zig"); const Fs = @import("../fs.zig"); -const ParseJSON = @import("../json_parser.zig").ParsePackageJSONUTF8; +const JSON = bun.JSON; const js_parser = bun.js_parser; const js_ast = bun.JSAst; const linker = @import("../linker.zig"); @@ -26,11 +26,10 @@ fn exists(path: anytype) bool { return bun.sys.exists(path); } pub const InitCommand = struct { - fn prompt( + pub fn prompt( alloc: std.mem.Allocator, comptime label: string, default: []const u8, - _: bool, ) ![]const u8 { Output.pretty(label, .{}); if (default.len > 0) { @@ -171,7 +170,7 @@ pub const InitCommand = struct { process_package_json: { var source = logger.Source.initPathString("package.json", package_json_contents.list.items); var log = logger.Log.init(alloc); - var package_json_expr = ParseJSON(&source, &log, alloc) catch { + var package_json_expr = JSON.parsePackageJSONUTF8(&source, &log, alloc) catch { package_json_file = null; break :process_package_json; }; @@ -248,7 +247,6 @@ pub const InitCommand = struct { alloc, "package name ", fields.name, - Output.enable_ansi_colors_stdout, ) catch |err| { if (err == error.EndOfStream) return; return err; @@ -260,7 +258,6 @@ pub const InitCommand = struct { alloc, "entry point ", fields.entry_point, - Output.enable_ansi_colors_stdout, ) catch |err| { if (err == error.EndOfStream) return; return err; @@ -439,7 +436,7 @@ pub const InitCommand = struct { " \"'", fields.entry_point, )) { - Output.prettyln(" bun run {any}", .{JSPrinter.formatJSONString(fields.entry_point)}); + Output.prettyln(" bun run {any}", .{bun.fmt.formatJSONString(fields.entry_point)}); } else { Output.prettyln(" bun run {s}", .{fields.entry_point}); } diff --git a/src/cli/pack_command.zig b/src/cli/pack_command.zig index c36df036b6..4d4bc36b40 100644 --- a/src/cli/pack_command.zig +++ b/src/cli/pack_command.zig @@ -34,6 +34,8 @@ const BoringSSL = bun.BoringSSL; const sha = bun.sha; const LogLevel = PackageManager.Options.LogLevel; const FileDescriptor = bun.FileDescriptor; +const Publish = bun.CLI.PublishCommand; +const Dependency = Install.Dependency; pub const PackCommand = struct { pub const Context = struct { @@ -49,49 +51,30 @@ pub const PackCommand = struct { bundled_deps: std.ArrayListUnmanaged(BundledDep) = .{}, - stats: struct { + stats: Stats = .{}, + + const Stats = struct { unpacked_size: usize = 0, total_files: usize = 0, ignored_files: usize = 0, ignored_directories: usize = 0, packed_size: usize = 0, bundled_deps: usize = 0, - } = .{}, - - pub const BundledDep = struct { - name: string, - was_packed: bool = false, - from_root_package_json: bool, }; - const IntegrityFormatter = struct { - bytes: [sha.SHA512.digest]u8, - - pub fn format(this: IntegrityFormatter, comptime _: string, _: std.fmt.FormatOptions, writer: anytype) !void { - var buf: [std.base64.standard.Encoder.calcSize(sha.SHA512.digest)]u8 = undefined; - const count = bun.simdutf.base64.encode(this.bytes[0..sha.SHA512.digest], &buf, false); - - const encoded = buf[0..count]; - - try writer.print("sha512-{s}[...]{s}", .{ encoded[0..13], encoded[encoded.len - 15 ..] }); - } - }; - - fn fmtIntegrity(bytes: [sha.SHA512.digest]u8) IntegrityFormatter { - return .{ - .bytes = bytes, - }; - } - - pub fn printSummary(this: *const Context, sha1_digest: ?[sha.SHA1.digest]u8, sha512_digest: ?[sha.SHA512.digest]u8, comptime log_level: LogLevel) void { - if (comptime log_level != .silent) { - const stats = this.stats; + pub fn printSummary( + stats: Stats, + maybe_shasum: ?[sha.SHA1.digest]u8, + maybe_integrity: ?[sha.SHA512.digest]u8, + log_level: LogLevel, + ) void { + if (log_level != .silent) { Output.prettyln("\nTotal files: {d}", .{stats.total_files}); - if (sha1_digest) |sha1| { - Output.prettyln("Shasum: {s}", .{bun.fmt.bytesToHex(sha1, .lower)}); + if (maybe_shasum) |shasum| { + Output.prettyln("Shasum: {s}", .{bun.fmt.bytesToHex(shasum, .lower)}); } - if (sha512_digest) |sha512| { - Output.prettyln("Integrity: {}", .{fmtIntegrity(sha512)}); + if (maybe_integrity) |integrity| { + Output.prettyln("Integrity: {}", .{bun.fmt.integrity(integrity, .short)}); } Output.prettyln("Unpacked size: {}", .{ bun.fmt.size(stats.unpacked_size, .{ .space_between_number_and_unit = false }), @@ -108,6 +91,12 @@ pub const PackCommand = struct { } }; + pub const BundledDep = struct { + name: string, + was_packed: bool = false, + from_root_package_json: bool, + }; + pub fn execWithManager(ctx: Command.Context, manager: *PackageManager) !void { Output.prettyln("bun pack v" ++ Global.package_json_version_with_sha ++ "", .{}); Output.flush(); @@ -146,7 +135,7 @@ pub const PackCommand = struct { }), } - if (ctx.log.hasErrors()) { + if (manager.log.hasErrors()) { switch (Output.enable_ansi_colors) { inline else => |enable_ansi_colors| try manager.log.printForLogLevelWithEnableAnsiColors( Output.errorWriter(), @@ -173,7 +162,7 @@ pub const PackCommand = struct { // } // just pack the current workspace - pack(&pack_ctx, manager.original_package_json_path, log_level) catch |err| { + pack(&pack_ctx, manager.original_package_json_path, log_level, false) catch |err| { switch (err) { error.OutOfMemory => bun.outOfMemory(), error.MissingPackageName, error.MissingPackageVersion => { @@ -219,13 +208,19 @@ pub const PackCommand = struct { return execWithManager(ctx, manager); } - const PackError = OOM || error{ - MissingPackageName, - InvalidPackageName, - MissingPackageVersion, - InvalidPackageVersion, - MissingPackageJSON, - }; + pub fn PackError(comptime for_publish: bool) type { + return OOM || error{ + MissingPackageName, + InvalidPackageName, + MissingPackageVersion, + InvalidPackageVersion, + MissingPackageJSON, + } || + if (for_publish) error{ + RestrictedUnscopedPackage, + PrivatePackage, + } else error{}; + } const package_prefix = "package/"; @@ -280,25 +275,25 @@ pub const PackCommand = struct { }; fn iterateIncludedProjectTree( - ctx: *Context, + allocator: std.mem.Allocator, includes: []const Pattern, root_dir: std.fs.Dir, comptime log_level: LogLevel, ) OOM!PackQueue { - var pack_queue = PackQueue.init(ctx.allocator, {}); + var pack_queue = PackQueue.init(allocator, {}); var ignores: std.ArrayListUnmanaged(IgnorePatterns) = .{}; - defer ignores.deinit(ctx.allocator); + defer ignores.deinit(allocator); var dirs: std.ArrayListUnmanaged(DirInfo) = .{}; - defer dirs.deinit(ctx.allocator); + defer dirs.deinit(allocator); - try dirs.append(ctx.allocator, .{ root_dir, "", 1 }); + try dirs.append(allocator, .{ root_dir, "", 1 }); var included_dirs: std.ArrayListUnmanaged(DirInfo) = .{}; - defer included_dirs.deinit(ctx.allocator); + defer included_dirs.deinit(allocator); - var subpath_dedupe = bun.StringHashMap(void).init(ctx.allocator); + var subpath_dedupe = bun.StringHashMap(void).init(allocator); defer subpath_dedupe.deinit(); // first find included dirs and files @@ -315,7 +310,7 @@ pub const PackCommand = struct { if (entry.kind != .file and entry.kind != .directory) continue; const entry_name = entry.name.slice(); - const entry_subpath = try entrySubpath(ctx, dir_subpath, entry_name); + const entry_subpath = try entrySubpath(allocator, dir_subpath, entry_name); var included = false; @@ -358,7 +353,7 @@ pub const PackCommand = struct { if (!included) { if (entry.kind == .directory) { const subdir = openSubdir(dir, entry_name, entry_subpath); - try dirs.append(ctx.allocator, .{ subdir, entry_subpath, dir_depth + 1 }); + try dirs.append(allocator, .{ subdir, entry_subpath, dir_depth + 1 }); } continue; @@ -367,7 +362,7 @@ pub const PackCommand = struct { switch (entry.kind) { .directory => { const subdir = openSubdir(dir, entry_name, entry_subpath); - try included_dirs.append(ctx.allocator, .{ subdir, entry_subpath, dir_depth + 1 }); + try included_dirs.append(allocator, .{ subdir, entry_subpath, dir_depth + 1 }); }, .file => { const dedupe_entry = try subpath_dedupe.getOrPut(entry_subpath); @@ -383,7 +378,7 @@ pub const PackCommand = struct { // for each included dir, traverse it's entries, exclude any with `negate_no_match`. for (included_dirs.items) |included_dir_info| { - try addEntireTree(ctx, included_dir_info, &pack_queue, &subpath_dedupe, log_level); + try addEntireTree(allocator, included_dir_info, &pack_queue, &subpath_dedupe, log_level); } return pack_queue; @@ -391,19 +386,19 @@ pub const PackCommand = struct { /// Adds all files in a directory tree to `pack_list` (default ignores still apply) fn addEntireTree( - ctx: *Context, + allocator: std.mem.Allocator, root_dir_info: DirInfo, pack_queue: *PackQueue, maybe_dedupe: ?*bun.StringHashMap(void), comptime log_level: LogLevel, ) OOM!void { var dirs: std.ArrayListUnmanaged(DirInfo) = .{}; - defer dirs.deinit(ctx.allocator); + defer dirs.deinit(allocator); - try dirs.append(ctx.allocator, root_dir_info); + try dirs.append(allocator, root_dir_info); var ignores: std.ArrayListUnmanaged(IgnorePatterns) = .{}; - defer ignores.deinit(ctx.allocator); + defer ignores.deinit(allocator); while (dirs.popOrNull()) |dir_info| { var dir, const dir_subpath, const dir_depth = dir_info; @@ -412,12 +407,12 @@ pub const PackCommand = struct { while (ignores.getLastOrNull()) |last| { if (last.depth < dir_depth) break; - last.deinit(ctx.allocator); + last.deinit(allocator); ignores.items.len -= 1; } - if (try IgnorePatterns.readFromDisk(ctx, dir, dir_depth)) |patterns| { - try ignores.append(ctx.allocator, patterns); + if (try IgnorePatterns.readFromDisk(allocator, dir, dir_depth)) |patterns| { + try ignores.append(allocator, patterns); } if (comptime Environment.isDebug) { @@ -434,7 +429,7 @@ pub const PackCommand = struct { if (entry.kind != .file and entry.kind != .directory) continue; const entry_name = entry.name.slice(); - const entry_subpath = try entrySubpath(ctx, dir_subpath, entry_name); + const entry_subpath = try entrySubpath(allocator, dir_subpath, entry_name); if (dir_depth == root_dir_info[2]) { if (entry.kind == .directory and strings.eqlComptime(entry_name, "node_modules")) continue; @@ -465,7 +460,7 @@ pub const PackCommand = struct { .directory => { const subdir = openSubdir(dir, entry_name, entry_subpath); - try dirs.append(ctx.allocator, .{ + try dirs.append(allocator, .{ subdir, entry_subpath, dir_depth + 1, @@ -492,11 +487,11 @@ pub const PackCommand = struct { } fn entrySubpath( - ctx: *Context, + allocator: std.mem.Allocator, dir_subpath: string, entry_name: string, ) OOM!stringZ { - return std.fmt.allocPrintZ(ctx.allocator, "{s}{s}{s}", .{ + return std.fmt.allocPrintZ(allocator, "{s}{s}{s}", .{ dir_subpath, if (dir_subpath.len == 0) "" else "/", entry_name, @@ -552,7 +547,7 @@ pub const PackCommand = struct { bun.assertWithLocation(dep.from_root_package_json, @src()); if (!strings.eqlLong(entry_name, dep.name, true)) continue; - const entry_subpath = try entrySubpath(ctx, "node_modules", entry_name); + const entry_subpath = try entrySubpath(ctx.allocator, "node_modules", entry_name); const dedupe_entry = try dedupe.getOrPut(entry_subpath); if (dedupe_entry.found_existing) { @@ -628,7 +623,7 @@ pub const PackCommand = struct { if (entry.kind != .file and entry.kind != .directory) continue; const entry_name = entry.name.slice(); - const entry_subpath = try entrySubpath(ctx, dir_subpath, entry_name); + const entry_subpath = try entrySubpath(ctx.allocator, dir_subpath, entry_name); if (dir_depth == bundled_dir_info[2]) root_depth: { if (strings.eqlComptime(entry_name, "package.json")) { @@ -639,7 +634,7 @@ pub const PackCommand = struct { Global.crash(); }; - const json = JSON.ParsePackageJSONUTF8(&source, ctx.manager.log, ctx.allocator) catch + const json = JSON.parsePackageJSONUTF8(&source, ctx.manager.log, ctx.allocator) catch break :root_depth; // for each dependency in `dependencies` find the closest node_modules folder @@ -738,21 +733,21 @@ pub const PackCommand = struct { /// Returns a list of files to pack and another list of files from bundled dependencies fn iterateProjectTree( - ctx: *Context, + allocator: std.mem.Allocator, root_dir: std.fs.Dir, comptime log_level: LogLevel, ) OOM!PackQueue { - var pack_queue = PackQueue.init(ctx.allocator, {}); + var pack_queue = PackQueue.init(allocator, {}); var ignores: std.ArrayListUnmanaged(IgnorePatterns) = .{}; - defer ignores.deinit(ctx.allocator); + defer ignores.deinit(allocator); // Stacks and depth-first traversal. Doing so means we can push and pop from // ignore patterns without needing to clone the entire list for future use. var dirs: std.ArrayListUnmanaged(DirInfo) = .{}; - defer dirs.deinit(ctx.allocator); + defer dirs.deinit(allocator); - try dirs.append(ctx.allocator, .{ root_dir, "", 1 }); + try dirs.append(allocator, .{ root_dir, "", 1 }); while (dirs.popOrNull()) |dir_info| { var dir, const dir_subpath, const dir_depth = dir_info; @@ -766,12 +761,12 @@ pub const PackCommand = struct { if (last.depth < dir_depth) break; // pop patterns from files greater than or equal to the current depth. - last.deinit(ctx.allocator); + last.deinit(allocator); ignores.items.len -= 1; } - if (try IgnorePatterns.readFromDisk(ctx, dir, dir_depth)) |patterns| { - try ignores.append(ctx.allocator, patterns); + if (try IgnorePatterns.readFromDisk(allocator, dir, dir_depth)) |patterns| { + try ignores.append(allocator, patterns); } if (comptime Environment.isDebug) { @@ -788,7 +783,7 @@ pub const PackCommand = struct { if (entry.kind != .file and entry.kind != .directory) continue; const entry_name = entry.name.slice(); - const entry_subpath = try entrySubpath(ctx, dir_subpath, entry_name); + const entry_subpath = try entrySubpath(allocator, dir_subpath, entry_name); if (dir_depth == 1) { // Special case root package.json. It is always included @@ -823,7 +818,7 @@ pub const PackCommand = struct { .directory => { const subdir = openSubdir(dir, entry_name, entry_subpath); - try dirs.append(ctx.allocator, .{ + try dirs.append(allocator, .{ subdir, entry_subpath, dir_depth + 1, @@ -838,11 +833,11 @@ pub const PackCommand = struct { } fn getBundledDeps( - ctx: *Context, + allocator: std.mem.Allocator, json: Expr, comptime field: string, - ) OOM!?std.ArrayListUnmanaged(Context.BundledDep) { - var deps: std.ArrayListUnmanaged(Context.BundledDep) = .{}; + ) OOM!?std.ArrayListUnmanaged(BundledDep) { + var deps: std.ArrayListUnmanaged(BundledDep) = .{}; const bundled_deps = json.get(field) orelse return null; invalid_field: { @@ -851,8 +846,8 @@ pub const PackCommand = struct { else => break :invalid_field, }; while (iter.next()) |bundled_dep_item| { - const bundled_dep = bundled_dep_item.asStringCloned(ctx.allocator) orelse break :invalid_field; - try deps.append(ctx.allocator, .{ + const bundled_dep = try bundled_dep_item.asStringCloned(allocator) orelse break :invalid_field; + try deps.append(allocator, .{ .name = bundled_dep, .from_root_package_json = true, }); @@ -876,7 +871,7 @@ pub const PackCommand = struct { }; fn getPackageBins( - ctx: *Context, + allocator: std.mem.Allocator, json: Expr, ) OOM![]const BinInfo { var bins: std.ArrayListUnmanaged(BinInfo) = .{}; @@ -884,10 +879,10 @@ pub const PackCommand = struct { var path_buf: PathBuffer = undefined; if (json.asProperty("bin")) |bin| { - if (bin.expr.asString(ctx.allocator)) |bin_str| { + if (bin.expr.asString(allocator)) |bin_str| { const normalized = bun.path.normalizeBuf(bin_str, &path_buf, .posix); - try bins.append(ctx.allocator, .{ - .path = try ctx.allocator.dupe(u8, normalized), + try bins.append(allocator, .{ + .path = try allocator.dupe(u8, normalized), .type = .file, }); return bins.items; @@ -899,10 +894,10 @@ pub const PackCommand = struct { for (bin_obj.properties.slice()) |bin_prop| { if (bin_prop.value) |bin_prop_value| { - if (bin_prop_value.asString(ctx.allocator)) |bin_str| { + if (bin_prop_value.asString(allocator)) |bin_str| { const normalized = bun.path.normalizeBuf(bin_str, &path_buf, .posix); - try bins.append(ctx.allocator, .{ - .path = try ctx.allocator.dupe(u8, normalized), + try bins.append(allocator, .{ + .path = try allocator.dupe(u8, normalized), .type = .file, }); } @@ -919,10 +914,10 @@ pub const PackCommand = struct { switch (directories.expr.data) { .e_object => |directories_obj| { if (directories_obj.asProperty("bin")) |bin| { - if (bin.expr.asString(ctx.allocator)) |bin_str| { + if (bin.expr.asString(allocator)) |bin_str| { const normalized = bun.path.normalizeBuf(bin_str, &path_buf, .posix); - try bins.append(ctx.allocator, .{ - .path = try ctx.allocator.dupe(u8, normalized), + try bins.append(allocator, .{ + .path = try allocator.dupe(u8, normalized), .type = .dir, }); } @@ -1079,11 +1074,12 @@ pub const PackCommand = struct { const BufferedFileReader = std.io.BufferedReader(1024 * 512, File.Reader); - fn pack( + pub fn pack( ctx: *Context, abs_package_json_path: stringZ, comptime log_level: LogLevel, - ) PackError!void { + comptime for_publish: bool, + ) PackError(for_publish)!if (for_publish) Publish.Context(true) else void { const manager = ctx.manager; const json = switch (manager.workspace_package_json_cache.getWithPath(manager.allocator, manager.log, abs_package_json_path, .{ .guess_indentation = true, @@ -1104,16 +1100,54 @@ pub const PackCommand = struct { .entry => |entry| entry, }; + if (comptime for_publish) { + if (json.root.get("publishConfig")) |config| { + if (manager.options.publish_config.tag.len == 0) { + if (try config.getStringCloned(ctx.allocator, "tag")) |tag| { + manager.options.publish_config.tag = tag; + } + } + if (manager.options.publish_config.access == null) { + if (try config.getString(ctx.allocator, "access")) |access| { + manager.options.publish_config.access = PackageManager.Options.Access.fromStr(access[0]) orelse { + Output.errGeneric("invalid `access` value: '{s}'", .{access[0]}); + Global.crash(); + }; + } + } + } + + // maybe otp + } + const package_name_expr: Expr = json.root.get("name") orelse return error.MissingPackageName; - const package_name = package_name_expr.asStringCloned(ctx.allocator) orelse return error.InvalidPackageName; - defer ctx.allocator.free(package_name); + const package_name = try package_name_expr.asStringCloned(ctx.allocator) orelse return error.InvalidPackageName; + if (comptime for_publish) { + const is_scoped = try Dependency.isScopedPackageName(package_name); + if (manager.options.publish_config.access) |access| { + if (access == .restricted and !is_scoped) { + return error.RestrictedUnscopedPackage; + } + } + } + defer if (comptime !for_publish) ctx.allocator.free(package_name); if (package_name.len == 0) return error.InvalidPackageName; const package_version_expr: Expr = json.root.get("version") orelse return error.MissingPackageVersion; - const package_version = package_version_expr.asStringCloned(ctx.allocator) orelse return error.InvalidPackageVersion; - defer ctx.allocator.free(package_version); + const package_version = try package_version_expr.asStringCloned(ctx.allocator) orelse return error.InvalidPackageVersion; + defer if (comptime !for_publish) ctx.allocator.free(package_version); if (package_version.len == 0) return error.InvalidPackageVersion; + if (comptime for_publish) { + if (json.root.get("private")) |private| { + if (private.asBool()) |is_private| { + if (is_private) { + return error.PrivatePackage; + } + } + } + } + var this_bundler: bun.bundler.Bundler = undefined; _ = RunCommand.configureEnvForRun( @@ -1134,12 +1168,38 @@ pub const PackCommand = struct { const abs_workspace_path: string = strings.withoutTrailingSlash(strings.withoutSuffixComptime(abs_package_json_path, "package.json")); - const postpack_script: ?string = postpack_script: { + const postpack_script, const publish_script: ?[]const u8, const postpublish_script: ?[]const u8 = post_scripts: { // --ignore-scripts - if (!manager.options.do.run_scripts) break :postpack_script null; + if (!manager.options.do.run_scripts) break :post_scripts .{ null, null, null }; - const scripts = json.root.asProperty("scripts") orelse break :postpack_script null; - if (scripts.expr.data != .e_object) break :postpack_script null; + const scripts = json.root.asProperty("scripts") orelse break :post_scripts .{ null, null, null }; + if (scripts.expr.data != .e_object) break :post_scripts .{ null, null, null }; + + if (comptime for_publish) { + if (scripts.expr.get("prepublishOnly")) |prepublish_only_script_str| { + if (prepublish_only_script_str.asString(ctx.allocator)) |prepublish_only| { + _ = RunCommand.runPackageScriptForeground( + ctx.command_ctx, + ctx.allocator, + prepublish_only, + "prepublishOnly", + abs_workspace_path, + this_bundler.env, + &.{}, + manager.options.log_level == .silent, + ctx.command_ctx.debug.use_system_shell, + ) catch |err| { + switch (err) { + error.MissingShell => { + Output.errGeneric("failed to find shell executable to run prepublishOnly script", .{}); + Global.crash(); + }, + error.OutOfMemory => |oom| return oom, + } + }; + } + } + } if (scripts.expr.get("prepack")) |prepack_script| { if (prepack_script.asString(ctx.allocator)) |prepack_script_str| { @@ -1189,13 +1249,25 @@ pub const PackCommand = struct { } } + var postpack_script: ?[]const u8 = null; if (scripts.expr.get("postpack")) |postpack| { - if (postpack.asString(ctx.allocator)) |postpack_str| { - break :postpack_script postpack_str; - } + postpack_script = postpack.asString(ctx.allocator); } - break :postpack_script null; + if (comptime for_publish) { + var publish_script: ?[]const u8 = null; + var postpublish_script: ?[]const u8 = null; + if (scripts.expr.get("publish")) |publish| { + publish_script = try publish.asStringCloned(ctx.allocator); + } + if (scripts.expr.get("postpublish")) |postpublish| { + postpublish_script = try postpublish.asStringCloned(ctx.allocator); + } + + break :post_scripts .{ postpack_script, publish_script, postpublish_script }; + } + + break :post_scripts .{ postpack_script, null, null }; }; var root_dir = root_dir: { @@ -1211,8 +1283,8 @@ pub const PackCommand = struct { }; defer root_dir.close(); - ctx.bundled_deps = try getBundledDeps(ctx, json.root, "bundledDependencies") orelse - try getBundledDeps(ctx, json.root, "bundleDependencies") orelse + ctx.bundled_deps = try getBundledDeps(ctx.allocator, json.root, "bundledDependencies") orelse + try getBundledDeps(ctx.allocator, json.root, "bundleDependencies") orelse .{}; var pack_queue = pack_queue: { @@ -1225,7 +1297,7 @@ pub const PackCommand = struct { var files_array = _files_array; while (files_array.next()) |files_entry| { if (files_entry.asString(ctx.allocator)) |file_entry_str| { - const parsed = try Pattern.fromUTF8(ctx, file_entry_str) orelse continue; + const parsed = try Pattern.fromUTF8(ctx.allocator, file_entry_str) orelse continue; try includes.append(ctx.allocator, parsed); continue; } @@ -1234,7 +1306,7 @@ pub const PackCommand = struct { } break :pack_queue try iterateIncludedProjectTree( - ctx, + ctx.allocator, includes.items, root_dir, log_level, @@ -1248,7 +1320,7 @@ pub const PackCommand = struct { // pack from project root break :pack_queue try iterateProjectTree( - ctx, + ctx.allocator, root_dir, log_level, ); @@ -1266,15 +1338,23 @@ pub const PackCommand = struct { printArchivedFilesAndPackages(ctx, root_dir, true, &pack_queue, 0); - if (manager.options.pack_destination.len == 0) { - Output.pretty("\n{}\n", .{fmtTarballFilename(package_name, package_version)}); - } else { - var dest_buf: PathBuffer = undefined; - const abs_tarball_dest, _ = absTarballDestination(ctx, abs_workspace_path, package_name, package_version, &dest_buf); - Output.pretty("\n{s}\n", .{abs_tarball_dest}); + if (comptime !for_publish) { + if (manager.options.pack_destination.len == 0) { + Output.pretty("\n{}\n", .{fmtTarballFilename(package_name, package_version)}); + } else { + var dest_buf: PathBuffer = undefined; + const abs_tarball_dest, _ = absTarballDestination( + ctx.manager.options.pack_destination, + abs_workspace_path, + package_name, + package_version, + &dest_buf, + ); + Output.pretty("\n{s}\n", .{abs_tarball_dest}); + } } - ctx.printSummary(null, null, log_level); + Context.printSummary(ctx.stats, null, null, log_level); if (postpack_script) |postpack_script_str| { _ = RunCommand.runPackageScriptForeground( @@ -1297,10 +1377,37 @@ pub const PackCommand = struct { } }; } + + if (comptime for_publish) { + var dest_buf: bun.PathBuffer = undefined; + const abs_tarball_dest, _ = absTarballDestination( + ctx.manager.options.pack_destination, + abs_workspace_path, + package_name, + package_version, + &dest_buf, + ); + return .{ + .allocator = ctx.allocator, + .command_ctx = ctx.command_ctx, + .manager = manager, + .package_name = package_name, + .package_version = package_version, + .abs_tarball_path = try ctx.allocator.dupeZ(u8, abs_tarball_dest), + .tarball_bytes = "", + .shasum = undefined, + .integrity = undefined, + .uses_workspaces = false, + .publish_script = publish_script, + .postpublish_script = postpublish_script, + .script_env = this_bundler.env, + }; + } + return; } - const bins = try getPackageBins(ctx, json.root); + const bins = try getPackageBins(ctx.allocator, json.root); defer for (bins) |bin| ctx.allocator.free(bin.path); var print_buf = std.ArrayList(u8).init(ctx.allocator); @@ -1316,7 +1423,7 @@ pub const PackCommand = struct { }, else => {}, } - switch (archive.writeSetCompressionGzip()) { + switch (archive.writeAddFilterGzip()) { .failed, .fatal, .warn => { Output.errGeneric("failed to set archive compression to gzip: {s}", .{archive.errorString()}); Global.crash(); @@ -1337,6 +1444,14 @@ pub const PackCommand = struct { } print_buf.clearRetainingCapacity(); + switch (archive.writeSetFilterOption(null, "os", "Unknown")) { + .failed, .fatal, .warn => { + Output.errGeneric("failed to set os to `Unknown`: {s}", .{archive.errorString()}); + Global.crash(); + }, + else => {}, + } + switch (archive.writeSetOptions("gzip:!timestamp")) { .failed, .fatal, .warn => { Output.errGeneric("failed to unset gzip timestamp option: {s}", .{archive.errorString()}); @@ -1347,7 +1462,7 @@ pub const PackCommand = struct { var dest_buf: PathBuffer = undefined; const abs_tarball_dest, const abs_tarball_dest_dir_end = absTarballDestination( - ctx, + ctx.manager.options.pack_destination, abs_workspace_path, package_name, package_version, @@ -1385,7 +1500,7 @@ pub const PackCommand = struct { var entry = Archive.Entry.new2(archive); - const package_json_size = archive_with_progress: { + const package_json = archive_with_progress: { var progress: if (log_level == .silent) void else Progress = if (comptime log_level == .silent) {} else .{}; var node = if (comptime log_level == .silent) {} else node: { progress.supports_ansi_escape_codes = Output.enable_ansi_colors; @@ -1395,7 +1510,7 @@ pub const PackCommand = struct { }; defer if (comptime log_level != .silent) node.end(); - entry, const edited_package_json_size = try editAndArchivePackageJSON(ctx, archive, entry, root_dir, json); + entry, const edited_package_json = try editAndArchivePackageJSON(ctx, archive, entry, root_dir, json); if (comptime log_level != .silent) node.completeOne(); while (pack_queue.removeOrNull()) |pathname| { @@ -1461,7 +1576,7 @@ pub const PackCommand = struct { ); } - break :archive_with_progress edited_package_json_size; + break :archive_with_progress edited_package_json; }; entry.free(); @@ -1482,10 +1597,10 @@ pub const PackCommand = struct { else => {}, } - var sha1_digest: sha.SHA1.Digest = undefined; - var sha512_digest: sha.SHA512.Digest = undefined; + var shasum: sha.SHA1.Digest = undefined; + var integrity: sha.SHA512.Digest = undefined; - { + const tarball_bytes = tarball_bytes: { const tarball_file = File.open(abs_tarball_dest, bun.O.RDONLY, 0).unwrap() catch |err| { Output.err(err, "failed to open tarball at: \"{s}\"", .{abs_tarball_dest}); Global.crash(); @@ -1498,6 +1613,23 @@ pub const PackCommand = struct { var sha512 = sha.SHA512.init(); defer sha512.deinit(); + if (comptime for_publish) { + const tarball_bytes = tarball_file.readToEnd(ctx.allocator).unwrap() catch |err| { + Output.err(err, "failed to read tarball: \"{s}\"", .{abs_tarball_dest}); + Global.crash(); + }; + + sha1.update(tarball_bytes); + sha512.update(tarball_bytes); + + sha1.final(&shasum); + sha512.final(&integrity); + + ctx.stats.packed_size = tarball_bytes.len; + + break :tarball_bytes tarball_bytes; + } + file_reader.* = .{ .unbuffered_reader = tarball_file.reader(), }; @@ -1517,29 +1649,36 @@ pub const PackCommand = struct { }; } - sha1.final(&sha1_digest); - sha512.final(&sha512_digest); + sha1.final(&shasum); + sha512.final(&integrity); ctx.stats.packed_size = size; - } + }; printArchivedFilesAndPackages( ctx, root_dir, false, pack_list, - package_json_size, + package_json.len, ); - if (manager.options.pack_destination.len == 0) { - Output.pretty("\n{}\n", .{fmtTarballFilename(package_name, package_version)}); - } else { - Output.pretty("\n{s}\n", .{abs_tarball_dest}); + if (comptime !for_publish) { + if (manager.options.pack_destination.len == 0) { + Output.pretty("\n{}\n", .{fmtTarballFilename(package_name, package_version)}); + } else { + Output.pretty("\n{s}\n", .{abs_tarball_dest}); + } } - ctx.printSummary(sha1_digest, sha512_digest, log_level); + Context.printSummary(ctx.stats, shasum, integrity, log_level); + + if (comptime for_publish) { + Output.flush(); + } if (postpack_script) |postpack_script_str| { + Output.pretty("\n", .{}); _ = RunCommand.runPackageScriptForeground( ctx.command_ctx, ctx.allocator, @@ -1560,10 +1699,28 @@ pub const PackCommand = struct { } }; } + + if (comptime for_publish) { + return .{ + .allocator = ctx.allocator, + .command_ctx = ctx.command_ctx, + .manager = manager, + .package_name = package_name, + .package_version = package_version, + .abs_tarball_path = try ctx.allocator.dupeZ(u8, abs_tarball_dest), + .tarball_bytes = tarball_bytes, + .shasum = shasum, + .integrity = integrity, + .uses_workspaces = false, + .publish_script = publish_script, + .postpublish_script = postpublish_script, + .script_env = this_bundler.env, + }; + } } fn absTarballDestination( - ctx: *Context, + pack_destination: string, abs_workspace_path: string, package_name: string, package_version: string, @@ -1572,7 +1729,7 @@ pub const PackCommand = struct { const tarball_destination_dir = bun.path.joinAbsStringBuf( abs_workspace_path, dest_buf, - &.{ctx.manager.options.pack_destination}, + &.{pack_destination}, .auto, ); @@ -1634,8 +1791,8 @@ pub const PackCommand = struct { entry: *Archive.Entry, root_dir: std.fs.Dir, json: *PackageManager.WorkspacePackageJSONCache.MapEntry, - ) OOM!struct { *Archive.Entry, usize } { - const edited_package_json = try editRootPackageJSON(ctx, json); + ) OOM!struct { *Archive.Entry, string } { + const edited_package_json = try editRootPackageJSON(ctx.allocator, ctx.lockfile, json); const stat = bun.sys.fstatat(bun.toFD(root_dir), "package.json").unwrap() catch |err| { Output.err(err, "failed to stat package.json", .{}); @@ -1661,7 +1818,7 @@ pub const PackCommand = struct { ctx.stats.unpacked_size += @intCast(archive.writeData(edited_package_json)); - return .{ entry.clear(), edited_package_json.len }; + return .{ entry.clear(), edited_package_json }; } fn addArchiveEntry( @@ -1693,11 +1850,7 @@ pub const PackCommand = struct { var perm: bun.Mode = @intCast(stat.mode); // https://github.com/npm/cli/blob/ec105f400281a5bfd17885de1ea3d54d0c231b27/node_modules/pacote/lib/util/tar-create-options.js#L20 - if (comptime !Environment.isWindows) { - // on windows we create a shim executable. the bin file permissions - // do not need to change - if (isPackageBin(bins, filename)) perm |= 0o111; - } + if (isPackageBin(bins, filename)) perm |= 0o111; entry.setPerm(@intCast(perm)); // '1985-10-26T08:15:00.000Z' @@ -1734,7 +1887,8 @@ pub const PackCommand = struct { /// Strip workspace protocols from dependency versions then /// returns the printed json fn editRootPackageJSON( - ctx: *Context, + allocator: std.mem.Allocator, + maybe_lockfile: ?*Lockfile, json: *PackageManager.WorkspacePackageJSONCache.MapEntry, ) OOM!string { for ([_]string{ @@ -1750,7 +1904,7 @@ pub const PackCommand = struct { if (dependency.key == null) continue; if (dependency.value == null) continue; - const package_spec = dependency.value.?.asString(ctx.allocator) orelse continue; + const package_spec = dependency.value.?.asString(allocator) orelse continue; if (strings.withoutPrefixIfPossibleComptime(package_spec, "workspace:")) |without_workspace_protocol| { // TODO: make semver parsing more strict. `^`, `~` are not valid @@ -1771,7 +1925,7 @@ pub const PackCommand = struct { // TODO: this might be too strict const c = without_workspace_protocol[0]; if (c == '^' or c == '~' or c == '*') { - const dependency_name = dependency.key.?.asString(ctx.allocator) orelse { + const dependency_name = dependency.key.?.asString(allocator) orelse { Output.errGeneric("expected string value for dependency name in \"{s}\"", .{ dependency_group, }); @@ -1780,15 +1934,15 @@ pub const PackCommand = struct { failed_to_resolve: { // find the current workspace version and append to package spec without `workspace:` - const lockfile = ctx.lockfile orelse break :failed_to_resolve; + const lockfile = maybe_lockfile orelse break :failed_to_resolve; const workspace_version = lockfile.workspace_versions.get(Semver.String.Builder.stringHash(dependency_name)) orelse break :failed_to_resolve; dependency.value = Expr.allocate( - ctx.manager.allocator, + allocator, E.String, .{ - .data = try std.fmt.allocPrint(ctx.allocator, "{s}{}", .{ + .data = try std.fmt.allocPrint(allocator, "{s}{}", .{ switch (c) { '^' => "^", '~' => "~", @@ -1814,10 +1968,10 @@ pub const PackCommand = struct { } dependency.value = Expr.allocate( - ctx.manager.allocator, + allocator, E.String, .{ - .data = try ctx.allocator.dupe(u8, without_workspace_protocol), + .data = try allocator.dupe(u8, without_workspace_protocol), }, .{}, ); @@ -1830,8 +1984,8 @@ pub const PackCommand = struct { } const has_trailing_newline = json.source.contents.len > 0 and json.source.contents[json.source.contents.len - 1] == '\n'; - var buffer_writer = try js_printer.BufferWriter.init(ctx.allocator); - try buffer_writer.buffer.list.ensureTotalCapacity(ctx.allocator, json.source.contents.len + 1); + var buffer_writer = try js_printer.BufferWriter.init(allocator); + try buffer_writer.buffer.list.ensureTotalCapacity(allocator, json.source.contents.len + 1); buffer_writer.append_newline = has_trailing_newline; var package_json_writer = js_printer.BufferPrinter.init(buffer_writer); @@ -1872,7 +2026,7 @@ pub const PackCommand = struct { @"leading **/": bool, - pub fn fromUTF8(ctx: *Context, pattern: string) OOM!?Pattern { + pub fn fromUTF8(allocator: std.mem.Allocator, pattern: string) OOM!?Pattern { var remain = pattern; var @"has leading **/, (could start with '!')" = false; const has_leading_or_middle_slash, const has_trailing_slash, const add_negate = check_slashes: { @@ -1915,10 +2069,10 @@ pub const PackCommand = struct { }; const length = bun.simdutf.length.utf32.from.utf8.le(remain) + @intFromBool(add_negate); - const buf = try ctx.allocator.alloc(u32, length); + const buf = try allocator.alloc(u32, length); const result = bun.simdutf.convert.utf8.to.utf32.with_errors.le(remain, buf[@intFromBool(add_negate)..]); if (!result.isSuccessful()) { - ctx.allocator.free(buf); + allocator.free(buf); return null; } @@ -1982,9 +2136,9 @@ pub const PackCommand = struct { } // ignore files are always ignored, don't need to worry about opening or reading twice - pub fn readFromDisk(ctx: *Context, dir: std.fs.Dir, dir_depth: usize) OOM!?IgnorePatterns { + pub fn readFromDisk(allocator: std.mem.Allocator, dir: std.fs.Dir, dir_depth: usize) OOM!?IgnorePatterns { var patterns: std.ArrayListUnmanaged(Pattern) = .{}; - errdefer patterns.deinit(ctx.allocator); + errdefer patterns.deinit(allocator); var ignore_kind: Kind = .@".npmignore"; @@ -2005,10 +2159,10 @@ pub const PackCommand = struct { }; defer ignore_file.close(); - const contents = File.from(ignore_file).readToEnd(ctx.allocator).unwrap() catch |err| { + const contents = File.from(ignore_file).readToEnd(allocator).unwrap() catch |err| { ignoreFileFail(dir, ignore_kind, .read, err); }; - defer ctx.allocator.free(contents); + defer allocator.free(contents); var has_rel_path = false; @@ -2030,8 +2184,8 @@ pub const PackCommand = struct { if (trimmed.len == 0) continue; - const parsed = try Pattern.fromUTF8(ctx, trimmed) orelse continue; - try patterns.append(ctx.allocator, parsed); + const parsed = try Pattern.fromUTF8(allocator, trimmed) orelse continue; + try patterns.append(allocator, parsed); has_rel_path = has_rel_path or parsed.rel_path; } @@ -2190,39 +2344,64 @@ pub const bindings = struct { var entries_info = std.ArrayList(EntryInfo).init(bun.default_allocator); defer entries_info.deinit(); - const archive = libarchive.archive_read_new(); - defer { - _ = libarchive.archive_read_close(archive); - _ = libarchive.archive_read_free(archive); + const archive = Archive.readNew(); + + switch (archive.readSupportFormatTar()) { + .failed, .fatal, .warn => { + global.throw("failed to support tar: {s}", .{archive.errorString()}); + return .zero; + }, + else => {}, + } + switch (archive.readSupportFormatGnutar()) { + .failed, .fatal, .warn => { + global.throw("failed to support gnutar: {s}", .{archive.errorString()}); + return .zero; + }, + else => {}, + } + switch (archive.readSupportFilterGzip()) { + .failed, .fatal, .warn => { + global.throw("failed to support gzip compression: {s}", .{archive.errorString()}); + return .zero; + }, + else => {}, } - _ = libarchive.archive_read_support_format_tar(archive); - _ = libarchive.archive_read_support_format_gnutar(archive); - _ = libarchive.archive_read_support_compression_gzip(archive); + switch (archive.readSetOptions("read_concatenated_archives")) { + .failed, .fatal, .warn => { + global.throw("failed to set read_concatenated_archives option: {s}", .{archive.errorString()}); + return .zero; + }, + else => {}, + } - _ = libarchive.archive_read_set_options(archive, "read_concatenated_archives"); + switch (archive.readOpenMemory(tarball)) { + .failed, .fatal, .warn => { + global.throw("failed to open archive in memory: {s}", .{archive.errorString()}); + return .zero; + }, + else => {}, + } - _ = libarchive.archive_read_open_memory(archive, tarball.ptr, tarball.len); - - var archive_entry: *libarchive.archive_entry = undefined; - - var header_status: Archive.Result = @enumFromInt(libarchive.archive_read_next_header(archive, &archive_entry)); + var archive_entry: *Archive.Entry = undefined; + var header_status = archive.readNextHeader(&archive_entry); var read_buf = std.ArrayList(u8).init(bun.default_allocator); defer read_buf.deinit(); - while (header_status != .eof) : (header_status = @enumFromInt(libarchive.archive_read_next_header(archive, &archive_entry))) { + while (header_status != .eof) : (header_status = archive.readNextHeader(&archive_entry)) { switch (header_status) { .eof => unreachable, .retry => continue, .failed, .fatal => { - global.throw("failed to read next archive header: {s}", .{Archive.errorString(@ptrCast(archive))}); + global.throw("failed to read archive header: {s}", .{Archive.errorString(@ptrCast(archive))}); return .zero; }, else => { - const pathname = std.mem.sliceTo(libarchive.archive_entry_pathname(archive_entry), 0); - const kind = bun.C.kindFromMode(libarchive.archive_entry_filetype(archive_entry)); - const perm = libarchive.archive_entry_perm(archive_entry); + const pathname = archive_entry.pathname(); + const kind = bun.C.kindFromMode(archive_entry.filetype()); + const perm = archive_entry.perm(); var entry_info: EntryInfo = .{ .pathname = String.createUTF8(pathname), @@ -2231,11 +2410,11 @@ pub const bindings = struct { }; if (kind == .file) { - const size: usize = @intCast(libarchive.archive_entry_size(archive_entry)); - read_buf.ensureTotalCapacity(size) catch bun.outOfMemory(); + const size: usize = @intCast(archive_entry.size()); + read_buf.resize(size) catch bun.outOfMemory(); defer read_buf.clearRetainingCapacity(); - const read = libarchive.archive_read_data(archive, read_buf.items.ptr, size); + const read = archive.readData(read_buf.items); if (read < 0) { global.throw("failed to read archive entry \"{}\": {s}", .{ bun.fmt.fmtPath(u8, pathname, .{}), @@ -2252,6 +2431,21 @@ pub const bindings = struct { } } + switch (archive.readClose()) { + .failed, .fatal, .warn => { + global.throw("failed to close read archive: {s}", .{archive.errorString()}); + return .zero; + }, + else => {}, + } + switch (archive.readFree()) { + .failed, .fatal, .warn => { + global.throw("failed to close read archive: {s}", .{archive.errorString()}); + return .zero; + }, + else => {}, + } + const entries = JSArray.createEmpty(global, entries_info.items.len); for (entries_info.items, 0..) |entry, i| { diff --git a/src/cli/package_manager_command.zig b/src/cli/package_manager_command.zig index 90c2cb11d3..f1ce6f4ad8 100644 --- a/src/cli/package_manager_command.zig +++ b/src/cli/package_manager_command.zig @@ -24,7 +24,7 @@ const UntrustedCommand = @import("./pm_trusted_command.zig").UntrustedCommand; const TrustCommand = @import("./pm_trusted_command.zig").TrustCommand; const DefaultTrustedCommand = @import("./pm_trusted_command.zig").DefaultTrustedCommand; const Environment = bun.Environment; -const PackCommand = @import("./pack_command.zig").PackCommand; +pub const PackCommand = @import("./pack_command.zig").PackCommand; const ByName = struct { dependencies: []const Dependency, diff --git a/src/cli/pm_trusted_command.zig b/src/cli/pm_trusted_command.zig index 159aad49b8..ae9a57a2d1 100644 --- a/src/cli/pm_trusted_command.zig +++ b/src/cli/pm_trusted_command.zig @@ -370,7 +370,7 @@ pub const TrustCommand = struct { const package_json_source = logger.Source.initPathString(PackageManager.package_json_cwd, package_json_contents); - var package_json = bun.JSON.ParseJSONUTF8(&package_json_source, ctx.log, ctx.allocator) catch |err| { + var package_json = bun.JSON.parseUTF8(&package_json_source, ctx.log, ctx.allocator) catch |err| { switch (Output.enable_ansi_colors) { inline else => |enable_ansi_colors| ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}, } diff --git a/src/cli/publish_command.zig b/src/cli/publish_command.zig new file mode 100644 index 0000000000..3ffe09ed41 --- /dev/null +++ b/src/cli/publish_command.zig @@ -0,0 +1,1074 @@ +const std = @import("std"); +const bun = @import("root").bun; +const Command = bun.CLI.Command; +const Output = bun.Output; +const Global = bun.Global; +const http = bun.http; +const OOM = bun.OOM; +const Headers = http.Headers; +const HeaderBuilder = http.HeaderBuilder; +const MutableString = bun.MutableString; +const URL = bun.URL; +const install = bun.install; +const PackageManager = install.PackageManager; +const strings = bun.strings; +const string = bun.string; +const stringZ = bun.stringZ; +const File = bun.sys.File; +const JSON = bun.JSON; +const sha = bun.sha; +const path = bun.path; +const FileSystem = bun.fs.FileSystem; +const Environment = bun.Environment; +const Archive = bun.libarchive.lib.Archive; +const logger = bun.logger; +const Dependency = install.Dependency; +const Pack = bun.CLI.PackCommand; +const Lockfile = install.Lockfile; +const MimeType = http.MimeType; +const Expr = bun.js_parser.Expr; +const prompt = bun.CLI.InitCommand.prompt; +const Npm = install.Npm; +const Run = bun.CLI.RunCommand; +const DotEnv = bun.DotEnv; +const Open = @import("../open.zig"); + +pub const PublishCommand = struct { + pub fn Context(comptime directory_publish: bool) type { + return struct { + manager: *PackageManager, + allocator: std.mem.Allocator, + command_ctx: Command.Context, + + package_name: string, + package_version: string, + abs_tarball_path: stringZ, + tarball_bytes: string, + shasum: sha.SHA1.Digest, + integrity: sha.SHA512.Digest, + uses_workspaces: bool, + + publish_script: if (directory_publish) ?[]const u8 else void = if (directory_publish) null else {}, + postpublish_script: if (directory_publish) ?[]const u8 else void = if (directory_publish) null else {}, + script_env: if (directory_publish) *DotEnv.Loader else void, + + const FromTarballError = OOM || error{ + MissingPackageJSON, + InvalidPackageJSON, + MissingPackageName, + MissingPackageVersion, + InvalidPackageName, + InvalidPackageVersion, + PrivatePackage, + RestrictedUnscopedPackage, + }; + + /// Retrieve information for publishing from a tarball path, `bun publish path/to/tarball.tgz` + pub fn fromTarballPath( + ctx: Command.Context, + manager: *PackageManager, + tarball_path: string, + ) FromTarballError!Context(directory_publish) { + var abs_buf: bun.PathBuffer = undefined; + const abs_tarball_path = path.joinAbsStringBufZ( + FileSystem.instance.top_level_dir, + &abs_buf, + &[_]string{tarball_path}, + .auto, + ); + + const tarball_bytes = File.readFrom(bun.invalid_fd, abs_tarball_path, ctx.allocator).unwrap() catch |err| { + Output.err(err, "failed to read tarball: '{s}'", .{tarball_path}); + Global.crash(); + }; + + var maybe_package_json_contents: ?[]const u8 = null; + + var iter = switch (Archive.Iterator.init(tarball_bytes)) { + .err => |err| { + Output.errGeneric("{s}: {s}", .{ + err.message, + err.archive.errorString(), + }); + + Global.crash(); + }, + .result => |res| res, + }; + + var unpacked_size: usize = 0; + var total_files: usize = 0; + + Output.print("\n", .{}); + + while (switch (iter.next()) { + .err => |err| { + Output.errGeneric("{s}: {s}", .{ err.message, err.archive.errorString() }); + Global.crash(); + }, + .result => |res| res, + }) |next| { + const pathname = if (comptime Environment.isWindows) + next.entry.pathnameW() + else + next.entry.pathname(); + + const size = next.entry.size(); + + unpacked_size += @intCast(@max(0, size)); + total_files += @intFromBool(next.kind == .file); + + // this is option `strip: 1` (npm expects a `package/` prefix for all paths) + if (strings.indexOfAnyT(bun.OSPathChar, pathname, "/\\")) |slash| { + const stripped = pathname[slash + 1 ..]; + if (stripped.len == 0) continue; + + Output.pretty("packed {} {}\n", .{ + bun.fmt.size(size, .{ .space_between_number_and_unit = false }), + bun.fmt.fmtOSPath(stripped, .{}), + }); + + if (next.kind != .file) continue; + + if (strings.indexOfAnyT(bun.OSPathChar, stripped, "/\\") == null) { + + // check for package.json, readme.md, ... + const filename = pathname[slash + 1 ..]; + + if (maybe_package_json_contents == null and strings.eqlCaseInsensitiveT(bun.OSPathChar, filename, "package.json")) { + maybe_package_json_contents = switch (try next.readEntryData(ctx.allocator, iter.archive)) { + .err => |err| { + Output.errGeneric("{s}: {s}", .{ err.message, err.archive.errorString() }); + Global.crash(); + }, + .result => |bytes| bytes, + }; + } + } + } else { + Output.pretty("packed {} {}\n", .{ + bun.fmt.size(size, .{ .space_between_number_and_unit = false }), + bun.fmt.fmtOSPath(pathname, .{}), + }); + } + } + + switch (iter.deinit()) { + .err => |err| { + Output.errGeneric("{s}: {s}", .{ err.message, err.archive.errorString() }); + Global.crash(); + }, + .result => {}, + } + + const package_json_contents = maybe_package_json_contents orelse return error.MissingPackageJSON; + + const package_name, const package_version = package_info: { + defer ctx.allocator.free(package_json_contents); + + const source = logger.Source.initPathString("package.json", package_json_contents); + const json = JSON.parsePackageJSONUTF8(&source, manager.log, ctx.allocator) catch |err| { + return switch (err) { + error.OutOfMemory => |oom| return oom, + else => error.InvalidPackageJSON, + }; + }; + + if (json.get("private")) |private| { + if (private.asBool()) |is_private| { + if (is_private) { + return error.PrivatePackage; + } + } + } + + if (json.get("publishConfig")) |config| { + if (manager.options.publish_config.tag.len == 0) { + if (try config.getStringCloned(ctx.allocator, "tag")) |tag| { + manager.options.publish_config.tag = tag; + } + } + + if (manager.options.publish_config.access == null) { + if (try config.getString(ctx.allocator, "access")) |access| { + manager.options.publish_config.access = PackageManager.Options.Access.fromStr(access[0]) orelse { + Output.errGeneric("invalid `access` value: '{s}'", .{access[0]}); + Global.crash(); + }; + } + } + + // maybe otp + } + + const name = try json.getStringCloned(ctx.allocator, "name") orelse return error.MissingPackageName; + const is_scoped = try Dependency.isScopedPackageName(name); + + if (manager.options.publish_config.access) |access| { + if (access == .restricted and !is_scoped) { + return error.RestrictedUnscopedPackage; + } + } + + const version = try json.getStringCloned(ctx.allocator, "version") orelse return error.MissingPackageVersion; + if (version.len == 0) return error.InvalidPackageVersion; + + break :package_info .{ name, version }; + }; + + var shasum: sha.SHA1.Digest = undefined; + var sha1 = sha.SHA1.init(); + defer sha1.deinit(); + + sha1.update(tarball_bytes); + sha1.final(&shasum); + + var integrity: sha.SHA512.Digest = undefined; + var sha512 = sha.SHA512.init(); + defer sha512.deinit(); + + sha512.update(tarball_bytes); + sha512.final(&integrity); + + Pack.Context.printSummary( + .{ + .total_files = total_files, + .unpacked_size = unpacked_size, + .packed_size = tarball_bytes.len, + }, + shasum, + integrity, + manager.options.log_level, + ); + + return .{ + .manager = manager, + .allocator = ctx.allocator, + .package_name = package_name, + .package_version = package_version, + .abs_tarball_path = try ctx.allocator.dupeZ(u8, abs_tarball_path), + .tarball_bytes = tarball_bytes, + .shasum = shasum, + .integrity = integrity, + .uses_workspaces = false, + .command_ctx = ctx, + .script_env = {}, + }; + } + + const FromWorkspaceError = Pack.PackError(true); + + /// `bun publish` without a tarball path. Automatically pack the current workspace and get + /// information required for publishing + pub fn fromWorkspace( + ctx: Command.Context, + manager: *PackageManager, + ) FromWorkspaceError!Context(directory_publish) { + var lockfile: Lockfile = undefined; + const load_from_disk_result = lockfile.loadFromDisk( + manager, + manager.allocator, + manager.log, + manager.options.lockfile_path, + false, + ); + + var pack_ctx: Pack.Context = .{ + .allocator = ctx.allocator, + .manager = manager, + .command_ctx = ctx, + .lockfile = switch (load_from_disk_result) { + .ok => |ok| ok.lockfile, + .not_found => null, + .err => |cause| err: { + switch (cause.step) { + .open_file => { + if (cause.value == error.ENOENT) break :err null; + Output.errGeneric("failed to open lockfile: {s}", .{@errorName(cause.value)}); + }, + .parse_file => { + Output.errGeneric("failed to parse lockfile: {s}", .{@errorName(cause.value)}); + }, + .read_file => { + Output.errGeneric("failed to read lockfile: {s}", .{@errorName(cause.value)}); + }, + .migrating => { + Output.errGeneric("failed to migrate lockfile: {s}", .{@errorName(cause.value)}); + }, + } + + if (manager.log.hasErrors()) { + switch (Output.enable_ansi_colors) { + inline else => |enable_ansi_colors| { + manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; + }, + } + } + + Global.crash(); + }, + }, + }; + + return switch (manager.options.log_level) { + inline else => |log_level| Pack.pack(&pack_ctx, manager.original_package_json_path, log_level, true), + }; + } + }; + } + + pub fn exec(ctx: Command.Context) !void { + Output.prettyln("bun publish v" ++ Global.package_json_version_with_sha ++ "", .{}); + Output.flush(); + + const cli = try PackageManager.CommandLineArguments.parse(ctx.allocator, .publish); + + const manager, const original_cwd = PackageManager.init(ctx, cli, .publish) catch |err| { + if (!cli.silent) { + if (err == error.MissingPackageJSON) { + Output.errGeneric("missing package.json, nothing to publish", .{}); + } + Output.errGeneric("failed to initialize bun install: {s}", .{@errorName(err)}); + } + Global.crash(); + }; + defer ctx.allocator.free(original_cwd); + + if (cli.positionals.len > 1) { + const context = Context(false).fromTarballPath(ctx, manager, cli.positionals[1]) catch |err| { + switch (err) { + error.OutOfMemory => bun.outOfMemory(), + error.MissingPackageName => { + Output.errGeneric("missing `name` string in package.json", .{}); + }, + error.MissingPackageVersion => { + Output.errGeneric("missing `version` string in package.json", .{}); + }, + error.InvalidPackageName, error.InvalidPackageVersion => { + Output.errGeneric("package.json `name` and `version` fields must be non-empty strings", .{}); + }, + error.MissingPackageJSON => { + Output.errGeneric("failed to find package.json in tarball '{s}'", .{cli.positionals[1]}); + }, + error.InvalidPackageJSON => { + switch (Output.enable_ansi_colors) { + inline else => |enable_ansi_colors| { + manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; + }, + } + Output.errGeneric("failed to parse tarball package.json", .{}); + }, + error.PrivatePackage => { + Output.errGeneric("attempted to publish a private package", .{}); + }, + error.RestrictedUnscopedPackage => { + Output.errGeneric("unable to restrict access to unscoped package", .{}); + }, + } + Global.crash(); + }; + + publish(false, &context) catch |err| { + switch (err) { + error.OutOfMemory => bun.outOfMemory(), + error.NeedAuth => { + Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); + Global.crash(); + }, + } + }; + + Output.prettyln("\n + {s}@{s}{s}", .{ + context.package_name, + Dependency.withoutBuildTag(context.package_version), + if (manager.options.dry_run) " (dry-run)" else "", + }); + + return; + } + + const context = Context(true).fromWorkspace(ctx, manager) catch |err| { + switch (err) { + error.OutOfMemory => bun.outOfMemory(), + error.MissingPackageName => { + Output.errGeneric("missing `name` string in package.json", .{}); + }, + error.MissingPackageVersion => { + Output.errGeneric("missing `version` string in package.json", .{}); + }, + error.InvalidPackageName, error.InvalidPackageVersion => { + Output.errGeneric("package.json `name` and `version` fields must be non-empty strings", .{}); + }, + error.MissingPackageJSON => { + Output.errGeneric("failed to find package.json from: '{s}'", .{FileSystem.instance.top_level_dir}); + }, + error.RestrictedUnscopedPackage => { + Output.errGeneric("unable to restrict access to unscoped package", .{}); + }, + error.PrivatePackage => { + Output.errGeneric("attempted to publish a private package", .{}); + }, + } + Global.crash(); + }; + + // TODO: read this into memory + _ = bun.sys.unlink(context.abs_tarball_path); + + publish(true, &context) catch |err| { + switch (err) { + error.OutOfMemory => bun.outOfMemory(), + error.NeedAuth => { + Output.errGeneric("missing authentication (run `bunx npm login`)", .{}); + Global.crash(); + }, + } + }; + + Output.prettyln("\n + {s}@{s}{s}", .{ + context.package_name, + Dependency.withoutBuildTag(context.package_version), + if (manager.options.dry_run) " (dry-run)" else "", + }); + + if (manager.options.do.run_scripts) { + const abs_workspace_path: string = strings.withoutTrailingSlash(strings.withoutSuffixComptime(manager.original_package_json_path, "package.json")); + if (context.publish_script) |publish_script| { + _ = Run.runPackageScriptForeground( + context.command_ctx, + context.allocator, + publish_script, + "publish", + abs_workspace_path, + context.script_env, + &.{}, + context.manager.options.log_level == .silent, + context.command_ctx.debug.use_system_shell, + ) catch |err| { + switch (err) { + error.MissingShell => { + Output.errGeneric("failed to find shell executable to run publish script", .{}); + Global.crash(); + }, + error.OutOfMemory => |oom| return oom, + } + }; + } + + if (context.postpublish_script) |postpublish_script| { + _ = Run.runPackageScriptForeground( + context.command_ctx, + context.allocator, + postpublish_script, + "postpublish", + abs_workspace_path, + context.script_env, + &.{}, + context.manager.options.log_level == .silent, + context.command_ctx.debug.use_system_shell, + ) catch |err| { + switch (err) { + error.MissingShell => { + Output.errGeneric("failed to find shell executable to run postpublish script", .{}); + Global.crash(); + }, + error.OutOfMemory => |oom| return oom, + } + }; + } + } + } + + const PublishError = OOM || error{ + NeedAuth, + }; + + pub fn publish( + comptime directory_publish: bool, + ctx: *const Context(directory_publish), + ) PublishError!void { + const registry = ctx.manager.scopeForPackageName(ctx.package_name); + + if (registry.token.len == 0 and (registry.url.password.len == 0 or registry.url.username.len == 0)) { + return error.NeedAuth; + } + + // continues from `printSummary` + Output.pretty( + \\Tag: {s} + \\Access: {s} + \\Registry: {s} + \\ + , .{ + if (ctx.manager.options.publish_config.tag.len > 0) ctx.manager.options.publish_config.tag else "latest", + if (ctx.manager.options.publish_config.access) |access| @tagName(access) else "default", + registry.url.href, + }); + + // dry-run stops here + if (ctx.manager.options.dry_run) return; + + const publish_req_body = try constructPublishRequestBody(directory_publish, ctx, registry); + + var print_buf: std.ArrayListUnmanaged(u8) = .{}; + defer print_buf.deinit(ctx.allocator); + var print_writer = print_buf.writer(ctx.allocator); + + const publish_headers = try constructPublishHeaders( + ctx.allocator, + &print_buf, + registry, + publish_req_body.len, + if (ctx.manager.options.publish_config.otp.len > 0) ctx.manager.options.publish_config.otp else null, + ctx.uses_workspaces, + ctx.manager.options.publish_config.auth_type, + ); + + var response_buf = try MutableString.init(ctx.allocator, 1024); + + try print_writer.print("{s}/{s}", .{ + strings.withoutTrailingSlash(registry.url.href), + bun.fmt.dependencyUrl(ctx.package_name), + }); + const publish_url = URL.parse(try ctx.allocator.dupe(u8, print_buf.items)); + print_buf.clearRetainingCapacity(); + + var req = http.AsyncHTTP.initSync( + ctx.allocator, + .PUT, + publish_url, + publish_headers.entries, + publish_headers.content.ptr.?[0..publish_headers.content.len], + &response_buf, + publish_req_body, + null, + null, + .follow, + ); + + const res = req.sendSync() catch |err| { + switch (err) { + error.OutOfMemory => |oom| return oom, + else => { + Output.err(err, "failed to publish package", .{}); + Global.crash(); + }, + } + }; + + switch (res.status_code) { + 400...std.math.maxInt(@TypeOf(res.status_code)) => { + const prompt_for_otp = prompt_for_otp: { + if (res.status_code != 401) break :prompt_for_otp false; + + if (authenticate: { + for (res.headers) |header| { + if (strings.eqlCaseInsensitiveASCII(header.name, "www-authenticate", true)) { + break :authenticate header.value; + } + } + break :authenticate null; + }) |@"www-authenticate"| { + var iter = strings.split(@"www-authenticate", ","); + while (iter.next()) |part| { + const trimmed = strings.trim(part, &strings.whitespace_chars); + if (strings.eqlCaseInsensitiveASCII(trimmed, "ipaddress", true)) { + Output.errGeneric("login is not allowed from your IP address", .{}); + Global.crash(); + } else if (strings.eqlCaseInsensitiveASCII(trimmed, "otp", true)) { + break :prompt_for_otp true; + } + } + + Output.errGeneric("unable to authenticate, need: {s}", .{@"www-authenticate"}); + Global.crash(); + } else if (strings.containsComptime(response_buf.list.items, "one-time pass")) { + // missing www-authenticate header but one-time pass is still included + break :prompt_for_otp true; + } + + break :prompt_for_otp false; + }; + + if (!prompt_for_otp) { + // general error + return handleResponseErrors(directory_publish, ctx, &req, &res, &response_buf, true); + } + + const otp = try getOTP(directory_publish, ctx, registry, &response_buf, &print_buf); + + const otp_headers = try constructPublishHeaders( + ctx.allocator, + &print_buf, + registry, + publish_req_body.len, + otp, + ctx.uses_workspaces, + ctx.manager.options.publish_config.auth_type, + ); + + response_buf.reset(); + + var otp_req = http.AsyncHTTP.initSync( + ctx.allocator, + .PUT, + publish_url, + otp_headers.entries, + otp_headers.content.ptr.?[0..otp_headers.content.len], + &response_buf, + publish_req_body, + null, + null, + .follow, + ); + + const otp_res = otp_req.sendSync() catch |err| { + switch (err) { + error.OutOfMemory => |oom| return oom, + else => { + Output.err(err, "failed to publish package", .{}); + Global.crash(); + }, + } + }; + + switch (otp_res.status_code) { + 400...std.math.maxInt(@TypeOf(otp_res.status_code)) => { + return handleResponseErrors(directory_publish, ctx, &otp_req, &otp_res, &response_buf, true); + }, + else => {}, + } + }, + else => {}, + } + } + + fn handleResponseErrors( + comptime directory_publish: bool, + ctx: *const Context(directory_publish), + req: *const http.AsyncHTTP, + res: *const bun.picohttp.Response, + response_body: *MutableString, + comptime check_for_success: bool, + ) OOM!void { + const message = message: { + const source = logger.Source.initPathString("???", response_body.list.items); + const json = JSON.parseUTF8(&source, ctx.manager.log, ctx.allocator) catch |err| { + switch (err) { + error.OutOfMemory => |oom| return oom, + else => break :message null, + } + }; + + if (comptime check_for_success) { + if (json.get("success")) |success_expr| { + if (success_expr.asBool()) |successful| { + if (successful) { + // possible to hit this with otp responses + return; + } + } + } + } + + const @"error", _ = try json.getString(ctx.allocator, "error") orelse break :message null; + break :message @"error"; + }; + + Output.prettyErrorln("\n{d}{s}{s}: {s}\n{s}{s}", .{ + res.status_code, + if (res.status.len > 0) " " else "", + res.status, + bun.fmt.redactedNpmUrl(req.url.href), + if (message != null) "\n - " else "", + message orelse "", + }); + Global.crash(); + } + + const GetOTPError = OOM || error{}; + + fn pressEnterToOpenInBrowser(auth_url: stringZ) void { + // unset `ENABLE_VIRTUAL_TERMINAL_INPUT` on windows. This prevents backspace from + // deleting the entire line + const original_mode: if (Environment.isWindows) ?bun.windows.DWORD else void = if (comptime Environment.isWindows) + bun.win32.unsetStdioModeFlags(0, bun.windows.ENABLE_VIRTUAL_TERMINAL_INPUT) catch null + else {}; + + defer if (comptime Environment.isWindows) { + if (original_mode) |mode| { + _ = bun.windows.SetConsoleMode(bun.win32.STDIN_FD.cast(), mode); + } + }; + + while ('\n' != Output.buffered_stdin.reader().readByte() catch return) {} + + var child = std.process.Child.init(&.{ Open.opener, auth_url }, bun.default_allocator); + _ = child.spawnAndWait() catch return; + } + + fn getOTP( + comptime directory_publish: bool, + ctx: *const Context(directory_publish), + registry: *const Npm.Registry.Scope, + response_buf: *MutableString, + print_buf: *std.ArrayListUnmanaged(u8), + ) GetOTPError![]const u8 { + const res_source = logger.Source.initPathString("???", response_buf.list.items); + + if (JSON.parseUTF8(&res_source, ctx.manager.log, ctx.allocator) catch |err| res_json: { + switch (err) { + error.OutOfMemory => |oom| return oom, + + // https://github.com/npm/cli/blob/63d6a732c3c0e9c19fd4d147eaa5cc27c29b168d/node_modules/npm-registry-fetch/lib/check-response.js#L65 + // invalid json is ignored + else => break :res_json null, + } + }) |json| try_web: { + const auth_url_str = try json.getStringClonedZ(ctx.allocator, "authUrl") orelse break :try_web; + + // important to clone because it belongs to `response_buf`, and `response_buf` will be + // reused with the following requests + const done_url_str = try json.getStringCloned(ctx.allocator, "doneUrl") orelse break :try_web; + const done_url = URL.parse(done_url_str); + + Output.prettyln("\nAuthenticate your account at (press ENTER to open in browser):\n", .{}); + + const offset = 0; + const padding = 1; + + const horizontal = if (Output.enable_ansi_colors) "─" else "-"; + const vertical = if (Output.enable_ansi_colors) "│" else "|"; + const top_left = if (Output.enable_ansi_colors) "┌" else "|"; + const top_right = if (Output.enable_ansi_colors) "┐" else "|"; + const bottom_left = if (Output.enable_ansi_colors) "└" else "|"; + const bottom_right = if (Output.enable_ansi_colors) "┘" else "|"; + + const width = (padding * 2) + auth_url_str.len; + + for (0..offset) |_| Output.print(" ", .{}); + Output.print("{s}", .{top_left}); + for (0..width) |_| Output.print("{s}", .{horizontal}); + Output.println("{s}", .{top_right}); + + for (0..offset) |_| Output.print(" ", .{}); + Output.print("{s}", .{vertical}); + for (0..padding) |_| Output.print(" ", .{}); + Output.pretty("{s}", .{auth_url_str}); + for (0..padding) |_| Output.print(" ", .{}); + Output.println("{s}", .{vertical}); + + for (0..offset) |_| Output.print(" ", .{}); + Output.print("{s}", .{bottom_left}); + for (0..width) |_| Output.print("{s}", .{horizontal}); + Output.println("{s}", .{bottom_right}); + Output.flush(); + + // on another thread because pressing enter is not required + (std.Thread.spawn(.{}, pressEnterToOpenInBrowser, .{auth_url_str}) catch |err| { + Output.err(err, "failed to spawn thread for opening auth url", .{}); + Global.crash(); + }).detach(); + + var auth_headers = try constructPublishHeaders( + ctx.allocator, + print_buf, + registry, + null, + null, + ctx.uses_workspaces, + ctx.manager.options.publish_config.auth_type, + ); + + while (true) { + response_buf.reset(); + + var req = http.AsyncHTTP.initSync( + ctx.allocator, + .GET, + done_url, + auth_headers.entries, + auth_headers.content.ptr.?[0..auth_headers.content.len], + response_buf, + "", + null, + null, + .follow, + ); + + const res = req.sendSync() catch |err| { + switch (err) { + error.OutOfMemory => |oom| return oom, + else => { + Output.err(err, "failed to send OTP request", .{}); + Global.crash(); + }, + } + }; + + switch (res.status_code) { + 202 => { + // retry + const nanoseconds = nanoseconds: { + default: for (res.headers) |header| { + if (strings.eqlCaseInsensitiveASCII(header.name, "retry-after", true)) { + const trimmed = strings.trim(header.value, &strings.whitespace_chars); + const seconds = bun.fmt.parseInt(u32, trimmed, 10) catch break :default; + break :nanoseconds seconds * std.time.ns_per_s; + } + } + + break :nanoseconds 500 * std.time.ns_per_ms; + }; + + std.time.sleep(nanoseconds); + continue; + }, + 200 => { + // login successful + const otp_done_source = logger.Source.initPathString("???", response_buf.list.items); + const otp_done_json = JSON.parseUTF8(&otp_done_source, ctx.manager.log, ctx.allocator) catch |err| { + switch (err) { + error.OutOfMemory => |oom| return oom, + else => { + Output.err("WebLogin", "failed to parse response json", .{}); + Global.crash(); + }, + } + }; + + return try otp_done_json.getStringCloned(ctx.allocator, "token") orelse { + Output.err("WebLogin", "missing `token` field in reponse json", .{}); + Global.crash(); + }; + }, + else => { + try handleResponseErrors(directory_publish, ctx, &req, &res, response_buf, false); + }, + } + } + } + + // classic + return prompt(ctx.allocator, "\nThis operation requires a one-time password.\nEnter OTP: ", "") catch |err| { + switch (err) { + error.OutOfMemory => |oom| return oom, + else => { + Output.err(err, "failed to read OTP input", .{}); + Global.crash(); + }, + } + }; + } + + fn constructPublishHeaders( + allocator: std.mem.Allocator, + print_buf: *std.ArrayListUnmanaged(u8), + registry: *const Npm.Registry.Scope, + maybe_json_len: ?usize, + maybe_otp: ?[]const u8, + uses_workspaces: bool, + auth_type: ?PackageManager.Options.AuthType, + ) OOM!http.HeaderBuilder { + var print_writer = print_buf.writer(allocator); + var headers: http.HeaderBuilder = .{}; + const npm_auth_type = if (maybe_otp == null) + if (auth_type) |auth| @tagName(auth) else "web" + else + "legacy"; + const ci_name = bun.detectCI(); + + { + headers.count("accept", "*/*"); + headers.count("accept-encoding", "gzip,deflate"); + + if (registry.token.len > 0) { + try print_writer.print("Bearer {s}", .{registry.token}); + headers.count("authorization", print_buf.items); + print_buf.clearRetainingCapacity(); + } else if (registry.auth.len > 0) { + try print_writer.print("Basic {s}", .{registry.auth}); + headers.count("authorization", print_buf.items); + print_buf.clearRetainingCapacity(); + } + + if (maybe_json_len != null) { + // not using `MimeType.json.value`, verdaccio will fail if it's anything other than `application/json` + headers.count("content-type", "application/json"); + } + + headers.count("npm-auth-type", npm_auth_type); + if (maybe_otp) |otp| { + headers.count("npm-otp", otp); + } + headers.count("npm-command", "publish"); + + try print_writer.print("{s} {s} {s} workspaces/{}{s}{s}", .{ + Global.user_agent, + Global.os_name, + Global.arch_name, + uses_workspaces, + if (ci_name != null) " ci/" else "", + ci_name orelse "", + }); + // headers.count("user-agent", "npm/10.8.3 node/v22.6.0 darwin arm64 workspaces/false"); + headers.count("user-agent", print_buf.items); + print_buf.clearRetainingCapacity(); + + headers.count("Connection", "keep-alive"); + headers.count("Host", registry.url.host); + + if (maybe_json_len) |json_len| { + try print_writer.print("{d}", .{json_len}); + headers.count("Content-Length", print_buf.items); + print_buf.clearRetainingCapacity(); + } + } + + try headers.allocate(allocator); + + { + headers.append("accept", "*/*"); + headers.append("accept-encoding", "gzip,deflate"); + + if (registry.token.len > 0) { + try print_writer.print("Bearer {s}", .{registry.token}); + headers.append("authorization", print_buf.items); + print_buf.clearRetainingCapacity(); + } else if (registry.auth.len > 0) { + try print_writer.print("Basic {s}", .{registry.auth}); + headers.append("authorization", print_buf.items); + print_buf.clearRetainingCapacity(); + } + + if (maybe_json_len != null) { + // not using `MimeType.json.value`, verdaccio will fail if it's anything other than `application/json` + headers.append("content-type", "application/json"); + } + + headers.append("npm-auth-type", npm_auth_type); + if (maybe_otp) |otp| { + headers.append("npm-otp", otp); + } + headers.append("npm-command", "publish"); + + try print_writer.print("{s} {s} {s} workspaces/{}{s}{s}", .{ + Global.user_agent, + Global.os_name, + Global.arch_name, + uses_workspaces, + if (ci_name != null) " ci/" else "", + ci_name orelse "", + }); + // headers.append("user-agent", "npm/10.8.3 node/v22.6.0 darwin arm64 workspaces/false"); + headers.append("user-agent", print_buf.items); + print_buf.clearRetainingCapacity(); + + headers.append("Connection", "keep-alive"); + headers.append("Host", registry.url.host); + + if (maybe_json_len) |json_len| { + try print_writer.print("{d}", .{json_len}); + headers.append("Content-Length", print_buf.items); + print_buf.clearRetainingCapacity(); + } + } + + return headers; + } + + fn constructPublishRequestBody( + comptime directory_publish: bool, + ctx: *const Context(directory_publish), + registry: *const Npm.Registry.Scope, + ) OOM![]const u8 { + const tag = if (ctx.manager.options.publish_config.tag.len > 0) + ctx.manager.options.publish_config.tag + else + "latest"; + + const encoded_tarball_len = std.base64.standard.Encoder.calcSize(ctx.tarball_bytes.len); + const version_without_build_tag = Dependency.withoutBuildTag(ctx.package_version); + + var buf = try std.ArrayListUnmanaged(u8).initCapacity( + ctx.allocator, + ctx.package_name.len * 5 + + version_without_build_tag.len * 4 + + ctx.abs_tarball_path.len + + encoded_tarball_len, + ); + var writer = buf.writer(ctx.allocator); + + try writer.print("{{\"_id\":\"{s}\",\"name\":\"{s}\"", .{ + ctx.package_name, + ctx.package_name, + }); + + try writer.print(",\"dist-tags\":{{\"{s}\":\"{s}\"}}", .{ + tag, + version_without_build_tag, + }); + + // "versions" + { + try writer.print(",\"versions\":{{\"{s}\":{{\"name\":\"{s}\",\"version\":\"{s}\"", .{ + version_without_build_tag, + ctx.package_name, + version_without_build_tag, + }); + + try writer.print(",\"_id\": \"{s}@{s}\"", .{ + ctx.package_name, + version_without_build_tag, + }); + + try writer.print(",\"_integrity\":\"{}\"", .{ + bun.fmt.integrity(ctx.integrity, .full), + }); + + try writer.print(",\"_nodeVersion\":\"{s}\",\"_npmVersion\":\"{s}\"", .{ + Environment.reported_nodejs_version, + // TODO: npm version + "10.8.3", + }); + + try writer.print(",\"dist\":{{\"integrity\":\"{}\",\"shasum\":\"{s}\"", .{ + bun.fmt.integrity(ctx.integrity, .full), + bun.fmt.bytesToHex(ctx.shasum, .lower), + }); + + // https://github.com/npm/cli/blob/63d6a732c3c0e9c19fd4d147eaa5cc27c29b168d/workspaces/libnpmpublish/lib/publish.js#L118 + // https:// -> http:// + try writer.print(",\"tarball\":\"http://{s}/{s}/-/{s}\"}}}}}}", .{ + strings.withoutTrailingSlash(registry.url.href), + ctx.package_name, + std.fs.path.basename(ctx.abs_tarball_path), + }); + } + + if (ctx.manager.options.publish_config.access) |access| { + try writer.print(",\"access\":\"{s}\"", .{@tagName(access)}); + } else { + try writer.writeAll(",\"access\":null"); + } + + // "_attachments" + { + try writer.print(",\"_attachments\":{{\"{s}\":{{\"content_type\":\"{s}\",\"data\":\"", .{ + std.fs.path.basename(ctx.abs_tarball_path), + "application/octet-stream", + }); + + try buf.ensureUnusedCapacity(ctx.allocator, encoded_tarball_len); + buf.items.len += encoded_tarball_len; + const count = bun.simdutf.base64.encode(ctx.tarball_bytes, buf.items[buf.items.len - encoded_tarball_len ..], false); + bun.assertWithLocation(count == encoded_tarball_len, @src()); + + try writer.print("\",\"length\":{d}}}}}}}", .{ + ctx.tarball_bytes.len, + }); + } + + return buf.items; + } +}; diff --git a/src/cli/upgrade_command.zig b/src/cli/upgrade_command.zig index c5fca9ef54..c75452a0fd 100644 --- a/src/cli/upgrade_command.zig +++ b/src/cli/upgrade_command.zig @@ -30,7 +30,7 @@ const bundler = bun.bundler; const fs = @import("../fs.zig"); const URL = @import("../url.zig").URL; const HTTP = bun.http; -const ParseJSON = @import("../json_parser.zig").ParseJSONUTF8; +const JSON = bun.JSON; const Archive = @import("../libarchive/libarchive.zig").Archive; const Zlib = @import("../zlib.zig"); const JSPrinter = bun.js_printer; @@ -251,7 +251,7 @@ pub const UpgradeCommand = struct { async_http.client.flags.reject_unauthorized = env_loader.getTLSRejectUnauthorized(); if (!silent) async_http.client.progress_node = progress.?; - const response = try async_http.sendSync(true); + const response = try async_http.sendSync(); switch (response.status_code) { 404 => return error.HTTP404, @@ -266,7 +266,7 @@ pub const UpgradeCommand = struct { defer if (comptime silent) log.deinit(); var source = logger.Source.initPathString("releases.json", metadata_body.list.items); initializeStore(); - var expr = ParseJSON(&source, &log, allocator) catch |err| { + var expr = JSON.parseUTF8(&source, &log, allocator) catch |err| { if (!silent) { progress.?.end(); refresher.?.refresh(); @@ -533,7 +533,7 @@ pub const UpgradeCommand = struct { async_http.client.progress_node = progress; async_http.client.flags.reject_unauthorized = env_loader.getTLSRejectUnauthorized(); - const response = try async_http.sendSync(true); + const response = try async_http.sendSync(); switch (response.status_code) { 404 => { diff --git a/src/compile_target.zig b/src/compile_target.zig index 67d0c0aab1..a6ec5f076c 100644 --- a/src/compile_target.zig +++ b/src/compile_target.zig @@ -170,7 +170,7 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc async_http.client.progress_node = progress; async_http.client.flags.reject_unauthorized = env.getTLSRejectUnauthorized(); - const response = try async_http.sendSync(true); + const response = try async_http.sendSync(); switch (response.status_code) { 404 => { @@ -254,13 +254,13 @@ pub fn downloadToPath(this: *const CompileTarget, env: *bun.DotEnv.Loader, alloc var node = refresher.start("Extracting", 0); defer node.end(); - const libarchive = @import("./libarchive//libarchive.zig"); + const libarchive = bun.libarchive; var tmpname_buf: [1024]u8 = undefined; const tempdir_name = bun.span(try bun.fs.FileSystem.instance.tmpname("tmp", &tmpname_buf, bun.fastRandom())); var tmpdir = try std.fs.cwd().makeOpenPath(tempdir_name, .{}); defer tmpdir.close(); defer std.fs.cwd().deleteTree(tempdir_name) catch {}; - _ = libarchive.Archive.extractToDir( + _ = libarchive.Archiver.extractToDir( tarball_bytes.items, tmpdir, null, diff --git a/src/defines.zig b/src/defines.zig index 504585e39e..39495728af 100644 --- a/src/defines.zig +++ b/src/defines.zig @@ -127,7 +127,7 @@ pub const DefineData = struct { .path = defines_path, .key_path = fs.Path.initWithNamespace("defines", "internal"), }; - const expr = try json_parser.ParseEnvJSON(&source, _log, allocator); + const expr = try json_parser.parseEnvJSON(&source, _log, allocator); const cloned = try expr.data.deepClone(allocator); user_defines.putAssumeCapacity(entry.key_ptr.*, DefineData{ .value = cloned, diff --git a/src/deps/picohttp.zig b/src/deps/picohttp.zig index 0f44ed7e72..08770fcde2 100644 --- a/src/deps/picohttp.zig +++ b/src/deps/picohttp.zig @@ -214,7 +214,7 @@ const StatusCodeFormatter = struct { pub const Response = struct { minor_version: usize = 0, - status_code: usize = 0, + status_code: u32 = 0, status: []const u8 = "", headers: []Header = &.{}, bytes_read: c_int = 0, @@ -295,7 +295,7 @@ pub const Response = struct { }, else => Response{ .minor_version = @as(usize, @intCast(minor_version)), - .status_code = @as(usize, @intCast(status_code)), + .status_code = @as(u32, @intCast(status_code)), .status = status, .headers = src[0..@min(num_headers, src.len)], .bytes_read = rc, diff --git a/src/exact_size_matcher.zig b/src/exact_size_matcher.zig index 1b780f54eb..f3a7d75c2c 100644 --- a/src/exact_size_matcher.zig +++ b/src/exact_size_matcher.zig @@ -5,7 +5,7 @@ pub fn ExactSizeMatcher(comptime max_bytes: usize) type { switch (max_bytes) { 1, 2, 4, 8, 12, 16 => {}, else => { - @compileError("max_bytes must be 1, 2, 4, 8, or 12."); + @compileError("max_bytes must be 1, 2, 4, 8, 12, or 16."); }, } diff --git a/src/fmt.zig b/src/fmt.zig index f2b9ec6f63..cf3094a518 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -7,6 +7,7 @@ const js_lexer = bun.js_lexer; const ComptimeStringMap = bun.ComptimeStringMap; const fmt = std.fmt; const Environment = bun.Environment; +const sha = bun.sha; pub usingnamespace std.fmt; @@ -106,6 +107,112 @@ pub fn Table( }; } +pub const RedactedNpmUrlFormatter = struct { + url: string, + + pub fn format(this: @This(), comptime _: string, _: std.fmt.FormatOptions, writer: anytype) !void { + var i: usize = 0; + while (i < this.url.len) { + if (strings.startsWithUUID(this.url[i..])) { + try writer.writeAll("***"); + i += 36; + continue; + } + + const npm_secret_len = strings.startsWithNpmSecret(this.url[i..]); + if (npm_secret_len > 0) { + try writer.writeAll("***"); + i += npm_secret_len; + continue; + } + + // TODO: redact password from `https://username:password@registry.com/` + + try writer.writeByte(this.url[i]); + i += 1; + } + } +}; + +pub fn redactedNpmUrl(str: string) RedactedNpmUrlFormatter { + return .{ + .url = str, + }; +} + +// https://github.com/npm/cli/blob/63d6a732c3c0e9c19fd4d147eaa5cc27c29b168d/node_modules/npm-package-arg/lib/npa.js#L163 +pub const DependencyUrlFormatter = struct { + url: string, + + pub fn format(this: @This(), comptime _: string, _: std.fmt.FormatOptions, writer: anytype) !void { + var remain = this.url; + while (strings.indexOfChar(remain, '/')) |slash| { + try writer.writeAll(remain[0..slash]); + try writer.writeAll("%2f"); + remain = remain[slash + 1 ..]; + } + try writer.writeAll(remain); + } +}; + +pub fn dependencyUrl(url: string) DependencyUrlFormatter { + return .{ + .url = url, + }; +} + +const IntegrityFormatStyle = enum { + short, + full, +}; + +pub fn IntegrityFormatter(comptime style: IntegrityFormatStyle) type { + return struct { + bytes: [sha.SHA512.digest]u8, + + pub fn format(this: @This(), comptime _: string, _: std.fmt.FormatOptions, writer: anytype) !void { + var buf: [std.base64.standard.Encoder.calcSize(sha.SHA512.digest)]u8 = undefined; + const count = bun.simdutf.base64.encode(this.bytes[0..sha.SHA512.digest], &buf, false); + + const encoded = buf[0..count]; + + if (comptime style == .short) + try writer.print("sha512-{s}[...]{s}", .{ encoded[0..13], encoded[encoded.len - 15 ..] }) + else + try writer.print("sha512-{s}", .{encoded}); + } + }; +} + +pub fn integrity(bytes: [sha.SHA512.digest]u8, comptime style: IntegrityFormatStyle) IntegrityFormatter(style) { + return .{ .bytes = bytes }; +} + +const JSONFormatter = struct { + input: []const u8, + + pub fn format(self: JSONFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try bun.js_printer.writeJSONString(self.input, @TypeOf(writer), writer, .latin1); + } +}; + +const JSONFormatterUTF8 = struct { + input: []const u8, + + pub fn format(self: JSONFormatterUTF8, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try bun.js_printer.writeJSONString(self.input, @TypeOf(writer), writer, .utf8); + } +}; + +/// Expects latin1 +pub fn formatJSONString(text: []const u8) JSONFormatter { + return .{ .input = text }; +} + +pub fn formatJSONStringUTF8(text: []const u8) JSONFormatterUTF8 { + return .{ .input = text }; +} + const SharedTempBuffer = [32 * 1024]u8; fn getSharedBuffer() []u8 { return std.mem.asBytes(shared_temp_buffer_ptr orelse brk: { diff --git a/src/http.zig b/src/http.zig index 1cfb72daee..f3f3783be7 100644 --- a/src/http.zig +++ b/src/http.zig @@ -2372,11 +2372,33 @@ pub const AsyncHTTP = struct { return this; } - pub fn initSync(allocator: std.mem.Allocator, method: Method, url: URL, headers: Headers.Entries, headers_buf: string, response_buffer: *MutableString, request_body: []const u8, http_proxy: ?URL, hostname: ?[]u8, redirect_type: FetchRedirect) AsyncHTTP { - return @This().init(allocator, method, url, headers, headers_buf, response_buffer, request_body, undefined, redirect_type, .{ - .http_proxy = http_proxy, - .hostname = hostname, - }); + pub fn initSync( + allocator: std.mem.Allocator, + method: Method, + url: URL, + headers: Headers.Entries, + headers_buf: string, + response_buffer: *MutableString, + request_body: []const u8, + http_proxy: ?URL, + hostname: ?[]u8, + redirect_type: FetchRedirect, + ) AsyncHTTP { + return @This().init( + allocator, + method, + url, + headers, + headers_buf, + response_buffer, + request_body, + undefined, + redirect_type, + .{ + .http_proxy = http_proxy, + .hostname = hostname, + }, + ); } fn reset(this: *AsyncHTTP) !void { @@ -2456,7 +2478,7 @@ pub const AsyncHTTP = struct { this.channel.writeItem(result) catch unreachable; } - pub fn sendSync(this: *AsyncHTTP, comptime _: bool) anyerror!picohttp.Response { + pub fn sendSync(this: *AsyncHTTP) anyerror!picohttp.Response { HTTPThread.init(); var ctx = try bun.default_allocator.create(SingleHTTPChannel); @@ -2469,14 +2491,13 @@ pub const AsyncHTTP = struct { var batch = bun.ThreadPool.Batch{}; this.schedule(bun.default_allocator, &batch); http_thread.schedule(batch); - while (true) { - const result: HTTPClientResult = ctx.channel.readItem() catch unreachable; - if (result.fail) |e| return e; - assert(result.metadata != null); - return result.metadata.?.response; - } - unreachable; + const result = ctx.channel.readItem() catch unreachable; + if (result.fail) |err| { + return err; + } + assert(result.metadata != null); + return result.metadata.?.response; } pub fn onAsyncHTTPCallback(this: *AsyncHTTP, async_http: *AsyncHTTP, result: HTTPClientResult) void { diff --git a/src/ini.zig b/src/ini.zig index 96229711f4..73a2c86cc6 100644 --- a/src/ini.zig +++ b/src/ini.zig @@ -242,7 +242,7 @@ pub const Parser = struct { var log = bun.logger.Log.init(arena_allocator); defer log.deinit(); // Try to parse it and it if fails will just treat it as a string - const json_val: Expr = bun.JSON.ParseJSONUTF8Impl(&src, &log, arena_allocator, true) catch { + const json_val: Expr = bun.JSON.parseUTF8Impl(&src, &log, arena_allocator, true) catch { break :out; }; @@ -882,28 +882,11 @@ pub fn loadNpmrcFromFile( ) void { var log = bun.logger.Log.init(allocator); defer log.deinit(); - const npmrc_file = switch (bun.sys.openat(bun.FD.cwd(), npmrc_path, bun.O.RDONLY, 0)) { - .result => |fd| fd, - .err => |err| { - if (auto_loaded) return; - Output.prettyErrorln("{}\nwhile opening .npmrc \"{s}\"", .{ - err, - npmrc_path, - }); - Global.exit(1); - }, - }; - defer _ = bun.sys.close(npmrc_file); - const source = switch (bun.sys.File.toSource(npmrc_path, allocator)) { - .result => |s| s, - .err => |e| { - Output.prettyErrorln("{}\nwhile reading .npmrc \"{s}\"", .{ - e, - npmrc_path, - }); - Global.exit(1); - }, + const source = bun.sys.File.toSource(npmrc_path, allocator).unwrap() catch |err| { + if (auto_loaded) return; + Output.err(err, "failed to read .npmrc: \"{s}\"", .{npmrc_path}); + Global.crash(); }; defer allocator.free(source.contents); diff --git a/src/install/dependency.zig b/src/install/dependency.zig index d21b8be319..6a1bd3a961 100644 --- a/src/install/dependency.zig +++ b/src/install/dependency.zig @@ -284,6 +284,25 @@ pub fn unscopedPackageName(name: []const u8) []const u8 { return name_[(strings.indexOfChar(name_, '/') orelse return name) + 1 ..]; } +pub fn isScopedPackageName(name: string) error{InvalidPackageName}!bool { + if (name.len == 0) return error.InvalidPackageName; + + if (name[0] != '@') return false; + + if (strings.indexOfChar(name, '/')) |slash| { + if (slash != 1 and slash != name.len - 1) { + return true; + } + } + + return error.InvalidPackageName; +} + +/// assumes version is valid +pub fn withoutBuildTag(version: string) string { + if (strings.indexOfChar(version, '+')) |plus| return version[0..plus] else return version; +} + pub const Version = struct { tag: Tag = .uninitialized, literal: String = .{}, diff --git a/src/install/extract_tarball.zig b/src/install/extract_tarball.zig index da159b0d56..d3bf173b93 100644 --- a/src/install/extract_tarball.zig +++ b/src/install/extract_tarball.zig @@ -200,7 +200,7 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractD defer extract_destination.close(); - const Archive = @import("../libarchive/libarchive.zig").Archive; + const Archiver = bun.libarchive.Archiver; const Zlib = @import("../zlib.zig"); var zlib_pool = Npm.Registry.BodyPool.get(default_allocator); zlib_pool.data.reset(); @@ -278,7 +278,7 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractD var dirname_reader = DirnameReader{ .outdirname = &resolved }; switch (PackageManager.verbose_install) { - inline else => |log| _ = try Archive.extractToDir( + inline else => |log| _ = try Archiver.extractToDir( zlib_pool.data.list.items, extract_destination, null, @@ -304,7 +304,7 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractD } }, else => switch (PackageManager.verbose_install) { - inline else => |log| _ = try Archive.extractToDir( + inline else => |log| _ = try Archiver.extractToDir( zlib_pool.data.list.items, extract_destination, null, diff --git a/src/install/install.zig b/src/install/install.zig index dd27ed84b0..17b8edceee 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -18,7 +18,7 @@ const JSLexer = bun.js_lexer; const logger = bun.logger; const js_parser = bun.js_parser; -const json_parser = bun.JSON; +const JSON = bun.JSON; const JSPrinter = bun.js_printer; const linker = @import("../linker.zig"); @@ -1236,7 +1236,7 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { initializeStore(); - var package_json_checker = json_parser.PackageJSONVersionChecker.init(allocator, &source, &log) catch return false; + var package_json_checker = JSON.PackageJSONVersionChecker.init(allocator, &source, &log) catch return false; _ = package_json_checker.parseExpr() catch return false; if (log.errors > 0 or !package_json_checker.has_found_name) return false; // workspaces aren't required to have a version @@ -2828,7 +2828,7 @@ pub const PackageManager = struct { if (comptime opts.init_reset_store) initializeStore(); - const json = json_parser.ParsePackageJSONUTF8WithOpts( + const json = JSON.parsePackageJSONUTF8WithOpts( &source, log, allocator, @@ -2882,7 +2882,7 @@ pub const PackageManager = struct { if (comptime opts.init_reset_store) initializeStore(); - const json_result = json_parser.ParsePackageJSONUTF8WithOpts( + const json_result = JSON.parsePackageJSONUTF8WithOpts( &source, log, allocator, @@ -6087,7 +6087,7 @@ pub const PackageManager = struct { json.buf, ); initializeStore(); - const json_root = json_parser.ParsePackageJSONUTF8( + const json_root = JSON.parsePackageJSONUTF8( &package_json_source, manager.log, manager.allocator, @@ -6941,6 +6941,37 @@ pub const PackageManager = struct { max_concurrent_lifecycle_scripts: usize, + publish_config: PublishConfig = .{}, + + pub const PublishConfig = struct { + access: ?Access = null, + tag: string = "", + otp: string = "", + auth_type: ?AuthType = null, + }; + + pub const Access = enum { + public, + restricted, + + const map = bun.ComptimeEnumMap(Access); + + pub fn fromStr(str: string) ?Access { + return map.get(str); + } + }; + + pub const AuthType = enum { + legacy, + web, + + const map = bun.ComptimeEnumMap(AuthType); + + pub fn fromStr(str: string) ?AuthType { + return map.get(str); + } + }; + pub fn shouldPrintCommandName(this: *const Options) bool { return this.log_level != .silent and this.do.summary; } @@ -7046,7 +7077,7 @@ pub const PackageManager = struct { allocator: std.mem.Allocator, log: *logger.Log, env: *DotEnv.Loader, - cli_: ?CommandLineArguments, + maybe_cli: ?CommandLineArguments, bun_install_: ?*Api.BunInstall, subcommand: Subcommand, ) !void { @@ -7200,20 +7231,6 @@ pub const PackageManager = struct { } } - if (cli_) |cli| { - if (cli.registry.len > 0) { - this.scope.url = URL.parse(cli.registry); - } - - if (cli.exact) { - this.enable.exact_versions = true; - } - - if (cli.token.len > 0) { - this.scope.token = cli.token; - } - } - if (env.get("BUN_CONFIG_YARN_LOCKFILE") != null) { this.do.save_yarn_lock = true; } @@ -7246,7 +7263,19 @@ pub const PackageManager = struct { this.enable.manifest_cache_control = false; } - if (cli_) |cli| { + if (maybe_cli) |cli| { + if (cli.registry.len > 0) { + this.scope.url = URL.parse(cli.registry); + } + + if (cli.exact) { + this.enable.exact_versions = true; + } + + if (cli.token.len > 0) { + this.scope.token = cli.token; + } + if (cli.no_save) { this.do.save_lockfile = false; this.do.write_package_json = false; @@ -7350,6 +7379,19 @@ pub const PackageManager = struct { }; }, } + + if (cli.publish_config.access) |cli_access| { + this.publish_config.access = cli_access; + } + if (cli.publish_config.tag.len > 0) { + this.publish_config.tag = cli.publish_config.tag; + } + if (cli.publish_config.otp.len > 0) { + this.publish_config.otp = cli.publish_config.otp; + } + if (cli.publish_config.auth_type) |auth_type| { + this.publish_config.auth_type = auth_type; + } } else { this.log_level = if (default_disable_progress_bar) LogLevel.default_no_progress else LogLevel.default; PackageManager.verbose_install = false; @@ -7664,7 +7706,7 @@ pub const PackageManager = struct { const value = dep.value orelse continue; if (value.data != .e_string) continue; - const version_literal = value.asStringCloned(allocator) orelse bun.outOfMemory(); + const version_literal = try value.asStringCloned(allocator) orelse bun.outOfMemory(); var tag = Dependency.Version.Tag.infer(version_literal); // only updating dependencies with npm versions, and dist-tags if `--latest`. @@ -7681,7 +7723,7 @@ pub const PackageManager = struct { } } - const key_str = key.asStringCloned(allocator) orelse unreachable; + const key_str = try key.asStringCloned(allocator) orelse unreachable; const entry = manager.updating_packages.getOrPut(allocator, key_str) catch bun.outOfMemory(); // If a dependency is present in more than one dependency group, only one of it's versions @@ -7865,7 +7907,7 @@ pub const PackageManager = struct { replacing += 1; } else { if (manager.subcommand == .update and options.before_install) add_packages_to_update: { - const version_literal = value.expr.asStringCloned(allocator) orelse break :add_packages_to_update; + const version_literal = try value.expr.asStringCloned(allocator) orelse break :add_packages_to_update; var tag = Dependency.Version.Tag.infer(version_literal); if (tag != .npm and tag != .dist_tag) break :add_packages_to_update; @@ -8250,6 +8292,18 @@ pub const PackageManager = struct { @"patch-commit", outdated, pack, + publish, + + // bin, + // hash, + // @"hash-print", + // @"hash-string", + // cache, + // @"default-trusted", + // untrusted, + // trust, + // ls, + // migrate, pub fn canGloballyInstallPackages(this: Subcommand) bool { return switch (this) { @@ -8265,6 +8319,14 @@ pub const PackageManager = struct { else => false, }; } + + // TODO: make all subcommands find root and chdir + pub fn shouldChdirToRoot(this: Subcommand) bool { + return switch (this) { + .link => false, + else => true, + }; + } }; pub fn init( @@ -8389,7 +8451,7 @@ pub const PackageManager = struct { // Check if this is a workspace; if so, use root package var found = false; - if (subcommand != .link) { + if (subcommand.shouldChdirToRoot()) { if (!created_package_json) { while (std.fs.path.dirname(this_cwd)) |parent| : (this_cwd = parent) { const parent_without_trailing_slash = strings.withoutTrailingSlash(parent); @@ -8412,7 +8474,7 @@ pub const PackageManager = struct { const json_path = try bun.getFdPath(json_file.handle, &package_json_cwd_buf); const json_source = logger.Source.initPathString(json_path, json_buf[0..json_len]); initializeStore(); - const json = try json_parser.ParsePackageJSONUTF8(&json_source, ctx.log, ctx.allocator); + const json = try JSON.parsePackageJSONUTF8(&json_source, ctx.log, ctx.allocator); if (json.asProperty("workspaces")) |prop| { const json_array = switch (prop.expr.data) { .e_array => |arr| arr, @@ -9139,7 +9201,7 @@ pub const PackageManager = struct { else "Possible values: \"hardlink\" (default), \"symlink\", \"copyfile\""; - const install_params_ = [_]ParamType{ + const shared_params = [_]ParamType{ clap.parseParam("-c, --config ? Specify path to config file (bunfig.toml)") catch unreachable, clap.parseParam("-y, --yarn Write a yarn.lock file (yarn v1)") catch unreachable, clap.parseParam("-p, --production Don't install devDependencies") catch unreachable, @@ -9165,7 +9227,7 @@ pub const PackageManager = struct { clap.parseParam("-h, --help Print this help menu") catch unreachable, }; - pub const install_params: []const ParamType = &(install_params_ ++ [_]ParamType{ + pub const install_params: []const ParamType = &(shared_params ++ [_]ParamType{ clap.parseParam("-d, --dev Add dependency to \"devDependencies\"") catch unreachable, clap.parseParam("-D, --development") catch unreachable, clap.parseParam("--optional Add dependency to \"optionalDependencies\"") catch unreachable, @@ -9173,12 +9235,12 @@ pub const PackageManager = struct { clap.parseParam(" ... ") catch unreachable, }); - pub const update_params: []const ParamType = &(install_params_ ++ [_]ParamType{ + pub const update_params: []const ParamType = &(shared_params ++ [_]ParamType{ clap.parseParam("--latest Update packages to their latest versions") catch unreachable, clap.parseParam(" ... \"name\" of packages to update") catch unreachable, }); - pub const pm_params: []const ParamType = &(install_params_ ++ [_]ParamType{ + pub const pm_params: []const ParamType = &(shared_params ++ [_]ParamType{ clap.parseParam("-a, --all") catch unreachable, // clap.parseParam("--filter ... Pack each matching workspace") catch unreachable, clap.parseParam("--destination The directory the tarball will be saved in") catch unreachable, @@ -9186,7 +9248,7 @@ pub const PackageManager = struct { clap.parseParam(" ... ") catch unreachable, }); - pub const add_params: []const ParamType = &(install_params_ ++ [_]ParamType{ + pub const add_params: []const ParamType = &(shared_params ++ [_]ParamType{ clap.parseParam("-d, --dev Add dependency to \"devDependencies\"") catch unreachable, clap.parseParam("-D, --development") catch unreachable, clap.parseParam("--optional Add dependency to \"optionalDependencies\"") catch unreachable, @@ -9194,42 +9256,51 @@ pub const PackageManager = struct { clap.parseParam(" ... \"name\" or \"name@version\" of package(s) to install") catch unreachable, }); - pub const remove_params: []const ParamType = &(install_params_ ++ [_]ParamType{ + pub const remove_params: []const ParamType = &(shared_params ++ [_]ParamType{ clap.parseParam(" ... \"name\" of package(s) to remove from package.json") catch unreachable, }); - pub const link_params: []const ParamType = &(install_params_ ++ [_]ParamType{ + pub const link_params: []const ParamType = &(shared_params ++ [_]ParamType{ clap.parseParam(" ... \"name\" install package as a link") catch unreachable, }); - pub const unlink_params: []const ParamType = &(install_params_ ++ [_]ParamType{ + pub const unlink_params: []const ParamType = &(shared_params ++ [_]ParamType{ clap.parseParam(" ... \"name\" uninstall package as a link") catch unreachable, }); - const patch_params: []const ParamType = &(install_params_ ++ [_]ParamType{ + const patch_params: []const ParamType = &(shared_params ++ [_]ParamType{ clap.parseParam(" ... \"name\" of the package to patch") catch unreachable, clap.parseParam("--commit Install a package containing modifications in `dir`") catch unreachable, clap.parseParam("--patches-dir The directory to put the patch file in (only if --commit is used)") catch unreachable, }); - const patch_commit_params: []const ParamType = &(install_params_ ++ [_]ParamType{ + const patch_commit_params: []const ParamType = &(shared_params ++ [_]ParamType{ clap.parseParam(" ... \"dir\" containing changes to a package") catch unreachable, clap.parseParam("--patches-dir The directory to put the patch file") catch unreachable, }); - const outdated_params: []const ParamType = &(install_params_ ++ [_]ParamType{ + const outdated_params: []const ParamType = &(shared_params ++ [_]ParamType{ // clap.parseParam("--json Output outdated information in JSON format") catch unreachable, clap.parseParam("--filter ... Display outdated dependencies for each matching workspace") catch unreachable, clap.parseParam(" ... Package patterns to filter by") catch unreachable, }); - const pack_params: []const ParamType = &(install_params_ ++ [_]ParamType{ + const pack_params: []const ParamType = &(shared_params ++ [_]ParamType{ // clap.parseParam("--filter ... Pack each matching workspace") catch unreachable, clap.parseParam("--destination The directory the tarball will be saved in") catch unreachable, clap.parseParam("--gzip-level Specify a custom compression level for gzip. Default is 9.") catch unreachable, clap.parseParam(" ... ") catch unreachable, }); + const publish_params: []const ParamType = &(shared_params ++ [_]ParamType{ + clap.parseParam(" ... Package tarball to publish") catch unreachable, + clap.parseParam("--access Set access level for scoped packages") catch unreachable, + clap.parseParam("--tag Tag the release. Default is \"latest\"") catch unreachable, + clap.parseParam("--otp Provide a one-time password for authentication") catch unreachable, + clap.parseParam("--auth-type Specify the type of one-time password authentication (default is 'web')") catch unreachable, + clap.parseParam("--gzip-level Specify a custom compression level for gzip. Default is 9.") catch unreachable, + }); + pub const CommandLineArguments = struct { cache_dir: string = "", lockfile: string = "", @@ -9276,6 +9347,8 @@ pub const PackageManager = struct { registry: string = "", + publish_config: Options.PublishConfig = .{}, + const PatchOpts = union(enum) { nothing: struct {}, patch: struct {}, @@ -9537,6 +9610,31 @@ pub const PackageManager = struct { Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); Output.flush(); }, + .publish => { + const intro_text = + \\Usage: bun publish [flags] + ; + + const outro_text = + \\Examples: + \\ Publish the package in the current working directory with public access. + \\ bun publish --access public + \\ + \\ Publish a pre-existing package tarball. + \\ bun publish ./path/to/tarball.tgz + \\ + \\ Publish with tag 'next'. + \\ bun publish --tag next + \\ + ; + + Output.pretty("\n" ++ intro_text ++ "\n", .{}); + Output.flush(); + Output.pretty("\nFlags:", .{}); + clap.simpleHelp(PackageManager.publish_params); + Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); + Output.flush(); + }, } } @@ -9555,6 +9653,7 @@ pub const PackageManager = struct { .@"patch-commit" => patch_commit_params, .outdated => outdated_params, .pack => pack_params, + .publish => publish_params, }; var diag = clap.Diagnostic{}; @@ -9601,9 +9700,11 @@ pub const PackageManager = struct { // cli.json_output = args.flag("--json"); } - if (comptime subcommand == .pack or subcommand == .pm) { - if (args.option("--destination")) |dest| { - cli.pack_destination = dest; + if (comptime subcommand == .pack or subcommand == .pm or subcommand == .publish) { + if (comptime subcommand != .publish) { + if (args.option("--destination")) |dest| { + cli.pack_destination = dest; + } } if (args.option("--gzip-level")) |level| { @@ -9611,6 +9712,30 @@ pub const PackageManager = struct { } } + if (comptime subcommand == .publish) { + if (args.option("--tag")) |tag| { + cli.publish_config.tag = tag; + } + + if (args.option("--access")) |access| { + cli.publish_config.access = Options.Access.fromStr(access) orelse { + Output.errGeneric("invalid `access` value: '{s}'", .{access}); + Global.crash(); + }; + } + + if (args.option("--otp")) |otp| { + cli.publish_config.otp = otp; + } + + if (args.option("--auth-type")) |auth_type| { + cli.publish_config.auth_type = Options.AuthType.fromStr(auth_type) orelse { + Output.errGeneric("invalid `auth-type` value: '{s}'", .{auth_type}); + Global.crash(); + }; + } + } + // link and unlink default to not saving, all others default to // saving. if (comptime subcommand == .link or subcommand == .unlink) { @@ -10463,7 +10588,7 @@ pub const PackageManager = struct { // Now, we _re_ parse our in-memory edited package.json // so we can commit the version we changed from the lockfile - var new_package_json = json_parser.ParsePackageJSONUTF8(&source, manager.log, manager.allocator) catch |err| { + var new_package_json = JSON.parsePackageJSONUTF8(&source, manager.log, manager.allocator) catch |err| { Output.prettyErrorln("package.json failed to parse due to error {s}", .{@errorName(err)}); Global.crash(); }; @@ -10829,7 +10954,7 @@ pub const PackageManager = struct { defer manager.allocator.free(package_json_source.contents); initializeStore(); - const json = json_parser.ParsePackageJSONUTF8AlwaysDecode(&package_json_source, manager.log, manager.allocator) catch |err| { + const json = JSON.parsePackageJSONUTF8AlwaysDecode(&package_json_source, manager.log, manager.allocator) catch |err| { switch (Output.enable_ansi_colors) { inline else => |enable_ansi_colors| { manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; @@ -11246,7 +11371,7 @@ pub const PackageManager = struct { defer manager.allocator.free(package_json_source.contents); initializeStore(); - const json = json_parser.ParsePackageJSONUTF8AlwaysDecode(&package_json_source, manager.log, manager.allocator) catch |err| { + const json = JSON.parsePackageJSONUTF8AlwaysDecode(&package_json_source, manager.log, manager.allocator) catch |err| { switch (Output.enable_ansi_colors) { inline else => |enable_ansi_colors| { manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 3e89da50b7..fa7e39a01c 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -18,7 +18,7 @@ const logger = bun.logger; const js_parser = bun.js_parser; const Expr = @import("../js_ast.zig").Expr; -const json_parser = bun.JSON; +const JSON = bun.JSON; const JSPrinter = bun.js_printer; const linker = @import("../linker.zig"); @@ -3098,7 +3098,7 @@ pub const Package = extern struct { }; initializeStore(); - break :brk try json_parser.ParsePackageJSONUTF8( + break :brk try JSON.parsePackageJSONUTF8( &json_src, log, allocator, @@ -3945,7 +3945,7 @@ pub const Package = extern struct { comptime features: Features, ) !void { initializeStore(); - const json = json_parser.ParsePackageJSONUTF8AlwaysDecode(&source, log, allocator) catch |err| { + const json = JSON.parsePackageJSONUTF8AlwaysDecode(&source, log, allocator) catch |err| { switch (Output.enable_ansi_colors) { inline else => |enable_ansi_colors| { log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors) catch {}; @@ -4338,7 +4338,7 @@ pub const Package = extern struct { }).unwrap(); const name_expr = workspace_json.root.get("name") orelse return error.MissingPackageName; - const name = name_expr.asStringCloned(allocator) orelse return error.MissingPackageName; + const name = try name_expr.asStringCloned(allocator) orelse return error.MissingPackageName; var entry = WorkspaceEntry{ .name = name, @@ -4346,7 +4346,7 @@ pub const Package = extern struct { }; debug("processWorkspaceName({s}) = {s}", .{ abs_package_json_path, entry.name }); if (workspace_json.root.get("version")) |version_expr| { - if (version_expr.asStringCloned(allocator)) |version| { + if (try version_expr.asStringCloned(allocator)) |version| { entry.version = version; } } @@ -4376,7 +4376,7 @@ pub const Package = extern struct { for (arr.slice()) |item| { // TODO: when does this get deallocated? - const input_path = item.asStringZ(allocator) orelse { + const input_path = try item.asStringZ(allocator) orelse { log.addErrorFmt(source, item.loc, allocator, \\Workspaces expects an array of strings, like: \\ "workspaces": [ @@ -5050,7 +5050,7 @@ pub const Package = extern struct { const value = prop.value.?; if (key.isString() and value.isString()) { var sfb = std.heap.stackFallback(1024, allocator); - const keyhash = key.asStringHash(sfb.get(), String.Builder.stringHash) orelse unreachable; + const keyhash = try key.asStringHash(sfb.get(), String.Builder.stringHash) orelse unreachable; const patch_path = string_builder.append(String, value.asString(allocator).?); lockfile.patched_dependencies.put(allocator, keyhash, .{ .path = patch_path }) catch unreachable; } diff --git a/src/install/migration.zig b/src/install/migration.zig index aa8043126e..6c44a9e59a 100644 --- a/src/install/migration.zig +++ b/src/install/migration.zig @@ -137,7 +137,7 @@ pub fn migrateNPMLockfile( Install.initializeStore(); const json_src = logger.Source.initPathString(abs_path, data); - const json = bun.JSON.ParseJSONUTF8(&json_src, log, allocator) catch return error.InvalidNPMLockfile; + const json = bun.JSON.parseUTF8(&json_src, log, allocator) catch return error.InvalidNPMLockfile; if (json.data != .e_object) { return error.InvalidNPMLockfile; diff --git a/src/install/npm.zig b/src/install/npm.zig index 77b7f55aa2..8352923ad6 100644 --- a/src/install/npm.zig +++ b/src/install/npm.zig @@ -1280,7 +1280,7 @@ pub const PackageManifest = struct { defer bun.JSAst.Stmt.Data.Store.memory_allocator.?.pop(); var arena = bun.ArenaAllocator.init(allocator); defer arena.deinit(); - const json = json_parser.ParseJSONUTF8( + const json = json_parser.parseUTF8( &source, log, arena.allocator(), diff --git a/src/js_ast.zig b/src/js_ast.zig index ffc73d4398..9a3651f87c 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -28,6 +28,7 @@ const js_lexer = @import("./js_lexer.zig"); const TypeScript = @import("./js_parser.zig").TypeScript; const ThreadlocalArena = @import("./mimalloc_arena.zig").Arena; const MimeType = bun.http.MimeType; +const OOM = bun.OOM; /// This is the index to the automatically-generated part containing code that /// calls "__export(exports, { ... getters ... })". This is used to generate @@ -1908,7 +1909,6 @@ pub const E = struct { pub const Rope = struct { head: Expr, next: ?*Rope = null, - const OOM = error{OutOfMemory}; pub fn append(this: *Rope, expr: Expr, allocator: std.mem.Allocator) OOM!*Rope { if (this.next) |next| { return try next.append(expr, allocator); @@ -2497,7 +2497,7 @@ pub const E = struct { strings.eqlComptimeUTF16(s.slice16()[0..value.len], value); } - pub fn string(s: *const String, allocator: std.mem.Allocator) !bun.string { + pub fn string(s: *const String, allocator: std.mem.Allocator) OOM!bun.string { if (s.isUTF8()) { return s.data; } else { @@ -2505,7 +2505,7 @@ pub const E = struct { } } - pub fn stringZ(s: *const String, allocator: std.mem.Allocator) !bun.stringZ { + pub fn stringZ(s: *const String, allocator: std.mem.Allocator) OOM!bun.stringZ { if (s.isUTF8()) { return allocator.dupeZ(u8, s.data); } else { @@ -2513,9 +2513,9 @@ pub const E = struct { } } - pub fn stringCloned(s: *const String, allocator: std.mem.Allocator) !bun.string { + pub fn stringCloned(s: *const String, allocator: std.mem.Allocator) OOM!bun.string { if (s.isUTF8()) { - return try allocator.dupe(u8, s.data); + return allocator.dupe(u8, s.data); } else { return strings.toUTF8Alloc(allocator, s.slice16()); } @@ -3349,7 +3349,7 @@ pub const Expr = struct { if (mime_type.category == .json) { var source = logger.Source.initPathString("fetch.json", bytes); - var out_expr = JSONParser.ParseJSONForMacro(&source, log, allocator) catch { + var out_expr = JSONParser.parseForMacro(&source, log, allocator) catch { return error.MacroFailed; }; out_expr.loc = loc; @@ -3424,10 +3424,54 @@ pub const Expr = struct { return this.data.toJS(allocator, globalObject, opts); } + pub inline fn isArray(this: *const Expr) bool { + return this.data == .e_array; + } + + pub inline fn isObject(this: *const Expr) bool { + return this.data == .e_object; + } + pub fn get(expr: *const Expr, name: string) ?Expr { return if (asProperty(expr, name)) |query| query.expr else null; } + pub fn getString(expr: *const Expr, allocator: std.mem.Allocator, name: string) OOM!?struct { string, logger.Loc } { + if (asProperty(expr, name)) |q| { + if (q.expr.asString(allocator)) |str| { + return .{ + str, + q.expr.loc, + }; + } + } + return null; + } + + pub fn getNumber(expr: *const Expr, name: string) ?struct { f64, logger.Loc } { + if (asProperty(expr, name)) |q| { + if (q.expr.asNumber()) |num| { + return .{ + num, + q.expr.loc, + }; + } + } + return null; + } + + pub fn getStringCloned(expr: *const Expr, allocator: std.mem.Allocator, name: string) OOM!?string { + return if (asProperty(expr, name)) |q| q.expr.asStringCloned(allocator) else null; + } + + pub fn getStringClonedZ(expr: *const Expr, allocator: std.mem.Allocator, name: string) OOM!?stringZ { + return if (asProperty(expr, name)) |q| q.expr.asStringZ(allocator) else null; + } + + pub fn getArray(expr: *const Expr, name: string) ?ArrayIterator { + return if (asProperty(expr, name)) |q| q.expr.asArray() else null; + } + pub fn getRope(self: *const Expr, rope: *const E.Object.Rope) ?E.Object.RopeQuery { if (self.get(rope.head.data.e_string.data)) |existing| { switch (existing.data) { @@ -3522,11 +3566,11 @@ pub const Expr = struct { else => return null, } } - pub inline fn asStringHash(expr: *const Expr, allocator: std.mem.Allocator, comptime hash_fn: *const fn (buf: []const u8) callconv(.Inline) u64) ?u64 { + pub inline fn asStringHash(expr: *const Expr, allocator: std.mem.Allocator, comptime hash_fn: *const fn (buf: []const u8) callconv(.Inline) u64) OOM!?u64 { switch (expr.data) { .e_string => |str| { if (str.isUTF8()) return hash_fn(str.data); - const utf8_str = str.string(allocator) catch return null; + const utf8_str = try str.string(allocator); defer allocator.free(utf8_str); return hash_fn(utf8_str); }, @@ -3535,18 +3579,18 @@ pub const Expr = struct { } } - pub inline fn asStringCloned(expr: *const Expr, allocator: std.mem.Allocator) ?string { + pub inline fn asStringCloned(expr: *const Expr, allocator: std.mem.Allocator) OOM!?string { switch (expr.data) { - .e_string => |str| return str.stringCloned(allocator) catch bun.outOfMemory(), - .e_utf8_string => |str| return allocator.dupe(u8, str.data) catch bun.outOfMemory(), + .e_string => |str| return try str.stringCloned(allocator), + .e_utf8_string => |str| return try allocator.dupe(u8, str.data), else => return null, } } - pub inline fn asStringZ(expr: *const Expr, allocator: std.mem.Allocator) ?stringZ { + pub inline fn asStringZ(expr: *const Expr, allocator: std.mem.Allocator) OOM!?stringZ { switch (expr.data) { - .e_string => |str| return str.stringZ(allocator) catch bun.outOfMemory(), - .e_utf8_string => |str| return allocator.dupeZ(u8, str.data) catch bun.outOfMemory(), + .e_string => |str| return try str.stringZ(allocator), + .e_utf8_string => |str| return try allocator.dupeZ(u8, str.data), else => return null, } } @@ -3559,6 +3603,12 @@ pub const Expr = struct { return expr.data.e_boolean.value; } + pub fn asNumber(expr: *const Expr) ?f64 { + if (expr.data != .e_number) return null; + + return expr.data.e_number.value; + } + pub const EFlags = enum { none, ts_decorator }; const Serializable = struct { diff --git a/src/js_printer.zig b/src/js_printer.zig index 026e8b30e9..5bc363937f 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -336,31 +336,6 @@ pub fn quoteForJSONBuffer(text: []const u8, bytes: *MutableString, comptime asci bytes.appendChar('"') catch unreachable; } -const JSONFormatter = struct { - input: []const u8, - - pub fn format(self: JSONFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - try writeJSONString(self.input, @TypeOf(writer), writer, .latin1); - } -}; - -const JSONFormatterUTF8 = struct { - input: []const u8, - - pub fn format(self: JSONFormatterUTF8, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - try writeJSONString(self.input, @TypeOf(writer), writer, .utf8); - } -}; - -/// Expects latin1 -pub fn formatJSONString(text: []const u8) JSONFormatter { - return .{ .input = text }; -} - -pub fn formatJSONStringUTF8(text: []const u8) JSONFormatterUTF8 { - return .{ .input = text }; -} - pub fn writeJSONString(input: []const u8, comptime Writer: type, writer: Writer, comptime encoding: strings.Encoding) !void { try writer.writeAll("\""); var text = input; diff --git a/src/json_parser.zig b/src/json_parser.zig index 56a5e6ac88..4998d86f0e 100644 --- a/src/json_parser.zig +++ b/src/json_parser.zig @@ -710,7 +710,7 @@ var empty_array_data = Expr.Data{ .e_array = &empty_array }; /// Parse JSON /// This leaves UTF-16 strings as UTF-16 strings /// The JavaScript Printer will handle escaping strings if necessary -pub fn ParseJSON( +pub fn parse( source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator, @@ -743,7 +743,7 @@ pub fn ParseJSON( /// This eagerly transcodes UTF-16 strings into UTF-8 strings /// Use this when the text may need to be reprinted to disk as JSON (and not as JavaScript) /// Eagerly converting UTF-8 to UTF-16 can cause a performance issue -pub fn ParsePackageJSONUTF8( +pub fn parsePackageJSONUTF8( source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator, @@ -779,7 +779,7 @@ pub fn ParsePackageJSONUTF8( return try parser.parseExpr(false, true); } -pub fn ParsePackageJSONUTF8AlwaysDecode( +pub fn parsePackageJSONUTF8AlwaysDecode( source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator, @@ -820,7 +820,7 @@ const JsonResult = struct { indentation: Indentation = .{}, }; -pub fn ParsePackageJSONUTF8WithOpts( +pub fn parsePackageJSONUTF8WithOpts( source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator, @@ -864,15 +864,15 @@ pub fn ParsePackageJSONUTF8WithOpts( /// This eagerly transcodes UTF-16 strings into UTF-8 strings /// Use this when the text may need to be reprinted to disk as JSON (and not as JavaScript) /// Eagerly converting UTF-8 to UTF-16 can cause a performance issue -pub fn ParseJSONUTF8( +pub fn parseUTF8( source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator, ) !Expr { - return try ParseJSONUTF8Impl(source, log, allocator, false); + return try parseUTF8Impl(source, log, allocator, false); } -pub fn ParseJSONUTF8Impl( +pub fn parseUTF8Impl( source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator, @@ -909,7 +909,7 @@ pub fn ParseJSONUTF8Impl( } return result; } -pub fn ParseJSONForMacro(source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator) !Expr { +pub fn parseForMacro(source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator) !Expr { switch (source.contents.len) { // This is to be consisntent with how disabled JS files are handled 0 => { @@ -944,7 +944,7 @@ pub const JSONParseResult = struct { }; }; -pub fn ParseJSONForBundling(source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator) !JSONParseResult { +pub fn parseForBundling(source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator) !JSONParseResult { switch (source.contents.len) { // This is to be consisntent with how disabled JS files are handled 0 => { @@ -973,7 +973,7 @@ pub fn ParseJSONForBundling(source: *const logger.Source, log: *logger.Log, allo // threadlocal var env_json_auto_quote_buffer: MutableString = undefined; // threadlocal var env_json_auto_quote_buffer_loaded: bool = false; -pub fn ParseEnvJSON(source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator) !Expr { +pub fn parseEnvJSON(source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator) !Expr { switch (source.contents.len) { // This is to be consisntent with how disabled JS files are handled 0 => { @@ -1024,7 +1024,7 @@ pub fn ParseEnvJSON(source: *const logger.Source, log: *logger.Log, allocator: s } } -pub fn ParseTSConfig(source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator, comptime force_utf8: bool) !Expr { +pub fn parseTSConfig(source: *const logger.Source, log: *logger.Log, allocator: std.mem.Allocator, comptime force_utf8: bool) !Expr { switch (source.contents.len) { // This is to be consisntent with how disabled JS files are handled 0 => { @@ -1073,7 +1073,7 @@ fn expectPrintedJSON(_contents: string, expected: string) !void { "source.json", contents, ); - const expr = try ParseJSON(&source, &log, default_allocator); + const expr = try parse(&source, &log, default_allocator); if (log.msgs.items.len > 0) { Output.panic("--FAIL--\nExpr {s}\nLog: {s}\n--FAIL--", .{ expr, log.msgs.items[0].data.text }); diff --git a/src/libarchive/libarchive-bindings.zig b/src/libarchive/libarchive-bindings.zig index 924cd7b031..d9bd69630a 100644 --- a/src/libarchive/libarchive-bindings.zig +++ b/src/libarchive/libarchive-bindings.zig @@ -1,14 +1,16 @@ +const std = @import("std"); const bun = @import("root").bun; -pub const wchar_t = u16; -pub const la_int64_t = i64; -pub const la_ssize_t = isize; -pub const struct_archive = opaque {}; -pub const struct_archive_entry = opaque {}; -pub const archive_entry = struct_archive_entry; +const wchar_t = u16; +const la_int64_t = i64; +const la_ssize_t = isize; +const struct_archive = opaque {}; +const struct_archive_entry = opaque {}; +const archive_entry = struct_archive_entry; const mode_t = bun.Mode; const FILE = @import("std").c.FILE; // const time_t = @import("std").c.time_t; const dev_t = @import("std").c.dev_t; +const OOM = bun.OOM; pub const FileType = enum(mode_t) { regular = 0o100000, @@ -25,111 +27,267 @@ pub const SymlinkType = enum(c_int) { file = 1, directory = 2, }; -pub const time_t = isize; -pub const ARCHIVE_VERSION_ONLY_STRING = "3.5.3dev"; -pub const ARCHIVE_VERSION_STRING = "libarchive " ++ ARCHIVE_VERSION_ONLY_STRING; -pub const ARCHIVE_EOF = @as(c_int, 1); -pub const ARCHIVE_OK = @as(c_int, 0); -pub const ARCHIVE_RETRY = -@as(c_int, 10); -pub const ARCHIVE_WARN = -@as(c_int, 20); -pub const ARCHIVE_FAILED = -@as(c_int, 25); -pub const ARCHIVE_FATAL = -@as(c_int, 30); -pub const ARCHIVE_FILTER_NONE = @as(c_int, 0); -pub const ARCHIVE_FILTER_GZIP = @as(c_int, 1); -pub const ARCHIVE_FILTER_BZIP2 = @as(c_int, 2); -pub const ARCHIVE_FILTER_COMPRESS = @as(c_int, 3); -pub const ARCHIVE_FILTER_PROGRAM = @as(c_int, 4); -pub const ARCHIVE_FILTER_LZMA = @as(c_int, 5); -pub const ARCHIVE_FILTER_XZ = @as(c_int, 6); -pub const ARCHIVE_FILTER_UU = @as(c_int, 7); -pub const ARCHIVE_FILTER_RPM = @as(c_int, 8); -pub const ARCHIVE_FILTER_LZIP = @as(c_int, 9); -pub const ARCHIVE_FILTER_LRZIP = @as(c_int, 10); -pub const ARCHIVE_FILTER_LZOP = @as(c_int, 11); -pub const ARCHIVE_FILTER_GRZIP = @as(c_int, 12); -pub const ARCHIVE_FILTER_LZ4 = @as(c_int, 13); -pub const ARCHIVE_FILTER_ZSTD = @as(c_int, 14); -pub const ARCHIVE_COMPRESSION_NONE = ARCHIVE_FILTER_NONE; -pub const ARCHIVE_COMPRESSION_GZIP = ARCHIVE_FILTER_GZIP; -pub const ARCHIVE_COMPRESSION_BZIP2 = ARCHIVE_FILTER_BZIP2; -pub const ARCHIVE_COMPRESSION_COMPRESS = ARCHIVE_FILTER_COMPRESS; -pub const ARCHIVE_COMPRESSION_PROGRAM = ARCHIVE_FILTER_PROGRAM; -pub const ARCHIVE_COMPRESSION_LZMA = ARCHIVE_FILTER_LZMA; -pub const ARCHIVE_COMPRESSION_XZ = ARCHIVE_FILTER_XZ; -pub const ARCHIVE_COMPRESSION_UU = ARCHIVE_FILTER_UU; -pub const ARCHIVE_COMPRESSION_RPM = ARCHIVE_FILTER_RPM; -pub const ARCHIVE_COMPRESSION_LZIP = ARCHIVE_FILTER_LZIP; -pub const ARCHIVE_COMPRESSION_LRZIP = ARCHIVE_FILTER_LRZIP; -pub const ARCHIVE_FORMAT_BASE_MASK = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xff0000, .hexadecimal); -pub const ARCHIVE_FORMAT_CPIO = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x10000, .hexadecimal); -pub const ARCHIVE_FORMAT_CPIO_POSIX = ARCHIVE_FORMAT_CPIO | @as(c_int, 1); -pub const ARCHIVE_FORMAT_CPIO_BIN_LE = ARCHIVE_FORMAT_CPIO | @as(c_int, 2); -pub const ARCHIVE_FORMAT_CPIO_BIN_BE = ARCHIVE_FORMAT_CPIO | @as(c_int, 3); -pub const ARCHIVE_FORMAT_CPIO_SVR4_NOCRC = ARCHIVE_FORMAT_CPIO | @as(c_int, 4); -pub const ARCHIVE_FORMAT_CPIO_SVR4_CRC = ARCHIVE_FORMAT_CPIO | @as(c_int, 5); -pub const ARCHIVE_FORMAT_CPIO_AFIO_LARGE = ARCHIVE_FORMAT_CPIO | @as(c_int, 6); -pub const ARCHIVE_FORMAT_CPIO_PWB = ARCHIVE_FORMAT_CPIO | @as(c_int, 7); -pub const ARCHIVE_FORMAT_SHAR = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x20000, .hexadecimal); -pub const ARCHIVE_FORMAT_SHAR_BASE = ARCHIVE_FORMAT_SHAR | @as(c_int, 1); -pub const ARCHIVE_FORMAT_SHAR_DUMP = ARCHIVE_FORMAT_SHAR | @as(c_int, 2); -pub const ARCHIVE_FORMAT_TAR = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x30000, .hexadecimal); -pub const ARCHIVE_FORMAT_TAR_USTAR = ARCHIVE_FORMAT_TAR | @as(c_int, 1); -pub const ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE = ARCHIVE_FORMAT_TAR | @as(c_int, 2); -pub const ARCHIVE_FORMAT_TAR_PAX_RESTRICTED = ARCHIVE_FORMAT_TAR | @as(c_int, 3); -pub const ARCHIVE_FORMAT_TAR_GNUTAR = ARCHIVE_FORMAT_TAR | @as(c_int, 4); -pub const ARCHIVE_FORMAT_ISO9660 = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x40000, .hexadecimal); -pub const ARCHIVE_FORMAT_ISO9660_ROCKRIDGE = ARCHIVE_FORMAT_ISO9660 | @as(c_int, 1); -pub const ARCHIVE_FORMAT_ZIP = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x50000, .hexadecimal); -pub const ARCHIVE_FORMAT_EMPTY = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x60000, .hexadecimal); -pub const ARCHIVE_FORMAT_AR = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x70000, .hexadecimal); -pub const ARCHIVE_FORMAT_AR_GNU = ARCHIVE_FORMAT_AR | @as(c_int, 1); -pub const ARCHIVE_FORMAT_AR_BSD = ARCHIVE_FORMAT_AR | @as(c_int, 2); -pub const ARCHIVE_FORMAT_MTREE = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x80000, .hexadecimal); -pub const ARCHIVE_FORMAT_RAW = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x90000, .hexadecimal); -pub const ARCHIVE_FORMAT_XAR = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xA0000, .hexadecimal); -pub const ARCHIVE_FORMAT_LHA = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xB0000, .hexadecimal); -pub const ARCHIVE_FORMAT_CAB = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xC0000, .hexadecimal); -pub const ARCHIVE_FORMAT_RAR = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xD0000, .hexadecimal); -pub const ARCHIVE_FORMAT_7ZIP = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xE0000, .hexadecimal); -pub const ARCHIVE_FORMAT_WARC = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xF0000, .hexadecimal); -pub const ARCHIVE_FORMAT_RAR_V5 = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x100000, .hexadecimal); -pub const ARCHIVE_READ_FORMAT_CAPS_NONE = @as(c_int, 0); -pub const ARCHIVE_READ_FORMAT_CAPS_ENCRYPT_DATA = @as(c_int, 1) << @as(c_int, 0); -pub const ARCHIVE_READ_FORMAT_CAPS_ENCRYPT_METADATA = @as(c_int, 1) << @as(c_int, 1); -pub const ARCHIVE_READ_FORMAT_ENCRYPTION_UNSUPPORTED = -@as(c_int, 2); -pub const ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW = -@as(c_int, 1); -pub const ARCHIVE_EXTRACT_OWNER = @as(c_int, 0x0001); -pub const ARCHIVE_EXTRACT_PERM = @as(c_int, 0x0002); -pub const ARCHIVE_EXTRACT_TIME = @as(c_int, 0x0004); -pub const ARCHIVE_EXTRACT_NO_OVERWRITE = @as(c_int, 0x0008); -pub const ARCHIVE_EXTRACT_UNLINK = @as(c_int, 0x0010); -pub const ARCHIVE_EXTRACT_ACL = @as(c_int, 0x0020); -pub const ARCHIVE_EXTRACT_FFLAGS = @as(c_int, 0x0040); -pub const ARCHIVE_EXTRACT_XATTR = @as(c_int, 0x0080); -pub const ARCHIVE_EXTRACT_SECURE_SYMLINKS = @as(c_int, 0x0100); -pub const ARCHIVE_EXTRACT_SECURE_NODOTDOT = @as(c_int, 0x0200); -pub const ARCHIVE_EXTRACT_NO_AUTODIR = @as(c_int, 0x0400); -pub const ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER = @as(c_int, 0x0800); -pub const ARCHIVE_EXTRACT_SPARSE = @as(c_int, 0x1000); -pub const ARCHIVE_EXTRACT_MAC_METADATA = @as(c_int, 0x2000); -pub const ARCHIVE_EXTRACT_NO_HFS_COMPRESSION = @as(c_int, 0x4000); -pub const ARCHIVE_EXTRACT_HFS_COMPRESSION_FORCED = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x8000, .hexadecimal); -pub const ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x10000, .hexadecimal); -pub const ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x20000, .hexadecimal); -pub const ARCHIVE_EXTRACT_SAFE_WRITES = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x40000, .hexadecimal); -pub const ARCHIVE_READDISK_RESTORE_ATIME = @as(c_int, 0x0001); -pub const ARCHIVE_READDISK_HONOR_NODUMP = @as(c_int, 0x0002); -pub const ARCHIVE_READDISK_MAC_COPYFILE = @as(c_int, 0x0004); -pub const ARCHIVE_READDISK_NO_TRAVERSE_MOUNTS = @as(c_int, 0x0008); -pub const ARCHIVE_READDISK_NO_XATTR = @as(c_int, 0x0010); -pub const ARCHIVE_READDISK_NO_ACL = @as(c_int, 0x0020); -pub const ARCHIVE_READDISK_NO_FFLAGS = @as(c_int, 0x0040); -pub const ARCHIVE_MATCH_MTIME = @as(c_int, 0x0100); -pub const ARCHIVE_MATCH_CTIME = @as(c_int, 0x0200); -pub const ARCHIVE_MATCH_NEWER = @as(c_int, 0x0001); -pub const ARCHIVE_MATCH_OLDER = @as(c_int, 0x0002); -pub const ARCHIVE_MATCH_EQUAL = @as(c_int, 0x0010); +const time_t = isize; + +pub const Flags = struct { + pub const Extract = enum(c_int) { + owner = ARCHIVE_EXTRACT_OWNER, + perm = ARCHIVE_EXTRACT_PERM, + time = ARCHIVE_EXTRACT_TIME, + no_overwrite = ARCHIVE_EXTRACT_NO_OVERWRITE, + unlink = ARCHIVE_EXTRACT_UNLINK, + acl = ARCHIVE_EXTRACT_ACL, + fflags = ARCHIVE_EXTRACT_FFLAGS, + xattr = ARCHIVE_EXTRACT_XATTR, + secure_symlinks = ARCHIVE_EXTRACT_SECURE_SYMLINKS, + secure_nodotdot = ARCHIVE_EXTRACT_SECURE_NODOTDOT, + no_autodir = ARCHIVE_EXTRACT_NO_AUTODIR, + no_overwrite_newer = ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER, + sparse = ARCHIVE_EXTRACT_SPARSE, + mac_metadata = ARCHIVE_EXTRACT_MAC_METADATA, + no_hfs_compression = ARCHIVE_EXTRACT_NO_HFS_COMPRESSION, + hfs_compression_forced = ARCHIVE_EXTRACT_HFS_COMPRESSION_FORCED, + secure_noabsolutepaths = ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS, + clear_nochange_fflags = ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS, + safe_writes = ARCHIVE_EXTRACT_SAFE_WRITES, + }; + + // Deprecated + // pub const Compression = enum(c_int) { + // none = ARCHIVE_COMPRESSION_NONE, + // gzip = ARCHIVE_COMPRESSION_GZIP, + // bzip2 = ARCHIVE_COMPRESSION_BZIP2, + // compress = ARCHIVE_COMPRESSION_COMPRESS, + // program = ARCHIVE_COMPRESSION_PROGRAM, + // lzma = ARCHIVE_COMPRESSION_LZMA, + // xz = ARCHIVE_COMPRESSION_XZ, + // uu = ARCHIVE_COMPRESSION_UU, + // rpm = ARCHIVE_COMPRESSION_RPM, + // lzip = ARCHIVE_COMPRESSION_LZIP, + // lrzip = ARCHIVE_COMPRESSION_LRZIP, + // }; + + pub const Format = enum(c_int) { + base_mask = ARCHIVE_FORMAT_BASE_MASK, + cpio = ARCHIVE_FORMAT_CPIO, + cpio_posix = ARCHIVE_FORMAT_CPIO_POSIX, + cpio_bin_le = ARCHIVE_FORMAT_CPIO_BIN_LE, + cpio_bin_be = ARCHIVE_FORMAT_CPIO_BIN_BE, + cpio_svr4_nocrc = ARCHIVE_FORMAT_CPIO_SVR4_NOCRC, + cpio_svr4_crc = ARCHIVE_FORMAT_CPIO_SVR4_CRC, + cpio_afio_large = ARCHIVE_FORMAT_CPIO_AFIO_LARGE, + cpio_pwb = ARCHIVE_FORMAT_CPIO_PWB, + shar = ARCHIVE_FORMAT_SHAR, + shar_base = ARCHIVE_FORMAT_SHAR_BASE, + shar_dump = ARCHIVE_FORMAT_SHAR_DUMP, + tar = ARCHIVE_FORMAT_TAR, + tar_ustar = ARCHIVE_FORMAT_TAR_USTAR, + tar_pax_interchange = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE, + tar_pax_restricted = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED, + tar_gnutar = ARCHIVE_FORMAT_TAR_GNUTAR, + iso9660 = ARCHIVE_FORMAT_ISO9660, + iso9660_rockridge = ARCHIVE_FORMAT_ISO9660_ROCKRIDGE, + zip = ARCHIVE_FORMAT_ZIP, + empty = ARCHIVE_FORMAT_EMPTY, + ar = ARCHIVE_FORMAT_AR, + ar_gnu = ARCHIVE_FORMAT_AR_GNU, + ar_bsd = ARCHIVE_FORMAT_AR_BSD, + mtree = ARCHIVE_FORMAT_MTREE, + raw = ARCHIVE_FORMAT_RAW, + xar = ARCHIVE_FORMAT_XAR, + lha = ARCHIVE_FORMAT_LHA, + cab = ARCHIVE_FORMAT_CAB, + rar = ARCHIVE_FORMAT_RAR, + @"7zip" = ARCHIVE_FORMAT_7ZIP, + warc = ARCHIVE_FORMAT_WARC, + rar_v5 = ARCHIVE_FORMAT_RAR_V5, + }; + + pub const Filter = enum(c_int) { + none = ARCHIVE_FILTER_NONE, + gzip = ARCHIVE_FILTER_GZIP, + bzip2 = ARCHIVE_FILTER_BZIP2, + compress = ARCHIVE_FILTER_COMPRESS, + program = ARCHIVE_FILTER_PROGRAM, + lzma = ARCHIVE_FILTER_LZMA, + xz = ARCHIVE_FILTER_XZ, + uu = ARCHIVE_FILTER_UU, + rpm = ARCHIVE_FILTER_RPM, + lzip = ARCHIVE_FILTER_LZIP, + lrzip = ARCHIVE_FILTER_LRZIP, + lzop = ARCHIVE_FILTER_LZOP, + grzip = ARCHIVE_FILTER_GRZIP, + lz4 = ARCHIVE_FILTER_LZ4, + zstd = ARCHIVE_FILTER_ZSTD, + }; + + pub const EntryDigest = enum(c_int) { + md5 = ARCHIVE_ENTRY_DIGEST_MD5, + rmd160 = ARCHIVE_ENTRY_DIGEST_RMD160, + sha1 = ARCHIVE_ENTRY_DIGEST_SHA1, + sha256 = ARCHIVE_ENTRY_DIGEST_SHA256, + sha384 = ARCHIVE_ENTRY_DIGEST_SHA384, + sha512 = ARCHIVE_ENTRY_DIGEST_SHA512, + }; + + pub const EntryACL = enum(c_int) { + entry_acl_execute = ARCHIVE_ENTRY_ACL_EXECUTE, + write = ARCHIVE_ENTRY_ACL_WRITE, + read = ARCHIVE_ENTRY_ACL_READ, + read_data = ARCHIVE_ENTRY_ACL_READ_DATA, + list_directory = ARCHIVE_ENTRY_ACL_LIST_DIRECTORY, + write_data = ARCHIVE_ENTRY_ACL_WRITE_DATA, + add_file = ARCHIVE_ENTRY_ACL_ADD_FILE, + append_data = ARCHIVE_ENTRY_ACL_APPEND_DATA, + add_subdirectory = ARCHIVE_ENTRY_ACL_ADD_SUBDIRECTORY, + read_named_attrs = ARCHIVE_ENTRY_ACL_READ_NAMED_ATTRS, + write_named_attrs = ARCHIVE_ENTRY_ACL_WRITE_NAMED_ATTRS, + delete_child = ARCHIVE_ENTRY_ACL_DELETE_CHILD, + read_attributes = ARCHIVE_ENTRY_ACL_READ_ATTRIBUTES, + write_attributes = ARCHIVE_ENTRY_ACL_WRITE_ATTRIBUTES, + delete = ARCHIVE_ENTRY_ACL_DELETE, + read_acl = ARCHIVE_ENTRY_ACL_READ_ACL, + write_acl = ARCHIVE_ENTRY_ACL_WRITE_ACL, + write_owner = ARCHIVE_ENTRY_ACL_WRITE_OWNER, + synchronize = ARCHIVE_ENTRY_ACL_SYNCHRONIZE, + perms_posix1_e = ARCHIVE_ENTRY_ACL_PERMS_POSIX1E, + perms_nfs4 = ARCHIVE_ENTRY_ACL_PERMS_NFS4, + entry_inherited = ARCHIVE_ENTRY_ACL_ENTRY_INHERITED, + entry_file_inherit = ARCHIVE_ENTRY_ACL_ENTRY_FILE_INHERIT, + entry_directory_inherit = ARCHIVE_ENTRY_ACL_ENTRY_DIRECTORY_INHERIT, + entry_no_propagate_inherit = ARCHIVE_ENTRY_ACL_ENTRY_NO_PROPAGATE_INHERIT, + entry_inherit_only = ARCHIVE_ENTRY_ACL_ENTRY_INHERIT_ONLY, + entry_successful_access = ARCHIVE_ENTRY_ACL_ENTRY_SUCCESSFUL_ACCESS, + entry_failed_access = ARCHIVE_ENTRY_ACL_ENTRY_FAILED_ACCESS, + inheritance_nfs4 = ARCHIVE_ENTRY_ACL_INHERITANCE_NFS4, + type_access = ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + type_default = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT, + type_allow = ARCHIVE_ENTRY_ACL_TYPE_ALLOW, + type_deny = ARCHIVE_ENTRY_ACL_TYPE_DENY, + type_audit = ARCHIVE_ENTRY_ACL_TYPE_AUDIT, + type_alarm = ARCHIVE_ENTRY_ACL_TYPE_ALARM, + type_posix1_e = ARCHIVE_ENTRY_ACL_TYPE_POSIX1E, + type_nfs4 = ARCHIVE_ENTRY_ACL_TYPE_NFS4, + user = ARCHIVE_ENTRY_ACL_USER, + user_obj = ARCHIVE_ENTRY_ACL_USER_OBJ, + group = ARCHIVE_ENTRY_ACL_GROUP, + group_obj = ARCHIVE_ENTRY_ACL_GROUP_OBJ, + mask = ARCHIVE_ENTRY_ACL_MASK, + other = ARCHIVE_ENTRY_ACL_OTHER, + everyone = ARCHIVE_ENTRY_ACL_EVERYONE, + style_extra_id = ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID, + style_mark_default = ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT, + style_solaris = ARCHIVE_ENTRY_ACL_STYLE_SOLARIS, + style_separator_comma = ARCHIVE_ENTRY_ACL_STYLE_SEPARATOR_COMMA, + style_compact = ARCHIVE_ENTRY_ACL_STYLE_COMPACT, + }; +}; + +const ARCHIVE_VERSION_ONLY_STRING = "3.5.3dev"; +const ARCHIVE_VERSION_STRING = "libarchive " ++ ARCHIVE_VERSION_ONLY_STRING; +const ARCHIVE_EOF = @as(c_int, 1); +const ARCHIVE_OK = @as(c_int, 0); +const ARCHIVE_RETRY = -@as(c_int, 10); +const ARCHIVE_WARN = -@as(c_int, 20); +const ARCHIVE_FAILED = -@as(c_int, 25); +const ARCHIVE_FATAL = -@as(c_int, 30); +const ARCHIVE_FILTER_NONE = @as(c_int, 0); +const ARCHIVE_FILTER_GZIP = @as(c_int, 1); +const ARCHIVE_FILTER_BZIP2 = @as(c_int, 2); +const ARCHIVE_FILTER_COMPRESS = @as(c_int, 3); +const ARCHIVE_FILTER_PROGRAM = @as(c_int, 4); +const ARCHIVE_FILTER_LZMA = @as(c_int, 5); +const ARCHIVE_FILTER_XZ = @as(c_int, 6); +const ARCHIVE_FILTER_UU = @as(c_int, 7); +const ARCHIVE_FILTER_RPM = @as(c_int, 8); +const ARCHIVE_FILTER_LZIP = @as(c_int, 9); +const ARCHIVE_FILTER_LRZIP = @as(c_int, 10); +const ARCHIVE_FILTER_LZOP = @as(c_int, 11); +const ARCHIVE_FILTER_GRZIP = @as(c_int, 12); +const ARCHIVE_FILTER_LZ4 = @as(c_int, 13); +const ARCHIVE_FILTER_ZSTD = @as(c_int, 14); +// Deprecated +// pub const ARCHIVE_COMPRESSION_NONE = ARCHIVE_FILTER_NONE; +// pub const ARCHIVE_COMPRESSION_GZIP = ARCHIVE_FILTER_GZIP; +// pub const ARCHIVE_COMPRESSION_BZIP2 = ARCHIVE_FILTER_BZIP2; +// pub const ARCHIVE_COMPRESSION_COMPRESS = ARCHIVE_FILTER_COMPRESS; +// pub const ARCHIVE_COMPRESSION_PROGRAM = ARCHIVE_FILTER_PROGRAM; +// pub const ARCHIVE_COMPRESSION_LZMA = ARCHIVE_FILTER_LZMA; +// pub const ARCHIVE_COMPRESSION_XZ = ARCHIVE_FILTER_XZ; +// pub const ARCHIVE_COMPRESSION_UU = ARCHIVE_FILTER_UU; +// pub const ARCHIVE_COMPRESSION_RPM = ARCHIVE_FILTER_RPM; +// pub const ARCHIVE_COMPRESSION_LZIP = ARCHIVE_FILTER_LZIP; +// pub const ARCHIVE_COMPRESSION_LRZIP = ARCHIVE_FILTER_LRZIP; +const ARCHIVE_FORMAT_BASE_MASK = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xff0000, .hexadecimal); +const ARCHIVE_FORMAT_CPIO = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x10000, .hexadecimal); +const ARCHIVE_FORMAT_CPIO_POSIX = ARCHIVE_FORMAT_CPIO | @as(c_int, 1); +const ARCHIVE_FORMAT_CPIO_BIN_LE = ARCHIVE_FORMAT_CPIO | @as(c_int, 2); +const ARCHIVE_FORMAT_CPIO_BIN_BE = ARCHIVE_FORMAT_CPIO | @as(c_int, 3); +const ARCHIVE_FORMAT_CPIO_SVR4_NOCRC = ARCHIVE_FORMAT_CPIO | @as(c_int, 4); +const ARCHIVE_FORMAT_CPIO_SVR4_CRC = ARCHIVE_FORMAT_CPIO | @as(c_int, 5); +const ARCHIVE_FORMAT_CPIO_AFIO_LARGE = ARCHIVE_FORMAT_CPIO | @as(c_int, 6); +const ARCHIVE_FORMAT_CPIO_PWB = ARCHIVE_FORMAT_CPIO | @as(c_int, 7); +const ARCHIVE_FORMAT_SHAR = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x20000, .hexadecimal); +const ARCHIVE_FORMAT_SHAR_BASE = ARCHIVE_FORMAT_SHAR | @as(c_int, 1); +const ARCHIVE_FORMAT_SHAR_DUMP = ARCHIVE_FORMAT_SHAR | @as(c_int, 2); +const ARCHIVE_FORMAT_TAR = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x30000, .hexadecimal); +const ARCHIVE_FORMAT_TAR_USTAR = ARCHIVE_FORMAT_TAR | @as(c_int, 1); +const ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE = ARCHIVE_FORMAT_TAR | @as(c_int, 2); +const ARCHIVE_FORMAT_TAR_PAX_RESTRICTED = ARCHIVE_FORMAT_TAR | @as(c_int, 3); +const ARCHIVE_FORMAT_TAR_GNUTAR = ARCHIVE_FORMAT_TAR | @as(c_int, 4); +const ARCHIVE_FORMAT_ISO9660 = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x40000, .hexadecimal); +const ARCHIVE_FORMAT_ISO9660_ROCKRIDGE = ARCHIVE_FORMAT_ISO9660 | @as(c_int, 1); +const ARCHIVE_FORMAT_ZIP = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x50000, .hexadecimal); +const ARCHIVE_FORMAT_EMPTY = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x60000, .hexadecimal); +const ARCHIVE_FORMAT_AR = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x70000, .hexadecimal); +const ARCHIVE_FORMAT_AR_GNU = ARCHIVE_FORMAT_AR | @as(c_int, 1); +const ARCHIVE_FORMAT_AR_BSD = ARCHIVE_FORMAT_AR | @as(c_int, 2); +const ARCHIVE_FORMAT_MTREE = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x80000, .hexadecimal); +const ARCHIVE_FORMAT_RAW = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x90000, .hexadecimal); +const ARCHIVE_FORMAT_XAR = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xA0000, .hexadecimal); +const ARCHIVE_FORMAT_LHA = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xB0000, .hexadecimal); +const ARCHIVE_FORMAT_CAB = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xC0000, .hexadecimal); +const ARCHIVE_FORMAT_RAR = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xD0000, .hexadecimal); +const ARCHIVE_FORMAT_7ZIP = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xE0000, .hexadecimal); +const ARCHIVE_FORMAT_WARC = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0xF0000, .hexadecimal); +const ARCHIVE_FORMAT_RAR_V5 = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x100000, .hexadecimal); +const ARCHIVE_READ_FORMAT_CAPS_NONE = @as(c_int, 0); +const ARCHIVE_READ_FORMAT_CAPS_ENCRYPT_DATA = @as(c_int, 1) << @as(c_int, 0); +const ARCHIVE_READ_FORMAT_CAPS_ENCRYPT_METADATA = @as(c_int, 1) << @as(c_int, 1); +const ARCHIVE_READ_FORMAT_ENCRYPTION_UNSUPPORTED = -@as(c_int, 2); +const ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW = -@as(c_int, 1); +const ARCHIVE_EXTRACT_OWNER = @as(c_int, 0x0001); +const ARCHIVE_EXTRACT_PERM = @as(c_int, 0x0002); +const ARCHIVE_EXTRACT_TIME = @as(c_int, 0x0004); +const ARCHIVE_EXTRACT_NO_OVERWRITE = @as(c_int, 0x0008); +const ARCHIVE_EXTRACT_UNLINK = @as(c_int, 0x0010); +const ARCHIVE_EXTRACT_ACL = @as(c_int, 0x0020); +const ARCHIVE_EXTRACT_FFLAGS = @as(c_int, 0x0040); +const ARCHIVE_EXTRACT_XATTR = @as(c_int, 0x0080); +const ARCHIVE_EXTRACT_SECURE_SYMLINKS = @as(c_int, 0x0100); +const ARCHIVE_EXTRACT_SECURE_NODOTDOT = @as(c_int, 0x0200); +const ARCHIVE_EXTRACT_NO_AUTODIR = @as(c_int, 0x0400); +const ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER = @as(c_int, 0x0800); +const ARCHIVE_EXTRACT_SPARSE = @as(c_int, 0x1000); +const ARCHIVE_EXTRACT_MAC_METADATA = @as(c_int, 0x2000); +const ARCHIVE_EXTRACT_NO_HFS_COMPRESSION = @as(c_int, 0x4000); +const ARCHIVE_EXTRACT_HFS_COMPRESSION_FORCED = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x8000, .hexadecimal); +const ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x10000, .hexadecimal); +const ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x20000, .hexadecimal); +const ARCHIVE_EXTRACT_SAFE_WRITES = @import("std").zig.c_translation.promoteIntLiteral(c_int, 0x40000, .hexadecimal); +const ARCHIVE_READDISK_RESTORE_ATIME = @as(c_int, 0x0001); +const ARCHIVE_READDISK_HONOR_NODUMP = @as(c_int, 0x0002); +const ARCHIVE_READDISK_MAC_COPYFILE = @as(c_int, 0x0004); +const ARCHIVE_READDISK_NO_TRAVERSE_MOUNTS = @as(c_int, 0x0008); +const ARCHIVE_READDISK_NO_XATTR = @as(c_int, 0x0010); +const ARCHIVE_READDISK_NO_ACL = @as(c_int, 0x0020); +const ARCHIVE_READDISK_NO_FFLAGS = @as(c_int, 0x0040); +const ARCHIVE_MATCH_MTIME = @as(c_int, 0x0100); +const ARCHIVE_MATCH_CTIME = @as(c_int, 0x0200); +const ARCHIVE_MATCH_NEWER = @as(c_int, 0x0001); +const ARCHIVE_MATCH_OLDER = @as(c_int, 0x0002); +const ARCHIVE_MATCH_EQUAL = @as(c_int, 0x0010); pub const Archive = opaque { pub const Result = enum(i32) { @@ -176,7 +334,9 @@ pub const Archive = opaque { extern fn archive_error_string(*Archive) [*c]const u8; pub fn errorString(archive: *Archive) []const u8 { - return bun.sliceTo(archive_error_string(archive), 0); + const err_str = archive_error_string(archive); + if (err_str == null) return ""; + return bun.sliceTo(err_str, 0); } extern fn archive_write_new() *Archive; @@ -189,7 +349,7 @@ pub const Archive = opaque { return archive_write_close(archive); } - pub extern fn archive_write_finish(*Archive) Result; + extern fn archive_write_finish(*Archive) Result; pub fn writeFinish(archive: *Archive) Result { return archive_write_finish(archive); } @@ -199,7 +359,7 @@ pub const Archive = opaque { return archive_free(archive); } - pub extern fn archive_write_set_options(_a: *Archive, opts: [*c]const u8) Result; + extern fn archive_write_set_options(_a: *Archive, opts: [*c]const u8) Result; pub fn writeSetOptions(archive: *Archive, opts: [:0]const u8) Result { return archive_write_set_options(archive, opts); } @@ -209,46 +369,114 @@ pub const Archive = opaque { return archive_write_set_format_pax_restricted(archive); } - pub extern fn archive_write_set_format_gnutar(*Archive) Result; + extern fn archive_write_set_format_gnutar(*Archive) Result; pub fn writeSetFormatGnutar(archive: *Archive) Result { return archive_write_set_format_gnutar(archive); } - pub extern fn archive_write_set_format_7zip(*Archive) Result; + extern fn archive_write_set_format_7zip(*Archive) Result; pub fn writeSetFormat7zip(archive: *Archive) Result { return archive_write_set_format_7zip(archive); } - pub extern fn archive_write_set_format_pax(*Archive) Result; + extern fn archive_write_set_format_pax(*Archive) Result; pub fn writeSetFormatPax(archive: *Archive) Result { return archive_write_set_format_pax(archive); } - pub extern fn archive_write_set_format_ustar(*Archive) Result; + extern fn archive_write_set_format_ustar(*Archive) Result; pub fn writeSetFormatUstar(archive: *Archive) Result { return archive_write_set_format_ustar(archive); } - pub extern fn archive_write_set_format_zip(*Archive) Result; + extern fn archive_write_set_format_zip(*Archive) Result; pub fn writeSetFormatZip(archive: *Archive) Result { return archive_write_set_format_zip(archive); } - pub extern fn archive_write_set_format_shar(*Archive) Result; + extern fn archive_write_set_format_shar(*Archive) Result; pub fn writeSetFormatShar(archive: *Archive) Result { return archive_write_set_format_shar(archive); } - extern fn archive_write_set_compression_gzip(*Archive) Result; - pub fn writeSetCompressionGzip(archive: *Archive) Result { - return archive_write_set_compression_gzip(archive); + extern fn archive_write_set_format(*struct_archive, format_code: i32) Result; + pub fn writeSetFormat(archive: *Archive, format: Flags.Format) Result { + return archive_write_set_format(archive, @intFromEnum(format)); } + // deprecated + // + // extern fn archive_write_set_compression_gzip(*Archive) Result; + // pub fn writeSetCompressionGzip(archive: *Archive) Result { + // return archive_write_set_compression_gzip(archive); + // } + extern fn archive_write_add_filter_gzip(*Archive) Result; pub fn writeAddFilterGzip(archive: *Archive) Result { return archive_write_add_filter_gzip(archive); } + extern fn archive_write_add_filter(*Archive, filter_code: i32) Result; + pub fn writeAddFilter(archive: *Archive, filter: Flags.Filter) Result { + return archive_write_add_filter(archive, @intFromEnum(filter)); + } + extern fn archive_write_add_filter_by_name(*Archive, name: [*c]const u8) Result; + pub fn writeAddFilterByName(archive: *Archive, name: [:0]const u8) Result { + return archive_write_add_filter_by_name(archive, name.ptr); + } + extern fn archive_write_add_filter_b64encode(*Archive) Result; + pub fn writeAddFilterB64encode(archive: *Archive) Result { + return archive_write_add_filter_b64encode(archive); + } + // extern fn archive_write_add_filter_bzip2(*Archive) Result; + // pub fn writeAddFilterBzip2(archive: *Archive) Result { + // return archive_write_add_filter_bzip2(archive); + // } + extern fn archive_write_add_filter_compress(*Archive) Result; + pub fn writeAddFilterCompress(archive: *Archive) Result { + return archive_write_add_filter_compress(archive); + } + extern fn archive_write_add_filter_grzip(*Archive) Result; + pub fn writeAddFilterGrzip(archive: *Archive) Result { + return archive_write_add_filter_grzip(archive); + } + extern fn archive_write_add_filter_lrzip(*Archive) Result; + pub fn writeAddFilterLrzip(archive: *Archive) Result { + return archive_write_add_filter_lrzip(archive); + } + extern fn archive_write_add_filter_lz4(*Archive) Result; + pub fn writeAddFilterLz4(archive: *Archive) Result { + return archive_write_add_filter_lz4(archive); + } + extern fn archive_write_add_filter_lzip(*Archive) Result; + pub fn writeAddFilterLzip(archive: *Archive) Result { + return archive_write_add_filter_lzip(archive); + } + extern fn archive_write_add_filter_lzma(*Archive) Result; + pub fn writeAddFilterLzma(archive: *Archive) Result { + return archive_write_add_filter_lzma(archive); + } + extern fn archive_write_add_filter_lzop(*Archive) Result; + pub fn writeAddFilterLzop(archive: *Archive) Result { + return archive_write_add_filter_lzop(archive); + } + extern fn archive_write_add_filter_none(*Archive) Result; + pub fn writeAddFilterNone(archive: *Archive) Result { + return archive_write_add_filter_none(archive); + } + extern fn archive_write_add_filter_uuencode(*Archive) Result; + pub fn writeAddFilterUuencode(archive: *Archive) Result { + return archive_write_add_filter_uuencode(archive); + } + extern fn archive_write_add_filter_xz(*Archive) Result; + pub fn writeAddFilterXz(archive: *Archive) Result { + return archive_write_add_filter_xz(archive); + } + extern fn archive_write_add_filter_zstd(*Archive) Result; + pub fn writeAddFilterZstd(archive: *Archive) Result { + return archive_write_add_filter_zstd(archive); + } + extern fn archive_write_set_filter_option(*Archive, [*c]const u8, [*c]const u8, [*c]const u8) Result; pub fn writeSetFilterOption(archive: *Archive, m: ?[:0]const u8, o: [:0]const u8, v: [:0]const u8) Result { return archive_write_set_filter_option(archive, m orelse null, o, v); @@ -259,12 +487,12 @@ pub const Archive = opaque { return archive_write_open_filename(archive, filename); } - pub extern fn archive_write_open_fd(*Archive, _fd: c_int) Result; + extern fn archive_write_open_fd(*Archive, _fd: c_int) Result; pub fn writeOpenFd(archive: *Archive, fd: bun.FileDescriptor) Result { return archive_write_open_fd(archive, fd.cast()); } - pub extern fn archive_write_open_memory(*Archive, _buffer: ?*anyopaque, _buffSize: usize, _used: [*c]usize) Result; + extern fn archive_write_open_memory(*Archive, _buffer: ?*anyopaque, _buffSize: usize, _used: [*c]usize) Result; pub fn writeOpenMemory(archive: *Archive, buf: ?*anyopaque, buf_size: usize, used: *usize) Result { return archive_write_open_memory(archive, buf, buf_size, used); } @@ -279,23 +507,260 @@ pub const Archive = opaque { return archive_write_data(archive, data.ptr, data.len); } - pub extern fn archive_write_finish_entry(*Archive) Result; + extern fn archive_write_finish_entry(*Archive) Result; pub fn writeFinishEntry(archive: *Archive) Result { return archive_write_finish_entry(archive); } - pub extern fn archive_write_free(*Archive) Result; + extern fn archive_write_free(*Archive) Result; pub fn writeFree(archive: *Archive) Result { return archive_write_free(archive); } + extern fn archive_read_new() *Archive; + pub fn readNew() *Archive { + return archive_read_new(); + } + + extern fn archive_read_close(*Archive) Result; + pub fn readClose(archive: *Archive) Result { + return archive_read_close(archive); + } + + pub extern fn archive_read_free(*Archive) Result; + pub fn readFree(archive: *Archive) Result { + return archive_read_free(archive); + } + + pub extern fn archive_read_finish(*Archive) Result; + pub fn readFinish(archive: *Archive) Result { + return archive_read_finish(archive); + } + + // these are deprecated + // + // extern fn archive_read_support_compression_all(*Archive) Result; + // pub fn readSupportCompressionAll(archive: *Archive) Result { + // return archive_read_support_compression_all(archive); + // } + // extern fn archive_read_support_compression_bzip2(*Archive) Result; + // pub fn readSupportCompressionBzip2(archive: *Archive) Result { + // return archive_read_support_compression_bzip2(archive); + // } + // extern fn archive_read_support_compression_compress(*Archive) Result; + // pub fn readSupportCompressionCompress(archive: *Archive) Result { + // return archive_read_support_compression_compress(archive); + // } + // extern fn archive_read_support_compression_gzip(*Archive) Result; + // pub fn readSupportCompressionGzip(archive: *Archive) Result { + // return archive_read_support_compression_gzip(archive); + // } + // extern fn archive_read_support_compression_lzip(*Archive) Result; + // pub fn readSupportCompressionLzip(archive: *Archive) Result { + // return archive_read_support_compression_lzip(archive); + // } + // extern fn archive_read_support_compression_lzma(*Archive) Result; + // pub fn readSupportCompressionLzma(archive: *Archive) Result { + // return archive_read_support_compression_lzma(archive); + // } + // extern fn archive_read_support_compression_none(*Archive) Result; + // pub fn readSupportCompressionNone(archive: *Archive) Result { + // return archive_read_support_compression_none(archive); + // } + // extern fn archive_read_support_compression_rpm(*Archive) Result; + // pub fn readSupportCompressionRpm(archive: *Archive) Result { + // return archive_read_support_compression_rpm(archive); + // } + // extern fn archive_read_support_compression_uu(*Archive) Result; + // pub fn readSupportCompressionUu(archive: *Archive) Result { + // return archive_read_support_compression_uu(archive); + // } + // extern fn archive_read_support_compression_xz(*Archive) Result; + // pub fn readSupportCompressionXz(archive: *Archive) Result { + // return archive_read_support_compression_xz(archive); + // } + + extern fn archive_read_support_format_7zip(*Archive) Result; + pub fn readSupportFormat7zip(archive: *Archive) Result { + return archive_read_support_format_7zip(archive); + } + extern fn archive_read_support_format_all(*Archive) Result; + pub fn readSupportFormatAll(archive: *Archive) Result { + return archive_read_support_format_all(archive); + } + extern fn archive_read_support_format_ar(*Archive) Result; + pub fn readSupportFormatAr(archive: *Archive) Result { + return archive_read_support_format_ar(archive); + } + extern fn archive_read_support_format_by_code(*Archive, c_int) Result; + pub fn readSupportFormatByCode(archive: *Archive, code: i32) Result { + return archive_read_support_format_by_code(archive, code); + } + extern fn archive_read_support_format_cab(*Archive) Result; + pub fn readSupportFormatCab(archive: *Archive) Result { + return archive_read_support_format_cab(archive); + } + extern fn archive_read_support_format_cpio(*Archive) Result; + pub fn readSupportFormatCpio(archive: *Archive) Result { + return archive_read_support_format_cpio(archive); + } + extern fn archive_read_support_format_empty(*Archive) Result; + pub fn readSupportFormatEmpty(archive: *Archive) Result { + return archive_read_support_format_empty(archive); + } + extern fn archive_read_support_format_gnutar(*Archive) Result; + pub fn readSupportFormatGnutar(archive: *Archive) Result { + return archive_read_support_format_gnutar(archive); + } + extern fn archive_read_support_format_iso9660(*Archive) Result; + pub fn readSupportFormatIso9660(archive: *Archive) Result { + return archive_read_support_format_iso9660(archive); + } + extern fn archive_read_support_format_lha(*Archive) Result; + pub fn readSupportFormatLha(archive: *Archive) Result { + return archive_read_support_format_lha(archive); + } + extern fn archive_read_support_format_mtree(*Archive) Result; + pub fn readSupportFormatMtree(archive: *Archive) Result { + return archive_read_support_format_mtree(archive); + } + extern fn archive_read_support_format_rar(*Archive) Result; + pub fn readSupportFormatRar(archive: *Archive) Result { + return archive_read_support_format_rar(archive); + } + extern fn archive_read_support_format_rar5(*Archive) Result; + pub fn readSupportFormatRar5(archive: *Archive) Result { + return archive_read_support_format_rar5(archive); + } + extern fn archive_read_support_format_raw(*Archive) Result; + pub fn readSupportFormatRaw(archive: *Archive) Result { + return archive_read_support_format_raw(archive); + } + extern fn archive_read_support_format_tar(*Archive) Result; + pub fn readSupportFormatTar(archive: *Archive) Result { + return archive_read_support_format_tar(archive); + } + extern fn archive_read_support_format_warc(*Archive) Result; + pub fn readSupportFormatWarc(archive: *Archive) Result { + return archive_read_support_format_warc(archive); + } + extern fn archive_read_support_format_xar(*Archive) Result; + pub fn readSupportFormatXar(archive: *Archive) Result { + return archive_read_support_format_xar(archive); + } + extern fn archive_read_support_format_zip(*Archive) Result; + pub fn readSupportFormatZip(archive: *Archive) Result { + return archive_read_support_format_zip(archive); + } + extern fn archive_read_support_format_zip_streamable(*Archive) Result; + pub fn readSupportFormatZipStreamable(archive: *Archive) Result { + return archive_read_support_format_zip_streamable(archive); + } + extern fn archive_read_support_format_zip_seekable(*Archive) Result; + pub fn readSupportFormatZipSeekable(archive: *Archive) Result { + return archive_read_support_format_zip_seekable(archive); + } + + extern fn archive_read_set_options(*Archive, [*c]const u8) Result; + pub fn readSetOptions(archive: *Archive, opts: [:0]const u8) Result { + return archive_read_set_options(archive, opts.ptr); + } + + extern fn archive_read_open_memory(*Archive, ?*const anyopaque, usize) Result; + pub fn readOpenMemory(archive: *Archive, buf: []const u8) Result { + return archive_read_open_memory(archive, buf.ptr, buf.len); + } + + extern fn archive_read_next_header(*Archive, **Entry) Result; + pub fn readNextHeader(archive: *Archive, entry: **Entry) Result { + return archive_read_next_header(archive, entry); + } + extern fn archive_read_next_header2(*Archive, *Entry) Result; + pub fn readNextHeader2(archive: *Archive, entry: *Entry) Result { + return archive_read_next_header2(archive, entry); + } + + extern fn archive_read_data(*Archive, ?*anyopaque, usize) isize; + pub fn readData(archive: *Archive, buf: []u8) isize { + return archive_read_data(archive, buf.ptr, buf.len); + } + extern fn archive_read_data_into_fd(*Archive, fd: c_int) Result; + pub fn readDataIntoFd(archive: *Archive, fd: c_int) Result { + return archive_read_data_into_fd(archive, fd); + } + + extern fn archive_read_support_filter_all(*Archive) Result; + pub fn readSupportFilterAll(archive: *Archive) Result { + return archive_read_support_filter_all(archive); + } + extern fn archive_read_support_filter_by_code(*Archive, c_int) Result; + pub fn readSupportFilterByCode(archive: *Archive, code: i32) Result { + return archive_read_support_filter_by_code(archive, code); + } + // extern fn archive_read_support_filter_bzip2(*Archive) Result; + // pub fn readSupportFilterbZip2(archive: *Archive) Result { + // return archive_read_support_filter_bzip2(archive); + // } + extern fn archive_read_support_filter_compress(*Archive) Result; + pub fn readSupportFilterCompress(archive: *Archive) Result { + return archive_read_support_filter_compress(archive); + } + extern fn archive_read_support_filter_gzip(*Archive) Result; + pub fn readSupportFilterGzip(archive: *Archive) Result { + return archive_read_support_filter_gzip(archive); + } + extern fn archive_read_support_filter_grzip(*Archive) Result; + pub fn readSupportFilterGrzip(archive: *Archive) Result { + return archive_read_support_filter_grzip(archive); + } + extern fn archive_read_support_filter_lrzip(*Archive) Result; + pub fn readSupportFilterLrzip(archive: *Archive) Result { + return archive_read_support_filter_lrzip(archive); + } + extern fn archive_read_support_filter_lz4(*Archive) Result; + pub fn readSupportFilterLz4(archive: *Archive) Result { + return archive_read_support_filter_lz4(archive); + } + extern fn archive_read_support_filter_lzip(*Archive) Result; + pub fn readSupportFilterLzip(archive: *Archive) Result { + return archive_read_support_filter_lzip(archive); + } + extern fn archive_read_support_filter_lzma(*Archive) Result; + pub fn readSupportFilterLzma(archive: *Archive) Result { + return archive_read_support_filter_lzma(archive); + } + extern fn archive_read_support_filter_lzop(*Archive) Result; + pub fn readSupportFilterLzop(archive: *Archive) Result { + return archive_read_support_filter_lzop(archive); + } + extern fn archive_read_support_filter_none(*Archive) Result; + pub fn readSupportFilterNone(archive: *Archive) Result { + return archive_read_support_filter_none(archive); + } + extern fn archive_read_support_filter_rpm(*Archive) Result; + pub fn readSupportFilterRpm(archive: *Archive) Result { + return archive_read_support_filter_rpm(archive); + } + extern fn archive_read_support_filter_uu(*Archive) Result; + pub fn readSupportFilterUu(archive: *Archive) Result { + return archive_read_support_filter_uu(archive); + } + extern fn archive_read_support_filter_xz(*Archive) Result; + pub fn readSupportFilterXz(archive: *Archive) Result { + return archive_read_support_filter_xz(archive); + } + extern fn archive_read_support_filter_zstd(*Archive) Result; + pub fn readSupportFilterZstd(archive: *Archive) Result { + return archive_read_support_filter_zstd(archive); + } + pub const Entry = opaque { extern fn archive_entry_new() *Entry; pub fn new() *Entry { return archive_entry_new(); } - pub extern fn archive_entry_new2(*Archive) *Entry; + extern fn archive_entry_new2(*Archive) *Entry; pub fn new2(archive: *Archive) *Entry { return archive_entry_new2(archive); } @@ -306,41 +771,41 @@ pub const Archive = opaque { } extern fn archive_entry_set_pathname(*Entry, [*c]const u8) void; - pub fn setPathname(entry: *Entry, pathname: [:0]const u8) void { - archive_entry_set_pathname(entry, pathname); + pub fn setPathname(entry: *Entry, name: [:0]const u8) void { + archive_entry_set_pathname(entry, name); } extern fn archive_entry_set_pathname_utf8(*Entry, [*c]const u8) void; - pub fn setPathnameUtf8(entry: *Entry, pathname: [:0]const u8) void { - archive_entry_set_pathname_utf8(entry, pathname); + pub fn setPathnameUtf8(entry: *Entry, name: [:0]const u8) void { + archive_entry_set_pathname_utf8(entry, name); } extern fn archive_entry_copy_pathname(*Entry, [*c]const u8) void; - pub fn copyPathname(entry: *Entry, pathname: [:0]const u8) void { - return archive_entry_copy_pathname(entry, pathname); + pub fn copyPathname(entry: *Entry, name: [:0]const u8) void { + return archive_entry_copy_pathname(entry, name); } - pub extern fn archive_entry_copy_pathname_w(*Entry, [*c]const u16) void; - pub fn copyPathnameW(entry: *Entry, pathname: [:0]const u16) void { - return archive_entry_copy_pathname_w(entry, pathname); + extern fn archive_entry_copy_pathname_w(*Entry, [*c]const u16) void; + pub fn copyPathnameW(entry: *Entry, name: [:0]const u16) void { + return archive_entry_copy_pathname_w(entry, name); } extern fn archive_entry_set_size(*Entry, i64) void; - pub fn setSize(entry: *Entry, size: i64) void { - archive_entry_set_size(entry, size); + pub fn setSize(entry: *Entry, s: i64) void { + archive_entry_set_size(entry, s); } extern fn archive_entry_set_filetype(*Entry, c_uint) void; - pub fn setFiletype(entry: *Entry, filetype: u32) void { - archive_entry_set_filetype(entry, filetype); + pub fn setFiletype(entry: *Entry, @"type": u32) void { + archive_entry_set_filetype(entry, @"type"); } extern fn archive_entry_set_perm(*Entry, bun.Mode) void; - pub fn setPerm(entry: *Entry, perm: bun.Mode) void { - archive_entry_set_perm(entry, perm); + pub fn setPerm(entry: *Entry, p: bun.Mode) void { + archive_entry_set_perm(entry, p); } - pub extern fn archive_entry_set_mode(*Entry, bun.Mode) void; + extern fn archive_entry_set_mode(*Entry, bun.Mode) void; pub fn setMode(entry: *Entry, mode: bun.Mode) void { archive_entry_set_mode(entry, mode); } @@ -354,6 +819,175 @@ pub const Archive = opaque { pub fn clear(entry: *Entry) *Entry { return archive_entry_clear(entry); } + + extern fn archive_entry_pathname(*Entry) [*c]const u8; + pub fn pathname(entry: *Entry) [:0]const u8 { + return bun.sliceTo(archive_entry_pathname(entry), 0); + } + extern fn archive_entry_pathname_utf8(*Entry) [*c]const u8; + pub fn pathnameUtf8(entry: *Entry) [:0]const u8 { + return bun.sliceTo(archive_entry_pathname_utf8(entry), 0); + } + extern fn archive_entry_pathname_w(*Entry) [*c]const u16; + pub fn pathnameW(entry: *Entry) [:0]const u16 { + return bun.sliceTo(archive_entry_pathname_w(entry), 0); + } + extern fn archive_entry_filetype(*Entry) bun.Mode; + pub fn filetype(entry: *Entry) bun.Mode { + return archive_entry_filetype(entry); + } + extern fn archive_entry_perm(*Entry) bun.Mode; + pub fn perm(entry: *Entry) bun.Mode { + return archive_entry_perm(entry); + } + extern fn archive_entry_size(*Entry) i64; + pub fn size(entry: *Entry) i64 { + return archive_entry_size(entry); + } + extern fn archive_entry_symlink(*Entry) [*c]const u8; + pub fn symlink(entry: *Entry) [:0]const u8 { + return bun.sliceTo(archive_entry_symlink(entry), 0); + } + pub extern fn archive_entry_symlink_utf8(*Entry) [*c]const u8; + pub fn symlinkUtf8(entry: *Entry) [:0]const u8 { + return bun.sliceTo(archive_entry_symlink_utf8(entry), 0); + } + pub extern fn archive_entry_symlink_type(*Entry) SymlinkType; + pub fn symlinkType(entry: *Entry) SymlinkType { + return archive_entry_symlink_type(entry); + } + pub extern fn archive_entry_symlink_w(*Entry) [*c]const u16; + pub fn symlinkW(entry: *Entry) [:0]const u16 { + return bun.sliceTo(archive_entry_symlink_w(entry), 0); + } + }; + + pub const Iterator = struct { + archive: *Archive, + filter: std.EnumSet(std.fs.File.Kind), + + fn Result(comptime T: type) type { + return union(enum) { + err: struct { + archive: *Archive, + message: []const u8, + }, + result: T, + + pub fn err(arch: *Archive, msg: []const u8) @This() { + return .{ .err = .{ .message = msg, .archive = arch } }; + } + + pub fn res(value: T) @This() { + return .{ .result = value }; + } + }; + } + + pub fn init(tarball_bytes: []const u8) Iterator.Result(@This()) { + const Return = Iterator.Result(@This()); + + const archive = Archive.readNew(); + + switch (archive.readSupportFormatTar()) { + .failed, .fatal, .warn => { + return Return.err(archive, "failed to enable tar format support"); + }, + else => {}, + } + switch (archive.readSupportFormatGnutar()) { + .failed, .fatal, .warn => { + return Return.err(archive, "failed to enable gnutar format support"); + }, + else => {}, + } + switch (archive.readSupportFilterGzip()) { + .failed, .fatal, .warn => { + return Return.err(archive, "failed to enable support for gzip compression"); + }, + else => {}, + } + + switch (archive.readSetOptions("read_concatenated_archives")) { + .failed, .fatal, .warn => { + return Return.err(archive, "failed to set option `read_concatenated_archives`"); + }, + else => {}, + } + + switch (archive.readOpenMemory(tarball_bytes)) { + .failed, .fatal, .warn => { + return Return.err(archive, "failed to read tarball"); + }, + else => {}, + } + + return Return.res(.{ + .archive = archive, + .filter = std.EnumSet(std.fs.File.Kind).initEmpty(), + }); + } + + const NextEntry = struct { + entry: *Archive.Entry, + kind: std.fs.File.Kind, + + pub fn readEntryData(this: *const @This(), allocator: std.mem.Allocator, archive: *Archive) OOM!Iterator.Result([]const u8) { + const Return = Iterator.Result([]const u8); + const size = this.entry.size(); + if (size < 0) return Return.err(archive, "invalid archive entry size"); + + const buf = try allocator.alloc(u8, @intCast(size)); + + const read = archive.readData(buf); + if (read < 0) { + return Return.err(archive, "failed to read archive data"); + } + return Return.res(buf[0..@intCast(read)]); + } + }; + + pub fn next(this: *@This()) Iterator.Result(?NextEntry) { + const Return = Iterator.Result(?NextEntry); + + var entry: *Archive.Entry = undefined; + while (true) { + return switch (this.archive.readNextHeader(&entry)) { + .retry => continue, + .eof => Return.res(null), + .ok => { + const kind = bun.C.kindFromMode(entry.filetype()); + + if (this.filter.contains(kind)) continue; + + return Return.res(.{ + .entry = entry, + .kind = kind, + }); + }, + else => Return.err(this.archive, "failed to read archive header"), + }; + } + } + + pub fn deinit(this: *@This()) Iterator.Result(void) { + const Return = Iterator.Result(void); + + switch (this.archive.readClose()) { + .failed, .fatal, .warn => { + return Return.err(this.archive, "failed to close archive read"); + }, + else => {}, + } + switch (this.archive.readFree()) { + .failed, .fatal, .warn => { + return Return.err(this.archive, "failed to free archive read"); + }, + else => {}, + } + + return Return.res({}); + } }; }; @@ -366,57 +1000,10 @@ pub const archive_close_callback = *const fn (*struct_archive, *anyopaque) callc pub const archive_free_callback = *const fn (*struct_archive, *anyopaque) callconv(.C) c_int; pub const archive_switch_callback = *const fn (*struct_archive, *anyopaque, ?*anyopaque) callconv(.C) c_int; pub const archive_passphrase_callback = *const fn (*struct_archive, *anyopaque) callconv(.C) [*c]const u8; -pub extern fn archive_read_new() *struct_archive; -pub extern fn archive_read_support_compression_all(*struct_archive) c_int; -pub extern fn archive_read_support_compression_bzip2(*struct_archive) c_int; -pub extern fn archive_read_support_compression_compress(*struct_archive) c_int; -pub extern fn archive_read_support_compression_gzip(*struct_archive) c_int; -pub extern fn archive_read_support_compression_lzip(*struct_archive) c_int; -pub extern fn archive_read_support_compression_lzma(*struct_archive) c_int; -pub extern fn archive_read_support_compression_none(*struct_archive) c_int; pub extern fn archive_read_support_compression_program(*struct_archive, command: [*c]const u8) c_int; pub extern fn archive_read_support_compression_program_signature(*struct_archive, [*c]const u8, ?*const anyopaque, usize) c_int; -pub extern fn archive_read_support_compression_rpm(*struct_archive) c_int; -pub extern fn archive_read_support_compression_uu(*struct_archive) c_int; -pub extern fn archive_read_support_compression_xz(*struct_archive) c_int; -pub extern fn archive_read_support_filter_all(*struct_archive) c_int; -pub extern fn archive_read_support_filter_by_code(*struct_archive, c_int) c_int; -pub extern fn archive_read_support_filter_bzip2(*struct_archive) c_int; -pub extern fn archive_read_support_filter_compress(*struct_archive) c_int; -pub extern fn archive_read_support_filter_gzip(*struct_archive) c_int; -pub extern fn archive_read_support_filter_grzip(*struct_archive) c_int; -pub extern fn archive_read_support_filter_lrzip(*struct_archive) c_int; -pub extern fn archive_read_support_filter_lz4(*struct_archive) c_int; -pub extern fn archive_read_support_filter_lzip(*struct_archive) c_int; -pub extern fn archive_read_support_filter_lzma(*struct_archive) c_int; -pub extern fn archive_read_support_filter_lzop(*struct_archive) c_int; -pub extern fn archive_read_support_filter_none(*struct_archive) c_int; pub extern fn archive_read_support_filter_program(*struct_archive, command: [*c]const u8) c_int; pub extern fn archive_read_support_filter_program_signature(*struct_archive, [*c]const u8, ?*const anyopaque, usize) c_int; -pub extern fn archive_read_support_filter_rpm(*struct_archive) c_int; -pub extern fn archive_read_support_filter_uu(*struct_archive) c_int; -pub extern fn archive_read_support_filter_xz(*struct_archive) c_int; -pub extern fn archive_read_support_filter_zstd(*struct_archive) c_int; -pub extern fn archive_read_support_format_7zip(*struct_archive) c_int; -pub extern fn archive_read_support_format_all(*struct_archive) c_int; -pub extern fn archive_read_support_format_ar(*struct_archive) c_int; -pub extern fn archive_read_support_format_by_code(*struct_archive, c_int) c_int; -pub extern fn archive_read_support_format_cab(*struct_archive) c_int; -pub extern fn archive_read_support_format_cpio(*struct_archive) c_int; -pub extern fn archive_read_support_format_empty(*struct_archive) c_int; -pub extern fn archive_read_support_format_gnutar(*struct_archive) c_int; -pub extern fn archive_read_support_format_iso9660(*struct_archive) c_int; -pub extern fn archive_read_support_format_lha(*struct_archive) c_int; -pub extern fn archive_read_support_format_mtree(*struct_archive) c_int; -pub extern fn archive_read_support_format_rar(*struct_archive) c_int; -pub extern fn archive_read_support_format_rar5(*struct_archive) c_int; -pub extern fn archive_read_support_format_raw(*struct_archive) c_int; -pub extern fn archive_read_support_format_tar(*struct_archive) c_int; -pub extern fn archive_read_support_format_warc(*struct_archive) c_int; -pub extern fn archive_read_support_format_xar(*struct_archive) c_int; -pub extern fn archive_read_support_format_zip(*struct_archive) c_int; -pub extern fn archive_read_support_format_zip_streamable(*struct_archive) c_int; -pub extern fn archive_read_support_format_zip_seekable(*struct_archive) c_int; pub extern fn archive_read_set_format(*struct_archive, c_int) c_int; pub extern fn archive_read_append_filter(*struct_archive, c_int) c_int; pub extern fn archive_read_append_filter_program(*struct_archive, [*c]const u8) c_int; @@ -439,62 +1026,36 @@ pub extern fn archive_read_open_filename(*struct_archive, _filename: [*c]const u pub extern fn archive_read_open_filenames(*struct_archive, _filenames: [*c][*c]const u8, _block_size: usize) c_int; pub extern fn archive_read_open_filename_w(*struct_archive, _filename: [*c]const wchar_t, _block_size: usize) c_int; pub extern fn archive_read_open_file(*struct_archive, _filename: [*c]const u8, _block_size: usize) c_int; -pub extern fn archive_read_open_memory(*struct_archive, buff: ?*const anyopaque, size: usize) c_int; pub extern fn archive_read_open_memory2(a: *struct_archive, buff: ?*const anyopaque, size: usize, read_size: usize) c_int; pub extern fn archive_read_open_fd(*struct_archive, _fd: c_int, _block_size: usize) c_int; pub extern fn archive_read_open_FILE(*struct_archive, _file: [*c]FILE) c_int; -pub extern fn archive_read_next_header(*struct_archive, [*c]*struct_archive_entry) c_int; -pub extern fn archive_read_next_header2(*struct_archive, *struct_archive_entry) c_int; pub extern fn archive_read_header_position(*struct_archive) la_int64_t; pub extern fn archive_read_has_encrypted_entries(*struct_archive) c_int; pub extern fn archive_read_format_capabilities(*struct_archive) c_int; -pub extern fn archive_read_data(*struct_archive, ?*anyopaque, usize) la_ssize_t; pub extern fn archive_seek_data(*struct_archive, la_int64_t, c_int) la_int64_t; pub extern fn archive_read_data_block(a: *struct_archive, buff: [*c]*const anyopaque, size: [*c]usize, offset: [*c]la_int64_t) c_int; pub extern fn archive_read_data_skip(*struct_archive) c_int; -pub extern fn archive_read_data_into_fd(*struct_archive, fd: c_int) c_int; pub extern fn archive_read_set_format_option(_a: *struct_archive, m: [*c]const u8, o: [*c]const u8, v: [*c]const u8) c_int; pub extern fn archive_read_set_filter_option(_a: *struct_archive, m: [*c]const u8, o: [*c]const u8, v: [*c]const u8) c_int; -pub extern fn archive_read_set_option(_a: *struct_archive, m: [*c]const u8, o: [*c]const u8, v: [*c]const u8) c_int; -pub extern fn archive_read_set_options(_a: *struct_archive, opts: [*c]const u8) c_int; pub extern fn archive_read_add_passphrase(*struct_archive, [*c]const u8) c_int; pub extern fn archive_read_set_passphrase_callback(*struct_archive, client_data: ?*anyopaque, ?archive_passphrase_callback) c_int; pub extern fn archive_read_extract(*struct_archive, *struct_archive_entry, flags: c_int) c_int; pub extern fn archive_read_extract2(*struct_archive, *struct_archive_entry, *struct_archive) c_int; pub extern fn archive_read_extract_set_progress_callback(*struct_archive, _progress_func: ?*const fn (?*anyopaque) callconv(.C) void, _user_data: ?*anyopaque) void; pub extern fn archive_read_extract_set_skip_file(*struct_archive, la_int64_t, la_int64_t) void; -pub extern fn archive_read_close(*struct_archive) c_int; -pub extern fn archive_read_free(*struct_archive) c_int; -pub extern fn archive_read_finish(*struct_archive) c_int; pub extern fn archive_write_set_bytes_per_block(*struct_archive, bytes_per_block: c_int) c_int; pub extern fn archive_write_get_bytes_per_block(*struct_archive) c_int; pub extern fn archive_write_set_bytes_in_last_block(*struct_archive, bytes_in_last_block: c_int) c_int; pub extern fn archive_write_get_bytes_in_last_block(*struct_archive) c_int; pub extern fn archive_write_set_skip_file(*struct_archive, la_int64_t, la_int64_t) c_int; -pub extern fn archive_write_set_compression_bzip2(*struct_archive) c_int; -pub extern fn archive_write_set_compression_compress(*struct_archive) c_int; -pub extern fn archive_write_set_compression_lzip(*struct_archive) c_int; -pub extern fn archive_write_set_compression_lzma(*struct_archive) c_int; -pub extern fn archive_write_set_compression_none(*struct_archive) c_int; -pub extern fn archive_write_set_compression_program(*struct_archive, cmd: [*c]const u8) c_int; -pub extern fn archive_write_set_compression_xz(*struct_archive) c_int; -pub extern fn archive_write_add_filter(*struct_archive, filter_code: c_int) c_int; -pub extern fn archive_write_add_filter_by_name(*struct_archive, name: [*c]const u8) c_int; -pub extern fn archive_write_add_filter_b64encode(*struct_archive) c_int; -pub extern fn archive_write_add_filter_bzip2(*struct_archive) c_int; -pub extern fn archive_write_add_filter_compress(*struct_archive) c_int; -pub extern fn archive_write_add_filter_grzip(*struct_archive) c_int; -pub extern fn archive_write_add_filter_lrzip(*struct_archive) c_int; -pub extern fn archive_write_add_filter_lz4(*struct_archive) c_int; -pub extern fn archive_write_add_filter_lzip(*struct_archive) c_int; -pub extern fn archive_write_add_filter_lzma(*struct_archive) c_int; -pub extern fn archive_write_add_filter_lzop(*struct_archive) c_int; -pub extern fn archive_write_add_filter_none(*struct_archive) c_int; -pub extern fn archive_write_add_filter_program(*struct_archive, cmd: [*c]const u8) c_int; -pub extern fn archive_write_add_filter_uuencode(*struct_archive) c_int; -pub extern fn archive_write_add_filter_xz(*struct_archive) c_int; -pub extern fn archive_write_add_filter_zstd(*struct_archive) c_int; -pub extern fn archive_write_set_format(*struct_archive, format_code: c_int) c_int; +// Deprecated +// pub extern fn archive_write_set_compression_bzip2(*struct_archive) c_int; +// pub extern fn archive_write_set_compression_compress(*struct_archive) c_int; +// pub extern fn archive_write_set_compression_lzip(*struct_archive) c_int; +// pub extern fn archive_write_set_compression_lzma(*struct_archive) c_int; +// pub extern fn archive_write_set_compression_none(*struct_archive) c_int; +// pub extern fn archive_write_set_compression_program(*struct_archive, cmd: [*c]const u8) c_int; +// pub extern fn archive_write_set_compression_xz(*struct_archive) c_int; pub extern fn archive_write_set_format_by_name(*struct_archive, name: [*c]const u8) c_int; pub extern fn archive_write_set_format_ar_bsd(*struct_archive) c_int; pub extern fn archive_write_set_format_ar_svr4(*struct_archive) c_int; @@ -616,7 +1177,6 @@ pub extern fn archive_entry_dev(*struct_archive_entry) dev_t; pub extern fn archive_entry_dev_is_set(*struct_archive_entry) c_int; pub extern fn archive_entry_devmajor(*struct_archive_entry) dev_t; pub extern fn archive_entry_devminor(*struct_archive_entry) dev_t; -pub extern fn archive_entry_filetype(*struct_archive_entry) mode_t; pub extern fn archive_entry_fflags(*struct_archive_entry, [*c]u64, [*c]u64) void; pub extern fn archive_entry_fflags_text(*struct_archive_entry) [*c]const u8; pub extern fn archive_entry_gid(*struct_archive_entry) la_int64_t; @@ -634,22 +1194,13 @@ pub extern fn archive_entry_mtime(*struct_archive_entry) time_t; pub extern fn archive_entry_mtime_nsec(*struct_archive_entry) c_long; pub extern fn archive_entry_mtime_is_set(*struct_archive_entry) c_int; pub extern fn archive_entry_nlink(*struct_archive_entry) c_uint; -pub extern fn archive_entry_pathname(*struct_archive_entry) [*c]const u8; -pub extern fn archive_entry_pathname_utf8(*struct_archive_entry) [*c]const u8; -pub extern fn archive_entry_pathname_w(*struct_archive_entry) [*c]const wchar_t; -pub extern fn archive_entry_perm(*struct_archive_entry) mode_t; pub extern fn archive_entry_rdev(*struct_archive_entry) dev_t; pub extern fn archive_entry_rdevmajor(*struct_archive_entry) dev_t; pub extern fn archive_entry_rdevminor(*struct_archive_entry) dev_t; pub extern fn archive_entry_sourcepath(*struct_archive_entry) [*c]const u8; pub extern fn archive_entry_sourcepath_w(*struct_archive_entry) [*c]const wchar_t; -pub extern fn archive_entry_size(*struct_archive_entry) la_int64_t; pub extern fn archive_entry_size_is_set(*struct_archive_entry) c_int; pub extern fn archive_entry_strmode(*struct_archive_entry) [*c]const u8; -pub extern fn archive_entry_symlink(*struct_archive_entry) [*c]const u8; -pub extern fn archive_entry_symlink_utf8(*struct_archive_entry) [*c]const u8; -pub extern fn archive_entry_symlink_type(*struct_archive_entry) SymlinkType; -pub extern fn archive_entry_symlink_w(*struct_archive_entry) [*c]const wchar_t; pub extern fn archive_entry_uid(*struct_archive_entry) la_int64_t; pub extern fn archive_entry_uname(*struct_archive_entry) [*c]const u8; pub extern fn archive_entry_uname_utf8(*struct_archive_entry) [*c]const u8; diff --git a/src/libarchive/libarchive.zig b/src/libarchive/libarchive.zig index 2cc794de95..7765ab4e46 100644 --- a/src/libarchive/libarchive.zig +++ b/src/libarchive/libarchive.zig @@ -13,7 +13,7 @@ const stringZ = bun.stringZ; const default_allocator = bun.default_allocator; const C = bun.C; const std = @import("std"); -const struct_archive = lib.struct_archive; +const Archive = lib.Archive; const JSC = bun.JSC; pub const Seek = enum(c_int) { set = std.posix.SEEK_SET, @@ -21,168 +21,6 @@ pub const Seek = enum(c_int) { end = std.posix.SEEK_END, }; -pub const Flags = struct { - pub const Extract = enum(c_int) { - owner = lib.ARCHIVE_EXTRACT_OWNER, - perm = lib.ARCHIVE_EXTRACT_PERM, - time = lib.ARCHIVE_EXTRACT_TIME, - no_overwrite = lib.ARCHIVE_EXTRACT_NO_OVERWRITE, - unlink = lib.ARCHIVE_EXTRACT_UNLINK, - acl = lib.ARCHIVE_EXTRACT_ACL, - fflags = lib.ARCHIVE_EXTRACT_FFLAGS, - xattr = lib.ARCHIVE_EXTRACT_XATTR, - secure_symlinks = lib.ARCHIVE_EXTRACT_SECURE_SYMLINKS, - secure_nodotdot = lib.ARCHIVE_EXTRACT_SECURE_NODOTDOT, - no_autodir = lib.ARCHIVE_EXTRACT_NO_AUTODIR, - no_overwrite_newer = lib.ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER, - sparse = lib.ARCHIVE_EXTRACT_SPARSE, - mac_metadata = lib.ARCHIVE_EXTRACT_MAC_METADATA, - no_hfs_compression = lib.ARCHIVE_EXTRACT_NO_HFS_COMPRESSION, - hfs_compression_forced = lib.ARCHIVE_EXTRACT_HFS_COMPRESSION_FORCED, - secure_noabsolutepaths = lib.ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS, - clear_nochange_fflags = lib.ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS, - safe_writes = lib.ARCHIVE_EXTRACT_SAFE_WRITES, - }; - - pub const Compression = enum(c_int) { - none = lib.ARCHIVE_COMPRESSION_NONE, - gzip = lib.ARCHIVE_COMPRESSION_GZIP, - bzip2 = lib.ARCHIVE_COMPRESSION_BZIP2, - compress = lib.ARCHIVE_COMPRESSION_COMPRESS, - program = lib.ARCHIVE_COMPRESSION_PROGRAM, - lzma = lib.ARCHIVE_COMPRESSION_LZMA, - xz = lib.ARCHIVE_COMPRESSION_XZ, - uu = lib.ARCHIVE_COMPRESSION_UU, - rpm = lib.ARCHIVE_COMPRESSION_RPM, - lzip = lib.ARCHIVE_COMPRESSION_LZIP, - lrzip = lib.ARCHIVE_COMPRESSION_LRZIP, - }; - - pub const Format = enum(c_int) { - base_mask = lib.ARCHIVE_FORMAT_BASE_MASK, - cpio = lib.ARCHIVE_FORMAT_CPIO, - cpio_posix = lib.ARCHIVE_FORMAT_CPIO_POSIX, - cpio_bin_le = lib.ARCHIVE_FORMAT_CPIO_BIN_LE, - cpio_bin_be = lib.ARCHIVE_FORMAT_CPIO_BIN_BE, - cpio_svr4_nocrc = lib.ARCHIVE_FORMAT_CPIO_SVR4_NOCRC, - cpio_svr4_crc = lib.ARCHIVE_FORMAT_CPIO_SVR4_CRC, - cpio_afio_large = lib.ARCHIVE_FORMAT_CPIO_AFIO_LARGE, - cpio_pwb = lib.ARCHIVE_FORMAT_CPIO_PWB, - shar = lib.ARCHIVE_FORMAT_SHAR, - shar_base = lib.ARCHIVE_FORMAT_SHAR_BASE, - shar_dump = lib.ARCHIVE_FORMAT_SHAR_DUMP, - tar = lib.ARCHIVE_FORMAT_TAR, - tar_ustar = lib.ARCHIVE_FORMAT_TAR_USTAR, - tar_pax_interchange = lib.ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE, - tar_pax_restricted = lib.ARCHIVE_FORMAT_TAR_PAX_RESTRICTED, - tar_gnutar = lib.ARCHIVE_FORMAT_TAR_GNUTAR, - iso9660 = lib.ARCHIVE_FORMAT_ISO9660, - iso9660_rockridge = lib.ARCHIVE_FORMAT_ISO9660_ROCKRIDGE, - zip = lib.ARCHIVE_FORMAT_ZIP, - empty = lib.ARCHIVE_FORMAT_EMPTY, - ar = lib.ARCHIVE_FORMAT_AR, - ar_gnu = lib.ARCHIVE_FORMAT_AR_GNU, - ar_bsd = lib.ARCHIVE_FORMAT_AR_BSD, - mtree = lib.ARCHIVE_FORMAT_MTREE, - raw = lib.ARCHIVE_FORMAT_RAW, - xar = lib.ARCHIVE_FORMAT_XAR, - lha = lib.ARCHIVE_FORMAT_LHA, - cab = lib.ARCHIVE_FORMAT_CAB, - rar = lib.ARCHIVE_FORMAT_RAR, - @"7zip" = lib.ARCHIVE_FORMAT_7ZIP, - warc = lib.ARCHIVE_FORMAT_WARC, - rar_v5 = lib.ARCHIVE_FORMAT_RAR_V5, - }; - - pub const Filter = enum(c_int) { - none = lib.ARCHIVE_FILTER_NONE, - gzip = lib.ARCHIVE_FILTER_GZIP, - bzip2 = lib.ARCHIVE_FILTER_BZIP2, - compress = lib.ARCHIVE_FILTER_COMPRESS, - program = lib.ARCHIVE_FILTER_PROGRAM, - lzma = lib.ARCHIVE_FILTER_LZMA, - xz = lib.ARCHIVE_FILTER_XZ, - uu = lib.ARCHIVE_FILTER_UU, - rpm = lib.ARCHIVE_FILTER_RPM, - lzip = lib.ARCHIVE_FILTER_LZIP, - lrzip = lib.ARCHIVE_FILTER_LRZIP, - lzop = lib.ARCHIVE_FILTER_LZOP, - grzip = lib.ARCHIVE_FILTER_GRZIP, - lz4 = lib.ARCHIVE_FILTER_LZ4, - zstd = lib.ARCHIVE_FILTER_ZSTD, - }; - - pub const EntryDigest = enum(c_int) { - md5 = lib.ARCHIVE_ENTRY_DIGEST_MD5, - rmd160 = lib.ARCHIVE_ENTRY_DIGEST_RMD160, - sha1 = lib.ARCHIVE_ENTRY_DIGEST_SHA1, - sha256 = lib.ARCHIVE_ENTRY_DIGEST_SHA256, - sha384 = lib.ARCHIVE_ENTRY_DIGEST_SHA384, - sha512 = lib.ARCHIVE_ENTRY_DIGEST_SHA512, - }; - - pub const EntryACL = enum(c_int) { - entry_acl_execute = lib.ARCHIVE_ENTRY_ACL_EXECUTE, - write = lib.ARCHIVE_ENTRY_ACL_WRITE, - read = lib.ARCHIVE_ENTRY_ACL_READ, - read_data = lib.ARCHIVE_ENTRY_ACL_READ_DATA, - list_directory = lib.ARCHIVE_ENTRY_ACL_LIST_DIRECTORY, - write_data = lib.ARCHIVE_ENTRY_ACL_WRITE_DATA, - add_file = lib.ARCHIVE_ENTRY_ACL_ADD_FILE, - append_data = lib.ARCHIVE_ENTRY_ACL_APPEND_DATA, - add_subdirectory = lib.ARCHIVE_ENTRY_ACL_ADD_SUBDIRECTORY, - read_named_attrs = lib.ARCHIVE_ENTRY_ACL_READ_NAMED_ATTRS, - write_named_attrs = lib.ARCHIVE_ENTRY_ACL_WRITE_NAMED_ATTRS, - delete_child = lib.ARCHIVE_ENTRY_ACL_DELETE_CHILD, - read_attributes = lib.ARCHIVE_ENTRY_ACL_READ_ATTRIBUTES, - write_attributes = lib.ARCHIVE_ENTRY_ACL_WRITE_ATTRIBUTES, - delete = lib.ARCHIVE_ENTRY_ACL_DELETE, - read_acl = lib.ARCHIVE_ENTRY_ACL_READ_ACL, - write_acl = lib.ARCHIVE_ENTRY_ACL_WRITE_ACL, - write_owner = lib.ARCHIVE_ENTRY_ACL_WRITE_OWNER, - synchronize = lib.ARCHIVE_ENTRY_ACL_SYNCHRONIZE, - perms_posix1_e = lib.ARCHIVE_ENTRY_ACL_PERMS_POSIX1E, - perms_nfs4 = lib.ARCHIVE_ENTRY_ACL_PERMS_NFS4, - entry_inherited = lib.ARCHIVE_ENTRY_ACL_ENTRY_INHERITED, - entry_file_inherit = lib.ARCHIVE_ENTRY_ACL_ENTRY_FILE_INHERIT, - entry_directory_inherit = lib.ARCHIVE_ENTRY_ACL_ENTRY_DIRECTORY_INHERIT, - entry_no_propagate_inherit = lib.ARCHIVE_ENTRY_ACL_ENTRY_NO_PROPAGATE_INHERIT, - entry_inherit_only = lib.ARCHIVE_ENTRY_ACL_ENTRY_INHERIT_ONLY, - entry_successful_access = lib.ARCHIVE_ENTRY_ACL_ENTRY_SUCCESSFUL_ACCESS, - entry_failed_access = lib.ARCHIVE_ENTRY_ACL_ENTRY_FAILED_ACCESS, - inheritance_nfs4 = lib.ARCHIVE_ENTRY_ACL_INHERITANCE_NFS4, - type_access = lib.ARCHIVE_ENTRY_ACL_TYPE_ACCESS, - type_default = lib.ARCHIVE_ENTRY_ACL_TYPE_DEFAULT, - type_allow = lib.ARCHIVE_ENTRY_ACL_TYPE_ALLOW, - type_deny = lib.ARCHIVE_ENTRY_ACL_TYPE_DENY, - type_audit = lib.ARCHIVE_ENTRY_ACL_TYPE_AUDIT, - type_alarm = lib.ARCHIVE_ENTRY_ACL_TYPE_ALARM, - type_posix1_e = lib.ARCHIVE_ENTRY_ACL_TYPE_POSIX1E, - type_nfs4 = lib.ARCHIVE_ENTRY_ACL_TYPE_NFS4, - user = lib.ARCHIVE_ENTRY_ACL_USER, - user_obj = lib.ARCHIVE_ENTRY_ACL_USER_OBJ, - group = lib.ARCHIVE_ENTRY_ACL_GROUP, - group_obj = lib.ARCHIVE_ENTRY_ACL_GROUP_OBJ, - mask = lib.ARCHIVE_ENTRY_ACL_MASK, - other = lib.ARCHIVE_ENTRY_ACL_OTHER, - everyone = lib.ARCHIVE_ENTRY_ACL_EVERYONE, - style_extra_id = lib.ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID, - style_mark_default = lib.ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT, - style_solaris = lib.ARCHIVE_ENTRY_ACL_STYLE_SOLARIS, - style_separator_comma = lib.ARCHIVE_ENTRY_ACL_STYLE_SEPARATOR_COMMA, - style_compact = lib.ARCHIVE_ENTRY_ACL_STYLE_COMPACT, - }; -}; - -pub const Status = enum(c_int) { - eof = lib.ARCHIVE_EOF, - ok = lib.ARCHIVE_OK, - retry = lib.ARCHIVE_RETRY, - warn = lib.ARCHIVE_WARN, - failed = lib.ARCHIVE_FAILED, - fatal = lib.ARCHIVE_FATAL, -}; - pub const BufferReadStream = struct { const Stream = @This(); buf: []const u8, @@ -190,27 +28,27 @@ pub const BufferReadStream = struct { block_size: usize = 16384, - archive: *struct_archive, + archive: *Archive, reading: bool = false, pub fn init(this: *BufferReadStream, buf: []const u8) void { this.* = BufferReadStream{ .buf = buf, .pos = 0, - .archive = lib.archive_read_new(), + .archive = Archive.readNew(), .reading = false, }; } pub fn deinit(this: *BufferReadStream) void { - _ = lib.archive_read_close(this.archive); + _ = this.archive.readClose(); // don't free it if we never actually read it // if (this.reading) { // _ = lib.archive_read_free(this.archive); // } } - pub fn openRead(this: *BufferReadStream) c_int { + pub fn openRead(this: *BufferReadStream) Archive.Result { // lib.archive_read_set_open_callback(this.archive, this.); // _ = lib.archive_read_set_read_callback(this.archive, archive_read_callback); // _ = lib.archive_read_set_seek_callback(this.archive, archive_seek_callback); @@ -219,21 +57,21 @@ pub const BufferReadStream = struct { // // lib.archive_read_set_switch_callback(this.archive, this.archive_s); // _ = lib.archive_read_set_callback_data(this.archive, this); - _ = lib.archive_read_support_format_tar(this.archive); - _ = lib.archive_read_support_format_gnutar(this.archive); - _ = lib.archive_read_support_compression_gzip(this.archive); + _ = this.archive.readSupportFormatTar(); + _ = this.archive.readSupportFormatGnutar(); + _ = this.archive.readSupportFilterGzip(); // Ignore zeroed blocks in the archive, which occurs when multiple tar archives // have been concatenated together. // Without this option, only the contents of // the first concatenated archive would be read. - _ = lib.archive_read_set_options(this.archive, "read_concatenated_archives"); + _ = this.archive.readSetOptions("read_concatenated_archives"); // _ = lib.archive_read_support_filter_none(this.archive); - const rc = lib.archive_read_open_memory(this.archive, this.buf.ptr, this.buf.len); + const rc = this.archive.readOpenMemory(this.buf); - this.reading = rc > -1; + this.reading = @intFromEnum(rc) > -1; // _ = lib.archive_read_support_compression_all(this.archive); @@ -249,14 +87,14 @@ pub const BufferReadStream = struct { } pub fn archive_close_callback( - _: *struct_archive, + _: *Archive, _: *anyopaque, ) callconv(.C) c_int { return 0; } pub fn archive_read_callback( - _: *struct_archive, + _: *Archive, ctx_: *anyopaque, buffer: [*c]*const anyopaque, ) callconv(.C) lib.la_ssize_t { @@ -271,7 +109,7 @@ pub const BufferReadStream = struct { } pub fn archive_skip_callback( - _: *struct_archive, + _: *Archive, ctx_: *anyopaque, offset: lib.la_int64_t, ) callconv(.C) lib.la_int64_t { @@ -287,7 +125,7 @@ pub const BufferReadStream = struct { } pub fn archive_seek_callback( - _: *struct_archive, + _: *Archive, ctx_: *anyopaque, offset: lib.la_int64_t, whence: c_int, @@ -317,7 +155,7 @@ pub const BufferReadStream = struct { } // pub fn archive_write_callback( - // archive: *struct_archive, + // archive: *Archive, // ctx_: *anyopaque, // buffer: *const anyopaque, // len: usize, @@ -326,20 +164,20 @@ pub const BufferReadStream = struct { // } // pub fn archive_close_callback( - // archive: *struct_archive, + // archive: *Archive, // ctx_: *anyopaque, // ) callconv(.C) c_int { // var this = fromCtx(ctx_); // } // pub fn archive_free_callback( - // archive: *struct_archive, + // archive: *Archive, // ctx_: *anyopaque, // ) callconv(.C) c_int { // var this = fromCtx(ctx_); // } // pub fn archive_switch_callback( - // archive: *struct_archive, + // archive: *Archive, // ctx1: *anyopaque, // ctx2: *anyopaque, // ) callconv(.C) c_int { @@ -350,7 +188,7 @@ pub const BufferReadStream = struct { const Kind = std.fs.File.Kind; -pub const Archive = struct { +pub const Archiver = struct { // impl: *lib.archive = undefined, // buf: []const u8 = undefined, // dir: FileDescriptorType = 0, @@ -389,12 +227,12 @@ pub const Archive = struct { pub fn getOverwritingFileList( file_buffer: []const u8, root: []const u8, - ctx: *Archive.Context, + ctx: *Archiver.Context, comptime FilePathAppender: type, appender: FilePathAppender, comptime depth_to_skip: usize, ) !void { - var entry: *lib.archive_entry = undefined; + var entry: *Archive.Entry = undefined; var stream: BufferReadStream = undefined; stream.init(file_buffer); @@ -413,17 +251,17 @@ pub const Archive = struct { }; loop: while (true) { - const r = @as(Status, @enumFromInt(lib.archive_read_next_header(archive, &entry))); + const r = archive.readNextHeader(&entry); switch (r) { - Status.eof => break :loop, - Status.retry => continue :loop, - Status.failed, Status.fatal => return error.Fail, + .eof => break :loop, + .retry => continue :loop, + .failed, .fatal => return error.Fail, else => { // do not use the utf8 name there // it will require us to pull in libiconv // though we should probably validate the utf8 here nonetheless - var pathname: [:0]const u8 = std.mem.sliceTo(lib.archive_entry_pathname(entry).?, 0); + var pathname = entry.pathname(); var tokenizer = std.mem.tokenize(u8, bun.asByteSlice(pathname), std.fs.path.sep_str); comptime var depth_i: usize = 0; inline while (depth_i < depth_to_skip) : (depth_i += 1) { @@ -434,7 +272,7 @@ pub const Archive = struct { pathname = std.mem.sliceTo(pathname_.ptr[0..pathname_.len :0], 0); const dirname = std.mem.trim(u8, std.fs.path.dirname(bun.asByteSlice(pathname)) orelse "", std.fs.path.sep_str); - const size = @as(usize, @intCast(@max(lib.archive_entry_size(entry), 0))); + const size: usize = @intCast(@max(entry.size(), 0)); if (size > 0) { var opened = dir.openFileZ(pathname, .{ .mode = .write_only }) catch continue :loop; defer opened.close(); @@ -479,12 +317,12 @@ pub const Archive = struct { pub fn extractToDir( file_buffer: []const u8, dir: std.fs.Dir, - ctx: ?*Archive.Context, + ctx: ?*Archiver.Context, comptime ContextType: type, appender: ContextType, options: ExtractOptions, ) !u32 { - var entry: *lib.archive_entry = undefined; + var entry: *Archive.Entry = undefined; var stream: BufferReadStream = undefined; stream.init(file_buffer); @@ -497,12 +335,12 @@ pub const Archive = struct { var normalized_buf: bun.OSPathBuffer = undefined; loop: while (true) { - const r: Status = @enumFromInt(lib.archive_read_next_header(archive, &entry)); + const r = archive.readNextHeader(&entry); switch (r) { - Status.eof => break :loop, - Status.retry => continue :loop, - Status.failed, Status.fatal => return error.Fail, + .eof => break :loop, + .retry => continue :loop, + .failed, .fatal => return error.Fail, else => { // TODO: // Due to path separator replacement and other copies that happen internally, libarchive changes the @@ -513,9 +351,9 @@ pub const Archive = struct { // Ideally, we find a way to tell libarchive to not convert the strings to wide characters and also to not // replace path separators. We can do both of these with our own normalization and utf8/utf16 string conversion code. var pathname: bun.OSPathSliceZ = if (comptime Environment.isWindows) - std.mem.sliceTo(lib.archive_entry_pathname_w(entry), 0) + entry.pathnameW() else - std.mem.sliceTo(lib.archive_entry_pathname(entry), 0); + entry.pathname(); if (comptime ContextType != void and @hasDecl(std.meta.Child(ContextType), "onFirstDirectoryName")) { if (appender.needs_first_dirname) { @@ -531,7 +369,7 @@ pub const Archive = struct { } } - const kind = C.kindFromMode(lib.archive_entry_filetype(entry)); + const kind = C.kindFromMode(entry.filetype()); if (options.npm) { // - ignore entries other than files (`true` can only be returned if type is file) @@ -584,8 +422,8 @@ pub const Archive = struct { count += 1; switch (kind) { - Kind.directory => { - var mode = @as(i32, @intCast(lib.archive_entry_perm(entry))); + .directory => { + var mode = @as(i32, @intCast(entry.perm())); // if dirs are readable, then they should be listable // https://github.com/npm/node-tar/blob/main/lib/mode-fix.js @@ -609,24 +447,22 @@ pub const Archive = struct { }; } }, - Kind.sym_link => { - const link_target = lib.archive_entry_symlink(entry).?; + .sym_link => { + const link_target = entry.symlink(); if (Environment.isPosix) { - std.posix.symlinkatZ(link_target, dir_fd, path) catch |err| brk: { + bun.sys.symlinkat(link_target, bun.toFD(dir_fd), path).unwrap() catch |err| brk: { switch (err) { - error.AccessDenied, error.FileNotFound => { + error.EPERM, error.ENOENT => { dir.makePath(std.fs.path.dirname(path_slice) orelse return err) catch {}; - break :brk try std.posix.symlinkatZ(link_target, dir_fd, path); - }, - else => { - return err; + break :brk try bun.sys.symlinkat(link_target, bun.toFD(dir_fd), path).unwrap(); }, + else => return err, } }; } }, - Kind.file => { - const mode: bun.Mode = if (comptime Environment.isWindows) 0 else @intCast(lib.archive_entry_perm(entry)); + .file => { + const mode: bun.Mode = if (comptime Environment.isWindows) 0 else @intCast(entry.perm()); const file_handle_native = brk: { if (Environment.isWindows) { @@ -683,8 +519,7 @@ pub const Archive = struct { _ = bun.sys.close(file_handle); }; - const entry_size = @max(lib.archive_entry_size(entry), 0); - const size = @as(usize, @intCast(entry_size)); + const size: usize = @intCast(@max(entry.size(), 0)); if (size > 0) { if (ctx) |ctx_| { const hash: u64 = if (ctx_.pluckers.len > 0) @@ -703,7 +538,7 @@ pub const Archive = struct { if (plucker_.filename_hash == hash) { try plucker_.contents.inflate(size); plucker_.contents.list.expandToCapacity(); - const read = lib.archive_read_data(archive, plucker_.contents.list.items.ptr, size); + const read = archive.readData(plucker_.contents.list.items); try plucker_.contents.inflate(@as(usize, @intCast(read))); plucker_.found = read > 0; plucker_.fd = file_handle; @@ -719,17 +554,17 @@ pub const Archive = struct { C.preallocate_file( file_handle.cast(), 0, - entry_size, + @intCast(size), ) catch {}; } } var retries_remaining: u8 = 5; possibly_retry: while (retries_remaining != 0) : (retries_remaining -= 1) { - switch (lib.archive_read_data_into_fd(archive, bun.uvfdcast(file_handle))) { - lib.ARCHIVE_EOF => break :loop, - lib.ARCHIVE_OK => break :possibly_retry, - lib.ARCHIVE_RETRY => { + switch (archive.readDataIntoFd(bun.uvfdcast(file_handle))) { + .eof => break :loop, + .ok => break :possibly_retry, + .retry => { if (options.log) { Output.err("libarchive error", "extracting {}, retry {d} / {d}", .{ bun.fmt.fmtOSPath(path_slice, .{}), @@ -764,7 +599,7 @@ pub const Archive = struct { pub fn extractToDisk( file_buffer: []const u8, root: []const u8, - ctx: ?*Archive.Context, + ctx: ?*Archiver.Context, comptime FilePathAppender: type, appender: FilePathAppender, comptime options: ExtractOptions, diff --git a/src/open.zig b/src/open.zig index bb83d9b60d..c14c2895c9 100644 --- a/src/open.zig +++ b/src/open.zig @@ -11,7 +11,7 @@ const C = bun.C; const std = @import("std"); const DotEnv = @import("env_loader.zig"); -const opener = switch (@import("builtin").target.os.tag) { +pub const opener = switch (@import("builtin").target.os.tag) { .macos => "/usr/bin/open", .windows => "start", else => "xdg-open", diff --git a/src/sourcemap/sourcemap.zig b/src/sourcemap/sourcemap.zig index 87cdd0558e..dc5cbdb8ac 100644 --- a/src/sourcemap/sourcemap.zig +++ b/src/sourcemap/sourcemap.zig @@ -132,7 +132,7 @@ pub fn parseJSON( bun.JSAst.Stmt.Data.Store.reset(); } debug("parse (JSON, {d} bytes)", .{source.len}); - var json = bun.JSON.ParseJSON(&json_src, &log, arena, false) catch { + var json = bun.JSON.parse(&json_src, &log, arena, false) catch { return error.InvalidJSON; }; diff --git a/src/sql/postgres.zig b/src/sql/postgres.zig index dd90a550e1..93168b63e5 100644 --- a/src/sql/postgres.zig +++ b/src/sql/postgres.zig @@ -1917,7 +1917,7 @@ pub const types = struct { defer value.deinit(); var str = bun.String.fromUTF8(value.slice()); defer str.deref(); - const parse_result = JSValue.parseJSON(str.toJS(globalObject), globalObject); + const parse_result = JSValue.parse(str.toJS(globalObject), globalObject); if (parse_result.isAnyError()) { globalObject.throwValue(parse_result); return error.JSError; diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 191d5b97c7..da92209a5e 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -216,6 +216,83 @@ pub fn isNPMPackageName(target: string) bool { return !scoped or slash_index > 0 and slash_index + 1 < target.len; } +pub fn startsWithUUID(str: string) bool { + const uuid_len = 36; + if (str.len < uuid_len) return false; + for (0..8) |i| { + switch (str[i]) { + '0'...'9', 'a'...'f', 'A'...'F' => {}, + else => return false, + } + } + if (str[8] != '-') return false; + for (9..13) |i| { + switch (str[i]) { + '0'...'9', 'a'...'f', 'A'...'F' => {}, + else => return false, + } + } + if (str[13] != '-') return false; + for (14..18) |i| { + switch (str[i]) { + '0'...'9', 'a'...'f', 'A'...'F' => {}, + else => return false, + } + } + if (str[18] != '-') return false; + for (19..23) |i| { + switch (str[i]) { + '0'...'9', 'a'...'f', 'A'...'F' => {}, + else => return false, + } + } + if (str[23] != '-') return false; + for (24..36) |i| { + switch (str[i]) { + '0'...'9', 'a'...'f', 'A'...'F' => {}, + else => return false, + } + } + return true; +} + +/// https://github.com/npm/cli/blob/63d6a732c3c0e9c19fd4d147eaa5cc27c29b168d/node_modules/%40npmcli/redact/lib/matchers.js#L7 +/// /\b(npms?_)[a-zA-Z0-9]{36,48}\b/gi +/// Returns the length of the secret if one exist. +pub fn startsWithNpmSecret(str: string) u8 { + if (str.len < "npm_".len + 36) return 0; + + if (!strings.hasPrefixCaseInsensitive(str, "npm")) return 0; + + var i: u8 = "npm".len; + + if (str[i] == '_') { + i += 1; + } else if (str[i] == 's' or str[i] == 'S') { + i += 1; + if (str[i] != '_') return 0; + i += 1; + } else { + return 0; + } + + const min_len = i + 36; + const max_len = i + 48; + + while (i < max_len) : (i += 1) { + if (i == str.len) { + return if (i >= min_len) i else 0; + } + + switch (str[i]) { + '0'...'9', 'a'...'z', 'A'...'Z' => {}, + else => return if (i >= min_len) i else 0, + } + } + + return i; +} + pub fn indexAnyComptime(target: string, comptime chars: string) ?usize { for (target, 0..) |parent, i| { inline for (chars) |char| { @@ -919,6 +996,31 @@ pub fn eqlCaseInsensitiveASCII(a: string, b: string, comptime check_len: bool) b return bun.C.strncasecmp(a.ptr, b.ptr, a.len) == 0; } +pub fn eqlCaseInsensitiveT(comptime T: type, a: []const T, b: []const u8) bool { + if (a.len != b.len or a.len == 0) return false; + if (comptime T == u8) return eqlCaseInsensitiveASCIIIgnoreLength(a, b); + + for (a, b) |c, d| { + switch (c) { + 'a'...'z' => if (c != d and c & 0b11011111 != d) return false, + 'A'...'Z' => if (c != d and c | 0b00100000 != d) return false, + else => if (c != d) return false, + } + } + + return true; +} + +pub fn hasPrefixCaseInsensitiveT(comptime T: type, str: []const T, prefix: []const u8) bool { + if (str.len < prefix.len) return false; + + return eqlCaseInsensitiveT(T, str[0..prefix.len], prefix); +} + +pub fn hasPrefixCaseInsensitive(str: []const u8, prefix: []const u8) bool { + return hasPrefixCaseInsensitiveT(u8, str, prefix); +} + pub fn eqlLong(a_str: string, b_str: string, comptime check_len: bool) bool { const len = b_str.len; diff --git a/src/string_mutable.zig b/src/string_mutable.zig index c894eaf2e9..41d7ca0813 100644 --- a/src/string_mutable.zig +++ b/src/string_mutable.zig @@ -9,6 +9,7 @@ const js_lexer = bun.js_lexer; const string = bun.string; const stringZ = bun.stringZ; const CodePoint = bun.CodePoint; +const OOM = bun.OOM; pub const MutableString = struct { allocator: std.mem.Allocator, @@ -18,7 +19,7 @@ pub const MutableString = struct { return MutableString.init(allocator, 2048); } - pub const Writer = std.io.Writer(*@This(), anyerror, MutableString.writeAll); + pub const Writer = std.io.Writer(*@This(), OOM, MutableString.writeAll); pub fn writer(self: *MutableString) Writer { return Writer{ .context = self, @@ -40,11 +41,11 @@ pub const MutableString = struct { return bun.isSliceInBuffer(slice, this.list.items.ptr[0..this.list.capacity]); } - pub fn growIfNeeded(self: *MutableString, amount: usize) !void { + pub fn growIfNeeded(self: *MutableString, amount: usize) OOM!void { try self.list.ensureUnusedCapacity(self.allocator, amount); } - pub fn write(self: *MutableString, bytes: anytype) !usize { + pub fn write(self: *MutableString, bytes: anytype) OOM!usize { bun.debugAssert(bytes.len == 0 or !bun.isSliceInBuffer(bytes, self.list.allocatedSlice())); try self.list.appendSlice(self.allocator, bytes); return bytes.len; @@ -54,7 +55,7 @@ pub const MutableString = struct { return BufferedWriter{ .context = self }; } - pub fn init(allocator: std.mem.Allocator, capacity: usize) std.mem.Allocator.Error!MutableString { + pub fn init(allocator: std.mem.Allocator, capacity: usize) OOM!MutableString { return MutableString{ .allocator = allocator, .list = if (capacity > 0) try std.ArrayListUnmanaged(u8).initCapacity(allocator, capacity) else @@ -67,7 +68,7 @@ pub const MutableString = struct { pub const ensureUnusedCapacity = growIfNeeded; - pub fn initCopy(allocator: std.mem.Allocator, str: anytype) !MutableString { + pub fn initCopy(allocator: std.mem.Allocator, str: anytype) OOM!MutableString { var mutable = try MutableString.init(allocator, str.len); try mutable.copy(str); return mutable; @@ -443,7 +444,7 @@ pub const MutableString = struct { } }; - pub fn writeAll(self: *MutableString, bytes: string) std.mem.Allocator.Error!usize { + pub fn writeAll(self: *MutableString, bytes: string) OOM!usize { try self.list.appendSlice(self.allocator, bytes); return bytes.len; } diff --git a/test/bun.lockb b/test/bun.lockb index 30b73a146ff19cea8e156cf7b9e4fb07d32af8e3..c6449d11b362221940df96be17313e21482c2be6 100755 GIT binary patch delta 65733 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 U5tcZ{qH^4D{6hV@)Uxvb0J+A$0{{R3 diff --git a/test/cli/install/bun-pack.test.ts b/test/cli/install/bun-pack.test.ts index f9aa414179..4f82725bc3 100644 --- a/test/cli/install/bun-pack.test.ts +++ b/test/cli/install/bun-pack.test.ts @@ -2,7 +2,7 @@ import { file, spawn, write } from "bun"; import { readTarball } from "bun:internal-for-testing"; import { beforeEach, describe, expect, test } from "bun:test"; import { exists, mkdir, rm } from "fs/promises"; -import { bunEnv, bunExe, isWindows, runBunInstall, tmpdirSync } from "harness"; +import { bunEnv, bunExe, runBunInstall, tmpdirSync, pack } from "harness"; import { join } from "path"; var packageDir: string; @@ -11,30 +11,6 @@ beforeEach(() => { packageDir = tmpdirSync(); }); -async function pack(cwd: string, env: NodeJS.ProcessEnv, ...args: string[]) { - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "pm", "pack", ...args], - cwd, - stdout: "pipe", - stderr: "pipe", - stdin: "ignore", - env, - }); - - const err = await Bun.readableStreamToText(stderr); - expect(err).not.toContain("error:"); - expect(err).not.toContain("warning:"); - expect(err).not.toContain("failed"); - expect(err).not.toContain("panic:"); - - const out = await Bun.readableStreamToText(stdout); - - const exitCode = await exited; - expect(exitCode).toBe(0); - - return { out, err }; -} - async function packExpectError(cwd: string, env: NodeJS.ProcessEnv, ...args: string[]) { const { stdout, stderr, exited } = spawn({ cmd: [bunExe(), "pm", "pack", ...args], @@ -974,11 +950,7 @@ describe("bins", () => { ]); expect(tarball.entries[0].perm & 0o644).toBe(0o644); - if (isWindows) { - expect(tarball.entries[1].perm & 0o111).toBe(0); - } else { - expect(tarball.entries[1].perm & (0o644 | 0o111)).toBe(0o644 | 0o111); - } + expect(tarball.entries[1].perm & (0o644 | 0o111)).toBe(0o644 | 0o111); }); test("directory", async () => { @@ -1013,13 +985,8 @@ describe("bins", () => { ]); expect(tarball.entries[0].perm & 0o644).toBe(0o644); - if (isWindows) { - expect(tarball.entries[1].perm & 0o111).toBe(0); - expect(tarball.entries[2].perm & 0o111).toBe(0); - } else { - expect(tarball.entries[1].perm & (0o644 | 0o111)).toBe(0o644 | 0o111); - expect(tarball.entries[2].perm & (0o644 | 0o111)).toBe(0o644 | 0o111); - } + expect(tarball.entries[1].perm & (0o644 | 0o111)).toBe(0o644 | 0o111); + expect(tarball.entries[2].perm & (0o644 | 0o111)).toBe(0o644 | 0o111); }); }); diff --git a/test/cli/install/registry/bun-install-registry.test.ts b/test/cli/install/registry/bun-install-registry.test.ts index 8966e19116..756c168b57 100644 --- a/test/cli/install/registry/bun-install-registry.test.ts +++ b/test/cli/install/registry/bun-install-registry.test.ts @@ -14,6 +14,7 @@ import { randomPort, runBunInstall, runBunUpdate, + pack, tempDirWithFiles, tmpdirSync, toBeValidBin, @@ -108,7 +109,6 @@ function registryUrl() { * Returns auth token */ async function generateRegistryUser(username: string, password: string): Promise { - console.log("GENERATE REGISTRY USER"); if (users[username]) { throw new Error("that user already exists"); } else users[username] = password; @@ -130,7 +130,6 @@ async function generateRegistryUser(username: string, password: string): Promise if (response.ok) { const data = await response.json(); - console.log(`Token: ${data.token}`); return data.token; } else { throw new Error("Failed to create user:", response.statusText); @@ -514,6 +513,446 @@ ${Object.keys(opts) ); }); +export async function publish( + env: any, + cwd: string, + ...args: string[] +): Promise<{ out: string; err: string; exitCode: number }> { + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "publish", ...args], + cwd, + stdout: "pipe", + stderr: "pipe", + env, + }); + + const out = await Bun.readableStreamToText(stdout); + const err = await Bun.readableStreamToText(stderr); + const exitCode = await exited; + return { out, err, exitCode }; +} + +async function authBunfig(user: string) { + const authToken = await generateRegistryUser(user, user); + return ` + [install] + cache = false + registry = { url = "http://localhost:${port}/", token = "${authToken}" } + `; +} + +describe("publish", async () => { + describe("otp", async () => { + for (const setAuthHeader of [true, false]) { + test("mock web login" + (setAuthHeader ? "" : " (without auth header)"), async () => { + using mockRegistry = Bun.serve({ + port: 0, + async fetch(req) { + if (req.url.endsWith("otp-pkg-1")) { + if (req.headers.get("npm-otp") === authToken) { + return new Response("OK", { status: 200 }); + } else { + const headers = new Headers(); + if (setAuthHeader) headers.set("www-authenticate", "OTP"); + return new Response( + JSON.stringify({ + // this isn't accurate, but we just want to check that finding this string works + mock: setAuthHeader ? "" : "one-time password", + + authUrl: `http://localhost:${this.port}/auth`, + doneUrl: `http://localhost:${this.port}/done`, + }), + { + status: 401, + headers, + }, + ); + } + } else if (req.url.endsWith("auth")) { + expect.unreachable("url given to user, bun publish should not request"); + } else if (req.url.endsWith("done")) { + // send a fake response saying the user has authenticated successfully with the auth url + return new Response(JSON.stringify({ token: authToken }), { status: 200 }); + } + + expect.unreachable("unexpected url"); + }, + }); + + const authToken = await generateRegistryUser("otp" + (setAuthHeader ? "" : "noheader"), "otp"); + const bunfig = ` + [install] + cache = false + registry = { url = "http://localhost:${mockRegistry.port}", token = "${authToken}" }`; + await Promise.all([ + rm(join(import.meta.dir, "packages", "otp-pkg-1"), { recursive: true, force: true }), + write(join(packageDir, "bunfig.toml"), bunfig), + write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "otp-pkg-1", + version: "2.2.2", + dependencies: { + "otp-pkg-1": "2.2.2", + }, + }), + ), + ]); + + const { out, err, exitCode } = await publish(env, packageDir); + expect(exitCode).toBe(0); + }); + } + }); + test("can publish a package then install it", async () => { + const bunfig = await authBunfig("basic"); + await Promise.all([ + rm(join(import.meta.dir, "packages", "publish-pkg-1"), { recursive: true, force: true }), + write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "publish-pkg-1", + version: "1.1.1", + dependencies: { + "publish-pkg-1": "1.1.1", + }, + }), + ), + write(join(packageDir, "bunfig.toml"), bunfig), + ]); + + const { out, err, exitCode } = await publish(env, packageDir); + expect(err).not.toContain("error:"); + expect(err).not.toContain("warn:"); + expect(exitCode).toBe(0); + + await runBunInstall(env, packageDir); + expect(await exists(join(packageDir, "node_modules", "publish-pkg-1", "package.json"))).toBeTrue(); + }); + test("can publish from a tarball", async () => { + const bunfig = await authBunfig("tarball"); + const json = { + name: "publish-pkg-2", + version: "2.2.2", + dependencies: { + "publish-pkg-2": "2.2.2", + }, + }; + await Promise.all([ + rm(join(import.meta.dir, "packages", "publish-pkg-2"), { recursive: true, force: true }), + write(join(packageDir, "package.json"), JSON.stringify(json)), + write(join(packageDir, "bunfig.toml"), bunfig), + ]); + + await pack(packageDir, env); + + let { out, err, exitCode } = await publish(env, packageDir, "./publish-pkg-2-2.2.2.tgz"); + expect(err).not.toContain("error:"); + expect(err).not.toContain("warn:"); + expect(exitCode).toBe(0); + + await runBunInstall(env, packageDir); + expect(await exists(join(packageDir, "node_modules", "publish-pkg-2", "package.json"))).toBeTrue(); + + await Promise.all([ + rm(join(import.meta.dir, "packages", "publish-pkg-2"), { recursive: true, force: true }), + rm(join(packageDir, "bun.lockb"), { recursive: true, force: true }), + rm(join(packageDir, "node_modules"), { recursive: true, force: true }), + ]); + + // now with an absoute path + ({ out, err, exitCode } = await publish(env, packageDir, join(packageDir, "publish-pkg-2-2.2.2.tgz"))); + expect(err).not.toContain("error:"); + expect(err).not.toContain("warn:"); + expect(exitCode).toBe(0); + + await runBunInstall(env, packageDir); + expect(await file(join(packageDir, "node_modules", "publish-pkg-2", "package.json")).json()).toEqual(json); + }); + + test("can publish workspace package", async () => { + const bunfig = await authBunfig("workspace"); + const pkgJson = { + name: "publish-pkg-3", + version: "3.3.3", + dependencies: { + "publish-pkg-3": "3.3.3", + }, + }; + await Promise.all([ + rm(join(import.meta.dir, "packages", "publish-pkg-3"), { recursive: true, force: true }), + write(join(packageDir, "bunfig.toml"), bunfig), + write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "root", + workspaces: ["packages/*"], + }), + ), + write(join(packageDir, "packages", "publish-pkg-3", "package.json"), JSON.stringify(pkgJson)), + ]); + + await publish(env, join(packageDir, "packages", "publish-pkg-3")); + + await write( + join(packageDir, "package.json"), + JSON.stringify({ name: "root", "dependencies": { "publish-pkg-3": "3.3.3" } }), + ); + + await runBunInstall(env, packageDir); + + expect(await file(join(packageDir, "node_modules", "publish-pkg-3", "package.json")).json()).toEqual(pkgJson); + }); + + describe("--dry-run", async () => { + test("does not publish", async () => { + const bunfig = await authBunfig("dryrun"); + await Promise.all([ + rm(join(import.meta.dir, "packages", "dry-run-1"), { recursive: true, force: true }), + write(join(packageDir, "bunfig.toml"), bunfig), + write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "dry-run-1", + version: "1.1.1", + dependencies: { + "dry-run-1": "1.1.1", + }, + }), + ), + ]); + + const { out, err, exitCode } = await publish(env, packageDir, "--dry-run"); + expect(exitCode).toBe(0); + + expect(await exists(join(import.meta.dir, "packages", "dry-run-1"))).toBeFalse(); + }); + test("does not publish from tarball path", async () => { + const bunfig = await authBunfig("dryruntarball"); + await Promise.all([ + rm(join(import.meta.dir, "packages", "dry-run-2"), { recursive: true, force: true }), + write(join(packageDir, "bunfig.toml"), bunfig), + write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "dry-run-2", + version: "2.2.2", + dependencies: { + "dry-run-2": "2.2.2", + }, + }), + ), + ]); + + await pack(packageDir, env); + + const { out, err, exitCode } = await publish(env, packageDir, "./dry-run-2-2.2.2.tgz", "--dry-run"); + expect(exitCode).toBe(0); + + expect(await exists(join(import.meta.dir, "packages", "dry-run-2"))).toBeFalse(); + }); + }); + + describe("lifecycle scripts", async () => { + const script = `const fs = require("fs"); + fs.writeFileSync(process.argv[2] + ".txt", \` +prepublishOnly: \${fs.existsSync("prepublishOnly.txt")} +publish: \${fs.existsSync("publish.txt")} +postpublish: \${fs.existsSync("postpublish.txt")} +prepack: \${fs.existsSync("prepack.txt")} +prepare: \${fs.existsSync("prepare.txt")} +postpack: \${fs.existsSync("postpack.txt")}\`)`; + const json = { + name: "publish-pkg-4", + version: "4.4.4", + scripts: { + // should happen in this order + "prepublishOnly": `${bunExe()} script.js prepublishOnly`, + "prepack": `${bunExe()} script.js prepack`, + "prepare": `${bunExe()} script.js prepare`, + "postpack": `${bunExe()} script.js postpack`, + "publish": `${bunExe()} script.js publish`, + "postpublish": `${bunExe()} script.js postpublish`, + }, + dependencies: { + "publish-pkg-4": "4.4.4", + }, + }; + + for (const arg of ["", "--dry-run"]) { + test(`should run in order${arg ? " (--dry-run)" : ""}`, async () => { + const bunfig = await authBunfig("lifecycle" + (arg ? "dry" : "")); + await Promise.all([ + rm(join(import.meta.dir, "packages", "publish-pkg-4"), { recursive: true, force: true }), + write(join(packageDir, "package.json"), JSON.stringify(json)), + write(join(packageDir, "script.js"), script), + write(join(packageDir, "bunfig.toml"), bunfig), + ]); + + const { out, err, exitCode } = await publish(env, packageDir, arg); + expect(exitCode).toBe(0); + + const results = await Promise.all([ + file(join(packageDir, "prepublishOnly.txt")).text(), + file(join(packageDir, "prepack.txt")).text(), + file(join(packageDir, "prepare.txt")).text(), + file(join(packageDir, "postpack.txt")).text(), + file(join(packageDir, "publish.txt")).text(), + file(join(packageDir, "postpublish.txt")).text(), + ]); + + expect(results).toEqual([ + "\nprepublishOnly: false\npublish: false\npostpublish: false\nprepack: false\nprepare: false\npostpack: false", + "\nprepublishOnly: true\npublish: false\npostpublish: false\nprepack: false\nprepare: false\npostpack: false", + "\nprepublishOnly: true\npublish: false\npostpublish: false\nprepack: true\nprepare: false\npostpack: false", + "\nprepublishOnly: true\npublish: false\npostpublish: false\nprepack: true\nprepare: true\npostpack: false", + "\nprepublishOnly: true\npublish: false\npostpublish: false\nprepack: true\nprepare: true\npostpack: true", + "\nprepublishOnly: true\npublish: true\npostpublish: false\nprepack: true\nprepare: true\npostpack: true", + ]); + }); + } + + test("--ignore-scripts", async () => { + const bunfig = await authBunfig("ignorescripts"); + await Promise.all([ + rm(join(import.meta.dir, "packages", "publish-pkg-5"), { recursive: true, force: true }), + write(join(packageDir, "package.json"), JSON.stringify(json)), + write(join(packageDir, "script.js"), script), + write(join(packageDir, "bunfig.toml"), bunfig), + ]); + + const { out, err, exitCode } = await publish(env, packageDir, "--ignore-scripts"); + expect(exitCode).toBe(0); + + const results = await Promise.all([ + exists(join(packageDir, "prepublishOnly.txt")), + exists(join(packageDir, "prepack.txt")), + exists(join(packageDir, "prepare.txt")), + exists(join(packageDir, "postpack.txt")), + exists(join(packageDir, "publish.txt")), + exists(join(packageDir, "postpublish.txt")), + ]); + + expect(results).toEqual([false, false, false, false, false, false]); + }); + }); + + test("attempting to publish a private package should fail", async () => { + const bunfig = await authBunfig("privatepackage"); + await Promise.all([ + rm(join(import.meta.dir, "packages", "publish-pkg-6"), { recursive: true, force: true }), + write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "publish-pkg-6", + version: "6.6.6", + private: true, + dependencies: { + "publish-pkg-6": "6.6.6", + }, + }), + ), + write(join(packageDir, "bunfig.toml"), bunfig), + ]); + + // should fail + let { out, err, exitCode } = await publish(env, packageDir); + expect(exitCode).toBe(1); + expect(err).toContain("error: attempted to publish a private package"); + expect(await exists(join(import.meta.dir, "packages", "publish-pkg-6-6.6.6.tgz"))).toBeFalse(); + + // try tarball + await pack(packageDir, env); + ({ out, err, exitCode } = await publish(env, packageDir, "./publish-pkg-6-6.6.6.tgz")); + expect(exitCode).toBe(1); + expect(err).toContain("error: attempted to publish a private package"); + expect(await exists(join(packageDir, "publish-pkg-6-6.6.6.tgz"))).toBeTrue(); + }); + + describe("access", async () => { + test("--access", async () => { + const bunfig = await authBunfig("accessflag"); + await Promise.all([ + rm(join(import.meta.dir, "packages", "publish-pkg-7"), { recursive: true, force: true }), + write(join(packageDir, "bunfig.toml"), bunfig), + write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "publish-pkg-7", + version: "7.7.7", + }), + ), + ]); + + // should fail + let { out, err, exitCode } = await publish(env, packageDir, "--access", "restricted"); + expect(exitCode).toBe(1); + expect(err).toContain("error: unable to restrict access to unscoped package"); + + ({ out, err, exitCode } = await publish(env, packageDir, "--access", "public")); + expect(exitCode).toBe(0); + + expect(await exists(join(import.meta.dir, "packages", "publish-pkg-7"))).toBeTrue(); + }); + + for (const access of ["restricted", "public"]) { + test(`access ${access}`, async () => { + const bunfig = await authBunfig("access" + access); + + const pkgJson = { + name: "@secret/publish-pkg-8", + version: "8.8.8", + dependencies: { + "@secret/publish-pkg-8": "8.8.8", + }, + publishConfig: { + access, + }, + }; + + await Promise.all([ + rm(join(import.meta.dir, "packages", "@secret", "publish-pkg-8"), { recursive: true, force: true }), + write(join(packageDir, "bunfig.toml"), bunfig), + write(join(packageDir, "package.json"), JSON.stringify(pkgJson)), + ]); + + let { out, err, exitCode } = await publish(env, packageDir); + expect(exitCode).toBe(0); + + await runBunInstall(env, packageDir); + + expect(await file(join(packageDir, "node_modules", "@secret", "publish-pkg-8", "package.json")).json()).toEqual( + pkgJson, + ); + }); + } + }); + + describe("tag", async () => { + test("can publish with a tag", async () => { + const bunfig = await authBunfig("simpletag"); + const pkgJson = { + name: "publish-pkg-9", + version: "9.9.9", + dependencies: { + "publish-pkg-9": "simpletag", + }, + }; + await Promise.all([ + rm(join(import.meta.dir, "packages", "publish-pkg-9"), { recursive: true, force: true }), + write(join(packageDir, "bunfig.toml"), bunfig), + write(join(packageDir, "package.json"), JSON.stringify(pkgJson)), + ]); + + let { out, err, exitCode } = await publish(env, packageDir, "--tag", "simpletag"); + expect(exitCode).toBe(0); + + await runBunInstall(env, packageDir); + expect(await file(join(packageDir, "node_modules", "publish-pkg-9", "package.json")).json()).toEqual(pkgJson); + }); + }); +}); + describe("package.json indentation", async () => { test("works for root and workspace packages", async () => { await Promise.all([ diff --git a/test/cli/install/registry/verdaccio.yaml b/test/cli/install/registry/verdaccio.yaml index e46176c27a..6d12f17444 100644 --- a/test/cli/install/registry/verdaccio.yaml +++ b/test/cli/install/registry/verdaccio.yaml @@ -75,6 +75,11 @@ packages: publish: $authenticated unpublish: $authenticated + "@secret/*": + access: $authenticated + publish: $authenticated + unpublish: $authenticated + "@*/*": # scoped packages access: $all @@ -92,7 +97,7 @@ packages: # allow all known users to publish/publish packages # (anyone can register by default, remember?) - publish: $authenticated + publish: $all unpublish: $all # if package is not available locally, proxy requests to 'npmjs' registry diff --git a/test/harness.ts b/test/harness.ts index 8abf61302e..fd49ed84ca 100644 --- a/test/harness.ts +++ b/test/harness.ts @@ -1048,6 +1048,30 @@ export async function runBunUpdate( return { out: out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/), err, exitCode }; } +export async function pack(cwd: string, env: NodeJS.ProcessEnv, ...args: string[]) { + const { stdout, stderr, exited } = Bun.spawn({ + cmd: [bunExe(), "pm", "pack", ...args], + cwd, + stdout: "pipe", + stderr: "pipe", + stdin: "ignore", + env, + }); + + const err = await Bun.readableStreamToText(stderr); + expect(err).not.toContain("error:"); + expect(err).not.toContain("warning:"); + expect(err).not.toContain("failed"); + expect(err).not.toContain("panic:"); + + const out = await Bun.readableStreamToText(stdout); + + const exitCode = await exited; + expect(exitCode).toBe(0); + + return { out, err }; +} + // If you need to modify, clone it export const expiredTls = Object.freeze({ cert: "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAKLdQVPy90jjMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\nBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\naWRnaXRzIFB0eSBMdGQwHhcNMTkwMjAzMTQ0OTM1WhcNMjAwMjAzMTQ0OTM1WjBF\nMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\nZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEA7i7IIEdICTiSTVx+ma6xHxOtcbd6wGW3nkxlCkJ1UuV8NmY5ovMsGnGD\nhJJtUQ2j5ig5BcJUf3tezqCNW4tKnSOgSISfEAKvpn2BPvaFq3yx2Yjz0ruvcGKp\nDMZBXmB/AAtGyN/UFXzkrcfppmLHJTaBYGG6KnmU43gPkSDy4iw46CJFUOupc51A\nFIz7RsE7mbT1plCM8e75gfqaZSn2k+Wmy+8n1HGyYHhVISRVvPqkS7gVLSVEdTea\nUtKP1Vx/818/HDWk3oIvDVWI9CFH73elNxBkMH5zArSNIBTehdnehyAevjY4RaC/\nkK8rslO3e4EtJ9SnA4swOjCiqAIQEwIDAQABo1AwTjAdBgNVHQ4EFgQUv5rc9Smm\n9c4YnNf3hR49t4rH4yswHwYDVR0jBBgwFoAUv5rc9Smm9c4YnNf3hR49t4rH4ysw\nDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATcL9CAAXg0u//eYUAlQa\nL+l8yKHS1rsq1sdmx7pvsmfZ2g8ONQGfSF3TkzkI2OOnCBokeqAYuyT8awfdNUtE\nEHOihv4ZzhK2YZVuy0fHX2d4cCFeQpdxno7aN6B37qtsLIRZxkD8PU60Dfu9ea5F\nDDynnD0TUabna6a0iGn77yD8GPhjaJMOz3gMYjQFqsKL252isDVHEDbpVxIzxPmN\nw1+WK8zRNdunAcHikeoKCuAPvlZ83gDQHp07dYdbuZvHwGj0nfxBLc9qt90XsBtC\n4IYR7c/bcLMmKXYf0qoQ4OzngsnPI5M+v9QEHvYWaKVwFY4CTcSNJEwfXw+BAeO5\nOA==\n-----END CERTIFICATE-----", diff --git a/test/package.json b/test/package.json index 08096cce5d..7406bf4486 100644 --- a/test/package.json +++ b/test/package.json @@ -59,7 +59,7 @@ "svelte": "3.55.1", "typescript": "5.0.2", "undici": "5.20.0", - "verdaccio": "5.27.0", + "verdaccio": "6.0.0", "vitest": "0.32.2", "webpack": "5.88.0", "webpack-cli": "4.7.2",