feat: init

This commit is contained in:
2026-02-13 22:02:30 +01:00
commit 8f9ff830fb
16711 changed files with 3307340 additions and 0 deletions

21
node_modules/@nuxt/telemetry/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Nuxt Project
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

103
node_modules/@nuxt/telemetry/README.md generated vendored Normal file
View File

@@ -0,0 +1,103 @@
# Nuxt Telemetry Module
Nuxt collects anonymous telemetry data about general usage. This helps us to accurately gauge Nuxt feature usage and customization across all our users.
This program is optional. You will be asked on first time to get permission and you can always [opt-out](#opting-out) if you'd not like to share any information.
## Why collecting Telemetry?
Nuxt has grown a lot from its [initial release](https://github.com/nuxt/nuxt.js/releases/tag/v0.2.0) (7 Nov 2016) and we keep listening to [community feedback](https://github.com/nuxt/nuxt.js/issues) to improve it.
However, this manual process only collects feedback from a subset of users that take the time to fill the issue template and it may have different needs or use-case than you.
Nuxt Telemetry collects anonymous telemetry data about general usage. This helps us to accurately gauge feature usage and customization across all our users. This data will let us better understand how Nuxt is used globally, measuring improvements made (DX and performances) and their relevance.
## Events
We collect multiple events:
- Command invoked (`nuxt dev`, `nuxt build`, etc)
- Versions of Nuxt and Node.js
- General machine information (MacOS/Linux/Windows and if the command is run within CI, ci name)
- Duration of the Webpack build and average size of the application, as well as the generation stats (when using `nuxt generate` or `nuxt export`)
- Your project's *public dependencies* (Nuxt modules)
You can see the list of events in [lib/events](./src/events).
Example of an event:
```json
{
"name": "NUXT_PROJECT",
"payload": {
"type": "GIT",
"isSSR": true,
"target": "server",
"isTypescriptBuild": false,
"isTypescriptRuntime": false,
"isProgrammatic": false,
"packageManager": "npm"
}
}
```
To display the exact data that will be sent, you can use `NUXT_TELEMETRY_DEBUG=1`.
## Sensitive data
We take your privacy and our security very seriously.
We do not collect any metrics which may contain sensitive data.
This includes, but is not limited to: environment variables, file paths, contents of files, logs, or serialized JavaScript errors.
The data we collect is completely anonymous, not traceable to the source (using hash+seed), and only meaningful in aggregate form. No data we collect is personally identifiable or trackable.
## Opting-out
You can disable Nuxt Telemetry for your project in several ways:
1. Setting `telemetry: false` in your `nuxt.config`:
```js
export default {
telemetry: false
}
```
2. Using an environment variable:
```bash
NUXT_TELEMETRY_DISABLED=1
```
3. Using `npx @nuxt/telemetry disable`
<!-- TODO: npx nuxt telemetry -->
```bash
npx @nuxt/telemetry [status|enable|disable] [-g,--global] [dir]
```
## Skip Prompt
If you encounter problems with the consent prompt and want to participate without being asked this question, you can set `telemetry: true` from `nuxt.config`:
```js
export default {
telemetry: true
}
```
## Thank you
We want to thank you for participating in this telemetry program to help us better understand how you use Nuxt to keep improving it 💚
## Development
- Run `yarn dev:prepare` to generate type stubs.
- Use `yarn dev` to start [playground](./playground) in development mode.
## License
[MIT License](./LICENSE)

6
node_modules/@nuxt/telemetry/bin/nuxt-telemetry.mjs generated vendored Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env node
process._startTime = Date.now()
import('../dist/cli.mjs').then(r => (r.default || r).main().catch((error) => {
console.error(error)
process.exit(1)
}))

188
node_modules/@nuxt/telemetry/dist/cli.cjs generated vendored Normal file
View File

@@ -0,0 +1,188 @@
'use strict';
const fs = require('node:fs');
const os = require('node:os');
const node_path = require('node:path');
const rc = require('rc9');
const utils = require('consola/utils');
const consola = require('consola');
const kit = require('@nuxt/kit');
const stdEnv = require('std-env');
const citty = require('citty');
const consent = require('./shared/telemetry.BikY0E3b.cjs');
function _interopNamespaceCompat(e) {
if (e && typeof e === 'object' && 'default' in e) return e;
const n = Object.create(null);
if (e) {
for (const k in e) {
n[k] = e[k];
}
}
n.default = e;
return n;
}
const rc__namespace = /*#__PURE__*/_interopNamespaceCompat(rc);
function isTruthy(val) {
return val === true || val === "true" || val === "1" || val === 1;
}
function parseDotenv(src) {
const result = {};
for (const line of src.split("\n")) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) continue;
const eqIndex = trimmed.indexOf("=");
if (eqIndex === -1) continue;
const key = trimmed.slice(0, eqIndex).trim();
let value = trimmed.slice(eqIndex + 1).trim();
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
value = value.slice(1, -1);
}
result[key] = value;
}
return result;
}
const RC_FILENAME = ".nuxtrc";
const sharedArgs = {
global: {
type: "boolean",
alias: "g",
default: false,
description: "Apply globally"
},
dir: {
type: "positional",
default: "."
}
};
const main = citty.createMain({
meta: {
name: "nuxt-telemetry",
description: "Manage consent for Nuxt collecting anonymous telemetry data about general usage.",
version: consent.version
},
subCommands: {
status: citty.defineCommand({
meta: {
name: "status",
description: "Show telemetry status"
},
args: sharedArgs,
async run({ args }) {
ensureNuxtProject(args);
const dir = node_path.resolve(args.dir);
await showStatus(dir, args.global);
}
}),
enable: citty.defineCommand({
meta: {
name: "enable",
description: "Enable telemetry"
},
args: sharedArgs,
async run({ args }) {
ensureNuxtProject(args);
const dir = node_path.resolve(args.dir);
setRC(dir, "telemetry.enabled", true, args.global);
setRC(dir, "telemetry.consent", consent.consentVersion, args.global);
await showStatus(dir, args.global);
consola.consola.info("You can disable telemetry with `npx nuxt-telemetry disable" + (args.global ? " --global" : args.dir ? " " + args.dir : "") + "`");
}
}),
disable: citty.defineCommand({
meta: {
name: "disable",
description: "Disable telemetry"
},
args: sharedArgs,
async run({ args }) {
ensureNuxtProject(args);
const dir = node_path.resolve(args.dir);
setRC(dir, "telemetry.enabled", false, args.global);
setRC(dir, "telemetry.consent", 0, args.global);
await showStatus(dir, args.global);
consola.consola.info("You can enable telemetry with `npx nuxt-telemetry enable" + (args.global ? " --global" : args.dir ? " " + args.dir : "") + "`");
}
}),
consent: citty.defineCommand({
meta: {
name: "consent",
description: "Prompt for user consent"
},
args: sharedArgs,
async run({ args }) {
ensureNuxtProject(args);
const dir = node_path.resolve(args.dir);
const accepted = await consent.ensureUserconsent({});
if (accepted && !args.global) {
setRC(dir, "telemetry.enabled", true, args.global);
setRC(dir, "telemetry.consent", consent.consentVersion, args.global);
}
await showStatus(dir, args.global);
}
})
}
});
async function _checkDisabled(dir) {
if (stdEnv.isTest) {
return "because you are running in a test environment";
}
if (isTruthy(process.env.NUXT_TELEMETRY_DISABLED)) {
return "by the `NUXT_TELEMETRY_DISABLED` environment variable";
}
const dotenvFile = node_path.resolve(dir, ".env");
if (fs.existsSync(dotenvFile)) {
const _env = parseDotenv(fs.readFileSync(dotenvFile, "utf8"));
if (isTruthy(_env.NUXT_TELEMETRY_DISABLED)) {
return "by the `NUXT_TELEMETRY_DISABLED` environment variable set in " + dotenvFile;
}
}
const disabledByConf = (conf) => conf.telemetry === false || conf.telemetry && conf.telemetry.enabled === false;
try {
const config = await kit.loadNuxtConfig({ cwd: dir });
for (const layer of config._layers) {
if (disabledByConf(layer.config)) {
return "by " + config._layers[0].configFile;
}
}
} catch {
}
if (disabledByConf(rc__namespace.read({ name: RC_FILENAME, dir }))) {
return "by " + node_path.resolve(dir, RC_FILENAME);
}
if (disabledByConf(rc__namespace.readUser({ name: RC_FILENAME }))) {
return "by " + node_path.resolve(os.homedir(), RC_FILENAME);
}
}
async function showStatus(dir, global) {
const disabled = await _checkDisabled(dir);
if (disabled) {
consola.consola.info(`Nuxt telemetry is ${utils.colors.yellow("disabled")} ${disabled}.`);
} else {
consola.consola.info(`Nuxt telemetry is ${utils.colors.green("enabled")}`, global ? "on your machine." : "in the current project.");
}
}
function setRC(dir, key, val, global) {
const update = { [key]: val };
if (global) {
rc__namespace.updateUser(update, RC_FILENAME);
} else {
rc__namespace.update(update, { name: RC_FILENAME, dir });
}
}
async function ensureNuxtProject(args) {
if (args.global) {
return;
}
const dir = node_path.resolve(args.dir);
const nuxtConfig = await kit.loadNuxtConfig({ cwd: dir });
if (!nuxtConfig || !nuxtConfig._layers[0]?.configFile) {
consola.consola.error("You are not in a Nuxt project.");
consola.consola.info("You can try specifying a directory or by using the `--global` flag to configure telemetry for your machine.");
process.exit();
}
}
exports.main = main;

5
node_modules/@nuxt/telemetry/dist/cli.d.cts generated vendored Normal file
View File

@@ -0,0 +1,5 @@
import * as citty from 'citty';
declare const main: (opts?: citty.RunMainOptions) => Promise<void>;
export { main };

5
node_modules/@nuxt/telemetry/dist/cli.d.mts generated vendored Normal file
View File

@@ -0,0 +1,5 @@
import * as citty from 'citty';
declare const main: (opts?: citty.RunMainOptions) => Promise<void>;
export { main };

172
node_modules/@nuxt/telemetry/dist/cli.mjs generated vendored Normal file
View File

@@ -0,0 +1,172 @@
import { existsSync, readFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { resolve } from 'node:path';
import * as rc from 'rc9';
import { colors } from 'consola/utils';
import { consola } from 'consola';
import { loadNuxtConfig } from '@nuxt/kit';
import { isTest } from 'std-env';
import { createMain, defineCommand } from 'citty';
import { v as version, e as ensureUserconsent, c as consentVersion } from './shared/telemetry.Dgzu-axG.mjs';
function isTruthy(val) {
return val === true || val === "true" || val === "1" || val === 1;
}
function parseDotenv(src) {
const result = {};
for (const line of src.split("\n")) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) continue;
const eqIndex = trimmed.indexOf("=");
if (eqIndex === -1) continue;
const key = trimmed.slice(0, eqIndex).trim();
let value = trimmed.slice(eqIndex + 1).trim();
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
value = value.slice(1, -1);
}
result[key] = value;
}
return result;
}
const RC_FILENAME = ".nuxtrc";
const sharedArgs = {
global: {
type: "boolean",
alias: "g",
default: false,
description: "Apply globally"
},
dir: {
type: "positional",
default: "."
}
};
const main = createMain({
meta: {
name: "nuxt-telemetry",
description: "Manage consent for Nuxt collecting anonymous telemetry data about general usage.",
version
},
subCommands: {
status: defineCommand({
meta: {
name: "status",
description: "Show telemetry status"
},
args: sharedArgs,
async run({ args }) {
ensureNuxtProject(args);
const dir = resolve(args.dir);
await showStatus(dir, args.global);
}
}),
enable: defineCommand({
meta: {
name: "enable",
description: "Enable telemetry"
},
args: sharedArgs,
async run({ args }) {
ensureNuxtProject(args);
const dir = resolve(args.dir);
setRC(dir, "telemetry.enabled", true, args.global);
setRC(dir, "telemetry.consent", consentVersion, args.global);
await showStatus(dir, args.global);
consola.info("You can disable telemetry with `npx nuxt-telemetry disable" + (args.global ? " --global" : args.dir ? " " + args.dir : "") + "`");
}
}),
disable: defineCommand({
meta: {
name: "disable",
description: "Disable telemetry"
},
args: sharedArgs,
async run({ args }) {
ensureNuxtProject(args);
const dir = resolve(args.dir);
setRC(dir, "telemetry.enabled", false, args.global);
setRC(dir, "telemetry.consent", 0, args.global);
await showStatus(dir, args.global);
consola.info("You can enable telemetry with `npx nuxt-telemetry enable" + (args.global ? " --global" : args.dir ? " " + args.dir : "") + "`");
}
}),
consent: defineCommand({
meta: {
name: "consent",
description: "Prompt for user consent"
},
args: sharedArgs,
async run({ args }) {
ensureNuxtProject(args);
const dir = resolve(args.dir);
const accepted = await ensureUserconsent({});
if (accepted && !args.global) {
setRC(dir, "telemetry.enabled", true, args.global);
setRC(dir, "telemetry.consent", consentVersion, args.global);
}
await showStatus(dir, args.global);
}
})
}
});
async function _checkDisabled(dir) {
if (isTest) {
return "because you are running in a test environment";
}
if (isTruthy(process.env.NUXT_TELEMETRY_DISABLED)) {
return "by the `NUXT_TELEMETRY_DISABLED` environment variable";
}
const dotenvFile = resolve(dir, ".env");
if (existsSync(dotenvFile)) {
const _env = parseDotenv(readFileSync(dotenvFile, "utf8"));
if (isTruthy(_env.NUXT_TELEMETRY_DISABLED)) {
return "by the `NUXT_TELEMETRY_DISABLED` environment variable set in " + dotenvFile;
}
}
const disabledByConf = (conf) => conf.telemetry === false || conf.telemetry && conf.telemetry.enabled === false;
try {
const config = await loadNuxtConfig({ cwd: dir });
for (const layer of config._layers) {
if (disabledByConf(layer.config)) {
return "by " + config._layers[0].configFile;
}
}
} catch {
}
if (disabledByConf(rc.read({ name: RC_FILENAME, dir }))) {
return "by " + resolve(dir, RC_FILENAME);
}
if (disabledByConf(rc.readUser({ name: RC_FILENAME }))) {
return "by " + resolve(homedir(), RC_FILENAME);
}
}
async function showStatus(dir, global) {
const disabled = await _checkDisabled(dir);
if (disabled) {
consola.info(`Nuxt telemetry is ${colors.yellow("disabled")} ${disabled}.`);
} else {
consola.info(`Nuxt telemetry is ${colors.green("enabled")}`, global ? "on your machine." : "in the current project.");
}
}
function setRC(dir, key, val, global) {
const update = { [key]: val };
if (global) {
rc.updateUser(update, RC_FILENAME);
} else {
rc.update(update, { name: RC_FILENAME, dir });
}
}
async function ensureNuxtProject(args) {
if (args.global) {
return;
}
const dir = resolve(args.dir);
const nuxtConfig = await loadNuxtConfig({ cwd: dir });
if (!nuxtConfig || !nuxtConfig._layers[0]?.configFile) {
consola.error("You are not in a Nuxt project.");
consola.info("You can try specifying a directory or by using the `--global` flag to configure telemetry for your machine.");
process.exit();
}
}
export { main };

429
node_modules/@nuxt/telemetry/dist/module.cjs generated vendored Normal file
View File

@@ -0,0 +1,429 @@
'use strict';
const kit = require('@nuxt/kit');
const consent = require('./shared/telemetry.BikY0E3b.cjs');
const ofetch = require('ofetch');
const os = require('node:os');
const fs = require('node:fs');
const node_child_process = require('node:child_process');
const stdEnv = require('std-env');
const node_crypto = require('node:crypto');
const node_path = require('node:path');
require('consola/utils');
require('consola');
require('rc9');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
const os__default = /*#__PURE__*/_interopDefaultCompat(os);
const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
async function postEvent(endpoint, body) {
const res = await ofetch.fetch(endpoint, {
method: "POST",
body: JSON.stringify(body),
headers: {
"content-type": "application/json",
"user-agent": "Nuxt Telemetry " + consent.version
}
});
if (!res.ok) {
throw new Error(res.statusText);
}
}
function hash(str) {
return node_crypto.createHash("sha256").update(str).digest("hex").substr(0, 16);
}
function randomSeed() {
return hash(node_crypto.randomUUID());
}
function getNuxtMajorVersion(nuxt) {
for (let i = 2; i < 10; i++) {
if (kit.isNuxtMajorVersion(i, nuxt)) {
return i;
}
}
return 2;
}
async function createContext(nuxt, options) {
const rootDir = nuxt.options.workspaceDir || nuxt.options.rootDir || process.cwd();
const git = await getGit(rootDir);
const packageManager = detectPackageManager(rootDir);
const { seed } = options;
const projectHash = getProjectHash(rootDir, git, seed);
const projectSession = getProjectSession(projectHash, seed);
const nuxtVersion = kit.getNuxtVersion(nuxt);
const nuxtMajorVersion = getNuxtMajorVersion(nuxt);
const nodeVersion = process.version.replace("v", "");
const isEdge = nuxtVersion.includes("edge");
return {
nuxt,
seed,
git,
projectHash,
projectSession,
nuxtVersion,
nuxtMajorVersion,
isEdge,
cli: getCLI(),
nodeVersion,
os: os__default.type().toLocaleLowerCase(),
environment: getEnv(),
packageManager: packageManager || "unknown",
concent: options.consent
};
}
function getEnv() {
if (stdEnv.provider) {
return stdEnv.provider;
}
if (consent.isDocker()) {
return "Docker";
}
return "unknown";
}
function getCLI() {
const entry = process.argv[1] ?? "";
const knownCLIs = {
"nuxt-ts.js": "nuxt-ts",
"nuxt-start.js": "nuxt-start",
"nuxt.js": "nuxt",
"nuxi": "nuxi"
};
for (const _key in knownCLIs) {
const key = _key;
if (entry.includes(key)) {
const edge = entry.includes("-edge") ? "-edge" : entry.includes("-nightly") ? "-nightly" : "";
return knownCLIs[key] + edge;
}
}
return "programmatic";
}
function getProjectSession(projectHash, sessionId) {
return hash(`${projectHash}#${sessionId}`);
}
function getProjectHash(rootDir, git, seed) {
let id;
if (git && git.url) {
id = `${git.source}#${git.owner}#${git.name}`;
} else {
id = `${rootDir}#${seed}`;
}
return hash(id);
}
async function getGitRemote(cwd) {
let gitRemoteUrl = null;
try {
gitRemoteUrl = node_child_process.execSync("git config --get remote.origin.url ", { encoding: "utf8", cwd }).trim() || null;
} catch {
}
return gitRemoteUrl;
}
function detectPackageManager(rootDir) {
const lockFiles = {
"bun.lockb": "bun",
"bun.lock": "bun",
"deno.lock": "deno",
"pnpm-lock.yaml": "pnpm",
"pnpm-workspace.yaml": "pnpm",
"yarn.lock": "yarn",
"package-lock.json": "npm",
"npm-shrinkwrap.json": "npm"
};
for (const [file, manager] of Object.entries(lockFiles)) {
if (fs.existsSync(`${rootDir}/${file}`)) {
return manager;
}
}
try {
const pkgJson = JSON.parse(fs.readFileSync(`${rootDir}/package.json`, "utf8"));
if (typeof pkgJson.packageManager === "string") {
const name = pkgJson.packageManager.split("@")[0];
if (name) return name;
}
} catch {
}
return "unknown";
}
function parseGitUrl(gitUrl) {
const normalized = gitUrl.trim();
const sshMatch = normalized.match(/^[\w-]+@([^:]+):(.+?)(?:\.git)?$/);
if (sshMatch) {
const source = sshMatch[1];
const path = sshMatch[2];
const parts = path.split("/");
if (parts.length >= 2) {
return { source, owner: parts.slice(0, -1).join("/"), name: parts.at(-1) };
}
}
try {
const url = new URL(normalized);
const pathname = url.pathname.replace(/\.git$/, "").replace(/^\//, "");
const parts = pathname.split("/");
if (parts.length >= 2) {
return { source: url.hostname, owner: parts.slice(0, -1).join("/"), name: parts.at(-1) };
}
} catch {
}
return null;
}
async function getGit(rootDir) {
const gitRemote = await getGitRemote(rootDir);
if (!gitRemote) {
return;
}
const meta = parseGitUrl(gitRemote);
if (!meta) {
return;
}
return {
url: `https://${meta.source}/${meta.owner}/${meta.name}`,
gitRemote,
source: meta.source,
owner: meta.owner,
name: meta.name
};
}
const logger = kit.useLogger("@nuxt/telemetry");
const build = function({ nuxt }, payload) {
const duration = { build: payload.duration.build };
let isSuccess = true;
for (const [name, stat] of Object.entries(payload.stats)) {
duration[name] = stat.duration;
if (!stat.success) {
isSuccess = false;
}
}
return {
name: "build",
isSuccess,
isDev: nuxt.options.dev || false,
duration
// size
};
};
const command = function({ nuxt }) {
let command2 = process.argv[2] || "unknown";
const flagMap = {
dev: "dev",
_generate: "generate",
_export: "export",
_build: "build",
_serve: "serve",
_start: "start"
};
for (const _flag in flagMap) {
const flag = _flag;
if (nuxt.options[flag]) {
command2 = flagMap[flag];
break;
}
}
return {
name: "command",
command: command2
};
};
const generate = function generate2({ nuxt }, payload) {
return {
name: "generate",
// @ts-expect-error Legacy type from Nuxt 2
isExport: !!nuxt.options._export,
routesCount: payload.routesCount,
duration: {
generate: payload.duration.generate
}
};
};
const module$2 = function({ nuxt: { options } }) {
const events = [];
const modules = (options._installedModules || []).filter((m) => m.meta?.version).map((m) => ({
name: m.meta.name,
version: m.meta.version,
timing: m.timings?.setup || 0
}));
for (const m of modules) {
events.push({
name: "module",
moduleName: m.name,
version: m.version,
timing: m.timing
});
}
return events;
};
const project = function(context) {
const { options } = context.nuxt;
return {
name: "project",
type: context.git && context.git.url ? "git" : "local",
isSSR: options.ssr !== false,
target: options.server ? "server" : "static",
packageManager: context.packageManager
};
};
const session = function({ seed }) {
return {
name: "session",
id: seed
};
};
const files = async function(context) {
const { options } = context.nuxt;
const nuxtIgnore = fs__default.existsSync(node_path.resolve(options.rootDir, ".nuxtignore"));
const nuxtRc = fs__default.existsSync(node_path.resolve(options.rootDir, ".nuxtrc"));
const appConfig = fs__default.existsSync(await kit.resolvePath("~/app.config"));
return {
name: "files",
nuxtIgnore,
nuxtRc,
appConfig
};
};
class Telemetry {
nuxt;
options;
storage;
// TODO
_contextPromise;
events = [];
eventFactories = {
build,
command,
generate,
module: module$2,
project,
session,
files
};
constructor(nuxt, options) {
this.nuxt = nuxt;
this.options = options;
}
getContext() {
if (!this._contextPromise) {
this._contextPromise = createContext(this.nuxt, this.options);
}
return this._contextPromise;
}
createEvent(name, payload) {
const eventFactory = this.eventFactories[name];
if (typeof eventFactory !== "function") {
logger.warn("Unknown event:", name);
return;
}
const eventPromise = this._invokeEvent(name, eventFactory, payload);
this.events.push(eventPromise);
}
async _invokeEvent(name, eventFactory, payload) {
try {
const context = await this.getContext();
const event = await eventFactory(context, payload);
event.name = name;
return event;
} catch (err) {
logger.error("Error while running event:", err);
}
}
async getPublicContext() {
const context = await this.getContext();
const eventContext = {};
for (const key of [
"nuxtVersion",
"nuxtMajorVersion",
"isEdge",
"nodeVersion",
"cli",
"os",
"environment",
"projectHash",
"projectSession"
]) {
eventContext[key] = context[key];
}
return eventContext;
}
async sendEvents(debug) {
const events = [].concat(...(await Promise.all(this.events)).filter(Boolean));
this.events = [];
const context = await this.getPublicContext();
const body = {
timestamp: Date.now(),
context,
events
};
if (this.options.endpoint) {
const start = Date.now();
try {
if (debug) {
logger.info("Sending events:", JSON.stringify(body, null, 2));
}
await postEvent(this.options.endpoint, body);
if (debug) {
logger.success(`Events sent to \`${this.options.endpoint}\` (${Date.now() - start} ms)`);
}
} catch (err) {
if (debug) {
logger.error(`Error sending sent to \`${this.options.endpoint}\` (${Date.now() - start} ms)
`, err);
}
}
}
}
}
const module$1 = kit.defineNuxtModule({
meta: {
name: "@nuxt/telemetry",
configKey: "telemetry"
},
defaults: {
endpoint: process.env.NUXT_TELEMETRY_ENDPOINT || "https://telemetry.nuxt.com",
debug: process.env.NUXT_TELEMETRY_DEBUG === "1" || process.env.NUXT_TELEMETRY_DEBUG === "true",
enabled: void 0,
seed: void 0
},
async setup(toptions, nuxt) {
if (!toptions.debug) {
logger.level = 0;
}
const _topLevelTelemetry = nuxt.options.telemetry;
if (_topLevelTelemetry !== true) {
if (toptions.enabled === false || _topLevelTelemetry === false || !await consent.ensureUserconsent(toptions)) {
logger.info("Telemetry disabled");
return;
}
}
logger.info("Telemetry enabled");
if (!toptions.seed || typeof toptions.seed !== "string") {
toptions.seed = randomSeed();
consent.updateUserNuxtRc("telemetry.seed", toptions.seed);
logger.info("Seed generated:", toptions.seed);
}
const t = new Telemetry(nuxt, toptions);
nuxt.hook("modules:done", async () => {
t.createEvent("project");
if (nuxt.options.dev) {
t.createEvent("session");
t.createEvent("files");
}
t.createEvent("command");
t.createEvent("module");
await nuxt.callHook("telemetry:setup", t);
t.sendEvents(toptions.debug);
});
}
});
module.exports = module$1;

58
node_modules/@nuxt/telemetry/dist/module.d.cts generated vendored Normal file
View File

@@ -0,0 +1,58 @@
import * as _nuxt_schema from '@nuxt/schema';
import { Nuxt } from '@nuxt/schema';
interface TelemetryOptions {
debug: boolean;
endpoint: string;
seed: string;
consent?: number;
enabled: boolean;
}
interface Context {
nuxt: Nuxt;
cli: string;
seed: string;
projectHash: string;
projectSession: string;
nuxtVersion: string;
nuxtMajorVersion: number;
isEdge: boolean;
nodeVersion: string;
os: string;
git?: {
url: string;
};
environment: string | null;
packageManager: string;
concent: number;
}
interface Event {
name: string;
[key: string]: any;
}
type EventFactoryResult<T> = Promise<T> | T | Promise<T>[] | T[];
type EventFactory<T extends Event> = (context: Context, payload: any) => EventFactoryResult<T>;
declare class Telemetry {
nuxt: Nuxt;
options: Required<TelemetryOptions>;
storage: any;
_contextPromise?: Promise<Context>;
events: Promise<EventFactoryResult<any>>[];
eventFactories: Record<string, EventFactory<any>>;
constructor(nuxt: Nuxt, options: Required<TelemetryOptions>);
getContext(): Promise<Context>;
createEvent(name: string, payload?: object): undefined | Promise<any>;
_invokeEvent(name: string, eventFactory: EventFactory<any>, payload?: object): Promise<any>;
getPublicContext(): Promise<Record<string, any>>;
sendEvents(debug?: boolean): Promise<void>;
}
type ModuleOptions = TelemetryOptions;
interface ModuleHooks {
'telemetry:setup': (telemetry: Telemetry) => void;
}
declare const _default: _nuxt_schema.NuxtModule<TelemetryOptions, TelemetryOptions, false>;
export = _default;
export type { ModuleHooks, ModuleOptions };

58
node_modules/@nuxt/telemetry/dist/module.d.mts generated vendored Normal file
View File

@@ -0,0 +1,58 @@
import * as _nuxt_schema from '@nuxt/schema';
import { Nuxt } from '@nuxt/schema';
interface TelemetryOptions {
debug: boolean;
endpoint: string;
seed: string;
consent?: number;
enabled: boolean;
}
interface Context {
nuxt: Nuxt;
cli: string;
seed: string;
projectHash: string;
projectSession: string;
nuxtVersion: string;
nuxtMajorVersion: number;
isEdge: boolean;
nodeVersion: string;
os: string;
git?: {
url: string;
};
environment: string | null;
packageManager: string;
concent: number;
}
interface Event {
name: string;
[key: string]: any;
}
type EventFactoryResult<T> = Promise<T> | T | Promise<T>[] | T[];
type EventFactory<T extends Event> = (context: Context, payload: any) => EventFactoryResult<T>;
declare class Telemetry {
nuxt: Nuxt;
options: Required<TelemetryOptions>;
storage: any;
_contextPromise?: Promise<Context>;
events: Promise<EventFactoryResult<any>>[];
eventFactories: Record<string, EventFactory<any>>;
constructor(nuxt: Nuxt, options: Required<TelemetryOptions>);
getContext(): Promise<Context>;
createEvent(name: string, payload?: object): undefined | Promise<any>;
_invokeEvent(name: string, eventFactory: EventFactory<any>, payload?: object): Promise<any>;
getPublicContext(): Promise<Record<string, any>>;
sendEvents(debug?: boolean): Promise<void>;
}
type ModuleOptions = TelemetryOptions;
interface ModuleHooks {
'telemetry:setup': (telemetry: Telemetry) => void;
}
declare const _default: _nuxt_schema.NuxtModule<TelemetryOptions, TelemetryOptions, false>;
export { _default as default };
export type { ModuleHooks, ModuleOptions };

9
node_modules/@nuxt/telemetry/dist/module.json generated vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"name": "@nuxt/telemetry",
"configKey": "telemetry",
"version": "2.7.0",
"builder": {
"@nuxt/module-builder": "1.0.2",
"unbuild": "3.6.1"
}
}

422
node_modules/@nuxt/telemetry/dist/module.mjs generated vendored Normal file
View File

@@ -0,0 +1,422 @@
import { getNuxtVersion, isNuxtMajorVersion, useLogger, resolvePath, defineNuxtModule } from '@nuxt/kit';
import { v as version, i as isDocker, e as ensureUserconsent, u as updateUserNuxtRc } from './shared/telemetry.Dgzu-axG.mjs';
import { fetch } from 'ofetch';
import os from 'node:os';
import fs, { existsSync, readFileSync } from 'node:fs';
import { execSync } from 'node:child_process';
import { provider } from 'std-env';
import { randomUUID, createHash } from 'node:crypto';
import { resolve } from 'node:path';
import 'consola/utils';
import 'consola';
import 'rc9';
async function postEvent(endpoint, body) {
const res = await fetch(endpoint, {
method: "POST",
body: JSON.stringify(body),
headers: {
"content-type": "application/json",
"user-agent": "Nuxt Telemetry " + version
}
});
if (!res.ok) {
throw new Error(res.statusText);
}
}
function hash(str) {
return createHash("sha256").update(str).digest("hex").substr(0, 16);
}
function randomSeed() {
return hash(randomUUID());
}
function getNuxtMajorVersion(nuxt) {
for (let i = 2; i < 10; i++) {
if (isNuxtMajorVersion(i, nuxt)) {
return i;
}
}
return 2;
}
async function createContext(nuxt, options) {
const rootDir = nuxt.options.workspaceDir || nuxt.options.rootDir || process.cwd();
const git = await getGit(rootDir);
const packageManager = detectPackageManager(rootDir);
const { seed } = options;
const projectHash = getProjectHash(rootDir, git, seed);
const projectSession = getProjectSession(projectHash, seed);
const nuxtVersion = getNuxtVersion(nuxt);
const nuxtMajorVersion = getNuxtMajorVersion(nuxt);
const nodeVersion = process.version.replace("v", "");
const isEdge = nuxtVersion.includes("edge");
return {
nuxt,
seed,
git,
projectHash,
projectSession,
nuxtVersion,
nuxtMajorVersion,
isEdge,
cli: getCLI(),
nodeVersion,
os: os.type().toLocaleLowerCase(),
environment: getEnv(),
packageManager: packageManager || "unknown",
concent: options.consent
};
}
function getEnv() {
if (provider) {
return provider;
}
if (isDocker()) {
return "Docker";
}
return "unknown";
}
function getCLI() {
const entry = process.argv[1] ?? "";
const knownCLIs = {
"nuxt-ts.js": "nuxt-ts",
"nuxt-start.js": "nuxt-start",
"nuxt.js": "nuxt",
"nuxi": "nuxi"
};
for (const _key in knownCLIs) {
const key = _key;
if (entry.includes(key)) {
const edge = entry.includes("-edge") ? "-edge" : entry.includes("-nightly") ? "-nightly" : "";
return knownCLIs[key] + edge;
}
}
return "programmatic";
}
function getProjectSession(projectHash, sessionId) {
return hash(`${projectHash}#${sessionId}`);
}
function getProjectHash(rootDir, git, seed) {
let id;
if (git && git.url) {
id = `${git.source}#${git.owner}#${git.name}`;
} else {
id = `${rootDir}#${seed}`;
}
return hash(id);
}
async function getGitRemote(cwd) {
let gitRemoteUrl = null;
try {
gitRemoteUrl = execSync("git config --get remote.origin.url ", { encoding: "utf8", cwd }).trim() || null;
} catch {
}
return gitRemoteUrl;
}
function detectPackageManager(rootDir) {
const lockFiles = {
"bun.lockb": "bun",
"bun.lock": "bun",
"deno.lock": "deno",
"pnpm-lock.yaml": "pnpm",
"pnpm-workspace.yaml": "pnpm",
"yarn.lock": "yarn",
"package-lock.json": "npm",
"npm-shrinkwrap.json": "npm"
};
for (const [file, manager] of Object.entries(lockFiles)) {
if (existsSync(`${rootDir}/${file}`)) {
return manager;
}
}
try {
const pkgJson = JSON.parse(readFileSync(`${rootDir}/package.json`, "utf8"));
if (typeof pkgJson.packageManager === "string") {
const name = pkgJson.packageManager.split("@")[0];
if (name) return name;
}
} catch {
}
return "unknown";
}
function parseGitUrl(gitUrl) {
const normalized = gitUrl.trim();
const sshMatch = normalized.match(/^[\w-]+@([^:]+):(.+?)(?:\.git)?$/);
if (sshMatch) {
const source = sshMatch[1];
const path = sshMatch[2];
const parts = path.split("/");
if (parts.length >= 2) {
return { source, owner: parts.slice(0, -1).join("/"), name: parts.at(-1) };
}
}
try {
const url = new URL(normalized);
const pathname = url.pathname.replace(/\.git$/, "").replace(/^\//, "");
const parts = pathname.split("/");
if (parts.length >= 2) {
return { source: url.hostname, owner: parts.slice(0, -1).join("/"), name: parts.at(-1) };
}
} catch {
}
return null;
}
async function getGit(rootDir) {
const gitRemote = await getGitRemote(rootDir);
if (!gitRemote) {
return;
}
const meta = parseGitUrl(gitRemote);
if (!meta) {
return;
}
return {
url: `https://${meta.source}/${meta.owner}/${meta.name}`,
gitRemote,
source: meta.source,
owner: meta.owner,
name: meta.name
};
}
const logger = useLogger("@nuxt/telemetry");
const build = function({ nuxt }, payload) {
const duration = { build: payload.duration.build };
let isSuccess = true;
for (const [name, stat] of Object.entries(payload.stats)) {
duration[name] = stat.duration;
if (!stat.success) {
isSuccess = false;
}
}
return {
name: "build",
isSuccess,
isDev: nuxt.options.dev || false,
duration
// size
};
};
const command = function({ nuxt }) {
let command2 = process.argv[2] || "unknown";
const flagMap = {
dev: "dev",
_generate: "generate",
_export: "export",
_build: "build",
_serve: "serve",
_start: "start"
};
for (const _flag in flagMap) {
const flag = _flag;
if (nuxt.options[flag]) {
command2 = flagMap[flag];
break;
}
}
return {
name: "command",
command: command2
};
};
const generate = function generate2({ nuxt }, payload) {
return {
name: "generate",
// @ts-expect-error Legacy type from Nuxt 2
isExport: !!nuxt.options._export,
routesCount: payload.routesCount,
duration: {
generate: payload.duration.generate
}
};
};
const module$2 = function({ nuxt: { options } }) {
const events = [];
const modules = (options._installedModules || []).filter((m) => m.meta?.version).map((m) => ({
name: m.meta.name,
version: m.meta.version,
timing: m.timings?.setup || 0
}));
for (const m of modules) {
events.push({
name: "module",
moduleName: m.name,
version: m.version,
timing: m.timing
});
}
return events;
};
const project = function(context) {
const { options } = context.nuxt;
return {
name: "project",
type: context.git && context.git.url ? "git" : "local",
isSSR: options.ssr !== false,
target: options.server ? "server" : "static",
packageManager: context.packageManager
};
};
const session = function({ seed }) {
return {
name: "session",
id: seed
};
};
const files = async function(context) {
const { options } = context.nuxt;
const nuxtIgnore = fs.existsSync(resolve(options.rootDir, ".nuxtignore"));
const nuxtRc = fs.existsSync(resolve(options.rootDir, ".nuxtrc"));
const appConfig = fs.existsSync(await resolvePath("~/app.config"));
return {
name: "files",
nuxtIgnore,
nuxtRc,
appConfig
};
};
class Telemetry {
nuxt;
options;
storage;
// TODO
_contextPromise;
events = [];
eventFactories = {
build,
command,
generate,
module: module$2,
project,
session,
files
};
constructor(nuxt, options) {
this.nuxt = nuxt;
this.options = options;
}
getContext() {
if (!this._contextPromise) {
this._contextPromise = createContext(this.nuxt, this.options);
}
return this._contextPromise;
}
createEvent(name, payload) {
const eventFactory = this.eventFactories[name];
if (typeof eventFactory !== "function") {
logger.warn("Unknown event:", name);
return;
}
const eventPromise = this._invokeEvent(name, eventFactory, payload);
this.events.push(eventPromise);
}
async _invokeEvent(name, eventFactory, payload) {
try {
const context = await this.getContext();
const event = await eventFactory(context, payload);
event.name = name;
return event;
} catch (err) {
logger.error("Error while running event:", err);
}
}
async getPublicContext() {
const context = await this.getContext();
const eventContext = {};
for (const key of [
"nuxtVersion",
"nuxtMajorVersion",
"isEdge",
"nodeVersion",
"cli",
"os",
"environment",
"projectHash",
"projectSession"
]) {
eventContext[key] = context[key];
}
return eventContext;
}
async sendEvents(debug) {
const events = [].concat(...(await Promise.all(this.events)).filter(Boolean));
this.events = [];
const context = await this.getPublicContext();
const body = {
timestamp: Date.now(),
context,
events
};
if (this.options.endpoint) {
const start = Date.now();
try {
if (debug) {
logger.info("Sending events:", JSON.stringify(body, null, 2));
}
await postEvent(this.options.endpoint, body);
if (debug) {
logger.success(`Events sent to \`${this.options.endpoint}\` (${Date.now() - start} ms)`);
}
} catch (err) {
if (debug) {
logger.error(`Error sending sent to \`${this.options.endpoint}\` (${Date.now() - start} ms)
`, err);
}
}
}
}
}
const module$1 = defineNuxtModule({
meta: {
name: "@nuxt/telemetry",
configKey: "telemetry"
},
defaults: {
endpoint: process.env.NUXT_TELEMETRY_ENDPOINT || "https://telemetry.nuxt.com",
debug: process.env.NUXT_TELEMETRY_DEBUG === "1" || process.env.NUXT_TELEMETRY_DEBUG === "true",
enabled: void 0,
seed: void 0
},
async setup(toptions, nuxt) {
if (!toptions.debug) {
logger.level = 0;
}
const _topLevelTelemetry = nuxt.options.telemetry;
if (_topLevelTelemetry !== true) {
if (toptions.enabled === false || _topLevelTelemetry === false || !await ensureUserconsent(toptions)) {
logger.info("Telemetry disabled");
return;
}
}
logger.info("Telemetry enabled");
if (!toptions.seed || typeof toptions.seed !== "string") {
toptions.seed = randomSeed();
updateUserNuxtRc("telemetry.seed", toptions.seed);
logger.info("Seed generated:", toptions.seed);
}
const t = new Telemetry(nuxt, toptions);
nuxt.hook("modules:done", async () => {
t.createEvent("project");
if (nuxt.options.dev) {
t.createEvent("session");
t.createEvent("files");
}
t.createEvent("command");
t.createEvent("module");
await nuxt.callHook("telemetry:setup", t);
t.sendEvents(toptions.debug);
});
}
});
export { module$1 as default };

View File

@@ -0,0 +1,76 @@
'use strict';
const utils = require('consola/utils');
const consola = require('consola');
const stdEnv = require('std-env');
const rc = require('rc9');
const fs = require('node:fs');
const version = "2.7.0";
const consentVersion = 1;
function updateUserNuxtRc(key, val) {
rc.updateUser({ [key]: val }, ".nuxtrc");
}
let isDockerCached;
function hasDockerEnv() {
try {
fs.statSync("/.dockerenv");
return true;
} catch {
return false;
}
}
function hasDockerCGroup() {
try {
return fs.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
} catch {
return false;
}
}
function hasDockerMountInfo() {
try {
return fs.readFileSync("/proc/self/mountinfo", "utf8").includes("/docker/containers/");
} catch {
return false;
}
}
function isDocker() {
isDockerCached ??= hasDockerEnv() || hasDockerCGroup() || hasDockerMountInfo();
return isDockerCached;
}
async function ensureUserconsent(options) {
if (options.consent && options.consent >= consentVersion) {
return true;
}
if (stdEnv.isMinimal || process.env.CODESANDBOX_SSE || process.env.NEXT_TELEMETRY_DISABLED || isDocker()) {
return false;
}
consola.consola.restoreAll();
process.stdout.write("\n");
consola.consola.info(`${utils.colors.green("Nuxt")} collects completely anonymous data about usage.
This will help us improve Nuxt developer experience over time.
Read more on ${utils.colors.underline(utils.colors.cyan("https://github.com/nuxt/telemetry"))}
`);
const accepted = await consola.consola.prompt("Are you interested in participating?", {
type: "confirm"
});
process.stdout.write("\n");
consola.consola.wrapAll();
if (accepted) {
updateUserNuxtRc("telemetry.consent", consentVersion);
updateUserNuxtRc("telemetry.enabled", true);
return true;
}
updateUserNuxtRc("telemetry.enabled", false);
return false;
}
exports.consentVersion = consentVersion;
exports.ensureUserconsent = ensureUserconsent;
exports.isDocker = isDocker;
exports.updateUserNuxtRc = updateUserNuxtRc;
exports.version = version;

View File

@@ -0,0 +1,70 @@
import { colors } from 'consola/utils';
import { consola } from 'consola';
import { isMinimal } from 'std-env';
import { updateUser } from 'rc9';
import { statSync, readFileSync } from 'node:fs';
const version = "2.7.0";
const consentVersion = 1;
function updateUserNuxtRc(key, val) {
updateUser({ [key]: val }, ".nuxtrc");
}
let isDockerCached;
function hasDockerEnv() {
try {
statSync("/.dockerenv");
return true;
} catch {
return false;
}
}
function hasDockerCGroup() {
try {
return readFileSync("/proc/self/cgroup", "utf8").includes("docker");
} catch {
return false;
}
}
function hasDockerMountInfo() {
try {
return readFileSync("/proc/self/mountinfo", "utf8").includes("/docker/containers/");
} catch {
return false;
}
}
function isDocker() {
isDockerCached ??= hasDockerEnv() || hasDockerCGroup() || hasDockerMountInfo();
return isDockerCached;
}
async function ensureUserconsent(options) {
if (options.consent && options.consent >= consentVersion) {
return true;
}
if (isMinimal || process.env.CODESANDBOX_SSE || process.env.NEXT_TELEMETRY_DISABLED || isDocker()) {
return false;
}
consola.restoreAll();
process.stdout.write("\n");
consola.info(`${colors.green("Nuxt")} collects completely anonymous data about usage.
This will help us improve Nuxt developer experience over time.
Read more on ${colors.underline(colors.cyan("https://github.com/nuxt/telemetry"))}
`);
const accepted = await consola.prompt("Are you interested in participating?", {
type: "confirm"
});
process.stdout.write("\n");
consola.wrapAll();
if (accepted) {
updateUserNuxtRc("telemetry.consent", consentVersion);
updateUserNuxtRc("telemetry.enabled", true);
return true;
}
updateUserNuxtRc("telemetry.enabled", false);
return false;
}
export { consentVersion as c, ensureUserconsent as e, isDocker as i, updateUserNuxtRc as u, version as v };

3
node_modules/@nuxt/telemetry/dist/types.d.cts generated vendored Normal file
View File

@@ -0,0 +1,3 @@
import { default as telemetryModule } from './module.mjs'
export = telemetryModule

9
node_modules/@nuxt/telemetry/dist/types.d.mts generated vendored Normal file
View File

@@ -0,0 +1,9 @@
import type { ModuleHooks } from './module.mjs'
declare module '@nuxt/schema' {
interface NuxtHooks extends ModuleHooks {}
}
export { default } from './module.mjs'
export { type ModuleHooks, type ModuleOptions } from './module.mjs'

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Pooya Parsa <pooya@pi0.io>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,105 @@
# 🌆 citty
<!-- automd:badges color=yellow bundlephobia -->
[![npm version](https://img.shields.io/npm/v/citty?color=yellow)](https://npmjs.com/package/citty)
[![npm downloads](https://img.shields.io/npm/dm/citty?color=yellow)](https://npmjs.com/package/citty)
[![bundle size](https://img.shields.io/bundlephobia/minzip/citty?color=yellow)](https://bundlephobia.com/package/citty)
<!-- /automd -->
Elegant CLI Builder
- Zero dependency
- Fast and lightweight argument parser (based on native [Node.js `util.parseArgs`](https://nodejs.org/api/util.html#utilparseargsconfig))
- Smart value parsing with typecast and boolean shortcuts
- Nested sub-commands
- Lazy and Async commands
- Pluggable and composable API
- Auto generated usage and help
## Usage
Install package:
```sh
npx nypm add -D citty
```
Import:
```js
import { defineCommand, runMain } from "citty";
```
Define main command to run:
```ts
import { defineCommand, runMain } from "citty";
const main = defineCommand({
meta: {
name: "hello",
version: "1.0.0",
description: "My Awesome CLI App",
},
args: {
name: {
type: "positional",
description: "Your name",
required: true,
},
friendly: {
type: "boolean",
description: "Use friendly greeting",
},
},
run({ args }) {
console.log(`${args.friendly ? "Hi" : "Greetings"} ${args.name}!`);
},
});
runMain(main);
```
## Utils
### `defineCommand`
`defineCommand` is a type helper for defining commands.
### `runMain`
Runs a command with usage support and graceful error handling.
### `createMain`
Create a wrapper around command that calls `runMain` when called.
### `runCommand`
Parses input args and runs command and sub-commands (unsupervised). You can access `result` key from returnd/awaited value to access command's result.
### `parseArgs`
Parses input arguments and applies defaults.
### `renderUsage`
Renders command usage to a string value.
### `showUsage`
Renders usage and prints to the console
## Development
- Clone this repository
- Install latest LTS version of [Node.js](https://nodejs.org/en/)
- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`
- Install dependencies using `pnpm install`
- Run interactive tests using `pnpm dev`
## License
Made with 💛 Published under [MIT License](./LICENSE).

View File

@@ -0,0 +1,33 @@
# Licenses of Bundled Dependencies
The published artifact additionally contains code with the following licenses:
MIT
# Bundled Dependencies
## scule
License: MIT
Repository: https://github.com/unjs/scule
> MIT License
>
> Copyright (c) Pooya Parsa <pooya@pi0.io>
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.

View File

@@ -0,0 +1,65 @@
const NUMBER_CHAR_RE = /\d/;
const STR_SPLITTERS = [
"-",
"_",
"/",
"."
];
function isUppercase(char = "") {
if (NUMBER_CHAR_RE.test(char)) return;
return char !== char.toLowerCase();
}
function splitByCase(str, separators) {
const splitters = separators ?? STR_SPLITTERS;
const parts = [];
if (!str || typeof str !== "string") return parts;
let buff = "";
let previousUpper;
let previousSplitter;
for (const char of str) {
const isSplitter = splitters.includes(char);
if (isSplitter === true) {
parts.push(buff);
buff = "";
previousUpper = void 0;
continue;
}
const isUpper = isUppercase(char);
if (previousSplitter === false) {
if (previousUpper === false && isUpper === true) {
parts.push(buff);
buff = char;
previousUpper = isUpper;
continue;
}
if (previousUpper === true && isUpper === false && buff.length > 1) {
const lastChar = buff.at(-1);
parts.push(buff.slice(0, Math.max(0, buff.length - 1)));
buff = lastChar + char;
previousUpper = isUpper;
continue;
}
}
buff += char;
previousUpper = isUpper;
previousSplitter = isSplitter;
}
parts.push(buff);
return parts;
}
function upperFirst(str) {
return str ? str[0].toUpperCase() + str.slice(1) : "";
}
function lowerFirst(str) {
return str ? str[0].toLowerCase() + str.slice(1) : "";
}
function pascalCase(str, opts) {
return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)).join("") : "";
}
function camelCase(str, opts) {
return lowerFirst(pascalCase(str || "", opts));
}
function kebabCase(str, joiner) {
return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => p.toLowerCase()).join(joiner ?? "-") : "";
}
export { kebabCase as n, camelCase as t };

View File

@@ -0,0 +1,101 @@
//#region src/types.d.ts
type ArgType = "boolean" | "string" | "enum" | "positional" | undefined;
type _ArgDef<T extends ArgType, VT extends boolean | number | string> = {
type?: T;
description?: string;
valueHint?: string;
alias?: string | string[];
default?: VT;
required?: boolean;
options?: string[];
};
type BooleanArgDef = Omit<_ArgDef<"boolean", boolean>, "options"> & {
negativeDescription?: string;
};
type StringArgDef = Omit<_ArgDef<"string", string>, "options">;
type EnumArgDef = _ArgDef<"enum", string>;
type PositionalArgDef = Omit<_ArgDef<"positional", string>, "alias" | "options">;
type ArgDef = BooleanArgDef | StringArgDef | PositionalArgDef | EnumArgDef;
type ArgsDef = Record<string, ArgDef>;
type Arg = ArgDef & {
name: string;
alias: string[];
};
type ResolveParsedArgType<T extends ArgDef, VT> = T extends {
default?: any;
required?: boolean;
} ? T["default"] extends NonNullable<VT> ? VT : T["required"] extends true ? VT : VT | undefined : VT | undefined;
type ParsedPositionalArg<T extends ArgDef> = T extends {
type: "positional";
} ? ResolveParsedArgType<T, string> : never;
type ParsedStringArg<T extends ArgDef> = T extends {
type: "string";
} ? ResolveParsedArgType<T, string> : never;
type ParsedBooleanArg<T extends ArgDef> = T extends {
type: "boolean";
} ? ResolveParsedArgType<T, boolean> : never;
type ParsedEnumArg<T extends ArgDef> = T extends {
type: "enum";
options: infer U;
} ? U extends Array<any> ? ResolveParsedArgType<T, U[number]> : never : never;
type RawArgs = {
_: string[];
};
type ParsedArg<T extends ArgDef> = T["type"] extends "positional" ? ParsedPositionalArg<T> : T["type"] extends "boolean" ? ParsedBooleanArg<T> : T["type"] extends "string" ? ParsedStringArg<T> : T["type"] extends "enum" ? ParsedEnumArg<T> : never;
type ParsedArgs<T extends ArgsDef = ArgsDef> = RawArgs & { [K in keyof T]: ParsedArg<T[K]> } & { [K in keyof T as T[K] extends {
alias: string;
} ? T[K]["alias"] : never]: ParsedArg<T[K]> } & { [K in keyof T as T[K] extends {
alias: string[];
} ? T[K]["alias"][number] : never]: ParsedArg<T[K]> } & Record<string, string | number | boolean | string[]>;
interface CommandMeta {
name?: string;
version?: string;
description?: string;
hidden?: boolean;
}
type SubCommandsDef = Record<string, Resolvable<CommandDef<any>>>;
type CommandDef<T extends ArgsDef = ArgsDef> = {
meta?: Resolvable<CommandMeta>;
args?: Resolvable<T>;
subCommands?: Resolvable<SubCommandsDef>;
setup?: (context: CommandContext<T>) => any | Promise<any>;
cleanup?: (context: CommandContext<T>) => any | Promise<any>;
run?: (context: CommandContext<T>) => any | Promise<any>;
};
type CommandContext<T extends ArgsDef = ArgsDef> = {
rawArgs: string[];
args: ParsedArgs<T>;
cmd: CommandDef<T>;
subCommand?: CommandDef<T>;
data?: any;
};
type Awaitable<T> = () => T | Promise<T>;
type Resolvable<T> = T | Promise<T> | (() => T) | (() => Promise<T>);
//#endregion
//#region src/command.d.ts
declare function defineCommand<const T extends ArgsDef = ArgsDef>(def: CommandDef<T>): CommandDef<T>;
interface RunCommandOptions {
rawArgs: string[];
data?: any;
showUsage?: boolean;
}
declare function runCommand<T extends ArgsDef = ArgsDef>(cmd: CommandDef<T>, opts: RunCommandOptions): Promise<{
result: unknown;
}>;
//#endregion
//#region src/usage.d.ts
declare function showUsage<T extends ArgsDef = ArgsDef>(cmd: CommandDef<T>, parent?: CommandDef<T>): Promise<void>;
declare function renderUsage<T extends ArgsDef = ArgsDef>(cmd: CommandDef<T>, parent?: CommandDef<T>): Promise<string>;
//#endregion
//#region src/main.d.ts
interface RunMainOptions {
rawArgs?: string[];
showUsage?: typeof showUsage;
}
declare function runMain<T extends ArgsDef = ArgsDef>(cmd: CommandDef<T>, opts?: RunMainOptions): Promise<void>;
declare function createMain<T extends ArgsDef = ArgsDef>(cmd: CommandDef<T>): (opts?: RunMainOptions) => Promise<void>;
//#endregion
//#region src/args.d.ts
declare function parseArgs<T extends ArgsDef = ArgsDef>(rawArgs: string[], argsDef: ArgsDef): ParsedArgs<T>;
//#endregion
export { Arg, ArgDef, ArgType, ArgsDef, Awaitable, BooleanArgDef, CommandContext, CommandDef, CommandMeta, EnumArgDef, ParsedArgs, PositionalArgDef, Resolvable, type RunCommandOptions, type RunMainOptions, StringArgDef, SubCommandsDef, _ArgDef, createMain, defineCommand, parseArgs, renderUsage, runCommand, runMain, showUsage };

View File

@@ -0,0 +1,297 @@
import { n as kebabCase, t as camelCase } from "./_chunks/libs/scule.mjs";
import { parseArgs as parseArgs$1 } from "node:util";
function toArray(val) {
if (Array.isArray(val)) return val;
return val === void 0 ? [] : [val];
}
function formatLineColumns(lines, linePrefix = "") {
const maxLength = [];
for (const line of lines) for (const [i, element] of line.entries()) maxLength[i] = Math.max(maxLength[i] || 0, element.length);
return lines.map((l) => l.map((c, i) => linePrefix + c[i === 0 ? "padStart" : "padEnd"](maxLength[i])).join(" ")).join("\n");
}
function resolveValue(input) {
return typeof input === "function" ? input() : input;
}
var CLIError = class extends Error {
code;
constructor(message, code) {
super(message);
this.name = "CLIError";
this.code = code;
}
};
function parseRawArgs(args = [], opts = {}) {
const booleans = new Set(opts.boolean || []);
const strings = new Set(opts.string || []);
const aliasMap = opts.alias || {};
const defaults = opts.default || {};
const aliasToMain = /* @__PURE__ */ new Map();
const mainToAliases = /* @__PURE__ */ new Map();
for (const [key, value] of Object.entries(aliasMap)) {
const targets = value;
for (const target of targets) {
aliasToMain.set(key, target);
if (!mainToAliases.has(target)) mainToAliases.set(target, []);
mainToAliases.get(target).push(key);
aliasToMain.set(target, key);
if (!mainToAliases.has(key)) mainToAliases.set(key, []);
mainToAliases.get(key).push(target);
}
}
const options = {};
function getType(name) {
if (booleans.has(name)) return "boolean";
const aliases = mainToAliases.get(name) || [];
for (const alias of aliases) if (booleans.has(alias)) return "boolean";
return "string";
}
const allOptions = new Set([
...booleans,
...strings,
...Object.keys(aliasMap),
...Object.values(aliasMap).flat(),
...Object.keys(defaults)
]);
for (const name of allOptions) if (!options[name]) options[name] = {
type: getType(name),
default: defaults[name]
};
for (const [alias, main] of aliasToMain.entries()) if (alias.length === 1 && options[main] && !options[main].short) options[main].short = alias;
const processedArgs = [];
const negatedFlags = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "--") {
processedArgs.push(...args.slice(i));
break;
}
if (arg.startsWith("--no-")) {
const flagName = arg.slice(5);
negatedFlags[flagName] = true;
continue;
}
processedArgs.push(arg);
}
let parsed;
try {
parsed = parseArgs$1({
args: processedArgs,
options: Object.keys(options).length > 0 ? options : void 0,
allowPositionals: true,
strict: false
});
} catch {
parsed = {
values: {},
positionals: processedArgs
};
}
const out = { _: [] };
out._ = parsed.positionals;
for (const [key, value] of Object.entries(parsed.values)) out[key] = value;
for (const [name] of Object.entries(negatedFlags)) {
out[name] = false;
const mainName = aliasToMain.get(name);
if (mainName) out[mainName] = false;
const aliases = mainToAliases.get(name);
if (aliases) for (const alias of aliases) out[alias] = false;
}
for (const [alias, main] of aliasToMain.entries()) {
if (out[alias] !== void 0 && out[main] === void 0) out[main] = out[alias];
if (out[main] !== void 0 && out[alias] === void 0) out[alias] = out[main];
}
return out;
}
const noColor = /* @__PURE__ */ (() => {
const env = globalThis.process?.env ?? {};
return env.NO_COLOR === "1" || env.TERM === "dumb" || env.TEST || env.CI;
})();
const _c = (c, r = 39) => (t) => noColor ? t : `\u001B[${c}m${t}\u001B[${r}m`;
const bold = /* @__PURE__ */ _c(1, 22);
const cyan = /* @__PURE__ */ _c(36);
const gray = /* @__PURE__ */ _c(90);
const underline = /* @__PURE__ */ _c(4, 24);
function parseArgs(rawArgs, argsDef) {
const parseOptions = {
boolean: [],
string: [],
alias: {},
default: {}
};
const args = resolveArgs(argsDef);
for (const arg of args) {
if (arg.type === "positional") continue;
if (arg.type === "string" || arg.type === "enum") parseOptions.string.push(arg.name);
else if (arg.type === "boolean") parseOptions.boolean.push(arg.name);
if (arg.default !== void 0) parseOptions.default[arg.name] = arg.default;
if (arg.alias) parseOptions.alias[arg.name] = arg.alias;
const camelName = camelCase(arg.name);
const kebabName = kebabCase(arg.name);
if (camelName !== arg.name || kebabName !== arg.name) {
const existingAliases = toArray(parseOptions.alias[arg.name] || []);
if (camelName !== arg.name && !existingAliases.includes(camelName)) existingAliases.push(camelName);
if (kebabName !== arg.name && !existingAliases.includes(kebabName)) existingAliases.push(kebabName);
if (existingAliases.length > 0) parseOptions.alias[arg.name] = existingAliases;
}
}
const parsed = parseRawArgs(rawArgs, parseOptions);
const [ ...positionalArguments] = parsed._;
const parsedArgsProxy = new Proxy(parsed, { get(target, prop) {
return target[prop] ?? target[camelCase(prop)] ?? target[kebabCase(prop)];
} });
for (const [, arg] of args.entries()) if (arg.type === "positional") {
const nextPositionalArgument = positionalArguments.shift();
if (nextPositionalArgument !== void 0) parsedArgsProxy[arg.name] = nextPositionalArgument;
else if (arg.default === void 0 && arg.required !== false) throw new CLIError(`Missing required positional argument: ${arg.name.toUpperCase()}`, "EARG");
else parsedArgsProxy[arg.name] = arg.default;
} else if (arg.type === "enum") {
const argument = parsedArgsProxy[arg.name];
const options = arg.options || [];
if (argument !== void 0 && options.length > 0 && !options.includes(argument)) throw new CLIError(`Invalid value for argument: ${cyan(`--${arg.name}`)} (${cyan(argument)}). Expected one of: ${options.map((o) => cyan(o)).join(", ")}.`, "EARG");
} else if (arg.required && parsedArgsProxy[arg.name] === void 0) throw new CLIError(`Missing required argument: --${arg.name}`, "EARG");
return parsedArgsProxy;
}
function resolveArgs(argsDef) {
const args = [];
for (const [name, argDef] of Object.entries(argsDef || {})) args.push({
...argDef,
name,
alias: toArray(argDef.alias)
});
return args;
}
function defineCommand(def) {
return def;
}
async function runCommand(cmd, opts) {
const cmdArgs = await resolveValue(cmd.args || {});
const parsedArgs = parseArgs(opts.rawArgs, cmdArgs);
const context = {
rawArgs: opts.rawArgs,
args: parsedArgs,
data: opts.data,
cmd
};
if (typeof cmd.setup === "function") await cmd.setup(context);
let result;
try {
const subCommands = await resolveValue(cmd.subCommands);
if (subCommands && Object.keys(subCommands).length > 0) {
const subCommandArgIndex = opts.rawArgs.findIndex((arg) => !arg.startsWith("-"));
const subCommandName = opts.rawArgs[subCommandArgIndex];
if (subCommandName) {
if (!subCommands[subCommandName]) throw new CLIError(`Unknown command ${cyan(subCommandName)}`, "E_UNKNOWN_COMMAND");
const subCommand = await resolveValue(subCommands[subCommandName]);
if (subCommand) await runCommand(subCommand, { rawArgs: opts.rawArgs.slice(subCommandArgIndex + 1) });
} else if (!cmd.run) throw new CLIError(`No command specified.`, "E_NO_COMMAND");
}
if (typeof cmd.run === "function") result = await cmd.run(context);
} finally {
if (typeof cmd.cleanup === "function") await cmd.cleanup(context);
}
return { result };
}
async function resolveSubCommand(cmd, rawArgs, parent) {
const subCommands = await resolveValue(cmd.subCommands);
if (subCommands && Object.keys(subCommands).length > 0) {
const subCommandArgIndex = rawArgs.findIndex((arg) => !arg.startsWith("-"));
const subCommandName = rawArgs[subCommandArgIndex];
const subCommand = await resolveValue(subCommands[subCommandName]);
if (subCommand) return resolveSubCommand(subCommand, rawArgs.slice(subCommandArgIndex + 1), cmd);
}
return [cmd, parent];
}
async function showUsage(cmd, parent) {
try {
console.log(await renderUsage(cmd, parent) + "\n");
} catch (error) {
console.error(error);
}
}
const negativePrefixRe = /^no[-A-Z]/;
async function renderUsage(cmd, parent) {
const cmdMeta = await resolveValue(cmd.meta || {});
const cmdArgs = resolveArgs(await resolveValue(cmd.args || {}));
const parentMeta = await resolveValue(parent?.meta || {});
const commandName = `${parentMeta.name ? `${parentMeta.name} ` : ""}` + (cmdMeta.name || process.argv[1]);
const argLines = [];
const posLines = [];
const commandsLines = [];
const usageLine = [];
for (const arg of cmdArgs) if (arg.type === "positional") {
const name = arg.name.toUpperCase();
const isRequired = arg.required !== false && arg.default === void 0;
const defaultHint = arg.default ? `="${arg.default}"` : "";
posLines.push([
cyan(name + defaultHint),
arg.description || "",
arg.valueHint ? `<${arg.valueHint}>` : ""
]);
usageLine.push(isRequired ? `<${name}>` : `[${name}]`);
} else {
const isRequired = arg.required === true && arg.default === void 0;
const argStr = [...(arg.alias || []).map((a) => `-${a}`), `--${arg.name}`].join(", ") + (arg.type === "string" && (arg.valueHint || arg.default) ? `=${arg.valueHint ? `<${arg.valueHint}>` : `"${arg.default || ""}"`}` : "") + (arg.type === "enum" && arg.options ? `=<${arg.options.join("|")}>` : "");
argLines.push([cyan(argStr + (isRequired ? " (required)" : "")), arg.description || ""]);
if (arg.type === "boolean" && (arg.default === true || arg.negativeDescription) && !negativePrefixRe.test(arg.name)) {
const negativeArgStr = [...(arg.alias || []).map((a) => `--no-${a}`), `--no-${arg.name}`].join(", ");
argLines.push([cyan(negativeArgStr + (isRequired ? " (required)" : "")), arg.negativeDescription || ""]);
}
if (isRequired) usageLine.push(argStr);
}
if (cmd.subCommands) {
const commandNames = [];
const subCommands = await resolveValue(cmd.subCommands);
for (const [name, sub] of Object.entries(subCommands)) {
const meta = await resolveValue((await resolveValue(sub))?.meta);
if (meta?.hidden) continue;
commandsLines.push([cyan(name), meta?.description || ""]);
commandNames.push(name);
}
usageLine.push(commandNames.join("|"));
}
const usageLines = [];
const version = cmdMeta.version || parentMeta.version;
usageLines.push(gray(`${cmdMeta.description} (${commandName + (version ? ` v${version}` : "")})`), "");
const hasOptions = argLines.length > 0 || posLines.length > 0;
usageLines.push(`${underline(bold("USAGE"))} ${cyan(`${commandName}${hasOptions ? " [OPTIONS]" : ""} ${usageLine.join(" ")}`)}`, "");
if (posLines.length > 0) {
usageLines.push(underline(bold("ARGUMENTS")), "");
usageLines.push(formatLineColumns(posLines, " "));
usageLines.push("");
}
if (argLines.length > 0) {
usageLines.push(underline(bold("OPTIONS")), "");
usageLines.push(formatLineColumns(argLines, " "));
usageLines.push("");
}
if (commandsLines.length > 0) {
usageLines.push(underline(bold("COMMANDS")), "");
usageLines.push(formatLineColumns(commandsLines, " "));
usageLines.push("", `Use ${cyan(`${commandName} <command> --help`)} for more information about a command.`);
}
return usageLines.filter((l) => typeof l === "string").join("\n");
}
async function runMain(cmd, opts = {}) {
const rawArgs = opts.rawArgs || process.argv.slice(2);
const showUsage$1 = opts.showUsage || showUsage;
try {
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
process.exit(0);
} else if (rawArgs.length === 1 && rawArgs[0] === "--version") {
const meta = typeof cmd.meta === "function" ? await cmd.meta() : await cmd.meta;
if (!meta?.version) throw new CLIError("No version specified", "E_NO_VERSION");
console.log(meta.version);
} else await runCommand(cmd, { rawArgs });
} catch (error) {
if (error instanceof CLIError) {
await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
console.error(error.message);
} else console.error(error, "\n");
process.exit(1);
}
}
function createMain(cmd) {
return (opts = {}) => runMain(cmd, opts);
}
export { createMain, defineCommand, parseArgs, renderUsage, runCommand, runMain, showUsage };

View File

@@ -0,0 +1,42 @@
{
"name": "citty",
"version": "0.2.1",
"description": "Elegant CLI Builder",
"license": "MIT",
"repository": "unjs/citty",
"files": [
"dist"
],
"type": "module",
"sideEffects": false,
"types": "./dist/index.d.mts",
"exports": {
".": "./dist/index.mjs"
},
"scripts": {
"build": "obuild",
"dev": "vitest dev",
"lint": "oxlint . && oxfmt --check",
"lint:fix": "oxlint . --fix && oxfmt",
"prepack": "pnpm run build",
"play": "node ./playground/cli.ts",
"release": "pnpm test && pnpm build && changelogen --release --push && npm publish",
"test": "pnpm lint && pnpm test:types && vitest run --coverage",
"test:types": "tsgo --noEmit"
},
"devDependencies": {
"@types/node": "^25.2.3",
"@typescript/native-preview": "7.0.0-dev.20260212.1",
"@vitest/coverage-v8": "^4.0.18",
"automd": "^0.4.3",
"changelogen": "^0.6.2",
"eslint-config-unjs": "^0.6.2",
"obuild": "^0.4.27",
"oxfmt": "^0.32.0",
"oxlint": "^1.47.0",
"scule": "^1.3.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"packageManager": "pnpm@10.29.3"
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Pooya Parsa <pooya@pi0.io>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,398 @@
# ofetch
<!-- automd:badges -->
[![npm version](https://img.shields.io/npm/v/ofetch)](https://npmjs.com/package/ofetch)
[![npm downloads](https://img.shields.io/npm/dm/ofetch)](https://npm.chart.dev/ofetch)
<!-- /automd -->
A better fetch API. Works on node, browser, and workers.
> [!IMPORTANT]
> You are on v2 (alpha) development branch. See [v1](https://github.com/unjs/ofetch/tree/v1) for v1 docs.
<details>
<summary>Spoiler</summary>
<img src="https://media.giphy.com/media/Dn1QRA9hqMcoMz9zVZ/giphy.gif">
</details>
## 🚀 Quick Start
Install:
```bash
npx nypm i ofetch
```
Import:
```js
import { ofetch } from "ofetch";
```
## ✔️ Parsing Response
`ofetch` smartly parse JSON responses.
```js
const { users } = await ofetch("/api/users");
```
For binary content types, `ofetch` will instead return a `Blob` object.
You can optionally provide a different parser than `JSON.parse`, or specify `blob`, `arrayBuffer`, `text` or `stream` to force parsing the body with the respective `FetchResponse` method.
```js
// Return text as is
await ofetch("/movie?lang=en", { parseResponse: (txt) => txt });
// Get the blob version of the response
await ofetch("/api/generate-image", { responseType: "blob" });
// Get the stream version of the response
await ofetch("/api/generate-image", { responseType: "stream" });
```
## ✔️ JSON Body
If an object or a class with a `.toJSON()` method is passed to the `body` option, `ofetch` automatically stringifies it.
`ofetch` utilizes `JSON.stringify()` to convert the passed object. Classes without a `.toJSON()` method have to be converted into a string value in advance before being passed to the `body` option.
For `PUT`, `PATCH`, and `POST` request methods, when a string or object body is set, `ofetch` adds the default `"content-type": "application/json"` and `accept: "application/json"` headers (which you can always override).
Additionally, `ofetch` supports binary responses with `Buffer`, `ReadableStream`, `Stream`, and [compatible body types](https://developer.mozilla.org/en-US/docs/Web/API/fetch#body). `ofetch` will automatically set the `duplex: "half"` option for streaming support!
**Example:**
```js
const { users } = await ofetch("/api/users", {
method: "POST",
body: { some: "json" },
});
```
## ✔️ Handling Errors
`ofetch` Automatically throws errors when `response.ok` is `false` with a friendly error message and compact stack (hiding internals).
A parsed error body is available with `error.data`. You may also use `FetchError` type.
```ts
await ofetch("https://google.com/404");
// FetchError: [GET] "https://google/404": 404 Not Found
// at async main (/project/playground.ts:4:3)
```
To catch error response:
```ts
await ofetch("/url").catch((error) => error.data);
```
To bypass status error catching you can set `ignoreResponseError` option:
```ts
await ofetch("/url", { ignoreResponseError: true });
```
## ✔️ Auto Retry
`ofetch` Automatically retries the request if an error happens and if the response status code is included in `retryStatusCodes` list:
**Retry status codes:**
- `408` - Request Timeout
- `409` - Conflict
- `425` - Too Early ([Experimental](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Early-Data))
- `429` - Too Many Requests
- `500` - Internal Server Error
- `502` - Bad Gateway
- `503` - Service Unavailable
- `504` - Gateway Timeout
You can specify the amount of retry and delay between them using `retry` and `retryDelay` options and also pass a custom array of codes using `retryStatusCodes` option.
The default for `retry` is `1` retry, except for `POST`, `PUT`, `PATCH`, and `DELETE` methods where `ofetch` does not retry by default to avoid introducing side effects. If you set a custom value for `retry` it will **always retry** for all requests.
The default for `retryDelay` is `0` ms.
```ts
await ofetch("http://google.com/404", {
retry: 3,
retryDelay: 500, // ms
retryStatusCodes: [404, 500], // response status codes to retry
});
```
## ✔️ Timeout
You can specify `timeout` in milliseconds to automatically abort a request after a timeout (default is disabled).
```ts
await ofetch("http://google.com/404", {
timeout: 3000, // Timeout after 3 seconds
});
```
## ✔️ Type Friendly
The response can be type assisted:
```ts
const article = await ofetch<Article>(`/api/article/${id}`);
// Auto complete working with article.id
```
## ✔️ Adding `baseURL`
By using `baseURL` option, `ofetch` prepends it for trailing/leading slashes and query search params for baseURL using [ufo](https://github.com/unjs/ufo):
```js
await ofetch("/config", { baseURL });
```
## ✔️ Adding Query Search Params
By using `query` option (or `params` as alias), `ofetch` adds query search params to the URL by preserving the query in the request itself using [ufo](https://github.com/unjs/ufo):
```js
await ofetch("/movie?lang=en", { query: { id: 123 } });
```
## ✔️ Interceptors
Providing async interceptors to hook into lifecycle events of `ofetch` call is possible.
You might want to use `ofetch.create` to set shared interceptors.
### `onRequest({ request, options })`
`onRequest` is called as soon as `ofetch` is called, allowing you to modify options or do simple logging.
```js
await ofetch("/api", {
async onRequest({ request, options }) {
// Log request
console.log("[fetch request]", request, options);
// Add `?t=1640125211170` to query search params
options.query = options.query || {};
options.query.t = new Date();
},
});
```
### `onRequestError({ request, options, error })`
`onRequestError` will be called when the fetch request fails.
```js
await ofetch("/api", {
async onRequestError({ request, options, error }) {
// Log error
console.log("[fetch request error]", request, error);
},
});
```
### `onResponse({ request, options, response })`
`onResponse` will be called after `fetch` call and parsing body.
```js
await ofetch("/api", {
async onResponse({ request, response, options }) {
// Log response
console.log("[fetch response]", request, response.status, response.body);
},
});
```
### `onResponseError({ request, options, response })`
`onResponseError` is the same as `onResponse` but will be called when fetch happens but `response.ok` is not `true`.
```js
await ofetch("/api", {
async onResponseError({ request, response, options }) {
// Log error
console.log(
"[fetch response error]",
request,
response.status,
response.body
);
},
});
```
### Passing array of interceptors
If necessary, it's also possible to pass an array of function that will be called sequentially.
```js
await ofetch("/api", {
onRequest: [
() => {
/* Do something */
},
() => {
/* Do something else */
},
],
});
```
## ✔️ Create fetch with default options
This utility is useful if you need to use common options across several fetch calls.
**Note:** Defaults will be cloned at one level and inherited. Be careful about nested options like `headers`.
```js
const apiFetch = ofetch.create({ baseURL: "/api" });
apiFetch("/test"); // Same as ofetch('/test', { baseURL: '/api' })
```
## 💡 Adding headers
By using `headers` option, `ofetch` adds extra headers in addition to the request default headers:
```js
await ofetch("/movies", {
headers: {
Accept: "application/json",
"Cache-Control": "no-cache",
},
});
```
## 🍣 Access to Raw Response
If you need to access raw response (for headers, etc), you can use `ofetch.raw`:
```js
const response = await ofetch.raw("/sushi");
// response._data
// response.headers
// ...
```
## 🌿 Using Native Fetch
As a shortcut, you can use `ofetch.native` that provides native `fetch` API
```js
const json = await ofetch.native("/sushi").then((r) => r.json());
```
## 📡 SSE
**Example:** Handle SSE response:
```js
const stream = await ofetch("/sse");
const reader = stream.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Here is the chunked text of the SSE response.
const text = decoder.decode(value);
}
```
## 🕵️ Adding HTTP(S) Agent
In Node.js (>= 18) environments, you can provide a custom dispatcher to intercept requests and support features such as Proxy and self-signed certificates. This feature is enabled by [undici](https://undici.nodejs.org/) built-in Node.js. [read more](https://undici.nodejs.org/#/docs/api/Dispatcher) about the Dispatcher API.
Some available agents:
- `ProxyAgent`: A Proxy Agent class that implements the Agent API. It allows the connection through a proxy in a simple way. ([docs](https://undici.nodejs.org/#/docs/api/ProxyAgent))
- `MockAgent`: A mocked Agent class that implements the Agent API. It allows one to intercept HTTP requests made through undici and return mocked responses instead. ([docs](https://undici.nodejs.org/#/docs/api/MockAgent))
- `Agent`: Agent allows dispatching requests against multiple different origins. ([docs](https://undici.nodejs.org/#/docs/api/Agent))
**Example:** Set a proxy agent for one request:
```ts
import { ProxyAgent } from "undici";
import { ofetch } from "ofetch";
const proxyAgent = new ProxyAgent("http://localhost:3128");
const data = await ofetch("https://icanhazip.com", { dispatcher: proxyAgent });
```
**Example:** Create a custom fetch instance that has proxy enabled:
```ts
import { ProxyAgent, setGlobalDispatcher } from "undici";
import { ofetch } from "ofetch";
const proxyAgent = new ProxyAgent("http://localhost:3128");
const fetchWithProxy = ofetch.create({ dispatcher: proxyAgent });
const data = await fetchWithProxy("https://icanhazip.com");
```
**Example:** Set a proxy agent for all requests:
```ts
import { ProxyAgent, setGlobalDispatcher } from "undici";
import { ofetch } from "ofetch";
const proxyAgent = new ProxyAgent("http://localhost:3128");
setGlobalDispatcher(proxyAgent);
const data = await ofetch("https://icanhazip.com");
```
**Example:** Allow self-signed certificates (USE AT YOUR OWN RISK!)
```ts
import { Agent } from "undici";
import { ofetch } from "ofetch";
// Note: This makes fetch unsecure against MITM attacks. USE AT YOUR OWN RISK!
const unsecureAgent = new Agent({ connect: { rejectUnauthorized: false } });
const unsecureFetch = ofetch.create({ dispatcher: unsecureAgent });
const data = await unsecureFetch("https://www.squid-cache.org/");
```
### 💪 Augment `FetchOptions` interface
You can augment the `FetchOptions` interface to add custom properties.
```ts
// Place this in any `.ts` or `.d.ts` file.
// Ensure it's included in the project's tsconfig.json "files".
declare module "ofetch" {
interface FetchOptions {
// Custom properties
requiresAuth?: boolean;
}
}
export {};
```
This lets you pass and use those properties with full type safety throughout `ofetch` calls.
```ts
const myFetch = ofetch.create({
onRequest(context) {
// ^? { ..., options: {..., requiresAuth?: boolean }}
console.log(context.options.requiresAuth);
},
});
myFetch("/foo", { requiresAuth: true });
```
## License
💛 Published under the [MIT](https://github.com/h3js/rou3/blob/main/LICENSE) license.

View File

@@ -0,0 +1,118 @@
import * as undici0 from "undici";
//#region src/types.d.ts
interface $Fetch {
<T = any, R extends ResponseType = "json">(request: FetchRequest, options?: FetchOptions<R>): Promise<MappedResponseType<R, T>>;
raw<T = any, R extends ResponseType = "json">(request: FetchRequest, options?: FetchOptions<R>): Promise<FetchResponse<MappedResponseType<R, T>>>;
native: Fetch;
create(defaults: FetchOptions, globalOptions?: CreateFetchOptions): $Fetch;
}
interface FetchOptions<R extends ResponseType = ResponseType, T = any> extends Omit<RequestInit, "body">, FetchHooks<T, R> {
baseURL?: string;
body?: RequestInit["body"] | Record<string, any>;
ignoreResponseError?: boolean;
/**
* @deprecated use query instead.
*/
params?: Record<string, any>;
query?: Record<string, any>;
parseResponse?: (responseText: string) => any;
responseType?: R;
/**
* @experimental Set to "half" to enable duplex streaming.
* Will be automatically set to "half" when using a ReadableStream as body.
* @see https://fetch.spec.whatwg.org/#enumdef-requestduplex
*/
duplex?: "half" | undefined;
/**
* Only supported in Node.js >= 18 using undici
*
* @see https://undici.nodejs.org/#/docs/api/Dispatcher
*/
dispatcher?: InstanceType<typeof undici0.Dispatcher>;
/**
* Only supported older Node.js versions using node-fetch-native polyfill.
*/
agent?: unknown;
/** timeout in milliseconds */
timeout?: number;
retry?: number | false;
/** Delay between retries in milliseconds. */
retryDelay?: number | ((context: FetchContext<T, R>) => number);
/** Default is [408, 409, 425, 429, 500, 502, 503, 504] */
retryStatusCodes?: number[];
}
interface ResolvedFetchOptions<R extends ResponseType = ResponseType, T = any> extends FetchOptions<R, T> {
headers: Headers;
}
interface CreateFetchOptions {
defaults?: FetchOptions;
fetch?: Fetch;
}
type GlobalOptions = Pick<FetchOptions, "timeout" | "retry" | "retryDelay">;
interface FetchContext<T = any, R extends ResponseType = ResponseType> {
request: FetchRequest;
options: ResolvedFetchOptions<R>;
response?: FetchResponse<T>;
error?: Error;
}
type MaybePromise<T> = T | Promise<T>;
type MaybeArray<T> = T | T[];
type FetchHook<C extends FetchContext = FetchContext> = (context: C) => MaybePromise<void>;
interface FetchHooks<T = any, R extends ResponseType = ResponseType> {
onRequest?: MaybeArray<FetchHook<FetchContext<T, R>>>;
onRequestError?: MaybeArray<FetchHook<FetchContext<T, R> & {
error: Error;
}>>;
onResponse?: MaybeArray<FetchHook<FetchContext<T, R> & {
response: FetchResponse<T>;
}>>;
onResponseError?: MaybeArray<FetchHook<FetchContext<T, R> & {
response: FetchResponse<T>;
}>>;
}
interface ResponseMap {
blob: Blob;
text: string;
arrayBuffer: ArrayBuffer;
stream: ReadableStream<Uint8Array>;
}
type ResponseType = keyof ResponseMap | "json";
type MappedResponseType<R extends ResponseType, JsonType = any> = R extends keyof ResponseMap ? ResponseMap[R] : JsonType;
interface FetchResponse<T> extends Response {
_data?: T;
}
interface IFetchError<T = any> extends Error {
request?: FetchRequest;
options?: FetchOptions;
response?: FetchResponse<T>;
data?: T;
status?: number;
statusText?: string;
statusCode?: number;
statusMessage?: string;
}
type Fetch = typeof globalThis.fetch;
type FetchRequest = RequestInfo;
interface SearchParameters {
[key: string]: any;
}
//#endregion
//#region src/fetch.d.ts
declare function createFetch(globalOptions?: CreateFetchOptions): $Fetch;
//#endregion
//#region src/error.d.ts
declare class FetchError<T = any> extends Error implements IFetchError<T> {
constructor(message: string, opts?: {
cause: unknown;
});
}
interface FetchError<T = any> extends IFetchError<T> {}
declare function createFetchError<T = any>(ctx: FetchContext<T>): IFetchError<T>;
//#endregion
//#region src/index.d.ts
declare const fetch: typeof globalThis.fetch;
declare const ofetch: $Fetch;
declare const $fetch: $Fetch;
//#endregion
export { $Fetch, $fetch, CreateFetchOptions, Fetch, FetchContext, FetchError, FetchHook, FetchHooks, FetchOptions, FetchRequest, FetchResponse, GlobalOptions, IFetchError, MappedResponseType, ResolvedFetchOptions, ResponseMap, ResponseType, SearchParameters, createFetch, createFetchError, fetch, ofetch };

View File

@@ -0,0 +1,278 @@
//#region src/utils.url.ts
/**
* Joins the given base URL and path, ensuring that there is only one slash between them.
*/
function joinURL(base, path) {
if (!base || base === "/") return path || "/";
if (!path || path === "/") return base || "/";
const baseHasTrailing = base[base.length - 1] === "/";
const pathHasLeading = path[0] === "/";
if (baseHasTrailing && pathHasLeading) return base + path.slice(1);
if (!baseHasTrailing && !pathHasLeading) return `${base}/${path}`;
return base + path;
}
/**
* Adds the base path to the input path, if it is not already present.
*/
function withBase(input = "", base = "") {
if (!base || base === "/") return input;
const _base = withoutTrailingSlash(base);
if (input.startsWith(_base)) return input;
return joinURL(_base, input);
}
function withoutTrailingSlash(path) {
if (!path || path === "/") return "/";
return path[path.length - 1] === "/" ? path.slice(0, -1) : path;
}
/**
* Returns the URL with the given query parameters. If a query parameter is undefined, it is omitted.
*/
function withQuery(input, query) {
if (!query || Object.keys(query).length === 0) return input;
const searchIndex = input.indexOf("?");
if (searchIndex === -1) {
const normalizedQuery = Object.entries(query).filter(([, value]) => value !== void 0).flatMap(([key, value]) => {
if (Array.isArray(value)) return value.map((item) => [key, normalizeQueryValue(item)]);
return [[key, normalizeQueryValue(value)]];
});
const queryString$1 = new URLSearchParams(normalizedQuery).toString();
return queryString$1 ? `${input}?${queryString$1}` : input;
}
const searchParams = new URLSearchParams(input.slice(searchIndex + 1));
const base = input.slice(0, searchIndex);
for (const [key, value] of Object.entries(query)) if (value === void 0) searchParams.delete(key);
else if (Array.isArray(value)) for (const item of value) searchParams.append(key, normalizeQueryValue(item));
else searchParams.set(key, normalizeQueryValue(value));
const queryString = searchParams.toString();
return queryString ? `${base}?${queryString}` : base;
}
function normalizeQueryValue(value) {
if (value === null) return "";
if (typeof value === "number" || typeof value === "boolean") return String(value);
if (typeof value === "object") return JSON.stringify(value);
return String(value);
}
//#endregion
//#region src/error.ts
var FetchError = class extends Error {
constructor(message, opts) {
super(message, opts);
this.name = "FetchError";
if (opts?.cause && !this.cause) this.cause = opts.cause;
}
};
function createFetchError(ctx) {
const errorMessage = ctx.error?.message || ctx.error?.toString() || "";
const method = ctx.request?.method || ctx.options?.method || "GET";
const url = ctx.request?.url || String(ctx.request) || "/";
const fetchError = new FetchError(`${`[${method}] ${JSON.stringify(url)}`}: ${ctx.response ? `${ctx.response.status} ${ctx.response.statusText}` : "<no response>"}${errorMessage ? ` ${errorMessage}` : ""}`, ctx.error ? { cause: ctx.error } : void 0);
for (const key of [
"request",
"options",
"response"
]) Object.defineProperty(fetchError, key, { get() {
return ctx[key];
} });
for (const [key, refKey] of [
["data", "_data"],
["status", "status"],
["statusCode", "status"],
["statusText", "statusText"],
["statusMessage", "statusText"]
]) Object.defineProperty(fetchError, key, { get() {
return ctx.response && ctx.response[refKey];
} });
return fetchError;
}
//#endregion
//#region src/utils.ts
const payloadMethods = new Set(Object.freeze([
"PATCH",
"POST",
"PUT",
"DELETE"
]));
function isPayloadMethod(method = "GET") {
return payloadMethods.has(method.toUpperCase());
}
function isJSONSerializable(value) {
if (value === void 0) return false;
const t = typeof value;
if (t === "string" || t === "number" || t === "boolean" || t === null) return true;
if (t !== "object") return false;
if (Array.isArray(value)) return true;
if (value.buffer) return false;
if (value instanceof FormData || value instanceof URLSearchParams) return false;
return value.constructor && value.constructor.name === "Object" || typeof value.toJSON === "function";
}
const textTypes = new Set([
"image/svg",
"application/xml",
"application/xhtml",
"application/html"
]);
const JSON_RE = /^application\/(?:[\w!#$%&*.^`~-]*\+)?json(;.+)?$/i;
function detectResponseType(_contentType = "") {
if (!_contentType) return "json";
const contentType = _contentType.split(";").shift() || "";
if (JSON_RE.test(contentType)) return "json";
if (contentType === "text/event-stream") return "stream";
if (textTypes.has(contentType) || contentType.startsWith("text/")) return "text";
return "blob";
}
function resolveFetchOptions(request, input, defaults, Headers$1) {
const headers = mergeHeaders(input?.headers ?? request?.headers, defaults?.headers, Headers$1);
let query;
if (defaults?.query || defaults?.params || input?.params || input?.query) query = {
...defaults?.params,
...defaults?.query,
...input?.params,
...input?.query
};
return {
...defaults,
...input,
query,
params: query,
headers
};
}
function mergeHeaders(input, defaults, Headers$1) {
if (!defaults) return new Headers$1(input);
const headers = new Headers$1(defaults);
if (input) for (const [key, value] of Symbol.iterator in input || Array.isArray(input) ? input : new Headers$1(input)) headers.set(key, value);
return headers;
}
async function callHooks(context, hooks) {
if (hooks) if (Array.isArray(hooks)) for (const hook of hooks) await hook(context);
else await hooks(context);
}
//#endregion
//#region src/fetch.ts
const retryStatusCodes = new Set([
408,
409,
425,
429,
500,
502,
503,
504
]);
const nullBodyResponses = new Set([
101,
204,
205,
304
]);
function createFetch(globalOptions = {}) {
const { fetch: fetch$1 = globalThis.fetch } = globalOptions;
async function onError(context) {
const isAbort = context.error && context.error.name === "AbortError" && !context.options.timeout || false;
if (context.options.retry !== false && !isAbort) {
let retries;
if (typeof context.options.retry === "number") retries = context.options.retry;
else retries = isPayloadMethod(context.options.method) ? 0 : 1;
const responseCode = context.response && context.response.status || 500;
if (retries > 0 && (Array.isArray(context.options.retryStatusCodes) ? context.options.retryStatusCodes.includes(responseCode) : retryStatusCodes.has(responseCode))) {
const retryDelay = typeof context.options.retryDelay === "function" ? context.options.retryDelay(context) : context.options.retryDelay || 0;
if (retryDelay > 0) await new Promise((resolve) => setTimeout(resolve, retryDelay));
return $fetchRaw(context.request, {
...context.options,
retry: retries - 1
});
}
}
const error = createFetchError(context);
if (Error.captureStackTrace) Error.captureStackTrace(error, $fetchRaw);
throw error;
}
const $fetchRaw = async function $fetchRaw$1(_request, _options = {}) {
const context = {
request: _request,
options: resolveFetchOptions(_request, _options, globalOptions.defaults, Headers),
response: void 0,
error: void 0
};
if (context.options.method) context.options.method = context.options.method.toUpperCase();
if (context.options.onRequest) await callHooks(context, context.options.onRequest);
if (typeof context.request === "string") {
if (context.options.baseURL) context.request = withBase(context.request, context.options.baseURL);
if (context.options.query) {
context.request = withQuery(context.request, context.options.query);
delete context.options.query;
}
if ("query" in context.options) delete context.options.query;
if ("params" in context.options) delete context.options.params;
}
if (context.options.body && isPayloadMethod(context.options.method)) {
if (isJSONSerializable(context.options.body)) {
const contentType = context.options.headers.get("content-type");
if (typeof context.options.body !== "string") context.options.body = contentType === "application/x-www-form-urlencoded" ? new URLSearchParams(context.options.body).toString() : JSON.stringify(context.options.body);
context.options.headers = new Headers(context.options.headers || {});
if (!contentType) context.options.headers.set("content-type", "application/json");
if (!context.options.headers.has("accept")) context.options.headers.set("accept", "application/json");
} else if ("pipeTo" in context.options.body && typeof context.options.body.pipeTo === "function" || typeof context.options.body.pipe === "function") {
if (!("duplex" in context.options)) context.options.duplex = "half";
}
}
if (context.options.timeout) context.options.signal = context.options.signal ? AbortSignal.any([AbortSignal.timeout(context.options.timeout), context.options.signal]) : AbortSignal.timeout(context.options.timeout);
try {
context.response = await fetch$1(context.request, context.options);
} catch (error) {
context.error = error;
if (context.options.onRequestError) await callHooks(context, context.options.onRequestError);
return await onError(context);
}
if ((context.response.body || context.response._bodyInit) && !nullBodyResponses.has(context.response.status) && context.options.method !== "HEAD") {
const responseType = (context.options.parseResponse ? "json" : context.options.responseType) || detectResponseType(context.response.headers.get("content-type") || "");
switch (responseType) {
case "json": {
const data = await context.response.text();
if (data) {
const parseFunction = context.options.parseResponse || JSON.parse;
context.response._data = parseFunction(data);
}
break;
}
case "stream":
context.response._data = context.response.body || context.response._bodyInit;
break;
default: context.response._data = await context.response[responseType]();
}
}
if (context.options.onResponse) await callHooks(context, context.options.onResponse);
if (!context.options.ignoreResponseError && context.response.status >= 400 && context.response.status < 600) {
if (context.options.onResponseError) await callHooks(context, context.options.onResponseError);
return await onError(context);
}
return context.response;
};
const $fetch$1 = async function $fetch$2(request, options) {
return (await $fetchRaw(request, options))._data;
};
$fetch$1.raw = $fetchRaw;
$fetch$1.native = (...args) => fetch$1(...args);
$fetch$1.create = (defaultOptions = {}, customGlobalOptions = {}) => createFetch({
...globalOptions,
...customGlobalOptions,
defaults: {
...globalOptions.defaults,
...customGlobalOptions.defaults,
...defaultOptions
}
});
return $fetch$1;
}
//#endregion
//#region src/index.ts
const fetch = globalThis.fetch ? (...args) => globalThis.fetch(...args) : () => Promise.reject(/* @__PURE__ */ new Error("[ofetch] global.fetch is not supported!"));
const ofetch = createFetch({ fetch });
const $fetch = ofetch;
//#endregion
export { $fetch, FetchError, createFetch, createFetchError, fetch, ofetch };

View File

@@ -0,0 +1,39 @@
{
"name": "ofetch",
"version": "2.0.0-alpha.3",
"description": "A better fetch API. Works on node, browser and workers.",
"repository": "unjs/ofetch",
"license": "MIT",
"sideEffects": false,
"type": "module",
"exports": "./dist/index.mjs",
"main": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"files": [
"dist"
],
"scripts": {
"build": "obuild src/index.ts",
"dev": "vitest",
"lint": "eslint . && prettier -c src test examples",
"lint:fix": "eslint --fix . && prettier -w src test examples",
"prepack": "pnpm build",
"release": "pnpm test && pnpm build && changelogen --release --prerelease --publish --publishTag alpha --push",
"test": "pnpm lint && vitest run --coverage"
},
"devDependencies": {
"@types/node": "^24.9.1",
"@vitest/coverage-v8": "^4.0.4",
"automd": "^0.4.2",
"changelogen": "^0.6.2",
"eslint": "^9.38.0",
"eslint-config-unjs": "^0.5.0",
"h3": "^2.0.1-rc.5",
"obuild": "^0.3.0",
"prettier": "^3.6.2",
"typescript": "^5.9.3",
"undici": "^7.16.0",
"vitest": "^4.0.4"
},
"packageManager": "pnpm@10.19.0"
}

77
node_modules/@nuxt/telemetry/package.json generated vendored Normal file
View File

@@ -0,0 +1,77 @@
{
"name": "@nuxt/telemetry",
"version": "2.7.0",
"repository": "nuxt/telemetry",
"license": "MIT",
"type": "module",
"exports": {
".": {
"types": "./dist/types.d.mts",
"import": "./dist/module.mjs",
"require": "./dist/module.cjs"
}
},
"main": "./dist/module.cjs",
"types": "./dist/types.d.mts",
"typesVersions": {
"*": {
".": [
"./dist/types.d.cts"
]
}
},
"bin": {
"nuxt-telemetry": "./bin/nuxt-telemetry.mjs"
},
"files": [
"dist",
"bin"
],
"dependencies": {
"citty": "^0.2.0",
"consola": "^3.4.2",
"ofetch": "^2.0.0-alpha.3",
"rc9": "^3.0.0",
"std-env": "^3.10.0"
},
"devDependencies": {
"@nuxt/eslint-config": "^1.14.0",
"@nuxt/kit": "^4.3.0",
"@nuxt/module-builder": "^1.0.2",
"@nuxt/schema": "^4.3.0",
"@nuxt/test-utils": "^4.0.0",
"@types/semver": "^7.7.1",
"@vitest/coverage-v8": "^4.0.18",
"changelogen": "^0.6.2",
"eslint": "^10.0.0",
"get-port-please": "^3.2.0",
"h3": "^1.15.5",
"installed-check": "^9.3.0",
"knip": "^5.83.1",
"nuxt": "^4.3.0",
"semver": "^7.7.4",
"typescript": "^5.9.3",
"unbuild": "^3.6.1",
"vitest": "^4.0.18",
"vue-tsc": "^3.2.4"
},
"peerDependencies": {
"@nuxt/kit": ">=3.0.0"
},
"engines": {
"node": ">=18.12.0"
},
"scripts": {
"build": "nuxt-module-build build",
"dev": "NUXT_TELEMETRY_DEBUG=1 nuxt dev playground",
"dev:build": "NUXT_TELEMETRY_DEBUG=1 nuxt build playground",
"dev:generate": "NUXT_TELEMETRY_DEBUG=1 nuxt generate playground",
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
"lint": "eslint .",
"nuxt-telemetry": "node ./bin/nuxt-telemetry.mjs",
"test": "vitest run",
"test:engines": "installed-check -d --no-workspaces",
"test:knip": "knip",
"test:types": "nuxt-module-build prepare && nuxt prepare playground && vue-tsc --noEmit"
}
}