vite-plus 0.1.0-alpha.0 → 0.1.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.
Files changed (45) hide show
  1. package/dist/bin.d.ts +1 -1
  2. package/dist/bin.js +7 -3
  3. package/dist/config/agent.d.ts +9 -0
  4. package/dist/config/agent.js +190 -0
  5. package/dist/config/bin.d.ts +1 -0
  6. package/dist/config/bin.js +79 -0
  7. package/dist/config/hooks.d.ts +6 -0
  8. package/dist/config/hooks.js +101 -0
  9. package/dist/global/browser-CY4NBwxR.js +6500 -0
  10. package/dist/global/browser-DFpJ6sKb.js +3 -0
  11. package/dist/global/chunk-CtfvYSle.js +48 -0
  12. package/dist/global/cli-truncate-BxinOqz5.js +187 -0
  13. package/dist/global/{init.js → config.js} +212 -56
  14. package/dist/global/create.js +35 -11
  15. package/dist/global/{help-DeHOTK5z.js → json-Bfvtp2rL.js} +20 -71
  16. package/dist/global/lib-CibYHP32.js +99 -0
  17. package/dist/global/log-update-DdU6_LCN.js +583 -0
  18. package/dist/global/migrate.js +37 -8
  19. package/dist/global/package-Pq2biU7_.js +47 -0
  20. package/dist/global/{agent-BQgTGptV.js → prompts-CAIahN1u.js} +274 -149
  21. package/dist/global/slice-ansi-BhwAwMdF.js +144 -0
  22. package/dist/global/src-C6aLHRsS.js +331 -0
  23. package/dist/global/staged.js +9036 -0
  24. package/dist/global/strip-ansi-BL-dgd7n.js +245 -0
  25. package/dist/global/terminal-Cb-NuRkb.js +102 -0
  26. package/dist/global/version.js +3 -2
  27. package/dist/global/{workspace-DxLkU2Bw.js → workspace-De4OKHV7.js} +606 -6913
  28. package/dist/global/wrap-ansi-BJxjUEQR.js +4 -0
  29. package/dist/global/wrap-ansi-Iww6Ak1s.js +208 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/pack-bin.js +2 -2
  32. package/dist/resolve-vite-config.d.ts +4 -0
  33. package/dist/resolve-vite-config.js +11 -4
  34. package/dist/staged/bin.d.ts +1 -0
  35. package/dist/staged/bin.js +183 -0
  36. package/dist/staged-config.d.ts +1 -0
  37. package/dist/staged-config.js +0 -0
  38. package/dist/utils/agent.d.ts +2 -1
  39. package/dist/utils/agent.js +21 -11
  40. package/dist/utils/prompts.d.ts +5 -1
  41. package/dist/utils/prompts.js +29 -2
  42. package/package.json +11 -10
  43. package/rules/vite-prepare.yml +8 -0
  44. package/rules/vite-tools.yml +10 -0
  45. package/dist/global/terminal-dIO5Uf8n.js +0 -96
package/dist/bin.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Unified entry point for both the local CLI (via bin/vp) and the global CLI (via Rust vp binary).
3
3
  *
4
- * Global commands (create, migrate, init, mcp, --version) are handled by rolldown-bundled modules.
4
+ * Global commands (create, migrate, config, mcp, staged, --version) are handled by rolldown-bundled modules.
5
5
  * All other commands are delegated to the Rust core through NAPI bindings, which
6
6
  * uses JavaScript tool resolver functions to locate tool binaries.
7
7
  *
package/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Unified entry point for both the local CLI (via bin/vp) and the global CLI (via Rust vp binary).
3
3
  *
4
- * Global commands (create, migrate, init, mcp, --version) are handled by rolldown-bundled modules.
4
+ * Global commands (create, migrate, config, mcp, staged, --version) are handled by rolldown-bundled modules.
5
5
  * All other commands are delegated to the Rust core through NAPI bindings, which
6
6
  * uses JavaScript tool resolver functions to locate tool binaries.
7
7
  *
@@ -35,9 +35,9 @@ else if (command === 'migrate') {
35
35
  // @ts-ignore — rolldown output
36
36
  await import('./global/migrate.js');
37
37
  }
38
- else if (command === 'init') {
38
+ else if (command === 'config') {
39
39
  // @ts-ignore — rolldown output
40
- await import('./global/init.js');
40
+ await import('./global/config.js');
41
41
  }
42
42
  else if (command === 'mcp') {
43
43
  // @ts-ignore — rolldown output
@@ -47,6 +47,10 @@ else if (command === '--version' || command === '-V') {
47
47
  // @ts-ignore — rolldown output
48
48
  await import('./global/version.js');
49
49
  }
50
+ else if (command === 'staged') {
51
+ // @ts-ignore — rolldown output
52
+ await import('./global/staged.js');
53
+ }
50
54
  else {
51
55
  // All other commands — delegate to Rust core via NAPI binding
52
56
  run({
@@ -0,0 +1,9 @@
1
+ import { type AgentConfig } from '../utils/agent.js';
2
+ export interface AgentSetupSelection {
3
+ instructionFilePath: 'CLAUDE.md' | 'AGENTS.md';
4
+ agents: AgentConfig[];
5
+ }
6
+ export declare function resolveAgentSetup(root: string, interactive: boolean): Promise<AgentSetupSelection>;
7
+ export declare function hasExistingAgentInstructions(root: string): boolean;
8
+ export declare function injectAgentBlock(root: string, filePath: string): void;
9
+ export declare function setupMcpConfig(root: string, selectedAgents: AgentConfig[]): void;
@@ -0,0 +1,190 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import * as prompts from '@voidzero-dev/vite-plus-prompts';
4
+ import { detectAgents, getAgentById, } from '../utils/agent.js';
5
+ import { writeJsonFile, readJsonFile } from '../utils/json.js';
6
+ import { pkgRoot } from '../utils/path.js';
7
+ function detectInstructionFilePath(root, agentConfigs) {
8
+ if (agentConfigs.some((a) => a.skillsDir === '.claude/skills')) {
9
+ return 'CLAUDE.md';
10
+ }
11
+ if (existsSync(join(root, 'CLAUDE.md'))) {
12
+ return 'CLAUDE.md';
13
+ }
14
+ return 'AGENTS.md';
15
+ }
16
+ async function pickAgentWhenUndetected() {
17
+ const choice = await prompts.select({
18
+ message: 'Could not detect your coding agent. Which one are you using?',
19
+ options: [
20
+ { value: 'claude-code', label: 'Claude Code' },
21
+ { value: 'cursor', label: 'Cursor' },
22
+ { value: 'codex', label: 'Codex' },
23
+ { value: 'gemini-cli', label: 'Gemini CLI' },
24
+ { value: 'generic', label: 'Generic' },
25
+ ],
26
+ });
27
+ if (prompts.isCancel(choice)) {
28
+ prompts.cancel('Setup cancelled.');
29
+ process.exit(0);
30
+ }
31
+ if (choice === 'generic') {
32
+ return {
33
+ instructionFilePath: 'AGENTS.md',
34
+ agents: [],
35
+ };
36
+ }
37
+ const selected = getAgentById(choice);
38
+ if (!selected) {
39
+ return {
40
+ instructionFilePath: 'AGENTS.md',
41
+ agents: [],
42
+ };
43
+ }
44
+ return {
45
+ instructionFilePath: choice === 'claude-code' ? 'CLAUDE.md' : 'AGENTS.md',
46
+ agents: [selected],
47
+ };
48
+ }
49
+ export async function resolveAgentSetup(root, interactive) {
50
+ const detected = detectAgents(root);
51
+ if (detected.length > 0 || !interactive) {
52
+ return {
53
+ instructionFilePath: detectInstructionFilePath(root, detected),
54
+ agents: detected,
55
+ };
56
+ }
57
+ return pickAgentWhenUndetected();
58
+ }
59
+ // --- Version and template reading ---
60
+ function getOwnVersion() {
61
+ const pkg = JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf-8'));
62
+ if (typeof pkg.version !== 'string') {
63
+ throw new Error('vite-plus package.json is missing a "version" field');
64
+ }
65
+ return pkg.version;
66
+ }
67
+ function readAgentPrompt() {
68
+ return readFileSync(join(pkgRoot, 'AGENTS.md'), 'utf-8');
69
+ }
70
+ // --- Versioned injection ---
71
+ const MARKER_OPEN_RE = /<!--injected-by-vite-plus-v([\w.+-]+)-->/;
72
+ const MARKER_CLOSE = '<!--/injected-by-vite-plus-->';
73
+ const MARKER_BLOCK_RE = /<!--injected-by-vite-plus-v[\w.+-]+-->\n[\s\S]*?<!--\/injected-by-vite-plus-->/;
74
+ export function hasExistingAgentInstructions(root) {
75
+ for (const file of ['AGENTS.md', 'CLAUDE.md']) {
76
+ const fullPath = join(root, file);
77
+ if (existsSync(fullPath)) {
78
+ const content = readFileSync(fullPath, 'utf-8');
79
+ if (MARKER_OPEN_RE.test(content)) {
80
+ return true;
81
+ }
82
+ }
83
+ }
84
+ return false;
85
+ }
86
+ export function injectAgentBlock(root, filePath) {
87
+ const fullPath = join(root, filePath);
88
+ const version = getOwnVersion();
89
+ const promptContent = readAgentPrompt();
90
+ const openMarker = `<!--injected-by-vite-plus-v${version}-->`;
91
+ const block = `${openMarker}\n${promptContent}\n${MARKER_CLOSE}`;
92
+ if (existsSync(fullPath)) {
93
+ const existing = readFileSync(fullPath, 'utf-8');
94
+ const match = existing.match(MARKER_OPEN_RE);
95
+ if (match) {
96
+ if (match[1] === version) {
97
+ prompts.log.info(`${filePath} already has Vite+ instructions (v${version})`);
98
+ return;
99
+ }
100
+ // Replace existing block with updated version
101
+ const updated = existing.replace(MARKER_BLOCK_RE, block);
102
+ if (updated === existing) {
103
+ // Closing marker is missing or malformed — append fresh block
104
+ const separator = existing.endsWith('\n') ? '\n' : '\n\n';
105
+ writeFileSync(fullPath, existing + separator + block + '\n');
106
+ prompts.log.warn(`Existing Vite+ block in ${filePath} was malformed; appended fresh block`);
107
+ }
108
+ else {
109
+ writeFileSync(fullPath, updated);
110
+ prompts.log.success(`Updated Vite+ instructions in ${filePath} (v${match[1]} → v${version})`);
111
+ }
112
+ }
113
+ else {
114
+ // Append block to end of file
115
+ const separator = existing.endsWith('\n') ? '\n' : '\n\n';
116
+ writeFileSync(fullPath, existing + separator + block + '\n');
117
+ prompts.log.success(`Added Vite+ instructions to ${filePath}`);
118
+ }
119
+ }
120
+ else {
121
+ writeFileSync(fullPath, block + '\n');
122
+ prompts.log.success(`Created ${filePath} with Vite+ instructions`);
123
+ }
124
+ }
125
+ // --- MCP config ---
126
+ function writeMcpConfigForTarget(root, target) {
127
+ const fullPath = join(root, target.filePath);
128
+ let existing = {};
129
+ if (existsSync(fullPath)) {
130
+ try {
131
+ existing = readJsonFile(fullPath);
132
+ }
133
+ catch {
134
+ prompts.log.warn(`Could not parse ${target.filePath} — skipping MCP config. Please add the config manually.`);
135
+ return;
136
+ }
137
+ }
138
+ if (!existing[target.rootKey]) {
139
+ existing[target.rootKey] = {};
140
+ }
141
+ if (existing[target.rootKey]['vite-plus']) {
142
+ prompts.log.info(`${target.filePath} already has vite-plus MCP config`);
143
+ return;
144
+ }
145
+ existing[target.rootKey]['vite-plus'] = {
146
+ command: 'npx',
147
+ args: ['vp', 'mcp'],
148
+ ...target.extraFields,
149
+ };
150
+ mkdirSync(dirname(fullPath), { recursive: true });
151
+ writeJsonFile(fullPath, existing);
152
+ prompts.log.success(`Added vite-plus MCP server to ${target.filePath}`);
153
+ }
154
+ function pickMcpTarget(root, targets) {
155
+ if (targets.length === 1) {
156
+ return targets[0];
157
+ }
158
+ return targets.find((t) => existsSync(join(root, t.filePath))) ?? targets[0];
159
+ }
160
+ export function setupMcpConfig(root, selectedAgents) {
161
+ if (selectedAgents.length === 0) {
162
+ prompts.note(JSON.stringify({
163
+ 'vite-plus': {
164
+ command: 'npx',
165
+ args: ['vp', 'mcp'],
166
+ },
167
+ }, null, 2), 'Add this MCP server config to your agent');
168
+ return;
169
+ }
170
+ const mcpAgents = [];
171
+ const hintAgents = [];
172
+ for (const agent of selectedAgents) {
173
+ if (agent.mcpConfig) {
174
+ mcpAgents.push({ agent, targets: agent.mcpConfig });
175
+ }
176
+ else if (agent.mcpHint) {
177
+ hintAgents.push({ agent, hint: agent.mcpHint });
178
+ }
179
+ }
180
+ // Print hints for agents without project-level config
181
+ for (const { agent, hint } of hintAgents) {
182
+ prompts.log.info(`${agent.displayName}: ${hint}`);
183
+ }
184
+ // Write config for agents with project-level support
185
+ for (const { agent, targets } of mcpAgents) {
186
+ const target = pickMcpTarget(root, targets);
187
+ prompts.log.info(`${agent.displayName} MCP target: ${target.filePath}`);
188
+ writeMcpConfigForTarget(root, target);
189
+ }
190
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,79 @@
1
+ // Unified `vp config` command — merges the old `vp prepare` (hooks setup) and
2
+ // `vp init` (agent integration) into a single entry point.
3
+ //
4
+ // Interactive mode (TTY, no CI): prompts on first run, updates silently after.
5
+ // Non-interactive mode (scripts.prepare, CI, piped): runs everything by default.
6
+ import { existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import mri from 'mri';
9
+ import { vitePlusHeader } from '../../binding/index.js';
10
+ import { renderCliDoc } from '../utils/help.js';
11
+ import { defaultInteractive, promptGitHooks } from '../utils/prompts.js';
12
+ import { linkSkillsForSpecificAgents } from '../utils/skills.js';
13
+ import { log } from '../utils/terminal.js';
14
+ import { resolveAgentSetup, hasExistingAgentInstructions, injectAgentBlock, setupMcpConfig, } from './agent.js';
15
+ import { install } from './hooks.js';
16
+ async function main() {
17
+ const args = mri(process.argv.slice(3), {
18
+ boolean: ['help', 'hooks-only'],
19
+ string: ['hooks-dir'],
20
+ alias: { h: 'help' },
21
+ });
22
+ if (args.help) {
23
+ const helpMessage = renderCliDoc({
24
+ usage: 'vp config [OPTIONS]',
25
+ summary: 'Configure Vite+ for the current project (hooks + agent integration).',
26
+ sections: [
27
+ {
28
+ title: 'Options',
29
+ rows: [
30
+ {
31
+ label: '--hooks-dir <path>',
32
+ description: 'Custom hooks directory (default: .vite-hooks)',
33
+ },
34
+ { label: '-h, --help', description: 'Show this help message' },
35
+ ],
36
+ },
37
+ {
38
+ title: 'Environment',
39
+ rows: [{ label: 'VITE_GIT_HOOKS=0', description: 'Skip hook installation' }],
40
+ },
41
+ ],
42
+ });
43
+ log(vitePlusHeader() + '\n');
44
+ log(helpMessage);
45
+ return;
46
+ }
47
+ const dir = args['hooks-dir'];
48
+ const hooksOnly = args['hooks-only'];
49
+ const interactive = defaultInteractive();
50
+ const root = process.cwd();
51
+ // --- Step 1: Hooks setup ---
52
+ const hooksDir = dir ?? '.vite-hooks';
53
+ const isFirstHooksRun = !existsSync(join(root, hooksDir, 'pre-commit'));
54
+ let shouldSetupHooks = true;
55
+ if (interactive && isFirstHooksRun && !dir) {
56
+ // --hooks-dir implies agreement; only prompt when using default dir on first run
57
+ shouldSetupHooks = await promptGitHooks({ interactive });
58
+ }
59
+ if (shouldSetupHooks) {
60
+ const { message, isError } = install(dir);
61
+ if (message) {
62
+ log(message);
63
+ if (isError) {
64
+ process.exit(1);
65
+ }
66
+ }
67
+ }
68
+ // --- Step 2: Agent setup (skipped with --hooks-only or during prepare lifecycle) ---
69
+ if (!hooksOnly && process.env.npm_lifecycle_event !== 'prepare') {
70
+ const isFirstAgentRun = !hasExistingAgentInstructions(root);
71
+ const agentSetup = await resolveAgentSetup(root, interactive && isFirstAgentRun);
72
+ injectAgentBlock(root, agentSetup.instructionFilePath);
73
+ setupMcpConfig(root, agentSetup.agents);
74
+ if (agentSetup.agents.length > 0) {
75
+ linkSkillsForSpecificAgents(root, agentSetup.agents);
76
+ }
77
+ }
78
+ }
79
+ void main();
@@ -0,0 +1,6 @@
1
+ export declare function hookScript(dir: string): string;
2
+ export interface InstallResult {
3
+ message: string;
4
+ isError: boolean;
5
+ }
6
+ export declare function install(dir?: string): InstallResult;
@@ -0,0 +1,101 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ const HOOKS = [
5
+ 'pre-commit',
6
+ 'pre-merge-commit',
7
+ 'prepare-commit-msg',
8
+ 'commit-msg',
9
+ 'post-commit',
10
+ 'applypatch-msg',
11
+ 'pre-applypatch',
12
+ 'post-applypatch',
13
+ 'pre-rebase',
14
+ 'post-rewrite',
15
+ 'post-checkout',
16
+ 'post-merge',
17
+ 'pre-push',
18
+ 'pre-auto-gc',
19
+ ];
20
+ // Build nested dirname expression: depth 3 → dirname "$(dirname "$(dirname "$0"))"
21
+ function nestedDirname(depth) {
22
+ let expr = '"$0"';
23
+ for (let i = 0; i < depth; i++) {
24
+ expr = `"$(dirname ${expr})"`;
25
+ }
26
+ return expr;
27
+ }
28
+ // The shell script that dispatches to user-defined hooks in <dir>/
29
+ // `depth` = number of path segments in `dir` + 2 (for `_` subdir + hook filename)
30
+ export function hookScript(dir) {
31
+ // Count segments: ".vite-hooks" → 1, ".config/husky" → 2
32
+ // Filter out empty strings and '.' to handle paths like "./.config/husky"
33
+ const segments = dir.split('/').filter((s) => s !== '' && s !== '.').length;
34
+ const depth = segments + 2; // +2 for _ subdir and hook filename
35
+ const rootExpr = nestedDirname(depth);
36
+ return `#!/usr/bin/env sh
37
+ { [ "$HUSKY" = "2" ] || [ "$VITE_GIT_HOOKS" = "2" ]; } && set -x
38
+ n=$(basename "$0")
39
+ s=$(dirname "$(dirname "$0")")/$n
40
+
41
+ [ ! -f "$s" ] && exit 0
42
+
43
+ i="\${XDG_CONFIG_HOME:-$HOME/.config}/vite-plus/hooks-init.sh"
44
+ [ ! -f "$i" ] && i="\${XDG_CONFIG_HOME:-$HOME/.config}/husky/init.sh"
45
+ [ -f "$i" ] && . "$i"
46
+
47
+ { [ "\${HUSKY-}" = "0" ] || [ "\${VITE_GIT_HOOKS-}" = "0" ]; } && exit 0
48
+
49
+ d=${rootExpr}
50
+ export PATH="$d/node_modules/.bin:$PATH"
51
+ sh -e "$s" "$@"
52
+ c=$?
53
+
54
+ [ $c != 0 ] && echo "VITE+ - $n script failed (code $c)"
55
+ [ $c = 127 ] && echo "VITE+ - command not found in PATH=$PATH"
56
+ exit $c`;
57
+ }
58
+ export function install(dir = '.vite-hooks') {
59
+ if (process.env.HUSKY === '0' || process.env.VITE_GIT_HOOKS === '0') {
60
+ return { message: 'skip install (git hooks disabled)', isError: false };
61
+ }
62
+ if (dir.includes('..')) {
63
+ return { message: '.. not allowed', isError: false };
64
+ }
65
+ // Use --show-prefix to get the relative path from git root to cwd.
66
+ // This avoids Windows path normalization issues (MSYS paths, 8.3 short names)
67
+ // that make path.relative() unreliable across git and Node.js representations.
68
+ const prefixResult = spawnSync('git', ['rev-parse', '--show-prefix']);
69
+ if (prefixResult.status == null) {
70
+ return { message: 'git command not found', isError: true };
71
+ }
72
+ if (prefixResult.status !== 0) {
73
+ return { message: ".git can't be found", isError: false };
74
+ }
75
+ const internal = (x = '') => join(dir, '_', x);
76
+ const rel = prefixResult.stdout.toString().trim().replace(/\/$/, '');
77
+ const target = rel ? `${rel}/${dir}/_` : `${dir}/_`;
78
+ const checkResult = spawnSync('git', ['config', '--local', 'core.hooksPath']);
79
+ const existingHooksPath = checkResult.status === 0 ? checkResult.stdout?.toString().trim() : '';
80
+ if (existingHooksPath && existingHooksPath !== target) {
81
+ return {
82
+ message: `core.hooksPath is already set to "${existingHooksPath}", skipping`,
83
+ isError: false,
84
+ };
85
+ }
86
+ const { status, stderr } = spawnSync('git', ['config', 'core.hooksPath', target]);
87
+ if (status == null) {
88
+ return { message: 'git command not found', isError: true };
89
+ }
90
+ if (status) {
91
+ return { message: '' + stderr, isError: true };
92
+ }
93
+ rmSync(internal('husky.sh'), { force: true });
94
+ mkdirSync(internal(), { recursive: true });
95
+ writeFileSync(internal('.gitignore'), '*');
96
+ writeFileSync(internal('h'), hookScript(dir), { mode: 0o755 });
97
+ for (const hook of HOOKS) {
98
+ writeFileSync(internal(hook), `#!/usr/bin/env sh\n. "$(dirname "$0")/h"`, { mode: 0o755 });
99
+ }
100
+ return { message: '', isError: false };
101
+ }