Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ For more information about search for {% data variables.product.prodname_ghe_ser

{% data variables.product.prodname_ghe_server %} reconciles the state of the search index with data on the instance automatically and regularly, including:

* Issues, pull requests, repositories, and users in the database
* Issues,{% ifversion ghes > 3.17 %} projects,{% endif %} pull requests, repositories, and users in the database
* Git repositories (source code) on disk

In normal use, enterprise owners do not need to create new indices or schedule repair jobs. For troubleshooting or other support purposes, {% data variables.contact.github_support %} may instruct you to run a repair job.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ In addition to these generic non-provider patterns, {% data variables.product.pr

Service providers update the patterns used to generate tokens periodically and may support more than one version of a token. Push protection only supports the most recent token versions that {% data variables.product.prodname_secret_scanning %} can identify with confidence. This avoids push protection blocking commits unnecessarily when a result may be a false positive, which is more likely to happen with legacy tokens.<!-- markdownlint-disable-line MD053 -->

#### Multi-part secrets

<a name="multi-part-secrets"></a>

By default, {% data variables.product.prodname_secret_scanning %} supports validation for pair-matched access keys and key IDs.

{% data variables.product.prodname_secret_scanning_caps %} also supports validation for individual key IDs for Amazon AWS Access Key IDs, in addition to existing pair matching.

A key ID will show as active if {% data variables.product.prodname_secret_scanning %} confirms the key ID exists, regardless of whether or not a corresponding access key is found. The key ID will show as `inactive` if it's invalid (for example, if it is not a real key ID).

Where a valid pair is found, the {% data variables.product.prodname_secret_scanning %} alerts will be linked.<!-- markdownlint-disable-line MD053 -->

## Further reading

* [AUTOTITLE](/code-security/secret-scanning/managing-alerts-from-secret-scanning/about-alerts)
Expand Down
3 changes: 3 additions & 0 deletions data/code-languages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ bash:
bicep:
name: Bicep
comment: slash
copilot:
name: Copilot Chat prompt
comment: none
csharp:
name: C#
comment: slash
Expand Down
26 changes: 26 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@
"tcp-port-used": "1.0.2",
"tsx": "^4.19.4",
"unified": "^11.0.5",
"unist-util-find": "^3.0.0",
"unist-util-visit": "^5.0.0",
"url-template": "^3.1.1",
"walk-sync": "^4.0.1"
Expand Down
3 changes: 3 additions & 0 deletions src/content-render/liquid/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Octicon from './octicon'
import Ifversion from './ifversion'
import { Tool, tags as toolTags } from './tool'
import { Spotlight, tags as spotlightTags } from './spotlight'
import { Prompt } from './prompt'

export const engine = new Liquid({
extname: '.html',
Expand All @@ -25,6 +26,8 @@ for (const tag in spotlightTags) {
engine.registerTag(tag, Spotlight)
}

engine.registerTag('prompt', Prompt)

/**
* Like the `size` filter, but specifically for
* getting the number of keys in an object
Expand Down
31 changes: 31 additions & 0 deletions src/content-render/liquid/prompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// src/content-render/liquid/prompt.js
// Defines {% prompt %}…{% endprompt %} to wrap its content in <code> and append the Copilot icon.

import octicons from '@primer/octicons'

export const Prompt = {
type: 'block',

// Collect everything until {% endprompt %}
parse(tagToken, remainTokens) {
this.templates = []
const stream = this.liquid.parser.parseStream(remainTokens)
stream
.on('template', (tpl) => this.templates.push(tpl))
.on('tag:endprompt', () => stream.stop())
.on('end', () => {
throw new Error(`{% prompt %} tag not closed`)
})
stream.start()
},

// Render the inner Markdown, wrap in <code>, then append the SVG
render: function* (scope) {
const content = yield this.liquid.renderer.renderTemplates(this.templates, scope)

// build a URL with the prompt text encoded as query parameter
const promptParam = encodeURIComponent(content)
const href = `https://github.com/copilot?prompt=${promptParam}`
return `<code>${content}</code><a href="${href}" target="_blank" class="tooltipped tooltipped-nw ml-1" aria-label="Run this prompt in Copilot Chat" style="text-decoration:none;">${octicons.copilot.toSVG()}</a>`
},
}
203 changes: 203 additions & 0 deletions src/content-render/tests/copilot-code-blocks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import { describe, it, expect, vi } from 'vitest'
import { renderContent } from '@/content-render/index'

describe('code-header plugin', () => {
describe('copilot language code blocks', () => {
it('should render basic copilot code block without header (no copy meta)', async () => {
const markdown = '```copilot\nImprove the variable names in this function\n```'

const html = await renderContent(markdown)

// Should keep copilot as the language (not convert to text without copy meta)
expect(html).toContain('language-copilot')
// Should NOT wrap in code-example div since no copy meta
expect(html).not.toContain('code-example')
// Should NOT have header since no copy meta
expect(html).not.toContain('<header')
})

it('should render copilot code block with copy button when copy meta is present', async () => {
const markdown = '```copilot copy\nImprove the variable names in this function\n```'

const html = await renderContent(markdown)

// Should be wrapped in code-example div
expect(html).toContain('code-example')
// Should have header with copy button
expect(html).toContain('<header')
expect(html).toContain('js-btn-copy')
expect(html).toContain('language-copilot')
// Should NOT have prompt button (no prompt meta)
expect(html).not.toContain('https://github.com/copilot?prompt=')
})

it('should render copilot code block with prompt button only (no copy meta)', async () => {
const markdown = '```copilot prompt\nImprove the variable names in this function\n```'

const html = await renderContent(markdown)

// Should be wrapped in code-example div
expect(html).toContain('code-example')
// Should have header
expect(html).toContain('<header')
// Should have prompt button
expect(html).toContain('https://github.com/copilot?prompt=')
expect(html).toContain('language-copilot')
// Should NOT have copy button
expect(html).not.toContain('js-btn-copy')
})

it('should render copilot code block with both copy and prompt buttons when prompt meta is present', async () => {
const markdown = '```copilot copy prompt\nImprove the variable names in this function\n```'

const html = await renderContent(markdown)

// Should be wrapped in code-example div
expect(html).toContain('code-example')
// Should have header with copy button
expect(html).toContain('<header')
expect(html).toContain('js-btn-copy')
// Should have prompt button with encoded URL
expect(html).toContain('https://github.com/copilot?prompt=')
expect(html).toContain('Improve%20the%20variable%20names%20in%20this%20function')
// Should have Copilot icon button
expect(html).toContain('aria-label="Run this prompt in Copilot Chat"')
expect(html).toContain('language-copilot')
})

it('should render copilot code block with context reference when ref meta is present', async () => {
const markdown = `
\`\`\`javascript id=js-age
function logPersonsAge(a, b, c) {
if (c) {
console.log(a + " is " + b + " years old.");
} else {
console.log(a + " does not want to reveal their age.");
}
}
\`\`\`

\`\`\`copilot copy prompt ref=js-age
Improve the variable names in this function
\`\`\`
`

const html = await renderContent(markdown)

// Should have prompt button with both code blocks in URL
expect(html).toContain('https://github.com/copilot?prompt=')
// Should contain encoded content from both the referenced code and the prompt
expect(html).toContain('function%20logPersonsAge')
expect(html).toContain('Improve%20the%20variable%20names')
// Should have different aria-label indicating context
expect(html).toContain('aria-label="Run this prompt with context in Copilot Chat"')
})

it('should render copilot code block with prompt and ref only (no copy meta)', async () => {
const markdown = `
\`\`\`javascript id=js-age
function logPersonsAge(a, b, c) {
if (c) {
console.log(a + " is " + b + " years old.");
} else {
console.log(a + " does not want to reveal their age.");
}
}
\`\`\`

\`\`\`copilot prompt ref=js-age
Improve the variable names in this function
\`\`\`
`

const html = await renderContent(markdown)

// Should have prompt button with both code blocks in URL
expect(html).toContain('https://github.com/copilot?prompt=')
// Should contain encoded content from both the referenced code and the prompt
expect(html).toContain('function%20logPersonsAge')
expect(html).toContain('Improve%20the%20variable%20names')
// Should have different aria-label indicating context
expect(html).toContain('aria-label="Run this prompt with context in Copilot Chat"')
// Should NOT have copy button
expect(html).not.toContain('js-btn-copy')
})
})

describe('edge cases', () => {
it('should handle missing reference gracefully and fall back to current code only', async () => {
// Mock console.warn to capture warning
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})

const markdown =
'```copilot copy prompt ref=nonexistent-id\nImprove the variable names in this function\n```'

const html = await renderContent(markdown)

// Should warn about missing reference
expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining("Can't find referenced code block with id=nonexistent-id"),
)

// Should still render with prompt button using current code only
expect(html).toContain('https://github.com/copilot?prompt=')
expect(html).toContain('Improve%20the%20variable%20names%20in%20this%20function')
// Should NOT contain any referenced code since none was found
expect(html).not.toContain('function%20logPersonsAge')
// Should have standard aria-label (not context version)
expect(html).toContain('aria-label="Run this prompt in Copilot Chat"')
// Should not crash or fail
expect(html).toContain('code-example')

// Restore console.warn
consoleWarnSpy.mockRestore()
})

it('should not process annotated code blocks', async () => {
const markdown = `\`\`\`javascript copy annotate
// This is an annotation
function test() {}
\`\`\``

const html = await renderContent(markdown)

// Should NOT wrap in code-example div (annotated blocks are excluded)
expect(html).not.toContain('code-example')
})

it('should handle regular code blocks with copy', async () => {
const markdown = '```javascript copy\nfunction test() {}\n```'

const html = await renderContent(markdown)

// Should render with copy button
expect(html).toContain('code-example')
expect(html).toContain('js-btn-copy')
expect(html).toContain('language-javascript')
})
})

describe('URL encoding', () => {
it('should properly encode special characters in prompt URLs', async () => {
const markdown = '```copilot copy prompt\nHow do I handle "quotes" and & symbols?\n```'

const html = await renderContent(markdown)

// Should encode quotes and ampersands properly
expect(html).toContain('%22quotes%22')
expect(html).toContain('%26%20symbols')
})

it('should handle multiline prompts correctly', async () => {
const markdown = `\`\`\`copilot copy prompt
This is line 1
This is line 2
\`\`\``

const html = await renderContent(markdown)

// Should encode newlines properly
expect(html).toContain('This%20is%20line%201%0AThis%20is%20line%202')
})
})
})
11 changes: 11 additions & 0 deletions src/content-render/tests/prompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, expect, test } from 'vitest'
import { renderContent } from '@/content-render/index'

describe('prompt tag', () => {
test('wraps content in <code> and appends svg', async () => {
const input = 'Here is your prompt: {% prompt %}example prompt text{% endprompt %}.'
const output = await renderContent(input)
expect(output).toContain('<code>example prompt text</code><a')
expect(output).toContain('<svg')
})
})
Loading
Loading