mirror of
https://github.com/modernweb-dev/rocket.git
synced 2026-03-21 08:51:18 +00:00
Compare commits
5 Commits
@rocket/bl
...
@rocket/el
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b58ac27658 | ||
|
|
f44a0f4fd4 | ||
|
|
750418bb51 | ||
|
|
bc2698c1ba | ||
|
|
74f7ddf478 |
@@ -1,7 +1,17 @@
|
||||
# Tools >> Check HTML Links ||30
|
||||
|
||||
```js
|
||||
import '@rocket/launch/inline-notification/inline-notification.js';
|
||||
```
|
||||
|
||||
A fast checker for broken links/references in HTML.
|
||||
|
||||
<inline-notification type="tip">
|
||||
|
||||
Read the [Introducing Check HTMl Links - no more bad links](../../blog/introducing-check-html-links.md) Blog post to find out how it came to be and how it works.
|
||||
|
||||
</inline-notification>
|
||||
|
||||
## Features
|
||||
|
||||
- Checks all html files for broken local links/references (in href, src, srcset)
|
||||
@@ -16,10 +26,25 @@ A fast checker for broken links/references in HTML.
|
||||
npm i -D check-html-links
|
||||
```
|
||||
|
||||
## Usage
|
||||
## CLI flags
|
||||
|
||||
```
|
||||
| Name | Type | Description |
|
||||
| ------------------- | ------- | --------------------------------------------------------------------------------------------------- |
|
||||
| root-dir | string | the root directory to serve files from. Defaults to the current working directory |
|
||||
| ignore-link-pattern | string | do not check links matching the pattern |
|
||||
| continue-on-error | boolean | if present it will not exit with an error code - useful while writing or for temporary passing a ci |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```bash
|
||||
# check a folder _site
|
||||
npx check-html-links _site
|
||||
|
||||
# ignore all links like <a href="/users/123">
|
||||
npx check-html-links _site --ignore-link-pattern "/users/*" "/users/**/*"
|
||||
|
||||
# ignore all links like <a href="/users/123"> & <a href="/users/123/details">
|
||||
npx check-html-links _site --ignore-link-pattern "/users/*" "/users/**/*"
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
@@ -129,3 +129,25 @@ const config = {
|
||||
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
## Enabling / Disabling
|
||||
|
||||
Generating images from SVG is quite fast but it can still add that's why by default during `rocket start` there will be no social media images created.
|
||||
|
||||
If you with so create them also during start you can
|
||||
|
||||
```js
|
||||
const config = {
|
||||
start: {
|
||||
createSocialMediaImages: true,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Similarly, if you never want to create social media images even during build then you can globally disable it via
|
||||
|
||||
```js
|
||||
const config = {
|
||||
createSocialMediaImages: true,
|
||||
};
|
||||
```
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# check-html-links
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 750418b: Uses a class for the CLI and adding the following options:
|
||||
|
||||
- `--root-dir` the root directory to serve files from. Defaults to the current working directory
|
||||
- `--ignore-link-pattern` do not check links matching the pattern
|
||||
- `--continue-on-error` if present it will not exit with an error code - useful while writing or for temporary passing a ci
|
||||
|
||||
BREAKING CHANGE:
|
||||
|
||||
- Exists with an error code if a broken link is found
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { validateFolder } from './src/validateFolder.js';
|
||||
export { formatErrors } from './src/formatErrors.js';
|
||||
export { CheckHtmlLinksCli } from './src/CheckHtmlLinksCli.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "check-html-links",
|
||||
"version": "0.1.2",
|
||||
"version": "0.2.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@@ -33,7 +33,9 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"chalk": "^4.0.0",
|
||||
"command-line-args": "^5.1.1",
|
||||
"glob": "^7.0.0",
|
||||
"minimatch": "^3.0.4",
|
||||
"sax-wasm": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
100
packages/check-html-links/src/CheckHtmlLinksCli.js
Normal file
100
packages/check-html-links/src/CheckHtmlLinksCli.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
|
||||
/** @typedef {import('../types/main').CheckHtmlLinksCliOptions} CheckHtmlLinksCliOptions */
|
||||
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import commandLineArgs from 'command-line-args';
|
||||
import { validateFiles } from './validateFolder.js';
|
||||
import { formatErrors } from './formatErrors.js';
|
||||
import { listFiles } from './listFiles.js';
|
||||
|
||||
export class CheckHtmlLinksCli {
|
||||
/** @type {CheckHtmlLinksCliOptions} */
|
||||
options;
|
||||
|
||||
constructor({ argv } = { argv: undefined }) {
|
||||
const mainDefinitions = [
|
||||
{ name: 'ignore-link-pattern', type: String, multiple: true },
|
||||
{ name: 'root-dir', type: String, defaultOption: true },
|
||||
{ name: 'continue-on-error', type: Boolean, defaultOption: false },
|
||||
];
|
||||
const options = commandLineArgs(mainDefinitions, {
|
||||
stopAtFirstUnknown: true,
|
||||
argv,
|
||||
});
|
||||
this.options = {
|
||||
printOnError: true,
|
||||
continueOnError: options['continue-on-error'],
|
||||
rootDir: options['root-dir'],
|
||||
ignoreLinkPatterns: options['ignore-link-pattern'],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Partial<CheckHtmlLinksCliOptions>} newOptions
|
||||
*/
|
||||
setOptions(newOptions) {
|
||||
this.options = {
|
||||
...this.options,
|
||||
...newOptions,
|
||||
};
|
||||
}
|
||||
|
||||
async run() {
|
||||
const { ignoreLinkPatterns, rootDir: userRootDir } = this.options;
|
||||
const rootDir = userRootDir ? path.resolve(userRootDir) : process.cwd();
|
||||
const performanceStart = process.hrtime();
|
||||
|
||||
console.log('👀 Checking if all internal links work...');
|
||||
const files = await listFiles('**/*.html', 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, { ignoreLinkPatterns });
|
||||
|
||||
console.log(`🔗 Found a total of ${chalk.green.bold(numberLinks)} links to validate!\n`);
|
||||
|
||||
const performance = process.hrtime(performanceStart);
|
||||
/** @type {string[]} */
|
||||
let output = [];
|
||||
let message = '';
|
||||
if (errors.length > 0) {
|
||||
let referenceCount = 0;
|
||||
for (const error of errors) {
|
||||
referenceCount += error.usage.length;
|
||||
}
|
||||
output = [
|
||||
`❌ Found ${chalk.red.bold(
|
||||
errors.length.toString(),
|
||||
)} missing reference targets (used by ${referenceCount} links) while checking ${
|
||||
files.length
|
||||
} files:`,
|
||||
...formatErrors(errors)
|
||||
.split('\n')
|
||||
.map(line => ` ${line}`),
|
||||
`Checking links duration: ${performance[0]}s ${performance[1] / 1000000}ms`,
|
||||
];
|
||||
message = output.join('\n');
|
||||
if (this.options.printOnError === true) {
|
||||
console.error(message);
|
||||
}
|
||||
if (this.options.continueOnError === false) {
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
`✅ All internal links are valid. (executed in ${performance[0]}s ${
|
||||
performance[1] / 1000000
|
||||
}ms)`,
|
||||
);
|
||||
}
|
||||
|
||||
return { errors, message };
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import { validateFiles } from './validateFolder.js';
|
||||
import { formatErrors } from './formatErrors.js';
|
||||
import { listFiles } from './listFiles.js';
|
||||
import { CheckHtmlLinksCli } from './CheckHtmlLinksCli.js';
|
||||
|
||||
async function main() {
|
||||
const userRootDir = process.argv[2];
|
||||
const rootDir = userRootDir ? path.resolve(userRootDir) : process.cwd();
|
||||
const performanceStart = process.hrtime();
|
||||
|
||||
console.log('👀 Checking if all internal links work...');
|
||||
const files = await listFiles('**/*.html', 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;
|
||||
for (const error of errors) {
|
||||
referenceCount += error.usage.length;
|
||||
}
|
||||
const output = [
|
||||
`❌ Found ${chalk.red.bold(
|
||||
errors.length.toString(),
|
||||
)} missing reference targets (used by ${referenceCount} links) while checking ${
|
||||
files.length
|
||||
} files:`,
|
||||
...formatErrors(errors)
|
||||
.split('\n')
|
||||
.map(line => ` ${line}`),
|
||||
`Checking links duration: ${performance[0]}s ${performance[1] / 1000000}ms`,
|
||||
];
|
||||
console.error(output.join('\n'));
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(
|
||||
`✅ All internal links are valid. (executed in %ds %dms)`,
|
||||
performance[0],
|
||||
performance[1] / 1000000,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
const cli = new CheckHtmlLinksCli();
|
||||
cli.run();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import fs from 'fs';
|
||||
import saxWasm from 'sax-wasm';
|
||||
import minimatch from 'minimatch';
|
||||
import { createRequire } from 'module';
|
||||
|
||||
import { listFiles } from './listFiles.js';
|
||||
@@ -10,6 +11,7 @@ import path from 'path';
|
||||
/** @typedef {import('../types/main').LocalFile} LocalFile */
|
||||
/** @typedef {import('../types/main').Usage} Usage */
|
||||
/** @typedef {import('../types/main').Error} Error */
|
||||
/** @typedef {import('../types/main').Options} Options */
|
||||
/** @typedef {import('sax-wasm').Attribute} Attribute */
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
@@ -185,8 +187,9 @@ function getValueAndAnchor(inValue) {
|
||||
* @param {object} options
|
||||
* @param {string} options.htmlFilePath
|
||||
* @param {string} options.rootDir
|
||||
* @param {function(string): boolean} options.ignoreUsage
|
||||
*/
|
||||
async function resolveLinks(links, { htmlFilePath, rootDir }) {
|
||||
async function resolveLinks(links, { htmlFilePath, rootDir, ignoreUsage }) {
|
||||
for (const hrefObj of links) {
|
||||
const { value, anchor } = getValueAndAnchor(hrefObj.value);
|
||||
|
||||
@@ -201,7 +204,9 @@ async function resolveLinks(links, { htmlFilePath, rootDir }) {
|
||||
|
||||
let valueFile = value.endsWith('/') ? path.join(value, 'index.html') : value;
|
||||
|
||||
if (value.includes('mailto:')) {
|
||||
if (ignoreUsage(value)) {
|
||||
// ignore
|
||||
} else if (value.includes('mailto:')) {
|
||||
// ignore for now - could add a check to validate if the email address is valid
|
||||
} else if (valueFile === '' && anchor !== '') {
|
||||
addLocalFile(htmlFilePath, anchor, usageObj);
|
||||
@@ -261,8 +266,9 @@ async function validateLocalFiles(checkLocalFiles) {
|
||||
/**
|
||||
* @param {string[]} files
|
||||
* @param {string} rootDir
|
||||
* @param {Options} opts?
|
||||
*/
|
||||
export async function validateFiles(files, rootDir) {
|
||||
export async function validateFiles(files, rootDir, opts) {
|
||||
await parserReferences.prepareWasm(saxWasmBuffer);
|
||||
await parserIds.prepareWasm(saxWasmBuffer);
|
||||
|
||||
@@ -270,10 +276,20 @@ export async function validateFiles(files, rootDir) {
|
||||
checkLocalFiles = [];
|
||||
idCache = new Map();
|
||||
let numberLinks = 0;
|
||||
|
||||
const ignoreLinkPatternRegExps = opts
|
||||
? opts.ignoreLinkPatterns?.map(pattern => minimatch.makeRe(pattern))
|
||||
: null;
|
||||
|
||||
/** @type {function(string): boolean} */
|
||||
const ignoreUsage = ignoreLinkPatternRegExps
|
||||
? usage => !!ignoreLinkPatternRegExps.find(regExp => usage.match(regExp))
|
||||
: () => false;
|
||||
|
||||
for (const htmlFilePath of files) {
|
||||
const { links } = await extractReferences(htmlFilePath);
|
||||
numberLinks += links.length;
|
||||
await resolveLinks(links, { htmlFilePath, rootDir });
|
||||
await resolveLinks(links, { htmlFilePath, rootDir, ignoreUsage });
|
||||
}
|
||||
await validateLocalFiles(checkLocalFiles);
|
||||
|
||||
@@ -282,10 +298,11 @@ export async function validateFiles(files, rootDir) {
|
||||
|
||||
/**
|
||||
* @param {string} inRootDir
|
||||
* @param {Options} opts?
|
||||
*/
|
||||
export async function validateFolder(inRootDir) {
|
||||
export async function validateFolder(inRootDir, opts) {
|
||||
const rootDir = path.resolve(inRootDir);
|
||||
const files = await listFiles('**/*.html', rootDir);
|
||||
const { errors } = await validateFiles(files, rootDir);
|
||||
const { errors } = await validateFiles(files, rootDir, opts);
|
||||
return errors;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<!-- ignore known subsystems -->
|
||||
<a href="/docs/"></a>
|
||||
<a href="/developer/getting-started.html#js"></a>
|
||||
<a href="/developer/language-guides/"></a>
|
||||
<a href="/developer/javascript.html"></a>
|
||||
@@ -0,0 +1,8 @@
|
||||
<a href="/absolute/index.html"></a>
|
||||
<a href="./relative/index.html"></a>
|
||||
<a href="./relative/subfolder/index.html"></a>
|
||||
|
||||
<!-- valid -->
|
||||
<a href="./page.html"></a>
|
||||
<a href=" ./page.html "></a>
|
||||
<a href=" /page.html "></a>
|
||||
@@ -5,9 +5,9 @@ import { validateFolder } from 'check-html-links';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export async function execute(inPath) {
|
||||
export async function execute(inPath, opts) {
|
||||
const testDir = path.join(__dirname, inPath.split('/').join(path.sep));
|
||||
const errors = await validateFolder(testDir);
|
||||
const errors = await validateFolder(testDir, opts);
|
||||
return {
|
||||
cleanup: items => {
|
||||
const newItems = [];
|
||||
|
||||
@@ -183,6 +183,28 @@ describe('validateFolder', () => {
|
||||
expect(cleanup(errors)).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it('ignoring a folder', async () => {
|
||||
const { errors, cleanup } = await execute('fixtures/internal-link-ignore', {
|
||||
ignoreLinkPatterns: ['./relative/*', './relative/**/*'],
|
||||
});
|
||||
expect(cleanup(errors)).to.deep.equal([
|
||||
{
|
||||
filePath: 'fixtures/internal-link-ignore/absolute/index.html',
|
||||
onlyAnchorMissing: false,
|
||||
usage: [
|
||||
{
|
||||
anchor: '',
|
||||
attribute: 'href',
|
||||
character: 9,
|
||||
file: 'fixtures/internal-link-ignore/index.html',
|
||||
line: 0,
|
||||
value: '/absolute/index.html',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('can handle img src', async () => {
|
||||
const { errors, cleanup } = await execute('fixtures/internal-images');
|
||||
expect(cleanup(errors)).to.deep.equal([
|
||||
|
||||
11
packages/check-html-links/types/main.d.ts
vendored
11
packages/check-html-links/types/main.d.ts
vendored
@@ -25,3 +25,14 @@ export interface Error {
|
||||
onlyAnchorMissing: boolean;
|
||||
usage: Usage[];
|
||||
}
|
||||
|
||||
interface Options {
|
||||
ignoreLinkPatterns: string[] | null;
|
||||
}
|
||||
|
||||
export interface CheckHtmlLinksCliOptions {
|
||||
printOnError: boolean;
|
||||
rootDir: string;
|
||||
ignoreLinkPatterns: string[] | null;
|
||||
continueOnError: boolean;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @rocket/cli
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f44a0f4: Rewrite dynamic imports with "`"
|
||||
- 74f7ddf: Adds performance improvements for social media images by:
|
||||
- creating social media images only in `rocket build` phase
|
||||
- adds a config `createSocialMediaImages` to enable (default) or disable it globally
|
||||
- adds config `start.createSocialMediaImages` to enable or disable (default) it during `rocket start`
|
||||
- 750418b: Use class-based node API of check-html-links
|
||||
- Updated dependencies [f44a0f4]
|
||||
- Updated dependencies [750418b]
|
||||
- @rocket/eleventy-plugin-mdjs-unified@0.3.1
|
||||
- check-html-links@0.2.0
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rocket/cli",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
@@ -58,7 +58,7 @@
|
||||
"@11ty/eleventy-img": "^0.7.4",
|
||||
"@rocket/building-rollup": "^0.1.2",
|
||||
"@rocket/core": "^0.1.1",
|
||||
"@rocket/eleventy-plugin-mdjs-unified": "^0.3.0",
|
||||
"@rocket/eleventy-plugin-mdjs-unified": "^0.3.1",
|
||||
"@rocket/eleventy-rocket-nav": "^0.2.1",
|
||||
"@rollup/plugin-babel": "^5.2.2",
|
||||
"@rollup/plugin-node-resolve": "^11.0.1",
|
||||
@@ -66,10 +66,11 @@
|
||||
"@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.2",
|
||||
"check-html-links": "^0.2.0",
|
||||
"command-line-args": "^5.1.1",
|
||||
"command-line-usage": "^6.1.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
"micromatch": "^4.0.2",
|
||||
"plugins-manager": "^0.2.0",
|
||||
"utf8": "^3.0.0"
|
||||
},
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
/** @typedef {import('../types/main').RocketCliOptions} RocketCliOptions */
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { validateFolder, formatErrors } from 'check-html-links';
|
||||
import { CheckHtmlLinksCli } from 'check-html-links';
|
||||
|
||||
export class RocketLint {
|
||||
static pluginName = 'RocketLint';
|
||||
@@ -49,31 +48,20 @@ export class RocketLint {
|
||||
return;
|
||||
}
|
||||
|
||||
const performanceStart = process.hrtime();
|
||||
console.log('👀 Checking if all internal links work...');
|
||||
const errors = await validateFolder(this.config.lintInputDir);
|
||||
const performance = process.hrtime(performanceStart);
|
||||
const checkLinks = new CheckHtmlLinksCli();
|
||||
checkLinks.setOptions({
|
||||
rootDir: this.config.lintInputDir,
|
||||
printOnError: false,
|
||||
continueOnError: true,
|
||||
});
|
||||
|
||||
const { errors, message } = await checkLinks.run();
|
||||
if (errors.length > 0) {
|
||||
let referenceCount = 0;
|
||||
for (const error of errors) {
|
||||
referenceCount += error.usage.length;
|
||||
}
|
||||
const output = [
|
||||
`❌ Found ${chalk.red.bold(
|
||||
errors.length.toString(),
|
||||
)} missing reference targets (used by ${referenceCount} links):`,
|
||||
...formatErrors(errors)
|
||||
.split('\n')
|
||||
.map(line => ` ${line}`),
|
||||
`Checking links duration: ${performance[0]}s ${performance[1] / 1000000}ms`,
|
||||
];
|
||||
if (this.config.watch) {
|
||||
console.log(output.join('\n'));
|
||||
if (this.config.command === 'start') {
|
||||
console.log(message);
|
||||
} else {
|
||||
throw new Error(output.join('\n'));
|
||||
throw new Error(message);
|
||||
}
|
||||
} else {
|
||||
console.log('✅ All internal links are valid.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export class RocketStart {
|
||||
*/
|
||||
setupCommand(config) {
|
||||
delete config.pathPrefix;
|
||||
config.createSocialMediaImages = !!config?.start?.createSocialMediaImages;
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ export async function normalizeConfig(inConfig) {
|
||||
eleventy: () => {},
|
||||
command: 'help',
|
||||
watch: true,
|
||||
createSocialMediaImages: true,
|
||||
inputDir: 'docs',
|
||||
outputDir: '_site',
|
||||
outputDevDir: '_site-dev',
|
||||
|
||||
@@ -2,14 +2,14 @@ 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');
|
||||
const { createSocialImageSvg: defaultCreateSocialImageSvg } = require('./createSocialImageSvg.cjs');
|
||||
|
||||
async function createSocialImage(args) {
|
||||
const {
|
||||
title = '',
|
||||
subTitle = '',
|
||||
footer = '',
|
||||
createSocialImageSvg = defaultcreateSocialImageSvg,
|
||||
createSocialImageSvg = defaultCreateSocialImageSvg,
|
||||
} = args;
|
||||
const cleanedUpArgs = { ...args };
|
||||
delete cleanedUpArgs.createSocialImageSvg;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { processContentWithTitle } = require('@rocket/core/title');
|
||||
const { createSocialImage: defaultcreateSocialImage } = require('./createSocialImage.cjs');
|
||||
const { createSocialImage: defaultCreateSocialImage } = require('./createSocialImage.cjs');
|
||||
const { getComputedConfig } = require('./computedConfig.cjs');
|
||||
const { executeSetupFunctions } = require('plugins-manager');
|
||||
|
||||
@@ -71,7 +71,7 @@ function layoutPlugin({ defaultLayout = 'layout-default' } = {}) {
|
||||
}
|
||||
|
||||
function socialMediaImagePlugin(args = {}) {
|
||||
const { createSocialImage = defaultcreateSocialImage } = args;
|
||||
const { createSocialImage = defaultCreateSocialImage, rocketConfig = {} } = args;
|
||||
|
||||
const cleanedUpArgs = { ...args };
|
||||
delete cleanedUpArgs.createSocialImage;
|
||||
@@ -80,6 +80,11 @@ function socialMediaImagePlugin(args = {}) {
|
||||
if (data.socialMediaImage) {
|
||||
return data.socialMediaImage;
|
||||
}
|
||||
|
||||
if (rocketConfig.createSocialMediaImages === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.title) {
|
||||
return;
|
||||
}
|
||||
@@ -158,7 +163,7 @@ function generateEleventyComputed() {
|
||||
{ name: 'title', plugin: titlePlugin },
|
||||
{ name: 'eleventyNavigation', plugin: eleventyNavigationPlugin },
|
||||
{ name: 'section', plugin: sectionPlugin },
|
||||
{ name: 'socialMediaImage', plugin: socialMediaImagePlugin },
|
||||
{ name: 'socialMediaImage', plugin: socialMediaImagePlugin, options: { rocketConfig } },
|
||||
{ name: '_joiningBlocks', plugin: joiningBlocksPlugin, options: rocketConfig },
|
||||
{ name: 'layout', plugin: layoutPlugin },
|
||||
];
|
||||
|
||||
@@ -19,6 +19,7 @@ export function setFixtureDir(importMetaUrl) {
|
||||
* @property {boolean} stripToBody
|
||||
* @property {boolean} stripStartEndWhitespace
|
||||
* @property {boolean} stripScripts
|
||||
* @property {boolean} formatHtml
|
||||
* @property {start|build} type
|
||||
*/
|
||||
|
||||
@@ -81,6 +82,16 @@ export async function readOutput(
|
||||
return text;
|
||||
}
|
||||
|
||||
export function startOutputExist(cli, fileName) {
|
||||
const outputDir = cli.config.outputDevDir;
|
||||
return fs.existsSync(path.join(outputDir, fileName));
|
||||
}
|
||||
|
||||
export function buildOutputExist(cli, fileName) {
|
||||
const outputDir = cli.config.outputDir;
|
||||
return fs.existsSync(path.join(outputDir, fileName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} cli
|
||||
* @param {string} fileName
|
||||
@@ -91,6 +102,16 @@ export async function readStartOutput(cli, fileName, options = {}) {
|
||||
return readOutput(cli, fileName, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} cli
|
||||
* @param {string} fileName
|
||||
* @param {readOutputOptions} options
|
||||
*/
|
||||
export async function readBuildOutput(cli, fileName, options = {}) {
|
||||
options.type = 'build';
|
||||
return readOutput(cli, fileName, options);
|
||||
}
|
||||
|
||||
export async function execute(cli, configFileDir) {
|
||||
await cli.setup();
|
||||
cli.config.outputDevDir = path.join(configFileDir, '__output-dev');
|
||||
@@ -110,6 +131,15 @@ export async function executeStart(pathToConfig) {
|
||||
return cli;
|
||||
}
|
||||
|
||||
export async function executeBuild(pathToConfig) {
|
||||
const configFile = path.join(fixtureDir, pathToConfig.split('/').join(path.sep));
|
||||
const cli = new RocketCli({
|
||||
argv: ['build', '--config-file', configFile],
|
||||
});
|
||||
await execute(cli, path.dirname(configFile));
|
||||
return cli;
|
||||
}
|
||||
|
||||
export async function executeLint(pathToConfig) {
|
||||
const configFile = path.join(fixtureDir, pathToConfig.split('/').join(path.sep));
|
||||
const cli = new RocketCli({
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import chai from 'chai';
|
||||
import chalk from 'chalk';
|
||||
import { executeStart, readOutput, readStartOutput, setFixtureDir } from '@rocket/cli/test-helpers';
|
||||
import {
|
||||
executeBuild,
|
||||
executeStart,
|
||||
readBuildOutput,
|
||||
readOutput,
|
||||
readStartOutput,
|
||||
setFixtureDir,
|
||||
} from '@rocket/cli/test-helpers';
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
@@ -58,6 +65,23 @@ describe('RocketCli computedConfig', () => {
|
||||
expect(withDataSection).be.undefined;
|
||||
});
|
||||
|
||||
it('will note create a social media image in "start"', async () => {
|
||||
cli = await executeStart('computed-config-fixtures/social-images-only-build/rocket.config.js');
|
||||
|
||||
const indexHtml = await readStartOutput(cli, 'index.html');
|
||||
expect(indexHtml).to.equal('');
|
||||
});
|
||||
|
||||
it('will create a social media image in "build"', async () => {
|
||||
cli = await executeBuild('computed-config-fixtures/social-images-only-build/rocket.config.js');
|
||||
|
||||
const indexHtml = await readBuildOutput(cli, 'index.html', {
|
||||
stripToBody: true,
|
||||
stripServiceWorker: true,
|
||||
});
|
||||
expect(indexHtml).to.equal('/_merged_assets/11ty-img/5893749-1200.png');
|
||||
});
|
||||
|
||||
it('will create a social media image for every page', async () => {
|
||||
cli = await executeStart('computed-config-fixtures/social-images/rocket.config.js');
|
||||
|
||||
|
||||
87
packages/cli/test-node/RocketCli.useCases.test.js
Normal file
87
packages/cli/test-node/RocketCli.useCases.test.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import chai from 'chai';
|
||||
import chalk from 'chalk';
|
||||
import {
|
||||
executeStart,
|
||||
readStartOutput,
|
||||
setFixtureDir,
|
||||
startOutputExist,
|
||||
} from '@rocket/cli/test-helpers';
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
describe('RocketCli use cases', () => {
|
||||
let cli;
|
||||
|
||||
before(() => {
|
||||
// ignore colors in tests as most CIs won't support it
|
||||
chalk.level = 0;
|
||||
setFixtureDir(import.meta.url);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (cli?.cleanup) {
|
||||
await cli.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it('supports dynamic imports', async () => {
|
||||
cli = await executeStart('use-cases/dynamic-imports/rocket.config.js');
|
||||
|
||||
expect(startOutputExist(cli, 'sub/assets/myData.js'), 'static files did not get copied').to.be
|
||||
.true;
|
||||
|
||||
const aboutHtml = await readStartOutput(cli, 'about/index.html', { formatHtml: true });
|
||||
expect(aboutHtml).to.equal(
|
||||
[
|
||||
'<p><code>about.md</code></p>',
|
||||
'<script type="module">',
|
||||
' import { myData } from "../sub/assets/myData.js";',
|
||||
' import("../sub/assets/myData.js");',
|
||||
' const name = "myData";',
|
||||
' import(`../sub/assets/${name}.js`);',
|
||||
'</script>',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
const subHtml = await readStartOutput(cli, 'sub/index.html', { formatHtml: true });
|
||||
expect(subHtml).to.equal(
|
||||
[
|
||||
'<p><code>sub/index.md</code></p>',
|
||||
'<script type="module">',
|
||||
' import { myData } from "./assets/myData.js";',
|
||||
' import("./assets/myData.js");',
|
||||
' const name = "myData";',
|
||||
' import(`./assets/${name}.js`);',
|
||||
'</script>',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
const subDetailsHtml = await readStartOutput(cli, 'sub/details/index.html', {
|
||||
formatHtml: true,
|
||||
});
|
||||
expect(subDetailsHtml).to.equal(
|
||||
[
|
||||
'<p><code>sub/details.md</code></p>',
|
||||
'<script type="module">',
|
||||
' import { myData } from "../assets/myData.js";',
|
||||
' import("../assets/myData.js");',
|
||||
' const name = "myData";',
|
||||
' import(`../assets/${name}.js`);',
|
||||
'</script>',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
const indexHtml = await readStartOutput(cli, 'index.html', { formatHtml: true });
|
||||
expect(indexHtml).to.equal(
|
||||
[
|
||||
'<p><code>index.md</code></p>',
|
||||
'<script type="module">',
|
||||
' import { myData } from "./sub/assets/myData.js";',
|
||||
' import("./sub/assets/myData.js");',
|
||||
' const name = "myData";',
|
||||
' import(`./sub/assets/${name}.js`);',
|
||||
'</script>',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
**/*.njk
|
||||
@@ -0,0 +1 @@
|
||||
# Rocket
|
||||
@@ -0,0 +1,4 @@
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {};
|
||||
|
||||
export default config;
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = 'layout.njk';
|
||||
@@ -2,6 +2,9 @@ import { adjustPluginOptions } from 'plugins-manager';
|
||||
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {
|
||||
start: {
|
||||
createSocialMediaImages: true,
|
||||
},
|
||||
setupEleventyComputedConfig: [
|
||||
adjustPluginOptions('socialMediaImage', {
|
||||
createSocialImageSvg: async () => {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = 'layout.njk';
|
||||
@@ -0,0 +1 @@
|
||||
{{ socialMediaImage }}
|
||||
@@ -1,4 +1,8 @@
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {};
|
||||
const config = {
|
||||
start: {
|
||||
createSocialMediaImages: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -29,6 +29,7 @@ describe('normalizeConfig', () => {
|
||||
|
||||
expect(cleanup(config)).to.deep.equal({
|
||||
command: 'help',
|
||||
createSocialMediaImages: true,
|
||||
devServer: {},
|
||||
build: {},
|
||||
watch: true,
|
||||
@@ -61,6 +62,7 @@ describe('normalizeConfig', () => {
|
||||
|
||||
expect(cleanup(config)).to.deep.equal({
|
||||
command: 'help',
|
||||
createSocialMediaImages: true,
|
||||
devServer: {
|
||||
more: 'settings',
|
||||
},
|
||||
@@ -92,6 +94,7 @@ describe('normalizeConfig', () => {
|
||||
|
||||
expect(cleanup(config)).to.deep.equal({
|
||||
command: 'help',
|
||||
createSocialMediaImages: true,
|
||||
devServer: {
|
||||
more: 'from-file',
|
||||
},
|
||||
@@ -128,6 +131,7 @@ describe('normalizeConfig', () => {
|
||||
|
||||
expect(cleanup(config)).to.deep.equal({
|
||||
command: 'help',
|
||||
createSocialMediaImages: true,
|
||||
devServer: {},
|
||||
build: {},
|
||||
watch: true,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
**/*.njk
|
||||
@@ -0,0 +1 @@
|
||||
{{ content | safe }}
|
||||
@@ -0,0 +1,8 @@
|
||||
`about.md`
|
||||
|
||||
```js script
|
||||
import { myData } from './sub/assets/myData.js';
|
||||
import('./sub/assets/myData.js');
|
||||
const name = 'myData';
|
||||
import(`./sub/assets/${name}.js`);
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
`index.md`
|
||||
|
||||
```js script
|
||||
import { myData } from './sub/assets/myData.js';
|
||||
import('./sub/assets/myData.js');
|
||||
const name = 'myData';
|
||||
import(`./sub/assets/${name}.js`);
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
export const myData = 'The answer to everything is 42';
|
||||
@@ -0,0 +1,8 @@
|
||||
`sub/details.md`
|
||||
|
||||
```js script
|
||||
import { myData } from './assets/myData.js';
|
||||
import('./assets/myData.js');
|
||||
const name = 'myData';
|
||||
import(`./assets/${name}.js`);
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
`sub/index.md`
|
||||
|
||||
```js script
|
||||
import { myData } from './assets/myData.js';
|
||||
import('./assets/myData.js');
|
||||
const name = 'myData';
|
||||
import(`./assets/${name}.js`);
|
||||
```
|
||||
@@ -0,0 +1,4 @@
|
||||
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||
const config = {};
|
||||
|
||||
export default config;
|
||||
9
packages/cli/types/main.d.ts
vendored
9
packages/cli/types/main.d.ts
vendored
@@ -13,14 +13,21 @@ export interface RocketPreset {
|
||||
setupEleventyComputedConfig: function[];
|
||||
}
|
||||
|
||||
interface RocketStartConfig {
|
||||
createSocialMediaImages?: boolean;
|
||||
}
|
||||
|
||||
export interface RocketCliOptions {
|
||||
presets: Array<RocketPreset>;
|
||||
pathPrefix?: string;
|
||||
inputDir: string;
|
||||
outputDir: string;
|
||||
emptyOutputDir?: boolen;
|
||||
emptyOutputDir?: boolean;
|
||||
absoluteBaseUrl?: string;
|
||||
watch: boolean;
|
||||
createSocialMediaImages?: boolean;
|
||||
|
||||
start?: RocketStartConfig;
|
||||
|
||||
// TODO: improve all setup functions
|
||||
setupUnifiedPlugins?: function[];
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @rocket/eleventy-plugin-mdjs-unified
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f44a0f4: Rewrite dynamic imports with "`"
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rocket/eleventy-plugin-mdjs-unified",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
@@ -72,10 +72,14 @@ async function processImports(source, inputPath) {
|
||||
newSource += '.' + importSrc;
|
||||
} else if (importSrc.startsWith("'./")) {
|
||||
newSource += "'." + importSrc.substring(1);
|
||||
} else if (importSrc.startsWith('`./')) {
|
||||
newSource += '`.' + importSrc.substring(1);
|
||||
} else if (importSrc.startsWith('../')) {
|
||||
newSource += '../' + importSrc;
|
||||
} else if (importSrc.startsWith("'../")) {
|
||||
newSource += "'../" + importSrc.substring(1);
|
||||
} else if (importSrc.startsWith('`../')) {
|
||||
newSource += '`../' + importSrc.substring(1);
|
||||
} else {
|
||||
newSource += importSrc;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user