Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 22 additions & 38 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "85.0.0"
adaptive_number:
dependency: transitive
description:
name: adaptive_number
sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
analyzer:
dependency: transitive
description:
Expand Down Expand Up @@ -117,10 +109,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.4.1"
checked_yaml:
dependency: transitive
description:
Expand Down Expand Up @@ -197,10 +189,10 @@ packages:
dependency: "direct main"
description:
name: dart_jsonwebtoken
sha256: c6ecb3bb991c459b91c5adf9e871113dcb32bbe8fe7ca2c92723f88ffc1e0b7a
sha256: cb79ed79baa02b4f59a597bf365873cbd83f9bb15273d63f7803802d21717c7d
url: "https://pub.dev"
source: hosted
version: "3.3.2"
version: "3.4.0"
dart_style:
dependency: transitive
description:
Expand All @@ -213,10 +205,10 @@ packages:
dependency: "direct main"
description:
name: dart_webrtc
sha256: "4ed7b9fa9924e5a81eb39271e2c2356739dd1039d60a13b86ba6c5f448625086"
sha256: "63a3ed6b958b5ba32d95ddba484c0dcf1cad4fd55eb551bb23c815b6f1e5d844"
url: "https://pub.dev"
source: hosted
version: "1.7.0"
version: "1.8.0"
dbus:
dependency: transitive
description:
Expand All @@ -241,14 +233,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.3"
ed25519_edwards:
dependency: transitive
description:
name: ed25519_edwards
sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
fake_async:
dependency: transitive
description:
Expand Down Expand Up @@ -300,10 +284,10 @@ packages:
dependency: "direct main"
description:
name: flutter_webrtc
sha256: "0f86b518e9349e71a136a96e0ea11294cad8a8531b2bc9ae99e69df332ac898a"
sha256: "8b220dc006c4891266735e516f7679bd08b7caaf7c36b1a93fb9357cec555f92"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.4.0"
frontend_server_client:
dependency: transitive
description:
Expand Down Expand Up @@ -332,10 +316,10 @@ packages:
dependency: transitive
description:
name: hooks
sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6"
sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388
url: "https://pub.dev"
source: hosted
version: "1.0.1"
version: "1.0.2"
http:
dependency: "direct main"
description:
Expand Down Expand Up @@ -436,10 +420,10 @@ packages:
dependency: transitive
description:
name: logger
sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3
sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c"
url: "https://pub.dev"
source: hosted
version: "2.6.2"
version: "2.7.0"
logging:
dependency: "direct main"
description:
Expand All @@ -452,18 +436,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.13.0"
meta:
dependency: "direct main"
description:
Expand Down Expand Up @@ -500,10 +484,10 @@ packages:
dependency: transitive
description:
name: native_toolchain_c
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572"
url: "https://pub.dev"
source: hosted
version: "0.17.4"
version: "0.17.6"
nm:
dependency: transitive
description:
Expand Down Expand Up @@ -753,10 +737,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.7"
version: "0.7.10"
timing:
dependency: transitive
description:
Expand Down Expand Up @@ -841,10 +825,10 @@ packages:
dependency: transitive
description:
name: webrtc_interface
sha256: ad0e5786b2acd3be72a3219ef1dde9e1cac071cf4604c685f11b61d63cdd6eb3
sha256: c6f100eac5057d9a817a60473126f9828c796d42884d498af4f339c97b21014f
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.5.1"
win32:
dependency: transitive
description:
Expand Down
224 changes: 224 additions & 0 deletions scripts/create_change.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#!/usr/bin/env dart
/*
* Copyright 2025 LiveKit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import 'dart:io';

/// Creates a new change entry in the .changes directory.
///
/// Usage:
/// dart scripts/create_change.dart
/// dart scripts/create_change.dart --level patch --type fixed --name my-fix --description "Fix something"
///
/// Interactive mode (no args) will prompt for each field with arrow-key selection.
/// CLI mode allows passing all fields as arguments.
///
/// Change file format:
/// level type="kind" "description"
///
/// Examples:
/// patch type="fixed" "Fix audio frame generation when publishing"
/// minor type="added" "Add support for custom audio processing"
/// major type="changed" "Breaking: Rename Room.connect() to Room.join()"

// ANSI escape codes
const _esc = '\x1B[';
const _reset = '\x1B[0m';
const _bold = '\x1B[1m';
const _dim = '\x1B[2m';
const _green = '\x1B[32m';
const _cyan = '\x1B[36m';
const _hideCursor = '\x1B[?25l';
const _showCursor = '\x1B[?25h';

const levels = ['patch', 'minor', 'major'];
const levelDescriptions = {
'patch': 'Bug fixes, no API changes',
'minor': 'New features, backwards compatible',
'major': 'Breaking changes',
};

const types = [
'added',
'changed',
'fixed',
'refactor',
'performance',
'security',
'deprecated',
'removed',
'docs',
];

/// Read a single raw keypress (handles arrow keys as 3-byte escape sequences).
List<int> readKey() {
stdin.echoMode = false;
stdin.lineMode = false;
try {
final first = stdin.readByteSync();
if (first == 27) {
// Escape sequence
final second = stdin.readByteSync();
if (second == 91) {
final third = stdin.readByteSync();
return [27, 91, third];
}
return [27, second];
}
return [first];
} finally {
stdin.lineMode = true;
stdin.echoMode = true;
}
}

/// Interactive picker with arrow keys. Returns selected index.
String pick(String label, List<String> options, {Map<String, String>? descriptions}) {
var selected = 0;

void render() {
stdout.write(_hideCursor);
for (var i = 0; i < options.length; i++) {
stdout.write('${_esc}2K'); // Clear entire line
if (i == selected) {
final desc = descriptions?[options[i]];
final suffix = desc != null ? ' $_dim- $desc$_reset' : '';
stdout.writeln(' $_cyan>$_reset $_bold${options[i]}$_reset$suffix');
} else {
stdout.writeln(' ${options[i]}');
}
}
}

stdout.writeln('$_bold$_green?$_reset $_bold$label$_reset ${_dim}(arrow keys, enter to confirm)$_reset');
render();

while (true) {
final key = readKey();

if (key.length == 3 && key[0] == 27 && key[1] == 91) {
if (key[2] == 65) {
// Up arrow
selected = (selected - 1) % options.length;
if (selected < 0) selected = options.length - 1;
} else if (key[2] == 66) {
// Down arrow
selected = (selected + 1) % options.length;
}
} else if (key.length == 1 && (key[0] == 10 || key[0] == 13)) {
// Enter
stdout.write(_showCursor);
// Clear the list and show selection
stdout.write('${_esc}${options.length}A'); // Move up
stdout.write('${_esc}0J'); // Clear from cursor down
stdout.writeln(' $_green>${_reset} $_bold${options[selected]}$_reset');
return options[selected];
} else if (key.length == 1 && (key[0] == 3 || key[0] == 27)) {
// Ctrl+C or Escape
stdout.write(_showCursor);
exit(0);
}

// Redraw
stdout.write('${_esc}${options.length}A'); // Move up to start of list
render();
}
}

/// Interactive text input with prompt.
String input(String label) {
stdout.write('$_bold$_green?$_reset $_bold$label$_reset ');
final value = stdin.readLineSync()?.trim() ?? '';
if (value.isEmpty) {
stderr.writeln('Input required.');
exit(1);
}
return value;
}

String slugify(String text) {
return text.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]+'), '-').replaceAll(RegExp(r'^-|-$'), '');
}

void main(List<String> args) {
final changesDir = Directory('.changes');
if (!changesDir.existsSync()) {
changesDir.createSync();
}

String? level;
String? type;
String? name;
String? description;

// Parse CLI args
for (var i = 0; i < args.length; i++) {
switch (args[i]) {
case '--level':
case '-l':
level = args[++i];
case '--type':
case '-t':
type = args[++i];
case '--name':
case '-n':
name = args[++i];
case '--description':
case '-d':
description = args[++i];
case '--help':
case '-h':
stdout.writeln('Usage: dart scripts/create_change.dart [options]');
stdout.writeln();
stdout.writeln('Options:');
stdout.writeln(' -l, --level Version bump level (${levels.join(', ')})');
stdout.writeln(' -t, --type Change type (${types.join(', ')})');
stdout.writeln(' -n, --name File name slug (e.g. fix-svc-dynacast)');
stdout.writeln(' -d, --description Change description');
stdout.writeln(' -h, --help Show this help');
exit(0);
}
}

// Interactive prompts for missing fields
level ??= pick('Level:', levels, descriptions: levelDescriptions);
type ??= pick('Type:', types);
name ??= slugify(input('Name (slug):'));
description ??= input('Description:');

// Validate
if (!levels.contains(level)) {
stderr.writeln('Invalid level: $level');
exit(1);
}
if (!types.contains(type)) {
stderr.writeln('Invalid type: $type');
exit(1);
}

final content = '$level type="$type" "$description"\n';
final file = File('.changes/$name');

if (file.existsSync()) {
stderr.writeln('Change file already exists: .changes/$name');
exit(1);
}

file.writeAsStringSync(content);
stdout.writeln();
stdout.writeln('$_green\u2713$_reset Created $_bold.changes/$name$_reset');
stdout.writeln(' ${_dim}${content.trim()}$_reset');
}
Loading
Loading