Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion .semver.build.tag
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.0+41161
0.3.3+50090
5 changes: 3 additions & 2 deletions .semver.commit.tag
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Last Commit: commit 28c1bb07d3d3950a3bc609c6406ca58f193e7d76
Build No: 50090
Last Commit: commit b097678b3fb3c703dc5b31bff7acbe65d6035769
Author: Lyns <lotsofthoughts@lynsei.com>
Date: Fri Jun 27 15:42:52 2025 -0400
Date: Wed Jul 9 16:04:00 2025 -0400
2 changes: 1 addition & 1 deletion .semver.version.tag
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.1
0.3.3
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ other objects.
| `$error` | Generic error functions for including messages with specific colors |
| `$file` | File regex and comparison functions. |
| `$perms` | Permissions scanning functions |
| `$ruleset` | Github Rulesets and Rule scopes for detecting branch protections |
| `$ruleset` | Github Rulesets next generation and support for legacy Branch Protections |
| `$report` | Async reporting stream for tabulation of callback data into a simplified report stream |
| `$reporting` | Reporting object and the concept of hooks and custom entry types/ exceptions. |
| `$streams` | Optic FileStream object for logging colored error messages to evaluated streams |
Expand Down
5 changes: 4 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@softdist/extensions",
"version": "0.3.1",
"version": "0.3.3+50090",
"license": "MIT",
"exports": "./src/mod.ts",
"test": {
Expand Down Expand Up @@ -56,6 +56,9 @@
},
"imports": {
"@octokit/core": "npm:@octokit/core@^7.0.2",
"@octokit/plugin-retry": "npm:@octokit/plugin-retry@^8.0.1",
"@octokit/plugin-throttling": "npm:@octokit/plugin-throttling@^11.0.1",
"@octokit/rest": "npm:@octokit/rest@^22.0.0",
"@onjara/optic": "jsr:@onjara/optic@^2.0.3",
"@alikia/random-key": "jsr:@alikia/random-key@^1.0.1",
"@deno/dnt": "jsr:@deno/dnt@^0.42.1",
Expand Down
55 changes: 55 additions & 0 deletions deno.lock

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

53 changes: 53 additions & 0 deletions release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,59 @@ _An abstract extension system for inspection of Github scopes and settings_
- Package: [@softdist/extensions](https://jsr.io/@softdist/extensions)
- Repository: [@pkgdist/extensions](https://github.com/pkgdist/extensions)

## ^0.3.3

- Added a write lock queue for report class aggregate file saves to fix a bug
where parallel write truncate the report unintentionally

## ^0.3.2

- Added Throttle and Retry Plugins to default Octokit.
- Added Octokit.graphql and OctokitRest object called as:

```typescript
const octokit = await $octokit.initOctokitWithThrottlingAndRetry('core')
// or REST
const octokit = await $octokit.initOctokitWithThrottlingAndRetry('rest')
```

- Added Legacy Branch Protections API:

| Name | Description |
| :--------- | :----------------------------------------------------------- |
| func_rules | inspectDetailedBranchProtection function supports legacy API |

## Support for Branch Protections API

| **ID** | **Description** | **Object** | **Logic** |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------- |
| branch-require-pull-request-before-merging | Branch requires all commits must be made to a non-protected branch and submitted via a pull request before they can be merged into a branch that matches this rule. | required_pull_request_reviews | If object exists |
| branch-require-pull-request-approvals | Branch requires pull requests targeting a matching branch require a number of approvals and no changes requested before they can be merged. | required_pull_request_reviews.required_approving_review_count | If number > 0 |
| Branch-require-pull-request-approval-number | Branch requires pull requests have this number of unique approvers | required_pull_request_reviews.required_approving_review_count | If number exists |
| branch-dismiss-stale-reviews | Branch requires PR checks will dismiss stale pull request approvals when new commits are pushed. | required_pull_request_reviews.dismiss_stale_reviews | If boolean true |
| branch-require-code-owner-reviews | Branch requires code owner reviews before merging. | required_pull_request_reviews.require_code_owner_reviews | If boolean true |
| branch-require-dismissal-restrictions | Branch require certain users, teams, or apps can dismiss restrictions on pull requests | required_pull_request_reviews.dismissal_restrictions | If object exists |
| branch-require-bypass-allowances | Branch requires certain users, teams, or apps can bypass required pull requests | required_pull_request_reviews.bypass_pull_request_allowances | If object exists |
| branch-allow-force-pushes | Branch requires most recent reviewable push must be approved by someone other than the person who pushed it. | required_pull_request_reviews.require_last_push_approval | If boolean is true |
| branch-require-status-checks | Branch requires status checks to pass before merging. | required_status_checks | If object exists |
| branch-require-strict-status-checks | Require branches to be up to date before merging. | required_status_checks.strict | If boolean true |
| branch-require-status-checks-contexts | Branch requires items of the list for status checks exist | required_status_checks.contexts | If array contains items |
| branch-require-conversation-resolution | Require all conversations to be resolved before merging. | required_conversation_resolution.enabled | If boolean true |
| branch-require-signed-commits | Branch requires signed commits on PR to main/master. | required_signatures.enabled | If boolean true |
| branch-require-linear-history | Branch requires PR will require linear commit history. | required_linear_history.enabled | If boolean true |
| branch-lock-branch | Branch requires the branch is read-only. Users cannot push to the branch. | lock_branch.enabled | If boolean true |
| branch-enforce-admins | Enforce branch protection for administrators. | enforce_admins.enabled | If boolean true |
| branch-restrict-push | Branch restricts who can push | restrictions | If object exists |
| branch-restrict-new | Branch restricts new branch creation | restrictions | If object exists |
| branch-allow-force-pushes | Allow force pushes to matching branches. | allow_force_pushes.enabled | If boolean is true |
| branch-allow-deletions | Allow deletion of matching branches. | allow_deletions.enabled | If boolean is true |
| branch-lock-branch | Lock matching branches. | lock_branch.enabled | If boolean is true |
| branch-copilot-code-review-enabled | Require Copilot AI code review agent for PRs to main branch. | NOT AVAILABLE IN API | |
| branch-restrict-commit-metadata | Restrict commit metadata (e.g., author, committer, dates). | NOT AVAILABLE IN API | |
| branch-require-codescanning-results | Require code scanning results before merging. | NOT AVAILABLE IN API | |
| branch-merge-queue | Branch Merge Queue settings | NOT AVAILABLE IN API | |
| branch-require-review-before-deployment | Branch requires review before deploy | NOT AVAILABLE IN API | |

## ^0.3.1

Additional comparison functions and permissions functions:
Expand Down
94 changes: 94 additions & 0 deletions src/check_branch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { log } from 'node:console'
import * as $ruleset from './func_rules.ts'
import * as $token from './func_token.ts'
import * as $reporting from './class_reporting.ts'
import { ExtensionSystemTypes as Type } from './types.d.ts'

const token = await $token.getToken()

if (!token) {
console.error(
'No GitHub token found. Please set the GITHUB_TOKEN environment variable.',
)
Deno.exit(1)
}

const repoData = {
token: token,
owner: Deno.env.get('PROD_OWNER')!,
repository: Deno.env.get('PROD_REPO')!,
branch: 'main',
}

const branch = await $ruleset.inspectDetailedBranchProtection(
token,
repoData.owner,
repoData.repository,
repoData.branch,
)
console.log(branch)

const branch_results = $ruleset.findParamPaths(
{ str: branch },
'required_pull_request_reviews',
)

console.log(
Array.isArray(branch_results) && branch_results.length > 0 ? 'true' : 'false',
)

// mock the reporting for fun
const report = await $reporting.createReport([], 'report_aggregate.json')
type ReportRecord = Record<string, unknown>
await report.addEntry(
'aggregate_report',
$reporting.createReportEntry<Type.ReportEntryWithNone<ReportRecord>>({
score: 2,
rule: 'branch-require-pull-request-before-merging',
description:
'Branch Protections requires all commits must be made to a non-protected branch and submitted via a pull request before they can be merged into the [main|master|default] branch',
repo: 'test',
path: 'test',
success: true,
customFields: {
notify: 'teams1',
owner: 'dynamic.owner',
},
}),
)

/* NOSONAR_START

// this code shows a manual API process for branch protection
// we used this for debug but I want to keep it for reference
try {
const branch_result = await octokit.request(
'GET /repos/{owner}/{repo}/branches/{branch}/protection',
{
owner: repoData.owner,
repo: repoData.repository,
branch: repoData.branch,
}
)


const rulesets = await octokit.request(
'GET /repos/{owner}/{repo}/rulesets',
{
owner: repoData.owner,
repo: repoData.repository,
branch: repoData.branch,
}
)

console.log(JSON.stringify(rulesets, null, 2))



} catch (error) {
console.error('Error fetching branch protection:', error)
}



NOSONAR_END */
13 changes: 9 additions & 4 deletions src/class_reporting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class Reporting<T> {
private aggregate: { repos: Record<string, T[]> } = { repos: {} }
private hooks: Array<(entry: Type.GenericReportEntry<T>) => Promise<void>> =
[]
private writeLock: Promise<void> = Promise.resolve()

constructor(private outputFile: string = 'report_aggregate.json') {}

Expand Down Expand Up @@ -120,10 +121,14 @@ export class Reporting<T> {

// Save the aggregate report as JSON
private async save() {
await Deno.writeTextFile(
this.outputFile,
JSON.stringify(this.aggregate, null, 2),
)
// Wait for any ongoing write to finish using a write lock queue
this.writeLock = this.writeLock.then(async () => {
await Deno.writeTextFile(
this.outputFile,
JSON.stringify(this.aggregate, null, 2),
)
})
await this.writeLock
}

// Optionally, load an existing report file
Expand Down
1 change: 1 addition & 0 deletions src/func_branch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import * as $octokit from './func_octokit.ts'
Loading
Loading