From c6acf5813ad3e1268e35d0287ba36a0a7b93845a Mon Sep 17 00:00:00 2001 From: httphypixelnet Date: Mon, 27 Jul 2026 12:50:08 -0700 Subject: [PATCH 1/7] Add pre-push and post-checkout hooks Modified ESLint and lint-staged configs to better support modular and conditional linting Should save time in creating commits and further reduce risk of outdated node_modules --- .husky/post-checkout | 3 +++ .husky/pre-push | 1 + .prettierignore | 2 ++ eslint.config.mjs | 25 ++++++++++++++++++----- lint-staged.config.mjs | 19 ++++++++++++++++++ package.json | 16 +++------------ pnpm-lock.yaml | 9 +++++++++ scripts/post-checkout.mjs | 39 ++++++++++++++++++++++++++++++++++++ scripts/postinstall.ts | 22 ++++++++++++++++++++ scripts/stamp.mjs | 28 ++++++++++++++++++++++++++ scripts/syncLockfile.lint.ts | 15 ++++++++++++++ 11 files changed, 161 insertions(+), 18 deletions(-) create mode 100755 .husky/post-checkout create mode 100644 .husky/pre-push create mode 100644 lint-staged.config.mjs create mode 100644 scripts/post-checkout.mjs create mode 100644 scripts/postinstall.ts create mode 100644 scripts/stamp.mjs create mode 100644 scripts/syncLockfile.lint.ts diff --git a/.husky/post-checkout b/.husky/post-checkout new file mode 100755 index 00000000..98b83bb7 --- /dev/null +++ b/.husky/post-checkout @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +node scripts/post-checkout.mjs "$1" "$2" "$3" \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 00000000..ef758695 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1 @@ +pnpm exec typecheck \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index b4df84b6..a830e313 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,5 @@ dist/ .styles/ pnpm-lock.yaml examples/ +.husky/ +.prettierignore \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index 085833e3..b1908278 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,9 +1,13 @@ +// @ts-check import js from '@eslint/js'; import tseslint from 'typescript-eslint'; import astro from 'eslint-plugin-astro'; +import process from 'node:process'; +import globals from 'globals'; -export default [ - { ignores: ['dist/', '.astro/', 'node_modules/', 'scripts/'] }, +import { defineConfig } from 'eslint/config'; +export default defineConfig([ + { ignores: ['dist/', '.astro/', 'node_modules/'] }, js.configs.recommended, @@ -18,11 +22,22 @@ export default [ { argsIgnorePattern: '^_' }, ], '@typescript-eslint/no-explicit-any': 'error', + // ci runs `astro check` which runs a full typescript checker + 'no-undef': process.env.CI ? 'off' : 'error', + }, + }, + { + files: ['scripts/*', 'src/plugins/*'], + languageOptions: { + globals: { + // scripts and plugins run in a node env + ...globals.node, + }, }, }, - { - files: ['**/*.astro'], + // also catch astro virtual files + files: ['**/*.astro', '**/*.astro/**/*.ts'], languageOptions: { globals: { ImageMetadata: 'readonly', @@ -32,4 +47,4 @@ export default [ 'no-undef': 'off', }, }, -]; +]); diff --git a/lint-staged.config.mjs b/lint-staged.config.mjs new file mode 100644 index 00000000..fd9f764a --- /dev/null +++ b/lint-staged.config.mjs @@ -0,0 +1,19 @@ +// @ts-check + +/** @type {import('lint-staged').Configuration} */ +export default { + '**/*': (files) => `prettier --write ${files.join(' ')}`, + '**/*.{astro,ts,mjs,js}': (files) => `eslint --fix ${files.join(' ')}`, + '**/*.{md,mdx}': (files) => [ + `pnpm remark ${files.join(' ')} --ext mdx --frail --no-stdout --quiet`, + ], + 'examples/**/*.{java,gradle}': () => [ + `./examples/gradlew -p examples spotlessApply`, + ], + 'src/data/glossary.ts': () => [ + 'pnpm generate:glossary', + 'git add src/content/docs/resources/glossary.mdx', + ], + // Yes, I know this should be a FunctionTask but those are kinda bad until https://github.com/lint-staged/lint-staged/issues/1826 is resolved + 'package.json': () => 'pnpm tsx scripts/syncLockfile.lint.ts', +}; diff --git a/package.json b/package.json index 0a416bcc..2c745aa1 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@astrojs/starlight": "^0.39.2", "@mdi/js": "^7.4.47", "astro": "^6.3.7", + "globals": "^17.7.0", "sharp": "^0.34.5", "unist-util-visit": "^5.1.0" }, @@ -38,11 +39,11 @@ "astro-eslint-parser": "^1.4.0", "eslint": "^10.5.0", "eslint-plugin-astro": "^1.7.0", - "mdx2vast": "^0.3.1", "husky": "^9.1.7", "lint-staged": "^17.0.8", - "prettier": "^3.8.4", "mdast-util-mdx-jsx": "^3.2.0", + "mdx2vast": "^0.3.1", + "prettier": "^3.8.4", "prettier-plugin-astro": "^0.14.1", "prettier-plugin-sentences-per-line": "^0.2.3", "remark-cli": "^12.0.1", @@ -53,16 +54,5 @@ "typescript": "^6.0.3", "typescript-eslint": "^8.61.1", "vfile": "^6.0.3" - }, - "lint-staged": { - "**/*": [ - "pnpm format", - "pnpm lint" - ], - "examples/**/*.java": "sh -c 'cd examples && ./gradlew spotlessApply'", - "src/data/glossary.ts": [ - "pnpm generate:glossary", - "git add src/content/docs/resources/glossary.mdx" - ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2cd03bcf..eda141c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: astro: specifier: ^6.3.7 version: 6.3.7(@types/node@24.12.4)(rollup@4.59.0)(tsx@4.22.4)(yaml@2.9.0) + globals: + specifier: ^17.7.0 + version: 17.7.0 sharp: specifier: ^0.34.5 version: 0.34.5 @@ -1771,6 +1774,10 @@ packages: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} + globals@17.7.0: + resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} + engines: {node: '>=18'} + h3@1.15.11: resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} @@ -5341,6 +5348,8 @@ snapshots: globals@16.5.0: {} + globals@17.7.0: {} + h3@1.15.11: dependencies: cookie-es: 1.2.3 diff --git a/scripts/post-checkout.mjs b/scripts/post-checkout.mjs new file mode 100644 index 00000000..13fdd29d --- /dev/null +++ b/scripts/post-checkout.mjs @@ -0,0 +1,39 @@ +#!/usr/bin/env node +// @ts-check +// scripts/post-checkout.ts +// +// Cross-platform pnpm sync check for git's post-checkout hook. +// Git calls this with three args: + +import { execSync } from 'node:child_process'; +import { hashLockfile, readStamp } from './stamp.mjs'; + +function main() { + const [, , , isBranchCheckoutArg] = process.argv; + + if (isBranchCheckoutArg !== '1') { + process.exit(0); + } + + const currentHash = hashLockfile(); + if (!currentHash) { + process.exit(0); + } + + if (currentHash === readStamp()) { + process.exit(0); + } + + console.log('lockfile changed since last checkout, running `pnpm install`'); + + try { + execSync('pnpm install', { stdio: 'inherit' }); + } catch { + console.error('pnpm install failed. retry manually.'); + process.exit(1); + } +} + +if (!process.env.CI) { + main(); +} diff --git a/scripts/postinstall.ts b/scripts/postinstall.ts new file mode 100644 index 00000000..a7e27e4f --- /dev/null +++ b/scripts/postinstall.ts @@ -0,0 +1,22 @@ +import { hashLockfile, STAMP, writeStamp } from './stamp.mjs'; +import { existsSync, unlinkSync } from 'node:fs'; + +function updateHash(): void { + const hash = hashLockfile(); + if (!hash) { + // no lockfile for some reason, remove stale stamp if it exists + if (existsSync(STAMP)) { + unlinkSync(STAMP); + } + process.exit(0); + } + writeStamp(hash); +} +// main function. put other functions in here to run on postinstall. do not put loose code in main, make your own method +function main(): void { + updateHash(); +} + +if (!process.env.CI) { + main(); +} diff --git a/scripts/stamp.mjs b/scripts/stamp.mjs new file mode 100644 index 00000000..71efb879 --- /dev/null +++ b/scripts/stamp.mjs @@ -0,0 +1,28 @@ +// @ts-check +import { createHash } from 'node:crypto'; +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +export const LOCKFILE = resolve(process.cwd(), 'pnpm-lock.yaml'); +export const STAMP = resolve(process.cwd(), 'node_modules', '.checksum.sha1'); + +/** + * @returns {string | null} the hash or null if the lockfile doesn't exist + */ +export function hashLockfile() { + if (!existsSync(LOCKFILE)) return null; + const contents = readFileSync(LOCKFILE); + return createHash('sha1').update(contents).digest('hex'); +} +/** + * @returns {string} the hash from the stamp file, or an empty string if it doesn't exist + */ +export function readStamp() { + return existsSync(STAMP) ? readFileSync(STAMP, 'utf-8').trim() : ''; +} +/** + * @param {string} hash + */ +export function writeStamp(hash) { + writeFileSync(STAMP, hash, 'utf-8'); +} diff --git a/scripts/syncLockfile.lint.ts b/scripts/syncLockfile.lint.ts new file mode 100644 index 00000000..0bb8c7ef --- /dev/null +++ b/scripts/syncLockfile.lint.ts @@ -0,0 +1,15 @@ +// @ts-check + +// Script to check if pnpm-lock.yaml is in sync with package.json. +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; + +await promisify(exec)( + // check if lockfile is in sync, don't run scripts, don't modify node_modules or lockfile + 'pnpm install --frozen-lockfile --lockfile-only --ignore-scripts', +).catch(() => { + console.error( + 'pnpm-lock.yaml is out of sync with package.json. Run `pnpm install` to update the lockfile.', + ); + process.exit(1); +}); From a428b46f20af438fa89a7a718cde816508950342 Mon Sep 17 00:00:00 2001 From: httphypixelnet Date: Mon, 27 Jul 2026 13:09:41 -0700 Subject: [PATCH 2/7] fix hook --- .husky/pre-push | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.husky/pre-push b/.husky/pre-push index ef758695..f2606026 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1 +1 @@ -pnpm exec typecheck \ No newline at end of file +pnpm typecheck --minimumFailingSeverity warning \ No newline at end of file From 6b68ebfb0a08a6133e05941774611ccf107cd493 Mon Sep 17 00:00:00 2001 From: httphypixelnet Date: Mon, 27 Jul 2026 13:43:00 -0700 Subject: [PATCH 3/7] remove unneeded ts-check statements --- .remarkrc.mjs | 2 -- eslint.config.mjs | 5 ++--- lint-staged.config.mjs | 2 -- scripts/post-checkout.mjs | 1 - scripts/stamp.mjs | 1 - scripts/syncLockfile.lint.ts | 2 -- src/plugins/remark-no-inline-code-fences.mjs | 1 - 7 files changed, 2 insertions(+), 12 deletions(-) diff --git a/.remarkrc.mjs b/.remarkrc.mjs index 64af7285..3a3379bb 100644 --- a/.remarkrc.mjs +++ b/.remarkrc.mjs @@ -1,5 +1,3 @@ -// @ts-check - import remarkFrontmatter from 'remark-frontmatter'; import remarkMdx from 'remark-mdx'; import remarkPresetLintRecommended from 'remark-preset-lint-recommended'; diff --git a/eslint.config.mjs b/eslint.config.mjs index b1908278..0efff8c4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,9 +1,8 @@ -// @ts-check import js from '@eslint/js'; -import tseslint from 'typescript-eslint'; import astro from 'eslint-plugin-astro'; -import process from 'node:process'; import globals from 'globals'; +import process from 'node:process'; +import tseslint from 'typescript-eslint'; import { defineConfig } from 'eslint/config'; export default defineConfig([ diff --git a/lint-staged.config.mjs b/lint-staged.config.mjs index 44fa1f27..db55818d 100644 --- a/lint-staged.config.mjs +++ b/lint-staged.config.mjs @@ -1,5 +1,3 @@ -// @ts-check - /** @type {import('lint-staged').Configuration} */ export default { '**/*': (files) => `prettier --write --ignore-unknown ${files.join(' ')}`, diff --git a/scripts/post-checkout.mjs b/scripts/post-checkout.mjs index 13fdd29d..4038b6a3 100644 --- a/scripts/post-checkout.mjs +++ b/scripts/post-checkout.mjs @@ -1,5 +1,4 @@ #!/usr/bin/env node -// @ts-check // scripts/post-checkout.ts // // Cross-platform pnpm sync check for git's post-checkout hook. diff --git a/scripts/stamp.mjs b/scripts/stamp.mjs index 71efb879..efc69968 100644 --- a/scripts/stamp.mjs +++ b/scripts/stamp.mjs @@ -1,4 +1,3 @@ -// @ts-check import { createHash } from 'node:crypto'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; diff --git a/scripts/syncLockfile.lint.ts b/scripts/syncLockfile.lint.ts index 0bb8c7ef..e2f0e005 100644 --- a/scripts/syncLockfile.lint.ts +++ b/scripts/syncLockfile.lint.ts @@ -1,5 +1,3 @@ -// @ts-check - // Script to check if pnpm-lock.yaml is in sync with package.json. import { exec } from 'node:child_process'; import { promisify } from 'node:util'; diff --git a/src/plugins/remark-no-inline-code-fences.mjs b/src/plugins/remark-no-inline-code-fences.mjs index f4eacb81..d8543659 100644 --- a/src/plugins/remark-no-inline-code-fences.mjs +++ b/src/plugins/remark-no-inline-code-fences.mjs @@ -1,4 +1,3 @@ -// @ts-check /// import { visit } from 'unist-util-visit'; From 8b0fde0b999d1bd7ce91cefd047b7e0dba3ffa30 Mon Sep 17 00:00:00 2001 From: httphypixelnet Date: Mon, 27 Jul 2026 22:50:56 -0700 Subject: [PATCH 4/7] downgrade globals to eslint version --- .husky/post-checkout | 2 +- package.json | 2 +- pnpm-lock.yaml | 12 +++--------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.husky/post-checkout b/.husky/post-checkout index 98b83bb7..788cd335 100755 --- a/.husky/post-checkout +++ b/.husky/post-checkout @@ -1,3 +1,3 @@ #!/usr/bin/env bash -node scripts/post-checkout.mjs "$1" "$2" "$3" \ No newline at end of file +node scripts/post-checkout.mjs "$1" "$2" "$3" diff --git a/package.json b/package.json index cb994129..31979789 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "@astrojs/markdown-remark": "^7.2.1", "@astrojs/starlight": "^0.41.4", "@mdi/js": "^7.4.47", - "globals": "^17.7.0", "astro": "^7.1.3", "sharp": "^0.34.5", "unist-util-visit": "^5.1.0" @@ -56,6 +55,7 @@ "remark-frontmatter": "^5.0.0", "remark-lint-no-dead-urls": "^2.0.1", "remark-mdx": "^3.1.1", + "globals": "^16.5.0", "remark-preset-lint-recommended": "^7.0.1", "starlight-links-validator": "^0.20.0", "tsx": "^4.22.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d31739c..5ba09bc2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,9 +20,6 @@ importers: astro: specifier: ^7.1.3 version: 7.1.3(@astrojs/markdown-remark@7.2.1)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@24.12.4)(rollup@4.59.0)(tsx@4.22.4)(yaml@2.9.0) - globals: - specifier: ^17.7.0 - version: 17.7.0 sharp: specifier: ^0.34.5 version: 0.34.5 @@ -51,6 +48,9 @@ importers: eslint-plugin-astro: specifier: ^1.7.0 version: 1.7.0(eslint@10.5.0) + globals: + specifier: ^16.5.0 + version: 16.5.0 husky: specifier: ^9.1.7 version: 9.1.7 @@ -1943,10 +1943,6 @@ packages: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} - globals@17.7.0: - resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} - engines: {node: '>=18'} - got@13.0.0: resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==} engines: {node: '>=16'} @@ -5895,8 +5891,6 @@ snapshots: globals@16.5.0: {} - globals@17.7.0: {} - got@13.0.0: dependencies: '@sindresorhus/is': 5.6.0 From 3609b288c18ab32595e67a4b039efb74c26f2044 Mon Sep 17 00:00:00 2001 From: httphypixelnet Date: Mon, 27 Jul 2026 22:51:52 -0700 Subject: [PATCH 5/7] add newline at end of files --- .husky/pre-push | 2 +- .prettierignore | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.husky/pre-push b/.husky/pre-push index f2606026..48f9bf50 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1 +1 @@ -pnpm typecheck --minimumFailingSeverity warning \ No newline at end of file +pnpm typecheck --minimumFailingSeverity warning diff --git a/.prettierignore b/.prettierignore index 5cf512eb..b4df84b6 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,4 +3,4 @@ dist/ .astro/ .styles/ pnpm-lock.yaml -examples/ \ No newline at end of file +examples/ From a963c845d694b1fdd08e38be2fbd7445e208771a Mon Sep 17 00:00:00 2001 From: httphypixelnet Date: Mon, 27 Jul 2026 22:55:08 -0700 Subject: [PATCH 6/7] remvoe stale comments --- lint-staged.config.mjs | 2 +- scripts/post-checkout.mjs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lint-staged.config.mjs b/lint-staged.config.mjs index db55818d..76d084ed 100644 --- a/lint-staged.config.mjs +++ b/lint-staged.config.mjs @@ -3,7 +3,7 @@ export default { '**/*': (files) => `prettier --write --ignore-unknown ${files.join(' ')}`, '**/*.{astro,ts,mjs,js}': (files) => `eslint --fix ${files.join(' ')}`, 'src/content/*.{md,mdx}': (files) => [ - `pnpm remark ${files.join(' ')} --ext mdx --frail --no-stdout --quiet`, + `pnpm remark ${files.join(' ')} --ext mdx --frail --no-stdout --quiet`, ], 'examples/**/*.{java,gradle}': () => [ `./examples/gradlew -p examples spotlessApply`, diff --git a/scripts/post-checkout.mjs b/scripts/post-checkout.mjs index 4038b6a3..a257a766 100644 --- a/scripts/post-checkout.mjs +++ b/scripts/post-checkout.mjs @@ -1,8 +1,4 @@ #!/usr/bin/env node -// scripts/post-checkout.ts -// -// Cross-platform pnpm sync check for git's post-checkout hook. -// Git calls this with three args: import { execSync } from 'node:child_process'; import { hashLockfile, readStamp } from './stamp.mjs'; From 45a3a0513723cdf6d41c34b5ce9aa36b9ba31919 Mon Sep 17 00:00:00 2001 From: httphypixelnet Date: Tue, 28 Jul 2026 21:13:42 -0700 Subject: [PATCH 7/7] Fix glob issue and off-by-one in checkout script --- lint-staged.config.mjs | 2 +- scripts/post-checkout.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lint-staged.config.mjs b/lint-staged.config.mjs index 76d084ed..1b56c8b9 100644 --- a/lint-staged.config.mjs +++ b/lint-staged.config.mjs @@ -2,7 +2,7 @@ export default { '**/*': (files) => `prettier --write --ignore-unknown ${files.join(' ')}`, '**/*.{astro,ts,mjs,js}': (files) => `eslint --fix ${files.join(' ')}`, - 'src/content/*.{md,mdx}': (files) => [ + 'src/content/**/*.{md,mdx}': (files) => [ `pnpm remark ${files.join(' ')} --ext mdx --frail --no-stdout --quiet`, ], 'examples/**/*.{java,gradle}': () => [ diff --git a/scripts/post-checkout.mjs b/scripts/post-checkout.mjs index a257a766..fb6558b3 100644 --- a/scripts/post-checkout.mjs +++ b/scripts/post-checkout.mjs @@ -4,7 +4,7 @@ import { execSync } from 'node:child_process'; import { hashLockfile, readStamp } from './stamp.mjs'; function main() { - const [, , , isBranchCheckoutArg] = process.argv; + const [, , , , isBranchCheckoutArg] = process.argv; if (isBranchCheckoutArg !== '1') { process.exit(0);