Files
upstream-mirror a774ac673a
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
2026-09-23 16:12:52 +05:00

125 lines
3.8 KiB
TypeScript

const MASK_PLACEHOLDER = '\x00MASKED\x00'
function sanitizeVariables(errorOutput: string, secretValues?: string[]): string {
let sanitized = errorOutput
// Value-based masking: replace any occurrence of known secret values (like GitHub Actions).
// Uses an internal placeholder so regex patterns below can't re-match masked output.
// Sort longest-first so a longer secret is masked before a shorter substring of it.
if (secretValues) {
const sorted = secretValues.filter((v) => v.length >= 8).sort((a, b) => b.length - a.length)
for (const value of sorted) {
sanitized = sanitized.replaceAll(value, MASK_PLACEHOLDER)
}
}
// Sanitize wrangler --var KEY:VALUE patterns
sanitized = sanitized.replace(/(--var\s+)(\w+):[^ \n]+/g, '$1$2:`***`')
// Sanitize KEY=VALUE patterns where KEY looks like an env var and value is long enough
// to be a secret (e.g. flyctl secrets set). Short values like =debug are left alone.
sanitized = sanitized.replace(/\b([A-Z][A-Z_0-9]{2,})=([^ \n]{8,})/g, '$1=`***`')
// Sanitize --token VALUE and --token=VALUE patterns (e.g. vercel --token xxx)
sanitized = sanitized.replace(/(--token[\s=])\S+/g, '$1`***`')
// Sanitize connection strings (postgres://, redis://, etc.)
sanitized = sanitized.replace(
/\b(postgres|postgresql|mysql|redis|mongodb|amqp):\/\/[^\s"']+/gi,
'$1://`***`'
)
// Replace internal placeholder with final mask
sanitized = sanitized.replaceAll(MASK_PLACEHOLDER, '`***`')
return sanitized
}
export class Discord {
static AT_TEAM_MENTION = '<@&1414904550917144586>'
constructor(opts: {
webhookUrl: string
shouldNotify: boolean
totalSteps?: number
messagePrefix?: string
secretValues?: string[]
}) {
this.webhookUrl = opts.webhookUrl
this.shouldNotify = opts.shouldNotify
this.totalSteps = opts.totalSteps ?? 0
this.messagePrefix = opts.messagePrefix ?? ''
this.secretValues = opts.secretValues
}
webhookUrl: string
shouldNotify: boolean
totalSteps: number
currentStep = 0
messagePrefix: string
private secretValues?: string[]
private async send(method: string, url: string, body: unknown): Promise<any> {
try {
const response = await fetch(`${this.webhookUrl}${url}`, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
if (!response.ok) {
console.warn(
`Discord webhook request failed: ${method} -> ${response.status} ${response.statusText}`
)
return null
}
return response.json()
} catch (err) {
const errString = err instanceof Error ? (err.stack ?? err.message) : String(err)
console.warn(
`Discord webhook request failed: ${method}\n${sanitizeVariables(errString, this.secretValues)}`
)
return null
}
}
async message(content: string, { always = false }: { always?: boolean } = {}) {
if (!always && !this.shouldNotify) {
return {
edit: () => {
// noop
},
}
}
const prefixedContent = this.messagePrefix ? `${this.messagePrefix} ${content}` : content
const message = await this.send('POST', '?wait=true', {
content: sanitizeVariables(prefixedContent, this.secretValues),
})
return {
edit: async (newContent: string) => {
if (!message?.id) return
const prefixedNewContent = this.messagePrefix
? `${this.messagePrefix} ${newContent}`
: newContent
await this.send('PATCH', `/messages/${message.id}`, {
content: sanitizeVariables(prefixedNewContent, this.secretValues),
})
},
}
}
async step<T>(content: string, cb: () => Promise<T>): Promise<T> {
this.currentStep++
const message = await this.message(`[${this.currentStep}/${this.totalSteps}] ${content}...`)
try {
const result = await cb()
await message.edit(`${content} ✅`)
return result
} catch (err) {
await message.edit(`${content} ❌`)
throw err
}
}
}