@dominikcz/greg 0.9.34 → 0.9.36
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/README.md +28 -1
- package/bin/init.js +59 -21
- package/bin/templates/greg.config.full.js +32 -0
- package/bin/templates/greg.config.full.ts +31 -0
- package/bin/templates/greg.config.js +7 -31
- package/bin/templates/greg.config.ts +7 -31
- package/docs/guide/getting-started.md +3 -1
- package/docs/guide/versioning.md +11 -0
- package/docs/reference/markdowndocs.md +17 -0
- package/package.json +1 -1
- package/scripts/build-versions.js +27 -3
- package/scripts/generate-static.js +9 -2
- package/src/lib/MarkdownDocs/MarkdownDocs.svelte +25 -0
- package/src/lib/MarkdownDocs/loadGregConfig.js +1 -38
- package/src/lib/MarkdownDocs/vitePluginGregConfig.js +11 -9
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ For unattended/default setup (no prompts):
|
|
|
16
16
|
npx @dominikcz/greg init --defaults
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The interactive wizard
|
|
19
|
+
The interactive wizard asks for docs path, site title, TypeScript preference, config mode (minimal or full), and starter docs type (empty, sample, or generated fake docs). It also creates a sensible `.gitignore`, copies default `public/` assets, and can install all required dependencies for you.
|
|
20
20
|
|
|
21
21
|
At the end you only need:
|
|
22
22
|
|
|
@@ -299,6 +299,33 @@ export default {
|
|
|
299
299
|
|
|
300
300
|
To override those labels per locale, use `versioning.locales`:
|
|
301
301
|
|
|
302
|
+
### Cache SHA length (branch mode)
|
|
303
|
+
|
|
304
|
+
In `branches` strategy, Greg stores build cache under `.greg/version-cache/builds/...` using a short commit SHA prefix.
|
|
305
|
+
|
|
306
|
+
- default prefix length: `7`
|
|
307
|
+
- configurable in `greg.config.* > versioning.cacheShaLength`
|
|
308
|
+
- can be overridden per run via env `GREG_CACHE_SHA_LENGTH`
|
|
309
|
+
- allowed range: `7..40`
|
|
310
|
+
- cache folder key format: `<shortSha>-<workspaceBuildFingerprint>`
|
|
311
|
+
- the suffix avoids stale cache reuse when local build inputs/config changed but branch commit SHA stayed the same
|
|
312
|
+
|
|
313
|
+
Example: if `main` still points to the same commit, but you changed `vite.config.js` or Markdown build plugins locally, Greg uses a different cache folder because the workspace fingerprint changes.
|
|
314
|
+
|
|
315
|
+
Example:
|
|
316
|
+
|
|
317
|
+
```js [greg.config.js]
|
|
318
|
+
export default {
|
|
319
|
+
versioning: {
|
|
320
|
+
cacheShaLength: 12,
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
```sh
|
|
326
|
+
GREG_CACHE_SHA_LENGTH=16 greg build
|
|
327
|
+
```
|
|
328
|
+
|
|
302
329
|
```js [greg.config.js]
|
|
303
330
|
export default {
|
|
304
331
|
versioning: {
|
package/bin/init.js
CHANGED
|
@@ -18,6 +18,7 @@ import { fork, spawnSync } from 'node:child_process';
|
|
|
18
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
19
|
const cliArgs = new Set(process.argv.slice(2));
|
|
20
20
|
const useDefaultsMode = cliArgs.has('--defaults') || cliArgs.has('--yes');
|
|
21
|
+
const forceLocalDependency = cliArgs.has('--local-dependency') || process.env.GREG_INIT_LOCAL_DEP === '1';
|
|
21
22
|
|
|
22
23
|
// ── ANSI helpers (used for file-creation log lines) ───────────────────────────
|
|
23
24
|
const g = (s) => `\x1b[32m${s}\x1b[0m`; // green
|
|
@@ -120,12 +121,7 @@ function isTransientCacheSpec(spec) {
|
|
|
120
121
|
|
|
121
122
|
function resolveGregDependencySpec(version) {
|
|
122
123
|
const packageRoot = join(__dirname, '..');
|
|
123
|
-
|
|
124
|
-
stdio: 'pipe',
|
|
125
|
-
shell: true,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
if (npmView.status === 0) {
|
|
124
|
+
if (!forceLocalDependency) {
|
|
129
125
|
return { spec: `^${version}`, source: 'registry' };
|
|
130
126
|
}
|
|
131
127
|
|
|
@@ -158,6 +154,7 @@ async function main() {
|
|
|
158
154
|
let desc = 'Documentation';
|
|
159
155
|
let useTS = true;
|
|
160
156
|
let addScripts = true;
|
|
157
|
+
let fullConfig = false;
|
|
161
158
|
let docsType = '1';
|
|
162
159
|
|
|
163
160
|
if (!useDefaultsMode) {
|
|
@@ -166,6 +163,7 @@ async function main() {
|
|
|
166
163
|
desc = orCancel(await p.text({ message: 'Site description', initialValue: 'Documentation' }));
|
|
167
164
|
useTS = orCancel(await p.confirm({ message: 'Use TypeScript for configuration?', initialValue: true }));
|
|
168
165
|
addScripts = orCancel(await p.confirm({ message: 'Add greg scripts to package.json?', initialValue: true }));
|
|
166
|
+
fullConfig = orCancel(await p.confirm({ message: 'Generate full greg.config template?', initialValue: false }));
|
|
169
167
|
docsType = orCancel(await p.select({
|
|
170
168
|
message: 'Documentation contents',
|
|
171
169
|
options: [
|
|
@@ -189,6 +187,9 @@ async function main() {
|
|
|
189
187
|
const rootPath = '';
|
|
190
188
|
const ext = useTS ? 'ts' : 'js';
|
|
191
189
|
const vars = { TITLE: title, DESCRIPTION: desc, DOCS_DIR: docsDir, ROOT_PATH: rootPath, EXT: ext };
|
|
190
|
+
const gregConfigTemplate = useTS
|
|
191
|
+
? (fullConfig ? 'greg.config.full.ts' : 'greg.config.ts')
|
|
192
|
+
: (fullConfig ? 'greg.config.full.js' : 'greg.config.js');
|
|
192
193
|
|
|
193
194
|
p.log.step('Creating files…');
|
|
194
195
|
|
|
@@ -199,7 +200,14 @@ async function main() {
|
|
|
199
200
|
ensure('src/app.css', tpl('src/app.css'));
|
|
200
201
|
ensure(`vite.config.${ext}`, tpl('vite.config.js', vars)); // same content for .js and .ts
|
|
201
202
|
ensure('svelte.config.js', tpl('svelte.config.js')); // always .js — vite-plugin-svelte requires it
|
|
202
|
-
ensure(`greg.config.${ext}`, tpl(
|
|
203
|
+
ensure(`greg.config.${ext}`, tpl(gregConfigTemplate, vars));
|
|
204
|
+
ensure('.gitignore', tpl('.gitignore'));
|
|
205
|
+
const packagePublicDir = join(__dirname, '..', 'public');
|
|
206
|
+
if (existsSync(packagePublicDir)) {
|
|
207
|
+
copyRawDir(packagePublicDir, 'public');
|
|
208
|
+
} else {
|
|
209
|
+
p.log.warn('public assets not found in package; skipping public/ scaffold');
|
|
210
|
+
}
|
|
203
211
|
|
|
204
212
|
if (useTS) {
|
|
205
213
|
ensure('tsconfig.json', tpl('tsconfig.json'));
|
|
@@ -244,7 +252,7 @@ async function main() {
|
|
|
244
252
|
let changed = false;
|
|
245
253
|
if (addScripts) {
|
|
246
254
|
pkg.scripts ??= {};
|
|
247
|
-
for (const [k, v] of [['dev', 'greg dev'], ['build', 'greg build'], ['preview', 'greg preview']]) {
|
|
255
|
+
for (const [k, v] of [['greg', 'greg'], ['dev', 'greg dev'], ['build', 'greg build'], ['preview', 'greg preview']]) {
|
|
248
256
|
if (!pkg.scripts[k]) { pkg.scripts[k] = v; changed = true; }
|
|
249
257
|
}
|
|
250
258
|
}
|
|
@@ -259,6 +267,18 @@ async function main() {
|
|
|
259
267
|
pkg.devDependencies['@dominikcz/greg'] = gregDependencySpec;
|
|
260
268
|
changed = true;
|
|
261
269
|
}
|
|
270
|
+
const peerDepsMap = {
|
|
271
|
+
'@sveltejs/vite-plugin-svelte': gregDevDeps['@sveltejs/vite-plugin-svelte'] ?? '^6',
|
|
272
|
+
svelte: gregDevDeps.svelte ?? '^5',
|
|
273
|
+
vite: gregDevDeps.vite ?? '^7',
|
|
274
|
+
...(useTS ? { typescript: '^5' } : {}),
|
|
275
|
+
};
|
|
276
|
+
for (const [depName, depVersion] of Object.entries(peerDepsMap)) {
|
|
277
|
+
if (!pkg.devDependencies[depName]) {
|
|
278
|
+
pkg.devDependencies[depName] = depVersion;
|
|
279
|
+
changed = true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
262
282
|
if (changed) {
|
|
263
283
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
264
284
|
p.log.success('package.json ' + d('(updated)'));
|
|
@@ -273,8 +293,14 @@ async function main() {
|
|
|
273
293
|
name: 'my-docs',
|
|
274
294
|
version: '0.0.1',
|
|
275
295
|
type: 'module',
|
|
276
|
-
...(addScripts ? { scripts: { dev: 'greg dev', build: 'greg build', preview: 'greg preview' } } : {}),
|
|
277
|
-
devDependencies: {
|
|
296
|
+
...(addScripts ? { scripts: { greg: 'greg', dev: 'greg dev', build: 'greg build', preview: 'greg preview' } } : {}),
|
|
297
|
+
devDependencies: {
|
|
298
|
+
'@dominikcz/greg': gregDependencySpec,
|
|
299
|
+
'@sveltejs/vite-plugin-svelte': gregDevDeps['@sveltejs/vite-plugin-svelte'] ?? '^6',
|
|
300
|
+
svelte: gregDevDeps.svelte ?? '^5',
|
|
301
|
+
vite: gregDevDeps.vite ?? '^7',
|
|
302
|
+
...(useTS ? { typescript: '^5' } : {}),
|
|
303
|
+
},
|
|
278
304
|
};
|
|
279
305
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
280
306
|
console.log(` ${g('+')} package.json`);
|
|
@@ -283,16 +309,8 @@ async function main() {
|
|
|
283
309
|
|
|
284
310
|
// ── Install dependencies ──────────────────────────────────────────────────
|
|
285
311
|
// @dominikcz/greg is already in package.json devDependencies — install only peer deps.
|
|
286
|
-
const peerDepsArr = [
|
|
287
|
-
`@sveltejs/vite-plugin-svelte@${gregDevDeps['@sveltejs/vite-plugin-svelte'] ?? '^6'}`,
|
|
288
|
-
`svelte@${gregDevDeps.svelte ?? '^5'}`,
|
|
289
|
-
`vite@${gregDevDeps.vite ?? '^7'}`,
|
|
290
|
-
...(useTS ? ['typescript'] : []),
|
|
291
|
-
];
|
|
292
312
|
const pm = detectPm();
|
|
293
|
-
const installArgs =
|
|
294
|
-
? ['install', '--save-dev', ...peerDepsArr]
|
|
295
|
-
: ['add', '-D', ...peerDepsArr];
|
|
313
|
+
const installArgs = ['install'];
|
|
296
314
|
|
|
297
315
|
const installNow = useDefaultsMode
|
|
298
316
|
? true
|
|
@@ -325,8 +343,8 @@ async function main() {
|
|
|
325
343
|
|
|
326
344
|
p.outro(
|
|
327
345
|
installNow
|
|
328
|
-
? `${b(g('Done!'))} Start your project:\n\n ${c('npm run dev')}`
|
|
329
|
-
: `${b(g('Done!'))} Next steps:\n\n ${c(`${pm} ${installArgs.join(' ')}`)}\n ${c('npm run dev')}`
|
|
346
|
+
? `${b(g('Done!'))} Start your project:\n\n ${c('npm run dev')}\n ${c('npx greg --help')}`
|
|
347
|
+
: `${b(g('Done!'))} Next steps:\n\n ${c(`${pm} ${installArgs.join(' ')}`)}\n ${c('npm run dev')}\n ${c('npx greg --help')}`
|
|
330
348
|
);
|
|
331
349
|
}
|
|
332
350
|
|
|
@@ -346,6 +364,26 @@ function copyTemplateDir(srcDir, destRel, vars) {
|
|
|
346
364
|
}
|
|
347
365
|
}
|
|
348
366
|
|
|
367
|
+
function copyRawDir(srcDir, destRel) {
|
|
368
|
+
for (const entry of readdirSync(srcDir)) {
|
|
369
|
+
const srcPath = join(srcDir, entry);
|
|
370
|
+
const destPath = join(destRel, entry);
|
|
371
|
+
if (statSync(srcPath).isDirectory()) {
|
|
372
|
+
copyRawDir(srcPath, destPath);
|
|
373
|
+
} else {
|
|
374
|
+
const full = join(cwd, destPath);
|
|
375
|
+
if (existsSync(full)) {
|
|
376
|
+
console.log(` ${y('~')} ${destPath} ${d('(skipped – already exists)')}`);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
const dir = dirname(full);
|
|
380
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
381
|
+
writeFileSync(full, readFileSync(srcPath));
|
|
382
|
+
console.log(` ${g('+')} ${destPath}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
349
387
|
main().catch((err) => {
|
|
350
388
|
console.error(err);
|
|
351
389
|
process.exit(1);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('@dominikcz/greg').GregConfig}
|
|
3
|
+
*/
|
|
4
|
+
export default {
|
|
5
|
+
base: '/',
|
|
6
|
+
outDir: 'dist',
|
|
7
|
+
srcDir: '{{DOCS_DIR}}',
|
|
8
|
+
docsBase: '',
|
|
9
|
+
mainTitle: '{{TITLE}}',
|
|
10
|
+
useDynamicPageTitle: true,
|
|
11
|
+
sidebar: 'auto',
|
|
12
|
+
outline: [2, 3],
|
|
13
|
+
nav: [
|
|
14
|
+
{ text: 'Guide', link: '/guide' },
|
|
15
|
+
{ text: 'Reference', link: '/reference' },
|
|
16
|
+
],
|
|
17
|
+
search: {
|
|
18
|
+
provider: 'local',
|
|
19
|
+
fuzzy: {
|
|
20
|
+
threshold: 0.35,
|
|
21
|
+
minMatchCharLength: 2,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
// versioning: {
|
|
25
|
+
// strategy: 'branches',
|
|
26
|
+
// default: 'latest',
|
|
27
|
+
// aliases: { latest: '1.0' },
|
|
28
|
+
// branches: [
|
|
29
|
+
// { version: '1.0', branch: 'main', title: '1.0' },
|
|
30
|
+
// ],
|
|
31
|
+
// },
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { GregConfig } from '@dominikcz/greg'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
base: '/',
|
|
5
|
+
outDir: 'dist',
|
|
6
|
+
srcDir: '{{DOCS_DIR}}',
|
|
7
|
+
docsBase: '',
|
|
8
|
+
mainTitle: '{{TITLE}}',
|
|
9
|
+
useDynamicPageTitle: true,
|
|
10
|
+
sidebar: 'auto',
|
|
11
|
+
outline: [2, 3],
|
|
12
|
+
nav: [
|
|
13
|
+
{ text: 'Guide', link: '/guide' },
|
|
14
|
+
{ text: 'Reference', link: '/reference' },
|
|
15
|
+
],
|
|
16
|
+
search: {
|
|
17
|
+
provider: 'local',
|
|
18
|
+
fuzzy: {
|
|
19
|
+
threshold: 0.35,
|
|
20
|
+
minMatchCharLength: 2,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
// versioning: {
|
|
24
|
+
// strategy: 'branches',
|
|
25
|
+
// default: 'latest',
|
|
26
|
+
// aliases: { latest: '1.0' },
|
|
27
|
+
// branches: [
|
|
28
|
+
// { version: '1.0', branch: 'main', title: '1.0' },
|
|
29
|
+
// ],
|
|
30
|
+
// },
|
|
31
|
+
} satisfies GregConfig
|
|
@@ -2,38 +2,14 @@
|
|
|
2
2
|
* @type {import('@dominikcz/greg').GregConfig}
|
|
3
3
|
*/
|
|
4
4
|
export default {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
srcDir: '',
|
|
9
|
-
docsBase: '',
|
|
5
|
+
base: '/',
|
|
6
|
+
outDir: 'dist',
|
|
7
|
+
srcDir: '{{DOCS_DIR}}',
|
|
10
8
|
mainTitle: '{{TITLE}}',
|
|
9
|
+
// useDynamicPageTitle: true,
|
|
11
10
|
sidebar: 'auto',
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// aliases: {
|
|
16
|
-
// latest: '2.1',
|
|
17
|
-
// stable: '2.0',
|
|
18
|
-
// },
|
|
19
|
-
// ui: {
|
|
20
|
-
// versionMenuLabel: 'Version',
|
|
21
|
-
// manifestUnavailableText: 'Version selector unavailable',
|
|
22
|
-
// showManifestUnavailableStatus: false,
|
|
23
|
-
// outdatedVersionMessage: 'You are viewing an older version ({current}). Recommended: {default}.',
|
|
24
|
-
// outdatedVersionActionLabel: 'Go to latest',
|
|
25
|
-
// },
|
|
26
|
-
// locales: {
|
|
27
|
-
// '/': {
|
|
28
|
-
// ui: { versionMenuLabel: 'Version' },
|
|
29
|
-
// },
|
|
30
|
-
// '/pl/': {
|
|
31
|
-
// ui: { versionMenuLabel: 'Wersja' },
|
|
32
|
-
// },
|
|
33
|
-
// },
|
|
34
|
-
// branches: [
|
|
35
|
-
// { version: '2.1', branch: 'main', title: '2.1' },
|
|
36
|
-
// { version: '2.0', branch: 'release/2.0', title: '2.0' },
|
|
37
|
-
// ],
|
|
11
|
+
// search: {
|
|
12
|
+
// provider: 'local',
|
|
13
|
+
// // fuzzy: { threshold: 0.35, minMatchCharLength: 2 },
|
|
38
14
|
// },
|
|
39
15
|
}
|
|
@@ -1,38 +1,14 @@
|
|
|
1
1
|
import type { GregConfig } from '@dominikcz/greg'
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
srcDir: '',
|
|
8
|
-
docsBase: '',
|
|
4
|
+
base: '/',
|
|
5
|
+
outDir: 'dist',
|
|
6
|
+
srcDir: '{{DOCS_DIR}}',
|
|
9
7
|
mainTitle: '{{TITLE}}',
|
|
8
|
+
// useDynamicPageTitle: true,
|
|
10
9
|
sidebar: 'auto',
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// aliases: {
|
|
15
|
-
// latest: '2.1',
|
|
16
|
-
// stable: '2.0',
|
|
17
|
-
// },
|
|
18
|
-
// ui: {
|
|
19
|
-
// versionMenuLabel: 'Version',
|
|
20
|
-
// manifestUnavailableText: 'Version selector unavailable',
|
|
21
|
-
// showManifestUnavailableStatus: false,
|
|
22
|
-
// outdatedVersionMessage: 'You are viewing an older version ({current}). Recommended: {default}.',
|
|
23
|
-
// outdatedVersionActionLabel: 'Go to latest',
|
|
24
|
-
// },
|
|
25
|
-
// locales: {
|
|
26
|
-
// '/': {
|
|
27
|
-
// ui: { versionMenuLabel: 'Version' },
|
|
28
|
-
// },
|
|
29
|
-
// '/pl/': {
|
|
30
|
-
// ui: { versionMenuLabel: 'Wersja' },
|
|
31
|
-
// },
|
|
32
|
-
// },
|
|
33
|
-
// branches: [
|
|
34
|
-
// { version: '2.1', branch: 'main', title: '2.1' },
|
|
35
|
-
// { version: '2.0', branch: 'release/2.0', title: '2.0' },
|
|
36
|
-
// ],
|
|
10
|
+
// search: {
|
|
11
|
+
// provider: 'local',
|
|
12
|
+
// // fuzzy: { threshold: 0.35, minMatchCharLength: 2 },
|
|
37
13
|
// },
|
|
38
14
|
} satisfies GregConfig
|
|
@@ -55,11 +55,13 @@ The wizard asks a handful of questions:
|
|
|
55
55
|
| **Site title** | Shown in the browser tab and the site header |
|
|
56
56
|
| **Description** | Used in the HTML `<meta>` tag |
|
|
57
57
|
| **TypeScript** | Creates `.ts` config files instead of `.js` |
|
|
58
|
-
| **
|
|
58
|
+
| **Config mode** | Choose **minimal** (default) or **full** `greg.config` template |
|
|
59
|
+
| **Add npm scripts** | Adds `greg`, `dev`, `build` and `preview` to `package.json` |
|
|
59
60
|
| **Starter content** | _Empty_ — bare minimum; _Sample_ — example pages; _Generated_ — large fake docs set |
|
|
60
61
|
| **Install now** | Runs `npm install` (or your package manager) right away |
|
|
61
62
|
|
|
62
63
|
When it finishes, you will see a summary of created files.
|
|
64
|
+
The scaffold also includes a default `.gitignore` and copies baseline assets into `public/`.
|
|
63
65
|
|
|
64
66
|
### 3. Start the development server
|
|
65
67
|
|
package/docs/guide/versioning.md
CHANGED
|
@@ -190,6 +190,17 @@ Optional maintenance flags:
|
|
|
190
190
|
- `--clean-versions`: removes the versioned output directory before building
|
|
191
191
|
- `--rebuild-all`: rebuilds every configured version and skips branch build cache reuse for this run
|
|
192
192
|
|
|
193
|
+
Cache SHA prefix length in branch build cache:
|
|
194
|
+
|
|
195
|
+
- default: `7`
|
|
196
|
+
- config: `versioning.cacheShaLength`
|
|
197
|
+
- env override: `GREG_CACHE_SHA_LENGTH`
|
|
198
|
+
- valid range: `7..40`
|
|
199
|
+
- cache key format: `<shortSha>-<workspaceBuildFingerprint>`
|
|
200
|
+
- the suffix prevents stale cache hits when build inputs/config changed but commit SHA did not
|
|
201
|
+
|
|
202
|
+
Example: if branch `main` still resolves to the same commit, but you changed local build inputs (for example `vite.config.js` or Markdown build plugins), Greg writes to a different cache folder because the workspace fingerprint changes.
|
|
203
|
+
|
|
193
204
|
## UI Components
|
|
194
205
|
|
|
195
206
|
Greg renders two visual versioning components automatically when a valid manifest is available:
|
|
@@ -133,6 +133,23 @@ Name of the project shown in the top-left header.
|
|
|
133
133
|
<MarkdownDocs srcDir="/" version="1.0.0" mainTitle="My Project" />
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
+
### `useDynamicPageTitle`
|
|
137
|
+
|
|
138
|
+
- **Type:** `boolean`
|
|
139
|
+
- **Default:** `true`
|
|
140
|
+
|
|
141
|
+
Controls browser tab title behavior:
|
|
142
|
+
|
|
143
|
+
- `true` (default): tab title follows active page title (for example `Getting Started | My Project`)
|
|
144
|
+
- `false`: tab title stays fixed to `mainTitle`
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
export default {
|
|
148
|
+
mainTitle: 'My Project',
|
|
149
|
+
useDynamicPageTitle: false,
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
136
153
|
### `outline`
|
|
137
154
|
|
|
138
155
|
- **Type:** `false | number | [number, number] | 'deep' | { level?: …, label?: string }`
|
package/package.json
CHANGED
|
@@ -166,7 +166,6 @@ function computeWorkspaceBuildFingerprint() {
|
|
|
166
166
|
'vite.config.ts',
|
|
167
167
|
'greg.config.js',
|
|
168
168
|
'greg.config.ts',
|
|
169
|
-
'prv/greg.config.js',
|
|
170
169
|
'package.json',
|
|
171
170
|
'src/main.js',
|
|
172
171
|
'src/App.svelte',
|
|
@@ -201,6 +200,28 @@ function getBranchSha(refName) {
|
|
|
201
200
|
return String(out.stdout || '').trim();
|
|
202
201
|
}
|
|
203
202
|
|
|
203
|
+
function toShortSha(sha, length = 7) {
|
|
204
|
+
return String(sha || '').slice(0, length);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function resolveCacheShaLength(versioning) {
|
|
208
|
+
const DEFAULT_LENGTH = 7;
|
|
209
|
+
const MIN_LENGTH = 7;
|
|
210
|
+
const MAX_LENGTH = 40;
|
|
211
|
+
const raw = process.env.GREG_CACHE_SHA_LENGTH ?? versioning.cacheShaLength;
|
|
212
|
+
|
|
213
|
+
if (raw == null || String(raw).trim() === '') {
|
|
214
|
+
return DEFAULT_LENGTH;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const parsed = Number.parseInt(String(raw), 10);
|
|
218
|
+
if (!Number.isInteger(parsed) || parsed < MIN_LENGTH || parsed > MAX_LENGTH) {
|
|
219
|
+
throw new Error(`Invalid cache SHA length '${raw}'. Use an integer between ${MIN_LENGTH} and ${MAX_LENGTH}.`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return parsed;
|
|
223
|
+
}
|
|
224
|
+
|
|
204
225
|
function ensureBranchDocsSnapshot(args) {
|
|
205
226
|
const { branch, sha, docsDir, branchCacheDir } = args;
|
|
206
227
|
const docsRel = String(docsDir || 'docs').replace(/^\/+|\/+$/g, '') || 'docs';
|
|
@@ -252,6 +273,7 @@ function runViteBuild(args) {
|
|
|
252
273
|
GREG_DOCS_DIR: docsDir,
|
|
253
274
|
GREG_DOCS_BASE: srcDir,
|
|
254
275
|
GREG_ROOT_PATH: srcDir,
|
|
276
|
+
GREG_OUT_DIR: outDir,
|
|
255
277
|
PATH:
|
|
256
278
|
path.resolve(PROJECT_ROOT, 'node_modules/.bin') +
|
|
257
279
|
(process.platform === 'win32' ? ';' : ':') +
|
|
@@ -259,7 +281,7 @@ function runViteBuild(args) {
|
|
|
259
281
|
};
|
|
260
282
|
|
|
261
283
|
console.log(`[greg] versions: vite build -> ${path.relative(PROJECT_ROOT, outDir)}`);
|
|
262
|
-
runCommand('vite', ['build',
|
|
284
|
+
runCommand('vite', ['build', ...passthrough], {
|
|
263
285
|
stdio: 'inherit',
|
|
264
286
|
shell: true,
|
|
265
287
|
env,
|
|
@@ -443,6 +465,7 @@ async function main() {
|
|
|
443
465
|
const args = parseArgs(process.argv.slice(2));
|
|
444
466
|
const config = await loadGregConfig();
|
|
445
467
|
const versioning = config.versioning ?? {};
|
|
468
|
+
const cacheShaLength = resolveCacheShaLength(versioning);
|
|
446
469
|
|
|
447
470
|
const strategy = args.strategy || versioning.strategy || 'branches';
|
|
448
471
|
validateVersioningConfig(versioning, strategy);
|
|
@@ -527,6 +550,7 @@ async function main() {
|
|
|
527
550
|
|
|
528
551
|
for (const entry of entries) {
|
|
529
552
|
const sha = getBranchSha(entry.branch);
|
|
553
|
+
const shortSha = toShortSha(sha, cacheShaLength);
|
|
530
554
|
const snapshot = ensureBranchDocsSnapshot({
|
|
531
555
|
branch: entry.branch,
|
|
532
556
|
sha,
|
|
@@ -538,7 +562,7 @@ async function main() {
|
|
|
538
562
|
branchCacheDir,
|
|
539
563
|
'builds',
|
|
540
564
|
sanitizeSegment(entry.version),
|
|
541
|
-
`${
|
|
565
|
+
`${shortSha}-${workspaceBuildFingerprint}`,
|
|
542
566
|
);
|
|
543
567
|
const hasBuildCache = !args.rebuildAll && fs.existsSync(path.join(buildCache, 'index.html'));
|
|
544
568
|
|
|
@@ -27,6 +27,12 @@ const DIST = path.resolve(distDir);
|
|
|
27
27
|
const DOCS = path.resolve(docsDir);
|
|
28
28
|
const ROOT_PATH = srcDir;
|
|
29
29
|
|
|
30
|
+
function normalizeRouteForLog(route) {
|
|
31
|
+
const cleaned = String(route || '').trim().replace(/\\/g, '/').replace(/\/+/g, '/');
|
|
32
|
+
if (!cleaned || cleaned === '/') return '/';
|
|
33
|
+
return '/' + cleaned.replace(/^\/+|\/+$/g, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
// ── Collect routes from the docs/ folder ────────────────────────────────────
|
|
31
37
|
|
|
32
38
|
function collectRoutes(dir, base) {
|
|
@@ -58,7 +64,7 @@ const routes = [...new Set([
|
|
|
58
64
|
|
|
59
65
|
const src = path.join(DIST, 'index.html');
|
|
60
66
|
if (!fs.existsSync(src)) {
|
|
61
|
-
console.error(
|
|
67
|
+
console.error(`${src} not found - run npm run build first.`);
|
|
62
68
|
process.exit(1);
|
|
63
69
|
}
|
|
64
70
|
|
|
@@ -73,7 +79,8 @@ for (const route of routes) {
|
|
|
73
79
|
fs.mkdirSync(outDir, { recursive: true });
|
|
74
80
|
fs.copyFileSync(src, outFile);
|
|
75
81
|
count++;
|
|
76
|
-
|
|
82
|
+
const routeLog = normalizeRouteForLog(route);
|
|
83
|
+
console.log(` ✓ ${routeLog === '/' ? '/index.html' : `${routeLog}/index.html`}`);
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
console.log(`\nStatic export: ${count} routes written to dist/`);
|
|
@@ -87,6 +87,8 @@
|
|
|
87
87
|
children?: Snippet;
|
|
88
88
|
version?: string;
|
|
89
89
|
mainTitle?: string;
|
|
90
|
+
/** Keep browser tab title in sync with active page title. Default: true. */
|
|
91
|
+
useDynamicPageTitle?: boolean;
|
|
90
92
|
carbonAds?: CarbonAdsOptions;
|
|
91
93
|
/** VitePress-compatible outline option. false = disabled, [2,3] = default. */
|
|
92
94
|
outline?: OutlineOption | boolean;
|
|
@@ -206,6 +208,7 @@
|
|
|
206
208
|
),
|
|
207
209
|
version: globalVersion = (gregConfig as any).version ?? "",
|
|
208
210
|
mainTitle: globalMainTitle = (gregConfig as any).mainTitle ?? "Greg",
|
|
211
|
+
useDynamicPageTitle = (gregConfig as any).useDynamicPageTitle ?? true,
|
|
209
212
|
carbonAds = (gregConfig as any).carbonAds,
|
|
210
213
|
outline: globalOutline =
|
|
211
214
|
(gregConfig as any).outline ?? ([2, 3] as [number, number]),
|
|
@@ -1345,6 +1348,28 @@
|
|
|
1345
1348
|
}
|
|
1346
1349
|
|
|
1347
1350
|
const title = $derived(router.title(flat));
|
|
1351
|
+
const browserTitle = $derived.by(() => {
|
|
1352
|
+
if (!useDynamicPageTitle) {
|
|
1353
|
+
const fixedTitle = String(mainTitle ?? "").trim();
|
|
1354
|
+
return fixedTitle || "Greg";
|
|
1355
|
+
}
|
|
1356
|
+
const brand =
|
|
1357
|
+
siteTitle === false
|
|
1358
|
+
? String(mainTitle || "").trim()
|
|
1359
|
+
: String(siteTitle ?? mainTitle ?? "").trim();
|
|
1360
|
+
const pageTitle = String(title ?? "").trim();
|
|
1361
|
+
if (!brand) return pageTitle || "Greg";
|
|
1362
|
+
if (!pageTitle || pageTitle === brand) return brand;
|
|
1363
|
+
return `${pageTitle} | ${brand}`;
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
$effect(() => {
|
|
1367
|
+
if (typeof document === "undefined") return;
|
|
1368
|
+
if (!browserTitle) return;
|
|
1369
|
+
if (document.title !== browserTitle) {
|
|
1370
|
+
document.title = browserTitle;
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1348
1373
|
|
|
1349
1374
|
// -- Content fetch -----------------------------------------------------------
|
|
1350
1375
|
async function fetchMarkdown(mdPath: string): Promise<string> {
|
|
@@ -6,24 +6,6 @@ function isPlainObject(value) {
|
|
|
6
6
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
function deepMerge(baseValue, overrideValue) {
|
|
10
|
-
if (!isPlainObject(baseValue) || !isPlainObject(overrideValue)) {
|
|
11
|
-
return overrideValue;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const result = { ...baseValue };
|
|
15
|
-
for (const [key, overrideEntry] of Object.entries(overrideValue)) {
|
|
16
|
-
const baseEntry = result[key];
|
|
17
|
-
if (isPlainObject(baseEntry) && isPlainObject(overrideEntry)) {
|
|
18
|
-
result[key] = deepMerge(baseEntry, overrideEntry);
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
result[key] = overrideEntry;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return result;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
9
|
function normalizeConfig(value) {
|
|
28
10
|
return isPlainObject(value) ? value : {};
|
|
29
11
|
}
|
|
@@ -37,17 +19,6 @@ export function resolveMainGregConfigPath(rootDir = process.cwd()) {
|
|
|
37
19
|
return null;
|
|
38
20
|
}
|
|
39
21
|
|
|
40
|
-
export function resolvePrvGregConfigPath(rootDir = process.cwd()) {
|
|
41
|
-
const prvPath = path.join(rootDir, 'prv', 'greg.config.js');
|
|
42
|
-
return fs.existsSync(prvPath) ? prvPath : null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function resolveGregConfigPaths(rootDir = process.cwd()) {
|
|
46
|
-
const mainConfigPath = resolveMainGregConfigPath(rootDir);
|
|
47
|
-
const prvConfigPath = resolvePrvGregConfigPath(rootDir);
|
|
48
|
-
return { mainConfigPath, prvConfigPath };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
22
|
export async function loadGregConfigFile(configPath) {
|
|
52
23
|
if (!configPath || !fs.existsSync(configPath)) return {};
|
|
53
24
|
|
|
@@ -70,13 +41,5 @@ export async function loadGregConfigFile(configPath) {
|
|
|
70
41
|
}
|
|
71
42
|
|
|
72
43
|
export async function loadGregConfig(rootDir = process.cwd()) {
|
|
73
|
-
|
|
74
|
-
const mainConfig = await loadGregConfigFile(mainConfigPath);
|
|
75
|
-
|
|
76
|
-
if (!prvConfigPath) {
|
|
77
|
-
return mainConfig;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const prvConfig = await loadGregConfigFile(prvConfigPath);
|
|
81
|
-
return deepMerge(mainConfig, prvConfig);
|
|
44
|
+
return loadGregConfigFile(resolveMainGregConfigPath(rootDir));
|
|
82
45
|
}
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* srcDir string – physical docs source directory, relative to project root (default: 'docs')
|
|
11
11
|
* srcExclude string[] – glob patterns to exclude from docs source (VitePress-compatible, default: [])
|
|
12
12
|
* docsBase string – URL prefix for the docs section, e.g. 'docs' → URLs like /docs/guide (default: 'docs')
|
|
13
|
-
* srcDir string – @deprecated, use docsBase instead
|
|
14
13
|
* version string – version badge text
|
|
15
14
|
* mainTitle string – site title shown in the header
|
|
16
15
|
* outline OutlineOption – global outline setting (VitePress-compatible)
|
|
@@ -22,12 +21,12 @@
|
|
|
22
21
|
* locales Record<string, LocaleConfig> – VitePress-style locale map
|
|
23
22
|
* sidebar 'auto' | SidebarItem[]
|
|
24
23
|
*
|
|
25
|
-
* HMR: changing greg.config.*
|
|
24
|
+
* HMR: changing greg.config.* triggers a full page reload.
|
|
26
25
|
*/
|
|
27
26
|
|
|
28
27
|
import path from 'node:path';
|
|
29
28
|
import { fileURLToPath } from 'node:url';
|
|
30
|
-
import { loadGregConfig,
|
|
29
|
+
import { loadGregConfig, resolveMainGregConfigPath } from './loadGregConfig.js';
|
|
31
30
|
import {
|
|
32
31
|
DEFAULT_OUTPUT_BASE_DIR,
|
|
33
32
|
DEFAULT_SITE_BASE,
|
|
@@ -43,10 +42,10 @@ const RESOLVED_ID = '\0' + VIRTUAL_ID;
|
|
|
43
42
|
|
|
44
43
|
export function vitePluginGregConfig() {
|
|
45
44
|
let root = process.cwd();
|
|
46
|
-
let
|
|
45
|
+
let mainConfigPath = resolveMainGregConfigPath(root);
|
|
47
46
|
|
|
48
47
|
function isWatchedConfig(file) {
|
|
49
|
-
return file === mainConfigPath
|
|
48
|
+
return file === mainConfigPath;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
return {
|
|
@@ -84,7 +83,10 @@ export function vitePluginGregConfig() {
|
|
|
84
83
|
if (hasBase) {
|
|
85
84
|
viteConfig.base = normalizeBasePath(resolved.base, DEFAULT_SITE_BASE);
|
|
86
85
|
}
|
|
87
|
-
|
|
86
|
+
const outDirOverride = process.env.GREG_OUT_DIR;
|
|
87
|
+
if (outDirOverride) {
|
|
88
|
+
viteConfig.build = { outDir: outDirOverride };
|
|
89
|
+
} else if (hasOutDir) {
|
|
88
90
|
const outDir = String(resolved.outDir || DEFAULT_OUTPUT_BASE_DIR).trim() || DEFAULT_OUTPUT_BASE_DIR;
|
|
89
91
|
viteConfig.build = { outDir };
|
|
90
92
|
}
|
|
@@ -97,7 +99,7 @@ export function vitePluginGregConfig() {
|
|
|
97
99
|
|
|
98
100
|
configResolved(config) {
|
|
99
101
|
root = config.root;
|
|
100
|
-
|
|
102
|
+
mainConfigPath = resolveMainGregConfigPath(root);
|
|
101
103
|
},
|
|
102
104
|
|
|
103
105
|
resolveId(id) {
|
|
@@ -106,7 +108,7 @@ export function vitePluginGregConfig() {
|
|
|
106
108
|
|
|
107
109
|
async load(id) {
|
|
108
110
|
if (id !== RESOLVED_ID) return;
|
|
109
|
-
if (!mainConfigPath
|
|
111
|
+
if (!mainConfigPath) return `export default {};`;
|
|
110
112
|
try {
|
|
111
113
|
const config = await loadGregConfig(root);
|
|
112
114
|
return `export default ${JSON.stringify(config)};`;
|
|
@@ -117,7 +119,7 @@ export function vitePluginGregConfig() {
|
|
|
117
119
|
},
|
|
118
120
|
|
|
119
121
|
handleHotUpdate({ file, server }) {
|
|
120
|
-
|
|
122
|
+
mainConfigPath = resolveMainGregConfigPath(root);
|
|
121
123
|
if (isWatchedConfig(file)) {
|
|
122
124
|
const mod = server.moduleGraph.getModuleById(RESOLVED_ID);
|
|
123
125
|
if (mod) {
|