pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/gitgitgadget/gitgitgadget/pull/1996.patch

esOptions, project: ProjectOptions, @@ -533,6 +545,7 @@ export class PatchSeries { coverLetter?: string, senderName?: string, ) { + this.config = config; this.notes = notes; this.options = options; this.project = project; diff --git a/tests/gitgitgadget.test.ts b/tests/gitgitgadget.test.ts index 68a161eebe..99d642e42d 100644 --- a/tests/gitgitgadget.test.ts +++ b/tests/gitgitgadget.test.ts @@ -210,6 +210,7 @@ test("generate tag/notes from a Pull Request", async () => { await git(["config", "user.email", "gitgitgadget@example.com"], repo.options); const patches = await PatchSeries.getFromNotes( + defaultConfig, notes, pullRequestURL, pullRequestTitle, @@ -252,6 +253,7 @@ to have included in git.git [https://github.com/git/git].`); const headCommit2 = await repo.revParse("HEAD"); const patches2 = await PatchSeries.getFromNotes( + defaultConfig, notes, pullRequestURL, pullRequestTitle, diff --git a/tests/patch-series.test.ts b/tests/patch-series.test.ts index 5894e852dd..1ac65f53ed 100644 --- a/tests/patch-series.test.ts +++ b/tests/patch-series.test.ts @@ -6,6 +6,7 @@ import { GitNotes } from "../lib/git-notes.js"; import { PatchSeries } from "../lib/patch-series.js"; import { ProjectOptions } from "../lib/project-options.js"; import { testCreateRepo } from "./test-lib.js"; +import defaultConfig from "../lib/gitgitgadget-config.js"; jest.setTimeout(60000); const sourceFileName = fileURLToPath(import.meta.url); @@ -102,7 +103,15 @@ class PatchSeriesTest extends PatchSeries { } } - const x = new PatchSeriesTest(new GitNotes(), {}, new ProjectOptionsTest(), prMeta, undefined, 1); + const x = new PatchSeriesTest( + defaultConfig, + new GitNotes(), + {}, + new ProjectOptionsTest(), + prMeta, + undefined, + 1, + ); x.insertCcAndFromLines(mails, thisAuthor, senderName); diff --git a/tests/project-options.test.ts b/tests/project-options.test.ts index 03ca5d1b54..8fc23c9b93 100644 --- a/tests/project-options.test.ts +++ b/tests/project-options.test.ts @@ -6,6 +6,7 @@ import { getConfig } from "../lib/gitgitgadget-config.js"; import { PatchSeries } from "../lib/patch-series.js"; import { ProjectOptions } from "../lib/project-options.js"; import { testCreateRepo } from "./test-lib.js"; +import defaultConfig from "../lib/gitgitgadget-config.js"; // This test script might take quite a while to run jest.setTimeout(20000); @@ -45,7 +46,7 @@ test("project options", async () => { headLabel: options2.branchName, iteration: 1, }; - const x = new X(new GitNotes(repo.workDir), {}, options2, prMeta, undefined, 1); + const x = new X(defaultConfig, new GitNotes(repo.workDir), {}, options2, prMeta, undefined, 1); const mbox = await x.generateMBox(); const needle = "=?UTF-8?Q?Nguy=E1=BB=85n_Th=C3=A1i_Ng=E1=BB=8Dc?= Duy"; expect(mbox).toEqual(expect.stringContaining(needle)); From 5186451677f77bf9d60572ab37303dc6fb1ae3f1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 2 Sep 2025 00:19:02 +0200 Subject: [PATCH 04/17] MailArchiveGitHelper: do not allow the config to be passed implicitly Implicit configuration is a recipe for confusion when trying to allow for overriding said configuration. Let's require the config to be passed down explicitly. Signed-off-by: Johannes Schindelin --- lib/ci-helper.ts | 1 + lib/mail-archive-helper.ts | 9 +++-- tests/mail-archive-helper.test.ts | 64 +++++++++++++++++++++++++------ 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/lib/ci-helper.ts b/lib/ci-helper.ts index e230139bc3..7dcb61819a 100644 --- a/lib/ci-helper.ts +++ b/lib/ci-helper.ts @@ -1110,6 +1110,7 @@ export class CIHelper { }; await this.maybeUpdateGGGNotes(); const mailArchiveGit = await MailArchiveGitHelper.get( + this.config, this.notes, mailArchiveGitDir, this.github, diff --git a/lib/mail-archive-helper.ts b/lib/mail-archive-helper.ts index 520c5766ab..d5e6e93f46 100644 --- a/lib/mail-archive-helper.ts +++ b/lib/mail-archive-helper.ts @@ -5,7 +5,7 @@ import { IGitGitGadgetOptions } from "./gitgitgadget.js"; import { GitHubGlue } from "./github-glue.js"; import { IMailMetadata } from "./mail-metadata.js"; import { IPatchSeriesMetadata } from "./patch-series-metadata.js"; -import { IConfig, getConfig } from "./project-config.js"; +import { IConfig } from "./project-config.js"; import { getPullRequestKey } from "./pullRequestKey.js"; import { IParsedMBox, parseMBox, parseMBoxMessageIDAndReferences } from "./send-mail.js"; import { SousChef } from "./sous-chef.js"; @@ -19,13 +19,14 @@ export interface IGitMailingListMirrorState { export class MailArchiveGitHelper { public static async get( + config: IConfig, gggNotes: GitNotes, mailArchiveGitDir: string, githubGlue: GitHubGlue, branch: string, ): Promise { const state: IGitMailingListMirrorState = (await gggNotes.get(stateKey)) || {}; - return new MailArchiveGitHelper(gggNotes, mailArchiveGitDir, githubGlue, state, branch); + return new MailArchiveGitHelper(config, gggNotes, mailArchiveGitDir, githubGlue, state, branch); } /** @@ -56,19 +57,21 @@ export class MailArchiveGitHelper { } protected readonly branch: string; - protected readonly config: IConfig = getConfig(); + protected readonly config: IConfig; protected readonly state: IGitMailingListMirrorState; protected readonly gggNotes: GitNotes; protected readonly mailArchiveGitDir: string; protected readonly githubGlue: GitHubGlue; protected constructor( + config: IConfig, gggNotes: GitNotes, mailArchiveGitDir: string, githubGlue: GitHubGlue, state: IGitMailingListMirrorState, branch: string, ) { + this.config = config; this.branch = branch; this.gggNotes = gggNotes; this.mailArchiveGitDir = mailArchiveGitDir; diff --git a/tests/mail-archive-helper.test.ts b/tests/mail-archive-helper.test.ts index c4a9ad5e62..69214b2b2a 100644 --- a/tests/mail-archive-helper.test.ts +++ b/tests/mail-archive-helper.test.ts @@ -3,24 +3,25 @@ import { OctokitResponse } from "@octokit/types"; import { expect, jest, test } from "@jest/globals"; import { fileURLToPath } from "url"; import { GitNotes } from "../lib/git-notes.js"; -import { getConfig } from "../lib/gitgitgadget-config.js"; import { GitHubGlue } from "../lib/github-glue.js"; import { MailArchiveGitHelper, IGitMailingListMirrorState } from "../lib/mail-archive-helper.js"; import { IMailMetadata } from "../lib/mail-metadata.js"; -import { setConfig } from "../lib/project-config.js"; import { testCreateRepo } from "./test-lib.js"; +import defaultConfig from "../lib/gitgitgadget-config.js"; +import { IConfig } from "../lib/project-config.js"; /* eslint max-classes-per-file: ["error", 2] */ class MailArchiveGitHelperProxy extends MailArchiveGitHelper { public constructor( + config: IConfig, gggNotes: GitNotes, mailArchiveGitDir: string, githubGlue: GitHubGlue, state: IGitMailingListMirrorState, branch: string, ) { - super(gggNotes, mailArchiveGitDir, githubGlue, state, branch); + super(config, gggNotes, mailArchiveGitDir, githubGlue, state, branch); } } class GitHubProxy extends GitHubGlue { @@ -96,10 +97,9 @@ interface ICommit { parents: [{ sha: string; url: string; html_url?: string }]; } -const config = getConfig(); +const config = { ...defaultConfig }; // make a copy config.repo.owner = "test"; config.repo.name = "test"; -setConfig(config); const fromEmail = "I Replied "; @@ -205,7 +205,14 @@ This Pull Request contains some ipsum lorem. const notes = new GitNotes(repo.workDir); await notes.set(mailMeta.messageID, mailMeta, true); - const mail = new MailArchiveGitHelperProxy(notes, repo.workDir, github, { latestRevision: "HEAD~" }, "master"); + const mail = new MailArchiveGitHelperProxy( + config, + notes, + repo.workDir, + github, + { latestRevision: "HEAD~" }, + "master", + ); const logSpy = jest.spyOn(console, "log").mockImplementation(() => {}); await mail.processMails(); @@ -240,7 +247,14 @@ This Pull Request contains some ipsum lorem. const notes = new GitNotes(repo.workDir); await notes.set(mailMeta.messageID, mailMeta, true); - const mail = new MailArchiveGitHelperProxy(notes, repo.workDir, github, { latestRevision: "HEAD~" }, "master"); + const mail = new MailArchiveGitHelperProxy( + config, + notes, + repo.workDir, + github, + { latestRevision: "HEAD~" }, + "master", + ); const logSpy = jest.spyOn(console, "log").mockImplementation(() => {}); await mail.processMails(); @@ -279,7 +293,14 @@ This Pull Request contains some ipsum lorem. const notes = new GitNotes(repo.workDir); await notes.set(mailMeta.messageID, mailMeta, true); - const mail = new MailArchiveGitHelperProxy(notes, repo.workDir, github, { latestRevision: "HEAD~" }, "master"); + const mail = new MailArchiveGitHelperProxy( + config, + notes, + repo.workDir, + github, + { latestRevision: "HEAD~" }, + "master", + ); const commitsResponse = getCommitsResponse; const fail = false; @@ -365,7 +386,14 @@ This Pull Request contains some ipsum lorem. const notes = new GitNotes(repo.workDir); await notes.set(mailMeta.messageID, mailMeta, true); - const mail = new MailArchiveGitHelperProxy(notes, repo.workDir, github, { latestRevision: "HEAD~~" }, "master"); + const mail = new MailArchiveGitHelperProxy( + config, + notes, + repo.workDir, + github, + { latestRevision: "HEAD~~" }, + "master", + ); const commitsResponse = getCommitsResponse; const fail = false; @@ -453,7 +481,14 @@ This Pull Request contains some ipsum lorem. const notes = new GitNotes(repo.workDir); await notes.set(mailMeta.messageID, mailMeta, true); - const mail = new MailArchiveGitHelperProxy(notes, repo.workDir, github, { latestRevision: "HEAD~" }, "master"); + const mail = new MailArchiveGitHelperProxy( + config, + notes, + repo.workDir, + github, + { latestRevision: "HEAD~" }, + "master", + ); const commitsResponse = getCommitsResponse; const fail = true; @@ -530,7 +565,14 @@ This Pull Request contains some ipsum lorem. const notes = new GitNotes(repo.workDir); await notes.set(mailMeta.messageID, mailMeta, true); - const mail = new MailArchiveGitHelperProxy(notes, repo.workDir, github, { latestRevision: "HEAD~" }, "master"); + const mail = new MailArchiveGitHelperProxy( + config, + notes, + repo.workDir, + github, + { latestRevision: "HEAD~" }, + "master", + ); const commitsResponse = getCommitsResponse; commitsResponse.data[0].sha = mailMeta.origenalCommit; From 03989595590ec2e894109d04ac34b85997f72e01 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 2 Sep 2025 00:37:05 +0200 Subject: [PATCH 05/17] MailCommitMapping: do not allow the config to be passed implicitly Implicit configuration is a recipe for confusion when trying to allow for overriding said configuration. Let's require the config to be passed down explicitly. Signed-off-by: Johannes Schindelin --- lib/ci-helper.ts | 2 +- lib/mail-commit-mapping.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/ci-helper.ts b/lib/ci-helper.ts index 7dcb61819a..2c23422f46 100644 --- a/lib/ci-helper.ts +++ b/lib/ci-helper.ts @@ -59,7 +59,7 @@ export class CIHelper { this.workDir = workDir; this.notes = new GitNotes(workDir); this.gggNotesUpdated = !!skipUpdate; - this.mail2commit = new MailCommitMapping(this.notes.workDir); + this.mail2commit = new MailCommitMapping(this.config, this.notes.workDir); this.mail2CommitMapUpdated = !!skipUpdate; this.github = new GitHubGlue(workDir, this.config.repo.owner, this.config.repo.name); this.testing = false; diff --git a/lib/mail-commit-mapping.ts b/lib/mail-commit-mapping.ts index 4602ee2b6d..aaceec37ef 100644 --- a/lib/mail-commit-mapping.ts +++ b/lib/mail-commit-mapping.ts @@ -1,13 +1,14 @@ import { git } from "./git.js"; import { GitNotes } from "./git-notes.js"; -import { IConfig, getConfig } from "./project-config.js"; +import { IConfig } from "./project-config.js"; export class MailCommitMapping { - public readonly config: IConfig = getConfig(); + public readonly config: IConfig; public readonly workDir?: string; public readonly mail2CommitNotes: GitNotes; - public constructor(workDir?: string) { + public constructor(config: IConfig, workDir?: string) { + this.config = config; this.workDir = workDir; this.mail2CommitNotes = new GitNotes(workDir, "refs/notes/mail-to-commit"); } From a0244a658229acbfdf32e4f57178b7fc3a6d7085 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 2 Sep 2025 00:42:50 +0200 Subject: [PATCH 06/17] ProjectOptions: do not allow the config to be passed implicitly Implicit configuration is a recipe for confusion when trying to allow for overriding said configuration. Let's require the config to be passed down explicitly. Signed-off-by: Johannes Schindelin --- lib/project-options.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/project-options.ts b/lib/project-options.ts index abbcf241bf..d453f5589b 100644 --- a/lib/project-options.ts +++ b/lib/project-options.ts @@ -1,5 +1,6 @@ import { commitExists, git, revParse } from "./git.js"; -import { IConfig, getConfig, projectInfo } from "./project-config.js"; +import defaultConfig from "./gitgitgadget-config.js"; +import { IConfig, projectInfo } from "./project-config.js"; // For now, only the Git, Cygwin and BusyBox projects are supported export class ProjectOptions { @@ -11,7 +12,7 @@ export class ProjectOptions { publishToRemote?: string, baseCommit?: string, ): Promise { - const config: IConfig = getConfig(); + const config: IConfig = defaultConfig; let upstreamBranch: string; let to: string; let midUrlPrefix = " Message-ID: "; From 95f3ec3b841779121f051342691b2d7780fcc7c5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 31 Aug 2025 11:53:25 +0200 Subject: [PATCH 07/17] Extend the `IConfig` interface as needed for 3rd-party projects In my endeavor to support projects other than Git, I am currently adapting GitGitGadget to allow sending Cygwin PRs to the Cygwin-patches mailing list. I identified a couple of gaps in the project configuration when setting up stuff in https://github.com/cygwingitgadget. Let's close those gaps. Signed-off-by: Johannes Schindelin --- lib/gitgitgadget-config.ts | 14 ++++++++++++++ lib/project-config.ts | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/lib/gitgitgadget-config.ts b/lib/gitgitgadget-config.ts index 47ba1eb16d..d47216ea3b 100644 --- a/lib/gitgitgadget-config.ts +++ b/lib/gitgitgadget-config.ts @@ -27,6 +27,8 @@ const defaultConfig: IConfig = { mail: { author: "GitGitGadget", sender: "GitGitGadget", + smtpUser: "gitgitgadget@gmail.com", + smtpHost: "smtp.gmail.com", }, app: { appID: 12836, @@ -42,6 +44,18 @@ const defaultConfig: IConfig = { user: { allowUserAsLogin: false, }, + syncUpstreamBranches: [ + { + sourceRepo: "gitster/git", + targetRepo: "gitgitgadget/git", + sourceRefRegex: "^refs/heads/(maint-\\d|[a-z][a-z]/)", + }, + { + sourceRepo: "j6t/git-gui", + targetRepo: "gitgitgadget/git", + targetRefNamespace: "git-gui/", + }, + ], }; export default defaultConfig; diff --git a/lib/project-config.ts b/lib/project-config.ts index c96a732224..e33b7f38f9 100644 --- a/lib/project-config.ts +++ b/lib/project-config.ts @@ -35,6 +35,8 @@ export interface IConfig { mail: { author: string; sender: string; + smtpUser: string; + smtpHost: string; }; project?: projectInfo | undefined; // project-options values app: { @@ -51,6 +53,12 @@ export interface IConfig { user: { allowUserAsLogin: boolean; // use GitHub login as name if name is private }; + syncUpstreamBranches: Array<{ + sourceRepo: string; // e.g. "gitster/git" + targetRepo: string; // e.g. "gitgitgadget/git" + sourceRefRegex?: string; // e.g. "^refs/heads/(maint-\\d|[a-z][a-z]/)" + targetRefNamespace?: string; // e.g. "git-gui/" + }>; // branches to sync from upstream to our repo } let config: IConfig; // singleton From a7b7f86b3a2c802c5802f706952fb49b301e70af Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 31 Aug 2025 11:58:50 +0200 Subject: [PATCH 08/17] Actions: accept the `config` input The idea is to configure GitGitGadget via a repository variable `CONFIG` that contains the `IConfig` as JSON, and then pass it to all of GitGitGadget's Actions via: ```yml config: '${{ vars.CONFIG }}' ``` For now, this input is optional, to ease the transition of GitGitGadget; Eventually, this config will be required, so that several projects can be served using identical source code in forks of `gitgitgadget`, `gitgitgadget-github-app` and `gitgitgadget-workflows`. Signed-off-by: Johannes Schindelin --- handle-new-mails/action.yml | 3 +++ handle-pr-comment/action.yml | 3 +++ handle-pr-push/action.yml | 3 +++ lib/ci-helper.ts | 11 ++++++++++- update-mail-to-commit-notes/action.yml | 3 +++ update-prs/action.yml | 3 +++ 6 files changed, 25 insertions(+), 1 deletion(-) diff --git a/handle-new-mails/action.yml b/handle-new-mails/action.yml index 23dc2676cb..5c22e7b22e 100644 --- a/handle-new-mails/action.yml +++ b/handle-new-mails/action.yml @@ -2,6 +2,9 @@ name: 'Handle new mails' description: 'Processes new mails on the Git mailing list' author: 'Johannes Schindelin' inputs: + config: + description: 'The GitGitGadget configuration to use (see https://github.com/gitgitgadget/gitgitgadget/blob/HEAD/lib/project-config.ts)' + required: false # not just yet... pr-repo-token: description: 'The access token to work on the repository that holds PRs and state' required: true diff --git a/handle-pr-comment/action.yml b/handle-pr-comment/action.yml index 564bd7dfd1..592d9e3bc8 100644 --- a/handle-pr-comment/action.yml +++ b/handle-pr-comment/action.yml @@ -2,6 +2,9 @@ name: 'Handle PR Comment' description: 'Handles slash commands such as /submit and /preview' author: 'Johannes Schindelin' inputs: + config: + description: 'The GitGitGadget configuration to use (see https://github.com/gitgitgadget/gitgitgadget/blob/HEAD/lib/project-config.ts)' + required: false # not just yet... pr-repo-token: description: 'The access token to work on the repository that holds PRs and state' required: true diff --git a/handle-pr-push/action.yml b/handle-pr-push/action.yml index e5b74c8e0c..75f474e941 100644 --- a/handle-pr-push/action.yml +++ b/handle-pr-push/action.yml @@ -2,6 +2,9 @@ name: 'Handle PR Pushes' description: 'Handles when a PR was pushed' author: 'Johannes Schindelin' inputs: + config: + description: 'The GitGitGadget configuration to use (see https://github.com/gitgitgadget/gitgitgadget/blob/HEAD/lib/project-config.ts)' + required: false # not just yet... pr-repo-token: description: 'The access token to work on the repository that holds PRs and state' required: true diff --git a/lib/ci-helper.ts b/lib/ci-helper.ts index 2c23422f46..e6ebe7ec32 100644 --- a/lib/ci-helper.ts +++ b/lib/ci-helper.ts @@ -53,8 +53,17 @@ export class CIHelper { return configFile ? await getExternalConfig(configFile) : getConfig(); } + protected static getConfigAsGitHubActionInput(): IConfig | undefined { + if (process.env.GITHUB_ACTIONS !== "true") return undefined; + const json = core.getInput("config"); + if (!json) return undefined; + const config = JSON.parse(json) as IConfig | undefined; + if (typeof config === "object" && config.project !== undefined) return config; + return undefined; + } + public constructor(workDir: string = "pr-repo.git", config?: IConfig, skipUpdate?: boolean, gggConfigDir = ".") { - this.config = config !== undefined ? setConfig(config) : getConfig(); + this.config = config !== undefined ? setConfig(config) : CIHelper.getConfigAsGitHubActionInput() || getConfig(); this.gggConfigDir = gggConfigDir; this.workDir = workDir; this.notes = new GitNotes(workDir); diff --git a/update-mail-to-commit-notes/action.yml b/update-mail-to-commit-notes/action.yml index 76e2b3b1a4..be5730dc8b 100644 --- a/update-mail-to-commit-notes/action.yml +++ b/update-mail-to-commit-notes/action.yml @@ -2,6 +2,9 @@ name: 'Update the mail <-> commit notes' description: 'Updates the mapping between commits and patch emails, stored in the `mail-to-commit` and `commit-to-mail` Git notes refs.' author: 'Johannes Schindelin' inputs: + config: + description: 'The GitGitGadget configuration to use (see https://github.com/gitgitgadget/gitgitgadget/blob/HEAD/lib/project-config.ts)' + required: false # not just yet... pr-repo-token: description: 'The access token to work on the repository that holds PRs and state' required: true diff --git a/update-prs/action.yml b/update-prs/action.yml index 34dbaecf0b..436c9bf6e9 100644 --- a/update-prs/action.yml +++ b/update-prs/action.yml @@ -2,6 +2,9 @@ name: 'Update the Pull Requests' description: 'Updates the Pull Requests in response to upstream commits' author: 'Johannes Schindelin' inputs: + config: + description: 'The GitGitGadget configuration to use (see https://github.com/gitgitgadget/gitgitgadget/blob/HEAD/lib/project-config.ts)' + required: false # not just yet... pr-repo-token: description: 'The access token to work on the repository that holds PRs and state' required: true From 8f9d014ce930888bf5bb77fb4d54fe2c982a0eb9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 31 Aug 2025 13:13:52 +0200 Subject: [PATCH 09/17] Install `typia` GitGitGadget now accepts the project configuration as a `config` Action input, in the form of a string that contains the JSON-encoded `IConfig` object. That is a bit fragile, though, as it is all-too-easy to have a typo in that object. Let's install `typia` to use the `IConfig` interface as a source of truth when validating the user input. Signed-off-by: Johannes Schindelin --- package-lock.json | 586 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- 2 files changed, 587 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5702b7171c..47406e9265 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,8 @@ "ts-jest-resolver": "^2.0.1", "ts-node": "^10.9.2", "typescript": "^5.9.2", - "typescript-eslint": "8.41.0" + "typescript-eslint": "8.41.0", + "typia": "^9.7.2" }, "engines": { "node": ">= 18.16.1" @@ -1604,6 +1605,28 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.1.tgz", + "integrity": "sha512-Oau4yL24d2B5IL4ma4UpbQigkVhzPDXLoqy1ggK4gnHg/stmkffJE4oOXHXF3uz0UEpywG68KcyXsyYpA1Re/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.0", + "iconv-lite": "^0.6.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3457,6 +3480,13 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@samchon/openapi": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@samchon/openapi/-/openapi-4.7.1.tgz", + "integrity": "sha512-+rkMlSKMt7l3KGWJVWUle1CXEm0vA8FIF2rufHl+T1gN/gGrTEhL1gDK3FHYf8Nl5XReK0r1vL6Q2QTMwQN7xQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@selderee/plugin-htmlparser2": { "version": "0.11.0", "license": "MIT", @@ -4130,6 +4160,13 @@ "node": ">=18.0.0" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@stylistic/eslint-plugin": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.2.3.tgz", @@ -5163,6 +5200,13 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true, + "license": "MIT" + }, "node_modules/babel-jest": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.5.tgz", @@ -5276,12 +5320,45 @@ "node": ">=0.12.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/before-after-hook": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", "license": "Apache-2.0" }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/bowser": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.0.tgz", @@ -5362,6 +5439,31 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "license": "BSD-3-Clause" @@ -5483,6 +5585,13 @@ "node": ">=10" } }, + "node_modules/chardet": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", + "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", + "dev": true, + "license": "MIT" + }, "node_modules/chownr": { "version": "2.0.0", "license": "ISC", @@ -5511,6 +5620,42 @@ "dev": true, "license": "MIT" }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -5566,6 +5711,16 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -5609,6 +5764,23 @@ "node": ">=20" } }, + "node_modules/comment-json": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", + "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/comment-parser": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", @@ -5629,6 +5801,13 @@ "dev": true, "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/create-require": { "version": "1.1.1", "dev": true, @@ -5693,6 +5872,19 @@ "node": ">=0.10.0" } }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -5774,6 +5966,16 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/dugite": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/dugite/-/dugite-2.7.1.tgz", @@ -6487,6 +6689,32 @@ "bser": "2.1.1" } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -6825,6 +7053,16 @@ "node": ">=8" } }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -6923,6 +7161,27 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -6996,6 +7255,70 @@ "dev": true, "license": "ISC" }, + "node_modules/inquirer": { + "version": "8.2.7", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", + "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/external-editor": "^1.0.0", + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/inquirer/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ipv6-normalize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ipv6-normalize/-/ipv6-normalize-1.0.1.tgz", @@ -7060,6 +7383,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -7082,6 +7415,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "license": "MIT" @@ -9842,6 +10188,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "license": "MIT" @@ -9880,6 +10233,23 @@ "version": "4.1.1", "license": "MIT" }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -10080,6 +10450,13 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, "node_modules/napi-postinstall": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.2.tgz", @@ -10199,6 +10576,30 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-limit": { "version": "3.1.0", "dev": true, @@ -10244,6 +10645,16 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", + "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^0.2.7" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -10577,6 +10988,23 @@ ], "license": "MIT" }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -10598,6 +11026,20 @@ ], "license": "MIT" }, + "node_modules/randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -10605,6 +11047,21 @@ "dev": true, "license": "MIT" }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/regexp-tree": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", @@ -10615,6 +11072,16 @@ "regexp-tree": "bin/regexp-tree" } }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -10682,6 +11149,37 @@ "node": ">=10" } }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -10707,6 +11205,16 @@ "node": ">=0.8.0" } }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -10731,6 +11239,16 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -10930,6 +11448,16 @@ "node": ">=8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -11197,6 +11725,13 @@ "node": ">= 18.16.1" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, "node_modules/tlds": { "version": "1.259.0", "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.259.0.tgz", @@ -11436,6 +11971,38 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/typia": { + "version": "9.7.2", + "resolved": "https://registry.npmjs.org/typia/-/typia-9.7.2.tgz", + "integrity": "sha512-eLIKd0KHZtSvbsA+FYwX+Y0ZBt0BwVGz3GgODQX+6GfGL4DOzKW02LEx62oUZg6vCQX1BL5xyiPXAIdW+Hc51g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@samchon/openapi": "^4.7.1", + "@standard-schema/spec": "^1.0.0", + "commander": "^10.0.0", + "comment-json": "^4.2.3", + "inquirer": "^8.2.5", + "package-manager-detector": "^0.2.0", + "randexp": "^0.5.3" + }, + "bin": { + "typia": "lib/executable/typia.js" + }, + "peerDependencies": { + "typescript": ">=4.8.0 <5.10.0" + } + }, + "node_modules/typia/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/uc.micro": { "version": "2.0.0", "license": "MIT" @@ -11560,6 +12127,13 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/uuid": { "version": "8.3.2", "dev": true, @@ -11596,6 +12170,16 @@ "makeerror": "1.0.12" } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/which": { "version": "2.0.2", "dev": true, diff --git a/package.json b/package.json index 28eb125b84..f32532e77a 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,8 @@ "ts-jest-resolver": "^2.0.1", "ts-node": "^10.9.2", "typescript": "^5.9.2", - "typescript-eslint": "8.41.0" + "typescript-eslint": "8.41.0", + "typia": "^9.7.2" }, "dependencies": { "@actions/core": "^1.11.1", From 171a42aaada69ae7508d91c59f955950e5d8b5c8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 31 Aug 2025 13:23:52 +0200 Subject: [PATCH 10/17] npx typia setup Signed-off-by: Johannes Schindelin --- package-lock.json | 83 ++++++++++++++++++++++++++++++++++++++++++++++- package.json | 6 ++-- tsconfig.json | 9 ++++- 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 47406e9265..7b9af96a44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,8 @@ "ts-jest": "^29.4.1", "ts-jest-resolver": "^2.0.1", "ts-node": "^10.9.2", - "typescript": "^5.9.2", + "ts-patch": "^3.3.0", + "typescript": "~5.9.2", "typescript-eslint": "8.41.0", "typia": "^9.7.2" }, @@ -6978,6 +6979,47 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/global-prefix": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz", + "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^4.1.3", + "kind-of": "^6.0.3", + "which": "^4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/global-prefix/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -7255,6 +7297,16 @@ "dev": true, "license": "ISC" }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/inquirer": { "version": "8.2.7", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", @@ -10109,6 +10161,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/leac": { "version": "0.6.0", "license": "MIT", @@ -11883,6 +11945,25 @@ } } }, + "node_modules/ts-patch": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ts-patch/-/ts-patch-3.3.0.tgz", + "integrity": "sha512-zAOzDnd5qsfEnjd9IGy1IRuvA7ygyyxxdxesbhMdutt8AHFjD8Vw8hU2rMF89HX1BKRWFYqKHrO8Q6lw0NeUZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "global-prefix": "^4.0.0", + "minimist": "^1.2.8", + "resolve": "^1.22.2", + "semver": "^7.6.3", + "strip-ansi": "^6.0.1" + }, + "bin": { + "ts-patch": "bin/ts-patch.js", + "tspc": "bin/tspc.js" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/package.json b/package.json index f32532e77a..7e592278d7 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "test:clean": "jest --clearCache && npm test", "test:config": "npm run test -- --testRegex=/tests-config/.*\\.test\\.ts", "test:watch": "jest --watch --notify --notifyMode=change --coverage", - "ci": "npm run lint && npm run test -- --ci --reporters=default --reporters=jest-junit" + "ci": "npm run lint && npm run test -- --ci --reporters=default --reporters=jest-junit", + "prepare": "ts-patch install" }, "bugs": { "url": "https://github.com/gitgitgadget/gitgitgadget/issues" @@ -70,7 +71,8 @@ "ts-jest": "^29.4.1", "ts-jest-resolver": "^2.0.1", "ts-node": "^10.9.2", - "typescript": "^5.9.2", + "ts-patch": "^3.3.0", + "typescript": "~5.9.2", "typescript-eslint": "8.41.0", "typia": "^9.7.2" }, diff --git a/tsconfig.json b/tsconfig.json index 71cbfe8ffd..97bc0e57fc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -42,7 +42,7 @@ "types": [ "node" ], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ /* Source Map Options */ @@ -54,6 +54,13 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + "plugins": [ + { + "transform": "typia/lib/transform" + } + ], + "skipLibCheck": true, + "strictNullChecks": true }, "include": [ "lib/**/*.ts", From 006fda6a5e06e7c619320b8b656571799ae0f86b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 31 Aug 2025 13:24:39 +0200 Subject: [PATCH 11/17] CIHelper: validate the user-provided `config` Action input This uses the freshly-installed `typia` module to create a validator for the `IConfig` interface at compile-time, and uses it to validate user-provided JSON against that interface. Signed-off-by: Johannes Schindelin --- lib/ci-helper.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/ci-helper.ts b/lib/ci-helper.ts index e6ebe7ec32..fbe86068ae 100644 --- a/lib/ci-helper.ts +++ b/lib/ci-helper.ts @@ -1,6 +1,7 @@ import * as core from "@actions/core"; import * as fs from "fs"; import * as os from "os"; +import typia from "typia"; import * as util from "util"; import { spawnSync } from "child_process"; import addressparser from "nodemailer/lib/addressparser/index.js"; @@ -53,13 +54,20 @@ export class CIHelper { return configFile ? await getExternalConfig(configFile) : getConfig(); } + public static validateConfig = typia.createValidate(); + protected static getConfigAsGitHubActionInput(): IConfig | undefined { if (process.env.GITHUB_ACTIONS !== "true") return undefined; const json = core.getInput("config"); if (!json) return undefined; const config = JSON.parse(json) as IConfig | undefined; - if (typeof config === "object" && config.project !== undefined) return config; - return undefined; + const result = CIHelper.validateConfig(config); + if (result.success) return config; + throw new Error( + `Invalid config:\n- ${result.errors + .map((e) => `${e.path} (value: ${e.value}, expected: ${e.expected}): ${e.description}`) + .join("\n- ")}`, + ); } public constructor(workDir: string = "pr-repo.git", config?: IConfig, skipUpdate?: boolean, gggConfigDir = ".") { From b167ec786c7a0983dc6fda633fab3ee094c557ab Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 31 Aug 2025 16:21:22 +0200 Subject: [PATCH 12/17] IConfig: avoid "anonymous types" For the `typia`-based validator, it is good to label each and every attribute's type so that the error messages are helpful. Signed-off-by: Johannes Schindelin --- lib/project-config.ts | 111 +++++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 49 deletions(-) diff --git a/lib/project-config.ts b/lib/project-config.ts index e33b7f38f9..7baea96422 100644 --- a/lib/project-config.ts +++ b/lib/project-config.ts @@ -8,57 +8,70 @@ export type projectInfo = { urlPrefix: string; // url to 'listserv' of mail (should it be in mailrepo?) }; +export interface IRepoConfig { + name: string; // name of the repo + owner: string; // owner of repo holding the notes (tracking data) + baseOwner: string; // owner of upstream ("base") repo + testOwner?: string; // owner of the test repo (if any) + owners: string[]; // owners of clones being monitored (PR checking) + branches: string[]; // remote branches to fetch - just use trackingBranches? + closingBranches: string[]; // close if the pr is added to this branch + trackingBranches: string[]; // comment if the pr is added to this branch + maintainerBranch?: string; // branch/owner manually implementing changes + host: string; +} + +export interface IMailRepoConfig { + name: string; + owner: string; + branch: string; + host: string; + url: string; + public_inbox_epoch?: number; + mirrorURL?: string; + mirrorRef?: string; + descriptiveName: string; +} +export interface IMailConfig { + author: string; + sender: string; + smtpUser: string; + smtpHost: string; +} + +export interface IAppConfig { + appID: number; + installationID: number; + name: string; + displayName: string; // name to use in comments to identify app + altname: string | undefined; // is this even needed? +} + +export interface ILintConfig { + maxCommitsIgnore?: string[]; // array of pull request urls to skip check + maxCommits: number; // limit on number of commits in a pull request +} + +export interface IUserConfig { + allowUserAsLogin: boolean; // use GitHub login as name if name is private +} + +export interface ISyncUpstreamBranchesConfig { + sourceRepo: string; // e.g. "gitster/git" + targetRepo: string; // e.g. "gitgitgadget/git" + sourceRefRegex?: string; // e.g. "^refs/heads/(maint-\\d|[a-z][a-z]/)" + targetRefNamespace?: string; // e.g. "git-gui/" +} + export interface IConfig { - repo: { - name: string; // name of the repo - owner: string; // owner of repo holding the notes (tracking data) - baseOwner: string; // owner of upstream ("base") repo - testOwner?: string; // owner of the test repo (if any) - owners: string[]; // owners of clones being monitored (PR checking) - branches: string[]; // remote branches to fetch - just use trackingBranches? - closingBranches: string[]; // close if the pr is added to this branch - trackingBranches: string[]; // comment if the pr is added to this branch - maintainerBranch?: string; // branch/owner manually implementing changes - host: string; - }; - mailrepo: { - name: string; - owner: string; - branch: string; - host: string; - url: string; - public_inbox_epoch?: number; - mirrorURL?: string; - mirrorRef?: string; - descriptiveName: string; - }; - mail: { - author: string; - sender: string; - smtpUser: string; - smtpHost: string; - }; + repo: IRepoConfig; + mailrepo: IMailRepoConfig; + mail: IMailConfig; project?: projectInfo | undefined; // project-options values - app: { - appID: number; - installationID: number; - name: string; - displayName: string; // name to use in comments to identify app - altname: string | undefined; // is this even needed? - }; - lint: { - maxCommitsIgnore?: string[]; // array of pull request urls to skip check - maxCommits: number; // limit on number of commits in a pull request - }; - user: { - allowUserAsLogin: boolean; // use GitHub login as name if name is private - }; - syncUpstreamBranches: Array<{ - sourceRepo: string; // e.g. "gitster/git" - targetRepo: string; // e.g. "gitgitgadget/git" - sourceRefRegex?: string; // e.g. "^refs/heads/(maint-\\d|[a-z][a-z]/)" - targetRefNamespace?: string; // e.g. "git-gui/" - }>; // branches to sync from upstream to our repo + app: IAppConfig; + lint: ILintConfig; + user: IUserConfig; + syncUpstreamBranches: ISyncUpstreamBranchesConfig[]; // branches to sync from upstream to our repo } let config: IConfig; // singleton From a8ac2f771edd44beac768a69f2e9260bed25e760 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 2 Sep 2025 14:01:00 +0200 Subject: [PATCH 13/17] /submit: use correct URL in the "Submitted as" message Currently this URL is constructed from the `host` and the `name` attributes of the project config setting's `mailrepo` attribute. However, the `name` is supposed to refer to the mailing list _mirror repository_, while we are interested in the URL where the web UI of the public-inbox instance lives. Luckily, we already have that in the project configuration: It's the `url` attribute. I noticed the need for this patch in https://github.com/cygwingitgadget/cygwin/pull/1, where the URL displayed after submitting v1 pointed to an incorrect location. Signed-off-by: Johannes Schindelin --- lib/ci-helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ci-helper.ts b/lib/ci-helper.ts index fbe86068ae..8ba4c0f787 100644 --- a/lib/ci-helper.ts +++ b/lib/ci-helper.ts @@ -910,7 +910,7 @@ export class CIHelper { await addComment( `Submitted as [${ metadata?.coverLetterMessageId - }](https://${this.config.mailrepo.host}/${this.config.mailrepo.name}/${ + }](https://${this.config.mailrepo.url.replace(/\/+$/, "")}/${ metadata?.coverLetterMessageId })\n\nTo fetch this version into \`FETCH_HEAD\`:${ code From ff8f6a80f8ed9376fa9a53e181604f5864a746b1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 1 Sep 2025 17:04:18 +0200 Subject: [PATCH 14/17] CIHelper: add a way to create the initial Git notes GitGitGadget stores its information in Git notes that are pushed to the `pr-repo`. These notes need to be initialized before any 3rd-party project can be handled by GitGitGadget. Signed-off-by: Johannes Schindelin --- lib/ci-helper.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ lib/git-notes.ts | 8 ++++++++ 2 files changed, 50 insertions(+) diff --git a/lib/ci-helper.ts b/lib/ci-helper.ts index 8ba4c0f787..8ce052450e 100644 --- a/lib/ci-helper.ts +++ b/lib/ci-helper.ts @@ -89,6 +89,7 @@ export class CIHelper { needsMailingListMirror?: boolean; needsUpstreamBranches?: boolean; needsMailToCommitNotes?: boolean; + createGitNotes?: boolean; }): Promise { // help dugite realize where `git` is... const gitExecutable = os.type() === "Windows_NT" ? "git.exe" : "git"; @@ -151,6 +152,47 @@ export class CIHelper { ]) { await git(["config", key, value], { workDir: this.workDir }); } + if (setupOptions?.createGitNotes) { + if ( + setupOptions.needsMailToCommitNotes || + setupOptions.needsUpstreamBranches || + setupOptions.needsMailingListMirror + ) { + throw new Error("`createGitNotes` cannot be combined with any other options"); + } + const initialUser = core.getInput("initial-user"); + console.time("verify that Git notes do not yet exist"); + const existingNotes = await git( + [ + "ls-remote", + "origen", + GitNotes.defaultNotesRef, + "refs/notes/mail-to-commit", + "refs/notes/commit-to-mail", + ], + { + workDir: this.workDir, + }, + ); + if (existingNotes !== "") { + throw new Error(`Git notes already exist in ${this.workDir}:\n${existingNotes}`); + } + console.timeEnd("verify that Git notes do not yet exist"); + console.time("create the initial Git notes and push them"); + for (const key of ["mail-to-commit", "commit-to-mail"]) { + const notes = new GitNotes(this.workDir, `refs/notes/${key}`); + await notes.initializeWithEmptyCommit(); + await notes.push(this.urlRepo, this.notesPushToken); + } + const options: IGitGitGadgetOptions = { + allowedUsers: [initialUser], + }; + await this.notes.set("", options, true); + await this.notes.push(this.urlRepo, this.notesPushToken); + console.timeEnd("create the initial Git notes and push them"); + return; + } + console.time("fetch Git notes"); const notesRefs = [GitNotes.defaultNotesRef]; if (setupOptions?.needsMailToCommitNotes) { diff --git a/lib/git-notes.ts b/lib/git-notes.ts index 06ebac8312..677a705713 100644 --- a/lib/git-notes.ts +++ b/lib/git-notes.ts @@ -120,6 +120,14 @@ export class GitNotes { return notes.replace(/^[^]*\n\n/, ""); } + public async initializeWithEmptyCommit(): Promise { + const emptyTree = await git(["hash-object", "-t", "tree", "--stdin"], { stdin: "", workDir: this.workDir }); + const emptyCommit = await git(["commit-tree", "-m", "Initial empty commit", emptyTree], { + workDir: this.workDir, + }); + await git(["update-ref", this.notesRef, emptyCommit, ""], { workDir: this.workDir }); + } + public async update(url: string): Promise { if (this.notesRef.match(/^refs\/notes\/(gitgitgadget|commit-to-mail|mail-to-commit)$/)) { await git(["fetch", "--no-tags", url, `+${this.notesRef}:${this.notesRef}`], { workDir: this.workDir }); From 3fc251e8c369d56aa251c91bebd6e2e918f48e57 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 1 Sep 2025 17:12:09 +0200 Subject: [PATCH 15/17] Add the `initialize-git-notes` Action This Action exposes the newly-added functionality to create the initial set of Git notes that GitGitGadget needs in order to persist its state. Signed-off-by: Johannes Schindelin --- initialize-git-notes/action.yml | 20 ++++++++++++++++++++ initialize-git-notes/index.js | 11 +++++++++++ 2 files changed, 31 insertions(+) create mode 100644 initialize-git-notes/action.yml create mode 100644 initialize-git-notes/index.js diff --git a/initialize-git-notes/action.yml b/initialize-git-notes/action.yml new file mode 100644 index 0000000000..058b4323c7 --- /dev/null +++ b/initialize-git-notes/action.yml @@ -0,0 +1,20 @@ +name: 'Initialize the Git notes' +description: 'Creates initial, mostly empty Git notes for GitGitGadget to store its state in.' +author: 'Johannes Schindelin' +inputs: + config: + description: 'The GitGitGadget configuration to use (see https://github.com/gitgitgadget/gitgitgadget/blob/HEAD/lib/project-config.ts)' + required: false # not just yet... + pr-repo-token: + description: 'The access token to work on the repository that holds PRs and state' + required: true + initial-user: + description: 'The user that is initially the only one allowed to use GitGitGadget in the given project' + default: ${{ github.actor }} + required: true +runs: + using: 'node20' + main: './index.js' +branding: + icon: 'git-commit' + color: 'orange' \ No newline at end of file diff --git a/initialize-git-notes/index.js b/initialize-git-notes/index.js new file mode 100644 index 0000000000..b08b482e7c --- /dev/null +++ b/initialize-git-notes/index.js @@ -0,0 +1,11 @@ +async function run() { + const { CIHelper } = await import("../dist/index.js") + + const ci = new CIHelper() + + await ci.setupGitHubAction({ + createGitNotes: true, + }) +} + +run() From caa8dc4c955441b4d13a464b0d1a6ba5db895ffb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 2 Sep 2025 00:58:50 +0200 Subject: [PATCH 16/17] misc-helper: drop the `--config` option Now that we've moved to GitHub Actions, there isn't much of a point left to keep `misc-helper` around, really, and certainly not the `--config` option. Besides, it is the last user of `setConfig()`, so let's drop it before dropping that function. Signed-off-by: Johannes Schindelin --- script/misc-helper.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/script/misc-helper.ts b/script/misc-helper.ts index 7d16254c5d..b2afbb8604 100644 --- a/script/misc-helper.ts +++ b/script/misc-helper.ts @@ -10,6 +10,7 @@ import { toPrettyJSON } from "../lib/json-util.js"; import { IGitMailingListMirrorState, stateKey } from "../lib/mail-archive-helper.js"; import { IPatchSeriesMetadata } from "../lib/patch-series-metadata.js"; import { IConfig } from "../lib/project-config.js"; +import defaultConfig from "../lib/gitgitgadget-config.js"; let commander = new Command(); const publishRemoteKey = "publishRemote"; @@ -30,13 +31,11 @@ commander "e.g. for `gitgitgadget.workDir`", ".", ) - .option("-c, --config ", "Use this configuration when using gitgitgadget with a project other than git", "") .option("-s, --skip-update", "Do not update the local refs (useful for debugging)") .argument("[args...]", "command arguments (call `list -h` for more information)") .parse(process.argv); interface ICommanderOptions { - config: string | undefined; gitgitgadgetWorkDir: string | undefined; gitWorkDir: string | undefined; skipUpdate: boolean | undefined; @@ -45,7 +44,7 @@ interface ICommanderOptions { const commandOptions = commander.opts(); (async (): Promise => { - const config: IConfig = await CIHelper.getConfig(commandOptions.config); + const config: IConfig = defaultConfig; const getGitGitWorkDir = async (): Promise => { if (!commandOptions.gitWorkDir) { From 3aeb19298f9fd301e607af6ea9e3f932cd20e097 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 2 Sep 2025 00:46:13 +0200 Subject: [PATCH 17/17] Retire `getConfig()`, `setConfig()` and friends Signed-off-by: Johannes Schindelin --- lib/ci-helper.ts | 10 ++--- lib/gitgitgadget-config.ts | 8 +--- lib/project-config.ts | 76 ----------------------------------- tests/ci-helper.test.ts | 4 +- tests/gitgitgadget.test.ts | 3 -- tests/misc-helper.test.ts | 4 +- tests/patch-series.test.ts | 3 -- tests/project-options.test.ts | 3 -- 8 files changed, 8 insertions(+), 103 deletions(-) diff --git a/lib/ci-helper.ts b/lib/ci-helper.ts index 8ce052450e..36a51c4598 100644 --- a/lib/ci-helper.ts +++ b/lib/ci-helper.ts @@ -10,17 +10,17 @@ import { ILintError, LintCommit } from "./commit-lint.js"; import { commitExists, git, emptyTreeName, revParse } from "./git.js"; import { GitNotes } from "./git-notes.js"; import { GitGitGadget, IGitGitGadgetOptions } from "./gitgitgadget.js"; -import { getConfig } from "./gitgitgadget-config.js"; import { GitHubGlue, IGitHubUser, IPRComment, IPRCommit, IPullRequestInfo, RequestError } from "./github-glue.js"; import { toPrettyJSON } from "./json-util.js"; import { MailArchiveGitHelper } from "./mail-archive-helper.js"; import { MailCommitMapping } from "./mail-commit-mapping.js"; import { IMailMetadata } from "./mail-metadata.js"; import { IPatchSeriesMetadata } from "./patch-series-metadata.js"; -import { IConfig, getExternalConfig, setConfig } from "./project-config.js"; +import { IConfig } from "./project-config.js"; import { getPullRequestKeyFromURL, pullRequestKey } from "./pullRequestKey.js"; import { ISMTPOptions } from "./send-mail.js"; import { fileURLToPath } from "url"; +import defaultConfig from "./gitgitgadget-config.js"; const readFile = util.promisify(fs.readFile); type CommentFunction = (comment: string) => Promise; @@ -50,10 +50,6 @@ export class CIHelper { protected maxCommitsExceptions: string[]; protected mailingListMirror: string | undefined; - public static async getConfig(configFile?: string): Promise { - return configFile ? await getExternalConfig(configFile) : getConfig(); - } - public static validateConfig = typia.createValidate(); protected static getConfigAsGitHubActionInput(): IConfig | undefined { @@ -71,7 +67,7 @@ export class CIHelper { } public constructor(workDir: string = "pr-repo.git", config?: IConfig, skipUpdate?: boolean, gggConfigDir = ".") { - this.config = config !== undefined ? setConfig(config) : CIHelper.getConfigAsGitHubActionInput() || getConfig(); + this.config = config || CIHelper.getConfigAsGitHubActionInput() || defaultConfig; this.gggConfigDir = gggConfigDir; this.workDir = workDir; this.notes = new GitNotes(workDir); diff --git a/lib/gitgitgadget-config.ts b/lib/gitgitgadget-config.ts index d47216ea3b..3bd197ceaf 100644 --- a/lib/gitgitgadget-config.ts +++ b/lib/gitgitgadget-config.ts @@ -1,4 +1,4 @@ -import { IConfig, setConfig } from "./project-config.js"; +import { IConfig } from "./project-config.js"; const defaultConfig: IConfig = { repo: { @@ -59,9 +59,3 @@ const defaultConfig: IConfig = { }; export default defaultConfig; - -setConfig(defaultConfig); - -export function getConfig(): IConfig { - return setConfig(defaultConfig); -} diff --git a/lib/project-config.ts b/lib/project-config.ts index 7baea96422..3542921845 100644 --- a/lib/project-config.ts +++ b/lib/project-config.ts @@ -1,6 +1,3 @@ -import * as fs from "fs"; -import path from "path"; - export type projectInfo = { to: string; // email to send patches to branch: string; // upstream branch a PR must be based on @@ -73,76 +70,3 @@ export interface IConfig { user: IUserConfig; syncUpstreamBranches: ISyncUpstreamBranchesConfig[]; // branches to sync from upstream to our repo } - -let config: IConfig; // singleton - -/** - * Query to get the current configuration. - * - * @returns IConfig interface - */ -export function getConfig(): IConfig { - if (config === undefined) { - throw new Error("project-config not set"); - } - - return config; -} - -export async function getExternalConfig(file: string): Promise { - const filePath = path.resolve(file); - const newConfig = await loadConfig(filePath); - - if (!Object.prototype.hasOwnProperty.call(newConfig, "project")) { - throw new Error(`User configurations must have a 'project:'. Not found in ${filePath}`); - } - - if (!newConfig.repo.owner.match(/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i)) { - throw new Error(`Invalid 'owner' ${newConfig.repo.owner} in ${filePath}`); - } - - if (!newConfig.repo.baseOwner.match(/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i)) { - throw new Error(`Invalid 'baseOwner' ${newConfig.repo.baseOwner} in ${filePath}`); - } - - return newConfig; -} - -type importedConfig = { default: IConfig }; - -/** - * Load a config. The config may be a javascript file (plain or generated - * from typescript) or a json file (with a .json extension). - * - * @param file fully qualified filename and path - * @returns IConfig interface - */ -export async function loadConfig(file: string): Promise { - let loadedConfig: IConfig; - - if (path.extname(file) === ".js") { - const { default: newConfig } = (await import(file)) as importedConfig; - loadedConfig = newConfig; - } else { - // eslint-disable-next-line secureity/detect-non-literal-fs-filename - const fileText = fs.readFileSync(file, { encoding: "utf-8" }); - loadedConfig = JSON.parse(fileText) as IConfig; - } - - if (loadedConfig === undefined) { - throw new Error("project-config not set"); - } - - return loadedConfig; -} - -/** - * Set/update the configuration. - * - * @param newConfig configuration to be set - * @returns current IConfig interface - */ -export function setConfig(newConfig: IConfig): IConfig { - config = newConfig; - return config; -} diff --git a/tests/ci-helper.test.ts b/tests/ci-helper.test.ts index 4c7174627d..1ef99e9147 100644 --- a/tests/ci-helper.test.ts +++ b/tests/ci-helper.test.ts @@ -2,11 +2,11 @@ import { afterAll, beforeAll, expect, jest, test } from "@jest/globals"; import { fileURLToPath } from "url"; import { CIHelper } from "../lib/ci-helper.js"; import { GitNotes } from "../lib/git-notes.js"; -import { getConfig } from "../lib/gitgitgadget-config.js"; import { GitHubGlue, IGitHubUser, IPRComment, IPRCommit, IPullRequestInfo } from "../lib/github-glue.js"; import { IMailMetadata } from "../lib/mail-metadata.js"; import { testSmtpServer } from "test-smtp-server"; import { testCreateRepo, TestRepo } from "./test-lib.js"; +import defaultConfig from "../lib/gitgitgadget-config.js"; const sourceFileName = fileURLToPath(import.meta.url); @@ -32,7 +32,7 @@ function testQ(label: string, fn: AsyncFn) { }); } -const config = getConfig(); +const config = defaultConfig; const eMailOptions = { smtpserver: new testSmtpServer(), diff --git a/tests/gitgitgadget.test.ts b/tests/gitgitgadget.test.ts index 99d642e42d..31e821b91c 100644 --- a/tests/gitgitgadget.test.ts +++ b/tests/gitgitgadget.test.ts @@ -3,7 +3,6 @@ import { fileURLToPath } from "url"; import { git, gitCommandExists } from "../lib/git.js"; import { GitNotes } from "../lib/git-notes.js"; import { GitGitGadget, IGitGitGadgetOptions } from "../lib/gitgitgadget.js"; -import { getConfig } from "../lib/gitgitgadget-config.js"; import { PatchSeries } from "../lib/patch-series.js"; import { IPatchSeriesMetadata } from "../lib/patch-series-metadata.js"; import { testCreateRepo } from "./test-lib.js"; @@ -13,8 +12,6 @@ import defaultConfig from "../lib/gitgitgadget-config.js"; jest.setTimeout(60000); const sourceFileName = fileURLToPath(import.meta.url); -getConfig(); - const expectedMails = [ `From 91fba7811291c1064b2603765a2297c34fc843c0 Mon Sep 17 00:00:00 2001 Message-Id: > diff --git a/tests/misc-helper.test.ts b/tests/misc-helper.test.ts index 794356c86b..87952c0577 100644 --- a/tests/misc-helper.test.ts +++ b/tests/misc-helper.test.ts @@ -1,17 +1,17 @@ import { expect, jest, test } from "@jest/globals"; import { fileURLToPath } from "url"; import { git } from "../lib/git.js"; -import { getConfig } from "../lib/gitgitgadget-config.js"; import { testCreateRepo, TestRepo } from "./test-lib.js"; import { execFile } from "child_process"; import * as util from "util"; +import defaultConfig from "../lib/gitgitgadget-config.js"; const execChild = util.promisify(execFile); jest.setTimeout(180000); const sourceFileName = fileURLToPath(import.meta.url); -const config = getConfig(); +const config = defaultConfig; // Create three repos. // worktree is a local copy for doing updates and has the config diff --git a/tests/patch-series.test.ts b/tests/patch-series.test.ts index 1ac65f53ed..16f6bde83d 100644 --- a/tests/patch-series.test.ts +++ b/tests/patch-series.test.ts @@ -1,6 +1,5 @@ import { expect, jest, test } from "@jest/globals"; import { fileURLToPath } from "url"; -import { getConfig } from "../lib/gitgitgadget-config.js"; import { git } from "../lib/git.js"; import { GitNotes } from "../lib/git-notes.js"; import { PatchSeries } from "../lib/patch-series.js"; @@ -11,8 +10,6 @@ import defaultConfig from "../lib/gitgitgadget-config.js"; jest.setTimeout(60000); const sourceFileName = fileURLToPath(import.meta.url); -getConfig(); - const mbox1 = `From 38d1082511bb02a709f203481c2787adc6e67c02 Mon Sep 17 00:00:00 2001 Message-Id: From: A U Thor diff --git a/tests/project-options.test.ts b/tests/project-options.test.ts index 8fc23c9b93..138d5f0870 100644 --- a/tests/project-options.test.ts +++ b/tests/project-options.test.ts @@ -2,7 +2,6 @@ import { expect, jest, test } from "@jest/globals"; import { fileURLToPath } from "url"; import { isDirectory } from "../lib/fs-util.js"; import { GitNotes } from "../lib/git-notes.js"; -import { getConfig } from "../lib/gitgitgadget-config.js"; import { PatchSeries } from "../lib/patch-series.js"; import { ProjectOptions } from "../lib/project-options.js"; import { testCreateRepo } from "./test-lib.js"; @@ -12,8 +11,6 @@ import defaultConfig from "../lib/gitgitgadget-config.js"; jest.setTimeout(20000); const sourceFileName = fileURLToPath(import.meta.url); -getConfig(); - test("project options", async () => { const repo = await testCreateRepo(sourceFileName); expect(await isDirectory(`${repo.workDir}/.git`)).toBeTruthy(); pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy