dotmd-cli 0.8.6 → 0.9.0
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/LICENSE +1 -1
- package/bin/dotmd.mjs +0 -0
- package/dotmd.config.example.mjs +13 -0
- package/package.json +1 -1
- package/src/config.mjs +18 -0
- package/src/lifecycle.mjs +7 -3
- package/src/validate.mjs +8 -3
package/LICENSE
CHANGED
package/bin/dotmd.mjs
CHANGED
|
File without changes
|
package/dotmd.config.example.mjs
CHANGED
|
@@ -16,6 +16,12 @@ export const excludeDirs = ['evidence'];
|
|
|
16
16
|
// Status workflow — order determines display grouping
|
|
17
17
|
export const statuses = {
|
|
18
18
|
order: ['active', 'ready', 'planned', 'research', 'blocked', 'reference', 'archived'],
|
|
19
|
+
// Additional statuses valid only in specific roots (merged with order)
|
|
20
|
+
// Useful when different doc areas track different things (e.g. plans vs module docs)
|
|
21
|
+
// rootStatuses: {
|
|
22
|
+
// 'docs/modules': ['implemented', 'partial', 'draft', 'deprecated'],
|
|
23
|
+
// 'docs/core': ['implemented', 'partial'],
|
|
24
|
+
// },
|
|
19
25
|
// Days after which a doc is considered stale (null = never stale)
|
|
20
26
|
staleDays: {
|
|
21
27
|
active: 14,
|
|
@@ -77,6 +83,13 @@ export const presets = {
|
|
|
77
83
|
actionable: ['--status', 'active,ready', '--has-next-step', '--sort', 'updated', '--all'],
|
|
78
84
|
};
|
|
79
85
|
|
|
86
|
+
// ─── Notion ──────────────────────────────────────────────────────────────────
|
|
87
|
+
// IMPORTANT: Use environment variables for tokens — never hardcode secrets in config files.
|
|
88
|
+
// export const notion = {
|
|
89
|
+
// token: process.env.NOTION_TOKEN,
|
|
90
|
+
// databaseId: process.env.NOTION_DATABASE_ID,
|
|
91
|
+
// };
|
|
92
|
+
|
|
80
93
|
// ─── Function Hooks ──────────────────────────────────────────────────────────
|
|
81
94
|
// Hooks are optional. Each receives a default implementation it can wrap or replace.
|
|
82
95
|
|
package/package.json
CHANGED
package/src/config.mjs
CHANGED
|
@@ -216,6 +216,16 @@ export async function resolveConfig(cwd, explicitConfigPath) {
|
|
|
216
216
|
staleDaysByStatus[status] = config.statuses.staleDays?.[status] ?? null;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
// Per-root additional statuses (merged with global validStatuses)
|
|
220
|
+
const rootStatusesRaw = config.statuses.rootStatuses ?? {};
|
|
221
|
+
const rootLabels = new Set(rootPaths.map(r => path.relative(configDir, path.resolve(configDir, r)).split(path.sep).join('/')));
|
|
222
|
+
const rootValidStatuses = new Map();
|
|
223
|
+
for (const [rootKey, extraStatuses] of Object.entries(rootStatusesRaw)) {
|
|
224
|
+
const merged = new Set(validStatuses);
|
|
225
|
+
for (const s of extraStatuses) merged.add(s);
|
|
226
|
+
rootValidStatuses.set(rootKey, merged);
|
|
227
|
+
}
|
|
228
|
+
|
|
219
229
|
const validSurfaces = config.taxonomy.surfaces
|
|
220
230
|
? new Set(config.taxonomy.surfaces)
|
|
221
231
|
: null;
|
|
@@ -235,6 +245,13 @@ export async function resolveConfig(cwd, explicitConfigPath) {
|
|
|
235
245
|
const skipStaleFor = new Set(lifecycle.skipStaleFor);
|
|
236
246
|
const skipWarningsFor = new Set(lifecycle.skipWarningsFor);
|
|
237
247
|
|
|
248
|
+
// Warn if rootStatuses keys don't match any configured root
|
|
249
|
+
for (const rootKey of Object.keys(rootStatusesRaw)) {
|
|
250
|
+
if (!rootLabels.has(rootKey)) {
|
|
251
|
+
earlyWarnings.push(`Config: statuses.rootStatuses key '${rootKey}' does not match any configured root.`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
238
255
|
const configWarnings = [...earlyWarnings, ...validateConfig(userConfig, config, validStatuses, indexPath)];
|
|
239
256
|
|
|
240
257
|
return {
|
|
@@ -252,6 +269,7 @@ export async function resolveConfig(cwd, explicitConfigPath) {
|
|
|
252
269
|
|
|
253
270
|
statusOrder,
|
|
254
271
|
validStatuses,
|
|
272
|
+
rootValidStatuses,
|
|
255
273
|
staleDaysByStatus,
|
|
256
274
|
|
|
257
275
|
lifecycle: { archiveStatuses, skipStaleFor, skipWarningsFor },
|
package/src/lifecycle.mjs
CHANGED
|
@@ -27,11 +27,16 @@ export async function runStatus(argv, config, opts = {}) {
|
|
|
27
27
|
die('Usage: dotmd status <file> <new-status>');
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
if (!config.validStatuses.has(newStatus)) { die(`Invalid status: ${newStatus}\nValid: ${[...config.validStatuses].join(', ')}`); }
|
|
31
|
-
|
|
32
30
|
const filePath = resolveDocPath(input, config);
|
|
33
31
|
if (!filePath) { die(`File not found: ${input}\nSearched: ${toRepoPath(config.repoRoot, config.repoRoot) || '.'}, ${toRepoPath(config.docsRoot, config.repoRoot)}`); }
|
|
34
32
|
|
|
33
|
+
// Validate status against root-specific vocabulary
|
|
34
|
+
const fileRoot = findFileRoot(filePath, config);
|
|
35
|
+
const rootLabel = path.relative(config.repoRoot, fileRoot).split(path.sep).join('/');
|
|
36
|
+
const rootSet = config.rootValidStatuses?.get(rootLabel);
|
|
37
|
+
const effectiveValid = rootSet ?? config.validStatuses;
|
|
38
|
+
if (!effectiveValid.has(newStatus)) { die(`Invalid status: ${newStatus}\nValid: ${[...effectiveValid].join(', ')}`); }
|
|
39
|
+
|
|
35
40
|
const raw = readFileSync(filePath, 'utf8');
|
|
36
41
|
const { frontmatter } = extractFrontmatter(raw);
|
|
37
42
|
const parsed = parseSimpleFrontmatter(frontmatter);
|
|
@@ -43,7 +48,6 @@ export async function runStatus(argv, config, opts = {}) {
|
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
const today = new Date().toISOString().slice(0, 10);
|
|
46
|
-
const fileRoot = findFileRoot(filePath, config);
|
|
47
51
|
const archiveDir = path.join(fileRoot, config.archiveDir);
|
|
48
52
|
const isArchiving = config.lifecycle.archiveStatuses.has(newStatus) && !filePath.includes(`/${config.archiveDir}/`);
|
|
49
53
|
const isUnarchiving = !config.lifecycle.archiveStatuses.has(newStatus) && filePath.includes(`/${config.archiveDir}/`);
|
package/src/validate.mjs
CHANGED
|
@@ -6,15 +6,20 @@ import { toRepoPath } from './util.mjs';
|
|
|
6
6
|
|
|
7
7
|
const NOW = new Date();
|
|
8
8
|
|
|
9
|
+
function isValidStatus(status, root, config) {
|
|
10
|
+
const rootSet = config.rootValidStatuses?.get(root);
|
|
11
|
+
if (rootSet) return rootSet.has(status);
|
|
12
|
+
return config.validStatuses.has(status);
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
export function validateDoc(doc, frontmatter, headingTitle, config) {
|
|
10
16
|
if (!doc.status) {
|
|
11
17
|
doc.errors.push({ path: doc.path, level: 'error', message: 'Missing frontmatter `status`.' });
|
|
12
|
-
} else if (!
|
|
18
|
+
} else if (!isValidStatus(doc.status, doc.root, config)) {
|
|
13
19
|
doc.warnings.push({ path: doc.path, level: 'warning', message: `Unknown status \`${doc.status}\`; not in statuses.order.` });
|
|
14
20
|
}
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
const knownStatus = config.validStatuses.has(doc.status);
|
|
22
|
+
const knownStatus = isValidStatus(doc.status, doc.root, config);
|
|
18
23
|
|
|
19
24
|
if (knownStatus && !config.lifecycle.skipWarningsFor.has(doc.status) && !doc.updated) {
|
|
20
25
|
doc.errors.push({ path: doc.path, level: 'error', message: 'Missing frontmatter `updated` for non-archived doc.' });
|