diff --git a/.agents/skills/triage/comment.md b/.agents/skills/triage/comment.md
index 6dc3d5ea1574..9697dc2316b4 100644
--- a/.agents/skills/triage/comment.md
+++ b/.agents/skills/triage/comment.md
@@ -35,24 +35,38 @@ Generate and return a GitHub comment following the template below.
The **Fix** line in the template has three possible forms. Choose the one that matches the triage outcome:
-1. **You created a fix:** Use `I was able to fix this issue.` and include the suggested fix link.
+1. **You created a fix:** Use `I found a potential fix for this issue.` and include the suggested fix link. Avoid claiming certainty, even if the fix passes tests, frame it as a suggestion that needs human review.
2. **The issue is already fixed on main** (e.g. the user is on an older major version and the bug doesn't reproduce on current main): Use `This issue has already been fixed.` and tell the user how to get the fix (e.g. upgrade). Link the relevant upgrade guide if applicable: [v6](https://docs.astro.build/en/guides/upgrade-to/v6/), [v5](https://docs.astro.build/en/guides/upgrade-to/v5/).
-3. **You could not find or create a fix:** Use `I was unable to find a fix for this issue.` and give guidance or a best guess at where the fix might be.
+3. **Low-confidence or no fix:** Use `I wasn't able to find a fix, but I identified some areas that may be relevant.` and list the files/code paths that seem related. Frame this as a jumping-off point for a human, not a diagnosis. If a failing test was added, mention it.
+4. **No leads at all:** Use `I was unable to determine the cause of this issue.` This should be rare, only use it when you genuinely have nothing useful to point to.
### "Priority" Instructions
The **Priority** line communicates the severity of this issue to maintainers. Its goal is to answer the question: **"How bad is it?"**
-Select exactly ONE priority label from the `priorityLabels` arg. Use the label descriptions to guide your decision, combined with the triage report's root cause and impact analysis. Render it in bold, with the `- ` prefix removed, like this: `**Priorty P2: Has Workaround.** Then, follow it with 1-2 sentences explaining _why_ you chose that priority. Answer: "who is likely to be affected and under what conditions?". If you are unsure, use your best judgment based on the label descriptions and the triage findings.
+Select exactly ONE priority label from the `priorityLabels` arg. Use the label descriptions to guide your decision, combined with the triage report's root cause and impact analysis. Render it in bold, with the `- ` prefix removed, like this: `**Priority P2: Has Workaround.**` Then, follow it with 1-2 sentences explaining _why_ you chose that priority. Answer: "who is likely to be affected and under what conditions?". If you are unsure, use your best judgment based on the label descriptions and the triage findings.
+
+**Priority calibration — err on the side of lower priority:**
+
+- **Experimental/unstable features** should almost never be higher than P3. Users of experimental features accept instability. (e.g. a broken option in `experimental.fonts`)
+- **Niche adapter/integration combos** (e.g. MDX + Svelte + Cloudflare) are typically P3 or lower unless they affect a core workflow.
+- **P4 vs P5** — the key question is breadth: how many typical Astro users would hit this in a standard workflow? (e.g. P4: wrong output for a common routing pattern; P5: `astro build` crashes for most projects)
+- **P2: Has Workaround vs P2: Nice to Have** — pick based on whether something behaves unexpectedly (but circumventable) vs. simply a convenience gap (e.g. Has Workaround: unexpected behavior with a way to restructure around it; Nice to Have: cosmetic issue in an error message). If there is no workaround at all, consider P3 or higher instead.
+- **When selecting between similar labels**, always refer to their descriptions in `priorityLabels` to make the final call.
+- **When in doubt, go lower.** A P3 that gets bumped up by a maintainer is much better than a P5 that causes false alarm.
### Template
+The comment must start with an at-a-glance summary, followed by short explanations, then the full report in a collapsible section. Keep the top section scannable, a maintainer should understand the status in under 5 seconds and be able to quickly jump into fixing the issue.
+
```markdown
-**[I was able to reproduce this issue. / I was unable to reproduce this issue.]** [2-3 sentences describing the root cause, result, and key observations.]
+- **Reproduced:** [Yes / No / Skipped — reason]
+- **Exploration:** [Yes / No / Partial / Already fixed on main] [If `branchName` is non-null: — [View branch](https://github.com/withastro/astro/compare/{branchName}?expand=1)]
+- **Priority:** [See "Priority" Instructions above. Keep to one line explaining why this priority was chosen, who is likely to be affected, and under what conditions (this section should answer the question: "how bad is it?")]
-**[See "Fix" Instructions above.]** [1-2 sentences describing the solution, where/when it was already fixed, or guidance on where a fix might be.] [If `branchName` is non-null: [View Suggested Fix](https://github.com/withastro/astro/compare/{branchName}?expand=1)]
+[2-3 sentences describing the root cause or key observations, or where/when it was already fixed. Be specific about what's happening and where in the codebase.]
-**[See "Priority" Instructions above.]** [1-2 sentences explaining why this priority was chosen, who is likely to be affected, and under what conditions (this section should answer the question: "how bad is it?")]
+**[See "Fix" Instructions above.]** [1-2 sentences describing the fix in more detail: what was changed, guidance on where a fix might be, or relevant code areas.]
Full Triage Report
@@ -61,7 +75,7 @@ Select exactly ONE priority label from the `priorityLabels` arg. Use the label d
-_This report was made by an LLM. Mistakes happen, check important info._
+_This report was made by an LLM. The analysis may be wrong, and the potential fix might not work, but is intended as a starting point for exploring the issue._
```
## Result
diff --git a/.agents/skills/triage/diagnose.md b/.agents/skills/triage/diagnose.md
index 8bd17de82eae..2a294786cd3d 100644
--- a/.agents/skills/triage/diagnose.md
+++ b/.agents/skills/triage/diagnose.md
@@ -61,6 +61,8 @@ Iterate until you understand:
- What data is being passed
- Where the logic diverges from expected behavior
+Once done, **revert all instrumentation** before moving on. Use `git checkout -- ` to remove your `console.log` additions from `packages/`. Debug logs must not leak into downstream steps.
+
## Step 4: Identify Root Cause
Once you understand the issue, document:
@@ -75,6 +77,9 @@ Consider:
- Is this a regression from a recent change?
- Does this affect other similar use cases?
- Are there edge cases to consider?
+- Never suggest removing a user's dependency (adapters, framework integrations, features like MDX or DB) as a fix, those are things the user needs. The fix must work within the user's existing stack and expected feature-set.
+
+**Tone calibration:** Describe the root cause factually, not dramatically. Avoid language that overstates impact ("critical flaw", "fundamentally broken", "severe vulnerability") unless the evidence genuinely supports it. A missing null check is a missing null check, not a "critical oversight in the rendering pipeline." The diagnosis should help a maintainer understand what's wrong, guiding them towards a fix, not alarm them.
## Step 5: Write Output
diff --git a/.agents/skills/triage/fix.md b/.agents/skills/triage/fix.md
index 17bb0805ef74..b6ac206090e6 100644
--- a/.agents/skills/triage/fix.md
+++ b/.agents/skills/triage/fix.md
@@ -35,7 +35,19 @@ Read `report.md` from the `triageDir` directory to understand:
- The suggested approach
- Any edge cases to consider
-**Skip if prerequisites unmet:** Check `report.md`: If bug not reproduced/skipped OR diagnosis confidence is `low`/`null` OR no root cause found → append "FIX SKIPPED: [reason]" to `report.md` and return `fixed: false`.
+**Skip if prerequisites unmet:** Check `report.md`: If bug was not reproduced or was skipped → append "FIX SKIPPED: Not reproduced" to `report.md` and return `fixed: false`. Do NOT attempt a fix based on guesswork when you cannot reproduce or diagnose the issue.
+
+**Low-confidence path:** If diagnosis confidence is `low` or `null`, or no clear root cause was found → do NOT attempt a code fix. Instead:
+
+1. Identify the most likely area(s) of the codebase related to the issue (files, functions, code paths).
+2. If possible, write a failing test that demonstrates the expected behavior described in the issue. Place it alongside existing tests for that area.
+3. If you identified specific code paths, add brief inline comments (prefixed `// TRIAGE:`) near the most relevant lines in `packages/` to help the implementor orient quickly. Keep to 2-3 comments max — these are signposts, not a diagnosis.
+4. Append to `report.md`: the areas you identified, why they seem relevant, and any failing test or comments you added.
+5. Return `fixed: false`.
+
+This "breadcrumb" approach is more useful to maintainers than a wrong fix.
+
+**High-confidence path:** If diagnosis confidence is `medium` or `high` and a clear root cause was identified → proceed with implementing a fix as described in the steps below.
**Note:** The repo may be messy from previous steps. Check `git status` and either work from the current state or `git reset --hard` to start clean.
@@ -55,6 +67,7 @@ Make changes in `packages/` source files. Follow these principles:
- Only change what's necessary to fix the bug
- Don't refactor unrelated code
- Don't add new features
+- **Never "fix" an issue by removing a user's dependency.** Removing an adapter (Cloudflare, Netlify, Vercel, etc.), framework integration (Svelte, React, Vue, etc.), or feature (MDX, DB, etc.) is not a fix, these are things the user needs. The fix must work within the user's existing stack or expected feature set.
**Consider edge cases:**
diff --git a/.changeset/two-eels-live.md b/.changeset/two-eels-live.md
new file mode 100644
index 000000000000..f1bd94acdf99
--- /dev/null
+++ b/.changeset/two-eels-live.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes an issue where i18n domains would return 404 when `trailingSlash` is set to `never`.
diff --git a/.flue/workflows/issue-triage/WORKFLOW.ts b/.flue/workflows/issue-triage/WORKFLOW.ts
index 8502c78626a5..8193ba26f224 100644
--- a/.flue/workflows/issue-triage/WORKFLOW.ts
+++ b/.flue/workflows/issue-triage/WORKFLOW.ts
@@ -241,19 +241,22 @@ export default async function triage(
const triageResult = await runTriagePipeline(flue, issueNumber, issueDetails);
let isPushed = false;
- // If a successful fix was created, push the fix up to a new branch on GitHub.
+ // Push the fix branch if there are meaningful changes (fix, failing test, etc.).
// The comment we post below will reference that branch, then a maintainer can choose to:
// - checkout that branch locally, using the fix as a starting point
// - create a PR from that branch entirely in the GH UI
// - ignore it completely
- if (triageResult.fixed) {
+ {
const diff = await flue.shell('git diff main --stat');
if (diff.stdout.trim()) {
const status = await flue.shell('git status --porcelain');
if (status.stdout.trim()) {
await flue.shell('git add -A');
+ const defaultMessage = triageResult.fixed
+ ? 'fix(auto-triage): automated fix'
+ : 'test(auto-triage): failing test and investigation notes';
await flue.shell(
- `git commit -m ${JSON.stringify(triageResult.commitMessage ?? 'fix(auto-triage): automated fix')}`,
+ `git commit -m ${JSON.stringify(triageResult.commitMessage ?? defaultMessage)}`,
);
}
const pushResult = await flue.shell(`git push -f origin ${branch}`);
@@ -274,7 +277,7 @@ export default async function triage(
result: v.pipe(
v.string(),
v.description(
- 'Return only the GitHub comment body generated from the template, following the included template directly. This returned comment must start with "**I was able to reproduce this issue.**" or "**I was unable to reproduce this issue.**"',
+ 'Return only the GitHub comment body generated from the template, following the included template directly. This returned comment must start with the bullet-point summary (- **Reproduced:** ...)',
),
),
});
diff --git a/.github/scripts/tsconfig.json b/.github/scripts/tsconfig.json
new file mode 100644
index 000000000000..2529f53ec0ef
--- /dev/null
+++ b/.github/scripts/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "composite": true,
+ "allowJs": true
+ }
+}
diff --git a/.github/workflows/check-merge.yml b/.github/workflows/check-merge.yml
index ebf67c0a2b6a..42db71d7f64d 100644
--- a/.github/workflows/check-merge.yml
+++ b/.github/workflows/check-merge.yml
@@ -39,10 +39,23 @@ jobs:
with:
files: |
.changeset/**/*.md
-
+ # Intentionally ran after the changed-files step so the github API is used to identify
+ # changed files rather than a local git diff, this is more reliable for pull requests
+ # originating from a forked repository.
+ - name: Checkout files
+ id: checkout
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ if: steps.changed-files.outputs.any_changed == 'true'
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ repository: ${{ github.event.pull_request.head.repo.full_name }}
+ fetch-depth: 1
+ persist-credentials: false
+ sparse-checkout: |
+ .changeset
- name: Check if any changesets contain minor or major changes
id: check
- if: steps.blocked.outputs.result != 'true'
+ if: steps.changed-files.outputs.any_changed == 'true'
env:
ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
run: |
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index 234dc416b8f1..3d1d59355f70 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -36,7 +36,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
- node-version: 24.14.0
+ node-version: 24.14.1
cache: "pnpm"
- name: Install dependencies
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 95038d18cf40..632c12e85ba8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -124,7 +124,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
- node-version: 24.14.0
+ node-version: 24.14.1
cache: "pnpm"
- name: Install dependencies
@@ -141,7 +141,7 @@ jobs:
run: pnpm run publint
- name: Type-check test files
- run: pnpm -C packages/astro run typecheck:tests
+ run: pnpm run typecheck:tests
test:
name: 'Test (${{ matrix.TEST_SUITE.name }}): ${{ matrix.os }} (node@${{ matrix.NODE_VERSION }})'
diff --git a/.github/workflows/congrats.yml b/.github/workflows/congrats.yml
index 4052bdac8776..6ad87c9ce9a1 100644
--- a/.github/workflows/congrats.yml
+++ b/.github/workflows/congrats.yml
@@ -9,7 +9,7 @@ jobs:
congrats:
name: congratsbot
if: ${{ github.repository_owner == 'withastro' && github.event.head_commit.message != '[ci] format' }}
- uses: withastro/automation/.github/workflows/congratsbot.yml@main
+ uses: withastro/automation/.github/workflows/congratsbot.yml@a5bd0c5748c4d56e687cdd558064f9ee8adfb1f2 # main
with:
EMOJIS: '🎉,🎊,🧑🚀,🥳,🙌,🚀,👏,<:houston_golden:1068575433647456447>,<:astrocoin:894990669515489301>,<:astro_pride:1130501345326157854>'
secrets:
diff --git a/.github/workflows/continuous_benchmark.yml b/.github/workflows/continuous_benchmark.yml
index 6eae291930a4..3e3f2e89a12a 100644
--- a/.github/workflows/continuous_benchmark.yml
+++ b/.github/workflows/continuous_benchmark.yml
@@ -41,7 +41,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
- node-version: 24.14.0
+ node-version: 24.14.1
cache: "pnpm"
- name: Install dependencies
@@ -56,7 +56,7 @@ jobs:
timeout-minutes: 15
- name: Run the benchmarks
- uses: CodSpeedHQ/action@1c8ae4843586d3ba879736b7f6b7b0c990757fab # v4.12.1
+ uses: CodSpeedHQ/action@db35df748deb45fdef0960669f57d627c1956c30 # v4.13.1
timeout-minutes: 30
with:
working-directory: ./benchmark
diff --git a/.github/workflows/diff-dependencies.yml b/.github/workflows/diff-dependencies.yml
index d48c503d2465..8809e4d3c55d 100644
--- a/.github/workflows/diff-dependencies.yml
+++ b/.github/workflows/diff-dependencies.yml
@@ -20,7 +20,7 @@ jobs:
fetch-depth: 0 # allows the diff action to access git history
- name: Create Diff
- uses: e18e/action-dependency-diff@d995338f3b229fe7b2cd82048df5da930f70c7c3 # v1.4.4
+ uses: e18e/action-dependency-diff@5d3c6ac2ad2de2eaca1dc120c5accfd9590764b6 # v1.5.1
with:
# We’re using this package primarily to track size changes, not as worried about duplicates
duplicate-threshold: 100
diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
index 9faaa1886608..656ac1b6dbe7 100644
--- a/.github/workflows/format.yml
+++ b/.github/workflows/format.yml
@@ -9,7 +9,7 @@ on:
jobs:
prettier:
if: github.repository_owner == 'withastro'
- uses: withastro/automation/.github/workflows/format.yml@main
+ uses: withastro/automation/.github/workflows/format.yml@a5bd0c5748c4d56e687cdd558064f9ee8adfb1f2 # main
with:
command: "format"
secrets: inherit
diff --git a/.github/workflows/issue-close-cleanup.yml b/.github/workflows/issue-close-cleanup.yml
new file mode 100644
index 000000000000..d623fea6f428
--- /dev/null
+++ b/.github/workflows/issue-close-cleanup.yml
@@ -0,0 +1,20 @@
+name: "Issue: Close Cleanup"
+
+on:
+ issues:
+ types: [closed]
+
+jobs:
+ cleanup-branch:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - name: Delete triage fix branch
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ BRANCH="flue/fix-${{ github.event.issue.number }}"
+ # Delete the branch if it exists; ignore errors if it doesn't
+ gh api "repos/${{ github.repository }}/git/refs/heads/${BRANCH}" \
+ -X DELETE 2>/dev/null && echo "Deleted branch ${BRANCH}" || echo "No branch ${BRANCH} to clean up"
diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml
index ca57bdde12da..096b5ceb9e94 100644
--- a/.github/workflows/issue-triage.yml
+++ b/.github/workflows/issue-triage.yml
@@ -72,7 +72,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
- node-version: 24.14.0
+ node-version: 24.14.1
cache: pnpm
- name: Clone Astro Compiler (for debugging)
diff --git a/.github/workflows/preview-release.yml b/.github/workflows/preview-release.yml
index 2506fafbde12..3992a0e7d598 100644
--- a/.github/workflows/preview-release.yml
+++ b/.github/workflows/preview-release.yml
@@ -48,7 +48,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
- node-version: 24.14.0
+ node-version: 24.14.1
cache: "pnpm"
- name: Install dependencies
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index af1d80d65d5e..dd35b0baa3d0 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -36,7 +36,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
- node-version: 24.14.0
+ node-version: 24.14.1
cache: "pnpm"
- name: Install dependencies
diff --git a/.github/workflows/scripts.yml b/.github/workflows/scripts.yml
index 01ad219dd8cb..68ed1dd565e6 100644
--- a/.github/workflows/scripts.yml
+++ b/.github/workflows/scripts.yml
@@ -38,7 +38,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
- node-version: 24.14.0
+ node-version: 24.14.1
cache: "pnpm"
- name: Install dependencies
diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml
new file mode 100644
index 000000000000..26794f96f54b
--- /dev/null
+++ b/.github/workflows/semgrep.yml
@@ -0,0 +1,14 @@
+name: Semgrep OSS scan
+on:
+ pull_request: {}
+ schedule:
+ - cron: '0 0 * * 6'
+jobs:
+ semgrep:
+ name: semgrep-oss
+ runs-on: ubuntu-latest
+ container:
+ image: semgrep/semgrep@sha256:500acf49f5e5785aa89af609b983f0427ac8cd08f7e34146277df6cffb002759 # v1.157.0
+ steps:
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
+ - run: semgrep scan --config=auto
diff --git a/biome.jsonc b/biome.jsonc
index c661d7f0e49e..63a65b9fdd6d 100644
--- a/biome.jsonc
+++ b/biome.jsonc
@@ -1,5 +1,5 @@
{
- "$schema": "https://biomejs.dev/schemas/2.4.2/schema.json",
+ "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
"files": {
"includes": [
"**",
@@ -46,7 +46,8 @@
// Enforce separate type imports for type-only imports to avoid bundling unneeded code
"useImportType": "error",
"useExportType": "error",
- "useNumberNamespace": "warn"
+ "useNumberNamespace": "warn",
+ "noInferrableTypes": "error"
},
"suspicious": {
// This one is specific to catch `console.log`. The rest of logs are permitted
@@ -187,6 +188,26 @@
}
}
}
+ },
+ {
+ "includes": ["**/astro/test/**"],
+ "linter": {
+ "rules": {
+ "style": {
+ "noRestrictedImports": {
+ "level": "error",
+ "options": {
+ "patterns": [
+ {
+ "group": ["**/src/**"],
+ "message": "The test should not import the source code. Import the code from the dist/ folder instead."
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
}
]
}
diff --git a/eslint.config.js b/eslint.config.js
index 55aa79531465..8686de7d256f 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -65,6 +65,7 @@ export default [
'@typescript-eslint/consistent-indexed-object-style': 'off',
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/dot-notation': 'off',
+ '@typescript-eslint/no-inferrable-types': 'off',
'@typescript-eslint/no-base-to-string': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-floating-promises': 'off',
diff --git a/examples/basics/package.json b/examples/basics/package.json
index 998a586ddf46..907071afa56f 100644
--- a/examples/basics/package.json
+++ b/examples/basics/package.json
@@ -13,6 +13,6 @@
"astro": "astro"
},
"dependencies": {
- "astro": "^6.1.2"
+ "astro": "^6.1.8"
}
}
diff --git a/examples/blog/README.md b/examples/blog/README.md
index 4307d60ba3c9..c5b756145819 100644
--- a/examples/blog/README.md
+++ b/examples/blog/README.md
@@ -36,6 +36,7 @@ Inside of your Astro project, you'll see the following folders and files:
```text
├── public/
├── src/
+│ ├── assets/
│ ├── components/
│ ├── content/
│ ├── layouts/
diff --git a/examples/blog/astro.config.mjs b/examples/blog/astro.config.mjs
index 0dbd924c3929..ea43603de26f 100644
--- a/examples/blog/astro.config.mjs
+++ b/examples/blog/astro.config.mjs
@@ -2,10 +2,34 @@
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
-import { defineConfig } from 'astro/config';
+import { defineConfig, fontProviders } from 'astro/config';
// https://astro.build/config
export default defineConfig({
site: 'https://example.com',
integrations: [mdx(), sitemap()],
+ fonts: [
+ {
+ provider: fontProviders.local(),
+ name: 'Atkinson',
+ cssVariable: '--font-atkinson',
+ fallbacks: ['sans-serif'],
+ options: {
+ variants: [
+ {
+ src: ['./src/assets/fonts/atkinson-regular.woff'],
+ weight: 400,
+ style: 'normal',
+ display: 'swap',
+ },
+ {
+ src: ['./src/assets/fonts/atkinson-bold.woff'],
+ weight: 700,
+ style: 'normal',
+ display: 'swap',
+ },
+ ],
+ },
+ },
+ ],
});
diff --git a/examples/blog/package.json b/examples/blog/package.json
index d777f6265bc6..a8fea9d8fb85 100644
--- a/examples/blog/package.json
+++ b/examples/blog/package.json
@@ -16,7 +16,7 @@
"@astrojs/mdx": "^5.0.3",
"@astrojs/rss": "^4.0.18",
"@astrojs/sitemap": "^3.7.2",
- "astro": "^6.1.2",
+ "astro": "^6.1.8",
"sharp": "^0.34.3"
}
}
diff --git a/examples/blog/public/fonts/atkinson-bold.woff b/examples/blog/src/assets/fonts/atkinson-bold.woff
similarity index 100%
rename from examples/blog/public/fonts/atkinson-bold.woff
rename to examples/blog/src/assets/fonts/atkinson-bold.woff
diff --git a/examples/blog/public/fonts/atkinson-regular.woff b/examples/blog/src/assets/fonts/atkinson-regular.woff
similarity index 100%
rename from examples/blog/public/fonts/atkinson-regular.woff
rename to examples/blog/src/assets/fonts/atkinson-regular.woff
diff --git a/examples/blog/src/components/BaseHead.astro b/examples/blog/src/components/BaseHead.astro
index 4a4384c4fd22..12fe4fa15712 100644
--- a/examples/blog/src/components/BaseHead.astro
+++ b/examples/blog/src/components/BaseHead.astro
@@ -5,6 +5,7 @@ import '../styles/global.css';
import type { ImageMetadata } from 'astro';
import FallbackImage from '../assets/blog-placeholder-1.jpg';
import { SITE_TITLE } from '../consts';
+import { Font } from 'astro:assets';
interface Props {
title: string;
@@ -31,9 +32,7 @@ const { title, description, image = FallbackImage } = Astro.props;
/>
-
-
-
+
diff --git a/examples/blog/src/styles/global.css b/examples/blog/src/styles/global.css
index bd6f8ced4fd9..8d0e05ff446e 100644
--- a/examples/blog/src/styles/global.css
+++ b/examples/blog/src/styles/global.css
@@ -16,22 +16,8 @@
0 2px 6px rgba(var(--gray), 25%), 0 8px 24px rgba(var(--gray), 33%),
0 16px 32px rgba(var(--gray), 33%);
}
-@font-face {
- font-family: "Atkinson";
- src: url("/fonts/atkinson-regular.woff") format("woff");
- font-weight: 400;
- font-style: normal;
- font-display: swap;
-}
-@font-face {
- font-family: "Atkinson";
- src: url("/fonts/atkinson-bold.woff") format("woff");
- font-weight: 700;
- font-style: normal;
- font-display: swap;
-}
body {
- font-family: "Atkinson", sans-serif;
+ font-family: var(--font-atkinson);
margin: 0;
padding: 0;
text-align: left;
diff --git a/examples/component/package.json b/examples/component/package.json
index ab320513f56d..e1c48e6524b9 100644
--- a/examples/component/package.json
+++ b/examples/component/package.json
@@ -18,7 +18,7 @@
],
"scripts": {},
"devDependencies": {
- "astro": "^6.1.2"
+ "astro": "^6.1.8"
},
"peerDependencies": {
"astro": "^5.0.0 || ^6.0.0"
diff --git a/examples/container-with-vitest/package.json b/examples/container-with-vitest/package.json
index 1b2688d88ffd..753d919f01f5 100644
--- a/examples/container-with-vitest/package.json
+++ b/examples/container-with-vitest/package.json
@@ -14,8 +14,8 @@
"test": "vitest run"
},
"dependencies": {
- "@astrojs/react": "^5.0.2",
- "astro": "^6.1.2",
+ "@astrojs/react": "^5.0.3",
+ "astro": "^6.1.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"vitest": "^4.1.0"
diff --git a/examples/framework-alpine/package.json b/examples/framework-alpine/package.json
index 930666f3c7e6..eb9e8ea3a349 100644
--- a/examples/framework-alpine/package.json
+++ b/examples/framework-alpine/package.json
@@ -16,6 +16,6 @@
"@astrojs/alpinejs": "^0.5.0",
"@types/alpinejs": "^3.13.11",
"alpinejs": "^3.15.8",
- "astro": "^6.1.2"
+ "astro": "^6.1.8"
}
}
diff --git a/examples/framework-multiple/package.json b/examples/framework-multiple/package.json
index cd06f32217ff..bb6603311063 100644
--- a/examples/framework-multiple/package.json
+++ b/examples/framework-multiple/package.json
@@ -13,14 +13,14 @@
"astro": "astro"
},
"dependencies": {
- "@astrojs/preact": "^5.1.0",
- "@astrojs/react": "^5.0.2",
+ "@astrojs/preact": "^5.1.1",
+ "@astrojs/react": "^5.0.3",
"@astrojs/solid-js": "^6.0.1",
- "@astrojs/svelte": "^8.0.4",
+ "@astrojs/svelte": "^8.0.5",
"@astrojs/vue": "^6.0.1",
"@types/react": "^18.3.28",
"@types/react-dom": "^18.3.7",
- "astro": "^6.1.2",
+ "astro": "^6.1.8",
"preact": "^10.28.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
diff --git a/examples/framework-preact/package.json b/examples/framework-preact/package.json
index ce4f08ecc17c..2a4301079abd 100644
--- a/examples/framework-preact/package.json
+++ b/examples/framework-preact/package.json
@@ -13,9 +13,9 @@
"astro": "astro"
},
"dependencies": {
- "@astrojs/preact": "^5.1.0",
+ "@astrojs/preact": "^5.1.1",
"@preact/signals": "^2.8.1",
- "astro": "^6.1.2",
+ "astro": "^6.1.8",
"preact": "^10.28.4"
}
}
diff --git a/examples/framework-react/package.json b/examples/framework-react/package.json
index ac51b152c884..6f5b9b86a35d 100644
--- a/examples/framework-react/package.json
+++ b/examples/framework-react/package.json
@@ -13,10 +13,10 @@
"astro": "astro"
},
"dependencies": {
- "@astrojs/react": "^5.0.2",
+ "@astrojs/react": "^5.0.3",
"@types/react": "^18.3.28",
"@types/react-dom": "^18.3.7",
- "astro": "^6.1.2",
+ "astro": "^6.1.8",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
diff --git a/examples/framework-solid/package.json b/examples/framework-solid/package.json
index 645463f9afa5..5a9546bc706c 100644
--- a/examples/framework-solid/package.json
+++ b/examples/framework-solid/package.json
@@ -14,7 +14,7 @@
},
"dependencies": {
"@astrojs/solid-js": "^6.0.1",
- "astro": "^6.1.2",
+ "astro": "^6.1.8",
"solid-js": "^1.9.11"
}
}
diff --git a/examples/framework-svelte/package.json b/examples/framework-svelte/package.json
index 33e4d0617014..c39df182b1f8 100644
--- a/examples/framework-svelte/package.json
+++ b/examples/framework-svelte/package.json
@@ -13,8 +13,8 @@
"astro": "astro"
},
"dependencies": {
- "@astrojs/svelte": "^8.0.4",
- "astro": "^6.1.2",
+ "@astrojs/svelte": "^8.0.5",
+ "astro": "^6.1.8",
"svelte": "^5.53.5"
}
}
diff --git a/examples/framework-vue/package.json b/examples/framework-vue/package.json
index 048e90cacb16..8f191e8e6bf2 100644
--- a/examples/framework-vue/package.json
+++ b/examples/framework-vue/package.json
@@ -14,7 +14,7 @@
},
"dependencies": {
"@astrojs/vue": "^6.0.1",
- "astro": "^6.1.2",
+ "astro": "^6.1.8",
"vue": "^3.5.29"
}
}
diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json
index 607ab44ef833..ff006a06999b 100644
--- a/examples/hackernews/package.json
+++ b/examples/hackernews/package.json
@@ -13,7 +13,7 @@
"astro": "astro"
},
"dependencies": {
- "@astrojs/node": "^10.0.4",
- "astro": "^6.1.2"
+ "@astrojs/node": "^10.0.5",
+ "astro": "^6.1.8"
}
}
diff --git a/examples/integration/package.json b/examples/integration/package.json
index cb25deee7185..4213dbd61fd7 100644
--- a/examples/integration/package.json
+++ b/examples/integration/package.json
@@ -18,7 +18,7 @@
],
"scripts": {},
"devDependencies": {
- "astro": "^6.1.2"
+ "astro": "^6.1.8"
},
"peerDependencies": {
"astro": "^4.0.0"
diff --git a/examples/minimal/package.json b/examples/minimal/package.json
index d0be1e2325f8..e2f04d513dac 100644
--- a/examples/minimal/package.json
+++ b/examples/minimal/package.json
@@ -13,6 +13,6 @@
"astro": "astro"
},
"dependencies": {
- "astro": "^6.1.2"
+ "astro": "^6.1.8"
}
}
diff --git a/examples/portfolio/package.json b/examples/portfolio/package.json
index 0c2e3e808b32..2c002652b07e 100644
--- a/examples/portfolio/package.json
+++ b/examples/portfolio/package.json
@@ -13,6 +13,6 @@
"astro": "astro"
},
"dependencies": {
- "astro": "^6.1.2"
+ "astro": "^6.1.8"
}
}
diff --git a/examples/ssr/package.json b/examples/ssr/package.json
index 2bc758bda901..64468234388e 100644
--- a/examples/ssr/package.json
+++ b/examples/ssr/package.json
@@ -14,9 +14,9 @@
"server": "node dist/server/entry.mjs"
},
"dependencies": {
- "@astrojs/node": "^10.0.4",
- "@astrojs/svelte": "^8.0.4",
- "astro": "^6.1.2",
+ "@astrojs/node": "^10.0.5",
+ "@astrojs/svelte": "^8.0.5",
+ "astro": "^6.1.8",
"svelte": "^5.53.5"
}
}
diff --git a/examples/starlog/package.json b/examples/starlog/package.json
index 2dc25c568649..0741f3fd322b 100644
--- a/examples/starlog/package.json
+++ b/examples/starlog/package.json
@@ -9,7 +9,7 @@
"astro": "astro"
},
"dependencies": {
- "astro": "^6.1.2",
+ "astro": "^6.1.8",
"sass": "^1.97.3",
"sharp": "^0.34.3"
},
diff --git a/examples/toolbar-app/package.json b/examples/toolbar-app/package.json
index e2198b271c7e..91e31d37d94d 100644
--- a/examples/toolbar-app/package.json
+++ b/examples/toolbar-app/package.json
@@ -15,8 +15,8 @@
"./app": "./dist/app.js"
},
"devDependencies": {
- "@types/node": "^18.17.8",
- "astro": "^6.1.2"
+ "@types/node": "^22.10.6",
+ "astro": "^6.1.8"
},
"engines": {
"node": ">=22.12.0"
diff --git a/examples/with-markdoc/package.json b/examples/with-markdoc/package.json
index de5c74aecbf2..21ab77221ffe 100644
--- a/examples/with-markdoc/package.json
+++ b/examples/with-markdoc/package.json
@@ -14,6 +14,6 @@
},
"dependencies": {
"@astrojs/markdoc": "^1.0.3",
- "astro": "^6.1.2"
+ "astro": "^6.1.8"
}
}
diff --git a/examples/with-mdx/package.json b/examples/with-mdx/package.json
index d8846698635c..55473c164b95 100644
--- a/examples/with-mdx/package.json
+++ b/examples/with-mdx/package.json
@@ -14,8 +14,8 @@
},
"dependencies": {
"@astrojs/mdx": "^5.0.3",
- "@astrojs/preact": "^5.1.0",
- "astro": "^6.1.2",
+ "@astrojs/preact": "^5.1.1",
+ "astro": "^6.1.8",
"preact": "^10.28.4"
}
}
diff --git a/examples/with-nanostores/package.json b/examples/with-nanostores/package.json
index 9e64c71a4e0f..51d4d470daef 100644
--- a/examples/with-nanostores/package.json
+++ b/examples/with-nanostores/package.json
@@ -13,9 +13,9 @@
"astro": "astro"
},
"dependencies": {
- "@astrojs/preact": "^5.1.0",
+ "@astrojs/preact": "^5.1.1",
"@nanostores/preact": "^1.0.0",
- "astro": "^6.1.2",
+ "astro": "^6.1.8",
"nanostores": "^1.1.1",
"preact": "^10.28.4"
}
diff --git a/examples/with-tailwindcss/package.json b/examples/with-tailwindcss/package.json
index aa5b0e9a6fe5..74c775b2f087 100644
--- a/examples/with-tailwindcss/package.json
+++ b/examples/with-tailwindcss/package.json
@@ -16,7 +16,7 @@
"@astrojs/mdx": "^5.0.3",
"@tailwindcss/vite": "^4.2.1",
"@types/canvas-confetti": "^1.9.0",
- "astro": "^6.1.2",
+ "astro": "^6.1.8",
"canvas-confetti": "^1.9.4",
"tailwindcss": "^4.2.1"
}
diff --git a/examples/with-vitest/package.json b/examples/with-vitest/package.json
index 2ac5b27629d7..e44ed3480a7c 100644
--- a/examples/with-vitest/package.json
+++ b/examples/with-vitest/package.json
@@ -14,7 +14,7 @@
"test": "vitest"
},
"dependencies": {
- "astro": "^6.1.2",
+ "astro": "^6.1.8",
"vitest": "^4.1.0"
}
}
diff --git a/knip.js b/knip.js
index 70702de01bc7..5e28a0378e0a 100644
--- a/knip.js
+++ b/knip.js
@@ -1,5 +1,5 @@
// @ts-check
-const testEntry = 'test/**/*.test.js';
+const testEntry = 'test/**/*.test.{js,ts}';
/** @type {import('knip').KnipConfig} */
export default {
@@ -33,7 +33,7 @@ export default {
testEntry,
'test/types/**/*',
'e2e/**/*.test.js',
- 'test/units/teardown.js',
+ 'test/units/teardown.ts',
// Can't detect this file when using inside a vite plugin
'src/vite-plugin-app/createAstroServerApp.ts',
],
diff --git a/package.json b/package.json
index 8943e7dee106..c2ae5c16c30b 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"test:e2e": "cd packages/astro && pnpm playwright install firefox && pnpm run test:e2e",
"test:e2e:match": "cd packages/astro && pnpm playwright install firefox && pnpm run test:e2e:match",
"test:e2e:hosts": "turbo run test:hosted",
+ "typecheck:tests": "pnpm -r typecheck:tests",
"benchmark": "astro-benchmark",
"lint": "biome lint && knip && eslint . --report-unused-disable-directives-severity=warn --concurrency=auto",
"lint:ci": "knip && pnpm run eslint:ci",
@@ -62,12 +63,12 @@
},
"devDependencies": {
"@astrojs/check": "^0.9.5",
- "@biomejs/biome": "2.4.2",
+ "@biomejs/biome": "2.4.10",
"@changesets/changelog-github": "^0.5.2",
"@changesets/cli": "^2.29.8",
"@flue/cli": "^0.0.47",
"@flue/client": "^0.0.29",
- "@types/node": "^18.19.115",
+ "@types/node": "^22.10.6",
"bgproc": "^0.2.0",
"esbuild": "0.25.5",
"eslint": "^9.39.3",
diff --git a/packages/astro-rss/package.json b/packages/astro-rss/package.json
index 715b8b4e8c67..ef76cd050187 100644
--- a/packages/astro-rss/package.json
+++ b/packages/astro-rss/package.json
@@ -24,7 +24,8 @@
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
"build:ci": "astro-scripts build \"src/**/*.ts\"",
"dev": "astro-scripts dev \"src/**/*.ts\"",
- "test": "astro-scripts test \"test/**/*.test.js\""
+ "test": "astro-scripts test \"test/**/*.test.ts\"",
+ "typecheck:tests": "tsc --build tsconfig.test.json"
},
"devDependencies": {
"@types/xml2js": "^0.4.14",
diff --git a/packages/astro-rss/test/pagesGlobToRssItems.test.js b/packages/astro-rss/test/pagesGlobToRssItems.test.js
deleted file mode 100644
index 36613c96c89e..000000000000
--- a/packages/astro-rss/test/pagesGlobToRssItems.test.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import assert from 'node:assert/strict';
-import { describe, it } from 'node:test';
-
-import { pagesGlobToRssItems } from '../dist/index.js';
-import { phpFeedItem, web1FeedItem } from './test-utils.js';
-
-describe('pagesGlobToRssItems', () => {
- it('should generate on valid result', async () => {
- const globResult = {
- './posts/php.md': () =>
- new Promise((resolve) =>
- resolve({
- url: phpFeedItem.link,
- frontmatter: {
- title: phpFeedItem.title,
- pubDate: phpFeedItem.pubDate,
- description: phpFeedItem.description,
- },
- }),
- ),
- './posts/nested/web1.md': () =>
- new Promise((resolve) =>
- resolve({
- url: web1FeedItem.link,
- frontmatter: {
- title: web1FeedItem.title,
- pubDate: web1FeedItem.pubDate,
- description: web1FeedItem.description,
- },
- }),
- ),
- };
-
- const items = await pagesGlobToRssItems(globResult);
- const expected = [
- {
- title: phpFeedItem.title,
- link: phpFeedItem.link,
- pubDate: new Date(phpFeedItem.pubDate),
- description: phpFeedItem.description,
- },
- {
- title: web1FeedItem.title,
- link: web1FeedItem.link,
- pubDate: new Date(web1FeedItem.pubDate),
- description: web1FeedItem.description,
- },
- ];
-
- assert.deepEqual(
- items.sort((a, b) => a.pubDate - b.pubDate),
- expected,
- );
- });
-
- it('should fail on missing "url"', () => {
- const globResult = {
- './posts/php.md': () =>
- new Promise((resolve) =>
- resolve({
- url: undefined,
- frontmatter: {
- pubDate: phpFeedItem.pubDate,
- description: phpFeedItem.description,
- },
- }),
- ),
- };
- return assert.rejects(pagesGlobToRssItems(globResult));
- });
-
- it('should fail on missing "title" key and "description"', () => {
- const globResult = {
- './posts/php.md': () =>
- new Promise((resolve) =>
- resolve({
- url: phpFeedItem.link,
- frontmatter: {
- title: undefined,
- pubDate: phpFeedItem.pubDate,
- description: undefined,
- },
- }),
- ),
- };
- return assert.rejects(pagesGlobToRssItems(globResult));
- });
-
- it('should not fail on missing "title" key if "description" is present', () => {
- const globResult = {
- './posts/php.md': () =>
- new Promise((resolve) =>
- resolve({
- url: phpFeedItem.link,
- frontmatter: {
- title: undefined,
- pubDate: phpFeedItem.pubDate,
- description: phpFeedItem.description,
- },
- }),
- ),
- };
- return assert.doesNotReject(pagesGlobToRssItems(globResult));
- });
-
- it('should not fail on missing "description" key if "title" is present', () => {
- const globResult = {
- './posts/php.md': () =>
- new Promise((resolve) =>
- resolve({
- url: phpFeedItem.link,
- frontmatter: {
- title: phpFeedItem.title,
- pubDate: phpFeedItem.pubDate,
- description: undefined,
- },
- }),
- ),
- };
- return assert.doesNotReject(pagesGlobToRssItems(globResult));
- });
-});
diff --git a/packages/astro-rss/test/pagesGlobToRssItems.test.ts b/packages/astro-rss/test/pagesGlobToRssItems.test.ts
new file mode 100644
index 000000000000..19e897e8648b
--- /dev/null
+++ b/packages/astro-rss/test/pagesGlobToRssItems.test.ts
@@ -0,0 +1,122 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+
+import { pagesGlobToRssItems } from '../dist/index.js';
+import { phpFeedItem, web1FeedItem } from './test-utils.ts';
+
+describe('pagesGlobToRssItems', () => {
+ it('should generate on valid result', async () => {
+ const globResult = {
+ './posts/php.md': () =>
+ new Promise((resolve) =>
+ resolve({
+ url: phpFeedItem.link,
+ frontmatter: {
+ title: phpFeedItem.title,
+ pubDate: phpFeedItem.pubDate,
+ description: phpFeedItem.description,
+ },
+ }),
+ ),
+ './posts/nested/web1.md': () =>
+ new Promise((resolve) =>
+ resolve({
+ url: web1FeedItem.link,
+ frontmatter: {
+ title: web1FeedItem.title,
+ pubDate: web1FeedItem.pubDate,
+ description: web1FeedItem.description,
+ },
+ }),
+ ),
+ };
+
+ const items = await pagesGlobToRssItems(globResult);
+ const expected = [
+ {
+ title: phpFeedItem.title,
+ link: phpFeedItem.link,
+ pubDate: new Date(phpFeedItem.pubDate),
+ description: phpFeedItem.description,
+ },
+ {
+ title: web1FeedItem.title,
+ link: web1FeedItem.link,
+ pubDate: new Date(web1FeedItem.pubDate),
+ description: web1FeedItem.description,
+ },
+ ];
+
+ assert.deepEqual(
+ items.sort((a, b) => a.pubDate!.getTime() - b.pubDate!.getTime()),
+ expected,
+ );
+ });
+
+ it('should fail on missing "url"', () => {
+ const globResult = {
+ './posts/php.md': () =>
+ new Promise((resolve) =>
+ resolve({
+ url: undefined,
+ frontmatter: {
+ pubDate: phpFeedItem.pubDate,
+ description: phpFeedItem.description,
+ },
+ }),
+ ),
+ };
+ return assert.rejects(pagesGlobToRssItems(globResult));
+ });
+
+ it('should fail on missing "title" key and "description"', () => {
+ const globResult = {
+ './posts/php.md': () =>
+ new Promise((resolve) =>
+ resolve({
+ url: phpFeedItem.link,
+ frontmatter: {
+ title: undefined,
+ pubDate: phpFeedItem.pubDate,
+ description: undefined,
+ },
+ }),
+ ),
+ };
+ return assert.rejects(pagesGlobToRssItems(globResult));
+ });
+
+ it('should not fail on missing "title" key if "description" is present', () => {
+ const globResult = {
+ './posts/php.md': () =>
+ new Promise((resolve) =>
+ resolve({
+ url: phpFeedItem.link,
+ frontmatter: {
+ title: undefined,
+ pubDate: phpFeedItem.pubDate,
+ description: phpFeedItem.description,
+ },
+ }),
+ ),
+ };
+ return assert.doesNotReject(pagesGlobToRssItems(globResult));
+ });
+
+ it('should not fail on missing "description" key if "title" is present', () => {
+ const globResult = {
+ './posts/php.md': () =>
+ new Promise((resolve) =>
+ resolve({
+ url: phpFeedItem.link,
+ frontmatter: {
+ title: phpFeedItem.title,
+ pubDate: phpFeedItem.pubDate,
+ description: undefined,
+ },
+ }),
+ ),
+ };
+ return assert.doesNotReject(pagesGlobToRssItems(globResult));
+ });
+});
diff --git a/packages/astro-rss/test/rss.test.js b/packages/astro-rss/test/rss.test.js
deleted file mode 100644
index a52caf45c361..000000000000
--- a/packages/astro-rss/test/rss.test.js
+++ /dev/null
@@ -1,301 +0,0 @@
-import assert from 'node:assert/strict';
-import { describe, it } from 'node:test';
-import * as z from 'zod/v4';
-import rss, { getRssString } from '../dist/index.js';
-import { rssSchema } from '../dist/schema.js';
-import {
- description,
- parseXmlString,
- phpFeedItem,
- phpFeedItemWithContent,
- phpFeedItemWithCustomData,
- phpFeedItemWithoutDate,
- site,
- title,
- web1FeedItem,
- web1FeedItemWithAllData,
- web1FeedItemWithContent,
-} from './test-utils.js';
-
-// note: I spent 30 minutes looking for a nice node-based snapshot tool
-// ...and I gave up. Enjoy big strings!
-
-// biome-ignore format: keep in one line
-const validXmlResult = `${site}/${site}${phpFeedItem.link}/${site}${phpFeedItem.link}/${new Date(phpFeedItem.pubDate).toUTCString()}${site}${web1FeedItem.link}/${site}${web1FeedItem.link}/${new Date(web1FeedItem.pubDate).toUTCString()}`;
-// biome-ignore format: keep in one line
-const validXmlWithContentResult = `${site}/${site}${phpFeedItemWithContent.link}/${site}${phpFeedItemWithContent.link}/${new Date(phpFeedItemWithContent.pubDate).toUTCString()}${site}${web1FeedItemWithContent.link}/${site}${web1FeedItemWithContent.link}/${new Date(web1FeedItemWithContent.pubDate).toUTCString()}`;
-// biome-ignore format: keep in one line
-const validXmlResultWithMissingDate = `${site}/${site}${phpFeedItemWithoutDate.link}/${site}${phpFeedItemWithoutDate.link}/${site}${phpFeedItem.link}/${site}${phpFeedItem.link}/${new Date(phpFeedItem.pubDate).toUTCString()}`;
-// biome-ignore format: keep in one line
-const validXmlResultWithAllData = `${site}/${site}${phpFeedItem.link}/${site}${phpFeedItem.link}/${new Date(phpFeedItem.pubDate).toUTCString()}${site}${web1FeedItemWithAllData.link}/${site}${web1FeedItemWithAllData.link}/${new Date(web1FeedItemWithAllData.pubDate).toUTCString()}${web1FeedItemWithAllData.categories[0]}${web1FeedItemWithAllData.categories[1]}${web1FeedItemWithAllData.author}${web1FeedItemWithAllData.commentsUrl}${web1FeedItemWithAllData.source.title}`;
-// biome-ignore format: keep in one line
-const validXmlWithCustomDataResult = `${site}/${site}${phpFeedItemWithCustomData.link}/${site}${phpFeedItemWithCustomData.link}/${new Date(phpFeedItemWithCustomData.pubDate).toUTCString()}${phpFeedItemWithCustomData.customData}${site}${web1FeedItemWithContent.link}/${site}${web1FeedItemWithContent.link}/${new Date(web1FeedItemWithContent.pubDate).toUTCString()}`;
-// biome-ignore format: keep in one line
-const validXmlWithStylesheet = `${site}/`;
-// biome-ignore format: keep in one line
-const validXmlWithXSLStylesheet = `${site}/`;
-// biome-ignore format: keep in one line
-const validXmlWithXSLTStylesheet = `${site}/`;
-
-function assertXmlDeepEqual(a, b) {
- const parsedA = parseXmlString(a);
- const parsedB = parseXmlString(b);
-
- assert.equal(parsedA.err, null);
- assert.equal(parsedB.err, null);
- assert.deepEqual(parsedA.result, parsedB.result);
-}
-
-describe('rss', () => {
- it('should return a response', async () => {
- const response = await rss({
- title,
- description,
- items: [phpFeedItem, web1FeedItem],
- site,
- });
-
- const str = await response.text();
-
- // NOTE: Chai used the below parser to perform the tests, but I have omitted it for now.
- // parser = new xml2js.Parser({ trim: flag(this, 'deep') });
-
- assertXmlDeepEqual(str, validXmlResult);
-
- const contentType = response.headers.get('Content-Type');
- assert.equal(contentType, 'application/xml');
- });
-
- it('should be the same string as getRssString', async () => {
- const options = {
- title,
- description,
- items: [phpFeedItem, web1FeedItem],
- site,
- };
-
- const response = await rss(options);
- const str1 = await response.text();
- const str2 = await getRssString(options);
-
- assert.equal(str1, str2);
- });
-});
-
-describe('getRssString', () => {
- it('should generate on valid RSSFeedItem array', async () => {
- const str = await getRssString({
- title,
- description,
- items: [phpFeedItem, web1FeedItem],
- site,
- });
-
- assertXmlDeepEqual(str, validXmlResult);
- });
-
- it('should generate on valid RSSFeedItem array with HTML content included', async () => {
- const str = await getRssString({
- title,
- description,
- items: [phpFeedItemWithContent, web1FeedItemWithContent],
- site,
- });
-
- assertXmlDeepEqual(str, validXmlWithContentResult);
- });
-
- it('should generate on valid RSSFeedItem array that is missing date', async () => {
- const str = await getRssString({
- title,
- description,
- items: [phpFeedItemWithoutDate, phpFeedItem],
- site,
- });
-
- assertXmlDeepEqual(str, validXmlResultWithMissingDate);
- });
-
- it('should generate on valid RSSFeedItem array with all RSS content included', async () => {
- const str = await getRssString({
- title,
- description,
- items: [phpFeedItem, web1FeedItemWithAllData],
- site,
- });
-
- assertXmlDeepEqual(str, validXmlResultWithAllData);
- });
-
- it('should generate on valid RSSFeedItem array with custom data included', async () => {
- const str = await getRssString({
- xmlns: {
- dc: 'http://purl.org/dc/elements/1.1/',
- },
- title,
- description,
- items: [phpFeedItemWithCustomData, web1FeedItemWithContent],
- site,
- });
-
- assertXmlDeepEqual(str, validXmlWithCustomDataResult);
- });
-
- it('should include xml-stylesheet instruction when stylesheet is defined', async () => {
- const str = await getRssString({
- title,
- description,
- items: [],
- site,
- stylesheet: '/feedstylesheet.css',
- });
-
- assertXmlDeepEqual(str, validXmlWithStylesheet);
- });
-
- it('should include xml-stylesheet instruction with xsl type when stylesheet is set to xsl file', async () => {
- const str = await getRssString({
- title,
- description,
- items: [],
- site,
- stylesheet: '/feedstylesheet.xsl',
- });
-
- // xml2js doesn't parse processing instructions. Assert the type is present.
- assert.equal(str.includes('type="text/xsl"'), true);
- assertXmlDeepEqual(str, validXmlWithXSLStylesheet);
- });
-
- it('should include xml-stylesheet instruction with xslt type when stylesheet is set to xslt file', async () => {
- const str = await getRssString({
- title,
- description,
- items: [],
- site,
- stylesheet: '/feedstylesheet.xslt',
- });
-
- // xml2js doesn't parse processing instructions. Assert the type is present.
- assert.equal(str.includes('type="text/xsl"'), true);
- assertXmlDeepEqual(str, validXmlWithXSLTStylesheet);
- });
-
- it('should preserve self-closing tags on `customData`', async () => {
- const customData =
- '';
- const str = await getRssString({
- title,
- description,
- items: [],
- site,
- xmlns: {
- atom: 'http://www.w3.org/2005/Atom',
- },
- customData,
- });
-
- assert.ok(str.includes(customData));
- });
-
- it('should not append trailing slash to URLs with the given option', async () => {
- const str = await getRssString({
- title,
- description,
- items: [phpFeedItem],
- site,
- trailingSlash: false,
- });
-
- assert.ok(str.includes('https://example.com<'));
- assert.ok(str.includes('https://example.com/php<'));
- });
-
- it('Deprecated import.meta.glob mapping still works', async () => {
- const globResult = {
- './posts/php.md': () =>
- new Promise((resolve) =>
- resolve({
- url: phpFeedItem.link,
- frontmatter: {
- title: phpFeedItem.title,
- pubDate: phpFeedItem.pubDate,
- description: phpFeedItem.description,
- },
- }),
- ),
- './posts/nested/web1.md': () =>
- new Promise((resolve) =>
- resolve({
- url: web1FeedItem.link,
- frontmatter: {
- title: web1FeedItem.title,
- pubDate: web1FeedItem.pubDate,
- description: web1FeedItem.description,
- },
- }),
- ),
- };
-
- const str = await getRssString({
- title,
- description,
- items: globResult,
- site,
- });
-
- assertXmlDeepEqual(str, validXmlResult);
- });
-
- it('should fail when an invalid date string is provided', async () => {
- const res = rssSchema.safeParse({
- title: phpFeedItem.title,
- pubDate: 'invalid date',
- description: phpFeedItem.description,
- link: phpFeedItem.link,
- });
-
- assert.equal(res.success, false);
- assert.equal(res.error.issues[0].path[0], 'pubDate');
- });
-
- it('should be extendable', () => {
- let error = null;
- try {
- rssSchema.extend({
- category: z.string().optional(),
- });
- } catch (e) {
- error = e.message;
- }
- assert.equal(error, null);
- });
-
- it('should not fail when an enclosure has a length of 0', async () => {
- let error = null;
- try {
- await getRssString({
- title,
- description,
- items: [
- {
- title: 'Title',
- pubDate: new Date().toISOString(),
- description: 'Description',
- link: '/link',
- enclosure: {
- url: '/enclosure',
- length: 0,
- type: 'audio/mpeg',
- },
- },
- ],
- site,
- });
- } catch (e) {
- error = e.message;
- }
-
- assert.equal(error, null);
- });
-});
diff --git a/packages/astro-rss/test/rss.test.ts b/packages/astro-rss/test/rss.test.ts
new file mode 100644
index 000000000000..402548b78eec
--- /dev/null
+++ b/packages/astro-rss/test/rss.test.ts
@@ -0,0 +1,301 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import * as z from 'zod/v4';
+import rss, { getRssString } from '../dist/index.js';
+import { rssSchema } from '../dist/schema.js';
+import {
+ description,
+ parseXmlString,
+ phpFeedItem,
+ phpFeedItemWithContent,
+ phpFeedItemWithCustomData,
+ phpFeedItemWithoutDate,
+ site,
+ title,
+ web1FeedItem,
+ web1FeedItemWithAllData,
+ web1FeedItemWithContent,
+} from './test-utils.ts';
+
+// note: I spent 30 minutes looking for a nice node-based snapshot tool
+// ...and I gave up. Enjoy big strings!
+
+// biome-ignore format: keep in one line
+const validXmlResult = `${site}/${site}${phpFeedItem.link}/${site}${phpFeedItem.link}/${new Date(phpFeedItem.pubDate).toUTCString()}${site}${web1FeedItem.link}/${site}${web1FeedItem.link}/${new Date(web1FeedItem.pubDate).toUTCString()}`;
+// biome-ignore format: keep in one line
+const validXmlWithContentResult = `${site}/${site}${phpFeedItemWithContent.link}/${site}${phpFeedItemWithContent.link}/${new Date(phpFeedItemWithContent.pubDate).toUTCString()}${site}${web1FeedItemWithContent.link}/${site}${web1FeedItemWithContent.link}/${new Date(web1FeedItemWithContent.pubDate).toUTCString()}`;
+// biome-ignore format: keep in one line
+const validXmlResultWithMissingDate = `${site}/${site}${phpFeedItemWithoutDate.link}/${site}${phpFeedItemWithoutDate.link}/${site}${phpFeedItem.link}/${site}${phpFeedItem.link}/${new Date(phpFeedItem.pubDate).toUTCString()}`;
+// biome-ignore format: keep in one line
+const validXmlResultWithAllData = `${site}/${site}${phpFeedItem.link}/${site}${phpFeedItem.link}/${new Date(phpFeedItem.pubDate).toUTCString()}${site}${web1FeedItemWithAllData.link}/${site}${web1FeedItemWithAllData.link}/${new Date(web1FeedItemWithAllData.pubDate).toUTCString()}${web1FeedItemWithAllData.categories[0]}${web1FeedItemWithAllData.categories[1]}${web1FeedItemWithAllData.author}${web1FeedItemWithAllData.commentsUrl}${web1FeedItemWithAllData.source.title}`;
+// biome-ignore format: keep in one line
+const validXmlWithCustomDataResult = `${site}/${site}${phpFeedItemWithCustomData.link}/${site}${phpFeedItemWithCustomData.link}/${new Date(phpFeedItemWithCustomData.pubDate).toUTCString()}${phpFeedItemWithCustomData.customData}${site}${web1FeedItemWithContent.link}/${site}${web1FeedItemWithContent.link}/${new Date(web1FeedItemWithContent.pubDate).toUTCString()}`;
+// biome-ignore format: keep in one line
+const validXmlWithStylesheet = `${site}/`;
+// biome-ignore format: keep in one line
+const validXmlWithXSLStylesheet = `${site}/`;
+// biome-ignore format: keep in one line
+const validXmlWithXSLTStylesheet = `${site}/`;
+
+function assertXmlDeepEqual(a: string, b: string) {
+ const parsedA = parseXmlString(a);
+ const parsedB = parseXmlString(b);
+
+ assert.equal(parsedA.err, null);
+ assert.equal(parsedB.err, null);
+ assert.deepEqual(parsedA.result, parsedB.result);
+}
+
+describe('rss', () => {
+ it('should return a response', async () => {
+ const response = await rss({
+ title,
+ description,
+ items: [phpFeedItem, web1FeedItem],
+ site,
+ });
+
+ const str = await response.text();
+
+ // NOTE: Chai used the below parser to perform the tests, but I have omitted it for now.
+ // parser = new xml2js.Parser({ trim: flag(this, 'deep') });
+
+ assertXmlDeepEqual(str, validXmlResult);
+
+ const contentType = response.headers.get('Content-Type');
+ assert.equal(contentType, 'application/xml');
+ });
+
+ it('should be the same string as getRssString', async () => {
+ const options = {
+ title,
+ description,
+ items: [phpFeedItem, web1FeedItem],
+ site,
+ };
+
+ const response = await rss(options);
+ const str1 = await response.text();
+ const str2 = await getRssString(options);
+
+ assert.equal(str1, str2);
+ });
+});
+
+describe('getRssString', () => {
+ it('should generate on valid RSSFeedItem array', async () => {
+ const str = await getRssString({
+ title,
+ description,
+ items: [phpFeedItem, web1FeedItem],
+ site,
+ });
+
+ assertXmlDeepEqual(str, validXmlResult);
+ });
+
+ it('should generate on valid RSSFeedItem array with HTML content included', async () => {
+ const str = await getRssString({
+ title,
+ description,
+ items: [phpFeedItemWithContent, web1FeedItemWithContent],
+ site,
+ });
+
+ assertXmlDeepEqual(str, validXmlWithContentResult);
+ });
+
+ it('should generate on valid RSSFeedItem array that is missing date', async () => {
+ const str = await getRssString({
+ title,
+ description,
+ items: [phpFeedItemWithoutDate, phpFeedItem],
+ site,
+ });
+
+ assertXmlDeepEqual(str, validXmlResultWithMissingDate);
+ });
+
+ it('should generate on valid RSSFeedItem array with all RSS content included', async () => {
+ const str = await getRssString({
+ title,
+ description,
+ items: [phpFeedItem, web1FeedItemWithAllData],
+ site,
+ });
+
+ assertXmlDeepEqual(str, validXmlResultWithAllData);
+ });
+
+ it('should generate on valid RSSFeedItem array with custom data included', async () => {
+ const str = await getRssString({
+ xmlns: {
+ dc: 'http://purl.org/dc/elements/1.1/',
+ },
+ title,
+ description,
+ items: [phpFeedItemWithCustomData, web1FeedItemWithContent],
+ site,
+ });
+
+ assertXmlDeepEqual(str, validXmlWithCustomDataResult);
+ });
+
+ it('should include xml-stylesheet instruction when stylesheet is defined', async () => {
+ const str = await getRssString({
+ title,
+ description,
+ items: [],
+ site,
+ stylesheet: '/feedstylesheet.css',
+ });
+
+ assertXmlDeepEqual(str, validXmlWithStylesheet);
+ });
+
+ it('should include xml-stylesheet instruction with xsl type when stylesheet is set to xsl file', async () => {
+ const str = await getRssString({
+ title,
+ description,
+ items: [],
+ site,
+ stylesheet: '/feedstylesheet.xsl',
+ });
+
+ // xml2js doesn't parse processing instructions. Assert the type is present.
+ assert.equal(str.includes('type="text/xsl"'), true);
+ assertXmlDeepEqual(str, validXmlWithXSLStylesheet);
+ });
+
+ it('should include xml-stylesheet instruction with xslt type when stylesheet is set to xslt file', async () => {
+ const str = await getRssString({
+ title,
+ description,
+ items: [],
+ site,
+ stylesheet: '/feedstylesheet.xslt',
+ });
+
+ // xml2js doesn't parse processing instructions. Assert the type is present.
+ assert.equal(str.includes('type="text/xsl"'), true);
+ assertXmlDeepEqual(str, validXmlWithXSLTStylesheet);
+ });
+
+ it('should preserve self-closing tags on `customData`', async () => {
+ const customData =
+ '';
+ const str = await getRssString({
+ title,
+ description,
+ items: [],
+ site,
+ xmlns: {
+ atom: 'http://www.w3.org/2005/Atom',
+ },
+ customData,
+ });
+
+ assert.ok(str.includes(customData));
+ });
+
+ it('should not append trailing slash to URLs with the given option', async () => {
+ const str = await getRssString({
+ title,
+ description,
+ items: [phpFeedItem],
+ site,
+ trailingSlash: false,
+ });
+
+ assert.ok(str.includes('https://example.com<'));
+ assert.ok(str.includes('https://example.com/php<'));
+ });
+
+ it('Deprecated import.meta.glob mapping still works', async () => {
+ const globResult = {
+ './posts/php.md': () =>
+ new Promise((resolve) =>
+ resolve({
+ url: phpFeedItem.link,
+ frontmatter: {
+ title: phpFeedItem.title,
+ pubDate: phpFeedItem.pubDate,
+ description: phpFeedItem.description,
+ },
+ }),
+ ),
+ './posts/nested/web1.md': () =>
+ new Promise((resolve) =>
+ resolve({
+ url: web1FeedItem.link,
+ frontmatter: {
+ title: web1FeedItem.title,
+ pubDate: web1FeedItem.pubDate,
+ description: web1FeedItem.description,
+ },
+ }),
+ ),
+ };
+
+ const str = await getRssString({
+ title,
+ description,
+ items: globResult,
+ site,
+ });
+
+ assertXmlDeepEqual(str, validXmlResult);
+ });
+
+ it('should fail when an invalid date string is provided', async () => {
+ const res = rssSchema.safeParse({
+ title: phpFeedItem.title,
+ pubDate: 'invalid date',
+ description: phpFeedItem.description,
+ link: phpFeedItem.link,
+ });
+
+ assert.equal(res.success, false);
+ assert.equal(res.error.issues[0].path[0], 'pubDate');
+ });
+
+ it('should be extendable', () => {
+ let error = null;
+ try {
+ rssSchema.extend({
+ category: z.string().optional(),
+ });
+ } catch (e) {
+ error = (e as Error).message;
+ }
+ assert.equal(error, null);
+ });
+
+ it('should not fail when an enclosure has a length of 0', async () => {
+ let error = null;
+ try {
+ await getRssString({
+ title,
+ description,
+ items: [
+ {
+ title: 'Title',
+ pubDate: new Date(),
+ description: 'Description',
+ link: '/link',
+ enclosure: {
+ url: '/enclosure',
+ length: 0,
+ type: 'audio/mpeg',
+ },
+ },
+ ],
+ site,
+ });
+ } catch (e) {
+ error = (e as Error).message;
+ }
+
+ assert.equal(error, null);
+ });
+});
diff --git a/packages/astro-rss/test/test-utils.js b/packages/astro-rss/test/test-utils.js
deleted file mode 100644
index d3ee8ca336c7..000000000000
--- a/packages/astro-rss/test/test-utils.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import xml2js from 'xml2js';
-
-export const title = 'My RSS feed';
-export const description = 'This sure is a nice RSS feed';
-export const site = 'https://example.com';
-
-export const phpFeedItemWithoutDate = {
- link: '/php',
- title: 'Remember PHP?',
- description:
- 'PHP is a general-purpose scripting language geared toward web development. It was originally created by Danish-Canadian programmer Rasmus Lerdorf in 1994.',
-};
-export const phpFeedItem = {
- ...phpFeedItemWithoutDate,
- pubDate: '1994-05-03',
-};
-export const phpFeedItemWithContent = {
- ...phpFeedItem,
- content: `
${phpFeedItem.title}
${phpFeedItem.description}
`,
-};
-export const phpFeedItemWithCustomData = {
- ...phpFeedItem,
- customData: '',
-};
-
-export const web1FeedItem = {
- // Should support empty string as a URL (possible for homepage route)
- link: '',
- title: 'Web 1.0',
- pubDate: '1997-05-03',
- description:
- 'Web 1.0 is the term used for the earliest version of the Internet as it emerged from its origins with Defense Advanced Research Projects Agency (DARPA) and became, for the first time, a global network representing the future of digital communications.',
-};
-export const web1FeedItemWithContent = {
- ...web1FeedItem,
- content: `
${web1FeedItem.title}
${web1FeedItem.description}
`,
-};
-export const web1FeedItemWithAllData = {
- ...web1FeedItem,
- categories: ['web1', 'history'],
- author: 'test@example.com',
- commentsUrl: 'http://example.com/comments',
- source: {
- url: 'http://example.com/source',
- title: 'The Web 1.0 blog',
- },
- enclosure: {
- url: '/podcast.mp3',
- length: 256,
- type: 'audio/mpeg',
- },
-};
-
-const parser = new xml2js.Parser({ trim: true });
-
-/**
- *
- * Utility function to parse an XML string into an object using `xml2js`.
- *
- * @param {string} xmlString - Stringified XML to parse.
- * @return {{ err: Error, result: any }} Represents an option containing the parsed XML string or an Error.
- */
-export function parseXmlString(xmlString) {
- let res;
- parser.parseString(xmlString, (err, result) => {
- res = { err, result };
- });
- return res;
-}
diff --git a/packages/astro-rss/test/test-utils.ts b/packages/astro-rss/test/test-utils.ts
new file mode 100644
index 000000000000..4cb276ed1bdf
--- /dev/null
+++ b/packages/astro-rss/test/test-utils.ts
@@ -0,0 +1,69 @@
+import xml2js from 'xml2js';
+
+export const title = 'My RSS feed';
+export const description = 'This sure is a nice RSS feed';
+export const site = 'https://example.com';
+
+export const phpFeedItemWithoutDate = {
+ link: '/php',
+ title: 'Remember PHP?',
+ description:
+ 'PHP is a general-purpose scripting language geared toward web development. It was originally created by Danish-Canadian programmer Rasmus Lerdorf in 1994.',
+};
+export const phpFeedItem = {
+ ...phpFeedItemWithoutDate,
+ pubDate: new Date('1994-05-03'),
+};
+export const phpFeedItemWithContent = {
+ ...phpFeedItem,
+ content: `
${phpFeedItem.title}
${phpFeedItem.description}
`,
+};
+export const phpFeedItemWithCustomData = {
+ ...phpFeedItem,
+ customData: '',
+};
+
+export const web1FeedItem = {
+ // Should support empty string as a URL (possible for homepage route)
+ link: '',
+ title: 'Web 1.0',
+ pubDate: new Date('1997-05-03'),
+ description:
+ 'Web 1.0 is the term used for the earliest version of the Internet as it emerged from its origins with Defense Advanced Research Projects Agency (DARPA) and became, for the first time, a global network representing the future of digital communications.',
+};
+export const web1FeedItemWithContent = {
+ ...web1FeedItem,
+ content: `
${web1FeedItem.title}
${web1FeedItem.description}
`,
+};
+export const web1FeedItemWithAllData = {
+ ...web1FeedItem,
+ categories: ['web1', 'history'],
+ author: 'test@example.com',
+ commentsUrl: 'http://example.com/comments',
+ source: {
+ url: 'http://example.com/source',
+ title: 'The Web 1.0 blog',
+ },
+ enclosure: {
+ url: '/podcast.mp3',
+ length: 256,
+ type: 'audio/mpeg',
+ },
+};
+
+const parser = new xml2js.Parser({ trim: true });
+
+/**
+ *
+ * Utility function to parse an XML string into an object using `xml2js`.
+ *
+ * @param xmlString - Stringified XML to parse.
+ * @return Represents an option containing the parsed XML string or an Error.
+ */
+export function parseXmlString(xmlString: string): { err: Error | null; result: unknown } {
+ let res!: { err: Error | null; result: unknown };
+ parser.parseString(xmlString, (err: Error | null, result: unknown) => {
+ res = { err, result };
+ });
+ return res;
+}
diff --git a/packages/astro-rss/tsconfig.test.json b/packages/astro-rss/tsconfig.test.json
new file mode 100644
index 000000000000..7d6bc4428b35
--- /dev/null
+++ b/packages/astro-rss/tsconfig.test.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "include": ["test/**/*.ts"],
+ "exclude": ["test/fixtures/**"],
+ "compilerOptions": {
+ "allowJs": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "rewriteRelativeImportExtensions": true
+ },
+ "references": [{ "path": "../astro/tsconfig.test.json" }]
+}
diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md
index 9106cfd12e50..3dff13a8e42c 100644
--- a/packages/astro/CHANGELOG.md
+++ b/packages/astro/CHANGELOG.md
@@ -1,5 +1,92 @@
# astro
+## 6.1.8
+
+### Patch Changes
+
+- [#16367](https://github.com/withastro/astro/pull/16367) [`a6866a7`](https://github.com/withastro/astro/commit/a6866a7ef086627f8f8237274361d8acc2f85121) Thanks [@ematipico](https://github.com/ematipico)! - Fixes an issue where build output files could contain special characters (`!`, `~`, `{`, `}`) in their names, causing deploy failures on platforms like Netlify.
+
+- [#16381](https://github.com/withastro/astro/pull/16381) [`217c5b3`](https://github.com/withastro/astro/commit/217c5b3b937f0aee7e59280e8a10cf2bd4237605) Thanks [@ematipico](https://github.com/ematipico)! - Slightly improved the performance of the dev server by caching the internal crawling of the dependencies of a project.
+
+- [#16348](https://github.com/withastro/astro/pull/16348) [`7d26cd7`](https://github.com/withastro/astro/commit/7d26cd77bc1b33cee81f0e7b408dc2d170be1bdd) Thanks [@ocavue](https://github.com/ocavue)! - Fixes a bug where emitted assets during a client build would contain always fresh, new hashes in their name. Now the build should be more stable.
+
+- [#16317](https://github.com/withastro/astro/pull/16317) [`d012bfe`](https://github.com/withastro/astro/commit/d012bfeadb5b33f9ab1175191d59357d629c327e) Thanks [@das-peter](https://github.com/das-peter)! - Fixes a bug where `allowedDomains` weren't correctly propagated when using the development server.
+
+- [#16379](https://github.com/withastro/astro/pull/16379) [`5a84551`](https://github.com/withastro/astro/commit/5a845514114ae21ca9820e98b56cce33c0cf579b) Thanks [@martrapp](https://github.com/martrapp)! - Improves Vue scoped style handling in DEV mode during client router navigation.
+
+- [#16317](https://github.com/withastro/astro/pull/16317) [`d012bfe`](https://github.com/withastro/astro/commit/d012bfeadb5b33f9ab1175191d59357d629c327e) Thanks [@das-peter](https://github.com/das-peter)! - Adds tests to verify settings are properly propagated when using the development server.
+
+- [#16282](https://github.com/withastro/astro/pull/16282) [`5b0fdaa`](https://github.com/withastro/astro/commit/5b0fdaa8ba3dc17f4b93d9847c3255150b0aeab2) Thanks [@jmurty](https://github.com/jmurty)! - Fixes build errors on platforms with skew protection enabled (e.g. Vercel, Netlify) for inter-chunk Javascript using dynamic imports
+
+- Updated dependencies [[`e0b240e`](https://github.com/withastro/astro/commit/e0b240edea4db632138def3a9003b4b12e12f765)]:
+ - @astrojs/telemetry@3.3.1
+
+## 6.1.7
+
+### Patch Changes
+
+- [#16027](https://github.com/withastro/astro/pull/16027) [`c62516b`](https://github.com/withastro/astro/commit/c62516bbbf8fdf95d38293440d28221c048c41f0) Thanks [@fkatsuhiro](https://github.com/fkatsuhiro)! - Fixes a bug where remote image dimensions were not validated during static builds on Netlify.
+
+- [#16311](https://github.com/withastro/astro/pull/16311) [`94048f2`](https://github.com/withastro/astro/commit/94048f27c30f47ae0e01f90231e0496ed80595f7) Thanks [@Arecsu](https://github.com/Arecsu)! - Fixes `--port` flag being ignored after a Vite-triggered server restart (e.g. when a `.env` file changes)
+
+- [#16316](https://github.com/withastro/astro/pull/16316) [`0fcd04c`](https://github.com/withastro/astro/commit/0fcd04cc985002b56c9e2d36bcb68da0d3f08d5f) Thanks [@ematipico](https://github.com/ematipico)! - Fixes the `/_image` endpoint accepting an arbitrary `f=svg` query parameter and serving non-SVG content as `image/svg+xml`. The endpoint now validates that the source is actually SVG before honoring `f=svg`, matching the same guard already enforced on the `` component path.
+
+## 6.1.6
+
+### Patch Changes
+
+- [#16202](https://github.com/withastro/astro/pull/16202) [`b5c2fba`](https://github.com/withastro/astro/commit/b5c2fba8bf2bc315db94e525f12f7661dd357822) Thanks [@matthewp](https://github.com/matthewp)! - Fixes Actions failing with `ActionsWithoutServerOutputError` when using `output: 'static'` with an adapter
+
+- [#16303](https://github.com/withastro/astro/pull/16303) [`b06eabf`](https://github.com/withastro/astro/commit/b06eabf01afda713066feb803bbc4c89af634aaf) Thanks [@matthewp](https://github.com/matthewp)! - Improves handling of special characters in inline `` variants (case-insensitive,
+ * whitespace, or self-closing forms) or `
+
+