vite-plus 0.2.7 → 0.2.9

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 (89) hide show
  1. package/AGENTS.md +7 -0
  2. package/LICENSE +5 -1
  3. package/README.md +9 -3
  4. package/bin/vpr +3 -2
  5. package/binding/index.cjs +193 -80
  6. package/binding/index.d.cts +106 -11
  7. package/dist/agent-DQU8uS6P.js +416 -0
  8. package/dist/bin.js +1388 -23
  9. package/dist/{cli-truncate-BVOlIIPy.js → cli-truncate-Bg3RDXpi.js} +0 -2
  10. package/dist/client/bundledDevClient.mjs +1 -0
  11. package/dist/config/bin.js +32 -133
  12. package/dist/{constants-BsuzPowC.js → constants-CG513DRa.js} +10 -2
  13. package/dist/create/bin.js +255 -268
  14. package/dist/{define-config-C2KfLHpC.js → define-config-CV4bQG_8.js} +3 -3
  15. package/dist/{define-config-eLQH9us2.d.ts → define-config-DWoAkNdp.d.ts} +16 -0
  16. package/dist/{define-config-DLh9fE95.cjs → define-config-DyNLA_f0.cjs} +1 -1
  17. package/dist/define-config.cjs +1 -1
  18. package/dist/define-config.d.ts +1 -1
  19. package/dist/define-config.js +1 -1
  20. package/dist/{dist-CKz3vcoG.js → dist-BDYZP12R.js} +196 -157
  21. package/dist/dist-Dqv1-Clg.js +3 -0
  22. package/dist/{agent-CPwWJ4qf.js → editor-g4PosU99.js} +819 -628
  23. package/dist/hooks/bin.d.ts +1 -0
  24. package/dist/hooks/bin.js +130 -0
  25. package/dist/hooks-pNCBWtFT.js +570 -0
  26. package/dist/index.cjs +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +1 -1
  29. package/dist/{json-DiRs8ceZ.js → json-qlK6UH0r.js} +40 -38
  30. package/dist/migration/bin.js +65 -58
  31. package/dist/{oxlint-plugin-config-Bxgc4cwF.js → oxlint-plugin-config-BEZ9IUf7.js} +1 -1
  32. package/dist/oxlint-plugin.js +1 -1
  33. package/dist/pack-bin.js +1 -1
  34. package/dist/{package-Ds3uThLb.js → package-B4T8RGMG.js} +2 -2
  35. package/dist/{tsconfig-Jia2vgMG.js → prompts-CHz_98bJ.js} +169 -219
  36. package/dist/{resolve-vite-config-BiuLZnn8.js → resolve-vite-config-Dmeyeyj-.js} +2 -9
  37. package/dist/{rolldown-runtime-C7HZzL1F.js → rolldown-runtime-CMFfr-1z.js} +1 -1
  38. package/dist/staged/bin.js +241 -226
  39. package/dist/terminal-Bz-ps6rJ.js +128 -0
  40. package/dist/test/browser/providers/playwright/context.d.ts +1 -1
  41. package/dist/test/browser/providers/playwright.d.ts +6 -6
  42. package/dist/test/browser/providers/preview/context.d.ts +1 -1
  43. package/dist/test/browser/providers/preview.d.ts +4 -4
  44. package/dist/test/browser/providers/webdriverio/context.d.ts +1 -1
  45. package/dist/test/browser/providers/webdriverio.d.ts +6 -6
  46. package/dist/test/browser-playwright/context.d.ts +1 -1
  47. package/dist/test/browser-playwright.d.ts +6 -6
  48. package/dist/test/browser-preview/context.d.ts +1 -1
  49. package/dist/test/browser-preview.d.ts +4 -4
  50. package/dist/test/browser-webdriverio/context.d.ts +1 -1
  51. package/dist/test/browser-webdriverio.d.ts +6 -6
  52. package/dist/test/context.d.ts +3 -3
  53. package/dist/test/locators.d.ts +1 -1
  54. package/dist/toolchain.d.ts +25 -0
  55. package/dist/toolchain.js +194 -0
  56. package/dist/toolchain.json +193 -0
  57. package/dist/tsconfig-7v_BHagU.js +151 -0
  58. package/dist/{tsgolint-path-B-yOos8p.js → tsgolint-path-CbDReEOx.js} +5 -3
  59. package/dist/tsgolint-path.js +1 -1
  60. package/dist/version.js +6 -6
  61. package/dist/versions.d.ts +3 -3
  62. package/dist/versions.js +6 -6
  63. package/docs/config/index.md +13 -0
  64. package/docs/config/staged.md +2 -1
  65. package/docs/guide/ci.md +37 -4
  66. package/docs/guide/commit-hooks.md +112 -21
  67. package/docs/guide/create.md +7 -4
  68. package/docs/guide/env.md +8 -4
  69. package/docs/guide/fmt.md +2 -2
  70. package/docs/guide/github-actions-cache.md +5 -1
  71. package/docs/guide/ide-integration.md +61 -2
  72. package/docs/guide/index.md +4 -2
  73. package/docs/guide/install.md +27 -0
  74. package/docs/guide/installer-env-vars.md +14 -3
  75. package/docs/guide/lint.md +3 -1
  76. package/docs/guide/migrate.md +49 -4
  77. package/docs/guide/monorepo.md +98 -10
  78. package/docs/guide/troubleshooting.md +12 -3
  79. package/docs/guide/upgrade.md +27 -4
  80. package/docs/package.json +1 -1
  81. package/docs/pnpm-workspace.yaml +7 -0
  82. package/docs/vite.config.ts +3 -0
  83. package/package.json +21 -23
  84. package/rules/vite-tools.yml +0 -7
  85. package/dist/dist-CtM2JRbM.js +0 -3
  86. package/dist/editor-BbT1lTNc.js +0 -675
  87. package/dist/help-YP84FSEz.js +0 -61
  88. package/dist/terminal-uTv0ZaMr.js +0 -33
  89. package/dist/{log-update-DHZRyJ2m.js → log-update-CoW8Z4Dl.js} +1 -1
@@ -2137,6 +2137,7 @@ export interface BindingDevtoolsOptions {
2137
2137
  }
2138
2138
 
2139
2139
  export interface BindingDevWatchOptions {
2140
+ enabled?: boolean;
2140
2141
  skipWrite?: boolean;
2141
2142
  usePolling?: boolean;
2142
2143
  pollInterval?: number;
@@ -2332,7 +2333,9 @@ export interface BindingEsmExternalRequirePluginConfig {
2332
2333
  export interface BindingExperimentalDevModeOptions {
2333
2334
  host?: string;
2334
2335
  port?: number;
2335
- implement?: string;
2336
+ implement: string;
2337
+ /** @deprecated Common runtime injection will be disabled by default in the future. */
2338
+ skipCommonRuntimeInjection?: boolean;
2336
2339
  lazy?: boolean;
2337
2340
  }
2338
2341
 
@@ -2388,6 +2391,23 @@ export interface BindingHookJsLoadOutput {
2388
2391
 
2389
2392
  export interface BindingHookJsResolveIdOptions {
2390
2393
  isEntry?: boolean;
2394
+ /**
2395
+ * - `import-statement`: `import { foo } from './lib.js';`
2396
+ * - `dynamic-import`: `import('./lib.js')`
2397
+ * - `require-call`: `require('./lib.js')`
2398
+ * - `import-rule`: `@import 'bg-color.css'`
2399
+ * - `url-token`: `url('./icon.png')`
2400
+ * - `new-url`: `new URL('./worker.js', import.meta.url)`
2401
+ * - `hot-accept`: `import.meta.hot.accept('./lib.js', () => {})`
2402
+ */
2403
+ kind?:
2404
+ | 'import-statement'
2405
+ | 'dynamic-import'
2406
+ | 'require-call'
2407
+ | 'import-rule'
2408
+ | 'url-token'
2409
+ | 'new-url'
2410
+ | 'hot-accept';
2391
2411
  scan?: boolean;
2392
2412
  custom?: BindingVitePluginCustom;
2393
2413
  }
@@ -2414,6 +2434,24 @@ export interface BindingHookRenderChunkOutput {
2414
2434
  map?: BindingSourcemap | null;
2415
2435
  }
2416
2436
 
2437
+ export interface BindingHookResolveFileUrlArgs {
2438
+ /** Preliminary filename of the chunk containing the reference. */
2439
+ chunkId: string;
2440
+ /** Filename of the emitted file, relative to the output directory. */
2441
+ fileName: string;
2442
+ format: 'es' | 'cjs' | 'iife' | 'umd';
2443
+ /** Id of the module containing the `import.meta.ROLLDOWN_FILE_URL_*` reference. */
2444
+ moduleId: string;
2445
+ referenceId: string;
2446
+ /** Path from the chunk to the emitted file. */
2447
+ relativePath: string;
2448
+ /**
2449
+ * The `<urlId>` of `import.meta.ROLLDOWN_FILE_URL_<referenceId>_<urlId>`, if present.
2450
+ * Only the rolldown-specific form carries it; the `ROLLUP_FILE_URL_` alias never does.
2451
+ */
2452
+ urlId?: string;
2453
+ }
2454
+
2417
2455
  export interface BindingHookResolveIdExtraArgs {
2418
2456
  custom?: number;
2419
2457
  isEntry: boolean;
@@ -2461,6 +2499,14 @@ export interface BindingHookTransformOutput {
2461
2499
  moduleType?: string;
2462
2500
  }
2463
2501
 
2502
+ export interface BindingHotUpdateArgs {
2503
+ kind: 'create' | 'update' | 'delete';
2504
+ /** Normalized absolute path of the changed file. */
2505
+ file: string;
2506
+ /** The affected module ids as currently computed (raw module ids). */
2507
+ modules: Array<string>;
2508
+ }
2509
+
2464
2510
  export interface BindingIndentOptions {
2465
2511
  exclude?: Array<Array<number>> | Array<number>;
2466
2512
  }
@@ -2514,6 +2560,8 @@ export interface BindingInputOptions {
2514
2560
  keepNames?: boolean;
2515
2561
  checks?: BindingChecksOptions;
2516
2562
  deferSyncScanData?: undefined | (() => BindingDeferSyncScanData[]);
2563
+ /** Asked for while the build is closing, so what it returns includes `closeBundle`. */
2564
+ pluginTimings?: undefined | (() => BindingPluginTimingsMeasurement);
2517
2565
  makeAbsoluteExternalsRelative?: BindingMakeAbsoluteExternalsRelative;
2518
2566
  devtools?: BindingDevtoolsOptions;
2519
2567
  invalidateJsSideCache?: () => void;
@@ -2549,6 +2597,13 @@ export interface BindingJsWatchChangeEvent {
2549
2597
  export interface BindingLazyChunkOutput {
2550
2598
  code: string;
2551
2599
  filename: string;
2600
+ /**
2601
+ * The chunk's sourcemap, when `sourcemap` is `File` or `Hidden`. Serve it
2602
+ * under `sourcemapFilename`, which is what the chunk's `sourceMappingURL`
2603
+ * refers to.
2604
+ */
2605
+ sourcemap?: string;
2606
+ sourcemapFilename?: string;
2552
2607
  }
2553
2608
 
2554
2609
  export interface BindingLog {
@@ -2691,6 +2746,8 @@ export interface BindingOutputs {
2691
2746
 
2692
2747
  export interface BindingOverwriteOptions {
2693
2748
  contentOnly?: boolean;
2749
+ /** Stores the replaced content in the generated sourcemap's `names` field. */
2750
+ storeName?: boolean;
2694
2751
  }
2695
2752
 
2696
2753
  export interface BindingPluginContextResolvedId {
@@ -2785,6 +2842,11 @@ export interface BindingPluginOptions {
2785
2842
  chunk: BindingRenderedChunk,
2786
2843
  ) => MaybePromise<void | string>;
2787
2844
  augmentChunkHashMeta?: BindingPluginHookMeta;
2845
+ resolveFileUrl?: (
2846
+ ctx: BindingPluginContext,
2847
+ args: BindingHookResolveFileUrlArgs,
2848
+ ) => MaybePromise<void | string | null>;
2849
+ resolveFileUrlMeta?: BindingPluginHookMeta;
2788
2850
  renderStart?: (ctx: BindingPluginContext, opts: BindingNormalizedOptions) => void;
2789
2851
  renderStartMeta?: BindingPluginHookMeta;
2790
2852
  renderError?: (ctx: BindingPluginContext, error: BindingError[]) => void;
@@ -2810,6 +2872,11 @@ export interface BindingPluginOptions {
2810
2872
  event: string,
2811
2873
  ) => MaybePromise<VoidNullable>;
2812
2874
  watchChangeMeta?: BindingPluginHookMeta;
2875
+ hotUpdate?: (
2876
+ ctx: BindingPluginContext,
2877
+ args: BindingHotUpdateArgs,
2878
+ ) => MaybePromise<VoidNullable<Array<string>>>;
2879
+ hotUpdateMeta?: BindingPluginHookMeta;
2813
2880
  closeWatcher?: (ctx: BindingPluginContext) => MaybePromise<VoidNullable>;
2814
2881
  closeWatcherMeta?: BindingPluginHookMeta;
2815
2882
  banner?: (ctx: BindingPluginContext, chunk: BindingRenderedChunk) => void;
@@ -2827,6 +2894,33 @@ export declare const enum BindingPluginOrder {
2827
2894
  Post = 1,
2828
2895
  }
2829
2896
 
2897
+ /**
2898
+ * What one callback cost, measured inside it on the JavaScript side — the one place the
2899
+ * measurement exists, since this side can only bracket dispatch and completion.
2900
+ */
2901
+ export interface BindingPluginTiming {
2902
+ /** The plugin the callback belongs to, or the options it was configured on. */
2903
+ owner: string;
2904
+ kind: 'plugin' | 'outputOption' | 'inputOption';
2905
+ hook: string;
2906
+ calls: number;
2907
+ ms: number;
2908
+ maxInFlight: number;
2909
+ /** How much of `ms` is double counted because calls overlapped. */
2910
+ overlapMs: number;
2911
+ /**
2912
+ * Whether `ms` may be compared against another row's — false once two calls of this hook
2913
+ * overlapped, because then their spans cover work each other was doing.
2914
+ */
2915
+ rankable: boolean;
2916
+ }
2917
+
2918
+ export interface BindingPluginTimingsMeasurement {
2919
+ /** Wall time in which any measured callback was running, counting overlap once. */
2920
+ busyMs: number;
2921
+ rows: Array<BindingPluginTiming>;
2922
+ }
2923
+
2830
2924
  export interface BindingPluginWithIndex {
2831
2925
  index: number;
2832
2926
  plugin: BindingPluginOptions;
@@ -2982,6 +3076,8 @@ export interface BindingTsconfigResult {
2982
3076
 
2983
3077
  export interface BindingUpdateOptions {
2984
3078
  overwrite?: boolean;
3079
+ /** Stores the replaced content in the generated sourcemap's `names` field. */
3080
+ storeName?: boolean;
2985
3081
  }
2986
3082
 
2987
3083
  export interface BindingViteAliasPluginAlias {
@@ -3241,19 +3337,14 @@ export declare function resolveTsconfig(
3241
3337
  ): BindingTsconfigResult | null;
3242
3338
 
3243
3339
  /**
3244
- * Shutdown the tokio runtime manually.
3340
+ * Release one holder of the tokio runtime, shutting it down once none are left.
3245
3341
  *
3246
3342
  * This is required for the wasm target with `tokio_unstable` cfg.
3247
3343
  * In the wasm runtime, the `park` threads will hang there until the tokio::Runtime is shutdown.
3248
3344
  */
3249
3345
  export declare function shutdownAsyncRuntime(): void;
3250
3346
 
3251
- /**
3252
- * Start the async runtime manually.
3253
- *
3254
- * This is required when the async runtime is shutdown manually.
3255
- * Usually it's used in test.
3256
- */
3347
+ /** Acquire one holder of the tokio runtime, starting it if it is not running. */
3257
3348
  export declare function startAsyncRuntime(): void;
3258
3349
 
3259
3350
  export interface ViteImportGlobMeta {
@@ -3288,6 +3379,10 @@ export interface CliOptions {
3288
3379
  cwd?: string;
3289
3380
  /** CLI arguments (should be process.argv.slice(2) from JavaScript) */
3290
3381
  args?: Array<string>;
3382
+ /** Generated toolchain manifest shipped with this vite-plus package. */
3383
+ toolchainManifestPath: string;
3384
+ /** Root directory of this vite-plus package. */
3385
+ vitePlusPackagePath: string;
3291
3386
  /** Read the vite.config.ts in the Node.js side and return the `lint` and `fmt` config JSON string back to the Rust side */
3292
3387
  resolveUniversalViteConfig: (err: Error | null, arg: string) => Promise<string>;
3293
3388
  }
@@ -3573,7 +3668,7 @@ export declare function rewriteScripts(scriptsJson: string, rulesYaml: string):
3573
3668
  * Main entry point for the CLI, called from JavaScript.
3574
3669
  *
3575
3670
  * This is an async function that spawns a new thread for the non-Send async code
3576
- * from vite_task, while allowing the NAPI async context to continue running
3671
+ * from vt, while allowing the NAPI async context to continue running
3577
3672
  * and process JavaScript callbacks (via ThreadsafeFunction).
3578
3673
  */
3579
3674
  export declare function run(options: CliOptions): Promise<number>;
@@ -3581,7 +3676,7 @@ export declare function run(options: CliOptions): Promise<number>;
3581
3676
  /**
3582
3677
  * Run a command with fspy tracking, callable from JavaScript.
3583
3678
  *
3584
- * This function wraps `vite_command::run_command_with_fspy` to provide
3679
+ * This function wraps `vp_command::run_command_with_fspy` to provide
3585
3680
  * a JavaScript-friendly interface for executing commands and tracking
3586
3681
  * their file system accesses.
3587
3682
  *
@@ -3651,7 +3746,7 @@ export interface RunCommandResult {
3651
3746
  /**
3652
3747
  * Whether the Vite+ banner should be emitted in the current environment.
3653
3748
  *
3654
- * Mirrors `vite_shared::header::should_print_header` so both CLIs apply
3749
+ * Mirrors `vp_shared::header::should_print_header` so both CLIs apply
3655
3750
  * the same TTY + git-hook gating without duplicating the rules in JS.
3656
3751
  */
3657
3752
  export declare function shouldPrintVitePlusHeader(): boolean;
@@ -0,0 +1,416 @@
1
+ import { A as isCancel, C as log, E as select, w as multiselect } from "./prompts-CHz_98bJ.js";
2
+ import path from "node:path";
3
+ import { styleText } from "node:util";
4
+ import fs from "node:fs";
5
+ import fsPromises from "node:fs/promises";
6
+ //#region src/utils/path.ts
7
+ function findPkgRoot() {
8
+ let dir = import.meta.dirname;
9
+ while (dir !== path.dirname(dir)) {
10
+ if (fs.existsSync(path.join(dir, "package.json"))) return dir;
11
+ dir = path.dirname(dir);
12
+ }
13
+ return dir;
14
+ }
15
+ const pkgRoot = findPkgRoot();
16
+ const templatesDir = path.join(pkgRoot, "templates");
17
+ const rulesDir = path.join(pkgRoot, "rules");
18
+ function displayRelative(to, from = process.cwd()) {
19
+ return path.relative(from, to).replaceAll("\\", "/");
20
+ }
21
+ //#endregion
22
+ //#region src/utils/agent.ts
23
+ const AGENTS = [
24
+ {
25
+ id: "agents",
26
+ label: "AGENTS.md",
27
+ targetPath: "AGENTS.md",
28
+ hint: "Codex, Amp, OpenCode, and similar agents",
29
+ aliases: [
30
+ "agents.md",
31
+ "chatgpt",
32
+ "chatgpt-codex",
33
+ "codex",
34
+ "amp",
35
+ "kilo",
36
+ "kilo-code",
37
+ "kiro",
38
+ "kiro-cli",
39
+ "opencode",
40
+ "other"
41
+ ]
42
+ },
43
+ {
44
+ id: "claude",
45
+ label: "CLAUDE.md",
46
+ targetPath: "CLAUDE.md",
47
+ hint: "Claude Code",
48
+ aliases: ["claude.md", "claude-code"]
49
+ },
50
+ {
51
+ id: "gemini",
52
+ label: "GEMINI.md",
53
+ targetPath: "GEMINI.md",
54
+ hint: "Gemini CLI",
55
+ aliases: ["gemini.md", "gemini-cli"]
56
+ },
57
+ {
58
+ id: "copilot",
59
+ label: ".github/copilot-instructions.md",
60
+ targetPath: ".github/copilot-instructions.md",
61
+ hint: "GitHub Copilot",
62
+ aliases: ["github-copilot", "copilot-instructions.md"]
63
+ },
64
+ {
65
+ id: "cursor",
66
+ label: ".cursor/rules/viteplus.mdc",
67
+ targetPath: ".cursor/rules/viteplus.mdc",
68
+ hint: "Cursor",
69
+ aliases: ["viteplus.mdc"]
70
+ },
71
+ {
72
+ id: "jetbrains",
73
+ label: ".aiassistant/rules/viteplus.md",
74
+ targetPath: ".aiassistant/rules/viteplus.md",
75
+ hint: "JetBrains AI Assistant",
76
+ aliases: [
77
+ "jetbrains",
78
+ "jetbrains-ai-assistant",
79
+ "aiassistant",
80
+ "viteplus.md"
81
+ ]
82
+ }
83
+ ];
84
+ const AGENT_DEFAULT_ID = "agents";
85
+ const AGENT_STANDARD_PATH = "AGENTS.md";
86
+ const COPILOT_AGENT_ID = "copilot";
87
+ const COPILOT_SETUP_WORKFLOW_PATH = ".github/workflows/copilot-setup-steps.yml";
88
+ const AGENT_INSTRUCTIONS_START_MARKER = "<!--VITE PLUS START-->";
89
+ const AGENT_INSTRUCTIONS_END_MARKER = "<!--VITE PLUS END-->";
90
+ const AGENT_ALIASES = Object.fromEntries(AGENTS.flatMap((option) => (option.aliases ?? []).map((alias) => [normalizeAgentName(alias), option.id])));
91
+ const COPILOT_SETUP_WORKFLOW_CONTENT = `name: "Copilot Setup Steps"
92
+
93
+ on:
94
+ workflow_dispatch:
95
+ push:
96
+ paths:
97
+ - .github/workflows/copilot-setup-steps.yml
98
+ pull_request:
99
+ paths:
100
+ - .github/workflows/copilot-setup-steps.yml
101
+
102
+ jobs:
103
+ copilot-setup-steps:
104
+ runs-on: ubuntu-latest
105
+ permissions:
106
+ contents: read
107
+ steps:
108
+ - name: Checkout code
109
+ uses: actions/checkout@v6
110
+ with:
111
+ persist-credentials: false
112
+ - name: Set up Vite+
113
+ uses: voidzero-dev/setup-vp@v1
114
+ with:
115
+ cache: true
116
+ run-install: true
117
+ - name: Verify Vite+
118
+ run: vp --version
119
+ `;
120
+ async function selectAgentTargets({ interactive, agent, onCancel }) {
121
+ if (agent === false) return {
122
+ targetPaths: void 0,
123
+ selectedAgents: []
124
+ };
125
+ if (interactive && !agent) {
126
+ const selectedAgentIds = await multiselect({
127
+ message: "Which coding agent instruction files should Vite+ create?",
128
+ options: AGENTS.map((option) => ({
129
+ label: option.label,
130
+ value: option.id,
131
+ hint: option.hint
132
+ })),
133
+ initialValues: [AGENT_DEFAULT_ID],
134
+ required: false
135
+ });
136
+ if (isCancel(selectedAgentIds)) {
137
+ onCancel();
138
+ return {
139
+ targetPaths: void 0,
140
+ selectedAgents: []
141
+ };
142
+ }
143
+ if (selectedAgentIds.length === 0) return {
144
+ targetPaths: void 0,
145
+ selectedAgents: []
146
+ };
147
+ const selectedAgents = resolveAgentOptions(selectedAgentIds);
148
+ return {
149
+ targetPaths: getAgentTargetPaths(selectedAgents),
150
+ selectedAgents
151
+ };
152
+ }
153
+ const selectedAgents = resolveAgentOptions(agent ?? AGENT_DEFAULT_ID);
154
+ return {
155
+ targetPaths: getAgentTargetPaths(selectedAgents),
156
+ selectedAgents
157
+ };
158
+ }
159
+ async function selectAgentTargetPaths({ interactive, agent, onCancel }) {
160
+ return (await selectAgentTargets({
161
+ interactive,
162
+ agent,
163
+ onCancel
164
+ })).targetPaths;
165
+ }
166
+ function detectExistingAgentTargetPaths(projectRoot) {
167
+ const detectedPaths = [];
168
+ const seenTargetPaths = /* @__PURE__ */ new Set();
169
+ for (const option of AGENTS) {
170
+ if (seenTargetPaths.has(option.targetPath)) continue;
171
+ seenTargetPaths.add(option.targetPath);
172
+ const targetPath = path.join(projectRoot, option.targetPath);
173
+ if (fs.existsSync(targetPath) && !fs.lstatSync(targetPath).isSymbolicLink()) detectedPaths.push(option.targetPath);
174
+ }
175
+ return detectedPaths.length > 0 ? detectedPaths : void 0;
176
+ }
177
+ /**
178
+ * Silently update agent instruction files that contain Vite+ markers.
179
+ * - No agent files → no writes
180
+ * - No Vite+ markers → no writes
181
+ * - Markers present, content up to date → no writes
182
+ * - Markers present, content outdated → update marked section
183
+ */
184
+ function updateExistingAgentInstructions(projectRoot) {
185
+ const targetPaths = detectExistingAgentTargetPaths(projectRoot);
186
+ if (!targetPaths) return;
187
+ const templatePath = path.join(pkgRoot, "AGENTS.md");
188
+ if (!fs.existsSync(templatePath)) return;
189
+ const templateContent = fs.readFileSync(templatePath, "utf-8");
190
+ for (const targetPath of targetPaths) try {
191
+ const fullPath = path.join(projectRoot, targetPath);
192
+ const existing = fs.readFileSync(fullPath, "utf-8");
193
+ const updated = replaceMarkedAgentInstructionsSection(existing, templateContent);
194
+ if (updated !== void 0 && updated !== existing) fs.writeFileSync(fullPath, updated);
195
+ } catch {}
196
+ }
197
+ function resolveAgentOptions(agent) {
198
+ const agentNames = parseAgentNames(agent);
199
+ const resolvedAgentNames = agentNames.length > 0 ? agentNames : [AGENT_DEFAULT_ID];
200
+ const dedupedAgents = [];
201
+ const seenAgentIds = /* @__PURE__ */ new Set();
202
+ for (const name of resolvedAgentNames) {
203
+ const option = resolveSingleAgentOption(name);
204
+ if (seenAgentIds.has(option.id)) continue;
205
+ seenAgentIds.add(option.id);
206
+ dedupedAgents.push(option);
207
+ }
208
+ return dedupedAgents;
209
+ }
210
+ function getAgentTargetPaths(agents) {
211
+ const dedupedTargetPaths = [];
212
+ const seenTargetPaths = /* @__PURE__ */ new Set();
213
+ for (const agent of agents) {
214
+ if (seenTargetPaths.has(agent.targetPath)) continue;
215
+ seenTargetPaths.add(agent.targetPath);
216
+ dedupedTargetPaths.push(agent.targetPath);
217
+ }
218
+ return dedupedTargetPaths;
219
+ }
220
+ function parseAgentNames(agent) {
221
+ if (!agent) return [];
222
+ return (Array.isArray(agent) ? agent : [agent]).filter((value) => typeof value === "string").flatMap((value) => value.split(",")).map((value) => value.trim()).filter((value) => value.length > 0);
223
+ }
224
+ function resolveSingleAgentOption(agent) {
225
+ const normalized = normalizeAgentName(agent);
226
+ const alias = AGENT_ALIASES[normalized];
227
+ const resolved = alias ? normalizeAgentName(alias) : normalized;
228
+ return AGENTS.find((option) => normalizeAgentName(option.id) === resolved || normalizeAgentName(option.label) === resolved || normalizeAgentName(option.targetPath) === resolved || option.aliases?.some((candidate) => normalizeAgentName(candidate) === resolved)) ?? AGENTS.find((option) => option.id === AGENT_DEFAULT_ID);
229
+ }
230
+ /** Order target paths for the shared detect/write traversal: AGENTS.md first. */
231
+ function orderAgentTargetPaths(projectRoot, targetPaths) {
232
+ const orderedPaths = targetPaths.includes(AGENT_STANDARD_PATH) ? [AGENT_STANDARD_PATH, ...targetPaths.filter((p) => p !== AGENT_STANDARD_PATH)] : targetPaths;
233
+ const dedupedPaths = [];
234
+ const seenDestinationPaths = /* @__PURE__ */ new Set();
235
+ for (const targetPath of orderedPaths) {
236
+ const destinationKey = path.resolve(path.join(projectRoot, targetPath));
237
+ if (seenDestinationPaths.has(destinationKey)) continue;
238
+ seenDestinationPaths.add(destinationKey);
239
+ dedupedPaths.push(targetPath);
240
+ }
241
+ return dedupedPaths;
242
+ }
243
+ /**
244
+ * Classify an existing agent instruction file for the shared detect/write
245
+ * traversal. Registers the file's realpath in the caller-owned `seenRealPaths`.
246
+ */
247
+ async function classifyExistingAgentTarget(destinationPath, incomingContent, seenRealPaths) {
248
+ if (fs.lstatSync(destinationPath).isSymbolicLink()) return { kind: "symlink" };
249
+ const destinationRealPath = await fsPromises.realpath(destinationPath);
250
+ if (seenRealPaths.has(destinationRealPath)) return { kind: "duplicate" };
251
+ seenRealPaths.add(destinationRealPath);
252
+ const existingContent = await fsPromises.readFile(destinationPath, "utf-8");
253
+ const updatedContent = replaceMarkedAgentInstructionsSection(existingContent, incomingContent);
254
+ if (updatedContent !== void 0) return {
255
+ kind: "markers",
256
+ existingContent,
257
+ updatedContent
258
+ };
259
+ return {
260
+ kind: "conflict",
261
+ existingContent
262
+ };
263
+ }
264
+ /**
265
+ * Detect agent instruction files that would conflict (exist without markers).
266
+ * Returns only files that need a user decision (append or skip).
267
+ * Read-only — does not write or modify any files.
268
+ */
269
+ async function detectAgentConflicts({ projectRoot, targetPaths }) {
270
+ if (!targetPaths || targetPaths.length === 0) return [];
271
+ const sourcePath = path.join(pkgRoot, "AGENTS.md");
272
+ if (!fs.existsSync(sourcePath)) return [];
273
+ const incomingContent = await fsPromises.readFile(sourcePath, "utf-8");
274
+ const shouldLinkToAgents = targetPaths.includes(AGENT_STANDARD_PATH);
275
+ const conflicts = [];
276
+ const seenRealPaths = /* @__PURE__ */ new Set();
277
+ for (const targetPathToCheck of orderAgentTargetPaths(projectRoot, targetPaths)) {
278
+ const destinationPath = path.join(projectRoot, targetPathToCheck);
279
+ if (shouldLinkToAgents && targetPathToCheck !== AGENT_STANDARD_PATH) {
280
+ if (await getExistingPathKind(destinationPath) !== "file") continue;
281
+ }
282
+ if (!fs.existsSync(destinationPath)) continue;
283
+ if ((await classifyExistingAgentTarget(destinationPath, incomingContent, seenRealPaths)).kind === "conflict") conflicts.push({ targetPath: targetPathToCheck });
284
+ }
285
+ return conflicts;
286
+ }
287
+ async function writeAgentInstructions({ projectRoot, targetPath, targetPaths, interactive, conflictDecisions, silent = false }) {
288
+ const paths = [...targetPaths ?? [], ...targetPath ? [targetPath] : []];
289
+ if (paths.length === 0) return;
290
+ const sourcePath = path.join(pkgRoot, "AGENTS.md");
291
+ if (!fs.existsSync(sourcePath)) {
292
+ if (!silent) log.warn("Agent instructions template not found; skipping.");
293
+ return;
294
+ }
295
+ const seenRealPaths = /* @__PURE__ */ new Set();
296
+ const incomingContent = await fsPromises.readFile(sourcePath, "utf-8");
297
+ const shouldLinkToAgents = paths.includes(AGENT_STANDARD_PATH);
298
+ for (const targetPathToWrite of orderAgentTargetPaths(projectRoot, paths)) {
299
+ const destinationPath = path.join(projectRoot, targetPathToWrite);
300
+ await fsPromises.mkdir(path.dirname(destinationPath), { recursive: true });
301
+ if (shouldLinkToAgents && targetPathToWrite !== AGENT_STANDARD_PATH) {
302
+ if (await tryLinkTargetToAgents(projectRoot, targetPathToWrite, silent)) continue;
303
+ }
304
+ if (fs.existsSync(destinationPath)) {
305
+ const state = await classifyExistingAgentTarget(destinationPath, incomingContent, seenRealPaths);
306
+ if (state.kind === "symlink") {
307
+ if (!silent) log.info(`Skipped writing ${targetPathToWrite} (symlink)`);
308
+ continue;
309
+ }
310
+ if (state.kind === "duplicate") {
311
+ if (!silent) log.info(`Skipped writing ${targetPathToWrite} (duplicate target)`);
312
+ continue;
313
+ }
314
+ if (state.kind === "markers") {
315
+ if (state.updatedContent !== state.existingContent) await fsPromises.writeFile(destinationPath, state.updatedContent);
316
+ continue;
317
+ }
318
+ let conflictAction;
319
+ const preResolved = conflictDecisions?.get(targetPathToWrite);
320
+ if (preResolved) conflictAction = preResolved;
321
+ else if (interactive) {
322
+ const action = await select({
323
+ message: `Agent instructions already exist at ${targetPathToWrite}.\n ` + styleText("gray", "The Vite+ template includes guidance on `vp` commands, the build pipeline, and project conventions."),
324
+ options: [{
325
+ label: "Append",
326
+ value: "append",
327
+ hint: "Add template content to the end"
328
+ }, {
329
+ label: "Skip",
330
+ value: "skip",
331
+ hint: "Leave existing file unchanged"
332
+ }],
333
+ initialValue: "skip"
334
+ });
335
+ conflictAction = isCancel(action) || action === "skip" ? "skip" : "append";
336
+ } else conflictAction = "skip";
337
+ if (conflictAction === "append") await appendAgentContent(destinationPath, targetPathToWrite, state.existingContent, incomingContent, silent);
338
+ else {
339
+ const suffix = !preResolved && !interactive ? " (already exists)" : "";
340
+ if (!silent) log.info(`Skipped writing ${targetPathToWrite}${suffix}`);
341
+ }
342
+ continue;
343
+ }
344
+ await fsPromises.writeFile(destinationPath, incomingContent);
345
+ if (!silent) log.success(`Wrote agent instructions to ${targetPathToWrite}`);
346
+ seenRealPaths.add(await fsPromises.realpath(destinationPath));
347
+ }
348
+ }
349
+ async function writeCopilotSetupWorkflow({ projectRoot, silent = false }) {
350
+ const destinationPath = path.join(projectRoot, COPILOT_SETUP_WORKFLOW_PATH);
351
+ await fsPromises.mkdir(path.dirname(destinationPath), { recursive: true });
352
+ if (fs.existsSync(destinationPath)) {
353
+ if (!silent) log.info(`Skipped writing ${COPILOT_SETUP_WORKFLOW_PATH} (already exists)`);
354
+ return;
355
+ }
356
+ await fsPromises.writeFile(destinationPath, COPILOT_SETUP_WORKFLOW_CONTENT);
357
+ if (!silent) log.success(`Wrote Copilot setup workflow to ${COPILOT_SETUP_WORKFLOW_PATH}`);
358
+ }
359
+ async function appendAgentContent(destinationPath, targetPath, existingContent, incomingContent, silent = false) {
360
+ const separator = existingContent.endsWith("\n") ? "" : "\n";
361
+ await fsPromises.appendFile(destinationPath, `${separator}\n${incomingContent}`);
362
+ if (!silent) log.success(`Appended agent instructions to ${targetPath}`);
363
+ }
364
+ function normalizeAgentName(value) {
365
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
366
+ }
367
+ function replaceMarkedAgentInstructionsSection(existing, incoming) {
368
+ const existingRange = getMarkedRange(existing, AGENT_INSTRUCTIONS_START_MARKER, AGENT_INSTRUCTIONS_END_MARKER);
369
+ if (!existingRange) return;
370
+ const incomingRange = getMarkedRange(incoming, AGENT_INSTRUCTIONS_START_MARKER, AGENT_INSTRUCTIONS_END_MARKER);
371
+ if (!incomingRange) return;
372
+ return `${existing.slice(0, existingRange.start)}${incoming.slice(incomingRange.start, incomingRange.end)}${existing.slice(existingRange.end)}`;
373
+ }
374
+ async function tryLinkTargetToAgents(projectRoot, targetPath, silent = false) {
375
+ const destinationPath = path.join(projectRoot, targetPath);
376
+ const agentsPath = path.join(projectRoot, AGENT_STANDARD_PATH);
377
+ const symlinkTarget = path.relative(path.dirname(destinationPath), agentsPath);
378
+ const existing = await getExistingPathKind(destinationPath);
379
+ if (existing === "file") return false;
380
+ if (existing === "symlink") {
381
+ const currentLink = await fsPromises.readlink(destinationPath);
382
+ if (path.resolve(path.dirname(destinationPath), currentLink) === agentsPath) {
383
+ if (!silent) log.info(`Skipped linking ${targetPath} (already linked to ${AGENT_STANDARD_PATH})`);
384
+ return true;
385
+ }
386
+ await fsPromises.unlink(destinationPath);
387
+ }
388
+ try {
389
+ await fsPromises.symlink(symlinkTarget, destinationPath);
390
+ } catch (err) {
391
+ if (err.code === "EPERM") {
392
+ await fsPromises.copyFile(agentsPath, destinationPath);
393
+ if (!silent) log.success(`Copied ${AGENT_STANDARD_PATH} to ${targetPath}`);
394
+ return true;
395
+ }
396
+ throw err;
397
+ }
398
+ if (!silent) log.success(`Linked ${targetPath} to ${AGENT_STANDARD_PATH}`);
399
+ return true;
400
+ }
401
+ async function getExistingPathKind(filePath) {
402
+ if (!fs.existsSync(filePath)) return "missing";
403
+ return (await fsPromises.lstat(filePath)).isSymbolicLink() ? "symlink" : "file";
404
+ }
405
+ function getMarkedRange(content, startMarker, endMarker) {
406
+ const start = content.indexOf(startMarker);
407
+ if (start === -1) return;
408
+ const endMarkerIndex = content.indexOf(endMarker, start + startMarker.length);
409
+ if (endMarkerIndex === -1) return;
410
+ return {
411
+ start,
412
+ end: endMarkerIndex + endMarker.length
413
+ };
414
+ }
415
+ //#endregion
416
+ export { selectAgentTargets as a, writeCopilotSetupWorkflow as c, templatesDir as d, selectAgentTargetPaths as i, displayRelative as l, detectAgentConflicts as n, updateExistingAgentInstructions as o, detectExistingAgentTargetPaths as r, writeAgentInstructions as s, COPILOT_AGENT_ID as t, rulesDir as u };