From 4d30eef13744fc5f857e674fc40524f9e2963349 Mon Sep 17 00:00:00 2001 From: Myers Carpenter Date: Fri, 17 Oct 2025 00:06:28 -0400 Subject: [PATCH] feat: enable installation from GitHub for monorepo packages This change allows Leva and its plugins to be installed directly from GitHub using pnpm. This is useful for testing unreleased features or using forked versions before PRs are merged. The implementation uses lifecycle event detection and path-based patterns to identify git installations and automatically build production artifacts during installation. This ensures consumers receive fully built packages without requiring local development dependencies. Changes: - Add postinstall script with git install detection via lifecycle events - Add prepare scripts to root and all packages for publish/git install support - Support pnpm's store tmp directory pattern for reliable detection - Enable git installation for all plugin packages (spring, dates, bezier, plot) - Skip builds appropriately for sub-dependencies and local development Installation example: pnpm add "leva@github:user/leva#branch&path:/packages/leva" --- package.json | 3 +- packages/leva/package.json | 8 +++++ packages/plugin-bezier/package.json | 7 ++++ packages/plugin-dates/package.json | 7 ++++ packages/plugin-plot/package.json | 7 ++++ packages/plugin-spring/package.json | 7 ++++ scripts/postinstall.js | 51 +++++++++++++++++++++++++++++ scripts/prepare-package.js | 35 ++++++++++++++++++++ yarn.lock | 8 ----- 9 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 scripts/postinstall.js create mode 100644 scripts/prepare-package.js diff --git a/package.json b/package.json index 6ca4595f..462a1348 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ ] }, "scripts": { - "postinstall": "husky install && preconstruct dev", + "postinstall": "node scripts/postinstall.js", "build": "preconstruct build", "watch": "preconstruct watch", "dev": "preconstruct dev", @@ -94,7 +94,6 @@ "eslint-plugin-react-hooks": "^4.3.0", "husky": "^7.0.4", "patch-package": "^6.4.7", - "postinstall-postinstall": "^2.1.0", "prettier": "^2.5.1", "pretty-quick": "^3.1.3", "react": "^18.0.0", diff --git a/packages/leva/package.json b/packages/leva/package.json index f411aa68..385a7f5d 100644 --- a/packages/leva/package.json +++ b/packages/leva/package.json @@ -4,6 +4,14 @@ "main": "dist/leva.cjs.js", "module": "dist/leva.esm.js", "types": "dist/leva.cjs.d.ts", + "files": [ + "dist", + "src", + "plugin" + ], + "scripts": { + "prepare": "node ../../scripts/prepare-package.js" + }, "license": "MIT", "repository": { "type": "git", diff --git a/packages/plugin-bezier/package.json b/packages/plugin-bezier/package.json index 989a586b..64d69598 100644 --- a/packages/plugin-bezier/package.json +++ b/packages/plugin-bezier/package.json @@ -4,6 +4,13 @@ "main": "dist/leva-ui-plugin-bezier.cjs.js", "module": "dist/leva-ui-plugin-bezier.esm.js", "types": "dist/leva-ui-plugin-bezier.cjs.d.ts", + "files": [ + "dist", + "src" + ], + "scripts": { + "prepare": "node ../../scripts/prepare-package.js" + }, "license": "MIT", "repository": { "type": "git", diff --git a/packages/plugin-dates/package.json b/packages/plugin-dates/package.json index f651234b..d64311f1 100644 --- a/packages/plugin-dates/package.json +++ b/packages/plugin-dates/package.json @@ -4,6 +4,13 @@ "main": "dist/leva-ui-plugin-dates.cjs.js", "module": "dist/leva-ui-plugin-dates.esm.js", "types": "dist/leva-ui-plugin-dates.cjs.d.ts", + "files": [ + "dist", + "src" + ], + "scripts": { + "prepare": "node ../../scripts/prepare-package.js" + }, "license": "MIT", "repository": { "type": "git", diff --git a/packages/plugin-plot/package.json b/packages/plugin-plot/package.json index 7c05e76e..c04baf53 100644 --- a/packages/plugin-plot/package.json +++ b/packages/plugin-plot/package.json @@ -4,6 +4,13 @@ "main": "dist/leva-ui-plugin-plot.cjs.js", "module": "dist/leva-ui-plugin-plot.esm.js", "types": "dist/leva-ui-plugin-plot.cjs.d.ts", + "files": [ + "dist", + "src" + ], + "scripts": { + "prepare": "node ../../scripts/prepare-package.js" + }, "license": "MIT", "repository": { "type": "git", diff --git a/packages/plugin-spring/package.json b/packages/plugin-spring/package.json index 42f985bd..564acf32 100644 --- a/packages/plugin-spring/package.json +++ b/packages/plugin-spring/package.json @@ -4,6 +4,13 @@ "main": "dist/leva-ui-plugin-spring.cjs.js", "module": "dist/leva-ui-plugin-spring.esm.js", "types": "dist/leva-ui-plugin-spring.cjs.d.ts", + "files": [ + "dist", + "src" + ], + "scripts": { + "prepare": "node ../../scripts/prepare-package.js" + }, "license": "MIT", "repository": { "type": "git", diff --git a/scripts/postinstall.js b/scripts/postinstall.js new file mode 100644 index 00000000..54bdcfc0 --- /dev/null +++ b/scripts/postinstall.js @@ -0,0 +1,51 @@ +const { execSync } = require('child_process') +const { existsSync } = require('fs') + +// Skip if explicitly disabled +if (process.env.SKIP_BUILD === '1' || process.env.SKIP_POSTINSTALL === '1') { + process.exit(0) +} + +// Detect if this is a git install via pnpm's synthetic script names +// pnpm creates 'npm-install', 'yarn-install', or 'pnpm-install' scripts +// ONLY when preparing git dependencies +const lifecycleEvent = process.env.npm_lifecycle_event + +// Detect git install via pnpm's store tmp directory pattern +// pnpm prepares git dependencies in /pnpm/store/v{version}/tmp/ +const cwd = process.cwd() +const isPnpmStoreTmp = /[/\\]pnpm[/\\]store[/\\]v\d+[/\\]tmp[/\\]/.test(cwd) + +const isGitInstall = + lifecycleEvent === 'npm-install' || + lifecycleEvent === 'yarn-install' || + lifecycleEvent === 'pnpm-install' || + isPnpmStoreTmp + +// Skip if installed as sub-dependency in local development +// (Don't skip for git installs - the ../../package.json would be the consuming project) +if (!isGitInstall) { + const isSubDependency = existsSync('../../package.json') + if (isSubDependency) { + process.exit(0) + } +} + +if (isGitInstall) { + // Git install: build production files + console.log('Building Leva for GitHub installation...') + try { + execSync('npx preconstruct build', { stdio: 'inherit' }) + } catch (e) { + console.error('Build failed:', e.message) + process.exit(1) + } +} else { + // Local development: use dev mode for fast linking + try { + execSync('npx preconstruct dev', { stdio: 'inherit' }) + } catch (e) { + console.error('Failed to run preconstruct dev:', e.message) + process.exit(1) + } +} diff --git a/scripts/prepare-package.js b/scripts/prepare-package.js new file mode 100644 index 00000000..a94eab58 --- /dev/null +++ b/scripts/prepare-package.js @@ -0,0 +1,35 @@ +const { execSync } = require('child_process') +const { existsSync } = require('fs') +const path = require('path') + +// Skip if explicitly disabled +if (process.env.SKIP_BUILD === '1') { + process.exit(0) +} + +const lifecycleEvent = process.env.npm_lifecycle_event + +// Git install: synthetic events from pnpm/npm/yarn +const isGitInstall = + lifecycleEvent === 'npm-install' || lifecycleEvent === 'yarn-install' || lifecycleEvent === 'pnpm-install' + +// Publishing: prepare runs before pack/publish +// When running from a package, go up to workspace root +// process.cwd() is the package directory (e.g., packages/leva) +const workspaceRoot = path.resolve(process.cwd(), '../..') +const isInWorkspace = existsSync(path.join(workspaceRoot, 'package.json')) + +// Only build if: +// 1. Git install (for consumers), OR +// 2. Publishing (not in workspace = preparing for publish) +if (isGitInstall || !isInWorkspace) { + try { + execSync('preconstruct build', { + stdio: 'inherit', + cwd: workspaceRoot, + }) + } catch (e) { + console.error('Build failed:', e.message) + process.exit(1) + } +} diff --git a/yarn.lock b/yarn.lock index 780bea1c..abac34f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2454,7 +2454,6 @@ __metadata: eslint-plugin-react-hooks: ^4.3.0 husky: ^7.0.4 patch-package: ^6.4.7 - postinstall-postinstall: ^2.1.0 prettier: ^2.5.1 pretty-quick: ^3.1.3 react: ^18.0.0 @@ -15863,13 +15862,6 @@ __metadata: languageName: node linkType: hard -"postinstall-postinstall@npm:^2.1.0": - version: 2.1.0 - resolution: "postinstall-postinstall@npm:2.1.0" - checksum: e1d34252cf8d2c5641c7d2db7426ec96e3d7a975f01c174c68f09ef5b8327bc8d5a9aa2001a45e693db2cdbf69577094d3fe6597b564ad2d2202b65fba76134b - languageName: node - linkType: hard - "potpack@npm:^1.0.1": version: 1.0.2 resolution: "potpack@npm:1.0.2"