Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3a81ce1
fix(cjs): require the ESM entry directly and fail clearly on Node wit…
Shinrai Oct 3, 2026
5075fab
fix(cjs): require the ESM entry directly and fail clearly on Node wit…
Shinrai Oct 3, 2026
517d19e
chore: bump version to 1.0.7
cldmv-bot[bot] Oct 3, 2026
10e7cf7
chore: relicense under Apache-2.0
Shinrai Oct 4, 2026
23ea828
fix: use the fallback file only when the primary cannot be read or pa…
Shinrai Oct 4, 2026
74b21be
fix: use the fallback file only when the primary cannot be read or pa…
Shinrai Oct 4, 2026
59cc5b3
chore: relicense under Apache-2.0 (#34)
Shinrai Oct 4, 2026
54d61eb
docs(changelog): backfill v1.0.0–v1.0.6 release notes
Shinrai Oct 3, 2026
eb14e69
docs: v1.0.7 release notes and README restructure
Shinrai Oct 3, 2026
808b5f8
docs: cover #35 and #34 in the v1.0.7 notes and README
Shinrai Oct 4, 2026
cb130f2
chore: add standard lint/format setup (prettier, eslint, precommit hook)
Shinrai Oct 4, 2026
4c49934
style: apply prettier formatting
Shinrai Oct 4, 2026
7da5fd5
style(readme): realign the options table for prettier
Shinrai Oct 4, 2026
5f2d0c6
chore: add standard lint/format setup (prettier, eslint, precommit ho…
Shinrai Oct 4, 2026
d784615
docs: v1.0.7 release notes and README restructure (#32)
Shinrai Oct 4, 2026
dea061a
test: migrate the suite to @cldmv/vitest-runner and enable coverage
Shinrai Oct 4, 2026
046a4a9
test: run the assert-strategy tests only where a failed import is not…
Shinrai Oct 4, 2026
6c0d6cf
chore: build the published package with tsup into dist/
Shinrai Oct 4, 2026
e944f48
fix: revive a namespace without a default export, and report a non-JS…
Shinrai Oct 4, 2026
b9f73c0
test: migrate the suite to @cldmv/vitest-runner and enable coverage (…
Shinrai Oct 4, 2026
34ec175
chore: build the published package with tsup into dist/ (#40)
Shinrai Oct 4, 2026
c33cd39
refactor: pass the import attributes through a helper in the strategy…
Shinrai Oct 4, 2026
b7e8d7f
fix: revive a namespace without a default export, and report a non-JS…
Shinrai Oct 4, 2026
5947fae
deps: bump @cldmv/fix-headers to 2.1.4
Shinrai Oct 4, 2026
fd6d0ab
docs: cover #36, #37, #40, #41 and #42 in the v1.0.7 notes and README
Shinrai Oct 4, 2026
e5e5fd8
deps: bump @cldmv/fix-headers to 2.1.4 (#42)
Shinrai Oct 4, 2026
4ee22a1
docs: cover #36, #37, #40, #41 and #42 in the v1.0.7 notes and README…
Shinrai Oct 4, 2026
9574b0d
deps: bump @cldmv/fix-headers to 2.2.0
Shinrai Oct 4, 2026
cca1645
deps: bump @cldmv/configs to 1.2.4
Shinrai Oct 5, 2026
442e94d
deps: bump @cldmv/fix-headers to 2.2.0 (#44)
Shinrai Oct 5, 2026
f232275
docs: update the v1.0.7 release notes
Shinrai Oct 5, 2026
c85bccd
docs: update the v1.0.7 release notes (#45)
Shinrai Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .configs/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"useTabs": true,
"tabWidth": 2,
"printWidth": 140,
"trailingComma": "none",
"quoteProps": "preserve",
"bracketSpacing": true,
"overrides": [
{
"files": "*.xml",
"options": {
"printWidth": 300
}
},
{
"files": "*.md",
"options": {
"proseWrap": "preserve"
}
},
{
"files": "*.jsonv",
"options": {
"parser": "jsonv"
}
}
],
"plugins": ["@cldmv/prettier-plugin-jsonv"]
}
78 changes: 63 additions & 15 deletions .configs/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,55 @@
*
* @Project: @cldmv/wisp
* @Filename: /.configs/eslint.config.mjs
* @Date: 2026-09-13T15:59:51-07:00 (1789340391)
* @Date: 2026-08-27T08:03:34-07:00 (1787843014)
* @Author: Nate Corcoran <CLDMV>
* @Email: <Shinrai@users.noreply.github.com>
* -----
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
* @Last modified time: 2026-10-02T15:12:03-07:00 (1790979123)
* @Last modified time: 2026-10-03T11:39:13-07:00 (1791052753)
* -----
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
*
*/

import js from "@eslint/js";
import globals from "globals";
import json from "@eslint/json";
import jsonvPlugin from "@cldmv/eslint-plugin-jsonv";
import markdown from "@eslint/markdown";
import css from "@eslint/css";
import { defineConfig } from "eslint/config";

export default defineConfig([
{
ignores: [
"tmp/**",
"trash/**",
"node_modules/**",
"dist/**",
"build/**",
".git/**",
".configs/**",
".vscode/**",
"coverage/**",
"tmp/**",
"trash/**",
"**/package-lock.json",
"reference/**",
"*.min.*",
// Test fixtures are deliberately-non-JS data files that happen to
// carry a .js extension (wisp's own fallback-loading tests exercise
// exactly this "not really parseable as a module" case) -- they're
// test data, not source, and were never meant to be linted as JS.
"test/fixtures/**"
// Deliberately-non-JS data files with a .js extension (fallback-loading tests); test data, not source.
"tests/fixtures/**",
"**/package-lock.json",
// Copy file patterns
"*copy/",
"*copy (*)/",
"*copy */",
"*copy.*",
"*copy (*).*",
"*copy *.*",
"**/*copy/",
"**/*copy (*)/",
"**/*copy */",
"**/*copy.*",
"**/*copy (*).*",
"**/*copy *.*"
]
},
{
Expand All @@ -48,12 +67,41 @@ export default defineConfig([
varsIgnorePattern: "^(_|___.*)$"
}
],
// wisp.mjs's multi-strategy import fallback (try `with`, then
// `assert`, then fs.readFile) deliberately swallows each earlier
// strategy's failure with an empty catch before trying the next.
// wisp's multi-strategy import fallback deliberately swallows each earlier strategy's failure with an empty catch.
"no-empty": ["error", { allowEmptyCatch: true }]
}
},
{ files: ["**/*.{js,mjs,cjs}"], languageOptions: { globals: { ...globals.node } } },
{ files: ["test/**/*.mjs"], languageOptions: { globals: { ...globals.mocha } } }
{ files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } },
{ files: ["**/*.{js,mjs,cjs}"], languageOptions: { globals: { ...globals.node, ...globals.browser } } },
{
files: ["tests/**/*.test.vitest.mjs"],
languageOptions: {
globals: {
beforeAll: true,
beforeEach: true,
afterAll: true,
afterEach: true,
describe: true,
it: true,
expect: true,
test: true,
vi: true
}
}
},
{ files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] },
{ files: ["**/*.jsonc"], plugins: { json }, language: "json/jsonc", extends: ["json/recommended"] },
{ files: ["**/*.json5"], plugins: { json }, language: "json/json5", extends: ["json/recommended"] },
{ files: ["**/*.jsonv"], plugins: { jsonv: jsonvPlugin }, language: "jsonv/jsonv", ...jsonvPlugin.configs.recommended },
{
files: ["**/*.md"],
plugins: { markdown },
language: "markdown/gfm",
extends: ["markdown/recommended"],
rules: {
// GitHub alerts like [!NOTE]/[!WARNING] are valid but trip this rule.
"markdown/no-missing-label-refs": "off"
}
},
{ files: ["**/*.css"], plugins: { css }, language: "css/css", extends: ["css/recommended"] }
]);
2 changes: 1 addition & 1 deletion .configs/fix-headers.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"extends": "@cldmv/configs/fix-headers.json",
"excludeFolders": ["test/fixtures"]
"excludeFolders": ["tests/fixtures"]
}
55 changes: 55 additions & 0 deletions .configs/vitest.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
*
* @Project: @cldmv/wisp
* @Filename: /.configs/vitest.config.mjs
* @Date: 2026-10-03T19:21:07-07:00 (1791080467)
* @Author: Nate Corcoran <CLDMV>
* @Email: <Shinrai@users.noreply.github.com>
* -----
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
* @Last modified time: 2026-10-03T19:27:20-07:00 (1791080840)
* -----
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
*
*/

import { defineConfig } from "vitest/config";
import { fileURLToPath } from "node:url";
import path from "node:path";

// Anchor the project root to the package directory so include/exclude work no
// matter what cwd vitest is invoked from.
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");

export default defineConfig({
root,
test: {
include: ["tests/**/*.test.vitest.mjs"],
exclude: ["node_modules"],
environment: "node",
testTimeout: 30000,
// Run test files and the code under test through Node's native import()
// instead of Vite's module runner. wisp's behaviour depends on Node's real
// dynamic-import semantics (import attributes `with` / legacy `assert`, JSON
// module loading); the module runner ignores import attributes and loads JSON
// itself, so under it the suite would exercise Vite rather than Node.
// nodeLoader: false also skips Vitest's own loader transform (only needed for
// vi.mock / import.meta.vitest, which this suite does not use), so V8 coverage is
// measured against the files exactly as Node runs them.
experimental: {
viteModuleRunner: false,
nodeLoader: false
},
// "dot" keeps CI logs to one character per test file; the final
// "Test Files X passed" / "Tests Y passed" summary is unaffected.
reporters: ["dot"],
coverage: {
provider: "v8",
// Measured on src/ (ESM entry, library and the CJS shim). The published dist/ is
// built from it and checked separately by tests/cjs + tests/bundle under node --test.
include: ["src/**/*.mjs", "src/**/*.cjs"],
exclude: ["**/*.json", "tests/**", "src/types/**"],
reporter: ["text", "html", "json-summary", "json"]
}
}
});
63 changes: 63 additions & 0 deletions .githooks/install.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env node
/**
*
* @Project: @cldmv/wisp
* @Filename: /.githooks/install.mjs
* @Date: 2026-10-03T11:23:18-07:00 (1791051798)
* @Author: Nate Corcoran <CLDMV>
* @Email: <Shinrai@users.noreply.github.com>
* -----
* @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
* @Last modified time: 2026-10-03T11:39:15-07:00 (1791052755)
* -----
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
*
*/

/**
* @fileoverview Installs the committed pre-commit hook into `.git/hooks/pre-commit`.
*
* Wire it into package.json so it runs on `npm install`. Use the guarded form
* below β€” NOT a bare `node .githooks/install.mjs`: `prepare` also runs on
* `npm pack` / `npm publish` against the packed tree, where `.githooks/` is
* excluded from `files`, so a bare invocation fails module resolution and
* aborts the publish before the guards below can run.
* "scripts": { "prepare": "node -e \"import('./.githooks/install.mjs').catch(()=>{})\"" }
* Copy this file + `pre-commit` (from CLDMV/.github examples/git-hooks/) into
* the repo's `.githooks/` directory.
*
* Why copy into `.git/hooks` rather than set `core.hooksPath`: a per-repo
* `core.hooksPath` SHADOWS a global `core.hooksPath` dispatcher, silently
* disabling any global commit policy (no-coauthor / no-unsigned-push) for that
* repo. A global dispatcher instead CHAINS to `.git/hooks/<name>`, so installing
* here composes with global policy instead of replacing it.
*
* Guards (each exits 0 β€” install is best-effort, never fails a build):
* - CI: nothing commits on CI, skip.
* - inside node_modules: this package installed as a dependency, skip.
* - no `.git` dir: tarball / shallow export / worktree pointer, skip.
*/
import { existsSync, mkdirSync, copyFileSync, chmodSync, statSync } from "node:fs";
import { dirname, join, resolve, sep } from "node:path";
import { fileURLToPath } from "node:url";

const here = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(here, "..");

if (process.env.CI) process.exit(0);
if (repoRoot.split(sep).includes("node_modules")) process.exit(0);

const gitDir = join(repoRoot, ".git");
if (!existsSync(gitDir) || !statSync(gitDir).isDirectory()) process.exit(0);

const hooksDir = join(gitDir, "hooks");
mkdirSync(hooksDir, { recursive: true });

const dest = join(hooksDir, "pre-commit");
copyFileSync(join(here, "pre-commit"), dest);
try {
chmodSync(dest, 0o755);
} catch {
/* Windows has no executable bit β€” ignore. */
}
console.log("βœ“ installed .git/hooks/pre-commit (CLDMV lint/format gate)");
26 changes: 26 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh
#
# CLDMV fleet pre-commit hook.
#
# Runs the repo's lint + format checks before a commit, then any repo-local
# `precommit:local` checks. Each uses `npm run <script> --if-present`, so this
# is a graceful no-op in a repo that hasn't defined the script yet β€” the same
# hook is safe to drop into any repo.
#
# Installed into .git/hooks/pre-commit by examples/git-hooks/install.mjs, wired
# to the package.json `prepare` script (fires on `npm install`). It installs
# into .git/hooks (NOT via core.hooksPath) on purpose: a global core.hooksPath
# dispatcher chains to .git/hooks/<name>, so writing here keeps global policy
# (e.g. no-coauthor / no-unsigned-push) intact while adding the lint/format gate.
#
# Project-specific extra checks: define a `precommit:local` npm script and it
# runs last, after lint/format β€” no need to fork this shared hook.
#
# On failure:
# - formatting: run `npm run format` to auto-fix, then re-stage.
# - lint: run `npm run lint` to see the problems.
set -e

npm run lint --if-present
npm run format:check --if-present
npm run precommit:local --if-present
2 changes: 1 addition & 1 deletion .github/workflows/bundle-size.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
with:
build_command: "npm run build:ci"
# Every pattern starts with `*` on purpose: the v4.29.2 measure action matches nothing for a bare top-level file and double-counts files when patterns have different roots.
dist_paths: "*index.mjs,*index.cjs,*src/**/*.mjs"
dist_paths: "*dist/index.mjs,*dist/index.cjs"
# warning_pct: 5
# warning_bytes: 500
# comment_mode: "update"
Expand Down
27 changes: 14 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ jobs:
# per-version regression. workflow_dispatch can still opt out (set false).
lts_only_matrix: ${{ github.event.inputs.lts_only_matrix != 'false' }}
package_manager: ${{ github.event.inputs.package_manager || 'npm' }}
test_command: "npm test" # mocha (test/**/*.mjs)
test_command: "npm test" # vitest via @cldmv/vitest-runner (tests/**/*.test.vitest.mjs), then the node:test CJS entry check
# test_command: "NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override NODE_OPTIONS only
# test_command: "NODE_ENV=test npm test" # Override NODE_ENV only
# test_command: "NODE_ENV=test NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override both
Expand All @@ -250,11 +250,15 @@ jobs:
lint_fix_command: ${{ github.event.inputs.lint_fix_command || 'npm run lint:fix --if-present' }}
lint_command: ${{ github.event.inputs.lint_command || 'npm run lint --if-present' }}
format_check_command: ${{ github.event.inputs.format_check_command || 'npm run format:check --if-present' }}
# wisp's "build" is type-declaration generation. `build:types` (tsc) both
# emits types/ AND type-checks (tsc fails on type errors), so it carries
# the type check on every matrix node. Not `build:ci` β€” that also re-runs
# mocha, which `test_command` above already ran.
build_command: "npm run build:types"
# The real build: `types:build` (tsc) emits types/ AND type-checks (tsc fails
# on type errors), so it carries the type check on every matrix node; `build`
# (tsup) bundles src/ into dist/. Not `build:ci` β€” that also re-runs the test
# suite, which `test_command` above already ran.
build_command: "npm run types:build && npm run build"
# Coverage legs build with the full gate (lint + format:check + types:build +
# tsup + tests) before measuring. Coverage is measured on src/**, which the
# build leaves in place.
coverage_build_command: "npm run build:ci"
skip_performance_tests: false
skip_matrix_tests: false

Expand All @@ -264,10 +268,9 @@ jobs:
# Only runs on direct pushes to default_branch β€” PRs and feature branches
# are automatically skipped so coverage always reflects merged master code.
# Requires: the coverage_command produces coverage/coverage-summary.json
# wisp has no coverage tooling (mocha only, no `ci:coverage` script, no
# coverage-summary.json, and no `badges` branch), so the coverage-badge
# job is disabled. Enable it once a coverage command is added.
enable_coverage_badge: false
# `npm run ci:coverage` runs the vitest suite with v8 coverage, whose
# json-summary reporter writes coverage/coverage-summary.json.
enable_coverage_badge: ${{ github.event.inputs.enable_coverage_badge != 'false' }}
default_branch: ${{ github.event.inputs.default_branch || 'master' }} # Badge only pushed when a push lands on this branch
coverage_command: ${{ github.event.inputs.coverage_command || 'npm run ci:coverage' }}
coverage_summary_path: ${{ github.event.inputs.coverage_summary_path || 'coverage/coverage-summary.json' }}
Expand All @@ -283,9 +286,7 @@ jobs:
# Injects a Shields.io badge + breakdown table directly into the PR body
# on every push to the PR branch. Only fires on pull_request events;
# skipped automatically on push and workflow_dispatch. No files committed.
# Disabled for the same reason as enable_coverage_badge above β€” no
# coverage data is produced, so there is nothing to inject into the PR.
enable_coverage_pr_comment: false
enable_coverage_pr_comment: ${{ github.event.inputs.enable_coverage_pr_comment != 'false' }}

# Authentication & Bot Configuration
# The workflow supports automatic App token detection for enhanced permissions and proper attribution:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/hotfixes-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
uses: CLDMV/.github/.github/workflows/workflow-hotfixes-release.yml@v4
with:
package_name: "@cldmv/wisp"
build_command: "npm run build:types"
build_command: "npm run types:build && npm run build"
secrets:
# Map your repo/org secrets to the expected names.
BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/next-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
uses: CLDMV/.github/.github/workflows/workflow-next-release.yml@v4
with:
package_name: "@cldmv/wisp"
build_command: "npm run build:types"
build_command: "npm run types:build && npm run build"
secrets:
# Map your repo/org secrets to the expected names.
BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }}
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,14 @@ jobs:
github_packages_publish_command: ""
min_node_version: ${{ github.event.inputs.min_node_version || '22.12.0' }}
max_node_major: ${{ github.event.inputs.max_node_major || '' }} # blank β‡’ inherit the CLDMV/.github reusable default
test_command: "npm test" # mocha (test/**/*.mjs)
test_command: "npm test" # vitest via @cldmv/vitest-runner (tests/**/*.test.vitest.mjs), then the node:test CJS entry check
# test_command: "NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override NODE_OPTIONS only
# test_command: "NODE_ENV=test npm test" # Override NODE_ENV only
# test_command: "NODE_ENV=test NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override both
test_environment: ${{ github.event.inputs.test_environment || 'development' }} # Alternative to setting in test_command
# build:types (tsc) emits the published types/ dir and type-checks it.
build_command: "npm run build:types"
# types:build (tsc) emits the published types/ dir and type-checks it; build (tsup)
# bundles src/ into the published dist/.
build_command: "npm run types:build && npm run build"
# --- Satellite packages (optional) ----------------------------------------
# Publish extra packages carved from this build's output (e.g. locale JSON,
# generated types) at the SAME version/commit as the core, each with its own
Expand Down
Loading
Loading