Compare commits

...

15 commits

Author SHA1 Message Date
Nate Moore
f55dbf1d45 fix: update regex 2021-06-28 12:17:42 -04:00
Nate Moore
1a0425820d format 2021-06-28 12:17:24 -04:00
Nate Moore
2b48f10b77 test: update draft test 2021-06-28 12:16:53 -04:00
Nate Moore
e2dfb1e195 chore: fix tests 2021-06-28 11:48:32 -04:00
Nate Moore
ff53c015bd docs: update draft comment 2021-06-28 11:48:32 -04:00
Nate Moore
ad8e937454 fix: use 403 error for unpublished docs 2021-06-28 11:48:32 -04:00
ewatch
c65b92c176 change to draft post 2021-06-28 11:48:32 -04:00
ewatch
271b45f16b adapt interpretation of config default value for buildOptions.draft
adds logic for collections
2021-06-28 11:48:32 -04:00
ewatch
cba4f2bd5c adds tests for markdown draft / published attributes 2021-06-28 11:48:32 -04:00
ewatch
4273c10a38 removes CLI argument 2021-06-28 11:48:32 -04:00
ewatch
6ef9664f98 removes debug output 2021-06-28 11:48:32 -04:00
ewatch
eadf186e42 adds draft configuration option to starter 2021-06-28 11:48:32 -04:00
ewatch
5c6c6c39bc adds handling for draft and published pages 2021-06-28 11:48:32 -04:00
ewatch
2aec0b8a6f adds handling for --no-drafts / --drafts cli command 2021-06-28 11:48:32 -04:00
ewatch
61ec2c54c0 adds default value for buildOptions.draft 2021-06-28 11:48:32 -04:00
19 changed files with 208 additions and 5 deletions

View file

@ -4,6 +4,7 @@ export default {
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// draft: true, // Ignore markdown documents marked as draft when running `astro build`
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},

View file

@ -26,6 +26,8 @@ export interface AstroConfig {
markdownOptions?: Partial<AstroMarkdownOptions>;
/** Options specific to `astro build` */
buildOptions: {
/** Generate pages from draft documents (set to "true" to enable) **/
draft?: boolean;
/** Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. */
site?: string;
/** Generate sitemap (set to "false" to disable) */

View file

@ -92,6 +92,7 @@ export async function buildStaticPage({ astroConfig, buildState, filepath, runti
const { pages: pagesRoot } = astroConfig;
const url = filepath.pathname.replace(pagesRoot.pathname, '/').replace(/(index)?\.(astro|md)$/, '');
const result = await runtime.load(url);
if (result.statusCode === 403) return;
if (result.statusCode !== 200) throw new Error((result as any).error);
const outFile = path.posix.join(url, '/index.html');
buildState[outFile] = {

View file

@ -38,6 +38,9 @@ function validateConfig(config: any): void {
throw new Error('[config] buildOptions.site must be a valid URL');
}
}
if (config.buildOptions.drafts !== undefined && typeof config.buildOptions.drafts !== 'boolean') {
throw new Error(`[config] buildOptions.drafts: ${JSON.stringify(config.buildOptions.drafts)}\n Expected boolean, received ${type(config.buildOptions.drafts)}.`);
}
}
// devOptions
@ -63,6 +66,7 @@ function configDefaults(userConfig?: any): any {
if (!config.buildOptions) config.buildOptions = {};
if (!config.markdownOptions) config.markdownOptions = {};
if (typeof config.buildOptions.sitemap === 'undefined') config.buildOptions.sitemap = true;
if (typeof config.buildOptions.draft === 'undefined') config.buildOptions.draft = false;
return config;
}

View file

@ -50,6 +50,25 @@ export default async function dev(astroConfig: AstroConfig) {
res.end();
break;
}
case 403: {
const fullurl = new URL(req.url || '/', astroConfig.buildOptions.site || `http://localhost${astroConfig.devOptions.port}`);
const reqPath = decodeURI(fullurl.pathname);
error(logging, 'static', 'Forbidden', reqPath);
res.statusCode = 403;
const fourOhThreeResult = await runtime.load('/403');
if (fourOhThreeResult.statusCode === 200) {
if (fourOhThreeResult.contentType) {
res.setHeader('Content-Type', fourOhThreeResult.contentType);
}
res.write(fourOhThreeResult.contents);
} else {
res.setHeader('Content-Type', 'text/plain');
res.write(`Forbidden: ${result.error.message}`);
}
res.end();
break;
}
case 404: {
const fullurl = new URL(req.url || '/', astroConfig.buildOptions.site || `http://localhost${astroConfig.devOptions.port}`);
const reqPath = decodeURI(fullurl.pathname);

View file

@ -46,11 +46,12 @@ type LoadResultSuccess = {
contents: string | Buffer;
contentType?: string | false;
};
type LoadResultForbidden = { statusCode: 403; error: Error; collectionInfo?: CollectionInfo };
type LoadResultNotFound = { statusCode: 404; error: Error; collectionInfo?: CollectionInfo };
type LoadResultRedirect = { statusCode: 301 | 302; location: string; collectionInfo?: CollectionInfo };
type LoadResultError = { statusCode: 500 } & ({ type: 'parse-error'; error: CompileError } | { type: 'not-found'; error: CompileError } | { type: 'unknown'; error: Error });
export type LoadResult = (LoadResultSuccess | LoadResultNotFound | LoadResultRedirect | LoadResultError) & { collectionInfo?: CollectionInfo };
export type LoadResult = (LoadResultSuccess | LoadResultForbidden | LoadResultNotFound | LoadResultRedirect | LoadResultError) & { collectionInfo?: CollectionInfo };
// Disable snowpack from writing to stdout/err.
configureSnowpackLogger(snowpackLogger);
@ -105,6 +106,23 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro
let collection = {} as CollectionResult;
let additionalURLs = new Set<string>();
if (mod.exports.__content) {
const hasDraftAttribute = mod.exports.__content.hasOwnProperty('draft');
const hasPublishAttribute = mod.exports.__content.hasOwnProperty('published');
if (hasPublishAttribute && mod.exports.__content['published'] === false) {
return {
statusCode: 403,
error: new Error('Document is not published')
};
}
if (hasDraftAttribute && mod.exports.__content['draft'] === true && !buildOptions.draft && config.mode === 'production') {
return {
statusCode: 403,
error: new Error('Document is not published')
};
}
}
if (mod.exports.createCollection) {
const createCollection: CreateCollection = await mod.exports.createCollection();
const VALID_KEYS = new Set(['data', 'routes', 'permalink', 'pageSize', 'rss']);
@ -137,6 +155,10 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro
let data: any[] = await loadData({ params: currentParams });
if (!data) throw new Error(`[createCollection] \`data()\` returned nothing (empty data)"`);
if (!Array.isArray(data)) data = [data]; // note: this is supposed to be a little friendlier to the user, but should we error out instead?
data = data.filter(entry => !entry.hasOwnProperty('published') || (entry.hasOwnProperty('published') && entry.published));
if (!buildOptions.draft && config.mode === "production") {
data = data.filter(entry => !entry.hasOwnProperty('draft') || (entry.hasOwnProperty('draft') && !entry.draft));
}
// handle RSS
if (createRSS) {

View file

@ -115,7 +115,7 @@ export function searchForPage(url: URL, astroConfig: AstroConfig): SearchResult
function loadCollection(url: string, astroConfig: AstroConfig): { currentPage?: number; location: PageLocation } | undefined {
const pages = glob('**/$*.astro', { cwd: fileURLToPath(astroConfig.pages), filesOnly: true });
for (const pageURL of pages) {
const reqURL = new RegExp('^/' + pageURL.replace(/\$([^/]+)\.astro/, '$1') + '(?:/(.*)|/?$)');
const reqURL = new RegExp('^/' + pageURL.replace(/\$([^/]+)\.astro/, '$1') + '(?:/(.+)|/?$)');
const match = url.match(reqURL);
if (match) {
let currentPage: number | undefined;

View file

@ -17,7 +17,7 @@ Collections('shallow selector (*.md)', async ({ runtime }) => {
}),
];
// assert they loaded in newest -> oldest order (not alphabetical)
assert.equal(urls, ['/post/three', '/post/two', '/post/one']);
assert.equal(urls, ['/post/six', '/post/five', '/post/three', '/post/two', '/post/one']);
});
Collections('deep selector (**/*.md)', async ({ runtime }) => {
@ -29,7 +29,7 @@ Collections('deep selector (**/*.md)', async ({ runtime }) => {
return $(this).attr('href');
}),
];
assert.equal(urls, ['/post/nested/a', '/post/three', '/post/two', '/post/one']);
assert.equal(urls, ['/post/nested/a', '/post/six', '/post/five', '/post/three', '/post/two', '/post/one']);
});
Collections('generates pagination successfully', async ({ runtime }) => {
@ -123,4 +123,18 @@ Collections('matches collection filename exactly', async ({ runtime }) => {
assert.equal(urls, ['/post/nested/a', '/post/three', '/post/two', '/post/one']);
});
Collections('have the proper amount of elements with respecting published / draft documents', async ({ runtime }) => {
const result = await runtime.load('/paginated');
if (result.error) throw new Error(result.error);
const $ = doc(result.contents);
const start = $('#start');
const end = $('#end');
const total = $('#total');
assert.equal(start.text(), "0");
assert.equal(end.text(), "4");
assert.equal(total.text(), "5");
});
Collections.run();

View file

@ -22,6 +22,12 @@ export async function createCollection() {
))}
</div>
<div id="results">
<span id="start">{collection.start}</span>
<span id="end">{collection.end}</span>
<span id="total">{collection.total}</span>
</div>
<nav>
{collection.url.prev && <a id="prev-page" href={collection.url.prev}>Previous page</a>}
{collection.url.next && <a id="next-page" href={collection.url.next}>Next page</a>}

View file

@ -8,7 +8,7 @@ export async function createCollection() {
data.sort((a, b) => new Date(b.date) - new Date(a.date));
return data;
},
pageSize: 4
pageSize: 5
};
}
---

View file

@ -0,0 +1,9 @@
---
title: Post Five
date: 2021-04-17 00:00:00
draft: false
---
# Post Five
Im the fifth blog post

View file

@ -0,0 +1,9 @@
---
title: Post Four
date: 2021-04-16 00:00:00
published: false
---
# Post Four
Im the fourth blog post

View file

@ -0,0 +1,9 @@
---
title: Post Six
date: 2021-04-18 00:00:00
published: true
---
# Post Six
Im the sixth blog post

View file

@ -2,6 +2,7 @@
title: Post Two
date: 2021-04-14 00:00:00
author: author-two
draft: true
---
# Post Two

View file

@ -0,0 +1,18 @@
---
layout: ../layouts/content.astro
title: My Blog Post
description: This is a post about some stuff.
draft: true
---
## Interesting Topic
Hello world!
```json
{
"key": "value"
}
```
<div id="first">Some content</div>

View file

@ -0,0 +1,18 @@
---
layout: ../layouts/content.astro
title: My Blog Post
description: This is a post about some stuff.
draft: false
---
## Interesting Topic
Hello world!
```json
{
"key": "value"
}
```
<div id="first">Some content</div>

View file

@ -0,0 +1,18 @@
---
layout: ../layouts/content.astro
title: My Blog Post
description: This is a post about some stuff.
published: true
---
## Interesting Topic
Hello world!
```json
{
"key": "value"
}
```
<div id="first">Some content</div>

View file

@ -0,0 +1,18 @@
---
layout: ../layouts/content.astro
title: My Blog Post
description: This is a post about some stuff.
published: false
---
## Interesting Topic
Hello world!
```json
{
"key": "value"
}
```
<div id="first">Some content</div>

View file

@ -30,6 +30,40 @@ Markdown('Can load a realworld markdown page with Astro', async ({ runtime }) =>
assert.equal($('pre').length, 7);
});
Markdown('Cant load a post being unpublished', async ({ runtime }) => {
const result = await runtime.load('/post-unpublished');
assert.type(result.error, 'object')
assert.equal(result.error.message, 'Document is not published');
assert.equal(result.statusCode, 403);
});
Markdown('Cant load a post being published', async ({ runtime }) => {
const result = await runtime.load('/post-published');
if (result.error) throw new Error(result.error);
assert.equal(result.statusCode, 200);
const $ = doc(result.contents);
assert.equal($('p').first().text(), 'Hello world!');
assert.equal($('#first').text(), 'Some content');
assert.equal($('#interesting-topic').text(), 'Interesting Topic');
});
Markdown('Cant load a post being in draft status', async ({ runtime }) => {
const result = await runtime.load('/post-draft');
if (result.error) throw new Error(result.error);
assert.equal(result.statusCode, 200);
const $ = doc(result.contents);
assert.equal($('p').first().text(), 'Hello world!');
assert.equal($('#first').text(), 'Some content');
assert.equal($('#interesting-topic').text(), 'Interesting Topic');
});
Markdown('Builds markdown pages for prod', async (context) => {
await context.build();
});