svelte-docsmith 0.12.0 → 0.12.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.
|
@@ -13,7 +13,7 @@ import path from 'node:path';
|
|
|
13
13
|
import { assertValidVersionId } from '../../core/version.js';
|
|
14
14
|
import { freezeLastUpdated, isInheritedRouteFile, rewriteDocsLinks } from '../archive.js';
|
|
15
15
|
import { ARCHIVE_MARKER, discoverArchives, markerContents } from '../archives.js';
|
|
16
|
-
import {
|
|
16
|
+
import { commitDates } from '../git.js';
|
|
17
17
|
import { docsBaseFrom } from '../paths.js';
|
|
18
18
|
import { isPageFile } from '../vite/pages.js';
|
|
19
19
|
import { CliError } from './error.js';
|
|
@@ -79,6 +79,14 @@ export function archiveVersion(options, log) {
|
|
|
79
79
|
yield { file, source: path.join(contentDir, path.relative(toDir, file)) };
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
// Read the dates before copying, so the walk sees the docs root as it was
|
|
83
|
+
// rather than a tree half full of untracked copies. Say so if they could not
|
|
84
|
+
// be read: an archive is written once and never edited again, so a page that
|
|
85
|
+
// misses its date here keeps the wrong one for good.
|
|
86
|
+
const dates = commitDates(contentDir, (reason) => {
|
|
87
|
+
log(`\n! Could not read commit dates: ${reason}`);
|
|
88
|
+
log(' Archived pages will keep no last-updated date.\n');
|
|
89
|
+
});
|
|
82
90
|
copyCurrent(contentDir, toDir);
|
|
83
91
|
fs.writeFileSync(path.join(toDir, ARCHIVE_MARKER), markerContents(id));
|
|
84
92
|
let pages = 0;
|
|
@@ -87,12 +95,15 @@ export function archiveVersion(options, log) {
|
|
|
87
95
|
continue;
|
|
88
96
|
pages++;
|
|
89
97
|
const text = fs.readFileSync(file, 'utf-8');
|
|
90
|
-
fs.writeFileSync(file, freezeLastUpdated(rewriteDocsLinks(text, { docsBase, versionId: id, archivedIds }),
|
|
98
|
+
fs.writeFileSync(file, freezeLastUpdated(rewriteDocsLinks(text, { docsBase, versionId: id, archivedIds }), dates.get(source)));
|
|
91
99
|
}
|
|
92
100
|
log(`\n✓ Archived the current docs into ${rel(toDir)} (${pages} pages)\n`);
|
|
93
|
-
log('Links were rewritten to stay inside the archive
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
log('Links were rewritten to stay inside the archive.');
|
|
102
|
+
// Only claim the dates were kept when the walk actually produced some.
|
|
103
|
+
if (dates.size)
|
|
104
|
+
log('Each page kept its real last-updated date.');
|
|
105
|
+
log('Review the diff, then update docsmith({ versions }) so the archive');
|
|
106
|
+
log('is served:\n');
|
|
96
107
|
log(' versions: {');
|
|
97
108
|
log(" current: { id: '<new release>', label: '<new release>' },");
|
|
98
109
|
log(` archived: [{ id: '${id}', label: '${label}' }${archivedIds.size ? ', …' : ''}]`);
|
package/dist/buildtime/git.d.ts
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Commit day (`YYYY-MM-DD`) every tracked file under `dir` last changed, keyed by
|
|
3
|
+
* absolute path. One history walk rather than a `git log` per file: the per-file
|
|
4
|
+
* form costs a process each, which is the build's largest single expense once a
|
|
5
|
+
* site has more than a handful of pages.
|
|
6
|
+
*
|
|
7
|
+
* `--relative` makes git print paths relative to `dir`, so they join straight
|
|
8
|
+
* back onto it with no second call to find the repository root. `--no-renames`
|
|
9
|
+
* makes a rename list both of its paths, which keeps the answer identical to
|
|
10
|
+
* asking about one file at a time.
|
|
11
|
+
*
|
|
12
|
+
* An empty map for a directory outside a repository, or one git could not read.
|
|
13
|
+
* That is the same "no date" every caller already handles, and it is deliberately
|
|
14
|
+
* all-or-nothing: a truncated read would date some pages and not others, which
|
|
15
|
+
* looks like real data and is not.
|
|
16
|
+
*
|
|
17
|
+
* Only a truncated read calls `onFail`, because it is the one failure that would
|
|
18
|
+
* otherwise date some pages and not others. A directory outside a repository, a
|
|
19
|
+
* missing directory, or a machine without git all yield no dates, which every
|
|
20
|
+
* caller already handles and which the plugin already reports where it matters.
|
|
21
|
+
* Reporting is the caller's to do, which is why this takes a callback rather
|
|
22
|
+
* than writing to the console itself.
|
|
23
|
+
*/
|
|
24
|
+
export declare function commitDates(dir: string, onFail?: (reason: string) => void,
|
|
25
|
+
/**
|
|
26
|
+
* Cap on the walk's output. The default is far above any real docs root; it is
|
|
27
|
+
* a parameter so the truncation path can be exercised without a repository
|
|
28
|
+
* large enough to produce 256 MB of history.
|
|
29
|
+
*/
|
|
30
|
+
maxBuffer?: number): Map<string, string>;
|
|
3
31
|
/**
|
|
4
32
|
* Release dates keyed by version, read from the commit that introduced each
|
|
5
33
|
* `## <version>` heading in a changelog. Changesets does not write dates, and a
|
package/dist/buildtime/git.js
CHANGED
|
@@ -12,14 +12,64 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { spawnSync } from 'node:child_process';
|
|
14
14
|
import path from 'node:path';
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Commit day (`YYYY-MM-DD`) every tracked file under `dir` last changed, keyed by
|
|
17
|
+
* absolute path. One history walk rather than a `git log` per file: the per-file
|
|
18
|
+
* form costs a process each, which is the build's largest single expense once a
|
|
19
|
+
* site has more than a handful of pages.
|
|
20
|
+
*
|
|
21
|
+
* `--relative` makes git print paths relative to `dir`, so they join straight
|
|
22
|
+
* back onto it with no second call to find the repository root. `--no-renames`
|
|
23
|
+
* makes a rename list both of its paths, which keeps the answer identical to
|
|
24
|
+
* asking about one file at a time.
|
|
25
|
+
*
|
|
26
|
+
* An empty map for a directory outside a repository, or one git could not read.
|
|
27
|
+
* That is the same "no date" every caller already handles, and it is deliberately
|
|
28
|
+
* all-or-nothing: a truncated read would date some pages and not others, which
|
|
29
|
+
* looks like real data and is not.
|
|
30
|
+
*
|
|
31
|
+
* Only a truncated read calls `onFail`, because it is the one failure that would
|
|
32
|
+
* otherwise date some pages and not others. A directory outside a repository, a
|
|
33
|
+
* missing directory, or a machine without git all yield no dates, which every
|
|
34
|
+
* caller already handles and which the plugin already reports where it matters.
|
|
35
|
+
* Reporting is the caller's to do, which is why this takes a callback rather
|
|
36
|
+
* than writing to the console itself.
|
|
37
|
+
*/
|
|
38
|
+
export function commitDates(dir, onFail,
|
|
39
|
+
/**
|
|
40
|
+
* Cap on the walk's output. The default is far above any real docs root; it is
|
|
41
|
+
* a parameter so the truncation path can be exercised without a repository
|
|
42
|
+
* large enough to produce 256 MB of history.
|
|
43
|
+
*/
|
|
44
|
+
maxBuffer = 256 * 1024 * 1024) {
|
|
45
|
+
const dates = new Map();
|
|
46
|
+
const res = spawnSync('git', ['log', '--format=%cs', '--name-only', '--relative', '--no-renames', '--', '.'],
|
|
47
|
+
// The 1 MB default would truncate rather than throw on a large history,
|
|
48
|
+
// which is why the cap is raised and the result checked below.
|
|
49
|
+
{ cwd: dir, encoding: 'utf-8', maxBuffer });
|
|
50
|
+
// `ENOBUFS` is the output outgrowing maxBuffer, which truncates rather than
|
|
51
|
+
// throws and is the only failure that could leave a half-filled map. `ENOENT`
|
|
52
|
+
// (no such directory, or no git installed) and a non-zero status ("not a
|
|
53
|
+
// repository") both mean no dates at all, which is a state callers expect.
|
|
54
|
+
if (res.error?.code === 'ENOBUFS') {
|
|
55
|
+
onFail?.(res.error.message);
|
|
56
|
+
}
|
|
57
|
+
if (res.error || res.status !== 0)
|
|
58
|
+
return dates;
|
|
59
|
+
let date;
|
|
60
|
+
for (const line of res.stdout.split('\n')) {
|
|
61
|
+
if (!line)
|
|
62
|
+
continue;
|
|
63
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(line)) {
|
|
64
|
+
date = line;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// Newest commit first, so the first date a file appears under is its latest.
|
|
68
|
+
const file = path.resolve(dir, line);
|
|
69
|
+
if (date && !dates.has(file))
|
|
70
|
+
dates.set(file, date);
|
|
71
|
+
}
|
|
72
|
+
return dates;
|
|
23
73
|
}
|
|
24
74
|
/**
|
|
25
75
|
* Release dates keyed by version, read from the commit that introduced each
|
|
@@ -26,6 +26,7 @@ import { docsBaseFrom } from '../paths.js';
|
|
|
26
26
|
import { isPageFile, readSourcePages, withCommitDates } from './pages.js';
|
|
27
27
|
import { contentIndex, llmsIndex, searchIndex } from './collect.js';
|
|
28
28
|
import { collectReleases } from './releases.js';
|
|
29
|
+
import { commitDates } from '../git.js';
|
|
29
30
|
/**
|
|
30
31
|
* The svelte-docsmith Vite plugin. Add it to `plugins` in `vite.config.ts`:
|
|
31
32
|
*
|
|
@@ -70,6 +71,17 @@ function contentIndexPlugin(options) {
|
|
|
70
71
|
const changelogRoute = options.changelogPath ?? '/changelog';
|
|
71
72
|
const changelogOverrides = path.join(routesDir, changelogRoute);
|
|
72
73
|
const indexOptions = { contentDir, routesDir, versions };
|
|
74
|
+
// One history walk, reused for the life of the plugin. A build loads this
|
|
75
|
+
// module once, so it is always fresh there; `dev` reloads it on every page
|
|
76
|
+
// save, and re-walking each time would cost git a process for dates that
|
|
77
|
+
// cannot have changed without a commit. The trade is that committing during
|
|
78
|
+
// `dev` leaves the dates as they were until the server restarts.
|
|
79
|
+
let dates;
|
|
80
|
+
const trackedDates = () => (dates ??= commitDates(contentDir, (reason) => {
|
|
81
|
+
console.warn(`[svelte-docsmith] could not read commit dates under ${contentDir}\n` +
|
|
82
|
+
` ${reason}\n` +
|
|
83
|
+
` Pages will render without a last-updated date, and sitemap entries without \`lastmod\`.`);
|
|
84
|
+
}));
|
|
73
85
|
return {
|
|
74
86
|
name: 'docsmith-content',
|
|
75
87
|
enforce: 'pre',
|
|
@@ -101,7 +113,7 @@ function contentIndexPlugin(options) {
|
|
|
101
113
|
const { pages, exists } = readSourcePages(contentDir);
|
|
102
114
|
watch(pages);
|
|
103
115
|
// Only this index has a date field, so only this one pays for git.
|
|
104
|
-
const docs = contentIndex(withCommitDates(pages), indexOptions);
|
|
116
|
+
const docs = contentIndex(withCommitDates(pages, trackedDates()), indexOptions);
|
|
105
117
|
warnAboutDocsRoot(contentDir, exists, docs.length);
|
|
106
118
|
const resolved = resolveVersions(versions, docsBase, docs);
|
|
107
119
|
return (`export const docs = ${JSON.stringify(docs, null, 2)};\n` +
|
|
@@ -39,18 +39,18 @@ export declare function readSourcePages(contentDir: string): {
|
|
|
39
39
|
*/
|
|
40
40
|
export declare function titleOf(page: SourcePage): string | undefined;
|
|
41
41
|
/**
|
|
42
|
-
* Date each page from
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* Date each page from `dates`, keyed by absolute path, unless it carries its own
|
|
43
|
+
* frontmatter `lastUpdated`. Archiving copies every page in one commit, so an
|
|
44
|
+
* archive's pages are written with the date they last really changed and must
|
|
45
|
+
* keep it.
|
|
46
46
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* untitled pages, which no index carries.
|
|
47
|
+
* Pure: the caller walks git once with {@link commitDates} and hands the result
|
|
48
|
+
* in. Asking per page cost a process each and was the build's largest single
|
|
49
|
+
* expense, and doing the walk here would put it behind a function the search and
|
|
50
|
+
* llms indexes also call, making them pay for a date neither has anywhere to put.
|
|
51
|
+
* For the same reason this skips untitled pages, which no index carries.
|
|
52
52
|
*/
|
|
53
|
-
export declare function withCommitDates(pages: SourcePage[]): SourcePage[];
|
|
53
|
+
export declare function withCommitDates(pages: SourcePage[], dates: Map<string, string>): SourcePage[];
|
|
54
54
|
/**
|
|
55
55
|
* List the absolute paths of every `+page.md`/`+page.svx` under `contentDir`.
|
|
56
56
|
*/
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { parseFrontmatter } from './frontmatter.js';
|
|
10
|
-
import { lastCommitDate } from '../git.js';
|
|
11
10
|
const PAGE_NAMES = ['+page.md', '+page.svx'];
|
|
12
11
|
/** Whether a filename is a doc page the plugin should index. */
|
|
13
12
|
export function isPageFile(file) {
|
|
@@ -40,18 +39,18 @@ export function titleOf(page) {
|
|
|
40
39
|
return typeof page.frontmatter.title === 'string' ? page.frontmatter.title : undefined;
|
|
41
40
|
}
|
|
42
41
|
/**
|
|
43
|
-
* Date each page from
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
42
|
+
* Date each page from `dates`, keyed by absolute path, unless it carries its own
|
|
43
|
+
* frontmatter `lastUpdated`. Archiving copies every page in one commit, so an
|
|
44
|
+
* archive's pages are written with the date they last really changed and must
|
|
45
|
+
* keep it.
|
|
47
46
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* untitled pages, which no index carries.
|
|
47
|
+
* Pure: the caller walks git once with {@link commitDates} and hands the result
|
|
48
|
+
* in. Asking per page cost a process each and was the build's largest single
|
|
49
|
+
* expense, and doing the walk here would put it behind a function the search and
|
|
50
|
+
* llms indexes also call, making them pay for a date neither has anywhere to put.
|
|
51
|
+
* For the same reason this skips untitled pages, which no index carries.
|
|
53
52
|
*/
|
|
54
|
-
export function withCommitDates(pages) {
|
|
53
|
+
export function withCommitDates(pages, dates) {
|
|
55
54
|
return pages.map((page) => {
|
|
56
55
|
if (titleOf(page) === undefined)
|
|
57
56
|
return page;
|
|
@@ -59,7 +58,7 @@ export function withCommitDates(pages) {
|
|
|
59
58
|
...page,
|
|
60
59
|
lastUpdated: typeof page.frontmatter.lastUpdated === 'string'
|
|
61
60
|
? page.frontmatter.lastUpdated
|
|
62
|
-
:
|
|
61
|
+
: dates.get(page.file)
|
|
63
62
|
};
|
|
64
63
|
});
|
|
65
64
|
}
|