From 1cc98e96ce8c37b3511608a6ea6d7a15b0e194ad Mon Sep 17 00:00:00 2001 From: Devin Abbott Date: Tue, 2 Dec 2025 12:42:07 -0800 Subject: [PATCH 1/6] test package build --- .github/workflows/package-smoke.yml | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/package-smoke.yml diff --git a/.github/workflows/package-smoke.yml b/.github/workflows/package-smoke.yml new file mode 100644 index 0000000..4de31dd --- /dev/null +++ b/.github/workflows/package-smoke.yml @@ -0,0 +1,36 @@ +name: Package smoke + +on: [push, pull_request] + +jobs: + verify: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: ['20.x', '22.x', '24.x'] + name: Package smoke (Node ${{ matrix.node-version }}) + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - run: yarn + - run: yarn build + - name: Verify CJS entrypoint + run: | + node - <<'NODE' + const pkg = require('.'); + if (typeof pkg.visit !== 'function') { + throw new Error('Expected visit export in CJS bundle'); + } + console.log('CJS entry OK'); + NODE + - name: Verify ESM entrypoint + run: | + node --input-type=module - <<'NODE' + const mod = await import('./'); + if (typeof mod.visit !== 'function') { + throw new Error('Expected visit export in ESM bundle'); + } + console.log('ESM entry OK'); + NODE From 99f00978ebfc95717f4c15ce6df23ff71531521d Mon Sep 17 00:00:00 2001 From: Devin Abbott Date: Tue, 2 Dec 2025 13:57:25 -0800 Subject: [PATCH 2/6] wip --- .github/workflows/package-smoke.yml | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/package-smoke.yml b/.github/workflows/package-smoke.yml index 4de31dd..e229904 100644 --- a/.github/workflows/package-smoke.yml +++ b/.github/workflows/package-smoke.yml @@ -16,21 +16,27 @@ jobs: node-version: ${{ matrix.node-version }} - run: yarn - run: yarn build - - name: Verify CJS entrypoint + - name: Verify packaged exports run: | + set -euo pipefail + TGZ=$(npm pack) + TMPDIR=$(mktemp -d) + pushd "$TMPDIR" >/dev/null + npm init -y >/dev/null + npm install --silent "$GITHUB_WORKSPACE/$TGZ" >/dev/null node - <<'NODE' - const pkg = require('.'); + const pkg = require('tree-visit') if (typeof pkg.visit !== 'function') { - throw new Error('Expected visit export in CJS bundle'); + throw new Error('Expected visit export in CJS bundle') } - console.log('CJS entry OK'); + console.log('CJS consumer OK') NODE - - name: Verify ESM entrypoint - run: | node --input-type=module - <<'NODE' - const mod = await import('./'); + const mod = await import('tree-visit') if (typeof mod.visit !== 'function') { - throw new Error('Expected visit export in ESM bundle'); + throw new Error('Expected visit export in ESM bundle') } - console.log('ESM entry OK'); + console.log('ESM consumer OK') NODE + popd >/dev/null + rm "$GITHUB_WORKSPACE/$TGZ" From 871495b45c7526e3096e4c1660d635b5abd407f1 Mon Sep 17 00:00:00 2001 From: Devin Abbott Date: Tue, 2 Dec 2025 15:24:36 -0800 Subject: [PATCH 3/6] wip --- package.json | 5 ++++- src/access.ts | 4 ++-- src/ancestors.ts | 6 ++--- src/defineTree.ts | 32 +++++++++++++------------- src/diagram.ts | 8 +++---- src/diagram/boxDiagram.ts | 4 ++-- src/diagram/directoryDiagram.ts | 4 ++-- src/entries.ts | 6 ++--- src/find.ts | 6 ++--- src/flat.ts | 4 ++-- src/index.ts | 40 ++++++++++++++++----------------- src/insert.ts | 6 ++--- src/map.ts | 6 ++--- src/move.ts | 10 ++++----- src/operation.ts | 10 ++++----- src/options.ts | 2 +- src/package.json | 4 ++++ src/reduce.ts | 6 ++--- src/remove.ts | 6 ++--- src/replace.ts | 6 ++--- src/sort.ts | 4 ++-- src/splice.ts | 6 ++--- src/transformPath.ts | 4 ++-- src/visit.ts | 4 ++-- src/withOptions.ts | 2 +- tsconfig.esm.json | 4 ++-- 26 files changed, 103 insertions(+), 96 deletions(-) create mode 100644 src/package.json diff --git a/package.json b/package.json index befd6f0..32545da 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,10 @@ "testEnvironment": "node", "testPathIgnorePatterns": [ "lib" - ] + ], + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.js$": "$1" + } }, "exports": { ".": { diff --git a/src/access.ts b/src/access.ts index 7ecef55..8633238 100644 --- a/src/access.ts +++ b/src/access.ts @@ -1,5 +1,5 @@ -import { IndexPath } from './indexPath' -import { BaseOptions, TraversalContext } from './options' +import { IndexPath } from './indexPath.js' +import { BaseOptions, TraversalContext } from './options.js' /** * Returns a node by its `IndexPath`. diff --git a/src/ancestors.ts b/src/ancestors.ts index edbe10f..33cb3e6 100644 --- a/src/ancestors.ts +++ b/src/ancestors.ts @@ -1,6 +1,6 @@ -import { IndexPath } from './indexPath' -import { comparePathsByComponent } from './sort' -import { KeyPath } from './types' +import { IndexPath } from './indexPath.js' +import { comparePathsByComponent } from './sort.js' +import { KeyPath } from './types.js' type AncestorPathsOptions = { /** diff --git a/src/defineTree.ts b/src/defineTree.ts index bbffe6f..7f11ec5 100644 --- a/src/defineTree.ts +++ b/src/defineTree.ts @@ -1,6 +1,6 @@ -import { access, accessPath, ancestors, get } from './access' -import { diagram, DiagramOptions } from './diagram' -import { entries } from './entries' +import { access, accessPath, ancestors, get } from './access.js' +import { diagram, DiagramOptions } from './diagram.js' +import { entries } from './entries.js' import { find, findAll, @@ -8,34 +8,34 @@ import { FindOptions, FindOptionsTyped, findPath, -} from './find' -import { flat } from './flat' -import { IndexPath } from './indexPath' +} from './find.js' +import { flat } from './flat.js' +import { IndexPath } from './indexPath.js' import { insert, InsertOptions, insertWithPathTracking, InsertWithPathTrackingOptions, -} from './insert' -import { flatMap, FlatMapOptions, map, MapOptions } from './map' -import { move, MoveOptions } from './move' -import { BaseOptions, MutationBaseOptions, TraversalContext } from './options' -import { reduce, ReduceOptions } from './reduce' +} from './insert.js' +import { flatMap, FlatMapOptions, map, MapOptions } from './map.js' +import { move, MoveOptions } from './move.js' +import { BaseOptions, MutationBaseOptions, TraversalContext } from './options.js' +import { reduce, ReduceOptions } from './reduce.js' import { remove, RemoveOptions, removeWithPathTracking, RemoveWithPathTrackingOptions, -} from './remove' -import { replace, ReplaceOptions } from './replace' +} from './remove.js' +import { replace, ReplaceOptions } from './replace.js' import { splice, SpliceOptions, spliceWithPathTracking, SpliceWithPathTrackingOptions, -} from './splice' -import { ExtractRequiredKeys, OptionCheck, Prettify } from './types' -import { visit, VisitOptions } from './visit' +} from './splice.js' +import { ExtractRequiredKeys, OptionCheck, Prettify } from './types.js' +import { visit, VisitOptions } from './visit.js' type WithoutBase = Omit> diff --git a/src/diagram.ts b/src/diagram.ts index a7a2e5a..9408fa8 100644 --- a/src/diagram.ts +++ b/src/diagram.ts @@ -1,7 +1,7 @@ -import { IndexPath } from './indexPath' -import { BaseOptions } from './options' -import { boxDiagram } from './diagram/boxDiagram' -import { directoryDiagram } from './diagram/directoryDiagram' +import { IndexPath } from './indexPath.js' +import { BaseOptions } from './options.js' +import { boxDiagram } from './diagram/boxDiagram.js' +import { directoryDiagram } from './diagram/directoryDiagram.js' export type DiagramType = 'directory' | 'box' diff --git a/src/diagram/boxDiagram.ts b/src/diagram/boxDiagram.ts index 825b945..462bf4b 100644 --- a/src/diagram/boxDiagram.ts +++ b/src/diagram/boxDiagram.ts @@ -1,5 +1,5 @@ -import { IndexPath } from '../indexPath' -import { DiagramOptions } from '../diagram' +import { IndexPath } from '../indexPath.js' +import { DiagramOptions } from '../diagram.js' enum BoxDrawing { TopLeft = '┌', diff --git a/src/diagram/directoryDiagram.ts b/src/diagram/directoryDiagram.ts index 6ee20c6..f1b3dfc 100644 --- a/src/diagram/directoryDiagram.ts +++ b/src/diagram/directoryDiagram.ts @@ -1,5 +1,5 @@ -import { DiagramOptions } from '../diagram' -import { IndexPath } from '../indexPath' +import { DiagramOptions } from '../diagram.js' +import { IndexPath } from '../indexPath.js' enum LinePrefix { Child = `├── `, diff --git a/src/entries.ts b/src/entries.ts index eef3d72..b2e2f64 100644 --- a/src/entries.ts +++ b/src/entries.ts @@ -1,6 +1,6 @@ -import { IndexPath } from './indexPath' -import { BaseOptions } from './options' -import { visit } from './visit' +import { IndexPath } from './indexPath.js' +import { BaseOptions } from './options.js' +import { visit } from './visit.js' export function entries(node: T, options: BaseOptions): [IndexPath, T][] { let result: [IndexPath, T][] = [] diff --git a/src/find.ts b/src/find.ts index a1a678b..8e8656e 100644 --- a/src/find.ts +++ b/src/find.ts @@ -1,6 +1,6 @@ -import { IndexPath } from './indexPath' -import { BaseOptions, TraversalDirection } from './options' -import { STOP, visit } from './visit' +import { IndexPath } from './indexPath.js' +import { BaseOptions, TraversalDirection } from './options.js' +import { STOP, visit } from './visit.js' export type FindOptions = BaseOptions & { /** diff --git a/src/flat.ts b/src/flat.ts index 27cb64e..57c8f4d 100644 --- a/src/flat.ts +++ b/src/flat.ts @@ -1,5 +1,5 @@ -import { BaseOptions } from './options' -import { reduce } from './reduce' +import { BaseOptions } from './options.js' +import { reduce } from './reduce.js' /** * Returns an array containing the root node and all of its descendants. diff --git a/src/index.ts b/src/index.ts index e253b59..c6c2dff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,20 +1,20 @@ -export * from './access' -export * from './ancestors' -export * from './defineTree' -export * from './diagram' -export * from './entries' -export * from './find' -export * from './flat' -export * from './indexPath' -export * from './insert' -export * from './map' -export * from './move' -export * from './options' -export * from './reduce' -export * from './remove' -export * from './replace' -export * from './sort' -export * from './splice' -export * from './transformPath' -export * from './visit' -export * from './withOptions' +export * from './access.js' +export * from './ancestors.js' +export * from './defineTree.js' +export * from './diagram.js' +export * from './entries.js' +export * from './find.js' +export * from './flat.js' +export * from './indexPath.js' +export * from './insert.js' +export * from './map.js' +export * from './move.js' +export * from './options.js' +export * from './reduce.js' +export * from './remove.js' +export * from './replace.js' +export * from './sort.js' +export * from './splice.js' +export * from './transformPath.js' +export * from './visit.js' +export * from './withOptions.js' diff --git a/src/insert.ts b/src/insert.ts index 97d54ac..8f97a5f 100644 --- a/src/insert.ts +++ b/src/insert.ts @@ -1,10 +1,10 @@ -import { IndexPath } from './indexPath' +import { IndexPath } from './indexPath.js' import { applyOperations, getInsertionOperations, transformPathsByOperations, -} from './operation' -import { MutationBaseOptions } from './options' +} from './operation.js' +import { MutationBaseOptions } from './options.js' export type InsertOptions = MutationBaseOptions & { nodes: T[] diff --git a/src/map.ts b/src/map.ts index 8720a80..0329015 100644 --- a/src/map.ts +++ b/src/map.ts @@ -1,6 +1,6 @@ -import { IndexPath } from './indexPath' -import { BaseOptions } from './options' -import { visit } from './visit' +import { IndexPath } from './indexPath.js' +import { BaseOptions } from './options.js' +import { visit } from './visit.js' export type MapOptions = BaseOptions & { /** diff --git a/src/move.ts b/src/move.ts index 2d753c3..1731e11 100644 --- a/src/move.ts +++ b/src/move.ts @@ -1,12 +1,12 @@ -import { access } from './access' -import { ancestorPaths } from './ancestors' -import { IndexPath } from './indexPath' +import { access } from './access.js' +import { ancestorPaths } from './ancestors.js' +import { IndexPath } from './indexPath.js' import { applyOperations, getInsertionOperations, getRemovalOperations, -} from './operation' -import { MutationBaseOptions } from './options' +} from './operation.js' +import { MutationBaseOptions } from './options.js' export type MoveOptions = MutationBaseOptions & { paths: IndexPath[] diff --git a/src/operation.ts b/src/operation.ts index 22da5ab..a5309d4 100644 --- a/src/operation.ts +++ b/src/operation.ts @@ -1,8 +1,8 @@ -import { ancestorPaths } from './ancestors' -import { IndexPath } from './indexPath' -import { map } from './map' -import { MutationBaseOptions } from './options' -import { transformPath } from './transformPath' +import { ancestorPaths } from './ancestors.js' +import { IndexPath } from './indexPath.js' +import { map } from './map.js' +import { MutationBaseOptions } from './options.js' +import { transformPath } from './transformPath.js' export type NodeOperation = | { diff --git a/src/options.ts b/src/options.ts index 23f19c6..40519f8 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,4 +1,4 @@ -import { IndexPath } from './indexPath' +import { IndexPath } from './indexPath.js' export type TraversalContext = { getRoot(): T diff --git a/src/package.json b/src/package.json new file mode 100644 index 0000000..52a3a1e --- /dev/null +++ b/src/package.json @@ -0,0 +1,4 @@ +{ + "type": "module" +} + diff --git a/src/reduce.ts b/src/reduce.ts index eb6c7de..9306fb7 100644 --- a/src/reduce.ts +++ b/src/reduce.ts @@ -1,6 +1,6 @@ -import { IndexPath } from './indexPath' -import { BaseOptions } from './options' -import { visit } from './visit' +import { IndexPath } from './indexPath.js' +import { BaseOptions } from './options.js' +import { visit } from './visit.js' export type ReduceOptions = BaseOptions & { /** diff --git a/src/remove.ts b/src/remove.ts index bb27c03..73cacfd 100644 --- a/src/remove.ts +++ b/src/remove.ts @@ -1,10 +1,10 @@ -import { IndexPath } from './indexPath' +import { IndexPath } from './indexPath.js' import { applyOperations, getRemovalOperations, transformPathsByOperations, -} from './operation' -import { MutationBaseOptions } from './options' +} from './operation.js' +import { MutationBaseOptions } from './options.js' export type RemoveOptions = MutationBaseOptions & { paths: IndexPath[] diff --git a/src/replace.ts b/src/replace.ts index 3dbcb47..c3c0e55 100644 --- a/src/replace.ts +++ b/src/replace.ts @@ -1,6 +1,6 @@ -import { IndexPath } from './indexPath' -import { applyOperations, getReplaceOperations } from './operation' -import { MutationBaseOptions } from './options' +import { IndexPath } from './indexPath.js' +import { applyOperations, getReplaceOperations } from './operation.js' +import { MutationBaseOptions } from './options.js' export type ReplaceOptions = MutationBaseOptions & { path: IndexPath diff --git a/src/sort.ts b/src/sort.ts index 7071794..90bf82a 100644 --- a/src/sort.ts +++ b/src/sort.ts @@ -1,5 +1,5 @@ -import { IndexPath } from './indexPath' -import { KeyPath } from './types' +import { IndexPath } from './indexPath.js' +import { KeyPath } from './types.js' export function comparePathsByComponent( a: TPath, diff --git a/src/splice.ts b/src/splice.ts index 25407c7..9b51399 100644 --- a/src/splice.ts +++ b/src/splice.ts @@ -1,11 +1,11 @@ -import { IndexPath } from './indexPath' +import { IndexPath } from './indexPath.js' import { applyOperations, getInsertionOperations, getRemovalOperations, transformPathsByOperations, -} from './operation' -import { MutationBaseOptions } from './options' +} from './operation.js' +import { MutationBaseOptions } from './options.js' export type SpliceOptions = MutationBaseOptions & { path: IndexPath diff --git a/src/transformPath.ts b/src/transformPath.ts index 1b244b3..a7e46e3 100644 --- a/src/transformPath.ts +++ b/src/transformPath.ts @@ -1,5 +1,5 @@ -import { IndexPath } from './indexPath' -import { comparePathsByComponent } from './sort' +import { IndexPath } from './indexPath.js' +import { comparePathsByComponent } from './sort.js' type TransformPathOperation = 'insert' | 'remove' diff --git a/src/visit.ts b/src/visit.ts index 4efd0e6..3ab4460 100644 --- a/src/visit.ts +++ b/src/visit.ts @@ -1,5 +1,5 @@ -import { IndexPath } from './indexPath' -import { BaseOptions, TraversalContext, TraversalDirection } from './options' +import { IndexPath } from './indexPath.js' +import { BaseOptions, TraversalContext, TraversalDirection } from './options.js' export const SKIP = 'skip' export const STOP = 'stop' diff --git a/src/withOptions.ts b/src/withOptions.ts index 8bb8e18..430d1cb 100644 --- a/src/withOptions.ts +++ b/src/withOptions.ts @@ -1,4 +1,4 @@ -import { defineTree } from './defineTree' +import { defineTree } from './defineTree.js' /** * Return every tree utility function with options partially applied. diff --git a/tsconfig.esm.json b/tsconfig.esm.json index ba10045..6abed2d 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -1,8 +1,8 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "module": "esnext", - "moduleResolution": "node", + "module": "NodeNext", + "moduleResolution": "NodeNext", "outDir": "./lib/esm", "declaration": false, "emitDeclarationOnly": false From 290e9e05eeed7ce7af3aeddb5f3806f543c2d383 Mon Sep 17 00:00:00 2001 From: Devin Abbott Date: Tue, 2 Dec 2025 15:52:55 -0800 Subject: [PATCH 4/6] wip --- .github/workflows/package-smoke.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package-smoke.yml b/.github/workflows/package-smoke.yml index e229904..e868b27 100644 --- a/.github/workflows/package-smoke.yml +++ b/.github/workflows/package-smoke.yml @@ -32,7 +32,7 @@ jobs: console.log('CJS consumer OK') NODE node --input-type=module - <<'NODE' - const mod = await import('tree-visit') + import * as mod from 'tree-visit' if (typeof mod.visit !== 'function') { throw new Error('Expected visit export in ESM bundle') } From f8a1c10f44e2f8dbb2adf6ba2872c512e3165d52 Mon Sep 17 00:00:00 2001 From: Devin Abbott Date: Tue, 2 Dec 2025 15:57:48 -0800 Subject: [PATCH 5/6] test ts --- .github/workflows/package-smoke.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package-smoke.yml b/.github/workflows/package-smoke.yml index e868b27..c0e0a8c 100644 --- a/.github/workflows/package-smoke.yml +++ b/.github/workflows/package-smoke.yml @@ -23,7 +23,7 @@ jobs: TMPDIR=$(mktemp -d) pushd "$TMPDIR" >/dev/null npm init -y >/dev/null - npm install --silent "$GITHUB_WORKSPACE/$TGZ" >/dev/null + npm install --silent "$GITHUB_WORKSPACE/$TGZ" typescript >/dev/null node - <<'NODE' const pkg = require('tree-visit') if (typeof pkg.visit !== 'function') { @@ -38,5 +38,25 @@ jobs: } console.log('ESM consumer OK') NODE + cat <<'TS' > smoke.ts + import type { VisitOptions } from 'tree-visit' + import { visit } from 'tree-visit' + + type Node = { value: number; children?: Node[] } + + const tree: Node = { value: 1, children: [{ value: 2 }] } + + visit( + tree, + { + includeTraversalContext: true, + getChildren: (node) => node.children ?? [], + onEnter(node) { + console.log(node.value) + }, + } satisfies VisitOptions + ) + TS + npx tsc smoke.ts --moduleResolution nodenext --module nodenext --target es2020 --noEmit popd >/dev/null rm "$GITHUB_WORKSPACE/$TGZ" From 811d4b21c4b89484539cfac11a74f9ec14328af4 Mon Sep 17 00:00:00 2001 From: Devin Abbott Date: Tue, 2 Dec 2025 16:17:27 -0800 Subject: [PATCH 6/6] update types --- src/defineTree.ts | 17 ++++++++++------- src/types.ts | 13 +++++-------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/defineTree.ts b/src/defineTree.ts index 7f11ec5..baff5e6 100644 --- a/src/defineTree.ts +++ b/src/defineTree.ts @@ -19,7 +19,11 @@ import { } from './insert.js' import { flatMap, FlatMapOptions, map, MapOptions } from './map.js' import { move, MoveOptions } from './move.js' -import { BaseOptions, MutationBaseOptions, TraversalContext } from './options.js' +import { + BaseOptions, + MutationBaseOptions, + TraversalContext, +} from './options.js' import { reduce, ReduceOptions } from './reduce.js' import { remove, @@ -34,7 +38,7 @@ import { spliceWithPathTracking, SpliceWithPathTrackingOptions, } from './splice.js' -import { ExtractRequiredKeys, OptionCheck, Prettify } from './types.js' +import { OptionCheck, Prettify } from './types.js' import { visit, VisitOptions } from './visit.js' type WithoutBase = Omit> @@ -42,13 +46,12 @@ type WithoutBase = Omit> type MutationOptions = WithoutBase> type DiagramOptionsWB = WithoutBase> -type DiagramRequiredOptions = Pick< - DiagramOptionsWB, - ExtractRequiredKeys> -> +type DiagramRequiredOptions = { + getLabel: DiagramOptionsWB['getLabel'] +} type DiagramOptionalOptions = Omit< DiagramOptionsWB, - ExtractRequiredKeys> + keyof DiagramRequiredOptions > type FindOptionsWB = WithoutBase> type VisitOptionsWB = WithoutBase> diff --git a/src/types.ts b/src/types.ts index b3c031d..bb9bb73 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,16 +1,13 @@ export type KeyPath = string[] -export type ExtractRequiredKeys = { - [K in keyof T]-?: undefined extends T[K] ? never : K -}[keyof T] - export type OptionCheck< AppliedOptions, OptionKey extends string, - R extends { [K in OptionKey]: any } -> = AppliedOptions extends Record - ? { [K in OptionKey]?: R[K] } - : { [K in OptionKey]: R[K] } + R extends { [K in OptionKey]: any }, +> = + AppliedOptions extends Record + ? { [K in OptionKey]?: R[K] } + : { [K in OptionKey]: R[K] } /** * Improve the type displayed in tooltips by flattening utility types.