Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Important changes to data models, configuration, and migrations between each
AppEngine version, listed here to ease deployment and troubleshooting.

## Next Release (replace with git tag when deployed)
* Note: new `Package.publishingConfig` field in Datastore, new `PUT /api/packages/<package>/publishing` endpoint.

## `20251023t081900-all`
* Bump runtimeVersion to `2025.10.22`.
Expand Down
20 changes: 17 additions & 3 deletions app/lib/frontend/handlers/pubapi.client.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions app/lib/frontend/handlers/pubapi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,17 @@ class PubApi {
) => putPackageOptionsHandler(request, package, body);

@EndPoint.put('/api/packages/<package>/automated-publishing')
Future<AutomatedPublishingConfig> setAutomatedPublishing(
Future<PkgPublishingConfig> setAutomatedPublishing(
Request request,
String package,
AutomatedPublishingConfig body,
PkgPublishingConfig body,
) => packageBackend.setAutomatedPublishing(package, body);

@EndPoint.put('/api/packages/<package>/publishing')
Future<PkgPublishingConfig> setPackagePublishing(
Request request,
String package,
PkgPublishingConfig body,
) => packageBackend.setAutomatedPublishing(package, body);

@EndPoint.get('/api/packages/<package>/versions/<version>/options')
Expand Down
24 changes: 22 additions & 2 deletions app/lib/frontend/handlers/pubapi.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions app/lib/frontend/templates/views/pkg/admin_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,8 @@ d.Node packageAdminPageNode({
}

d.Node _automatedPublishing(Package package) {
final github = package.automatedPublishing?.githubConfig;
final gcp = package.automatedPublishing?.gcpConfig;
final github = package.publishingConfig?.githubConfig;
final gcp = package.publishingConfig?.gcpConfig;
final isGitHubEnabled = github?.isEnabled ?? false;
return d.fragment([
d.a(name: 'automated-publishing'),
Expand Down Expand Up @@ -469,7 +469,7 @@ d.Node _automatedPublishing(Package package) {
}

d.Node _manualPublishing(Package package) {
final manual = package.automatedPublishing?.manualConfig;
final manual = package.publishingConfig?.manualConfig;
return d.fragment([
d.a(name: 'manual-publishing'),
d.h3(text: 'Manual publishing'),
Expand Down
50 changes: 25 additions & 25 deletions app/lib/package/backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -624,9 +624,9 @@ class PackageBackend {

/// Verifies an update to the credential-less publishing settings and
/// updates the Datastore entity if everything is valid.
Future<api.AutomatedPublishingConfig> setAutomatedPublishing(
Future<api.PkgPublishingConfig> setAutomatedPublishing(
String package,
api.AutomatedPublishingConfig body,
api.PkgPublishingConfig body,
) async {
final authenticatedUser = await requireAuthenticatedWebUser();
final user = authenticatedUser.user;
Expand Down Expand Up @@ -714,30 +714,30 @@ class PackageBackend {
}

// update lock
final current = p.automatedPublishing;
final current = p.publishingConfig;
final githubChanged =
(githubConfig?.isEnabled != current?.githubConfig?.isEnabled) ||
(githubConfig?.repository != current?.githubConfig?.repository);
if (githubChanged) {
p.automatedPublishing?.githubLock = null;
p.publishingConfig?.githubLock = null;
}
final gcpChanged =
(gcpConfig?.isEnabled != current?.gcpConfig?.isEnabled) ||
(gcpConfig?.serviceAccountEmail !=
current?.gcpConfig?.serviceAccountEmail);
if (gcpChanged) {
p.automatedPublishing?.gcpLock = null;
p.publishingConfig?.gcpLock = null;
}

// finalize changes
final automatedPublishing = p.automatedPublishing ??=
AutomatedPublishing();
automatedPublishing.githubConfig =
githubConfig ?? automatedPublishing.githubConfig;
automatedPublishing.gcpConfig =
gcpConfig ?? automatedPublishing.gcpConfig;
automatedPublishing.manualConfig =
manualConfig ?? automatedPublishing.manualConfig;
final publishingConfig = p.publishingConfig ?? PublishingConfig();
p.automatedPublishing = publishingConfig;
p.newPublishingConfig = publishingConfig;
publishingConfig.githubConfig =
githubConfig ?? publishingConfig.githubConfig;
publishingConfig.gcpConfig = gcpConfig ?? publishingConfig.gcpConfig;
publishingConfig.manualConfig =
manualConfig ?? publishingConfig.manualConfig;

p.updated = clock.now().toUtc();
tx.insert(p);
Expand All @@ -748,10 +748,10 @@ class PackageBackend {
user: user,
),
);
return api.AutomatedPublishingConfig(
github: p.automatedPublishing!.githubConfig,
gcp: p.automatedPublishing!.gcpConfig,
manual: p.automatedPublishing!.manualConfig,
return api.PkgPublishingConfig(
github: p.publishingConfig!.githubConfig,
gcp: p.publishingConfig!.gcpConfig,
manual: p.publishingConfig!.manualConfig,
);
});
}
Expand Down Expand Up @@ -1617,7 +1617,7 @@ class PackageBackend {
if (agent is AuthenticatedUser &&
await packageBackend.isPackageAdmin(package, agent.user.userId)) {
final isEnabled =
package.automatedPublishing?.manualConfig?.isEnabled ?? true;
package.publishingConfig?.manualConfig?.isEnabled ?? true;
if (!isEnabled) {
throw AuthorizationException.manualPublishingDisabled(package.name!);
}
Expand Down Expand Up @@ -1647,8 +1647,8 @@ class PackageBackend {
Package package,
String newVersion,
) async {
final githubConfig = package.automatedPublishing?.githubConfig;
final githubLock = package.automatedPublishing?.githubLock;
final githubConfig = package.publishingConfig?.githubConfig;
final githubLock = package.publishingConfig?.githubLock;

if (githubConfig?.isEnabled != true) {
throw AuthorizationException.githubActionIssue(
Expand Down Expand Up @@ -1722,7 +1722,7 @@ class PackageBackend {
);
await withRetryTransaction(db, (tx) async {
final p = await tx.lookupValue<Package>(package.key);
p.automatedPublishing!.githubConfig!.isEnabled = false;
p.publishingConfig!.githubConfig!.isEnabled = false;
tx.insert(p);
});
throw AuthorizationException.githubActionIssue(
Expand All @@ -1737,8 +1737,8 @@ class PackageBackend {
Package package,
String newVersion,
) async {
final gcpConfig = package.automatedPublishing?.gcpConfig;
final gcpLock = package.automatedPublishing?.gcpLock;
final gcpConfig = package.publishingConfig?.gcpConfig;
final gcpLock = package.publishingConfig?.gcpLock;
if (gcpConfig?.isEnabled != true) {
throw AuthorizationException.serviceAccountPublishingIssue(
'publishing with service account is not enabled',
Expand Down Expand Up @@ -1766,7 +1766,7 @@ class PackageBackend {
);
await withRetryTransaction(db, (tx) async {
final p = await tx.lookupValue<Package>(package.key);
p.automatedPublishing!.gcpConfig!.isEnabled = false;
p.publishingConfig!.gcpConfig!.isEnabled = false;
tx.insert(p);
});
throw AuthorizationException.githubActionIssue(
Expand Down Expand Up @@ -2008,7 +2008,7 @@ class PackageBackend {
Package package,
AuthenticatedAgent agent,
) {
final current = package.automatedPublishing;
final current = package.publishingConfig;
if (current == null) {
if (agent is AuthenticatedGitHubAction ||
agent is AuthenticatedGcpServiceAccount) {
Expand Down
38 changes: 21 additions & 17 deletions app/lib/package/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,15 @@ class Package extends db.ExpandoModel<String> {
@db.StringListProperty()
List<String>? deletedVersions;

/// Scheduling state for all versions of this package.
@AutomatedPublishingProperty()
AutomatedPublishing? automatedPublishing;
/// The configuration for automated and manual publishing.
@PublishingConfigProperty()
PublishingConfig? automatedPublishing;

@PublishingConfigProperty(propertyName: 'publishingConfig')
PublishingConfig? newPublishingConfig;

PublishingConfig? get publishingConfig =>
newPublishingConfig ?? automatedPublishing;

/// The latest point in time at which a security advisory that affects this
/// package has been synchronized into pub.
Expand Down Expand Up @@ -455,33 +461,31 @@ class Release {
}

@JsonSerializable(explicitToJson: true, includeIfNull: false)
class AutomatedPublishing {
class PublishingConfig {
GitHubPublishingConfig? githubConfig;
GitHubPublishingLock? githubLock;
GcpPublishingConfig? gcpConfig;
GcpPublishingLock? gcpLock;
ManualPublishingConfig? manualConfig;

AutomatedPublishing({
PublishingConfig({
this.githubConfig,
this.githubLock,
this.gcpConfig,
this.gcpLock,
this.manualConfig,
});

factory AutomatedPublishing.fromJson(Map<String, dynamic> json) =>
_$AutomatedPublishingFromJson(json);
factory PublishingConfig.fromJson(Map<String, dynamic> json) =>
_$PublishingConfigFromJson(json);

Map<String, dynamic> toJson() => _$AutomatedPublishingToJson(this);
Map<String, dynamic> toJson() => _$PublishingConfigToJson(this);
}

/// A [db.Property] encoding [AutomatedPublishing] as JSON.
class AutomatedPublishingProperty extends db.Property {
const AutomatedPublishingProperty({
super.propertyName,
super.required = false,
}) : super(indexed: false);
/// A [db.Property] encoding [PublishingConfig] as JSON.
class PublishingConfigProperty extends db.Property {
const PublishingConfigProperty({super.propertyName, super.required = false})
: super(indexed: false);

@override
Object? encodeValue(
Expand All @@ -490,21 +494,21 @@ class AutomatedPublishingProperty extends db.Property {
bool forComparison = false,
}) {
if (value == null) return null;
return json.encode(value as AutomatedPublishing);
return json.encode(value as PublishingConfig);
}

@override
Object? decodePrimitiveValue(db.ModelDB mdb, Object? value) {
if (value == null) return null;
return AutomatedPublishing.fromJson(
return PublishingConfig.fromJson(
json.decode(value as String) as Map<String, dynamic>,
);
}

@override
bool validate(db.ModelDB mdb, Object? value) =>
super.validate(mdb, value) &&
(value == null || value is AutomatedPublishing);
(value == null || value is PublishingConfig);
}

@JsonSerializable(explicitToJson: true, includeIfNull: false)
Expand Down
21 changes: 10 additions & 11 deletions app/lib/package/models.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion app/lib/tool/backfill/backfill_new_fields.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.

import 'package:logging/logging.dart';
import 'package:pub_dev/package/models.dart';
import 'package:pub_dev/shared/datastore.dart';

final _logger = Logger('backfill_new_fields');

Expand All @@ -12,5 +14,14 @@ final _logger = Logger('backfill_new_fields');
/// CHANGELOG.md must be updated with the new fields, and the next
/// release could remove the backfill from here.
Future<void> backfillNewFields() async {
_logger.info('Nothing to backfill.');
_logger.info('Backfill new Package.publishingConfig.');
await for (final p in dbService.query<Package>().run()) {
if (p.automatedPublishing != null && p.newPublishingConfig == null) {
await withRetryTransaction(dbService, (tx) async {
final pkg = await tx.lookupValue<Package>(p.key);
pkg.newPublishingConfig = pkg.automatedPublishing;
tx.insert(pkg);
});
}
}
}
Loading