Documentation & licensing: - Add the EUPL-1.2 licence (LICENSE.md) and set the project licence; refresh the README (name origin wink, updated feature list, documentation index). - Add CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, CHANGELOG, AUTHORS, and THIRD_PARTY_NOTICES, plus docs/ (ARCHITECTURE, BUILD, USER_GUIDE, SHORTCUTS, LICENSE_COMPLIANCE) and .github/ (CI workflow, issue/PR templates). - Bring docs/FILE_FORMAT.md in line with current behaviour (code & chart slides, per-slide TLP comment, annotation .ink.json sidecar, chart data/ CSVs). Open-source compliance: - Add tool/check_licenses.dart and a `make licenses` target (wired into check-full and CI) that verifies every resolved dependency uses a recognised open-source licence. A scan of all 151 packages and bundled assets found only OSI-approved licences. Charts (Fase 1.1): - Replace the chart CSV textarea with an in-app editable data grid (editable series/labels/values, add/remove row & column, read-only when linked). - Centralize the linked-CSV directory name (`data/`) in a shared constant. Also normalize formatting repo-wide with `dart format` and fix one curly-braces lint, so `make check` and CI are green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
157 lines
4.6 KiB
Dart
157 lines
4.6 KiB
Dart
// Verifies that every resolved Dart/Flutter dependency (direct and transitive)
|
|
// uses a recognised open-source licence. Bundled JS/font assets are documented
|
|
// separately in THIRD_PARTY_NOTICES.md.
|
|
//
|
|
// dart run tool/check_licenses.dart (or: make licenses)
|
|
//
|
|
// Exits non-zero if any package has an unrecognised or non-open-source licence,
|
|
// so it can run in CI.
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
/// Licence families we accept (all OSI-approved / open source).
|
|
const allowed = <String>{
|
|
'MIT',
|
|
'BSD',
|
|
'BSD-2-Clause',
|
|
'BSD-3-Clause',
|
|
'Apache-2.0',
|
|
'MPL-2.0',
|
|
'ISC',
|
|
'Zlib',
|
|
'BSL-1.0',
|
|
'Unlicense',
|
|
'OFL-1.1',
|
|
'CC0-1.0',
|
|
'EUPL-1.2', // OciDeck itself
|
|
};
|
|
|
|
/// Packages that ship as part of the Flutter SDK without their own LICENSE file
|
|
/// (covered by the Flutter SDK licence, BSD-3-Clause).
|
|
const sdkNoLicenseOk = <String>{
|
|
'flutter',
|
|
'flutter_test',
|
|
'flutter_localizations',
|
|
'flutter_web_plugins',
|
|
'flutter_driver',
|
|
'integration_test',
|
|
'sky_engine',
|
|
};
|
|
|
|
String classify(String text) {
|
|
final t = text.toLowerCase();
|
|
if (t.contains('european union public licence') ||
|
|
t.contains('european union public license')) {
|
|
return 'EUPL-1.2';
|
|
}
|
|
// Note: the MPL-2.0 and EUPL texts *reference* the GNU licences in their
|
|
// compatibility clauses, so detect those reciprocal-but-distinct licences
|
|
// before the GNU keywords to avoid false positives.
|
|
if (t.contains('mozilla public license')) return 'MPL-2.0';
|
|
if (t.contains('apache license')) return 'Apache-2.0';
|
|
if (t.contains('gnu affero')) return 'AGPL';
|
|
if (t.contains('gnu lesser general public')) return 'LGPL';
|
|
if (t.contains('gnu general public')) return 'GPL';
|
|
if (t.contains('sil open font')) return 'OFL-1.1';
|
|
if (t.contains('isc license')) return 'ISC';
|
|
if (t.contains('boost software license')) return 'BSL-1.0';
|
|
if (t.contains('the unlicense')) return 'Unlicense';
|
|
if (t.contains('cc0')) return 'CC0-1.0';
|
|
if (t.contains('permission is hereby granted, free of charge')) return 'MIT';
|
|
if (t.contains('mit license')) return 'MIT';
|
|
if (t.contains('bsd 3-clause') ||
|
|
(t.contains('redistribution and use') &&
|
|
t.contains('neither the name'))) {
|
|
return 'BSD-3-Clause';
|
|
}
|
|
if (t.contains('redistribution and use in source and binary forms')) {
|
|
return 'BSD';
|
|
}
|
|
if (t.contains('bsd')) return 'BSD';
|
|
return 'UNKNOWN';
|
|
}
|
|
|
|
void main() {
|
|
final cfgFile = File('.dart_tool/package_config.json');
|
|
if (!cfgFile.existsSync()) {
|
|
stderr.writeln(
|
|
'No .dart_tool/package_config.json — run "flutter pub get" first.',
|
|
);
|
|
exit(2);
|
|
}
|
|
final base = cfgFile.absolute.parent.uri;
|
|
final cfg = jsonDecode(cfgFile.readAsStringSync()) as Map<String, dynamic>;
|
|
|
|
final rows = <(String, String)>[];
|
|
final problems = <String>[];
|
|
|
|
for (final pkg in (cfg['packages'] as List)) {
|
|
final name = pkg['name'] as String;
|
|
final rootUri = pkg['rootUri'] as String;
|
|
final resolved = rootUri.startsWith('file:')
|
|
? Uri.parse(rootUri.endsWith('/') ? rootUri : '$rootUri/')
|
|
: base.resolve(rootUri.endsWith('/') ? rootUri : '$rootUri/');
|
|
final root = Directory.fromUri(resolved);
|
|
|
|
File? lic;
|
|
for (final c in [
|
|
'LICENSE',
|
|
'LICENSE.md',
|
|
'LICENSE.txt',
|
|
'COPYING',
|
|
'license',
|
|
]) {
|
|
final f = File('${root.path}/$c');
|
|
if (f.existsSync()) {
|
|
lic = f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
String kind;
|
|
if (name == 'ocideck') {
|
|
kind = 'EUPL-1.2';
|
|
} else if (lic == null) {
|
|
kind = sdkNoLicenseOk.contains(name)
|
|
? 'BSD-3-Clause (Flutter SDK)'
|
|
: 'NO LICENSE FILE';
|
|
} else {
|
|
final txt = lic.readAsStringSync();
|
|
kind = classify(txt.length > 6000 ? txt.substring(0, 6000) : txt);
|
|
}
|
|
|
|
rows.add((name, kind));
|
|
final family = kind.split(' ').first;
|
|
if (!allowed.contains(family)) problems.add('$name → $kind');
|
|
}
|
|
|
|
rows.sort((a, b) => a.$1.compareTo(b.$1));
|
|
|
|
final counts = <String, int>{};
|
|
for (final r in rows) {
|
|
final k = r.$2.split(' ').first;
|
|
counts[k] = (counts[k] ?? 0) + 1;
|
|
}
|
|
stdout.writeln('Scanned ${rows.length} packages:');
|
|
final keys = counts.keys.toList()..sort((a, b) => counts[b]! - counts[a]!);
|
|
for (final k in keys) {
|
|
stdout.writeln(' ${counts[k]!.toString().padLeft(3)} $k');
|
|
}
|
|
|
|
if (problems.isEmpty) {
|
|
stdout.writeln(
|
|
'\nOK — all dependencies use recognised open-source licences.',
|
|
);
|
|
stdout.writeln(
|
|
'Bundled JS/font assets are listed in THIRD_PARTY_NOTICES.md.',
|
|
);
|
|
exit(0);
|
|
}
|
|
|
|
stderr.writeln('\nPROBLEM — ${problems.length} package(s) need review:');
|
|
for (final p in problems) {
|
|
stderr.writeln(' $p');
|
|
}
|
|
exit(1);
|
|
}
|