Skip to content

Commit 8a4bff3

Browse files
committed
feat(generate): add new structures cli to replace cc-ssg plugins
1 parent c34564b commit 8a4bff3

30 files changed

+605
-844
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ package-lock.json
2424
coverage
2525
**/bookshop-test-dir/*
2626
javascript-modules/integration-tests/support/starters/jekyll/*.lock
27-
javascript-modules/integration-tests/.bookshop-tmp-test-dir/*
27+
javascript-modules/integration-tests/.bookshop-tmp-test-dir/*
28+
hugo_renderer.wasm

bookshop-packages.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"javascript-modules/engines/eleventy-engine": {},
1919
"javascript-modules/engines/svelte-engine": {},
2020
"javascript-modules/gen": {},
21+
"javascript-modules/generate": {},
2122
"javascript-modules/generator-plugins/eleventy/cloudcannon-eleventy-bookshop": {},
2223
"javascript-modules/generator-plugins/eleventy/eleventy-bookshop": {},
2324
"javascript-modules/helpers": {},

javascript-modules/builder/main.js

+8-9
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,25 @@ export default async (options) => {
2525
options.bookshopDirs = filterBookshops(options.bookshopDirs);
2626
options.bookshopConfig = await loadConfig(options.bookshopDirs[0]);
2727

28-
const plugins = esbuildOptions.plugins || [];
29-
plugins.push(bookshopComponentPlugin(options));
30-
plugins.push(bookshopConfigPlugin(options));
31-
plugins.push(bookshopFilePlugin(options));
32-
plugins.push(bookshopGlobPlugin(options));
33-
plugins.push(bookshopStylesPlugin(options));
28+
esbuildOptions.plugins.push(bookshopComponentPlugin(options));
29+
esbuildOptions.plugins.push(bookshopConfigPlugin(options));
30+
esbuildOptions.plugins.push(bookshopFilePlugin(options));
31+
esbuildOptions.plugins.push(bookshopGlobPlugin(options));
32+
esbuildOptions.plugins.push(bookshopStylesPlugin(options));
3433

3534
options.bookshopConfig?.engines?.forEach(engine => {
3635
engine?.buildPlugins?.forEach(plugin => {
37-
plugins.push(plugin);
36+
esbuildOptions.plugins.push(plugin);
3837
});
3938
esbuildOptions.loader = {
4039
...esbuildOptions.loader,
4140
...(engine?.buildLoaders || {})
4241
};
42+
engine?.esbuildConfigFn?.(esbuildOptions);
4343
});
4444

4545
return await esbuild.build({
4646
...esbuildOptions,
47-
bundle: true,
48-
plugins: plugins,
47+
bundle: true
4948
});
5049
}

javascript-modules/builder/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"@bookshop/helpers": "2.2.3",
2323
"@bookshop/styles": "2.2.3",
2424
"esbuild": "^0.13.10",
25+
"fast-glob": "^3.2.7",
2526
"normalize-path": "^3.0.0"
2627
},
2728
"engines": {

javascript-modules/generate/.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
engine-strict = true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
engines: {
3+
"@bookshop/jekyll-engine": { name: "Jekyll Test" }
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[component]
2+
structures = ["test"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>card - {{ include.text }}</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
p { color: palegoldenrod; }
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { fileURLToPath } from "url";
2+
import path from 'path';
3+
4+
export const __filename = u => fileURLToPath(u);
5+
export const __dirname = u => path.dirname(__filename(u));

javascript-modules/generate/main.js

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#! /usr/bin/env node
2+
import path from 'path';
3+
import fs from 'fs';
4+
import { Command } from "commander";
5+
import fastGlob from 'fast-glob';
6+
import TOML from '@ltd/j-toml';
7+
import normalizePath from "normalize-path";
8+
import Structures from "@bookshop/cloudcannon-structures";
9+
import Narrator from "@bookshop/toml-narrator";
10+
11+
const cwd = process.cwd();
12+
const program = new Command();
13+
14+
const plur = (num, str, pluralStr) => {
15+
const pluralized = num === 1 ? str : (pluralStr ?? `${str}s`);
16+
return `${num} ${pluralized}`;
17+
}
18+
19+
const addComponentTo = (obj, component) => {
20+
const { structures, ...fields } = component;
21+
structures?.forEach(structure => {
22+
obj[structure] = obj[structure] || {};
23+
obj[structure]["id_key"] = "_bookshop_name"
24+
obj[structure]["values"] = obj[structure]["values"] || [];
25+
obj[structure]["values"].push(fields);
26+
});
27+
}
28+
29+
async function run() {
30+
program.option("-d, --dot", "Look for Bookshops inside . directories");
31+
program.parse(process.argv);
32+
const options = program.opts();
33+
34+
const bookshopConfigFiles = await fastGlob(`./**/bookshop.config.js`, {
35+
cwd,
36+
dot: !!options.dot
37+
});
38+
39+
const tomlFiles = [];
40+
41+
for (const bookshopConfig of bookshopConfigFiles) {
42+
const bookshopRoot = path.dirname(path.dirname(bookshopConfig));
43+
console.log(`📚 Reading Bookshop ./${bookshopRoot}`);
44+
const bookshopPath = normalizePath(`${bookshopRoot}/**/*.bookshop.toml`);
45+
tomlFiles.push(...await fastGlob(bookshopPath, {
46+
cwd
47+
}));
48+
}
49+
50+
const files = Array.from(new Set(tomlFiles.sort())).map(file => { return { path: file } });
51+
let structureCount = 0;
52+
53+
files?.forEach(file => {
54+
let contents = fs.readFileSync(file.path, "utf8");
55+
contents = Narrator.RewriteTOML(contents);
56+
file.contents = TOML.parse(contents, 1.0, '\n', false);
57+
file.components = Structures.TransformComponent(file.path, file.contents);
58+
structureCount += file.components.length;
59+
});
60+
61+
const infoJsonFiles = await fastGlob(`./**/_cloudcannon/info.json`, {
62+
cwd,
63+
dot: !!options.dot
64+
});
65+
66+
for (const infoJsonFile of infoJsonFiles) {
67+
const siteRoot = path.dirname(path.dirname(infoJsonFile));
68+
console.log(`📚 Modifying built site at ./${siteRoot}`);
69+
const contents = fs.readFileSync(infoJsonFile, "utf8");
70+
const info_json = JSON.parse(contents);
71+
info_json["_structures"] = info_json["_structures"] || {};
72+
73+
files?.forEach(file => {
74+
file.components?.forEach(component => {
75+
addComponentTo(info_json["_structures"], component);
76+
if (typeof info_json["_array_structures"] === 'object') {
77+
addComponentTo(info_json["_array_structures"], component);
78+
}
79+
});
80+
});
81+
82+
fs.writeFileSync(infoJsonFile, JSON.stringify(info_json, null, 2));
83+
}
84+
85+
console.log(`📚 Added ${plur(structureCount, "structure")} from ${plur(bookshopConfigFiles.length, "Bookshop")} to ${plur(infoJsonFiles.length, "site")}.`);
86+
}
87+
88+
run();
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import test from 'ava';
2+
3+
test.skip("TODO: Test this package", t => { });
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "@bookshop/generate",
3+
"packageManager": "[email protected]",
4+
"version": "2.2.3",
5+
"description": "Bookshop generate powers rich editing experiences on your Bookshop website",
6+
"type": "module",
7+
"main": "main.js",
8+
"scripts": {
9+
"test": "nyc ava -v"
10+
},
11+
"bin": {
12+
"bookshop-generate": "main.js"
13+
},
14+
"author": "@bglw",
15+
"license": "MIT",
16+
"publishConfig": {
17+
"access": "public"
18+
},
19+
"devDependencies": {
20+
"ava": "^3.15.0",
21+
"nyc": "^15.1.0"
22+
},
23+
"dependencies": {
24+
"@bookshop/builder": "2.2.3",
25+
"@bookshop/cloudcannon-structures": "2.2.3",
26+
"@bookshop/helpers": "2.2.3",
27+
"@bookshop/toml-narrator": "2.2.3",
28+
"@ltd/j-toml": "^1.17.0",
29+
"commander": "^8.1.0",
30+
"fast-glob": "^3.2.7",
31+
"normalize-path": "^3.0.0"
32+
},
33+
"engines": {
34+
"node": ">=14.16"
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Feature: Bookshop Structure Generation
2+
As a user who is using a bookshop
3+
I want my CMS interfaces to be preconfigured
4+
So that I can build pages out of my components
5+
6+
Background:
7+
Given the file tree:
8+
"""
9+
component-lib/
10+
bookshop/
11+
bookshop.config.js from starters/jekyll/bookshop.config.js
12+
site/
13+
public/
14+
_cloudcannon/
15+
info.json from starters/generate/info.json # <-- this .json line hurts my syntax highlighting
16+
package.json from starters/generate/package.json # <-- this .json line hurts my syntax highlighting
17+
"""
18+
19+
Scenario: Generating structures
20+
Given a component-lib/components/card/card.bookshop.toml file containing:
21+
"""
22+
[component]
23+
structures = [ "content_blocks" ]
24+
label = "Card"
25+
description = "Card component"
26+
icon = "nature_people"
27+
tags = ["Card"]
28+
29+
[props]
30+
card_text = "This is the card"
31+
color.select = ["Red", "Blue"]
32+
color.default = "Blue" #: Comment
33+
"""
34+
When I run "npm start" in the . directory
35+
Then stderr should be empty
36+
And stdout should contain "📚 Added 1 structure from 1 Bookshop to 1 site."
37+
And site/public/_cloudcannon/info.json should leniently contain each row:
38+
| text |
39+
| "id_key" : "_bookshop_name" |
40+
| "value" : { "_bookshop_name" : "card" , "card_text" : null, "color" : "Blue" } |
41+
| "label" : "Card" |
42+
| "_select_data" : { "colors" : [ "Red" , "Blue" ] } |
43+
| "_comments" : { "color" : "Comment" } |

javascript-modules/integration-tests/features/hugo/hugo_bookshop_browser.feature

-28
This file was deleted.

0 commit comments

Comments
 (0)