@lownoise-studio/rendershield 0.1.6 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +6 -0
- package/dist/cli.js +35 -15
- package/dist/cli.js.map +1 -1
- package/dist/commands/build.js +96 -0
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/init.js +7 -7
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/verify.js +132 -38
- package/dist/commands/verify.js.map +1 -1
- package/dist/core/generateWorker.js +85 -68
- package/dist/core/generateWorker.js.map +1 -1
- package/dist/core/loadConfig.js +74 -6
- package/dist/core/loadConfig.js.map +1 -1
- package/dist/core/loadMarkdown.js.map +1 -1
- package/dist/core/renderHtml.js +5 -1
- package/dist/core/renderHtml.js.map +1 -1
- package/dist/core/validateOutput.js +154 -29
- package/dist/core/validateOutput.js.map +1 -1
- package/package.json +7 -3
- package/src/cli.ts +75 -54
- package/src/commands/build.ts +185 -72
- package/src/commands/init.ts +7 -7
- package/src/commands/verify.ts +236 -114
- package/src/core/generateWorker.ts +97 -80
- package/src/core/loadConfig.ts +173 -74
- package/src/core/loadMarkdown.ts +2 -2
- package/src/core/renderHtml.ts +5 -1
- package/src/core/validateOutput.ts +217 -37
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lownoise-studio/rendershield",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Boring bot-aware prerendering: real HTML for bots, SPA for humans.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"build": "tsc -p tsconfig.json",
|
|
12
12
|
"dev": "node --enable-source-maps dist/cli.js",
|
|
13
13
|
"start": "node dist/cli.js",
|
|
14
|
-
"prepack": "npm run build"
|
|
14
|
+
"prepack": "npm run build",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"pretest": "npm run build"
|
|
15
17
|
},
|
|
16
18
|
"files": [
|
|
17
19
|
"dist/**",
|
|
@@ -20,6 +22,7 @@
|
|
|
20
22
|
"content/**",
|
|
21
23
|
"DEPLOY.md",
|
|
22
24
|
"README.md",
|
|
25
|
+
"CHANGELOG.md",
|
|
23
26
|
"LICENSE"
|
|
24
27
|
],
|
|
25
28
|
"repository": {
|
|
@@ -38,6 +41,7 @@
|
|
|
38
41
|
},
|
|
39
42
|
"devDependencies": {
|
|
40
43
|
"@types/fs-extra": "^11.0.4",
|
|
41
|
-
"typescript": "^5.5.4"
|
|
44
|
+
"typescript": "^5.5.4",
|
|
45
|
+
"vitest": "^2.1.0"
|
|
42
46
|
}
|
|
43
47
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,54 +1,75 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { cmdInit } from "./commands/init.js";
|
|
4
|
+
import { cmdBuild } from "./commands/build.js";
|
|
5
|
+
import { cmdVerify } from "./commands/verify.js";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const pkg = require("../package.json") as { version?: string };
|
|
9
|
+
const VERSION = pkg.version ?? "0.0.0";
|
|
10
|
+
|
|
11
|
+
function printHelp() {
|
|
12
|
+
console.log(`
|
|
13
|
+
RenderShield v${VERSION} — boring bot-aware prerendering.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
rendershield init
|
|
17
|
+
rendershield build
|
|
18
|
+
rendershield verify [--prod <url>]
|
|
19
|
+
|
|
20
|
+
verify Print curl commands for local/build output.
|
|
21
|
+
verify --prod Fetch URL as bot and human; verify bot sees full HTML and contract fields.
|
|
22
|
+
Requires x-rendershield: bot-hit from the Worker.
|
|
23
|
+
|
|
24
|
+
Notes:
|
|
25
|
+
- Config file: rendershield.config.json
|
|
26
|
+
- Content: content/<collection>/**/*.md (frontmatter required)
|
|
27
|
+
- Output: dist-prerender/
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function main() {
|
|
32
|
+
const args = process.argv.slice(2);
|
|
33
|
+
const cmd = args[0]?.trim();
|
|
34
|
+
|
|
35
|
+
if (!cmd || cmd === "-h" || cmd === "--help") {
|
|
36
|
+
printHelp();
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
if (cmd === "-V" || cmd === "--version") {
|
|
40
|
+
console.log(VERSION);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
if (cmd === "init") {
|
|
46
|
+
await cmdInit();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (cmd === "build") {
|
|
50
|
+
await cmdBuild();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (cmd === "verify") {
|
|
54
|
+
const verifyArgs = args.slice(1);
|
|
55
|
+
if (verifyArgs.includes("-h") || verifyArgs.includes("--help")) {
|
|
56
|
+
printHelp();
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
const prodIdx = verifyArgs.indexOf("--prod");
|
|
60
|
+
const prodUrl = prodIdx >= 0 && verifyArgs[prodIdx + 1] ? verifyArgs[prodIdx + 1].trim() : undefined;
|
|
61
|
+
await cmdVerify(undefined, prodUrl ? { prodUrl } : undefined);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.error(`Unknown command: ${cmd}\n`);
|
|
66
|
+
printHelp();
|
|
67
|
+
process.exit(1);
|
|
68
|
+
} catch (err: unknown) {
|
|
69
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
70
|
+
console.error(`\nRenderShield error: ${msg}\n`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
main();
|
package/src/commands/build.ts
CHANGED
|
@@ -1,72 +1,185 @@
|
|
|
1
|
-
import fs from "fs-extra";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { loadConfig } from "../core/loadConfig.js";
|
|
4
|
-
import { loadAllMarkdownDocs } from "../core/loadMarkdown.js";
|
|
5
|
-
import { renderPageHtml } from "../core/renderHtml.js";
|
|
6
|
-
import { generateSitemapXml } from "../core/generateSitemap.js";
|
|
7
|
-
import { generateRobotsTxt } from "../core/generateRobots.js";
|
|
8
|
-
import { generateWorkerJs } from "../core/generateWorker.js";
|
|
9
|
-
import { validatePrerenderHtml } from "../core/validateOutput.js";
|
|
10
|
-
|
|
11
|
-
function routeToOutDir(outDirAbs: string, routePath: string): string {
|
|
12
|
-
// /blog/slug -> outDir/blog/slug/index.html
|
|
13
|
-
const clean = routePath.replace(/^\//, "");
|
|
14
|
-
return path.join(outDirAbs, clean);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { loadConfig } from "../core/loadConfig.js";
|
|
4
|
+
import { loadAllMarkdownDocs } from "../core/loadMarkdown.js";
|
|
5
|
+
import { renderPageHtml } from "../core/renderHtml.js";
|
|
6
|
+
import { generateSitemapXml } from "../core/generateSitemap.js";
|
|
7
|
+
import { generateRobotsTxt } from "../core/generateRobots.js";
|
|
8
|
+
import { generateWorkerJs } from "../core/generateWorker.js";
|
|
9
|
+
import { validatePrerenderHtml } from "../core/validateOutput.js";
|
|
10
|
+
|
|
11
|
+
function routeToOutDir(outDirAbs: string, routePath: string): string {
|
|
12
|
+
// /blog/slug -> outDir/blog/slug/index.html
|
|
13
|
+
const clean = routePath.replace(/^\//, "");
|
|
14
|
+
return path.join(outDirAbs, clean);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validates output path before any destructive operation (fs.remove).
|
|
19
|
+
* Pass: outDir is a subdirectory inside project root; no symlink escape.
|
|
20
|
+
* Fail: "/", "C:\", "..", "../", or outDir (or any of its existing parents) resolving outside project.
|
|
21
|
+
* Policy: strict — build hard-fails before any delete attempt.
|
|
22
|
+
* Segment-safe: uses path.relative(root, out) only; no prefix startsWith.
|
|
23
|
+
*/
|
|
24
|
+
async function validateOutputPath(outDir: string, cwd: string): Promise<void> {
|
|
25
|
+
const cwdAbs = path.resolve(cwd);
|
|
26
|
+
let cwdReal: string;
|
|
27
|
+
try {
|
|
28
|
+
cwdReal = await fs.realpath(cwdAbs);
|
|
29
|
+
} catch (err: unknown) {
|
|
30
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
31
|
+
throw new Error(`Cannot resolve project root: ${cwdAbs}. ${msg}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const outDirAbs = path.resolve(cwdAbs, outDir);
|
|
35
|
+
// Order: realpath → normalize → then case-fold for comparisons
|
|
36
|
+
const normalizedCwd = path.normalize(cwdReal);
|
|
37
|
+
const normalizedOut = path.normalize(outDirAbs);
|
|
38
|
+
|
|
39
|
+
// Segment-safe: use path.relative only (no prefix startsWith on full path). Cross-platform: relative()
|
|
40
|
+
// rarely returns absolute on Windows; startsWith("..") and path.isAbsolute(rel) cover escapes.
|
|
41
|
+
const relative = path.relative(normalizedCwd, normalizedOut);
|
|
42
|
+
if (relative.startsWith("..") || relative === ".." || path.isAbsolute(relative)) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Output directory "${outDir}" resolves outside project root. Use a relative path within the project.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Reject: root filesystem (/, C:\, C:/) — early reject; real safety is "inside root" above
|
|
49
|
+
const rootPaths = ["/", "c:\\", "c:/"];
|
|
50
|
+
const outLower = normalizedOut.toLowerCase();
|
|
51
|
+
if (rootPaths.includes(outLower)) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Output directory "${outDir}" resolves to root filesystem. This is not allowed for safety.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Reject: output dir equals project root (would delete entire project). Case-fold after normalize.
|
|
58
|
+
const cwdLower = normalizedCwd.toLowerCase();
|
|
59
|
+
if (outLower === cwdLower) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Output directory "${outDir}" cannot be the project root. Use a subdirectory (e.g. dist-prerender).`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (await fs.pathExists(outDirAbs)) {
|
|
66
|
+
// Path exists: resolve symlinks and ensure real path is inside root
|
|
67
|
+
let outDirReal: string;
|
|
68
|
+
try {
|
|
69
|
+
outDirReal = await fs.realpath(outDirAbs);
|
|
70
|
+
} catch {
|
|
71
|
+
outDirReal = outDirAbs;
|
|
72
|
+
}
|
|
73
|
+
const outDirRealNorm = path.normalize(outDirReal);
|
|
74
|
+
const relativeReal = path.relative(normalizedCwd, outDirRealNorm);
|
|
75
|
+
if (relativeReal.startsWith("..") || relativeReal === ".." || path.isAbsolute(relativeReal)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Output directory "${outDir}" resolves (via symlink) outside project root. Use a path that does not escape the project.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// Path does not exist: walk up to nearest existing parent, realpath it, ensure it stays inside root.
|
|
82
|
+
// Guards e.g. outDir "dist-link/prerender-new" where dist-link is a symlink to /.
|
|
83
|
+
// If we never find an existing parent (or the only one is root), we must reject — do not treat as "fine."
|
|
84
|
+
let current = normalizedOut;
|
|
85
|
+
const rootDir = path.normalize(path.parse(normalizedCwd).root);
|
|
86
|
+
let foundParentInsideRoot = false;
|
|
87
|
+
while (current) {
|
|
88
|
+
if (await fs.pathExists(current)) {
|
|
89
|
+
let parentReal: string;
|
|
90
|
+
try {
|
|
91
|
+
parentReal = await fs.realpath(current);
|
|
92
|
+
} catch {
|
|
93
|
+
parentReal = current;
|
|
94
|
+
}
|
|
95
|
+
const parentRealNorm = path.normalize(parentReal);
|
|
96
|
+
// Nearest existing parent is filesystem root → outside project, reject
|
|
97
|
+
const parentLower = parentRealNorm.toLowerCase();
|
|
98
|
+
if (rootPaths.includes(parentLower)) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Output directory "${outDir}" has a parent that resolves to filesystem root. Use a path inside the project.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const relParent = path.relative(normalizedCwd, parentRealNorm);
|
|
104
|
+
if (relParent.startsWith("..") || relParent === ".." || path.isAbsolute(relParent)) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Output directory "${outDir}" has a parent that resolves (via symlink) outside project root. Use a path that does not escape the project.`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
foundParentInsideRoot = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
const parent = path.dirname(current);
|
|
113
|
+
if (parent === current) break;
|
|
114
|
+
current = parent;
|
|
115
|
+
}
|
|
116
|
+
if (!foundParentInsideRoot) {
|
|
117
|
+
// No existing parent found before hitting dirname loop stop (shouldn't happen on a normal FS)
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Output directory "${outDir}" could not be validated: no existing parent path found. Use a path inside the project.`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function cmdBuild(cwd = process.cwd()) {
|
|
126
|
+
const cfg = await loadConfig(cwd);
|
|
127
|
+
|
|
128
|
+
// Validate output path before any destructive operations
|
|
129
|
+
await validateOutputPath(cfg.output.outDir, cwd);
|
|
130
|
+
|
|
131
|
+
const outDirAbs = path.join(cwd, cfg.output.outDir);
|
|
132
|
+
|
|
133
|
+
// Clean output (boring + deterministic)
|
|
134
|
+
await fs.remove(outDirAbs);
|
|
135
|
+
await fs.ensureDir(outDirAbs);
|
|
136
|
+
|
|
137
|
+
const docs = await loadAllMarkdownDocs(cfg, cwd);
|
|
138
|
+
|
|
139
|
+
if (docs.length === 0) {
|
|
140
|
+
throw new Error("No markdown documents found. Check content paths/patterns.");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Generate pages (validate BEFORE writing)
|
|
144
|
+
for (const doc of docs) {
|
|
145
|
+
const pageDir = routeToOutDir(outDirAbs, doc.routePath);
|
|
146
|
+
await fs.ensureDir(pageDir);
|
|
147
|
+
|
|
148
|
+
const outFile = path.join(pageDir, "index.html");
|
|
149
|
+
const html = renderPageHtml(cfg, doc);
|
|
150
|
+
|
|
151
|
+
validatePrerenderHtml({
|
|
152
|
+
html,
|
|
153
|
+
outFile,
|
|
154
|
+
routePath: doc.routePath,
|
|
155
|
+
sourcePath: doc.sourcePath,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await fs.writeFile(outFile, html, "utf8");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// sitemap.xml
|
|
162
|
+
if (cfg.sitemap.enabled) {
|
|
163
|
+
const sitemapXml = generateSitemapXml(cfg, docs);
|
|
164
|
+
const sitemapPath = path.join(outDirAbs, cfg.sitemap.path.replace(/^\//, ""));
|
|
165
|
+
await fs.writeFile(sitemapPath, sitemapXml, "utf8");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// robots.txt
|
|
169
|
+
if (cfg.robots.enabled) {
|
|
170
|
+
const robotsTxt = generateRobotsTxt(cfg);
|
|
171
|
+
const robotsPath = path.join(outDirAbs, cfg.robots.path.replace(/^\//, ""));
|
|
172
|
+
await fs.writeFile(robotsPath, robotsTxt, "utf8");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// worker.js
|
|
176
|
+
if (cfg.worker.enabled) {
|
|
177
|
+
const workerJs = generateWorkerJs(cfg);
|
|
178
|
+
await fs.writeFile(path.join(outDirAbs, "worker.js"), workerJs, "utf8");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log(`Built ${docs.length} pages into ${cfg.output.outDir}/`);
|
|
182
|
+
console.log(
|
|
183
|
+
`Output includes: ${cfg.sitemap.enabled ? "sitemap.xml " : ""}${cfg.robots.enabled ? "robots.txt " : ""}${cfg.worker.enabled ? "worker.js" : ""}`
|
|
184
|
+
);
|
|
185
|
+
}
|
package/src/commands/init.ts
CHANGED
|
@@ -16,9 +16,9 @@ const DEFAULT_CONFIG = {
|
|
|
16
16
|
baseDir: "content",
|
|
17
17
|
collections: [
|
|
18
18
|
{
|
|
19
|
-
name: "
|
|
20
|
-
pattern: "
|
|
21
|
-
routeBase: "/
|
|
19
|
+
name: "blog",
|
|
20
|
+
pattern: "blog/**/*.md",
|
|
21
|
+
routeBase: "/blog",
|
|
22
22
|
schemaType: "Article"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
@@ -39,7 +39,7 @@ const DEFAULT_CONFIG = {
|
|
|
39
39
|
worker: {
|
|
40
40
|
enabled: true,
|
|
41
41
|
lovableOrigin: "https://YOUR_SITE.lovable.app",
|
|
42
|
-
rewriteRouteBases: ["/
|
|
42
|
+
rewriteRouteBases: ["/blog/"],
|
|
43
43
|
botUserAgentPatterns: [
|
|
44
44
|
"googlebot",
|
|
45
45
|
"bingbot",
|
|
@@ -78,7 +78,7 @@ That's the whole trick.
|
|
|
78
78
|
|
|
79
79
|
export async function cmdInit(cwd = process.cwd()) {
|
|
80
80
|
const configPath = path.join(cwd, CONFIG_NAME);
|
|
81
|
-
const contentDir = path.join(cwd, "content", "
|
|
81
|
+
const contentDir = path.join(cwd, "content", "blog");
|
|
82
82
|
const samplePath = path.join(contentDir, "hello-world.md");
|
|
83
83
|
|
|
84
84
|
const configExists = await fs.pathExists(configPath);
|
|
@@ -98,7 +98,7 @@ export async function cmdInit(cwd = process.cwd()) {
|
|
|
98
98
|
const sampleExists = await fs.pathExists(samplePath);
|
|
99
99
|
if (!sampleExists) {
|
|
100
100
|
await fs.writeFile(samplePath, SAMPLE_POST, "utf8");
|
|
101
|
-
console.log(`Created sample content: content/
|
|
101
|
+
console.log(`Created sample content: content/blog/hello-world.md`);
|
|
102
102
|
} else {
|
|
103
103
|
console.log(`Sample content already exists (leaving it alone)`);
|
|
104
104
|
}
|
|
@@ -106,7 +106,7 @@ export async function cmdInit(cwd = process.cwd()) {
|
|
|
106
106
|
console.log(`
|
|
107
107
|
Next:
|
|
108
108
|
1) Edit rendershield.config.json
|
|
109
|
-
2) Add content under content/
|
|
109
|
+
2) Add content under content/blog/
|
|
110
110
|
3) Run: rendershield build
|
|
111
111
|
`);
|
|
112
112
|
}
|