docpensieve 0.1.0 → 0.1.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.
Files changed (42) hide show
  1. package/README.md +5 -2
  2. package/bin/docpensieve.js +1 -0
  3. package/package.json +4 -2
  4. package/src/commands/dev.js +1 -1
  5. package/src/commands/init.js +214 -39
  6. package/starter/01-guide/01-installation.md +79 -0
  7. package/starter/01-guide/02-first-site.md +97 -0
  8. package/starter/01-guide/03-writing-pages.md +138 -0
  9. package/starter/01-guide/04-versions.md +179 -0
  10. package/starter/01-guide/05-themes.md +116 -0
  11. package/starter/01-guide/06-deployment.md +124 -0
  12. package/starter/01-guide/index.md +38 -0
  13. package/starter/02-components/01-card.mdx +195 -0
  14. package/starter/02-components/02-columns.mdx +193 -0
  15. package/starter/02-components/03-time-timer.mdx +120 -0
  16. package/starter/02-components/04-tooltip.mdx +100 -0
  17. package/starter/02-components/05-tree.mdx +163 -0
  18. package/starter/02-components/06-scroll-to-top.mdx +103 -0
  19. package/starter/02-components/07-skill.mdx +214 -0
  20. package/starter/02-components/08-logo-icon.mdx +122 -0
  21. package/starter/02-components/icons/banner.svg +15 -0
  22. package/starter/02-components/icons/book.svg +4 -0
  23. package/starter/02-components/icons/lightning.svg +3 -0
  24. package/starter/02-components/icons/shield.svg +4 -0
  25. package/starter/02-components/icons/star.svg +3 -0
  26. package/starter/02-components/index.md +41 -0
  27. package/starter/03-reference/01-cli.md +134 -0
  28. package/starter/03-reference/02-configuration.md +132 -0
  29. package/starter/03-reference/03-frontmatter.md +110 -0
  30. package/starter/03-reference/04-theme.md +149 -0
  31. package/starter/03-reference/index.md +32 -0
  32. package/starter/04-architecture.md +106 -0
  33. package/starter/icons/blocks.svg +6 -0
  34. package/starter/icons/book.svg +4 -0
  35. package/starter/icons/branch.svg +6 -0
  36. package/starter/icons/compass.svg +4 -0
  37. package/starter/icons/lightning.svg +3 -0
  38. package/starter/icons/list.svg +4 -0
  39. package/starter/icons/shield.svg +4 -0
  40. package/starter/icons/star.svg +3 -0
  41. package/starter/index.mdx +244 -0
  42. package/types/commands/init.d.ts +7 -4
package/README.md CHANGED
@@ -22,8 +22,11 @@ npx docpensieve check # reads the produced site back: links, markup
22
22
  npx docpensieve serve # serves the output folder
23
23
  ```
24
24
 
25
- `init` asks for the project name, its URL and the CSS framework. In a script
26
- or in CI, `--yes --theme tailwind` skips the dialogue.
25
+ `init` asks for the project name, its URL and the CSS framework, and installs
26
+ DocPensieve's documentation in a section of the new site's menu, matching the
27
+ installed version — `--minimal` leaves it out. The configuration file it writes
28
+ lists every option, each with a comment. In a script or in CI,
29
+ `--yes --theme tailwind` skips the dialogue.
27
30
 
28
31
  The development server reloads the browser after every rebuild, through a
29
32
  script injected **at serving time**: the output of `build` stays free of
@@ -33,6 +33,7 @@ program
33
33
  .option('--version-name <version>', 'first version, e.g. 1.0')
34
34
  .option('-y, --yes', 'accept the defaults without a dialogue')
35
35
  .option('-f, --force', 'overwrite an existing configuration')
36
+ .option('--minimal', "leave DocPensieve's documentation out of the new site")
36
37
  .action(async (dir, options) => {
37
38
  await init(dir, { ...options, version: options.versionName });
38
39
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docpensieve",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "DocPensieve command-line interface (init, build, check, dev, serve)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,6 +20,7 @@
20
20
  "files": [
21
21
  "bin",
22
22
  "src",
23
+ "starter",
23
24
  "types"
24
25
  ],
25
26
  "dependencies": {
@@ -54,6 +55,7 @@
54
55
  },
55
56
  "types": "./types/index.d.ts",
56
57
  "scripts": {
57
- "prepack": "tsc -b tsconfig.build.json"
58
+ "prepack": "tsc -b tsconfig.build.json && node scripts/starter.mjs",
59
+ "postpack": "node scripts/starter.mjs --clean"
58
60
  }
59
61
  }
@@ -80,7 +80,7 @@ export async function dev(options = {}) {
80
80
  console.log(`served at ${url}`);
81
81
 
82
82
  const watched = [
83
- path.resolve(cwd, CONFIG_FILENAME),
83
+ config.configFile ?? path.resolve(cwd, CONFIG_FILENAME),
84
84
  ...config.versions.map((version) => path.resolve(cwd, version.folder)),
85
85
  ];
86
86
  const watcher = chokidar.watch(watched, { ignoreInitial: true });
@@ -4,12 +4,19 @@
4
4
  * @module docpensieve/commands/init
5
5
  */
6
6
 
7
- import { existsSync } from 'node:fs';
7
+ import { cpSync, existsSync, readdirSync, rmSync } from 'node:fs';
8
8
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
9
+ import { createRequire } from 'node:module';
9
10
  import path from 'node:path';
10
11
  import { createInterface } from 'node:readline/promises';
12
+ import { fileURLToPath } from 'node:url';
11
13
 
12
- import { CONFIG_FILENAME, DocPensieveError, THEME_FRAMEWORKS } from '@docpensieve/shared';
14
+ import {
15
+ CONFIG_FILENAME,
16
+ CONFIG_FILENAMES,
17
+ DocPensieveError,
18
+ THEME_FRAMEWORKS,
19
+ } from '@docpensieve/shared';
13
20
 
14
21
  /**
15
22
  * Description shown next to each framework.
@@ -23,6 +30,23 @@ const FRAMEWORK_LABELS = {
23
30
  /** Answers used when there is no dialogue. */
24
31
  const DEFAULTS = { name: 'My documentation', siteUrl: '', theme: 'tailwind', version: '1.0' };
25
32
 
33
+ /**
34
+ * Folder of the installed DocPensieve documentation, inside the version folder.
35
+ *
36
+ * The prefix puts it last in the menu, after the project's own pages, and
37
+ * vanishes from the URL: the section is served under `/docpensieve/`.
38
+ */
39
+ const DOCS_FOLDER = '99-docpensieve';
40
+
41
+ /**
42
+ * Entries of DocPensieve's documentation that are not installed: its home page
43
+ * and the icons only that page uses belong to DocPensieve's own site.
44
+ */
45
+ const NOT_INSTALLED = new Set(['index.md', 'index.mdx', 'icons']);
46
+
47
+ /** Where the generated configuration sends readers for every field. */
48
+ const DOCUMENTATION_URL = 'https://juniors017.github.io/docpensieve/';
49
+
26
50
  /**
27
51
  * Asks a question, with a default value shown between brackets.
28
52
  *
@@ -66,11 +90,33 @@ async function askFramework(rl) {
66
90
  }
67
91
  }
68
92
 
93
+ /**
94
+ * Asks whether to install DocPensieve's documentation in the new site.
95
+ *
96
+ * @param {import('node:readline/promises').Interface} rl
97
+ * @returns {Promise<boolean>}
98
+ */
99
+ async function askDocumentation(rl) {
100
+ for (;;) {
101
+ const answer = (await rl.question("Install DocPensieve's documentation in the site? [Y/n]: "))
102
+ .trim()
103
+ .toLowerCase();
104
+ if (answer === '' || answer === 'y' || answer === 'yes') return true;
105
+ if (answer === 'n' || answer === 'no') return false;
106
+ console.log('Answer not understood. Expected: y or n.');
107
+ }
108
+ }
109
+
69
110
  /**
70
111
  * Gathers the answers, through a dialogue or from the options.
71
112
  *
72
- * @param {{ name?: string, theme?: string, siteUrl?: string, version?: string, yes?: boolean }} options
73
- * @returns {Promise<{ name: string, theme: string, siteUrl: string, version: string }>}
113
+ * @param {{
114
+ * name?: string, theme?: string, siteUrl?: string, version?: string,
115
+ * yes?: boolean, minimal?: boolean,
116
+ * }} options
117
+ * @returns {Promise<{
118
+ * name: string, theme: string, siteUrl: string, version: string, docs: boolean,
119
+ * }>}
74
120
  */
75
121
  async function collect(options) {
76
122
  const fromOptions = {
@@ -78,6 +124,7 @@ async function collect(options) {
78
124
  siteUrl: options.siteUrl ?? DEFAULTS.siteUrl,
79
125
  theme: options.theme ?? DEFAULTS.theme,
80
126
  version: options.version ?? DEFAULTS.version,
127
+ docs: !options.minimal,
81
128
  };
82
129
 
83
130
  // Without a terminal — script, CI, pipe — the dialogue would never complete:
@@ -90,7 +137,8 @@ async function collect(options) {
90
137
  const siteUrl = await ask(rl, 'Public URL of the site (optional)', fromOptions.siteUrl);
91
138
  const version = await ask(rl, 'First version', fromOptions.version);
92
139
  const theme = options.theme ?? (await askFramework(rl));
93
- return { name, siteUrl, version, theme };
140
+ const docs = options.minimal ? false : await askDocumentation(rl);
141
+ return { name, siteUrl, version, theme, docs };
94
142
  } finally {
95
143
  rl.close();
96
144
  }
@@ -108,8 +156,14 @@ async function collect(options) {
108
156
  const quote = (value) => `'${String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
109
157
 
110
158
  /**
159
+ * Renders `docpensieve.config.mjs`.
160
+ *
161
+ * Every field the configuration accepts appears in it — set to its default,
162
+ * or commented out with an example — so that the first file a user opens also
163
+ * tells them everything they can change.
164
+ *
111
165
  * @param {{ name: string, theme: string, siteUrl: string, version: string }} answers
112
- * @returns {string} Contents of `docpensieve.config.js`.
166
+ * @returns {string} Contents of `docpensieve.config.mjs`.
113
167
  */
114
168
  function renderConfig({ name, theme, siteUrl, version }) {
115
169
  const slug = versionSlug(version);
@@ -121,45 +175,74 @@ function renderConfig({ name, theme, siteUrl, version }) {
121
175
  const lines = [
122
176
  "/** @type {import('@docpensieve/core').DocPensieveConfig} */",
123
177
  'export default {',
178
+ ' // Name shown in the header, in the page titles and in the structured data.',
124
179
  ` projectName: ${quote(name)},`,
125
- ];
126
-
127
- if (siteUrl) {
128
- lines.push(
129
- ` siteUrl: ${quote(siteUrl)},`,
130
- ' // baseUrl is derived from the path of siteUrl. Set it to force it.',
131
- );
132
- }
133
-
134
- lines.push(
135
180
  '',
136
- ' // One entry per version. The compiled output then goes to an orphan',
137
- ' // branch with the same slug.',
181
+ ' // Public address of the site. It feeds the canonical links and the',
182
+ ' // structured data, and its path gives the deployment prefix.',
183
+ siteUrl ? ` siteUrl: ${quote(siteUrl)},` : " // siteUrl: 'https://example.com/my-project',",
184
+ " // baseUrl: '/my-project/', // only to depart from the path of siteUrl",
185
+ '',
186
+ ' // Language of the pages, in <html lang>. The labels of the page shell',
187
+ ' // stay in English.',
188
+ " lang: 'en',",
189
+ '',
190
+ ' // One entry per version, each one a folder of Markdown and MDX pages.',
191
+ ' // current the version the site root leads to — at most one',
192
+ ' // prerelease in preparation: a banner on every page, kept out of search',
193
+ ' // archived no longer maintained: a banner, still indexed',
138
194
  ' versions: [',
139
- ` {`,
140
- ` slug: ${quote(slug)},`,
141
- ` name: ${quote(version)},`,
195
+ ' {',
196
+ ` slug: ${quote(slug)}, // URL segment, and name of the version's branch`,
197
+ ` name: ${quote(version)}, // label in the version switcher`,
142
198
  ` folder: ${quote(`docs/${slug}`)},`,
143
- ` current: true,`,
144
- ` },`,
199
+ ' current: true,',
200
+ ' },',
145
201
  ' ],',
146
202
  '',
203
+ ' // Output folder of "docpensieve build".',
147
204
  " outDir: 'dist',",
148
205
  '',
149
206
  ' theme: {',
207
+ " // 'tailwind' compiles the utilities your pages use; 'custom' is a plain",
208
+ ' // stylesheet with no dependency.',
150
209
  ` framework: ${quote(theme)},`,
151
210
  " darkMode: 'class',",
152
- " // Override the palette: tokens: { '--dp-accent': '#008060' },",
211
+ '',
212
+ ' // Design tokens to override, for instance the accent colour:',
213
+ " // tokens: { '--dp-accent': '#008060', '--dp-radius': '0.75rem' },",
214
+ '',
215
+ ' // CSS appended to the stylesheet, outside any layer: it wins over the',
216
+ ' // default rules.',
217
+ " // css: '.dp-article h2 { letter-spacing: -0.01em; }',",
218
+ ...(theme === 'tailwind'
219
+ ? [
220
+ '',
221
+ ' // Entry stylesheet handed to Tailwind, to add a @theme block for',
222
+ ' // instance.',
223
+ ` // source: '@import "tailwindcss";',`,
224
+ ]
225
+ : []),
153
226
  ' },',
154
227
  '',
155
- " // 'auto': the sidebar follows the file tree and the 01-, 02- prefixes.",
228
+ " // 'auto': the menu follows the folders and the 01-, 02- prefixes.",
156
229
  " sidebar: 'auto',",
157
230
  '',
231
+ ' // The shipped components — Card, Columns, Tooltip… — usable in any .mdx',
232
+ ' // page without an import. false removes them, to use your own names.',
158
233
  ' globalComponents: true,',
234
+ '',
235
+ ' // Back-to-top button on every page.',
236
+ ' scrollToTop: true,',
237
+ '',
238
+ ' // Structured data (JSON-LD) generated from the frontmatter of each page.',
159
239
  ' jsonld: { enabled: true },',
160
240
  '};',
161
241
  '',
162
- );
242
+ '// Every field is described in the reference of the DocPensieve documentation:',
243
+ `// the DocPensieve section of your site, or ${DOCUMENTATION_URL}`,
244
+ '',
245
+ ];
163
246
 
164
247
  return lines.join('\n');
165
248
  }
@@ -180,9 +263,10 @@ function versionSlug(version) {
180
263
 
181
264
  /**
182
265
  * @param {string} name Project name, for the title of the home page.
266
+ * @param {boolean} docs Whether DocPensieve's documentation is installed.
183
267
  * @returns {string}
184
268
  */
185
- const renderIndex = (name) => `---
269
+ const renderIndex = (name, docs) => `---
186
270
  title: Introduction
187
271
  description: Documentation of ${name}.
188
272
  date: ${new Date().toISOString().slice(0, 10)}
@@ -202,7 +286,17 @@ Pages live in \`docs/\`. The \`01-\` prefix of a file orders the menu
202
286
  without appearing in the URL.
203
287
 
204
288
  See the [installation guide](/guide/installation/).
205
- `;
289
+ ${
290
+ docs
291
+ ? `
292
+ ## Learning DocPensieve
293
+
294
+ The [DocPensieve](/docpensieve/) section of the menu is the documentation of the
295
+ tool that builds this site, installed along with it. Delete its folder,
296
+ \`${DOCS_FOLDER}\`, when you no longer need it.
297
+ `
298
+ : ''
299
+ }`;
206
300
 
207
301
  /** @returns {string} Sample page, showing ordering and highlighting. */
208
302
  const renderGuide = () => `---
@@ -227,23 +321,85 @@ npm run dev
227
321
  \`\`\`
228
322
  `;
229
323
 
324
+ /**
325
+ * @param {string} slug Version slug, to name the folder to delete.
326
+ * @returns {string} Entry page of the installed documentation section.
327
+ */
328
+ const renderDocsIndex = (slug) => `---
329
+ title: DocPensieve
330
+ description: Documentation of the tool this site is built with, installed along with it.
331
+ ---
332
+
333
+ # DocPensieve
334
+
335
+ This section is the documentation of DocPensieve, the tool this site is built
336
+ with. \`docpensieve init\` installed it, and it matches the version you use.
337
+
338
+ - [Guide](./guide/) — from installation to deployment, in order.
339
+ - [Components](./components/) — the components usable in any page.
340
+ - [Reference](./reference/) — commands, configuration, frontmatter and theme.
341
+ - [Architecture](./architecture/) — how a page becomes HTML.
342
+
343
+ When you no longer need it, delete the \`docs/${slug}/${DOCS_FOLDER}\` folder:
344
+ nothing else depends on it.
345
+ `;
346
+
347
+ /**
348
+ * Folder holding DocPensieve's documentation, ready to be installed.
349
+ *
350
+ * The published package carries it in `starter/`, copied at packing time from
351
+ * the documentation of its own version. In this repository, outside packing,
352
+ * the same pages are read straight from `docs/`.
353
+ *
354
+ * @returns {string | null} The folder, or `null` when neither exists.
355
+ */
356
+ function documentationSource() {
357
+ const packed = fileURLToPath(new URL('../../starter/', import.meta.url));
358
+ if (existsSync(packed)) return packed;
359
+
360
+ const { version } = createRequire(import.meta.url)('../../package.json');
361
+ const [major, minor] = String(version).split('.');
362
+ const repository = fileURLToPath(
363
+ new URL(`../../../../docs/v${major}.${minor}/`, import.meta.url),
364
+ );
365
+ return existsSync(repository) ? repository : null;
366
+ }
367
+
368
+ /**
369
+ * Copies DocPensieve's documentation into the new project, in its own folder.
370
+ *
371
+ * @param {string} source Folder of the documentation to install.
372
+ * @param {string} target Folder of the section, in the version folder.
373
+ * @param {string} slug Version slug.
374
+ */
375
+ async function installDocumentation(source, target, slug) {
376
+ await mkdir(target, { recursive: true });
377
+ for (const entry of readdirSync(source)) {
378
+ if (NOT_INSTALLED.has(entry)) continue;
379
+ cpSync(path.join(source, entry), path.join(target, entry), { recursive: true });
380
+ }
381
+ await writeFile(path.join(target, 'index.md'), renderDocsIndex(slug), 'utf8');
382
+ }
383
+
230
384
  /**
231
385
  * Sets up a documentation project.
232
386
  *
233
387
  * @param {string} [dir] Target folder, created if needed.
234
388
  * @param {{
235
389
  * name?: string, theme?: string, siteUrl?: string, version?: string,
236
- * yes?: boolean, force?: boolean,
237
- * }} [options]
238
- * @returns {Promise<{ dir: string, theme: string }>}
239
- * @throws {DocPensieveError} Unknown framework, or project already initialised.
390
+ * yes?: boolean, force?: boolean, minimal?: boolean,
391
+ * }} [options] `minimal` leaves DocPensieve's documentation out of the site.
392
+ * @returns {Promise<{ dir: string, theme: string, docs: boolean }>}
393
+ * @throws {DocPensieveError} Unknown framework, project already initialised,
394
+ * or documentation to install missing.
240
395
  */
241
396
  export async function init(dir = '.', options = {}) {
242
397
  const target = path.resolve(dir);
243
398
  const configPath = path.join(target, CONFIG_FILENAME);
399
+ const existing = CONFIG_FILENAMES.filter((name) => existsSync(path.join(target, name)));
244
400
 
245
- if (existsSync(configPath) && !options.force) {
246
- throw new DocPensieveError(`${CONFIG_FILENAME} already exists in ${target}.`, {
401
+ if (existing.length > 0 && !options.force) {
402
+ throw new DocPensieveError(`${existing[0]} already exists in ${target}.`, {
247
403
  hint: 'Use --force to overwrite it, or pick another folder.',
248
404
  });
249
405
  }
@@ -259,23 +415,42 @@ export async function init(dir = '.', options = {}) {
259
415
 
260
416
  const answers = await collect(options);
261
417
 
418
+ // Located before anything is written: a project left half set up would be
419
+ // worse than a clear error.
420
+ const documentation = answers.docs ? documentationSource() : null;
421
+ if (answers.docs && !documentation) {
422
+ throw new DocPensieveError('The DocPensieve documentation to install cannot be found.', {
423
+ hint: 'Reinstall docpensieve, or run init with --minimal to go without it.',
424
+ });
425
+ }
426
+
262
427
  const slug = versionSlug(answers.version);
263
428
  const docsDir = path.join(target, 'docs', slug);
264
429
 
265
- await mkdir(path.join(docsDir, 'guide'), { recursive: true });
430
+ // Overwritten, a project keeps a single configuration file: the one written
431
+ // here. An older spelling left behind would make the next build refuse both.
432
+ for (const name of existing) rmSync(path.join(target, name), { force: true });
433
+
434
+ await mkdir(path.join(docsDir, '01-guide'), { recursive: true });
266
435
  await writeFile(configPath, renderConfig(answers), 'utf8');
267
- await writeFile(path.join(docsDir, 'index.md'), renderIndex(answers.name), 'utf8');
268
- await writeFile(path.join(docsDir, 'guide', '01-installation.md'), renderGuide(), 'utf8');
436
+ await writeFile(path.join(docsDir, 'index.md'), renderIndex(answers.name, answers.docs), 'utf8');
437
+ await writeFile(path.join(docsDir, '01-guide', '01-installation.md'), renderGuide(), 'utf8');
438
+ if (documentation) {
439
+ await installDocumentation(documentation, path.join(docsDir, DOCS_FOLDER), slug);
440
+ }
269
441
  await ignoreOutput(target);
270
442
 
271
443
  console.log(`\nProject initialised in ${target}`);
272
444
  console.log(` ${CONFIG_FILENAME}`);
273
445
  console.log(` docs/${slug}/index.md`);
274
- console.log(` docs/${slug}/guide/01-installation.md`);
446
+ console.log(` docs/${slug}/01-guide/01-installation.md`);
447
+ if (documentation) {
448
+ console.log(` docs/${slug}/${DOCS_FOLDER}/ DocPensieve's documentation, to delete when done`);
449
+ }
275
450
  console.log(`\nTheme: ${answers.theme} — ${FRAMEWORK_LABELS[answers.theme]}`);
276
451
  console.log('\nNext: npx docpensieve dev');
277
452
 
278
- return { dir: target, theme: answers.theme };
453
+ return { dir: target, theme: answers.theme, docs: answers.docs };
279
454
  }
280
455
 
281
456
  /**
@@ -0,0 +1,79 @@
1
+ ---
2
+ title: Installation
3
+ description: What you need, and how to set up a documentation project.
4
+ date: 2026-09-09
5
+ tags: [guide, installation]
6
+
7
+ jsonld:
8
+ type: TechArticle
9
+ breadcrumbs: true
10
+ ---
11
+
12
+ # Installation
13
+
14
+ ## What you need
15
+
16
+ **Node.js 22 or later.** It is the only requirement. The generator runs at
17
+ build time, not on the reader's side: nothing else to install on the server
18
+ that will host the site, which only has to serve files.
19
+
20
+ ## Setting up a project
21
+
22
+ ```bash
23
+ npx docpensieve init my-site
24
+ cd my-site
25
+ ```
26
+
27
+ The command creates the folder and writes a configuration, a first
28
+ documentation folder and a home page into it. It asks a few questions; `--yes`
29
+ skips them and accepts the defaults.
30
+
31
+ It also installs this very documentation, in a **DocPensieve** section at the
32
+ end of the new site's menu. It matches the version you installed, and its
33
+ folder, `99-docpensieve`, can be deleted as soon as you no longer need it;
34
+ `--minimal` leaves it out. The configuration file lists every option, each with
35
+ a comment, set to its default or given as an example.
36
+
37
+ ```bash
38
+ npx docpensieve init my-site --yes --name "My documentation"
39
+ ```
40
+
41
+ | Option | Effect |
42
+ | -------------------------- | ------------------------------------------------------- |
43
+ | `-n, --name <name>` | Project name, shown in the header |
44
+ | `-t, --theme <framework>` | `tailwind` or `custom` |
45
+ | `-u, --site-url <url>` | Public URL, from which the deployment prefix is derived |
46
+ | `--version-name <version>` | First version, `1.0` for instance |
47
+ | `-y, --yes` | Accepts the defaults without a dialogue |
48
+ | `-f, --force` | Overwrites an existing configuration |
49
+ | `--minimal` | Leaves DocPensieve's documentation out of the site |
50
+
51
+ ## In an existing project
52
+
53
+ ```bash
54
+ npm install docpensieve
55
+ npx docpensieve init . --force
56
+ ```
57
+
58
+ `init` on an occupied folder refuses to overwrite an existing configuration:
59
+ you have to ask for it with `--force`. The refusal is deliberate — a
60
+ configuration overwritten by mistake is only noticed at the next deployment.
61
+
62
+ ## Choosing the theme
63
+
64
+ `tailwind` is the default and installs Tailwind as a dependency. `custom` does
65
+ without it entirely: the site is then styled by a stylesheet written in the
66
+ package, with no styling dependency.
67
+
68
+ Both are equivalent in use — the templates are the same, only the styling
69
+ changes. The choice is not final: it fits in one field of the configuration,
70
+ described in [Themes](./themes/).
71
+
72
+ ## Checking
73
+
74
+ ```bash
75
+ npx docpensieve build
76
+ ```
77
+
78
+ If the output folder appears with an `index.html` inside, everything is in
79
+ place. Next: [First site](./first-site/).
@@ -0,0 +1,97 @@
1
+ ---
2
+ title: First site
3
+ description: Build, serve, and understand what was produced.
4
+ tags: [guide]
5
+
6
+ jsonld:
7
+ type: TechArticle
8
+ breadcrumbs: true
9
+ ---
10
+
11
+ # First site
12
+
13
+ ## Building
14
+
15
+ ```bash
16
+ npx docpensieve build
17
+ ```
18
+
19
+ Every declared version is built. To build just one, pass its slug:
20
+
21
+ ```bash
22
+ npx docpensieve build v1.0
23
+ ```
24
+
25
+ ## Looking at the result
26
+
27
+ ```bash
28
+ npx docpensieve serve
29
+ ```
30
+
31
+ The output folder is served statically on port 4000. It is exactly what a host
32
+ will do: no difference between this server and going live.
33
+
34
+ ## Working
35
+
36
+ ```bash
37
+ npx docpensieve dev
38
+ ```
39
+
40
+ The development server watches the sources and rebuilds on every save. It
41
+ listens on port 3000; `--port` changes that.
42
+
43
+ The difference with `serve` is this: `dev` rebuilds, `serve` only serves. To
44
+ check what will really be published, chain `build` then `serve`.
45
+
46
+ ## What was produced
47
+
48
+ ```
49
+ dist/
50
+ ├── index.html redirect to the current version
51
+ ├── versions.json the versions and their URLs
52
+ └── versions/
53
+ └── v1.0/
54
+ ├── index.html the home page of the version
55
+ ├── assets/
56
+ │ └── docpensieve.css
57
+ └── guide/
58
+ └── installation/
59
+ └── index.html
60
+ ```
61
+
62
+ Three things are worth noticing.
63
+
64
+ **Each page is a folder with an `index.html`.** That is what gives URLs without
65
+ an extension — `/guide/installation/` rather than `/guide/installation.html` —
66
+ without asking anything of the host.
67
+
68
+ **A single stylesheet for the whole version.** It is compiled last, once the
69
+ pages are written, because a utility theme needs to know which classes were
70
+ actually used in order to emit only those.
71
+
72
+ **The `versions.json` at the root** describes the available versions. It is
73
+ what lets versions built long ago stay online without ever rebuilding them.
74
+
75
+ ## Reading the output back
76
+
77
+ A successful build says nothing of a dead link: nothing in the chain looks at
78
+ them. A command takes care of it:
79
+
80
+ ```bash
81
+ npx docpensieve build
82
+ npx docpensieve check
83
+ ```
84
+
85
+ It walks the produced pages, removes the deployment prefix from every internal
86
+ target and checks that the file exists. It also reports the targets that
87
+ **ignore** that prefix — the file is there, but the link will lead nowhere once
88
+ online.
89
+
90
+ ```
91
+ index.html
92
+ /guide/installation/
93
+ → ignores the deployment prefix "/my-project/"
94
+ ```
95
+
96
+ It is the check to run after any change of `baseUrl`, of folder structure or of
97
+ URL.