diff --git a/.github/workflows/web_generator.yaml b/.github/workflows/web_generator.yaml index 398cc5b2..4efb3e32 100644 --- a/.github/workflows/web_generator.yaml +++ b/.github/workflows/web_generator.yaml @@ -30,6 +30,9 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-node@v4 + with: + node-version: 22 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c with: sdk: ${{ matrix.sdk }} diff --git a/web_generator/lib/src/config.dart b/web_generator/lib/src/config.dart index 6f7092e7..45620d29 100644 --- a/web_generator/lib/src/config.dart +++ b/web_generator/lib/src/config.dart @@ -7,6 +7,8 @@ import 'package:path/path.dart' as p; import 'package:pub_semver/pub_semver.dart'; import 'package:yaml/yaml.dart'; +import 'util.dart'; + class FunctionConfig { /// The number of variable arguments final int? varArgs; @@ -154,11 +156,13 @@ class YamlConfig implements Config { throw TypeError(); } + final allFiles = + expandGlobs(inputFiles, extension: '.d.ts', cwd: p.dirname(filename)); + return YamlConfig._( filename: Uri.file(filename), - input: inputFiles - .map((file) => p.join(p.dirname(filename), file)) - .toList(), + input: + allFiles.map((file) => p.join(p.dirname(filename), file)).toList(), output: p.join(p.dirname(filename), (yaml['output'] ?? output) as String), name: yaml['name'] as String?, diff --git a/web_generator/lib/src/dart_main.dart b/web_generator/lib/src/dart_main.dart index 26f34d56..a6d65ba0 100644 --- a/web_generator/lib/src/dart_main.dart +++ b/web_generator/lib/src/dart_main.dart @@ -36,7 +36,7 @@ void main(List args) async { await generateIDLBindings( input: (argResult['input'] as List).isEmpty ? null - : argResult['input'] as Iterable, + : argResult['input'] as List, output: argResult['output'] as String, generateAll: argResult['generate-all'] as bool, languageVersion: Version.parse(languageVersionString), @@ -55,7 +55,8 @@ void main(List args) async { ); } else { config = Config( - input: argResult['input'] as List, + input: + expandGlobs(argResult['input'] as List, extension: '.d.ts'), output: argResult['output'] as String, languageVersion: Version.parse(languageVersionString), ); @@ -138,11 +139,12 @@ Future generateIDLBindings({ fs.writeFileSync('$output/$libraryPath'.toJS, contents); } } else { + final allInputFiles = expandGlobs(input.toList(), extension: '.idl'); // parse individual files ensureDirectoryExists(output); final bindings = await generateBindingsForFiles({ - for (final file in input) + for (final file in allInputFiles) file: (fs.readFileSync( file.toJS, JSReadFileOptions(encoding: 'utf-8'.toJS)) as JSString) diff --git a/web_generator/lib/src/js/filesystem_api.dart b/web_generator/lib/src/js/filesystem_api.dart index 09c503e8..b41b2b5e 100644 --- a/web_generator/lib/src/js/filesystem_api.dart +++ b/web_generator/lib/src/js/filesystem_api.dart @@ -40,4 +40,18 @@ extension type FileSystem._(JSObject _) implements JSObject { external JSAny readFileSync(JSString path, [JSReadFileOptions options]); external void writeFileSync(JSString path, JSString contents); + + external JSArray globSync(JSArray patterns, + [FSGlobSyncOptions? options]); +} + +extension type FSGlobSyncOptions._(JSObject _) implements JSObject { + external FSGlobSyncOptions({JSString cwd, FSGlobSyncExcludeFunc? exclude}); + + external JSString get cwd; + external JSArray? get exclude; +} + +extension type FSGlobSyncExcludeFunc(JSFunction _) implements JSFunction { + external bool call(String entry); } diff --git a/web_generator/lib/src/util.dart b/web_generator/lib/src/util.dart index b9d00210..bb0ad1bc 100644 --- a/web_generator/lib/src/util.dart +++ b/web_generator/lib/src/util.dart @@ -4,6 +4,8 @@ import 'dart:js_interop'; +import 'package:path/path.dart' as p; + import 'js/filesystem_api.dart'; // TODO(joshualitt): Let's find a better place for these. @@ -47,3 +49,17 @@ const packageRoot = 'package:web'; String capitalize(String s) => s.isEmpty ? '' : '${s[0].toUpperCase()}${s.substring(1)}'; + +List expandGlobs(List input, + {String? cwd, required String extension}) { + cwd ??= p.current; + final globSync = fs.globSync( + input.map((i) => i.toJS).toList().toJS, + FSGlobSyncOptions( + cwd: cwd.toJS, + )); + return globSync.toDart + .map((i) => i.toDart) + .where((f) => f.endsWith(extension)) + .toList(); +} diff --git a/web_generator/test/assets/config.yaml b/web_generator/test/assets/config.yaml index 0c430126..6ceb7dec 100644 --- a/web_generator/test/assets/config.yaml +++ b/web_generator/test/assets/config.yaml @@ -4,7 +4,7 @@ preamble: | // GENERATED FILE: DO NOT EDIT // // Created by `web_generator` -input: test.d.ts +input: 'test.d.ts' output: '../../.dart_tool/test_config.dart' include: - APP_NAME diff --git a/web_generator/test/config_test.dart b/web_generator/test/config_test.dart deleted file mode 100644 index d13b99af..00000000 --- a/web_generator/test/config_test.dart +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -@TestOn('vm') -library; - -import 'dart:io'; - -import 'package:path/path.dart' as p; -import 'package:test/test.dart'; -import 'package:web_generator/src/config.dart'; -import 'package:yaml/yaml.dart'; - -final configurationTests = { - 'basic': { - 'name': 'IDL Demo Gen', - 'single': true, - 'input': ['./intro.d.ts'], - 'valid': true - }, - 'multi_file': { - 'name': 'Nanoid and Lodash', - 'single': false, - 'input': ['./nanoid.d.ts', './lodash.d.ts'], - 'valid': true - }, - 'invalid': {'valid': false}, - 'invalid_output': {'valid': false} -}; - -void main() { - group('Configuration Test', () { - group('YAML Config Test', () { - final assetsDir = p.join('test', 'assets'); - - final assets = Directory(assetsDir); - - for (final file in assets.listSync().whereType().where((f) => - p.basenameWithoutExtension(f.path).endsWith('_config') && - p.extension(f.path) == '.yaml')) { - final fileName = - p.basenameWithoutExtension(file.path).replaceFirst('_config', ''); - final fileContents = file.readAsStringSync(); - final configTest = configurationTests[fileName]; - - if (configTest == null) continue; - - test(fileName, () { - final yaml = loadYamlDocument(fileContents); - - if (configTest.containsKey('valid') && configTest['valid'] == false) { - expect( - () => YamlConfig.fromYaml(yaml.contents as YamlMap, - filename: p.basename(file.path)), - throwsA(isA())); - } else { - // validate - final config = YamlConfig.fromYaml(yaml.contents as YamlMap, - filename: p.basename(file.path)); - - expect(config.name, equals(configTest['name'])); - expect(config.input, equals(configTest['input'])); - expect(config.singleFileOutput, equals(configTest['single'])); - } - }); - } - }); - }); -} diff --git a/web_generator/test/integration/idl_test.dart b/web_generator/test/integration/idl_test.dart index 7aa6b9bb..0208ff17 100644 --- a/web_generator/test/integration/idl_test.dart +++ b/web_generator/test/integration/idl_test.dart @@ -13,6 +13,7 @@ void main() { group('IDL Integration Test', () { final testGenFolder = p.join('test', 'integration', 'idl'); final inputDir = Directory(testGenFolder); + final outputDir = p.join('.dart_tool', 'idl'); setUpAll(() async { // set up npm @@ -20,6 +21,10 @@ void main() { // compile file await compileDartMain(dir: bindingsGenPath); + + if (!(await Directory(outputDir).exists())) { + await Directory(outputDir).create(recursive: true); + } }); for (final inputFile in inputDir @@ -29,8 +34,7 @@ void main() { final inputFileName = p.basenameWithoutExtension(inputFile.path); final inputName = inputFileName.replaceFirst('_input', ''); - final outputActualPath = - p.join('.dart_tool', 'idl', '${inputName}_actual.dart'); + final outputActualPath = p.join(outputDir, '${inputName}_actual.dart'); final outputExpectedPath = p.join(testGenFolder, '${inputName}_expected.dart');