├── .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 | 
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 |
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 |
12 |
13 |
14 | {title}
15 | →
16 |
17 |
18 | {body}
19 |
20 |
21 |
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 | [](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
8 | [](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
9 | [](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
10 |
11 | > 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
12 |
13 | 
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 |
86 |
91 |
96 |
101 |
106 |
107 |
108 |
109 |
110 |
171 |
--------------------------------------------------------------------------------