diff --git a/.agents/skills/recreate-terminal-demo/scripts/render-demo.sh b/.agents/skills/recreate-terminal-demo/scripts/render-demo.sh index 20b012a..6325ca2 100755 --- a/.agents/skills/recreate-terminal-demo/scripts/render-demo.sh +++ b/.agents/skills/recreate-terminal-demo/scripts/render-demo.sh @@ -48,12 +48,12 @@ git check-attr binary -- "$gif_path" | grep -q 'binary: set' || { } test ! -e generated/version-info.ts || { - echo "Tape cleanup failed: generated/version-info.ts still exists" >&2 + echo "Dry-run created generated/version-info.ts" >&2 exit 1 } mkdir -p "$frames_dir" -for sample in "3:help" "7:generate" "11.5:result"; do +for sample in "3:help" "7:generate" "11:verbose" "17:dry-run"; do timestamp="${sample%%:*}" name="${sample#*:}" docker run --rm --entrypoint ffmpeg -v "$repo_root:/vhs" "$vhs_image" \ @@ -81,4 +81,5 @@ printf 'SHA-256: %s\n' "$checksum" printf 'Inspection frames:\n' printf ' %s/help.png\n' "$frames_dir" printf ' %s/generate.png\n' "$frames_dir" -printf ' %s/result.png\n' "$frames_dir" +printf ' %s/verbose.png\n' "$frames_dir" +printf ' %s/dry-run.png\n' "$frames_dir" diff --git a/.github/actions/report-pr/action.yml b/.github/actions/report-pr/action.yml index a01e3e7..a442137 100644 --- a/.github/actions/report-pr/action.yml +++ b/.github/actions/report-pr/action.yml @@ -16,13 +16,13 @@ runs: with: filename: ${{ inputs.coverage-file }} badge: true - fail_below_min: false + fail_below_min: true format: markdown hide_branch_rate: true hide_complexity: true indicators: true output: both - thresholds: '60 80' + thresholds: '95 100' - name: Add Coverage PR Comment uses: marocchino/sticky-pull-request-comment@v2 diff --git a/README.md b/README.md index d7d9430..af031c5 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ npx package-version-info --output src/version-info.ts ``` ```text -✅ Generated src/version-info.ts (v1.0.0, 2.00ms) +✓ Generated src/version-info.ts • v1.0.0 • 2.00 ms ``` ### 3. Import it @@ -107,7 +107,8 @@ npx package-version-info | --- | --- | --- | --- | | `--help` | `-h` | — | Display usage information. | | `--version` | `-v` | — | Display the installed package version. | -| `--verbose` | — | Disabled | Display detailed generation progress. | +| `--verbose` | — | Disabled | Display a detailed generation summary. | +| `--dry-run` | — | Disabled | Print TypeScript without creating a file. | | `--input ` | `-i` | `package.json` | Input package file. | | `--output ` | `-o` | `version-info.ts` | Generated TypeScript file. | | `--git ` | `-g` | `.git` | Git directory or pointer file used for branch and commit metadata. | @@ -124,6 +125,12 @@ npx package-version-info # Show detailed progress npx package-version-info --verbose +# Preview generated TypeScript without creating a file +npx package-version-info --dry-run + +# Pipe the preview to another command +npx package-version-info --dry-run | less + # Use custom paths npx package-version-info \ --input package.json \ @@ -142,7 +149,7 @@ npx package-version-info \ Compact mode is designed for normal builds: ```text -✅ Generated src/version-info.ts (v1.0.0, 2.00ms) +✓ Generated src/version-info.ts • v1.0.0 • 2.00 ms ``` Use verbose mode when diagnosing package, author, timestamp, Git, or output-path behavior: @@ -151,6 +158,27 @@ Use verbose mode when diagnosing package, author, timestamp, Git, or output-path npx package-version-info --verbose ``` +```text +› Package package.json • v1.0.0 +› Author Dominik Hladík • dominik.hladik@seznam.cz • https://github.com/Celtian +› Timestamp 2026-07-16T18:53:01.908Z +› Git main • 8be6ca60256cc90e0a41d40b9ee222165f239444 +✓ Generated version-info.ts • v1.0.0 • 2.00 ms +``` + +### Dry-run preview + +Use `--dry-run` to print the generated TypeScript to stdout without creating the configured output +file or its parent directories: + +```bash +npx package-version-info --dry-run +``` + +The stdout stream contains only TypeScript, with no colors or status text, so it can be redirected +or piped directly. Combining `--verbose` with `--dry-run` keeps the TypeScript on stdout and writes +the colored diagnostic summary to stderr, ending with `✓ Previewed`. + ## 🛠️ Build-tool integration Generate version information automatically before your application build: diff --git a/docs/assets/terminal-demo.gif b/docs/assets/terminal-demo.gif index 485aa7d..b57e77e 100644 Binary files a/docs/assets/terminal-demo.gif and b/docs/assets/terminal-demo.gif differ diff --git a/docs/terminal-demo.tape b/docs/terminal-demo.tape index d38154e..a2df88e 100644 --- a/docs/terminal-demo.tape +++ b/docs/terminal-demo.tape @@ -1,8 +1,8 @@ Output docs/assets/terminal-demo.gif Set Shell bash -Set Width 1200 -Set Height 720 +Set Width 1280 +Set Height 640 Set FontSize 20 Set TypingSpeed 25ms Set Framerate 24 @@ -41,10 +41,22 @@ Sleep 2s Ctrl+L Sleep 500ms -Type "sed -n '1,22p' generated/version-info.ts" +Type "package-version-info --verbose --output generated/version-info.ts" +Enter +Sleep 3s + +Hide +Type "rm -f generated/version-info.ts" +Enter +Show + +Ctrl+L +Sleep 500ms + +Type "package-version-info --dry-run" Enter Sleep 4s Hide -Type "rm -f generated/version-info.ts /tmp/pvi-demo-bin/package-version-info" +Type "rm -f /tmp/pvi-demo-bin/package-version-info" Enter diff --git a/scripts/coverage.sh b/scripts/coverage.sh index d7de1b9..ed9768c 100644 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -25,6 +25,17 @@ kcov \ "$coverage_dir/root" \ "$coverage_dir/cli" +coverage_xml="$coverage_dir/merged/kcov-merged/cobertura.xml" + sed -i \ 's/ branch-rate="[^"]*" branches-covered="[^"]*" branches-rate="[^"]*"/ branch-rate="0" branches-covered="0" branches-valid="0"/' \ - "$coverage_dir/merged/kcov-merged/cobertura.xml" + "$coverage_xml" + +line_rate=$(sed -n 's/^" ++ color.reset ++ " Input package.json (default: package.json)." ++ "\n " ++ color.green ++ "-o, --output " ++ color.reset ++ @@ -114,6 +119,7 @@ test "no arguments generate with default paths" { try std.testing.expectEqualStrings("version-info.ts", options.output_path); try std.testing.expectEqualStrings(".git", options.git_path); try std.testing.expect(!options.verbose); + try std.testing.expect(!options.dry_run); } test "help flags show help" { @@ -129,6 +135,7 @@ test "version flags show version" { test "generation options are parsed" { const action = parseTest(&.{ "--verbose", + "--dry-run", "--input", "input.json", "-o", @@ -141,11 +148,13 @@ test "generation options are parsed" { try std.testing.expectEqualStrings("generated/info.ts", options.output_path); try std.testing.expectEqualStrings("../.git", options.git_path); try std.testing.expect(options.verbose); + try std.testing.expect(options.dry_run); } -test "verbose enables detailed generation progress" { - const options = parseTest(&.{"--verbose"}).generate; +test "verbose dry run enables a detailed preview" { + const options = parseTest(&.{ "--verbose", "--dry-run" }).generate; try std.testing.expect(options.verbose); + try std.testing.expect(options.dry_run); try std.testing.expectEqualStrings("package.json", options.input_path); } @@ -181,4 +190,5 @@ test "help contains usage commands and options" { try std.testing.expect(std.mem.indexOf(u8, help, " generate") == null); try std.testing.expect(std.mem.indexOf(u8, help, "--help") != null); try std.testing.expect(std.mem.indexOf(u8, help, "--verbose") != null); + try std.testing.expect(std.mem.indexOf(u8, help, "--dry-run") != null); } diff --git a/src/main.zig b/src/main.zig index 557cf43..da6d4e4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -19,14 +19,17 @@ pub fn main(init: std.process.Init) !void { std.process.exit(2); }, .generate => |options| { - if (options.verbose) { + if (options.verbose or options.dry_run) { try version_info.generateVersionInfoWithOptions( init.gpa, init.io, options.input_path, options.output_path, options.git_path, - .{ .verbose = true }, + .{ + .verbose = options.verbose, + .dry_run = options.dry_run, + }, ); } else { try version_info.generateVersionInfo( diff --git a/src/root.zig b/src/root.zig index 01f48cd..3549aae 100644 --- a/src/root.zig +++ b/src/root.zig @@ -2,10 +2,12 @@ const std = @import("std"); const embedded_package_json = @embedFile("package.json"); -const compact_summary_format = "✅ Generated {s} (v{s}, {d:.2}ms)"; +const compact_summary_format = "✓ Generated {s} • v{s} • {d:.2} ms"; const color = struct { const reset = "\x1b[0m"; + const bold = "\x1b[1m"; + const dim = "\x1b[2m"; const yellow = "\x1b[33m"; const blue = "\x1b[34m"; const cyan = "\x1b[36m"; @@ -27,16 +29,143 @@ fn writeCompactSummary(writer: anytype, output_path: []const u8, version: []cons try writer.print(compact_summary_format, .{ output_path, version, duration_ms }); } +fn writeSeparator(writer: anytype) !void { + try writer.writeAll(" • "); +} + +fn writeVerboseSummary( + writer: anytype, + package_json_path: []const u8, + output_path: []const u8, + version: []const u8, + date_str: []const u8, + author_info: ?AuthorInfo, + git_info: ?GitInfo, + summary_label: []const u8, + duration_ms: f64, +) !void { + try writer.print("› Package {s} • v{s}\n", .{ package_json_path, version }); + + try writer.writeAll("› Author "); + if (author_info) |author| { + try writer.writeAll(author.name); + if (author.email.len > 0) { + try writeSeparator(writer); + try writer.writeAll(author.email); + } + if (author.url.len > 0) { + try writeSeparator(writer); + try writer.writeAll(author.url); + } + } else { + try writer.writeAll("Not found"); + } + + try writer.print("\n› Timestamp {s}\n› Git ", .{date_str}); + if (git_info) |info| { + try writer.print("{s} • {s}", .{ info.branch, info.commit }); + } else { + try writer.writeAll("Not a repository"); + } + + try writer.print("\n✓ {s:<11}{s} • v{s} • {d:.2} ms\n", .{ + summary_label, + output_path, + version, + duration_ms, + }); +} + +fn logSeparator() void { + std.debug.print(" {s}•{s} ", .{ color.dim, color.reset }); +} + +fn logVerbosePrefix(comptime marker: []const u8, accent: []const u8, label: []const u8) void { + std.debug.print("{s}{s}" ++ marker ++ "{s} {s}{s}{s:<11}{s}", .{ + color.bold, + accent, + color.reset, + color.bold, + accent, + label, + color.reset, + }); +} + fn logCompactSummary(output_path: []const u8, version: []const u8, duration_ms: f64) void { - std.debug.print("{s}" ++ compact_summary_format ++ "{s}\n", .{ + std.debug.print("{s}{s}✓{s} Generated {s}{s}{s}{s} {s}•{s} {s}v{s}{s} {s}•{s} {s}{d:.2} ms{s}\n", .{ + color.bold, color.bright_green, + color.reset, + color.bold, + color.bright_cyan, output_path, + color.reset, + color.dim, + color.reset, + color.bright_magenta, version, + color.reset, + color.dim, + color.reset, + color.bright_blue, duration_ms, color.reset, }); } +fn logVerboseSummary( + package_json_path: []const u8, + output_path: []const u8, + version: []const u8, + date_str: []const u8, + author_info: ?AuthorInfo, + git_info: ?GitInfo, + summary_label: []const u8, + duration_ms: f64, +) void { + logVerbosePrefix("›", color.bright_cyan, "Package"); + std.debug.print("{s}{s}{s}{s}", .{ color.bold, color.bright_cyan, package_json_path, color.reset }); + logSeparator(); + std.debug.print("{s}v{s}{s}\n", .{ color.bright_magenta, version, color.reset }); + + logVerbosePrefix("›", color.bright_blue, "Author"); + if (author_info) |author| { + std.debug.print("{s}{s}{s}", .{ color.bright_blue, author.name, color.reset }); + if (author.email.len > 0) { + logSeparator(); + std.debug.print("{s}{s}{s}", .{ color.cyan, author.email, color.reset }); + } + if (author.url.len > 0) { + logSeparator(); + std.debug.print("{s}{s}{s}", .{ color.blue, author.url, color.reset }); + } + } else { + std.debug.print("{s}Not found{s}", .{ color.yellow, color.reset }); + } + std.debug.print("\n", .{}); + + logVerbosePrefix("›", color.cyan, "Timestamp"); + std.debug.print("{s}{s}{s}\n", .{ color.cyan, date_str, color.reset }); + + logVerbosePrefix("›", color.bright_magenta, "Git"); + if (git_info) |info| { + std.debug.print("{s}{s}{s}", .{ color.bright_magenta, info.branch, color.reset }); + logSeparator(); + std.debug.print("{s}{s}{s}", .{ color.bright_magenta, info.commit, color.reset }); + } else { + std.debug.print("{s}Not a repository{s}", .{ color.yellow, color.reset }); + } + std.debug.print("\n", .{}); + + logVerbosePrefix("✓", color.bright_green, summary_label); + std.debug.print("{s}{s}{s}{s}", .{ color.bold, color.bright_cyan, output_path, color.reset }); + logSeparator(); + std.debug.print("{s}v{s}{s}", .{ color.bright_magenta, version, color.reset }); + logSeparator(); + std.debug.print("{s}{d:.2} ms{s}\n", .{ color.bright_blue, duration_ms, color.reset }); +} + /// Prints the tool's version from embedded package.json pub fn printToolVersion(allocator: std.mem.Allocator) !void { const parsed = try std.json.parseFromSlice(std.json.Value, allocator, embedded_package_json, .{}); @@ -71,6 +200,7 @@ pub const GitInfo = struct { pub const GenerateOptions = struct { verbose: bool = false, + dry_run: bool = false, }; fn readFileAlloc(allocator: std.mem.Allocator, io: std.Io, path: []const u8, limit: std.Io.Limit) ![]u8 { @@ -255,6 +385,41 @@ fn writeVersionInfo(writer: anytype, version: []const u8, date_str: []const u8, try writer.writeAll("\n};\n"); } +const OutputDestination = union(enum) { + file: []const u8, + writer: *std.Io.Writer, +}; + +fn writeVersionInfoOutput( + io: std.Io, + destination: OutputDestination, + version: []const u8, + date_str: []const u8, + author_info: ?AuthorInfo, + git_info: ?GitInfo, +) !void { + switch (destination) { + .file => |output_path| { + const cwd = std.Io.Dir.cwd(); + if (std.fs.path.dirname(output_path)) |dir_path| { + try cwd.createDirPath(io, dir_path); + } + + const output_file = try cwd.createFile(io, output_path, .{}); + defer output_file.close(io); + + var output_buffer: [4096]u8 = undefined; + var output_writer = output_file.writer(io, &output_buffer); + try writeVersionInfo(&output_writer.interface, version, date_str, author_info, git_info); + try output_writer.interface.flush(); + }, + .writer => |writer| { + try writeVersionInfo(writer, version, date_str, author_info, git_info); + try writer.flush(); + }, + } +} + /// Formats a Unix timestamp in milliseconds to ISO 8601 format (YYYY-MM-DDTHH:MM:SS.sssZ) fn formatTimestampISO8601(buffer: []u8, millis: i64) ![]const u8 { const epoch: std.time.epoch.EpochSeconds = .{ @@ -290,14 +455,6 @@ pub fn generateVersionInfoWithOptions( options: GenerateOptions, ) !void { const start_time = std.Io.Clock.awake.now(io); - if (options.verbose) { - logNewLine(); - log(color.yellow, "🚀", "Starting version info generation...", .{}); - - logNewLine(); - log(color.blue, "📖", "Reading {s}...", .{package_json_path}); - } - const cwd = std.Io.Dir.cwd(); const file_content = try readFileAlloc(allocator, io, package_json_path, .limited(1024 * 1024)); defer allocator.free(file_content); @@ -305,74 +462,51 @@ pub fn generateVersionInfoWithOptions( defer parsed.deinit(); const version = parsed.value.object.get("version").?.string; - if (options.verbose) { - log(color.blue, "📦", "Version: {s}", .{version}); - - logNewLine(); - log(color.bright_blue, "👤", "Reading author info...", .{}); - } const author_info = getAuthor(parsed.value); - if (options.verbose) { - if (author_info) |author| { - log(color.bright_blue, "✍️ ", "Name: {s}", .{author.name}); - if (author.email.len > 0) log(color.bright_blue, "📧", "Email: {s}", .{author.email}); - if (author.url.len > 0) log(color.bright_blue, "🔗", "URL: {s}", .{author.url}); - } else { - log(color.yellow, "⚠️ ", "No author info found in package.json", .{}); - } - - logNewLine(); - log(color.cyan, "⏰", "Generating timestamp...", .{}); - } const millis = std.Io.Clock.real.now(io).toMilliseconds(); var date_buffer: [30]u8 = undefined; const date_str = try formatTimestampISO8601(&date_buffer, millis); - if (options.verbose) { - log(color.cyan, "📅", "Date: {s}", .{date_str}); - - logNewLine(); - log(color.bright_magenta, "🌿", "Reading git info...", .{}); - } const git_info = try getGitInfo(allocator, io, git_path); defer if (git_info) |info| info.deinit(allocator); - if (options.verbose) { - if (git_info) |info| { - log(color.bright_magenta, "📍", "Branch: {s}", .{info.branch}); - log(color.bright_magenta, "🔖", "Commit: {s}", .{info.commit}); - } else { - log(color.yellow, "⚠️ ", "Not a git repository (git info skipped)", .{}); - } - - logNewLine(); - log(color.bright_green, "✍️ ", "Writing to {s}...", .{output_path}); - } - - if (std.fs.path.dirname(output_path)) |dir_path| { - try cwd.createDirPath(io, dir_path); + if (options.dry_run) { + var output_buffer: [4096]u8 = undefined; + var output_writer = std.Io.File.stdout().writer(io, &output_buffer); + try writeVersionInfoOutput( + io, + .{ .writer = &output_writer.interface }, + version, + date_str, + author_info, + git_info, + ); + } else { + try writeVersionInfoOutput( + io, + .{ .file = output_path }, + version, + date_str, + author_info, + git_info, + ); } - const output_file = try cwd.createFile(io, output_path, .{}); - defer output_file.close(io); - - var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = output_file.writer(io, &stdout_buffer); - const writer = &stdout_writer.interface; - - try writeVersionInfo(writer, version, date_str, author_info, git_info); - - try writer.flush(); - const end_time = std.Io.Clock.awake.now(io); const duration_ns = start_time.durationTo(end_time).toNanoseconds(); const duration_ms = @as(f64, @floatFromInt(duration_ns)) / std.time.ns_per_ms; if (options.verbose) { - log(color.bright_green, "✅", "Successfully generated {s}", .{output_path}); - logNewLine(); - log(color.yellow, "⏱️ ", "Duration: {d:.2}ms", .{duration_ms}); - logNewLine(); - } else { + logVerboseSummary( + package_json_path, + output_path, + version, + date_str, + author_info, + git_info, + if (options.dry_run) "Previewed" else "Generated", + duration_ms, + ); + } else if (!options.dry_run) { logCompactSummary(output_path, version, duration_ms); } } @@ -455,9 +589,141 @@ test "writes compact generation summary" { try writeCompactSummary(&output.writer, "generated/version-info.ts", "0.0.0", 2); try std.testing.expectEqualStrings( - "✅ Generated generated/version-info.ts (v0.0.0, 2.00ms)", + "✓ Generated generated/version-info.ts • v0.0.0 • 2.00 ms", + output.writer.buffered(), + ); +} + +test "writes complete verbose generation summary" { + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + + try writeVerboseSummary( + &output.writer, + "package.json", + "generated/version-info.ts", + "1.2.3", + "2026-07-16T12:34:56.789Z", + .{ .name = "Name", .email = "mail@example.com", .url = "https://example.com" }, + .{ .branch = "main", .commit = "abc123" }, + "Generated", + 2, + ); + + try std.testing.expectEqualStrings( + \\› Package package.json • v1.2.3 + \\› Author Name • mail@example.com • https://example.com + \\› Timestamp 2026-07-16T12:34:56.789Z + \\› Git main • abc123 + \\✓ Generated generated/version-info.ts • v1.2.3 • 2.00 ms + \\ + , output.writer.buffered()); +} + +test "omits unavailable optional verbose author fields" { + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + + try writeVerboseSummary( + &output.writer, + "package.json", + "version-info.ts", + "1.2.3", + "2026-07-16T12:34:56.789Z", + .{ .name = "Name", .email = "", .url = "" }, + .{ .branch = "main", .commit = "abc123" }, + "Generated", + 2, + ); + + try std.testing.expect(std.mem.indexOf(u8, output.writer.buffered(), "› Author Name\n") != null); + try std.testing.expect(std.mem.indexOf(u8, output.writer.buffered(), "Name •") == null); +} + +test "describes missing verbose metadata" { + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + + try writeVerboseSummary( + &output.writer, + "package.json", + "version-info.ts", + "1.2.3", + "2026-07-16T12:34:56.789Z", + null, + null, + "Generated", + 2, + ); + + try std.testing.expect(std.mem.indexOf(u8, output.writer.buffered(), "› Author Not found\n") != null); + try std.testing.expect(std.mem.indexOf(u8, output.writer.buffered(), "› Git Not a repository\n") != null); +} + +test "writes preview label in verbose dry run summary" { + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + + try writeVerboseSummary( + &output.writer, + "package.json", + "version-info.ts", + "1.2.3", + "2026-07-16T12:34:56.789Z", + null, + null, + "Previewed", + 2, + ); + + try std.testing.expect(std.mem.indexOf( + u8, output.writer.buffered(), + "✓ Previewed version-info.ts • v1.2.3 • 2.00 ms\n", + ) != null); +} + +test "writes TypeScript to a file or dry run writer" { + const allocator = std.testing.allocator; + const io = std.testing.io; + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + var root_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; + const root_len = try tmp.dir.realPath(io, &root_buffer); + const root_path = root_buffer[0..root_len]; + const output_path = try std.fs.path.join(allocator, &.{ root_path, "generated/version-info.ts" }); + defer allocator.free(output_path); + + var preview: std.Io.Writer.Allocating = .init(allocator); + defer preview.deinit(); + try writeVersionInfoOutput( + io, + .{ .writer = &preview.writer }, + "1.2.3", + "2026-07-16T12:34:56.789Z", + null, + null, ); + + try std.testing.expectError(error.FileNotFound, tmp.dir.openDir(io, "generated", .{})); + try std.testing.expect(std.mem.startsWith(u8, preview.writer.buffered(), "/**\n")); + try std.testing.expect(std.mem.indexOf(u8, preview.writer.buffered(), "version: \"1.2.3\"") != null); + try std.testing.expect(std.mem.indexOf(u8, preview.writer.buffered(), "✓") == null); + + try writeVersionInfoOutput( + io, + .{ .file = output_path }, + "1.2.3", + "2026-07-16T12:34:56.789Z", + null, + null, + ); + + const file_content = try readFileAlloc(allocator, io, output_path, .limited(1024 * 1024)); + defer allocator.free(file_content); + try std.testing.expectEqualStrings(preview.writer.buffered(), file_content); } test "writes complete TypeScript file" { diff --git a/yarn.lock b/yarn.lock index 41de4a3..ba63078 100644 --- a/yarn.lock +++ b/yarn.lock @@ -415,9 +415,9 @@ brace-expansion@^1.1.7: concat-map "0.0.1" brace-expansion@^5.0.5: - version "5.0.7" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.7.tgz#1b0e46965b479dad65af737b4a02790a05498337" - integrity sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA== + version "5.0.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.8.tgz#135ad0d8d808eb18eb5e0ec9a21f3a0b92ef18cf" + integrity sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg== dependencies: balanced-match "^4.0.2" @@ -593,9 +593,9 @@ fast-deep-equal@^3.1.3: integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-uri@^3.0.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.3.tgz#f695a40f006aba505631573a0021ddb21194ad11" - integrity sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg== + version "3.1.4" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.4.tgz#3b3daf9ce68f41f956df0b505132c0cfce9ec7af" + integrity sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw== fs-extra@^11.3.6: version "11.3.6"