dotmd-cli 0.32.0 → 0.32.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/package.json +1 -1
- package/src/graph.mjs +2 -2
- package/src/lifecycle.mjs +11 -6
- package/src/validate.mjs +28 -16
package/package.json
CHANGED
package/src/graph.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { toSlug, toRepoPath, warn } from './util.mjs';
|
|
2
|
+
import { toSlug, toRepoPath, warn, resolveRefPath } from './util.mjs';
|
|
3
3
|
import { bold, red, green, dim } from './color.mjs';
|
|
4
4
|
|
|
5
5
|
const STATUS_COLORS = {
|
|
@@ -60,7 +60,7 @@ export function buildGraph(index, config, filters = {}) {
|
|
|
60
60
|
|
|
61
61
|
for (const field of allRefFields) {
|
|
62
62
|
for (const relPath of (doc.refFields[field] || [])) {
|
|
63
|
-
const resolved = path.resolve(docDir, relPath);
|
|
63
|
+
const resolved = resolveRefPath(relPath, docDir, config.repoRoot) ?? path.resolve(docDir, relPath);
|
|
64
64
|
const targetPath = toRepoPath(resolved, config.repoRoot);
|
|
65
65
|
const edgeKey = `${doc.path}|${targetPath}|${field}`;
|
|
66
66
|
if (edgeKeys.has(edgeKey)) continue;
|
package/src/lifecycle.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { extractFrontmatter, parseSimpleFrontmatter, replaceFrontmatter } from './frontmatter.mjs';
|
|
4
|
-
import { asString, toRepoPath, die, warn, resolveDocPath, escapeRegex, nowIso } from './util.mjs';
|
|
4
|
+
import { asString, toRepoPath, die, warn, resolveDocPath, resolveRefPath, escapeRegex, nowIso } from './util.mjs';
|
|
5
5
|
import { gitMv, getGitLastModified, getGitLastModifiedBatch } from './git.mjs';
|
|
6
6
|
import { buildIndex, collectDocFiles } from './index.mjs';
|
|
7
7
|
import { renderIndexFile, writeIndex } from './index-file.mjs';
|
|
@@ -717,12 +717,17 @@ function updateRefsFromMovedFile(oldPath, newPath, config) {
|
|
|
717
717
|
let raw = readFileSync(newPath, 'utf8');
|
|
718
718
|
const { frontmatter, body } = extractFrontmatter(raw);
|
|
719
719
|
|
|
720
|
-
// Fix frontmatter ref fields (YAML list items like - ./path.md)
|
|
720
|
+
// Fix frontmatter ref fields (YAML list items like - ./path.md).
|
|
721
|
+
// Resolve doc-relative first, then repo-root-relative — so a ref like
|
|
722
|
+
// `docs/foo/bar.md` written from any nesting level gets rewritten correctly
|
|
723
|
+
// when the source moves. Without the repo-root fallback, repo-relative refs
|
|
724
|
+
// silently skipped rewriting (existsSync on the doubled doc-relative path
|
|
725
|
+
// returned false).
|
|
721
726
|
let newFm = frontmatter;
|
|
722
727
|
const refRegex = /^(\s+-\s+)(\S+\.md)$/gm;
|
|
723
728
|
newFm = newFm.replace(refRegex, (match, prefix, refPath) => {
|
|
724
|
-
const absTarget =
|
|
725
|
-
if (!
|
|
729
|
+
const absTarget = resolveRefPath(refPath, oldDir, config.repoRoot);
|
|
730
|
+
if (!absTarget) return match;
|
|
726
731
|
const newRelPath = path.relative(newDir, absTarget).split(path.sep).join('/');
|
|
727
732
|
return `${prefix}${newRelPath}`;
|
|
728
733
|
});
|
|
@@ -732,8 +737,8 @@ function updateRefsFromMovedFile(oldPath, newPath, config) {
|
|
|
732
737
|
const linkRegex = /(\[[^\]]*\]\()([^)]+\.md)(\))/g;
|
|
733
738
|
newBody = newBody.replace(linkRegex, (match, pre, href, post) => {
|
|
734
739
|
if (href.startsWith('http')) return match;
|
|
735
|
-
const absTarget =
|
|
736
|
-
if (!
|
|
740
|
+
const absTarget = resolveRefPath(href, oldDir, config.repoRoot);
|
|
741
|
+
if (!absTarget) return match;
|
|
737
742
|
const newHref = path.relative(newDir, absTarget).split(path.sep).join('/');
|
|
738
743
|
return `${pre}${newHref}${post}`;
|
|
739
744
|
});
|
package/src/validate.mjs
CHANGED
|
@@ -106,7 +106,7 @@ export function validateDoc(doc, frontmatter, headingTitle, config) {
|
|
|
106
106
|
doc.errors.push({ path: doc.path, level: 'error', message: '`module` is required for active/ready/planned/blocked docs; use a real module, `platform`, or `none`. Accepts singular `module:` or plural `modules:` list.' });
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
if (config.validSurfaces) {
|
|
109
|
+
if (config.validSurfaces && !config.lifecycle.skipWarningsFor.has(doc.status)) {
|
|
110
110
|
for (const surface of doc.surfaces) {
|
|
111
111
|
if (!config.validSurfaces.has(surface)) {
|
|
112
112
|
doc.warnings.push({ path: doc.path, level: 'warning', message: `Unknown surface \`${surface}\`; expected a known surface taxonomy value.` });
|
|
@@ -164,21 +164,28 @@ export function validateDoc(doc, frontmatter, headingTitle, config) {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
// Validate reference fields resolve to existing files
|
|
167
|
+
// Validate reference fields resolve to existing files. Terminal statuses
|
|
168
|
+
// (archived, deprecated, etc.) document historical state — their refs may
|
|
169
|
+
// legitimately point at moved/deleted targets and shouldn't gate the
|
|
170
|
+
// exit code with a hard error.
|
|
168
171
|
const docDir = path.dirname(path.join(config.repoRoot, doc.path));
|
|
169
172
|
const allRefFields = [...(config.referenceFields.bidirectional || []), ...(config.referenceFields.unidirectional || [])];
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
const skipRefValidation = config.lifecycle.terminalStatuses.has(doc.status)
|
|
174
|
+
|| config.lifecycle.skipWarningsFor.has(doc.status);
|
|
175
|
+
if (!skipRefValidation) {
|
|
176
|
+
for (const field of allRefFields) {
|
|
177
|
+
for (const relPath of (doc.refFields[field] || [])) {
|
|
178
|
+
if (!resolveRefPath(relPath, docDir, config.repoRoot)) {
|
|
179
|
+
doc.errors.push({ path: doc.path, level: 'error', message: `${field} entry \`${relPath}\` does not resolve to an existing file.` });
|
|
180
|
+
}
|
|
174
181
|
}
|
|
175
182
|
}
|
|
176
|
-
}
|
|
177
183
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
184
|
+
// Validate body links resolve to existing files
|
|
185
|
+
for (const link of (doc.bodyLinks || [])) {
|
|
186
|
+
if (!resolveRefPath(link.href, docDir, config.repoRoot)) {
|
|
187
|
+
doc.warnings.push({ path: doc.path, level: 'warning', message: `body link \`${link.href}\` does not resolve to an existing file.` });
|
|
188
|
+
}
|
|
182
189
|
}
|
|
183
190
|
}
|
|
184
191
|
}
|
|
@@ -271,19 +278,24 @@ export function validatePlanShape(doc, body, frontmatter, config) {
|
|
|
271
278
|
});
|
|
272
279
|
}
|
|
273
280
|
|
|
274
|
-
// 3. surface AND surfaces both populated
|
|
275
|
-
|
|
281
|
+
// 3. surface AND surfaces both populated with DIVERGENT values. When the
|
|
282
|
+
// singular value is already a member of the plural array, src/index.mjs
|
|
283
|
+
// merges them transparently — warning would be noise. Only divergence
|
|
284
|
+
// actually risks data loss when readers consult one form vs. the other.
|
|
285
|
+
if (frontmatter.surface && Array.isArray(frontmatter.surfaces) && frontmatter.surfaces.length > 0
|
|
286
|
+
&& !frontmatter.surfaces.includes(frontmatter.surface)) {
|
|
276
287
|
doc.warnings.push({
|
|
277
288
|
path: doc.path,
|
|
278
289
|
level: 'warning',
|
|
279
|
-
message:
|
|
290
|
+
message: `Both \`surface\` (singular: \`${frontmatter.surface}\`) and \`surfaces\` (array) are set with different values. Pick one — prefer \`surfaces\` array form.`,
|
|
280
291
|
});
|
|
281
292
|
}
|
|
282
|
-
if (frontmatter.module && Array.isArray(frontmatter.modules) && frontmatter.modules.length > 0
|
|
293
|
+
if (frontmatter.module && Array.isArray(frontmatter.modules) && frontmatter.modules.length > 0
|
|
294
|
+
&& !frontmatter.modules.includes(frontmatter.module)) {
|
|
283
295
|
doc.warnings.push({
|
|
284
296
|
path: doc.path,
|
|
285
297
|
level: 'warning',
|
|
286
|
-
message:
|
|
298
|
+
message: `Both \`module\` (singular: \`${frontmatter.module}\`) and \`modules\` (array) are set with different values. Pick one — prefer \`modules\` array form.`,
|
|
287
299
|
});
|
|
288
300
|
}
|
|
289
301
|
|