Skip to content

Commit cbe366e

Browse files
committed
feat: Local pub server (#2919)
* ci: Fix db_common tests on Windows * Use powershell * feat: Add `pub_server` package Adds a minimal package for running a local, embedded pub server. * chore: Improve license script Running license script without `addlicense` installed should not leave a bunch of files around and should be runnable without sudo * feat(pub_server): Add launcher Adds a mechanism for seeding the local pub server with either the packages of a local directory or of a remote git repo. * feat(aft): Add `serve` command Adds a command for serving the repo's packages on a local pub server. * chore(pub_server): Clean up * ci: Add `pub_server` workflow * chore(pub_server): Add more doc comments * test(pub_server): Add e2e tests
1 parent a5bab58 commit cbe366e

26 files changed

+2366
-57
lines changed

.github/dependabot.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ updates:
7474
- "version-update:semver-patch"
7575
- dependency-name: "aws_common"
7676
- dependency-name: "amplify_lints"
77+
- dependency-name: "pub_server"
7778
- dependency-name: "smithy"
7879
- dependency-name: "smithy_codegen"
7980
- dependency-name: "aws_signature_v4"
@@ -1414,6 +1415,17 @@ updates:
14141415
- dependency-name: "aws_common"
14151416
- dependency-name: "amplify_lints"
14161417
- dependency-name: "aws_signature_v4"
1418+
- package-ecosystem: "pub"
1419+
directory: "packages/test/pub_server"
1420+
schedule:
1421+
interval: "daily"
1422+
ignore:
1423+
# Ignore patch version bumps
1424+
- dependency-name: "*"
1425+
update-types:
1426+
- "version-update:semver-patch"
1427+
- dependency-name: "aws_common"
1428+
- dependency-name: "amplify_lints"
14171429
- package-ecosystem: "pub"
14181430
directory: "packages/worker_bee/e2e"
14191431
schedule:

.github/workflows/pub_server.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Generated with aft. To update, run: `aft generate workflows`
2+
name: pub_server
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- stable
8+
pull_request:
9+
paths:
10+
- '.github/workflows/dart_native.yaml'
11+
- '.github/workflows/dart_vm.yaml'
12+
- '.github/workflows/pub_server.yaml'
13+
- 'packages/amplify_lints/lib/**/*.yaml'
14+
- 'packages/amplify_lints/pubspec.yaml'
15+
- 'packages/aws_common/lib/**/*.dart'
16+
- 'packages/aws_common/pubspec.yaml'
17+
- 'packages/test/pub_server/**/*.dart'
18+
- 'packages/test/pub_server/**/*.yaml'
19+
- 'packages/test/pub_server/lib/**/*'
20+
- 'packages/test/pub_server/test/**/*'
21+
schedule:
22+
- cron: "0 0 * * 0" # Every Sunday at 00:00
23+
defaults:
24+
run:
25+
shell: bash
26+
permissions: read-all
27+
28+
jobs:
29+
test:
30+
uses: ./.github/workflows/dart_vm.yaml
31+
with:
32+
package-name: pub_server
33+
working-directory: packages/test/pub_server
34+
native_test:
35+
needs: test
36+
uses: ./.github/workflows/dart_native.yaml
37+
with:
38+
package-name: pub_server
39+
working-directory: packages/test/pub_server

packages/aft/lib/aft.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export 'src/commands/link_command.dart';
1515
export 'src/commands/list_packages_command.dart';
1616
export 'src/commands/publish_command.dart';
1717
export 'src/commands/run_command.dart';
18+
export 'src/commands/serve_command.dart';
1819
export 'src/commands/version_bump_command.dart';
1920
export 'src/models.dart';
2021
export 'src/pub/pub_runner.dart';

packages/aft/lib/src/command_runner.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ Future<void> run(List<String> args) async {
3131
..addCommand(CreateCommand())
3232
..addCommand(SaveRepoStateCommand())
3333
..addCommand(RunCommand())
34-
..addCommand(DocsCommand());
34+
..addCommand(DocsCommand())
35+
..addCommand(ServeCommand());
3536

3637
try {
3738
final argResults = runner.argParser.parse(args);

packages/aft/lib/src/commands/publish_command.dart

Lines changed: 69 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,39 @@ import 'package:collection/collection.dart';
1111
import 'package:graphs/graphs.dart';
1212
import 'package:path/path.dart' as p;
1313

14-
/// Command to publish all Dart/Flutter packages in the repo.
15-
class PublishCommand extends AmplifyCommand with GlobOptions {
16-
PublishCommand() {
17-
argParser
18-
..addFlag(
19-
'force',
20-
abbr: 'f',
21-
help: 'Ignores errors in pre-publishing commands and publishes '
22-
'without prompt',
23-
negatable: false,
24-
)
25-
..addFlag(
26-
'dry-run',
27-
help: 'Passes `--dry-run` flag to `dart` or `flutter` publish command',
28-
negatable: false,
29-
);
30-
}
14+
/// Helpers for commands which publish packages to `pub.dev`.
15+
mixin PublishHelpers on AmplifyCommand {
16+
bool get dryRun;
17+
bool get force;
3118

32-
late final bool force = argResults!['force'] as bool;
33-
late final bool dryRun = argResults!['dry-run'] as bool;
19+
/// Gathers the subset of packages which are publishable and whose latest
20+
/// version is not already available on `pub.dev`.
21+
Future<List<PackageInfo>> unpublishedPackages(
22+
List<PackageInfo> publishablePackages,
23+
) async {
24+
final unpublishedPackages = (await Future.wait([
25+
for (final package in publishablePackages) checkPublishable(package),
26+
]))
27+
.whereType<PackageInfo>()
28+
.toList();
3429

35-
@override
36-
String get description =>
37-
'Publishes all packages in the Amplify Flutter repo which '
38-
'need publishing.';
30+
try {
31+
sortPackagesTopologically<PackageInfo>(
32+
unpublishedPackages,
33+
(pkg) => pkg.pubspecInfo.pubspec,
34+
);
35+
} on CycleException<dynamic> {
36+
if (!force) {
37+
exitError('Cannot sort packages with inter-dependencies.');
38+
}
39+
}
3940

40-
@override
41-
String get name => 'publish';
41+
return unpublishedPackages;
42+
}
4243

4344
/// Checks if [package] can be published based on whether the local version
4445
/// is newer than the one published to `pub.dev`.
45-
Future<PackageInfo?> _checkPublishable(PackageInfo package) async {
46+
Future<PackageInfo?> checkPublishable(PackageInfo package) async {
4647
final publishTo = package.pubspecInfo.pubspec.publishTo;
4748
if (publishTo == 'none') {
4849
return null;
@@ -61,8 +62,8 @@ class PublishCommand extends AmplifyCommand with GlobOptions {
6162
final publishedVersion = maxBy(
6263
[
6364
if (versionInfo?.latestPrerelease != null)
64-
versionInfo?.latestPrerelease!,
65-
if (versionInfo?.latestVersion != null) versionInfo?.latestVersion!,
65+
versionInfo!.latestPrerelease!,
66+
if (versionInfo?.latestVersion != null) versionInfo!.latestVersion!,
6667
],
6768
(v) => v,
6869
);
@@ -85,7 +86,7 @@ class PublishCommand extends AmplifyCommand with GlobOptions {
8586

8687
/// Runs pre-publish operations for [package], most importantly any necessary
8788
/// `build_runner` tasks.
88-
Future<void> _prePublish(PackageInfo package) async {
89+
Future<void> prePublish(PackageInfo package) async {
8990
logger.info('Running pre-publish checks for ${package.name}...');
9091
if (!dryRun) {
9192
// Remove any overrides so that `pub` commands resolve against
@@ -136,7 +137,7 @@ class PublishCommand extends AmplifyCommand with GlobOptions {
136137
static final _validationErrorRegex = RegExp(r'^\s*\*');
137138

138139
/// Publishes the package using `pub`.
139-
Future<void> _publish(PackageInfo package) async {
140+
Future<void> publish(PackageInfo package) async {
140141
logger.info('Publishing ${package.name}${dryRun ? ' (dry run)' : ''}...');
141142
final publishCmd = await Process.start(
142143
package.flavor.entrypoint,
@@ -181,21 +182,49 @@ class PublishCommand extends AmplifyCommand with GlobOptions {
181182
}
182183
}
183184
}
185+
}
186+
187+
/// Command to publish all Dart/Flutter packages in the repo.
188+
class PublishCommand extends AmplifyCommand with GlobOptions, PublishHelpers {
189+
PublishCommand() {
190+
argParser
191+
..addFlag(
192+
'force',
193+
abbr: 'f',
194+
help: 'Ignores errors in pre-publishing commands and publishes '
195+
'without prompt',
196+
negatable: false,
197+
)
198+
..addFlag(
199+
'dry-run',
200+
help: 'Passes `--dry-run` flag to `dart` or `flutter` publish command',
201+
negatable: false,
202+
);
203+
}
204+
205+
@override
206+
late final bool force = argResults!['force'] as bool;
207+
208+
@override
209+
late final bool dryRun = argResults!['dry-run'] as bool;
210+
211+
@override
212+
String get description =>
213+
'Publishes all packages in the Amplify Flutter repo which '
214+
'need publishing.';
215+
216+
@override
217+
String get name => 'publish';
184218

185219
@override
186220
Future<void> run() async {
187221
await super.run();
188222
// Gather packages which can be published.
189-
final publishablePackages = repo
190-
.publishablePackages(commandPackages)
191-
.where((pkg) => pkg.pubspecInfo.pubspec.publishTo != 'none');
223+
final publishablePackages = repo.publishablePackages(commandPackages);
192224

193225
// Gather packages which need to be published.
194-
final packagesNeedingPublish = (await Future.wait([
195-
for (final package in publishablePackages) _checkPublishable(package),
196-
]))
197-
.whereType<PackageInfo>()
198-
.toList();
226+
final packagesNeedingPublish =
227+
await unpublishedPackages(publishablePackages);
199228

200229
// Publishable packages which are being held back.
201230
final unpublishablePackages = publishablePackages.where(
@@ -207,17 +236,6 @@ class PublishCommand extends AmplifyCommand with GlobOptions {
207236
return;
208237
}
209238

210-
try {
211-
sortPackagesTopologically<PackageInfo>(
212-
packagesNeedingPublish,
213-
(pkg) => pkg.pubspecInfo.pubspec,
214-
);
215-
} on CycleException<dynamic> {
216-
if (!force) {
217-
exitError('Cannot sort packages with inter-dependencies.');
218-
}
219-
}
220-
221239
stdout
222240
..writeln('Preparing to publish${dryRun ? ' (dry run)' : ''}: ')
223241
..writeln(
@@ -247,8 +265,8 @@ class PublishCommand extends AmplifyCommand with GlobOptions {
247265
// some packages will not be published, it also means that the command
248266
// can be re-run to pick up where it left off.
249267
for (final package in packagesNeedingPublish) {
250-
await _prePublish(package);
251-
await _publish(package);
268+
await prePublish(package);
269+
await publish(package);
252270
}
253271

254272
stdout.writeln(
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import 'dart:io';
5+
6+
import 'package:aft/aft.dart';
7+
import 'package:aft/src/options/glob_options.dart';
8+
import 'package:async/async.dart';
9+
import 'package:pub_server/pub_server.dart';
10+
import 'package:shelf/shelf_io.dart' as io;
11+
12+
/// A command for serving all packages in the Amplify Flutter repo on a local
13+
/// pub server.
14+
class ServeCommand extends AmplifyCommand with GlobOptions, PublishHelpers {
15+
ServeCommand() {
16+
argParser.addOption(
17+
'port',
18+
abbr: 'p',
19+
defaultsTo: '0',
20+
help: 'The port to serve on.',
21+
);
22+
}
23+
24+
/// The port to serve on.
25+
late final int port = int.parse(argResults!['port'] as String);
26+
27+
@override
28+
bool get force => true;
29+
30+
@override
31+
bool get dryRun => false;
32+
33+
@override
34+
String get description => 'Serves all packages in the Amplify Flutter repo '
35+
'on a local pub server.';
36+
37+
@override
38+
String get name => 'serve';
39+
40+
@override
41+
Future<PackageInfo?> checkPublishable(PackageInfo package) async {
42+
final publishTo = package.pubspecInfo.pubspec.publishTo;
43+
if (publishTo == 'none') {
44+
return null;
45+
}
46+
return package;
47+
}
48+
49+
@override
50+
Future<void> run() async {
51+
await super.run();
52+
await linkPackages();
53+
54+
final pubServer = await io.serve(
55+
PubServer.prod().handler,
56+
InternetAddress.anyIPv4,
57+
port,
58+
);
59+
final pubServerUri = Uri.parse('http://localhost:${pubServer.port}');
60+
61+
// Gather packages which can be published.
62+
final publishablePackages = repo.publishablePackages(commandPackages);
63+
64+
// Publish packages to local pub server.
65+
final packagesNeedingPublish =
66+
await unpublishedPackages(publishablePackages);
67+
final launcherPackages = packagesNeedingPublish
68+
.map(
69+
(pkg) => LocalPackage(
70+
name: pkg.name,
71+
path: pkg.path,
72+
pubspec: pkg.pubspecInfo.pubspec,
73+
),
74+
)
75+
.toList();
76+
final launcher = AmplifyPubLauncher(
77+
pubServerUri,
78+
launcherPackages,
79+
prePublish: (LocalPackage pkg) {
80+
final package = repo.allPackages[pkg.name]!;
81+
return prePublish(package);
82+
},
83+
);
84+
await launcher.run();
85+
86+
logger.info('Serving packages on $pubServerUri. Press Ctrl+C to exit.');
87+
88+
await StreamGroup.merge([
89+
ProcessSignal.sigint.watch(),
90+
ProcessSignal.sigterm.watch(),
91+
]).first;
92+
93+
await pubServer.close(force: true);
94+
}
95+
}
96+
97+
class AmplifyPubLauncher extends PubLauncher {
98+
AmplifyPubLauncher(
99+
super.pubServerUri,
100+
super.packages, {
101+
required Future<void> Function(LocalPackage) prePublish,
102+
}) : _prePublish = prePublish;
103+
104+
final Future<void> Function(LocalPackage) _prePublish;
105+
106+
@override
107+
Future<void> prePublish(LocalPackage package) => _prePublish(package);
108+
}

packages/aft/lib/src/models.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const falsePositiveExamples = [
2020
'amplify_auth_cognito_test',
2121
'amplify_secure_storage_test',
2222
'amplify_native_legacy_wrapper',
23+
'pub_server',
2324

2425
// Smithy Golden packages
2526
'aws_json1_0_v1',

packages/aft/pubspec.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ dependencies:
3131
path: any
3232
pub_api_client: ^2.4.0
3333
pub_semver: ^2.1.1
34+
pub_server:
35+
path: ../test/pub_server
3436
pubspec_parse: ^1.2.0
3537
shelf: ^1.4.0
3638
shelf_static: ^1.1.1

0 commit comments

Comments
 (0)