From 1500102b989ebe65c88cfce23a6b4e8fd5333187 Mon Sep 17 00:00:00 2001 From: Danpa_cho Date: Wed, 10 Sep 2025 19:18:37 +0900 Subject: [PATCH 1/6] feat: add support for file tree node visit/modification/helper utility methods --- packages/build_system/src/parser/node.ts | 478 +++++++++++++++++++++++ 1 file changed, 478 insertions(+) diff --git a/packages/build_system/src/parser/node.ts b/packages/build_system/src/parser/node.ts index 016759b0..7c829561 100644 --- a/packages/build_system/src/parser/node.ts +++ b/packages/build_system/src/parser/node.ts @@ -122,6 +122,154 @@ export abstract class FileTreeNode< * @param extension The file extension to check. */ public static readonly is: (extension: string | undefined) => boolean + + /** + * Checks whether this node can contain children (is a folder). + * Returns `true` if the node reports a `children` array or has category 'FOLDER'. + */ + public isFolder(): this is FolderNode { + return ( + ((this as any).children !== undefined && + Array.isArray((this as any).children)) || + this.category === 'FOLDER' + ) + } + + /** + * Checks whether this node is a leaf node (cannot have children). + */ + public isLeaf(): boolean { + return !this.isFolder() + } + + /** + * Returns the parent node if available. + */ + public getParentNode(): FileTreeNode | undefined { + return this._parentInfo?.node + } + + /** + * Clears the parentInfo, detaching this node from its parent. + * This does not modify other fields (absolutePath, nodeDepth). + */ + public clearParentInfo(): void { + this._parentInfo = undefined + } + + /** + * Returns the list of ancestor nodes from immediate parent up to the root. + * The immediate parent is the first element. + */ + public getAncestors(): FileTreeNode[] { + const out: FileTreeNode[] = [] + let cur = this.getParentNode() + while (cur) { + out.push(cur) + cur = cur.getParentNode() + } + return out + } + + /** + * Returns the path (array of nodes) from the root to this node. + * Root is first element, this node is last. + */ + public getPathFromRoot(): FileTreeNode[] { + const ancestors = this.getAncestors() + return [...ancestors].reverse().concat([this]) + } + + /** + * Computes depth dynamically by counting parent links. + * Note: this returns the computed runtime depth and does not mutate the readonly `nodeDepth` property. + */ + public getComputedDepth(): number { + return this.getAncestors().length + } + + /** + * Returns `true` if this node is an ancestor of the provided node. + * Walks the parent chain of `node`. + */ + public isAncestorOf(node: FileTreeNode): boolean { + let cur = node.getParentNode() + while (cur) { + if (cur === this) return true + cur = cur.getParentNode() + } + return false + } + + /** + * Returns `true` if this node is a descendant of the provided node. + */ + public isDescendantOf(node: FileTreeNode): boolean { + return node.isAncestorOf(this) + } + + /** + * Equality by absolute path. + * Use when identity semantics are path-based rather than object reference-based. + */ + public equals(other: FileTreeNode): boolean { + return this.absolutePath === other.absolutePath + } + + /** + * Returns a simple relative path from the provided parent node to this node. + * If the parent is not actually a parent (paths do not share prefix) returns the absolute path. + */ + public getRelativePathTo(parent: FileTreeNode): string { + const relativeToParent = _pathResolver.getRelativePath( + this.absolutePath, + parent.absolutePath + ) + return relativeToParent + } + + /** + * Returns a small list of path segments for absolutePath + */ + public getPathSegments(): string[] { + return _pathResolver.splitToPathSegments(this.absolutePath) + } + + /** + * Checks whether this node has injected build information. + */ + public hasBuildInfo(): boolean { + return !!this._buildInfo + } + + /** + * Returns the build `id` if available. + */ + public getBuildId() { + return this._buildInfo?.id + } + + /** + * Returns the content id if available. + */ + public getContentId() { + return this._buildInfo?.content_id + } + + /** + * Convenience accessor for file extension on the instance. + */ + public getFileExtension(): Extension { + return this.fileExtension + } + + /** + * Checks whether this node matches the provided node type. + * Use for quick category checks (e.g. 'TEXT_FILE', 'FOLDER'). + */ + public isOfType(t: NodeType): boolean { + return this.category === t + } } const _pathResolver = new PathResolver() @@ -142,6 +290,265 @@ export class FolderNode extends FileTreeNode { label ) } + + /** + * Adds a child node to this folder. + * If `position` is provided the child will be inserted at that index, otherwise appended. + * Returns `true` if the add was successful. + * + * This method will: + * - refuse to add a node into its own descendant (cycle protection). + * - detach the child from its previous parent (if any FolderNode parent exists). + * - set child's `parentInfo` to point to this node. + * + * Note: this does not change readonly fields such as `absolutePath` or `nodeDepth`. + */ + public addChild(child: FileTreeNode, position?: number): boolean { + // disallow self-insert + if (child === this) return false + // cycle check: child must not be ancestor of this + if (child.isAncestorOf(this)) return false + + // remove from old parent (if a FolderNode) + const oldParent = child.getParentNode() + if (oldParent && oldParent instanceof FolderNode) { + oldParent.removeChild(child) + } + + if (!this.children) this.children = [] + + const pos = + typeof position === 'number' + ? Math.max(0, Math.min(position, this.children.length)) + : this.children.length + + this.children.splice(pos, 0, child) + child.setParentInfo({ + absolutePath: this.absolutePath, + fileName: this.fileName, + node: this, + }) + return true + } + + /** + * Removes a direct child from this folder. + * Returns `true` if the child was found and removed. + * The removed child will have its `parentInfo` cleared. + */ + public removeChild(child: FileTreeNode): boolean { + if (!this.children) return false + const idx = this.children.indexOf(child) + if (idx === -1) return false + this.children.splice(idx, 1) + // detach + child.clearParentInfo() + return true + } + + /** + * Removes a direct child by index. + * Returns the removed child instance or `undefined` if index out of range. + */ + public removeChildAt(index: number): FileTreeNode | undefined { + if (!this.children) return undefined + if (index < 0 || index >= this.children.length) return undefined + const [removed] = this.children.splice(index, 1) + removed!.clearParentInfo() + return removed + } + + /** + * Returns the index of the provided direct child, or -1 if not found. + */ + public indexOf(child: FileTreeNode): number { + if (!this.children) return -1 + return this.children.indexOf(child) + } + + /** + * Depth-first traversal generator starting at this folder. + * Yields this folder first, then its descendants. + * Iterative implementation avoids recursion limits. + */ + public *traverseDFS(): Generator { + const stack: FileTreeNode[] = [this] + while (stack.length) { + const node = stack.pop()! + yield node + if (node.isFolder()) { + const children = (node as FolderNode).children ?? [] + // push children in reverse to preserve left-to-right order + if (children.length === 0) { + continue + } + for (let i = children.length - 1; i >= 0; --i) + stack.push(children[i]!) + } + } + } + + /** + * Breadth-first traversal starting at this folder. + * Iterative queue implementation. + */ + public *traverseBFS(): Generator { + const queue: FileTreeNode[] = [this] + while (queue.length) { + const node = queue.shift()! + yield node + if (node.isFolder()) { + const children = (node as FolderNode).children ?? [] + for (const c of children) queue.push(c) + } + } + } + + /** + * Finds the first node in this subtree satisfying the predicate using DFS. + */ + public find( + predicate: (node: FileTreeNode) => boolean + ): FileTreeNode | undefined { + for (const n of this.traverseDFS()) { + if (predicate(n)) return n + } + return undefined + } + + /** + * Finds all nodes in this subtree satisfying the predicate. + */ + public findAll(predicate: (node: FileTreeNode) => boolean): FileTreeNode[] { + const out: FileTreeNode[] = [] + for (const n of this.traverseDFS()) { + if (predicate(n)) out.push(n) + } + return out + } + + /** + * Finds a node by its absolute path in this subtree. + */ + public findByAbsolutePath(absolutePath: string): FileTreeNode | undefined { + return this.find((n) => n.absolutePath === absolutePath) + } + + /** + * Finds a node by file name in this subtree. + * Returns the first match found in DFS order. + */ + public findByFileName(fileName: string): FileTreeNode | undefined { + return this.find((n) => n.fileName === fileName) + } + + /** + * Returns all descendant nodes (excludes this folder). + */ + public getDescendants(): FileTreeNode[] { + const out: FileTreeNode[] = [] + for (const n of this.traverseDFS()) { + if (n !== this) out.push(n) + } + return out + } + + /** + * Returns all leaf nodes contained in this subtree. + */ + public getLeaves(): FileTreeNode[] { + const out: FileTreeNode[] = [] + for (const n of this.traverseDFS()) { + if (n.isLeaf()) out.push(n) + } + return out + } + + /** + * Returns the number of nodes in this subtree including this folder. + */ + public size(): number { + let c = 0 + for (const _ of this.traverseDFS()) c++ + return c + } + + /** + * Returns true if the folder has no children or children array is empty. + */ + public isEmpty(): boolean { + return !this.children || this.children.length === 0 + } + + /** + * Sorts direct children in-place using the provided comparator. + * If no comparator is provided children are sorted by fileName. + */ + public sortChildren( + comparator?: (a: FileTreeNode, b: FileTreeNode) => number + ): void { + if (!this.children) return + if (!comparator) { + this.children.sort((x, y) => + x.fileName < y.fileName ? -1 : x.fileName > y.fileName ? 1 : 0 + ) + } else { + this.children.sort(comparator) + } + } + + /** + * Replaces an existing direct child with a new node, preserving index. + * Returns true on success. + */ + public replaceChild( + oldChild: FileTreeNode, + newChild: FileTreeNode + ): boolean { + if (!this.children) return false + const idx = this.children.indexOf(oldChild) + if (idx === -1) return false + // cycle check + if (newChild.isAncestorOf(this)) return false + // detach old + oldChild.clearParentInfo() + // attach new + this.children[idx] = newChild + newChild.setParentInfo({ + absolutePath: this.absolutePath, + fileName: this.fileName, + node: this, + }) + return true + } + + /** + * Move a direct child of this folder to another FolderNode. + * Returns true on success. + * + * This operation will: + * - remove the child from this folder (if present), + * - insert it into `targetFolder` at optional `position`, + * - update the child's parentInfo to the target folder. + * + * It will refuse the move if it would create a cycle. + */ + public moveChildTo( + child: FileTreeNode, + targetFolder: FolderNode, + position?: number + ): boolean { + if (!this.children) return false + const idx = this.children.indexOf(child) + if (idx === -1) return false + // cannot move into descendant + if (child.isAncestorOf(targetFolder)) return false + // remove from old + this.children.splice(idx, 1) + child.clearParentInfo() + // add to target + return targetFolder.addChild(child, position) + } } export type TextFileExtension = 'md' | 'mdx' | 'txt' | 'html' @@ -169,6 +576,29 @@ export class TextFileNode extends FileTreeNode { extension ? TextFileNode.FileExtensionList.has(extension as TextFileExtension) : false + + /** + * Returns true when this node is a text file (by category or extension). + */ + public isTextFile(): boolean { + return ( + this.category === 'TEXT_FILE' || TextFileNode.is(this.fileExtension) + ) + } + + /** + * Returns the display title for previews: prefer `label` then `fileName`. + */ + public getPreviewTitle(): string { + return this.label ?? this.fileName + } + + /** + * Checks whether this node's extension matches the provided extension. + */ + public matchesExtension(extension: string | undefined): boolean { + return TextFileNode.is(extension) && this.fileExtension === extension + } } export type ImageFileExtension = @@ -226,6 +656,30 @@ export class ImageFileNode extends FileTreeNode { extension as ImageFileExtension ) : false + + /** + * Returns true when this node is an image file (by category or extension). + */ + public isImageFile(): boolean { + return ( + this.category === 'IMAGE_FILE' || + ImageFileNode.is(this.fileExtension) + ) + } + + /** + * Returns a short label suitable for image lists: prefer label, then filename. + */ + public getDisplayLabel(): string { + return this.label ?? this.fileName + } + + /** + * Checks whether this node's extension matches the provided extension. + */ + public matchesExtension(extension: string | undefined): boolean { + return ImageFileNode.is(extension) && this.fileExtension === extension + } } export type AudioFileExtension = @@ -273,4 +727,28 @@ export class AudioFileNode extends FileTreeNode { extension as AudioFileExtension ) : false + + /** + * Returns true when this node is an audio file (by category or extension). + */ + public isAudioFile(): boolean { + return ( + this.category === 'AUDIO_FILE' || + AudioFileNode.is(this.fileExtension) + ) + } + + /** + * Returns a user-friendly label for audio lists. + */ + public getDisplayLabel(): string { + return this.label ?? this.fileName + } + + /** + * Checks whether this node's extension matches the provided extension. + */ + public matchesExtension(extension: string | undefined): boolean { + return AudioFileNode.is(extension) && this.fileExtension === extension + } } From 480a37d843a7f2d709e97a5888aae99ffada2f9f Mon Sep 17 00:00:00 2001 From: Danpa_cho Date: Wed, 10 Sep 2025 19:18:55 +0900 Subject: [PATCH 2/6] test: add file tree node testing suites --- .../src/parser/__tests__/node.test.ts | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 packages/build_system/src/parser/__tests__/node.test.ts diff --git a/packages/build_system/src/parser/__tests__/node.test.ts b/packages/build_system/src/parser/__tests__/node.test.ts new file mode 100644 index 00000000..f9bf735a --- /dev/null +++ b/packages/build_system/src/parser/__tests__/node.test.ts @@ -0,0 +1,235 @@ +import { beforeEach, describe, expect, it } from 'vitest' + +import { AudioFileNode, FolderNode, ImageFileNode, TextFileNode } from '../node' + +describe('FileTreeNode and FolderNode', () => { + let root: FolderNode + let docs: FolderNode + let images: FolderNode + let guide: TextFileNode + let photo: ImageFileNode + let readme: TextFileNode + let audio: AudioFileNode + + beforeEach(() => { + // [AST] + // + // / (root) + // ├── docs/ + // │ └── guide.md + // ├── images/ + // │ └── photo.png + // │ └── sound.mp3 + // └── README.md + root = new FolderNode('/root', 0) + docs = new FolderNode('/root/docs', 1) + images = new FolderNode('/root/images', 1) + readme = new TextFileNode('/root/README.md', 1) + guide = new TextFileNode('/root/docs/guide.md', 2) + photo = new ImageFileNode('/root/images/photo.png', 2) + audio = new AudioFileNode('/root/images/sound.mp3', 2) + + root.addChild(docs) + root.addChild(images) + root.addChild(readme) + docs.addChild(guide) + images.addChild(photo) + images.addChild(audio) + }) + + describe('FileTreeNode Helpers', () => { + it('should correctly identify folder and leaf nodes', () => { + expect(root.isFolder()).toBe(true) + expect(docs.isFolder()).toBe(true) + expect(guide.isFolder()).toBe(false) + expect(guide.isLeaf()).toBe(true) + expect(photo.isLeaf()).toBe(true) + }) + + it('should correctly retrieve the parent node', () => { + expect(docs.getParentNode()).toBe(root) + expect(guide.getParentNode()).toBe(docs) + expect(root.getParentNode()).toBeUndefined() + }) + + it('should get ancestors correctly', () => { + expect(guide.getAncestors()).toEqual([docs, root]) + expect(docs.getAncestors()).toEqual([root]) + expect(root.getAncestors()).toEqual([]) + }) + + it('should get path from root correctly', () => { + expect(guide.getPathFromRoot()).toEqual([root, docs, guide]) + expect(root.getPathFromRoot()).toEqual([root]) + }) + + it('should compute depth correctly', () => { + expect(guide.getComputedDepth()).toBe(2) + expect(docs.getComputedDepth()).toBe(1) + expect(root.getComputedDepth()).toBe(0) + }) + + it('should correctly check ancestor/descendant relationships', () => { + expect(root.isAncestorOf(guide)).toBe(true) + expect(docs.isAncestorOf(guide)).toBe(true) + expect(guide.isDescendantOf(root)).toBe(true) + expect(images.isAncestorOf(guide)).toBe(false) + expect(guide.isDescendantOf(images)).toBe(false) + }) + + it('should check equality by absolute path', () => { + const anotherGuide = new TextFileNode('/root/docs/guide.md', 2) + expect(guide.equals(anotherGuide)).toBe(true) + expect(guide.equals(photo)).toBe(false) + }) + }) + + describe('FolderNode Mutations', () => { + it('should add a child correctly', () => { + const newFile = new TextFileNode('/root/new.md', 1) + root.addChild(newFile) + expect(root.children).toContain(newFile) + expect(newFile.getParentNode()).toBe(root) + expect(root.children.length).toBe(4) + }) + + it('should remove a child correctly', () => { + const childCount = root.children.length + const result = root.removeChild(docs) + expect(result).toBe(true) + expect(root.children).not.toContain(docs) + expect(docs.getParentNode()).toBeUndefined() + expect(root.children.length).toBe(childCount - 1) + }) + + it('should move a child to another folder', () => { + // Move guide.md from /docs to /images + docs.moveChildTo(guide, images) + expect(docs.children).not.toContain(guide) + expect(images.children).toContain(guide) + expect(guide.getParentNode()).toBe(images) + }) + + it('should prevent moving a node into its own descendant (cycle)', () => { + // Attempt to add `root` as a child of `docs`. This would create a cycle. + const addResult = docs.addChild(root) + expect(addResult).toBe(false) + + // Create a new folder inside `docs` + const subDocs = new FolderNode('/root/docs/sub', 3) + docs.addChild(subDocs) + + // Attempt to move `docs` into `subDocs`. This would also create a cycle. + const moveResult = root.moveChildTo(docs, subDocs) + expect(moveResult).toBe(false) + }) + + it('should replace a child', () => { + const newReadme = new TextFileNode('/root/NEW_README.md', 1) + const result = root.replaceChild(readme, newReadme) + expect(result).toBe(true) + expect(root.children).toContain(newReadme) + expect(root.children).not.toContain(readme) + expect(newReadme.getParentNode()).toBe(root) + expect(readme.getParentNode()).toBeUndefined() + }) + + it('should re-parent a node when addChild is called', () => { + // Move `photo` from `images` to `docs` + docs.addChild(photo) + expect(images.children).not.toContain(photo) + expect(docs.children).toContain(photo) + expect(photo.getParentNode()).toBe(docs) + }) + }) + + describe('FolderNode Traversal and Queries', () => { + it('should traverse in DFS order', () => { + const visited = [...root.traverseDFS()].map((n) => n.fileName) + const expectedOrder = [ + 'root', + 'docs', + 'guide.md', + 'images', + 'photo.png', + 'sound.mp3', + 'README.md', + ] + expect(visited).toEqual(expectedOrder) + }) + + it('should traverse in BFS order', () => { + const visited = [...root.traverseBFS()].map((n) => n.fileName) + const expectedOrder = [ + 'root', + 'docs', + 'images', + 'README.md', + 'guide.md', + 'photo.png', + 'sound.mp3', + ] + expect(visited).toEqual(expectedOrder) + }) + + it('should find a node by predicate', () => { + const found = root.find((node) => node.fileName === 'photo.png') + expect(found).toBe(photo) + }) + + it('should find all nodes by predicate', () => { + const textFiles = root.findAll( + (node) => node instanceof TextFileNode + ) + expect(textFiles).toHaveLength(2) + expect(textFiles).toContain(readme) + expect(textFiles).toContain(guide) + }) + + it('should get all descendants', () => { + const descendants = root.getDescendants() + expect(descendants).toHaveLength(6) + expect(descendants).not.toContain(root) + }) + + it('should get all leaf nodes', () => { + const leaves = root.getLeaves() + expect(leaves).toHaveLength(4) + expect(leaves).toContain(guide) + expect(leaves).toContain(photo) + expect(leaves).toContain(readme) + expect(leaves).toContain(audio) + }) + + it('should report correct size', () => { + expect(root.size()).toBe(7) + expect(images.size()).toBe(3) + }) + }) + + describe('Typed File Nodes', () => { + it('TextFileNode should correctly identify itself', () => { + expect(guide.isTextFile()).toBe(true) + expect(guide.matchesExtension('md')).toBe(true) + expect(guide.matchesExtension('png')).toBe(false) + //@ts-expect-error + expect(photo.isTextFile).toBeUndefined() + }) + + it('ImageFileNode should correctly identify itself', () => { + expect(photo.isImageFile()).toBe(true) + expect(photo.matchesExtension('png')).toBe(true) + expect(photo.matchesExtension('md')).toBe(false) + //@ts-expect-error + expect(guide.isImageFile).toBeUndefined() + }) + + it('AudioFileNode should correctly identify itself', () => { + expect(audio.isAudioFile()).toBe(true) + expect(audio.matchesExtension('mp3')).toBe(true) + expect(audio.matchesExtension('wav')).toBe(false) + //@ts-expect-error + expect(guide.isAudioFile).toBeUndefined() + }) + }) +}) From da1e024e200f7ecad4734530e27fdee72e0ee2cf Mon Sep 17 00:00:00 2001 From: Danpa_cho Date: Wed, 10 Sep 2025 19:19:10 +0900 Subject: [PATCH 3/6] move: parser test location --- packages/build_system/src/parser/{ => __tests__}/parser.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/build_system/src/parser/{ => __tests__}/parser.test.ts (99%) diff --git a/packages/build_system/src/parser/parser.test.ts b/packages/build_system/src/parser/__tests__/parser.test.ts similarity index 99% rename from packages/build_system/src/parser/parser.test.ts rename to packages/build_system/src/parser/__tests__/parser.test.ts index 1ed07369..54fcd52c 100644 --- a/packages/build_system/src/parser/parser.test.ts +++ b/packages/build_system/src/parser/__tests__/parser.test.ts @@ -1,7 +1,7 @@ import { IO as IOManager } from '@obsidian_blogger/helpers' import { describe, expect, it } from 'vitest' -import { FileTreeParser } from './parser' +import { FileTreeParser } from '../parser' describe('FileTreeParser', () => { const parser = new FileTreeParser({ From 26616a8a52c2ccb313f7c9b2f84e8a0aecd05600 Mon Sep 17 00:00:00 2001 From: Danpa_cho Date: Wed, 10 Sep 2025 19:20:49 +0900 Subject: [PATCH 4/6] refactor: change core method name, and purpose add optional cleanup tree build process --- .../src/builder/plugin/build.tree.plugin.ts | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/build_system/src/builder/plugin/build.tree.plugin.ts b/packages/build_system/src/builder/plugin/build.tree.plugin.ts index ae74ff52..e16ea0f2 100644 --- a/packages/build_system/src/builder/plugin/build.tree.plugin.ts +++ b/packages/build_system/src/builder/plugin/build.tree.plugin.ts @@ -7,7 +7,7 @@ import { type BuildPluginStaticConfig, } from './build.plugin' -import type { FileTreeNode, WalkOption } from '../../parser' +import type { FileTreeNode, FolderNode, WalkOption } from '../../parser' import type { BuildInformation } from '../core' import type { PluginCachePipelines } from './cache.interface' import type { PluginDynamicConfigSchema } from '@obsidian_blogger/plugin_api' @@ -77,8 +77,10 @@ export abstract class BuildTreePlugin< /** * Walking a original file tree for rebuilding the file tree + * + * Direct modification of vault root origin generated AST */ - public abstract walk( + public abstract buildNode( /** * Current node */ @@ -102,6 +104,15 @@ export abstract class BuildTreePlugin< } ): Promise + /** + * Build final tree + * + * Direct modification of root ast folder node + * + * @param finalAst Final generated ast via `walk` by `buildNode` call. + */ + public async buildTree?(finalAst: FolderNode): Promise + /** * Optional cache checker function for determining if the build state and node information * should be cached. @@ -142,7 +153,7 @@ export abstract class BuildTreePlugin< this.$jobManager.registerJob({ name: 'build:tree', prepare: async () => { - this.walk = this.walk.bind(this) + this.buildNode = this.buildNode.bind(this) }, execute: async () => { const parser = this.getRunTimeDependency('parser') @@ -173,7 +184,7 @@ export abstract class BuildTreePlugin< } // Execute plugin try { - await this.walk(node, context) + await this.buildNode(node, context) } catch (e) { error.push({ filepath: node.fileName, @@ -199,6 +210,16 @@ export abstract class BuildTreePlugin< history: this.$logger.getHistory(), } }, + cleanup: async () => { + const parser = this.getRunTimeDependency('parser') + + const finalAst = parser.ast + + if (finalAst && this.buildTree) { + await this.buildTree(finalAst) + } + return + }, }) await this.$jobManager.processJobs() From a81f64d1e079f375013fc8f17591d8970dc53821 Mon Sep 17 00:00:00 2001 From: Danpa_cho Date: Wed, 10 Sep 2025 19:21:13 +0900 Subject: [PATCH 5/6] refactor: update build tree related module interface --- packages/build_system/src/builder/builder.internal.ts | 2 +- packages/build_system/src/builder/plugin/cache.interface.ts | 2 +- .../src/builder/plugin/core/build_tree/exclude_draft.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/build_system/src/builder/builder.internal.ts b/packages/build_system/src/builder/builder.internal.ts index 0d0b1790..aa0b2b72 100644 --- a/packages/build_system/src/builder/builder.internal.ts +++ b/packages/build_system/src/builder/builder.internal.ts @@ -519,7 +519,7 @@ export class BuilderPluginCachePipelines extends PluginCachePipelines { node: FileTreeNode context: | Parameters[1] - | Parameters[1] + | Parameters[1] config: | WalkTreePlugin['dynamicConfig'] | BuildTreePlugin['dynamicConfig'] diff --git a/packages/build_system/src/builder/plugin/cache.interface.ts b/packages/build_system/src/builder/plugin/cache.interface.ts index 640a5a58..a374d295 100644 --- a/packages/build_system/src/builder/plugin/cache.interface.ts +++ b/packages/build_system/src/builder/plugin/cache.interface.ts @@ -18,7 +18,7 @@ export abstract class PluginCachePipelines { node: FileTreeNode context: | Parameters[1] - | Parameters[1] + | Parameters[1] cacheManager: BuildCacheManager config: | WalkTreePlugin['dynamicConfig'] diff --git a/packages/build_system/src/builder/plugin/core/build_tree/exclude_draft.ts b/packages/build_system/src/builder/plugin/core/build_tree/exclude_draft.ts index e35eeb45..1b7b64db 100644 --- a/packages/build_system/src/builder/plugin/core/build_tree/exclude_draft.ts +++ b/packages/build_system/src/builder/plugin/core/build_tree/exclude_draft.ts @@ -43,7 +43,7 @@ export class ExcludeDraftPlugin extends BuildTreePlugin< } } - public override async walk(node: FileTreeNode): Promise { + public override async buildNode(node: FileTreeNode): Promise { if (node.category !== 'TEXT_FILE') return const content = await this.$io.reader.readFile(node.absolutePath) From 1bc8be18665e8ac9cd1f2bc38a33f95fb43518d9 Mon Sep 17 00:00:00 2001 From: Danpa_cho Date: Wed, 10 Sep 2025 19:23:00 +0900 Subject: [PATCH 6/6] release: build_system 0.8.0 --- packages/build_system/CHANGELOG.md | 6 ++++++ packages/build_system/package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/build_system/CHANGELOG.md b/packages/build_system/CHANGELOG.md index 1e6ebcd9..3de8ec4a 100644 --- a/packages/build_system/CHANGELOG.md +++ b/packages/build_system/CHANGELOG.md @@ -1,5 +1,11 @@ # @obsidian_blogger/build_system +## 0.8.0 + +### Minor Changes + +- Add support for file tree node visit/modification/helper utility methods + ## 0.7.2 ### Patch Changes diff --git a/packages/build_system/package.json b/packages/build_system/package.json index 35d1d8c2..c79ebba3 100644 --- a/packages/build_system/package.json +++ b/packages/build_system/package.json @@ -1,6 +1,6 @@ { "name": "@obsidian_blogger/build_system", - "version": "0.7.2", + "version": "0.8.0", "description": "Post build system", "author": "danpacho", "license": "MIT",