vite-plus 0.1.24 → 0.2.1

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 (174) hide show
  1. package/LICENSE +30 -0
  2. package/README.md +8 -6
  3. package/bin/oxfmt +5 -2
  4. package/bin/oxlint +12 -2
  5. package/binding/index.cjs +84 -67
  6. package/binding/index.d.cts +155 -4
  7. package/dist/{agent-Nuk-9l77.js → agent--cKmgD_n.js} +941 -70
  8. package/dist/bin.js +22 -30
  9. package/dist/{compat-DXZgnEyq.js → compat-Cql3K40m.js} +1 -1
  10. package/dist/config/bin.js +30 -14
  11. package/dist/constants-CrfJQIUX.js +66 -0
  12. package/dist/create/bin.d.ts +6 -0
  13. package/dist/create/bin.js +508 -232
  14. package/dist/define-config-2tfJoXr1.d.ts +305 -0
  15. package/dist/define-config-BGSjF6Xp.cjs +488 -0
  16. package/dist/define-config-DJUehepE.js +445 -0
  17. package/dist/define-config.cjs +8 -1
  18. package/dist/define-config.d.ts +2 -2
  19. package/dist/define-config.js +2 -2
  20. package/dist/dist-DRJUd9bL.js +3 -0
  21. package/dist/{dist-BgQuvbtq.js → dist-Oxo16Y0q.js} +4 -4
  22. package/dist/index.cjs +9 -4
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +3 -3
  25. package/dist/{main-DpJl3LoU.js → json-Dn87fvjk.js} +137 -1
  26. package/dist/migration/bin.js +292 -76
  27. package/dist/{oxlint-plugin-config-B89iKTKN.js → oxlint-plugin-config-q8a5PFch.js} +1 -1
  28. package/dist/oxlint-plugin.js +11 -3
  29. package/dist/pack-bin.js +44 -15
  30. package/dist/{package-PmBUZ-ve.js → package-BHirM1_v.js} +3 -138
  31. package/dist/{report-DgSBQUdz.js → report-BHSkWqRR.js} +2 -0
  32. package/dist/{resolve-vite-config-TTvhycU1.js → resolve-vite-config-CmdsfQzS.js} +13 -4
  33. package/dist/staged/bin.js +5 -5
  34. package/dist/test/_at-vitest-browser/context.d.ts +2 -0
  35. package/dist/test/_at-vitest-browser.d.ts +2 -0
  36. package/dist/test/browser/context.d.ts +2 -2
  37. package/dist/test/browser/context.js +1 -1
  38. package/dist/test/browser/providers/playwright/context.d.ts +1 -0
  39. package/dist/test/browser/providers/playwright/context.js +1 -0
  40. package/dist/test/browser/providers/playwright.d.ts +124 -2
  41. package/dist/test/browser/providers/playwright.js +1 -1
  42. package/dist/test/browser/providers/preview/context.d.ts +1 -0
  43. package/dist/test/browser/providers/preview/context.js +1 -0
  44. package/dist/test/browser/providers/preview.d.ts +32 -2
  45. package/dist/test/browser/providers/preview.js +1 -1
  46. package/dist/test/browser/providers/webdriverio/context.d.ts +1 -0
  47. package/dist/test/browser/providers/webdriverio/context.js +1 -0
  48. package/dist/test/browser/providers/webdriverio.d.ts +77 -2
  49. package/dist/test/browser/providers/webdriverio.js +1 -1
  50. package/dist/test/browser-compat.d.ts +2 -0
  51. package/dist/test/browser-compat.js +1 -1
  52. package/dist/test/browser-playwright/context.d.ts +1 -0
  53. package/dist/test/browser-playwright/context.js +1 -0
  54. package/dist/test/browser-playwright.d.ts +124 -2
  55. package/dist/test/browser-playwright.js +1 -1
  56. package/dist/test/browser-preview/context.d.ts +1 -0
  57. package/dist/test/browser-preview/context.js +1 -0
  58. package/dist/test/browser-preview.d.ts +32 -2
  59. package/dist/test/browser-preview.js +1 -1
  60. package/dist/test/browser-webdriverio/context.d.ts +1 -0
  61. package/dist/test/browser-webdriverio/context.js +1 -0
  62. package/dist/test/browser-webdriverio.d.ts +77 -2
  63. package/dist/test/browser-webdriverio.js +1 -1
  64. package/dist/test/browser.d.ts +2 -2
  65. package/dist/test/browser.js +1 -1
  66. package/dist/test/client.js +1 -1
  67. package/dist/test/config.cjs +1 -1
  68. package/dist/test/config.d.ts +2 -2
  69. package/dist/test/config.js +1 -1
  70. package/dist/test/context.d.ts +942 -2
  71. package/dist/test/context.js +1 -1
  72. package/dist/test/coverage.d.ts +2 -2
  73. package/dist/test/coverage.js +1 -1
  74. package/dist/test/environments.d.ts +2 -2
  75. package/dist/test/environments.js +1 -1
  76. package/dist/test/globals.d.ts +2 -2
  77. package/dist/test/import-meta.d.ts +2 -2
  78. package/dist/test/importMeta.d.ts +2 -2
  79. package/dist/test/index.cjs +1 -1
  80. package/dist/test/index.d.cts +2 -2
  81. package/dist/test/index.d.ts +2 -2
  82. package/dist/test/index.js +1 -1
  83. package/dist/test/internal/browser.d.ts +2 -2
  84. package/dist/test/internal/browser.js +1 -1
  85. package/dist/test/jsdom.d.ts +2 -2
  86. package/dist/test/locators.d.ts +294 -0
  87. package/dist/test/locators.js +1 -1
  88. package/dist/test/matchers.d.ts +29 -0
  89. package/dist/test/matchers.js +1 -1
  90. package/dist/test/node.d.ts +2 -2
  91. package/dist/test/node.js +1 -1
  92. package/dist/test/optional-runtime-types.js.d.ts +2 -2
  93. package/dist/test/optional-types.js.d.ts +2 -2
  94. package/dist/test/plugins/browser-client.js +1 -1
  95. package/dist/test/plugins/browser-context.js +1 -1
  96. package/dist/test/plugins/browser-locators.js +1 -1
  97. package/dist/test/plugins/browser-playwright.js +1 -1
  98. package/dist/test/plugins/browser-preview.js +1 -1
  99. package/dist/test/plugins/browser-webdriverio.js +1 -1
  100. package/dist/test/plugins/browser.js +1 -1
  101. package/dist/test/plugins/expect.js +1 -1
  102. package/dist/test/plugins/mocker-automock.js +1 -1
  103. package/dist/test/plugins/mocker-browser.js +1 -1
  104. package/dist/test/plugins/mocker-node.js +1 -1
  105. package/dist/test/plugins/mocker-redirect.js +1 -1
  106. package/dist/test/plugins/mocker-register.js +1 -1
  107. package/dist/test/plugins/mocker-transforms.js +1 -1
  108. package/dist/test/plugins/mocker.js +1 -1
  109. package/dist/test/plugins/pretty-format.js +1 -1
  110. package/dist/test/plugins/runner-types.js +1 -1
  111. package/dist/test/plugins/runner-utils.js +1 -1
  112. package/dist/test/plugins/runner.js +1 -1
  113. package/dist/test/plugins/snapshot-environment.js +1 -1
  114. package/dist/test/plugins/snapshot-manager.js +1 -1
  115. package/dist/test/plugins/snapshot.js +1 -1
  116. package/dist/test/plugins/spy.js +1 -1
  117. package/dist/test/plugins/utils-constants.js +1 -1
  118. package/dist/test/plugins/utils-diff.js +1 -1
  119. package/dist/test/plugins/utils-display.js +1 -1
  120. package/dist/test/plugins/utils-error.js +1 -1
  121. package/dist/test/plugins/utils-helpers.js +1 -1
  122. package/dist/test/plugins/utils-offset.js +1 -1
  123. package/dist/test/plugins/utils-resolver.js +1 -1
  124. package/dist/test/plugins/utils-serialize.js +1 -1
  125. package/dist/test/plugins/utils-source-map-node.js +1 -1
  126. package/dist/test/plugins/utils-source-map.js +1 -1
  127. package/dist/test/plugins/utils-timers.js +1 -1
  128. package/dist/test/plugins/utils.js +1 -1
  129. package/dist/test/reporters.d.ts +2 -2
  130. package/dist/test/reporters.js +1 -1
  131. package/dist/test/runners.d.ts +2 -2
  132. package/dist/test/runners.js +1 -1
  133. package/dist/test/runtime.d.ts +2 -2
  134. package/dist/test/runtime.js +1 -1
  135. package/dist/test/snapshot.d.ts +2 -2
  136. package/dist/test/snapshot.js +1 -1
  137. package/dist/test/suite.d.ts +2 -2
  138. package/dist/test/suite.js +1 -1
  139. package/dist/test/utils.js +1 -1
  140. package/dist/test/worker.d.ts +2 -2
  141. package/dist/test/worker.js +1 -1
  142. package/dist/{tsconfig-DFb5BKyT.js → tsconfig-BWQPmGKz.js} +565 -231
  143. package/dist/tsgolint-path-B-yOos8p.js +32 -0
  144. package/dist/tsgolint-path.d.ts +8 -0
  145. package/dist/tsgolint-path.js +2 -0
  146. package/dist/version.js +3 -3
  147. package/dist/versions.d.ts +1 -1
  148. package/dist/versions.js +6 -6
  149. package/dist/{workspace-NL-m9wgM.js → workspace-D0AVy4fu.js} +11 -9
  150. package/docs/_data/team.ts +2 -1
  151. package/docs/config/create.md +36 -1
  152. package/docs/config/index.md +7 -5
  153. package/docs/guide/commit-hooks.md +9 -0
  154. package/docs/guide/create.md +106 -2
  155. package/docs/guide/env.md +33 -5
  156. package/docs/guide/index.md +5 -3
  157. package/docs/guide/install.md +31 -12
  158. package/docs/guide/migrate.md +13 -3
  159. package/docs/guide/troubleshooting.md +2 -2
  160. package/docs/guide/upgrade.md +26 -7
  161. package/docs/package.json +3 -3
  162. package/docs/pnpm-lock.yaml +298 -395
  163. package/package.json +103 -55
  164. package/templates/generator/bin/index.ts +6 -3
  165. package/templates/generator/package.json +2 -3
  166. package/templates/generator/src/template.ts +0 -2
  167. package/templates/monorepo/package.json +1 -1
  168. package/dist/constants-DCBWlNrn.js +0 -33
  169. package/dist/define-config-BR1Y88zz.cjs +0 -84
  170. package/dist/define-config-BRC7qPNE.js +0 -21
  171. package/dist/define-config-COdn-tsn.d.ts +0 -177
  172. package/dist/dist-Bapm49IR.js +0 -3
  173. package/dist/test/plugins/utils-highlight.js +0 -1
  174. /package/dist/{chunk-DnnnRqeS.js → rolldown-runtime-DnnnRqeS.js} +0 -0
@@ -1,14 +1,16 @@
1
- import { r as __toESM, t as __commonJSMin } from "../chunk-DnnnRqeS.js";
2
- import { A as R, C as log, D as spinner, E as select, M as runCommandSilently, N as require_cross_spawn, O as text, S as intro, _ as DependencyType, b as cancel, c as defaultInteractive, d as promptGitHooks, f as promptGitInit, h as selectPackageManager, j as runCommand$1, k as require_picocolors, l as downloadPackageManager$1, m as runViteInstall, p as runViteFmt, v as PackageManager, w as multiselect, x as confirm } from "../tsconfig-DFb5BKyT.js";
1
+ import { r as __toESM, t as __commonJSMin } from "../rolldown-runtime-DnnnRqeS.js";
2
+ import { o as VITE_PLUS_NAME } from "../constants-CrfJQIUX.js";
3
+ import { A as select, D as log, E as intro, F as runCommand$1, I as runCommandSilently, L as require_cross_spawn, M as text, N as require_picocolors, O as multiselect, P as R, S as PackageManager, T as confirm, _ as approveBuilds, d as promptGitHooks, f as resolveGitInit, h as selectPackageManager, j as spinner, l as defaultInteractive, m as runViteInstall, p as runViteFmt, u as downloadPackageManager$1, v as detectGatedBuilds, w as cancel, x as DependencyType, y as resolveApproveBuildTargets } from "../tsconfig-BWQPmGKz.js";
3
4
  import { a as printHeader, i as muted, o as success, r as log$1, t as accent } from "../terminal-uTv0ZaMr.js";
4
- import { i as resolveViteConfig, n as hasViteConfig, t as findWorkspaceRoot } from "../resolve-vite-config-TTvhycU1.js";
5
+ import { r as readJsonFile, t as editJsonFile } from "../json-Dn87fvjk.js";
6
+ import { a as resolveViteConfig, n as findWorkspaceRoot, r as hasViteConfig, t as findViteConfig } from "../resolve-vite-config-CmdsfQzS.js";
5
7
  import { t as lib_default } from "../lib-L3DWSRQp.js";
6
- import { c as editJsonFile, o as fetchNpmResource, s as getNpmRegistry, t as checkNpmPackageExists, u as readJsonFile } from "../package-PmBUZ-ve.js";
7
- import { A as promptEslintMigration, F as setPackageManager, H as displayRelative, M as rewriteMonorepo, N as rewriteMonorepoProject, P as rewriteStandaloneProject, S as injectCreateDefaultTemplate, U as templatesDir, a as selectAgentTargets, b as hasFrameworkShim, c as writeCopilotSetupWorkflow, h as detectFramework, j as promptPrettierMigration, l as addFrameworkShim, m as detectEslintProject, r as detectExistingAgentTargetPaths, s as writeAgentInstructions, t as COPILOT_AGENT_ID, v as detectPrettierProject, w as installGitHooks } from "../agent-Nuk-9l77.js";
8
- import { a as detectExistingEditors, c as writeEditorConfigs, n as updatePackageJsonWithDeps, r as updateWorkspaceConfig, s as selectEditors, t as detectWorkspace$1 } from "../workspace-NL-m9wgM.js";
8
+ import { o as fetchNpmResource, s as getNpmRegistry, t as checkNpmPackageExists } from "../package-BHirM1_v.js";
9
+ import { B as setPackageManager, D as injectCreateDefaultTemplate, F as promptEslintMigration, I as promptPrettierMigration, J as templatesDir, L as rewriteMonorepo, R as rewriteMonorepoProject, T as hasFrameworkShim, a as selectAgentTargets, b as detectPrettierProject, c as writeCopilotSetupWorkflow, h as detectFramework, k as installGitHooks, l as addFrameworkShim, m as detectEslintProject, q as displayRelative, r as detectExistingAgentTargetPaths, s as writeAgentInstructions, t as COPILOT_AGENT_ID, z as rewriteStandaloneProject } from "../agent--cKmgD_n.js";
10
+ import { c as selectEditors, i as updateWorkspaceConfig, l as writeEditorConfigs, n as isBingoTemplate, o as detectExistingEditors, r as updatePackageJsonWithDeps, t as detectWorkspace$1 } from "../workspace-D0AVy4fu.js";
9
11
  import { t as renderCliDoc } from "../help-YP84FSEz.js";
10
12
  import path from "node:path";
11
- import { runCommand, vitePlusHeader } from "../../binding/index.js";
13
+ import { runCommand, upsertJsonConfig, vitePlusHeader } from "../../binding/index.js";
12
14
  import fs from "node:fs";
13
15
  import { styleText } from "node:util";
14
16
  import os from "node:os";
@@ -32,7 +34,7 @@ async function createInitialCommit(cwd) {
32
34
  cwd,
33
35
  envs: process.env
34
36
  });
35
- return (await runCommandSilently({
37
+ const result = await runCommandSilently({
36
38
  command: "git",
37
39
  args: [
38
40
  "commit",
@@ -41,7 +43,11 @@ async function createInitialCommit(cwd) {
41
43
  ],
42
44
  cwd,
43
45
  envs: process.env
44
- })).exitCode === 0;
46
+ });
47
+ return {
48
+ success: result.exitCode === 0,
49
+ output: `${result.stdout.toString()}${result.stderr.toString()}`.trim()
50
+ };
45
51
  }
46
52
  //#endregion
47
53
  //#region src/create/command.ts
@@ -121,6 +127,220 @@ function prependToPathToEnvs(extraPath, envs) {
121
127
  return envs;
122
128
  }
123
129
  //#endregion
130
+ //#region src/create/org-manifest.ts
131
+ /**
132
+ * Parse the org picker specifier: `@scope` (scope only → picker) or
133
+ * `@scope:name` (direct manifest-entry selection). Colon mirrors the
134
+ * existing `vite:monorepo` / `vite:library` builtin-template syntax and
135
+ * keeps manifest entries syntactically distinct from real
136
+ * `@scope/package-name` npm specifiers.
137
+ *
138
+ * Returns `null` for anything else — including the plain `@scope/name`
139
+ * form, which routes to the existing `@scope/create-name` shorthand as
140
+ * it did before the org-manifest feature.
141
+ *
142
+ * The optional `version` suffix (`@scope@1.2.3`, `@scope:name@1.2.3`)
143
+ * pins `@scope/create` to a specific release rather than `dist-tags.latest`.
144
+ */
145
+ function parseOrgScopedSpec(spec) {
146
+ if (!spec.startsWith("@")) return null;
147
+ if (spec.includes("/")) return null;
148
+ const colonIndex = spec.indexOf(":");
149
+ if (colonIndex === -1) {
150
+ const atIndex = spec.indexOf("@", 1);
151
+ if (atIndex === -1) return { scope: spec };
152
+ const version = spec.slice(atIndex + 1);
153
+ return version ? {
154
+ scope: spec.slice(0, atIndex),
155
+ version
156
+ } : { scope: spec.slice(0, atIndex) };
157
+ }
158
+ const scope = spec.slice(0, colonIndex);
159
+ const rest = spec.slice(colonIndex + 1);
160
+ const atIndex = rest.indexOf("@");
161
+ const name = atIndex === -1 ? rest : rest.slice(0, atIndex);
162
+ const version = atIndex === -1 ? "" : rest.slice(atIndex + 1);
163
+ if (!name) return version ? {
164
+ scope,
165
+ version
166
+ } : { scope };
167
+ return version ? {
168
+ scope,
169
+ name,
170
+ version
171
+ } : {
172
+ scope,
173
+ name
174
+ };
175
+ }
176
+ /**
177
+ * Schema-level failure. Never falls through silently — a maintainer who
178
+ * shipped an invalid manifest should see the offending field.
179
+ */
180
+ var OrgManifestSchemaError = class extends Error {
181
+ packageName;
182
+ constructor(message, packageName) {
183
+ super(`${packageName}: ${message}`);
184
+ this.packageName = packageName;
185
+ this.name = "OrgManifestSchemaError";
186
+ }
187
+ };
188
+ function isRelativePath(spec) {
189
+ return spec.startsWith("./") || spec.startsWith("../");
190
+ }
191
+ /**
192
+ * Validate the `{ name, description, template }` fields shared by org manifest
193
+ * entries and local `create.templates` entries. `label` is the config path
194
+ * used in error messages (e.g. `createConfig.templates` or `create.templates`)
195
+ * and `makeError` builds the thrown error so each source uses its own type.
196
+ */
197
+ function validateTemplateEntry(entry, index, label, makeError) {
198
+ if (!entry || typeof entry !== "object") throw makeError(`${label}[${index}] must be an object`);
199
+ const raw = entry;
200
+ const requireString = (field) => {
201
+ const value = raw[field];
202
+ if (typeof value !== "string" || value.length === 0) throw makeError(`${label}[${index}].${field} must be a non-empty string`);
203
+ return value;
204
+ };
205
+ const name = requireString("name");
206
+ if (name.startsWith("__vp_")) throw makeError(`${label}[${index}].name uses the reserved \`__vp_\` prefix`);
207
+ const description = requireString("description");
208
+ const template = requireString("template");
209
+ if (isRelativePath(template)) {
210
+ const resolved = path.posix.resolve("/root", template.replaceAll("\\", "/"));
211
+ if (resolved !== "/root" && !resolved.startsWith("/root/")) throw makeError(`${label}[${index}].template escapes the package root: ${template}`);
212
+ }
213
+ return {
214
+ name,
215
+ description,
216
+ template
217
+ };
218
+ }
219
+ /**
220
+ * Validate a list of entries, rejecting duplicate `name`s. Shared by org
221
+ * manifests and local `create.templates`.
222
+ */
223
+ function validateTemplateEntries(templates, label, makeError, validateOne) {
224
+ const entries = [];
225
+ const seen = /* @__PURE__ */ new Set();
226
+ for (let index = 0; index < templates.length; index += 1) {
227
+ const entry = validateOne(templates[index], index);
228
+ if (seen.has(entry.name)) throw makeError(`${label}[${index}].name duplicates an earlier entry: "${entry.name}"`);
229
+ seen.add(entry.name);
230
+ entries.push(entry);
231
+ }
232
+ return entries;
233
+ }
234
+ function validateEntry(entry, index, packageName) {
235
+ const makeError = (message) => new OrgManifestSchemaError(message, packageName);
236
+ const base = validateTemplateEntry(entry, index, "createConfig.templates", makeError);
237
+ let monorepo;
238
+ const raw = entry;
239
+ if (raw.monorepo !== void 0) {
240
+ if (typeof raw.monorepo !== "boolean") throw makeError(`createConfig.templates[${index}].monorepo must be a boolean`);
241
+ monorepo = raw.monorepo;
242
+ }
243
+ return {
244
+ ...base,
245
+ ...monorepo !== void 0 ? { monorepo } : {}
246
+ };
247
+ }
248
+ function validateManifest(raw, packageName) {
249
+ if (!raw || typeof raw !== "object") return null;
250
+ const createConfig = raw.createConfig;
251
+ if (!createConfig || typeof createConfig !== "object") return null;
252
+ const templates = createConfig.templates;
253
+ if (templates === void 0) return null;
254
+ if (!Array.isArray(templates)) throw new OrgManifestSchemaError("createConfig.templates must be an array", packageName);
255
+ if (templates.length === 0) return null;
256
+ return validateTemplateEntries(templates, "createConfig.templates", (message) => new OrgManifestSchemaError(message, packageName), (entry, index) => validateEntry(entry, index, packageName));
257
+ }
258
+ /**
259
+ * Schema-level failure for `create.templates` in `vite.config.ts`. A misconfigured
260
+ * local template should surface clearly rather than silently disappear.
261
+ */
262
+ var CreateConfigSchemaError = class extends Error {
263
+ constructor(message) {
264
+ super(message);
265
+ this.name = "CreateConfigSchemaError";
266
+ }
267
+ };
268
+ /**
269
+ * Validate `create.templates` from `vite.config.ts`. Returns `[]` when the field
270
+ * is absent or an empty array; throws {@link CreateConfigSchemaError} when present
271
+ * but malformed.
272
+ */
273
+ function validateCreateTemplates(templates) {
274
+ if (templates === void 0) return [];
275
+ if (!Array.isArray(templates)) throw new CreateConfigSchemaError("create.templates must be an array");
276
+ const makeError = (message) => new CreateConfigSchemaError(message);
277
+ return validateTemplateEntries(templates, "create.templates", makeError, (entry, index) => {
278
+ const validated = validateTemplateEntry(entry, index, "create.templates", makeError);
279
+ if (validated.name.startsWith("vite:")) throw makeError(`create.templates[${index}].name uses the reserved \`vite:\` prefix`);
280
+ return validated;
281
+ });
282
+ }
283
+ async function fetchPackument(scope, packageName) {
284
+ const response = await fetchNpmResource(`${getNpmRegistry(scope)}/${packageName}`, {
285
+ headers: { accept: "application/json" },
286
+ timeoutMs: 5e3
287
+ });
288
+ if (response.status === 404) return null;
289
+ if (!response.ok) throw new Error(`npm registry responded with ${response.status} for ${packageName}`);
290
+ return await response.json();
291
+ }
292
+ /**
293
+ * Fetch `@scope/create` from the npm registry and parse its `createConfig.templates`
294
+ * manifest.
295
+ *
296
+ * Returns `null` when:
297
+ * - the package does not exist on the registry (404), or
298
+ * - the package exists but has no `createConfig.templates` field
299
+ *
300
+ * Throws when:
301
+ * - the `createConfig.templates` field is present but malformed (`OrgManifestSchemaError`), or
302
+ * - the registry request fails for any non-404 reason
303
+ *
304
+ * `requestedVersion` pins the lookup to a specific `versions[...]` entry
305
+ * (equivalent to `vp create @scope@1.2.3`); omit it to resolve `dist-tags.latest`.
306
+ */
307
+ async function readOrgManifest(scope, requestedVersion) {
308
+ if (!scope.startsWith("@")) return null;
309
+ const packageName = `${scope}/create`;
310
+ const packument = await fetchPackument(scope, packageName);
311
+ if (!packument) return null;
312
+ let resolvedVersion;
313
+ if (requestedVersion) {
314
+ resolvedVersion = packument["dist-tags"]?.[requestedVersion] ?? (packument.versions?.[requestedVersion] ? requestedVersion : void 0);
315
+ if (!resolvedVersion) throw new OrgManifestSchemaError(`version "${requestedVersion}" not found (known tags: ${Object.keys(packument["dist-tags"] ?? {}).join(", ") || "none"})`, packageName);
316
+ } else {
317
+ resolvedVersion = packument["dist-tags"]?.latest;
318
+ if (!resolvedVersion) return null;
319
+ }
320
+ const meta = packument.versions?.[resolvedVersion];
321
+ if (!meta) return null;
322
+ const templates = validateManifest(meta, packageName);
323
+ if (!templates) return null;
324
+ if (!meta.dist?.tarball) throw new OrgManifestSchemaError(`missing dist.tarball for ${resolvedVersion}`, packageName);
325
+ return {
326
+ scope,
327
+ packageName,
328
+ version: resolvedVersion,
329
+ tarballUrl: meta.dist.tarball,
330
+ integrity: meta.dist.integrity,
331
+ templates
332
+ };
333
+ }
334
+ /**
335
+ * Apply the in-monorepo filter rule from the RFC: entries with
336
+ * `monorepo: true` are hidden when the command is invoked inside an
337
+ * existing monorepo, mirroring `initial-template-options.ts:9-31`.
338
+ */
339
+ function filterManifestForContext(templates, isMonorepo) {
340
+ if (!isMonorepo) return [...templates];
341
+ return templates.filter((entry) => !entry.monorepo);
342
+ }
343
+ //#endregion
124
344
  //#region src/create/templates/types.ts
125
345
  const LibraryTemplateRepo = "github:sxzz/tsdown-templates/vite-plus";
126
346
  const BuiltinTemplate = {
@@ -151,9 +371,24 @@ function inferGitHubRepoName(templateName) {
151
371
  if (!degitPath) return null;
152
372
  return degitPath.split("/").pop() || null;
153
373
  }
154
- function discoverTemplate(templateName, templateArgs, workspaceInfo, interactive, bundledLocalPath, skipShorthand) {
374
+ function localTemplateDir(workspaceInfo, templateName) {
375
+ if (isRelativePath(templateName)) return templateName.replace(/^\.\//, "");
376
+ return workspaceInfo.packages.find((pkg) => pkg.name === templateName)?.path;
377
+ }
378
+ function resolveLocalBinPath(localPackagePath, packageName, bin) {
379
+ if (!bin) return;
380
+ if (typeof bin === "string") return path.join(localPackagePath, bin);
381
+ const entries = Object.entries(bin);
382
+ if (entries.length === 0) return;
383
+ if (entries.length === 1) return path.join(localPackagePath, entries[0][1]);
384
+ const unscopedName = packageName.slice(packageName.lastIndexOf("/") + 1);
385
+ const preferred = bin[packageName] ?? bin[unscopedName];
386
+ if (preferred) return path.join(localPackagePath, preferred);
387
+ throw new Error(`Local template package "${packageName}" defines multiple "bin" entries (${entries.map(([name]) => name).join(", ")}); add a "bin" entry named "${packageName}" so the template entry is unambiguous`);
388
+ }
389
+ function discoverTemplate(templateName, templateArgs, workspaceInfo, interactive, bundledLocalPath, skipShorthand, localTemplate) {
155
390
  const envs = prependToPathToEnvs(workspaceInfo.downloadPackageManager.binPrefix, { ...process.env });
156
- const parentDir = inferParentDir(templateName, workspaceInfo);
391
+ const parentDir = inferParentDir(templateName, workspaceInfo, localTemplate);
157
392
  if (bundledLocalPath) return {
158
393
  command: "",
159
394
  args: [...templateArgs],
@@ -182,23 +417,22 @@ function discoverTemplate(templateName, templateArgs, workspaceInfo, interactive
182
417
  interactive
183
418
  };
184
419
  }
185
- const localPackage = workspaceInfo.packages.find((pkg) => pkg.name === templateName);
186
- if (localPackage) {
187
- const localPackagePath = path.join(workspaceInfo.rootDir, localPackage.path);
188
- const pkg = readJsonFile(path.join(localPackagePath, "package.json"));
189
- let binPath = "";
190
- if (pkg.bin) if (typeof pkg.bin === "string") binPath = path.join(localPackagePath, pkg.bin);
191
- else {
192
- const binName = Object.keys(pkg.bin)[0];
193
- binPath = path.join(localPackagePath, pkg.bin[binName]);
194
- }
420
+ if (localTemplate) {
421
+ const localDir = localTemplateDir(workspaceInfo, templateName);
422
+ if (!localDir) throw new Error(`Local template "${templateName}" does not match any workspace package; update the \`create.templates\` entry in vite.config.ts`);
423
+ const localPackagePath = path.join(workspaceInfo.rootDir, localDir);
424
+ const packageJsonPath = path.join(localPackagePath, "package.json");
425
+ if (!fs.existsSync(packageJsonPath)) throw new Error(`Local template "${templateName}" has no package.json, so it cannot be run as a template`);
426
+ const pkg = readJsonFile(packageJsonPath);
427
+ const binPath = resolveLocalBinPath(localPackagePath, pkg.name ?? templateName, pkg.bin);
428
+ if (!binPath) throw new Error(`Local template "${templateName}" has no "bin" entry in its package.json, so it cannot be run as a template`);
195
429
  const args = [binPath, ...templateArgs];
196
430
  let type = TemplateType.remote;
197
- if (pkg.keywords?.includes("bingo-template") || !!pkg.dependencies?.bingo) {
431
+ if (isBingoTemplate(pkg)) {
198
432
  type = TemplateType.bingo;
199
433
  args.push("--skip-requests");
200
434
  }
201
- if (binPath) return {
435
+ return {
202
436
  command: "node",
203
437
  args,
204
438
  envs,
@@ -261,8 +495,13 @@ function expandCreateShorthand(templateName) {
261
495
  if (name === "svelte") return `sv${version}`;
262
496
  return `create-${name}${version}`;
263
497
  }
264
- function inferParentDir(templateName, workspaceInfo) {
498
+ function inferParentDir(templateName, workspaceInfo, localTemplate = false) {
265
499
  if (workspaceInfo.parentDirs.length === 0) return;
500
+ const localDir = localTemplate ? localTemplateDir(workspaceInfo, templateName) : void 0;
501
+ if (localDir) {
502
+ const ownParentDir = path.dirname(localDir);
503
+ if (workspaceInfo.parentDirs.includes(ownParentDir)) return ownParentDir;
504
+ }
266
505
  let rule = /app/i;
267
506
  if (templateName === BuiltinTemplate.library) rule = /lib|component|package/i;
268
507
  else if (templateName === BuiltinTemplate.generator) rule = /generator|tool/i;
@@ -270,7 +509,7 @@ function inferParentDir(templateName, workspaceInfo) {
270
509
  }
271
510
  //#endregion
272
511
  //#region src/create/initial-template-options.ts
273
- function getInitialTemplateOptions(isMonorepo) {
512
+ function getInitialTemplateOptions(isMonorepo, templates = []) {
274
513
  return [
275
514
  ...!isMonorepo ? [{
276
515
  label: "Vite+ Monorepo",
@@ -286,178 +525,15 @@ function getInitialTemplateOptions(isMonorepo) {
286
525
  label: "Vite+ Library",
287
526
  value: BuiltinTemplate.library,
288
527
  hint: "Create vite libraries"
289
- }
528
+ },
529
+ ...isMonorepo ? templates.map((entry) => ({
530
+ label: entry.name,
531
+ value: entry.name,
532
+ hint: entry.description
533
+ })) : []
290
534
  ];
291
535
  }
292
536
  //#endregion
293
- //#region src/create/org-manifest.ts
294
- /**
295
- * Parse the org picker specifier: `@scope` (scope only → picker) or
296
- * `@scope:name` (direct manifest-entry selection). Colon mirrors the
297
- * existing `vite:monorepo` / `vite:library` builtin-template syntax and
298
- * keeps manifest entries syntactically distinct from real
299
- * `@scope/package-name` npm specifiers.
300
- *
301
- * Returns `null` for anything else — including the plain `@scope/name`
302
- * form, which routes to the existing `@scope/create-name` shorthand as
303
- * it did before the org-manifest feature.
304
- *
305
- * The optional `version` suffix (`@scope@1.2.3`, `@scope:name@1.2.3`)
306
- * pins `@scope/create` to a specific release rather than `dist-tags.latest`.
307
- */
308
- function parseOrgScopedSpec(spec) {
309
- if (!spec.startsWith("@")) return null;
310
- if (spec.includes("/")) return null;
311
- const colonIndex = spec.indexOf(":");
312
- if (colonIndex === -1) {
313
- const atIndex = spec.indexOf("@", 1);
314
- if (atIndex === -1) return { scope: spec };
315
- const version = spec.slice(atIndex + 1);
316
- return version ? {
317
- scope: spec.slice(0, atIndex),
318
- version
319
- } : { scope: spec.slice(0, atIndex) };
320
- }
321
- const scope = spec.slice(0, colonIndex);
322
- const rest = spec.slice(colonIndex + 1);
323
- const atIndex = rest.indexOf("@");
324
- const name = atIndex === -1 ? rest : rest.slice(0, atIndex);
325
- const version = atIndex === -1 ? "" : rest.slice(atIndex + 1);
326
- if (!name) return version ? {
327
- scope,
328
- version
329
- } : { scope };
330
- return version ? {
331
- scope,
332
- name,
333
- version
334
- } : {
335
- scope,
336
- name
337
- };
338
- }
339
- /**
340
- * Schema-level failure. Never falls through silently — a maintainer who
341
- * shipped an invalid manifest should see the offending field.
342
- */
343
- var OrgManifestSchemaError = class extends Error {
344
- packageName;
345
- constructor(message, packageName) {
346
- super(`${packageName}: ${message}`);
347
- this.packageName = packageName;
348
- this.name = "OrgManifestSchemaError";
349
- }
350
- };
351
- function isRelativePath(spec) {
352
- return spec.startsWith("./") || spec.startsWith("../");
353
- }
354
- function validateEntry(entry, index, packageName) {
355
- if (!entry || typeof entry !== "object") throw new OrgManifestSchemaError(`createConfig.templates[${index}] must be an object`, packageName);
356
- const raw = entry;
357
- const requireString = (field) => {
358
- const value = raw[field];
359
- if (typeof value !== "string" || value.length === 0) throw new OrgManifestSchemaError(`createConfig.templates[${index}].${field} must be a non-empty string`, packageName);
360
- return value;
361
- };
362
- const name = requireString("name");
363
- if (name.startsWith("__vp_")) throw new OrgManifestSchemaError(`createConfig.templates[${index}].name uses the reserved \`__vp_\` prefix`, packageName);
364
- const description = requireString("description");
365
- const template = requireString("template");
366
- let monorepo;
367
- if (raw.monorepo !== void 0) {
368
- if (typeof raw.monorepo !== "boolean") throw new OrgManifestSchemaError(`createConfig.templates[${index}].monorepo must be a boolean`, packageName);
369
- monorepo = raw.monorepo;
370
- }
371
- if (isRelativePath(template)) {
372
- const resolved = path.posix.resolve("/root", template.replaceAll("\\", "/"));
373
- if (resolved !== "/root" && !resolved.startsWith("/root/")) throw new OrgManifestSchemaError(`createConfig.templates[${index}].template escapes the package root: ${template}`, packageName);
374
- }
375
- return {
376
- name,
377
- description,
378
- template,
379
- ...monorepo !== void 0 ? { monorepo } : {}
380
- };
381
- }
382
- function validateManifest(raw, packageName) {
383
- if (!raw || typeof raw !== "object") return null;
384
- const createConfig = raw.createConfig;
385
- if (!createConfig || typeof createConfig !== "object") return null;
386
- const templates = createConfig.templates;
387
- if (templates === void 0) return null;
388
- if (!Array.isArray(templates)) throw new OrgManifestSchemaError("createConfig.templates must be an array", packageName);
389
- if (templates.length === 0) return null;
390
- const entries = [];
391
- const seen = /* @__PURE__ */ new Set();
392
- for (let index = 0; index < templates.length; index += 1) {
393
- const entry = validateEntry(templates[index], index, packageName);
394
- if (seen.has(entry.name)) throw new OrgManifestSchemaError(`createConfig.templates[${index}].name duplicates an earlier entry: "${entry.name}"`, packageName);
395
- seen.add(entry.name);
396
- entries.push(entry);
397
- }
398
- return entries;
399
- }
400
- async function fetchPackument(scope, packageName) {
401
- const response = await fetchNpmResource(`${getNpmRegistry(scope)}/${packageName}`, {
402
- headers: { accept: "application/json" },
403
- timeoutMs: 5e3
404
- });
405
- if (response.status === 404) return null;
406
- if (!response.ok) throw new Error(`npm registry responded with ${response.status} for ${packageName}`);
407
- return await response.json();
408
- }
409
- /**
410
- * Fetch `@scope/create` from the npm registry and parse its `createConfig.templates`
411
- * manifest.
412
- *
413
- * Returns `null` when:
414
- * - the package does not exist on the registry (404), or
415
- * - the package exists but has no `createConfig.templates` field
416
- *
417
- * Throws when:
418
- * - the `createConfig.templates` field is present but malformed (`OrgManifestSchemaError`), or
419
- * - the registry request fails for any non-404 reason
420
- *
421
- * `requestedVersion` pins the lookup to a specific `versions[...]` entry
422
- * (equivalent to `vp create @scope@1.2.3`); omit it to resolve `dist-tags.latest`.
423
- */
424
- async function readOrgManifest(scope, requestedVersion) {
425
- if (!scope.startsWith("@")) return null;
426
- const packageName = `${scope}/create`;
427
- const packument = await fetchPackument(scope, packageName);
428
- if (!packument) return null;
429
- let resolvedVersion;
430
- if (requestedVersion) {
431
- resolvedVersion = packument["dist-tags"]?.[requestedVersion] ?? (packument.versions?.[requestedVersion] ? requestedVersion : void 0);
432
- if (!resolvedVersion) throw new OrgManifestSchemaError(`version "${requestedVersion}" not found (known tags: ${Object.keys(packument["dist-tags"] ?? {}).join(", ") || "none"})`, packageName);
433
- } else {
434
- resolvedVersion = packument["dist-tags"]?.latest;
435
- if (!resolvedVersion) return null;
436
- }
437
- const meta = packument.versions?.[resolvedVersion];
438
- if (!meta) return null;
439
- const templates = validateManifest(meta, packageName);
440
- if (!templates) return null;
441
- if (!meta.dist?.tarball) throw new OrgManifestSchemaError(`missing dist.tarball for ${resolvedVersion}`, packageName);
442
- return {
443
- scope,
444
- packageName,
445
- version: resolvedVersion,
446
- tarballUrl: meta.dist.tarball,
447
- integrity: meta.dist.integrity,
448
- templates
449
- };
450
- }
451
- /**
452
- * Apply the in-monorepo filter rule from the RFC: entries with
453
- * `monorepo: true` are hidden when the command is invoked inside an
454
- * existing monorepo, mirroring `initial-template-options.ts:9-31`.
455
- */
456
- function filterManifestForContext(templates, isMonorepo) {
457
- if (!isMonorepo) return [...templates];
458
- return templates.filter((entry) => !entry.monorepo);
459
- }
460
- //#endregion
461
537
  //#region src/create/org-picker.ts
462
538
  const ORG_PICKER_CANCEL = Symbol("org-picker-cancel");
463
539
  const ORG_PICKER_BUILTIN_ESCAPE = Symbol("org-picker-builtin-escape");
@@ -4136,24 +4212,133 @@ async function resolveOrgManifestForCreate(args) {
4136
4212
  return resolveEntry(manifest, entry);
4137
4213
  }
4138
4214
  /**
4139
- * Read `create.defaultTemplate` from the workspace root's `vite.config.ts`.
4215
+ * Read the `create` config (`defaultTemplate` + validated `templates`) from
4216
+ * a workspace's `vite.config.ts` in a single config evaluation.
4140
4217
  *
4141
- * Walks up from `startDir` via `findWorkspaceRoot` (monorepo markers
4142
- * only — `pnpm-workspace.yaml`, `workspaces` in `package.json`,
4143
- * `lerna.json`) so monorepo invocations from any subdirectory still
4144
- * pick up the root config. Standalone repos without a monorepo marker
4145
- * only see a config that sits at `startDir` itself.
4218
+ * By default, walks up from `startDir` via `findWorkspaceRoot` (monorepo
4219
+ * markers only — `pnpm-workspace.yaml`, `workspaces` in `package.json`,
4220
+ * `lerna.json`) so monorepo invocations from any subdirectory still pick up
4221
+ * the root config. Pass `walkUp: false` to read `startDir` directly when the
4222
+ * caller already holds the exact workspace root.
4146
4223
  *
4147
- * Best-effort: if there's no config file or evaluation fails, return
4148
- * `undefined` so the create flow behaves as if no default was set.
4224
+ * Best-effort for resolution: a missing or unresolvable config reads as
4225
+ * empty. A present-but-malformed `create.templates` still throws a
4226
+ * {@link CreateConfigSchemaError} so the misconfiguration surfaces.
4227
+ *
4228
+ * Pass `throwOnReadError: true` for read-modify-write callers (registration):
4229
+ * if a config file exists but cannot be evaluated, an empty read would let a
4230
+ * later write clobber the real `create` block, so the eval error is rethrown
4231
+ * instead of swallowed.
4232
+ */
4233
+ async function getConfiguredCreate(startDir, options) {
4234
+ const projectRoot = options?.walkUp === false ? startDir : findWorkspaceRoot(startDir) ?? startDir;
4235
+ if (!hasViteConfig(projectRoot)) return { templates: [] };
4236
+ let create;
4237
+ try {
4238
+ create = (await resolveViteConfig(projectRoot)).create;
4239
+ } catch (error) {
4240
+ if (options?.throwOnReadError) throw error;
4241
+ return { templates: [] };
4242
+ }
4243
+ const defaultTemplate = typeof create?.defaultTemplate === "string" && create.defaultTemplate.length > 0 ? create.defaultTemplate : void 0;
4244
+ const templates = validateCreateTemplates(create?.templates);
4245
+ return {
4246
+ ...defaultTemplate !== void 0 ? { defaultTemplate } : {},
4247
+ templates
4248
+ };
4249
+ }
4250
+ /**
4251
+ * Read `create.defaultTemplate` only. Best-effort for missing or unresolvable
4252
+ * configs (returns `undefined`), but a malformed `create.templates` still
4253
+ * rethrows its {@link CreateConfigSchemaError}: swallowing it here would
4254
+ * silently drop a valid `defaultTemplate` along with the diagnostic.
4149
4255
  */
4150
4256
  async function getConfiguredDefaultTemplate(startDir) {
4151
- const projectRoot = findWorkspaceRoot(startDir) ?? startDir;
4152
- if (!hasViteConfig(projectRoot)) return;
4153
4257
  try {
4154
- const value = (await resolveViteConfig(projectRoot)).create?.defaultTemplate;
4155
- if (typeof value === "string" && value.length > 0) return value;
4156
- } catch {}
4258
+ return (await getConfiguredCreate(startDir)).defaultTemplate;
4259
+ } catch (error) {
4260
+ if (error instanceof CreateConfigSchemaError) throw error;
4261
+ return;
4262
+ }
4263
+ }
4264
+ //#endregion
4265
+ //#region src/create/register-template.ts
4266
+ /**
4267
+ * Register a local template into `create.templates` in a monorepo's root
4268
+ * `vite.config.ts`. Used after `vp create vite:generator` scaffolds a
4269
+ * generator so the generated template shows up in this workspace's
4270
+ * `vp create` picker.
4271
+ *
4272
+ * Behavior:
4273
+ * - Reads the existing `create` config from the workspace root's `vite.config.*`.
4274
+ * - If an entry with the same `name` already exists → no-op (idempotent),
4275
+ * warning when it points at a different `template` so a stale entry does
4276
+ * not silently shadow the new generator.
4277
+ * - Otherwise appends `entry` to `create.templates`, preserving any sibling
4278
+ * `create.defaultTemplate` and any existing entries, and writes back.
4279
+ * - If there is no `vite.config.*` yet, or no `create` block, it is created.
4280
+ *
4281
+ * Read-modify-write: the existing `create` object is read in full first and
4282
+ * the complete, recomputed object is written back via `upsertJsonConfig`
4283
+ * (replace the existing `create` value, or insert the key), so
4284
+ * `defaultTemplate` and prior `templates` are kept. Throws when the config
4285
+ * shape is not supported by the upsert, rather than writing nothing or a key
4286
+ * that is dead at runtime.
4287
+ *
4288
+ * Returns the absolute path of the config file written, so the caller can fold
4289
+ * it into its own formatting pass (the upsert writes a JSON-style block that
4290
+ * needs reformatting). Returns `undefined` for the idempotent no-op.
4291
+ */
4292
+ async function registerLocalTemplate(workspaceRoot, entry, silent = false) {
4293
+ const configPath = findViteConfig(workspaceRoot);
4294
+ const existing = await getConfiguredCreate(workspaceRoot, {
4295
+ walkUp: false,
4296
+ throwOnReadError: true
4297
+ });
4298
+ const existingEntry = existing.templates.find((t) => t.name === entry.name);
4299
+ if (existingEntry) {
4300
+ if (existingEntry.template !== entry.template) log.warn(`create.templates already has an entry named '${entry.name}' pointing at '${existingEntry.template}'; left unchanged.\nUpdate it by hand if it should run '${entry.template}' instead.`);
4301
+ return;
4302
+ }
4303
+ const nextCreate = {
4304
+ ...existing.defaultTemplate !== void 0 ? { defaultTemplate: existing.defaultTemplate } : {},
4305
+ templates: [...existing.templates, entry]
4306
+ };
4307
+ const targetPath = configPath ?? ensureViteConfig(workspaceRoot, silent);
4308
+ writeCreateBlock(targetPath, nextCreate);
4309
+ return targetPath;
4310
+ }
4311
+ /**
4312
+ * Create a minimal `vite.config.ts` (matching the migrator's
4313
+ * `ensureViteConfig` shape) and return its absolute path.
4314
+ */
4315
+ function ensureViteConfig(workspaceRoot, silent) {
4316
+ const configPath = path.join(workspaceRoot, "vite.config.ts");
4317
+ fs.writeFileSync(configPath, `import { defineConfig } from '${VITE_PLUS_NAME}';\n\nexport default defineConfig({});\n`);
4318
+ if (!silent) log.success(`✔ Created vite.config.ts in ${displayRelative(configPath)}`);
4319
+ return configPath;
4320
+ }
4321
+ /**
4322
+ * Write the full `create` object into vite.config.ts via the shared config
4323
+ * upsert: replace the existing `create:` value in place, or insert the key
4324
+ * when absent. The caller reformats the file afterward, so the JSON-style
4325
+ * block written here is normalized to the surrounding style.
4326
+ *
4327
+ * Throws when the config shape is not supported (`updated: false`, e.g.
4328
+ * `module.exports` or `export default someVar`), so the caller can warn and
4329
+ * point at a manual edit instead of reporting a registration that never
4330
+ * happened.
4331
+ */
4332
+ function writeCreateBlock(configPath, create) {
4333
+ const tempPath = path.join(os.tmpdir(), `vite-plus-create-register-${randomUUID()}.json`);
4334
+ fs.writeFileSync(tempPath, JSON.stringify(create));
4335
+ try {
4336
+ const result = upsertJsonConfig(configPath, tempPath, "create");
4337
+ if (!result.updated) throw new Error(`could not find a supported config object in ${displayRelative(configPath)}`);
4338
+ fs.writeFileSync(configPath, result.content);
4339
+ } finally {
4340
+ fs.rmSync(tempPath, { force: true });
4341
+ }
4157
4342
  }
4158
4343
  //#endregion
4159
4344
  //#region src/create/templates/generator.ts
@@ -4448,7 +4633,7 @@ const helpMessage = renderCliDoc({
4448
4633
  `- Default: ${accent("vite:monorepo")}, ${accent("vite:application")}, ${accent("vite:library")}, ${accent("vite:generator")}`,
4449
4634
  "- Remote: vite, @tanstack/start, create-next-app,",
4450
4635
  " create-nuxt, github:user/repo, https://github.com/user/template-repo, etc.",
4451
- "- Local: @company/generator-*, ./tools/create-ui-component",
4636
+ "- Local: a `create.templates` entry name from vite.config.ts (monorepo)",
4452
4637
  `- Org scope: ${accent("@your-org")} → picker from ${accent("@your-org/create")}'s ${accent("createConfig.templates")} manifest`,
4453
4638
  `- Org entry: ${accent("@your-org:web")} → manifest entry "web" from ${accent("@your-org/create")}`,
4454
4639
  `When omitted, uses \`create.defaultTemplate\` from vite.config.ts if set.`
@@ -4466,10 +4651,18 @@ const helpMessage = renderCliDoc({
4466
4651
  label: "--agent NAME",
4467
4652
  description: "Write coding agent instructions to AGENTS.md, CLAUDE.md, etc."
4468
4653
  },
4654
+ {
4655
+ label: "--no-agent",
4656
+ description: "Skip writing coding agent instructions"
4657
+ },
4469
4658
  {
4470
4659
  label: "--editor NAME",
4471
4660
  description: "Write editor config files for the specified editor."
4472
4661
  },
4662
+ {
4663
+ label: "--no-editor",
4664
+ description: "Skip writing editor config files"
4665
+ },
4473
4666
  {
4474
4667
  label: "--git",
4475
4668
  description: "Initialize a git repository with an initial commit"
@@ -4490,6 +4683,10 @@ const helpMessage = renderCliDoc({
4490
4683
  label: "--package-manager NAME",
4491
4684
  description: "Use specified package manager (pnpm, npm, yarn, bun)"
4492
4685
  },
4686
+ {
4687
+ label: "--approve-builds",
4688
+ description: "Approve and run gated dependency build scripts without prompting"
4689
+ },
4493
4690
  {
4494
4691
  label: "--verbose",
4495
4692
  description: "Show detailed scaffolding output"
@@ -4638,7 +4835,8 @@ function parseArgs() {
4638
4835
  "interactive",
4639
4836
  "hooks",
4640
4837
  "verbose",
4641
- "git"
4838
+ "git",
4839
+ "approve-builds"
4642
4840
  ],
4643
4841
  string: [
4644
4842
  "directory",
@@ -4660,7 +4858,8 @@ function parseArgs() {
4660
4858
  editor: normalizeEditorOption(parsed.editor),
4661
4859
  git: parsed.git,
4662
4860
  hooks: parsed.hooks,
4663
- packageManager: parsed["package-manager"]
4861
+ packageManager: parsed["package-manager"],
4862
+ approveBuilds: parsed["approve-builds"] || false
4664
4863
  },
4665
4864
  templateArgs
4666
4865
  };
@@ -4770,11 +4969,28 @@ async function main() {
4770
4969
  let shouldSetupHooks = false;
4771
4970
  let bundled;
4772
4971
  let skipShorthandExpansion = false;
4972
+ let registeredConfigPath;
4773
4973
  const installArgs = process.env.CI ? ["--no-frozen-lockfile"] : void 0;
4774
- if (!selectedTemplateName) {
4775
- const defaultTemplate = await getConfiguredDefaultTemplate(workspaceInfoOptional.rootDir);
4974
+ let localTemplates = [];
4975
+ if (isMonorepo) try {
4976
+ const configuredCreate = await getConfiguredCreate(workspaceInfoOptional.rootDir, { throwOnReadError: true });
4977
+ localTemplates = configuredCreate.templates;
4978
+ if (!selectedTemplateName && configuredCreate.defaultTemplate) selectedTemplateName = configuredCreate.defaultTemplate;
4979
+ } catch (error) {
4980
+ if (error instanceof CreateConfigSchemaError) cancelAndExit(error.message, 1);
4981
+ log.warn(`Could not read \`create\` config from the workspace vite.config (${error.message}); local templates are unavailable`);
4982
+ }
4983
+ else if (!selectedTemplateName) {
4984
+ let defaultTemplate;
4985
+ try {
4986
+ defaultTemplate = await getConfiguredDefaultTemplate(workspaceInfoOptional.rootDir);
4987
+ } catch (error) {
4988
+ if (error instanceof CreateConfigSchemaError) cancelAndExit(error.message, 1);
4989
+ throw error;
4990
+ }
4776
4991
  if (defaultTemplate) selectedTemplateName = defaultTemplate;
4777
4992
  }
4993
+ let resolvedByOrg = false;
4778
4994
  if (selectedTemplateName) {
4779
4995
  const resolved = await resolveOrgManifestForCreate({
4780
4996
  templateName: selectedTemplateName,
@@ -4784,8 +5000,11 @@ async function main() {
4784
5000
  if (resolved.kind === "replaced") {
4785
5001
  selectedTemplateName = resolved.templateName;
4786
5002
  skipShorthandExpansion = true;
4787
- } else if (resolved.kind === "bundled") bundled = resolved;
4788
- else if (resolved.kind === "escape-hatch") selectedTemplateName = "";
5003
+ resolvedByOrg = true;
5004
+ } else if (resolved.kind === "bundled") {
5005
+ bundled = resolved;
5006
+ resolvedByOrg = true;
5007
+ } else if (resolved.kind === "escape-hatch") selectedTemplateName = "";
4789
5008
  }
4790
5009
  if (!selectedTemplateName && !options.interactive) {
4791
5010
  console.error(`
@@ -4804,11 +5023,17 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
4804
5023
  if (!selectedTemplateName) {
4805
5024
  const template = await select({
4806
5025
  message: "",
4807
- options: getInitialTemplateOptions(isMonorepo)
5026
+ options: getInitialTemplateOptions(isMonorepo, localTemplates)
4808
5027
  });
4809
5028
  if (R(template)) cancelAndExit();
4810
5029
  selectedTemplateName = template;
4811
5030
  }
5031
+ const matchedLocalTemplate = resolvedByOrg ? void 0 : localTemplates.find((entry) => entry.name === selectedTemplateName);
5032
+ if (matchedLocalTemplate) {
5033
+ selectedTemplateName = matchedLocalTemplate.template;
5034
+ skipShorthandExpansion = true;
5035
+ }
5036
+ const isLocalTemplate = matchedLocalTemplate !== void 0;
4812
5037
  const isBuiltinTemplate = selectedTemplateName.startsWith("vite:");
4813
5038
  const isBundledTemplate = bundled !== void 0;
4814
5039
  const isBundledMonorepo = bundled?.monorepo === true;
@@ -4823,6 +5048,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
4823
5048
  log.info("The vite:generator template requires a monorepo workspace.\nRun this command inside a Vite+ monorepo, or create one first with `vp create vite:monorepo`");
4824
5049
  cancelAndExit("Cannot create a generator outside a monorepo", 1);
4825
5050
  }
5051
+ if (isMonorepo && options.git !== void 0) cancelAndExit("The --git/--no-git options are not available when adding a package to an existing monorepo", 1);
4826
5052
  if (isInSubdirectory && !compactOutput) log.info(`Detected monorepo root at ${accent(workspaceInfoOptional.rootDir)}`);
4827
5053
  if (isMonorepo && options.interactive && !targetDir) {
4828
5054
  let parentDir;
@@ -4845,7 +5071,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
4845
5071
  const selected = await select({
4846
5072
  message: "Where should the new package be added to the monorepo:",
4847
5073
  options: dirOptions,
4848
- initialValue: shouldOfferCwdOption ? cwdRelativeToRoot : inferParentDir(selectedTemplateName, workspaceInfoOptional) ?? workspaceInfoOptional.parentDirs[0]
5074
+ initialValue: shouldOfferCwdOption ? cwdRelativeToRoot : inferParentDir(selectedTemplateName, workspaceInfoOptional, isLocalTemplate) ?? workspaceInfoOptional.parentDirs[0]
4849
5075
  });
4850
5076
  if (R(selected)) cancelAndExit();
4851
5077
  if (selected !== "other") parentDir = selected;
@@ -4865,7 +5091,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
4865
5091
  }
4866
5092
  if (isMonorepo && !options.interactive && !targetDir) {
4867
5093
  if (isInSubdirectory && !compactOutput) log.info(`Use ${accent("--directory")} to specify a different target location.`);
4868
- selectedParentDir = inferParentDir(selectedTemplateName, workspaceInfoOptional) ?? workspaceInfoOptional.parentDirs[0];
5094
+ selectedParentDir = inferParentDir(selectedTemplateName, workspaceInfoOptional, isLocalTemplate) ?? workspaceInfoOptional.parentDirs[0];
4869
5095
  }
4870
5096
  if (isGitHubUrl(selectedTemplateName)) if (hasExplicitTargetDir(selectedTemplateArgs)) remoteTargetDir = selectedTemplateArgs[0];
4871
5097
  else {
@@ -4931,7 +5157,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
4931
5157
  editor: options.editor,
4932
5158
  onCancel: () => cancelAndExit()
4933
5159
  });
4934
- const shouldSetupGit = await promptGitInit(options);
5160
+ const shouldSetupGit = await resolveGitInit(options, isMonorepo);
4935
5161
  if (!isMonorepo) shouldSetupHooks = await promptGitHooks(options);
4936
5162
  const createProgress = options.interactive && compactOutput ? spinner({ indicator: "timer" }) : void 0;
4937
5163
  let createProgressStarted = false;
@@ -4970,8 +5196,28 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
4970
5196
  createProgressStarted = true;
4971
5197
  }
4972
5198
  };
5199
+ let migratePendingBuilds = [];
5200
+ const handleIgnoredBuilds = async (projectPath, installCwd, summary) => {
5201
+ if (summary?.status !== "installed") return;
5202
+ const reportedBuilds = [...new Set([...summary.pendingBuilds ?? [], ...migratePendingBuilds])];
5203
+ const targets = resolveApproveBuildTargets(projectPath, await detectGatedBuilds(installCwd, workspaceInfo.packageManager, reportedBuilds), workspaceInfo.packageManager);
5204
+ if (targets.length === 0) return;
5205
+ pauseCreateProgress();
5206
+ const approved = await approveBuilds({
5207
+ cwd: installCwd,
5208
+ projectDir: projectPath,
5209
+ packageManager: workspaceInfo.packageManager,
5210
+ packageManagerVersion: workspaceInfo.downloadPackageManager.version,
5211
+ targets,
5212
+ interactive: options.interactive,
5213
+ autoApprove: options.approveBuilds === true,
5214
+ silent: compactOutput
5215
+ });
5216
+ resumeCreateProgress();
5217
+ if (!approved && !options.interactive && options.approveBuilds === true) process.exitCode = 1;
5218
+ };
4973
5219
  updateCreateProgress("Scaffolding project");
4974
- const templateInfo = discoverTemplate(selectedTemplateName, selectedTemplateArgs, workspaceInfo, options.interactive, bundled?.bundledLocalPath, skipShorthandExpansion);
5220
+ const templateInfo = discoverTemplate(selectedTemplateName, selectedTemplateArgs, workspaceInfo, options.interactive, bundled?.bundledLocalPath, skipShorthandExpansion, isLocalTemplate);
4975
5221
  if (selectedParentDir) templateInfo.parentDir = selectedParentDir;
4976
5222
  if (targetDir) templateInfo.parentDir = void 0;
4977
5223
  if (remoteTargetDir) {
@@ -4982,7 +5228,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
4982
5228
  }
4983
5229
  if (templateInfo.command === BuiltinTemplate.monorepo || isBundledMonorepo) {
4984
5230
  let shouldInitGit = shouldSetupGit;
4985
- if (options.interactive && !compactOutput) {
5231
+ if (options.interactive && !compactOutput && options.git === void 0) {
4986
5232
  pauseCreateProgress();
4987
5233
  const selected = await confirm({
4988
5234
  message: "Initialize git repository:",
@@ -5061,13 +5307,19 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
5061
5307
  const installSummary = await runViteInstall(fullPath, options.interactive, installArgs, {
5062
5308
  silent: compactOutput,
5063
5309
  packageManager: workspaceInfo.packageManager,
5064
- packageManagerVersion: workspaceInfo.downloadPackageManager.version
5310
+ packageManagerVersion: workspaceInfo.downloadPackageManager.version,
5311
+ detectIgnoredBuilds: true
5065
5312
  });
5313
+ await handleIgnoredBuilds(fullPath, fullPath, installSummary);
5066
5314
  updateCreateProgress("Formatting code");
5067
5315
  await runViteFmt(fullPath, options.interactive, void 0, { silent: compactOutput });
5068
5316
  if (shouldSetupGit) {
5069
5317
  updateCreateProgress("Creating initial commit");
5070
- if (!await createInitialCommit(fullPath)) log.warn("Initial commit failed. Check your git user.name/user.email config");
5318
+ const commitResult = await createInitialCommit(fullPath);
5319
+ if (!commitResult.success) {
5320
+ log.warn("Initial commit failed");
5321
+ if (commitResult.output) log.info(commitResult.output);
5322
+ }
5071
5323
  }
5072
5324
  clearCreateProgress();
5073
5325
  showCreateSummary({
@@ -5123,6 +5375,24 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
5123
5375
  process.exit(0);
5124
5376
  }
5125
5377
  const fullPath = path.join(workspaceInfo.rootDir, projectDir);
5378
+ if (selectedTemplateName === BuiltinTemplate.generator && isMonorepo) {
5379
+ updateCreateProgress("Registering generator");
5380
+ pauseCreateProgress();
5381
+ const generatorTemplatePath = `./${projectDir.split(path.sep).join("/")}`;
5382
+ let generatorName = packageName;
5383
+ try {
5384
+ const generatorPkg = readJsonFile(path.join(fullPath, "package.json"));
5385
+ generatorName = generatorPkg.name ?? packageName;
5386
+ if (generatorName) registeredConfigPath = await registerLocalTemplate(workspaceInfo.rootDir, {
5387
+ name: generatorName,
5388
+ description: generatorPkg.description || `Run the ${generatorName} generator`,
5389
+ template: generatorTemplatePath
5390
+ }, compactOutput);
5391
+ } catch (error) {
5392
+ log.warn(`Could not register the generator in create.templates (${error.message}).\nAdd it by hand: { name: '${generatorName || path.basename(projectDir)}', template: '${generatorTemplatePath}' }`);
5393
+ }
5394
+ resumeCreateProgress();
5395
+ }
5126
5396
  const agentInstructionsRoot = isMonorepo ? workspaceInfo.rootDir : fullPath;
5127
5397
  updateCreateProgress("Writing agent instructions");
5128
5398
  pauseCreateProgress();
@@ -5162,9 +5432,11 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
5162
5432
  installSummary = await runViteInstall(installCwd, options.interactive, installArgs, {
5163
5433
  silent: compactOutput,
5164
5434
  packageManager: workspaceInfo.packageManager,
5165
- packageManagerVersion: workspaceInfo.downloadPackageManager.version
5435
+ packageManagerVersion: workspaceInfo.downloadPackageManager.version,
5436
+ detectIgnoredBuilds: true
5166
5437
  });
5167
5438
  if (installSummary.status !== "installed") return;
5439
+ if (installSummary.pendingBuilds && installSummary.pendingBuilds.length > 0) migratePendingBuilds = installSummary.pendingBuilds;
5168
5440
  updateCreateProgress("Migrating lint and format tools");
5169
5441
  pauseCreateProgress();
5170
5442
  await promptEslintMigration(fullPath, false);
@@ -5213,15 +5485,13 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
5213
5485
  installSummary = await runViteInstall(workspaceInfo.rootDir, options.interactive, installArgs, {
5214
5486
  silent: compactOutput,
5215
5487
  packageManager: workspaceInfo.packageManager,
5216
- packageManagerVersion: workspaceInfo.downloadPackageManager.version
5488
+ packageManagerVersion: workspaceInfo.downloadPackageManager.version,
5489
+ detectIgnoredBuilds: true
5217
5490
  });
5491
+ await handleIgnoredBuilds(fullPath, workspaceInfo.rootDir, installSummary);
5218
5492
  updateCreateProgress("Formatting code");
5219
- await runViteFmt(workspaceInfo.rootDir, options.interactive, [projectDir], { silent: compactOutput });
5220
- if (shouldSetupGit) {
5221
- updateCreateProgress("Creating initial commit");
5222
- await initGitRepository(workspaceInfo.rootDir);
5223
- await createInitialCommit(workspaceInfo.rootDir);
5224
- }
5493
+ const fmtPaths = registeredConfigPath ? [projectDir, path.relative(workspaceInfo.rootDir, registeredConfigPath)] : [projectDir];
5494
+ await runViteFmt(workspaceInfo.rootDir, options.interactive, fmtPaths, { silent: compactOutput });
5225
5495
  } else {
5226
5496
  if (shouldMigrateLintFmtTools) await installAndMigrate(fullPath);
5227
5497
  updateCreateProgress("Applying Vite+ project setup");
@@ -5236,13 +5506,19 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
5236
5506
  installSummary = await runViteInstall(fullPath, options.interactive, installArgs, {
5237
5507
  silent: compactOutput,
5238
5508
  packageManager: workspaceInfo.packageManager,
5239
- packageManagerVersion: workspaceInfo.downloadPackageManager.version
5509
+ packageManagerVersion: workspaceInfo.downloadPackageManager.version,
5510
+ detectIgnoredBuilds: true
5240
5511
  });
5512
+ await handleIgnoredBuilds(fullPath, fullPath, installSummary);
5241
5513
  updateCreateProgress("Formatting code");
5242
5514
  await runViteFmt(fullPath, options.interactive, void 0, { silent: compactOutput });
5243
5515
  if (shouldSetupGit) {
5244
5516
  updateCreateProgress("Creating initial commit");
5245
- if (!await createInitialCommit(fullPath)) log.warn("Initial commit failed. Check your git user.name/user.email config");
5517
+ const commitResult = await createInitialCommit(fullPath);
5518
+ if (!commitResult.success) {
5519
+ log.warn("Initial commit failed");
5520
+ if (commitResult.output) log.info(commitResult.output);
5521
+ }
5246
5522
  }
5247
5523
  }
5248
5524
  clearCreateProgress();