Merge pull request #1726 from spotify/shmidt-i/create-app-backend

Create app backend with PG support & E2E Test
This commit is contained in:
Ivan Shmidt
2020-07-27 16:22:06 +02:00
committed by GitHub
27 changed files with 996 additions and 50 deletions
+17
View File
@@ -13,6 +13,18 @@ jobs:
build:
runs-on: ${{ matrix.os }}
services:
postgres:
image: postgres:latest
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432/tcp
# needed because the postgres container does not provide a healthcheck
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
strategy:
matrix:
os: [ubuntu-latest]
@@ -44,6 +56,11 @@ jobs:
- run: yarn tsc
- run: yarn build
- name: verify app and plugin creation
env:
POSTGRES_HOST: localhost
POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }}
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
run: |
sudo sysctl fs.inotify.max_user_watches=524288
node ${{ github.workspace }}/packages/cli/e2e-test/cli-e2e-test.js
+13 -13
View File
@@ -1,13 +1,13 @@
| Organization | Contact | Description of Use |
| ---------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------- |
| [Spotify](https://www.spotify.com) | [@stefanalund](https://github.com/stefanalund) | Main interface towards all of Spotify's infrastructure and technical documentation. |
| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. |
| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. |
| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up |
| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. |
| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. |
| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. |
| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications |
| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. |
| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. |
| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D
| Organization | Contact | Description of Use |
| --------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| [Spotify](https://www.spotify.com) | [@stefanalund](https://github.com/stefanalund) | Main interface towards all of Spotify's infrastructure and technical documentation. |
| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. |
| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. |
| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up |
| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. |
| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. |
| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. |
| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications |
| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. |
| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. |
| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D |
+1
View File
@@ -35,6 +35,7 @@
"dockerode": "^3.2.0",
"express": "^4.17.1",
"knex": "^0.21.1",
"pg": "^8.3.0",
"sqlite3": "^4.2.0",
"winston": "^3.2.1"
},
+31 -6
View File
@@ -29,7 +29,7 @@ import {
} from '@backstage/backend-common';
import { ConfigReader, AppConfig } from '@backstage/config';
import { loadConfig } from '@backstage/config-loader';
import knex from 'knex';
import knex, { PgConnectionConfig } from 'knex';
import healthcheck from './plugins/healthcheck';
import auth from './plugins/auth';
import catalog from './plugins/catalog';
@@ -47,11 +47,36 @@ function makeCreateEnv(loadedConfigs: AppConfig[]) {
return (plugin: string): PluginEnvironment => {
const logger = getRootLogger().child({ type: 'plugin', plugin });
const database = knex({
client: 'sqlite3',
connection: ':memory:',
useNullAsDefault: true,
});
// Supported DBs are sqlite and postgres
const isPg = [
'POSTGRES_USER',
'POSTGRES_HOST',
'POSTGRES_PASSWORD',
].every(key => config.getOptional(`backend.${key}`));
let knexConfig;
if (isPg) {
knexConfig = {
client: 'pg',
useNullAsDefault: true,
connection: {
port: config.getOptionalNumber('backend.POSTGRES_PORT'),
host: config.getString('backend.POSTGRES_HOST'),
user: config.getString('backend.POSTGRES_USER'),
password: config.getString('backend.POSTGRES_PASSWORD'),
database: `backstage_plugin_${plugin}`,
} as PgConnectionConfig,
};
} else {
knexConfig = {
client: 'sqlite3',
connection: ':memory:',
useNullAsDefault: true,
};
}
const database = knex(knexConfig);
database.client.pool.on('createSuccess', (_eventId: any, resource: any) => {
resource.run('PRAGMA foreign_keys = ON', () => {});
});
+98 -2
View File
@@ -16,6 +16,7 @@
const os = require('os');
const fs = require('fs-extra');
const fetch = require('node-fetch');
const killTree = require('tree-kill');
const { resolve: resolvePath, join: joinPath } = require('path');
const Browser = require('zombie');
@@ -28,6 +29,7 @@ const {
waitForExit,
print,
} = require('./helpers');
const pgtools = require('pgtools');
async function main() {
const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-'));
@@ -36,8 +38,9 @@ async function main() {
print('Building dist workspace');
const workspaceDir = await buildDistWorkspace('workspace', rootDir);
const isPostgres = Boolean(process.env.POSTGRES_USER);
print('Creating a Backstage App');
const appDir = await createApp('test-app', workspaceDir, rootDir);
const appDir = await createApp('test-app', isPostgres, workspaceDir, rootDir);
print('Creating a Backstage Plugin');
const pluginName = await createPlugin('test-plugin', appDir);
@@ -45,6 +48,9 @@ async function main() {
print('Starting the app');
await testAppServe(pluginName, appDir);
print('Testing the backend startup');
await testBackendStart(appDir, isPostgres);
print('All tests successful, removing test dir');
await fs.remove(rootDir);
}
@@ -97,7 +103,7 @@ async function pinYarnVersion(dir) {
/**
* Creates a new app inside rootDir called test-app, using packages from the workspaceDir
*/
async function createApp(appName, workspaceDir, rootDir) {
async function createApp(appName, isPostgres, workspaceDir, rootDir) {
const child = spawnPiped(
[
'node',
@@ -119,6 +125,14 @@ async function createApp(appName, workspaceDir, rootDir) {
await waitFor(() => stdout.includes('Enter a name for the app'));
child.stdin.write(`${appName}\n`);
await waitFor(() => stdout.includes('Select database for the backend'));
if (!isPostgres) {
// Simulate down arrow press
child.stdin.write(`\u001B\u005B\u0042`);
}
child.stdin.write(`\n`);
print('Waiting for app create script to be done');
await waitForExit(child);
@@ -250,5 +264,87 @@ async function testAppServe(pluginName, appDir) {
}
}
/** Creates PG databases (drops if exists before) */
async function createDB(database) {
const config = {
host: process.env.POSTGRES_HOST,
port: process.env.POSTGRES_PORT,
user: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
};
try {
await pgtools.dropdb({ config }, database);
} catch (_) {
/* do nothing*/
}
return pgtools.createdb(config, database);
}
/**
* Start serving the newly created backend and make sure that all db migrations works correctly
*/
async function testBackendStart(appDir, isPostgres) {
if (isPostgres) {
print('Creating DBs');
await Promise.all(
[
'catalog',
'scaffolder',
'auth',
'identity',
'proxy',
'techdocs',
].map(name => createDB(`backstage_plugin_${name}`)),
);
print('Created DBs');
}
const child = spawnPiped(['yarn', 'workspace', 'backend', 'start'], {
cwd: appDir,
});
let stdout = '';
let stderr = '';
child.stdout.on('data', data => {
stdout = stdout + data.toString('utf8');
});
child.stderr.on('data', data => {
stderr = stderr + data.toString('utf8');
});
let successful = false;
try {
await waitFor(() => stdout.includes('Listening on ') || stderr !== '');
if (stderr !== '') {
// Skipping the whole block
throw new Error(stderr);
}
print('Try to fetch entities from the backend');
// Try fetch entities, should be ok
await fetch('http://localhost:7000/catalog/entities').then(res =>
res.json(),
);
print('Entities fetched successfully');
successful = true;
} catch (error) {
throw new Error(`Backend failed to startup: ${error}`);
} finally {
print('Stopping the child process');
// Kill entire process group, otherwise we'll end up with hanging serve processes
killTree(child.pid);
}
try {
await waitForExit(child);
} catch (error) {
if (!successful) {
throw new Error(`Backend failed to startup: ${stderr}`);
}
print('Backend startup test finished successfully');
}
}
process.on('unhandledRejection', handleError);
main(process.argv.slice(2)).catch(handleError);
+6
View File
@@ -76,6 +76,12 @@ function handleError(err) {
}
}
/**
* Waits for fn() to be true
* Checks every 100ms
* .cancel() is available
* @returns {Promise} Promise of resolution
*/
function waitFor(fn) {
return new Promise(resolve => {
const handle = setInterval(() => {
+2
View File
@@ -68,7 +68,9 @@
"jest-css-modules": "^2.1.0",
"jest-esm-transformer": "^1.0.0",
"mini-css-extract-plugin": "^0.9.0",
"node-fetch": "^2.6.0",
"ora": "^4.0.3",
"pgtools": "^0.3.0",
"raw-loader": "^4.0.1",
"react": "^16.0.0",
"react-dev-utils": "^10.2.1",
@@ -104,8 +104,17 @@ export default async (cmd: Command): Promise<void> => {
return true;
},
},
{
type: 'list',
name: 'dbType',
message: chalk.blue('Select database for the backend [required]'),
// @ts-ignore
choices: ['PostgreSQL', 'SQLite'],
},
];
const answers: Answers = await inquirer.prompt(questions);
answers.dbTypePG = answers.dbType === 'PostgreSQL';
answers.dbTypeSqlite = answers.dbType === 'SQLite';
const templateDir = paths.resolveOwn('templates/default-app');
const tempDir = resolvePath(os.tmpdir(), answers.name);
@@ -1,3 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
root: true,
};
@@ -4,3 +4,19 @@ app:
organization:
name: Acme Corporation
backend:
baseUrl: http://localhost:7000
listen: 0.0.0.0:7000
cors:
origin: http://localhost:3000
methods: [GET, POST, PUT, DELETE]
credentials: true
proxy:
'/test':
target: 'https://example.com'
changeOrigin: true
techdocs:
storageUrl: https://techdocs-mock-sites.storage.googleapis.com
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
};
@@ -0,0 +1,9 @@
FROM node:12
WORKDIR /usr/src/app
COPY . .
RUN yarn install --frozen-lockfile --production
CMD ["node", "packages/backend"]
@@ -0,0 +1,68 @@
# example-backend
This package is an EXAMPLE of a Backstage backend.
The main purpose of this package is to provide a test bed for Backstage plugins
that have a backend part. Feel free to experiment locally or within your fork
by adding dependencies and routes to this backend, to try things out.
Our goal is to eventually amend the create-app flow of the CLI, such that a
production ready version of a backend skeleton is made alongside the frontend
app. Until then, feel free to experiment here!
## Development
To run the example backend, first go to the project root and run
```bash
yarn install
yarn tsc
yarn build
```
You should only need to do this once.
After that, go to the `packages/backend` directory and run
```bash
AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \
AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \
AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \
AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \
LOG_LEVEL=debug \
yarn start
```
Substitute `x` for actual values, or leave them as
dummy values just to try out the backend without using the auth or sentry features.
The backend starts up on port 7000 per default.
## Populating The Catalog
If you want to use the catalog functionality, you need to add so called locations
to the backend. These are places where the backend can find some entity descriptor
data to consume and serve.
To get started, you can issue the following after starting the backend, from inside
the `plugins/catalog-backend` directory:
```bash
yarn mock-data
```
You should then start seeing data on `localhost:7000/catalog/entities`.
The catalog currently runs in-memory only, so feel free to try it out, but it will
need to be re-populated on next startup.
## Authentication
We chose [Passport](http://www.passportjs.org/) as authentication platform due to its comprehensive set of supported authentication [strategies](http://www.passportjs.org/packages/).
Read more about the [auth-backend](https://github.com/spotify/backstage/blob/master/plugins/auth-backend/README.md) and [how to add a new provider](https://github.com/spotify/backstage/blob/master/docs/auth/add-auth-provider.md)
## Documentation
- [Backstage Readme](https://github.com/spotify/backstage/blob/master/README.md)
- [Backstage Documentation](https://github.com/spotify/backstage/blob/master/docs/README.md)
@@ -0,0 +1,51 @@
{
"name": "backend",
"version": "0.0.0",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"private": true,
"engines": {
"node": "12"
},
"scripts": {
"build": "backstage-cli backend:build",
"build-image": "backstage-cli backend:build-image example-backend",
"start": "backstage-cli backend:dev",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"clean": "backstage-cli clean",
"migrate:create": "knex migrate:make -x ts"
},
"dependencies": {
"@backstage/backend-common": "^{{version}}",
"@backstage/catalog-model": "^{{version}}",
"@backstage/config": "^0.1.1-alpha.13",
"@backstage/config-loader": "^0.1.1-alpha.13",
"@backstage/plugin-auth-backend": "^{{version}}",
"@backstage/plugin-catalog-backend": "^{{version}}",
"@backstage/plugin-identity-backend": "^{{version}}",
"@backstage/plugin-proxy-backend": "^{{version}}",
"@backstage/plugin-rollbar-backend": "^{{version}}",
"@backstage/plugin-scaffolder-backend": "^{{version}}",
"@backstage/plugin-sentry-backend": "^{{version}}",
"@backstage/plugin-techdocs-backend": "^{{version}}",
"@octokit/rest": "^18.0.0",
"dockerode": "^3.2.0",
"express": "^4.17.1",
"knex": "^0.21.1",
{{#if dbTypePG}}
"pg": "^8.3.0",
{{/if}}
{{#if dbTypeSqlite}}
"sqlite3": "^4.2.0",
{{/if}}
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.15",
"@types/dockerode": "^2.5.32",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5",
"@types/helmet": "^0.0.47"
}
}
@@ -0,0 +1,24 @@
/*
* 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 { PluginEnvironment } from './types';
describe('test', () => {
it('unbreaks the test runner', () => {
const unbreaker = {} as PluginEnvironment;
expect(unbreaker).toBeTruthy();
});
});
@@ -0,0 +1,111 @@
/*
* 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.
*/
/*
* Hi!
*
* Note that this is an EXAMPLE Backstage backend. Please check the README.
*
* Happy hacking!
*/
import {
createServiceBuilder,
getRootLogger,
useHotMemoize,
} from '@backstage/backend-common';
import { ConfigReader, AppConfig } from '@backstage/config';
import { loadConfig } from '@backstage/config-loader';
{{#if dbTypePG}}
import knex, { PgConnectionConfig } from 'knex';
{{/if}}
{{#if dbTypeSqlite}}
import knex from 'knex';
{{/if}}
import auth from './plugins/auth';
import catalog from './plugins/catalog';
import identity from './plugins/identity';
import scaffolder from './plugins/scaffolder';
import proxy from './plugins/proxy';
import techdocs from './plugins/techdocs';
import { PluginEnvironment } from './types';
function makeCreateEnv(loadedConfigs: AppConfig[]) {
const config = ConfigReader.fromConfigs(loadedConfigs);
return (plugin: string): PluginEnvironment => {
const logger = getRootLogger().child({ type: 'plugin', plugin });
{{#if dbTypePG}}
const knexConfig = {
client: 'pg',
useNullAsDefault: true,
connection: {
port: process.env.POSTGRES_PORT,
host: process.env.POSTGRES_HOST,
user: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
database: `backstage_plugin_${plugin}`,
} as PgConnectionConfig,
};
{{/if}}
{{#if dbTypeSqlite}}
const knexConfig = {
client: 'sqlite3',
connection: ':memory:',
useNullAsDefault: true,
};
{{/if}}
const database = knex(knexConfig);
database.client.pool.on('createSuccess', (_eventId: any, resource: any) => {
resource.run('PRAGMA foreign_keys = ON', () => {});
});
return { logger, database, config };
};
}
async function main() {
const configs = await loadConfig();
const configReader = ConfigReader.fromConfigs(configs);
const createEnv = makeCreateEnv(configs);
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
const authEnv = useHotMemoize(module, () => createEnv('auth'));
const identityEnv = useHotMemoize(module, () => createEnv('identity'));
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
const service = createServiceBuilder(module)
.loadConfig(configReader)
.addRouter('/catalog', await catalog(catalogEnv))
.addRouter('/scaffolder', await scaffolder(scaffolderEnv))
.addRouter('/auth', await auth(authEnv))
.addRouter('/identity', await identity(identityEnv))
.addRouter('/techdocs', await techdocs(techdocsEnv))
.addRouter('/proxy', await proxy(proxyEnv));
await service.start().catch(err => {
console.log(err);
process.exit(1);
});
}
module.hot?.accept();
main().catch(error => {
console.error(`Backend failed to start up, ${error}`);
process.exit(1);
});
@@ -0,0 +1,26 @@
/*
* 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 { createRouter } from '@backstage/plugin-auth-backend';
import { PluginEnvironment } from '../types';
export default async function createPlugin({
logger,
database,
config,
}: PluginEnvironment) {
return await createRouter({ logger, config, database });
}
@@ -0,0 +1,56 @@
/*
* 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 {
createRouter,
DatabaseEntitiesCatalog,
DatabaseLocationsCatalog,
DatabaseManager,
HigherOrderOperations,
LocationReaders,
runPeriodically,
} from '@backstage/plugin-catalog-backend';
import { PluginEnvironment } from '../types';
import { useHotCleanup } from '@backstage/backend-common';
export default async function createPlugin({
logger,
database,
}: PluginEnvironment) {
const locationReader = new LocationReaders(logger);
const db = await DatabaseManager.createDatabase(database, { logger });
const entitiesCatalog = new DatabaseEntitiesCatalog(db);
const locationsCatalog = new DatabaseLocationsCatalog(db);
const higherOrderOperation = new HigherOrderOperations(
entitiesCatalog,
locationsCatalog,
locationReader,
logger,
);
useHotCleanup(
module,
runPeriodically(() => higherOrderOperation.refreshAllLocations(), 10000),
);
return await createRouter({
entitiesCatalog,
locationsCatalog,
higherOrderOperation,
logger,
});
}
@@ -0,0 +1,22 @@
/*
* 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 { createRouter } from '@backstage/plugin-identity-backend';
import { PluginEnvironment } from '../types';
export default async function createPlugin({ logger }: PluginEnvironment) {
return await createRouter({ logger });
}
@@ -0,0 +1,25 @@
/*
* 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.
*/
// @ts-ignore
import { createRouter } from '@backstage/plugin-proxy-backend';
import { PluginEnvironment } from '../types';
export default async function createPlugin({
logger,
config,
}: PluginEnvironment) {
return await createRouter({ logger, config });
}
@@ -0,0 +1,56 @@
/*
* 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 {
CookieCutter,
createRouter,
FilePreparer,
GithubPreparer,
Preparers,
GithubPublisher,
CreateReactAppTemplater,
Templaters,
} from '@backstage/plugin-scaffolder-backend';
import { Octokit } from '@octokit/rest';
import type { PluginEnvironment } from '../types';
import Docker from 'dockerode';
export default async function createPlugin({ logger }: PluginEnvironment) {
const cookiecutterTemplater = new CookieCutter();
const craTemplater = new CreateReactAppTemplater();
const templaters = new Templaters();
templaters.register('cookiecutter', cookiecutterTemplater);
templaters.register('cra', craTemplater);
const filePreparer = new FilePreparer();
const githubPreparer = new GithubPreparer();
const preparers = new Preparers();
preparers.register('file', filePreparer);
preparers.register('github', githubPreparer);
const githubClient = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN });
const publisher = new GithubPublisher({ client: githubClient });
const dockerClient = new Docker();
return await createRouter({
preparers,
templaters,
publisher,
logger,
dockerClient,
});
}
@@ -0,0 +1,22 @@
/*
* 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 { createRouter } from '@backstage/plugin-techdocs-backend';
import { PluginEnvironment } from '../types';
export default async function createPlugin({ logger }: PluginEnvironment) {
return await createRouter({ logger });
}
@@ -0,0 +1,25 @@
/*
* 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 Knex from 'knex';
import { Logger } from 'winston';
import { Config } from '@backstage/config';
export type PluginEnvironment = {
logger: Logger;
database: Knex;
config: Config;
};
@@ -1,8 +1,14 @@
{
"extends": "@backstage/cli/config/tsconfig.json",
"include": ["packages/*/src", "plugins/*/src", "plugins/*/dev"],
"exclude": ["**/node_modules"],
"include": [
"packages/*/src",
"plugins/*/src",
"plugins/*/dev",
"plugins/*/migrations"
],
"exclude": ["node_modules"],
"compilerOptions": {
"outDir": "dist"
"outDir": "dist",
"skipLibCheck": true
}
}
+26 -21
View File
@@ -7,29 +7,34 @@ Website: [https://github.com/actions](https://github.com/actions)
TBD
## Setup
### Generic Requirements
1. Provide OAuth credentials:
1. [Create an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) with callback URL set to ```https://localhost:3000/auth/github```.
2. Take Client ID and Client Secret from the newly created app's settings page and put them into ```AUTH_GITHUB_CLIENT_ID``` and ```AUTH_GITHUB_CLIENT_SECRET``` env variables.
2. Annotate your component with a correct GitHub Actions repository and owner:
The annotation key is ```backstage.io/github-actions-id```.
Example:
```
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage
description: backstage.io
annotations:
backstage.io/github-actions-id: 'spotify/backstage'
spec:
type: website
lifecycle: production
owner: guest
```
### Generic Requirements
1. Provide OAuth credentials:
1. [Create an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) with callback URL set to `https://localhost:3000/auth/github`.
2. Take Client ID and Client Secret from the newly created app's settings page and put them into `AUTH_GITHUB_CLIENT_ID` and `AUTH_GITHUB_CLIENT_SECRET` env variables.
2. Annotate your component with a correct GitHub Actions repository and owner:
The annotation key is `backstage.io/github-actions-id`.
Example:
```
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage
description: backstage.io
annotations:
backstage.io/github-actions-id: 'spotify/backstage'
spec:
type: website
lifecycle: production
owner: guest
```
### Standalone app requirements
If you didn't clone this repo you have to do some extra work.
1. Add plugin API to your Backstage instance:
+1 -2
View File
@@ -6,8 +6,7 @@ _This plugin was created through the Backstage CLI_
## Getting started
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn
start` in the root directory, and then navigating to [/graphql](http://localhost:3000/graphql).
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/graphql](http://localhost:3000/graphql).
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
+268 -2
View File
@@ -6187,6 +6187,16 @@ buffer-indexof@^1.0.0:
resolved "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==
buffer-writer@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08"
integrity sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg=
buffer-writer@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04"
integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==
buffer-xor@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
@@ -6397,6 +6407,11 @@ camelcase@^2.0.0:
resolved "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
camelcase@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo=
camelcase@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
@@ -6694,6 +6709,15 @@ clipboard@^2.0.0:
select "^1.1.2"
tiny-emitter "^2.0.0"
cliui@^3.2.0:
version "3.2.0"
resolved "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=
dependencies:
string-width "^1.0.1"
strip-ansi "^3.0.1"
wrap-ansi "^2.0.0"
cliui@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
@@ -7846,7 +7870,7 @@ decamelize-keys@^1.0.0:
decamelize "^1.1.0"
map-obj "^1.0.0"
decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0:
decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -9899,6 +9923,11 @@ generic-names@^2.0.1:
dependencies:
loader-utils "^1.1.0"
generic-pool@2.4.3:
version "2.4.3"
resolved "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff"
integrity sha1-eAw29p360FpaBF3Te+etyhGk9v8=
genfun@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537"
@@ -9909,6 +9938,11 @@ gensync@^1.0.0-beta.1:
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==
get-caller-file@^1.0.1:
version "1.0.3"
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
@@ -11272,6 +11306,11 @@ invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4:
dependencies:
loose-envify "^1.0.0"
invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@@ -12318,6 +12357,11 @@ js-cookie@^2.2.1:
resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
js-string-escape@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -12726,6 +12770,13 @@ lazy-universal-dotenv@^3.0.1:
dotenv "^8.0.0"
dotenv-expand "^5.1.0"
lcid@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=
dependencies:
invert-kv "^1.0.0"
left-pad@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
@@ -12995,6 +13046,11 @@ lodash._reinterpolate@^3.0.0:
resolved "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
lodash.assign@^4.1.0, lodash.assign@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=
lodash.camelcase@^4.3.0:
version "4.3.0"
resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
@@ -14376,6 +14432,11 @@ oauth@0.9.x:
resolved "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
object-assign@4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
integrity sha1-ejs9DpgGPUP0wD8uiubNUahog6A=
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -14625,6 +14686,13 @@ os-homedir@^1.0.0:
resolved "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
os-locale@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=
dependencies:
lcid "^1.0.0"
os-name@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801"
@@ -14805,6 +14873,16 @@ package-json@^6.3.0:
registry-url "^5.0.0"
semver "^6.2.0"
packet-reader@0.3.1:
version "0.3.1"
resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz#cd62e60af8d7fea8a705ec4ff990871c46871f27"
integrity sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc=
packet-reader@1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74"
integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==
pako@~1.0.5:
version "1.0.11"
resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
@@ -15173,11 +15251,111 @@ performance-now@^2.1.0:
resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
pg-connection-string@0.1.3, pg-connection-string@^0.1.3:
version "0.1.3"
resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7"
integrity sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=
pg-connection-string@2.2.0:
version "2.2.0"
resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.2.0.tgz#caab4d38a9de4fdc29c9317acceed752897de41c"
integrity sha512-xB/+wxcpFipUZOQcSzcgkjcNOosGhEoPSjz06jC89lv1dj7mc9bZv6wLVy8M2fVjP0a/xN0N988YDq1L0FhK3A==
pg-connection-string@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.3.0.tgz#c13fcb84c298d0bfa9ba12b40dd6c23d946f55d6"
integrity sha512-ukMTJXLI7/hZIwTW7hGMZJ0Lj0S2XQBCJ4Shv4y1zgQ/vqVea+FLhzywvPj0ujSuofu+yA4MYHGZPTsgjBgJ+w==
pg-int8@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
pg-pool@1.*:
version "1.8.0"
resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-1.8.0.tgz#f7ec73824c37a03f076f51bfdf70e340147c4f37"
integrity sha1-9+xzgkw3oD8Hb1G/33DjQBR8Tzc=
dependencies:
generic-pool "2.4.3"
object-assign "4.1.0"
pg-pool@^3.2.1:
version "3.2.1"
resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.1.tgz#5f4afc0f58063659aeefa952d36af49fa28b30e0"
integrity sha512-BQDPWUeKenVrMMDN9opfns/kZo4lxmSWhIqo+cSAF7+lfi9ZclQbr9vfnlNaPr8wYF3UYjm5X0yPAhbcgqNOdA==
pg-protocol@^1.2.5:
version "1.2.5"
resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.2.5.tgz#28a1492cde11646ff2d2d06bdee42a3ba05f126c"
integrity sha512-1uYCckkuTfzz/FCefvavRywkowa6M5FohNMF5OjKrqo9PSR8gYc8poVmwwYQaBxhmQdBjhtP514eXy9/Us2xKg==
pg-types@1.*:
version "1.13.0"
resolved "https://registry.npmjs.org/pg-types/-/pg-types-1.13.0.tgz#75f490b8a8abf75f1386ef5ec4455ecf6b345c63"
integrity sha512-lfKli0Gkl/+za/+b6lzENajczwZHc7D5kiUCZfgm914jipD2kIOIvEkAhZ8GrW3/TUoP9w8FHjwpPObBye5KQQ==
dependencies:
pg-int8 "1.0.1"
postgres-array "~1.0.0"
postgres-bytea "~1.0.0"
postgres-date "~1.0.0"
postgres-interval "^1.1.0"
pg-types@^2.1.0:
version "2.2.0"
resolved "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==
dependencies:
pg-int8 "1.0.1"
postgres-array "~2.0.0"
postgres-bytea "~1.0.0"
postgres-date "~1.0.4"
postgres-interval "^1.1.0"
pg@^6.1.0:
version "6.4.2"
resolved "https://registry.npmjs.org/pg/-/pg-6.4.2.tgz#c364011060eac7a507a2ae063eb857ece910e27f"
integrity sha1-w2QBEGDqx6UHoq4GPrhX7OkQ4n8=
dependencies:
buffer-writer "1.0.1"
js-string-escape "1.0.1"
packet-reader "0.3.1"
pg-connection-string "0.1.3"
pg-pool "1.*"
pg-types "1.*"
pgpass "1.*"
semver "4.3.2"
pg@^8.3.0:
version "8.3.0"
resolved "https://registry.npmjs.org/pg/-/pg-8.3.0.tgz#941383300d38eef51ecb88a0188cec441ab64d81"
integrity sha512-jQPKWHWxbI09s/Z9aUvoTbvGgoj98AU7FDCcQ7kdejupn/TcNpx56v2gaOTzXkzOajmOEJEdi9eTh9cA2RVAjQ==
dependencies:
buffer-writer "2.0.0"
packet-reader "1.0.0"
pg-connection-string "^2.3.0"
pg-pool "^3.2.1"
pg-protocol "^1.2.5"
pg-types "^2.1.0"
pgpass "1.x"
semver "4.3.2"
pgpass@1.*, pgpass@1.x:
version "1.0.2"
resolved "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz#2a7bb41b6065b67907e91da1b07c1847c877b306"
integrity sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=
dependencies:
split "^1.0.0"
pgtools@^0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/pgtools/-/pgtools-0.3.0.tgz#ee7decf4183ada28299c63df71e1e73c95b5bc53"
integrity sha512-8NxDCJ8xJ6hOp9hVNZqxi+TZl7hM1Jc8pQyj8DlAbyaWnk5OsGwf3gB/UyDODdOguiim9QzbzPsslp//apO+Uw==
dependencies:
bluebird "^3.3.5"
pg "^6.1.0"
pg-connection-string "^0.1.3"
yargs "^5.0.0"
picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2:
version "2.2.2"
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
@@ -15709,6 +15887,33 @@ postcss@^6.0.1:
source-map "^0.6.1"
supports-color "^5.4.0"
postgres-array@~1.0.0:
version "1.0.3"
resolved "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz#c561fc3b266b21451fc6555384f4986d78ec80f5"
integrity sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ==
postgres-array@~2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==
postgres-bytea@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35"
integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=
postgres-date@~1.0.0, postgres-date@~1.0.4:
version "1.0.5"
resolved "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.5.tgz#710b27de5f27d550f6e80b5d34f7ba189213c2ee"
integrity sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA==
postgres-interval@^1.1.0:
version "1.2.0"
resolved "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695"
integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==
dependencies:
xtend "^4.0.0"
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -17033,6 +17238,11 @@ require-directory@^2.1.1:
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-main-filename@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
@@ -17476,6 +17686,11 @@ semver-regex@^2.0.0:
resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
semver@4.3.2:
version "4.3.2"
resolved "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=
semver@7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
@@ -18258,7 +18473,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
string-width@^1.0.1:
string-width@^1.0.1, string-width@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
@@ -20111,6 +20326,11 @@ whatwg-url@^8.0.0:
tr46 "^2.0.2"
webidl-conversions "^5.0.0"
which-module@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
@@ -20149,6 +20369,11 @@ widest-line@^3.1.0:
dependencies:
string-width "^4.0.0"
window-size@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"
integrity sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=
windows-release@^3.1.0:
version "3.2.0"
resolved "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f"
@@ -20203,6 +20428,14 @@ worker-rpc@^0.1.0:
dependencies:
microevent.ts "~0.1.1"
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=
dependencies:
string-width "^1.0.1"
strip-ansi "^3.0.1"
wrap-ansi@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba"
@@ -20402,6 +20635,11 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
y18n@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
@@ -20458,6 +20696,14 @@ yargs-parser@^15.0.1:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^3.2.0:
version "3.2.0"
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-3.2.0.tgz#5081355d19d9d0c8c5d81ada908cb4e6d186664f"
integrity sha1-UIE1XRnZ0MjF2BrakIy05tGGZk8=
dependencies:
camelcase "^3.0.0"
lodash.assign "^4.1.0"
yargs@^13.3.2:
version "13.3.2"
resolved "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
@@ -20508,6 +20754,26 @@ yargs@^15.3.1:
y18n "^4.0.0"
yargs-parser "^18.1.1"
yargs@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/yargs/-/yargs-5.0.0.tgz#3355144977d05757dbb86d6e38ec056123b3a66e"
integrity sha1-M1UUSXfQV1fbuG1uOOwFYSOzpm4=
dependencies:
cliui "^3.2.0"
decamelize "^1.1.1"
get-caller-file "^1.0.1"
lodash.assign "^4.2.0"
os-locale "^1.4.0"
read-pkg-up "^1.0.1"
require-directory "^2.1.1"
require-main-filename "^1.0.1"
set-blocking "^2.0.0"
string-width "^1.0.2"
which-module "^1.0.0"
window-size "^0.2.0"
y18n "^3.2.1"
yargs-parser "^3.2.0"
yauzl@2.10.0, yauzl@^2.10.0:
version "2.10.0"
resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"