mirror of https://github.com/sveltejs/svelte
commit
c22f2fa603
@ -1,229 +0,0 @@
|
|||||||
name: pkg.pr.new
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, synchronize]
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
sha:
|
|
||||||
description: 'Commit SHA to build'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
pr:
|
|
||||||
description: 'PR number to comment on'
|
|
||||||
required: true
|
|
||||||
type: number
|
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
# Skip pull_request_target events from forks — maintainers can use workflow_dispatch instead
|
|
||||||
if: >
|
|
||||||
github.event_name != 'pull_request_target' ||
|
|
||||||
github.event.pull_request.head.repo.full_name == github.repository
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# No permissions — this job runs user-controlled code
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
# For pull_request_target, check out the PR head.
|
|
||||||
# For workflow_dispatch, check out the manually specified SHA.
|
|
||||||
# For push, fall back to the push SHA.
|
|
||||||
ref: ${{ github.event.pull_request.head.sha || inputs.sha || github.sha }}
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4
|
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 22.x
|
|
||||||
cache: pnpm
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm build
|
|
||||||
|
|
||||||
- run: pnpx pkg-pr-new publish --comment=off --json output.json --compact --no-template './packages/svelte'
|
|
||||||
|
|
||||||
- name: Upload output
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: output
|
|
||||||
path: ./output.json
|
|
||||||
|
|
||||||
# Sanitizes the untrusted output from the build job before it's consumed by
|
|
||||||
# jobs with elevated permissions. This ensures that only known package names
|
|
||||||
# and valid SHA prefixes make it through.
|
|
||||||
sanitize:
|
|
||||||
needs: build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Download artifact
|
|
||||||
uses: actions/download-artifact@v7
|
|
||||||
with:
|
|
||||||
name: output
|
|
||||||
|
|
||||||
- name: Sanitize output
|
|
||||||
uses: actions/github-script@v8
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const fs = require('fs');
|
|
||||||
const raw = JSON.parse(fs.readFileSync('output.json', 'utf8'));
|
|
||||||
|
|
||||||
const ALLOWED_PACKAGES = new Set(['svelte']);
|
|
||||||
const SHA_PATTERN = /^[0-9a-f]{7}$/;
|
|
||||||
|
|
||||||
const packages = (raw.packages || [])
|
|
||||||
.filter(p => {
|
|
||||||
if (!ALLOWED_PACKAGES.has(p.name)) {
|
|
||||||
console.log(`Skipping unexpected package: ${JSON.stringify(p.name)}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const sha = p.url?.replace(/^.+@([^@]+)$/, '$1');
|
|
||||||
if (!sha || !SHA_PATTERN.test(sha)) {
|
|
||||||
console.log(`Skipping package with invalid SHA: ${JSON.stringify(p.url)}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.map(p => ({
|
|
||||||
name: p.name,
|
|
||||||
sha: p.url.replace(/^.+@([^@]+)$/, '$1'),
|
|
||||||
}));
|
|
||||||
|
|
||||||
fs.writeFileSync('sanitized-output.json', JSON.stringify({ packages }), 'utf8');
|
|
||||||
|
|
||||||
- name: Upload sanitized output
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: sanitized-output
|
|
||||||
path: ./sanitized-output.json
|
|
||||||
|
|
||||||
comment:
|
|
||||||
needs: sanitize
|
|
||||||
if: github.event_name == 'pull_request_target' || github.event_name == 'workflow_dispatch'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Download sanitized artifact
|
|
||||||
uses: actions/download-artifact@v7
|
|
||||||
with:
|
|
||||||
name: sanitized-output
|
|
||||||
|
|
||||||
- name: Resolve PR number
|
|
||||||
id: pr
|
|
||||||
uses: actions/github-script@v8
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
if (context.eventName === 'pull_request_target') {
|
|
||||||
core.setOutput('number', context.issue.number);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For workflow_dispatch, use the explicitly provided PR number.
|
|
||||||
// We can't use listPullRequestsAssociatedWithCommit because fork
|
|
||||||
// commits don't exist in the base repo, so the API returns nothing.
|
|
||||||
const pr = Number('${{ inputs.pr }}');
|
|
||||||
if (!pr || isNaN(pr)) {
|
|
||||||
core.setFailed('workflow_dispatch requires a valid pr input');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
core.setOutput('number', pr);
|
|
||||||
|
|
||||||
- name: Post or update comment
|
|
||||||
uses: actions/github-script@v8
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
script: |
|
|
||||||
const fs = require('fs');
|
|
||||||
const { packages } = JSON.parse(fs.readFileSync('sanitized-output.json', 'utf8'));
|
|
||||||
|
|
||||||
if (packages.length === 0) {
|
|
||||||
console.log('No valid packages found. Skipping comment.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const issue_number = parseInt('${{ steps.pr.outputs.number }}', 10);
|
|
||||||
|
|
||||||
const bot_comment_identifier = `<!-- pkg.pr.new comment -->`;
|
|
||||||
|
|
||||||
const body = `${bot_comment_identifier}
|
|
||||||
|
|
||||||
[Playground](https://svelte.dev/playground?version=pr-${issue_number})
|
|
||||||
|
|
||||||
\`\`\`
|
|
||||||
${packages.map(p => `pnpm add https://pkg.pr.new/${p.name}@${issue_number}`).join('\n')}
|
|
||||||
\`\`\`
|
|
||||||
`;
|
|
||||||
|
|
||||||
const comments = await github.rest.issues.listComments({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number,
|
|
||||||
});
|
|
||||||
const existing = comments.data.find(c => c.body.includes(bot_comment_identifier));
|
|
||||||
|
|
||||||
if (existing) {
|
|
||||||
await github.rest.issues.updateComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
comment_id: existing.id,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
log:
|
|
||||||
needs: sanitize
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Download sanitized artifact
|
|
||||||
uses: actions/download-artifact@v7
|
|
||||||
with:
|
|
||||||
name: sanitized-output
|
|
||||||
|
|
||||||
- name: Log publish info
|
|
||||||
uses: actions/github-script@v8
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const fs = require('fs');
|
|
||||||
const { packages } = JSON.parse(fs.readFileSync('sanitized-output.json', 'utf8'));
|
|
||||||
|
|
||||||
if (packages.length === 0) {
|
|
||||||
console.log('No valid packages found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n' + '='.repeat(50));
|
|
||||||
console.log('Publish Information');
|
|
||||||
console.log('='.repeat(50));
|
|
||||||
for (const p of packages) {
|
|
||||||
console.log(`${p.name} - pnpm add https://pkg.pr.new/${p.name}@${p.sha}`);
|
|
||||||
}
|
|
||||||
const svelte = packages.find(p => p.name === 'svelte');
|
|
||||||
if (svelte) {
|
|
||||||
console.log(`\nPlayground: https://svelte.dev/playground?version=commit-${svelte.sha}`);
|
|
||||||
}
|
|
||||||
console.log('='.repeat(50));
|
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { ok, test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['client', 'hydrate'],
|
||||||
|
|
||||||
|
async test({ assert, component, target }) {
|
||||||
|
const select = target.querySelector('select');
|
||||||
|
ok(select);
|
||||||
|
|
||||||
|
assert.equal(select.selectedIndex, 0);
|
||||||
|
|
||||||
|
component.toggle();
|
||||||
|
flushSync();
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
assert.equal(select.selectedIndex, 0);
|
||||||
|
|
||||||
|
component.toggle();
|
||||||
|
flushSync();
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
assert.equal(select.selectedIndex, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<script>
|
||||||
|
let show_extra = false;
|
||||||
|
const attributes = { 'aria-label': 'choice' };
|
||||||
|
|
||||||
|
export function toggle() {
|
||||||
|
show_extra = !show_extra;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<select {...attributes}>
|
||||||
|
<option value="">Choose an option</option>
|
||||||
|
<option value="first">First</option>
|
||||||
|
{#if show_extra}
|
||||||
|
<option value="extra">Extra</option>
|
||||||
|
{/if}
|
||||||
|
</select>
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['hydrate'],
|
||||||
|
ssrHtml: '<p>failed: error</p> <button>reset</button>',
|
||||||
|
transformError: () => 'error',
|
||||||
|
|
||||||
|
test({ assert, target, logs }) {
|
||||||
|
// `onerror` is called upon hydration with the deserialized error
|
||||||
|
assert.deepEqual(logs, ['onerror: error']);
|
||||||
|
|
||||||
|
const btn = target.querySelector('button');
|
||||||
|
btn?.click();
|
||||||
|
flushSync();
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, '<p>recovered</p> <button>reset</button>');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
<script>
|
||||||
|
throw new Error('unlucky');
|
||||||
|
</script>
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
<script>
|
||||||
|
import Child from './child.svelte';
|
||||||
|
|
||||||
|
let recovered = $state(false);
|
||||||
|
let reset_fn = $state();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:boundary
|
||||||
|
onerror={(error, reset) => {
|
||||||
|
console.log(`onerror: ${error}`);
|
||||||
|
reset_fn = reset;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if recovered}
|
||||||
|
<p>recovered</p>
|
||||||
|
{:else}
|
||||||
|
<Child />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#snippet failed(error)}
|
||||||
|
<p>failed: {error}</p>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onclick={() => {
|
||||||
|
recovered = true;
|
||||||
|
reset_fn();
|
||||||
|
}}>reset</button>
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['hydrate'],
|
||||||
|
ssrHtml: '<p>failed: error</p> <button>reset</button>',
|
||||||
|
transformError: () => 'error',
|
||||||
|
|
||||||
|
test({ assert, target }) {
|
||||||
|
const btn = target.querySelector('button');
|
||||||
|
btn?.click();
|
||||||
|
flushSync();
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, '<p>recovered</p>');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
<script>
|
||||||
|
throw new Error('unlucky');
|
||||||
|
</script>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<script>
|
||||||
|
import Child from './child.svelte';
|
||||||
|
|
||||||
|
let recovered = $state(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:boundary>
|
||||||
|
{#if recovered}
|
||||||
|
<p>recovered</p>
|
||||||
|
{:else}
|
||||||
|
<Child />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#snippet failed(error, reset)}
|
||||||
|
<p>failed: {error}</p>
|
||||||
|
<button
|
||||||
|
onclick={() => {
|
||||||
|
recovered = true;
|
||||||
|
reset();
|
||||||
|
}}>reset</button>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
@ -1,3 +1,15 @@
|
|||||||
|
minimumReleaseAge: 2880
|
||||||
|
minimumReleaseAgeExclude:
|
||||||
|
- '@sveltejs/*'
|
||||||
|
- svelte
|
||||||
|
- esrap
|
||||||
|
- devalue
|
||||||
|
- zimmerframe
|
||||||
|
- prettier-plugin-svelte
|
||||||
|
- svelte-check
|
||||||
|
- esm-env
|
||||||
|
blockExoticSubdeps: true
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
- 'packages/*'
|
- 'packages/*'
|
||||||
- 'playgrounds/*'
|
- 'playgrounds/*'
|
||||||
|
|||||||
Loading…
Reference in new issue