@foss.global/codefeed 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * autocreated commitinfo by @push.rocks/commitinfo
3
+ */
4
+ export declare const commitinfo: {
5
+ name: string;
6
+ version: string;
7
+ description: string;
8
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * autocreated commitinfo by @push.rocks/commitinfo
3
+ */
4
+ export const commitinfo = {
5
+ name: '@foss.global/codefeed',
6
+ version: '1.2.1',
7
+ description: 'a module for creating feeds for code development'
8
+ };
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx1QkFBdUI7SUFDN0IsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLGtEQUFrRDtDQUNoRSxDQUFBIn0=
@@ -0,0 +1,4 @@
1
+ import * as qenv from '@push.rocks/qenv';
2
+ import * as smartnpm from '@push.rocks/smartnpm';
3
+ import * as smartxml from '@push.rocks/smartxml';
4
+ export { qenv, smartnpm, smartxml, };
@@ -0,0 +1,6 @@
1
+ // @push.rocks
2
+ import * as qenv from '@push.rocks/qenv';
3
+ import * as smartnpm from '@push.rocks/smartnpm';
4
+ import * as smartxml from '@push.rocks/smartxml';
5
+ export { qenv, smartnpm, smartxml, };
6
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29kZWZlZWQucGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NvZGVmZWVkLnBsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYztBQUNkLE9BQU8sS0FBSyxJQUFJLE1BQU0sa0JBQWtCLENBQUM7QUFDekMsT0FBTyxLQUFLLFFBQVEsTUFBTSxzQkFBc0IsQ0FBQztBQUNqRCxPQUFPLEtBQUssUUFBUSxNQUFNLHNCQUFzQixDQUFDO0FBRWpELE9BQU8sRUFDTCxJQUFJLEVBQ0osUUFBUSxFQUNSLFFBQVEsR0FDVCxDQUFBIn0=
@@ -0,0 +1,47 @@
1
+ interface CommitResult {
2
+ baseUrl: string;
3
+ org: string;
4
+ repo: string;
5
+ timestamp: string;
6
+ hash: string;
7
+ commitMessage: string;
8
+ tagged: boolean;
9
+ publishedOnNpm: boolean;
10
+ }
11
+ export declare class CodeFeed {
12
+ private baseUrl;
13
+ private token?;
14
+ private npmRegistry;
15
+ private smartxmlInstance;
16
+ private lastRunTimestamp;
17
+ constructor(baseUrl: string, token?: string, lastRunTimestamp?: string);
18
+ /**
19
+ * Fetch all organizations from the Gitea instance.
20
+ */
21
+ private fetchAllOrganizations;
22
+ /**
23
+ * Fetch organization-level activity RSS feed.
24
+ */
25
+ private fetchOrgRssFeed;
26
+ /**
27
+ * Check if the organization's RSS feed has any new activities since the last run.
28
+ */
29
+ private hasNewActivity;
30
+ /**
31
+ * Fetch all repositories accessible to the token/user.
32
+ */
33
+ private fetchAllRepositories;
34
+ /**
35
+ * Fetch all tags for a given repository.
36
+ */
37
+ private fetchTags;
38
+ /**
39
+ * Fetch commits from the last 24 hours for a repository.
40
+ */
41
+ private fetchRecentCommitsForRepo;
42
+ /**
43
+ * Fetch all commits by querying all organizations.
44
+ */
45
+ fetchAllCommitsFromInstance(): Promise<CommitResult[]>;
46
+ }
47
+ export {};
@@ -0,0 +1,238 @@
1
+ import * as plugins from './codefeed.plugins.js';
2
+ export class CodeFeed {
3
+ constructor(baseUrl, token, lastRunTimestamp) {
4
+ this.npmRegistry = new plugins.smartnpm.NpmRegistry();
5
+ this.smartxmlInstance = new plugins.smartxml.SmartXml();
6
+ this.baseUrl = baseUrl;
7
+ this.token = token;
8
+ this.lastRunTimestamp = lastRunTimestamp || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
9
+ console.log('CodeFeed initialized with last run timestamp:', this.lastRunTimestamp);
10
+ }
11
+ /**
12
+ * Fetch all organizations from the Gitea instance.
13
+ */
14
+ async fetchAllOrganizations() {
15
+ const url = `${this.baseUrl}/api/v1/orgs`;
16
+ const response = await fetch(url, {
17
+ headers: this.token ? { Authorization: `token ${this.token}` } : {},
18
+ });
19
+ if (!response.ok) {
20
+ throw new Error(`Failed to fetch organizations: ${response.statusText}`);
21
+ }
22
+ const data = await response.json();
23
+ return data.map((org) => org.username);
24
+ }
25
+ /**
26
+ * Fetch organization-level activity RSS feed.
27
+ */
28
+ async fetchOrgRssFeed(optionsArg) {
29
+ let rssUrl;
30
+ if (optionsArg.orgName && !optionsArg.repoName) {
31
+ rssUrl = `${this.baseUrl}/${optionsArg.orgName}.atom`;
32
+ }
33
+ else if (optionsArg.orgName && optionsArg.repoName) {
34
+ rssUrl = `${this.baseUrl}/${optionsArg.orgName}/${optionsArg.repoName}.atom`;
35
+ }
36
+ const response = await fetch(rssUrl);
37
+ if (!response.ok) {
38
+ throw new Error(`Failed to fetch RSS feed for organization ${optionsArg.orgName}/${optionsArg.repoName}: ${response.statusText}`);
39
+ }
40
+ const rssText = await response.text();
41
+ // Parse the Atom feed using fast-xml-parser
42
+ const rssData = this.smartxmlInstance.parseXmlToObject(rssText);
43
+ // Return the <entry> elements from the feed
44
+ return rssData.feed.entry || [];
45
+ }
46
+ /**
47
+ * Check if the organization's RSS feed has any new activities since the last run.
48
+ */
49
+ async hasNewActivity(optionsArg) {
50
+ const entries = await this.fetchOrgRssFeed(optionsArg);
51
+ // Filter entries to find new activities since the last run
52
+ return entries.some((entry) => {
53
+ const updated = new Date(entry.updated);
54
+ return updated > new Date(this.lastRunTimestamp);
55
+ });
56
+ }
57
+ /**
58
+ * Fetch all repositories accessible to the token/user.
59
+ */
60
+ async fetchAllRepositories() {
61
+ let page = 1;
62
+ const allRepos = [];
63
+ while (true) {
64
+ const url = new URL(`${this.baseUrl}/api/v1/repos/search`);
65
+ url.searchParams.set('limit', '50');
66
+ url.searchParams.set('page', page.toString());
67
+ const resp = await fetch(url.href, {
68
+ headers: this.token ? { 'Authorization': `token ${this.token}` } : {},
69
+ });
70
+ if (!resp.ok) {
71
+ throw new Error(`Failed to fetch repositories: ${resp.statusText}`);
72
+ }
73
+ const data = await resp.json();
74
+ allRepos.push(...data.data);
75
+ if (data.data.length < 50) {
76
+ break;
77
+ }
78
+ page++;
79
+ }
80
+ return allRepos;
81
+ }
82
+ /**
83
+ * Fetch all tags for a given repository.
84
+ */
85
+ async fetchTags(owner, repo) {
86
+ let page = 1;
87
+ const tags = [];
88
+ while (true) {
89
+ const url = new URL(`${this.baseUrl}/api/v1/repos/${owner}/${repo}/tags`);
90
+ url.searchParams.set('limit', '50');
91
+ url.searchParams.set('page', page.toString());
92
+ const resp = await fetch(url.href, {
93
+ headers: this.token ? { 'Authorization': `token ${this.token}` } : {},
94
+ });
95
+ if (!resp.ok) {
96
+ console.error(`Failed to fetch tags for ${owner}/${repo}: ${resp.status} ${resp.statusText} at ${url.href}`);
97
+ throw new Error(`Failed to fetch tags for ${owner}/${repo}: ${resp.statusText}`);
98
+ }
99
+ const data = await resp.json();
100
+ tags.push(...data);
101
+ if (data.length < 50) {
102
+ break;
103
+ }
104
+ page++;
105
+ }
106
+ const taggedCommitShas = new Set();
107
+ for (const t of tags) {
108
+ if (t.commit?.sha) {
109
+ taggedCommitShas.add(t.commit.sha);
110
+ }
111
+ }
112
+ return taggedCommitShas;
113
+ }
114
+ /**
115
+ * Fetch commits from the last 24 hours for a repository.
116
+ */
117
+ async fetchRecentCommitsForRepo(owner, repo) {
118
+ const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
119
+ let page = 1;
120
+ const recentCommits = [];
121
+ while (true) {
122
+ const url = new URL(`${this.baseUrl}/api/v1/repos/${owner}/${repo}/commits`);
123
+ url.searchParams.set('limit', '50');
124
+ url.searchParams.set('page', page.toString());
125
+ const resp = await fetch(url.href, {
126
+ headers: this.token ? { 'Authorization': `token ${this.token}` } : {},
127
+ });
128
+ if (!resp.ok) {
129
+ console.error(`Failed to fetch commits for ${owner}/${repo}: ${resp.status} ${resp.statusText} at ${url.href}`);
130
+ throw new Error(`Failed to fetch commits for ${owner}/${repo}: ${resp.statusText}`);
131
+ }
132
+ const data = await resp.json();
133
+ if (data.length === 0) {
134
+ break;
135
+ }
136
+ for (const commit of data) {
137
+ const commitDate = new Date(commit.commit.author.date);
138
+ if (commitDate > twentyFourHoursAgo) {
139
+ recentCommits.push(commit);
140
+ }
141
+ else {
142
+ return recentCommits;
143
+ }
144
+ }
145
+ page++;
146
+ }
147
+ return recentCommits;
148
+ }
149
+ /**
150
+ * Fetch all commits by querying all organizations.
151
+ */
152
+ async fetchAllCommitsFromInstance() {
153
+ const orgs = await this.fetchAllOrganizations();
154
+ console.log(`Found ${orgs.length} organizations`);
155
+ let allCommits = [];
156
+ for (const orgName of orgs) {
157
+ console.log(`Checking activity for organization: ${orgName}`);
158
+ try {
159
+ const hasActivity = await this.hasNewActivity({
160
+ orgName,
161
+ });
162
+ if (!hasActivity) {
163
+ console.log(`No new activity for organization: ${orgName}`);
164
+ continue;
165
+ }
166
+ }
167
+ catch (error) {
168
+ console.error(`Error fetching activity for organization ${orgName}:`, error.message);
169
+ continue;
170
+ }
171
+ console.log(`New activity detected for organization: ${orgName}. Processing repositories...`);
172
+ const repos = await this.fetchAllRepositories();
173
+ for (const r of repos.filter((repo) => repo.owner.login === orgName)) {
174
+ try {
175
+ const hasActivity = await this.hasNewActivity({
176
+ orgName,
177
+ repoName: r.name,
178
+ });
179
+ if (!hasActivity) {
180
+ console.log(`No new activity for repository: ${orgName}/${r.name}`);
181
+ continue;
182
+ }
183
+ }
184
+ catch (error) {
185
+ console.error(`Error fetching activity for repository ${orgName}/${r.name}:`, error.message);
186
+ continue;
187
+ }
188
+ const org = r.owner.login;
189
+ const repo = r.name;
190
+ console.log(`Processing repository ${org}/${repo}`);
191
+ try {
192
+ const taggedCommitShas = await this.fetchTags(org, repo);
193
+ const commits = await this.fetchRecentCommitsForRepo(org, repo);
194
+ const commitResults = commits.map((c) => ({
195
+ baseUrl: this.baseUrl,
196
+ org,
197
+ repo,
198
+ timestamp: c.commit.author.date,
199
+ hash: c.sha,
200
+ commitMessage: c.commit.message,
201
+ tagged: taggedCommitShas.has(c.sha),
202
+ publishedOnNpm: false,
203
+ }));
204
+ if (commitResults.length > 0) {
205
+ try {
206
+ const packageInfo = await this.npmRegistry.getPackageInfo(`@${org}/${repo}`);
207
+ for (const commit of commitResults.filter((c) => c.tagged)) {
208
+ const correspondingVersion = packageInfo.allVersions.find((versionArg) => {
209
+ return versionArg.version === commit.commitMessage.replace('\n', '');
210
+ });
211
+ if (correspondingVersion) {
212
+ commit.publishedOnNpm = true;
213
+ }
214
+ }
215
+ }
216
+ catch (error) {
217
+ console.error(`Failed to fetch package info for ${org}/${repo}:`, error.message);
218
+ }
219
+ }
220
+ allCommits.push(...commitResults);
221
+ }
222
+ catch (error) {
223
+ console.error(`Skipping repository ${org}/${repo} due to error:`, error.message);
224
+ }
225
+ }
226
+ }
227
+ console.log(`Processed ${allCommits.length} commits in total.`);
228
+ for (const c of allCommits) {
229
+ console.log(`______________________________________________________
230
+ Commit ${c.hash} by ${c.org}/${c.repo} at ${c.timestamp}
231
+ ${c.commitMessage}
232
+ Published on npm: ${c.publishedOnNpm}
233
+ `);
234
+ }
235
+ return allCommits;
236
+ }
237
+ }
238
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHVCQUF1QixDQUFDO0FBOENqRCxNQUFNLE9BQU8sUUFBUTtJQU9uQixZQUFZLE9BQWUsRUFBRSxLQUFjLEVBQUUsZ0JBQXlCO1FBSjlELGdCQUFXLEdBQUcsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQ2pELHFCQUFnQixHQUFHLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUl6RCxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztRQUN2QixJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQztRQUNuQixJQUFJLENBQUMsZ0JBQWdCLEdBQUcsZ0JBQWdCLElBQUksSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQ3JHLE9BQU8sQ0FBQyxHQUFHLENBQUMsK0NBQStDLEVBQUUsSUFBSSxDQUFDLGdCQUFnQixDQUFDLENBQUM7SUFDdEYsQ0FBQztJQUVEOztPQUVHO0lBQ0ssS0FBSyxDQUFDLHFCQUFxQjtRQUNqQyxNQUFNLEdBQUcsR0FBRyxHQUFHLElBQUksQ0FBQyxPQUFPLGNBQWMsQ0FBQztRQUMxQyxNQUFNLFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLEVBQUU7WUFDaEMsT0FBTyxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsYUFBYSxFQUFFLFNBQVMsSUFBSSxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUU7U0FDcEUsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLEVBQUUsQ0FBQztZQUNq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
package/npmextra.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "gitzone": {
3
+ "projectType": "npm",
4
+ "module": {
5
+ "githost": "code.foss.global",
6
+ "gitscope": "foss.global",
7
+ "gitrepo": "codefeed",
8
+ "description": "a module for creating feeds for code development",
9
+ "npmPackagename": "@foss.global/codefeed",
10
+ "license": "MIT",
11
+ "projectDomain": "foss.global"
12
+ }
13
+ },
14
+ "npmci": {
15
+ "npmGlobalTools": [],
16
+ "npmAccessLevel": "public"
17
+ }
18
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@foss.global/codefeed",
3
+ "version": "1.2.1",
4
+ "private": false,
5
+ "description": "a module for creating feeds for code development",
6
+ "main": "dist_ts/index.js",
7
+ "typings": "dist_ts/index.d.ts",
8
+ "type": "module",
9
+ "author": "Task Venture Capital GmbH",
10
+ "license": "MIT",
11
+ "devDependencies": {
12
+ "@git.zone/tsbuild": "^2.1.25",
13
+ "@git.zone/tsbundle": "^2.0.5",
14
+ "@git.zone/tsrun": "^1.2.46",
15
+ "@git.zone/tstest": "^1.0.44",
16
+ "@push.rocks/tapbundle": "^5.0.15",
17
+ "@types/node": "^20.8.7"
18
+ },
19
+ "dependencies": {
20
+ "@push.rocks/qenv": "^6.1.0",
21
+ "@push.rocks/smartnpm": "^2.0.4",
22
+ "@push.rocks/smartxml": "^1.0.8"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://code.foss.global/foss.global/codefeed.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://code.foss.global/foss.global/codefeed/issues"
30
+ },
31
+ "homepage": "https://code.foss.global/foss.global/codefeed#readme",
32
+ "files": [
33
+ "ts/**/*",
34
+ "ts_web/**/*",
35
+ "dist/**/*",
36
+ "dist_*/**/*",
37
+ "dist_ts/**/*",
38
+ "dist_ts_web/**/*",
39
+ "assets/**/*",
40
+ "cli.js",
41
+ "npmextra.json",
42
+ "readme.md"
43
+ ],
44
+ "scripts": {
45
+ "test": "(tstest test/ --web)",
46
+ "build": "(tsbuild --web --allowimplicitany)",
47
+ "buildDocs": "(tsdoc)"
48
+ }
49
+ }
@@ -0,0 +1,3 @@
1
+ # Project Readme Hints
2
+
3
+ This is the initial readme hints file.
package/readme.md ADDED
@@ -0,0 +1,7 @@
1
+ # @foss.global/codefeed
2
+
3
+ a module for creating feeds for code development
4
+
5
+ ## How to create the docs
6
+
7
+ To create docs run gitzone aidoc.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * autocreated commitinfo by @push.rocks/commitinfo
3
+ */
4
+ export const commitinfo = {
5
+ name: '@foss.global/codefeed',
6
+ version: '1.2.1',
7
+ description: 'a module for creating feeds for code development'
8
+ }
@@ -0,0 +1,10 @@
1
+ // @push.rocks
2
+ import * as qenv from '@push.rocks/qenv';
3
+ import * as smartnpm from '@push.rocks/smartnpm';
4
+ import * as smartxml from '@push.rocks/smartxml';
5
+
6
+ export {
7
+ qenv,
8
+ smartnpm,
9
+ smartxml,
10
+ }
package/ts/index.ts ADDED
@@ -0,0 +1,329 @@
1
+ import * as plugins from './codefeed.plugins.js';
2
+
3
+ interface RepositoryOwner {
4
+ login: string;
5
+ }
6
+
7
+ interface Repository {
8
+ owner: RepositoryOwner;
9
+ name: string;
10
+ }
11
+
12
+ interface CommitAuthor {
13
+ date: string;
14
+ }
15
+
16
+ interface CommitDetail {
17
+ message: string;
18
+ author: CommitAuthor;
19
+ }
20
+
21
+ interface Commit {
22
+ sha: string;
23
+ commit: CommitDetail;
24
+ }
25
+
26
+ interface Tag {
27
+ commit?: {
28
+ sha?: string;
29
+ };
30
+ }
31
+
32
+ interface RepoSearchResponse {
33
+ data: Repository[];
34
+ }
35
+
36
+ interface CommitResult {
37
+ baseUrl: string;
38
+ org: string;
39
+ repo: string;
40
+ timestamp: string;
41
+ hash: string;
42
+ commitMessage: string;
43
+ tagged: boolean;
44
+ publishedOnNpm: boolean;
45
+ }
46
+
47
+ export class CodeFeed {
48
+ private baseUrl: string;
49
+ private token?: string;
50
+ private npmRegistry = new plugins.smartnpm.NpmRegistry();
51
+ private smartxmlInstance = new plugins.smartxml.SmartXml();
52
+ private lastRunTimestamp: string;
53
+
54
+ constructor(baseUrl: string, token?: string, lastRunTimestamp?: string) {
55
+ this.baseUrl = baseUrl;
56
+ this.token = token;
57
+ this.lastRunTimestamp = lastRunTimestamp || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
58
+ console.log('CodeFeed initialized with last run timestamp:', this.lastRunTimestamp);
59
+ }
60
+
61
+ /**
62
+ * Fetch all organizations from the Gitea instance.
63
+ */
64
+ private async fetchAllOrganizations(): Promise<string[]> {
65
+ const url = `${this.baseUrl}/api/v1/orgs`;
66
+ const response = await fetch(url, {
67
+ headers: this.token ? { Authorization: `token ${this.token}` } : {},
68
+ });
69
+
70
+ if (!response.ok) {
71
+ throw new Error(`Failed to fetch organizations: ${response.statusText}`);
72
+ }
73
+
74
+ const data: { username: string }[] = await response.json();
75
+ return data.map((org) => org.username);
76
+ }
77
+
78
+ /**
79
+ * Fetch organization-level activity RSS feed.
80
+ */
81
+ private async fetchOrgRssFeed(optionsArg: {
82
+ orgName: string,
83
+ repoName?: string,
84
+ }): Promise<any[]> {
85
+ let rssUrl: string
86
+ if (optionsArg.orgName && !optionsArg.repoName) {
87
+ rssUrl = `${this.baseUrl}/${optionsArg.orgName}.atom`;
88
+ } else if (optionsArg.orgName && optionsArg.repoName) {
89
+ rssUrl = `${this.baseUrl}/${optionsArg.orgName}/${optionsArg.repoName}.atom`;
90
+ }
91
+
92
+ const response = await fetch(rssUrl);
93
+ if (!response.ok) {
94
+ throw new Error(`Failed to fetch RSS feed for organization ${optionsArg.orgName}/${optionsArg.repoName}: ${response.statusText}`);
95
+ }
96
+
97
+ const rssText = await response.text();
98
+
99
+ // Parse the Atom feed using fast-xml-parser
100
+ const rssData = this.smartxmlInstance.parseXmlToObject(rssText);
101
+
102
+ // Return the <entry> elements from the feed
103
+ return rssData.feed.entry || [];
104
+ }
105
+
106
+ /**
107
+ * Check if the organization's RSS feed has any new activities since the last run.
108
+ */
109
+ private async hasNewActivity(optionsArg: {
110
+ orgName: string,
111
+ repoName?: string,
112
+ }): Promise<boolean> {
113
+ const entries = await this.fetchOrgRssFeed(optionsArg);
114
+
115
+ // Filter entries to find new activities since the last run
116
+ return entries.some((entry: any) => {
117
+ const updated = new Date(entry.updated);
118
+ return updated > new Date(this.lastRunTimestamp);
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Fetch all repositories accessible to the token/user.
124
+ */
125
+ private async fetchAllRepositories(): Promise<Repository[]> {
126
+ let page = 1;
127
+ const allRepos: Repository[] = [];
128
+
129
+ while (true) {
130
+ const url = new URL(`${this.baseUrl}/api/v1/repos/search`);
131
+ url.searchParams.set('limit', '50');
132
+ url.searchParams.set('page', page.toString());
133
+
134
+ const resp = await fetch(url.href, {
135
+ headers: this.token ? { 'Authorization': `token ${this.token}` } : {},
136
+ });
137
+
138
+ if (!resp.ok) {
139
+ throw new Error(`Failed to fetch repositories: ${resp.statusText}`);
140
+ }
141
+
142
+ const data: RepoSearchResponse = await resp.json();
143
+ allRepos.push(...data.data);
144
+
145
+ if (data.data.length < 50) {
146
+ break;
147
+ }
148
+ page++;
149
+ }
150
+
151
+ return allRepos;
152
+ }
153
+
154
+ /**
155
+ * Fetch all tags for a given repository.
156
+ */
157
+ private async fetchTags(owner: string, repo: string): Promise<Set<string>> {
158
+ let page = 1;
159
+ const tags: Tag[] = [];
160
+
161
+ while (true) {
162
+ const url = new URL(`${this.baseUrl}/api/v1/repos/${owner}/${repo}/tags`);
163
+ url.searchParams.set('limit', '50');
164
+ url.searchParams.set('page', page.toString());
165
+
166
+ const resp = await fetch(url.href, {
167
+ headers: this.token ? { 'Authorization': `token ${this.token}` } : {},
168
+ });
169
+
170
+ if (!resp.ok) {
171
+ console.error(`Failed to fetch tags for ${owner}/${repo}: ${resp.status} ${resp.statusText} at ${url.href}`);
172
+ throw new Error(`Failed to fetch tags for ${owner}/${repo}: ${resp.statusText}`);
173
+ }
174
+
175
+ const data: Tag[] = await resp.json();
176
+ tags.push(...data);
177
+
178
+ if (data.length < 50) {
179
+ break;
180
+ }
181
+ page++;
182
+ }
183
+
184
+ const taggedCommitShas = new Set<string>();
185
+ for (const t of tags) {
186
+ if (t.commit?.sha) {
187
+ taggedCommitShas.add(t.commit.sha);
188
+ }
189
+ }
190
+
191
+ return taggedCommitShas;
192
+ }
193
+
194
+ /**
195
+ * Fetch commits from the last 24 hours for a repository.
196
+ */
197
+ private async fetchRecentCommitsForRepo(owner: string, repo: string): Promise<Commit[]> {
198
+ const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
199
+ let page = 1;
200
+ const recentCommits: Commit[] = [];
201
+
202
+ while (true) {
203
+ const url = new URL(`${this.baseUrl}/api/v1/repos/${owner}/${repo}/commits`);
204
+ url.searchParams.set('limit', '50');
205
+ url.searchParams.set('page', page.toString());
206
+
207
+ const resp = await fetch(url.href, {
208
+ headers: this.token ? { 'Authorization': `token ${this.token}` } : {},
209
+ });
210
+
211
+ if (!resp.ok) {
212
+ console.error(`Failed to fetch commits for ${owner}/${repo}: ${resp.status} ${resp.statusText} at ${url.href}`);
213
+ throw new Error(`Failed to fetch commits for ${owner}/${repo}: ${resp.statusText}`);
214
+ }
215
+
216
+ const data: Commit[] = await resp.json();
217
+ if (data.length === 0) {
218
+ break;
219
+ }
220
+
221
+ for (const commit of data) {
222
+ const commitDate = new Date(commit.commit.author.date);
223
+ if (commitDate > twentyFourHoursAgo) {
224
+ recentCommits.push(commit);
225
+ } else {
226
+ return recentCommits;
227
+ }
228
+ }
229
+
230
+ page++;
231
+ }
232
+
233
+ return recentCommits;
234
+ }
235
+
236
+ /**
237
+ * Fetch all commits by querying all organizations.
238
+ */
239
+ public async fetchAllCommitsFromInstance(): Promise<CommitResult[]> {
240
+ const orgs = await this.fetchAllOrganizations();
241
+ console.log(`Found ${orgs.length} organizations`);
242
+ let allCommits: CommitResult[] = [];
243
+
244
+ for (const orgName of orgs) {
245
+ console.log(`Checking activity for organization: ${orgName}`);
246
+
247
+ try {
248
+ const hasActivity = await this.hasNewActivity({
249
+ orgName,
250
+ });
251
+ if (!hasActivity) {
252
+ console.log(`No new activity for organization: ${orgName}`);
253
+ continue;
254
+ }
255
+ } catch (error) {
256
+ console.error(`Error fetching activity for organization ${orgName}:`, error.message);
257
+ continue;
258
+ }
259
+
260
+ console.log(`New activity detected for organization: ${orgName}. Processing repositories...`);
261
+
262
+ const repos = await this.fetchAllRepositories();
263
+ for (const r of repos.filter((repo) => repo.owner.login === orgName)) {
264
+ try {
265
+ const hasActivity = await this.hasNewActivity({
266
+ orgName,
267
+ repoName: r.name,
268
+ });
269
+ if (!hasActivity) {
270
+ console.log(`No new activity for repository: ${orgName}/${r.name}`);
271
+ continue;
272
+ }
273
+ } catch (error) {
274
+ console.error(`Error fetching activity for repository ${orgName}/${r.name}:`, error.message);
275
+ continue;
276
+ }
277
+ const org = r.owner.login;
278
+ const repo = r.name;
279
+ console.log(`Processing repository ${org}/${repo}`);
280
+
281
+ try {
282
+ const taggedCommitShas = await this.fetchTags(org, repo);
283
+ const commits = await this.fetchRecentCommitsForRepo(org, repo);
284
+
285
+ const commitResults = commits.map((c) => ({
286
+ baseUrl: this.baseUrl,
287
+ org,
288
+ repo,
289
+ timestamp: c.commit.author.date,
290
+ hash: c.sha,
291
+ commitMessage: c.commit.message,
292
+ tagged: taggedCommitShas.has(c.sha),
293
+ publishedOnNpm: false,
294
+ }));
295
+
296
+ if (commitResults.length > 0) {
297
+ try {
298
+ const packageInfo = await this.npmRegistry.getPackageInfo(`@${org}/${repo}`);
299
+ for (const commit of commitResults.filter((c) => c.tagged)) {
300
+ const correspondingVersion = packageInfo.allVersions.find((versionArg) => {
301
+ return versionArg.version === commit.commitMessage.replace('\n', '');
302
+ });
303
+ if (correspondingVersion) {
304
+ commit.publishedOnNpm = true;
305
+ }
306
+ }
307
+ } catch (error) {
308
+ console.error(`Failed to fetch package info for ${org}/${repo}:`, error.message);
309
+ }
310
+ }
311
+
312
+ allCommits.push(...commitResults);
313
+ } catch (error) {
314
+ console.error(`Skipping repository ${org}/${repo} due to error:`, error.message);
315
+ }
316
+ }
317
+ }
318
+
319
+ console.log(`Processed ${allCommits.length} commits in total.`);
320
+ for (const c of allCommits) {
321
+ console.log(`______________________________________________________
322
+ Commit ${c.hash} by ${c.org}/${c.repo} at ${c.timestamp}
323
+ ${c.commitMessage}
324
+ Published on npm: ${c.publishedOnNpm}
325
+ `);
326
+ }
327
+ return allCommits;
328
+ }
329
+ }