diff --git a/.configs/.prettierrc b/.configs/.prettierrc new file mode 100644 index 0000000..f84f51b --- /dev/null +++ b/.configs/.prettierrc @@ -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"] +} diff --git a/.configs/eslint.config.mjs b/.configs/eslint.config.mjs index eab033b..f680449 100644 --- a/.configs/eslint.config.mjs +++ b/.configs/eslint.config.mjs @@ -2,12 +2,12 @@ * * @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 * @Email: * ----- * @Last modified by: Nate Corcoran (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. * @@ -15,23 +15,42 @@ 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 *.*" ] }, { @@ -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"] } ]); diff --git a/.configs/fix-headers.json b/.configs/fix-headers.json index a9588e8..33ba6d5 100644 --- a/.configs/fix-headers.json +++ b/.configs/fix-headers.json @@ -1,4 +1,4 @@ { "extends": "@cldmv/configs/fix-headers.json", - "excludeFolders": ["test/fixtures"] + "excludeFolders": ["tests/fixtures"] } diff --git a/.configs/vitest.config.mjs b/.configs/vitest.config.mjs new file mode 100644 index 0000000..dd6259e --- /dev/null +++ b/.configs/vitest.config.mjs @@ -0,0 +1,55 @@ +/** + * + * @Project: @cldmv/wisp + * @Filename: /.configs/vitest.config.mjs + * @Date: 2026-10-03T19:21:07-07:00 (1791080467) + * @Author: Nate Corcoran + * @Email: + * ----- + * @Last modified by: Nate Corcoran (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"] + } + } +}); diff --git a/.githooks/install.mjs b/.githooks/install.mjs new file mode 100644 index 0000000..0d51682 --- /dev/null +++ b/.githooks/install.mjs @@ -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 + * @Email: + * ----- + * @Last modified by: Nate Corcoran (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/`, 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)"); diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..cc0711b --- /dev/null +++ b/.githooks/pre-commit @@ -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