@backstage/cli
Version:
CLI for developing Backstage plugins and apps
226 lines (219 loc) • 7.6 kB
JavaScript
var fs = require('fs-extra');
var chalk = require('chalk');
var yaml = require('yaml');
var inquirer = require('inquirer');
var index$1 = require('./index-d2845aa8.cjs.js');
var crypto = require('crypto');
var openBrowser = require('react-dev-utils/openBrowser');
var request = require('@octokit/request');
var express = require('express');
var fetch = require('node-fetch');
require('commander');
require('semver');
require('@backstage/cli-common');
require('@backstage/errors');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
var inquirer__default = /*#__PURE__*/_interopDefaultLegacy(inquirer);
var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
var openBrowser__default = /*#__PURE__*/_interopDefaultLegacy(openBrowser);
var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
const FORM_PAGE = `
<html>
<body>
<form id="form" action="ACTION_URL" method="post">
<input type="hidden" name="manifest" value="MANIFEST_JSON">
<input type="submit" value="Continue">
</form>
<script>
document.getElementById("form").submit()
<\/script>
</body>
</html>
`;
class GithubCreateAppServer {
constructor(actionUrl, permissions) {
this.actionUrl = actionUrl;
this.permissions = permissions;
this.formHandler = (_req, res) => {
const baseUrl = this.baseUrl;
if (!baseUrl) {
throw new Error("baseUrl is not set");
}
const manifest = {
default_events: ["create", "delete", "push", "repository"],
default_permissions: {
metadata: "read",
...this.permissions.includes("members") && { members: "read" },
...this.permissions.includes("read") && { contents: "read" },
...this.permissions.includes("write") && {
contents: "write",
actions: "write"
}
},
name: "Backstage-<changeme>",
url: "https://backstage.io",
description: "GitHub App for Backstage",
public: false,
redirect_url: `${baseUrl}/callback`,
hook_attributes: {
url: this.webhookUrl,
active: false
}
};
const manifestJson = JSON.stringify(manifest).replace(/\"/g, """);
let body = FORM_PAGE;
body = body.replace("MANIFEST_JSON", manifestJson);
body = body.replace("ACTION_URL", this.actionUrl);
res.setHeader("content-type", "text/html");
res.send(body);
};
const webhookId = crypto__default["default"].randomBytes(15).toString("base64").replace(/[\+\/]/g, "");
this.webhookUrl = `https://smee.io/${webhookId}`;
}
static async run(options) {
const encodedOrg = encodeURIComponent(options.org);
const actionUrl = `https://github.com/organizations/${encodedOrg}/settings/apps/new`;
const server = new GithubCreateAppServer(actionUrl, options.permissions);
return server.start();
}
async start() {
const app = express__default["default"]();
app.get("/", this.formHandler);
const callPromise = new Promise((resolve, reject) => {
app.get("/callback", (req, res) => {
request.request(
`POST /app-manifests/${encodeURIComponent(
req.query.code
)}/conversions`
).then(({ data }) => {
resolve({
name: data.name,
slug: data.slug,
appId: data.id,
webhookUrl: this.webhookUrl,
clientId: data.client_id,
clientSecret: data.client_secret,
webhookSecret: data.webhook_secret,
privateKey: data.pem
});
res.redirect(302, `${data.html_url}/installations/new`);
}, reject);
});
});
this.baseUrl = await this.listen(app);
openBrowser__default["default"](this.baseUrl);
return callPromise;
}
async listen(app) {
return new Promise((resolve, reject) => {
const listener = app.listen(0, () => {
const info = listener.address();
if (typeof info !== "object" || info === null) {
reject(new Error(`Unexpected listener info '${info}'`));
return;
}
const { port } = info;
resolve(`http://localhost:${port}`);
});
});
}
}
var index = async (org) => {
const answers = await inquirer__default["default"].prompt({
name: "appType",
type: "checkbox",
message: "Select permissions [required] (these can be changed later but then require approvals in all installations)",
choices: [
{
name: "Read access to content (required by Software Catalog to ingest data from repositories)",
value: "read",
checked: true
},
{
name: "Read access to members (required by Software Catalog to ingest GitHub teams)",
value: "members",
checked: true
},
{
name: "Read and Write to content and actions (required by Software Templates to create new repositories)",
value: "write"
}
]
});
if (answers.appType.length === 0) {
console.log(chalk__default["default"].red("You must select at least one permission"));
process.exit(1);
}
await verifyGithubOrg(org);
const { slug, name, ...config } = await GithubCreateAppServer.run({
org,
permissions: answers.appType
});
const fileName = `github-app-${slug}-credentials.yaml`;
const content = `# Name: ${name}
${yaml.stringify(config)}`;
await fs__default["default"].writeFile(index$1.paths.resolveTargetRoot(fileName), content);
console.log(`GitHub App configuration written to ${chalk__default["default"].cyan(fileName)}`);
console.log(
chalk__default["default"].yellow(
"This file contains sensitive credentials, it should not be committed to version control and handled with care!"
)
);
console.log(
"Here's an example on how to update the integrations section in app-config.yaml"
);
console.log(
chalk__default["default"].green(`
integrations:
github:
- host: github.com
apps:
- $include: ${fileName}`)
);
};
async function verifyGithubOrg(org) {
let response;
try {
response = await fetch__default["default"](
`https://api.github.com/orgs/${encodeURIComponent(org)}`
);
} catch (e) {
console.log(
chalk__default["default"].yellow(
"Warning: Unable to verify existence of GitHub organization. ",
e
)
);
}
if ((response == null ? void 0 : response.status) === 404) {
const questions = [
{
type: "confirm",
name: "shouldCreateOrg",
message: `GitHub organization ${chalk__default["default"].cyan(
org
)} does not exist. Would you like to create a new Organization instead?`
}
];
const answers = await inquirer__default["default"].prompt(questions);
if (!answers.shouldCreateOrg) {
console.log(
chalk__default["default"].yellow("GitHub organization must exist to create GitHub app")
);
process.exit(1);
}
openBrowser__default["default"]("https://github.com/account/organizations/new");
console.log(
chalk__default["default"].yellow(
"Please re-run this command when you have created your new organization"
)
);
process.exit(0);
}
}
exports["default"] = index;
//# sourceMappingURL=index-9a88f8b3.cjs.js.map
;