@hover-dev/cli 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for describing the origin of the Work and
141
+ reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 Hyperyond Studio
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @hover-dev/cli
2
+
3
+ One-command setup for [Hover](https://github.com/Hyperyond/Hover) — detects your bundler, installs the right integration package, and wires it into your config file.
4
+
5
+ ## Usage
6
+
7
+ No installation required. `npx` runs the latest published version on demand:
8
+
9
+ ```bash
10
+ npx @hover-dev/cli add
11
+ ```
12
+
13
+ That's it. The CLI:
14
+
15
+ 1. **Reads your `package.json`** to figure out your bundler (Vite, Astro, Nuxt, or Webpack).
16
+ 2. **Reads your lockfile** to pick the right package manager (pnpm, yarn, bun, or npm).
17
+ 3. **Installs** the matching Hover package as a dev dependency.
18
+ 4. **Updates** your config file (`vite.config.ts` / `astro.config.mjs` / `nuxt.config.ts` / `webpack.config.js`) to load the plugin.
19
+
20
+ Then run your dev server and click the floating ✨ in the bottom-right corner.
21
+
22
+ ## Force a specific bundler
23
+
24
+ If detection picks the wrong one (e.g. your project has multiple bundlers, or you're starting from a fresh repo), use a flag:
25
+
26
+ ```bash
27
+ npx @hover-dev/cli add --vite # vite-plugin-hover
28
+ npx @hover-dev/cli add --astro # @hover-dev/astro
29
+ npx @hover-dev/cli add --nuxt # @hover-dev/nuxt
30
+ npx @hover-dev/cli add --webpack # webpack-plugin-hover
31
+ ```
32
+
33
+ ## Preview without modifying anything
34
+
35
+ ```bash
36
+ npx @hover-dev/cli add --dry-run
37
+ ```
38
+
39
+ Prints what would be installed + which config file would be modified, then exits without changing anything.
40
+
41
+ ## What if my config file is unusual?
42
+
43
+ The CLI uses [magicast](https://github.com/unjs/magicast) to safely mutate `defineConfig({ ... })` and bare-object configs. If your config has an unusual shape (re-exported config, conditional logic, etc.), the CLI will:
44
+
45
+ 1. Still install the right Hover package.
46
+ 2. Skip the config mutation and print the exact lines you need to paste in.
47
+
48
+ This is also what happens if you have no config file at all — many projects rely on bundler defaults until they need to customise.
49
+
50
+ ## Use as a project dep (optional)
51
+
52
+ `npx` is the default and recommended way. If you want to lock the CLI version per-project (e.g. to make a setup command for new teammates), install it as a dev dep:
53
+
54
+ ```bash
55
+ pnpm add -D @hover-dev/cli
56
+ pnpm hover add
57
+ ```
58
+
59
+ ## License
60
+
61
+ Apache-2.0 — same as the rest of Hover.
@@ -0,0 +1,41 @@
1
+ import { type Framework } from './frameworks.js';
2
+ export interface PackageJson {
3
+ dependencies?: Record<string, string>;
4
+ devDependencies?: Record<string, string>;
5
+ [key: string]: unknown;
6
+ }
7
+ /**
8
+ * Locate and parse the user's package.json. Returns the parsed contents +
9
+ * the absolute directory it lives in (so callers know the project root
10
+ * for spawning the package manager + resolving config-file paths).
11
+ *
12
+ * Walks up from `startDir` looking for `package.json` — this lets the user
13
+ * run `npx @hover-dev/cli add` from a subdirectory and still target the
14
+ * project root. Stops at the filesystem root.
15
+ */
16
+ export declare function readUserPackageJson(startDir?: string): {
17
+ pkg: PackageJson;
18
+ rootDir: string;
19
+ } | null;
20
+ /**
21
+ * Detect which framework the user's package.json signals. Returns the
22
+ * highest-priority framework whose `detectDeps` overlap with the user's
23
+ * combined dep tree. Returns null if none match (likely a fresh project
24
+ * or non-JS repo).
25
+ */
26
+ export declare function detectFramework(pkg: PackageJson): Framework | null;
27
+ /**
28
+ * Detect which package manager the user is on by sniffing the lockfile.
29
+ * Falls back to npm if nothing matches (every Node install has npm), but
30
+ * prints a warning so the user knows we're guessing.
31
+ *
32
+ * Priority is lockfile-first because lockfile commitment is the strongest
33
+ * signal — `packageManager` field in package.json is a softer hint that
34
+ * users often forget to update.
35
+ */
36
+ export type PackageManager = 'pnpm' | 'yarn' | 'bun' | 'npm';
37
+ export declare function detectPackageManager(rootDir: string): {
38
+ pm: PackageManager;
39
+ reason: string;
40
+ };
41
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE7D,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,GAAE,MAAsB,GAAG;IAAE,GAAG,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAgBlH;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAQlE;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;AAE7D,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAoB5F"}
package/dist/detect.js ADDED
@@ -0,0 +1,68 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { FRAMEWORKS } from './frameworks.js';
4
+ /**
5
+ * Locate and parse the user's package.json. Returns the parsed contents +
6
+ * the absolute directory it lives in (so callers know the project root
7
+ * for spawning the package manager + resolving config-file paths).
8
+ *
9
+ * Walks up from `startDir` looking for `package.json` — this lets the user
10
+ * run `npx @hover-dev/cli add` from a subdirectory and still target the
11
+ * project root. Stops at the filesystem root.
12
+ */
13
+ export function readUserPackageJson(startDir = process.cwd()) {
14
+ let dir = resolve(startDir);
15
+ while (true) {
16
+ const candidate = join(dir, 'package.json');
17
+ if (existsSync(candidate)) {
18
+ try {
19
+ const pkg = JSON.parse(readFileSync(candidate, 'utf-8'));
20
+ return { pkg, rootDir: dir };
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ const parent = resolve(dir, '..');
27
+ if (parent === dir)
28
+ return null;
29
+ dir = parent;
30
+ }
31
+ }
32
+ /**
33
+ * Detect which framework the user's package.json signals. Returns the
34
+ * highest-priority framework whose `detectDeps` overlap with the user's
35
+ * combined dep tree. Returns null if none match (likely a fresh project
36
+ * or non-JS repo).
37
+ */
38
+ export function detectFramework(pkg) {
39
+ const allDeps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
40
+ for (const framework of FRAMEWORKS) {
41
+ for (const dep of framework.detectDeps) {
42
+ if (dep in allDeps)
43
+ return framework;
44
+ }
45
+ }
46
+ return null;
47
+ }
48
+ export function detectPackageManager(rootDir) {
49
+ const lockfileMap = [
50
+ { file: 'pnpm-lock.yaml', pm: 'pnpm' },
51
+ { file: 'yarn.lock', pm: 'yarn' },
52
+ { file: 'bun.lockb', pm: 'bun' },
53
+ { file: 'bun.lock', pm: 'bun' },
54
+ { file: 'package-lock.json', pm: 'npm' },
55
+ ];
56
+ for (const { file, pm } of lockfileMap) {
57
+ if (existsSync(join(rootDir, file))) {
58
+ return { pm, reason: `lockfile ${file}` };
59
+ }
60
+ }
61
+ // Soft hint: explicit `packageManager` field.
62
+ const pkgRaw = readFileSync(join(rootDir, 'package.json'), 'utf-8');
63
+ const pkgManagerMatch = /"packageManager"\s*:\s*"(pnpm|yarn|bun|npm)/.exec(pkgRaw);
64
+ if (pkgManagerMatch) {
65
+ return { pm: pkgManagerMatch[1], reason: 'packageManager field' };
66
+ }
67
+ return { pm: 'npm', reason: 'no lockfile found; defaulting to npm' };
68
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Registry of frameworks `@hover-dev/cli` knows how to wire Hover into.
3
+ *
4
+ * To add a new framework (e.g. SvelteKit, SolidStart, Qwik, Next-with-Turbopack):
5
+ * write a new entry here and a matching mutator in `mutate.ts`. Nothing else
6
+ * in the CLI changes — `detect.ts`, `install.ts`, and the top-level
7
+ * dispatcher all consume this list generically.
8
+ *
9
+ * `detectDeps` is matched against the user's `package.json` `dependencies` +
10
+ * `devDependencies` keys (and a couple of indirect signals). The order in
11
+ * the array is the priority order — a Nuxt project legitimately has `vite`
12
+ * in its dep tree, so Nuxt must check before Vite. `--<framework>` flags
13
+ * override detection entirely.
14
+ *
15
+ * `configCandidates` is the list of filenames the mutator will look for,
16
+ * in priority order. The first one that exists in cwd wins.
17
+ */
18
+ export type FrameworkId = 'astro' | 'nuxt' | 'webpack' | 'vite';
19
+ export interface Framework {
20
+ /** Short id used as the --<id> CLI flag and the `Detected: <id>` output. */
21
+ id: FrameworkId;
22
+ /** Human-readable name printed in logs. */
23
+ label: string;
24
+ /** Hover package the user gets installed for this framework. */
25
+ hoverPackage: string;
26
+ /** package.json dependency keys whose presence signals this framework.
27
+ * Checked in priority order across the registry. */
28
+ detectDeps: string[];
29
+ /** Possible filenames for the framework's config file. First match wins. */
30
+ configCandidates: string[];
31
+ }
32
+ /**
33
+ * Detection priority is high → low. A monorepo or framework whose dep tree
34
+ * legitimately contains a lower-priority framework (Nuxt has `vite`, Astro
35
+ * has `vite`) must come first so the right shim wins.
36
+ *
37
+ * The `Webpack` entry below intentionally checks the user-facing webpack
38
+ * tooling (webpack-cli / next) rather than a transitive `webpack` dep —
39
+ * every Vite project has `webpack` somewhere in its tree because of
40
+ * dev-server internals.
41
+ */
42
+ export declare const FRAMEWORKS: Framework[];
43
+ export declare function findFrameworkById(id: FrameworkId): Framework | undefined;
44
+ //# sourceMappingURL=frameworks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frameworks.d.ts","sourceRoot":"","sources":["../src/frameworks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AAEhE,MAAM,WAAW,SAAS;IACxB,4EAA4E;IAC5E,EAAE,EAAE,WAAW,CAAC;IAChB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,YAAY,EAAE,MAAM,CAAC;IACrB;yDACqD;IACrD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,4EAA4E;IAC5E,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,EAiCjC,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CAExE"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Detection priority is high → low. A monorepo or framework whose dep tree
3
+ * legitimately contains a lower-priority framework (Nuxt has `vite`, Astro
4
+ * has `vite`) must come first so the right shim wins.
5
+ *
6
+ * The `Webpack` entry below intentionally checks the user-facing webpack
7
+ * tooling (webpack-cli / next) rather than a transitive `webpack` dep —
8
+ * every Vite project has `webpack` somewhere in its tree because of
9
+ * dev-server internals.
10
+ */
11
+ export const FRAMEWORKS = [
12
+ {
13
+ id: 'astro',
14
+ label: 'Astro',
15
+ hoverPackage: '@hover-dev/astro',
16
+ detectDeps: ['astro'],
17
+ configCandidates: ['astro.config.mjs', 'astro.config.ts', 'astro.config.js'],
18
+ },
19
+ {
20
+ id: 'nuxt',
21
+ label: 'Nuxt',
22
+ hoverPackage: '@hover-dev/nuxt',
23
+ detectDeps: ['nuxt'],
24
+ configCandidates: ['nuxt.config.ts', 'nuxt.config.js', 'nuxt.config.mjs'],
25
+ },
26
+ {
27
+ id: 'webpack',
28
+ label: 'Webpack',
29
+ hoverPackage: 'webpack-plugin-hover',
30
+ // `webpack-cli` is the user-facing wrapper; `next` ships its own webpack
31
+ // (and our plugin works under `next dev --webpack`). Pure `webpack` as a
32
+ // transitive dep is too noisy to detect on.
33
+ detectDeps: ['webpack-cli', 'next'],
34
+ configCandidates: ['webpack.config.js', 'webpack.config.mjs', 'webpack.config.ts'],
35
+ },
36
+ {
37
+ id: 'vite',
38
+ label: 'Vite',
39
+ hoverPackage: 'vite-plugin-hover',
40
+ // Catch-all for Vite-based projects that aren't Astro / Nuxt / Svelte.
41
+ detectDeps: ['vite'],
42
+ configCandidates: ['vite.config.ts', 'vite.config.js', 'vite.config.mjs'],
43
+ },
44
+ ];
45
+ export function findFrameworkById(id) {
46
+ return FRAMEWORKS.find(f => f.id === id);
47
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { detectFramework, detectPackageManager, readUserPackageJson } from './detect.js';
6
+ import { findFrameworkById, FRAMEWORKS } from './frameworks.js';
7
+ import { installPackage } from './install.js';
8
+ import { mutateConfig } from './mutate.js';
9
+ import { bold, cyan, dim, err, info, ok, spark, warn } from './log.js';
10
+ function parseArgs(argv) {
11
+ const out = {
12
+ command: null,
13
+ framework: null,
14
+ dryRun: false,
15
+ help: false,
16
+ version: false,
17
+ };
18
+ for (const arg of argv) {
19
+ if (arg === '--help' || arg === '-h')
20
+ out.help = true;
21
+ else if (arg === '--version' || arg === '-v')
22
+ out.version = true;
23
+ else if (arg === '--dry-run')
24
+ out.dryRun = true;
25
+ else if (arg.startsWith('--')) {
26
+ const candidate = arg.slice(2);
27
+ if (FRAMEWORKS.some(f => f.id === candidate)) {
28
+ out.framework = candidate;
29
+ }
30
+ else {
31
+ err(`Unknown flag: ${arg}`);
32
+ process.exit(2);
33
+ }
34
+ }
35
+ else if (!out.command) {
36
+ out.command = arg;
37
+ }
38
+ else {
39
+ err(`Unexpected positional argument: ${arg}`);
40
+ process.exit(2);
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+ function printUsage() {
46
+ console.log(`${bold('@hover-dev/cli')} — wire Hover into your dev workflow
47
+
48
+ Usage:
49
+ npx @hover-dev/cli add ${dim('# auto-detect bundler, install, wire')}
50
+ npx @hover-dev/cli add --vite ${dim('# force a specific bundler')}
51
+ npx @hover-dev/cli add --astro
52
+ npx @hover-dev/cli add --nuxt
53
+ npx @hover-dev/cli add --webpack
54
+ npx @hover-dev/cli add --dry-run ${dim('# show what would happen, change nothing')}
55
+ npx @hover-dev/cli --help
56
+ npx @hover-dev/cli --version
57
+
58
+ What it does:
59
+ 1. Detects your bundler (Vite / Astro / Nuxt / Webpack) from package.json.
60
+ 2. Detects your package manager (pnpm / yarn / bun / npm) from your lockfile.
61
+ 3. Installs the matching Hover integration as a dev dependency.
62
+ 4. Adds the plugin/integration to your config file.
63
+
64
+ For more: https://github.com/Hyperyond/Hover
65
+ `);
66
+ }
67
+ async function runAdd(args) {
68
+ const found = readUserPackageJson();
69
+ if (!found) {
70
+ err(`No package.json found in the current directory or any parent.`);
71
+ err(`Run this command from your project root, or run \`npm init\` first.`);
72
+ return 1;
73
+ }
74
+ const { pkg, rootDir } = found;
75
+ // Step 1: pick framework — explicit flag wins over detection.
76
+ const framework = args.framework
77
+ ? findFrameworkById(args.framework)
78
+ : detectFramework(pkg);
79
+ if (!framework) {
80
+ err(`Couldn't detect a supported bundler in package.json.`);
81
+ info(`Supported: ${FRAMEWORKS.map(f => f.id).join(', ')}.`);
82
+ info(`Force one with --vite / --astro / --nuxt / --webpack.`);
83
+ return 1;
84
+ }
85
+ if (args.framework) {
86
+ info(`Using ${bold(framework.label)} (forced via --${framework.id}).`);
87
+ }
88
+ else {
89
+ info(`Detected ${bold(framework.label)} project.`);
90
+ }
91
+ // Step 2: pick package manager.
92
+ const { pm, reason } = detectPackageManager(rootDir);
93
+ info(`Using package manager: ${bold(pm)} ${dim(`(${reason})`)}.`);
94
+ if (args.dryRun) {
95
+ warn(`Dry-run — not installing or modifying files.`);
96
+ info(`Would install: ${cyan(framework.hoverPackage)} (dev dependency, via ${pm}).`);
97
+ info(`Would mutate: ${cyan(framework.configCandidates[0])} (or whichever candidate exists).`);
98
+ return 0;
99
+ }
100
+ // Step 3: install the right Hover package as a dev dependency.
101
+ info(`Installing ${cyan(framework.hoverPackage)} ...`);
102
+ const installCode = await installPackage(pm, framework.hoverPackage, rootDir);
103
+ if (installCode !== 0) {
104
+ err(`Package manager exited with code ${installCode}. Aborting.`);
105
+ return installCode;
106
+ }
107
+ ok(`Installed ${framework.hoverPackage}.`);
108
+ // Step 4: wire the plugin into the config file.
109
+ info(`Wiring into ${cyan(framework.configCandidates[0])} ...`);
110
+ const result = await mutateConfig(rootDir, framework);
111
+ switch (result.kind) {
112
+ case 'ok':
113
+ if (result.alreadyWired) {
114
+ ok(`${result.configPath} already wired — left as-is.`);
115
+ }
116
+ else {
117
+ ok(`Updated ${result.configPath}.`);
118
+ }
119
+ break;
120
+ case 'manual':
121
+ warn(`Couldn't update your config automatically: ${result.reason}.`);
122
+ console.log(result.instructions);
123
+ break;
124
+ case 'error':
125
+ warn(`Skipped config update — magicast couldn't safely mutate the file.`);
126
+ warn(`Reason: ${result.reason}`);
127
+ console.log(result.instructions);
128
+ break;
129
+ }
130
+ spark(`Done. Run your dev server and click the floating ✨.`);
131
+ return 0;
132
+ }
133
+ async function main() {
134
+ const args = parseArgs(process.argv.slice(2));
135
+ if (args.help) {
136
+ printUsage();
137
+ return;
138
+ }
139
+ if (args.version) {
140
+ // Read version from our own package.json at runtime so we don't bake a
141
+ // string into source that drifts from package.json. Walk up from
142
+ // src/ or dist/ until we find the package's own package.json.
143
+ const here = dirname(fileURLToPath(import.meta.url));
144
+ let dir = here;
145
+ while (dir !== '/' && !existsSync(join(dir, 'package.json')))
146
+ dir = dirname(dir);
147
+ const own = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
148
+ console.log(own.version);
149
+ return;
150
+ }
151
+ if (args.command === 'add') {
152
+ const code = await runAdd(args);
153
+ process.exit(code);
154
+ }
155
+ if (!args.command) {
156
+ printUsage();
157
+ process.exit(0);
158
+ }
159
+ err(`Unknown command: ${args.command}`);
160
+ printUsage();
161
+ process.exit(2);
162
+ }
163
+ main().catch(e => {
164
+ err(e instanceof Error ? e.stack ?? e.message : String(e));
165
+ process.exit(1);
166
+ });
@@ -0,0 +1,12 @@
1
+ import type { PackageManager } from './detect.js';
2
+ /**
3
+ * Spawn the user's package manager with the install command. Inherits stdio
4
+ * so the user sees the PM's native progress output (it's better than
5
+ * anything we could synthesise). Resolves with the exit code; the caller
6
+ * decides whether to bail.
7
+ *
8
+ * `cwd` should be the user's project root (where package.json lives) —
9
+ * `detect.readUserPackageJson` returns it as `rootDir`.
10
+ */
11
+ export declare function installPackage(pm: PackageManager, pkg: string, cwd: string): Promise<number>;
12
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAqBlD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO5F"}
@@ -0,0 +1,36 @@
1
+ import { spawn } from 'node:child_process';
2
+ /**
3
+ * Map (package manager, install kind) → argv. Each PM has its own dialect:
4
+ * - `pnpm add -D`, `yarn add -D`, `bun add -d`, `npm install --save-dev`.
5
+ * We pin to dev-dependency installs because every Hover integration is a
6
+ * dev-only tool (no production runtime code).
7
+ */
8
+ function buildInstallArgv(pm, pkg) {
9
+ switch (pm) {
10
+ case 'pnpm':
11
+ return ['add', '-D', pkg];
12
+ case 'yarn':
13
+ return ['add', '-D', pkg];
14
+ case 'bun':
15
+ return ['add', '-d', pkg];
16
+ case 'npm':
17
+ return ['install', '--save-dev', pkg];
18
+ }
19
+ }
20
+ /**
21
+ * Spawn the user's package manager with the install command. Inherits stdio
22
+ * so the user sees the PM's native progress output (it's better than
23
+ * anything we could synthesise). Resolves with the exit code; the caller
24
+ * decides whether to bail.
25
+ *
26
+ * `cwd` should be the user's project root (where package.json lives) —
27
+ * `detect.readUserPackageJson` returns it as `rootDir`.
28
+ */
29
+ export function installPackage(pm, pkg, cwd) {
30
+ return new Promise((resolveExit, rejectExit) => {
31
+ const argv = buildInstallArgv(pm, pkg);
32
+ const child = spawn(pm, argv, { cwd, stdio: 'inherit' });
33
+ child.on('error', rejectExit);
34
+ child.on('exit', code => resolveExit(code ?? -1));
35
+ });
36
+ }
package/dist/log.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ export declare const dim: (s: string) => string;
2
+ export declare const bold: (s: string) => string;
3
+ export declare const green: (s: string) => string;
4
+ export declare const yellow: (s: string) => string;
5
+ export declare const red: (s: string) => string;
6
+ export declare const blue: (s: string) => string;
7
+ export declare const cyan: (s: string) => string;
8
+ export declare const info: (msg: string) => void;
9
+ export declare const ok: (msg: string) => void;
10
+ export declare const warn: (msg: string) => void;
11
+ export declare const err: (msg: string) => void;
12
+ export declare const spark: (msg: string) => void;
13
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,KAAG,MAAsB,CAAC;AACvD,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,KAAG,MAAsB,CAAC;AACxD,eAAO,MAAM,KAAK,GAAI,GAAG,MAAM,KAAG,MAAuB,CAAC;AAC1D,eAAO,MAAM,MAAM,GAAI,GAAG,MAAM,KAAG,MAAuB,CAAC;AAC3D,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,KAAG,MAAuB,CAAC;AACxD,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,KAAG,MAAuB,CAAC;AACzD,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,KAAG,MAAuB,CAAC;AAWzD,eAAO,MAAM,IAAI,GAAI,KAAK,MAAM,KAAG,IAAyC,CAAC;AAC7E,eAAO,MAAM,EAAE,GAAI,KAAK,MAAM,KAAG,IAAuC,CAAC;AACzE,eAAO,MAAM,IAAI,GAAI,KAAK,MAAM,KAAG,IAAyC,CAAC;AAC7E,eAAO,MAAM,GAAG,GAAI,KAAK,MAAM,KAAG,IAA0C,CAAC;AAC7E,eAAO,MAAM,KAAK,GAAI,KAAK,MAAM,KAAG,IAA0C,CAAC"}
package/dist/log.js ADDED
@@ -0,0 +1,26 @@
1
+ // Minimal ANSI color + symbol helpers. We avoid pulling in chalk / picocolors
2
+ // to keep the CLI dependency surface tight (faster npx cold-start). Falls
3
+ // back to plain text if the output stream isn't a TTY (e.g. CI piping into
4
+ // a log file) so we don't dump escape codes into logs.
5
+ const isTTY = process.stdout.isTTY === true;
6
+ const wrap = (code, s) => (isTTY ? `\x1b[${code}m${s}\x1b[0m` : s);
7
+ export const dim = (s) => wrap('2', s);
8
+ export const bold = (s) => wrap('1', s);
9
+ export const green = (s) => wrap('32', s);
10
+ export const yellow = (s) => wrap('33', s);
11
+ export const red = (s) => wrap('31', s);
12
+ export const blue = (s) => wrap('34', s);
13
+ export const cyan = (s) => wrap('36', s);
14
+ // Status symbols mirror what tools like Vite / Astro / shadcn use.
15
+ const SYM = {
16
+ info: blue('ℹ'),
17
+ ok: green('✓'),
18
+ warn: yellow('⚠'),
19
+ err: red('✗'),
20
+ spark: cyan('✨'),
21
+ };
22
+ export const info = (msg) => console.log(`${SYM.info} ${msg}`);
23
+ export const ok = (msg) => console.log(`${SYM.ok} ${msg}`);
24
+ export const warn = (msg) => console.log(`${SYM.warn} ${msg}`);
25
+ export const err = (msg) => console.error(`${SYM.err} ${msg}`);
26
+ export const spark = (msg) => console.log(`${SYM.spark} ${msg}`);
@@ -0,0 +1,36 @@
1
+ import type { Framework, FrameworkId } from './frameworks.js';
2
+ /**
3
+ * Result of attempting to wire Hover into the user's config file.
4
+ *
5
+ * - `ok` — config was modified (or was already wired; idempotent).
6
+ * - `manual` — we couldn't safely mutate (no config file, unusual shape).
7
+ * `instructions` contains the lines the user should paste themselves.
8
+ * - `error` — magicast threw; we bailed without touching the file.
9
+ */
10
+ export type MutateResult = {
11
+ kind: 'ok';
12
+ configPath: string;
13
+ alreadyWired: boolean;
14
+ } | {
15
+ kind: 'manual';
16
+ reason: string;
17
+ instructions: string;
18
+ } | {
19
+ kind: 'error';
20
+ reason: string;
21
+ instructions: string;
22
+ };
23
+ /**
24
+ * Wire Hover into the user's config file. Picks the first existing
25
+ * `configCandidates` entry, dispatches to a per-framework mutator. Each
26
+ * mutator is responsible for being idempotent (re-running the CLI on an
27
+ * already-wired project should not duplicate the import or array entry).
28
+ */
29
+ export declare function mutateConfig(rootDir: string, framework: Framework): Promise<MutateResult>;
30
+ /**
31
+ * Plain-text fallback instructions for `kind: 'manual'` / `kind: 'error'`.
32
+ * Mirrors what the AST mutator would have produced, so a user with an
33
+ * unusual config file can hand-edit and get the same result.
34
+ */
35
+ export declare function manualInstructions(id: FrameworkId): string;
36
+ //# sourceMappingURL=mutate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutate.d.ts","sourceRoot":"","sources":["../src/mutate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9D;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CA+B/F;AAoHD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,CAiC1D"}
package/dist/mutate.js ADDED
@@ -0,0 +1,184 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { builders, loadFile, writeFile } from 'magicast';
4
+ /**
5
+ * Wire Hover into the user's config file. Picks the first existing
6
+ * `configCandidates` entry, dispatches to a per-framework mutator. Each
7
+ * mutator is responsible for being idempotent (re-running the CLI on an
8
+ * already-wired project should not duplicate the import or array entry).
9
+ */
10
+ export async function mutateConfig(rootDir, framework) {
11
+ const configPath = framework.configCandidates
12
+ .map(name => join(rootDir, name))
13
+ .find(p => existsSync(p));
14
+ if (!configPath) {
15
+ return {
16
+ kind: 'manual',
17
+ reason: `no ${framework.configCandidates[0]} found`,
18
+ instructions: manualInstructions(framework.id),
19
+ };
20
+ }
21
+ try {
22
+ switch (framework.id) {
23
+ case 'vite':
24
+ return await mutateVite(configPath);
25
+ case 'astro':
26
+ return await mutateAstro(configPath);
27
+ case 'nuxt':
28
+ return await mutateNuxt(configPath);
29
+ case 'webpack':
30
+ return await mutateWebpack(configPath);
31
+ }
32
+ }
33
+ catch (err) {
34
+ const msg = err instanceof Error ? err.message : String(err);
35
+ return {
36
+ kind: 'error',
37
+ reason: msg,
38
+ instructions: manualInstructions(framework.id),
39
+ };
40
+ }
41
+ }
42
+ // ─── Vite: hover() in plugins array ─────────────────────────────────────
43
+ async function mutateVite(configPath) {
44
+ const mod = await loadFile(configPath);
45
+ // Idempotency: bail if `hover` is already imported from vite-plugin-hover.
46
+ // (We could be more clever and re-push if missing-from-array but present
47
+ // in imports, but that's a corner case — overwriting an intentional removal
48
+ // would be more harmful.)
49
+ if (alreadyImported(mod, 'vite-plugin-hover')) {
50
+ return { kind: 'ok', configPath, alreadyWired: true };
51
+ }
52
+ mod.imports.$add({ from: 'vite-plugin-hover', imported: 'hover' });
53
+ const config = unwrapDefineConfig(mod.exports.default);
54
+ ensureArray(config, 'plugins');
55
+ config.plugins.push(builders.functionCall('hover'));
56
+ await writeFile(mod, configPath);
57
+ return { kind: 'ok', configPath, alreadyWired: false };
58
+ }
59
+ // ─── Astro: hover() in integrations array ───────────────────────────────
60
+ async function mutateAstro(configPath) {
61
+ const mod = await loadFile(configPath);
62
+ if (alreadyImported(mod, '@hover-dev/astro')) {
63
+ return { kind: 'ok', configPath, alreadyWired: true };
64
+ }
65
+ mod.imports.$add({ from: '@hover-dev/astro', imported: 'hover' });
66
+ const config = unwrapDefineConfig(mod.exports.default);
67
+ ensureArray(config, 'integrations');
68
+ config.integrations.push(builders.functionCall('hover'));
69
+ await writeFile(mod, configPath);
70
+ return { kind: 'ok', configPath, alreadyWired: false };
71
+ }
72
+ // ─── Nuxt: '@hover-dev/nuxt' string in modules array ────────────────────
73
+ async function mutateNuxt(configPath) {
74
+ const mod = await loadFile(configPath);
75
+ const config = unwrapDefineConfig(mod.exports.default);
76
+ ensureArray(config, 'modules');
77
+ // Idempotency: Nuxt modules are referenced by string, so check the array.
78
+ // magicast arrays are iterable proxies, not real Arrays, so don't trust
79
+ // `Array.isArray` — iterate instead.
80
+ for (const m of config.modules) {
81
+ if (m === '@hover-dev/nuxt') {
82
+ return { kind: 'ok', configPath, alreadyWired: true };
83
+ }
84
+ }
85
+ config.modules.push('@hover-dev/nuxt');
86
+ await writeFile(mod, configPath);
87
+ return { kind: 'ok', configPath, alreadyWired: false };
88
+ }
89
+ // ─── Webpack: new HoverPlugin() in plugins array ────────────────────────
90
+ async function mutateWebpack(configPath) {
91
+ const mod = await loadFile(configPath);
92
+ if (alreadyImported(mod, 'webpack-plugin-hover')) {
93
+ return { kind: 'ok', configPath, alreadyWired: true };
94
+ }
95
+ mod.imports.$add({ from: 'webpack-plugin-hover', imported: 'HoverPlugin' });
96
+ const config = unwrapDefineConfig(mod.exports.default);
97
+ ensureArray(config, 'plugins');
98
+ config.plugins.push(builders.newExpression('HoverPlugin'));
99
+ await writeFile(mod, configPath);
100
+ return { kind: 'ok', configPath, alreadyWired: false };
101
+ }
102
+ // ─── Helpers ────────────────────────────────────────────────────────────
103
+ /**
104
+ * Check whether a module already has a named/default import from `source`.
105
+ * magicast's `imports` proxy is keyed by binding name; each entry exposes
106
+ * its source as `.from`. Defensive: returns false on any unexpected shape.
107
+ */
108
+ function alreadyImported(mod, source) {
109
+ try {
110
+ for (const item of mod.imports.$items) {
111
+ if (item.from === source)
112
+ return true;
113
+ }
114
+ }
115
+ catch {
116
+ /* fall through */
117
+ }
118
+ return false;
119
+ }
120
+ /**
121
+ * `export default defineConfig({ ... })` is the convention across Vite /
122
+ * Astro / Nuxt. Unwrap to the inner object so callers can mutate
123
+ * `plugins` / `integrations` / `modules` directly. Bare object exports
124
+ * (`export default { ... }`) are returned unchanged.
125
+ */
126
+ function unwrapDefineConfig(exp) {
127
+ if (exp?.$type === 'function-call' && Array.isArray(exp.$args) && exp.$args[0]) {
128
+ return exp.$args[0];
129
+ }
130
+ return exp;
131
+ }
132
+ /**
133
+ * Ensure a key on the magicast-proxied config object holds an array. The
134
+ * obvious-looking `cfg[key] ??= []` does NOT work reliably against the
135
+ * proxy — the proxy returns "something" for missing keys (not undefined),
136
+ * so the nullish-assignment short-circuits, and subsequent pushes go into
137
+ * a detached array that never makes it back to the AST. Explicit `if not
138
+ * present then assign a real []` does work, because the proxy turns the
139
+ * assignment into an actual AST node.
140
+ */
141
+ function ensureArray(config, key) {
142
+ if (config[key] === undefined || config[key] === null) {
143
+ config[key] = [];
144
+ }
145
+ }
146
+ /**
147
+ * Plain-text fallback instructions for `kind: 'manual'` / `kind: 'error'`.
148
+ * Mirrors what the AST mutator would have produced, so a user with an
149
+ * unusual config file can hand-edit and get the same result.
150
+ */
151
+ export function manualInstructions(id) {
152
+ switch (id) {
153
+ case 'vite':
154
+ return [
155
+ `Add to your vite config:`,
156
+ ``,
157
+ ` import { hover } from 'vite-plugin-hover';`,
158
+ ` // ...`,
159
+ ` plugins: [react(), hover()],`,
160
+ ].join('\n');
161
+ case 'astro':
162
+ return [
163
+ `Add to your astro config:`,
164
+ ``,
165
+ ` import { hover } from '@hover-dev/astro';`,
166
+ ` // ...`,
167
+ ` integrations: [hover()],`,
168
+ ].join('\n');
169
+ case 'nuxt':
170
+ return [
171
+ `Add to your nuxt config:`,
172
+ ``,
173
+ ` modules: ['@hover-dev/nuxt'],`,
174
+ ].join('\n');
175
+ case 'webpack':
176
+ return [
177
+ `Add to your webpack config:`,
178
+ ``,
179
+ ` const { HoverPlugin } = require('webpack-plugin-hover');`,
180
+ ` // ...`,
181
+ ` plugins: [..., new HoverPlugin()],`,
182
+ ].join('\n');
183
+ }
184
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@hover-dev/cli",
3
+ "version": "0.3.0",
4
+ "description": "CLI for Hover. Detects your bundler, installs the right Hover integration package, and wires it into your config — one command.",
5
+ "license": "Apache-2.0",
6
+ "author": "Hyperyond",
7
+ "homepage": "https://github.com/Hyperyond/Hover#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Hyperyond/Hover.git",
11
+ "directory": "packages/cli"
12
+ },
13
+ "bugs": "https://github.com/Hyperyond/Hover/issues",
14
+ "keywords": [
15
+ "hover",
16
+ "cli",
17
+ "scaffold",
18
+ "vite",
19
+ "astro",
20
+ "nuxt",
21
+ "webpack",
22
+ "playwright",
23
+ "claude"
24
+ ],
25
+ "type": "module",
26
+ "main": "dist/index.js",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md"
36
+ ],
37
+ "dependencies": {
38
+ "magicast": "^0.5.0"
39
+ },
40
+ "publishConfig": {
41
+ "registry": "https://registry.npmjs.org",
42
+ "access": "public"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc -p tsconfig.build.json",
46
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
47
+ },
48
+ "types": "dist/index.d.ts",
49
+ "bin": {
50
+ "hover": "dist/index.js"
51
+ }
52
+ }