Merge remote-tracking branch 'upstream/next' into kimi-runtime-support

This commit is contained in:
Viktorplus
2026-06-07 16:38:21 +02:00
10 changed files with 415 additions and 12 deletions

View File

@@ -1,7 +1,7 @@
'use strict';
process.env.GSD_TEST_MODE = '1';
const { test, describe, before, after } = require('node:test');
const { test, describe, before, after, beforeEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
@@ -36,6 +36,11 @@ function runRender(args = []) {
};
}
function runRenderRaw(args = []) {
const r = cp.spawnSync(process.execPath, [SCRIPT, 'render', '--repo', tmp, ...args], { encoding: 'utf8' });
return { status: r.status, stdout: r.stdout || '', stderr: r.stderr || '' };
}
before(() => { tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-changeset-')); });
after(() => { cleanup(tmp); });
@@ -928,3 +933,107 @@ describe('changeset cli render --allow-empty', () => {
}
});
});
// ---------------------------------------------------------------------------
// GROUP D — render --preview (#759)
// ---------------------------------------------------------------------------
describe('changeset cli render --preview (#759)', () => {
// Helper: reset the shared tmp dir to a clean state for preview tests.
function resetTmp() {
// Remove CHANGELOG.md if present.
const changelogPath = path.join(tmp, 'CHANGELOG.md');
if (fs.existsSync(changelogPath)) {
fs.unlinkSync(changelogPath);
}
// Remove any leftover .changeset/*.md fragments.
const changesetDir = path.join(tmp, '.changeset');
if (fs.existsSync(changesetDir)) {
for (const f of fs.readdirSync(changesetDir)) {
if (f.endsWith('.md') && f !== 'README.md') {
fs.unlinkSync(path.join(changesetDir, f));
}
}
}
}
// Reset state before each preview test so a new test added to this group
// can never inherit a CHANGELOG.md or fragments left by the previous one.
beforeEach(resetTmp);
test('render --preview prints the section to stdout and mutates nothing', () => {
writeFragment('preview-frag-one', 'Added', 900, 'preview-added-feature');
const fragmentPath = path.join(tmp, '.changeset', 'preview-frag-one.md');
const r = runRenderRaw(['--version', '9.9.0', '--date', '2026-01-02', '--preview']);
assert.equal(r.status, 0, `expected exit 0; stderr=${r.stderr}`);
assert.ok(r.stdout.includes('## [9.9.0]'), `stdout must contain ## [9.9.0]; got: ${r.stdout}`);
assert.ok(r.stdout.includes('### Added'), `stdout must contain ### Added; got: ${r.stdout}`);
assert.ok(r.stdout.includes('preview-added-feature'), `stdout must contain fragment body; got: ${r.stdout}`);
// CHANGELOG.md must NOT have been created.
assert.ok(!fs.existsSync(path.join(tmp, 'CHANGELOG.md')), 'CHANGELOG.md must NOT be created by --preview');
// Fragment file must still exist.
assert.ok(fs.existsSync(fragmentPath), 'fragment file must still exist after --preview');
});
test('render --preview with zero fragments emits the placeholder and writes nothing', () => {
// No fragments written — zero-fragment scenario.
const r = runRenderRaw(['--version', '9.9.0', '--date', '2026-01-02', '--preview']);
assert.equal(r.status, 0, `expected exit 0; stderr=${r.stderr}`);
assert.ok(r.stdout.includes('## ['), `stdout must contain a release heading; got: ${r.stdout}`);
assert.ok(r.stdout.includes('_No notable changes._'), `stdout must contain placeholder; got: ${r.stdout}`);
// CHANGELOG.md must NOT have been created.
assert.ok(!fs.existsSync(path.join(tmp, 'CHANGELOG.md')), 'CHANGELOG.md must NOT be created by zero-fragment --preview');
});
test('render --preview --json returns preview text in report with consumed 0', () => {
writeFragment('preview-frag-two', 'Fixed', 901, 'preview-fixed-bug');
const fragmentPath = path.join(tmp, '.changeset', 'preview-frag-two.md');
// runRender appends --json automatically.
const r = runRender(['--version', '9.9.0', '--date', '2026-01-02', '--preview']);
assert.equal(r.status, 0, `expected exit 0; stderr=${r.stderr}`);
assert.ok(r.report, 'report must be parseable JSON');
assert.strictEqual(r.report.consumed, 0, 'consumed must be 0 for preview');
assert.strictEqual(r.report.fragmentCount, 1, 'fragmentCount must be 1');
assert.ok(typeof r.report.preview === 'string', 'report.preview must be a string');
assert.ok(r.report.preview.includes('## [9.9.0]'), `report.preview must contain ## [9.9.0]; got: ${r.report.preview}`);
// Fragment file must still exist.
assert.ok(fs.existsSync(fragmentPath), 'fragment file must still exist after --preview --json');
});
test('render --preview with an existing CHANGELOG.md leaves it byte-identical and shows only the new section', () => {
// Seed an existing CHANGELOG with a prior dated release.
const changelogPath = path.join(tmp, 'CHANGELOG.md');
const existing =
'# Changelog\n\n## [1.0.0] - 2020-01-01\n\n### Added\n\n- old prior feature (#1)\n';
fs.writeFileSync(changelogPath, existing);
writeFragment('preview-frag-three', 'Added', 902, 'brand-new-thing');
const before = fs.readFileSync(changelogPath, 'utf8');
const r = runRenderRaw(['--version', '9.9.0', '--date', '2026-01-02', '--preview']);
assert.equal(r.status, 0, `expected exit 0; stderr=${r.stderr}`);
// Output shows the new section...
assert.ok(r.stdout.includes('## [9.9.0]'), `stdout must contain new heading; got: ${r.stdout}`);
assert.ok(r.stdout.includes('brand-new-thing'), `stdout must contain new fragment body; got: ${r.stdout}`);
// ...and NOT the prior release history (preview is the new section only).
assert.ok(!r.stdout.includes('## [1.0.0]'), `preview must NOT include prior releases; got: ${r.stdout}`);
assert.ok(!r.stdout.includes('old prior feature'), `preview must NOT include prior bullets; got: ${r.stdout}`);
// Existing CHANGELOG.md must be byte-identical — preview mutates nothing.
assert.strictEqual(
fs.readFileSync(changelogPath, 'utf8'),
before,
'CHANGELOG.md must be byte-identical after --preview',
);
});
});

View File

@@ -29,9 +29,53 @@ describe('PR policy workflow maintainer carve-outs', () => {
assertMaintainerSkip(workflow);
});
test('draft PR auto-close triggers on pull_request_target so fork PRs cannot bypass it', () => {
const workflow = readWorkflow('.github/workflows/close-draft-prs.yml');
// A bare `pull_request` trigger hands fork PRs (how first-time/external
// contributors contribute) a read-only GITHUB_TOKEN, so the close/comment
// API calls 403 and the draft PR survives — bypassing the auto-close.
// `pull_request_target` runs in the base-repo context with a write-capable
// token. Guard against a regression back to the bypassable trigger.
assert.match(workflow, /^\s*pull_request_target:/m);
assert.doesNotMatch(workflow, /^\s*pull_request:\s*$/m);
});
test('PR target validator does not run for maintainer-authored PRs', () => {
const workflow = readWorkflow('.github/workflows/pr-target-validator.yml');
assertMaintainerSkip(workflow);
});
test('draft PR sweep enforces the same policy as the event-driven close', () => {
const workflow = readWorkflow('.github/workflows/close-draft-prs-sweep.yml');
// Timer-driven in base-repo context, plus a manual dispatch for testing.
// It must NOT be a fork-triggered event (no pull_request / pull_request_target trigger).
assert.match(workflow, /schedule:/);
assert.match(workflow, /cron:\s*'0 \*\/6 \* \* \*'/);
assert.match(workflow, /workflow_dispatch:/);
assert.doesNotMatch(workflow, /^\s*pull_request(_target)?:/m);
// Write-capable token (needed to close PRs from base context). Tolerant of
// intervening blank lines or additional permission keys.
assert.match(workflow, /permissions:\s+pull-requests:\s*write/);
// Identical maintainer carve-out to close-draft-prs.yml — a Set membership
// test over author_association, negated (no github.event.pull_request in a
// scheduled run).
assert.match(workflow, /new Set\(\['OWNER', 'MEMBER', 'COLLABORATOR'\]\)/);
assert.match(workflow, /!MAINTAINER_ASSOCIATIONS\.has\([^)]*\.author_association\)/);
// Paginates over open PRs and filters to drafts.
assert.match(workflow, /github\.paginate\(github\.rest\.pulls\.list/);
assert.match(workflow, /state:\s*'open'/);
assert.match(workflow, /pr\.draft === true/);
// Same user-facing policy message as close-draft-prs.yml (locks the core
// content so the sweep cannot silently drift to a weaker message).
assert.match(workflow, /## Draft PRs are not accepted/);
assert.match(workflow, /npm run test:coverage/);
assert.match(workflow, /CONTRIBUTING\.md#pull-request-guidelines/);
});
});