pull/2701/head
Eric Windmill 1 month ago
parent b023fdabcb
commit b23c073be5

@ -1,32 +0,0 @@
# Changelog
The purpose of this changelog is to track the freshness of samples and which
samples reflect *current best practices*. It describes **human-made, significant
** changes made to the repository or samples in the repository.
While all samples in this repository build and run, some of them were written
long ago, and no longer reflect what we want developers to learn. For example,
samples should have been refactored when Dart 3 released to include patterns and
records, where appropriate.
* **DO include:**
* The addition of new samples.
* The removal of existing samples.
* Considerable refactoring of any given sample.
* **DO NOT include:**
* Simple changes that reflect minor version bumps in Flutter. For example,
in a recent Flutter update, `Color.red` became `Color.r`.
* Dependency updates.
* Bug fixes.
* Any changes made to simply 'keep the lights on'.
# Log
| DATE (YYYY-MM-DD) | Sample(s) | author | Changes |
|-------------------|-------------------|--------------|-----------------------------------------------|
| NEXT GOES HERE | | | |
| | | | |
| 2024-12-04 | N/A - repo change | ericwindmill | Added changelog |
| 2024-11-27 | fake_sample | ericwindmill | Refactored fake_sample to use Dart 3 features |
| 2020-04-17 | fake_sample | ericwindmill | Created fake_sample |

@ -2,7 +2,7 @@ include: package:flutter_lints/flutter.yaml
formatter: formatter:
trailing_commas: preserve trailing_commas: preserve
page_width: 75 page_width: 79
analyzer: analyzer:
language: language:

@ -1,31 +0,0 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:analysis_defaults/flutter.yaml
# Uncomment the following section to specify additional rules.
# linter:
# rules:
# - camel_case_types
# analyzer:
# exclude:
# - path/to/excluded/files/**
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options

@ -47,18 +47,3 @@ You are an AI assistant tasked with keeping a repository of Flutter projects up-
Please proceed with this task. Please proceed with this task.
Next:
- Run the CLI command `flutter channel beta` to ensure the Flutter SDK on my machine is using the latest beta
- Run the CI running the bash file @/tool/flutter_ci_script_stable.sh
- When there's a non-0 response, attempt to fix the issue.
- remove samples and replace with a README
- Update script to look into sub-directories. Because some projects are themselves mono repos.
- Make sure this is in every pubspec yaml
analysis_defaults:
path: ../analysis_defaults
- shouldn't run flutter-test unless theirs a test dir

@ -8,72 +8,49 @@ import 'package:yaml_edit/yaml_edit.dart';
const String logDir = 'logs'; const String logDir = 'logs';
late File _logFile;
void main(List<String> arguments) async { void main(List<String> arguments) async {
final runner = ReleaseScriptRunner();
await runner.run(arguments);
}
class ReleaseScriptRunner {
late final File _logFile;
bool _isDryRun = false;
Future<void> run(List<String> arguments) async {
final parser = ArgParser() final parser = ArgParser()
..addFlag('dry-run', ..addFlag('dry-run',
negatable: false, negatable: false,
help: help:
'Prints the commands that would be executed, but does not run them.'); 'Prints the commands that would be executed, but does not run them.');
final argResults = parser.parse(arguments); final argResults = parser.parse(arguments);
final isDryRun = argResults['dry-run'] as bool; _isDryRun = argResults['dry-run'] as bool;
setupLogging(); _setupLogging();
log( log(
styleBold styleBold
.wrap('Flutter Monorepo Update Script Started: ${DateTime.now()}')!, .wrap('Flutter Monorepo Update Script Started: ${DateTime.now()}'),
stdout); stdout);
log(blue.wrap('Log file: ${_logFile.path}')!, stdout); log(blue.wrap('Log file: ${_logFile.path}')!, stdout);
if (isDryRun) { if (_isDryRun) {
log(yellow.wrap('--- DRY RUN MODE ---')!, stdout); log(yellow.wrap('--- DRY RUN MODE ---')!, stdout);
} }
if (!await isFlutterInstalled()) { if (!await _isFlutterInstalled()) {
log(red.wrap('Flutter is not installed or not in PATH')!, stderr); log(red.wrap('Flutter is not installed or not in PATH')!, stderr);
exit(1); exit(1);
} }
// log(styleBold.wrap('\n=========================================')!, stdout); final dartVersion = await _getDartVersion();
// log(styleBold.wrap('Upgrading Flutter SDK')!, stdout);
// log(styleBold.wrap('=========================================')!, stdout);
// final (didFlutterUpgrade, _) =
// await runCommand('flutter', ['upgrade'], isDryRun: isDryRun);
// if (!didFlutterUpgrade) {
// log(red.wrap('Failed to upgrade Flutter SDK')!, stderr);
// exit(1);
// }
// log(styleBold.wrap('\n=========================================')!, stdout);
// log(styleBold.wrap('Resolving workspace dependencies')!, stdout);
// log(styleBold.wrap('=========================================')!, stdout);
// final (didPubUpgrade, _) =
// await runCommand('flutter', ['pub', 'upgrade'], isDryRun: isDryRun);
// if (!didPubUpgrade) {
// log(red.wrap('Failed to upgrade workspace dependencies'), stderr);
// exit(1);
// }
// final (didPubGet, _) =
// await runCommand('flutter', ['pub', 'get'], isDryRun: isDryRun);
// if (!didPubGet) {
// log(red.wrap('Failed to get workspace dependencies'), stderr);
// exit(1);
// }
final dartVersion = await getDartVersion();
if (dartVersion == null) { if (dartVersion == null) {
log(red.wrap('Failed to get Dart SDK version.'), stderr); log(red.wrap('Failed to get Dart SDK version.'), stderr);
exit(1); exit(1);
} }
log(blue.wrap('Using Dart SDK version: $dartVersion'), stdout); log(blue.wrap('Using Dart SDK version: $dartVersion'), stdout);
final packages = await getWorkspacePackages(); final packages = await _getWorkspacePackages();
if (packages.isEmpty) { if (packages.isEmpty) {
log(yellow.wrap('No packages found in the root pubspec.yaml workspace.'), log(yellow.wrap('No packages found in the root pubspec.yaml workspace.'),
stdout); stdout);
@ -87,13 +64,13 @@ void main(List<String> arguments) async {
final failedProjects = <String>[]; final failedProjects = <String>[];
for (final packagePath in packages) { for (final packagePath in packages) {
final success = await processProject(packagePath, dartVersion, isDryRun); final success = await _processProject(packagePath, dartVersion);
if (!success) { if (!success) {
failedProjects.add(packagePath); failedProjects.add(packagePath);
} }
} }
printSummary(packages.length, failedProjects); _printSummary(packages.length, failedProjects);
log( log(
styleBold.wrap( styleBold.wrap(
@ -105,7 +82,7 @@ void main(List<String> arguments) async {
} }
} }
void setupLogging() { void _setupLogging() {
final logsDir = Directory(logDir); final logsDir = Directory(logDir);
if (!logsDir.existsSync()) { if (!logsDir.existsSync()) {
logsDir.createSync(recursive: true); logsDir.createSync(recursive: true);
@ -122,12 +99,12 @@ void log(String? message, IOSink sink) {
void logToFile(String? message) { void logToFile(String? message) {
if (message == null) return; if (message == null) return;
_logFile.writeAsStringSync( _logFile.writeAsStringSync(
'\n${stripAnsiCodes(message)}', '\n${_stripAnsiCodes(message)}',
mode: FileMode.append, mode: FileMode.append,
); );
} }
Future<bool> isFlutterInstalled() async { Future<bool> _isFlutterInstalled() async {
try { try {
final result = await Process.run('flutter', ['--version']); final result = await Process.run('flutter', ['--version']);
return result.exitCode == 0; return result.exitCode == 0;
@ -136,7 +113,7 @@ Future<bool> isFlutterInstalled() async {
} }
} }
Future<String?> getDartVersion() async { Future<String?> _getDartVersion() async {
try { try {
final result = await Process.run('flutter', ['--version', '--machine']); final result = await Process.run('flutter', ['--version', '--machine']);
if (result.exitCode == 0) { if (result.exitCode == 0) {
@ -153,7 +130,7 @@ Future<String?> getDartVersion() async {
return null; return null;
} }
Future<List<String>> getWorkspacePackages() async { Future<List<String>> _getWorkspacePackages() async {
final rootPubspec = File('pubspec.yaml'); final rootPubspec = File('pubspec.yaml');
if (!rootPubspec.existsSync()) { if (!rootPubspec.existsSync()) {
log(red.wrap('Root pubspec.yaml not found!'), stderr); log(red.wrap('Root pubspec.yaml not found!'), stderr);
@ -173,8 +150,7 @@ Future<List<String>> getWorkspacePackages() async {
return []; return [];
} }
Future<bool> processProject( Future<bool> _processProject(String projectPath, String dartVersion) async {
String projectPath, String dartVersion, bool isDryRun) async {
final projectName = p.basename(projectPath); final projectName = p.basename(projectPath);
final projectDir = Directory(projectPath); final projectDir = Directory(projectPath);
final issues = []; final issues = [];
@ -184,14 +160,13 @@ Future<bool> processProject(
return false; return false;
} }
// For stdout
log(styleBold.wrap('\n========================================='), stdout); log(styleBold.wrap('\n========================================='), stdout);
log(styleBold.wrap('Processing project: $projectName'), stdout); log(styleBold.wrap('Processing project: $projectName'), stdout);
log(styleBold.wrap('=========================================')!, stdout); log(styleBold.wrap('=========================================')!, stdout);
log(blue.wrap('Updating SDK constraints to use Dart $dartVersion'), stdout); log(blue.wrap('Updating SDK constraints to use Dart $dartVersion'), stdout);
if (!isDryRun) { if (!_isDryRun) {
if (!await updateSdkConstraints(projectPath, dartVersion)) { if (!await _updateSdkConstraints(projectPath, dartVersion)) {
log(red.wrap('Failed to update SDK constraints for $projectName'), log(red.wrap('Failed to update SDK constraints for $projectName'),
stderr); stderr);
return false; return false;
@ -206,18 +181,17 @@ Future<bool> processProject(
final testDir = Directory(p.join(projectPath, 'test')); final testDir = Directory(p.join(projectPath, 'test'));
if (projectName != 'material_3_demo' && testDir.existsSync()) { if (projectName != 'material_3_demo' && testDir.existsSync()) {
commands commands.add(
.add(Command('flutter test', 'Running tests...', 'flutter', ['test'])); Command('flutter test', 'Running tests...', 'flutter', ['test']));
} }
for (final command in commands) { for (final command in commands) {
log(blue.wrap(command.description), stdout); log(blue.wrap(command.description), stdout);
final (didPass, output) = await runCommand( final (didPass, output) = await _runCommand(
command.executable, command.executable,
command.arguments, command.arguments,
workingDirectory: projectPath, workingDirectory: projectPath,
isDryRun: isDryRun,
); );
if (!didPass) { if (!didPass) {
@ -245,7 +219,7 @@ Future<bool> processProject(
if (issues.isNotEmpty) { if (issues.isNotEmpty) {
logToFile('- Issues found in $projectName'); logToFile('- Issues found in $projectName');
for (final issue in issues) { for (final issue in issues) {
if (isOnlyWhitespace(issue)) continue; if (_isOnlyWhitespace(issue)) continue;
logToFile('-- $issue'); logToFile('-- $issue');
} }
} }
@ -254,7 +228,7 @@ Future<bool> processProject(
return true; return true;
} }
Future<bool> updateSdkConstraints( Future<bool> _updateSdkConstraints(
String projectDir, String versionString) async { String projectDir, String versionString) async {
final pubspecFile = File(p.join(projectDir, 'pubspec.yaml')); final pubspecFile = File(p.join(projectDir, 'pubspec.yaml'));
if (!pubspecFile.existsSync()) { if (!pubspecFile.existsSync()) {
@ -276,15 +250,16 @@ Future<bool> updateSdkConstraints(
stdout); stdout);
return true; return true;
} catch (e) { } catch (e) {
log(red.wrap('Failed to update SDK constraint in $projectDir: $e'), stderr); log(red.wrap('Failed to update SDK constraint in $projectDir: $e'),
stderr);
return false; return false;
} }
} }
Future<(bool, String)> runCommand(String executable, List<String> arguments, Future<(bool, String)> _runCommand(String executable, List<String> arguments,
{String? workingDirectory, bool isDryRun = false}) async { {String? workingDirectory}) async {
final commandString = '$executable ${arguments.join(' ')}'; final commandString = '$executable ${arguments.join(' ')}';
if (isDryRun) { if (_isDryRun) {
log( log(
yellow.wrap( yellow.wrap(
' [DRY RUN] Would execute: `$commandString` in `${workingDirectory ?? '.'}`'), ' [DRY RUN] Would execute: `$commandString` in `${workingDirectory ?? '.'}`'),
@ -300,13 +275,13 @@ Future<(bool, String)> runCommand(String executable, List<String> arguments,
final stdoutFuture = final stdoutFuture =
process.stdout.transform(SystemEncoding().decoder).forEach((line) { process.stdout.transform(SystemEncoding().decoder).forEach((line) {
log(line, stdout); log(line, stdout);
if (!isOnlyWhitespace(line)) output.writeln('${line.trim().padLeft(2)}'); if (!_isOnlyWhitespace(line)) output.writeln('${line.trim().padLeft(2)}');
}); });
final stderrFuture = final stderrFuture =
process.stderr.transform(SystemEncoding().decoder).forEach((line) { process.stderr.transform(SystemEncoding().decoder).forEach((line) {
log(red.wrap(line), stderr); log(red.wrap(line), stderr);
if (!isOnlyWhitespace(line)) output.writeln('${line.trim().padLeft(2)}'); if (!_isOnlyWhitespace(line)) output.writeln('${line.trim().padLeft(2)}');
}); });
await Future.wait([stdoutFuture, stderrFuture]); await Future.wait([stdoutFuture, stderrFuture]);
@ -314,7 +289,7 @@ Future<(bool, String)> runCommand(String executable, List<String> arguments,
return (await process.exitCode == 0, output.toString()); return (await process.exitCode == 0, output.toString());
} }
void printSummary(int total, List<String> failed) { void _printSummary(int total, List<String> failed) {
log(styleBold.wrap('\n=========================================')!, stdout); log(styleBold.wrap('\n=========================================')!, stdout);
log(styleBold.wrap('Update Summary')!, stdout); log(styleBold.wrap('Update Summary')!, stdout);
log(styleBold.wrap('=========================================')!, stdout); log(styleBold.wrap('=========================================')!, stdout);
@ -327,6 +302,16 @@ void printSummary(int total, List<String> failed) {
} }
} }
String _stripAnsiCodes(String text) {
final ansiRegex = RegExp(r'\x1B\[[0-?]*[ -/]*[@-~]');
return text.replaceAll(ansiRegex, '');
}
bool _isOnlyWhitespace(String text) {
return text.trim().isEmpty;
}
}
class Command { class Command {
final String displayName; final String displayName;
final String description; final String description;
@ -335,12 +320,3 @@ class Command {
Command(this.displayName, this.description, this.executable, this.arguments); Command(this.displayName, this.description, this.executable, this.arguments);
} }
String stripAnsiCodes(String text) {
final ansiRegex = RegExp(r'\x1B\[[0-?]*[ -/]*[@-~]');
return text.replaceAll(ansiRegex, '');
}
bool isOnlyWhitespace(String text) {
return text.trim().isEmpty;
}

@ -1,267 +0,0 @@
#!/bin/bash
set -e
# Configuration
LOGS_DIR="logs"
LOG_FILE="$LOGS_DIR/flutter_update_$(date +%Y-%m-%d_%H-%M-%S).log"
TEMP_LOG="/tmp/flutter_temp_$$.log"
# Create logs directory if it doesn't exist
mkdir -p "$LOGS_DIR"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging function
log() {
echo -e "$1" | tee -a "$LOG_FILE"
}
log_error() {
echo -e "${RED}ERROR: $1${NC}" | tee -a "$LOG_FILE"
}
log_warning() {
echo -e "${YELLOW}WARNING: $1${NC}" | tee -a "$LOG_FILE"
}
log_info() {
echo -e "${BLUE}INFO: $1${NC}" | tee -a "$LOG_FILE"
}
# Function to check if directory is a Flutter project
is_flutter_project() {
local dir="$1"
[[ -f "$dir/pubspec.yaml" ]] && grep -q "flutter:" "$dir/pubspec.yaml"
}
# Function to get current Flutter version
get_flutter_version() {
flutter --version --machine | grep '"frameworkVersion"' | cut -d'"' -f4
}
# Function to update SDK constraints in pubspec.yaml
update_sdk_constraints() {
local project_dir="$1"
local current_version="$2"
local pubspec="pubspec.yaml"
if [[ ! -f "$pubspec" ]]; then
log_error "pubspec.yaml not found in $project_dir"
return 1
fi
# Extract major and minor version numbers
local major=$(echo "$current_version" | cut -d'.' -f1)
local minor=$(echo "$current_version" | cut -d'.' -f2)
local next_major=$((major + 1))
# Create SDK constraint string
local sdk_constraint=">=$current_version <$next_major.0.0"
# Update only the Dart SDK constraint under environment: section
# This specifically targets the sdk: line under environment: and not flutter dependencies
if grep -A5 "^environment:" "$pubspec" | grep -q "sdk:"; then
# Use perl to update only the environment sdk constraint
perl -i -pe '
BEGIN { $in_env = 0; }
if (/^environment:/) { $in_env = 1; }
elsif (/^\w/ && !/^\s/) { $in_env = 0; }
if ($in_env && /(\s+sdk:\s*)['\''"]?[^'\''"]*['\''"]?/) {
s/(\s+sdk:\s*)['\''"]?[^'\''"]*['\''"]?/$1'\'''"$sdk_constraint"'\''/;
}
' "$pubspec"
log_info "Updated Dart SDK constraint in $project_dir to: $sdk_constraint"
else
log_warning "No Dart SDK constraint found under environment: in $project_dir/pubspec.yaml"
return 1
fi
}
# Function to process a single Flutter project
process_project() {
local project_dir="$1"
local project_name=$(basename "$project_dir")
log ""
log "========================================="
log "Processing project: $project_name"
log "========================================="
cd "$project_dir"
# Get current Flutter version for SDK constraints
local flutter_version
flutter_version=$(get_flutter_version)
if [[ $? -ne 0 ]]; then
log_error "Failed to get Flutter version for $project_name"
return 1
fi
# Update SDK constraints
log_info "Updating SDK constraints to use Flutter $flutter_version"
if ! update_sdk_constraints "$project_dir" "$flutter_version"; then
log_error "Failed to update SDK constraints for $project_name"
return 1
fi
# Update dependencies
log_info "Updating dependencies..."
if ! flutter pub upgrade > "$TEMP_LOG" 2>&1; then
log_error "Failed to upgrade dependencies for $project_name"
cat "$TEMP_LOG" >> "$LOG_FILE"
return 1
fi
# Run pub get
log_info "Running pub get..."
if ! flutter pub get > "$TEMP_LOG" 2>&1; then
log_error "Failed to run pub get for $project_name"
cat "$TEMP_LOG" >> "$LOG_FILE"
return 1
fi
# Run dart analyze and capture output
log_info "Running dart analyze..."
if ! dart analyze > "$TEMP_LOG" 2>&1; then
local exit_code=$?
log_error "Dart analyze found issues in $project_name (exit code: $exit_code)"
# Filter and log only errors, warnings, and infos (not success messages)
if grep -E "(error|warning|info):" "$TEMP_LOG" > /dev/null; then
log "Analyze results for $project_name:"
grep -E "(error|warning|info):" "$TEMP_LOG" >> "$LOG_FILE"
fi
# Check if there are fatal errors (errors that would prevent compilation)
if grep -E "^error:" "$TEMP_LOG" > /dev/null; then
log_error "Fatal errors found in $project_name, skipping further processing"
return 1
fi
else
# Even on success, check for warnings and infos
if grep -E "(warning|info):" "$TEMP_LOG" > /dev/null; then
log "Analyze results for $project_name:"
grep -E "(warning|info):" "$TEMP_LOG" >> "$LOG_FILE"
fi
fi
# Run dart format
log_info "Running dart format..."
if ! dart format . > "$TEMP_LOG" 2>&1; then
log_error "Failed to format code for $project_name"
cat "$TEMP_LOG" >> "$LOG_FILE"
# Continue anyway, formatting errors are not fatal
fi
# Run tests
log_info "Running tests..."
if ! flutter test > "$TEMP_LOG" 2>&1; then
local exit_code=$?
log_error "Tests failed for $project_name (exit code: $exit_code)"
# Log test failures
if grep -E "(FAILED|ERROR|Exception)" "$TEMP_LOG" > /dev/null; then
log "Test failures for $project_name:"
grep -E "(FAILED|ERROR|Exception)" "$TEMP_LOG" >> "$LOG_FILE"
fi
return 1
fi
log_info "Successfully processed $project_name"
return 0
}
# Main execution
main() {
local original_dir="$PWD"
log "Flutter Monorepo Update Script Started: $(date)"
log "Log file: $LOG_FILE"
# Check if Flutter is installed
if ! command -v flutter &> /dev/null; then
log_error "Flutter is not installed or not in PATH"
exit 1
fi
# Upgrade Flutter SDK
log ""
log "========================================="
log "Upgrading Flutter SDK"
log "========================================="
if ! flutter upgrade > "$TEMP_LOG" 2>&1; then
log_error "Failed to upgrade Flutter SDK"
cat "$TEMP_LOG" >> "$LOG_FILE"
exit 1
fi
local flutter_version
flutter_version=$(get_flutter_version)
log_info "Flutter upgraded to version: $flutter_version"
# Find all Flutter projects in the current directory
local projects=()
for dir in */; do
if [[ -d "$dir" ]] && is_flutter_project "$dir"; then
projects+=("$dir")
fi
done
if [[ ${#projects[@]} -eq 0 ]]; then
log_warning "No Flutter projects found in the current directory"
exit 0
fi
log_info "Found ${#projects[@]} Flutter project(s): ${projects[*]}"
# Process each project
local success_count=0
local failure_count=0
local failed_projects=()
for project in "${projects[@]}"; do
if process_project "$project"; then
((success_count++))
else
((failure_count++))
failed_projects+=("$(basename "$project")")
fi
# Return to root directory
cd "$original_dir"
done
# Summary
log ""
log "========================================="
log "Update Summary"
log "========================================="
log_info "Total projects processed: ${#projects[@]}"
log_info "Successful: $success_count"
if [[ $failure_count -gt 0 ]]; then
log_error "Failed: $failure_count"
log_error "Failed projects: ${failed_projects[*]}"
fi
log ""
log "Flutter Monorepo Update Script Completed: $(date)"
# Cleanup
rm -f "$TEMP_LOG"
# Exit with error code if any projects failed
if [[ $failure_count -gt 0 ]]; then
exit 1
fi
}
# Run main function
main "$@"

@ -1,8 +1,25 @@
You are a Dart developer that maintains this monorepo of Flutter sample projects. You are a Dart developer that maintains this monorepo of sample projects. These are the tasks you need to do when necessary:
- Update for release of a new version of flutter/dart
- Read the release blog posts at (USER WILL PROVIDE)
- In the repository, checkout the "beta" git branch and make sure its up to date
- Make sure the local flutter SDK is pointing to the beta channel
- Run 'tool/release.dart', which will create a log file.
- Read the log file and address any issues it describes.
- Use the gh cli to make a PR
- Delete samples and replace with a README
- Remove all files within a sample project directory.
- Create a README and explain that the project is deprecated.
- Look at issues on (USER WILL PROVIDE LINK TO GITHUB ISSUES)
- If there are any open issues that describe deleting this sample, link to it in the README
- Mark stale code
- Create a 'stale_code.md' file if it doesn't exist.
- For each sample, look at the git logs and find the most recent commit to a .dart file in sample made by a real person.
- If that commit was more than one year ago, log it by appending to the stale code log 'sample name, git user name, date of last commit'
-
- Read https://dart.dev/tools/pub/workspaces
- Create a gloabl pubspec.yaml and populate it
- Determine 5 packages in this repository that could easily be migrated to workspaces.
- Migrate those 5 packages.

Loading…
Cancel
Save