diff --git a/.github/workflows/sync-openapi.yml b/.github/workflows/sync-openapi.yml index fda313da70..ee347de378 100644 --- a/.github/workflows/sync-openapi.yml +++ b/.github/workflows/sync-openapi.yml @@ -48,6 +48,7 @@ jobs: # will be checked out repository: github/models-gateway path: models-gateway + ref: main - uses: ./.github/actions/node-npm-setup diff --git a/.github/workflows/triage-issue-comments.yml b/.github/workflows/triage-issue-comments.yml index 1e80dc69f5..bdb522be37 100644 --- a/.github/workflows/triage-issue-comments.yml +++ b/.github/workflows/triage-issue-comments.yml @@ -47,9 +47,22 @@ jobs: - uses: ./.github/actions/node-npm-setup + - name: Check issue exists + id: exists + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_URL: ${{ github.event.issue.html_url }} + run: | + if gh issue view $ISSUE_URL > /dev/null 2>&1 + then + echo "exists=y" >> $GITHUB_OUTPUT + else + echo "exists=n" >> $GITHUB_OUTPUT + fi + - name: Label issues with new comments with 'triage' uses: ./.github/actions/labeler - if: ${{ steps.is-internal-contributor.outputs.result == 'false' }} + if: ${{ steps.is-internal-contributor.outputs.result == 'false' && steps.exists.outputs.exists == 'y' }} with: addLabels: 'triage' ignoreIfLabeled: true diff --git a/content/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning-with-codeql.md b/content/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning-with-codeql.md index 45567a21b6..e0bf7f0ec4 100644 --- a/content/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning-with-codeql.md +++ b/content/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning-with-codeql.md @@ -47,8 +47,13 @@ For information about {% data variables.product.prodname_code_scanning %} alerts {% data variables.product.prodname_codeql %} supports both compiled and interpreted languages, and can find vulnerabilities and errors in code that's written in the supported languages. +{% data variables.product.prodname_codeql %} supports the following languages: + {% data reusables.code-scanning.codeql-languages-bullets %} +> [!IMPORTANT] +> {% data variables.product.prodname_codeql %} does **not** support languages that are not listed above. This includes, but is not limited to, **Rust**, **PHP**, **Scala**, and others. Attempting to use {% data variables.product.prodname_codeql %} with unsupported languages may result in no alerts being generated and incomplete analysis. + ## Modeling custom or niche frameworks {% data variables.product.github %} experts, security researchers, and community contributors write libraries to model the flow of data in popular frameworks and libraries. If you use custom dependencies that aren't modeled, then you can use the {% data variables.product.prodname_codeql %} extension for {% data variables.product.prodname_vscode %} to create models for these dependencies and use them to extend your analysis. For more information, see [AUTOTITLE](/code-security/codeql-for-vs-code/using-the-advanced-functionality-of-the-codeql-for-vs-code-extension/using-the-codeql-model-editor). diff --git a/content/code-security/securing-your-organization/enabling-security-features-in-your-organization/applying-the-github-recommended-security-configuration-in-your-organization.md b/content/code-security/securing-your-organization/enabling-security-features-in-your-organization/applying-the-github-recommended-security-configuration-in-your-organization.md index 95c3a4e774..a2ba1600d4 100644 --- a/content/code-security/securing-your-organization/enabling-security-features-in-your-organization/applying-the-github-recommended-security-configuration-in-your-organization.md +++ b/content/code-security/securing-your-organization/enabling-security-features-in-your-organization/applying-the-github-recommended-security-configuration-in-your-organization.md @@ -17,7 +17,7 @@ topics: The {% data variables.product.prodname_github_security_configuration %} is a collection of enablement settings for {% data variables.product.company_short %}'s security features that is created and maintained by subject matter experts at {% data variables.product.company_short %}. The {% data variables.product.prodname_github_security_configuration %} is designed to successfully reduce the security risks for low- and high-impact repositories. We recommend you apply this configuration to all the repositories in your organization. > [!NOTE] -> The {% data variables.product.prodname_github_security_configuration %} includes {% data variables.product.prodname_GH_code_security %} and {% data variables.product.prodname_GH_secret_protection %} features. Applying the configuration to private and internal repositories in your organization will incur usage costs or require licenses. +> The {% data variables.product.prodname_github_security_configuration %} includes {% data variables.product.prodname_GH_code_security %} and {% data variables.product.prodname_GH_secret_protection %} features. Applying the configuration to repositories in your organization will incur usage costs or require licenses. ## Applying the {% data variables.product.prodname_github_security_configuration %} to all repositories in your organization diff --git a/content/code-security/securing-your-organization/introduction-to-securing-your-organization-at-scale/choosing-a-security-configuration-for-your-repositories.md b/content/code-security/securing-your-organization/introduction-to-securing-your-organization-at-scale/choosing-a-security-configuration-for-your-repositories.md index 0711ce802a..c2d4ab79da 100644 --- a/content/code-security/securing-your-organization/introduction-to-securing-your-organization-at-scale/choosing-a-security-configuration-for-your-repositories.md +++ b/content/code-security/securing-your-organization/introduction-to-securing-your-organization-at-scale/choosing-a-security-configuration-for-your-repositories.md @@ -31,7 +31,7 @@ The {% data variables.product.prodname_github_security_configuration %} offers a * It is the quickest {% data variables.product.prodname_security_configuration %} to apply to all repositories in your organization. * It is designed to effectively secure both low- and high-impact repositories. -The {% data variables.product.prodname_github_security_configuration %} includes {% data variables.product.prodname_GH_code_security %} and {% data variables.product.prodname_GH_secret_protection %} features. Applying the configuration to private and internal repositories in your organization will incur usage costs or require licenses. +The {% data variables.product.prodname_github_security_configuration %} includes {% data variables.product.prodname_GH_code_security %} and {% data variables.product.prodname_GH_secret_protection %} features. Applying the configuration to repositories in your organization will incur usage costs or require licenses. To start securing repositories in your organization with the {% data variables.product.prodname_github_security_configuration %}, see [AUTOTITLE](/code-security/securing-your-organization/enabling-security-features-in-your-organization/applying-the-github-recommended-security-configuration-in-your-organization). diff --git a/content/github-models/use-github-models/prototyping-with-ai-models.md b/content/github-models/use-github-models/prototyping-with-ai-models.md index 0a5a6da0c8..9101ca4499 100644 --- a/content/github-models/use-github-models/prototyping-with-ai-models.md +++ b/content/github-models/use-github-models/prototyping-with-ai-models.md @@ -265,7 +265,7 @@ Low, high, and embedding models have different rate limits. To see which type of 1 - Azure OpenAI o1 and o3 + Azure OpenAI o1 and o3, xAI Grok-3 Requests per minute Not applicable 1 diff --git a/content/github-models/use-github-models/storing-prompts-in-github-repositories.md b/content/github-models/use-github-models/storing-prompts-in-github-repositories.md index be50486880..77e56dee64 100644 --- a/content/github-models/use-github-models/storing-prompts-in-github-repositories.md +++ b/content/github-models/use-github-models/storing-prompts-in-github-repositories.md @@ -18,27 +18,36 @@ Prompts can be stored as files directly within GitHub repositories. This unlocks ## Supported file format -Store prompts in markdown files with optional YAML front matter. +Store prompts in YAML files. -The file can be located anywhere in your repository, but it **must have the extension `.prompt.md`**. +The file can be located anywhere in your repository, but _must have the extension `.prompt.yml` or `.prompt.yaml`._ Example: -```yaml ---- -name: Summarizer -description: Summarizes a given text -model: openai/gpt-4o -model_parameters: +``` yaml copy +name: Text Summarizer +description: Summarizes input text concisely +model: gpt-4o-mini +modelParameters: temperature: 0.5 ---- -system: -You are a text summarizer. Your only job is to summarize a given text to you. -user: -Summarize the given text: - -{% raw %}{{text}}{% endraw %} - +messages: + - role: system + content: You are a text summarizer. Your only job is to summarize text given to you. + - role: user + content: | + Summarize the given text, beginning with "Summary -": + + {% raw %}{{input}}{% endraw %} + +testData: + - input: | + The quick brown fox jumped over the lazy dog. + The dog was too tired to react. + expected: Summary - A fox jumped over a lazy, unresponsive dog. +evaluators: + - name: Output should start with 'Summary -' + string: + startsWith: 'Summary -' ``` ## Prompt structure @@ -46,11 +55,12 @@ Summarize the given text: Prompts have two key parts: * **Runtime information** (required) - * Prompt templates (system, user, etc.) using simple {{variable}} placeholders + * Prompt templates (system, user, etc.) using simple {% raw %}`{{variable}}`{% endraw %} placeholders * **Development information** (optional) * Human-readable name and description * Model identifier and parameters * Sample data for testing and evaluations + * Data describing the evaluators themselves ## Limitations diff --git a/data/ui.yml b/data/ui.yml index fd016a36d5..87dd1ea686 100644 --- a/data/ui.yml +++ b/data/ui.yml @@ -47,7 +47,7 @@ search: privacy_disclaimer: For product and service improvement purposes, the GitHub Docs team will retain questions and answers generated in the Docs search function. Please see the GitHub Privacy Statement to review how GitHub collects and uses your data. ai: disclaimer: Copilot uses AI. Check for mistakes. - references: Additional docs + references: Copilot Sources loading_status_message: Loading Copilot response... done_loading_status_message: Done loading Copilot response copy_answer: Copy answer diff --git a/package.json b/package.json index 7b8b238e5d..f13a312198 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "content-changes-table-comment": "tsx src/workflows/content-changes-table-comment.ts", "copy-fixture-data": "tsx src/tests/scripts/copy-fixture-data.js", "count-translation-corruptions": "tsx src/languages/scripts/count-translation-corruptions.ts", - "create-enterprise-issue": "tsx src/ghes-releases/scripts/create-enterprise-issue.js", + "create-enterprise-issue": "tsx src/ghes-releases/scripts/create-enterprise-issue.ts", "debug": "cross-env NODE_ENV=development ENABLED_LANGUAGES=en nodemon --inspect src/frame/server.ts", "delete-orphan-translation-files": "tsx src/workflows/delete-orphan-translation-files.ts", "deleted-assets-pr-comment": "tsx src/assets/scripts/deleted-assets-pr-comment.ts", @@ -35,6 +35,7 @@ "deprecate-ghes": "tsx src/ghes-releases/scripts/deprecate/index.ts", "deprecate-ghes-archive": "cross-env NODE_OPTIONS=--max-old-space-size=16384 tsx src/ghes-releases/scripts/deprecate/archive-version.ts", "dev": "cross-env npm start", + "dev-toc": "tsx src/dev-toc/generate.ts", "enable-automerge": "tsx src/workflows/enable-automerge.ts", "find-orphaned-assets": "tsx src/assets/scripts/find-orphaned-assets.ts", "find-orphaned-features": "tsx src/data-directory/scripts/find-orphaned-features/index.ts", @@ -75,7 +76,7 @@ "purge-fastly-edge-cache-per-language": "tsx src/languages/scripts/purge-fastly-edge-cache-per-language.js", "purge-old-workflow-runs": "tsx src/workflows/purge-old-workflow-runs.js", "ready-for-docs-review": "tsx src/workflows/ready-for-docs-review.ts", - "release-banner": "tsx src/ghes-releases/scripts/release-banner.js", + "release-banner": "tsx src/ghes-releases/scripts/release-banner.ts", "reusables": "tsx src/content-render/scripts/reusables-cli.ts", "rendered-content-link-checker": "tsx src/links/scripts/rendered-content-link-checker.ts", "rendered-content-link-checker-cli": "tsx src/links/scripts/rendered-content-link-checker-cli.ts", @@ -97,7 +98,7 @@ "tsc": "tsc --noEmit", "unallowed-contributions": "tsx src/workflows/unallowed-contributions.ts", "update-data-and-image-paths": "tsx src/early-access/scripts/update-data-and-image-paths.js", - "update-enterprise-dates": "tsx src/ghes-releases/scripts/update-enterprise-dates.js", + "update-enterprise-dates": "tsx src/ghes-releases/scripts/update-enterprise-dates.ts", "update-internal-links": "tsx src/links/scripts/update-internal-links.ts", "validate-asset-images": "tsx src/assets/scripts/validate-asset-images.ts", "validate-github-github-docs-urls": "tsx src/links/scripts/validate-github-github-docs-urls/index.ts", diff --git a/src/audit-logs/tests/rendering.js b/src/audit-logs/tests/rendering.ts similarity index 99% rename from src/audit-logs/tests/rendering.js rename to src/audit-logs/tests/rendering.ts index 42bd0d585f..84f7f9a320 100644 --- a/src/audit-logs/tests/rendering.js +++ b/src/audit-logs/tests/rendering.ts @@ -20,7 +20,7 @@ describe('audit log events docs', () => { path: '/authentication/keeping-your-account-and-data-secure/security-log-events', type: 'user', }, - ] + ] as const // This test ensures that the audit log event page components and Markdown // file are in sync. Additionally, it checks all event categories are @@ -32,7 +32,7 @@ describe('audit log events docs', () => { // the enterprise events page has no FPT versioned audit log data if (page.type === 'enterprise' && version === 'free-pro-team@latest') continue - const auditLogEvents = getCategorizedAuditLogEvents(page.type, version, true) + const auditLogEvents = getCategorizedAuditLogEvents(page.type, version) if (Object.keys(auditLogEvents).length === 0) { console.warn(`There are no audit log events for ${page.path} with version '${version}'.`) diff --git a/src/audit-logs/tests/unit/filter-events.js b/src/audit-logs/tests/unit/filter-events.ts similarity index 72% rename from src/audit-logs/tests/unit/filter-events.js rename to src/audit-logs/tests/unit/filter-events.ts index 626779a028..40d4bea2a6 100644 --- a/src/audit-logs/tests/unit/filter-events.js +++ b/src/audit-logs/tests/unit/filter-events.ts @@ -1,70 +1,90 @@ import { describe, expect, test } from 'vitest' import { filterByAllowlistValues, filterAndUpdateGhesDataByAllowlistValues } from '../../lib' +import type { RawAuditLogEventT, VersionedAuditLogData } from '../../types' describe('audit log event fitering', () => { test('matches single allowlist value', () => { - const eventsToProcess = [ + const eventsToProcess: RawAuditLogEventT[] = [ { action: 'repo.create', _allowlists: ['user'], description: 'repo was created', + docs_reference_links: '', + ghes: {}, }, ] - const filteredEvents = filterByAllowlistValues(eventsToProcess, 'user') + const filteredEvents = filterByAllowlistValues(eventsToProcess, 'user', [], { + sha: '', + appendedDescriptions: {}, + }) expect(filteredEvents[0].action).toEqual('repo.create') }) test('matches multiple allowlist values', () => { - const eventsToProcess = [ + const eventsToProcess: RawAuditLogEventT[] = [ { action: 'repo.create', _allowlists: ['user'], description: 'repo was created', + docs_reference_links: '', + ghes: {}, }, { action: 'repo.delete', _allowlists: ['organization'], description: 'repo was deleted', + docs_reference_links: '', + ghes: {}, }, ] - const filteredEvents = filterByAllowlistValues(eventsToProcess, ['user', 'organization']) + const filteredEvents = filterByAllowlistValues(eventsToProcess, ['user', 'organization'], [], { + sha: '', + appendedDescriptions: {}, + }) expect(filteredEvents[0].action).toEqual('repo.create') expect(filteredEvents.length).toEqual(2) }) test('does not match non-matching allowlist value', () => { - const eventsToProcess = [ + const eventsToProcess: RawAuditLogEventT[] = [ { action: 'repo.create', _allowlists: ['user'], description: 'repo was created', + docs_reference_links: '', + ghes: {}, }, ] - const filteredEvents = filterByAllowlistValues(eventsToProcess, 'organization') + const filteredEvents = filterByAllowlistValues(eventsToProcess, 'organization', [], { + sha: '', + appendedDescriptions: {}, + }) expect(filteredEvents.length).toBe(0) }) test('ghes filters and updates multiple ghes versions', () => { - const eventsToProcess = [ + const eventsToProcess: RawAuditLogEventT[] = [ { action: 'repo.create', description: 'repo was created', + docs_reference_links: '', + _allowlists: [], ghes: { '3.10': { _allowlists: ['user'], }, - 3.11: { + '3.11': { _allowlists: ['user'], }, }, }, ] - const currentEvents = { + const currentEvents: VersionedAuditLogData = { 'ghes-3.11': { organization: [ { @@ -90,10 +110,13 @@ describe('audit log event fitering', () => { eventsToProcess, 'user', currentEvents, - {}, + { + sha: '', + appendedDescriptions: {}, + }, auditLogPage, ) - const getActions = (version) => + const getActions = (version: string) => currentEvents[version][auditLogPage].map((event) => event.action) expect(getActions('ghes-3.10').includes('repo.create')).toBe(true) expect(getActions('ghes-3.11').includes('repo.create')).toBe(true) diff --git a/src/events/components/experiments/experiments.ts b/src/events/components/experiments/experiments.ts index 6bd8f1ba29..b1da9440f6 100644 --- a/src/events/components/experiments/experiments.ts +++ b/src/events/components/experiments/experiments.ts @@ -21,7 +21,7 @@ export const EXPERIMENTS = { ai_search_experiment: { key: 'ai_search_experiment', isActive: true, // Set to false when the experiment is over - percentOfUsersToGetExperiment: 20, // 20% of users will get the experiment + percentOfUsersToGetExperiment: 30, // 30% of users will get the experiment includeVariationInContext: true, // All events will include the `experiment_variation` of the `ai_search_experiment` limitToLanguages: ['en'], // Only users with the `en` language will be included in the experiment alwaysShowForStaff: true, // When set to true, staff will always see the experiment (determined by the `staffonly` cookie) diff --git a/src/fixtures/fixtures/data/ui.yml b/src/fixtures/fixtures/data/ui.yml index fd016a36d5..87dd1ea686 100644 --- a/src/fixtures/fixtures/data/ui.yml +++ b/src/fixtures/fixtures/data/ui.yml @@ -47,7 +47,7 @@ search: privacy_disclaimer: For product and service improvement purposes, the GitHub Docs team will retain questions and answers generated in the Docs search function. Please see the GitHub Privacy Statement to review how GitHub collects and uses your data. ai: disclaimer: Copilot uses AI. Check for mistakes. - references: Additional docs + references: Copilot Sources loading_status_message: Loading Copilot response... done_loading_status_message: Done loading Copilot response copy_answer: Copy answer diff --git a/src/ghes-releases/scripts/create-enterprise-issue.js b/src/ghes-releases/scripts/create-enterprise-issue.ts old mode 100755 new mode 100644 similarity index 75% rename from src/ghes-releases/scripts/create-enterprise-issue.js rename to src/ghes-releases/scripts/create-enterprise-issue.ts index ef4fd11d91..0af52f0d79 --- a/src/ghes-releases/scripts/create-enterprise-issue.js +++ b/src/ghes-releases/scripts/create-enterprise-issue.ts @@ -8,6 +8,39 @@ import { latest, oldestSupported } from '#src/versions/lib/enterprise-server-rel import { getContents } from '#src/workflows/git-utils.ts' import github from '#src/workflows/github.ts' +interface ReleaseDates { + [releaseNumber: string]: { + start: string + end: string + prp?: string + feature_freeze?: string + code_freeze?: string + release_candidate?: string + } +} + +interface ReleaseTemplate { + content: string + issue?: { + number: number + html_url: string + } +} + +interface ReleaseTemplates { + [templateName: string]: ReleaseTemplate +} + +interface ReleaseTemplateContext { + [key: string]: string +} + +interface IssueSearchOpts { + labels?: string[] + searchQuery?: string + titleMatch?: string +} + // Required by github() to authenticate if (!process.env.GITHUB_TOKEN) { throw new Error('Error! You must have a GITHUB_TOKEN set in an .env file to run this script.') @@ -99,7 +132,7 @@ async function createReleaseIssue() { // Only open an issue if today is within 30 days before // the release candidate date - if (getNumberDaysUntilMilestone(rcDate) > 30) { + if (getNumberDaysUntilMilestone(rcDate || '') > 30) { console.log( `The ${releaseNumber} release candidate is not until ${rcDate}! An issue will be opened 30 days prior to the release candidate date.`, ) @@ -134,11 +167,18 @@ async function createReleaseIssue() { releaseTemplateContext, ) await addRepoLabels(repo, labels) - await updateIssue(repo, template.issue.number, title, body, labels) + await updateIssue(repo, template.issue!.number, title, body, labels) } } -async function createIssue(fullRepo, title, body, labels, releaseNumber, releaseType) { +async function createIssue( + fullRepo: string, + title: string, + body: string, + labels: string[], + releaseNumber: string, + releaseType: string, +) { const [owner, repo] = fullRepo.split('/') if (!owner || !repo) throw new Error('Please provide a valid repo name in the format owner/repo') let issue @@ -150,7 +190,7 @@ async function createIssue(fullRepo, title, body, labels, releaseNumber, release body, labels, }) - } catch (error) { + } catch (error: any) { console.log(`#ERROR# ${error}\nšŸ›‘ There was an error creating the issue.`) throw error } @@ -163,7 +203,13 @@ async function createIssue(fullRepo, title, body, labels, releaseNumber, release return issue } -async function updateIssue(fullRepo, issueNumber, title, body, labels) { +async function updateIssue( + fullRepo: string, + issueNumber: number, + title: string, + body: string, + labels: string[], +) { const [owner, repo] = fullRepo.split('/') if (!owner || !repo) throw new Error('Please provide a valid repo name in the format owner/repo') @@ -177,7 +223,7 @@ async function updateIssue(fullRepo, issueNumber, title, body, labels) { body, labels, }) - } catch (error) { + } catch (error: any) { console.log( `#ERROR# ${error}\nšŸ›‘ There was an error updating issue ${issueNumber} in ${fullRepo}.`, ) @@ -188,9 +234,9 @@ async function updateIssue(fullRepo, issueNumber, title, body, labels) { } } -async function addRepoLabels(fullRepo, labels) { +async function addRepoLabels(fullRepo: string, labels: string[]) { const [owner, repo] = fullRepo.split('/') - const labelsToAdd = [] + const labelsToAdd: string[] = [] for (const name of labels) { try { await octokit.request('GET /repos/{owner}/{repo}/labels/{name}', { @@ -198,7 +244,7 @@ async function addRepoLabels(fullRepo, labels) { repo, name, }) - } catch (error) { + } catch (error: any) { if (error.status === 404) { labelsToAdd.push(name) } else { @@ -214,65 +260,78 @@ async function addRepoLabels(fullRepo, labels) { repo, name, }) - } catch (error) { + } catch (error: any) { console.log(`#ERROR# ${error}\nšŸ›‘ There was an error adding the label ${name}.`) throw error } } } -function getReleaseTemplates() { +function getReleaseTemplates(): ReleaseTemplates { const templateFiles = walk('src/ghes-releases/lib/release-templates', { includeBasePath: true, directories: false, globs: ['**/*.md'], ignore: ['**/README.md'], }) - const releaseTemplates = {} + const releaseTemplates: ReleaseTemplates = {} for (const file of templateFiles) { releaseTemplates[basename(file, '.md')] = { content: readFileSync(file, 'utf8') } } return releaseTemplates } -function getReleaseTemplateContext(releaseNumber, releaseInfo, releaseTemplates) { - const context = { +function getReleaseTemplateContext( + releaseNumber: string, + releaseInfo: ReleaseDates[string], + releaseTemplates: ReleaseTemplates, +): ReleaseTemplateContext { + const context: ReleaseTemplateContext = { 'release-number': releaseNumber, 'release-target-date': releaseInfo.start, - 'release-prp': releaseInfo.prp, - 'release-feature-freeze-date': releaseInfo.feature_freeze, - 'release-code-freeze-date': releaseInfo.code_freeze, - 'release-rc-target-date': releaseInfo.release_candidate, + 'release-prp': releaseInfo.prp || '', + 'release-feature-freeze-date': releaseInfo.feature_freeze || '', + 'release-code-freeze-date': releaseInfo.code_freeze || '', + 'release-rc-target-date': releaseInfo.release_candidate || '', } // Add a context variable for each issue url for (const [templateName, template] of Object.entries(releaseTemplates)) { - context[`${templateName}-url`] = template.issue.html_url + if (template.issue) { + context[`${templateName}-url`] = template.issue.html_url + } } // Create a context variable for each of the // 7 days before release-rc-target-date - const rcTargetDate = new Date(releaseInfo.release_candidate).getTime() - for (let i = 1; i <= 7; i++) { - const day = i - const milliSecondsBefore = day * 24 * 60 * 60 * 1000 - const rcDateBefore = new Date(rcTargetDate - milliSecondsBefore).toISOString().slice(0, 10) - context[`release-rc-target-date-minus-${day}`] = rcDateBefore + if (releaseInfo.release_candidate) { + const rcTargetDate = new Date(releaseInfo.release_candidate).getTime() + for (let i = 1; i <= 7; i++) { + const day = i + const milliSecondsBefore = day * 24 * 60 * 60 * 1000 + const rcDateBefore = new Date(rcTargetDate - milliSecondsBefore).toISOString().slice(0, 10) + context[`release-rc-target-date-minus-${day}`] = rcDateBefore + } } return context } -async function getRenderedTemplate(templateContent, releaseTemplateContext) { +async function getRenderedTemplate( + templateContent: string, + releaseTemplateContext: ReleaseTemplateContext, +) { const { content, data } = matter(templateContent) const title = await liquid.parseAndRender(data.title, releaseTemplateContext) const body = await liquid.parseAndRender(content, releaseTemplateContext) const labels = await Promise.all( - data.labels.map(async (label) => await liquid.parseAndRender(label, releaseTemplateContext)), + data.labels.map( + async (label: string) => await liquid.parseAndRender(label, releaseTemplateContext), + ), ) return { title, body, labels } } -function getNumberDaysUntilMilestone(milestoneDate) { +function getNumberDaysUntilMilestone(milestoneDate: string): number { const today = new Date().toISOString().slice(0, 10) const nextMilestoneDateTime = new Date(milestoneDate).getTime() const todayTime = new Date(today).getTime() @@ -281,7 +340,7 @@ function getNumberDaysUntilMilestone(milestoneDate) { return Math.floor(differenceInMilliseconds / (1000 * 60 * 60 * 24)) } -function getNextReleaseNumber(releaseDates) { +function getNextReleaseNumber(releaseDates: ReleaseDates): string { const indexOfLatest = Object.keys(releaseDates).indexOf(latest) const indexOfNext = indexOfLatest + 1 return Object.keys(releaseDates)[indexOfNext] @@ -292,9 +351,9 @@ function getNextReleaseNumber(releaseDates) { // labels: ['enterprise deprecation', 'ghes 3.0'] // titleMatch: 'GHES 3.0' async function isExistingIssue( - repo, - opts = { labels: undefined, searchQuery: undefined, titleMatch: undefined }, -) { + repo: string, + opts: IssueSearchOpts = { labels: undefined, searchQuery: undefined, titleMatch: undefined }, +): Promise { const { labels, searchQuery, titleMatch } = opts const labelQuery = labels && labels.map((label) => `label:"${encodeURI(label)}"`).join('+') let query = encodeURIComponent('is:issue ' + `repo:${repo} `) @@ -314,21 +373,21 @@ async function isExistingIssue( console.log(`Issue ${issue.html_url} already exists for this release.`) return true } - return + return false } } const issueExists = !!issues.data.items.length if (issueExists) { console.log( - `Issue ${issues.data.items.map((item) => item.html_url)} already exists for this release.`, + `Issue ${issues.data.items.map((item: { html_url: string }) => item.html_url)} already exists for this release.`, ) } return issueExists } -async function getReleaseDates() { - let rawDates = [] +async function getReleaseDates(): Promise { + let rawDates: ReleaseDates = {} try { rawDates = JSON.parse( await getContents('github', 'enterprise-releases', 'master', 'releases.json'), diff --git a/src/ghes-releases/scripts/deprecate/create-docs-ghes-version-repo.sh b/src/ghes-releases/scripts/deprecate/create-docs-ghes-version-repo.sh index cd9f5e449d..eb8ec4c9d1 100755 --- a/src/ghes-releases/scripts/deprecate/create-docs-ghes-version-repo.sh +++ b/src/ghes-releases/scripts/deprecate/create-docs-ghes-version-repo.sh @@ -50,7 +50,7 @@ gh api -X POST "/repos/github/docs-ghes-$version/pages" \ echo "--- Update custom properties" gh api --method PATCH /repos/github/docs-ghes-$version/properties/values \ -f "properties[][property_name]=ownership-name" \ - -f "properties[][value]=@github/docs-engineering" \ + -f "properties[][value]=@github/docs" \ -f "properties[][property_name]=ownership-type" \ -f "properties[][value]=Team" \ --silent @@ -74,4 +74,4 @@ git push cd .. echo "--- END FILE UPDATES" echo "--- MANUAL NOTES" -echo "Manually disable releases, packages, and deployments" \ No newline at end of file +echo "Manually disable releases, packages, and deployments" diff --git a/src/ghes-releases/scripts/release-banner.js b/src/ghes-releases/scripts/release-banner.ts old mode 100755 new mode 100644 similarity index 51% rename from src/ghes-releases/scripts/release-banner.js rename to src/ghes-releases/scripts/release-banner.ts index da02a072d5..886e0c7170 --- a/src/ghes-releases/scripts/release-banner.js +++ b/src/ghes-releases/scripts/release-banner.ts @@ -10,7 +10,9 @@ import { program } from 'commander' import { allVersions } from '#src/versions/lib/all-versions.js' const releaseCandidateJSFile = 'src/versions/lib/enterprise-server-releases.js' -const allowedActions = ['create', 'remove'] +const allowedActions = ['create', 'remove'] as const + +type AllowedAction = (typeof allowedActions)[number] program .description('Create or remove a release candidate banner for the provided docs version.') @@ -23,7 +25,7 @@ program const options = program.opts() -if (!allowedActions.includes(options.action)) { +if (!allowedActions.includes(options.action as AllowedAction)) { console.log(`Error! You must specify --action <${allowedActions.join(' or ')}>.`) process.exit(1) } @@ -36,30 +38,37 @@ if (!Object.keys(allVersions).includes(options.version)) { } // Load the release candidate variable -let jsCode = await fs.readFile(releaseCandidateJSFile, 'utf8') -const lineRegex = /export const releaseCandidate = .*/ -if (!lineRegex.test(jsCode)) { - throw new Error( - `The file ${releaseCandidateJSFile} does not contain a release candidate variable. ('export const releaseCandidate = ...')`, - ) -} +async function main(): Promise { + let jsCode = await fs.readFile(releaseCandidateJSFile, 'utf8') + const lineRegex = /export const releaseCandidate = .*/ + if (!lineRegex.test(jsCode)) { + throw new Error( + `The file ${releaseCandidateJSFile} does not contain a release candidate variable. ('export const releaseCandidate = ...')`, + ) + } -// Create or remove the variable -if (options.action === 'create') { - jsCode = jsCode.replace( - lineRegex, - `export const releaseCandidate = '${options.version.split('@')[1]}'`, - ) -} else if (options.action === 'remove') { - jsCode = jsCode.replace(lineRegex, `export const releaseCandidate = null`) -} + // Create or remove the variable + if (options.action === 'create') { + jsCode = jsCode.replace( + lineRegex, + `export const releaseCandidate = '${options.version.split('@')[1]}'`, + ) + } else if (options.action === 'remove') { + jsCode = jsCode.replace(lineRegex, `export const releaseCandidate = null`) + } -// Update the file -await fs.writeFile(releaseCandidateJSFile, jsCode) + // Update the file + await fs.writeFile(releaseCandidateJSFile, jsCode) -// Display next steps -console.log(`\nDone! Commit the update to ${releaseCandidateJSFile}. This ${options.action}s the banner for ${options.version}. + // Display next steps + console.log(`\nDone! Commit the update to ${releaseCandidateJSFile}. This ${options.action}s the banner for ${options.version}. - To change the banner text, you can edit header.notices.release_candidate in data/ui.yml. - To change the banner styling, you can edit includes/header-notification.html. `) +} + +main().catch((error) => { + console.error('Error:', error) + process.exit(1) +}) diff --git a/src/ghes-releases/scripts/update-enterprise-dates.js b/src/ghes-releases/scripts/update-enterprise-dates.ts old mode 100755 new mode 100644 similarity index 82% rename from src/ghes-releases/scripts/update-enterprise-dates.js rename to src/ghes-releases/scripts/update-enterprise-dates.ts index 0f60612189..74aa7d3e58 --- a/src/ghes-releases/scripts/update-enterprise-dates.js +++ b/src/ghes-releases/scripts/update-enterprise-dates.ts @@ -11,6 +11,21 @@ import fs from 'fs/promises' import { getContents } from '#src/workflows/git-utils.ts' +interface EnterpriseDates { + [releaseNumber: string]: { + releaseDate: string + deprecationDate: string + } +} + +interface RawReleaseData { + [releaseNumber: string]: { + release_candidate?: string + start: string + end: string + } +} + const __dirname = path.dirname(fileURLToPath(import.meta.url)) const enterpriseDatesFile = path.join(__dirname, '../lib/enterprise-dates.json') const enterpriseDatesString = await fs.readFile(enterpriseDatesFile, 'utf8') @@ -22,9 +37,9 @@ if (!process.env.GITHUB_TOKEN) { main() -async function main() { +async function main(): Promise { // send owner, repo, ref, path - let rawDates = [] + let rawDates: RawReleaseData = {} try { rawDates = JSON.parse( await getContents('github', 'enterprise-releases', 'master', 'releases.json'), @@ -36,7 +51,7 @@ async function main() { throw error } - const formattedDates = {} + const formattedDates: EnterpriseDates = {} Object.entries(rawDates).forEach(([releaseNumber, releaseObject]) => { formattedDates[releaseNumber] = { releaseDate: releaseObject.release_candidate || releaseObject.start, diff --git a/src/search/components/input/AskAIResults.tsx b/src/search/components/input/AskAIResults.tsx index d374466f9a..7017cebfe5 100644 --- a/src/search/components/input/AskAIResults.tsx +++ b/src/search/components/input/AskAIResults.tsx @@ -4,7 +4,14 @@ import { executeAISearch } from '../helpers/execute-search-actions' import { useRouter } from 'next/router' import { useTranslation } from '@/languages/components/useTranslation' import { ActionList, IconButton, Spinner } from '@primer/react' -import { CheckIcon, CopyIcon, FileIcon, ThumbsdownIcon, ThumbsupIcon } from '@primer/octicons-react' +import { + CheckIcon, + CopilotIcon, + CopyIcon, + FileIcon, + ThumbsdownIcon, + ThumbsupIcon, +} from '@primer/octicons-react' import { announce } from '@primer/live-region-element' import { useAISearchLocalStorageCache } from '../hooks/useAISearchLocalStorageCache' import { UnrenderedMarkdownContent } from '@/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent' @@ -334,6 +341,64 @@ export function AskAIResults({ return (
+ {!aiCouldNotAnswer && references && references.length > 0 ? ( + <> + + + + {t('search.ai.references')} + + {references + .map((source, index) => { + if (index >= MAX_REFERENCES_TO_SHOW) { + return null + } + const refIndex = index + referencesIndexOffset + return ( + { + referenceOnSelect(source.url) + }} + active={refIndex === selectedIndex} + ref={(element) => { + if (listElementsRef.current) { + listElementsRef.current[refIndex] = element + } + }} + > + + {source.title} + + ) + }) + .filter(Boolean)} + + + + ) : null} + + + {t('search.overlay.ai_autocomplete_list_heading')} + {initialLoading ? (
@@ -423,55 +488,6 @@ export function AskAIResults({ >
) : null} - {!aiCouldNotAnswer && references && references.length > 0 ? ( - <> -