mirror of
https://github.com/github/awesome-copilot.git
synced 2026-05-27 17:11:44 +00:00
Splitting ref and sha into two fields correctly for the intake form (#1788)
* Splitting ref and sha into two fields correctly for the intake form * Enforce 40-character commit SHA in validateImmutableRef Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> * Add backward compatibility for legacy checklist text and field title Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> * Avoid unnecessary array spread when iterating checklist equivalents Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>
This commit is contained in:
@@ -14,7 +14,7 @@ body:
|
|||||||
Before you continue:
|
Before you continue:
|
||||||
- Public submissions are **GitHub-only** in v1.
|
- Public submissions are **GitHub-only** in v1.
|
||||||
- The plugin must live in a **public GitHub repository**.
|
- The plugin must live in a **public GitHub repository**.
|
||||||
- Use an **immutable ref** for review: a release tag or full 40-character commit SHA.
|
- Provide an immutable **ref**, **sha**, or both for review.
|
||||||
- Do **not** open a PR that edits `plugins/external.json` directly.
|
- Do **not** open a PR that edits `plugins/external.json` directly.
|
||||||
- type: input
|
- type: input
|
||||||
id: plugin-name
|
id: plugin-name
|
||||||
@@ -51,11 +51,19 @@ body:
|
|||||||
- type: input
|
- type: input
|
||||||
id: immutable-ref
|
id: immutable-ref
|
||||||
attributes:
|
attributes:
|
||||||
label: Immutable ref to review
|
label: Ref to review
|
||||||
description: Release tag or full 40-character commit SHA.
|
description: Optional release tag or tag ref. Submit this, a commit SHA, or both.
|
||||||
placeholder: refs/tags/v1.2.3 or 0123456789abcdef0123456789abcdef01234567
|
placeholder: v1.2.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: false
|
||||||
|
- type: input
|
||||||
|
id: immutable-sha
|
||||||
|
attributes:
|
||||||
|
label: Commit SHA to review
|
||||||
|
description: Optional full 40-character commit SHA. Submit this, a ref, or both.
|
||||||
|
placeholder: 0123456789abcdef0123456789abcdef01234567
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
- type: input
|
- type: input
|
||||||
id: version
|
id: version
|
||||||
attributes:
|
attributes:
|
||||||
@@ -119,7 +127,7 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: The plugin lives in a public GitHub repository.
|
- label: The plugin lives in a public GitHub repository.
|
||||||
required: true
|
required: true
|
||||||
- label: The ref I provided is an immutable release tag or full 40-character commit SHA, not a branch.
|
- label: The ref and/or sha I provided is immutable (release tag and/or full 40-character commit SHA), not a branch.
|
||||||
required: true
|
required: true
|
||||||
- label: This submission follows this repository's contribution, security, and responsible AI policies.
|
- label: This submission follows this repository's contribution, security, and responsible AI policies.
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ When adding a new agent, instruction, skill, hook, workflow, or plugin:
|
|||||||
|
|
||||||
1. Do not open a direct PR that edits `plugins/external.json` for a public third-party plugin submission
|
1. Do not open a direct PR that edits `plugins/external.json` for a public third-party plugin submission
|
||||||
2. Public external plugin submissions use the external plugin issue workflow documented in [CONTRIBUTING.md](CONTRIBUTING.md#adding-external-plugins)
|
2. Public external plugin submissions use the external plugin issue workflow documented in [CONTRIBUTING.md](CONTRIBUTING.md#adding-external-plugins)
|
||||||
3. In v1, only GitHub-hosted plugins are accepted for public submission, using a public repo plus an immutable `ref`
|
3. In v1, only GitHub-hosted plugins are accepted for public submission, using a public repo plus an immutable `ref`, `sha`, or both
|
||||||
4. The shared validator in `eng/external-plugin-validation.mjs` is the canonical source of truth for external plugin data rules; reuse it instead of duplicating checks in scripts or workflows
|
4. The shared validator in `eng/external-plugin-validation.mjs` is the canonical source of truth for external plugin data rules; reuse it instead of duplicating checks in scripts or workflows
|
||||||
5. Submission issues move through `external-plugin` + `awaiting-review` -> `ready-for-review` -> `approved` or `rejected`
|
5. Submission issues move through `external-plugin` + `awaiting-review` -> `ready-for-review` -> `approved` or `rejected`
|
||||||
6. After issue edits, the issue author or a maintainer can comment `/rerun-intake` to re-run automated intake without opening a new submission issue
|
6. After issue edits, the issue author or a maintainer can comment `/rerun-intake` to re-run automated intake without opening a new submission issue
|
||||||
|
|||||||
+7
-5
@@ -202,7 +202,8 @@ The external plugin issue form will collect these fields:
|
|||||||
- Short description
|
- Short description
|
||||||
- GitHub repository in `owner/repo` format
|
- GitHub repository in `owner/repo` format
|
||||||
- Plugin path inside the repository (optional when the plugin is at the repository root)
|
- Plugin path inside the repository (optional when the plugin is at the repository root)
|
||||||
- Immutable ref to review (`ref`), using a release tag or full commit SHA rather than a branch
|
- Ref to review (`ref`), using a release tag or tag ref rather than a branch
|
||||||
|
- Commit SHA to review (`sha`), using a full 40-character commit SHA
|
||||||
- Plugin version
|
- Plugin version
|
||||||
- License identifier
|
- License identifier
|
||||||
- Author name
|
- Author name
|
||||||
@@ -210,7 +211,7 @@ The external plugin issue form will collect these fields:
|
|||||||
- Homepage URL (optional)
|
- Homepage URL (optional)
|
||||||
- Keywords/tags
|
- Keywords/tags
|
||||||
- Additional notes for reviewers (optional)
|
- Additional notes for reviewers (optional)
|
||||||
- Confirmation checkboxes that the repository is public, the ref is immutable, the submission follows this repository's policies, and the plugin is not a duplicate listing
|
- Confirmation checkboxes that the repository is public, the submitted ref and/or sha is immutable, the submission follows this repository's policies, and the plugin is not a duplicate listing
|
||||||
|
|
||||||
The repository's canonical validation rules live in `eng/external-plugin-validation.mjs`. Build scripts reuse the `marketplace` policy from that module, and the issue intake automation uses the stricter `publicSubmission` policy so the JSON contract and workflow checks stay aligned.
|
The repository's canonical validation rules live in `eng/external-plugin-validation.mjs`. Build scripts reuse the `marketplace` policy from that module, and the issue intake automation uses the stricter `publicSubmission` policy so the JSON contract and workflow checks stay aligned.
|
||||||
|
|
||||||
@@ -223,7 +224,7 @@ For entries committed to `plugins/external.json`, the current marketplace valida
|
|||||||
- `source.source: "github"` plus `source.repo` in `owner/repo` format
|
- `source.source: "github"` plus `source.repo` in `owner/repo` format
|
||||||
- optional `source.path` values of `/` for repository root, or a repository-relative folder where the plugin structure starts (do not point to `plugin.json` directly)
|
- optional `source.path` values of `/` for repository root, or a repository-relative folder where the plugin structure starts (do not point to `plugin.json` directly)
|
||||||
|
|
||||||
The public-submission policy builds on those rules and also requires `license` plus an immutable `source.ref`.
|
The public-submission policy builds on those rules and also requires `license` plus at least one immutable source locator: `source.ref`, `source.sha`, or both.
|
||||||
|
|
||||||
##### Review workflow
|
##### Review workflow
|
||||||
|
|
||||||
@@ -240,7 +241,7 @@ The public-submission policy builds on those rules and also requires `license` p
|
|||||||
Maintainers are responsible for confirming that the submission:
|
Maintainers are responsible for confirming that the submission:
|
||||||
|
|
||||||
- Clearly fits the Awesome Copilot collection and adds value beyond existing listings
|
- Clearly fits the Awesome Copilot collection and adds value beyond existing listings
|
||||||
- Uses a public GitHub repository and an immutable ref that can be reviewed reliably
|
- Uses a public GitHub repository and an immutable ref and/or SHA that can be reviewed reliably
|
||||||
- Includes the required metadata for `plugins/external.json` (`name`, `description`, `version`, `author.name`, `repository`, `keywords`, and `source`), plus any supplied homepage/license fields
|
- Includes the required metadata for `plugins/external.json` (`name`, `description`, `version`, `author.name`, `repository`, `keywords`, and `source`), plus any supplied homepage/license fields
|
||||||
- Does not obviously duplicate an existing marketplace entry
|
- Does not obviously duplicate an existing marketplace entry
|
||||||
- Continues to meet this repository's content, security, and responsible AI policies
|
- Continues to meet this repository's content, security, and responsible AI policies
|
||||||
@@ -284,7 +285,8 @@ Approved submissions are converted into `plugins/external.json` entries followin
|
|||||||
"source": "github",
|
"source": "github",
|
||||||
"repo": "owner/plugin-repo",
|
"repo": "owner/plugin-repo",
|
||||||
"path": ".github/plugins/my-external-plugin",
|
"path": ".github/plugins/my-external-plugin",
|
||||||
"ref": "v1.0.0"
|
"ref": "v1.0.0",
|
||||||
|
"sha": "0123456789abcdef0123456789abcdef01234567"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -15,11 +15,17 @@ const RERUN_INTAKE_COMMAND_PATTERN = new RegExp(
|
|||||||
);
|
);
|
||||||
const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");
|
const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");
|
||||||
|
|
||||||
|
// Each entry is a Set of equivalent checklist item texts (new + legacy aliases).
|
||||||
|
// A submission passes if the checked items contain at least one text from each Set.
|
||||||
const REQUIRED_CHECKLIST_ITEMS = [
|
const REQUIRED_CHECKLIST_ITEMS = [
|
||||||
"The plugin lives in a public GitHub repository.",
|
new Set(["The plugin lives in a public GitHub repository."]),
|
||||||
|
new Set([
|
||||||
|
"The ref and/or sha I provided is immutable (release tag and/or full 40-character commit SHA), not a branch.",
|
||||||
|
// Legacy text used in the original issue template
|
||||||
"The ref I provided is an immutable release tag or full 40-character commit SHA, not a branch.",
|
"The ref I provided is an immutable release tag or full 40-character commit SHA, not a branch.",
|
||||||
"This submission follows this repository's contribution, security, and responsible AI policies.",
|
]),
|
||||||
"This plugin is not already listed in the Awesome Copilot marketplace.",
|
new Set(["This submission follows this repository's contribution, security, and responsible AI policies."]),
|
||||||
|
new Set(["This plugin is not already listed in the Awesome Copilot marketplace."]),
|
||||||
];
|
];
|
||||||
|
|
||||||
const FIELD_TITLES = Object.freeze({
|
const FIELD_TITLES = Object.freeze({
|
||||||
@@ -27,7 +33,8 @@ const FIELD_TITLES = Object.freeze({
|
|||||||
shortDescription: "Short description",
|
shortDescription: "Short description",
|
||||||
githubRepository: "GitHub repository",
|
githubRepository: "GitHub repository",
|
||||||
pluginPath: "Plugin path inside the repository",
|
pluginPath: "Plugin path inside the repository",
|
||||||
immutableRef: "Immutable ref to review",
|
immutableRef: "Ref to review",
|
||||||
|
immutableSha: "Commit SHA to review",
|
||||||
version: "Version",
|
version: "Version",
|
||||||
license: "License identifier",
|
license: "License identifier",
|
||||||
authorName: "Author name",
|
authorName: "Author name",
|
||||||
@@ -38,6 +45,11 @@ const FIELD_TITLES = Object.freeze({
|
|||||||
submissionChecklist: "Submission checklist",
|
submissionChecklist: "Submission checklist",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Legacy field title used in the original issue template (before the ref/sha split)
|
||||||
|
const LEGACY_FIELD_TITLES = Object.freeze({
|
||||||
|
immutableRef: "Immutable ref to review",
|
||||||
|
});
|
||||||
|
|
||||||
function normalizeMultilineText(value) {
|
function normalizeMultilineText(value) {
|
||||||
return String(value ?? "").replace(/\r\n/g, "\n");
|
return String(value ?? "").replace(/\r\n/g, "\n");
|
||||||
}
|
}
|
||||||
@@ -156,7 +168,7 @@ function encodeRepoPath(repo) {
|
|||||||
return `${encodeURIComponent(owner ?? "")}/${encodeURIComponent(name ?? "")}`;
|
return `${encodeURIComponent(owner ?? "")}/${encodeURIComponent(name ?? "")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validateRemoteRepository(repo, ref, errors, warnings, token) {
|
async function validateRemoteRepository(repo, { ref, sha }, errors, warnings, token) {
|
||||||
const encodedRepo = encodeRepoPath(repo);
|
const encodedRepo = encodeRepoPath(repo);
|
||||||
const repositoryResponse = await fetchGitHubJson(`/repos/${encodedRepo}`, token);
|
const repositoryResponse = await fetchGitHubJson(`/repos/${encodedRepo}`, token);
|
||||||
|
|
||||||
@@ -177,6 +189,15 @@ async function validateRemoteRepository(repo, ref, errors, warnings, token) {
|
|||||||
warnings.push(`submission: GitHub repository "${repo}" is archived`);
|
warnings.push(`submission: GitHub repository "${repo}" is archived`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sha) {
|
||||||
|
if (/^[0-9a-f]{40}$/i.test(sha)) {
|
||||||
|
const commitResponse = await fetchGitHubJson(`/repos/${encodedRepo}/commits/${encodeURIComponent(sha)}`, token);
|
||||||
|
if (!commitResponse.ok) {
|
||||||
|
errors.push(`submission: commit "${sha}" was not found in GitHub repository "${repo}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!ref) {
|
if (!ref) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -189,6 +210,14 @@ async function validateRemoteRepository(repo, ref, errors, warnings, token) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ref.startsWith("refs/heads/") || ["main", "master", "develop", "development", "dev", "trunk"].includes(ref)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref.startsWith("refs/") && !ref.startsWith("refs/tags/")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const tagName = ref.startsWith("refs/tags/") ? ref.slice("refs/tags/".length) : ref;
|
const tagName = ref.startsWith("refs/tags/") ? ref.slice("refs/tags/".length) : ref;
|
||||||
const tagResponse = await fetchGitHubJson(`/repos/${encodedRepo}/git/ref/tags/${encodeURIComponent(tagName)}`, token);
|
const tagResponse = await fetchGitHubJson(`/repos/${encodedRepo}/git/ref/tags/${encodeURIComponent(tagName)}`, token);
|
||||||
|
|
||||||
@@ -197,7 +226,7 @@ async function validateRemoteRepository(repo, ref, errors, warnings, token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (/^[0-9a-f]+$/i.test(ref) && ref.length !== 40) {
|
if (/^[0-9a-f]+$/i.test(ref) && ref.length !== 40) {
|
||||||
errors.push('submission: commit SHAs in "Immutable ref to review" must use the full 40-character SHA');
|
errors.push('submission: commit SHAs in "Ref to review" must use the full 40-character SHA or be submitted in "Commit SHA to review"');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +250,11 @@ export function parseExternalPluginIssueBody(body) {
|
|||||||
const pluginName = requiredField(FIELD_TITLES.pluginName);
|
const pluginName = requiredField(FIELD_TITLES.pluginName);
|
||||||
const shortDescription = requiredField(FIELD_TITLES.shortDescription);
|
const shortDescription = requiredField(FIELD_TITLES.shortDescription);
|
||||||
const repoInput = normalizeGitHubRepo(requiredField(FIELD_TITLES.githubRepository));
|
const repoInput = normalizeGitHubRepo(requiredField(FIELD_TITLES.githubRepository));
|
||||||
const immutableRef = requiredField(FIELD_TITLES.immutableRef);
|
// Support both the current field title and the legacy title used before the ref/sha split
|
||||||
|
const immutableRef = stripNoResponse(
|
||||||
|
sections.get(FIELD_TITLES.immutableRef) ?? sections.get(LEGACY_FIELD_TITLES.immutableRef),
|
||||||
|
);
|
||||||
|
const immutableSha = stripNoResponse(sections.get(FIELD_TITLES.immutableSha));
|
||||||
const version = requiredField(FIELD_TITLES.version);
|
const version = requiredField(FIELD_TITLES.version);
|
||||||
const license = requiredField(FIELD_TITLES.license);
|
const license = requiredField(FIELD_TITLES.license);
|
||||||
const authorName = requiredField(FIELD_TITLES.authorName);
|
const authorName = requiredField(FIELD_TITLES.authorName);
|
||||||
@@ -233,9 +266,22 @@ export function parseExternalPluginIssueBody(body) {
|
|||||||
const additionalNotes = stripNoResponse(sections.get(FIELD_TITLES.additionalNotes));
|
const additionalNotes = stripNoResponse(sections.get(FIELD_TITLES.additionalNotes));
|
||||||
const checkedItems = parseChecklist(sections.get(FIELD_TITLES.submissionChecklist));
|
const checkedItems = parseChecklist(sections.get(FIELD_TITLES.submissionChecklist));
|
||||||
|
|
||||||
for (const item of REQUIRED_CHECKLIST_ITEMS) {
|
if (!immutableRef && !immutableSha) {
|
||||||
if (!checkedItems.has(item)) {
|
errors.push(`submission: one of "${FIELD_TITLES.immutableRef}" or "${FIELD_TITLES.immutableSha}" is required`);
|
||||||
errors.push(`submission: checklist item must be checked: "${item}"`);
|
}
|
||||||
|
|
||||||
|
for (const equivalents of REQUIRED_CHECKLIST_ITEMS) {
|
||||||
|
let isChecked = false;
|
||||||
|
for (const text of equivalents) {
|
||||||
|
if (checkedItems.has(text)) {
|
||||||
|
isChecked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isChecked) {
|
||||||
|
// Report using the canonical (first) text in each equivalents Set
|
||||||
|
const [canonical] = equivalents;
|
||||||
|
errors.push(`submission: checklist item must be checked: "${canonical}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,6 +302,7 @@ export function parseExternalPluginIssueBody(body) {
|
|||||||
repo: repoInput,
|
repo: repoInput,
|
||||||
...(pluginPath ? { path: pluginPath } : {}),
|
...(pluginPath ? { path: pluginPath } : {}),
|
||||||
...(immutableRef ? { ref: immutableRef } : {}),
|
...(immutableRef ? { ref: immutableRef } : {}),
|
||||||
|
...(immutableSha ? { sha: immutableSha } : {}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -297,8 +344,8 @@ export async function evaluateExternalPluginIssue({ issue, token } = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsed.plugin?.source?.repo && parsed.plugin?.source?.ref) {
|
if (parsed.plugin?.source?.repo && (parsed.plugin?.source?.ref || parsed.plugin?.source?.sha)) {
|
||||||
await validateRemoteRepository(parsed.plugin.source.repo, parsed.plugin.source.ref, errors, warnings, token);
|
await validateRemoteRepository(parsed.plugin.source.repo, parsed.plugin.source, errors, warnings, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dedupedErrors = [...new Set(errors)];
|
const dedupedErrors = [...new Set(errors)];
|
||||||
@@ -324,7 +371,8 @@ export async function evaluateExternalPluginIssue({ issue, token } = {}) {
|
|||||||
"",
|
"",
|
||||||
`- **Plugin:** ${parsed.plugin.name}`,
|
`- **Plugin:** ${parsed.plugin.name}`,
|
||||||
`- **Repository:** ${parsed.plugin.repository}`,
|
`- **Repository:** ${parsed.plugin.repository}`,
|
||||||
`- **Ref:** ${parsed.plugin.source.ref}`,
|
parsed.plugin.source.ref ? `- **Ref:** ${parsed.plugin.source.ref}` : undefined,
|
||||||
|
parsed.plugin.source.sha ? `- **SHA:** ${parsed.plugin.source.sha}` : undefined,
|
||||||
`- **Keywords:** ${normalizedKeywords}`,
|
`- **Keywords:** ${normalizedKeywords}`,
|
||||||
"",
|
"",
|
||||||
"### Canonical external.json payload",
|
"### Canonical external.json payload",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const EXTERNAL_PLUGIN_POLICIES = Object.freeze({
|
|||||||
requireRepository: true,
|
requireRepository: true,
|
||||||
requireKeywords: true,
|
requireKeywords: true,
|
||||||
requireLicense: false,
|
requireLicense: false,
|
||||||
requireImmutableRef: false,
|
requireImmutableLocator: false,
|
||||||
}),
|
}),
|
||||||
publicSubmission: Object.freeze({
|
publicSubmission: Object.freeze({
|
||||||
allowedSourceTypes: ["github"],
|
allowedSourceTypes: ["github"],
|
||||||
@@ -19,7 +19,7 @@ export const EXTERNAL_PLUGIN_POLICIES = Object.freeze({
|
|||||||
requireRepository: true,
|
requireRepository: true,
|
||||||
requireKeywords: true,
|
requireKeywords: true,
|
||||||
requireLicense: true,
|
requireLicense: true,
|
||||||
requireImmutableRef: true,
|
requireImmutableLocator: true,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -263,9 +263,24 @@ function validateImmutableRef(ref, prefix, errors) {
|
|||||||
if (ref.startsWith("refs/") && !ref.startsWith("refs/tags/")) {
|
if (ref.startsWith("refs/") && !ref.startsWith("refs/tags/")) {
|
||||||
errors.push(`${prefix}: "source.ref" must be a tag ref or commit SHA`);
|
errors.push(`${prefix}: "source.ref" must be a tag ref or commit SHA`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (/^[0-9a-f]+$/i.test(ref) && ref.length !== 40) {
|
||||||
|
errors.push(`${prefix}: "source.ref" must be a full 40-character commit SHA when referencing a commit`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateGitHubSource(source, prefix, errors, requireImmutableRef) {
|
function validateCommitSha(sha, prefix, errors) {
|
||||||
|
if (!isNonEmptyString(sha)) {
|
||||||
|
errors.push(`${prefix}: "source.sha" must be a non-empty string when provided`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^[0-9a-f]{40}$/i.test(sha)) {
|
||||||
|
errors.push(`${prefix}: "source.sha" must be a full 40-character commit SHA`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateGitHubSource(source, prefix, errors, requireImmutableLocator) {
|
||||||
if (!source || typeof source !== "object" || Array.isArray(source)) {
|
if (!source || typeof source !== "object" || Array.isArray(source)) {
|
||||||
errors.push(`${prefix}: "source" must be an object`);
|
errors.push(`${prefix}: "source" must be an object`);
|
||||||
return;
|
return;
|
||||||
@@ -287,8 +302,14 @@ function validateGitHubSource(source, prefix, errors, requireImmutableRef) {
|
|||||||
|
|
||||||
if (source.ref !== undefined) {
|
if (source.ref !== undefined) {
|
||||||
validateImmutableRef(source.ref, prefix, errors);
|
validateImmutableRef(source.ref, prefix, errors);
|
||||||
} else if (requireImmutableRef) {
|
}
|
||||||
errors.push(`${prefix}: "source.ref" is required for public external plugin submissions`);
|
|
||||||
|
if (source.sha !== undefined) {
|
||||||
|
validateCommitSha(source.sha, prefix, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requireImmutableLocator && source.ref === undefined && source.sha === undefined) {
|
||||||
|
errors.push(`${prefix}: one of "source.ref" or "source.sha" is required for public external plugin submissions`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +346,7 @@ export function validateExternalPlugin(plugin, index, options = {}) {
|
|||||||
} else if (!policy.allowedSourceTypes.includes(plugin.source.source)) {
|
} else if (!policy.allowedSourceTypes.includes(plugin.source.source)) {
|
||||||
errors.push(`${prefix}: "source.source" must be one of: ${policy.allowedSourceTypes.join(", ")}`);
|
errors.push(`${prefix}: "source.source" must be one of: ${policy.allowedSourceTypes.join(", ")}`);
|
||||||
} else if (plugin.source.source === "github") {
|
} else if (plugin.source.source === "github") {
|
||||||
validateGitHubSource(plugin.source, prefix, errors, policy.requireImmutableRef);
|
validateGitHubSource(plugin.source, prefix, errors, policy.requireImmutableLocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { errors, warnings };
|
return { errors, warnings };
|
||||||
|
|||||||
Reference in New Issue
Block a user