Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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"]
}
63 changes: 48 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.
"test/fixtures/**",
"**/package-lock.json",
// Copy file patterns
"*copy/",
"*copy (*)/",
"*copy */",
"*copy.*",
"*copy (*).*",
"*copy *.*",
"**/*copy/",
"**/*copy (*)/",
"**/*copy */",
"**/*copy.*",
"**/*copy (*).*",
"**/*copy *.*"
]
},
{
Expand All @@ -48,12 +67,26 @@ 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: ["test/**/*.mjs"], languageOptions: { globals: { ...globals.mocha } } },
{ 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"] }
]);
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
18 changes: 18 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Dependencies + generated/build output (not hand-authored)
node_modules/
dist/
build/
coverage/
tmp/
trash/
types/
package-lock.json
# minified / compact generated files of any extension (*.min.js/.css/.json/.map…)
*.min.*

# YAML β€” CI workflows are deliberately 4-space indented; prettier (tabWidth 2,
# and YAML disallows tabs) would reindent every file 4->2 for no real gain.
*.yml
*.yaml
reference/
test/fixtures/
17 changes: 11 additions & 6 deletions BUGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
**Date Reported:** November 10, 2025
**Status:** βœ… FIXED
**Severity:** Critical
**Affected Versions:** v1.0.0
**Affected Versions:** v1.0.0

### Description

The wisp module had a fundamental flaw in its caller path resolution system that caused relative paths to be resolved from the wrong location, leading to `ENOENT` errors when loading JSON files.

### Error Example

```
```text
Error: @cldmv/wisp: Failed to load JSON file at file:///P:/Dropbox/Sync/Documents/CLDMV/repos/tv-control/node_modules/@cldmv/wisp/examples/test-devices.json: ENOENT: no such file or directory
```

When calling:

```javascript
const testDevicesJson = wispSync("../examples/test-devices.json");
```
Expand All @@ -36,12 +37,14 @@ The issue was in `src/lib/resolve-from-caller.mjs` with multiple contributing fa
### Technical Details

**Expected Stack Trace:**
```

```text
user-code.mjs β†’ index.mjs β†’ src/wisp.mjs β†’ src/lib/resolve-from-caller.mjs
```

**Actual Stack Trace:**
```

```text
user-code.mjs β†’ src/wisp.mjs β†’ src/lib/resolve-from-caller.mjs
```

Expand All @@ -57,6 +60,7 @@ Replaced the flawed hardcoded approach with a **generic, package-agnostic** solu
4. **Removed Hardcoded Names**: No more dependency on specific filenames

**Key Changes:**

- Added `findPackageRoot()` function for dynamic package detection
- Reimplemented `pickPrimaryBaseFile()` with exit-based detection
- Simplified logic to handle `index.mjs` optimization
Expand All @@ -65,6 +69,7 @@ Replaced the flawed hardcoded approach with a **generic, package-agnostic** solu
### Test Coverage

Enhanced test suite to include:

- Proper entry point usage (`index.mjs` instead of `src/wisp.mjs`)
- Specific relative path resolution tests
- Real-world usage scenario validation
Expand All @@ -75,7 +80,7 @@ Enhanced test suite to include:
βœ… All existing tests pass
βœ… New relative path tests pass
βœ… Works through proper entry point
βœ… Generic solution works for any package structure
βœ… Generic solution works for any package structure

### Files Modified

Expand All @@ -84,4 +89,4 @@ Enhanced test suite to include:

### Impact

This fix resolves the fundamental caller resolution issue that prevented wisp from working correctly in consuming modules. The solution is now robust and package-agnostic, making it suitable for reuse in other projects.
This fix resolves the fundamental caller resolution issue that prevented wisp from working correctly in consuming modules. The solution is now robust and package-agnostic, making it suitable for reuse in other projects.
Loading
Loading