Skip to content

Commit 7eda363

Browse files
Only pick up last 24hrs in daily publish job (#733)
1 parent cc24099 commit 7eda363

7 files changed

Lines changed: 116 additions & 32 deletions

File tree

app/src/App/Effect/GitHub.purs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module Registry.App.Effect.GitHub
1212
, getRefCommit
1313
, handle
1414
, interpret
15+
, listCommitsSince
1516
, listTags
1617
, listTeamMembers
1718
) where
@@ -73,6 +74,7 @@ instance FsEncodable GitHubCache where
7374

7475
data GitHub a
7576
= ListTags Address (Either GitHubError (Array Tag) -> a)
77+
| ListCommitsSince Address DateTime (Either GitHubError (Array String) -> a)
7678
| ListTeamMembers Team (Either GitHubError (Array String) -> a)
7779
| GetContent Address String FilePath (Either GitHubError String -> a)
7880
| GetRefCommit Address String (Either GitHubError String -> a)
@@ -90,6 +92,10 @@ _github = Proxy
9092
listTags :: forall r. Address -> Run (GITHUB + r) (Either GitHubError (Array Tag))
9193
listTags address = Run.lift _github (ListTags address identity)
9294

95+
-- | List commits since a given date. Returns an array of commit SHAs.
96+
listCommitsSince :: forall r. Address -> DateTime -> Run (GITHUB + r) (Either GitHubError (Array String))
97+
listCommitsSince address since = Run.lift _github (ListCommitsSince address since identity)
98+
9399
-- | List the members of the provided team. Requires that the authorization on
94100
-- | the request has read rights for the given organization and team.
95101
listTeamMembers :: forall r. Team -> Run (GITHUB + r) (Either GitHubError (Array String))
@@ -139,6 +145,11 @@ handle env = Cache.interpret _githubCache (Cache.handleMemoryFs { cache: env.cac
139145
result <- request env.octokit (Octokit.listTagsRequest address)
140146
pure $ reply result
141147

148+
ListCommitsSince address since reply -> do
149+
Log.debug $ "Listing commits since " <> Internal.Codec.formatIso8601 since <> " for " <> address.owner <> "/" <> address.repo
150+
result <- request env.octokit (Octokit.listCommitsSinceRequest { address, since })
151+
pure $ reply $ map (map _.sha) result
152+
142153
ListTeamMembers team reply -> do
143154
Log.debug $ "Listing members of team " <> team.org <> "/" <> team.team
144155
result <- request env.octokit (Octokit.listTeamMembersRequest team)

app/test/Test/Assert/Run.purs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,9 @@ handleGitHubMock env = case _ of
403403

404404
pure $ reply $ Right tags
405405

406+
ListCommitsSince _address _since reply ->
407+
pure $ reply $ Left $ UnexpectedError "Unimplemented"
408+
406409
ListTeamMembers team reply -> pure $ reply $ case team of
407410
{ org: "purescript", team: "packaging" } -> Right [ "pacchettibotti", "f-f", "thomashoneyman" ]
408411
_ -> Left $ APIError { statusCode: 404, message: "No fixture provided for team " <> team.org <> "/" <> team.team }

foreign/src/Foreign/Octokit.purs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ module Registry.Foreign.Octokit
2929
, githubApiErrorCodec
3030
, githubErrorCodec
3131
, isPermanentGitHubError
32+
, listCommitsSinceRequest
3233
, listTagsRequest
3334
, listTeamMembersRequest
35+
, CommitSha
3436
, newOctokit
3537
, noArgs
3638
, printGitHubError
@@ -158,6 +160,19 @@ listTagsRequest address =
158160
toJsonRep { name, sha, url } = { name, commit: { sha, url } }
159161
fromJsonRep { name, commit } = { name, sha: commit.sha, url: commit.url }
160162

163+
type CommitSha = { sha :: String }
164+
165+
-- | List repository commits since a given date
166+
-- | https://docs.github.com/en/rest/commits/commits#list-commits
167+
listCommitsSinceRequest :: { address :: Address, since :: DateTime } -> Request (Array CommitSha)
168+
listCommitsSinceRequest { address, since } =
169+
{ route: GitHubRoute GET [ "repos", address.owner, address.repo, "commits" ] (Map.singleton "since" (Internal.Codec.formatIso8601 since))
170+
, headers: Object.empty
171+
, args: noArgs
172+
, paginate: true
173+
, codec: CJ.array $ CJ.named "CommitSha" $ CJ.Record.object { sha: CJ.string }
174+
}
175+
161176
-- | Fetch a specific file from the provided repository at the given ref and
162177
-- | filepath. Filepaths should lead to a single file from the root of the repo.
163178
-- | https://github.com/octokit/plugin-rest-endpoint-methods.js/blob/v5.16.0/docs/repos/getContent.md

lib/src/Internal/Codec.purs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module Registry.Internal.Codec
2-
( iso8601Date
2+
( formatIso8601
3+
, iso8601Date
34
, iso8601DateTime
45
, limitedString
56
, packageMap
@@ -40,6 +41,12 @@ import Registry.PackageName as PackageName
4041
import Registry.Version (Version)
4142
import Registry.Version as Version
4243

44+
-- | INTERNAL
45+
-- |
46+
-- | Format a DateTime as an ISO8601 string.
47+
formatIso8601 :: DateTime -> String
48+
formatIso8601 = Formatter.DateTime.format Internal.Format.iso8601DateTime
49+
4350
-- | INTERNAL
4451
-- |
4552
-- | A codec for date times that encode as JSON strings in the ISO8601 date-time

nix/test/config.nix

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,33 @@ let
309309
];
310310
};
311311
}
312+
# Commits endpoint for prelude - return empty (no recent commits)
313+
{
314+
request = {
315+
method = "GET";
316+
urlPattern = "/repos/purescript/purescript-prelude/commits\\?since=.*";
317+
};
318+
response = {
319+
status = 200;
320+
headers."Content-Type" = "application/json";
321+
jsonBody = [ ];
322+
};
323+
}
324+
# Commits endpoint for type-equality - return the v4.0.2 commit sha
325+
# This makes the DailyImporter detect that v4.0.2 is a recent commit
326+
{
327+
request = {
328+
method = "GET";
329+
urlPattern = "/repos/purescript/purescript-type-equality/commits\\?since=.*";
330+
};
331+
response = {
332+
status = 200;
333+
headers."Content-Type" = "application/json";
334+
jsonBody = [
335+
{ sha = "type-eq-sha-402"; }
336+
];
337+
};
338+
}
312339
# Tags for type-equality package (used by two scheduler tests):
313340
# 1. Transfer detection: metadata says purescript, commit URLs point to new-owner
314341
# 2. Legacy imports: v4.0.2 is a new version not yet published

scripts/src/DailyImporter.purs

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
-- | This script checks for new package versions by fetching GitHub tags for all
2-
-- | packages in the registry. When a new version is discovered (a tag that hasn't
3-
-- | been published or unpublished), it submits a publish job to the registry API.
1+
-- | This script checks for new package versions by fetching commits from the last
2+
-- | 24 hours for all packages in the registry. When a recent commit is found that
3+
-- | corresponds to an unpublished version tag, it submits a publish job to the
4+
-- | registry API.
45
-- |
56
-- | Run via Nix:
67
-- | nix run .#daily-importer -- --dry-run # Log what would be submitted
78
-- | nix run .#daily-importer -- --submit # Actually submit to the API
89
-- |
910
-- | Required environment variables:
10-
-- | GITHUB_TOKEN - GitHub API token for fetching tags
11+
-- | GITHUB_TOKEN - GitHub API token for fetching commits and tags
1112
-- | REGISTRY_API_URL - Registry API URL (default: https://registry.purescript.org)
1213
module Registry.Scripts.DailyImporter where
1314

@@ -18,10 +19,13 @@ import ArgParse.Basic as Arg
1819
import Codec.JSON.DecodeError as CJ.DecodeError
1920
import Data.Array as Array
2021
import Data.Codec.JSON as CJ
22+
import Data.DateTime as DateTime
2123
import Data.Map as Map
2224
import Data.Set as Set
25+
import Data.Time.Duration (Hours(..))
2326
import Effect.Aff as Aff
2427
import Effect.Class.Console as Console
28+
import Effect.Now as Now
2529
import Fetch (Method(..))
2630
import Fetch as Fetch
2731
import JSON as JSON
@@ -112,46 +116,63 @@ runDailyImport :: Mode -> URL -> Run DailyImportEffects Unit
112116
runDailyImport mode registryApiUrl = do
113117
Log.info "Daily Importer: checking for new package versions..."
114118

119+
now <- Run.liftEffect Now.nowDateTime
120+
let since = fromMaybe now $ DateTime.adjust (Hours (-24.0)) now
121+
115122
allMetadata <- Registry.readAllMetadata
116123
let packages = Map.toUnfoldable allMetadata :: Array (Tuple PackageName Metadata)
117124

118-
Log.info $ "Checking " <> show (Array.length packages) <> " packages for new versions..."
125+
Log.info $ "Checking " <> show (Array.length packages) <> " packages for commits in the last 24 hours..."
119126

120127
submitted <- for packages \(Tuple name (Metadata metadata)) -> do
121128
case metadata.location of
122129
Git _ -> pure 0 -- Skip non-GitHub packages for now
123130
GitHub { owner, repo } -> do
124-
GitHub.listTags { owner, repo } >>= case _ of
131+
let address = { owner, repo }
132+
-- First, check if there are any recent commits
133+
GitHub.listCommitsSince address since >>= case _ of
125134
Left err -> do
126-
Log.debug $ "Failed to fetch tags for " <> PackageName.print name <> ": " <> Octokit.printGitHubError err
135+
Log.debug $ "Failed to fetch commits for " <> PackageName.print name <> ": " <> Octokit.printGitHubError err
136+
pure 0
137+
Right [] -> do
138+
-- No recent commits, skip fetching tags
127139
pure 0
128-
Right tags -> do
129-
let
130-
-- Combine published and unpublished versions into a set
131-
publishedVersions = Set.fromFoldable
132-
$ Map.keys metadata.published
133-
<> Map.keys metadata.unpublished
134-
135-
-- Parse tags as versions and filter out already published ones
136-
newVersions = Array.catMaybes $ tags <#> \tag ->
137-
case LenientVersion.parse tag.name of
138-
Left _ -> Nothing -- Not a valid version tag
139-
Right result ->
140-
let
141-
version = LenientVersion.version result
142-
in
143-
if Set.member version publishedVersions then Nothing
144-
else Just { version, ref: tag.name }
145-
146-
-- Submit publish jobs for new versions
147-
count <- for newVersions \{ version, ref } -> do
148-
submitPublishJob mode registryApiUrl name version ref
149-
150-
pure $ Array.length $ Array.filter identity count
140+
Right recentCommitShas -> do
141+
let recentShas = Set.fromFoldable recentCommitShas
142+
-- There are recent commits, now fetch tags to see if any point to them
143+
GitHub.listTags address >>= case _ of
144+
Left err -> do
145+
Log.debug $ "Failed to fetch tags for " <> PackageName.print name <> ": " <> Octokit.printGitHubError err
146+
pure 0
147+
Right tags -> do
148+
let
149+
publishedVersions = combinedPublishedVersions { published: metadata.published, unpublished: metadata.unpublished }
150+
newVersions = findNewVersions tags recentShas publishedVersions
151+
152+
-- Submit publish jobs for new versions
153+
count <- for newVersions \{ version, ref } -> do
154+
submitPublishJob mode registryApiUrl name version ref
155+
156+
pure $ Array.length $ Array.filter identity count
151157

152158
let totalSubmitted = Array.foldl (+) 0 submitted
153159
Log.info $ "Daily Importer complete. Submitted " <> show totalSubmitted <> " publish jobs."
154160

161+
-- | Combine published and unpublished versions into a set
162+
combinedPublishedVersions :: forall a b. { published :: Map Version a, unpublished :: Map Version b } -> Set Version
163+
combinedPublishedVersions metadata = Set.fromFoldable $ Map.keys metadata.published <> Map.keys metadata.unpublished
164+
165+
-- | Find new version tags that point to recent commits and haven't been published
166+
findNewVersions :: Array Octokit.Tag -> Set String -> Set Version -> Array { version :: Version, ref :: String }
167+
findNewVersions tags recentShas publishedVersions = Array.catMaybes $ tags <#> \tag ->
168+
case LenientVersion.parse tag.name of
169+
Left _ -> Nothing -- Not a valid version tag
170+
Right result -> do
171+
let version = LenientVersion.version result
172+
if not (Set.member tag.sha recentShas) then Nothing -- Tag doesn't point to a recent commit
173+
else if Set.member version publishedVersions then Nothing -- Already published
174+
else Just { version, ref: tag.name }
175+
155176
-- | Submit a publish job for a new package version. The compiler is not specified; the registry
156177
-- | API will discover the latest compatible compiler based on the package's dependencies.
157178
submitPublishJob :: Mode -> URL -> PackageName -> Version -> String -> Run DailyImportEffects Boolean

scripts/src/VerifyIntegrity.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import Data.Either (isLeft)
1212
import Data.Foldable (class Foldable, foldM, intercalate)
1313
import Data.Formatter.DateTime as Formatter.DateTime
1414
import Data.Map as Map
15-
import Effect.Aff as Aff
1615
import Data.Set as Set
1716
import Data.String as String
17+
import Effect.Aff as Aff
1818
import Effect.Class.Console (log)
1919
import Effect.Class.Console as Console
2020
import Node.FS.Aff as FS.Aff

0 commit comments

Comments
 (0)