├── .gitignore ├── playground ├── src │ ├── pages │ │ ├── Test.astro │ │ ├── test │ │ │ └── [a]-[b] │ │ │ │ └── foo │ │ │ │ └── [c].png.ts │ │ ├── index.png.ts │ │ ├── bar │ │ │ └── og.png.ts │ │ ├── [...rest].astro │ │ ├── foo │ │ │ └── [bar].astro │ │ └── index.astro │ ├── env.d.ts │ ├── layouts │ │ └── Layout.astro │ └── components │ │ └── Card.astro ├── .vscode │ ├── extensions.json │ └── launch.json ├── tsconfig.json ├── tailwind.config.mjs ├── .gitignore ├── astro.config.mts ├── package.json ├── public │ └── favicon.svg └── README.md ├── packages └── astro-typed-links │ ├── .gitignore │ ├── env.d.ts │ ├── tsconfig.json │ ├── src │ ├── index.ts │ ├── link.ts │ └── integration.ts │ ├── tsup.config.ts │ ├── package.json │ ├── README.md │ └── CHANGELOG.md ├── pnpm-workspace.yaml ├── .vscode └── settings.json ├── .github ├── screenshot.png ├── renovate.json └── workflows │ ├── ci.yml │ └── release.yml ├── .changeset ├── config.json └── README.md ├── README.md ├── biome.json ├── package.json └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /playground/src/pages/Test.astro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/astro-typed-links/.gitignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /playground/src/pages/test/[a]-[b]/foo/[c].png.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - playground -------------------------------------------------------------------------------- /packages/astro-typed-links/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "biomejs.biome" 3 | } 4 | -------------------------------------------------------------------------------- /.github/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florian-lefebvre/astro-typed-links/HEAD/.github/screenshot.png -------------------------------------------------------------------------------- /playground/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /playground/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /playground/src/pages/index.png.ts: -------------------------------------------------------------------------------- 1 | import type { APIRoute } from "astro"; 2 | 3 | export const GET: APIRoute = () => { 4 | return new Response("test"); 5 | }; 6 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "jsx": "preserve" 5 | }, 6 | "exclude": ["dist"] 7 | } 8 | -------------------------------------------------------------------------------- /playground/src/pages/bar/og.png.ts: -------------------------------------------------------------------------------- 1 | import type { APIRoute } from "astro"; 2 | 3 | export const GET: APIRoute = () => { 4 | return new Response("test"); 5 | }; 6 | -------------------------------------------------------------------------------- /playground/src/pages/[...rest].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { GetStaticPaths } from "astro"; 3 | export const getStaticPaths = (() => { 4 | return []; 5 | }) satisfies GetStaticPaths; 6 | --- -------------------------------------------------------------------------------- /playground/src/pages/foo/[bar].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { GetStaticPaths } from "astro"; 3 | export const getStaticPaths = (() => { 4 | return []; 5 | }) satisfies GetStaticPaths; 6 | --- -------------------------------------------------------------------------------- /playground/tailwind.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/astro-typed-links/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strictest", 3 | "compilerOptions": { 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "jsx": "preserve" 7 | }, 8 | "exclude": ["dist"] 9 | } 10 | -------------------------------------------------------------------------------- /playground/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /playground/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { "repo": "florian-lefebvre/astro-typed-links" } 6 | ], 7 | "commit": false, 8 | "fixed": [], 9 | "linked": [], 10 | "access": "public", 11 | "baseBranch": "main", 12 | "updateInternalDependencies": "patch", 13 | "ignore": ["playground"] 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # astro-typed-links 2 | 3 | Automatically get typed links to your pages. 4 | 5 | To see how to get started, check out the [package README](./packages/astro-typed-links/README.md) 6 | 7 | ![Code snippet showing types and autocompletion in action](./.github/screenshot.png) 8 | 9 | ## Licensing 10 | 11 | [MIT Licensed](./LICENSE). Made with ❤️ by [Florian Lefebvre](https://github.com/florian-lefebvre). 12 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", 3 | "assist": { "actions": { "source": { "organizeImports": "on" } } }, 4 | "linter": { 5 | "enabled": true, 6 | "rules": { 7 | "recommended": true, 8 | "suspicious": { 9 | "noExplicitAny": "warn" 10 | } 11 | } 12 | }, 13 | "files": { 14 | "includes": ["**", "!playground", "!**/dist", "!**/.astro"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/astro-typed-links/src/index.ts: -------------------------------------------------------------------------------- 1 | import { integration } from "./integration.js"; 2 | import { link as _link } from "./link.js"; 3 | 4 | /** 5 | * @deprecated Use `link` exported from `astro-typed-links/link` instead. Importing this 6 | * function client-side will cause the build to fail. It will be removed in the next major 7 | */ 8 | export const link = _link; 9 | 10 | export default integration; 11 | 12 | // biome-ignore lint/suspicious/noEmptyInterface: used for augmentation 13 | export interface AstroTypedLinks {} 14 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /packages/astro-typed-links/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | import { peerDependencies } from "./package.json"; 3 | 4 | export default defineConfig((options) => { 5 | const dev = !!options.watch; 6 | return { 7 | entry: ["src/**/*.(ts|js)"], 8 | format: ["esm"], 9 | target: "node18", 10 | bundle: true, 11 | dts: true, 12 | sourcemap: true, 13 | clean: true, 14 | splitting: false, 15 | minify: !dev, 16 | external: [...Object.keys(peerDependencies)], 17 | tsconfig: "tsconfig.json", 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | ":disableDependencyDashboard", 6 | ":disablePeerDependencies", 7 | "schedule:weekly", 8 | "helpers:pinGitHubActionDigestsToSemver", 9 | "group:allNonMajor" 10 | ], 11 | "rangeStrategy": "bump", 12 | "ignorePaths": ["**/node_modules/**"], 13 | "minimumReleaseAge": "3 days", 14 | "postUpdateOptions": ["pnpmDedupe"], 15 | "packageRules": [ 16 | { 17 | "groupName": "github-actions", 18 | "matchManagers": ["github-actions"] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /playground/astro.config.mts: -------------------------------------------------------------------------------- 1 | import tailwind from "@astrojs/tailwind"; 2 | import { createResolver } from "astro-integration-kit"; 3 | import { hmrIntegration } from "astro-integration-kit/dev"; 4 | import { defineConfig } from "astro/config"; 5 | 6 | const { default: typedLinks } = await import("astro-typed-links"); 7 | 8 | // https://astro.build/config 9 | export default defineConfig({ 10 | trailingSlash: "always", 11 | // base: "/docs", 12 | integrations: [ 13 | tailwind(), 14 | typedLinks(), 15 | hmrIntegration({ 16 | directory: createResolver(import.meta.url).resolve("../packages/astro-typed-links/dist"), 17 | }), 18 | ], 19 | }); 20 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "astro dev", 9 | "build": "astro check && astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/tailwind": "^5.1.5", 15 | "astro": "^5.16.5", 16 | "astro-integration-kit": "^0.19.1", 17 | "astro-typed-links": "workspace:*", 18 | "tailwindcss": "^3.4.19" 19 | }, 20 | "devDependencies": { 21 | "@astrojs/check": "^0.9.6", 22 | "@types/node": "^20.19.26", 23 | "typescript": "^5.9.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | permissions: {} 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 16 | with: 17 | persist-credentials: false 18 | - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 19 | - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 20 | with: 21 | node-version: 24.12.0 22 | cache: pnpm 23 | - run: pnpm install 24 | - run: pnpm --filter astro-typed-links build -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "packageManager": "pnpm@10.25.0", 5 | "engines": { 6 | "node": ">=18.20.8" 7 | }, 8 | "scripts": { 9 | "package:dev": "pnpm --filter astro-typed-links dev", 10 | "playground:dev": "pnpm --filter playground dev", 11 | "dev": "pnpm --stream -r -parallel dev", 12 | "changeset": "changeset", 13 | "lint": "biome check .", 14 | "lint:fix": "biome check --write .", 15 | "ci-version": "changeset version && pnpm install --no-frozen-lockfile", 16 | "ci-publish": "changeset publish" 17 | }, 18 | "devDependencies": { 19 | "@biomejs/biome": "2.3.8", 20 | "@changesets/cli": "^2.29.8", 21 | "@changesets/changelog-github": "^0.5.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /playground/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Florian Lefebvre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /playground/src/layouts/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | title: string; 4 | } 5 | 6 | const { title } = Astro.props; 7 | --- 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {title} 18 | 19 | 20 | 21 | 22 | 23 | 52 | -------------------------------------------------------------------------------- /packages/astro-typed-links/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-typed-links", 3 | "version": "1.1.5", 4 | "description": "An Astro integration to automatically get typed links to your pages.", 5 | "type": "module", 6 | "sideEffects": false, 7 | "exports": { 8 | ".": { 9 | "types": "./dist/index.d.ts", 10 | "default": "./dist/index.js" 11 | }, 12 | "./link": { 13 | "types": "./dist/link.d.ts", 14 | "default": "./dist/link.js" 15 | } 16 | }, 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "dev": "tsup --watch", 22 | "build": "tsup", 23 | "prepublishOnly": "pnpm build" 24 | }, 25 | "keywords": [ 26 | "astro-integration", 27 | "astro-component", 28 | "withastro", 29 | "astro", 30 | "optimization" 31 | ], 32 | "author": "florian-lefebvre", 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/florian-lefebvre/astro-typed-links.git" 36 | }, 37 | "bugs": "https://github.com/florian-lefebvre/astro-typed-links/issues", 38 | "homepage": "https://github.com/florian-lefebvre/astro-typed-links#readme", 39 | "license": "MIT", 40 | "peerDependencies": { 41 | "astro": "^5.0.0" 42 | }, 43 | "devDependencies": { 44 | "tsup": "^8.5.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /playground/src/components/Card.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | title: string; 4 | body: string; 5 | href: string; 6 | } 7 | 8 | const { href, title, body } = Astro.props; 9 | --- 10 | 11 | 22 | 62 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | permissions: {} 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | if: ${{ github.repository_owner == 'florian-lefebvre' }} 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | pull-requests: write 16 | id-token: write 17 | steps: 18 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 19 | with: 20 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 21 | fetch-depth: 0 22 | persist-credentials: false 23 | - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 24 | - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 25 | with: 26 | node-version: 24.12.0 27 | cache: 'pnpm' 28 | - run: pnpm install 29 | - name: Create Release Pull Request 30 | uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba # v1.5.3 31 | with: 32 | version: pnpm ci-version 33 | publish: pnpm ci-publish 34 | commit: '[ci] release' 35 | title: '[ci] release' 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | NPM_TOKEN: "" # See https://github.com/changesets/changesets/issues/1152#issuecomment-3190884868 -------------------------------------------------------------------------------- /packages/astro-typed-links/src/link.ts: -------------------------------------------------------------------------------- 1 | import type { AstroTypedLinks } from "./index.js"; 2 | 3 | type Prettify = { 4 | [K in keyof T]: T[K]; 5 | } & {}; 6 | 7 | type Opts = Prettify< 8 | ([T] extends [never] 9 | ? { 10 | params?: never; 11 | } 12 | : { 13 | params: T; 14 | }) & { 15 | searchParams?: Record | URLSearchParams; 16 | hash?: string; 17 | } 18 | >; 19 | 20 | /** 21 | * Get type-safe links to your Astro routes. 22 | */ 23 | export const link = ( 24 | path: TPath, 25 | ...[opts]: AstroTypedLinks[TPath] extends never 26 | ? [opts?: Opts] 27 | : [opts: Opts] 28 | ) => { 29 | let newPath = path as string; 30 | if (opts?.params) { 31 | for (const [key, value] of Object.entries( 32 | opts.params as Record, 33 | )) { 34 | newPath = newPath 35 | .replace(`[${key}]`, value ?? "") 36 | .replace(`[...${key}]`, value ?? ""); 37 | } 38 | // When using spread parameters with a trailing slash, it results in invalid // 39 | // We clean that up 40 | newPath = newPath.replace(/\/\//g, "/"); 41 | } 42 | if (opts?.searchParams) { 43 | if (opts.searchParams instanceof URLSearchParams) { 44 | newPath += `?${opts.searchParams.toString()}`; 45 | } else { 46 | // We need custom handling to avoid encoding 47 | const entries = Object.entries(opts.searchParams); 48 | for (let i = 0; i < entries.length; i++) { 49 | // biome-ignore lint/style/noNonNullAssertion: we know the element exists for this index 50 | const [key, value] = entries[i]!; 51 | newPath += `${i === 0 ? "?" : "&"}${key}=${value}`; 52 | } 53 | } 54 | } 55 | if (opts?.hash) { 56 | newPath += `#${opts.hash}`; 57 | } 58 | return newPath; 59 | }; 60 | -------------------------------------------------------------------------------- /packages/astro-typed-links/README.md: -------------------------------------------------------------------------------- 1 | # `astro-typed-links` 2 | 3 | This is an [Astro integration](https://docs.astro.build/en/guides/integrations-guide/) that automatically get typed links to your pages. 4 | 5 | ## Usage 6 | 7 | ### Prerequisites 8 | 9 | - Requires Astro `>=5.0.0` 10 | 11 | ### Installation 12 | 13 | Install the integration **automatically** using the Astro CLI: 14 | 15 | ```bash 16 | pnpm astro add astro-typed-links 17 | ``` 18 | 19 | ```bash 20 | npx astro add astro-typed-links 21 | ``` 22 | 23 | ```bash 24 | yarn astro add astro-typed-links 25 | ``` 26 | 27 | Or install it **manually**: 28 | 29 | 1. Install the required dependencies 30 | 31 | ```bash 32 | pnpm add astro-typed-links 33 | ``` 34 | 35 | ```bash 36 | npm install astro-typed-links 37 | ``` 38 | 39 | ```bash 40 | yarn add astro-typed-links 41 | ``` 42 | 43 | 2. Add the integration to your astro config 44 | 45 | ```diff 46 | +import typedLinks from "astro-typed-links"; 47 | 48 | export default defineConfig({ 49 | integrations: [ 50 | + typedLinks(), 51 | ], 52 | }); 53 | ``` 54 | 55 | ### `link` helper 56 | 57 | Import `link` from `astro-typed-links` and that's it! 58 | 59 | ```ts 60 | import { link } from 'astro-typed-links' 61 | 62 | link('/') 63 | link('/blog/[slug]', { params: { slug: 'foo' }}) 64 | link('/about', { searchParams: { foo: 'bar' }, hash: 'contact' }) 65 | ``` 66 | 67 | ## Contributing 68 | 69 | This package is structured as a monorepo: 70 | 71 | - `playground` contains code for testing the package 72 | - `package` contains the actual package 73 | 74 | Install dependencies using pnpm: 75 | 76 | ```bash 77 | pnpm i --frozen-lockfile 78 | ``` 79 | 80 | Start the playground and package watcher: 81 | 82 | ```bash 83 | pnpm dev 84 | ``` 85 | 86 | You can now edit files in `package`. Please note that making changes to those files may require restarting the playground dev server. 87 | 88 | ## Licensing 89 | 90 | [MIT Licensed](https://github.com/florian-lefebvre/astro-typed-links/blob/main/LICENSE). Made with ❤️ by [Florian Lefebvre](https://github.com/florian-lefebvre). 91 | -------------------------------------------------------------------------------- /playground/README.md: -------------------------------------------------------------------------------- 1 | # Astro Starter Kit: Basics 2 | 3 | ```sh 4 | npm create astro@latest -- --template basics 5 | ``` 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 8 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics) 9 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json) 10 | 11 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 12 | 13 | ![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554) 14 | 15 | ## 🚀 Project Structure 16 | 17 | Inside of your Astro project, you'll see the following folders and files: 18 | 19 | ```text 20 | / 21 | ├── public/ 22 | │ └── favicon.svg 23 | ├── src/ 24 | │ ├── components/ 25 | │ │ └── Card.astro 26 | │ ├── layouts/ 27 | │ │ └── Layout.astro 28 | │ └── pages/ 29 | │ └── index.astro 30 | └── package.json 31 | ``` 32 | 33 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 34 | 35 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 36 | 37 | Any static assets, like images, can be placed in the `public/` directory. 38 | 39 | ## 🧞 Commands 40 | 41 | All commands are run from the root of the project, from a terminal: 42 | 43 | | Command | Action | 44 | | :------------------------ | :----------------------------------------------- | 45 | | `npm install` | Installs dependencies | 46 | | `npm run dev` | Starts local dev server at `localhost:4321` | 47 | | `npm run build` | Build your production site to `./dist/` | 48 | | `npm run preview` | Preview your build locally, before deploying | 49 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | 50 | | `npm run astro -- --help` | Get help using the Astro CLI | 51 | 52 | ## 👀 Want to learn more? 53 | 54 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 55 | -------------------------------------------------------------------------------- /packages/astro-typed-links/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # astro-typed-links 2 | 3 | ## 1.1.5 4 | 5 | ### Patch Changes 6 | 7 | - [#9](https://github.com/florian-lefebvre/astro-typed-links/pull/9) [`6b01ab1`](https://github.com/florian-lefebvre/astro-typed-links/commit/6b01ab19025eda90f5d5ed0541a9751c60b181f0) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Bumps version, no code change 8 | 9 | ## 1.1.4 10 | 11 | ### Patch Changes 12 | 13 | - 274ad5f: Fixes a case where links containing uppercase characters would be lowercased 14 | 15 | ## 1.1.3 16 | 17 | ### Patch Changes 18 | 19 | - d8c028b: Fixes invalid urls when using trailing slash and providing `undefined` for a spread parameter 20 | 21 | ## 1.1.2 22 | 23 | ### Patch Changes 24 | 25 | - ebc9d41: Fixes a case where `searchParams` provided as an object would be encoded. The object is no longer used within a `URLSearchParams` instance. 26 | 27 | ```js 28 | // BEFORE 29 | link("/", { searchParams: { foo: "{BAR}" } }); // /?foo=%7BTEST%7D 30 | 31 | // AFTER 32 | link("/", { searchParams: { foo: "{BAR}" } }); // /?foo={BAR} 33 | 34 | // To match the old behavior 35 | link("/", { searchParams: new URLSearchParams({ foo: "{BAR}" }) }); // /?foo=%7BTEST%7D 36 | ``` 37 | 38 | ## 1.1.1 39 | 40 | ### Patch Changes 41 | 42 | - 0cf982c: Fixes a case where atrailing slash would be incorrectly appended to endpoints patterns ending with file extensions 43 | 44 | ## 1.1.0 45 | 46 | ### Minor Changes 47 | 48 | - a51742b: Updates how `link` should be imported 49 | 50 | Importing `link` from `astro-typed-links` is deprecated, `astro-typed-links/link` should now be used. This change was necessary because code from the integration was leaking when used client side: 51 | 52 | ```diff 53 | -import { link } from "astro-typed-links" 54 | +import { link } from "astro-typed-links/link" 55 | ``` 56 | 57 | ## 1.0.0 58 | 59 | ### Major Changes 60 | 61 | - 390f225: This update contains **breaking changes**. 62 | 63 | - Drops support for Astro 4.0 in favor of 5.0 64 | - Usage is simplified 65 | - Rest parameters must not be provided with the leading `...` 66 | 67 | To simplify the usage of the integration, updates have been made in Astro core in 5.0, hence the drop of Astro 4.0 support. The README used to specify changes to your `package.json`, you can revert those changes: 68 | 69 | ```diff 70 | { 71 | "scripts": { 72 | - "sync": "astro build --sync && astro sync" 73 | } 74 | } 75 | ``` 76 | 77 | When dealing with rest parameters, you must not include the leading `...` anymore: 78 | 79 | ```diff 80 | link("/[...rest]", { 81 | - "...rest": "foo/bar" 82 | + rest: "foo/bar" 83 | }) 84 | ``` 85 | 86 | ## 0.1.1 87 | 88 | ### Patch Changes 89 | 90 | - a7a9e48: Improves docs 91 | 92 | ## 0.1.0 93 | 94 | ### Minor Changes 95 | 96 | - 3370bda: Initial release 97 | -------------------------------------------------------------------------------- /packages/astro-typed-links/src/integration.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from "node:fs"; 2 | import type { 3 | AstroConfig, 4 | AstroIntegration, 5 | IntegrationResolvedRoute, 6 | RoutePart, 7 | } from "astro"; 8 | 9 | const withTrailingSlash = (path: string) => 10 | path.endsWith("/") ? path : `${path}/`; 11 | 12 | const withoutTrailingSlash = (path: string) => 13 | path.endsWith("/") ? path.slice(0, -1) : path; 14 | 15 | // https://github.com/withastro/astro/blob/main/packages/astro/src/core/routing/manifest/create.ts#L761-L767 16 | const joinSegments = (segments: RoutePart[][]): string => { 17 | const arr = segments.map((segment) => { 18 | return segment 19 | .map((rp) => (rp.dynamic ? `[${rp.content}]` : rp.content)) 20 | .join(""); 21 | }); 22 | 23 | return `/${arr.join("/")}`; 24 | }; 25 | 26 | function getDtsContent( 27 | { base, trailingSlash }: AstroConfig, 28 | routes: Array, 29 | ) { 30 | const data: Array<{ pattern: string; params: Array }> = []; 31 | 32 | for (const route of routes) { 33 | const { params, type } = route; 34 | if (!(type === "page" || type === "endpoint")) { 35 | continue; 36 | } 37 | // `route.pattern` cannot be used because it is lowercased by Astro so we have to rebuild the original path from its segments 38 | const pattern = `${withoutTrailingSlash(base)}${joinSegments( 39 | route.segments, 40 | )}`; 41 | 42 | const segments = route.segments.flat(); 43 | const shouldApplyTrailingSlash = 44 | // Page should alwyas respect the setting. It's trickier with endpoints 45 | type === "page" || 46 | // No segments so it's probably an index route 47 | segments.length === 0 || 48 | // If there are no static segments, we apply 49 | segments.every((seg) => seg.dynamic) || 50 | // If the latest static segment has a dot, we don't apply the setting 51 | // biome-ignore lint/style/noNonNullAssertion: checked earlier 52 | !segments.findLast((seg) => !seg.dynamic)!.content.includes("."); 53 | 54 | if (trailingSlash === "always") { 55 | data.push({ 56 | pattern: shouldApplyTrailingSlash 57 | ? withTrailingSlash(pattern) 58 | : pattern, 59 | params, 60 | }); 61 | } else if (trailingSlash === "never") { 62 | data.push({ pattern, params }); 63 | } else { 64 | data.push({ pattern, params }); 65 | if (!shouldApplyTrailingSlash) { 66 | continue; 67 | } 68 | const r = withTrailingSlash(pattern); 69 | if (pattern !== r) { 70 | data.push({ pattern: r, params }); 71 | } 72 | } 73 | } 74 | 75 | let types = ""; 76 | for (const { pattern, params } of data) { 77 | types += ` "${pattern}": ${ 78 | params.length === 0 79 | ? "never" 80 | : `{${params 81 | .map( 82 | (key) => 83 | `"${key.replace("...", "")}": ${ 84 | key.startsWith("...") ? "string | undefined" : "string" 85 | }`, 86 | ) 87 | .join("; ")}}` 88 | };\n`; 89 | } 90 | return `declare module "astro-typed-links" {\n interface AstroTypedLinks {\n${types} }\n}\n\nexport {}`; 91 | } 92 | 93 | export function integration(): AstroIntegration { 94 | let config: AstroConfig; 95 | let routes: Array; 96 | let dtsURL: URL; 97 | 98 | return { 99 | name: "astro-typed-links", 100 | hooks: { 101 | "astro:routes:resolved": (params) => { 102 | routes = params.routes.filter((route) => route.origin !== "internal"); 103 | // In dev, this hook runs on route change 104 | if (dtsURL) { 105 | writeFileSync(dtsURL, getDtsContent(config, routes)); 106 | } 107 | }, 108 | "astro:config:done": (params) => { 109 | config = params.config; 110 | dtsURL = params.injectTypes({ 111 | filename: "types.d.ts", 112 | content: getDtsContent(config, routes), 113 | }); 114 | }, 115 | }, 116 | }; 117 | } 118 | -------------------------------------------------------------------------------- /playground/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { link } from "astro-typed-links/link"; 3 | import Card from "../components/Card.astro"; 4 | import Layout from "../layouts/Layout.astro"; 5 | 6 | const a = link("/"); 7 | const b = link("/[...rest]/", { 8 | params: { 9 | rest: "foo", 10 | }, 11 | searchParams: { 12 | bar: "baz", 13 | }, 14 | hash: "header", 15 | }); 16 | const c = link("/foo/[bar]/", { params: { bar: "ihaihgfaih" } }); 17 | const d = `${Astro.url.origin}${b}`; 18 | const e = link("/", { 19 | searchParams: { 20 | foo: "{TEST}", 21 | }, 22 | }); 23 | const f = link("/", { 24 | searchParams: new URLSearchParams({ 25 | foo: "{TEST}", 26 | }), 27 | }); 28 | const g = link("/[...rest]/", { params: { rest: undefined } }); 29 | const h = link("/Test/") 30 | console.log({ a, b, c, d, e, f, g, h }); 31 | --- 32 | 33 | 34 | 50 |
51 | 79 |

Welcome to Astro

80 |

81 | To get started, open the directory src/pages in your project.
83 | Code Challenge: Tweak the "Welcome to Astro" message above. 84 |

85 | 107 |
108 |
109 | 110 | 171 | --------------------------------------------------------------------------------