mdrefine 0.1.0-beta.1 → 0.1.0-beta.2
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/CONTRIBUTING.md +6 -0
- package/README.md +10 -6
- package/dist/cli/main.js +9 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/config/resolve.d.ts +12 -0
- package/dist/config/resolve.js +7 -1
- package/dist/config/resolve.js.map +1 -1
- package/dist/core/engine.js +19 -4
- package/dist/core/engine.js.map +1 -1
- package/dist/core/types.d.ts +7 -1
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/rules/links.js +28 -5
- package/dist/rules/links.js.map +1 -1
- package/dist/rules/structure.d.ts +2 -0
- package/dist/rules/structure.js +24 -0
- package/dist/rules/structure.js.map +1 -0
- package/dist/rules/style.js +78 -15
- package/dist/rules/style.js.map +1 -1
- package/dist/syntax/obsidian.js +17 -0
- package/dist/syntax/obsidian.js.map +1 -1
- package/dist/workspace/files.d.ts +5 -2
- package/dist/workspace/files.js +15 -6
- package/dist/workspace/files.js.map +1 -1
- package/dist/workspace/index.d.ts +4 -1
- package/dist/workspace/index.js +53 -15
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/selection.d.ts +2 -0
- package/dist/workspace/selection.js +31 -0
- package/dist/workspace/selection.js.map +1 -0
- package/docs/cli.md +18 -0
- package/docs/configuration.md +86 -25
- package/docs/contributing.md +87 -5
- package/docs/design.md +15 -5
- package/docs/plugins.md +11 -4
- package/docs/quick-start.md +1 -1
- package/docs/releases/0.1.0-beta.2.md +83 -0
- package/docs/releases.md +33 -10
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/workspace/files.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstat, readdir, readFile, realpath, open, rename, unlink } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport ignore from \"ignore\";\nimport { minimatch } from \"minimatch\";\nimport { exists } from \"../config/load.js\";\n\nexport interface FileSet {\n root: string;\n files: Record<string, string | null>;\n selected: string[];\n strictLineBreaks?: boolean;\n}\nexport async function discover(\n rootPath: string,\n supplied: string[],\n excluded: string[],\n): Promise<FileSet> {\n const root = await realpath(rootPath);\n const files: Record<string, string | null> = {};\n const selected: string[] = [];\n const requests: string[] = [];\n for (const input of supplied.length ? supplied : [root]) {\n const absolute = path.resolve(input);\n if ((await lstat(absolute)).isSymbolicLink())\n throw new Error(`Refusing symbolic-link input: ${input}`);\n const resolved = await realpath(absolute);\n const relative = path.relative(root, resolved).split(path.sep).join(\"/\");\n if (relative.startsWith(\"../\") || relative === \"..\" || path.isAbsolute(relative))\n throw new Error(`Input is outside the workspace: ${input}`);\n requests.push(relative);\n }\n interface IgnoreLayer {\n base: string;\n matcher: ReturnType<typeof ignore>;\n }\n async function walk(relative: string, inherited: IgnoreLayer[]) {\n const directory = path.join(root, relative);\n const layers = [...inherited];\n if (await exists(path.join(directory, \".gitignore\")))\n layers.push({\n base: relative,\n matcher: ignore().add(await readFile(path.join(directory, \".gitignore\"), \"utf8\")),\n });\n for (const entry of await readdir(directory, { withFileTypes: true })) {\n if (\n [\".git\", \".obsidian\", \"node_modules\", \".npm-cache\", \"dist\", \"coverage\"].includes(\n entry.name,\n ) ||\n entry.isSymbolicLink()\n )\n continue;\n const name = relative ? `${relative}/${entry.name}` : entry.name;\n if (\n layers.some((layer) =>\n layer.matcher.ignores(\n name.slice(layer.base ? layer.base.length + 1 : 0) + (entry.isDirectory() ? \"/\" : \"\"),\n ),\n )\n )\n continue;\n const full = path.join(root, name);\n if (entry.isDirectory()) {\n if (!(await exists(path.join(full, \".git\")))) await walk(name, layers);\n } else if (entry.isFile()) {\n const markdown = /\\.md$/i.test(name);\n files[name] = markdown ? await readFile(full, \"utf8\") : null;\n if (\n markdown &&\n requests.some(\n (request) => !request || name === request || name.startsWith(`${request}/`),\n ) &&\n !excluded.some((pattern) => minimatch(name, pattern, { dot: true }))\n )\n selected.push(name);\n }\n }\n }\n await walk(\"\", []);\n let strictLineBreaks: boolean | undefined;\n const app = path.join(root, \".obsidian\", \"app.json\");\n if (await exists(app)) {\n const settings: unknown = JSON.parse(await readFile(app, \"utf8\"));\n strictLineBreaks =\n typeof settings === \"object\" &&\n settings !== null &&\n \"strictLineBreaks\" in settings &&\n settings.strictLineBreaks === true;\n }\n return {\n root,\n files,\n selected: selected.sort(),\n ...(strictLineBreaks !== undefined ? { strictLineBreaks } : {}),\n };\n}\nexport async function writeAtomic(file: string, before: string, after: string): Promise<void> {\n const info = await lstat(file);\n if (!info.isFile() || info.isSymbolicLink())\n throw new Error(`Refusing non-regular file: ${file}`);\n if ((await readFile(file, \"utf8\")) !== before)\n throw new Error(`File changed during formatting: ${file}`);\n const temporary = path.join(path.dirname(file), `.mdtools-${randomUUID()}.tmp`);\n const handle = await open(temporary, \"wx\", info.mode);\n try {\n await handle.writeFile(after, \"utf8\");\n await handle.chmod(info.mode);\n await handle.sync();\n await handle.close();\n const current = await lstat(file);\n if (\n current.isSymbolicLink() ||\n current.ino !== info.ino ||\n (await readFile(file, \"utf8\")) !== before\n )\n throw new Error(`File changed before replacement: ${file}`);\n await rename(temporary, file);\n } finally {\n await handle.close();\n await unlink(temporary).catch((error: NodeJS.ErrnoException) => {\n if (error.code !== \"ENOENT\") throw error;\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/workspace/files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC5F,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAS3C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,QAAgB,EAChB,QAAkB,EAClB,QAAkB,EAClB,UAA0B,EAAE;IAE5B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,KAAK,GAAoC,EAAE,CAAC;IAClD,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,EAAE;YAC1C,MAAM,IAAI,KAAK,CAAC,iCAAiC,KAAK,EAAE,CAAC,CAAC;QAC5D,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzE,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAC9E,MAAM,IAAI,KAAK,CAAC,mCAAmC,KAAK,EAAE,CAAC,CAAC;QAC9D,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAKD,KAAK,UAAU,IAAI,CAAC,QAAgB,EAAE,SAAwB,EAAE,UAAU,GAAG,KAAK;QAChF,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC;QAC9B,IAAI,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;YAClD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC;aAClF,CAAC,CAAC;QACL,KAAK,MAAM,KAAK,IAAI,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YACtE,IACE,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,QAAQ,CAC9E,KAAK,CAAC,IAAI,CACX;gBACD,KAAK,CAAC,cAAc,EAAE;gBAEtB,SAAS;YACX,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;YACjE,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACvC,KAAK,CAAC,OAAO,CAAC,OAAO,CACnB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CACtF,CACF,CAAC;YACF,IAAI,UAAU,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI;gBAAE,SAAS;YACxD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACnC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACvB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;gBACrD,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,kBAAkB,KAAK,IAAI;oBAChD,MAAM,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,IAAI,UAAU,IAAI,MAAM,CAAC,CAAC;YACjE,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC1B,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACrC,IAAI,MAA0B,CAAC;gBAC/B,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,KAAK,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC9E,IACE,QAAQ;oBACR,CAAC,UAAU;oBACX,CAAC,UAAU;oBACX,QAAQ,CAAC,IAAI,CACX,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,OAAO,GAAG,CAAC,CAC5E;oBACD,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;oBAEpE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACnB,IAAI,gBAAqC,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;IACrD,IAAI,MAAM,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;QAClE,gBAAgB;YACd,OAAO,QAAQ,KAAK,QAAQ;gBAC5B,QAAQ,KAAK,IAAI;gBACjB,kBAAkB,IAAI,QAAQ;gBAC9B,QAAQ,CAAC,gBAAgB,KAAK,IAAI,CAAC;IACvC,CAAC;IACD,OAAO;QACL,IAAI;QACJ,KAAK;QACL,WAAW;QACX,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE;QACzB,GAAG,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChE,CAAC;AACJ,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,MAAc,EAAE,KAAa;IAC3E,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,cAAc,EAAE;QACzC,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,EAAE,CAAC,CAAC;IACxD,IAAI,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,MAAM;QAC3C,MAAM,IAAI,KAAK,CAAC,mCAAmC,IAAI,EAAE,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,YAAY,UAAU,EAAE,MAAM,CAAC,CAAC;IAChF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACtC,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;QAClC,IACE,OAAO,CAAC,cAAc,EAAE;YACxB,OAAO,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG;YACxB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,MAAM;YAEzC,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,EAAE,CAAC,CAAC;QAC9D,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,KAA4B,EAAE,EAAE;YAC7D,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAC;QAC3C,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["import { lstat, readdir, readFile, realpath, open, rename, unlink } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { readFileSync } from \"node:fs\";\nimport type { ResolveOptions } from \"../core/types.js\";\nimport type { WorkspaceSource } from \"./index.js\";\nimport { randomUUID } from \"node:crypto\";\nimport ignore from \"ignore\";\nimport { minimatch } from \"minimatch\";\nimport { exists } from \"../config/load.js\";\n\nexport interface FileSet {\n root: string;\n files: Record<string, WorkspaceSource>;\n directories: string[];\n selected: string[];\n strictLineBreaks?: boolean;\n}\nexport async function discover(\n rootPath: string,\n supplied: string[],\n excluded: string[],\n resolve: ResolveOptions = {},\n): Promise<FileSet> {\n const root = await realpath(rootPath);\n const files: Record<string, WorkspaceSource> = {};\n const directories: string[] = [];\n const selected: string[] = [];\n const requests: string[] = [];\n for (const input of supplied.length ? supplied : [root]) {\n const absolute = path.resolve(input);\n if ((await lstat(absolute)).isSymbolicLink())\n throw new Error(`Refusing symbolic-link input: ${input}`);\n const resolved = await realpath(absolute);\n const relative = path.relative(root, resolved).split(path.sep).join(\"/\");\n if (relative.startsWith(\"../\") || relative === \"..\" || path.isAbsolute(relative))\n throw new Error(`Input is outside the workspace: ${input}`);\n requests.push(relative);\n }\n interface IgnoreLayer {\n base: string;\n matcher: ReturnType<typeof ignore>;\n }\n async function walk(relative: string, inherited: IgnoreLayer[], targetOnly = false) {\n const directory = path.join(root, relative);\n const layers = [...inherited];\n if (await exists(path.join(directory, \".gitignore\")))\n layers.push({\n base: relative,\n matcher: ignore().add(await readFile(path.join(directory, \".gitignore\"), \"utf8\")),\n });\n for (const entry of await readdir(directory, { withFileTypes: true })) {\n if (\n [\".git\", \".obsidian\", \"node_modules\", \".npm-cache\", \"dist\", \"coverage\"].includes(\n entry.name,\n ) ||\n entry.isSymbolicLink()\n )\n continue;\n const name = relative ? `${relative}/${entry.name}` : entry.name;\n const gitIgnored = layers.some((layer) =>\n layer.matcher.ignores(\n name.slice(layer.base ? layer.base.length + 1 : 0) + (entry.isDirectory() ? \"/\" : \"\"),\n ),\n );\n if (gitIgnored && resolve.gitIgnored !== true) continue;\n const full = path.join(root, name);\n if (entry.isDirectory()) {\n directories.push(name);\n const nested = await exists(path.join(full, \".git\"));\n if (!nested || resolve.nestedRepositories === true)\n await walk(name, layers, targetOnly || gitIgnored || nested);\n } else if (entry.isFile()) {\n const markdown = /\\.md$/i.test(name);\n let cached: string | undefined;\n files[name] = markdown ? () => (cached ??= readFileSync(full, \"utf8\")) : null;\n if (\n markdown &&\n !targetOnly &&\n !gitIgnored &&\n requests.some(\n (request) => !request || name === request || name.startsWith(`${request}/`),\n ) &&\n !excluded.some((pattern) => minimatch(name, pattern, { dot: true }))\n )\n selected.push(name);\n }\n }\n }\n await walk(\"\", []);\n let strictLineBreaks: boolean | undefined;\n const app = path.join(root, \".obsidian\", \"app.json\");\n if (await exists(app)) {\n const settings: unknown = JSON.parse(await readFile(app, \"utf8\"));\n strictLineBreaks =\n typeof settings === \"object\" &&\n settings !== null &&\n \"strictLineBreaks\" in settings &&\n settings.strictLineBreaks === true;\n }\n return {\n root,\n files,\n directories,\n selected: selected.sort(),\n ...(strictLineBreaks !== undefined ? { strictLineBreaks } : {}),\n };\n}\nexport async function writeAtomic(file: string, before: string, after: string): Promise<void> {\n const info = await lstat(file);\n if (!info.isFile() || info.isSymbolicLink())\n throw new Error(`Refusing non-regular file: ${file}`);\n if ((await readFile(file, \"utf8\")) !== before)\n throw new Error(`File changed during formatting: ${file}`);\n const temporary = path.join(path.dirname(file), `.mdtools-${randomUUID()}.tmp`);\n const handle = await open(temporary, \"wx\", info.mode);\n try {\n await handle.writeFile(after, \"utf8\");\n await handle.chmod(info.mode);\n await handle.sync();\n await handle.close();\n const current = await lstat(file);\n if (\n current.isSymbolicLink() ||\n current.ino !== info.ino ||\n (await readFile(file, \"utf8\")) !== before\n )\n throw new Error(`File changed before replacement: ${file}`);\n await rename(temporary, file);\n } finally {\n await handle.close();\n await unlink(temporary).catch((error: NodeJS.ErrnoException) => {\n if (error.code !== \"ENOENT\") throw error;\n });\n }\n}\n"]}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Dialect, Workspace } from "../core/types.js";
|
|
2
|
+
export type WorkspaceSource = string | null | (() => string);
|
|
2
3
|
export interface WorkspaceOptions {
|
|
3
4
|
dialect?: Dialect;
|
|
4
5
|
strictLineBreaks?: boolean;
|
|
6
|
+
/** Existing directories, including empty ones; never treated as note targets. */
|
|
7
|
+
directories?: string[];
|
|
5
8
|
}
|
|
6
9
|
export declare function splitDestination(destination: string): {
|
|
7
10
|
path: string;
|
|
8
11
|
fragment: string;
|
|
9
12
|
};
|
|
10
|
-
export declare function createWorkspace(files: Record<string,
|
|
13
|
+
export declare function createWorkspace(files: Record<string, WorkspaceSource>, options?: WorkspaceOptions): Workspace;
|
package/dist/workspace/index.js
CHANGED
|
@@ -18,10 +18,28 @@ export function splitDestination(destination) {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
export function createWorkspace(files, options = {}) {
|
|
21
|
+
const sources = new Map(Object.entries(files).map(([name, source]) => [
|
|
22
|
+
name.replaceAll("\\", "/").replace(/^\.\//, ""),
|
|
23
|
+
source,
|
|
24
|
+
]));
|
|
25
|
+
const directories = new Set((options.directories ?? []).map((name) => name.replaceAll("\\", "/")));
|
|
26
|
+
directories.add(".");
|
|
27
|
+
for (const name of sources.keys()) {
|
|
28
|
+
let directory = path.posix.dirname(name);
|
|
29
|
+
while (directory !== "." && directory !== path.posix.dirname(directory)) {
|
|
30
|
+
directories.add(directory);
|
|
31
|
+
directory = path.posix.dirname(directory);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
21
34
|
const entries = new Map();
|
|
22
|
-
|
|
35
|
+
function fragments(name) {
|
|
36
|
+
const cached = entries.get(name);
|
|
37
|
+
if (cached)
|
|
38
|
+
return cached;
|
|
23
39
|
const entry = { headings: new Set(), slugs: new Set(), blocks: new Set() };
|
|
24
|
-
|
|
40
|
+
const value = sources.get(name);
|
|
41
|
+
if (value !== null && value !== undefined && /\.md$/i.test(name)) {
|
|
42
|
+
const source = typeof value === "function" ? value() : value;
|
|
25
43
|
const document = parse(source, options.dialect ?? "commonmark", name);
|
|
26
44
|
const slugger = new GithubSlugger();
|
|
27
45
|
visit(document.tree, "heading", (node) => {
|
|
@@ -35,7 +53,26 @@ export function createWorkspace(files, options = {}) {
|
|
|
35
53
|
entry.blocks.add(match[1]);
|
|
36
54
|
});
|
|
37
55
|
}
|
|
38
|
-
entries.set(name
|
|
56
|
+
entries.set(name, entry);
|
|
57
|
+
return entry;
|
|
58
|
+
}
|
|
59
|
+
let suffixes;
|
|
60
|
+
function suffixCandidates(target) {
|
|
61
|
+
if (!suffixes) {
|
|
62
|
+
suffixes = new Map();
|
|
63
|
+
for (const name of sources.keys()) {
|
|
64
|
+
const parts = name.split("/");
|
|
65
|
+
for (let i = 0; i < parts.length; i++) {
|
|
66
|
+
const suffix = parts.slice(i).join("/");
|
|
67
|
+
for (const key of suffix.endsWith(".md") ? [suffix, suffix.slice(0, -3)] : [suffix]) {
|
|
68
|
+
if (!suffixes.has(key))
|
|
69
|
+
suffixes.set(key, new Set());
|
|
70
|
+
suffixes.get(key).add(name);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return suffixes.get(target);
|
|
39
76
|
}
|
|
40
77
|
return {
|
|
41
78
|
...(options.strictLineBreaks !== undefined
|
|
@@ -44,20 +81,24 @@ export function createWorkspace(files, options = {}) {
|
|
|
44
81
|
resolve(source, destination, dialect) {
|
|
45
82
|
if (/^[a-z][a-z\d+.-]*:/i.test(destination) || destination.startsWith("//"))
|
|
46
83
|
return { status: "external" };
|
|
84
|
+
source = source.replaceAll("\\", "/");
|
|
47
85
|
const parts = splitDestination(destination);
|
|
48
86
|
const targetPath = parts.path;
|
|
49
87
|
if (dialect !== "obsidian" && (targetPath.startsWith("/") || targetPath.includes("?")))
|
|
50
88
|
return { status: "unavailable" };
|
|
51
89
|
const candidates = new Set();
|
|
90
|
+
let directory = false;
|
|
52
91
|
const add = (candidate) => {
|
|
53
92
|
const normalized = path.posix.normalize(candidate);
|
|
54
93
|
if (normalized.startsWith("../") || path.posix.isAbsolute(normalized))
|
|
55
94
|
return;
|
|
56
|
-
if (
|
|
95
|
+
if (directories.has(normalized.replace(/\/$/, "")))
|
|
96
|
+
directory = true;
|
|
97
|
+
if (sources.has(normalized))
|
|
57
98
|
candidates.add(normalized);
|
|
58
99
|
if (dialect === "obsidian" &&
|
|
59
100
|
!path.posix.extname(normalized) &&
|
|
60
|
-
|
|
101
|
+
sources.has(`${normalized}.md`))
|
|
61
102
|
candidates.add(`${normalized}.md`);
|
|
62
103
|
};
|
|
63
104
|
if (!targetPath)
|
|
@@ -67,26 +108,23 @@ export function createWorkspace(files, options = {}) {
|
|
|
67
108
|
add(path.posix.join(path.posix.dirname(source), targetPath));
|
|
68
109
|
else {
|
|
69
110
|
add(targetPath.replace(/^\//, ""));
|
|
70
|
-
if (!targetPath.startsWith("/"))
|
|
111
|
+
if (candidates.size === 0 && !directory && !targetPath.startsWith("/"))
|
|
71
112
|
add(path.posix.join(path.posix.dirname(source), targetPath));
|
|
72
|
-
if (candidates.size === 0) {
|
|
73
|
-
for (const name of
|
|
74
|
-
|
|
75
|
-
name.endsWith(`/${targetPath}`) ||
|
|
76
|
-
name === `${targetPath}.md` ||
|
|
77
|
-
name.endsWith(`/${targetPath}.md`))
|
|
78
|
-
candidates.add(name);
|
|
79
|
-
}
|
|
113
|
+
if (candidates.size === 0 && !directory && !targetPath.startsWith("/")) {
|
|
114
|
+
for (const name of suffixCandidates(targetPath) ?? [])
|
|
115
|
+
candidates.add(name);
|
|
80
116
|
}
|
|
81
117
|
}
|
|
82
118
|
}
|
|
83
119
|
else
|
|
84
120
|
add(path.posix.join(path.posix.dirname(source), targetPath));
|
|
121
|
+
if (candidates.size === 0 && directory)
|
|
122
|
+
return { status: "directory" };
|
|
85
123
|
if (candidates.size !== 1)
|
|
86
124
|
return { status: candidates.size ? "ambiguous" : "missing" };
|
|
87
125
|
const target = [...candidates][0];
|
|
88
|
-
const entry = entries.get(target);
|
|
89
126
|
const fragment = parts.fragment;
|
|
127
|
+
const entry = fragment ? fragments(target) : undefined;
|
|
90
128
|
const fragmentExists = !fragment ||
|
|
91
129
|
(dialect === "obsidian"
|
|
92
130
|
? fragment.startsWith("^")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/workspace/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,aAAa,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAWxD,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE;QAC/B,IAAI,CAAC;YACH,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,CAAC;IACF,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACjE,QAAQ,EAAE,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;KAC9D,CAAC;AACJ,CAAC;AACD,MAAM,UAAU,eAAe,CAC7B,KAAoC,EACpC,UAA4B,EAAE;IAE9B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACnD,MAAM,KAAK,GAAU,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;QAClF,IAAI,MAAM,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3C,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,IAAI,YAAY,EAAE,IAAI,CAAC,CAAC;YACtE,MAAM,OAAO,GAAG,IAAI,aAAa,EAAE,CAAC;YACpC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;gBACvC,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;gBAC/B,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBACzB,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;YACH,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACpC,MAAM,KAAK,GAAG,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC/D,IAAI,KAAK;oBAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;YACzC,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;IACtE,CAAC;IACD,OAAO;QACL,GAAG,CAAC,OAAO,CAAC,gBAAgB,KAAK,SAAS;YACxC,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,EAAE;YAChD,CAAC,CAAC,EAAE,CAAC;QACP,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO;YAClC,IAAI,qBAAqB,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC;gBACzE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;YAChC,MAAM,KAAK,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;YAC5C,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC;YAC9B,IAAI,OAAO,KAAK,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBACpF,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;YACnC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;YACrC,MAAM,GAAG,GAAG,CAAC,SAAiB,EAAE,EAAE;gBAChC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;gBACnD,IAAI,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC;oBAAE,OAAO;gBAC9E,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;oBAAE,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBACxD,IACE,OAAO,KAAK,UAAU;oBACtB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;oBAC/B,OAAO,CAAC,GAAG,CAAC,GAAG,UAAU,KAAK,CAAC;oBAE/B,UAAU,CAAC,GAAG,CAAC,GAAG,UAAU,KAAK,CAAC,CAAC;YACvC,CAAC,CAAC;YACF,IAAI,CAAC,UAAU;gBAAE,GAAG,CAAC,MAAM,CAAC,CAAC;iBACxB,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;gBAChC,IAAI,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC;oBAC/B,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;qBAC1D,CAAC;oBACJ,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;oBACnC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC;wBAC7B,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;oBAC/D,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;wBAC1B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;4BAClC,IACE,IAAI,KAAK,UAAU;gCACnB,IAAI,CAAC,QAAQ,CAAC,IAAI,UAAU,EAAE,CAAC;gCAC/B,IAAI,KAAK,GAAG,UAAU,KAAK;gCAC3B,IAAI,CAAC,QAAQ,CAAC,IAAI,UAAU,KAAK,CAAC;gCAElC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;wBACzB,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;;gBAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;YACpE,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC;gBAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;YACxF,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAE,CAAC;YACnC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;YAChC,MAAM,cAAc,GAClB,CAAC,QAAQ;gBACT,CAAC,OAAO,KAAK,UAAU;oBACrB,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC;wBACxB,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;wBACrC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;oBAChC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;YACjC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;QAClE,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import path from \"node:path\";\nimport GithubSlugger from \"github-slugger\";\nimport { visit } from \"unist-util-visit\";\nimport type { Dialect, LinkResolution, Workspace } from \"../core/types.js\";\nimport { parse, textContent } from \"../syntax/parse.js\";\n\ninterface Entry {\n headings: Set<string>;\n slugs: Set<string>;\n blocks: Set<string>;\n}\nexport interface WorkspaceOptions {\n dialect?: Dialect;\n strictLineBreaks?: boolean;\n}\nexport function splitDestination(destination: string): { path: string; fragment: string } {\n const hash = destination.indexOf(\"#\");\n const decode = (value: string) => {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n };\n return {\n path: decode(hash < 0 ? destination : destination.slice(0, hash)),\n fragment: decode(hash < 0 ? \"\" : destination.slice(hash + 1)),\n };\n}\nexport function createWorkspace(\n files: Record<string, string | null>,\n options: WorkspaceOptions = {},\n): Workspace {\n const entries = new Map<string, Entry>();\n for (const [name, source] of Object.entries(files)) {\n const entry: Entry = { headings: new Set(), slugs: new Set(), blocks: new Set() };\n if (source !== null && /\\.md$/i.test(name)) {\n const document = parse(source, options.dialect ?? \"commonmark\", name);\n const slugger = new GithubSlugger();\n visit(document.tree, \"heading\", (node) => {\n const text = textContent(node);\n entry.headings.add(text);\n entry.slugs.add(slugger.slug(text));\n });\n visit(document.tree, \"text\", (node) => {\n const match = /(?:^|\\s)\\^([A-Za-z0-9-]+)\\s*$/.exec(node.value);\n if (match) entry.blocks.add(match[1]!);\n });\n }\n entries.set(name.replaceAll(\"\\\\\", \"/\").replace(/^\\.\\//, \"\"), entry);\n }\n return {\n ...(options.strictLineBreaks !== undefined\n ? { strictLineBreaks: options.strictLineBreaks }\n : {}),\n resolve(source, destination, dialect): LinkResolution {\n if (/^[a-z][a-z\\d+.-]*:/i.test(destination) || destination.startsWith(\"//\"))\n return { status: \"external\" };\n const parts = splitDestination(destination);\n const targetPath = parts.path;\n if (dialect !== \"obsidian\" && (targetPath.startsWith(\"/\") || targetPath.includes(\"?\")))\n return { status: \"unavailable\" };\n const candidates = new Set<string>();\n const add = (candidate: string) => {\n const normalized = path.posix.normalize(candidate);\n if (normalized.startsWith(\"../\") || path.posix.isAbsolute(normalized)) return;\n if (entries.has(normalized)) candidates.add(normalized);\n if (\n dialect === \"obsidian\" &&\n !path.posix.extname(normalized) &&\n entries.has(`${normalized}.md`)\n )\n candidates.add(`${normalized}.md`);\n };\n if (!targetPath) add(source);\n else if (dialect === \"obsidian\") {\n if (/^\\.{1,2}\\//.test(targetPath))\n add(path.posix.join(path.posix.dirname(source), targetPath));\n else {\n add(targetPath.replace(/^\\//, \"\"));\n if (!targetPath.startsWith(\"/\"))\n add(path.posix.join(path.posix.dirname(source), targetPath));\n if (candidates.size === 0) {\n for (const name of entries.keys()) {\n if (\n name === targetPath ||\n name.endsWith(`/${targetPath}`) ||\n name === `${targetPath}.md` ||\n name.endsWith(`/${targetPath}.md`)\n )\n candidates.add(name);\n }\n }\n }\n } else add(path.posix.join(path.posix.dirname(source), targetPath));\n if (candidates.size !== 1) return { status: candidates.size ? \"ambiguous\" : \"missing\" };\n const target = [...candidates][0]!;\n const entry = entries.get(target)!;\n const fragment = parts.fragment;\n const fragmentExists =\n !fragment ||\n (dialect === \"obsidian\"\n ? fragment.startsWith(\"^\")\n ? entry.blocks.has(fragment.slice(1))\n : entry.headings.has(fragment)\n : entry.slugs.has(fragment));\n return { status: \"resolved\", target, fragment, fragmentExists };\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/workspace/index.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport GithubSlugger from \"github-slugger\";\nimport { visit } from \"unist-util-visit\";\nimport type { Dialect, LinkResolution, Workspace } from \"../core/types.js\";\nimport { parse, textContent } from \"../syntax/parse.js\";\n\nexport type WorkspaceSource = string | null | (() => string);\n\ninterface Entry {\n headings: Set<string>;\n slugs: Set<string>;\n blocks: Set<string>;\n}\nexport interface WorkspaceOptions {\n dialect?: Dialect;\n strictLineBreaks?: boolean;\n /** Existing directories, including empty ones; never treated as note targets. */\n directories?: string[];\n}\nexport function splitDestination(destination: string): { path: string; fragment: string } {\n const hash = destination.indexOf(\"#\");\n const decode = (value: string) => {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n };\n return {\n path: decode(hash < 0 ? destination : destination.slice(0, hash)),\n fragment: decode(hash < 0 ? \"\" : destination.slice(hash + 1)),\n };\n}\nexport function createWorkspace(\n files: Record<string, WorkspaceSource>,\n options: WorkspaceOptions = {},\n): Workspace {\n const sources = new Map(\n Object.entries(files).map(([name, source]) => [\n name.replaceAll(\"\\\\\", \"/\").replace(/^\\.\\//, \"\"),\n source,\n ]),\n );\n const directories = new Set(\n (options.directories ?? []).map((name) => name.replaceAll(\"\\\\\", \"/\")),\n );\n directories.add(\".\");\n for (const name of sources.keys()) {\n let directory = path.posix.dirname(name);\n while (directory !== \".\" && directory !== path.posix.dirname(directory)) {\n directories.add(directory);\n directory = path.posix.dirname(directory);\n }\n }\n const entries = new Map<string, Entry>();\n function fragments(name: string): Entry {\n const cached = entries.get(name);\n if (cached) return cached;\n const entry: Entry = { headings: new Set(), slugs: new Set(), blocks: new Set() };\n const value = sources.get(name);\n if (value !== null && value !== undefined && /\\.md$/i.test(name)) {\n const source = typeof value === \"function\" ? value() : value;\n const document = parse(source, options.dialect ?? \"commonmark\", name);\n const slugger = new GithubSlugger();\n visit(document.tree, \"heading\", (node) => {\n const text = textContent(node);\n entry.headings.add(text);\n entry.slugs.add(slugger.slug(text));\n });\n visit(document.tree, \"text\", (node) => {\n const match = /(?:^|\\s)\\^([A-Za-z0-9-]+)\\s*$/.exec(node.value);\n if (match) entry.blocks.add(match[1]!);\n });\n }\n entries.set(name, entry);\n return entry;\n }\n let suffixes: Map<string, Set<string>> | undefined;\n function suffixCandidates(target: string): Set<string> | undefined {\n if (!suffixes) {\n suffixes = new Map();\n for (const name of sources.keys()) {\n const parts = name.split(\"/\");\n for (let i = 0; i < parts.length; i++) {\n const suffix = parts.slice(i).join(\"/\");\n for (const key of suffix.endsWith(\".md\") ? [suffix, suffix.slice(0, -3)] : [suffix]) {\n if (!suffixes.has(key)) suffixes.set(key, new Set());\n suffixes.get(key)!.add(name);\n }\n }\n }\n }\n return suffixes.get(target);\n }\n return {\n ...(options.strictLineBreaks !== undefined\n ? { strictLineBreaks: options.strictLineBreaks }\n : {}),\n resolve(source, destination, dialect): LinkResolution {\n if (/^[a-z][a-z\\d+.-]*:/i.test(destination) || destination.startsWith(\"//\"))\n return { status: \"external\" };\n source = source.replaceAll(\"\\\\\", \"/\");\n const parts = splitDestination(destination);\n const targetPath = parts.path;\n if (dialect !== \"obsidian\" && (targetPath.startsWith(\"/\") || targetPath.includes(\"?\")))\n return { status: \"unavailable\" };\n const candidates = new Set<string>();\n let directory = false;\n const add = (candidate: string) => {\n const normalized = path.posix.normalize(candidate);\n if (normalized.startsWith(\"../\") || path.posix.isAbsolute(normalized)) return;\n if (directories.has(normalized.replace(/\\/$/, \"\"))) directory = true;\n if (sources.has(normalized)) candidates.add(normalized);\n if (\n dialect === \"obsidian\" &&\n !path.posix.extname(normalized) &&\n sources.has(`${normalized}.md`)\n )\n candidates.add(`${normalized}.md`);\n };\n if (!targetPath) add(source);\n else if (dialect === \"obsidian\") {\n if (/^\\.{1,2}\\//.test(targetPath))\n add(path.posix.join(path.posix.dirname(source), targetPath));\n else {\n add(targetPath.replace(/^\\//, \"\"));\n if (candidates.size === 0 && !directory && !targetPath.startsWith(\"/\"))\n add(path.posix.join(path.posix.dirname(source), targetPath));\n if (candidates.size === 0 && !directory && !targetPath.startsWith(\"/\")) {\n for (const name of suffixCandidates(targetPath) ?? []) candidates.add(name);\n }\n }\n } else add(path.posix.join(path.posix.dirname(source), targetPath));\n if (candidates.size === 0 && directory) return { status: \"directory\" };\n if (candidates.size !== 1) return { status: candidates.size ? \"ambiguous\" : \"missing\" };\n const target = [...candidates][0]!;\n const fragment = parts.fragment;\n const entry = fragment ? fragments(target) : undefined;\n const fragmentExists =\n !fragment ||\n (dialect === \"obsidian\"\n ? fragment.startsWith(\"^\")\n ? entry!.blocks.has(fragment.slice(1))\n : entry!.headings.has(fragment)\n : entry!.slugs.has(fragment));\n return { status: \"resolved\", target, fragment, fragmentExists };\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
/** Exclusions are literal paths relative to cwd, never glob patterns. */
|
|
4
|
+
export async function excludeSelection(root, selected, exclusions) {
|
|
5
|
+
// Resolve filesystem aliases (including Windows short names) consistently
|
|
6
|
+
// with discovery, while allowing exclusions for paths that do not exist yet.
|
|
7
|
+
async function canonical(file) {
|
|
8
|
+
try {
|
|
9
|
+
return await realpath(file);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (error.code !== "ENOENT")
|
|
13
|
+
throw error;
|
|
14
|
+
const parent = path.dirname(file);
|
|
15
|
+
if (parent === file)
|
|
16
|
+
throw error;
|
|
17
|
+
return path.join(await canonical(parent), path.basename(file));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const relative = await Promise.all(exclusions.map(async (input) => {
|
|
21
|
+
const name = path
|
|
22
|
+
.relative(root, await canonical(path.resolve(input)))
|
|
23
|
+
.split(path.sep)
|
|
24
|
+
.join("/");
|
|
25
|
+
if (name === ".." || name.startsWith("../") || path.isAbsolute(name))
|
|
26
|
+
throw new Error(`Excluded path is outside the workspace: ${input}`);
|
|
27
|
+
return name;
|
|
28
|
+
}));
|
|
29
|
+
return selected.filter((name) => !relative.some((excluded) => !excluded || name === excluded || name.startsWith(`${excluded}/`)));
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.js","sourceRoot":"","sources":["../../src/workspace/selection.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAY,EACZ,QAAkB,EAClB,UAAoB;IAEpB,0EAA0E;IAC1E,6EAA6E;IAC7E,KAAK,UAAU,SAAS,CAAC,IAAY;QACnC,IAAI,CAAC;YACH,OAAO,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAC;YACpE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAClC,IAAI,MAAM,KAAK,IAAI;gBAAE,MAAM,KAAK,CAAC;YACjC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,IAAI;aACd,QAAQ,CAAC,IAAI,EAAE,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;aACpD,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;aACf,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAClE,MAAM,IAAI,KAAK,CAAC,2CAA2C,KAAK,EAAE,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CACH,CAAC;IACF,OAAO,QAAQ,CAAC,MAAM,CACpB,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,QAAQ,CAAC,IAAI,CACZ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,QAAQ,GAAG,CAAC,CAChF,CACJ,CAAC;AACJ,CAAC","sourcesContent":["import path from \"node:path\";\nimport { realpath } from \"node:fs/promises\";\n\n/** Exclusions are literal paths relative to cwd, never glob patterns. */\nexport async function excludeSelection(\n root: string,\n selected: string[],\n exclusions: string[],\n): Promise<string[]> {\n // Resolve filesystem aliases (including Windows short names) consistently\n // with discovery, while allowing exclusions for paths that do not exist yet.\n async function canonical(file: string): Promise<string> {\n try {\n return await realpath(file);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n const parent = path.dirname(file);\n if (parent === file) throw error;\n return path.join(await canonical(parent), path.basename(file));\n }\n }\n const relative = await Promise.all(\n exclusions.map(async (input) => {\n const name = path\n .relative(root, await canonical(path.resolve(input)))\n .split(path.sep)\n .join(\"/\");\n if (name === \"..\" || name.startsWith(\"../\") || path.isAbsolute(name))\n throw new Error(`Excluded path is outside the workspace: ${input}`);\n return name;\n }),\n );\n return selected.filter(\n (name) =>\n !relative.some(\n (excluded) => !excluded || name === excluded || name.startsWith(`${excluded}/`),\n ),\n );\n}\n"]}
|
package/docs/cli.md
CHANGED
|
@@ -19,6 +19,24 @@ honors `.gitignore` and configuration ignore patterns, skips symlinks and nested
|
|
|
19
19
|
repositories, and does not expand path globs itself. See the
|
|
20
20
|
[selection details](configuration.md#selection-output-and-exit-codes).
|
|
21
21
|
|
|
22
|
+
Use a caller or Git hook to choose changed or staged paths and pass them as
|
|
23
|
+
explicit file arguments. The CLI processes working-tree contents, not the Git
|
|
24
|
+
index. Pass paths as separate arguments (after `--` when they could begin with
|
|
25
|
+
`-`), retain `--root` for workspace-wide link resolution, and skip the
|
|
26
|
+
invocation when the selection is empty: no file arguments means the whole
|
|
27
|
+
workspace.
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
node dist/cli/main.js lint --exclude generated
|
|
31
|
+
node dist/cli/main.js format --exclude notes/archive.md --exclude vendor --diff
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Repeat `--exclude <path>` to omit exact files or directory subtrees from any
|
|
35
|
+
file selection. Exclusions are literal paths relative to the current directory
|
|
36
|
+
(or absolute paths), must be inside the workspace, and do not remove link
|
|
37
|
+
targets from the index. They cannot be combined with stdin. Config ignores and
|
|
38
|
+
repository boundaries still apply to all selections.
|
|
39
|
+
|
|
22
40
|
## Use an explicit configuration
|
|
23
41
|
|
|
24
42
|
```sh
|
package/docs/configuration.md
CHANGED
|
@@ -57,22 +57,62 @@ Problem rules report findings and never become formatting edits.
|
|
|
57
57
|
|
|
58
58
|
## Built-in rules
|
|
59
59
|
|
|
60
|
-
| Rule | Kind | Options
|
|
61
|
-
| ----------------------------- | ------- |
|
|
62
|
-
| `style/wrap` | Style | `width
|
|
63
|
-
| `style/
|
|
64
|
-
| `style/
|
|
65
|
-
| `style/
|
|
66
|
-
| `style/
|
|
67
|
-
| `style/
|
|
68
|
-
| `
|
|
69
|
-
| `links/
|
|
70
|
-
| `links/
|
|
71
|
-
| `
|
|
72
|
-
| `github/
|
|
73
|
-
| `
|
|
74
|
-
| `obsidian/
|
|
75
|
-
| `obsidian/
|
|
60
|
+
| Rule | Kind | Options |
|
|
61
|
+
| ----------------------------- | ------- | ---------------------------------------------------------------------- |
|
|
62
|
+
| `style/wrap` | Style | `width`, `measure`, `reportUnreflowed`, `reportUnbreakable`; see below |
|
|
63
|
+
| `style/inline-code` | Style | None; joins multiline code spans, opt-in |
|
|
64
|
+
| `style/emphasis` | Style | `marker`: `_` (default) or `*` |
|
|
65
|
+
| `style/strong` | Style | `marker`: `*` (default) or `_` |
|
|
66
|
+
| `style/final-newline` | Style | None; adds a missing final newline |
|
|
67
|
+
| `style/table` | Style | None; aligns top-level GFM tables |
|
|
68
|
+
| `style/heading` | Style | None; converts eligible Setext headings to ATX |
|
|
69
|
+
| `links/valid` | Problem | None; local files and heading/block fragments |
|
|
70
|
+
| `links/path` | Style | `style`, `brackets`, `extension`, `leadingDot`; see below |
|
|
71
|
+
| `links/notation` | Style | `style`: `markdown` or `wiki`; Obsidian only |
|
|
72
|
+
| `github/task-marker` | Style | None; `[X]` becomes `[x]` |
|
|
73
|
+
| `github/alert-marker` | Style | None; known GitHub alert types become uppercase |
|
|
74
|
+
| `obsidian/callout-marker` | Style | None; Obsidian callout types become lowercase |
|
|
75
|
+
| `obsidian/block-reference` | Problem | None; duplicate trailing `^block-id` markers |
|
|
76
|
+
| `obsidian/strict-line-breaks` | Problem | None; report unverified/incompatible reflow settings |
|
|
77
|
+
|
|
78
|
+
## Document structure
|
|
79
|
+
|
|
80
|
+
Enable `structure/initial-heading` explicitly with severity `warn` or `error`;
|
|
81
|
+
no preset enables it. It accepts no options and reports problems without
|
|
82
|
+
changing content. Use path `overrides` to exempt generated documents or trees
|
|
83
|
+
with different conventions.
|
|
84
|
+
|
|
85
|
+
`structure/initial-heading` skips YAML/TOML front matter and requires a level-1
|
|
86
|
+
heading as the first content block. Both ATX and Setext headings count; use
|
|
87
|
+
`style/heading` separately to require ATX spelling. Empty documents are
|
|
88
|
+
reported. Comments and other content before the title are not skipped.
|
|
89
|
+
|
|
90
|
+
Filename/title matching, metadata placement, and guesses about accidental
|
|
91
|
+
Markdown markers are consumer policies. Implement those checks through
|
|
92
|
+
[rule plugins](plugins.md#rule-plugins), which receive the document source,
|
|
93
|
+
parsed tree, and path. Modified-date updates belong in a consumer wrapper or
|
|
94
|
+
library workflow; formatting rules must preserve document meaning.
|
|
95
|
+
|
|
96
|
+
## Wrapping and inline code
|
|
97
|
+
|
|
98
|
+
`style/wrap` accepts widths from 20 through 500 and defaults to `width: 80` and
|
|
99
|
+
`measure: "columns"` (display columns). Set `measure: "codepoints"` to count
|
|
100
|
+
Unicode scalar values instead; astral characters count once and combining marks
|
|
101
|
+
count separately. Container prefixes count toward the width in either mode.
|
|
102
|
+
|
|
103
|
+
`reportUnreflowed: true` reports over-width paragraphs protected by hard breaks,
|
|
104
|
+
block IDs, inline HTML, callout headers, or unsupported multiline syntax or
|
|
105
|
+
containers. `reportUnbreakable: true` reports overflow remaining after reflow
|
|
106
|
+
because an atom cannot be split. Both default to false, propose no edits, and
|
|
107
|
+
use the rule's severity. Use severity `error` or `--max-warnings 0` to make
|
|
108
|
+
these diagnostics fail a check; `--check` does not change rule settings.
|
|
109
|
+
|
|
110
|
+
Enable `style/inline-code` to join multiline code spans before paragraph reflow.
|
|
111
|
+
This opt-in rule handles single and multiple backticks, including list and quote
|
|
112
|
+
containers. It preserves the parsed code value, converting each line ending to
|
|
113
|
+
one space under CommonMark's code-span rules. Other code whitespace remains
|
|
114
|
+
significant. It is useful for Obsidian editors that display split spans
|
|
115
|
+
differently from Reading view.
|
|
76
116
|
|
|
77
117
|
## Link policies
|
|
78
118
|
|
|
@@ -95,6 +135,9 @@ vault-relative path. `leadingDot: true` prefixes ordinary relative paths with
|
|
|
95
135
|
Markdown destinations are percent-encoded. `extension` accepts `preserve`
|
|
96
136
|
(default), `include`, or `omit`; omission is Obsidian specific. Fragments are
|
|
97
137
|
resolved according to the dialect, separately from percent-decoded filenames.
|
|
138
|
+
Already compliant destinations retain their percent-encoding spelling, including
|
|
139
|
+
literal `?` and `%` in resolving Obsidian paths. Newly generated paths encode
|
|
140
|
+
literal percent signs and hashes to preserve target identity.
|
|
98
141
|
|
|
99
142
|
Path edits require a resolved target and valid fragment. Unaliased ordinary
|
|
100
143
|
wikilinks retain their target spelling because changing it may change the
|
|
@@ -110,10 +153,15 @@ separate passes.
|
|
|
110
153
|
|
|
111
154
|
Network URLs are recognized but never fetched. Website-root paths and
|
|
112
155
|
query-bearing destinations in CommonMark/GitHub are outside local resolution.
|
|
113
|
-
Missing or ambiguous targets are reported, never guessed.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
the
|
|
156
|
+
Missing or ambiguous targets are reported, never guessed. Obsidian resolution
|
|
157
|
+
uses explicit `./` or `../` paths as source-relative; otherwise an exact
|
|
158
|
+
vault-root match precedes an exact source-relative match, followed by unique
|
|
159
|
+
suffix matching. Multiple candidates within the chosen tier remain ambiguous.
|
|
160
|
+
Relative rewrites use `./` when needed to avoid a vault-root collision. Existing
|
|
161
|
+
directories get a distinct diagnostic in Obsidian; they are not rewritten to
|
|
162
|
+
README or index notes. CommonMark/GitHub directory links are accepted because a
|
|
163
|
+
hosting site can serve them. Shortest-name/alias resolution is not a complete
|
|
164
|
+
clone of Obsidian.
|
|
117
165
|
|
|
118
166
|
## Suppressions
|
|
119
167
|
|
|
@@ -139,11 +187,24 @@ it directly above the content to suppress.
|
|
|
139
187
|
|
|
140
188
|
Commands accept explicit files/directories, or no paths to select the workspace.
|
|
141
189
|
The CLI honors `.gitignore` files and configured ignore patterns. Config-ignored
|
|
142
|
-
documents remain available for link resolution
|
|
143
|
-
indexed.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
190
|
+
documents remain available for link resolution. Git-ignored content and nested
|
|
191
|
+
repositories are not indexed by default. To make their files available only as
|
|
192
|
+
link targets, set:
|
|
193
|
+
|
|
194
|
+
```jsonc
|
|
195
|
+
{
|
|
196
|
+
"resolve": { "gitIgnored": true, "nestedRepositories": true },
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
These options are independent and default to false. They never select those
|
|
201
|
+
files for linting or formatting, including when a path is supplied explicitly.
|
|
202
|
+
Markdown targets are read and parsed only for fragment checks; attachments need
|
|
203
|
+
only a discovered file entry. Built-in traversal exclusions still apply. `.git`,
|
|
204
|
+
`.obsidian`, `node_modules`, `.npm-cache`, `dist`, and `coverage` are excluded
|
|
205
|
+
from traversal, as are symlinks. Nested Git repositories are traversed for
|
|
206
|
+
targets only when opted in. Explicit symlink inputs are rejected. Shell-expanded
|
|
207
|
+
globs work; the CLI does not expand path globs.
|
|
147
208
|
|
|
148
209
|
`lint --json` and `format --json` print a single object with `version`, `mode`,
|
|
149
210
|
`files`, and `written`. Each file carries diagnostics with rule ID, severity,
|
package/docs/contributing.md
CHANGED
|
@@ -28,8 +28,8 @@ respectful and focused on reproducible behavior; harassment and disclosure of
|
|
|
28
28
|
private data are not acceptable. Contributions are provided under the MIT
|
|
29
29
|
license.
|
|
30
30
|
|
|
31
|
-
The branch policy requires the
|
|
32
|
-
|
|
31
|
+
The branch policy requires the three CI checks and resolved review threads. It
|
|
32
|
+
does not require approval from a second account: this project is
|
|
33
33
|
agent-maintained and cannot assume another maintainer is available. Request
|
|
34
34
|
independent review when available, especially for file writes and release
|
|
35
35
|
automation. Passing CI is not a claim of human review.
|
|
@@ -41,6 +41,77 @@ versions and release tags are never replaced. Follow the
|
|
|
41
41
|
reports belong in the private channel described in
|
|
42
42
|
[SECURITY.md](https://github.com/viell-dev/slop-markdown-tools/blob/main/SECURITY.md).
|
|
43
43
|
|
|
44
|
+
## Issue and pull request labels
|
|
45
|
+
|
|
46
|
+
Use one primary `Type:` label on every issue and PR, and at most one `Status:`
|
|
47
|
+
label describing the current disposition or next action. The type describes what
|
|
48
|
+
the item is about; a feature implementation PR also uses
|
|
49
|
+
`Type: Feature Request`. For mixed historical items, choose the main purpose and
|
|
50
|
+
explain separate decisions in comments. Split new requests when their parts need
|
|
51
|
+
independent scope decisions. Do not add priority labels.
|
|
52
|
+
|
|
53
|
+
The
|
|
54
|
+
[label catalog](https://github.com/viell-dev/slop-markdown-tools/blob/main/.github/labels.json)
|
|
55
|
+
is the source of truth for exact names, descriptions, and hexadecimal colors.
|
|
56
|
+
Keep it, this guide, GitHub, issue templates, and Dependabot settings aligned.
|
|
57
|
+
Use the GitHub UI or the existing `gh` CLI; no label-sync service is required.
|
|
58
|
+
|
|
59
|
+
| Label | Color | Use |
|
|
60
|
+
| --------------------------- | --------- | ---------------------------------------------------------------------------- |
|
|
61
|
+
| `Type: Bug` | `#d73a4a` | Incorrect behavior, broken functionality, or a regression. |
|
|
62
|
+
| `Type: Enhancement` | `#a2eeef` | Improve an existing capability, including usability or performance. |
|
|
63
|
+
| `Type: Feature Request` | `#0e8a16` | Add a new independently useful capability or rule. |
|
|
64
|
+
| `Type: Maintenance` | `#6a737d` | Dependencies, CI, releases, refactoring, or repository upkeep. |
|
|
65
|
+
| `Type: Documentation` | `#0075ca` | Documentation, examples, or contributor instructions. |
|
|
66
|
+
| `Type: Discussion` | `#5319e7` | Explore a design or project direction before an actionable proposal. |
|
|
67
|
+
| `Type: Question` | `#d876e3` | Ask how the project works or how to use it. |
|
|
68
|
+
| `Status: Needs Triage` | `#ededed` | Awaiting maintainer review and a scope decision. |
|
|
69
|
+
| `Status: Confirmed` | `#0e8a16` | Reproduced defect or accepted actionable request; not a delivery commitment. |
|
|
70
|
+
| `Status: Needs Information` | `#fbca04` | Waiting for reporter details, answers, or validation results. |
|
|
71
|
+
| `Status: Blocked` | `#d93f0b` | Accepted work is waiting on an identified external dependency. |
|
|
72
|
+
| `Status: Can't Reproduce` | `#fef2c0` | A documented reproduction attempt did not reproduce the report. |
|
|
73
|
+
| `Status: Duplicate` | `#cfd3d7` | Already tracked elsewhere; link the canonical issue or PR. |
|
|
74
|
+
| `Status: Won't Do` | `#666666` | Declined as out of scope or unsuitable in its proposed form. |
|
|
75
|
+
| `Good First Issue` | `#7057ff` | A scoped, ready task with a clear approach and beginner-friendly validation. |
|
|
76
|
+
| `Help Wanted` | `#008672` | A scoped, accepted task for which outside contributions are welcome. |
|
|
77
|
+
|
|
78
|
+
Review labels whenever answering, triaging, implementing, closing, reopening, or
|
|
79
|
+
otherwise updating an issue or PR:
|
|
80
|
+
|
|
81
|
+
- Replace `Status: Needs Triage` after review. Use `Status: Confirmed` only for
|
|
82
|
+
a reproduced defect or an accepted request within project scope; neither
|
|
83
|
+
reproduction nor acceptance follows automatically from a report.
|
|
84
|
+
- When waiting on the reporter, use `Status: Needs Information` and state what
|
|
85
|
+
evidence is needed. Use `Status: Blocked` for an identified external
|
|
86
|
+
dependency, with a link and an unblock condition. Choose the immediate next
|
|
87
|
+
action rather than stacking statuses.
|
|
88
|
+
- Use `Status: Can't Reproduce` only after documenting the attempted version,
|
|
89
|
+
input, and result. An incomplete report first needs information.
|
|
90
|
+
- On duplicate closure, link the canonical item and retain `Status: Duplicate`.
|
|
91
|
+
On rejection, explain the scope or suitability decision and retain
|
|
92
|
+
`Status: Won't Do`. A declined proposal can be reconsidered with new evidence.
|
|
93
|
+
- On successful completion or merge, remove transient status labels and retain
|
|
94
|
+
the type. GitHub's open/closed/merged state records completion; there is no
|
|
95
|
+
redundant Done or Merged label. A closed, unmerged PR should retain its
|
|
96
|
+
applicable disposition, rather than being labeled as completed.
|
|
97
|
+
- On reopening, reassess the evidence and replace the old disposition; do not
|
|
98
|
+
carry a stale rejection, duplicate, or information request forward.
|
|
99
|
+
- Apply `Good First Issue` only to a ready, well-explained beginner task,
|
|
100
|
+
together with `Help Wanted`. Remove both when the item closes or is no longer
|
|
101
|
+
ready for contribution. Waiting for a reporter's private-workspace benchmark
|
|
102
|
+
is not a general help request.
|
|
103
|
+
|
|
104
|
+
Colors follow familiar GitHub cues: red for defects, blue for documentation,
|
|
105
|
+
green for accepted work, yellow/orange for missing information or blockers, and
|
|
106
|
+
gray for neutral or declined dispositions. Names carry the meaning without
|
|
107
|
+
relying on color. The unprefixed contributor labels follow
|
|
108
|
+
[GitHub's label conventions](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels);
|
|
109
|
+
the readiness criteria draw on the
|
|
110
|
+
[Kubernetes contributor guide](https://www.kubernetes.dev/docs/guide/help-wanted/).
|
|
111
|
+
The `Type:` / `Status:` grouping also appears in
|
|
112
|
+
[community label catalogs](https://gist.github.com/dysfunc/fc722e865a6a960a2d9c5ecf43f5a963);
|
|
113
|
+
this repository intentionally uses a smaller set.
|
|
114
|
+
|
|
44
115
|
## Develop locally
|
|
45
116
|
|
|
46
117
|
Follow the [checkout setup](quick-start.md#install-from-the-repository), then:
|
|
@@ -55,9 +126,20 @@ site build, and regression/property/CLI tests. `test:package` packs the tool,
|
|
|
55
126
|
installs it into a temporary consumer project, and exercises the installed CLI
|
|
56
127
|
and library. It can require access to the npm registry.
|
|
57
128
|
|
|
58
|
-
GitHub Actions
|
|
59
|
-
|
|
60
|
-
|
|
129
|
+
GitHub Actions runs the full `check` and dependency audit on Linux with
|
|
130
|
+
Node.js 24. Linux with Node.js 22 and Windows with Node.js 24 run the build and
|
|
131
|
+
tests; all three jobs verify the installed package. Linting, formatting checks,
|
|
132
|
+
and documentation builds run once rather than on every platform. Superseded PR
|
|
133
|
+
runs are canceled.
|
|
134
|
+
|
|
135
|
+
Runtime dependencies have no OS-specific installation constraints. Development
|
|
136
|
+
tooling does include native platform packages, but that alone does not justify
|
|
137
|
+
repeating the full toolchain matrix. Windows coverage protects filesystem path
|
|
138
|
+
handling and installed command behavior; it has already caught a path-alias
|
|
139
|
+
regression. macOS is not a routine CI target.
|
|
140
|
+
|
|
141
|
+
Keep changes scoped and describe remaining limitations in the pull request.
|
|
142
|
+
Agent contributors should also read the repository's
|
|
61
143
|
[AGENTS.md](https://github.com/viell-dev/slop-markdown-tools/blob/main/AGENTS.md).
|
|
62
144
|
|
|
63
145
|
## Write and preview documentation
|
package/docs/design.md
CHANGED
|
@@ -59,11 +59,21 @@ Heading nesting paths, property aliases, PDF subpaths, and every Obsidian
|
|
|
59
59
|
plugin's syntax are not fully supported. Embeds are preserved as embeds; image
|
|
60
60
|
dimensions in their aliases remain intact.
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
The workspace indexes eligible paths first and parses target Markdown only when
|
|
63
|
+
a fragment is checked, caching the result for that invocation. CLI discovery
|
|
64
|
+
also defers reading Markdown until it is selected or needed for a fragment.
|
|
65
|
+
Rule-schema validators are reused across documents. A suffix lookup index is
|
|
66
|
+
built on first use instead of scanning every path for each shortened link. There
|
|
67
|
+
is no persistent cache, worker pool, watch service, editor extension, or
|
|
68
|
+
language server yet. The library has a pluggable workspace interface for
|
|
69
|
+
specialized hosts.
|
|
70
|
+
|
|
71
|
+
Run `npm run build` and `node scripts/benchmark.mjs 1000` for a reproducible
|
|
72
|
+
synthetic library benchmark. It reports path-index construction and lint time
|
|
73
|
+
separately for 1,000 short interlinked notes; pass a different count to scale
|
|
74
|
+
it. These timings exclude filesystem traversal and do not predict a particular
|
|
75
|
+
vault's throughput. Measure the original workload before adding caches or
|
|
76
|
+
workers.
|
|
67
77
|
|
|
68
78
|
## Maintenance priorities
|
|
69
79
|
|
package/docs/plugins.md
CHANGED
|
@@ -26,10 +26,17 @@ const result = format(files["note.md"], options);
|
|
|
26
26
|
|
|
27
27
|
Library calls do not read or write files. `path` and workspace keys use
|
|
28
28
|
forward-slash paths relative to the workspace root. Use `null` values for
|
|
29
|
-
non-Markdown attachments.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
non-Markdown attachments. A Markdown value can also be a synchronous loader
|
|
30
|
+
`() => string`; `createWorkspace` calls it only when that target needs fragment
|
|
31
|
+
validation, then caches its parsed heading/block index. Callers must provide a
|
|
32
|
+
stable source snapshot for each workspace instance. The CLI provides its own
|
|
33
|
+
per-invocation file loaders. `WorkspaceOptions.directories` can list existing
|
|
34
|
+
empty directories; parent directories of file entries are inferred. Resolution
|
|
35
|
+
returns `status: "directory"` for a directory instead of `"missing"`.
|
|
36
|
+
|
|
37
|
+
Without a workspace, local target checks and path rewriting are unavailable.
|
|
38
|
+
Obsidian reflow also requires an explicit `workspace.strictLineBreaks: true`;
|
|
39
|
+
callers are responsible for verifying that renderer setting.
|
|
33
40
|
|
|
34
41
|
`parse`, `range`, `textContent`, `resolveConfig`, `configSchema`, `presets`,
|
|
35
42
|
`builtInRules`, `ruleRegistry`, `applyEdits`, and `semanticFingerprint` are also
|