diff --git a/.changeset/cool-experts-shake.md b/.changeset/cool-experts-shake.md new file mode 100644 index 000000000000..f0bbc7e78d21 --- /dev/null +++ b/.changeset/cool-experts-shake.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Added better types to importing Markdown diff --git a/.changeset/large-birds-repair.md b/.changeset/large-birds-repair.md new file mode 100644 index 000000000000..669ab6b86a10 --- /dev/null +++ b/.changeset/large-birds-repair.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Markdown file.url now respects `trailingSlash` and `base` diff --git a/LICENSE b/LICENSE index 96b135cf7b3c..1f0bcaa7dcef 100644 --- a/LICENSE +++ b/LICENSE @@ -32,3 +32,30 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + + +""" +This license applies to parts of the `packages/create-astro` and `packages/astro` subdirectories originating from the https://github.com/vitejs/vite repository: + +MIT License + +Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" diff --git a/packages/astro/env.d.ts b/packages/astro/env.d.ts index 54f9b17279e5..d9dfe741a7e0 100644 --- a/packages/astro/env.d.ts +++ b/packages/astro/env.d.ts @@ -11,3 +11,16 @@ type Astro = import('astro').AstroGlobal; declare const Astro: Readonly; declare const Fragment: any; + +declare module '*.md' { + type MD = import('astro').MarkdownInstance>; + + export const frontmatter: MD['frontmatter']; + export const file: MD['file']; + export const url: MD['url']; + export const getHeaders: MD['getHeaders']; + export const Content: MD['Content']; + + const load: MD['default']; + export default load; +} diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 3047ae1b2bff..baf4a22e761c 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -725,7 +725,13 @@ export interface MarkdownInstance> { file: string; url: string | undefined; Content: AstroComponentFactory; - getHeaders(): Promise<{ depth: number; slug: string; text: string }[]>; + getHeaders(): Promise; + default: () => Promise<{ + metadata: MarkdownMetadata; + frontmatter: MarkdownContent; + $$metadata: Metadata; + default: AstroComponentFactory; + }>; } export type GetHydrateCallback = () => Promise< @@ -767,18 +773,35 @@ export interface ManifestData { routes: RouteData[]; } +export interface MarkdownHeader { + depth: number; + slug: string; + text: string; +} + +export interface MarkdownMetadata { + headers: MarkdownHeader[]; + source: string; + html: string; +} + export interface MarkdownParserResponse { frontmatter: { [key: string]: any; }; - metadata: { - headers: any[]; - source: string; - html: string; - }; + metadata: MarkdownMetadata; code: string; } +/** + * The `content` prop given to a Layout + * https://docs.astro.build/guides/markdown-content/#markdown-layouts + */ +export interface MarkdownContent { + [key: string]: any; + astro: MarkdownMetadata; +} + /** * paginate() Options * Docs: https://docs.astro.build/guides/pagination/#calling-the-paginate-function diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index e76d51ca38f7..d68f8faf30d4 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -179,7 +179,7 @@ async function generatePath( // If a base path was provided, append it to the site URL. This ensures that // all injected scripts and links are referenced relative to the site and subpath. const site = - astroConfig.base && astroConfig.base !== './' + astroConfig.base !== '/' ? joinPaths(astroConfig.site?.toString() || 'http://localhost/', astroConfig.base) : astroConfig.site; const links = createLinkStylesheetElementSet(linkIds.reverse(), site); diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index 57b45ef1b42d..de947530d888 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -94,8 +94,8 @@ export const AstroConfigSchema = z.object({ base: z .string() .optional() - .default('./') - .transform((val) => (val ? appendForwardSlash(trimSlashes(val)) : val)), + .default('/') + .transform((val) => appendForwardSlash(trimSlashes(val))), trailingSlash: z .union([z.literal('always'), z.literal('never'), z.literal('ignore')]) .optional() diff --git a/packages/astro/src/core/render/dev/html.ts b/packages/astro/src/core/render/dev/html.ts index 243eeb29579e..065b07cf0245 100644 --- a/packages/astro/src/core/render/dev/html.ts +++ b/packages/astro/src/core/render/dev/html.ts @@ -66,32 +66,10 @@ export function collectResources(html: string): Resource[] { // Vite’s `transformIndexHtml()` API for ease-of-use and consistency. But we need // to borrow a few private methods in Vite to make that available here. // https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/html.ts +// +// See LICENSE for more info. // ------------------------------------------------------------------------------- -// Vite is released under the MIT license: - -// MIT License - -// Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - const unaryTags = new Set(['link', 'meta', 'base']); function serializeTag({ tag, attrs, children }: vite.HtmlTagDescriptor, indent = ''): string { diff --git a/packages/astro/src/vite-plugin-astro-server/index.ts b/packages/astro/src/vite-plugin-astro-server/index.ts index d21fa7e8eb89..0fdb8705b778 100644 --- a/packages/astro/src/vite-plugin-astro-server/index.ts +++ b/packages/astro/src/vite-plugin-astro-server/index.ts @@ -114,8 +114,7 @@ async function handle404Response( // HACK: redirect without the base path for assets in publicDir const redirectTo = req.method === 'GET' && - config.base && - config.base !== './' && + config.base !== '/' && pathname.startsWith(config.base) && pathname.replace(config.base, '/'); diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts index 231977002a36..105ff47e093c 100644 --- a/packages/astro/src/vite-plugin-markdown/index.ts +++ b/packages/astro/src/vite-plugin-markdown/index.ts @@ -78,13 +78,18 @@ export default function markdown({ config }: AstroPluginOptions): Plugin { // Return the file's JS representation, including all Markdown // frontmatter and a deferred `import() of the compiled markdown content. if (id.endsWith(`.md${MARKDOWN_IMPORT_FLAG}`)) { - const sitePathname = config.site - ? appendForwardSlash(new URL(config.base, config.site).pathname) - : '/'; + const sitePathname = appendForwardSlash( + config.site ? new URL(config.base, config.site).pathname : config.base + ); + const fileId = id.replace(MARKDOWN_IMPORT_FLAG, ''); - const fileUrl = fileId.includes('/pages/') - ? fileId.replace(/^.*\/pages\//, sitePathname).replace(/(\/index)?\.md$/, '') + let fileUrl = fileId.includes('/pages/') + ? fileId.replace(/^.*?\/pages\//, sitePathname).replace(/(\/index)?\.md$/, '') : undefined; + if (fileUrl && config.trailingSlash === 'always') { + fileUrl = appendForwardSlash(fileUrl); + } + const source = await fs.promises.readFile(fileId, 'utf8'); const { data: frontmatter } = matter(source); return { diff --git a/packages/astro/test/astro-markdown-url.test.js b/packages/astro/test/astro-markdown-url.test.js new file mode 100644 index 000000000000..54ce8bfba496 --- /dev/null +++ b/packages/astro/test/astro-markdown-url.test.js @@ -0,0 +1,100 @@ +import { expect } from 'chai'; +import cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('Astro Markdown URL', () => { + describe('With subpath', () => { + const baseUrl = `/my-cool-base/docs/pages/how-to-make-a-page`; + + it('trailingSlash: always', async () => { + let fixture = await loadFixture({ + root: './fixtures/astro-markdown-url/', + outDir: new URL('./fixtures/astro-markdown-url/with-subpath-always/', import.meta.url), + base: '/my-cool-base', + trailingSlash: 'always', + }); + await fixture.build(); + + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + + expect($('#url').attr('href')).to.equal(baseUrl + '/'); + }); + + it('trailingSlash: never', async () => { + let fixture = await loadFixture({ + root: './fixtures/astro-markdown-url/', + outDir: new URL('./fixtures/astro-markdown-url/with-subpath-never/', import.meta.url), + base: '/my-cool-base', + trailingSlash: 'never', + }); + await fixture.build(); + + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + + expect($('#url').attr('href')).to.equal(baseUrl); + }); + + it('trailingSlash: ignore', async () => { + let fixture = await loadFixture({ + root: './fixtures/astro-markdown-url/', + outDir: new URL('./fixtures/astro-markdown-url/with-subpath-ignore/', import.meta.url), + base: '/my-cool-base', + trailingSlash: 'ignore', + }); + await fixture.build(); + + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + + expect($('#url').attr('href')).to.equal(baseUrl); + }); + }); + + describe('Without subpath', () => { + const baseUrl = `/docs/pages/how-to-make-a-page`; + + it('trailingSlash: always', async () => { + let fixture = await loadFixture({ + root: './fixtures/astro-markdown-url/', + outDir: new URL('./fixtures/astro-markdown-url/without-subpath-always/', import.meta.url), + trailingSlash: 'always', + }); + await fixture.build(); + + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + + expect($('#url').attr('href')).to.equal(baseUrl + '/'); + }); + + it('trailingSlash: never', async () => { + let fixture = await loadFixture({ + root: './fixtures/astro-markdown-url/', + outDir: new URL('./fixtures/astro-markdown-url/without-subpath-never/', import.meta.url), + trailingSlash: 'never', + }); + await fixture.build(); + + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + + expect($('#url').attr('href')).to.equal(baseUrl); + }); + + it('trailingSlash: ignore', async () => { + let fixture = await loadFixture({ + root: './fixtures/astro-markdown-url/', + outDir: new URL('./fixtures/astro-markdown-url/without-subpath-ignore/', import.meta.url), + trailingSlash: 'ignore', + }); + await fixture.build(); + + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + + expect($('#url').attr('href')).to.equal(baseUrl); + }); + }); +}); diff --git a/packages/astro/test/fixtures/astro-markdown-url/.gitignore b/packages/astro/test/fixtures/astro-markdown-url/.gitignore new file mode 100644 index 000000000000..dad8532948d7 --- /dev/null +++ b/packages/astro/test/fixtures/astro-markdown-url/.gitignore @@ -0,0 +1,6 @@ +with-subpath-always/ +with-subpath-never/ +with-subpath-ignore/ +without-subpath-always/ +without-subpath-never/ +without-subpath-ignore/ diff --git a/packages/astro/test/fixtures/astro-markdown-url/package.json b/packages/astro/test/fixtures/astro-markdown-url/package.json new file mode 100644 index 000000000000..341814951a03 --- /dev/null +++ b/packages/astro/test/fixtures/astro-markdown-url/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/astro-markdown-url", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/astro-markdown-url/src/pages/docs/pages/how-to-make-a-page.md b/packages/astro/test/fixtures/astro-markdown-url/src/pages/docs/pages/how-to-make-a-page.md new file mode 100644 index 000000000000..901c35955aec --- /dev/null +++ b/packages/astro/test/fixtures/astro-markdown-url/src/pages/docs/pages/how-to-make-a-page.md @@ -0,0 +1,5 @@ +--- +title: How to make a page +--- + +Test diff --git a/packages/astro/test/fixtures/astro-markdown-url/src/pages/index.astro b/packages/astro/test/fixtures/astro-markdown-url/src/pages/index.astro new file mode 100644 index 000000000000..c6a77b0e1a59 --- /dev/null +++ b/packages/astro/test/fixtures/astro-markdown-url/src/pages/index.astro @@ -0,0 +1,5 @@ +--- +import * as page from './docs/pages/how-to-make-a-page.md' +--- + +{page.frontmatter.title} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a470892db1e..baa7a59ca2d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -860,6 +860,12 @@ importers: dependencies: astro: link:../../../.. + packages/astro/test/fixtures/astro-markdown-url: + specifiers: + astro: workspace:* + dependencies: + astro: link:../../.. + packages/astro/test/fixtures/astro-page-directory-url: specifiers: astro: workspace:* diff --git a/scripts/stats/stats.csv b/scripts/stats/stats.csv index 66273814fb33..83a35ae53516 100644 --- a/scripts/stats/stats.csv +++ b/scripts/stats/stats.csv @@ -1,4 +1,7 @@ Date,Commits (24hr),Issues (24hr),Issues:BUG (24hr),Issues:RFC (24hr),Issues:DOC (24hr),PRs (24hr),Open PRs,Open Issues,Bugs: Needs Triage,Bugs: Accepted,RFC: In Progress,RFC: Accepted,Date (ISO) +"Monday, April 25, 2022",2,3,3,0,0,2,14,105,66,35,0,0,"2022-04-25T12:06:54.900Z" +"Sunday, April 24, 2022",1,4,4,0,0,1,13,102,63,35,0,0,"2022-04-24T12:01:45.655Z" +"Saturday, April 23, 2022",10,1,1,0,0,9,12,98,59,35,0,0,"2022-04-23T12:05:15.509Z" "Friday, April 22, 2022",20,4,4,0,0,16,12,98,59,35,0,0,"2022-04-22T12:02:01.807Z" "Thursday, April 21, 2022",7,3,3,0,0,6,9,101,58,37,0,0,"2022-04-21T12:04:09.984Z" "Wednesday, April 20, 2022",19,0,0,0,0,7,6,108,61,40,0,0,"2022-04-20T12:02:15.590Z"