mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-18 01:15:58 +00:00
feat(kit): support config extends
using unjs/c12
(#3008)
This commit is contained in:
parent
083f90b719
commit
1672148a87
16
examples/config-extends/app.vue
Normal file
16
examples/config-extends/app.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script setup>
|
||||||
|
const themeConfig = useRuntimeConfig().theme
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NuxtExampleLayout example="config-extends">
|
||||||
|
theme runtimeConfig
|
||||||
|
<pre>{{ JSON.stringify(themeConfig, null, 2) }}</pre>
|
||||||
|
</NuxtExampleLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
pre {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
10
examples/config-extends/base/nuxt.config.ts
Normal file
10
examples/config-extends/base/nuxt.config.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { defineNuxtConfig } from 'nuxt3'
|
||||||
|
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
publicRuntimeConfig: {
|
||||||
|
theme: {
|
||||||
|
primaryColor: 'base_primary',
|
||||||
|
secondaryColor: 'base_secondary'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
13
examples/config-extends/nuxt.config.ts
Normal file
13
examples/config-extends/nuxt.config.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { defineNuxtConfig } from 'nuxt3'
|
||||||
|
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
extends: './base',
|
||||||
|
publicRuntimeConfig: {
|
||||||
|
theme: {
|
||||||
|
primaryColor: 'user_primary'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modules: [
|
||||||
|
'@nuxt/ui'
|
||||||
|
]
|
||||||
|
})
|
13
examples/config-extends/package.json
Normal file
13
examples/config-extends/package.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "example-config-extends",
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@nuxt/ui": "npm:@nuxt/ui-edge@latest",
|
||||||
|
"nuxt3": "latest"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "nuxi dev",
|
||||||
|
"build": "nuxi build",
|
||||||
|
"start": "nuxi preview"
|
||||||
|
}
|
||||||
|
}
|
3
examples/config-extends/tsconfig.json
Normal file
3
examples/config-extends/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
|
}
|
@ -14,9 +14,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/schema": "^3.0.0",
|
"@nuxt/schema": "^3.0.0",
|
||||||
|
"c12": "^0.1.1",
|
||||||
"consola": "^2.15.3",
|
"consola": "^2.15.3",
|
||||||
"defu": "^5.0.1",
|
"defu": "^5.0.1",
|
||||||
"dotenv": "^15.0.0",
|
|
||||||
"globby": "^13.1.0",
|
"globby": "^13.1.0",
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"jiti": "^1.12.15",
|
"jiti": "^1.12.15",
|
||||||
@ -24,7 +24,6 @@
|
|||||||
"mlly": "^0.4.1",
|
"mlly": "^0.4.1",
|
||||||
"pathe": "^0.2.0",
|
"pathe": "^0.2.0",
|
||||||
"pkg-types": "^0.3.2",
|
"pkg-types": "^0.3.2",
|
||||||
"rc9": "^1.2.0",
|
|
||||||
"scule": "^0.2.1",
|
"scule": "^0.2.1",
|
||||||
"semver": "^7.3.5",
|
"semver": "^7.3.5",
|
||||||
"unctx": "^1.0.2",
|
"unctx": "^1.0.2",
|
||||||
|
@ -1,128 +0,0 @@
|
|||||||
import { promises as fsp, existsSync } from 'fs'
|
|
||||||
import { resolve } from 'pathe'
|
|
||||||
import dotenv from 'dotenv'
|
|
||||||
|
|
||||||
export interface DotenvOptions {
|
|
||||||
/** The project root directory (either absolute or relative to the current working directory). */
|
|
||||||
rootDir: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* What file to look in for environment variables (either absolute or relative
|
|
||||||
* to the current working directory). For example, `.env`.
|
|
||||||
*/
|
|
||||||
fileName: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to interpolate variables within .env.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```env
|
|
||||||
* BASE_DIR="/test"
|
|
||||||
* # resolves to "/test/further"
|
|
||||||
* ANOTHER_DIR="${BASE_DIR}/further"
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
expand: boolean
|
|
||||||
|
|
||||||
/** An object describing environment variables (key, value pairs). */
|
|
||||||
env: NodeJS.ProcessEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Env = typeof process.env
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load and interpolate environment variables into `process.env`.
|
|
||||||
* If you need more control (or access to the values), consider using `loadDotenv` instead
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export async function setupDotenv (options: DotenvOptions): Promise<Env> {
|
|
||||||
const targetEnv = options.env ?? process.env
|
|
||||||
|
|
||||||
// Load env
|
|
||||||
const env = await loadDotenv({
|
|
||||||
rootDir: options.rootDir,
|
|
||||||
fileName: options.fileName ?? '.env',
|
|
||||||
env: targetEnv,
|
|
||||||
expand: options.expand ?? true
|
|
||||||
})
|
|
||||||
|
|
||||||
// Fill process.env
|
|
||||||
for (const key in env) {
|
|
||||||
if (!key.startsWith('_') && targetEnv[key] === undefined) {
|
|
||||||
targetEnv[key] = env[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Load environment variables into an object. */
|
|
||||||
export async function loadDotenv (opts: DotenvOptions): Promise<Env> {
|
|
||||||
const env = Object.create(null)
|
|
||||||
|
|
||||||
const dotenvFile = resolve(opts.rootDir, opts.fileName)
|
|
||||||
|
|
||||||
if (existsSync(dotenvFile)) {
|
|
||||||
const parsed = dotenv.parse(await fsp.readFile(dotenvFile, 'utf-8'))
|
|
||||||
Object.assign(env, parsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply process.env
|
|
||||||
if (!opts.env._applied) {
|
|
||||||
Object.assign(env, opts.env)
|
|
||||||
env._applied = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interpolate env
|
|
||||||
if (opts.expand) {
|
|
||||||
expand(env)
|
|
||||||
}
|
|
||||||
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
||||||
// Based on https://github.com/motdotla/dotenv-expand
|
|
||||||
function expand (target: Record<string, any>, source: Record<string, any> = {}, parse = (v: any) => v) {
|
|
||||||
function getValue (key: string) {
|
|
||||||
// Source value 'wins' over target value
|
|
||||||
return source[key] !== undefined ? source[key] : target[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
function interpolate (value: unknown, parents: string[] = []) {
|
|
||||||
if (typeof value !== 'string') {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
const matches = value.match(/(.?\${?(?:[a-zA-Z0-9_:]+)?}?)/g) || []
|
|
||||||
return parse(matches.reduce((newValue, match) => {
|
|
||||||
const parts = /(.?)\${?([a-zA-Z0-9_:]+)?}?/g.exec(match)
|
|
||||||
const prefix = parts[1]
|
|
||||||
|
|
||||||
let value, replacePart: string
|
|
||||||
|
|
||||||
if (prefix === '\\') {
|
|
||||||
replacePart = parts[0]
|
|
||||||
value = replacePart.replace('\\$', '$')
|
|
||||||
} else {
|
|
||||||
const key = parts[2]
|
|
||||||
replacePart = parts[0].substring(prefix.length)
|
|
||||||
|
|
||||||
// Avoid recursion
|
|
||||||
if (parents.includes(key)) {
|
|
||||||
console.warn(`Please avoid recursive environment variables ( loop: ${parents.join(' > ')} > ${key} )`)
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
value = getValue(key)
|
|
||||||
|
|
||||||
// Resolve recursive interpolations
|
|
||||||
value = interpolate(value, [...parents, key])
|
|
||||||
}
|
|
||||||
|
|
||||||
return value !== undefined ? newValue.replace(replacePart, value) : newValue
|
|
||||||
}, value))
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in target) {
|
|
||||||
target[key] = interpolate(getValue(key))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,10 @@
|
|||||||
import { existsSync } from 'fs'
|
|
||||||
import { resolve } from 'pathe'
|
import { resolve } from 'pathe'
|
||||||
import defu from 'defu'
|
|
||||||
import { applyDefaults } from 'untyped'
|
import { applyDefaults } from 'untyped'
|
||||||
import * as rc from 'rc9'
|
import { loadConfig, DotenvOptions } from 'c12'
|
||||||
import type { NuxtOptions } from '@nuxt/schema'
|
import type { NuxtOptions } from '@nuxt/schema'
|
||||||
import { NuxtConfigSchema } from '@nuxt/schema'
|
import { NuxtConfigSchema } from '@nuxt/schema'
|
||||||
import { tryResolveModule, requireModule, scanRequireTree } from '../internal/cjs'
|
// TODO
|
||||||
import { setupDotenv, DotenvOptions } from '../internal/dotenv'
|
// import { tryResolveModule, requireModule, scanRequireTree } from '../internal/cjs'
|
||||||
|
|
||||||
export interface LoadNuxtConfigOptions {
|
export interface LoadNuxtConfigOptions {
|
||||||
/** Your project root directory (either absolute or relative to the current working directory). */
|
/** Your project root directory (either absolute or relative to the current working directory). */
|
||||||
@ -25,39 +23,22 @@ export interface LoadNuxtConfigOptions {
|
|||||||
export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> {
|
export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> {
|
||||||
const rootDir = resolve(process.cwd(), opts.rootDir || '.')
|
const rootDir = resolve(process.cwd(), opts.rootDir || '.')
|
||||||
|
|
||||||
if (opts.dotenv !== false) {
|
const { config: nuxtConfig, configFile, layers } = await loadConfig({
|
||||||
await setupDotenv({ rootDir, ...opts.dotenv })
|
cwd: rootDir,
|
||||||
}
|
name: 'nuxt',
|
||||||
|
configFile: 'nuxt.config',
|
||||||
|
rcFile: '.nuxtrc',
|
||||||
|
dotenv: opts.dotenv,
|
||||||
|
globalRc: true,
|
||||||
|
overrides: opts.config
|
||||||
|
})
|
||||||
|
|
||||||
const nuxtConfigFile = tryResolveModule(resolve(rootDir, opts.configFile || 'nuxt.config'))
|
nuxtConfig.rootDir = nuxtConfig.rootDir || rootDir
|
||||||
|
|
||||||
let nuxtConfig: any = {}
|
nuxtConfig._nuxtConfigFile = configFile
|
||||||
|
nuxtConfig._nuxtConfigFiles = [configFile]
|
||||||
|
|
||||||
if (nuxtConfigFile && existsSync(nuxtConfigFile)) {
|
nuxtConfig.layers = layers
|
||||||
nuxtConfig = requireModule(nuxtConfigFile, { clearCache: true })
|
|
||||||
|
|
||||||
if (typeof nuxtConfig === 'function') {
|
|
||||||
nuxtConfig = await nuxtConfig(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
nuxtConfig = { ...nuxtConfig }
|
|
||||||
nuxtConfig._nuxtConfigFile = nuxtConfigFile
|
|
||||||
nuxtConfig._nuxtConfigFiles = Array.from(scanRequireTree(nuxtConfigFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combine configs
|
|
||||||
// Priority: configOverrides > nuxtConfig > .nuxtrc > .nuxtrc (global)
|
|
||||||
nuxtConfig = defu(
|
|
||||||
opts.config,
|
|
||||||
nuxtConfig,
|
|
||||||
rc.read({ name: '.nuxtrc', dir: rootDir }),
|
|
||||||
rc.readUser('.nuxtrc')
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set rootDir
|
|
||||||
if (!nuxtConfig.rootDir) {
|
|
||||||
nuxtConfig.rootDir = rootDir
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve and apply defaults
|
// Resolve and apply defaults
|
||||||
return applyDefaults(NuxtConfigSchema, nuxtConfig) as NuxtOptions
|
return applyDefaults(NuxtConfigSchema, nuxtConfig) as NuxtOptions
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"unbuild": "latest"
|
"unbuild": "latest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"c12": "^0.1.1",
|
||||||
"create-require": "^1.1.1",
|
"create-require": "^1.1.1",
|
||||||
"defu": "^5.0.1",
|
"defu": "^5.0.1",
|
||||||
"jiti": "^1.12.15",
|
"jiti": "^1.12.15",
|
||||||
|
@ -6,6 +6,19 @@ import jiti from 'jiti'
|
|||||||
import defu from 'defu'
|
import defu from 'defu'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
/**
|
||||||
|
* Extend nested configurations from multiple local or remoted sources
|
||||||
|
*
|
||||||
|
* Value should be either a string or array of strings pointing to source directories or config path relative to current config.
|
||||||
|
*
|
||||||
|
* You can use `github:`, `gitlab:`, `bitbucket:` or `https://` to extend from a remote git repository.
|
||||||
|
*
|
||||||
|
* @typedef {string|string[]}
|
||||||
|
*
|
||||||
|
* @version 3
|
||||||
|
*/
|
||||||
|
extends: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define the workspace directory of your application.
|
* Define the workspace directory of your application.
|
||||||
*
|
*
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { ConfigSchema } from '../../schema/config'
|
import { ConfigSchema } from '../../schema/config'
|
||||||
|
import type { ResolvedConfig } from 'c12'
|
||||||
|
|
||||||
/** Normalized Nuxt options available as `nuxt.options.*` */
|
/** Normalized Nuxt options available as `nuxt.options.*` */
|
||||||
export interface NuxtOptions extends ConfigSchema { }
|
export interface NuxtOptions extends ConfigSchema {
|
||||||
|
layers: ResolvedConfig<NuxtOptions>[]
|
||||||
|
}
|
||||||
|
|
||||||
type DeepPartial<T> = T extends Record<string, any> ? { [P in keyof T]?: DeepPartial<T[P]> | T[P] } : T
|
type DeepPartial<T> = T extends Record<string, any> ? { [P in keyof T]?: DeepPartial<T[P]> | T[P] } : T
|
||||||
|
|
||||||
|
49
yarn.lock
49
yarn.lock
@ -2859,9 +2859,9 @@ __metadata:
|
|||||||
"@nuxt/schema": ^3.0.0
|
"@nuxt/schema": ^3.0.0
|
||||||
"@types/lodash.template": ^4
|
"@types/lodash.template": ^4
|
||||||
"@types/semver": ^7
|
"@types/semver": ^7
|
||||||
|
c12: ^0.1.1
|
||||||
consola: ^2.15.3
|
consola: ^2.15.3
|
||||||
defu: ^5.0.1
|
defu: ^5.0.1
|
||||||
dotenv: ^15.0.0
|
|
||||||
globby: ^13.1.0
|
globby: ^13.1.0
|
||||||
hash-sum: ^2.0.0
|
hash-sum: ^2.0.0
|
||||||
jiti: ^1.12.15
|
jiti: ^1.12.15
|
||||||
@ -2869,7 +2869,6 @@ __metadata:
|
|||||||
mlly: ^0.4.1
|
mlly: ^0.4.1
|
||||||
pathe: ^0.2.0
|
pathe: ^0.2.0
|
||||||
pkg-types: ^0.3.2
|
pkg-types: ^0.3.2
|
||||||
rc9: ^1.2.0
|
|
||||||
scule: ^0.2.1
|
scule: ^0.2.1
|
||||||
semver: ^7.3.5
|
semver: ^7.3.5
|
||||||
unbuild: latest
|
unbuild: latest
|
||||||
@ -3021,6 +3020,7 @@ __metadata:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/lodash.template": ^4
|
"@types/lodash.template": ^4
|
||||||
"@types/semver": ^7
|
"@types/semver": ^7
|
||||||
|
c12: ^0.1.1
|
||||||
create-require: ^1.1.1
|
create-require: ^1.1.1
|
||||||
defu: ^5.0.1
|
defu: ^5.0.1
|
||||||
jiti: ^1.12.15
|
jiti: ^1.12.15
|
||||||
@ -6744,6 +6744,21 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"c12@npm:^0.1.1":
|
||||||
|
version: 0.1.1
|
||||||
|
resolution: "c12@npm:0.1.1"
|
||||||
|
dependencies:
|
||||||
|
defu: ^5.0.1
|
||||||
|
dotenv: ^14.3.2
|
||||||
|
gittar: ^0.1.1
|
||||||
|
jiti: ^1.12.14
|
||||||
|
mlly: ^0.4.1
|
||||||
|
pathe: ^0.2.0
|
||||||
|
rc9: ^1.2.0
|
||||||
|
checksum: 628bd42845926249f9dfbaf14371793de72c4b9fa764e8a635ae56f5e67c99d765f8ea09ac44e3876e334095a627ca24d7029ca5d6254829998b9fab67927460
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"cacache@npm:^12.0.2":
|
"cacache@npm:^12.0.2":
|
||||||
version: 12.0.4
|
version: 12.0.4
|
||||||
resolution: "cacache@npm:12.0.4"
|
resolution: "cacache@npm:12.0.4"
|
||||||
@ -8908,20 +8923,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"dotenv@npm:^14.3.0":
|
"dotenv@npm:^14.3.0, dotenv@npm:^14.3.2":
|
||||||
version: 14.3.2
|
version: 14.3.2
|
||||||
resolution: "dotenv@npm:14.3.2"
|
resolution: "dotenv@npm:14.3.2"
|
||||||
checksum: 86c06758915d6facc35275f4a7fafc16705b6f3b44befaa8abca91367991efc8ff8db5437d3cc14778231d19fb97610fe82d60f8a53ba723cdb69fe4171439aa
|
checksum: 86c06758915d6facc35275f4a7fafc16705b6f3b44befaa8abca91367991efc8ff8db5437d3cc14778231d19fb97610fe82d60f8a53ba723cdb69fe4171439aa
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"dotenv@npm:^15.0.0":
|
|
||||||
version: 15.0.0
|
|
||||||
resolution: "dotenv@npm:15.0.0"
|
|
||||||
checksum: be5d852b2ad1708780e7038481c0687ce4bb9edf354d439872511e966a5f28c083f296e1d1c182256d695bfdd67e320382eb3b0f2e9d83efa3615cd1757257ab
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"dotenv@npm:^8.2.0":
|
"dotenv@npm:^8.2.0":
|
||||||
version: 8.6.0
|
version: 8.6.0
|
||||||
resolution: "dotenv@npm:8.6.0"
|
resolution: "dotenv@npm:8.6.0"
|
||||||
@ -10081,6 +10089,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"example-config-extends@workspace:examples/config-extends":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "example-config-extends@workspace:examples/config-extends"
|
||||||
|
dependencies:
|
||||||
|
"@nuxt/ui": "npm:@nuxt/ui-edge@latest"
|
||||||
|
nuxt3: latest
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"example-hello-world@workspace:examples/hello-world":
|
"example-hello-world@workspace:examples/hello-world":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "example-hello-world@workspace:examples/hello-world"
|
resolution: "example-hello-world@workspace:examples/hello-world"
|
||||||
@ -11116,6 +11133,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"gittar@npm:^0.1.1":
|
||||||
|
version: 0.1.1
|
||||||
|
resolution: "gittar@npm:0.1.1"
|
||||||
|
dependencies:
|
||||||
|
mkdirp: ^0.5.1
|
||||||
|
tar: ^4.4.1
|
||||||
|
checksum: fcc6127a9ce36116f1d2e429b896ab133c27ddd84817a48ccb88b3039caca33e1f8566fe00d14c6e7c16a0aa65cf543c94588427e5b72c52c1cd4d8d87414744
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"glob-parent@npm:^3.1.0":
|
"glob-parent@npm:^3.1.0":
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
resolution: "glob-parent@npm:3.1.0"
|
resolution: "glob-parent@npm:3.1.0"
|
||||||
@ -19608,7 +19635,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tar@npm:^4, tar@npm:^4.4.12":
|
"tar@npm:^4, tar@npm:^4.4.1, tar@npm:^4.4.12":
|
||||||
version: 4.4.19
|
version: 4.4.19
|
||||||
resolution: "tar@npm:4.4.19"
|
resolution: "tar@npm:4.4.19"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
Loading…
Reference in New Issue
Block a user