vite-plus 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/oxfmt +6 -3
- package/bin/oxlint +6 -3
- package/bin/vpr +9 -0
- package/binding/index.cjs +53 -53
- package/binding/index.d.cts +86 -144
- package/dist/agent-D3nZAMGI.js +9456 -0
- package/dist/bin.js +10 -7
- package/dist/{cli-truncate-CWsmbK3p.js → cli-truncate-BVOlIIPy.js} +1 -1
- package/dist/config/bin.js +2 -2
- package/dist/{constants-CrfJQIUX.js → constants-CZ7Mnq-C.js} +7 -3
- package/dist/create/bin.js +800 -806
- package/dist/{define-config-2tfJoXr1.d.ts → define-config-BuMs_LKa.d.ts} +64 -9
- package/dist/{define-config-DJUehepE.js → define-config-D2jo1xVA.js} +43 -18
- package/dist/{define-config-BGSjF6Xp.cjs → define-config-Dut6XD42.cjs} +61 -22
- package/dist/define-config.cjs +2 -1
- package/dist/define-config.d.ts +2 -2
- package/dist/define-config.js +2 -2
- package/dist/{dist-Oxo16Y0q.js → dist-CKz3vcoG.js} +4 -4
- package/dist/dist-CtM2JRbM.js +3 -0
- package/dist/editor-D1BZiU1h.js +584 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{json-Dn87fvjk.js → json-DiRs8ceZ.js} +13 -1
- package/dist/{log-update-lyIiuflf.js → log-update-DHZRyJ2m.js} +2 -2
- package/dist/migration/bin.d.ts +1 -10
- package/dist/migration/bin.js +531 -170
- package/dist/migration/compat/worker.d.ts +1 -0
- package/dist/migration/compat/worker.js +35 -0
- package/dist/{oxlint-plugin-config-q8a5PFch.js → oxlint-plugin-config-BSbv66Qn.js} +1 -1
- package/dist/oxlint-plugin.js +78 -9
- package/dist/pack-bin.js +1 -1
- package/dist/{package-BHirM1_v.js → package-Cpo2ei_V.js} +52 -3
- package/dist/protocol-D99W10Qi.js +4 -0
- package/dist/{report-BHSkWqRR.js → report-BS7ULx7A.js} +2 -0
- package/dist/{resolve-vite-config-CmdsfQzS.js → resolve-vite-config-DX2o0SWM.js} +14 -10
- package/dist/{rolldown-runtime-DnnnRqeS.js → rolldown-runtime-C7HZzL1F.js} +1 -1
- package/dist/staged/bin.js +23 -19
- package/dist/{tsconfig-BWQPmGKz.js → tsconfig-D6sTl0dN.js} +439 -208
- package/dist/version.js +2 -5
- package/dist/versions.js +5 -5
- package/dist/{wrap-ansi-k7Dn4VtV.js → wrap-ansi-DNjkuBEp.js} +1 -1
- package/dist/wrap-ansi-DUi52C6W.js +2 -0
- package/docs/_data/team.ts +9 -0
- package/docs/config/check.md +35 -0
- package/docs/config/index.md +2 -0
- package/docs/config/run.md +61 -12
- package/docs/guide/automatic-data-tracking.md +145 -0
- package/docs/guide/cache.md +14 -35
- package/docs/guide/check.md +16 -0
- package/docs/guide/ci.md +4 -0
- package/docs/guide/docker.md +175 -0
- package/docs/guide/env.md +13 -1
- package/docs/guide/github-actions-cache.md +165 -0
- package/docs/guide/install.md +12 -2
- package/docs/guide/migrate-rules.md +347 -0
- package/docs/guide/migrate.md +14 -0
- package/docs/guide/pack.md +8 -0
- package/docs/guide/run.md +33 -3
- package/docs/guide/troubleshooting.md +3 -3
- package/docs/guide/upgrade.md +43 -21
- package/docs/package.json +3 -2
- package/docs/vite.config.ts +17 -0
- package/package.json +19 -18
- package/dist/agent--cKmgD_n.js +0 -3341
- package/dist/compat-Cql3K40m.js +0 -14
- package/dist/dist-DRJUd9bL.js +0 -3
- package/dist/workspace-D0AVy4fu.js +0 -5830
- package/dist/wrap-ansi-CeQuiQ31.js +0 -2
package/dist/create/bin.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { r as __toESM, t as __commonJSMin } from "../rolldown-runtime-
|
|
2
|
-
import {
|
|
3
|
-
import { A as
|
|
1
|
+
import { r as __toESM, t as __commonJSMin } from "../rolldown-runtime-C7HZzL1F.js";
|
|
2
|
+
import { s as VITE_PLUS_NAME } from "../constants-CZ7Mnq-C.js";
|
|
3
|
+
import { A as intro, B as runCommandSilently, C as resolveApproveBuildTargets, E as PackageManager, F as spinner, I as text, L as require_picocolors, M as multiselect, O as cancel, P as select, R as isCancel, S as detectGatedBuilds, T as DependencyType, V as require_cross_spawn, d as downloadPackageManager$1, f as promptGitHooks, g as selectPackageManager, h as runViteInstall, j as log, k as confirm, m as runViteFmt, p as resolveGitInit, u as defaultInteractive, x as approveBuilds, z as runCommand$1 } from "../tsconfig-D6sTl0dN.js";
|
|
4
4
|
import { a as printHeader, i as muted, o as success, r as log$1, t as accent } from "../terminal-uTv0ZaMr.js";
|
|
5
|
-
import {
|
|
6
|
-
import { a as resolveViteConfig, n as findWorkspaceRoot, r as hasViteConfig, t as findViteConfig } from "../resolve-vite-config-
|
|
5
|
+
import { i as readJsonFile, n as editJsonFile } from "../json-DiRs8ceZ.js";
|
|
6
|
+
import { a as resolveViteConfig, n as findWorkspaceRoot, r as hasViteConfig, t as findViteConfig } from "../resolve-vite-config-DX2o0SWM.js";
|
|
7
7
|
import { t as lib_default } from "../lib-L3DWSRQp.js";
|
|
8
|
-
import { o as fetchNpmResource, s as getNpmRegistry, t as checkNpmPackageExists } from "../package-
|
|
9
|
-
import {
|
|
10
|
-
import { c as
|
|
8
|
+
import { o as fetchNpmResource, s as getNpmRegistry, t as checkNpmPackageExists } from "../package-Cpo2ei_V.js";
|
|
9
|
+
import { A as detectWorkspace$1, F as detectEslintProject, M as updatePackageJsonWithDeps, N as updateWorkspaceConfig, R as promptEslintMigration, S as injectCreateDefaultTemplate, X as templatesDir, Y as displayRelative, a as selectAgentTargets, c as writeCopilotSetupWorkflow, d as rewriteStandaloneProject, g as setPackageManager, j as isBingoTemplate, l as rewriteMonorepo, r as detectExistingAgentTargetPaths, s as writeAgentInstructions, t as COPILOT_AGENT_ID, u as rewriteMonorepoProject, y as installGitHooks } from "../agent-D3nZAMGI.js";
|
|
10
|
+
import { a as writeEditorConfigs, c as hasFrameworkShim, f as promptPrettierMigration, i as selectEditors, n as detectExistingEditors, o as addFrameworkShim, s as detectFramework, u as detectPrettierProject } from "../editor-D1BZiU1h.js";
|
|
11
11
|
import { t as renderCliDoc } from "../help-YP84FSEz.js";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { runCommand, upsertJsonConfig, vitePlusHeader } from "../../binding/index.js";
|
|
@@ -27,28 +27,6 @@ async function initGitRepository(cwd) {
|
|
|
27
27
|
envs: process.env
|
|
28
28
|
})).exitCode === 0;
|
|
29
29
|
}
|
|
30
|
-
async function createInitialCommit(cwd) {
|
|
31
|
-
await runCommandSilently({
|
|
32
|
-
command: "git",
|
|
33
|
-
args: ["add", "-A"],
|
|
34
|
-
cwd,
|
|
35
|
-
envs: process.env
|
|
36
|
-
});
|
|
37
|
-
const result = await runCommandSilently({
|
|
38
|
-
command: "git",
|
|
39
|
-
args: [
|
|
40
|
-
"commit",
|
|
41
|
-
"-m",
|
|
42
|
-
"Initial commit from Vite+"
|
|
43
|
-
],
|
|
44
|
-
cwd,
|
|
45
|
-
envs: process.env
|
|
46
|
-
});
|
|
47
|
-
return {
|
|
48
|
-
success: result.exitCode === 0,
|
|
49
|
-
output: `${result.stdout.toString()}${result.stderr.toString()}`.trim()
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
30
|
//#endregion
|
|
53
31
|
//#region src/create/command.ts
|
|
54
32
|
async function runCommandAndDetectProjectDir(options, parentDir) {
|
|
@@ -127,829 +105,861 @@ function prependToPathToEnvs(extraPath, envs) {
|
|
|
127
105
|
return envs;
|
|
128
106
|
}
|
|
129
107
|
//#endregion
|
|
130
|
-
//#region
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
108
|
+
//#region ../../node_modules/.pnpm/nanotar@0.3.0/node_modules/nanotar/dist/index.mjs
|
|
109
|
+
const tarItemTypeMap = {
|
|
110
|
+
"0": "file",
|
|
111
|
+
"1": "hardLink",
|
|
112
|
+
"2": "symbolicLink",
|
|
113
|
+
"3": "characterDevice",
|
|
114
|
+
"4": "blockDevice",
|
|
115
|
+
"5": "directory",
|
|
116
|
+
"6": "fifo",
|
|
117
|
+
"7": "contiguousFile",
|
|
118
|
+
"g": "globalExtendedHeader",
|
|
119
|
+
"x": "extendedHeader",
|
|
120
|
+
"D": "gnuDirectory",
|
|
121
|
+
"I": "gnuInodeMetadata",
|
|
122
|
+
"K": "gnuLongLinkName",
|
|
123
|
+
"L": "gnuLongFileName",
|
|
124
|
+
"N": "gnuOldLongFileName",
|
|
125
|
+
"M": "gnuMultiVolume",
|
|
126
|
+
"S": "gnuSparseFile",
|
|
127
|
+
"E": "gnuExtendedSparse",
|
|
128
|
+
"A": "solarisAcl",
|
|
129
|
+
"V": "solarisVolumeLabel",
|
|
130
|
+
"X": "solarisOldExtendedHeader"
|
|
131
|
+
};
|
|
132
|
+
function parseTar(data, opts) {
|
|
133
|
+
const buffer = data.buffer || data;
|
|
134
|
+
const files = [];
|
|
135
|
+
let offset = 0;
|
|
136
|
+
let nextExtendedHeader;
|
|
137
|
+
let globalExtendedHeader;
|
|
138
|
+
while (offset < buffer.byteLength - 512) {
|
|
139
|
+
let name = _readString(buffer, offset, 100);
|
|
140
|
+
if (name.length === 0) break;
|
|
141
|
+
if (nextExtendedHeader) {
|
|
142
|
+
const longName = nextExtendedHeader.path || nextExtendedHeader.linkpath;
|
|
143
|
+
if (longName) name = longName;
|
|
144
|
+
}
|
|
145
|
+
const mode = _readString(buffer, offset + 100, 8).trim();
|
|
146
|
+
const uid = Number.parseInt(_readString(buffer, offset + 108, 8));
|
|
147
|
+
const gid = Number.parseInt(_readString(buffer, offset + 116, 8));
|
|
148
|
+
const size = _readNumber(buffer, offset + 124, 12);
|
|
149
|
+
const seek = 512 + 512 * Math.trunc(size / 512) + (size % 512 ? 512 : 0);
|
|
150
|
+
const mtime = _readNumber(buffer, offset + 136, 12);
|
|
151
|
+
const _type = _readString(buffer, offset + 156, 1) || "0";
|
|
152
|
+
const type = tarItemTypeMap[_type] || _type;
|
|
153
|
+
switch (type) {
|
|
154
|
+
case "extendedHeader":
|
|
155
|
+
case "globalExtendedHeader": {
|
|
156
|
+
const headers = _parseExtendedHeaders(new Uint8Array(buffer, offset + 512, size));
|
|
157
|
+
if (type === "extendedHeader") nextExtendedHeader = headers;
|
|
158
|
+
else {
|
|
159
|
+
nextExtendedHeader = void 0;
|
|
160
|
+
globalExtendedHeader = {
|
|
161
|
+
...globalExtendedHeader,
|
|
162
|
+
...headers
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
offset += seek;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
case "gnuLongFileName":
|
|
169
|
+
case "gnuOldLongFileName":
|
|
170
|
+
case "gnuLongLinkName":
|
|
171
|
+
nextExtendedHeader = { path: _readString(buffer, offset + 512, size) };
|
|
172
|
+
offset += seek;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const user = _readString(buffer, offset + 265, 32);
|
|
176
|
+
const group = _readString(buffer, offset + 297, 32);
|
|
177
|
+
name = _sanitizePath(name);
|
|
178
|
+
const meta = {
|
|
179
|
+
name,
|
|
180
|
+
type,
|
|
181
|
+
size,
|
|
182
|
+
attrs: {
|
|
183
|
+
...globalExtendedHeader,
|
|
184
|
+
...nextExtendedHeader,
|
|
185
|
+
mode,
|
|
186
|
+
uid,
|
|
187
|
+
gid,
|
|
188
|
+
mtime,
|
|
189
|
+
user,
|
|
190
|
+
group
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
nextExtendedHeader = void 0;
|
|
194
|
+
if (opts?.filter && !opts.filter(meta)) {
|
|
195
|
+
offset += seek;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (opts?.metaOnly) {
|
|
199
|
+
files.push(meta);
|
|
200
|
+
offset += seek;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const data2 = size === 0 ? void 0 : new Uint8Array(buffer, offset + 512, size);
|
|
204
|
+
files.push({
|
|
205
|
+
...meta,
|
|
206
|
+
data: data2,
|
|
207
|
+
get text() {
|
|
208
|
+
return new TextDecoder().decode(this.data);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
offset += seek;
|
|
157
212
|
}
|
|
158
|
-
|
|
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
|
-
};
|
|
213
|
+
return files;
|
|
175
214
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
215
|
+
async function parseTarGzip(data, opts = {}) {
|
|
216
|
+
const stream = new ReadableStream({ start(controller) {
|
|
217
|
+
controller.enqueue(new Uint8Array(data));
|
|
218
|
+
controller.close();
|
|
219
|
+
} }).pipeThrough(new DecompressionStream(opts.compression ?? "gzip"));
|
|
220
|
+
return parseTar(await new Response(stream).arrayBuffer(), opts);
|
|
221
|
+
}
|
|
222
|
+
function _sanitizePath(path) {
|
|
223
|
+
let normalized = path.replace(/\\/g, "/");
|
|
224
|
+
normalized = normalized.replace(/^[a-zA-Z]:\//, "");
|
|
225
|
+
normalized = normalized.replace(/^\/+/, "");
|
|
226
|
+
const hasLeadingDotSlash = normalized.startsWith("./");
|
|
227
|
+
const parts = normalized.split("/");
|
|
228
|
+
const resolved = [];
|
|
229
|
+
for (const part of parts) if (part === "..") resolved.pop();
|
|
230
|
+
else if (part !== "." && part !== "") resolved.push(part);
|
|
231
|
+
let result = resolved.join("/");
|
|
232
|
+
if (hasLeadingDotSlash && !result.startsWith("./")) result = "./" + result;
|
|
233
|
+
if (path.endsWith("/") && !result.endsWith("/")) result += "/";
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
function _readString(buffer, offset, size) {
|
|
237
|
+
const view = new Uint8Array(buffer, offset, size);
|
|
238
|
+
const i = view.indexOf(0);
|
|
239
|
+
return new TextDecoder().decode(i === -1 ? view : view.slice(0, i));
|
|
240
|
+
}
|
|
241
|
+
function _readNumber(buffer, offset, size) {
|
|
242
|
+
const view = new Uint8Array(buffer, offset, size);
|
|
243
|
+
let str = "";
|
|
244
|
+
for (let i = 0; i < size; i++) str += String.fromCodePoint(view[i]);
|
|
245
|
+
return Number.parseInt(str, 8);
|
|
246
|
+
}
|
|
247
|
+
function _parseExtendedHeaders(data) {
|
|
248
|
+
const dataStr = new TextDecoder().decode(data);
|
|
249
|
+
const headers = {};
|
|
250
|
+
for (const line of dataStr.split("\n")) {
|
|
251
|
+
const s = line.split(" ")[1]?.split("=");
|
|
252
|
+
if (s) headers[s[0]] = s[1];
|
|
186
253
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
254
|
+
return headers;
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/create/org-tarball.ts
|
|
258
|
+
function getCacheRoot() {
|
|
259
|
+
const home = process.env.VP_HOME || path.join(os.homedir(), ".vite-plus");
|
|
260
|
+
return path.join(home, "tmp", "create-org");
|
|
190
261
|
}
|
|
191
262
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
263
|
+
* Replace characters that are illegal in Windows path segments
|
|
264
|
+
* (`\ / : * ? " < > |` plus the IPv6 bracket pair `[ ]`). The host
|
|
265
|
+
* comes from `new URL(...).host` which can carry a port (`:4873`) or
|
|
266
|
+
* IPv6 literal (`[::1]`); both end up in the cache path otherwise.
|
|
196
267
|
*/
|
|
197
|
-
function
|
|
198
|
-
|
|
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
|
-
};
|
|
268
|
+
function sanitizeHostForPath(host) {
|
|
269
|
+
return host.replaceAll(/[\\/:*?"<>|[\]]/g, "_");
|
|
218
270
|
}
|
|
219
271
|
/**
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
seen.add(entry.name);
|
|
230
|
-
entries.push(entry);
|
|
231
|
-
}
|
|
232
|
-
return entries;
|
|
272
|
+
* Cache extracted tarballs under `<host>/<scope>/create/<version>` so two
|
|
273
|
+
* repos resolving the same `<scope>@<version>` through different registries
|
|
274
|
+
* (via `.npmrc` scope mappings) don't share a cache slot. The registry
|
|
275
|
+
* guarantees `manifest.tarballUrl` is a valid URL, so any parse failure
|
|
276
|
+
* here is a real bug worth surfacing.
|
|
277
|
+
*/
|
|
278
|
+
function getExtractionDir(manifest) {
|
|
279
|
+
const { host } = new URL(manifest.tarballUrl);
|
|
280
|
+
return path.join(getCacheRoot(), sanitizeHostForPath(host), manifest.scope, "create", manifest.version);
|
|
233
281
|
}
|
|
234
|
-
function
|
|
235
|
-
const
|
|
236
|
-
|
|
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
|
-
}
|
|
282
|
+
function parseIntegrity(integrity) {
|
|
283
|
+
const match = integrity.split(/\s+/)[0].match(/^(sha\d+)-(.+)$/);
|
|
284
|
+
if (!match) return null;
|
|
243
285
|
return {
|
|
244
|
-
|
|
245
|
-
|
|
286
|
+
algorithm: match[1],
|
|
287
|
+
expected: match[2]
|
|
246
288
|
};
|
|
247
289
|
}
|
|
248
|
-
function
|
|
249
|
-
if (!
|
|
250
|
-
const
|
|
251
|
-
if (!
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (
|
|
256
|
-
return validateTemplateEntries(templates, "createConfig.templates", (message) => new OrgManifestSchemaError(message, packageName), (entry, index) => validateEntry(entry, index, packageName));
|
|
290
|
+
function verifyIntegrity(bytes, integrity) {
|
|
291
|
+
if (!integrity) return;
|
|
292
|
+
const parsed = parseIntegrity(integrity);
|
|
293
|
+
if (!parsed) return;
|
|
294
|
+
const hash = createHash(parsed.algorithm);
|
|
295
|
+
hash.update(bytes);
|
|
296
|
+
const actual = hash.digest("base64");
|
|
297
|
+
if (actual !== parsed.expected) throw new Error(`integrity check failed: expected ${integrity}, got ${parsed.algorithm}-${actual}`);
|
|
257
298
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
299
|
+
const MAX_TARBALL_BYTES = 50 * 1024 * 1024;
|
|
300
|
+
async function downloadTarball(url) {
|
|
301
|
+
const response = await fetchNpmResource(url, { timeoutMs: 3e4 });
|
|
302
|
+
if (!response.ok) throw new Error(`failed to download tarball (${response.status}): ${url}`);
|
|
303
|
+
const contentLength = Number(response.headers.get("content-length"));
|
|
304
|
+
if (Number.isFinite(contentLength) && contentLength > MAX_TARBALL_BYTES) throw new Error(`tarball exceeds ${MAX_TARBALL_BYTES} byte size limit: ${url}`);
|
|
305
|
+
const reader = response.body?.getReader();
|
|
306
|
+
if (!reader) throw new Error(`tarball response has no body: ${url}`);
|
|
307
|
+
const chunks = [];
|
|
308
|
+
let total = 0;
|
|
309
|
+
while (true) {
|
|
310
|
+
const { done, value } = await reader.read();
|
|
311
|
+
if (done) break;
|
|
312
|
+
total += value.byteLength;
|
|
313
|
+
if (total > MAX_TARBALL_BYTES) {
|
|
314
|
+
await reader.cancel();
|
|
315
|
+
throw new Error(`tarball exceeds ${MAX_TARBALL_BYTES} byte size limit: ${url}`);
|
|
316
|
+
}
|
|
317
|
+
chunks.push(value);
|
|
266
318
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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();
|
|
319
|
+
const bytes = new Uint8Array(total);
|
|
320
|
+
let offset = 0;
|
|
321
|
+
for (const chunk of chunks) {
|
|
322
|
+
bytes.set(chunk, offset);
|
|
323
|
+
offset += chunk.byteLength;
|
|
324
|
+
}
|
|
325
|
+
return bytes;
|
|
291
326
|
}
|
|
292
327
|
/**
|
|
293
|
-
*
|
|
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
|
|
328
|
+
* Download a package tarball and parse its `package/package.json`.
|
|
299
329
|
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
330
|
+
* Some registries (GitHub Packages among them) strip fields they don't
|
|
331
|
+
* recognize — including `createConfig` — from packument *version metadata*
|
|
332
|
+
* while preserving the published tarball byte-for-byte. This gives
|
|
333
|
+
* `readOrgManifest` a fallback source of truth for those registries.
|
|
303
334
|
*
|
|
304
|
-
* `
|
|
305
|
-
*
|
|
335
|
+
* Returns `null` when the archive contains no `package/package.json`.
|
|
336
|
+
* Throws on download/integrity failures or unparsable JSON.
|
|
306
337
|
*/
|
|
307
|
-
async function
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
338
|
+
async function readPackageJsonFromTarball(tarballUrl, integrity) {
|
|
339
|
+
const bytes = await downloadTarball(tarballUrl);
|
|
340
|
+
verifyIntegrity(bytes, integrity);
|
|
341
|
+
const entries = await parseTarGzip(bytes);
|
|
342
|
+
for (const entry of entries) {
|
|
343
|
+
if (normalizeEntryName(entry.name) !== "package.json" || !entry.data) continue;
|
|
344
|
+
const text = new TextDecoder().decode(entry.data);
|
|
345
|
+
try {
|
|
346
|
+
return JSON.parse(text);
|
|
347
|
+
} catch {
|
|
348
|
+
throw new Error(`invalid package.json in tarball: ${tarballUrl}`);
|
|
349
|
+
}
|
|
319
350
|
}
|
|
320
|
-
|
|
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
|
-
};
|
|
351
|
+
return null;
|
|
333
352
|
}
|
|
353
|
+
const STAGING_SUFFIX_PREFIX = ".tmp-";
|
|
334
354
|
/**
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
355
|
+
* Parse a tar entry's stored mode (always octal) into the numeric
|
|
356
|
+
* permission bits (low 9 bits — `rwxrwxrwx`). Returns `undefined` when
|
|
357
|
+
* the mode is missing or unparsable so the caller leaves the file with
|
|
358
|
+
* its default (umask-derived) permissions instead of downgrading.
|
|
338
359
|
*/
|
|
339
|
-
function
|
|
340
|
-
if (!
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
//#region src/create/templates/types.ts
|
|
345
|
-
const LibraryTemplateRepo = "github:sxzz/tsdown-templates/vite-plus";
|
|
346
|
-
const BuiltinTemplate = {
|
|
347
|
-
generator: "vite:generator",
|
|
348
|
-
monorepo: "vite:monorepo",
|
|
349
|
-
application: "vite:application",
|
|
350
|
-
library: "vite:library"
|
|
351
|
-
};
|
|
352
|
-
const TemplateType = {
|
|
353
|
-
builtin: "builtin",
|
|
354
|
-
bingo: "bingo",
|
|
355
|
-
remote: "remote",
|
|
356
|
-
bundled: "bundled"
|
|
357
|
-
};
|
|
358
|
-
//#endregion
|
|
359
|
-
//#region src/create/discovery.ts
|
|
360
|
-
function isGitHubUrl(templateName) {
|
|
361
|
-
return templateName.startsWith("https://github.com/") || templateName.startsWith("github:") || templateName.includes("github.com/");
|
|
362
|
-
}
|
|
363
|
-
function parseGitHubUrl(url) {
|
|
364
|
-
if (url.startsWith("github:")) return url.slice(7);
|
|
365
|
-
const match = url.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
366
|
-
if (match) return match[1].replace(/\.git$/, "");
|
|
367
|
-
return null;
|
|
360
|
+
function parseEntryMode(raw) {
|
|
361
|
+
if (!raw) return;
|
|
362
|
+
const parsed = Number.parseInt(raw, 8);
|
|
363
|
+
if (!Number.isFinite(parsed)) return;
|
|
364
|
+
return parsed & 511;
|
|
368
365
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
366
|
+
/**
|
|
367
|
+
* Strip the `package/` prefix from an `npm pack` tarball entry. Returns
|
|
368
|
+
* `null` for entries to skip (root dir, PaxHeader, anything outside
|
|
369
|
+
* `package/`).
|
|
370
|
+
*/
|
|
371
|
+
function normalizeEntryName(rawName) {
|
|
372
|
+
const name = rawName.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
373
|
+
if (!name || name === "package" || name === "package/") return null;
|
|
374
|
+
if (name.startsWith("PaxHeader/") || name.includes("/PaxHeader/")) return null;
|
|
375
|
+
if (!name.startsWith("package/")) return null;
|
|
376
|
+
return name.slice(8);
|
|
373
377
|
}
|
|
374
|
-
function
|
|
375
|
-
|
|
376
|
-
|
|
378
|
+
async function extractTarballTo(bytes, destDir) {
|
|
379
|
+
const entries = await parseTarGzip(bytes);
|
|
380
|
+
const stagingDir = `${destDir}${STAGING_SUFFIX_PREFIX}${process.pid}-${Date.now()}`;
|
|
381
|
+
await fs.promises.mkdir(stagingDir, { recursive: true });
|
|
382
|
+
const resolvedStaging = path.resolve(stagingDir);
|
|
383
|
+
try {
|
|
384
|
+
for (const entry of entries) {
|
|
385
|
+
const relativeName = normalizeEntryName(entry.name);
|
|
386
|
+
if (relativeName === null) continue;
|
|
387
|
+
const targetPath = path.join(stagingDir, relativeName);
|
|
388
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
389
|
+
if (resolvedTarget !== resolvedStaging && !resolvedTarget.startsWith(`${resolvedStaging}${path.sep}`)) throw new Error(`tarball entry escapes extraction root: ${entry.name}`);
|
|
390
|
+
if (entry.type === "directory" || relativeName.endsWith("/")) {
|
|
391
|
+
await fs.promises.mkdir(targetPath, { recursive: true });
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });
|
|
395
|
+
const data = entry.data ?? /* @__PURE__ */ new Uint8Array(0);
|
|
396
|
+
await fs.promises.writeFile(targetPath, data);
|
|
397
|
+
const mode = parseEntryMode(entry.attrs?.mode);
|
|
398
|
+
if (mode !== void 0) await fs.promises.chmod(targetPath, mode);
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
await fs.promises.rename(stagingDir, destDir);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
const code = error.code;
|
|
404
|
+
if ((code === "ENOTEMPTY" || code === "EEXIST") && fs.existsSync(path.join(destDir, "package.json"))) {
|
|
405
|
+
await fs.promises.rm(stagingDir, {
|
|
406
|
+
recursive: true,
|
|
407
|
+
force: true
|
|
408
|
+
}).catch(() => {});
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
} catch (error) {
|
|
414
|
+
await fs.promises.rm(stagingDir, {
|
|
415
|
+
recursive: true,
|
|
416
|
+
force: true
|
|
417
|
+
}).catch(() => {});
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
377
420
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
args: [...templateArgs],
|
|
395
|
-
envs,
|
|
396
|
-
type: TemplateType.bundled,
|
|
397
|
-
parentDir,
|
|
398
|
-
interactive,
|
|
399
|
-
localPath: bundledLocalPath
|
|
400
|
-
};
|
|
401
|
-
if (templateName.startsWith("vite:")) return {
|
|
402
|
-
command: templateName,
|
|
403
|
-
args: [...templateArgs],
|
|
404
|
-
envs,
|
|
405
|
-
type: TemplateType.builtin,
|
|
406
|
-
parentDir,
|
|
407
|
-
interactive
|
|
408
|
-
};
|
|
409
|
-
if (isGitHubUrl(templateName)) {
|
|
410
|
-
const degitPath = parseGitHubUrl(templateName);
|
|
411
|
-
if (degitPath) return {
|
|
412
|
-
command: "degit",
|
|
413
|
-
args: [degitPath, ...templateArgs],
|
|
414
|
-
envs,
|
|
415
|
-
type: TemplateType.remote,
|
|
416
|
-
parentDir,
|
|
417
|
-
interactive
|
|
418
|
-
};
|
|
419
|
-
}
|
|
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`);
|
|
429
|
-
const args = [binPath, ...templateArgs];
|
|
430
|
-
let type = TemplateType.remote;
|
|
431
|
-
if (isBingoTemplate(pkg)) {
|
|
432
|
-
type = TemplateType.bingo;
|
|
433
|
-
args.push("--skip-requests");
|
|
434
|
-
}
|
|
435
|
-
return {
|
|
436
|
-
command: "node",
|
|
437
|
-
args,
|
|
438
|
-
envs,
|
|
439
|
-
type,
|
|
440
|
-
parentDir,
|
|
441
|
-
interactive
|
|
442
|
-
};
|
|
421
|
+
const STAGING_STALE_MS = 1440 * 60 * 1e3;
|
|
422
|
+
/**
|
|
423
|
+
* Remove `<destDir>.tmp-*` siblings left behind by a previous crash so
|
|
424
|
+
* repeated aborts don't accumulate orphaned staging trees. Only deletes
|
|
425
|
+
* entries whose mtime is older than 24 hours — a concurrent `vp create`
|
|
426
|
+
* that's still actively extracting will always be younger than that, so
|
|
427
|
+
* the age gate keeps this safe to run at the top of every extract.
|
|
428
|
+
*/
|
|
429
|
+
async function cleanupStaleStagingDirs(destDir) {
|
|
430
|
+
const parent = path.dirname(destDir);
|
|
431
|
+
const prefix = `${path.basename(destDir)}${STAGING_SUFFIX_PREFIX}`;
|
|
432
|
+
let entries;
|
|
433
|
+
try {
|
|
434
|
+
entries = await fs.promises.readdir(parent);
|
|
435
|
+
} catch {
|
|
436
|
+
return;
|
|
443
437
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
438
|
+
const cutoff = Date.now() - STAGING_STALE_MS;
|
|
439
|
+
await Promise.all(entries.filter((name) => name.startsWith(prefix)).map(async (name) => {
|
|
440
|
+
const fullPath = path.join(parent, name);
|
|
441
|
+
try {
|
|
442
|
+
if ((await fs.promises.stat(fullPath)).mtimeMs < cutoff) await fs.promises.rm(fullPath, {
|
|
443
|
+
recursive: true,
|
|
444
|
+
force: true
|
|
445
|
+
});
|
|
446
|
+
} catch {}
|
|
447
|
+
}));
|
|
452
448
|
}
|
|
453
449
|
/**
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
* - `vite@latest` → `create-vite@latest`
|
|
459
|
-
*
|
|
460
|
-
* Special cases for packages where the convention doesn't work:
|
|
461
|
-
* - `nitro` → `create-nitro-app` (create-nitro is abandoned)
|
|
462
|
-
* - `svelte` → `sv`
|
|
463
|
-
* - `@tanstack/start` → `@tanstack/cli` (@tanstack/create-start is deprecated)
|
|
450
|
+
* Ensure the `@org/create` package tarball for the given manifest has been
|
|
451
|
+
* downloaded and extracted locally. Returns the absolute path to the
|
|
452
|
+
* extracted package root (i.e. the directory that contains
|
|
453
|
+
* `package.json`).
|
|
464
454
|
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
* - Local paths (`./`, `../`, `/`)
|
|
469
|
-
* - Names already starting with `create-` (or `@scope/create-`)
|
|
455
|
+
* Idempotent: subsequent calls for the same `<scope, version>` reuse the
|
|
456
|
+
* cached extraction. Concurrent calls race on the final rename; the loser
|
|
457
|
+
* cleans up and returns the existing directory.
|
|
470
458
|
*/
|
|
471
|
-
function
|
|
472
|
-
|
|
473
|
-
if (
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const scope = templateName.slice(0, slashIndex);
|
|
482
|
-
const rest = templateName.slice(slashIndex + 1);
|
|
483
|
-
const atIndex = rest.indexOf("@");
|
|
484
|
-
const name = atIndex === -1 ? rest : rest.slice(0, atIndex);
|
|
485
|
-
const version = atIndex === -1 ? "" : rest.slice(atIndex);
|
|
486
|
-
if (name.startsWith("create-")) return templateName;
|
|
487
|
-
if (scope === "@tanstack" && name === "start") return `@tanstack/cli${version}`;
|
|
488
|
-
return `${scope}/create-${name}${version}`;
|
|
489
|
-
}
|
|
490
|
-
const atIndex = templateName.indexOf("@");
|
|
491
|
-
const name = atIndex === -1 ? templateName : templateName.slice(0, atIndex);
|
|
492
|
-
const version = atIndex === -1 ? "" : templateName.slice(atIndex);
|
|
493
|
-
if (name.startsWith("create-")) return templateName;
|
|
494
|
-
if (name === "nitro") return `create-nitro-app${version}`;
|
|
495
|
-
if (name === "svelte") return `sv${version}`;
|
|
496
|
-
return `create-${name}${version}`;
|
|
497
|
-
}
|
|
498
|
-
function inferParentDir(templateName, workspaceInfo, localTemplate = false) {
|
|
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
|
-
}
|
|
505
|
-
let rule = /app/i;
|
|
506
|
-
if (templateName === BuiltinTemplate.library) rule = /lib|component|package/i;
|
|
507
|
-
else if (templateName === BuiltinTemplate.generator) rule = /generator|tool/i;
|
|
508
|
-
for (const parentDir of workspaceInfo.parentDirs) if (rule.test(parentDir)) return parentDir;
|
|
459
|
+
async function ensureOrgPackageExtracted(manifest) {
|
|
460
|
+
const extractedRoot = getExtractionDir(manifest);
|
|
461
|
+
if (fs.existsSync(path.join(extractedRoot, "package.json"))) return extractedRoot;
|
|
462
|
+
const parent = path.dirname(extractedRoot);
|
|
463
|
+
await fs.promises.mkdir(parent, { recursive: true });
|
|
464
|
+
await cleanupStaleStagingDirs(extractedRoot);
|
|
465
|
+
const bytes = await downloadTarball(manifest.tarballUrl);
|
|
466
|
+
verifyIntegrity(bytes, manifest.integrity);
|
|
467
|
+
await extractTarballTo(bytes, extractedRoot);
|
|
468
|
+
return extractedRoot;
|
|
509
469
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
{
|
|
525
|
-
label: "Vite+ Library",
|
|
526
|
-
value: BuiltinTemplate.library,
|
|
527
|
-
hint: "Create vite libraries"
|
|
528
|
-
},
|
|
529
|
-
...isMonorepo ? templates.map((entry) => ({
|
|
530
|
-
label: entry.name,
|
|
531
|
-
value: entry.name,
|
|
532
|
-
hint: entry.description
|
|
533
|
-
})) : []
|
|
534
|
-
];
|
|
470
|
+
/**
|
|
471
|
+
* Resolve a manifest entry's relative `./...` path against an already-
|
|
472
|
+
* extracted package root, rejecting any path that escapes the root (via
|
|
473
|
+
* `..` walks or an absolute specifier).
|
|
474
|
+
*
|
|
475
|
+
* Existence is NOT checked here — the subsequent `copyDir` surfaces any
|
|
476
|
+
* missing-directory error with a clearer errno.
|
|
477
|
+
*/
|
|
478
|
+
function resolveBundledPath(extractedRoot, relativePath) {
|
|
479
|
+
if (path.isAbsolute(relativePath)) throw new Error(`bundled template path must be relative, got ${relativePath}`);
|
|
480
|
+
const resolvedRoot = path.resolve(extractedRoot);
|
|
481
|
+
const resolvedTarget = path.resolve(extractedRoot, relativePath);
|
|
482
|
+
if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path.sep}`)) throw new Error(`bundled template path escapes the package root: ${relativePath}`);
|
|
483
|
+
return resolvedTarget;
|
|
535
484
|
}
|
|
536
485
|
//#endregion
|
|
537
|
-
//#region src/create/org-
|
|
538
|
-
const ORG_PICKER_CANCEL = Symbol("org-picker-cancel");
|
|
539
|
-
const ORG_PICKER_BUILTIN_ESCAPE = Symbol("org-picker-builtin-escape");
|
|
540
|
-
const ESCAPE_HATCH = Symbol("builtin-escape");
|
|
486
|
+
//#region src/create/org-manifest.ts
|
|
541
487
|
/**
|
|
542
|
-
*
|
|
543
|
-
*
|
|
488
|
+
* Parse the org picker specifier: `@scope` (scope only → picker) or
|
|
489
|
+
* `@scope:name` (direct manifest-entry selection). Colon mirrors the
|
|
490
|
+
* existing `vite:monorepo` / `vite:library` builtin-template syntax and
|
|
491
|
+
* keeps manifest entries syntactically distinct from real
|
|
492
|
+
* `@scope/package-name` npm specifiers.
|
|
544
493
|
*
|
|
545
|
-
*
|
|
546
|
-
* existing
|
|
494
|
+
* Returns `null` for anything else — including the plain `@scope/name`
|
|
495
|
+
* form, which routes to the existing `@scope/create-name` shorthand as
|
|
496
|
+
* it did before the org-manifest feature.
|
|
547
497
|
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
498
|
+
* The optional `version` suffix (`@scope@1.2.3`, `@scope:name@1.2.3`)
|
|
499
|
+
* pins `@scope/create` to a specific release rather than `dist-tags.latest`.
|
|
550
500
|
*/
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if (
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
kind: "entry",
|
|
581
|
-
entry: found
|
|
501
|
+
function parseOrgScopedSpec(spec) {
|
|
502
|
+
if (!spec.startsWith("@")) return null;
|
|
503
|
+
if (spec.includes("/")) return null;
|
|
504
|
+
const colonIndex = spec.indexOf(":");
|
|
505
|
+
if (colonIndex === -1) {
|
|
506
|
+
const atIndex = spec.indexOf("@", 1);
|
|
507
|
+
if (atIndex === -1) return { scope: spec };
|
|
508
|
+
const version = spec.slice(atIndex + 1);
|
|
509
|
+
return version ? {
|
|
510
|
+
scope: spec.slice(0, atIndex),
|
|
511
|
+
version
|
|
512
|
+
} : { scope: spec.slice(0, atIndex) };
|
|
513
|
+
}
|
|
514
|
+
const scope = spec.slice(0, colonIndex);
|
|
515
|
+
const rest = spec.slice(colonIndex + 1);
|
|
516
|
+
const atIndex = rest.indexOf("@");
|
|
517
|
+
const name = atIndex === -1 ? rest : rest.slice(0, atIndex);
|
|
518
|
+
const version = atIndex === -1 ? "" : rest.slice(atIndex + 1);
|
|
519
|
+
if (!name) return version ? {
|
|
520
|
+
scope,
|
|
521
|
+
version
|
|
522
|
+
} : { scope };
|
|
523
|
+
return version ? {
|
|
524
|
+
scope,
|
|
525
|
+
name,
|
|
526
|
+
version
|
|
527
|
+
} : {
|
|
528
|
+
scope,
|
|
529
|
+
name
|
|
582
530
|
};
|
|
583
531
|
}
|
|
584
532
|
/**
|
|
585
|
-
*
|
|
586
|
-
*
|
|
587
|
-
* available template names without a `--json` flag.
|
|
533
|
+
* Schema-level failure. Never falls through silently — a maintainer who
|
|
534
|
+
* shipped an invalid manifest should see the offending field.
|
|
588
535
|
*/
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
536
|
+
var OrgManifestSchemaError = class extends Error {
|
|
537
|
+
packageName;
|
|
538
|
+
constructor(message, packageName) {
|
|
539
|
+
super(`${packageName}: ${message}`);
|
|
540
|
+
this.packageName = packageName;
|
|
541
|
+
this.name = "OrgManifestSchemaError";
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
function isRelativePath(spec) {
|
|
545
|
+
return spec.startsWith("./") || spec.startsWith("../");
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Validate the `{ name, description, template }` fields shared by org manifest
|
|
549
|
+
* entries and local `create.templates` entries. `label` is the config path
|
|
550
|
+
* used in error messages (e.g. `createConfig.templates` or `create.templates`)
|
|
551
|
+
* and `makeError` builds the thrown error so each source uses its own type.
|
|
552
|
+
*/
|
|
553
|
+
function validateTemplateEntry(entry, index, label, makeError) {
|
|
554
|
+
if (!entry || typeof entry !== "object") throw makeError(`${label}[${index}] must be an object`);
|
|
555
|
+
const raw = entry;
|
|
556
|
+
const requireString = (field) => {
|
|
557
|
+
const value = raw[field];
|
|
558
|
+
if (typeof value !== "string" || value.length === 0) throw makeError(`${label}[${index}].${field} must be a non-empty string`);
|
|
559
|
+
return value;
|
|
560
|
+
};
|
|
561
|
+
const name = requireString("name");
|
|
562
|
+
if (name.startsWith("__vp_")) throw makeError(`${label}[${index}].name uses the reserved \`__vp_\` prefix`);
|
|
563
|
+
const description = requireString("description");
|
|
564
|
+
const template = requireString("template");
|
|
565
|
+
if (isRelativePath(template)) {
|
|
566
|
+
const resolved = path.posix.resolve("/root", template.replaceAll("\\", "/"));
|
|
567
|
+
if (resolved !== "/root" && !resolved.startsWith("/root/")) throw makeError(`${label}[${index}].template escapes the package root: ${template}`);
|
|
568
|
+
}
|
|
597
569
|
return {
|
|
598
|
-
|
|
599
|
-
|
|
570
|
+
name,
|
|
571
|
+
description,
|
|
572
|
+
template
|
|
600
573
|
};
|
|
601
574
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
"x": "extendedHeader",
|
|
615
|
-
"D": "gnuDirectory",
|
|
616
|
-
"I": "gnuInodeMetadata",
|
|
617
|
-
"K": "gnuLongLinkName",
|
|
618
|
-
"L": "gnuLongFileName",
|
|
619
|
-
"N": "gnuOldLongFileName",
|
|
620
|
-
"M": "gnuMultiVolume",
|
|
621
|
-
"S": "gnuSparseFile",
|
|
622
|
-
"E": "gnuExtendedSparse",
|
|
623
|
-
"A": "solarisAcl",
|
|
624
|
-
"V": "solarisVolumeLabel",
|
|
625
|
-
"X": "solarisOldExtendedHeader"
|
|
626
|
-
};
|
|
627
|
-
function parseTar(data, opts) {
|
|
628
|
-
const buffer = data.buffer || data;
|
|
629
|
-
const files = [];
|
|
630
|
-
let offset = 0;
|
|
631
|
-
let nextExtendedHeader;
|
|
632
|
-
let globalExtendedHeader;
|
|
633
|
-
while (offset < buffer.byteLength - 512) {
|
|
634
|
-
let name = _readString(buffer, offset, 100);
|
|
635
|
-
if (name.length === 0) break;
|
|
636
|
-
if (nextExtendedHeader) {
|
|
637
|
-
const longName = nextExtendedHeader.path || nextExtendedHeader.linkpath;
|
|
638
|
-
if (longName) name = longName;
|
|
639
|
-
}
|
|
640
|
-
const mode = _readString(buffer, offset + 100, 8).trim();
|
|
641
|
-
const uid = Number.parseInt(_readString(buffer, offset + 108, 8));
|
|
642
|
-
const gid = Number.parseInt(_readString(buffer, offset + 116, 8));
|
|
643
|
-
const size = _readNumber(buffer, offset + 124, 12);
|
|
644
|
-
const seek = 512 + 512 * Math.trunc(size / 512) + (size % 512 ? 512 : 0);
|
|
645
|
-
const mtime = _readNumber(buffer, offset + 136, 12);
|
|
646
|
-
const _type = _readString(buffer, offset + 156, 1) || "0";
|
|
647
|
-
const type = tarItemTypeMap[_type] || _type;
|
|
648
|
-
switch (type) {
|
|
649
|
-
case "extendedHeader":
|
|
650
|
-
case "globalExtendedHeader": {
|
|
651
|
-
const headers = _parseExtendedHeaders(new Uint8Array(buffer, offset + 512, size));
|
|
652
|
-
if (type === "extendedHeader") nextExtendedHeader = headers;
|
|
653
|
-
else {
|
|
654
|
-
nextExtendedHeader = void 0;
|
|
655
|
-
globalExtendedHeader = {
|
|
656
|
-
...globalExtendedHeader,
|
|
657
|
-
...headers
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
offset += seek;
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
case "gnuLongFileName":
|
|
664
|
-
case "gnuOldLongFileName":
|
|
665
|
-
case "gnuLongLinkName":
|
|
666
|
-
nextExtendedHeader = { path: _readString(buffer, offset + 512, size) };
|
|
667
|
-
offset += seek;
|
|
668
|
-
continue;
|
|
669
|
-
}
|
|
670
|
-
const user = _readString(buffer, offset + 265, 32);
|
|
671
|
-
const group = _readString(buffer, offset + 297, 32);
|
|
672
|
-
name = _sanitizePath(name);
|
|
673
|
-
const meta = {
|
|
674
|
-
name,
|
|
675
|
-
type,
|
|
676
|
-
size,
|
|
677
|
-
attrs: {
|
|
678
|
-
...globalExtendedHeader,
|
|
679
|
-
...nextExtendedHeader,
|
|
680
|
-
mode,
|
|
681
|
-
uid,
|
|
682
|
-
gid,
|
|
683
|
-
mtime,
|
|
684
|
-
user,
|
|
685
|
-
group
|
|
686
|
-
}
|
|
687
|
-
};
|
|
688
|
-
nextExtendedHeader = void 0;
|
|
689
|
-
if (opts?.filter && !opts.filter(meta)) {
|
|
690
|
-
offset += seek;
|
|
691
|
-
continue;
|
|
692
|
-
}
|
|
693
|
-
if (opts?.metaOnly) {
|
|
694
|
-
files.push(meta);
|
|
695
|
-
offset += seek;
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
const data2 = size === 0 ? void 0 : new Uint8Array(buffer, offset + 512, size);
|
|
699
|
-
files.push({
|
|
700
|
-
...meta,
|
|
701
|
-
data: data2,
|
|
702
|
-
get text() {
|
|
703
|
-
return new TextDecoder().decode(this.data);
|
|
704
|
-
}
|
|
705
|
-
});
|
|
706
|
-
offset += seek;
|
|
575
|
+
/**
|
|
576
|
+
* Validate a list of entries, rejecting duplicate `name`s. Shared by org
|
|
577
|
+
* manifests and local `create.templates`.
|
|
578
|
+
*/
|
|
579
|
+
function validateTemplateEntries(templates, label, makeError, validateOne) {
|
|
580
|
+
const entries = [];
|
|
581
|
+
const seen = /* @__PURE__ */ new Set();
|
|
582
|
+
for (let index = 0; index < templates.length; index += 1) {
|
|
583
|
+
const entry = validateOne(templates[index], index);
|
|
584
|
+
if (seen.has(entry.name)) throw makeError(`${label}[${index}].name duplicates an earlier entry: "${entry.name}"`);
|
|
585
|
+
seen.add(entry.name);
|
|
586
|
+
entries.push(entry);
|
|
707
587
|
}
|
|
708
|
-
return
|
|
709
|
-
}
|
|
710
|
-
async function parseTarGzip(data, opts = {}) {
|
|
711
|
-
const stream = new ReadableStream({ start(controller) {
|
|
712
|
-
controller.enqueue(new Uint8Array(data));
|
|
713
|
-
controller.close();
|
|
714
|
-
} }).pipeThrough(new DecompressionStream(opts.compression ?? "gzip"));
|
|
715
|
-
return parseTar(await new Response(stream).arrayBuffer(), opts);
|
|
716
|
-
}
|
|
717
|
-
function _sanitizePath(path) {
|
|
718
|
-
let normalized = path.replace(/\\/g, "/");
|
|
719
|
-
normalized = normalized.replace(/^[a-zA-Z]:\//, "");
|
|
720
|
-
normalized = normalized.replace(/^\/+/, "");
|
|
721
|
-
const hasLeadingDotSlash = normalized.startsWith("./");
|
|
722
|
-
const parts = normalized.split("/");
|
|
723
|
-
const resolved = [];
|
|
724
|
-
for (const part of parts) if (part === "..") resolved.pop();
|
|
725
|
-
else if (part !== "." && part !== "") resolved.push(part);
|
|
726
|
-
let result = resolved.join("/");
|
|
727
|
-
if (hasLeadingDotSlash && !result.startsWith("./")) result = "./" + result;
|
|
728
|
-
if (path.endsWith("/") && !result.endsWith("/")) result += "/";
|
|
729
|
-
return result;
|
|
588
|
+
return entries;
|
|
730
589
|
}
|
|
731
|
-
function
|
|
732
|
-
const
|
|
733
|
-
const
|
|
734
|
-
|
|
590
|
+
function validateEntry(entry, index, packageName) {
|
|
591
|
+
const makeError = (message) => new OrgManifestSchemaError(message, packageName);
|
|
592
|
+
const base = validateTemplateEntry(entry, index, "createConfig.templates", makeError);
|
|
593
|
+
let monorepo;
|
|
594
|
+
const raw = entry;
|
|
595
|
+
if (raw.monorepo !== void 0) {
|
|
596
|
+
if (typeof raw.monorepo !== "boolean") throw makeError(`createConfig.templates[${index}].monorepo must be a boolean`);
|
|
597
|
+
monorepo = raw.monorepo;
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
...base,
|
|
601
|
+
...monorepo !== void 0 ? { monorepo } : {}
|
|
602
|
+
};
|
|
735
603
|
}
|
|
736
|
-
function
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
604
|
+
function validateManifest(raw, packageName) {
|
|
605
|
+
if (!raw || typeof raw !== "object") return null;
|
|
606
|
+
const createConfig = raw.createConfig;
|
|
607
|
+
if (!createConfig || typeof createConfig !== "object") return null;
|
|
608
|
+
const templates = createConfig.templates;
|
|
609
|
+
if (templates === void 0) return null;
|
|
610
|
+
if (!Array.isArray(templates)) throw new OrgManifestSchemaError("createConfig.templates must be an array", packageName);
|
|
611
|
+
if (templates.length === 0) return null;
|
|
612
|
+
return validateTemplateEntries(templates, "createConfig.templates", (message) => new OrgManifestSchemaError(message, packageName), (entry, index) => validateEntry(entry, index, packageName));
|
|
741
613
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
614
|
+
/**
|
|
615
|
+
* Schema-level failure for `create.templates` in `vite.config.ts`. A misconfigured
|
|
616
|
+
* local template should surface clearly rather than silently disappear.
|
|
617
|
+
*/
|
|
618
|
+
var CreateConfigSchemaError = class extends Error {
|
|
619
|
+
constructor(message) {
|
|
620
|
+
super(message);
|
|
621
|
+
this.name = "CreateConfigSchemaError";
|
|
748
622
|
}
|
|
749
|
-
|
|
623
|
+
};
|
|
624
|
+
/**
|
|
625
|
+
* Validate `create.templates` from `vite.config.ts`. Returns `[]` when the field
|
|
626
|
+
* is absent or an empty array; throws {@link CreateConfigSchemaError} when present
|
|
627
|
+
* but malformed.
|
|
628
|
+
*/
|
|
629
|
+
function validateCreateTemplates(templates) {
|
|
630
|
+
if (templates === void 0) return [];
|
|
631
|
+
if (!Array.isArray(templates)) throw new CreateConfigSchemaError("create.templates must be an array");
|
|
632
|
+
const makeError = (message) => new CreateConfigSchemaError(message);
|
|
633
|
+
return validateTemplateEntries(templates, "create.templates", makeError, (entry, index) => {
|
|
634
|
+
const validated = validateTemplateEntry(entry, index, "create.templates", makeError);
|
|
635
|
+
if (validated.name.startsWith("vite:")) throw makeError(`create.templates[${index}].name uses the reserved \`vite:\` prefix`);
|
|
636
|
+
return validated;
|
|
637
|
+
});
|
|
750
638
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
639
|
+
async function fetchPackument(scope, packageName) {
|
|
640
|
+
const response = await fetchNpmResource(`${getNpmRegistry(scope)}/${packageName}`, {
|
|
641
|
+
headers: { accept: "application/json" },
|
|
642
|
+
timeoutMs: 5e3
|
|
643
|
+
});
|
|
644
|
+
if (response.status === 404) return null;
|
|
645
|
+
if (!response.ok) throw new Error(`npm registry responded with ${response.status} for ${packageName}`);
|
|
646
|
+
return await response.json();
|
|
756
647
|
}
|
|
757
648
|
/**
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
649
|
+
* Fetch `@scope/create` from the npm registry and parse its `createConfig.templates`
|
|
650
|
+
* manifest.
|
|
651
|
+
*
|
|
652
|
+
* Returns `null` when:
|
|
653
|
+
* - the package does not exist on the registry (404), or
|
|
654
|
+
* - the package exists but has no `createConfig.templates` field
|
|
655
|
+
*
|
|
656
|
+
* When the packument version metadata lacks `createConfig` entirely, the
|
|
657
|
+
* published tarball's package.json is consulted before giving up — some
|
|
658
|
+
* registries (GitHub Packages among them) strip custom fields from version
|
|
659
|
+
* metadata while preserving the tarball byte-for-byte.
|
|
660
|
+
*
|
|
661
|
+
* Throws when:
|
|
662
|
+
* - the `createConfig.templates` field is present but malformed (`OrgManifestSchemaError`), or
|
|
663
|
+
* - the registry request fails for any non-404 reason
|
|
664
|
+
*
|
|
665
|
+
* `requestedVersion` pins the lookup to a specific `versions[...]` entry
|
|
666
|
+
* (equivalent to `vp create @scope@1.2.3`); omit it to resolve `dist-tags.latest`.
|
|
762
667
|
*/
|
|
763
|
-
function
|
|
764
|
-
|
|
668
|
+
async function readOrgManifest(scope, requestedVersion) {
|
|
669
|
+
if (!scope.startsWith("@")) return null;
|
|
670
|
+
const packageName = `${scope}/create`;
|
|
671
|
+
const packument = await fetchPackument(scope, packageName);
|
|
672
|
+
if (!packument) return null;
|
|
673
|
+
let resolvedVersion;
|
|
674
|
+
if (requestedVersion) {
|
|
675
|
+
resolvedVersion = packument["dist-tags"]?.[requestedVersion] ?? (packument.versions?.[requestedVersion] ? requestedVersion : void 0);
|
|
676
|
+
if (!resolvedVersion) throw new OrgManifestSchemaError(`version "${requestedVersion}" not found (known tags: ${Object.keys(packument["dist-tags"] ?? {}).join(", ") || "none"})`, packageName);
|
|
677
|
+
} else {
|
|
678
|
+
resolvedVersion = packument["dist-tags"]?.latest;
|
|
679
|
+
if (!resolvedVersion) return null;
|
|
680
|
+
}
|
|
681
|
+
const meta = packument.versions?.[resolvedVersion];
|
|
682
|
+
if (!meta) return null;
|
|
683
|
+
let templates = validateManifest(meta, packageName);
|
|
684
|
+
if (!templates && meta.createConfig === void 0 && meta.dist?.tarball) templates = validateManifest(await readPackageJsonFromTarball(meta.dist.tarball, meta.dist.integrity).catch(() => null), packageName);
|
|
685
|
+
if (!templates) return null;
|
|
686
|
+
if (!meta.dist?.tarball) throw new OrgManifestSchemaError(`missing dist.tarball for ${resolvedVersion}`, packageName);
|
|
687
|
+
return {
|
|
688
|
+
scope,
|
|
689
|
+
packageName,
|
|
690
|
+
version: resolvedVersion,
|
|
691
|
+
tarballUrl: meta.dist.tarball,
|
|
692
|
+
integrity: meta.dist.integrity,
|
|
693
|
+
templates
|
|
694
|
+
};
|
|
765
695
|
}
|
|
766
696
|
/**
|
|
767
|
-
*
|
|
768
|
-
*
|
|
769
|
-
*
|
|
770
|
-
* guarantees `manifest.tarballUrl` is a valid URL, so any parse failure
|
|
771
|
-
* here is a real bug worth surfacing.
|
|
697
|
+
* Apply the in-monorepo filter rule from the RFC: entries with
|
|
698
|
+
* `monorepo: true` are hidden when the command is invoked inside an
|
|
699
|
+
* existing monorepo, mirroring `initial-template-options.ts:9-31`.
|
|
772
700
|
*/
|
|
773
|
-
function
|
|
774
|
-
|
|
775
|
-
return
|
|
701
|
+
function filterManifestForContext(templates, isMonorepo) {
|
|
702
|
+
if (!isMonorepo) return [...templates];
|
|
703
|
+
return templates.filter((entry) => !entry.monorepo);
|
|
776
704
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
705
|
+
//#endregion
|
|
706
|
+
//#region src/create/templates/types.ts
|
|
707
|
+
const LibraryTemplateRepo = "github:sxzz/tsdown-templates/vite-plus";
|
|
708
|
+
const BuiltinTemplate = {
|
|
709
|
+
generator: "vite:generator",
|
|
710
|
+
monorepo: "vite:monorepo",
|
|
711
|
+
application: "vite:application",
|
|
712
|
+
library: "vite:library"
|
|
713
|
+
};
|
|
714
|
+
const TemplateType = {
|
|
715
|
+
builtin: "builtin",
|
|
716
|
+
bingo: "bingo",
|
|
717
|
+
remote: "remote",
|
|
718
|
+
bundled: "bundled"
|
|
719
|
+
};
|
|
720
|
+
//#endregion
|
|
721
|
+
//#region src/create/discovery.ts
|
|
722
|
+
function isGitHubUrl(templateName) {
|
|
723
|
+
return templateName.startsWith("https://github.com/") || templateName.startsWith("github:") || templateName.includes("github.com/");
|
|
784
724
|
}
|
|
785
|
-
function
|
|
786
|
-
if (
|
|
787
|
-
const
|
|
788
|
-
if (
|
|
789
|
-
|
|
790
|
-
hash.update(bytes);
|
|
791
|
-
const actual = hash.digest("base64");
|
|
792
|
-
if (actual !== parsed.expected) throw new Error(`integrity check failed: expected ${integrity}, got ${parsed.algorithm}-${actual}`);
|
|
725
|
+
function parseGitHubUrl(url) {
|
|
726
|
+
if (url.startsWith("github:")) return url.slice(7);
|
|
727
|
+
const match = url.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
728
|
+
if (match) return match[1].replace(/\.git$/, "");
|
|
729
|
+
return null;
|
|
793
730
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
const contentLength = Number(response.headers.get("content-length"));
|
|
799
|
-
if (Number.isFinite(contentLength) && contentLength > MAX_TARBALL_BYTES) throw new Error(`tarball exceeds ${MAX_TARBALL_BYTES} byte size limit: ${url}`);
|
|
800
|
-
const reader = response.body?.getReader();
|
|
801
|
-
if (!reader) throw new Error(`tarball response has no body: ${url}`);
|
|
802
|
-
const chunks = [];
|
|
803
|
-
let total = 0;
|
|
804
|
-
while (true) {
|
|
805
|
-
const { done, value } = await reader.read();
|
|
806
|
-
if (done) break;
|
|
807
|
-
total += value.byteLength;
|
|
808
|
-
if (total > MAX_TARBALL_BYTES) {
|
|
809
|
-
await reader.cancel();
|
|
810
|
-
throw new Error(`tarball exceeds ${MAX_TARBALL_BYTES} byte size limit: ${url}`);
|
|
811
|
-
}
|
|
812
|
-
chunks.push(value);
|
|
813
|
-
}
|
|
814
|
-
const bytes = new Uint8Array(total);
|
|
815
|
-
let offset = 0;
|
|
816
|
-
for (const chunk of chunks) {
|
|
817
|
-
bytes.set(chunk, offset);
|
|
818
|
-
offset += chunk.byteLength;
|
|
819
|
-
}
|
|
820
|
-
return bytes;
|
|
731
|
+
function inferGitHubRepoName(templateName) {
|
|
732
|
+
const degitPath = parseGitHubUrl(templateName);
|
|
733
|
+
if (!degitPath) return null;
|
|
734
|
+
return degitPath.split("/").pop() || null;
|
|
821
735
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
if (
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
736
|
+
function localTemplateDir(workspaceInfo, templateName) {
|
|
737
|
+
if (isRelativePath(templateName)) return templateName.replace(/^\.\//, "");
|
|
738
|
+
return workspaceInfo.packages.find((pkg) => pkg.name === templateName)?.path;
|
|
739
|
+
}
|
|
740
|
+
function resolveLocalBinPath(localPackagePath, packageName, bin) {
|
|
741
|
+
if (!bin) return;
|
|
742
|
+
if (typeof bin === "string") return path.join(localPackagePath, bin);
|
|
743
|
+
const entries = Object.entries(bin);
|
|
744
|
+
if (entries.length === 0) return;
|
|
745
|
+
if (entries.length === 1) return path.join(localPackagePath, entries[0][1]);
|
|
746
|
+
const unscopedName = packageName.slice(packageName.lastIndexOf("/") + 1);
|
|
747
|
+
const preferred = bin[packageName] ?? bin[unscopedName];
|
|
748
|
+
if (preferred) return path.join(localPackagePath, preferred);
|
|
749
|
+
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`);
|
|
750
|
+
}
|
|
751
|
+
function discoverTemplate(templateName, templateArgs, workspaceInfo, interactive, bundledLocalPath, skipShorthand, localTemplate) {
|
|
752
|
+
const envs = prependToPathToEnvs(workspaceInfo.downloadPackageManager.binPrefix, { ...process.env });
|
|
753
|
+
const parentDir = inferParentDir(templateName, workspaceInfo, localTemplate);
|
|
754
|
+
if (bundledLocalPath) return {
|
|
755
|
+
command: "",
|
|
756
|
+
args: [...templateArgs],
|
|
757
|
+
envs,
|
|
758
|
+
type: TemplateType.bundled,
|
|
759
|
+
parentDir,
|
|
760
|
+
interactive,
|
|
761
|
+
localPath: bundledLocalPath
|
|
762
|
+
};
|
|
763
|
+
if (templateName.startsWith("vite:")) return {
|
|
764
|
+
command: templateName,
|
|
765
|
+
args: [...templateArgs],
|
|
766
|
+
envs,
|
|
767
|
+
type: TemplateType.builtin,
|
|
768
|
+
parentDir,
|
|
769
|
+
interactive
|
|
770
|
+
};
|
|
771
|
+
if (isGitHubUrl(templateName)) {
|
|
772
|
+
const degitPath = parseGitHubUrl(templateName);
|
|
773
|
+
if (degitPath) return {
|
|
774
|
+
command: "degit",
|
|
775
|
+
args: [degitPath, ...templateArgs],
|
|
776
|
+
envs,
|
|
777
|
+
type: TemplateType.remote,
|
|
778
|
+
parentDir,
|
|
779
|
+
interactive
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
if (localTemplate) {
|
|
783
|
+
const localDir = localTemplateDir(workspaceInfo, templateName);
|
|
784
|
+
if (!localDir) throw new Error(`Local template "${templateName}" does not match any workspace package; update the \`create.templates\` entry in vite.config.ts`);
|
|
785
|
+
const localPackagePath = path.join(workspaceInfo.rootDir, localDir);
|
|
786
|
+
const packageJsonPath = path.join(localPackagePath, "package.json");
|
|
787
|
+
if (!fs.existsSync(packageJsonPath)) throw new Error(`Local template "${templateName}" has no package.json, so it cannot be run as a template`);
|
|
788
|
+
const pkg = readJsonFile(packageJsonPath);
|
|
789
|
+
const binPath = resolveLocalBinPath(localPackagePath, pkg.name ?? templateName, pkg.bin);
|
|
790
|
+
if (!binPath) throw new Error(`Local template "${templateName}" has no "bin" entry in its package.json, so it cannot be run as a template`);
|
|
791
|
+
const args = [binPath, ...templateArgs];
|
|
792
|
+
let type = TemplateType.remote;
|
|
793
|
+
if (isBingoTemplate(pkg)) {
|
|
794
|
+
type = TemplateType.bingo;
|
|
795
|
+
args.push("--skip-requests");
|
|
796
|
+
}
|
|
797
|
+
return {
|
|
798
|
+
command: "node",
|
|
799
|
+
args,
|
|
800
|
+
envs,
|
|
801
|
+
type,
|
|
802
|
+
parentDir,
|
|
803
|
+
interactive
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
command: skipShorthand ? templateName : expandCreateShorthand(templateName),
|
|
808
|
+
args: [...templateArgs],
|
|
809
|
+
envs,
|
|
810
|
+
type: TemplateType.remote,
|
|
811
|
+
parentDir,
|
|
812
|
+
interactive
|
|
813
|
+
};
|
|
834
814
|
}
|
|
835
815
|
/**
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
* `
|
|
816
|
+
* Expand shorthand template names to their full `create-*` package names.
|
|
817
|
+
*
|
|
818
|
+
* This follows the same convention as `npm create` / `pnpm create`:
|
|
819
|
+
* - `vite` → `create-vite`
|
|
820
|
+
* - `vite@latest` → `create-vite@latest`
|
|
821
|
+
*
|
|
822
|
+
* Special cases for packages where the convention doesn't work:
|
|
823
|
+
* - `nitro` → `create-nitro-app` (create-nitro is abandoned)
|
|
824
|
+
* - `svelte` → `sv`
|
|
825
|
+
* - `@tanstack/start` → `@tanstack/cli` (@tanstack/create-start is deprecated)
|
|
826
|
+
*
|
|
827
|
+
* Skips expansion for:
|
|
828
|
+
* - Builtin templates (`vite:*`)
|
|
829
|
+
* - GitHub URLs
|
|
830
|
+
* - Local paths (`./`, `../`, `/`)
|
|
831
|
+
* - Names already starting with `create-` (or `@scope/create-`)
|
|
839
832
|
*/
|
|
840
|
-
function
|
|
841
|
-
|
|
842
|
-
if (
|
|
843
|
-
if (
|
|
844
|
-
if (
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
const stagingDir = `${destDir}${STAGING_SUFFIX_PREFIX}${process.pid}-${Date.now()}`;
|
|
850
|
-
await fs.promises.mkdir(stagingDir, { recursive: true });
|
|
851
|
-
const resolvedStaging = path.resolve(stagingDir);
|
|
852
|
-
try {
|
|
853
|
-
for (const entry of entries) {
|
|
854
|
-
const relativeName = normalizeEntryName(entry.name);
|
|
855
|
-
if (relativeName === null) continue;
|
|
856
|
-
const targetPath = path.join(stagingDir, relativeName);
|
|
857
|
-
const resolvedTarget = path.resolve(targetPath);
|
|
858
|
-
if (resolvedTarget !== resolvedStaging && !resolvedTarget.startsWith(`${resolvedStaging}${path.sep}`)) throw new Error(`tarball entry escapes extraction root: ${entry.name}`);
|
|
859
|
-
if (entry.type === "directory" || relativeName.endsWith("/")) {
|
|
860
|
-
await fs.promises.mkdir(targetPath, { recursive: true });
|
|
861
|
-
continue;
|
|
862
|
-
}
|
|
863
|
-
await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });
|
|
864
|
-
const data = entry.data ?? new Uint8Array(0);
|
|
865
|
-
await fs.promises.writeFile(targetPath, data);
|
|
866
|
-
const mode = parseEntryMode(entry.attrs?.mode);
|
|
867
|
-
if (mode !== void 0) await fs.promises.chmod(targetPath, mode);
|
|
868
|
-
}
|
|
869
|
-
try {
|
|
870
|
-
await fs.promises.rename(stagingDir, destDir);
|
|
871
|
-
} catch (error) {
|
|
872
|
-
const code = error.code;
|
|
873
|
-
if ((code === "ENOTEMPTY" || code === "EEXIST") && fs.existsSync(path.join(destDir, "package.json"))) {
|
|
874
|
-
await fs.promises.rm(stagingDir, {
|
|
875
|
-
recursive: true,
|
|
876
|
-
force: true
|
|
877
|
-
}).catch(() => {});
|
|
878
|
-
return;
|
|
879
|
-
}
|
|
880
|
-
throw error;
|
|
833
|
+
function expandCreateShorthand(templateName) {
|
|
834
|
+
if (templateName.includes(":")) return templateName;
|
|
835
|
+
if (isGitHubUrl(templateName)) return templateName;
|
|
836
|
+
if (templateName.startsWith("./") || templateName.startsWith("../") || templateName.startsWith("/")) return templateName;
|
|
837
|
+
if (templateName.startsWith("@")) {
|
|
838
|
+
const slashIndex = templateName.indexOf("/");
|
|
839
|
+
if (slashIndex === -1) {
|
|
840
|
+
const atIndex = templateName.indexOf("@", 1);
|
|
841
|
+
return `${atIndex === -1 ? templateName : templateName.slice(0, atIndex)}/create${atIndex === -1 ? "" : templateName.slice(atIndex)}`;
|
|
881
842
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
843
|
+
const scope = templateName.slice(0, slashIndex);
|
|
844
|
+
const rest = templateName.slice(slashIndex + 1);
|
|
845
|
+
const atIndex = rest.indexOf("@");
|
|
846
|
+
const name = atIndex === -1 ? rest : rest.slice(0, atIndex);
|
|
847
|
+
const version = atIndex === -1 ? "" : rest.slice(atIndex);
|
|
848
|
+
if (name.startsWith("create-")) return templateName;
|
|
849
|
+
if (scope === "@tanstack" && name === "start") return `@tanstack/cli${version}`;
|
|
850
|
+
return `${scope}/create-${name}${version}`;
|
|
888
851
|
}
|
|
852
|
+
const atIndex = templateName.indexOf("@");
|
|
853
|
+
const name = atIndex === -1 ? templateName : templateName.slice(0, atIndex);
|
|
854
|
+
const version = atIndex === -1 ? "" : templateName.slice(atIndex);
|
|
855
|
+
if (name.startsWith("create-")) return templateName;
|
|
856
|
+
if (name === "nitro") return `create-nitro-app${version}`;
|
|
857
|
+
if (name === "svelte") return `sv${version}`;
|
|
858
|
+
return `create-${name}${version}`;
|
|
889
859
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
* the age gate keeps this safe to run at the top of every extract.
|
|
897
|
-
*/
|
|
898
|
-
async function cleanupStaleStagingDirs(destDir) {
|
|
899
|
-
const parent = path.dirname(destDir);
|
|
900
|
-
const prefix = `${path.basename(destDir)}${STAGING_SUFFIX_PREFIX}`;
|
|
901
|
-
let entries;
|
|
902
|
-
try {
|
|
903
|
-
entries = await fs.promises.readdir(parent);
|
|
904
|
-
} catch {
|
|
905
|
-
return;
|
|
860
|
+
function inferParentDir(templateName, workspaceInfo, localTemplate = false) {
|
|
861
|
+
if (workspaceInfo.parentDirs.length === 0) return;
|
|
862
|
+
const localDir = localTemplate ? localTemplateDir(workspaceInfo, templateName) : void 0;
|
|
863
|
+
if (localDir) {
|
|
864
|
+
const ownParentDir = path.dirname(localDir);
|
|
865
|
+
if (workspaceInfo.parentDirs.includes(ownParentDir)) return ownParentDir;
|
|
906
866
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
867
|
+
let rule = /app/i;
|
|
868
|
+
if (templateName === BuiltinTemplate.library) rule = /lib|component|package/i;
|
|
869
|
+
else if (templateName === BuiltinTemplate.generator) rule = /generator|tool/i;
|
|
870
|
+
for (const parentDir of workspaceInfo.parentDirs) if (rule.test(parentDir)) return parentDir;
|
|
871
|
+
}
|
|
872
|
+
//#endregion
|
|
873
|
+
//#region src/create/initial-template-options.ts
|
|
874
|
+
function getInitialTemplateOptions(isMonorepo, templates = []) {
|
|
875
|
+
return [
|
|
876
|
+
...!isMonorepo ? [{
|
|
877
|
+
label: "Vite+ Monorepo",
|
|
878
|
+
value: BuiltinTemplate.monorepo,
|
|
879
|
+
hint: "Create a new Vite+ monorepo project"
|
|
880
|
+
}] : [],
|
|
881
|
+
{
|
|
882
|
+
label: "Vite+ Application",
|
|
883
|
+
value: BuiltinTemplate.application,
|
|
884
|
+
hint: "Create vite applications"
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
label: "Vite+ Library",
|
|
888
|
+
value: BuiltinTemplate.library,
|
|
889
|
+
hint: "Create vite libraries"
|
|
890
|
+
},
|
|
891
|
+
...isMonorepo ? templates.map((entry) => ({
|
|
892
|
+
label: entry.name,
|
|
893
|
+
value: entry.name,
|
|
894
|
+
hint: entry.description
|
|
895
|
+
})) : []
|
|
896
|
+
];
|
|
917
897
|
}
|
|
898
|
+
//#endregion
|
|
899
|
+
//#region src/create/org-picker.ts
|
|
900
|
+
const ORG_PICKER_CANCEL = Symbol("org-picker-cancel");
|
|
901
|
+
const ORG_PICKER_BUILTIN_ESCAPE = Symbol("org-picker-builtin-escape");
|
|
902
|
+
const ESCAPE_HATCH = Symbol("builtin-escape");
|
|
918
903
|
/**
|
|
919
|
-
*
|
|
920
|
-
*
|
|
921
|
-
* extracted package root (i.e. the directory that contains
|
|
922
|
-
* `package.json`).
|
|
904
|
+
* Render the interactive picker for an org manifest. Always appends a
|
|
905
|
+
* trailing "Vite+ built-in templates" escape-hatch entry.
|
|
923
906
|
*
|
|
924
|
-
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
907
|
+
* Context-filters entries with `monorepo: true` when running inside an
|
|
908
|
+
* existing monorepo, mirroring `initial-template-options.ts:9-31`.
|
|
909
|
+
*
|
|
910
|
+
* Returns `ORG_PICKER_BUILTIN_ESCAPE` when the escape hatch is selected,
|
|
911
|
+
* or `ORG_PICKER_CANCEL` when the user hits Ctrl-C.
|
|
927
912
|
*/
|
|
928
|
-
async function
|
|
929
|
-
const
|
|
930
|
-
if (
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
913
|
+
async function pickOrgTemplate(manifest, opts) {
|
|
914
|
+
const filtered = filterManifestForContext(manifest.templates, opts.isMonorepo);
|
|
915
|
+
if (filtered.length === 0) return ORG_PICKER_BUILTIN_ESCAPE;
|
|
916
|
+
const escapeValue = `__vp_builtin_escape__::${randomUUID()}`;
|
|
917
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
918
|
+
const options = filtered.map((entry) => {
|
|
919
|
+
lookup.set(entry.name, entry);
|
|
920
|
+
return {
|
|
921
|
+
value: entry.name,
|
|
922
|
+
label: entry.name,
|
|
923
|
+
hint: entry.description
|
|
924
|
+
};
|
|
925
|
+
});
|
|
926
|
+
lookup.set(escapeValue, ESCAPE_HATCH);
|
|
927
|
+
const builtinHint = opts.isMonorepo ? "Use defaults (application / library)" : "Use defaults (monorepo / application / library)";
|
|
928
|
+
options.push({
|
|
929
|
+
value: escapeValue,
|
|
930
|
+
label: "Vite+ built-in templates",
|
|
931
|
+
hint: builtinHint
|
|
932
|
+
});
|
|
933
|
+
const picked = await select({
|
|
934
|
+
message: `Pick a template from ${manifest.scope}`,
|
|
935
|
+
options
|
|
936
|
+
});
|
|
937
|
+
if (isCancel(picked)) return ORG_PICKER_CANCEL;
|
|
938
|
+
const found = lookup.get(picked);
|
|
939
|
+
if (found === ESCAPE_HATCH) return ORG_PICKER_BUILTIN_ESCAPE;
|
|
940
|
+
if (!found) throw new Error(`org-picker: prompts.select returned an unregistered value: ${picked}`);
|
|
941
|
+
return {
|
|
942
|
+
kind: "entry",
|
|
943
|
+
entry: found
|
|
944
|
+
};
|
|
938
945
|
}
|
|
939
946
|
/**
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
*
|
|
944
|
-
* Existence is NOT checked here — the subsequent `copyDir` surfaces any
|
|
945
|
-
* missing-directory error with a clearer errno.
|
|
947
|
+
* Render the manifest as a plain-text table for the `--no-interactive`
|
|
948
|
+
* error output. Fixed column order so AI agents and scripts can recover
|
|
949
|
+
* available template names without a `--json` flag.
|
|
946
950
|
*/
|
|
947
|
-
function
|
|
948
|
-
|
|
949
|
-
const
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
951
|
+
function formatManifestTable(manifest, isMonorepo) {
|
|
952
|
+
const visible = filterManifestForContext(manifest.templates, isMonorepo);
|
|
953
|
+
const filteredCount = manifest.templates.length - visible.length;
|
|
954
|
+
const nameWidth = Math.max(4, ...visible.map((entry) => entry.name.length));
|
|
955
|
+
const descWidth = Math.max(11, ...visible.map((entry) => entry.description.length));
|
|
956
|
+
const lines = [];
|
|
957
|
+
lines.push(` ${"NAME".padEnd(nameWidth)} ${"DESCRIPTION".padEnd(descWidth)} TEMPLATE`);
|
|
958
|
+
for (const entry of visible) lines.push(` ${entry.name.padEnd(nameWidth)} ${entry.description.padEnd(descWidth)} ${entry.template}`);
|
|
959
|
+
return {
|
|
960
|
+
lines,
|
|
961
|
+
filteredCount
|
|
962
|
+
};
|
|
953
963
|
}
|
|
954
964
|
//#endregion
|
|
955
965
|
//#region ../../node_modules/.pnpm/validate-npm-package-name@7.0.2/node_modules/validate-npm-package-name/lib/builtin-modules.json
|
|
@@ -4019,7 +4029,7 @@ async function promptPackageNameAndTargetDir(defaultPackageName, interactive) {
|
|
|
4019
4029
|
return result?.errors?.[0] ?? result?.warnings?.[0] ?? "Invalid package name";
|
|
4020
4030
|
}
|
|
4021
4031
|
});
|
|
4022
|
-
if (
|
|
4032
|
+
if (isCancel(selected)) cancelAndExit();
|
|
4023
4033
|
packageName = selected;
|
|
4024
4034
|
targetDir = getProjectDirFromPackageName(packageName);
|
|
4025
4035
|
} else {
|
|
@@ -4041,7 +4051,7 @@ async function promptTargetDir(defaultTargetDir, interactive, options) {
|
|
|
4041
4051
|
defaultValue: defaultTargetDir,
|
|
4042
4052
|
validate: (value) => validateTargetDir(value ?? defaultTargetDir, options?.cwd).error
|
|
4043
4053
|
});
|
|
4044
|
-
if (
|
|
4054
|
+
if (isCancel(selected)) cancelAndExit();
|
|
4045
4055
|
targetDir = validateTargetDir(selected ?? defaultTargetDir, options?.cwd).directory;
|
|
4046
4056
|
} else {
|
|
4047
4057
|
targetDir = validateTargetDir(defaultTargetDir, options?.cwd).directory;
|
|
@@ -4074,7 +4084,7 @@ async function checkProjectDirExists(projectDirFullPath, interactive) {
|
|
|
4074
4084
|
value: "yes"
|
|
4075
4085
|
}]
|
|
4076
4086
|
});
|
|
4077
|
-
if (
|
|
4087
|
+
if (isCancel(overwrite)) cancelAndExit();
|
|
4078
4088
|
switch (overwrite) {
|
|
4079
4089
|
case "yes":
|
|
4080
4090
|
emptyDir(projectDirFullPath);
|
|
@@ -4352,7 +4362,7 @@ async function executeGeneratorScaffold(workspaceInfo, templateInfo, options) {
|
|
|
4352
4362
|
placeholder: defaultDescription,
|
|
4353
4363
|
defaultValue: defaultDescription
|
|
4354
4364
|
});
|
|
4355
|
-
if (!
|
|
4365
|
+
if (!isCancel(descPrompt)) description = descPrompt;
|
|
4356
4366
|
}
|
|
4357
4367
|
const fullPath = path.join(workspaceInfo.rootDir, templateInfo.targetDir);
|
|
4358
4368
|
copyDir(path.join(templatesDir, "generator"), fullPath);
|
|
@@ -4665,7 +4675,7 @@ const helpMessage = renderCliDoc({
|
|
|
4665
4675
|
},
|
|
4666
4676
|
{
|
|
4667
4677
|
label: "--git",
|
|
4668
|
-
description: "Initialize a git repository
|
|
4678
|
+
description: "Initialize a git repository"
|
|
4669
4679
|
},
|
|
4670
4680
|
{
|
|
4671
4681
|
label: "--no-git",
|
|
@@ -5025,7 +5035,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
|
|
|
5025
5035
|
message: "",
|
|
5026
5036
|
options: getInitialTemplateOptions(isMonorepo, localTemplates)
|
|
5027
5037
|
});
|
|
5028
|
-
if (
|
|
5038
|
+
if (isCancel(template)) cancelAndExit();
|
|
5029
5039
|
selectedTemplateName = template;
|
|
5030
5040
|
}
|
|
5031
5041
|
const matchedLocalTemplate = resolvedByOrg ? void 0 : localTemplates.find((entry) => entry.name === selectedTemplateName);
|
|
@@ -5073,7 +5083,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
|
|
|
5073
5083
|
options: dirOptions,
|
|
5074
5084
|
initialValue: shouldOfferCwdOption ? cwdRelativeToRoot : inferParentDir(selectedTemplateName, workspaceInfoOptional, isLocalTemplate) ?? workspaceInfoOptional.parentDirs[0]
|
|
5075
5085
|
});
|
|
5076
|
-
if (
|
|
5086
|
+
if (isCancel(selected)) cancelAndExit();
|
|
5077
5087
|
if (selected !== "other") parentDir = selected;
|
|
5078
5088
|
}
|
|
5079
5089
|
if (!parentDir) {
|
|
@@ -5084,7 +5094,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
|
|
|
5084
5094
|
return value ? formatTargetDir(value).error : "Target directory is required";
|
|
5085
5095
|
}
|
|
5086
5096
|
});
|
|
5087
|
-
if (
|
|
5097
|
+
if (isCancel(customTargetDir)) cancelAndExit();
|
|
5088
5098
|
parentDir = customTargetDir;
|
|
5089
5099
|
}
|
|
5090
5100
|
selectedParentDir = parentDir;
|
|
@@ -5199,7 +5209,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
|
|
|
5199
5209
|
let migratePendingBuilds = [];
|
|
5200
5210
|
const handleIgnoredBuilds = async (projectPath, installCwd, summary) => {
|
|
5201
5211
|
if (summary?.status !== "installed") return;
|
|
5202
|
-
const reportedBuilds = [
|
|
5212
|
+
const reportedBuilds = [.../* @__PURE__ */ new Set([...summary.pendingBuilds ?? [], ...migratePendingBuilds])];
|
|
5203
5213
|
const targets = resolveApproveBuildTargets(projectPath, await detectGatedBuilds(installCwd, workspaceInfo.packageManager, reportedBuilds), workspaceInfo.packageManager);
|
|
5204
5214
|
if (targets.length === 0) return;
|
|
5205
5215
|
pauseCreateProgress();
|
|
@@ -5235,7 +5245,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
|
|
|
5235
5245
|
initialValue: true
|
|
5236
5246
|
});
|
|
5237
5247
|
resumeCreateProgress();
|
|
5238
|
-
if (
|
|
5248
|
+
if (isCancel(selected)) {
|
|
5239
5249
|
log.info("Operation cancelled. Skipping git initialization");
|
|
5240
5250
|
shouldInitGit = false;
|
|
5241
5251
|
} else shouldInitGit = selected;
|
|
@@ -5313,14 +5323,6 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
|
|
|
5313
5323
|
await handleIgnoredBuilds(fullPath, fullPath, installSummary);
|
|
5314
5324
|
updateCreateProgress("Formatting code");
|
|
5315
5325
|
await runViteFmt(fullPath, options.interactive, void 0, { silent: compactOutput });
|
|
5316
|
-
if (shouldSetupGit) {
|
|
5317
|
-
updateCreateProgress("Creating initial commit");
|
|
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
|
-
}
|
|
5323
|
-
}
|
|
5324
5326
|
clearCreateProgress();
|
|
5325
5327
|
showCreateSummary({
|
|
5326
5328
|
description: describeScaffold(selectedTemplateName, selectedTemplateArgs),
|
|
@@ -5459,7 +5461,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
|
|
|
5459
5461
|
required: false
|
|
5460
5462
|
});
|
|
5461
5463
|
let selectedDepTypes = [];
|
|
5462
|
-
if (!
|
|
5464
|
+
if (!isCancel(selectedDepTypeOptions)) selectedDepTypes = selectedDepTypeOptions;
|
|
5463
5465
|
for (const selectedDepType of selectedDepTypes) {
|
|
5464
5466
|
const selected = await multiselect({
|
|
5465
5467
|
message: `Which packages should be added as ${selectedDepType} to ${success(projectDir)}?`,
|
|
@@ -5470,7 +5472,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
|
|
|
5470
5472
|
required: false
|
|
5471
5473
|
});
|
|
5472
5474
|
let selectedDeps = [];
|
|
5473
|
-
if (!
|
|
5475
|
+
if (!isCancel(selected)) selectedDeps = selected;
|
|
5474
5476
|
if (selectedDeps.length > 0) updatePackageJsonWithDeps(workspaceInfo.rootDir, projectDir, selectedDeps, selectedDepType);
|
|
5475
5477
|
}
|
|
5476
5478
|
resumeCreateProgress();
|
|
@@ -5512,14 +5514,6 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
|
|
|
5512
5514
|
await handleIgnoredBuilds(fullPath, fullPath, installSummary);
|
|
5513
5515
|
updateCreateProgress("Formatting code");
|
|
5514
5516
|
await runViteFmt(fullPath, options.interactive, void 0, { silent: compactOutput });
|
|
5515
|
-
if (shouldSetupGit) {
|
|
5516
|
-
updateCreateProgress("Creating initial commit");
|
|
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
|
-
}
|
|
5522
|
-
}
|
|
5523
5517
|
}
|
|
5524
5518
|
clearCreateProgress();
|
|
5525
5519
|
showCreateSummary({
|