mirror of
https://github.com/modernweb-dev/rocket.git
synced 2026-03-21 15:54:57 +00:00
Compare commits
11 Commits
check-html
...
@rocket/cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b968badf43 | ||
|
|
c92769a145 | ||
|
|
562e91fc43 | ||
|
|
ffd06fcee9 | ||
|
|
0eb507d7ef | ||
|
|
45cd7206f1 | ||
|
|
eb74110dd8 | ||
|
|
517c7780ab | ||
|
|
e4852db673 | ||
|
|
c6c564ede2 | ||
|
|
a498a5da44 |
@@ -35,6 +35,9 @@ export default {
|
||||
// add a plugin to eleventy (e.g. a filter packs)
|
||||
setupEleventyPlugins: [],
|
||||
|
||||
// add a computedConfig to eleventy (e.g. site wide default variables like socialMediaImage)
|
||||
setupEleventyComputedConfig: [],
|
||||
|
||||
// add a plugin to the cli (e.g. a new command like "rocket my-command")
|
||||
setupCliPlugins: [],
|
||||
};
|
||||
|
||||
41
docs/docs/configuration/setupEleventyComputedConfig.md
Normal file
41
docs/docs/configuration/setupEleventyComputedConfig.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Configuration >> setupEleventyComputedConfig ||20
|
||||
|
||||
If you want to add data that depends on other data then you can do it via [11ty's computed data](https://www.11ty.dev/docs/data-computed/).
|
||||
|
||||
Rocket exposes it via `setupEleventyComputedConfig`.
|
||||
|
||||
## Set your own data
|
||||
|
||||
Let's say you want to add a `Welcome to the contact page` everyhwere. (a filter might be a better choise but it's a good example of the concept)
|
||||
|
||||
👉 `rocket.config.mjs` (or your theme config file)
|
||||
|
||||
```js
|
||||
import { addPlugin } from 'plugins-manager';
|
||||
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {
|
||||
setupEleventyComputedConfig: [
|
||||
addPlugin({ name: 'greeting', plugin: data => `Welcome to the ${data.title} page.` }),
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
Now you can use everywhere {{ greeting }}.
|
||||
{% endraw %}
|
||||
And it will correctly replaced with a Welcome and the page title.
|
||||
|
||||
## Default Available Configs
|
||||
|
||||
```js
|
||||
[
|
||||
{ name: 'titleMeta', plugin: titleMetaPlugin },
|
||||
{ name: 'title', plugin: titlePlugin },
|
||||
{ name: 'eleventyNavigation', plugin: eleventyNavigationPlugin },
|
||||
{ name: 'section', plugin: sectionPlugin },
|
||||
{ name: 'socialMediaImage', plugin: socialMediaImagePlugin },
|
||||
];
|
||||
```
|
||||
@@ -9,6 +9,8 @@ It will look like this but with your logo
|
||||
|
||||
There are multiple ways you can modify it.
|
||||
|
||||
Note: If your logo has an `<?xml>` tag it will throw an error as it will be inlined into this svg and nested xml tags are not allowed.
|
||||
|
||||
## Setting it via frontMatter
|
||||
|
||||
You can create your own image and link it with something like this
|
||||
@@ -25,20 +27,105 @@ Sometimes extracting the title + title of parent is not enough but you still wan
|
||||
|
||||
You can create an `11tydata.cjs` file next to your page. If your page is `docs/guides/overview.md` then you create a `docs/guides/overview.11tydata.cjs`.
|
||||
|
||||
In there you can use the default `createPageSocialImage` but provide your own values.
|
||||
In there you can use the default `createSocialImage` but provide your own values.
|
||||
|
||||
```js
|
||||
const { createPageSocialImage } = require('@rocket/cli');
|
||||
const { createSocialImage } = require('@rocket/cli');
|
||||
|
||||
module.exports = async function () {
|
||||
const socialMediaImage = await createPageSocialImage({
|
||||
const socialMediaImage = await createSocialImage({
|
||||
title: 'Learning Rocket',
|
||||
subTitle: 'Have a website',
|
||||
subTitle2: 'in 5 Minutes',
|
||||
footer: 'Rocket Guides',
|
||||
// you can also override the svg only for this page by providing
|
||||
// createSocialImageSvg: async () => '<svg>...</svg>'
|
||||
});
|
||||
return {
|
||||
socialMediaImage,
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Override the default image
|
||||
|
||||
Often you want to have a unique style for your social media images.
|
||||
For that you can provide your own function which returns a string of an svg to render the image.
|
||||
|
||||
👉 `rocket.config.mjs`
|
||||
|
||||
```js
|
||||
import { adjustPluginOptions } from 'plugins-manager';
|
||||
|
||||
/** @type {Partial<import("@rocket/cli").RocketCliOptions>} */
|
||||
const config = {
|
||||
setupEleventyComputedConfig: [
|
||||
adjustPluginOptions('socialMediaImage', {
|
||||
createSocialImageSvg: async ({
|
||||
title = '',
|
||||
subTitle = '',
|
||||
subTitle2 = '',
|
||||
footer = '',
|
||||
logo = '',
|
||||
}) => {
|
||||
let svgStr = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" style="fill: #ecedef;">
|
||||
<defs/>
|
||||
<rect width="100%" height="100%" fill="#38393e"/>
|
||||
<g transform="matrix(0.45, 0, 0, 0.45, 300, 60)">${logo}</g>
|
||||
<g style="
|
||||
font-size: 70px;
|
||||
text-anchor: middle;
|
||||
font-family: 'Bitstream Vera Sans','Helvetica',sans-serif;
|
||||
font-weight: 700;
|
||||
">
|
||||
<text x="50%" y="470">
|
||||
${title}
|
||||
</text>
|
||||
<text x="50%" y="520" style="font-size: 30px;">
|
||||
${subTitle}
|
||||
</text>
|
||||
</g>
|
||||
<text x="10" y="620" style="font-size: 30px; fill: gray;">
|
||||
${footer}
|
||||
</text>
|
||||
</svg>
|
||||
`;
|
||||
return svgStr;
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
## Using an svg file as a src with nunjucks
|
||||
|
||||
If you have multiple variations it may be easier to save them as svg files and using a template system
|
||||
|
||||
WARNING: Untested example
|
||||
|
||||
👉 `rocket.config.mjs`
|
||||
|
||||
{% raw %}
|
||||
|
||||
```js
|
||||
import { adjustPluginOptions } from 'plugins-manager';
|
||||
|
||||
/** @type {Partial<import("@rocket/cli").RocketCliOptions>} */
|
||||
const config = {
|
||||
setupEleventyComputedConfig: [
|
||||
adjustPluginOptions('socialMediaImage', {
|
||||
createSocialImageSvg: async (args = {}) => {
|
||||
// inside of the svg you can use {{ title }}
|
||||
const svgBuffer = await fs.promises.readFile('/path/to/your/svg/file');
|
||||
const svg = logoBuffer.toString();
|
||||
return nunjucks.renderString(svg, args);
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { createPageSocialImage } = require('@rocket/cli');
|
||||
const { createSocialImage } = require('@rocket/cli');
|
||||
|
||||
module.exports = async function () {
|
||||
const socialMediaImage = await createPageSocialImage({
|
||||
const socialMediaImage = await createSocialImage({
|
||||
title: 'Learning Rocket',
|
||||
subTitle: 'Have a website',
|
||||
subTitle2: 'in 5 Minutes',
|
||||
|
||||
@@ -1,7 +1,106 @@
|
||||
# Presets >> Create your own || 90
|
||||
|
||||
All loaded presets will be combined but you can override each file.
|
||||
A preset is setup function and a folder including `_assets`, `_data` and `_includes` (all optional).
|
||||
|
||||
Take a look at `docs/_merged_includes` and override what you want to override by placing the same filename into `_includes`.
|
||||
To play around with a preset you can create a folder `fire-theme`.
|
||||
|
||||
Also works for `_assets`, `_data` ...
|
||||
You then create the setup function for it with only one property called `path` which will allow Rocket to properly resolve it.
|
||||
|
||||
## Create a Preset Config File
|
||||
|
||||
👉 `fire-theme/fireTheme.js`
|
||||
|
||||
```js
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export function fireTheme() {
|
||||
return {
|
||||
path: path.resolve(__dirname),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Once you have that you can start filling in content you need.
|
||||
|
||||
For example a we could override the full `layout.css` by adding a it like so
|
||||
|
||||
👉 `fire-theme/layout.css`
|
||||
|
||||
```css
|
||||
body {
|
||||
background: hotpink;
|
||||
}
|
||||
```
|
||||
|
||||
Once you have that you can add it to your Rocket Config.
|
||||
|
||||
NOTE: The order of presets is important, as for example in this case we take everything from `rocketLaunch` but later override via `fireTheme`.
|
||||
|
||||
👉 `rocket-config.js`
|
||||
|
||||
```js
|
||||
import { rocketLaunch } from '@rocket/launch';
|
||||
import { fireTheme } from 'path/to/fire-theme/fireTheme.js';
|
||||
|
||||
export default {
|
||||
presets: [rocketLaunch(), fireTheme()],
|
||||
};
|
||||
```
|
||||
|
||||
## Publish a preset
|
||||
|
||||
If you would like to publish a preset to use it on multiple websites or share it with your friends you can do like so.
|
||||
|
||||
1. Pick a name for the package => for this example we take `fire-theme`.
|
||||
2. Create a new folder `fire-theme`
|
||||
3. Create a folder `fire-theme/preset` copy `fireTheme.js` from [above](#create-a-preset-config-file) into `preset/fireTheme.js`
|
||||
4. Add a 👉 `package.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "fire-theme",
|
||||
"version": "0.3.0",
|
||||
"description": "Fire Theme for Rocket",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./preset/": "./preset/"
|
||||
},
|
||||
"files": ["*.js", "preset"],
|
||||
"keywords": ["rocket", "preset"]
|
||||
}
|
||||
```
|
||||
|
||||
5. Add a 👉 `index.js`
|
||||
|
||||
```js
|
||||
export { fireTheme } from './preset/fireTheme.js';
|
||||
```
|
||||
|
||||
6. Add a 👉 `README.md`
|
||||
|
||||
````
|
||||
# FireTheme
|
||||
|
||||
This is a theme/preset for [Rocket](https://rocket.modern-web.dev/).
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm i -D fire-theme
|
||||
```
|
||||
|
||||
Add it to your 👉 `rocket.config.js`
|
||||
|
||||
```js
|
||||
import { fireTheme } from 'fire-theme';
|
||||
|
||||
export default {
|
||||
presets: [fireTheme()],
|
||||
};
|
||||
```
|
||||
````
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { createPageSocialImage } = require('@rocket/cli');
|
||||
const { createSocialImage } = require('@rocket/cli');
|
||||
|
||||
module.exports = async function () {
|
||||
const socialMediaImage = await createPageSocialImage({
|
||||
const socialMediaImage = await createSocialImage({
|
||||
title: 'Rocket',
|
||||
subTitle: 'Static sites with',
|
||||
subTitle2: 'a sprinkle of JavaScript.',
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
# check-html-links
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- eb74110: Add info about how many files and links will be checked
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- cd22231: Initial release
|
||||
|
||||
@@ -16,7 +16,7 @@ npx check-html-links _site
|
||||
|
||||
For docs please see our homepage [https://rocket.modern-web.dev/docs/tools/check-html-links/](https://rocket.modern-web.dev/docs/tools/check-html-links/).
|
||||
|
||||
## Comparision
|
||||
## Comparison
|
||||
|
||||
Checking the output of [11ty-website](https://github.com/11ty/11ty-website) with 13 missing reference targets (used by 516 links) while checking 501 files. (on January 17, 2021)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "check-html-links",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
@@ -13,7 +13,17 @@ async function main() {
|
||||
|
||||
console.log('👀 Checking if all internal links work...');
|
||||
const files = await listFiles('**/*.html', rootDir);
|
||||
const errors = await validateFiles(files, rootDir);
|
||||
|
||||
const filesOutput =
|
||||
files.length == 0
|
||||
? '🧐 No files to check. Did you select the correct folder?'
|
||||
: `🔥 Found a total of ${chalk.green.bold(files.length)} files to check!`;
|
||||
console.log(filesOutput);
|
||||
|
||||
const { errors, numberLinks } = await validateFiles(files, rootDir);
|
||||
|
||||
console.log(`🔗 Found a total of ${chalk.green.bold(numberLinks)} links to validate!\n`);
|
||||
|
||||
const performance = process.hrtime(performanceStart);
|
||||
if (errors.length > 0) {
|
||||
let referenceCount = 0;
|
||||
|
||||
@@ -267,13 +267,15 @@ export async function validateFiles(files, rootDir) {
|
||||
errors = [];
|
||||
checkLocalFiles = [];
|
||||
idCache = new Map();
|
||||
let numberLinks = 0;
|
||||
for (const htmlFilePath of files) {
|
||||
const { links } = await extractReferences(htmlFilePath);
|
||||
numberLinks += links.length;
|
||||
await resolveLinks(links, { htmlFilePath, rootDir });
|
||||
}
|
||||
await validateLocalFiles(checkLocalFiles);
|
||||
|
||||
return errors;
|
||||
return { errors: errors, numberLinks: numberLinks };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,6 +284,6 @@ export async function validateFiles(files, rootDir) {
|
||||
export async function validateFolder(inRootDir) {
|
||||
const rootDir = path.resolve(inRootDir);
|
||||
const files = await listFiles('**/*.html', rootDir);
|
||||
const errors = await validateFiles(files, rootDir);
|
||||
const { errors } = await validateFiles(files, rootDir);
|
||||
return errors;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# @rocket/cli
|
||||
|
||||
## 0.4.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c92769a: Processing links and asset urls to generate the final html output is now utf8 safe
|
||||
- 562e91f: Make sure logos do not have "<?xml" in their code
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 0eb507d: Adds the capability to configure the svg template for the social media images.
|
||||
- 0eb507d: Adds `setupEleventyComputedConfig` option to enable configuration of 11ty's `eleventyComputed`. The plugins-manager system is used for it.
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a498a5d: Make sure links to `*index.md` files are not treated as folder index files like `index.md`
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const { setComputedConfig, getComputedConfig } = require('./src/public/computedConfig.cjs');
|
||||
const rocketEleventyComputed = require('./src/public/rocketEleventyComputed.cjs');
|
||||
const { createPageSocialImage } = require('./src/public/createPageSocialImage.cjs');
|
||||
const { generateEleventyComputed } = require('./src/public/generateEleventyComputed.cjs');
|
||||
const { createSocialImage } = require('./src/public/createSocialImage.cjs');
|
||||
|
||||
module.exports = {
|
||||
setComputedConfig,
|
||||
getComputedConfig,
|
||||
rocketEleventyComputed,
|
||||
createPageSocialImage,
|
||||
generateEleventyComputed,
|
||||
createSocialImage,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rocket/cli",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.1",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@@ -62,11 +62,12 @@
|
||||
"@web/dev-server": "^0.1.4",
|
||||
"@web/dev-server-rollup": "^0.3.2",
|
||||
"@web/rollup-plugin-copy": "^0.2.0",
|
||||
"check-html-links": "^0.1.0",
|
||||
"check-html-links": "^0.1.1",
|
||||
"command-line-args": "^5.1.1",
|
||||
"command-line-usage": "^6.1.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
"plugins-manager": "^0.2.0"
|
||||
"plugins-manager": "^0.2.0",
|
||||
"utf8": "^3.0.0"
|
||||
},
|
||||
"types": "dist-types/index.d.ts"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { rocketEleventyComputed } = require('@rocket/cli');
|
||||
const { generateEleventyComputed } = require('@rocket/cli');
|
||||
|
||||
module.exports = {
|
||||
...rocketEleventyComputed,
|
||||
...generateEleventyComputed(),
|
||||
};
|
||||
|
||||
@@ -199,7 +199,7 @@ export class RocketCli {
|
||||
setComputedConfig({});
|
||||
if (this.eleventy) {
|
||||
this.eleventy.finish();
|
||||
// this.eleventy.stopWatch();
|
||||
// await this.eleventy.stopWatch();
|
||||
}
|
||||
this.stop();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const utf8 = require('utf8');
|
||||
const { SaxEventType, SAXParser } = require('sax-wasm');
|
||||
|
||||
const saxPath = require.resolve('sax-wasm/lib/sax-wasm.wasm');
|
||||
@@ -39,22 +40,25 @@ const templateEndings = [
|
||||
'.pug',
|
||||
];
|
||||
|
||||
function endsWithAny(string, suffixes) {
|
||||
for (let suffix of suffixes) {
|
||||
if (string.endsWith(suffix)) {
|
||||
function isTemplateFile(href) {
|
||||
for (const templateEnding of templateEndings) {
|
||||
if (href.endsWith(templateEnding)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isTemplateFile(href) {
|
||||
return endsWithAny(href, templateEndings);
|
||||
}
|
||||
|
||||
function isIndexTemplateFile(href) {
|
||||
const hrefParsed = path.parse(href);
|
||||
const indexTemplateEndings = templateEndings.map(ending => `index${ending}`);
|
||||
return endsWithAny(href, indexTemplateEndings);
|
||||
|
||||
for (const indexTemplateEnding of indexTemplateEndings) {
|
||||
if (hrefParsed.base === indexTemplateEnding) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,12 +95,46 @@ function extractReferences(html, inputPath) {
|
||||
return { hrefs, assets };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} inValue
|
||||
*/
|
||||
function getValueAndAnchor(inValue) {
|
||||
let value = inValue.replace(/&#/g, '--__check-html-links__--');
|
||||
let anchor = '';
|
||||
let suffix = '';
|
||||
|
||||
if (value.includes('#')) {
|
||||
[value, anchor] = value.split('#');
|
||||
suffix = `#${anchor}`;
|
||||
}
|
||||
if (value.includes('?')) {
|
||||
value = value.split('?')[0];
|
||||
}
|
||||
if (anchor.includes(':~:')) {
|
||||
anchor = anchor.split(':~:')[0];
|
||||
}
|
||||
if (value.includes(':~:')) {
|
||||
value = value.split(':~:')[0];
|
||||
}
|
||||
|
||||
value = value.replace(/--__check-html-links__--/g, '&#');
|
||||
anchor = anchor.replace(/--__check-html-links__--/g, '&#');
|
||||
suffix = suffix.replace(/--__check-html-links__--/g, '&#');
|
||||
value = value.trim();
|
||||
anchor = anchor.trim();
|
||||
|
||||
return {
|
||||
value,
|
||||
anchor,
|
||||
suffix,
|
||||
};
|
||||
}
|
||||
|
||||
function calculateNewHrefs(hrefs, inputPath) {
|
||||
const newHrefs = [];
|
||||
for (const hrefObj of hrefs) {
|
||||
const newHrefObj = { ...hrefObj };
|
||||
const [href, anchor] = newHrefObj.value.split('#');
|
||||
const suffix = anchor ? `#${anchor}` : '';
|
||||
const { value: href, suffix } = getValueAndAnchor(hrefObj.value);
|
||||
|
||||
if (isRelativeLink(href) && isTemplateFile(href)) {
|
||||
const hrefParsed = path.parse(href);
|
||||
@@ -167,15 +205,15 @@ function applyChanges(_changes, _content) {
|
||||
return content.replace(/XXXRocketProcessLocalReferencesXXX/g, '\n');
|
||||
}
|
||||
|
||||
async function processLocalReferences(content) {
|
||||
async function processLocalReferences(_content) {
|
||||
const content = utf8.encode(_content);
|
||||
const inputPath = this.inputPath;
|
||||
const { hrefs, assets } = extractReferences(content, inputPath);
|
||||
const newHrefs = calculateNewHrefs(hrefs, inputPath);
|
||||
const newAssets = calculateNewAssets(assets, inputPath);
|
||||
|
||||
const newContent = applyChanges([...newHrefs, ...newAssets], content);
|
||||
|
||||
return newContent;
|
||||
return utf8.decode(newContent);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -31,6 +31,7 @@ export async function normalizeConfig(inConfig) {
|
||||
setupDevPlugins: [],
|
||||
setupBuildPlugins: [],
|
||||
setupEleventyPlugins: [],
|
||||
setupEleventyComputedConfig: [],
|
||||
setupCliPlugins: [],
|
||||
eleventy: () => {},
|
||||
command: 'help',
|
||||
@@ -106,6 +107,12 @@ export async function normalizeConfig(inConfig) {
|
||||
...preset.setupEleventyPlugins,
|
||||
];
|
||||
}
|
||||
if (preset.setupEleventyComputedConfig) {
|
||||
config.setupEleventyComputedConfig = [
|
||||
...config.setupEleventyComputedConfig,
|
||||
...preset.setupEleventyComputedConfig,
|
||||
];
|
||||
}
|
||||
if (preset.setupCliPlugins) {
|
||||
config.setupCliPlugins = [...config.setupCliPlugins, ...preset.setupCliPlugins];
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const Image = require('@11ty/eleventy-img');
|
||||
const { getComputedConfig } = require('./computedConfig.cjs');
|
||||
|
||||
async function createPageSocialImage({ title = '', subTitle = '', subTitle2 = '', footer = '' }) {
|
||||
const rocketConfig = getComputedConfig();
|
||||
const outputDir = path.join(rocketConfig.outputDevDir, '_merged_assets', '11ty-img');
|
||||
|
||||
const logoPath = path.join(rocketConfig._inputDirCwdRelative, '_merged_assets', 'logo.svg');
|
||||
|
||||
const logoBuffer = await fs.promises.readFile(logoPath);
|
||||
const logo = logoBuffer.toString();
|
||||
|
||||
let svgStr = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#4a4a4a" font-family="sans-serif" font-size="80" style="background-color:#fff" viewBox="0 0 1200 630">
|
||||
<defs></defs>
|
||||
<rect width="100%" height="100%" fill="#fff" />
|
||||
<circle cx="1000" cy="230" r="530" fill="#ebebeb"></circle>
|
||||
`;
|
||||
|
||||
if (logo) {
|
||||
svgStr += `<g transform="matrix(0.7, 0, 0, 0.7, 500, 100)">${logo}</g>`;
|
||||
}
|
||||
|
||||
if (title) {
|
||||
svgStr += `
|
||||
<text x="70" y="200" font-family="'Bitstream Vera Sans','Helvetica',sans-serif" font-weight="700">
|
||||
${title}
|
||||
</text>
|
||||
`;
|
||||
}
|
||||
|
||||
if (subTitle) {
|
||||
svgStr += `
|
||||
<text x="70" y="320" font-family="'Bitstream Vera Sans','Helvetica',sans-serif" font-weight="700" font-size="60">
|
||||
${subTitle}
|
||||
</text>
|
||||
`;
|
||||
}
|
||||
|
||||
if (subTitle2) {
|
||||
svgStr += `
|
||||
<text x="70" y="420" font-family="'Bitstream Vera Sans','Helvetica',sans-serif" font-weight="700" font-size="60">
|
||||
${subTitle2}
|
||||
</text>
|
||||
`;
|
||||
}
|
||||
|
||||
if (footer) {
|
||||
svgStr += `
|
||||
<text x="70" y="560" fill="gray" font-size="40">
|
||||
${footer}
|
||||
</text>
|
||||
`;
|
||||
}
|
||||
|
||||
svgStr += '</svg>';
|
||||
|
||||
let stats = await Image(Buffer.from(svgStr), {
|
||||
widths: [1200], // Facebook Opengraph image is 1200 x 630
|
||||
formats: ['png'],
|
||||
outputDir,
|
||||
urlPath: '/_merged_assets/11ty-img/',
|
||||
sourceUrl: `${title}${subTitle}${footer}${logo}`, // This is only used to generate the output filename hash
|
||||
});
|
||||
|
||||
return stats['png'][0].url;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createPageSocialImage,
|
||||
};
|
||||
45
packages/cli/src/public/createSocialImage.cjs
Normal file
45
packages/cli/src/public/createSocialImage.cjs
Normal file
@@ -0,0 +1,45 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const Image = require('@11ty/eleventy-img');
|
||||
const { getComputedConfig } = require('./computedConfig.cjs');
|
||||
const { createSocialImageSvg: defaultcreateSocialImageSvg } = require('./createSocialImageSvg.cjs');
|
||||
|
||||
async function createSocialImage(args) {
|
||||
const {
|
||||
title = '',
|
||||
subTitle = '',
|
||||
footer = '',
|
||||
createSocialImageSvg = defaultcreateSocialImageSvg,
|
||||
} = args;
|
||||
const cleanedUpArgs = { ...args };
|
||||
delete cleanedUpArgs.createSocialImageSvg;
|
||||
|
||||
const rocketConfig = getComputedConfig();
|
||||
const outputDir = path.join(rocketConfig.outputDevDir, '_merged_assets', '11ty-img');
|
||||
|
||||
const logoPath = path.join(rocketConfig._inputDirCwdRelative, '_merged_assets', 'logo.svg');
|
||||
|
||||
const logoBuffer = await fs.promises.readFile(logoPath);
|
||||
const logo = logoBuffer.toString();
|
||||
|
||||
if (logo.includes('<?xml')) {
|
||||
throw new Error('You should not have an "<?xml" tag in your logo.svg');
|
||||
}
|
||||
|
||||
const svgStr = await createSocialImageSvg({ logo, ...args });
|
||||
|
||||
// TODO: cache images for 24h and not only for the given run (using @11ty/eleventy-cache-assets)
|
||||
let stats = await Image(Buffer.from(svgStr), {
|
||||
widths: [1200], // Facebook Opengraph image is 1200 x 630
|
||||
formats: ['png'],
|
||||
outputDir,
|
||||
urlPath: '/_merged_assets/11ty-img/',
|
||||
sourceUrl: `${title}${subTitle}${footer}${logo}`, // This is only used to generate the output filename hash
|
||||
});
|
||||
|
||||
return stats['png'][0].url;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createSocialImage,
|
||||
};
|
||||
33
packages/cli/src/public/createSocialImageSvg.cjs
Normal file
33
packages/cli/src/public/createSocialImageSvg.cjs
Normal file
@@ -0,0 +1,33 @@
|
||||
async function createSocialImageSvg({
|
||||
title = '',
|
||||
subTitle = '',
|
||||
subTitle2 = '',
|
||||
footer = '',
|
||||
logo = '',
|
||||
}) {
|
||||
let svgStr = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630">
|
||||
<defs></defs>
|
||||
<rect width="100%" height="100%" fill="#fff" />
|
||||
<circle cx="1000" cy="230" r="530" fill="#ebebeb"></circle>
|
||||
<g transform="matrix(0.6, 0, 0, 0.6, 580, 100)">${logo}</g>
|
||||
<text x="70" y="200" font-family="'Bitstream Vera Sans','Helvetica',sans-serif" font-weight="700" font-size="80">
|
||||
${title}
|
||||
</text>
|
||||
<text x="70" y="320" font-family="'Bitstream Vera Sans','Helvetica',sans-serif" font-weight="700" font-size="60">
|
||||
${subTitle}
|
||||
</text>
|
||||
<text x="70" y="420" font-family="'Bitstream Vera Sans','Helvetica',sans-serif" font-weight="700" font-size="60">
|
||||
${subTitle2}
|
||||
</text>
|
||||
<text x="70" y="560" fill="gray" font-size="40">
|
||||
${footer}
|
||||
</text>
|
||||
</svg>
|
||||
`;
|
||||
return svgStr;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createSocialImageSvg,
|
||||
};
|
||||
116
packages/cli/src/public/generateEleventyComputed.cjs
Normal file
116
packages/cli/src/public/generateEleventyComputed.cjs
Normal file
@@ -0,0 +1,116 @@
|
||||
const fs = require('fs');
|
||||
const { processContentWithTitle } = require('@rocket/core/title');
|
||||
const { createSocialImage: defaultcreateSocialImage } = require('./createSocialImage.cjs');
|
||||
const { getComputedConfig } = require('./computedConfig.cjs');
|
||||
const { executeSetupFunctions } = require('plugins-manager');
|
||||
|
||||
function titleMetaPlugin() {
|
||||
return async data => {
|
||||
if (data.titleMeta) {
|
||||
return data.titleMeta;
|
||||
}
|
||||
let text = await fs.promises.readFile(data.page.inputPath);
|
||||
text = text.toString();
|
||||
const titleMetaFromContent = processContentWithTitle(text, 'md');
|
||||
if (titleMetaFromContent) {
|
||||
return titleMetaFromContent;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
function titlePlugin() {
|
||||
return async data => {
|
||||
if (data.title) {
|
||||
return data.title;
|
||||
}
|
||||
return data.titleMeta?.title;
|
||||
};
|
||||
}
|
||||
|
||||
function eleventyNavigationPlugin() {
|
||||
return async data => {
|
||||
if (data.eleventyNavigation) {
|
||||
return data.eleventyNavigation;
|
||||
}
|
||||
return data.titleMeta?.eleventyNavigation;
|
||||
};
|
||||
}
|
||||
|
||||
function sectionPlugin() {
|
||||
return async data => {
|
||||
if (data.section) {
|
||||
return data.section;
|
||||
}
|
||||
|
||||
if (data.page.filePathStem) {
|
||||
// filePathStem: '/sub/subsub/index'
|
||||
// filePathStem: '/index',
|
||||
const parts = data.page.filePathStem.split('/');
|
||||
if (parts.length > 2) {
|
||||
return parts[1];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function socialMediaImagePlugin(args = {}) {
|
||||
const { createSocialImage = defaultcreateSocialImage } = args;
|
||||
|
||||
const cleanedUpArgs = { ...args };
|
||||
delete cleanedUpArgs.createSocialImage;
|
||||
|
||||
return async data => {
|
||||
if (data.socialMediaImage) {
|
||||
return data.socialMediaImage;
|
||||
}
|
||||
if (!data.title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const title = data.titleMeta.parts ? data.titleMeta.parts[0] : '';
|
||||
const subTitle =
|
||||
data.titleMeta.parts && data.titleMeta.parts[1] ? `in ${data.titleMeta.parts[1]}` : '';
|
||||
const section = data.section ? ' ' + data.section[0].toUpperCase() + data.section.slice(1) : '';
|
||||
const footer = `${data.site.name}${section}`;
|
||||
|
||||
const imgUrl = await createSocialImage({
|
||||
title,
|
||||
subTitle,
|
||||
footer,
|
||||
section,
|
||||
...cleanedUpArgs,
|
||||
});
|
||||
return imgUrl;
|
||||
};
|
||||
}
|
||||
|
||||
function generateEleventyComputed() {
|
||||
const rocketConfig = getComputedConfig();
|
||||
|
||||
let metaPlugins = [
|
||||
{ name: 'titleMeta', plugin: titleMetaPlugin },
|
||||
{ name: 'title', plugin: titlePlugin },
|
||||
{ name: 'eleventyNavigation', plugin: eleventyNavigationPlugin },
|
||||
{ name: 'section', plugin: sectionPlugin },
|
||||
{ name: 'socialMediaImage', plugin: socialMediaImagePlugin },
|
||||
];
|
||||
|
||||
const finalMetaPlugins = executeSetupFunctions(
|
||||
rocketConfig.setupEleventyComputedConfig,
|
||||
metaPlugins,
|
||||
);
|
||||
|
||||
const eleventyComputed = {};
|
||||
for (const pluginObj of finalMetaPlugins) {
|
||||
if (pluginObj.options) {
|
||||
eleventyComputed[pluginObj.name] = pluginObj.plugin(pluginObj.options);
|
||||
} else {
|
||||
eleventyComputed[pluginObj.name] = pluginObj.plugin();
|
||||
}
|
||||
}
|
||||
|
||||
return eleventyComputed;
|
||||
}
|
||||
|
||||
module.exports = { generateEleventyComputed };
|
||||
@@ -1,63 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const { processContentWithTitle } = require('@rocket/core/title');
|
||||
const { createPageSocialImage } = require('./createPageSocialImage.cjs');
|
||||
|
||||
module.exports = {
|
||||
titleMeta: async data => {
|
||||
if (data.titleMeta) {
|
||||
return data.titleMeta;
|
||||
}
|
||||
let text = await fs.promises.readFile(data.page.inputPath);
|
||||
text = text.toString();
|
||||
const titleMetaFromContent = processContentWithTitle(text, 'md');
|
||||
if (titleMetaFromContent) {
|
||||
return titleMetaFromContent;
|
||||
}
|
||||
return {};
|
||||
},
|
||||
title: async data => {
|
||||
if (data.title) {
|
||||
return data.title;
|
||||
}
|
||||
return data.titleMeta?.title;
|
||||
},
|
||||
eleventyNavigation: async data => {
|
||||
if (data.eleventyNavigation) {
|
||||
return data.eleventyNavigation;
|
||||
}
|
||||
return data.titleMeta?.eleventyNavigation;
|
||||
},
|
||||
section: async data => {
|
||||
if (data.section) {
|
||||
return data.section;
|
||||
}
|
||||
|
||||
if (data.page.filePathStem) {
|
||||
// filePathStem: '/sub/subsub/index'
|
||||
// filePathStem: '/index',
|
||||
const parts = data.page.filePathStem.split('/');
|
||||
if (parts.length > 2) {
|
||||
return parts[1];
|
||||
}
|
||||
}
|
||||
},
|
||||
socialMediaImage: async data => {
|
||||
if (data.socialMediaImage) {
|
||||
return data.socialMediaImage;
|
||||
}
|
||||
if (!data.title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const section = data.section ? ' ' + data.section[0].toUpperCase() + data.section.slice(1) : '';
|
||||
const footer = `${data.site.name}${section}`;
|
||||
|
||||
const imgUrl = await createPageSocialImage({
|
||||
title: data.titleMeta.parts ? data.titleMeta.parts[0] : '',
|
||||
subTitle:
|
||||
data.titleMeta.parts && data.titleMeta.parts[1] ? `in ${data.titleMeta.parts[1]}` : '',
|
||||
footer,
|
||||
});
|
||||
return imgUrl;
|
||||
},
|
||||
};
|
||||
@@ -8,6 +8,7 @@ const rocketCollections = require('../eleventy-plugins/rocketCollections.cjs');
|
||||
|
||||
module.exports = function (eleventyConfig) {
|
||||
const config = getComputedConfig();
|
||||
|
||||
const { pathPrefix, _inputDirCwdRelative, outputDevDir } = config;
|
||||
|
||||
let metaPlugins = [
|
||||
@@ -46,12 +47,16 @@ module.exports = function (eleventyConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const pluginObj of metaPlugins) {
|
||||
if (pluginObj.options) {
|
||||
eleventyConfig.addPlugin(pluginObj.plugin, pluginObj.options);
|
||||
} else {
|
||||
eleventyConfig.addPlugin(pluginObj.plugin);
|
||||
try {
|
||||
for (const pluginObj of metaPlugins) {
|
||||
if (pluginObj.options) {
|
||||
eleventyConfig.addPlugin(pluginObj.plugin, pluginObj.options);
|
||||
} else {
|
||||
eleventyConfig.addPlugin(pluginObj.plugin);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('An eleventy plugin had an error', err);
|
||||
}
|
||||
|
||||
if (config.eleventy) {
|
||||
|
||||
182
packages/cli/test-node/RocketCli.computedConfig.test.js
Normal file
182
packages/cli/test-node/RocketCli.computedConfig.test.js
Normal file
@@ -0,0 +1,182 @@
|
||||
import chai from 'chai';
|
||||
import chalk from 'chalk';
|
||||
import { executeStart, readOutput, readStartOutput } from './helpers.js';
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
describe('RocketCli computedConfig', () => {
|
||||
let cli;
|
||||
|
||||
before(() => {
|
||||
// ignore colors in tests as most CIs won't support it
|
||||
chalk.level = 0;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (cli?.cleanup) {
|
||||
await cli.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it('will extract a title from markdown and set first folder as section', async () => {
|
||||
cli = await executeStart('computed-config-fixtures/headlines/rocket.config.js');
|
||||
|
||||
const indexHtml = await readOutput(cli, 'index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const [indexTitle, indexSection] = indexHtml.split('\n');
|
||||
expect(indexTitle).to.equal('Root');
|
||||
expect(indexSection).to.be.undefined;
|
||||
|
||||
const subHtml = await readOutput(cli, 'sub/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const [subTitle, subSection] = subHtml.split('\n');
|
||||
expect(subTitle).to.equal('Root: Sub');
|
||||
expect(subSection).to.equal('sub');
|
||||
|
||||
const subSubHtml = await readOutput(cli, 'sub/subsub/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const [subSubTitle, subSubSection] = subSubHtml.split('\n');
|
||||
expect(subSubTitle).to.equal('Sub: SubSub');
|
||||
expect(subSubSection).to.equal('sub');
|
||||
|
||||
const sub2Html = await readOutput(cli, 'sub2/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const [sub2Title, sub2Section] = sub2Html.split('\n');
|
||||
expect(sub2Title).to.equal('Root: Sub2');
|
||||
expect(sub2Section).to.equal('sub2');
|
||||
|
||||
const withDataHtml = await readOutput(cli, 'with-data/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const [withDataTitle, withDataSection] = withDataHtml.split('\n');
|
||||
expect(withDataTitle).to.equal('Set via data');
|
||||
expect(withDataSection).be.undefined;
|
||||
});
|
||||
|
||||
it('will create a social media image for every page', async () => {
|
||||
cli = await executeStart('computed-config-fixtures/social-images/rocket.config.js');
|
||||
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
expect(indexHtml).to.equal('/_merged_assets/11ty-img/c4c29ec7-1200.png');
|
||||
|
||||
const guidesHtml = await readStartOutput(cli, 'guides/index.html');
|
||||
expect(guidesHtml).to.equal('/_merged_assets/11ty-img/5e6f6f8c-1200.png');
|
||||
|
||||
const gettingStartedHtml = await readStartOutput(
|
||||
cli,
|
||||
'guides/first-pages/getting-started/index.html',
|
||||
);
|
||||
expect(gettingStartedHtml).to.equal('/_merged_assets/11ty-img/d989ab1a-1200.png');
|
||||
});
|
||||
|
||||
it('can override the svg function globally to adjust all social media image', async () => {
|
||||
cli = await executeStart('computed-config-fixtures/social-images-override/rocket.config.js');
|
||||
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
expect(indexHtml).to.equal('/_merged_assets/11ty-img/d76265ed-1200.png');
|
||||
|
||||
const guidesHtml = await readStartOutput(cli, 'guides/index.html');
|
||||
expect(guidesHtml).to.equal('/_merged_assets/11ty-img/d76265ed-1200.png');
|
||||
|
||||
const gettingStartedHtml = await readStartOutput(
|
||||
cli,
|
||||
'guides/first-pages/getting-started/index.html',
|
||||
);
|
||||
expect(gettingStartedHtml).to.equal('/_merged_assets/11ty-img/d76265ed-1200.png');
|
||||
});
|
||||
|
||||
it('will add "../" for links and image urls only within named template files', async () => {
|
||||
cli = await executeStart('computed-config-fixtures/image-link/rocket.config.js');
|
||||
|
||||
const namedMdContent = [
|
||||
'<p><a href="../">Root</a>',
|
||||
'<a href="../one-level/raw/">Raw</a>',
|
||||
'<img src="../images/my-img.svg" alt="my-img">',
|
||||
'<img src="/images/my-img.svg" alt="absolute-img"></p>',
|
||||
];
|
||||
|
||||
const namedHtmlContent = [
|
||||
'<div id="with-anchor">',
|
||||
' <a href="../">Root</a>',
|
||||
' <a href="../one-level/raw/">Raw</a>',
|
||||
' <img src="../images/my-img.svg" alt="my-img">',
|
||||
' <img src="/images/my-img.svg" alt="absolute-img">',
|
||||
' <picture>',
|
||||
' <source media="(min-width:465px)" srcset="../images/picture-min-465.jpg">',
|
||||
' <img src="../images/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
||||
' </picture>',
|
||||
'</div>',
|
||||
];
|
||||
|
||||
const templateHtml = await readStartOutput(cli, 'template/index.html');
|
||||
expect(templateHtml, 'template/index.html does not match').to.equal(
|
||||
namedHtmlContent.join('\n'),
|
||||
);
|
||||
|
||||
const guidesHtml = await readStartOutput(cli, 'guides/index.html');
|
||||
expect(guidesHtml, 'guides/index.html does not match').to.equal(
|
||||
[...namedMdContent, ...namedHtmlContent].join('\n'),
|
||||
);
|
||||
|
||||
const noAdjustHtml = await readStartOutput(cli, 'no-adjust/index.html');
|
||||
expect(noAdjustHtml, 'no-adjust/index.html does not match').to.equal(
|
||||
'<p>Nothing to adjust in here</p>',
|
||||
);
|
||||
|
||||
const rawHtml = await readStartOutput(cli, 'one-level/raw/index.html');
|
||||
expect(rawHtml, 'raw/index.html does not match').to.equal(
|
||||
[
|
||||
'<div>',
|
||||
' <a href="../../">Root</a>',
|
||||
' <a href="../../guides/#with-anchor">Guides</a>',
|
||||
' <img src="../../images/my-img.svg" alt="my-img">',
|
||||
' <img src="/images/my-img.svg" alt="absolute-img">',
|
||||
' <picture>',
|
||||
' <source media="(min-width:465px)" srcset="/images/picture-min-465.jpg">',
|
||||
' <img src="../../images/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
||||
' </picture>',
|
||||
'</div>',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
// for index files no '../' will be added
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
expect(indexHtml, 'index.html does not match').to.equal(
|
||||
[
|
||||
'<p><a href="./">Root</a>',
|
||||
'<a href="guides/#with-anchor">Guides</a>',
|
||||
'<a href="./one-level/raw/">Raw</a>',
|
||||
'<a href="template/">Template</a>',
|
||||
'<a href="./rules/tabindex/">EndingIndex</a>',
|
||||
'<img src="./images/my-img.svg" alt="my-img">',
|
||||
'<img src="/images/my-img.svg" alt="absolute-img"></p>',
|
||||
'<div>',
|
||||
' <a href="./">Root</a>',
|
||||
' 👇<a href="guides/#with-anchor">Guides</a>',
|
||||
' 👉 <a href="./one-level/raw/">Raw</a>',
|
||||
' <a href="template/">Template</a>',
|
||||
' <a href="./rules/tabindex/">EndingIndex</a>',
|
||||
' <img src="./images/my-img.svg" alt="my-img">',
|
||||
' <img src="/images/my-img.svg" alt="absolute-img">',
|
||||
' <picture>',
|
||||
' <source media="(min-width:465px)" srcset="./images/picture-min-465.jpg">',
|
||||
' <img src="./images/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
||||
' </picture>',
|
||||
'</div>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('can be configured via setupEleventyComputedConfig', async () => {
|
||||
cli = await executeStart('computed-config-fixtures/setup/addPlugin.rocket.config.js');
|
||||
|
||||
const indexHtml = await readOutput(cli, 'index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
expect(indexHtml).to.equal('test-value');
|
||||
});
|
||||
});
|
||||
@@ -61,11 +61,6 @@ describe('RocketCli e2e', () => {
|
||||
return text;
|
||||
}
|
||||
|
||||
function readStartOutput(fileName, options = {}) {
|
||||
options.type = 'start';
|
||||
return readOutput(fileName, options);
|
||||
}
|
||||
|
||||
async function execute() {
|
||||
await cli.setup();
|
||||
cli.config.outputDevDir = path.join(__dirname, 'e2e-fixtures', '__output-dev');
|
||||
@@ -76,17 +71,6 @@ describe('RocketCli e2e', () => {
|
||||
await cli.run();
|
||||
}
|
||||
|
||||
async function executeStart(pathToConfig) {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'start',
|
||||
'--config-file',
|
||||
path.join(__dirname, pathToConfig.split('/').join(path.sep)),
|
||||
],
|
||||
});
|
||||
await execute();
|
||||
}
|
||||
|
||||
async function executeLint(pathToConfig) {
|
||||
cli = new RocketCli({
|
||||
argv: ['lint', '--config-file', path.join(__dirname, pathToConfig.split('/').join(path.sep))],
|
||||
@@ -286,153 +270,6 @@ describe('RocketCli e2e', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('will extract a title from markdown and set first folder as section', async () => {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'start',
|
||||
'--config-file',
|
||||
path.join(__dirname, 'e2e-fixtures', 'headlines', 'rocket.config.js'),
|
||||
],
|
||||
});
|
||||
await execute();
|
||||
|
||||
const indexHtml = await readOutput('index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const [indexTitle, indexSection] = indexHtml.split('\n');
|
||||
expect(indexTitle).to.equal('Root');
|
||||
expect(indexSection).to.be.undefined;
|
||||
|
||||
const subHtml = await readOutput('sub/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const [subTitle, subSection] = subHtml.split('\n');
|
||||
expect(subTitle).to.equal('Root: Sub');
|
||||
expect(subSection).to.equal('sub');
|
||||
|
||||
const subSubHtml = await readOutput('sub/subsub/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const [subSubTitle, subSubSection] = subSubHtml.split('\n');
|
||||
expect(subSubTitle).to.equal('Sub: SubSub');
|
||||
expect(subSubSection).to.equal('sub');
|
||||
|
||||
const sub2Html = await readOutput('sub2/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const [sub2Title, sub2Section] = sub2Html.split('\n');
|
||||
expect(sub2Title).to.equal('Root: Sub2');
|
||||
expect(sub2Section).to.equal('sub2');
|
||||
|
||||
const withDataHtml = await readOutput('with-data/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
const [withDataTitle, withDataSection] = withDataHtml.split('\n');
|
||||
expect(withDataTitle).to.equal('Set via data');
|
||||
expect(withDataSection).be.undefined;
|
||||
});
|
||||
|
||||
it('will create a social media image for every page', async () => {
|
||||
cli = new RocketCli({
|
||||
argv: [
|
||||
'start',
|
||||
'--config-file',
|
||||
path.join(__dirname, 'e2e-fixtures', 'social-images', 'rocket.config.js'),
|
||||
],
|
||||
});
|
||||
await execute();
|
||||
|
||||
const indexHtml = await readOutput('index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
expect(indexHtml).to.equal('/_merged_assets/11ty-img/c0a892f2-1200.png');
|
||||
|
||||
const guidesHtml = await readOutput('guides/first-pages/getting-started/index.html', {
|
||||
type: 'start',
|
||||
});
|
||||
expect(guidesHtml).to.equal('/_merged_assets/11ty-img/58b7e437-1200.png');
|
||||
});
|
||||
|
||||
it.only('will add "../" for links and image urls only within named template files', async () => {
|
||||
await executeStart('e2e-fixtures/image-link/rocket.config.js');
|
||||
|
||||
const namedMdContent = [
|
||||
'<p><a href="../">Root</a>',
|
||||
'<a href="../one-level/raw/">Raw</a>',
|
||||
'<img src="../images/my-img.svg" alt="my-img">',
|
||||
'<img src="/images/my-img.svg" alt="absolute-img"></p>',
|
||||
];
|
||||
|
||||
const namedHtmlContent = [
|
||||
'<div id="with-anchor">',
|
||||
' <a href="../">Root</a>',
|
||||
' <a href="../one-level/raw/">Raw</a>',
|
||||
' <img src="../images/my-img.svg" alt="my-img">',
|
||||
' <img src="/images/my-img.svg" alt="absolute-img">',
|
||||
' <picture>',
|
||||
' <source media="(min-width:465px)" srcset="../images/picture-min-465.jpg">',
|
||||
' <img src="../images/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
||||
' </picture>',
|
||||
'</div>',
|
||||
];
|
||||
|
||||
const templateHtml = await readStartOutput('template/index.html');
|
||||
expect(templateHtml, 'template/index.html does not match').to.equal(
|
||||
namedHtmlContent.join('\n'),
|
||||
);
|
||||
|
||||
const guidesHtml = await readStartOutput('guides/index.html');
|
||||
expect(guidesHtml, 'guides/index.html does not match').to.equal(
|
||||
[...namedMdContent, ...namedHtmlContent].join('\n'),
|
||||
);
|
||||
|
||||
const noAdjustHtml = await readStartOutput('no-adjust/index.html');
|
||||
expect(noAdjustHtml, 'no-adjust/index.html does not match').to.equal(
|
||||
'<p>Nothing to adjust in here</p>',
|
||||
);
|
||||
|
||||
const rawHtml = await readStartOutput('one-level/raw/index.html');
|
||||
expect(rawHtml, 'raw/index.html does not match').to.equal(
|
||||
[
|
||||
'<div>',
|
||||
' <a href="../../">Root</a>',
|
||||
' <a href="../../guides/#with-anchor">Guides</a>',
|
||||
' <img src="../../images/my-img.svg" alt="my-img">',
|
||||
' <img src="/images/my-img.svg" alt="absolute-img">',
|
||||
' <picture>',
|
||||
' <source media="(min-width:465px)" srcset="/images/picture-min-465.jpg">',
|
||||
' <img src="../../images/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
||||
' </picture>',
|
||||
'</div>',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
// for index files no '../' will be added
|
||||
const indexHtml = await readStartOutput('index.html');
|
||||
expect(indexHtml, 'index.html does not match').to.equal(
|
||||
[
|
||||
'<p><a href="./">Root</a>',
|
||||
'<a href="guides/#with-anchor">Guides</a>',
|
||||
'<a href="./one-level/raw/">Raw</a>',
|
||||
'<a href="template/">Template</a>',
|
||||
'<img src="./images/my-img.svg" alt="my-img">',
|
||||
'<img src="/images/my-img.svg" alt="absolute-img"></p>',
|
||||
'<div>',
|
||||
' <a href="./">Root</a>',
|
||||
' <a href="guides/#with-anchor">Guides</a>',
|
||||
' <a href="./one-level/raw/">Raw</a>',
|
||||
' <a href="template/">Template</a>',
|
||||
' <img src="./images/my-img.svg" alt="my-img">',
|
||||
' <img src="/images/my-img.svg" alt="absolute-img">',
|
||||
' <picture>',
|
||||
' <source media="(min-width:465px)" srcset="./images/picture-min-465.jpg">',
|
||||
' <img src="./images/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
||||
' </picture>',
|
||||
'</div>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('smoke test for link checking', async () => {
|
||||
await expectThrowsAsync(() => executeLint('e2e-fixtures/lint-links/rocket.config.js'), {
|
||||
errorMatch: /Found 1 missing reference targets/,
|
||||
|
||||
|
Before Width: | Height: | Size: 118 B After Width: | Height: | Size: 118 B |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
@@ -2,14 +2,16 @@
|
||||
[Guides](./guides.md#with-anchor)
|
||||
[Raw](./one-level/raw.html)
|
||||
[Template](./template.njk)
|
||||
[EndingIndex](./rules/tabindex.md)
|
||||

|
||||

|
||||
|
||||
<div>
|
||||
<a href="./">Root</a>
|
||||
<a href="./guides.md#with-anchor">Guides</a>
|
||||
<a href="./one-level/raw.html">Raw</a>
|
||||
👇<a href="./guides.md#with-anchor">Guides</a>
|
||||
👉 <a href="./one-level/raw.html">Raw</a>
|
||||
<a href="./template.njk">Template</a>
|
||||
<a href="./rules/tabindex.md">EndingIndex</a>
|
||||
<img src="./images/my-img.svg" alt="my-img">
|
||||
<img src="/images/my-img.svg" alt="absolute-img">
|
||||
<picture>
|
||||
@@ -0,0 +1,8 @@
|
||||
import { addPlugin } from 'plugins-manager';
|
||||
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {
|
||||
setupEleventyComputedConfig: [addPlugin({ name: 'test', plugin: () => 'test-value' })],
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1 @@
|
||||
{{ test }}
|
||||
@@ -0,0 +1 @@
|
||||
**/*.njk
|
||||
@@ -0,0 +1 @@
|
||||
# Guides
|
||||
@@ -0,0 +1,22 @@
|
||||
import { adjustPluginOptions } from 'plugins-manager';
|
||||
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {
|
||||
setupEleventyComputedConfig: [
|
||||
adjustPluginOptions('socialMediaImage', {
|
||||
createSocialImageSvg: async () => {
|
||||
return `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630">
|
||||
<defs></defs>
|
||||
<rect width="100%" height="100%" fill="#fff" />
|
||||
<text x="70" y="200" font-family="'Bitstream Vera Sans','Helvetica',sans-serif" font-weight="700" font-size="80">
|
||||
Hard Coded Title
|
||||
</text>
|
||||
</svg>
|
||||
`;
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1 @@
|
||||
**/*.njk
|
||||
@@ -0,0 +1 @@
|
||||
{{ socialMediaImage }}
|
||||
@@ -0,0 +1 @@
|
||||
# First Pages >> Getting Started
|
||||
@@ -0,0 +1,12 @@
|
||||
const { createSocialImage } = require('@rocket/cli');
|
||||
|
||||
module.exports = async function () {
|
||||
const socialMediaImage = await createSocialImage({
|
||||
title: 'Rocket Guides',
|
||||
subTitle: 'Lerning how to',
|
||||
subTitle2: 'build a rocket site.',
|
||||
});
|
||||
return {
|
||||
socialMediaImage,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
# Guides
|
||||
@@ -0,0 +1,43 @@
|
||||
const { createSocialImage } = require('@rocket/cli');
|
||||
|
||||
module.exports = async function () {
|
||||
const socialMediaImage = await createSocialImage({
|
||||
title: 'Rocket',
|
||||
subTitle: 'Static sites with a',
|
||||
subTitle2: 'sprinkle of JavaScript.',
|
||||
footer: 'A Modern Web Product',
|
||||
createSocialImageSvg: async ({
|
||||
title = '',
|
||||
subTitle = '',
|
||||
subTitle2 = '',
|
||||
footer = '',
|
||||
logo = '',
|
||||
}) => {
|
||||
let svgStr = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630">
|
||||
<defs></defs>
|
||||
<rect width="100%" height="100%" fill="#ddd" />
|
||||
<circle cx="1000" cy="230" r="530" fill="#efefef"></circle>
|
||||
<rect width="100%" height="100%" style="fill:none; stroke-width:20; stroke:red;" />
|
||||
<g transform="matrix(0.6, 0, 0, 0.6, 580, 100)">${logo}</g>
|
||||
<text x="70" y="200" font-family="'Bitstream Vera Sans','Helvetica',sans-serif" font-weight="700" font-size="80">
|
||||
${title}
|
||||
</text>
|
||||
<text x="70" y="320" font-family="'Bitstream Vera Sans','Helvetica',sans-serif" font-weight="700" font-size="60">
|
||||
${subTitle}
|
||||
</text>
|
||||
<text x="70" y="420" font-family="'Bitstream Vera Sans','Helvetica',sans-serif" font-weight="700" font-size="60">
|
||||
${subTitle2}
|
||||
</text>
|
||||
<text x="70" y="560" fill="gray" font-size="40">
|
||||
${footer}
|
||||
</text>
|
||||
</svg>
|
||||
`;
|
||||
return svgStr;
|
||||
},
|
||||
});
|
||||
return {
|
||||
socialMediaImage,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
# Rocket
|
||||
@@ -1,12 +0,0 @@
|
||||
const { createPageSocialImage } = require('@rocket/cli');
|
||||
|
||||
module.exports = async function () {
|
||||
const socialMediaImage = await createPageSocialImage({
|
||||
title: 'Rocket',
|
||||
subTitle: 'Static sites with',
|
||||
subTitle2: 'a sprinkle of JavaScript.',
|
||||
});
|
||||
return {
|
||||
socialMediaImage,
|
||||
};
|
||||
};
|
||||
91
packages/cli/test-node/helpers.js
Normal file
91
packages/cli/test-node/helpers.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import chai from 'chai';
|
||||
import { RocketCli } from '../src/RocketCli.js';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
/**
|
||||
* @param {function} method
|
||||
* @param {string} errorMessage
|
||||
*/
|
||||
export async function expectThrowsAsync(method, { errorMatch, errorMessage } = {}) {
|
||||
let error = null;
|
||||
try {
|
||||
await method();
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
expect(error).to.be.an('Error', 'No error was thrown');
|
||||
if (errorMatch) {
|
||||
expect(error.message).to.match(errorMatch);
|
||||
}
|
||||
if (errorMessage) {
|
||||
expect(error.message).to.equal(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
export async function readOutput(
|
||||
cli,
|
||||
fileName,
|
||||
{
|
||||
stripServiceWorker = false,
|
||||
stripToBody = false,
|
||||
stripStartEndWhitespace = true,
|
||||
type = 'build',
|
||||
} = {},
|
||||
) {
|
||||
const outputDir = type === 'build' ? cli.config.outputDir : cli.config.outputDevDir;
|
||||
let text = await fs.promises.readFile(path.join(outputDir, fileName));
|
||||
text = text.toString();
|
||||
if (stripToBody) {
|
||||
const bodyOpenTagEnd = text.indexOf('>', text.indexOf('<body') + 1) + 1;
|
||||
const bodyCloseTagStart = text.indexOf('</body>');
|
||||
text = text.substring(bodyOpenTagEnd, bodyCloseTagStart);
|
||||
}
|
||||
if (stripServiceWorker) {
|
||||
const scriptOpenTagEnd = text.indexOf('<script inject-service-worker');
|
||||
const scriptCloseTagStart = text.indexOf('</script>', scriptOpenTagEnd) + 9;
|
||||
text = text.substring(0, scriptOpenTagEnd) + text.substring(scriptCloseTagStart);
|
||||
}
|
||||
if (stripStartEndWhitespace) {
|
||||
text = text.trim();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
export async function readStartOutput(cli, fileName, options = {}) {
|
||||
options.type = 'start';
|
||||
return readOutput(cli, fileName, options);
|
||||
}
|
||||
|
||||
export async function execute(cli, configFileDir) {
|
||||
await cli.setup();
|
||||
cli.config.outputDevDir = path.join(configFileDir, '__output-dev');
|
||||
cli.config.devServer.open = false;
|
||||
cli.config.watch = false;
|
||||
cli.config.outputDir = path.join(configFileDir, '__output');
|
||||
await cli.run();
|
||||
return cli;
|
||||
}
|
||||
|
||||
export async function executeStart(pathToConfig) {
|
||||
const configFile = path.join(__dirname, pathToConfig.split('/').join(path.sep));
|
||||
const cli = new RocketCli({
|
||||
argv: ['start', '--config-file', configFile],
|
||||
});
|
||||
await execute(cli, path.dirname(configFile));
|
||||
return cli;
|
||||
}
|
||||
|
||||
export async function executeLint(pathToConfig) {
|
||||
const configFile = path.join(__dirname, pathToConfig.split('/').join(path.sep));
|
||||
const cli = new RocketCli({
|
||||
argv: ['lint', '--config-file', configFile],
|
||||
});
|
||||
await execute(cli, path.dirname(configFile));
|
||||
return cli;
|
||||
}
|
||||
@@ -37,6 +37,7 @@ describe('normalizeConfig', () => {
|
||||
setupDevAndBuildPlugins: [],
|
||||
setupDevPlugins: [],
|
||||
setupEleventyPlugins: [],
|
||||
setupEleventyComputedConfig: [],
|
||||
setupCliPlugins: [],
|
||||
presets: [],
|
||||
plugins: [
|
||||
@@ -71,6 +72,7 @@ describe('normalizeConfig', () => {
|
||||
setupDevPlugins: [],
|
||||
setupEleventyPlugins: [],
|
||||
setupCliPlugins: [],
|
||||
setupEleventyComputedConfig: [],
|
||||
presets: [],
|
||||
plugins: [
|
||||
{ commands: ['start'] },
|
||||
@@ -101,6 +103,7 @@ describe('normalizeConfig', () => {
|
||||
setupDevPlugins: [],
|
||||
setupEleventyPlugins: [],
|
||||
setupCliPlugins: [],
|
||||
setupEleventyComputedConfig: [],
|
||||
presets: [],
|
||||
plugins: [
|
||||
{ commands: ['start'] },
|
||||
@@ -134,6 +137,7 @@ describe('normalizeConfig', () => {
|
||||
setupDevPlugins: [],
|
||||
setupEleventyPlugins: [],
|
||||
setupCliPlugins: [],
|
||||
setupEleventyComputedConfig: [],
|
||||
presets: [],
|
||||
plugins: [
|
||||
{ commands: ['start'] },
|
||||
|
||||
2
packages/cli/types/main.d.ts
vendored
2
packages/cli/types/main.d.ts
vendored
@@ -10,6 +10,7 @@ export interface RocketPreset {
|
||||
setupDevPlugins: function[];
|
||||
setupCliPlugins: function[];
|
||||
setupEleventyPlugins: function[];
|
||||
setupEleventyComputedConfig: function[];
|
||||
}
|
||||
|
||||
export interface RocketCliOptions {
|
||||
@@ -28,6 +29,7 @@ export interface RocketCliOptions {
|
||||
setupDevPlugins: function[];
|
||||
setupCliPlugins: function[];
|
||||
setupEleventyPlugins: function[];
|
||||
setupEleventyComputedConfig: function[];
|
||||
|
||||
// advanced
|
||||
devServer: DevServerConfig;
|
||||
|
||||
@@ -8664,6 +8664,11 @@ uri-js@^4.2.2:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
utf8@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1"
|
||||
integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==
|
||||
|
||||
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
|
||||
Reference in New Issue
Block a user