From 1275f029a1e4b4f3e9f8f05d657e54ab0481a214 Mon Sep 17 00:00:00 2001 From: Matthias Reisacher Date: Fri, 17 Apr 2026 14:42:50 +0200 Subject: [PATCH 1/5] Add validation script for examples.json files --- .gitignore | 7 + .husky/pre-commit | 2 + .prettierignore | 2 + .prettierrc | 7 + .tool-versions | 1 + .vscode/settings.json | 11 + eslint.config.mjs | 5 + package.json | 15 + pnpm-lock.yaml | 1152 ++++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 2 + tools/docs-cli/package.json | 16 + tools/docs-cli/src/utils.ts | 21 + tools/docs-cli/src/validate.ts | 59 ++ tools/docs-cli/tsconfig.json | 22 + 14 files changed, 1322 insertions(+) create mode 100644 .husky/pre-commit create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 .tool-versions create mode 100644 .vscode/settings.json create mode 100644 eslint.config.mjs create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 tools/docs-cli/package.json create mode 100644 tools/docs-cli/src/utils.ts create mode 100644 tools/docs-cli/src/validate.ts create mode 100644 tools/docs-cli/tsconfig.json diff --git a/.gitignore b/.gitignore index 7585238..f0db70f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ book + +# TypeScript +node_modules +dist +.npmrc +.nx +*.tsbuildinfo diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..1d6a30d --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +#!/bin/sh +pnpm --filter @shapediver/docs-cli run validate "${PWD}/src" diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..4c782e7 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +# Ignore markdown files +/src diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..042671e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 99, + "tabWidth": 4, + "semi": true, + "singleQuote": true, + "trailingComma": "es5" +} diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..ef93fec --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 24.12.0 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9b1fa16 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", + "search.exclude": { + "**/.git": true, + }, + "terminal.integrated.defaultProfile.osx": "zsh - work", + "js/ts.preferences.includePackageJsonAutoImports": "on", + "cSpell.words": [ + "mdbook" + ] +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..0e981fd --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,5 @@ +import { defineConfig } from 'eslint/config'; +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default defineConfig([eslint.configs.recommended, ...tseslint.configs.recommended]); diff --git a/package.json b/package.json new file mode 100644 index 0000000..c9ad2c3 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "root", + "private": true, + "scripts": { + "prepare": "husky" + }, + "devDependencies": { + "@eslint/js": "10.0.1", + "eslint": "10.2.0", + "husky": "9.1.7", + "npm-check-updates": "21.0.0", + "prettier": "3.8.3", + "typescript-eslint": "8.58.2" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..35d887a --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1152 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@eslint/js': + specifier: 10.0.1 + version: 10.0.1(eslint@10.2.0) + eslint: + specifier: 10.2.0 + version: 10.2.0 + husky: + specifier: 9.1.7 + version: 9.1.7 + npm-check-updates: + specifier: 21.0.0 + version: 21.0.0 + prettier: + specifier: 3.8.3 + version: 3.8.3 + typescript-eslint: + specifier: 8.58.2 + version: 8.58.2(eslint@10.2.0)(typescript@6.0.2) + + tools/docs-cli: + dependencies: + zod: + specifier: 4.3.6 + version: 4.3.6 + devDependencies: + '@types/node': + specifier: 24.1.0 + version: 24.1.0 + tsx: + specifier: 4.21.0 + version: 4.21.0 + typescript: + specifier: 6.0.2 + version: 6.0.2 + +packages: + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.5.5': + resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.1': + resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.1.0': + resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==} + + '@typescript-eslint/eslint-plugin@8.58.2': + resolution: {integrity: sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.58.2 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.58.2': + resolution: {integrity: sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.58.2': + resolution: {integrity: sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.58.2': + resolution: {integrity: sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.58.2': + resolution: {integrity: sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.58.2': + resolution: {integrity: sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.58.2': + resolution: {integrity: sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.58.2': + resolution: {integrity: sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.58.2': + resolution: {integrity: sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.58.2': + resolution: {integrity: sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.2.0: + resolution: {integrity: sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + npm-check-updates@21.0.0: + resolution: {integrity: sha512-iGFLoW1QWsEDLR6Cnklyk+iHTf20hS84o79idR6AKhjSwk0whMdCd5FS0bTgEe6gMrRnJ0fGr2P6BEZ2zOelYg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: '>=10.0.0'} + hasBin: true + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.58.2: + resolution: {integrity: sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@6.0.2: + resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.2.0)': + dependencies: + eslint: 10.2.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.5.5': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.2.0)': + optionalDependencies: + eslint: 10.2.0 + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.1': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@24.1.0': + dependencies: + undici-types: 7.8.0 + + '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/type-utils': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/visitor-keys': 8.58.2 + eslint: 10.2.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.2) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) + '@typescript-eslint/visitor-keys': 8.58.2 + debug: 4.4.3 + eslint: 10.2.0 + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.58.2(typescript@6.0.2)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.2) + '@typescript-eslint/types': 8.58.2 + debug: 4.4.3 + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.58.2': + dependencies: + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/visitor-keys': 8.58.2 + + '@typescript-eslint/tsconfig-utils@8.58.2(typescript@6.0.2)': + dependencies: + typescript: 6.0.2 + + '@typescript-eslint/type-utils@8.58.2(eslint@10.2.0)(typescript@6.0.2)': + dependencies: + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + debug: 4.4.3 + eslint: 10.2.0 + ts-api-utils: 2.5.0(typescript@6.0.2) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.58.2': {} + + '@typescript-eslint/typescript-estree@8.58.2(typescript@6.0.2)': + dependencies: + '@typescript-eslint/project-service': 8.58.2(typescript@6.0.2) + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@6.0.2) + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/visitor-keys': 8.58.2 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@6.0.2) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.58.2(eslint@10.2.0)(typescript@6.0.2)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0) + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) + eslint: 10.2.0 + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.58.2': + dependencies: + '@typescript-eslint/types': 8.58.2 + eslint-visitor-keys: 5.0.1 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + balanced-match@4.0.4: {} + + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escape-string-regexp@4.0.0: {} + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.2.0: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.5.5 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + fsevents@2.3.3: + optional: true + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + husky@9.1.7: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + npm-check-updates@21.0.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picomatch@4.0.4: {} + + prelude-ls@1.2.1: {} + + prettier@3.8.3: {} + + punycode@2.3.1: {} + + resolve-pkg-maps@1.0.0: {} + + semver@7.7.4: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + ts-api-utils@2.5.0(typescript@6.0.2): + dependencies: + typescript: 6.0.2 + + tsx@4.21.0: + dependencies: + esbuild: 0.27.7 + get-tsconfig: 4.14.0 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.58.2(eslint@10.2.0)(typescript@6.0.2): + dependencies: + '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@10.2.0)(typescript@6.0.2))(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.2(eslint@10.2.0)(typescript@6.0.2) + eslint: 10.2.0 + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + typescript@6.0.2: {} + + undici-types@7.8.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yocto-queue@0.1.0: {} + + zod@4.3.6: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..b256a1a --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'tools/docs-cli/' diff --git a/tools/docs-cli/package.json b/tools/docs-cli/package.json new file mode 100644 index 0000000..74eec8f --- /dev/null +++ b/tools/docs-cli/package.json @@ -0,0 +1,16 @@ +{ + "name": "@shapediver/docs-cli", + "version": "1.0.0", + "private": true, + "scripts": { + "validate": "tsx src/validate.ts" + }, + "dependencies": { + "zod": "4.3.6" + }, + "devDependencies": { + "@types/node": "24.1.0", + "tsx": "4.21.0", + "typescript": "6.0.2" + } +} diff --git a/tools/docs-cli/src/utils.ts b/tools/docs-cli/src/utils.ts new file mode 100644 index 0000000..18e6d41 --- /dev/null +++ b/tools/docs-cli/src/utils.ts @@ -0,0 +1,21 @@ +/* This file contains utility functions and constants for the docs-cli tool. */ +import { z } from 'zod'; + +/** The name of the JSON file holding information about ShapeDiver models used as examples. */ +export const FILE_NAME_EXAMPLES = 'examples.json'; + +/** The schema of the JSON file holding information about ShapeDiver models used as examples. */ +export const SCHEMA_EXAMPLES = z.record( + z.string(), + z.object({ + anchor: z.string(), + slug: z.string(), + title: z.string(), + modelLink: z.boolean(), + appLink: z.boolean(), + rating: z.number().min(1).max(3).int(), + docLink: z.string(), + videoLink: z.string(), + files: z.array(z.string()), + }) +); diff --git a/tools/docs-cli/src/validate.ts b/tools/docs-cli/src/validate.ts new file mode 100644 index 0000000..6d641f2 --- /dev/null +++ b/tools/docs-cli/src/validate.ts @@ -0,0 +1,59 @@ +import fs from 'fs'; +import path from 'path'; +import { z } from 'zod'; +import { FILE_NAME_EXAMPLES, SCHEMA_EXAMPLES } from './utils'; + +(function () { + // husky passes the absolute path of the mdbook directory as an argument + const mdbookPath = process.argv[2]; + if (!fs.existsSync(mdbookPath)) { + console.error(`Path to mdbook does not exist: ${mdbookPath}`); + process.exit(1); + } + + // Find all example files one level deep in mdbook directory + const exampleFiles = fs + .readdirSync(mdbookPath, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => path.join(mdbookPath, entry.name, FILE_NAME_EXAMPLES)) + .filter((filePath) => fs.existsSync(filePath)); + + let hasError = false; + + for (const filePath of exampleFiles) { + try { + const fileContent = fs.readFileSync(filePath, 'utf-8'); + const jsonData = JSON.parse(fileContent); + + // Validate JSON structure + const parsedData = SCHEMA_EXAMPLES.parse(jsonData); + + const baseDir = path.dirname(filePath); + + // Validate physical files exist relative to the JSON file + for (const ghxName of Object.keys(parsedData)) { + const absolutePath = path.join(baseDir, ghxName); + if (!fs.existsSync(absolutePath)) { + console.error(`ERROR: Missing referenced file '${ghxName}' in ${filePath}.`); + hasError = true; + } + } + } catch (e) { + console.error(`ERROR: Validation failed for ${filePath}:`); + if (e instanceof z.ZodError) { + console.error(e.issues); + } else if (e instanceof Error) { + console.error(e.message); + } else { + console.error(e); + } + hasError = true; + } + } + + // Abort the commit if any errors were found + if (hasError) { + console.error('\nValidation failed. Please fix the errors above before committing.'); + process.exit(1); + } +})(); diff --git a/tools/docs-cli/tsconfig.json b/tools/docs-cli/tsconfig.json new file mode 100644 index 0000000..d568158 --- /dev/null +++ b/tools/docs-cli/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": [ + "ES2024" + ], + "types": [ + "node" + ], + "module": "nodenext", + "target": "ES2024", + "moduleResolution": "nodenext", + "noEmit": true, + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true + }, + "include": [ + "./src/**/*.ts" + ] +} From 0009a1153dae38b6480d41758f3957419c129767 Mon Sep 17 00:00:00 2001 From: Matthias Reisacher Date: Mon, 15 Jun 2026 17:51:28 +0200 Subject: [PATCH 2/5] Add create-tables script Creates definitions.md files from their examples.json specification. --- .husky/pre-commit | 2 +- .tool-versions | 1 - README.md | 22 ++- mise.toml | 3 + package.json | 4 +- pnpm-workspace.yaml | 2 + src/01-Basics/definitions.md | 6 +- src/01-Basics/examples.json | 26 +++ src/02-Inputs/definitions.md | 16 +- src/02-Inputs/examples.json | 148 +++++++++++++++ src/03-Outputs/definitions.md | 8 +- src/03-Outputs/examples.json | 50 +++++ src/04-Display/definitions.md | 7 +- src/04-Display/examples.json | 44 +++++ src/05-Images/definitions.md | 9 +- src/05-Images/examples.json | 62 +++++++ src/06-JSON/definitions.md | 5 +- src/06-JSON/examples.json | 14 ++ src/07-Attributes/definitions.md | 5 +- src/07-Attributes/examples.json | 14 ++ src/08-Plugins/definitions.md | 7 +- src/08-Plugins/examples.json | 38 ++++ src/11-AppBuilder/definitions.md | 26 +-- src/11-AppBuilder/examples.json | 174 ++++++++++++++++++ src/12-DesktopClients/definitions.md | 4 +- src/12-DesktopClients/examples.json | 15 ++ src/BETA/definitions.md | 16 +- src/BETA/examples.json | 160 ++++++++++++++++ ... 2410A-Webinar_BarcelonaNeighbourhood.ghx} | 0 ....ghx => 2410B-Webinar_ModularCabinets.ghx} | 0 ...ghx => 2503A-Webinar_SpecklePavillion.ghx} | 0 ....ghx => 2503B-Webinar_ShopifyWorkflow.ghx} | 0 ...=> 2605-Webinar_NewAppBuilderFeatures.ghx} | 0 src/Webinars/definitions.md | 12 +- src/Webinars/examples.json | 63 +++++++ tools/docs-cli/package.json | 1 + tools/docs-cli/src/createTables.ts | 147 +++++++++++++++ tools/docs-cli/src/utils.ts | 53 +++++- 38 files changed, 1050 insertions(+), 114 deletions(-) delete mode 100644 .tool-versions create mode 100644 mise.toml create mode 100644 src/01-Basics/examples.json create mode 100644 src/02-Inputs/examples.json create mode 100644 src/03-Outputs/examples.json create mode 100644 src/04-Display/examples.json create mode 100644 src/05-Images/examples.json create mode 100644 src/06-JSON/examples.json create mode 100644 src/07-Attributes/examples.json create mode 100644 src/08-Plugins/examples.json create mode 100644 src/11-AppBuilder/examples.json create mode 100644 src/12-DesktopClients/examples.json create mode 100644 src/BETA/examples.json rename src/Webinars/{Webinar_2410_A_BarcelonaNeighbourhood.ghx => 2410A-Webinar_BarcelonaNeighbourhood.ghx} (100%) rename src/Webinars/{Webinar_2410_B_ModularCabinets.ghx => 2410B-Webinar_ModularCabinets.ghx} (100%) rename src/Webinars/{Webinar_2503_A_SpecklePavillion.ghx => 2503A-Webinar_SpecklePavillion.ghx} (100%) rename src/Webinars/{Webinar_2503_B_ShopifyWorkflow.ghx => 2503B-Webinar_ShopifyWorkflow.ghx} (100%) rename src/Webinars/{Webinar_2605_NewAppBuilderFeatures.ghx => 2605-Webinar_NewAppBuilderFeatures.ghx} (100%) create mode 100644 src/Webinars/examples.json create mode 100644 tools/docs-cli/src/createTables.ts diff --git a/.husky/pre-commit b/.husky/pre-commit index 1d6a30d..3f304fe 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,2 +1,2 @@ #!/bin/sh -pnpm --filter @shapediver/docs-cli run validate "${PWD}/src" +npm run docs:validate diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index ef93fec..0000000 --- a/.tool-versions +++ /dev/null @@ -1 +0,0 @@ -nodejs 24.12.0 diff --git a/README.md b/README.md index 045a098..fb11df9 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,31 @@ You can clone this repository to download all models at once. ### Overview -This repository uses [mdBook](https://rust-lang.github.io/mdBook/) to auto-generate documentation based on [Markdown](https://rust-lang.github.io/mdBook/format/markdown.html). A [GitHub action](.github/workflows/static.yml) is used to deploy to [GitHub pages](https://shapediver.github.io/GrasshopperExampleModels/). +This repository uses [mdBook](https://rust-lang.github.io/mdBook/) to auto-generate documentation based on [Markdown](https://rust-lang.github.io/mdBook/format/markdown.html). A [GitHub action](.github/workflows/static.yml) is used to deploy to [GitHub pages](https://shapediver.github.io/GrasshopperExampleModels/). + +The source of truth for example metadata is `src/**/examples.json`. Markdown tables in `src/**/definitions.md` are generated from these JSON files and should not be created or edited manually. + +### Repository structure + +- `src/` contains the mdBook content, example metadata, and model files. +- `tools/` contains repository tooling. In particular, `tools/docs-cli/` provides the scripts used to validate `examples.json` files and generate markdown tables from them. ### Guidelines * Use the `ghx` XML file format of Grasshopper. + * Update `examples.json` when changing example metadata. + * Add only additional explanatory text manually to Markdown files. + * Do not manually create or edit generated markdown tables; they are generated via GitHub workflows. + +### Commands + +- `npm run docs:validate` validates all `src/**/examples.json` files and checks referenced files. +- `npm run docs:create-tables` regenerates markdown tables from `src/**/examples.json`. +- `mdbook serve .` starts a local preview server for the documentation. ### Local testing -Take the following setup steps to locally test auto-generation of the documentation. +Take the following setup steps to locally test auto-generation of the documentation. * Install [rust](https://www.rust-lang.org/tools/install) * Install [mdBook](https://rust-lang.github.io/mdBook/guide/installation.html) @@ -26,3 +42,5 @@ Take the following setup steps to locally test auto-generation of the documentat `cargo install mdbook-external-links` * From the root directory of this repository run `mdbook serve .` +In normal workflows, markdown tables are generated through GitHub workflows and should not be created manually. + diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..bb496dc --- /dev/null +++ b/mise.toml @@ -0,0 +1,3 @@ +[tools] +node = "24" +pnpm = "11" diff --git a/package.json b/package.json index c9ad2c3..1108068 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,9 @@ "name": "root", "private": true, "scripts": { - "prepare": "husky" + "prepare": "husky", + "docs:create-tables": "pnpm --filter @shapediver/docs-cli run create-tables \"${PWD}/src/\"", + "docs:validate": "pnpm --filter @shapediver/docs-cli run validate \"${PWD}/src/\"" }, "devDependencies": { "@eslint/js": "10.0.1", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b256a1a..58a0f16 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,4 @@ packages: - 'tools/docs-cli/' +allowBuilds: + esbuild: true diff --git a/src/01-Basics/definitions.md b/src/01-Basics/definitions.md index 612636c..011dfe2 100644 --- a/src/01-Basics/definitions.md +++ b/src/01-Basics/definitions.md @@ -1,7 +1,3 @@ # Basics - -| Ref | Description | ShapeDiver links | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------| -| **1A** | Basic example without the ShapeDiver plugin | [Model](https://www.shapediver.com/app/m/1a-examplewithoutshapediverplugin-110) | [Download](1A-ExampleWithoutShapeDiverPlugin.ghx) | ★ ☆ ☆ | -| **1B** | Basic example with the ShapeDiver plugin | [Model](https://www.shapediver.com/app/m/1b-examplewithshapediverplugin-108) | [Download](1B-ExampleWithShapeDiverPlugin.ghx) | ★ ☆ ☆ | \ No newline at end of file +[basics]: # diff --git a/src/01-Basics/examples.json b/src/01-Basics/examples.json new file mode 100644 index 0000000..47dd498 --- /dev/null +++ b/src/01-Basics/examples.json @@ -0,0 +1,26 @@ +{ + "1A-ExampleWithoutShapeDiverPlugin.ghx": { + "anchor": "basics", + "slug": "1a-examplewithoutshapediverplugin-110", + "title": "Basic example without the ShapeDiver plugin", + "modelLink": true, + "appLink": false, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "1B-ExampleWithShapeDiverPlugin.ghx": { + "anchor": "basics", + "slug": "1b-examplewithshapediverplugin-108", + "title": "Basic example with the ShapeDiver plugin", + "modelLink": true, + "appLink": false, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + } +} diff --git a/src/02-Inputs/definitions.md b/src/02-Inputs/definitions.md index fbf203d..d6808f9 100644 --- a/src/02-Inputs/definitions.md +++ b/src/02-Inputs/definitions.md @@ -1,17 +1,3 @@ # Inputs - -| Ref | Resources | Description | ShapeDiver links | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------|---------| -| **2A** | [📖](https://help.shapediver.com/doc/direct-text-input) | Direct Text Input | [Model](https://www.shapediver.com/app/m/2a-directtextinput-27) | [Download](2A-DirectTextInput.ghx) | ★ ☆ ☆ | -| **2B** | [📖](https://help.shapediver.com/doc/import-components) [🎥](https://www.youtube.com/watch?v=6rZiZflGrRo) | File Imports | [Model](https://www.shapediver.com/app/m/2b-fileimports) | [Download](2B-FileImports.ghx) | ★ ☆ ☆ | -| **2C** | | Import document properties and User Text using ShapeDiver Attributes. | [Model](https://www.shapediver.com/app/m/2c-importwithattributes) | [Download](2C-ImportWithAttributes.ghx) / [Input](2C-ImportWithAttributes.3dm) | ★ ★ ☆ | -| **2D** | [📖](https://help.shapediver.com/doc/selection-input) [🎥](https://www.youtube.com/watch?v=m9BsPMvKPDo) | Selection Input basic tutorial | [Model]() / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=2d-selectioninput-tutorial1) | [Download](2D-SelectionInput_Basic.ghx) | ★ ☆ ☆ | -| **2E** | [📖](https://help.shapediver.com/doc/selection-input) [🎥](https://www.youtube.com/watch?v=m9BsPMvKPDo) | Use a Selection Input to influence both geometry and build a contextual App user interface | [Model]() / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=2e-selectioninput-tutorial2) | [Download](2E-SelectionInput_Advanced.ghx) | ★ ★ ☆ | -| **2F** | [📖](https://help.shapediver.com/doc/gumball-input) | Gumball Input basic tutorial | [Model](https://www.shapediver.com/app/m/2f-minimalgumballexample) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=2f-minimalgumballexample) | [Download](2F-GumballInput_Basic.ghx) | ★ ☆ ☆ | -| **2G** | [📖](https://help.shapediver.com/doc/gumball-input) [🎥](https://www.youtube.com/watch?v=Pu9nDFJXZC0) | Use a Gumball Input to transform geometry in the viewer and apply the transformations in Grasshopper | [Model](https://www.shapediver.com/app/m/2g-gumballinput-tutorial1) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=2g-gumballinput-tutorial1) | [Download](2G-GumballInput_Tutorial1.ghx) | ★ ★ ☆ | -| **2H** | [📖](https://help.shapediver.com/doc/gumball-input) | Advanced Gumball example with dynamic transformable objects. | [Model](https://www.shapediver.com/app/m/2h-gumballinput-tutorial2-3) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=2h-gumballinput-tutorial2-3&redirect=0) | [Download](2H-Gumballinput_Tutorial2.ghx) | ★ ★ ★ | -| **2I** | [📖](https://help.shapediver.com/doc/points-input) [🎥](https://www.youtube.com/watch?v=vXmEo7hJ5T0) | Points Input basic tutorial | [Model](https://www.shapediver.com/m/2i-pointsinputsimpleexample) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=2i-pointsinputsimpleexample) | [Download](2I-PointsInputBasicTutorial.ghx) | ★ ★ ☆ | -| **2J** | [📖](https://help.shapediver.com/doc/points-input) [🎥](https://www.youtube.com/watch?v=h-8ro_QQ0CU) | Points Input with geometry constraints | [Model](https://www.shapediver.com/m/2j-pointsinputgeometryconstraints) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=2j-pointsinputgeometryconstraints) | [Download](2J-PointsInputGeometryConstraints.ghx) | ★ ★ ☆ | -| **2K** | | Define various types of value lists with images | [Model](https://www.shapediver.com/app/m/2k-valuelistwithimages) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=2k-valuelistwithimages&redirect=0) | [Download](2K-ValueListWithImages.ghx) | ★ ★ ☆ | -| **2L** | | Create or override slider settings such as min and max values, step and marks. | [Model](https://www.shapediver.com/app/m/2l-slidersettings) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=2l-slidersettings&redirect=0) | [Download](2L-SliderSettings.ghx) | ★ ☆ ☆ | \ No newline at end of file +[inputs]: # diff --git a/src/02-Inputs/examples.json b/src/02-Inputs/examples.json new file mode 100644 index 0000000..b7f12f0 --- /dev/null +++ b/src/02-Inputs/examples.json @@ -0,0 +1,148 @@ +{ + "2A-DirectTextInput.ghx": { + "anchor": "inputs", + "slug": "2a-directtextinput-27", + "title": "Direct Text Input", + "modelLink": true, + "appLink": false, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/direct-text-input", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "2B-FileImports.ghx": { + "anchor": "inputs", + "slug": "2b-fileimports", + "title": "File Imports", + "modelLink": true, + "appLink": false, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/import-components", + "videoLink": "https://www.youtube.com/watch?v=6rZiZflGrRo", + "ghFiles": {}, + "files": {} + }, + "2C-ImportWithAttributes.ghx": { + "anchor": "inputs", + "slug": "2c-importwithattributes", + "title": "Import document properties and User Text using ShapeDiver Attributes.", + "modelLink": true, + "appLink": false, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": { + "Input": "2C-ImportWithAttributes.3dm" + }, + "files": {} + }, + "2D-SelectionInput_Basic.ghx": { + "anchor": "inputs", + "slug": "2d-selectioninput-tutorial1", + "title": "Selection Input basic tutorial", + "modelLink": false, + "appLink": true, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/selection-input", + "videoLink": "https://www.youtube.com/watch?v=m9BsPMvKPDo", + "ghFiles": {}, + "files": {} + }, + "2E-SelectionInput_Advanced.ghx": { + "anchor": "inputs", + "slug": "2e-selectioninput-tutorial2", + "title": "Use a Selection Input to influence both geometry and build a contextual App user interface", + "modelLink": false, + "appLink": true, + "rating": 2, + "docLink": "https://help.shapediver.com/doc/selection-input", + "videoLink": "https://www.youtube.com/watch?v=m9BsPMvKPDo", + "ghFiles": {}, + "files": {} + }, + "2F-GumballInput_Basic.ghx": { + "anchor": "inputs", + "slug": "2f-minimalgumballexample", + "title": "Gumball Input basic tutorial", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/gumball-input", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "2G-GumballInput_Tutorial1.ghx": { + "anchor": "inputs", + "slug": "2g-gumballinput-tutorial1", + "title": "Use a Gumball Input to transform geometry in the viewer and apply the transformations in Grasshopper", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "https://help.shapediver.com/doc/gumball-input", + "videoLink": "https://www.youtube.com/watch?v=Pu9nDFJXZC0", + "ghFiles": {}, + "files": {} + }, + "2H-Gumballinput_Tutorial2.ghx": { + "anchor": "inputs", + "slug": "2h-gumballinput-tutorial2-3", + "title": "Advanced Gumball example with dynamic transformable objects.", + "modelLink": true, + "appLink": true, + "rating": 3, + "docLink": "https://help.shapediver.com/doc/gumball-input", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "2I-PointsInputBasicTutorial.ghx": { + "anchor": "inputs", + "slug": "2i-pointsinputsimpleexample", + "title": "Points Input basic tutorial", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "https://help.shapediver.com/doc/points-input", + "videoLink": "https://www.youtube.com/watch?v=vXmEo7hJ5T0", + "ghFiles": {}, + "files": {} + }, + "2J-PointsInputGeometryConstraints.ghx": { + "anchor": "inputs", + "slug": "2j-pointsinputgeometryconstraints", + "title": "Points Input with geometry constraints", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "https://help.shapediver.com/doc/points-input", + "videoLink": "https://www.youtube.com/watch?v=h-8ro_QQ0CU", + "ghFiles": {}, + "files": {} + }, + "2K-ValueListWithImages.ghx": { + "anchor": "inputs", + "slug": "2k-valuelistwithimages", + "title": "Define various types of value lists with images", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "2L-SliderSettings.ghx": { + "anchor": "inputs", + "slug": "2l-slidersettings", + "title": "Create or override slider settings such as min and max values, step and marks.", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + } +} diff --git a/src/03-Outputs/definitions.md b/src/03-Outputs/definitions.md index 9acfc30..c65e876 100644 --- a/src/03-Outputs/definitions.md +++ b/src/03-Outputs/definitions.md @@ -1,9 +1,3 @@ # Outputs - -| Ref | Resources | Description | ShapeDiver links | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------|---------| -| **3A** | [📖](https://help.shapediver.com/doc/download-export) | Export Components basic tutorial | [Model](https://www.shapediver.com/app/m/3a-exportexamples-27) | [Download](3A-ExportComponentsBasicTutorial.ghx) | ★ ☆ ☆ | -| **3B** | [📖](https://help.shapediver.com/doc/export-attributes) | Use attributes to export document properties (names, layers, colors, user text...). | [Model](https://www.shapediver.com/app/m/3b-exportwithattributes) | [Download](3B-ExportWithAttributes.ghx) | ★ ☆ ☆ | -| **3E** | [📖](https://help.shapediver.com/doc/direct-data-output) | Direct Data Output - Explore all supported data types. | -- | [Download](3E-DataOutputTypes.ghx) | ★ ☆ ☆ | -| **3H** | [📖](https://help.shapediver.com/doc/zip-streams) | Export multiple files using the Create Stream and Zip Streams components. | [Model](https://www.shapediver.com/app/m/3h-exportziparchive) | [Download](3H-ExportZipArchive.ghx) | ★ ☆ ☆ | \ No newline at end of file +[outputs]: # diff --git a/src/03-Outputs/examples.json b/src/03-Outputs/examples.json new file mode 100644 index 0000000..851315b --- /dev/null +++ b/src/03-Outputs/examples.json @@ -0,0 +1,50 @@ +{ + "3A-ExportComponentsBasicTutorial.ghx": { + "anchor": "outputs", + "slug": "3a-exportexamples-27", + "title": "Export Components basic tutorial", + "modelLink": true, + "appLink": false, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/download-export", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "3B-ExportWithAttributes.ghx": { + "anchor": "outputs", + "slug": "3b-exportwithattributes", + "title": "Use attributes to export document properties (names, layers, colors, user text...).", + "modelLink": true, + "appLink": false, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/export-attributes", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "3E-DataOutputTypes.ghx": { + "anchor": "outputs", + "slug": "", + "title": "Direct Data Output - Explore all supported data types.", + "modelLink": false, + "appLink": false, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/direct-data-output", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "3H-ExportZipArchive.ghx": { + "anchor": "outputs", + "slug": "3h-exportziparchive", + "title": "Export multiple files using the Create Stream and Zip Streams components.", + "modelLink": true, + "appLink": false, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/zip-streams", + "videoLink": "", + "ghFiles": {}, + "files": {} + } +} diff --git a/src/04-Display/definitions.md b/src/04-Display/definitions.md index 3765793..ccde013 100644 --- a/src/04-Display/definitions.md +++ b/src/04-Display/definitions.md @@ -1,8 +1,3 @@ # Display - -| Ref | Description | ShapeDiver links | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------| -| **4A** | Display Basic tutorial - All the ways to display geometry in the ShapeDiver viewer | [Model](https://www.shapediver.com/app/m/4a-displaybasictutorial) | [Download](4A-DisplayBasicTutorial.ghx) | ★ ☆ ☆ | -| **4B** | Materials Presets | [Model](https://www.shapediver.com/app/m/4b-materialpresets) | [Download](4B-MaterialPresets.ghx) | ★ ☆ ☆ | -| **4C** | Rendering Guide: How to Make Your Models Look Great Online | [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=4c-renderingguide&redirect=0) | [START](4C-start.ghx) / [After Chapter 2](4C-after-chapter-2.ghx) / [After Chapter 3](4C-after-chapter-3.ghx) / [After Chapter 4](4C-after-chapter-4.ghx) / [FINAL](4C-RenderingGuide.ghx) | ★ ★ ☆ | \ No newline at end of file +[display]: # diff --git a/src/04-Display/examples.json b/src/04-Display/examples.json new file mode 100644 index 0000000..870abf6 --- /dev/null +++ b/src/04-Display/examples.json @@ -0,0 +1,44 @@ +{ + "4A-DisplayBasicTutorial.ghx": { + "anchor": "display", + "slug": "4a-displaybasictutorial", + "title": "Display Basic tutorial - All the ways to display geometry in the ShapeDiver viewer", + "modelLink": true, + "appLink": false, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "4B-MaterialPresets.ghx": { + "anchor": "display", + "slug": "4b-materialpresets", + "title": "Materials Presets", + "modelLink": true, + "appLink": false, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "4C-RenderingGuide.ghx": { + "anchor": "display", + "slug": "4c-renderingguide", + "title": "Rendering Guide: How to Make Your Models Look Great Online", + "modelLink": false, + "appLink": true, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": { + "START": "4C-start.ghx", + "After Chapter 2": "4C-after-chapter-2.ghx", + "After Chapter 3": "4C-after-chapter-3.ghx", + "After Chapter 4": "4C-after-chapter-4.ghx", + "FINAL": "4C-RenderingGuide.ghx" + }, + "files": {} + } +} diff --git a/src/05-Images/definitions.md b/src/05-Images/definitions.md index f27e32e..1544e73 100644 --- a/src/05-Images/definitions.md +++ b/src/05-Images/definitions.md @@ -1,10 +1,3 @@ # Images - -| Ref | Description | ShapeDiver links | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------| -| **5A** | Introduction to Grasshopper Bitmaps | [Model](https://www.shapediver.com/app/m/5a-grasshopperbitmap-8) | [Download](5A-GrasshopperBitmap.ghx) | ★ ☆ ☆ | -| **5B** | ShapeDiver Image Sampler | [Model](https://www.shapediver.com/app/m/5b-shapediverimagesampler-4) | [Download](5B-ShapeDiverImageSampler.ghx) | ★ ☆ ☆ | -| **5C** | ShapeDiver Image to Mesh | [Model](https://www.shapediver.com/app/m/5c-shapediverimagetomesh-40) | [Download](5C-ShapeDiverImageToMesh.ghx) | ★ ☆ ☆ | -| **5D** | Use the Squid plugin to generate bitmaps in Grasshopper | [Model](https://www.shapediver.com/app/m/5d-squidshapediveredition) | [Download](5D-SquidShapeDiverEdition.ghx) | ★ ★ ☆ | -| **5Dbis** | Use the Squid plugin to generate pdfs in Grasshopper | [Model](https://www.shapediver.com/app/m/5dbis-squidshapedivereditionpdf) | [Download](5Dbis-SquidShapeDiverEditionPDF.ghx) | ★ ★ ☆ | +[images]: # diff --git a/src/05-Images/examples.json b/src/05-Images/examples.json new file mode 100644 index 0000000..0110039 --- /dev/null +++ b/src/05-Images/examples.json @@ -0,0 +1,62 @@ +{ + "5A-GrasshopperBitmap.ghx": { + "anchor": "images", + "slug": "5a-grasshopperbitmap-8", + "title": "Introduction to Grasshopper Bitmaps", + "modelLink": true, + "appLink": false, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "5B-ShapeDiverImageSampler.ghx": { + "anchor": "images", + "slug": "5b-shapediverimagesampler-4", + "title": "ShapeDiver Image Sampler", + "modelLink": true, + "appLink": false, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "5C-ShapeDiverImageToMesh.ghx": { + "anchor": "images", + "slug": "5c-shapediverimagetomesh-40", + "title": "ShapeDiver Image to Mesh", + "modelLink": true, + "appLink": false, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "5D-SquidShapeDiverEdition.ghx": { + "anchor": "images", + "slug": "5d-squidshapediveredition", + "title": "Use the Squid plugin to generate bitmaps in Grasshopper", + "modelLink": true, + "appLink": false, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "5Dbis-SquidShapeDiverEditionPDF.ghx": { + "anchor": "images", + "slug": "5dbis-squidshapedivereditionpdf", + "title": "Use the Squid plugin to generate pdfs in Grasshopper", + "modelLink": true, + "appLink": false, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + } +} diff --git a/src/06-JSON/definitions.md b/src/06-JSON/definitions.md index 9273a11..56d24c2 100644 --- a/src/06-JSON/definitions.md +++ b/src/06-JSON/definitions.md @@ -1,6 +1,3 @@ # JSON - -| Ref | Description | ShapeDiver links | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------| -| **6** | Introduction to JSON object manipulation | -- | [Download](6A-JSONTutorial.ghx) | ★ ☆ ☆ | \ No newline at end of file +[json]: # diff --git a/src/06-JSON/examples.json b/src/06-JSON/examples.json new file mode 100644 index 0000000..733a9b7 --- /dev/null +++ b/src/06-JSON/examples.json @@ -0,0 +1,14 @@ +{ + "6A-JSONTutorial.ghx": { + "anchor": "json", + "slug": "", + "title": "Introduction to JSON object manipulation", + "modelLink": false, + "appLink": false, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + } +} diff --git a/src/07-Attributes/definitions.md b/src/07-Attributes/definitions.md index 277845f..d4ea9c6 100644 --- a/src/07-Attributes/definitions.md +++ b/src/07-Attributes/definitions.md @@ -1,6 +1,3 @@ # Attributes - -| Ref | Resources | Description | ShapeDiver links | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------|---------| -| **7A** | [📖](https://shapediver.atlassian.net/wiki/spaces/DOC/pages/2104721409/ShapeDiver+Output) | Basic attribute visualization setup. | [Model](https://www.shapediver.com/app/m/7a-attributevisualization-3) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=7a-attributevisualization-3) | [Download](7A-AttributeVisualization.ghx) | ★ ☆ ☆ | \ No newline at end of file +[attributes]: # diff --git a/src/07-Attributes/examples.json b/src/07-Attributes/examples.json new file mode 100644 index 0000000..71e40f2 --- /dev/null +++ b/src/07-Attributes/examples.json @@ -0,0 +1,14 @@ +{ + "7A-AttributeVisualization.ghx": { + "anchor": "attributes", + "slug": "7a-attributevisualization-3", + "title": "Basic attribute visualization setup.", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "https://shapediver.atlassian.net/wiki/spaces/DOC/pages/2104721409/ShapeDiver+Output", + "videoLink": "", + "ghFiles": {}, + "files": {} + } +} diff --git a/src/08-Plugins/definitions.md b/src/08-Plugins/definitions.md index 7e35284..6c06812 100644 --- a/src/08-Plugins/definitions.md +++ b/src/08-Plugins/definitions.md @@ -1,8 +1,3 @@ # Plugins & Integrations - -| Ref | Resources | Description | ShapeDiver links | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------|---------| -| **8A** | [📖](https://help.shapediver.com/doc/shapediver-for-speckle) [🎥](https://www.youtube.com/watch?v=Son87CXLqkM) | Publish data to a Speckle model | [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=8a-specklepublish-3) | [Download](8A-SpecklePublish.ghx) | ★ ☆ ☆ | -| **8B** | [📖](https://help.shapediver.com/doc/shapediver-for-speckle) [🎥](https://www.youtube.com/watch?v=Son87CXLqkM) | Load data from a Speckle model | [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=8b-speckleload) | [Download](8B-SpeckleLoad.ghx) | ★ ☆ ☆ | -| **8C** | [📖](https://help.shapediver.com/doc/shapediver-for-skema) | Exchange data with a Skema project | [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=8c-skemaintegration&redirect=0) | [Download](8C-SkemaIntegration.ghx) | ★ ☆ ☆ | \ No newline at end of file +[plugins]: # diff --git a/src/08-Plugins/examples.json b/src/08-Plugins/examples.json new file mode 100644 index 0000000..bbd9508 --- /dev/null +++ b/src/08-Plugins/examples.json @@ -0,0 +1,38 @@ +{ + "8A-SpecklePublish.ghx": { + "anchor": "plugins", + "slug": "8a-specklepublish-3", + "title": "Publish data to a Speckle model", + "modelLink": false, + "appLink": true, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/shapediver-for-speckle", + "videoLink": "https://www.youtube.com/watch?v=Son87CXLqkM", + "ghFiles": {}, + "files": {} + }, + "8B-SpeckleLoad.ghx": { + "anchor": "plugins", + "slug": "8b-speckleload", + "title": "Load data from a Speckle model", + "modelLink": false, + "appLink": true, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/shapediver-for-speckle", + "videoLink": "https://www.youtube.com/watch?v=Son87CXLqkM", + "ghFiles": {}, + "files": {} + }, + "8C-SkemaIntegration.ghx": { + "anchor": "plugins", + "slug": "8c-skemaintegration", + "title": "Exchange data with a Skema project", + "modelLink": false, + "appLink": true, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/shapediver-for-skema", + "videoLink": "", + "ghFiles": {}, + "files": {} + } +} diff --git a/src/11-AppBuilder/definitions.md b/src/11-AppBuilder/definitions.md index 2d57de3..c1fb4ac 100644 --- a/src/11-AppBuilder/definitions.md +++ b/src/11-AppBuilder/definitions.md @@ -1,33 +1,17 @@ # App Builder -Some examples using [App Builder](https://help.shapediver.com/doc/shapediver-app-builder). +Some examples using [App Builder](https://help.shapediver.com/doc/shapediver-app-builder). ## Full workflows -| Ref | Description | ShapeDiver links | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------| -| 11A | Build a simple user interface with controls and text widgets | [Model](https://www.shapediver.com/app/m/appbuilder-tutorial1-simpleexample) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=appbuilder-tutorial1-simpleexample) | [Download](11A-AppBuilder_Tutorial1.ghx) | ★ ☆ ☆ | -| 11B | Use parametric logic to build a contextual interface | [Model](https://www.shapediver.com/app/m/appbuilder-tutorial2-contextualui) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=appbuilder-tutorial2-contextualui) | [Download](11B-AppBuilder_Tutorial2.ghx) / [Input](11B-AppBuilder_Tutorial2_ExampleInput.3dm) | ★ ☆ ☆ | -| 11C | Link Export components to image widgets in your apps. | [Model](https://www.shapediver.com/app/m/appbuilder-tutorial3-imagewidget) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=appbuilder-tutorial3-imagewidget) | [Download](11C-AppBuilder_Tutorial3.ghx) / [Input](11B-AppBuilder_Tutorial2_ExampleInput.3dm) | ★ ★ ☆ | -| 11D | Define charts and graphs | [Model](https://www.shapediver.com/app/m/appbuilder-tutorial4-charts) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=appbuilder-tutorial4-charts) | [Download](11D-AppBuilder_Tutorial4.ghx) | ★ ★ ★ | +[workflows]: # + ## Components -| Ref | Description | ShapeDiver links | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------| -| 11E | Parameter and Export Controls | [Model](https://www.shapediver.com/app/m/11e-parameterandexportcontrols) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=11e-parameterandexportcontrols&redirect=0) | [Download](11E-ParameterAndExportControls.ghx) | ★ ☆ ☆ | -| 11F | Action Control | [Model](https://www.shapediver.com/app/m/11f-actioncontrol) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=11f-actioncontrol) | [Download](11F-ActionControl.ghx) | ★ ☆ ☆ | -| 11G | Accordion and Stack Widgets | [Model](https://www.shapediver.com/app/m/11g-accordionandstackwidgets) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=11g-accordionandstackwidgets) | [Download](11G-AccordionAndStackWidgets.ghx) | ★ ☆ ☆ | +[components]: # ## Interaction inputs in App Builder -| Ref | Resources | Description | ShapeDiver model | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------|---------| -| **2D** | [📖](https://help.shapediver.com/doc/selection-input) [🎥](https://www.youtube.com/watch?v=m9BsPMvKPDo) | Selection Input basic tutorial | [Model]() / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=2d-selectioninput-tutorial1) | [Download](../02-Inputs/2D-SelectionInput_Basic.ghx) | ★ ☆ ☆ | -| **2E** | [📖](https://help.shapediver.com/doc/selection-input) [🎥](https://www.youtube.com/watch?v=m9BsPMvKPDo) | Use a Selection Input to influence both geometry and build a contextual App user interface | [Model]() / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=2e-selectioninput-tutorial2) | [Download](../02-Inputs/2E-SelectionInput_Advanced.ghx) | ★ ★ ☆ | -| **2F** | [📖](https://help.shapediver.com/doc/gumball-input) | Gumball Input basic tutorial | [Model](https://www.shapediver.com/app/m/2f-minimalgumballexample) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=2f-minimalgumballexample) | [Download](../02-Inputs/2F-GumballInput_Basic.ghx) | ★ ☆ ☆ | -| **2G** | [📖](https://help.shapediver.com/doc/gumball-input) [🎥](https://www.youtube.com/watch?v=Pu9nDFJXZC0) | Use a Gumball Input to transform geometry in the viewer and apply the transformations in Grasshopper | [Model](https://www.shapediver.com/app/m/2g-gumballinput-tutorial1) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=2g-gumballinput-tutorial1) | [Download](../02-Inputs/2G-GumballInput_Tutorial1.ghx) | ★ ★ ☆ | -| **2H** | [📖](https://help.shapediver.com/doc/gumball-input) | Advanced Gumball example with dynamic transformable objects. | [Model](https://www.shapediver.com/app/m/2h-gumballinput-tutorial2-3) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=2h-gumballinput-tutorial2-3&redirect=0) | [Download](../02-Inputs/2H-Gumballinput_Tutorial2.ghx) | ★ ★ ★ | -| **2I** | [📖](https://help.shapediver.com/doc/points-input) [🎥](https://www.youtube.com/watch?v=vXmEo7hJ5T0) | Points Input basic tutorial | [Model](https://www.shapediver.com/m/2i-pointsinputsimpleexample) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=2i-pointsinputsimpleexample) | [Download](../02-Inputs/2I-PointsInputBasicTutorial.ghx) | ★ ★ ☆ | -| **2J** | [📖](https://help.shapediver.com/doc/points-input) [🎥](https://www.youtube.com/watch?v=h-8ro_QQ0CU) | Points Input with geometry constraints | [Model](https://www.shapediver.com/m/2j-pointsinputgeometryconstraints) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=2j-pointsinputgeometryconstraints) | [Download](../02-Inputs/2J-PointsInputGeometryConstraints.ghx) | ★ ★ ☆ | \ No newline at end of file +[interaction]: # diff --git a/src/11-AppBuilder/examples.json b/src/11-AppBuilder/examples.json new file mode 100644 index 0000000..3cdd25b --- /dev/null +++ b/src/11-AppBuilder/examples.json @@ -0,0 +1,174 @@ +{ + "11A-AppBuilder_Tutorial1.ghx": { + "anchor": "workflows", + "slug": "appbuilder-tutorial1-simpleexample", + "title": "Build a simple user interface with controls and text widgets", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "11B-AppBuilder_Tutorial2.ghx": { + "anchor": "workflows", + "slug": "appbuilder-tutorial2-contextualui", + "title": "Use parametric logic to build a contextual interface", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": { + "Input": "11B-AppBuilder_Tutorial2_ExampleInput.3dm" + } + }, + "11C-AppBuilder_Tutorial3.ghx": { + "anchor": "workflows", + "slug": "appbuilder-tutorial3-imagewidget", + "title": "Link Export components to image widgets in your apps.", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": { + "Input": "11B-AppBuilder_Tutorial2_ExampleInput.3dm" + }, + "files": {} + }, + "11D-AppBuilder_Tutorial4.ghx": { + "anchor": "workflows", + "slug": "appbuilder-tutorial4-charts", + "title": "Define charts and graphs", + "modelLink": true, + "appLink": true, + "rating": 3, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "11E-ParameterAndExportControls.ghx": { + "anchor": "components", + "slug": "11e-parameterandexportcontrols", + "title": "Parameter and Export Controls", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "11F-ActionControl.ghx": { + "anchor": "components", + "slug": "11f-actioncontrol", + "title": "Action Control", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "11G-AccordionAndStackWidgets.ghx": { + "anchor": "components", + "slug": "11g-accordionandstackwidgets", + "title": "Accordion and Stack Widgets", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "../02-Inputs/2D-SelectionInput_Basic.ghx": { + "anchor": "interaction", + "slug": "2d-selectioninput-tutorial1", + "title": "Selection Input basic tutorial", + "modelLink": false, + "appLink": true, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/selection-input", + "videoLink": "https://www.youtube.com/watch?v=m9BsPMvKPDo", + "ghFiles": {}, + "files": {} + }, + "../02-Inputs/2E-SelectionInput_Advanced.ghx": { + "anchor": "interaction", + "slug": "2e-selectioninput-tutorial2", + "title": "Use a Selection Input to influence both geometry and build a contextual App user interface", + "modelLink": false, + "appLink": true, + "rating": 2, + "docLink": "https://help.shapediver.com/doc/selection-input", + "videoLink": "https://www.youtube.com/watch?v=m9BsPMvKPDo", + "ghFiles": {}, + "files": {} + }, + "../02-Inputs/2F-GumballInput_Basic.ghx": { + "anchor": "interaction", + "slug": "2f-minimalgumballexample", + "title": "Gumball Input basic tutorial", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "https://help.shapediver.com/doc/gumball-input", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "../02-Inputs/2G-GumballInput_Tutorial1.ghx": { + "anchor": "interaction", + "slug": "2g-gumballinput-tutorial1", + "title": "Use a Gumball Input to transform geometry in the viewer and apply the transformations in Grasshopper", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "https://help.shapediver.com/doc/gumball-input", + "videoLink": "https://www.youtube.com/watch?v=Pu9nDFJXZC0", + "ghFiles": {}, + "files": {} + }, + "../02-Inputs/2H-Gumballinput_Tutorial2.ghx": { + "anchor": "interaction", + "slug": "2h-gumballinput-tutorial2-3", + "title": "Advanced Gumball example with dynamic transformable objects.", + "modelLink": true, + "appLink": true, + "rating": 3, + "docLink": "https://help.shapediver.com/doc/gumball-input", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "../02-Inputs/2I-PointsInputBasicTutorial.ghx": { + "anchor": "interaction", + "slug": "2i-pointsinputsimpleexample", + "title": "Points Input basic tutorial", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "https://help.shapediver.com/doc/points-input", + "videoLink": "https://www.youtube.com/watch?v=vXmEo7hJ5T0", + "ghFiles": {}, + "files": {} + }, + "../02-Inputs/2J-PointsInputGeometryConstraints.ghx": { + "anchor": "interaction", + "slug": "2j-pointsinputgeometryconstraints", + "title": "Points Input with geometry constraints", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "https://help.shapediver.com/doc/points-input", + "videoLink": "https://www.youtube.com/watch?v=h-8ro_QQ0CU", + "ghFiles": {}, + "files": {} + } +} diff --git a/src/12-DesktopClients/definitions.md b/src/12-DesktopClients/definitions.md index ee47734..fa68ef7 100644 --- a/src/12-DesktopClients/definitions.md +++ b/src/12-DesktopClients/definitions.md @@ -2,6 +2,4 @@ ShapeDiver Apps can interact with so-called "Desktop Clients", allowing to send and receive geometry from local software environments in real-time. So far, Rhino and Adobe Illustrator are supported as desktlop clients, as well as some custom integratiosn with industry-specific software. Get in touch if you would like us to support other desktop clients. -| Ref | Resources | Description | ShapeDiver links | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------|---------| -| **2A** | _Soon_ | Introduction to Desktop Clients | [Model](https://www.shapediver.com/app/m/12a-desktopclientstutorial) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=12a-desktopclientstutorial) | [Download](12A-DesktopClientsTutorial.ghx) | ★ ☆ ☆ | \ No newline at end of file +[clients]: # diff --git a/src/12-DesktopClients/examples.json b/src/12-DesktopClients/examples.json new file mode 100644 index 0000000..01fe714 --- /dev/null +++ b/src/12-DesktopClients/examples.json @@ -0,0 +1,15 @@ + +{ + "12A-DesktopClientsTutorial.ghx": { + "anchor": "clients", + "slug": "12a-desktopclientstutorial", + "title": "Introduction to Desktop Clients", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + } +} diff --git a/src/BETA/definitions.md b/src/BETA/definitions.md index 77c7e8b..6e7a59b 100644 --- a/src/BETA/definitions.md +++ b/src/BETA/definitions.md @@ -6,18 +6,4 @@ Note that the final components and component flow might differ from the current > Some of the examples below make use of the JSwan Grasshopper plugin (version 1.2.0). -| Ref | Description | ShapeDiver links | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------| -| BETA | Dynamic Sliders Tutorial | [Model](https://www.shapediver.com/app/m/beta-dynamicsliderstutorial) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-dynamicsliderstutorial&redirect=0) | [Download](BETA-DynamicSlidersTutorial.ghx) | ★ ★ ☆ | -| BETA | Dynamic Value Lists Tutorial | [Model](https://www.shapediver.com/app/m/beta-dynamicvalueliststutorial) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-dynamicvalueliststutorial&redirect=0) | [Download](BETA-DynamicValueListsTutorial.ghx) | ★ ★ ☆ | -| BETA | Define searchable value lists | [Model](https://www.shapediver.com/app/m/beta-searchablevaluelist-1) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-searchablevaluelist-1&redirect=0) | [Download](BETA-SearchableValueList.ghx) | ★ ☆ ☆ | -| BETA | Define multiline text inputs | [Model](https://www.shapediver.com/app/m/beta-multilinetextinput) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-multilinetextinput&redirect=0) | [Download](BETA-MultilineTextInput.ghx) | ★ ☆ ☆ | -| BETA | Control instances of other ShapeDiver models | [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-instancestutorial1-controller&redirect=0) | [Download](BETA-InstancesTutorial1-Controller.ghx) / [Parametric Box](BETA-ParametricBoxInstance.ghx) | ★ ★ ☆ | -| BETA | Create anchor containers attached to 3d locations in the scene | [Model](https://www.shapediver.com/app/m/beta-anchor3d) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-anchor3d&redirect=0) | [Download](BETA-Anchor3dSimple.ghx) | ★ ★ ☆ | -| BETA | Create anchor containers attached to 2d locations on the screen | [Model](https://www.shapediver.com/app/m/beta-anchor2d) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-anchor2d&redirect=0) | [Download](BETA-Anchor2dTutorial.ghx) | ★ ★ ☆ | -| BETA | Play a sound| [Model](https://www.shapediver.com/app/m/beta-soundaction) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-soundaction&redirect=0) | [Download](BETA-SoundAction.ghx) | ★ ☆ ☆ | -| BETA | Loop the value of a model state back to Grasshopper and include it in an export email | [Model](https://www.shapediver.com/app/m/beta-exportwithstate) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-exportwithstate&redirect=0) | [Download](BETA-ExportWithState.ghx) | ★ ☆ ☆ | -| BETA | Send a screenshot of the App back to Grasshopper and include it in an export email | [Model](https://www.shapediver.com/app/m/beta-exportwithscreenshot) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-exportwithscreenshot&redirect=0) | [Download](BETA-ExportWithScreenshot.ghx) | ★ ☆ ☆ | -| BETA | Create form inputs linked to a file export | [Model](https://www.shapediver.com/app/m/beta-formwidget) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-formwidget&redirect=0) | [Download](BETA-FormWidget.ghx) | ★ ☆ ☆ | -| BETA | Trigger camera movements and animations using a Camera Action. | [Model](https://www.shapediver.com/app/m/beta-cameraaction) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-cameraaction&redirect=0) | [Download](BETA-CameraAction.ghx) | ★ ★ ☆ | -| BETA | Saved States Widget | [Model](https://www.shapediver.com/app/m/beta-savedstateswidget) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=beta-savedstateswidget&redirect=0) | [Download](BETA-SavedStatesWidget.ghx) | ★ ☆ ☆ | \ No newline at end of file +[beta]: # diff --git a/src/BETA/examples.json b/src/BETA/examples.json new file mode 100644 index 0000000..3fa0946 --- /dev/null +++ b/src/BETA/examples.json @@ -0,0 +1,160 @@ +{ + "BETA-DynamicSlidersTutorial.ghx": { + "anchor": "beta", + "slug": "beta-dynamicsliderstutorial", + "title": "Dynamic Sliders Tutorial", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "BETA-DynamicValueListsTutorial.ghx": { + "anchor": "beta", + "slug": "beta-dynamicvalueliststutorial", + "title": "Dynamic Value Lists Tutorial", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "BETA-SearchableValueList.ghx": { + "anchor": "beta", + "slug": "beta-searchablevaluelist-1", + "title": "Define searchable value lists", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "BETA-MultilineTextInput.ghx": { + "anchor": "beta", + "slug": "beta-multilinetextinput", + "title": "Define multiline text inputs", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "BETA-InstancesTutorial1-Controller.ghx": { + "anchor": "beta", + "slug": "beta-instancestutorial1-controller", + "title": "Control instances of other ShapeDiver models", + "modelLink": false, + "appLink": true, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": { + "Parametric Box": "BETA-ParametricBoxInstance.ghx" + }, + "files": {} + }, + "BETA-Anchor3dSimple.ghx": { + "anchor": "beta", + "slug": "beta-anchor3d", + "title": "Create anchor containers attached to 3d locations in the scene", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "BETA-Anchor2dTutorial.ghx": { + "anchor": "beta", + "slug": "beta-anchor2d", + "title": "Create anchor containers attached to 2d locations on the screen", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "BETA-SoundAction.ghx": { + "anchor": "beta", + "slug": "beta-soundaction", + "title": "Play a sound", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "BETA-ExportWithState.ghx": { + "anchor": "beta", + "slug": "beta-exportwithstate", + "title": "Loop the value of a model state back to Grasshopper and include it in an export email", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "BETA-ExportWithScreenshot.ghx": { + "anchor": "beta", + "slug": "beta-exportwithscreenshot", + "title": "Send a screenshot of the App back to Grasshopper and include it in an export email", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "BETA-FormWidget.ghx": { + "anchor": "beta", + "slug": "beta-formwidget", + "title": "Create form inputs linked to a file export", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "BETA-CameraAction.ghx": { + "anchor": "beta", + "slug": "beta-cameraaction", + "title": "Trigger camera movements and animations using a Camera Action.", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "BETA-SavedStatesWidget.ghx": { + "anchor": "beta", + "slug": "beta-savedstateswidget", + "title": "Saved States Widget", + "modelLink": true, + "appLink": true, + "rating": 1, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + } +} diff --git a/src/Webinars/Webinar_2410_A_BarcelonaNeighbourhood.ghx b/src/Webinars/2410A-Webinar_BarcelonaNeighbourhood.ghx similarity index 100% rename from src/Webinars/Webinar_2410_A_BarcelonaNeighbourhood.ghx rename to src/Webinars/2410A-Webinar_BarcelonaNeighbourhood.ghx diff --git a/src/Webinars/Webinar_2410_B_ModularCabinets.ghx b/src/Webinars/2410B-Webinar_ModularCabinets.ghx similarity index 100% rename from src/Webinars/Webinar_2410_B_ModularCabinets.ghx rename to src/Webinars/2410B-Webinar_ModularCabinets.ghx diff --git a/src/Webinars/Webinar_2503_A_SpecklePavillion.ghx b/src/Webinars/2503A-Webinar_SpecklePavillion.ghx similarity index 100% rename from src/Webinars/Webinar_2503_A_SpecklePavillion.ghx rename to src/Webinars/2503A-Webinar_SpecklePavillion.ghx diff --git a/src/Webinars/Webinar_2503_B_ShopifyWorkflow.ghx b/src/Webinars/2503B-Webinar_ShopifyWorkflow.ghx similarity index 100% rename from src/Webinars/Webinar_2503_B_ShopifyWorkflow.ghx rename to src/Webinars/2503B-Webinar_ShopifyWorkflow.ghx diff --git a/src/Webinars/Webinar_2605_NewAppBuilderFeatures.ghx b/src/Webinars/2605-Webinar_NewAppBuilderFeatures.ghx similarity index 100% rename from src/Webinars/Webinar_2605_NewAppBuilderFeatures.ghx rename to src/Webinars/2605-Webinar_NewAppBuilderFeatures.ghx diff --git a/src/Webinars/definitions.md b/src/Webinars/definitions.md index 74dcf9a..375ebf3 100644 --- a/src/Webinars/definitions.md +++ b/src/Webinars/definitions.md @@ -1,13 +1,3 @@ # Webinar examples - -| Tutorial | Description | ShapeDiver model | Grasshopper definition | Level     | -|---------|---------|---------|---------|---------| -| **2410A** | [🎥 Fall '24 Update](https://www.youtube.com/watch?v=Rnu86ss0qT4): Barcelona Neighbourhood Analysis (Interactions components and Dynamic Parameters) | [Model](https://www.shapediver.com/app/m/webinar-2410-a-barcelonaneighbourhood) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=webinar-2410-a-barcelonaneighbourhood) | [Download](Webinar_2410_A_BarcelonaNeighbourhood.ghx) | ★ ★ ☆ | -| **2410B** | [🎥 Fall '24 Update](https://www.youtube.com/watch?v=Rnu86ss0qT4): Modular cabinets configurator (Interactions components and Dynamic Parameters) | [Model](https://www.shapediver.com/app/m/webinar-2410-b-modularcabinets8) / [App](https://appbuilder.shapediver.com/v1/main/latest/?slug=webinar-2410-b-modularcabinets) | [Download](Webinar_2410_B_ModularCabinets.ghx) | ★ ★ ★ | -| **2503A** | [🎥 Spring '25 Update](https://shapediver.com/blog/shapediver-spring-25-update-live-webinar): Speckle pavillion | [Model](https://www.shapediver.com/app/m/2503a-speckle-pavillion) / [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=2503a-speckle-pavillion&redirect=0) | [Download](Webinar_2503_A_SpecklePavillion.ghx) | ★ ★ ☆ | -| **2503B** | [🎥 Spring '25 Update](https://www.youtube.com/watch?v=mAy7dLjXbGA): Shopify workflow | [Model](https://www.shapediver.com/app/m/2503b-shopifyworkflow) / [App](https://appbuilder.shapediver.com/v1/ijewel3d/latest/?slug=2503b-shopifyworkflow&g=https://cdn.shopify.com/s/files/1/0897/7802/8923/files/example-agent-ring-theme.json&webgiScene=https://cdn.shopify.com/s/files/1/0585/2721/9918/files/3dConfiguratorScene.vjson) | [Download](Webinar_2503_B_ShopifyWorkflow.ghx) | ★ ★ ☆ | -| **2605** | [🎥 Food4Rhino '26 Update](https://www.youtube.com/watch?v=A5qYzP4X_W4) | [App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=webinar-2605-newappbuilderfeatures&redirect=0) | [Download](Webinar_2605_NewAppBuilderFeatures) | ★ ★ ☆ | - - - +[webinar]: # diff --git a/src/Webinars/examples.json b/src/Webinars/examples.json new file mode 100644 index 0000000..0bb6056 --- /dev/null +++ b/src/Webinars/examples.json @@ -0,0 +1,63 @@ + +{ + "2410A-Webinar_BarcelonaNeighbourhood.ghx": { + "anchor": "webinar", + "slug": "webinar-2410-a-barcelonaneighbourhood", + "title": "[🎥 Fall '24 Update](https://www.youtube.com/watch?v=Rnu86ss0qT4): Barcelona Neighborhood Analysis (Interactions components and Dynamic Parameters)", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "2410B-Webinar_ModularCabinets.ghx": { + "anchor": "webinar", + "slug": "webinar-2410-b-modularcabinets", + "title": "[🎥 Fall '24 Update](https://www.youtube.com/watch?v=Rnu86ss0qT4): Modular cabinets configurator (Interactions components and Dynamic Parameters)", + "modelLink": true, + "appLink": true, + "rating": 3, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "2503A-Webinar_SpecklePavillion.ghx": { + "anchor": "webinar", + "slug": "2503a-speckle-pavillion", + "title": "[🎥 Spring '25 Update](https://shapediver.com/blog/shapediver-spring-25-update-live-webinar): Speckle pavillion", + "modelLink": true, + "appLink": true, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "2503B-Webinar_ShopifyWorkflow.ghx": { + "anchor": "webinar", + "slug": "2503b-shopifyworkflow", + "title": "[🎥 Spring '25 Update](https://www.youtube.com/watch?v=mAy7dLjXbGA): Shopify workflow", + "modelLink": true, + "appLink": "https://appbuilder.shapediver.com/v1/ijewel3d/latest/?slug=2503b-shopifyworkflow&g=https://cdn.shopify.com/s/files/1/0897/7802/8923/files/example-agent-ring-theme.json&webgiScene=https://cdn.shopify.com/s/files/1/0585/2721/9918/files/3dConfiguratorScene.vjson", + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + }, + "2605-Webinar_NewAppBuilderFeatures.ghx": { + "anchor": "webinar", + "slug": "webinar-2605-newappbuilderfeatures", + "title": "[🎥 Food4Rhino '26 Update](https://www.youtube.com/watch?v=A5qYzP4X_W4)", + "modelLink": false, + "appLink": true, + "rating": 2, + "docLink": "", + "videoLink": "", + "ghFiles": {}, + "files": {} + } +} diff --git a/tools/docs-cli/package.json b/tools/docs-cli/package.json index 74eec8f..608e19b 100644 --- a/tools/docs-cli/package.json +++ b/tools/docs-cli/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "private": true, "scripts": { + "create-tables": "tsx src/createTables.ts", "validate": "tsx src/validate.ts" }, "dependencies": { diff --git a/tools/docs-cli/src/createTables.ts b/tools/docs-cli/src/createTables.ts new file mode 100644 index 0000000..b4d770c --- /dev/null +++ b/tools/docs-cli/src/createTables.ts @@ -0,0 +1,147 @@ +import fs from 'fs'; +import path from 'path'; +import { + Examples, + FILE_NAME_DEFINITIONS, + FILE_NAME_EXAMPLES, + MDBOOK_ANCHOR_REGEX, + SCHEMA_EXAMPLES, +} from './utils'; + +(function () { + const mdbookPath = process.argv[2]; + if (!fs.existsSync(mdbookPath)) { + console.error(`Path to mdbook does not exist: ${mdbookPath}`); + process.exit(1); + } + + // Find all folders one level deep in mdbook directory that contain an example file + const foldersWithExampleFile = fs + .readdirSync(mdbookPath, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .filter((entry) => fs.existsSync(path.join(mdbookPath, entry.name, FILE_NAME_EXAMPLES))); + + if (foldersWithExampleFile.length === 0) { + console.warn('No example files found in mdbook directory - nothing to do.'); + process.exit(0); + } + + for (const folderWithExampleFile of foldersWithExampleFile) { + const dir = path.join(mdbookPath, folderWithExampleFile.name), + tables: { [anchor: string]: Examples[] } = {}; + + console.log('Processing folder:', dir); + + try { + // Parse and validate examples JSON file + const examplesFile = path.join(dir, FILE_NAME_EXAMPLES), + fileContent = fs.readFileSync(examplesFile, 'utf-8'), + jsonData = JSON.parse(fileContent), + examplesData = SCHEMA_EXAMPLES.parse(jsonData); + + // Parse description markdown file + const markdownFile = path.join(dir, FILE_NAME_DEFINITIONS), + markdownData = fs.readFileSync(markdownFile, 'utf-8'); + + // Group examples by their anchor property + for (const [ghxName, example] of Object.entries(examplesData)) { + const { anchor } = example; + if (!tables[anchor]) tables[anchor] = []; + tables[anchor].push({ [ghxName]: example }); + } + + // Validate anchor count matches + const tableEntries = Object.entries(tables); + const matches = [...markdownData.matchAll(MDBOOK_ANCHOR_REGEX)]; + if (matches.length !== tableEntries.length) { + throw new Error(`Number of anchors in markdown file (${matches.length}) does not +match number of unique anchors in examples JSON (${tableEntries.length}).`); + } + + // Replace each anchor line with its generated table + const updatedMarkdown = markdownData.replace(MDBOOK_ANCHOR_REGEX, (_, anchor) => + createTable(tables[anchor] ?? []) + ); + + fs.writeFileSync(path.join(dir, FILE_NAME_DEFINITIONS), updatedMarkdown, 'utf-8'); + } catch (e) { + console.error('ERROR:', e); + process.exit(1); + } + } +})(); + +/** Converts a numeric rating (1-3) into a string of star characters. */ +function ratingToStars(rating: number): string { + return Array.from({ length: 3 }, (_, i) => (i < rating ? '★' : '☆')).join(' '); +} + +/** + * Creates a markdown table from the given examples, including conditional resources column if any + * example has links. + * @param examples The examples to include in the table, grouped by their anchor. + */ +function createTable(examples: Examples[]): string { + const hasResources = examples.some((example) => + Object.values(example).some((e) => e.docLink || e.videoLink) + ); + const hasFiles = examples.some((example) => + Object.values(example).some((e) => e.files && Object.keys(e.files).length > 0) + ); + + const columns = [ + 'Ref', + ...(hasResources ? ['Resources'] : []), + 'Description', + 'ShapeDiver links', + 'Grasshopper definition', + ...(hasFiles ? ['Files'] : []), + 'Level    ', + ]; + + const header = `| ${columns.join(' | ')} |\n| ${columns.map(() => '---').join(' | ')} |\n`; + + const rows = examples.map((example) => { + const [[ghxName, data]] = Object.entries(example); + const ref = `**${ghxName.split('/').at(-1)!.split('-')[0]}**`; + const resourceCell = hasResources + ? [ + data.docLink ? `[📖](${data.docLink})` : '', + data.videoLink ? `[🎥](${data.videoLink})` : '', + ] + .filter(Boolean) + .join(' ') + : null; + const shapeDiverLinks = [ + data.modelLink ? `[Model](https://www.shapediver.com/app/m/${data.slug})` : '', + typeof data.appLink === 'string' + ? `[App](${data.appLink})` + : data.appLink + ? `[App](https://www.shapediver.com/app/builder/v1/main/latest/?slug=${data.slug}&redirect=0)` + : '', + ] + .filter(Boolean) + .join(' / '); + const ghDefinitionCell = [ + `[Download](${ghxName})`, + ...Object.entries(data.ghFiles).map(([name, path]) => `[${name}](${path})`), + ].join(' / '); + const filesCell = Object.entries(data.files) + .map(([name, path]) => `[${name}](${path})`) + .join(' / '); + + const cells = [ + ref, + ...(resourceCell !== null ? [resourceCell] : []), + data.title, + shapeDiverLinks, + ghDefinitionCell, + ...(hasFiles ? [filesCell] : []), + ratingToStars(data.rating), + ]; + + return `| ${cells.join(' | ')} |`; + }); + + return header + rows.join('\n') + '\n'; +} diff --git a/tools/docs-cli/src/utils.ts b/tools/docs-cli/src/utils.ts index 18e6d41..1e4c99f 100644 --- a/tools/docs-cli/src/utils.ts +++ b/tools/docs-cli/src/utils.ts @@ -4,18 +4,63 @@ import { z } from 'zod'; /** The name of the JSON file holding information about ShapeDiver models used as examples. */ export const FILE_NAME_EXAMPLES = 'examples.json'; +/** The name of the Markdown file holding definitions for ShapeDiver models used as examples. */ +export const FILE_NAME_DEFINITIONS = 'definitions.md'; + /** The schema of the JSON file holding information about ShapeDiver models used as examples. */ export const SCHEMA_EXAMPLES = z.record( z.string(), z.object({ - anchor: z.string(), + /** + * The name of the markdown comment that acts as a placeholder for a single table. + * Format: + * `[anchor]: #`, where "anchor" is the name of the markdown comment. + * The markdown comment must be unique across all example definitions. + */ + anchor: z.string().min(1), + + /** The slug of the ShapeDiver model. */ slug: z.string(), - title: z.string(), + + /** The title of the ShapeDiver model. */ + title: z.string().min(1), + + /** + * Indicates whether the model link should be included. + * When `true`, the model link is generated from the slug. + */ modelLink: z.boolean(), - appLink: z.boolean(), + + /** + * Indicates whether the ShapeDiver application link should be included. When `true`, the + * AppBuilder link is generated from the slug. Alternatively, a string value can be provided + * to directly specify the app link. + */ + appLink: z.union([z.boolean(), z.string().min(1)]), + + /** The difficulty rating of the example. */ rating: z.number().min(1).max(3).int(), + + /** The ShapeDiver documentation link for the example. */ docLink: z.string(), + + /** The video link for the example. */ videoLink: z.string(), - files: z.array(z.string()), + + /** + * List of auxiliary Grasshopper files that are shown next to the model's GH file's download + * link. Each key is a file description and each value is a file name. + */ + ghFiles: z.record(z.string(), z.string()), + + /** + * List of additional non-Grasshopper files, where each key is a file description and each + * value is a file name. + */ + files: z.record(z.string(), z.string()), }) ); +export type Examples = z.infer; + +/** A regular expression to match markdown anchors in the format of `[anchor]: #`. */ +export const MDBOOK_ANCHOR_REGEX = new RegExp(/^\[([^\]]+)\]: #$/, 'gm'); From 295043caa307505e30ae98040d6a24d33bba3acf Mon Sep 17 00:00:00 2001 From: Matthias Reisacher Date: Mon, 15 Jun 2026 17:40:58 +0200 Subject: [PATCH 3/5] Add agents file --- AGENTS.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b32f85c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,76 @@ +# AGENTS.md + +## Repository purpose + +This repository contains example Grasshopper models and their documentation for ShapeDiver. Documentation is built with mdBook from the `src/` directory. + +## Important paths + +- `src/**/examples.json` — source of truth for example metadata +- `src/**/definitions.md` — chapter documentation with human-written prose and generated tables +- `tools/docs-cli/src/utils.ts` — defines `SCHEMA_EXAMPLES` +- `tools/docs-cli/src/validate.ts` — validation logic +- `tools/docs-cli/src/createTables.ts` — markdown table generation +- `book.toml` — mdBook configuration + +## Source of truth + +Treat these as the canonical sources: + +- `src/**/examples.json` +- human-written prose in `src/**/definitions.md` +- existing model files provided by humans + +Treat generated markdown tables in `src/**/definitions.md` as derived output. + +## Hard rules + +- Never manually edit generated markdown tables in `src/**/definitions.md`. +- If table content must change, update `src/**/examples.json` instead. +- Never create, modify, or rewrite `.ghx`, `.gh`, or `.3dm` files. +- Do not rename, move, or delete large groups of examples or docs unless the user explicitly asks. +- Avoid broad structural reorganizations of chapter folders or documentation layout unless explicitly asked. + +## Editing guidance + +- You may add or edit explanatory prose in `src/**/definitions.md`. +- Preserve mdBook structure and existing chapter organization unless the user asks for restructuring. +- AI may help develop repository code, including the docs CLI and related automation. + +## Adding or updating examples + +Preferred workflow: + +1. Use the existing human-provided model artifact in the appropriate chapter folder. +2. Add or update the matching entry in that chapter's `src/**/examples.json`. +3. Add explanatory prose to `definitions.md` only when needed. +4. Do not manually regenerate or rewrite markdown tables unless the user explicitly asks for that local workflow. + +## Naming convention + +Example files follow this pattern: + +`{chapter number}{alphabetical identifier starting from A}-{description}.{extension}` + +Example: + +`11A-AppBuilder_Tutorial1.ghx` + +## Validation + +After changing any `src/**/examples.json`, run: + +```bash +npm run docs:validate +``` + +This validation is required, but it does not check everything. If your change affects behavior or content not fully covered by the validation script, explicitly warn the user about the remaining unchecked risk. + +## Useful commands + +```bash +npm run docs:validate +mdbook serve . +``` + +Use `npm run docs:validate` for metadata validation and `mdbook serve .` for local documentation preview. From 6dd2032378500b6a4feec5a2ced5f70ef18b1ab1 Mon Sep 17 00:00:00 2001 From: Matthias Reisacher Date: Thu, 18 Jun 2026 17:46:03 +0200 Subject: [PATCH 4/5] Strengthen validation --- src/01-Basics/examples.json | 8 +-- src/02-Inputs/examples.json | 42 ++++++++-------- src/03-Outputs/examples.json | 8 +-- src/04-Display/examples.json | 18 +++---- src/05-Images/examples.json | 20 ++++---- src/06-JSON/examples.json | 4 +- src/07-Attributes/examples.json | 4 +- src/08-Plugins/examples.json | 8 +-- src/11-AppBuilder/examples.json | 60 +++++++++++----------- src/12-DesktopClients/examples.json | 7 ++- src/BETA/examples.json | 78 ++++++++++++++--------------- src/Webinars/examples.json | 31 ++++++------ tools/docs-cli/src/createTables.ts | 10 ++-- tools/docs-cli/src/utils.ts | 56 +++++++++++++++++---- tools/docs-cli/src/validate.ts | 68 +++++++++++++++++++------ 15 files changed, 247 insertions(+), 175 deletions(-) diff --git a/src/01-Basics/examples.json b/src/01-Basics/examples.json index 47dd498..68f6f5b 100644 --- a/src/01-Basics/examples.json +++ b/src/01-Basics/examples.json @@ -6,8 +6,8 @@ "modelLink": true, "appLink": false, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, "files": {} }, @@ -18,8 +18,8 @@ "modelLink": true, "appLink": false, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, "files": {} } diff --git a/src/02-Inputs/examples.json b/src/02-Inputs/examples.json index b7f12f0..d3174df 100644 --- a/src/02-Inputs/examples.json +++ b/src/02-Inputs/examples.json @@ -7,9 +7,9 @@ "appLink": false, "rating": 1, "docLink": "https://help.shapediver.com/doc/direct-text-input", - "videoLink": "", + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "2B-FileImports.ghx": { "anchor": "inputs", @@ -21,7 +21,7 @@ "docLink": "https://help.shapediver.com/doc/import-components", "videoLink": "https://www.youtube.com/watch?v=6rZiZflGrRo", "ghFiles": {}, - "files": {} + "files": {} }, "2C-ImportWithAttributes.ghx": { "anchor": "inputs", @@ -30,12 +30,12 @@ "modelLink": true, "appLink": false, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": { "Input": "2C-ImportWithAttributes.3dm" }, - "files": {} + "files": {} }, "2D-SelectionInput_Basic.ghx": { "anchor": "inputs", @@ -47,7 +47,7 @@ "docLink": "https://help.shapediver.com/doc/selection-input", "videoLink": "https://www.youtube.com/watch?v=m9BsPMvKPDo", "ghFiles": {}, - "files": {} + "files": {} }, "2E-SelectionInput_Advanced.ghx": { "anchor": "inputs", @@ -59,7 +59,7 @@ "docLink": "https://help.shapediver.com/doc/selection-input", "videoLink": "https://www.youtube.com/watch?v=m9BsPMvKPDo", "ghFiles": {}, - "files": {} + "files": {} }, "2F-GumballInput_Basic.ghx": { "anchor": "inputs", @@ -69,9 +69,9 @@ "appLink": true, "rating": 1, "docLink": "https://help.shapediver.com/doc/gumball-input", - "videoLink": "", + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "2G-GumballInput_Tutorial1.ghx": { "anchor": "inputs", @@ -83,7 +83,7 @@ "docLink": "https://help.shapediver.com/doc/gumball-input", "videoLink": "https://www.youtube.com/watch?v=Pu9nDFJXZC0", "ghFiles": {}, - "files": {} + "files": {} }, "2H-Gumballinput_Tutorial2.ghx": { "anchor": "inputs", @@ -93,9 +93,9 @@ "appLink": true, "rating": 3, "docLink": "https://help.shapediver.com/doc/gumball-input", - "videoLink": "", + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "2I-PointsInputBasicTutorial.ghx": { "anchor": "inputs", @@ -107,7 +107,7 @@ "docLink": "https://help.shapediver.com/doc/points-input", "videoLink": "https://www.youtube.com/watch?v=vXmEo7hJ5T0", "ghFiles": {}, - "files": {} + "files": {} }, "2J-PointsInputGeometryConstraints.ghx": { "anchor": "inputs", @@ -119,7 +119,7 @@ "docLink": "https://help.shapediver.com/doc/points-input", "videoLink": "https://www.youtube.com/watch?v=h-8ro_QQ0CU", "ghFiles": {}, - "files": {} + "files": {} }, "2K-ValueListWithImages.ghx": { "anchor": "inputs", @@ -128,10 +128,10 @@ "modelLink": true, "appLink": true, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "2L-SliderSettings.ghx": { "anchor": "inputs", @@ -140,9 +140,9 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} } } diff --git a/src/03-Outputs/examples.json b/src/03-Outputs/examples.json index 851315b..f6c4215 100644 --- a/src/03-Outputs/examples.json +++ b/src/03-Outputs/examples.json @@ -7,7 +7,7 @@ "appLink": false, "rating": 1, "docLink": "https://help.shapediver.com/doc/download-export", - "videoLink": "", + "videoLink": false, "ghFiles": {}, "files": {} }, @@ -19,7 +19,7 @@ "appLink": false, "rating": 1, "docLink": "https://help.shapediver.com/doc/export-attributes", - "videoLink": "", + "videoLink": false, "ghFiles": {}, "files": {} }, @@ -31,7 +31,7 @@ "appLink": false, "rating": 1, "docLink": "https://help.shapediver.com/doc/direct-data-output", - "videoLink": "", + "videoLink": false, "ghFiles": {}, "files": {} }, @@ -43,7 +43,7 @@ "appLink": false, "rating": 1, "docLink": "https://help.shapediver.com/doc/zip-streams", - "videoLink": "", + "videoLink": false, "ghFiles": {}, "files": {} } diff --git a/src/04-Display/examples.json b/src/04-Display/examples.json index 870abf6..de33e5e 100644 --- a/src/04-Display/examples.json +++ b/src/04-Display/examples.json @@ -6,10 +6,10 @@ "modelLink": true, "appLink": false, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "4B-MaterialPresets.ghx": { "anchor": "display", @@ -18,10 +18,10 @@ "modelLink": true, "appLink": false, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "4C-RenderingGuide.ghx": { "anchor": "display", @@ -30,8 +30,8 @@ "modelLink": false, "appLink": true, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": { "START": "4C-start.ghx", "After Chapter 2": "4C-after-chapter-2.ghx", @@ -39,6 +39,6 @@ "After Chapter 4": "4C-after-chapter-4.ghx", "FINAL": "4C-RenderingGuide.ghx" }, - "files": {} + "files": {} } } diff --git a/src/05-Images/examples.json b/src/05-Images/examples.json index 0110039..bbbf695 100644 --- a/src/05-Images/examples.json +++ b/src/05-Images/examples.json @@ -6,8 +6,8 @@ "modelLink": true, "appLink": false, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, "files": {} }, @@ -18,8 +18,8 @@ "modelLink": true, "appLink": false, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, "files": {} }, @@ -30,8 +30,8 @@ "modelLink": true, "appLink": false, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, "files": {} }, @@ -42,8 +42,8 @@ "modelLink": true, "appLink": false, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, "files": {} }, @@ -54,8 +54,8 @@ "modelLink": true, "appLink": false, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, "files": {} } diff --git a/src/06-JSON/examples.json b/src/06-JSON/examples.json index 733a9b7..a13803f 100644 --- a/src/06-JSON/examples.json +++ b/src/06-JSON/examples.json @@ -6,8 +6,8 @@ "modelLink": false, "appLink": false, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, "files": {} } diff --git a/src/07-Attributes/examples.json b/src/07-Attributes/examples.json index 71e40f2..193415d 100644 --- a/src/07-Attributes/examples.json +++ b/src/07-Attributes/examples.json @@ -7,8 +7,8 @@ "appLink": true, "rating": 1, "docLink": "https://shapediver.atlassian.net/wiki/spaces/DOC/pages/2104721409/ShapeDiver+Output", - "videoLink": "", + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} } } diff --git a/src/08-Plugins/examples.json b/src/08-Plugins/examples.json index bbd9508..329ed8e 100644 --- a/src/08-Plugins/examples.json +++ b/src/08-Plugins/examples.json @@ -9,7 +9,7 @@ "docLink": "https://help.shapediver.com/doc/shapediver-for-speckle", "videoLink": "https://www.youtube.com/watch?v=Son87CXLqkM", "ghFiles": {}, - "files": {} + "files": {} }, "8B-SpeckleLoad.ghx": { "anchor": "plugins", @@ -21,7 +21,7 @@ "docLink": "https://help.shapediver.com/doc/shapediver-for-speckle", "videoLink": "https://www.youtube.com/watch?v=Son87CXLqkM", "ghFiles": {}, - "files": {} + "files": {} }, "8C-SkemaIntegration.ghx": { "anchor": "plugins", @@ -31,8 +31,8 @@ "appLink": true, "rating": 1, "docLink": "https://help.shapediver.com/doc/shapediver-for-skema", - "videoLink": "", + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} } } diff --git a/src/11-AppBuilder/examples.json b/src/11-AppBuilder/examples.json index 3cdd25b..eea6206 100644 --- a/src/11-AppBuilder/examples.json +++ b/src/11-AppBuilder/examples.json @@ -6,10 +6,10 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "11B-AppBuilder_Tutorial2.ghx": { "anchor": "workflows", @@ -18,10 +18,10 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": { + "files": { "Input": "11B-AppBuilder_Tutorial2_ExampleInput.3dm" } }, @@ -32,12 +32,12 @@ "modelLink": true, "appLink": true, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": { "Input": "11B-AppBuilder_Tutorial2_ExampleInput.3dm" }, - "files": {} + "files": {} }, "11D-AppBuilder_Tutorial4.ghx": { "anchor": "workflows", @@ -46,10 +46,10 @@ "modelLink": true, "appLink": true, "rating": 3, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "11E-ParameterAndExportControls.ghx": { "anchor": "components", @@ -58,10 +58,10 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "11F-ActionControl.ghx": { "anchor": "components", @@ -70,10 +70,10 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "11G-AccordionAndStackWidgets.ghx": { "anchor": "components", @@ -82,10 +82,10 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "../02-Inputs/2D-SelectionInput_Basic.ghx": { "anchor": "interaction", @@ -97,7 +97,7 @@ "docLink": "https://help.shapediver.com/doc/selection-input", "videoLink": "https://www.youtube.com/watch?v=m9BsPMvKPDo", "ghFiles": {}, - "files": {} + "files": {} }, "../02-Inputs/2E-SelectionInput_Advanced.ghx": { "anchor": "interaction", @@ -109,7 +109,7 @@ "docLink": "https://help.shapediver.com/doc/selection-input", "videoLink": "https://www.youtube.com/watch?v=m9BsPMvKPDo", "ghFiles": {}, - "files": {} + "files": {} }, "../02-Inputs/2F-GumballInput_Basic.ghx": { "anchor": "interaction", @@ -119,9 +119,9 @@ "appLink": true, "rating": 1, "docLink": "https://help.shapediver.com/doc/gumball-input", - "videoLink": "", + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "../02-Inputs/2G-GumballInput_Tutorial1.ghx": { "anchor": "interaction", @@ -133,7 +133,7 @@ "docLink": "https://help.shapediver.com/doc/gumball-input", "videoLink": "https://www.youtube.com/watch?v=Pu9nDFJXZC0", "ghFiles": {}, - "files": {} + "files": {} }, "../02-Inputs/2H-Gumballinput_Tutorial2.ghx": { "anchor": "interaction", @@ -143,9 +143,9 @@ "appLink": true, "rating": 3, "docLink": "https://help.shapediver.com/doc/gumball-input", - "videoLink": "", + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "../02-Inputs/2I-PointsInputBasicTutorial.ghx": { "anchor": "interaction", @@ -157,7 +157,7 @@ "docLink": "https://help.shapediver.com/doc/points-input", "videoLink": "https://www.youtube.com/watch?v=vXmEo7hJ5T0", "ghFiles": {}, - "files": {} + "files": {} }, "../02-Inputs/2J-PointsInputGeometryConstraints.ghx": { "anchor": "interaction", @@ -169,6 +169,6 @@ "docLink": "https://help.shapediver.com/doc/points-input", "videoLink": "https://www.youtube.com/watch?v=h-8ro_QQ0CU", "ghFiles": {}, - "files": {} + "files": {} } } diff --git a/src/12-DesktopClients/examples.json b/src/12-DesktopClients/examples.json index 01fe714..32ceae8 100644 --- a/src/12-DesktopClients/examples.json +++ b/src/12-DesktopClients/examples.json @@ -1,4 +1,3 @@ - { "12A-DesktopClientsTutorial.ghx": { "anchor": "clients", @@ -7,9 +6,9 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} } } diff --git a/src/BETA/examples.json b/src/BETA/examples.json index 3fa0946..a6c5449 100644 --- a/src/BETA/examples.json +++ b/src/BETA/examples.json @@ -6,10 +6,10 @@ "modelLink": true, "appLink": true, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "BETA-DynamicValueListsTutorial.ghx": { "anchor": "beta", @@ -18,10 +18,10 @@ "modelLink": true, "appLink": true, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "BETA-SearchableValueList.ghx": { "anchor": "beta", @@ -30,10 +30,10 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "BETA-MultilineTextInput.ghx": { "anchor": "beta", @@ -42,10 +42,10 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "BETA-InstancesTutorial1-Controller.ghx": { "anchor": "beta", @@ -54,12 +54,12 @@ "modelLink": false, "appLink": true, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": { "Parametric Box": "BETA-ParametricBoxInstance.ghx" }, - "files": {} + "files": {} }, "BETA-Anchor3dSimple.ghx": { "anchor": "beta", @@ -68,10 +68,10 @@ "modelLink": true, "appLink": true, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "BETA-Anchor2dTutorial.ghx": { "anchor": "beta", @@ -80,10 +80,10 @@ "modelLink": true, "appLink": true, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "BETA-SoundAction.ghx": { "anchor": "beta", @@ -92,10 +92,10 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "BETA-ExportWithState.ghx": { "anchor": "beta", @@ -104,10 +104,10 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "BETA-ExportWithScreenshot.ghx": { "anchor": "beta", @@ -116,10 +116,10 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "BETA-FormWidget.ghx": { "anchor": "beta", @@ -128,10 +128,10 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "BETA-CameraAction.ghx": { "anchor": "beta", @@ -140,10 +140,10 @@ "modelLink": true, "appLink": true, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "BETA-SavedStatesWidget.ghx": { "anchor": "beta", @@ -152,9 +152,9 @@ "modelLink": true, "appLink": true, "rating": 1, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} } } diff --git a/src/Webinars/examples.json b/src/Webinars/examples.json index 0bb6056..634e18b 100644 --- a/src/Webinars/examples.json +++ b/src/Webinars/examples.json @@ -1,4 +1,3 @@ - { "2410A-Webinar_BarcelonaNeighbourhood.ghx": { "anchor": "webinar", @@ -7,10 +6,10 @@ "modelLink": true, "appLink": true, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "2410B-Webinar_ModularCabinets.ghx": { "anchor": "webinar", @@ -19,10 +18,10 @@ "modelLink": true, "appLink": true, "rating": 3, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "2503A-Webinar_SpecklePavillion.ghx": { "anchor": "webinar", @@ -31,10 +30,10 @@ "modelLink": true, "appLink": true, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "2503B-Webinar_ShopifyWorkflow.ghx": { "anchor": "webinar", @@ -43,10 +42,10 @@ "modelLink": true, "appLink": "https://appbuilder.shapediver.com/v1/ijewel3d/latest/?slug=2503b-shopifyworkflow&g=https://cdn.shopify.com/s/files/1/0897/7802/8923/files/example-agent-ring-theme.json&webgiScene=https://cdn.shopify.com/s/files/1/0585/2721/9918/files/3dConfiguratorScene.vjson", "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} }, "2605-Webinar_NewAppBuilderFeatures.ghx": { "anchor": "webinar", @@ -55,9 +54,9 @@ "modelLink": false, "appLink": true, "rating": 2, - "docLink": "", - "videoLink": "", + "docLink": false, + "videoLink": false, "ghFiles": {}, - "files": {} + "files": {} } } diff --git a/tools/docs-cli/src/createTables.ts b/tools/docs-cli/src/createTables.ts index b4d770c..1d86ed1 100644 --- a/tools/docs-cli/src/createTables.ts +++ b/tools/docs-cli/src/createTables.ts @@ -76,6 +76,11 @@ function ratingToStars(rating: number): string { return Array.from({ length: 3 }, (_, i) => (i < rating ? '★' : '☆')).join(' '); } +/** Creates an optional markdown link for boolean-or-string example metadata fields. */ +function createOptionalLink(icon: string, value: false | string): string { + return value ? `[${icon}](${value})` : ''; +} + /** * Creates a markdown table from the given examples, including conditional resources column if any * example has links. @@ -105,10 +110,7 @@ function createTable(examples: Examples[]): string { const [[ghxName, data]] = Object.entries(example); const ref = `**${ghxName.split('/').at(-1)!.split('-')[0]}**`; const resourceCell = hasResources - ? [ - data.docLink ? `[📖](${data.docLink})` : '', - data.videoLink ? `[🎥](${data.videoLink})` : '', - ] + ? [createOptionalLink('📖', data.docLink), createOptionalLink('🎥', data.videoLink)] .filter(Boolean) .join(' ') : null; diff --git a/tools/docs-cli/src/utils.ts b/tools/docs-cli/src/utils.ts index 1e4c99f..d86839b 100644 --- a/tools/docs-cli/src/utils.ts +++ b/tools/docs-cli/src/utils.ts @@ -7,9 +7,45 @@ export const FILE_NAME_EXAMPLES = 'examples.json'; /** The name of the Markdown file holding definitions for ShapeDiver models used as examples. */ export const FILE_NAME_DEFINITIONS = 'definitions.md'; +/** + * Strict string for metadata fields that are treated as user-authored identifiers or file paths. + * Rejects empty strings, whitespace-only strings, and strings with leading/trailing whitespace. + */ +const SCHEMA_NON_EMPTY_TRIMMED_STRING = z + .string() + .min(1) + .refine((value) => value.trim().length > 0, { + message: 'String must not be empty or whitespace only.', + }) + .refine((value) => value === value.trim(), { + message: 'String must not have leading or trailing whitespace.', + }); + +/** Optional documentation/video link fields are either disabled (`false`) or set explicitly. */ +const SCHEMA_OPTIONAL_LINK = z.union([z.literal(false), SCHEMA_NON_EMPTY_TRIMMED_STRING]); + +/** + * Record schema that validates keys with detailed string errors instead of Zod's generic + * "Invalid key in record" message. + */ +function createExamplesRecordSchema(valueSchema: T) { + return z.record(z.string(), valueSchema).superRefine((record, ctx) => { + for (const key of Object.keys(record)) { + const parsedKey = SCHEMA_NON_EMPTY_TRIMMED_STRING.safeParse(key); + if (!parsedKey.success) { + for (const issue of parsedKey.error.issues) { + ctx.addIssue({ + ...issue, + path: [key, ...issue.path], + }); + } + } + } + }); +} + /** The schema of the JSON file holding information about ShapeDiver models used as examples. */ -export const SCHEMA_EXAMPLES = z.record( - z.string(), +export const SCHEMA_EXAMPLES = createExamplesRecordSchema( z.object({ /** * The name of the markdown comment that acts as a placeholder for a single table. @@ -17,13 +53,13 @@ export const SCHEMA_EXAMPLES = z.record( * `[anchor]: #`, where "anchor" is the name of the markdown comment. * The markdown comment must be unique across all example definitions. */ - anchor: z.string().min(1), + anchor: SCHEMA_NON_EMPTY_TRIMMED_STRING, /** The slug of the ShapeDiver model. */ slug: z.string(), /** The title of the ShapeDiver model. */ - title: z.string().min(1), + title: SCHEMA_NON_EMPTY_TRIMMED_STRING, /** * Indicates whether the model link should be included. @@ -41,23 +77,23 @@ export const SCHEMA_EXAMPLES = z.record( /** The difficulty rating of the example. */ rating: z.number().min(1).max(3).int(), - /** The ShapeDiver documentation link for the example. */ - docLink: z.string(), + /** Optional ShapeDiver documentation link for the example. */ + docLink: SCHEMA_OPTIONAL_LINK, - /** The video link for the example. */ - videoLink: z.string(), + /** Optional video link for the example. */ + videoLink: SCHEMA_OPTIONAL_LINK, /** * List of auxiliary Grasshopper files that are shown next to the model's GH file's download * link. Each key is a file description and each value is a file name. */ - ghFiles: z.record(z.string(), z.string()), + ghFiles: createExamplesRecordSchema(SCHEMA_NON_EMPTY_TRIMMED_STRING), /** * List of additional non-Grasshopper files, where each key is a file description and each * value is a file name. */ - files: z.record(z.string(), z.string()), + files: createExamplesRecordSchema(SCHEMA_NON_EMPTY_TRIMMED_STRING), }) ); export type Examples = z.infer; diff --git a/tools/docs-cli/src/validate.ts b/tools/docs-cli/src/validate.ts index 6d641f2..e9e6ee6 100644 --- a/tools/docs-cli/src/validate.ts +++ b/tools/docs-cli/src/validate.ts @@ -3,6 +3,44 @@ import path from 'path'; import { z } from 'zod'; import { FILE_NAME_EXAMPLES, SCHEMA_EXAMPLES } from './utils'; +/** + * Validate a file reference declared inside an examples.json file. + * + * All references are resolved relative to the examples.json file, may use `..`, must not be + * absolute, must exist, must be regular files, and must not be symlinks. + */ +function validateReferencedFile( + reference: string, + fieldPath: string, + examplesFilePath: string +): void { + if (path.isAbsolute(reference)) { + throw new Error( + `Invalid absolute path in '${fieldPath}': '${reference}' in ${examplesFilePath}.` + ); + } + + const absolutePath = path.resolve(path.dirname(examplesFilePath), reference); + if (!fs.existsSync(absolutePath)) { + throw new Error( + `Missing referenced file in '${fieldPath}': '${reference}' in ${examplesFilePath}.` + ); + } + + const stats = fs.lstatSync(absolutePath); + if (stats.isSymbolicLink()) { + throw new Error( + `Referenced file must not be a symlink in '${fieldPath}': '${reference}' in ${examplesFilePath}.` + ); + } + + if (!stats.isFile()) { + throw new Error( + `Referenced path is not a regular file in '${fieldPath}': '${reference}' in ${examplesFilePath}.` + ); + } +} + (function () { // husky passes the absolute path of the mdbook directory as an argument const mdbookPath = process.argv[2]; @@ -18,8 +56,6 @@ import { FILE_NAME_EXAMPLES, SCHEMA_EXAMPLES } from './utils'; .map((entry) => path.join(mdbookPath, entry.name, FILE_NAME_EXAMPLES)) .filter((filePath) => fs.existsSync(filePath)); - let hasError = false; - for (const filePath of exampleFiles) { try { const fileContent = fs.readFileSync(filePath, 'utf-8'); @@ -28,14 +64,19 @@ import { FILE_NAME_EXAMPLES, SCHEMA_EXAMPLES } from './utils'; // Validate JSON structure const parsedData = SCHEMA_EXAMPLES.parse(jsonData); - const baseDir = path.dirname(filePath); + // Validate all file references declared by each example entry. + for (const [main, example] of Object.entries(parsedData)) { + // The top-level key is the main Grasshopper file for the example. + validateReferencedFile(main, main, filePath); + + // Auxiliary Grasshopper assets are declared as label -> relative file path. + for (const [label, referencedFile] of Object.entries(example.ghFiles)) { + validateReferencedFile(referencedFile, `${main}.ghFiles.${label}`, filePath); + } - // Validate physical files exist relative to the JSON file - for (const ghxName of Object.keys(parsedData)) { - const absolutePath = path.join(baseDir, ghxName); - if (!fs.existsSync(absolutePath)) { - console.error(`ERROR: Missing referenced file '${ghxName}' in ${filePath}.`); - hasError = true; + // Additional non-Grasshopper assets are declared as label -> relative file path. + for (const [label, referencedFile] of Object.entries(example.files)) { + validateReferencedFile(referencedFile, `${main}.files.${label}`, filePath); } } } catch (e) { @@ -47,13 +88,8 @@ import { FILE_NAME_EXAMPLES, SCHEMA_EXAMPLES } from './utils'; } else { console.error(e); } - hasError = true; + console.error('\nValidation failed. Please fix the errors above before committing.'); + process.exit(1); } } - - // Abort the commit if any errors were found - if (hasError) { - console.error('\nValidation failed. Please fix the errors above before committing.'); - process.exit(1); - } })(); From 3618d4b5da65557ae7495f90b0d229fb819a34aa Mon Sep 17 00:00:00 2001 From: Matthias Reisacher Date: Thu, 18 Jun 2026 18:03:17 +0200 Subject: [PATCH 5/5] Extend static github-action --- .github/workflows/static.yml | 14 +++++++++----- mise.toml | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 8c153b0..f6ffce7 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -33,17 +33,21 @@ jobs: MDBOOK_EXTERNAL_LINKS_VERSION: 0.1.2 steps: - uses: actions/checkout@v4 + - name: Setup mise tools + uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2.4.4 + - name: Install dependencies + run: pnpm install --frozen-lockfile - name: Install mdBook run: | - curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh - rustup update cargo install --version ${MDBOOK_VERSION} mdbook cargo install --version ${MDBOOK_EXTERNAL_LINKS_VERSION} mdbook-external-links + - name: Create docs tables + run: npm run docs:create-tables + - name: Build with mdBook + run: mdbook build - name: Setup Pages id: pages uses: actions/configure-pages@v5 - - name: Build with mdBook - run: mdbook build - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: @@ -59,4 +63,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file + uses: actions/deploy-pages@v4 diff --git a/mise.toml b/mise.toml index bb496dc..8e4c250 100644 --- a/mise.toml +++ b/mise.toml @@ -1,3 +1,4 @@ [tools] node = "24" pnpm = "11" +rust = "stable"