Skip to content

Commit 8e7e60c

Browse files
Merge remote-tracking branch 'origin/main' into fix/6337-shell-startup
# Conflicts: # apps/server/src/project/ProjectSetupScriptRunner.ts
2 parents 4348e5c + 8b23382 commit 8e7e60c

707 files changed

Lines changed: 28519 additions & 7308 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/actions/setup-ci-deps/action.yml‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ runs:
3535
- name: Cache Vite+
3636
id: vp_cache
3737
if: steps.vp.outputs.version != ''
38-
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
38+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
3939
with:
4040
path: ~/.vite-plus
4141
key: vite-plus-home-v2-${{ runner.os }}-${{ runner.arch }}-${{ steps.vp.outputs.version }}-node${{ steps.vp.outputs.node }}-${{ steps.vp.outputs.pnpm }}
@@ -56,7 +56,7 @@ runs:
5656
# partial bytes, so a shorter limit costs a slow download only a retry.
5757
- name: Setup Vite+
5858
if: steps.vp_cache.outputs.cache-hit != 'true'
59-
uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1
59+
uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1.15.0
6060
env:
6161
VP_DOWNLOAD_TIMEOUT: "120"
6262
with:
@@ -92,7 +92,7 @@ runs:
9292
- name: Restore pnpm store
9393
id: pnpm_cache
9494
if: inputs.install == 'true'
95-
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
95+
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
9696
with:
9797
path: ${{ steps.pnpm_store.outputs.path }}
9898
key: pnpm-${{ steps.pnpm_store.outputs.namespace }}-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', 'package.json') }}
@@ -106,7 +106,7 @@ runs:
106106
- name: Restore lockfile verification
107107
id: pnpm_verified
108108
if: inputs.install == 'true'
109-
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
109+
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
110110
with:
111111
path: ${{ steps.pnpm_store.outputs.verified }}
112112
key: pnpm-verified-v1-${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', 'package.json') }}
@@ -121,7 +121,7 @@ runs:
121121

122122
- name: Save lockfile verification
123123
if: inputs.install == 'true' && steps.pnpm_verified.outputs.cache-hit != 'true'
124-
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
124+
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
125125
with:
126126
path: ${{ steps.pnpm_store.outputs.verified }}
127127
key: ${{ steps.pnpm_verified.outputs.cache-primary-key }}
@@ -138,7 +138,7 @@ runs:
138138
steps.pnpm_cache.outputs.cache-hit != 'true' &&
139139
(steps.pnpm_store.outputs.namespace == 'full' ||
140140
steps.pnpm_cache.outputs.cache-matched-key != format('pnpm-full-v1-{0}-{1}-{2}', runner.os, runner.arch, hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', 'package.json')))
141-
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
141+
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
142142
with:
143143
path: ${{ steps.pnpm_store.outputs.path }}
144144
key: ${{ steps.pnpm_cache.outputs.cache-primary-key }}

‎.github/scripts/check-nightly-release.cjs‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,54 @@ async function resolveLatestNightlyCommit({ github, context, core }) {
121121
return { tag, sha: commit.sha, version };
122122
}
123123

124+
// The two places a nightly install looks for an update: the `update-feed` pointer, and
125+
// releases.atom, which holds the 10 newest git tags of any kind. Older installs take the first
126+
// nightly in the feed. Reports what each names beside the newest published nightly.
127+
async function readNightlyUpdateFeed({ github, context, fetch = globalThis.fetch }) {
128+
const latest = await findLatestNightly({ github, context });
129+
if (!latest) return {};
130+
131+
const base = `https://github.com/${context.repo.owner}/${context.repo.repo}`;
132+
// Only a missing or malformed pointer is stale. Rewriting deletes the old file first, so a
133+
// GitHub outage must not look like one.
134+
// Node waits five minutes for a stalled response, which is this job's whole time limit.
135+
const signal = () => AbortSignal.timeout(30_000);
136+
const pointer = await fetch(`${base}/releases/download/update-feed/nightly.json`, {
137+
signal: signal(),
138+
});
139+
if (!pointer.ok && pointer.status !== 404) {
140+
throw new Error(`The update-feed pointer answered HTTP ${pointer.status}.`);
141+
}
142+
let pointerTag;
143+
if (pointer.ok) {
144+
// A body that fails to arrive throws here; only one that is not JSON counts as malformed.
145+
const body = await pointer.text();
146+
try {
147+
pointerTag = JSON.parse(body)?.tag_name;
148+
} catch {}
149+
}
150+
151+
// An unreadable feed leaves `feedTags` unset, so the pointer is still repaired. An error page
152+
// can answer HTTP 200, so only an Atom document counts as read.
153+
let feedTags;
154+
try {
155+
const feed = await fetch(`${base}/releases.atom`, { signal: signal() });
156+
const body = feed.ok ? await feed.text() : "";
157+
if (/<feed[\s>]/.test(body)) {
158+
feedTags = Array.from(body.matchAll(/href="[^"]*\/releases\/tag\/([^"]+)"/g), (match) =>
159+
decodeURIComponent(match[1]),
160+
);
161+
}
162+
} catch {}
163+
const feedNightly = feedTags?.find((tag) => /^v?\d+\.\d+\.\d+-nightly\./.test(tag));
164+
165+
return { tag: latest.tagName, pointerTag, feedNightly, feedTags };
166+
}
167+
124168
module.exports = {
125169
assertCommitOnDefaultBranch,
126170
assertReleaseSource,
127171
shouldReleaseNightly,
128172
resolveLatestNightlyCommit,
173+
readNightlyUpdateFeed,
129174
};

‎.github/scripts/check-nightly-release.test.cjs‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,81 @@ test("stable releases fail when no nightly is published", async () => {
237237
const { options } = nightlyCommitFixture({ releases: [nightly(1, { tagName: "v1.0.0" })] });
238238
await assert.rejects(resolveLatestNightlyCommit(options), /No published nightly found/);
239239
});
240+
241+
const { readNightlyUpdateFeed } = require("./check-nightly-release.cjs");
242+
243+
// GitHub percent-encodes tag names in the feed, and any tag in the repository takes a slot.
244+
const atomEntry = (tag) =>
245+
`<entry><link rel="alternate" type="text/html" href="https://github.com/example/app/releases/tag/${encodeURIComponent(tag)}"/></entry>`;
246+
const atomFeed = (tags) => new Response(`<feed>${tags.map(atomEntry).join("")}</feed>`);
247+
const NEWEST = "v1.0.1-nightly.20260905.123";
248+
const OLDER = "v1.0.1-nightly.20260905.100";
249+
250+
function updateFeedFixture({ feed, pointer }) {
251+
const { options } = fixture({
252+
releases: [nightly(1), nightly(0, { tagName: "v1.0.2-preview.1" })],
253+
});
254+
options.fetch = async (url) =>
255+
url === "https://github.com/example/app/releases.atom"
256+
? feed
257+
: url === "https://github.com/example/app/releases/download/update-feed/nightly.json"
258+
? pointer
259+
: assert.fail(`unexpected fetch ${url}`);
260+
return options;
261+
}
262+
263+
const missing = () => new Response("Not Found", { status: 404 });
264+
// HTTP 200, then the connection drops before the body arrives.
265+
const brokenBody = () =>
266+
new Response(new ReadableStream({ start: (controller) => controller.error(new Error("reset")) }));
267+
for (const [name, { feed, pointer }, expected] of [
268+
[
269+
"a nightly crowded out of the feed and a missing pointer",
270+
{ feed: atomFeed(["v1.0.2-preview.1", "mobile-android-build/2.3.0/abc"]), pointer: missing() },
271+
{ pointerTag: undefined, feedNightly: undefined },
272+
],
273+
[
274+
"the first nightly in the feed, which is the one older installs take",
275+
{ feed: atomFeed(["update-feed", OLDER, NEWEST]), pointer: Response.json({ tag_name: OLDER }) },
276+
{ pointerTag: OLDER, feedNightly: OLDER },
277+
],
278+
[
279+
"a malformed pointer as stale and an unreadable feed as naming nothing",
280+
{ feed: new Response("", { status: 503 }), pointer: Response.json(null) },
281+
{ pointerTag: undefined, feedNightly: undefined, unreadable: true },
282+
],
283+
[
284+
"an HTML page with release links as an unreadable feed",
285+
{
286+
feed: new Response(
287+
`<html><feedback-form></feedback-form><a href="/example/app/releases/tag/${OLDER}">x</a></html>`,
288+
),
289+
pointer: Response.json({ tag_name: NEWEST }),
290+
},
291+
{ pointerTag: NEWEST, feedNightly: undefined, unreadable: true },
292+
],
293+
[
294+
"the pointer when the feed's body fails to arrive",
295+
{ feed: brokenBody(), pointer: Response.json({ tag_name: NEWEST }) },
296+
{ pointerTag: NEWEST, feedNightly: undefined, unreadable: true },
297+
],
298+
]) {
299+
test(`update feed check reports ${name}`, async () => {
300+
const { tag, pointerTag, feedNightly, feedTags } = await readNightlyUpdateFeed(
301+
updateFeedFixture({ feed, pointer }),
302+
);
303+
const { unreadable = false, ...named } = expected;
304+
assert.deepEqual({ tag, pointerTag, feedNightly }, { tag: NEWEST, ...named });
305+
assert.equal(feedTags === undefined, unreadable);
306+
});
307+
}
308+
309+
for (const [name, pointer, error] of [
310+
["answers with an error", () => new Response("", { status: 503 }), /pointer answered HTTP 503/],
311+
["drops the connection", brokenBody, /reset/],
312+
]) {
313+
test(`update feed check does not call a pointer stale when GitHub ${name}`, async () => {
314+
const options = updateFeedFixture({ feed: atomFeed([NEWEST]), pointer: pointer() });
315+
await assert.rejects(readNightlyUpdateFeed(options), error);
316+
});
317+
}

‎.github/workflows/agent-context-budget.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
ALLOW_INCREASE: ${{ contains(github.event.pull_request.labels.*.name, 'agent-context-increase') }}
2525
steps:
2626
- name: Checkout
27-
uses: actions/checkout@v6
27+
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
2828
with:
2929
sparse-checkout: apps/server/src/provider/agentContextBudget.json
3030
sparse-checkout-cone-mode: false

0 commit comments

Comments
 (0)