feat(shorebird_ci): codecov token plumbing + explicit fatal-warnings (#3772)
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
<!-- cspell:words toplevel -->
|
||||
|
||||
# 0.2.2
|
||||
|
||||
- Generated Dart workflows now emit `dart analyze --fatal-warnings .` explicitly. The SDK already defaults `--fatal-warnings` to on, so this is behavior-preserving today, but the explicit form documents intent and survives any future flip in the SDK default. Flutter workflows are unchanged: `flutter analyze` defaults `--fatal-warnings` to on with no CLI flag.
|
||||
- New `--codecov-token-secret <NAME>` option on `generate`. When set, the static orchestrator emits `secrets: inherit` on each reusable workflow call, and the codecov-action step in the reusable workflows receives `token: ${{ secrets.<NAME> }}`. The dynamic style threads the same `token:` into its single codecov-action step. When unset (the default), no token plumbing is emitted, matching prior behavior. Private repos that need a token to upload coverage should pass `--codecov-token-secret CODECOV_TOKEN` (or the secret name of their choice).
|
||||
|
||||
# 0.2.1
|
||||
|
||||
- Static main workflow YAML map keys default to each package's `name:` and fall back to `<parent_dir>_<package_name>` only when two or more packages share a name. Avoids prefixing in the common case while still handling duplicate-`name:` repos. The `package_name:` input passed to the reusable workflow keeps the actual package name for codecov flag display.
|
||||
|
||||
@@ -82,6 +82,25 @@ leaves the static pins in the template as-is. Use it in offline
|
||||
environments. Once you push, Dependabot picks up bumps on its weekly
|
||||
schedule.
|
||||
|
||||
### `--codecov-token-secret <NAME>`
|
||||
|
||||
Pass the name of the GitHub Actions secret holding your Codecov
|
||||
upload token. When set:
|
||||
|
||||
- `--style static`: the orchestrator emits `secrets: inherit` on each
|
||||
reusable workflow call, and the codecov-action step in the reusable
|
||||
workflows receives `token: ${{ secrets.<NAME> }}`.
|
||||
- `--style dynamic` (default): the single codecov-action step receives
|
||||
the same `token:` line.
|
||||
|
||||
When unset (the default), no token plumbing is emitted. Whether you
|
||||
need a token is a Codecov question — refer to their docs for whether
|
||||
your repo requires one to upload.
|
||||
|
||||
```bash
|
||||
shorebird_ci generate --codecov-token-secret CODECOV_TOKEN
|
||||
```
|
||||
|
||||
### `--style static` (advanced)
|
||||
|
||||
`generate --style static` emits a pre-computed dorny `filters:` block,
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
dart pub get --no-example -C $sub
|
||||
done
|
||||
- run: dart format --set-exit-if-changed .
|
||||
- run: dart analyze .
|
||||
- run: dart analyze --fatal-warnings .
|
||||
- name: Bloc Lint
|
||||
if: matrix.has_bloc_lint == true
|
||||
run: bloc lint .
|
||||
|
||||
@@ -46,6 +46,18 @@ class GenerateCommand extends Command<int> with RepoRootOption {
|
||||
'auto-bumps action versions to current latest. Pass this flag '
|
||||
'in offline environments.',
|
||||
negatable: false,
|
||||
)
|
||||
..addOption(
|
||||
'codecov-token-secret',
|
||||
help: r'''
|
||||
Name of the GitHub Actions secret holding the Codecov upload token.
|
||||
When set, the codecov-action step receives
|
||||
`token: ${{ secrets.<NAME> }}` in both --style dynamic (default) and
|
||||
--style static output. In --style static, the generated orchestrator
|
||||
additionally emits `secrets: inherit` on each reusable workflow call
|
||||
so the secret is reachable inside the reusable workflow. When unset
|
||||
(the default), no token plumbing is emitted. Refer to Codecov's docs
|
||||
for whether your repo requires a token to upload.''',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,6 +74,7 @@ class GenerateCommand extends Command<int> with RepoRootOption {
|
||||
final outputPath = argResults!['output'] as String;
|
||||
final dryRun = argResults!.flag('dry-run');
|
||||
final style = argResults!['style'] as String;
|
||||
final codecovTokenSecret = argResults!['codecov-token-secret'] as String?;
|
||||
|
||||
final analyzer = RepositoryAnalyzer();
|
||||
final repoDir = Directory(repoRoot);
|
||||
@@ -81,8 +94,17 @@ class GenerateCommand extends Command<int> with RepoRootOption {
|
||||
// Action versions are emitted as the hardcoded defaults below;
|
||||
// run `shorebird_ci update_actions` to bump them.
|
||||
final files = style == 'static'
|
||||
? _buildStaticFiles(repository, outputPath: outputPath)
|
||||
: {outputPath: _buildDynamicYaml(repository)};
|
||||
? _buildStaticFiles(
|
||||
repository,
|
||||
outputPath: outputPath,
|
||||
codecovTokenSecret: codecovTokenSecret,
|
||||
)
|
||||
: {
|
||||
outputPath: _buildDynamicYaml(
|
||||
repository,
|
||||
codecovTokenSecret: codecovTokenSecret,
|
||||
),
|
||||
};
|
||||
|
||||
if (dryRun) {
|
||||
final sortedKeys = files.keys.toList()..sort();
|
||||
@@ -174,6 +196,7 @@ updates:
|
||||
Map<String, String> _buildStaticFiles(
|
||||
RepositoryDescription repository, {
|
||||
required String outputPath,
|
||||
required String? codecovTokenSecret,
|
||||
}) {
|
||||
final packages = repository.packages.toList()
|
||||
..sort(
|
||||
@@ -192,15 +215,22 @@ updates:
|
||||
outputPath: _buildStaticMainYaml(
|
||||
repository: repository,
|
||||
packages: packages,
|
||||
codecovTokenSecret: codecovTokenSecret,
|
||||
),
|
||||
};
|
||||
if (hasDart) {
|
||||
files['.github/workflows/_shorebird_ci_dart.yaml'] =
|
||||
_buildDartReusableWorkflow(hasCodecov: repository.hasCodecov);
|
||||
_buildDartReusableWorkflow(
|
||||
hasCodecov: repository.hasCodecov,
|
||||
codecovTokenSecret: codecovTokenSecret,
|
||||
);
|
||||
}
|
||||
if (hasFlutter) {
|
||||
files['.github/workflows/_shorebird_ci_flutter.yaml'] =
|
||||
_buildFlutterReusableWorkflow(hasCodecov: repository.hasCodecov);
|
||||
_buildFlutterReusableWorkflow(
|
||||
hasCodecov: repository.hasCodecov,
|
||||
codecovTokenSecret: codecovTokenSecret,
|
||||
);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
@@ -208,7 +238,12 @@ updates:
|
||||
String _buildStaticMainYaml({
|
||||
required RepositoryDescription repository,
|
||||
required List<PackageDescription> packages,
|
||||
required String? codecovTokenSecret,
|
||||
}) {
|
||||
final emitSecretsInherit =
|
||||
codecovTokenSecret != null &&
|
||||
codecovTokenSecret.isNotEmpty &&
|
||||
repository.hasCodecov;
|
||||
final resolver = DependencyResolver(repository.root.path);
|
||||
final slugs = computePackageSlugs(
|
||||
packages: packages,
|
||||
@@ -317,6 +352,10 @@ jobs:
|
||||
);
|
||||
}
|
||||
|
||||
if (emitSecretsInherit) {
|
||||
buffer.writeln(' secrets: inherit');
|
||||
}
|
||||
|
||||
buffer.writeln();
|
||||
}
|
||||
|
||||
@@ -327,22 +366,25 @@ jobs:
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
String _buildDartReusableWorkflow({required bool hasCodecov}) {
|
||||
String _buildDartReusableWorkflow({
|
||||
required bool hasCodecov,
|
||||
required String? codecovTokenSecret,
|
||||
}) {
|
||||
final testStep = hasCodecov
|
||||
? r'''
|
||||
? '''
|
||||
- name: Run Tests
|
||||
if: inputs.has_unit_tests
|
||||
working-directory: ${{ inputs.package_path }}
|
||||
working-directory: \${{ inputs.package_path }}
|
||||
run: |
|
||||
dart pub global activate coverage && \
|
||||
dart test --coverage=coverage && \
|
||||
dart pub global activate coverage && \\
|
||||
dart test --coverage=coverage && \\
|
||||
dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --report-on=lib --check-ignore
|
||||
- if: inputs.has_unit_tests
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
flags: ${{ inputs.package_name }}
|
||||
working-directory: ${{ inputs.package_path }}
|
||||
'''
|
||||
flags: \${{ inputs.package_name }}
|
||||
working-directory: \${{ inputs.package_path }}
|
||||
${_codecovTokenLine(codecovTokenSecret)}'''
|
||||
: r'''
|
||||
- if: inputs.has_unit_tests
|
||||
working-directory: ${{ inputs.package_path }}
|
||||
@@ -395,7 +437,7 @@ jobs:
|
||||
- working-directory: \${{ inputs.package_path }}
|
||||
run: dart format --set-exit-if-changed .
|
||||
- working-directory: \${{ inputs.package_path }}
|
||||
run: dart analyze .
|
||||
run: dart analyze --fatal-warnings .
|
||||
- name: Bloc Lint
|
||||
if: inputs.has_bloc_lint
|
||||
working-directory: \${{ inputs.package_path }}
|
||||
@@ -403,18 +445,21 @@ jobs:
|
||||
$testStep''';
|
||||
}
|
||||
|
||||
String _buildFlutterReusableWorkflow({required bool hasCodecov}) {
|
||||
String _buildFlutterReusableWorkflow({
|
||||
required bool hasCodecov,
|
||||
required String? codecovTokenSecret,
|
||||
}) {
|
||||
final testStep = hasCodecov
|
||||
? r'''
|
||||
? '''
|
||||
- if: inputs.has_unit_tests
|
||||
working-directory: ${{ inputs.package_path }}
|
||||
working-directory: \${{ inputs.package_path }}
|
||||
run: flutter test --coverage
|
||||
- if: inputs.has_unit_tests
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
flags: ${{ inputs.package_name }}
|
||||
working-directory: ${{ inputs.package_path }}
|
||||
'''
|
||||
flags: \${{ inputs.package_name }}
|
||||
working-directory: \${{ inputs.package_path }}
|
||||
${_codecovTokenLine(codecovTokenSecret)}'''
|
||||
: r'''
|
||||
- if: inputs.has_unit_tests
|
||||
working-directory: ${{ inputs.package_path }}
|
||||
@@ -493,7 +538,10 @@ $testStep - name: Integration Tests
|
||||
|
||||
// ── Dynamic (affected_packages + matrix) ─────────────────────────
|
||||
|
||||
String _buildDynamicYaml(RepositoryDescription repository) {
|
||||
String _buildDynamicYaml(
|
||||
RepositoryDescription repository, {
|
||||
required String? codecovTokenSecret,
|
||||
}) {
|
||||
final hasDart = repository.packages.any(
|
||||
(pkg) => !RepositoryAnalyzer.dependsOnFlutter(root: pkg.root),
|
||||
);
|
||||
@@ -532,6 +580,7 @@ jobs:
|
||||
outputKey: 'dart_packages',
|
||||
sdk: 'dart',
|
||||
hasCodecov: repository.hasCodecov,
|
||||
codecovTokenSecret: codecovTokenSecret,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -542,6 +591,7 @@ jobs:
|
||||
outputKey: 'flutter_packages',
|
||||
sdk: 'flutter',
|
||||
hasCodecov: repository.hasCodecov,
|
||||
codecovTokenSecret: codecovTokenSecret,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -632,6 +682,7 @@ jobs:
|
||||
required String outputKey,
|
||||
required String sdk,
|
||||
required bool hasCodecov,
|
||||
required String? codecovTokenSecret,
|
||||
}) {
|
||||
final isFlutter = sdk == 'flutter';
|
||||
final executable = isFlutter ? 'flutter' : 'dart';
|
||||
@@ -651,7 +702,9 @@ jobs:
|
||||
);
|
||||
|
||||
if (isFlutter) buffer.write(_integrationTestsStep);
|
||||
if (hasCodecov) buffer.write(_codecovUploadStep);
|
||||
if (hasCodecov) {
|
||||
buffer.write(_codecovUploadStep(codecovTokenSecret: codecovTokenSecret));
|
||||
}
|
||||
|
||||
buffer.writeln();
|
||||
}
|
||||
@@ -709,9 +762,17 @@ jobs:
|
||||
}
|
||||
|
||||
String _formatAndAnalyzeSteps({required String executable}) {
|
||||
// `dart analyze` defaults `--fatal-warnings` to on, so passing it is a
|
||||
// no-op today. Emitting it explicitly documents intent and keeps the
|
||||
// generated workflow correct if the SDK default ever flips.
|
||||
// `flutter analyze` already defaults `--fatal-warnings` to on with no
|
||||
// CLI flag, so it doesn't need the same treatment.
|
||||
final analyzeCmd = executable == 'dart'
|
||||
? 'dart analyze --fatal-warnings .'
|
||||
: '$executable analyze .';
|
||||
return '''
|
||||
- run: dart format --set-exit-if-changed .
|
||||
- run: $executable analyze .
|
||||
- run: $analyzeCmd
|
||||
- name: Bloc Lint
|
||||
if: matrix.has_bloc_lint == true
|
||||
run: bloc lint .
|
||||
@@ -740,11 +801,21 @@ jobs:
|
||||
run: flutter test integration_test
|
||||
''';
|
||||
|
||||
static const _codecovUploadStep = r'''
|
||||
String _codecovUploadStep({required String? codecovTokenSecret}) {
|
||||
return '''
|
||||
- uses: codecov/codecov-action@v5
|
||||
with:
|
||||
flags: ${{ matrix.name }}
|
||||
''';
|
||||
flags: \${{ matrix.name }}
|
||||
${_codecovTokenLine(codecovTokenSecret)}''';
|
||||
}
|
||||
|
||||
/// Emits the `token:` line for a codecov-action `with:` block, or the
|
||||
/// empty string when no token secret is configured. Indented 10 spaces
|
||||
/// to match `flags:` peers in both static and dynamic templates.
|
||||
String _codecovTokenLine(String? secret) {
|
||||
if (secret == null || secret.isEmpty) return '';
|
||||
return ' token: \${{ secrets.$secret }}\n';
|
||||
}
|
||||
|
||||
void _writeCspellJob(
|
||||
StringBuffer buffer,
|
||||
|
||||
@@ -3,7 +3,7 @@ description: >-
|
||||
CI tooling for Dart and Flutter monorepos. Generates GitHub Actions
|
||||
workflows, resolves affected packages via dependency graphs, and
|
||||
verifies path filters stay in sync.
|
||||
version: 0.2.1
|
||||
version: 0.2.2
|
||||
homepage: https://shorebird.dev
|
||||
repository: https://github.com/shorebirdtech/shorebird/tree/main/packages/shorebird_ci
|
||||
topics: [ci, github-actions, monorepo, shorebird]
|
||||
|
||||
@@ -187,6 +187,73 @@ void main() {
|
||||
// The verify step must itself be present in the generated workflow.
|
||||
expect(yaml, contains('shorebird_ci verify'));
|
||||
});
|
||||
|
||||
test(
|
||||
'Dart analyze step emits --fatal-warnings explicitly',
|
||||
() async {
|
||||
createPackage(tempDir, 'packages/foo', 'foo');
|
||||
initGitRepo(tempDir);
|
||||
|
||||
await runGenerate(tempDir);
|
||||
final yaml = _readMain(tempDir);
|
||||
|
||||
expect(yaml, contains('dart analyze --fatal-warnings .'));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'Flutter analyze step does not need --fatal-warnings (default on)',
|
||||
() async {
|
||||
createPackage(
|
||||
tempDir,
|
||||
'packages/flutter_pkg',
|
||||
'flutter_pkg',
|
||||
flutterLine: 'any',
|
||||
);
|
||||
initGitRepo(tempDir);
|
||||
|
||||
await runGenerate(tempDir);
|
||||
final yaml = _readMain(tempDir);
|
||||
|
||||
expect(yaml, contains('flutter analyze .'));
|
||||
expect(yaml, isNot(contains('flutter analyze --fatal-warnings')));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'--codecov-token-secret threads token into codecov-action',
|
||||
() async {
|
||||
createPackage(tempDir, 'packages/foo', 'foo', addTestDir: true);
|
||||
File(p.join(tempDir.path, 'codecov.yml')).writeAsStringSync('');
|
||||
initGitRepo(tempDir);
|
||||
|
||||
await runGenerate(
|
||||
tempDir,
|
||||
extra: ['--codecov-token-secret', 'CODECOV_TOKEN'],
|
||||
);
|
||||
final yaml = _readMain(tempDir);
|
||||
|
||||
expect(yaml, contains(r'token: ${{ secrets.CODECOV_TOKEN }}'));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'no --codecov-token-secret → no token plumbing emitted',
|
||||
() async {
|
||||
createPackage(tempDir, 'packages/foo', 'foo', addTestDir: true);
|
||||
File(p.join(tempDir.path, 'codecov.yml')).writeAsStringSync('');
|
||||
initGitRepo(tempDir);
|
||||
|
||||
await runGenerate(tempDir);
|
||||
final yaml = _readMain(tempDir);
|
||||
|
||||
// Asserting on `token:` alone is enough to prove the codecov
|
||||
// token plumbing didn't fire. A broader assertion on `secrets.`
|
||||
// would trip on any unrelated future step that references a
|
||||
// GitHub Actions secret.
|
||||
expect(yaml, isNot(contains('token:')));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('generate --style static', () {
|
||||
@@ -390,6 +457,192 @@ void main() {
|
||||
expect(yaml, contains('cspell:'));
|
||||
expect(yaml, contains('streetsidesoftware/cspell-action'));
|
||||
});
|
||||
|
||||
test(
|
||||
'Dart reusable analyze step emits --fatal-warnings explicitly',
|
||||
() async {
|
||||
createPackage(tempDir, 'packages/foo', 'foo');
|
||||
initGitRepo(tempDir);
|
||||
|
||||
await runGenerate(tempDir, extra: ['--style', 'static']);
|
||||
final dartReusable = File(
|
||||
p.join(
|
||||
tempDir.path,
|
||||
'.github',
|
||||
'workflows',
|
||||
'_shorebird_ci_dart.yaml',
|
||||
),
|
||||
).readAsStringSync();
|
||||
|
||||
expect(dartReusable, contains('dart analyze --fatal-warnings .'));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'Flutter reusable analyze step does not need --fatal-warnings',
|
||||
() async {
|
||||
createPackage(
|
||||
tempDir,
|
||||
'packages/flutter_pkg',
|
||||
'flutter_pkg',
|
||||
flutterLine: 'any',
|
||||
);
|
||||
initGitRepo(tempDir);
|
||||
|
||||
await runGenerate(tempDir, extra: ['--style', 'static']);
|
||||
final flutterReusable = File(
|
||||
p.join(
|
||||
tempDir.path,
|
||||
'.github',
|
||||
'workflows',
|
||||
'_shorebird_ci_flutter.yaml',
|
||||
),
|
||||
).readAsStringSync();
|
||||
|
||||
expect(flutterReusable, contains('flutter analyze .'));
|
||||
expect(
|
||||
flutterReusable,
|
||||
isNot(contains('flutter analyze --fatal-warnings')),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'--codecov-token-secret emits secrets: inherit on each '
|
||||
'orchestrator uses: call and threads token into reusable',
|
||||
() async {
|
||||
createPackage(tempDir, 'packages/foo', 'foo', addTestDir: true);
|
||||
createPackage(
|
||||
tempDir,
|
||||
'packages/flutter_pkg',
|
||||
'flutter_pkg',
|
||||
flutterLine: 'any',
|
||||
addTestDir: true,
|
||||
);
|
||||
File(p.join(tempDir.path, 'codecov.yml')).writeAsStringSync('');
|
||||
initGitRepo(tempDir);
|
||||
|
||||
await runGenerate(
|
||||
tempDir,
|
||||
extra: [
|
||||
'--style',
|
||||
'static',
|
||||
'--codecov-token-secret',
|
||||
'CODECOV_TOKEN',
|
||||
],
|
||||
);
|
||||
final mainYaml = _readMain(tempDir);
|
||||
final dartReusable = File(
|
||||
p.join(
|
||||
tempDir.path,
|
||||
'.github',
|
||||
'workflows',
|
||||
'_shorebird_ci_dart.yaml',
|
||||
),
|
||||
).readAsStringSync();
|
||||
final flutterReusable = File(
|
||||
p.join(
|
||||
tempDir.path,
|
||||
'.github',
|
||||
'workflows',
|
||||
'_shorebird_ci_flutter.yaml',
|
||||
),
|
||||
).readAsStringSync();
|
||||
|
||||
// One `secrets: inherit` per package job (2 packages).
|
||||
expect(
|
||||
'secrets: inherit'.allMatches(mainYaml).length,
|
||||
equals(2),
|
||||
);
|
||||
expect(
|
||||
dartReusable,
|
||||
contains(r'token: ${{ secrets.CODECOV_TOKEN }}'),
|
||||
);
|
||||
expect(
|
||||
flutterReusable,
|
||||
contains(r'token: ${{ secrets.CODECOV_TOKEN }}'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'no --codecov-token-secret → no secrets: inherit, no token',
|
||||
() async {
|
||||
createPackage(tempDir, 'packages/foo', 'foo', addTestDir: true);
|
||||
File(p.join(tempDir.path, 'codecov.yml')).writeAsStringSync('');
|
||||
initGitRepo(tempDir);
|
||||
|
||||
await runGenerate(tempDir, extra: ['--style', 'static']);
|
||||
final mainYaml = _readMain(tempDir);
|
||||
final dartReusable = File(
|
||||
p.join(
|
||||
tempDir.path,
|
||||
'.github',
|
||||
'workflows',
|
||||
'_shorebird_ci_dart.yaml',
|
||||
),
|
||||
).readAsStringSync();
|
||||
|
||||
expect(mainYaml, isNot(contains('secrets: inherit')));
|
||||
expect(dartReusable, isNot(contains('token:')));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'--codecov-token-secret w/o codecov config → no plumbing emitted',
|
||||
() async {
|
||||
// Repo has no codecov config, so reusable workflow has no
|
||||
// codecov-action step. The flag should be a no-op here even
|
||||
// when passed.
|
||||
createPackage(tempDir, 'packages/foo', 'foo', addTestDir: true);
|
||||
initGitRepo(tempDir);
|
||||
|
||||
await runGenerate(
|
||||
tempDir,
|
||||
extra: [
|
||||
'--style',
|
||||
'static',
|
||||
'--codecov-token-secret',
|
||||
'CODECOV_TOKEN',
|
||||
],
|
||||
);
|
||||
final mainYaml = _readMain(tempDir);
|
||||
|
||||
expect(mainYaml, isNot(contains('secrets: inherit')));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'--codecov-token-secret accepts an arbitrary secret name',
|
||||
() async {
|
||||
createPackage(tempDir, 'packages/foo', 'foo', addTestDir: true);
|
||||
File(p.join(tempDir.path, 'codecov.yml')).writeAsStringSync('');
|
||||
initGitRepo(tempDir);
|
||||
|
||||
await runGenerate(
|
||||
tempDir,
|
||||
extra: [
|
||||
'--style',
|
||||
'static',
|
||||
'--codecov-token-secret',
|
||||
'MY_CUSTOM_NAME',
|
||||
],
|
||||
);
|
||||
final dartReusable = File(
|
||||
p.join(
|
||||
tempDir.path,
|
||||
'.github',
|
||||
'workflows',
|
||||
'_shorebird_ci_dart.yaml',
|
||||
),
|
||||
).readAsStringSync();
|
||||
|
||||
expect(
|
||||
dartReusable,
|
||||
contains(r'token: ${{ secrets.MY_CUSTOM_NAME }}'),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('dependabot.yml', () {
|
||||
|
||||
Reference in New Issue
Block a user