@dominikcz/greg 0.9.34 → 0.9.35

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 CHANGED
@@ -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: {
@@ -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:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dominikcz/greg",
3
- "version": "0.9.34",
3
+ "version": "0.9.35",
4
4
  "type": "module",
5
5
  "types": "./types/index.d.ts",
6
6
  "bin": {
@@ -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', '--outDir', outDir, ...passthrough], {
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
- `${sha}-${workspaceBuildFingerprint}`,
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('dist/index.html not found run npm run build first.');
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
- console.log(` ✓ ${route}/index.html`);
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/`);
@@ -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
- const { mainConfigPath, prvConfigPath } = resolveGregConfigPaths(rootDir);
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.* or prv/greg.config.js triggers a full page reload.
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, resolveGregConfigPaths } from './loadGregConfig.js';
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 { mainConfigPath, prvConfigPath } = resolveGregConfigPaths(root);
45
+ let mainConfigPath = resolveMainGregConfigPath(root);
47
46
 
48
47
  function isWatchedConfig(file) {
49
- return file === mainConfigPath || file === prvConfigPath;
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
- if (hasOutDir) {
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
- ({ mainConfigPath, prvConfigPath } = resolveGregConfigPaths(root));
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 && !prvConfigPath) return `export default {};`;
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
- ({ mainConfigPath, prvConfigPath } = resolveGregConfigPaths(root));
122
+ mainConfigPath = resolveMainGregConfigPath(root);
121
123
  if (isWatchedConfig(file)) {
122
124
  const mod = server.moduleGraph.getModuleById(RESOLVED_ID);
123
125
  if (mod) {