config-loader: switch to using --config options to load in config

This commit is contained in:
Patrik Oldsberg
2020-10-20 13:08:05 +02:00
parent ad221957cd
commit 8c2b76e45f
27 changed files with 200 additions and 302 deletions
+21
View File
@@ -0,0 +1,21 @@
---
'@backstage/backend-common': minor
'@backstage/cli': minor
'@backstage/config-loader': minor
'example-backend': patch
'@backstage/create-app': patch
---
**BREAKING CHANGE**
The existing loading of additional config files like `app-config.development.yaml` using APP_ENV or NODE_ENV has been removed.
Instead, the CLI and backend process now accept one or more `--config` flags to load config files.
Without passing any flags, `app-config.yaml` and, if it exists, `app-config.local.yaml` will be loaded.
If passing any `--config <path>` flags, only those files will be loaded, **NOT** the default `app-config.yaml` one.
The old behaviour of for example `APP_ENV=development` can be replicated using the following flags:
```bash
--config ../../app-config.yaml --config ../../app-config.development.yaml
```
+2
View File
@@ -44,6 +44,7 @@
"knex": "^0.21.1",
"lodash": "^4.17.15",
"logform": "^2.1.1",
"minimist": "^1.2.5",
"morgan": "^1.10.0",
"node-fetch": "^2.6.0",
"prom-client": "^12.0.0",
@@ -63,6 +64,7 @@
"@backstage/cli": "^0.1.1-alpha.25",
"@types/compression": "^1.7.0",
"@types/http-errors": "^1.6.3",
"@types/minimist": "^1.2.0",
"@types/morgan": "^1.9.0",
"@types/node-fetch": "^2.5.7",
"@types/stoppable": "^1.1.0",
+10 -2
View File
@@ -14,24 +14,32 @@
* limitations under the License.
*/
import { resolve as resolvePath } from 'path';
import parseArgs from 'minimist';
import { Logger } from 'winston';
import { findPaths } from '@backstage/cli-common';
import { Config, ConfigReader } from '@backstage/config';
import { loadConfig } from '@backstage/config-loader';
import { Logger } from 'winston';
type Options = {
logger: Logger;
// process.argv or any other overrides
argv: string[];
};
/**
* Load configuration for a Backend
*/
export async function loadBackendConfig(options: Options): Promise<Config> {
const args = parseArgs(options.argv);
const configOpts: string[] = args.config ?? [];
/* eslint-disable-next-line no-restricted-syntax */
const paths = findPaths(__dirname);
const configs = await loadConfig({
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development',
rootPaths: [paths.targetRoot, paths.targetDir],
configRoot: paths.targetRoot,
configPaths: configOpts.map(opt => resolvePath(opt)),
shouldReadSecrets: true,
});
+4 -1
View File
@@ -64,7 +64,10 @@ function makeCreateEnv(config: Config) {
}
async function main() {
const config = await loadBackendConfig({ logger: getRootLogger() });
const config = await loadBackendConfig({
argv: process.argv,
logger: getRootLogger(),
});
const createEnv = makeCreateEnv(config);
const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck'));
+2 -14
View File
@@ -15,27 +15,15 @@
*/
import { Command } from 'commander';
import { loadConfig } from '@backstage/config-loader';
import { ConfigReader } from '@backstage/config';
import { paths } from '../../lib/paths';
import { buildBundle } from '../../lib/bundler';
import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel';
import { loadCliConfig } from '../../lib/config';
export default async (cmd: Command) => {
const appConfigs = await loadConfig({
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'production',
rootPaths: [paths.targetRoot, paths.targetDir],
});
console.log(
`Loaded config from ${appConfigs.map(c => c.context).join(', ')}`,
);
await buildBundle({
entry: 'src/index',
parallel: parseParallel(process.env[PARALLEL_ENV_VAR]),
statsJsonEnabled: cmd.stats,
config: ConfigReader.fromConfigs(appConfigs),
appConfigs,
...(await loadCliConfig(cmd.config)),
});
};
+2 -14
View File
@@ -15,26 +15,14 @@
*/
import { Command } from 'commander';
import { loadConfig } from '@backstage/config-loader';
import { ConfigReader } from '@backstage/config';
import { paths } from '../../lib/paths';
import { serveBundle } from '../../lib/bundler';
import { loadCliConfig } from '../../lib/config';
export default async (cmd: Command) => {
const appConfigs = await loadConfig({
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development',
rootPaths: [paths.targetRoot, paths.targetDir],
});
console.log(
`Loaded config from ${appConfigs.map(c => c.context).join(', ')}`,
);
const waitForExit = await serveBundle({
entry: 'src/index',
checksEnabled: cmd.check,
config: ConfigReader.fromConfigs(appConfigs),
appConfigs,
...(await loadCliConfig(cmd.config)),
});
await waitForExit();
-14
View File
@@ -14,28 +14,14 @@
* limitations under the License.
*/
import { ConfigReader } from '@backstage/config';
import { loadConfig } from '@backstage/config-loader';
import { Command } from 'commander';
import { paths } from '../../lib/paths';
import { serveBackend } from '../../lib/bundler/backend';
export default async (cmd: Command) => {
const appConfigs = await loadConfig({
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development',
rootPaths: [paths.targetRoot, paths.targetDir],
});
console.log(
`Loaded config from ${appConfigs.map(c => c.context).join(', ')}`,
);
const waitForExit = await serveBackend({
entry: 'src/index',
checksEnabled: cmd.check,
inspectEnabled: cmd.inspect,
config: ConfigReader.fromConfigs(appConfigs),
appConfigs,
});
await waitForExit();
+3 -14
View File
@@ -15,24 +15,13 @@
*/
import { Command } from 'commander';
import { loadConfig } from '@backstage/config-loader';
import { ConfigReader } from '@backstage/config';
import { paths } from '../../lib/paths';
import { stringify as stringifyYaml } from 'yaml';
import { loadCliConfig } from '../../lib/config';
export default async (cmd: Command) => {
const appConfigs = await loadConfig({
env:
cmd.env ?? process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development',
shouldReadSecrets: cmd.withSecrets ?? false,
rootPaths: [paths.targetRoot, paths.targetDir],
});
const { config } = await loadCliConfig(cmd.config, cmd.withSecrets ?? false);
console.log(
`Loaded config from ${appConfigs.map(c => c.context).join(', ')}`,
);
const flatConfig = ConfigReader.fromConfigs(appConfigs).get();
const flatConfig = config.get();
if (cmd.format === 'json') {
process.stdout.write(`${JSON.stringify(flatConfig, null, 2)}\n`);
+14 -4
View File
@@ -18,16 +18,25 @@ import { CommanderStatic } from 'commander';
import { exitWithError } from '../lib/errors';
export function registerCommands(program: CommanderStatic) {
const configOption = [
'--config <path>',
'Config files to load instead of app-config.yaml',
(opt: string, opts: string[]) => [...opts, opt],
Array<string>(),
] as const;
program
.command('app:build')
.description('Build an app for a production release')
.option('--stats', 'Write bundle stats to output directory')
.option(...configOption)
.action(lazy(() => import('./app/build').then(m => m.default)));
program
.command('app:serve')
.description('Serve an app for local development')
.option('--check', 'Enable type checking and linting')
.option(...configOption)
.action(lazy(() => import('./app/serve').then(m => m.default)));
program
@@ -50,6 +59,8 @@ export function registerCommands(program: CommanderStatic) {
.description('Start local development server with HMR for the backend')
.option('--check', 'Enable type checking and linting')
.option('--inspect', 'Enable debugger')
// We don't actually use the config in the CLI, just pass them on to the NodeJS process
.option(...configOption)
.action(lazy(() => import('./backend/dev').then(m => m.default)));
program
@@ -89,12 +100,14 @@ export function registerCommands(program: CommanderStatic) {
.command('plugin:serve')
.description('Serves the dev/ folder of a plugin')
.option('--check', 'Enable type checking and linting')
.option(...configOption)
.action(lazy(() => import('./plugin/serve').then(m => m.default)));
program
.command('plugin:export')
.description('Exports the dev/ folder of a plugin')
.option('--stats', 'Write bundle stats to output directory')
.option(...configOption)
.action(lazy(() => import('./plugin/export').then(m => m.default)));
program
@@ -131,14 +144,11 @@ export function registerCommands(program: CommanderStatic) {
program
.command('config:print')
.option('--with-secrets', 'Include secrets in the printed configuration')
.option(
'--env <env>',
'The environment to print configuration for [APP_ENV or NODE_ENV or development]',
)
.option(
'--format <format>',
'Format to print the configuration in, either json or yaml [yaml]',
)
.option(...configOption)
.description('Print the app configuration for the current package')
.action(lazy(() => import('./config/print').then(m => m.default)));
+2 -14
View File
@@ -15,25 +15,13 @@
*/
import { Command } from 'commander';
import { loadConfig } from '@backstage/config-loader';
import { ConfigReader } from '@backstage/config';
import { paths } from '../../lib/paths';
import { buildBundle } from '../../lib/bundler';
import { loadCliConfig } from '../../lib/config';
export default async (cmd: Command) => {
const appConfigs = await loadConfig({
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'production',
rootPaths: [paths.targetRoot, paths.targetDir],
});
console.log(
`Loaded config from ${appConfigs.map(c => c.context).join(', ')}`,
);
await buildBundle({
entry: 'dev/index',
statsJsonEnabled: cmd.stats,
config: ConfigReader.fromConfigs(appConfigs),
appConfigs,
...(await loadCliConfig(cmd.config)),
});
};
+2 -14
View File
@@ -15,26 +15,14 @@
*/
import { Command } from 'commander';
import { loadConfig } from '@backstage/config-loader';
import { ConfigReader } from '@backstage/config';
import { paths } from '../../lib/paths';
import { serveBundle } from '../../lib/bundler';
import { loadCliConfig } from '../../lib/config';
export default async (cmd: Command) => {
const appConfigs = await loadConfig({
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development',
rootPaths: [paths.targetRoot, paths.targetDir],
});
console.log(
`Loaded config from ${appConfigs.map(c => c.context).join(', ')}`,
);
const waitForExit = await serveBundle({
entry: 'dev/index',
checksEnabled: cmd.check,
config: ConfigReader.fromConfigs(appConfigs),
appConfigs,
...(await loadCliConfig(cmd.config)),
});
await waitForExit();
+2 -6
View File
@@ -17,13 +17,9 @@
import webpack from 'webpack';
import { createBackendConfig } from './config';
import { resolveBundlingPaths } from './paths';
import { ServeOptions } from './types';
import { BackendServeOptions } from './types';
export async function serveBackend(
options: ServeOptions & {
inspectEnabled: boolean;
},
) {
export async function serveBackend(options: BackendServeOptions) {
const paths = resolveBundlingPaths(options);
const config = await createBackendConfig(paths, {
...options,
+12 -4
View File
@@ -27,10 +27,6 @@ export type BundlingOptions = {
parallel?: ParallelOption;
};
export type BackendBundlingOptions = Omit<BundlingOptions, 'baseUrl'> & {
inspectEnabled: boolean;
};
export type ServeOptions = BundlingPathsOptions & {
checksEnabled: boolean;
config: Config;
@@ -43,3 +39,15 @@ export type BuildOptions = BundlingPathsOptions & {
config: Config;
appConfigs: AppConfig[];
};
export type BackendBundlingOptions = {
checksEnabled: boolean;
isDev: boolean;
parallel?: ParallelOption;
inspectEnabled: boolean;
};
export type BackendServeOptions = BundlingPathsOptions & {
checksEnabled: boolean;
inspectEnabled: boolean;
};
+42
View File
@@ -0,0 +1,42 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { loadConfig } from '@backstage/config-loader';
import { ConfigReader } from '@backstage/config';
import { paths } from './paths';
export async function loadCliConfig(
configArgs: string[],
shouldReadSecrets: boolean = false,
) {
const configPaths = configArgs.map(arg => paths.resolveTarget(arg));
const appConfigs = await loadConfig({
shouldReadSecrets,
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'production',
configRoot: paths.targetRoot,
configPaths,
});
console.log(
`Loaded config from ${appConfigs.map(c => c.context).join(', ')}`,
);
return {
appConfigs,
config: ConfigReader.fromConfigs(appConfigs),
};
}
-1
View File
@@ -14,7 +14,6 @@
* limitations under the License.
*/
export { resolveStaticConfig } from './resolver';
export { readConfigFile } from './reader';
export { readEnvConfig } from './env';
export { readSecret } from './secrets';
@@ -1,119 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import mockFs from 'mock-fs';
import { resolveStaticConfig } from './resolver';
function normalizePaths(paths: string[]) {
return paths.map(p =>
p
.replace(/^[a-z]:/i, '')
.split('\\')
.join('/'),
);
}
describe('resolveStaticConfig', () => {
afterEach(() => {
mockFs.restore();
});
it('should resolve no files for empty roots', async () => {
mockFs({});
const resolved = await resolveStaticConfig({
env: 'development',
rootPaths: [],
});
expect(normalizePaths(resolved)).toEqual([]);
});
it('should resolve a single app-config', async () => {
mockFs({ '/repo/app-config.yaml': '' });
const resolved = await resolveStaticConfig({
env: 'development',
rootPaths: ['/repo'],
});
expect(normalizePaths(resolved)).toEqual(['/repo/app-config.yaml']);
});
it('should resolve a app-configs in different directories', async () => {
mockFs({
'/repo/app-config.yaml': '',
'/repo/packages/a/app-config.yaml': '',
});
const resolved = await resolveStaticConfig({
env: 'development',
rootPaths: [
'/repo',
'/other-repo',
'/repo/packages/a',
'/repo/packages/b',
],
});
expect(normalizePaths(resolved)).toEqual([
'/repo/app-config.yaml',
'/repo/packages/a/app-config.yaml',
]);
});
it('should resolve env and local configs', async () => {
mockFs({
'/repo/app-config.yaml': '',
'/repo/app-config.local.yaml': '',
'/repo/app-config.production.yaml': '',
'/repo/app-config.production.local.yaml': '',
'/repo/app-config.development.local.yaml': '',
'/repo/packages/a/app-config.development.yaml': '',
'/repo/packages/a/app-config.local.yaml': '',
});
const resolved = await resolveStaticConfig({
env: 'development',
rootPaths: ['/repo', '/repo/packages/a'],
});
expect(normalizePaths(resolved)).toEqual([
'/repo/app-config.yaml',
'/repo/app-config.local.yaml',
'/repo/app-config.development.local.yaml',
'/repo/packages/a/app-config.local.yaml',
'/repo/packages/a/app-config.development.yaml',
]);
});
it('resolves suffixed configs in the correct order', async () => {
mockFs({
'/repo/app-config.yaml': '',
'/repo/app-config.local.yaml': '',
'/repo/app-config.production.yaml': '',
'/repo/app-config.production.local.yaml': '',
});
const resolved = await resolveStaticConfig({
env: 'production',
rootPaths: ['/repo'],
});
expect(normalizePaths(resolved)).toEqual([
'/repo/app-config.yaml',
'/repo/app-config.local.yaml',
'/repo/app-config.production.yaml',
'/repo/app-config.production.local.yaml',
]);
});
});
@@ -1,59 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { resolve as resolvePath } from 'path';
import { pathExists } from 'fs-extra';
type ResolveOptions = {
// Root paths to search for config files. Config from earlier paths has lower priority.
rootPaths: string[];
// The environment that we're loading config for, e.g. 'development', 'production'.
env: string;
};
/**
* Resolves all configuration files that should be loaded in the given environment.
*
* For each root directory, search for the default app-config.yaml, along with suffixed
* APP_ENV and local variants, e.g. app-config.production.yaml or app-config.development.local.yaml
*
* The priority order of config loaded through suffixes is `env > local > none`, meaning that
* for example app-config.development.yaml has higher priority than `app-config.local.yaml`.
*
*/
export async function resolveStaticConfig(
options: ResolveOptions,
): Promise<string[]> {
const filePaths = [
`app-config.yaml`,
`app-config.local.yaml`,
`app-config.${options.env}.yaml`,
`app-config.${options.env}.local.yaml`,
];
const resolvedPaths = [];
for (const rootPath of options.rootPaths) {
for (const filePath of filePaths) {
const path = resolvePath(rootPath, filePath);
if (await pathExists(path)) {
resolvedPaths.push(path);
}
}
}
return resolvedPaths;
}
+34 -4
View File
@@ -38,10 +38,31 @@ describe('loadConfig', () => {
mockFs.restore();
});
it('load config from default path', async () => {
await expect(
loadConfig({
configRoot: '/root',
configPaths: [],
env: 'production',
shouldReadSecrets: false,
}),
).resolves.toEqual([
{
context: 'app-config.yaml',
data: {
app: {
title: 'Example App',
},
},
},
]);
});
it('loads config without secrets', async () => {
await expect(
loadConfig({
rootPaths: ['/root'],
configRoot: '/root',
configPaths: ['/root/app-config.yaml'],
env: 'production',
shouldReadSecrets: false,
}),
@@ -60,7 +81,8 @@ describe('loadConfig', () => {
it('loads config with secrets', async () => {
await expect(
loadConfig({
rootPaths: ['/root'],
configRoot: '/root',
configPaths: ['/root/app-config.yaml'],
env: 'production',
shouldReadSecrets: true,
}),
@@ -80,7 +102,11 @@ describe('loadConfig', () => {
it('loads development config without secrets', async () => {
await expect(
loadConfig({
rootPaths: ['/root'],
configRoot: '/root',
configPaths: [
'/root/app-config.yaml',
'/root/app-config.development.yaml',
],
env: 'development',
shouldReadSecrets: false,
}),
@@ -105,7 +131,11 @@ describe('loadConfig', () => {
it('loads development config with secrets', async () => {
await expect(
loadConfig({
rootPaths: ['/root'],
configRoot: '/root',
configPaths: [
'/root/app-config.yaml',
'/root/app-config.development.yaml',
],
env: 'development',
shouldReadSecrets: true,
}),
+33 -11
View File
@@ -15,20 +15,18 @@
*/
import fs from 'fs-extra';
import { resolve as resolvePath, dirname } from 'path';
import { resolve as resolvePath, dirname, isAbsolute } from 'path';
import { AppConfig, JsonObject } from '@backstage/config';
import {
resolveStaticConfig,
readConfigFile,
readEnvConfig,
readSecret,
} from './lib';
import { readConfigFile, readEnvConfig, readSecret } from './lib';
export type LoadConfigOptions = {
// Root paths to search for config files. Config from earlier paths has lower priority.
rootPaths: string[];
// The root directory of the config loading context. Used to find default configs.
configRoot: string;
// The environment that we're loading config for, e.g. 'development', 'production'.
// Absolute paths to load config files from. Configs from earlier paths have lower priority.
configPaths: string[];
// TODO(Rugvip): This will be removed in the future, but for now we use it to warn about possible mistakes.
env: string;
// Whether to read secrets or omit them, defaults to false.
@@ -77,13 +75,37 @@ export async function loadConfig(
options: LoadConfigOptions,
): Promise<AppConfig[]> {
const configs = [];
const { configRoot } = options;
const configPaths = options.configPaths.slice();
const configPaths = await resolveStaticConfig(options);
// If no paths are provided, we default to reading
// `app-config.yaml` and, if it exists, `app-config.local.yaml`
if (configPaths.length === 0) {
configPaths.push(resolvePath(configRoot, 'app-config.yaml'));
const localConfig = resolvePath(configRoot, 'app-config.local.yaml');
if (await fs.pathExists(localConfig)) {
configPaths.push(localConfig);
}
const envFile = `app-config.${options.env}.yaml`;
if (await fs.pathExists(resolvePath(configRoot, envFile))) {
console.error(
`Env config file '${envFile}' is not loaded as APP_ENV and NODE_ENV-based config loading has been removed`,
);
console.error(
`To load the config file, use --config <path>, listing every config file that you want to load`,
);
}
}
try {
const secretPaths = new Set<string>();
for (const configPath of configPaths) {
if (!isAbsolute(configPath)) {
throw new Error(`Config load path is not absolute: '${configPath}'`);
}
const config = await readConfigFile(
configPath,
new Context({
@@ -42,7 +42,7 @@ function makeCreateEnv(config: Config) {
}
async function main() {
const config = await loadBackendConfig({logger: getRootLogger()});
const config = await loadBackendConfig({logger: getRootLogger(), configPaths: []});
const createEnv = makeCreateEnv(config);
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
@@ -33,7 +33,7 @@ export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: 'auth-backend' });
const config = await loadBackendConfig({ logger });
const config = await loadBackendConfig({ logger, argv: process.argv });
const discovery = SingleHostDiscovery.fromConfig(config);
const database = useHotMemoize(module, () => {
@@ -36,7 +36,7 @@ export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: 'catalog-backend' });
const config = await loadBackendConfig({ logger });
const config = await loadBackendConfig({ logger, argv: process.argv });
const reader = UrlReaders.default({ logger, config });
const db = useHotMemoize(module, () =>
DatabaseManager.createInMemoryDatabaseConnection(),
@@ -41,7 +41,7 @@ const mockCreateProxyMiddleware = createProxyMiddleware as jest.MockedFunction<
describe('createRouter', () => {
it('works', async () => {
const logger = winston.createLogger();
const config = await loadBackendConfig({ logger });
const config = await loadBackendConfig({ logger, argv: [] });
const discovery = SingleHostDiscovery.fromConfig(config);
const router = await createRouter({
config,
@@ -36,7 +36,7 @@ export async function startStandaloneServer(
logger.debug('Creating application...');
const config = await loadBackendConfig({ logger });
const config = await loadBackendConfig({ logger, argv: process.argv });
const discovery = SingleHostDiscovery.fromConfig(config);
const router = await createRouter({
config,
@@ -32,7 +32,7 @@ export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: 'rollbar-backend' });
const config = await loadBackendConfig({ logger });
const config = await loadBackendConfig({ logger, argv: process.argv });
logger.debug('Creating application...');
@@ -231,7 +231,10 @@ export const getDefaultBranch = async (
repositoryUrl: string,
): Promise<string> => {
// TODO(Rugvip): Config should not be loaded here, pass it in instead
const config = await loadBackendConfig({ logger: getRootLogger() });
const config = await loadBackendConfig({
logger: getRootLogger(),
argv: process.argv,
});
const type = getGitRepoType(repositoryUrl);
try {
+5
View File
@@ -5351,6 +5351,11 @@
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/minimist@^1.2.0":
version "1.2.0"
resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6"
integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
"@types/minipass@*":
version "2.2.0"
resolved "https://registry.npmjs.org/@types/minipass/-/minipass-2.2.0.tgz#51ad404e8eb1fa961f75ec61205796807b6f9651"