@backstage/cli 0.27.1 → 0.28.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/config/jest.js +114 -68
- package/dist/cjs/{build-D9YZ-dyI.cjs.js → build-CQdcGuBr.cjs.js} +10 -9
- package/dist/cjs/{buildBackend-CmtimF8a.cjs.js → buildBackend-CkhZWCz1.cjs.js} +6 -6
- package/dist/cjs/{buildWorkspace-C4wCq5WM.cjs.js → buildWorkspace-CZPp9oRm.cjs.js} +7 -6
- package/dist/cjs/{bump-BQ6YRL6D.cjs.js → bump-BHEh5ytx.cjs.js} +73 -9
- package/dist/cjs/{clean-BllZTky1.cjs.js → clean-W6nxsHeK.cjs.js} +2 -2
- package/dist/cjs/{clean-CVl--Ec9.cjs.js → clean-a6Q4k9Vm.cjs.js} +2 -2
- package/dist/cjs/{config-BMsjTUVf.cjs.js → config-DBpmZirN.cjs.js} +2 -2
- package/dist/cjs/{createDistWorkspace-DxOWPD6z.cjs.js → createDistWorkspace-DdHPGSMS.cjs.js} +8 -6
- package/dist/cjs/{docs-DPzCP6Jp.cjs.js → docs-BGyA6jwW.cjs.js} +3 -3
- package/dist/cjs/{entryPoints-CoHH4lBA.cjs.js → entryPoints-coip0t-x.cjs.js} +4 -1
- package/dist/cjs/{fix-BM8I_DZ2.cjs.js → fix-COitqgqm.cjs.js} +2 -2
- package/dist/cjs/{index-CXG8-26G.cjs.js → index-BXv4Xa2e.cjs.js} +15 -12
- package/dist/cjs/{index-Cw0lUK20.cjs.js → index-CGuAP7nv.cjs.js} +11 -10
- package/dist/cjs/{index-DmUbBCFk.cjs.js → index-b1ouG3q6.cjs.js} +62 -82
- package/dist/cjs/{index-BgEQ8aeF.cjs.js → index-j193pV_Y.cjs.js} +2 -2
- package/dist/cjs/{info-FKrzx-9W.cjs.js → info-DuAv1Tsx.cjs.js} +5 -6
- package/dist/cjs/{lint-6vrbdjyg.cjs.js → lint-BwiDJkjE.cjs.js} +2 -2
- package/dist/cjs/{lint-Cm_9pg85.cjs.js → lint-Dsiocf9K.cjs.js} +2 -2
- package/dist/cjs/{list-deprecations-C6R3rEyS.cjs.js → list-deprecations-CtUaQgaP.cjs.js} +2 -2
- package/dist/cjs/{moduleFederation-8XXecxLD.cjs.js → moduleFederation-DmStnvEg.cjs.js} +4 -4
- package/dist/cjs/{new-DJUBFwiF.cjs.js → new-CEnFhTT-.cjs.js} +336 -97
- package/dist/cjs/{pack-BqFGqZb5.cjs.js → pack-XLRcGJqH.cjs.js} +5 -4
- package/dist/cjs/{packageExports-DvjdOWjC.cjs.js → packageExports-BJBwdvUH.cjs.js} +3 -3
- package/dist/cjs/{packageLintConfigs-DGkvTpBd.cjs.js → packageLintConfigs-DeUGBP17.cjs.js} +3 -3
- package/dist/cjs/{packageRole-CHz7zkIQ.cjs.js → packageRole-Iuv9NRii.cjs.js} +2 -2
- package/dist/cjs/{print-CQU7JzAh.cjs.js → print-Dd6aChXU.cjs.js} +3 -3
- package/dist/cjs/{productionPack-BWU8WkGs.cjs.js → productionPack-BxoMbBkH.cjs.js} +106 -5
- package/dist/cjs/{role-8b0z7P0n.cjs.js → role-BjiBExhi.cjs.js} +2 -2
- package/dist/cjs/{run-CSt1n0F1.cjs.js → run-CpZGNJQr.cjs.js} +2 -2
- package/dist/cjs/{schema-B4250t0W.cjs.js → schema-D93FRhBL.cjs.js} +3 -3
- package/dist/cjs/{test-Diil1uTk.cjs.js → test-COxIko8N.cjs.js} +3 -3
- package/dist/cjs/{test-DVUsmgyZ.cjs.js → test-JcLI2pPM.cjs.js} +3 -3
- package/dist/cjs/{validate-DDIGkK2r.cjs.js → validate-CELljsEX.cjs.js} +3 -3
- package/dist/cjs/{Lockfile-B4mqBkH6.cjs.js → yarn-6FNAgNBK.cjs.js} +31 -1
- package/dist/index.cjs.js +1 -1
- package/package.json +21 -22
- package/dist/cjs/codeowners-FKKtpciN.cjs.js +0 -91
- package/dist/cjs/createPlugin-Dj7O_us6.cjs.js +0 -280
- package/dist/cjs/diff-Drt115Zb.cjs.js +0 -436
- package/dist/cjs/index-DRp-18FB.cjs.js +0 -1027
- package/dist/cjs/install-BMA3RshT.cjs.js +0 -268
- package/dist/cjs/lint-Dkx_fBkS.cjs.js +0 -10
- package/dist/cjs/packages-Cuogjl7j.cjs.js +0 -75
- package/dist/cjs/tasks-DtAiMv5G.cjs.js +0 -188
- package/dist/cjs/yarn-Ukl9MOS0.cjs.js +0 -34
|
@@ -1,1027 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chalk = require('chalk');
|
|
4
|
-
var inquirer = require('inquirer');
|
|
5
|
-
var tasks = require('./tasks-DtAiMv5G.cjs.js');
|
|
6
|
-
var oauthApp = require('@octokit/oauth-app');
|
|
7
|
-
var fs = require('fs-extra');
|
|
8
|
-
var yaml = require('yaml');
|
|
9
|
-
var cliCommon = require('@backstage/cli-common');
|
|
10
|
-
var path = require('path');
|
|
11
|
-
var differ = require('diff');
|
|
12
|
-
var config = require('./config-BMsjTUVf.cjs.js');
|
|
13
|
-
var catalogModel = require('@backstage/catalog-model');
|
|
14
|
-
var z = require('zod');
|
|
15
|
-
var integration = require('@backstage/integration');
|
|
16
|
-
var graphql = require('@octokit/graphql');
|
|
17
|
-
var parseGitUrl = require('git-url-parse');
|
|
18
|
-
var fetch = require('node-fetch');
|
|
19
|
-
require('handlebars');
|
|
20
|
-
require('ora');
|
|
21
|
-
require('util');
|
|
22
|
-
require('recursive-readdir');
|
|
23
|
-
require('child_process');
|
|
24
|
-
require('@backstage/errors');
|
|
25
|
-
require('./index-DmUbBCFk.cjs.js');
|
|
26
|
-
require('commander');
|
|
27
|
-
require('semver');
|
|
28
|
-
require('@backstage/config-loader');
|
|
29
|
-
require('@backstage/config');
|
|
30
|
-
require('@manypkg/get-packages');
|
|
31
|
-
require('@backstage/cli-node');
|
|
32
|
-
|
|
33
|
-
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
34
|
-
|
|
35
|
-
function _interopNamespaceCompat(e) {
|
|
36
|
-
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
37
|
-
var n = Object.create(null);
|
|
38
|
-
if (e) {
|
|
39
|
-
Object.keys(e).forEach(function (k) {
|
|
40
|
-
if (k !== 'default') {
|
|
41
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
42
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
43
|
-
enumerable: true,
|
|
44
|
-
get: function () { return e[k]; }
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
n.default = e;
|
|
50
|
-
return Object.freeze(n);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
var chalk__default = /*#__PURE__*/_interopDefaultCompat(chalk);
|
|
54
|
-
var inquirer__default = /*#__PURE__*/_interopDefaultCompat(inquirer);
|
|
55
|
-
var fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs);
|
|
56
|
-
var yaml__default = /*#__PURE__*/_interopDefaultCompat(yaml);
|
|
57
|
-
var path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
|
|
58
|
-
var differ__namespace = /*#__PURE__*/_interopNamespaceCompat(differ);
|
|
59
|
-
var z__default = /*#__PURE__*/_interopDefaultCompat(z);
|
|
60
|
-
var parseGitUrl__default = /*#__PURE__*/_interopDefaultCompat(parseGitUrl);
|
|
61
|
-
var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
|
|
62
|
-
|
|
63
|
-
const readYaml = async (file) => {
|
|
64
|
-
return yaml__default.default.parse(await fs__namespace.readFile(file, "utf8"));
|
|
65
|
-
};
|
|
66
|
-
const updateConfigFile = async (file, config) => {
|
|
67
|
-
const staticContent = "# Backstage override configuration for your local development environment \n";
|
|
68
|
-
const content = fs__namespace.existsSync(file) ? yaml__default.default.stringify(
|
|
69
|
-
{ ...await readYaml(file), ...config },
|
|
70
|
-
{
|
|
71
|
-
indent: 2
|
|
72
|
-
}
|
|
73
|
-
) : staticContent.concat(
|
|
74
|
-
yaml__default.default.stringify(
|
|
75
|
-
{ ...config },
|
|
76
|
-
{
|
|
77
|
-
indent: 2
|
|
78
|
-
}
|
|
79
|
-
)
|
|
80
|
-
);
|
|
81
|
-
return await fs__namespace.writeFile(file, content, "utf8");
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const { targetRoot: targetRoot$1, ownDir } = cliCommon.findPaths(__dirname);
|
|
85
|
-
const APP_CONFIG_FILE = path__namespace.join(targetRoot$1, "app-config.local.yaml");
|
|
86
|
-
const DISCOVERED_ENTITIES_FILE = path__namespace.join(
|
|
87
|
-
targetRoot$1,
|
|
88
|
-
"examples",
|
|
89
|
-
"discovered-entities.yaml"
|
|
90
|
-
);
|
|
91
|
-
const PATCH_FOLDER = path__namespace.join(
|
|
92
|
-
ownDir,
|
|
93
|
-
"src",
|
|
94
|
-
"commands",
|
|
95
|
-
"onboard",
|
|
96
|
-
"auth",
|
|
97
|
-
"patches"
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
const { targetRoot } = cliCommon.findPaths(__dirname);
|
|
101
|
-
const patch = async (patchFile) => {
|
|
102
|
-
const patchContent = await fs__namespace.readFile(
|
|
103
|
-
path__namespace.join(PATCH_FOLDER, patchFile),
|
|
104
|
-
"utf8"
|
|
105
|
-
);
|
|
106
|
-
const targetName = patchContent.split("\n")[0].replace("--- a", "");
|
|
107
|
-
const targetFile = path__namespace.join(targetRoot, targetName);
|
|
108
|
-
const oldContent = await fs__namespace.readFile(targetFile, "utf8");
|
|
109
|
-
const newContent = differ__namespace.applyPatch(oldContent, patchContent);
|
|
110
|
-
if (!newContent) {
|
|
111
|
-
throw new Error(
|
|
112
|
-
`Patch ${patchFile} was not applied correctly.
|
|
113
|
-
Did you change ${targetName} manually before running this command?`
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
return await fs__namespace.writeFile(targetFile, newContent, "utf8");
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const validateCredentials = async (clientId, clientSecret) => {
|
|
120
|
-
try {
|
|
121
|
-
const app = new oauthApp.OAuthApp({
|
|
122
|
-
clientId,
|
|
123
|
-
clientSecret
|
|
124
|
-
});
|
|
125
|
-
await app.createToken({
|
|
126
|
-
code: "%NOT-VALID-CODE%"
|
|
127
|
-
});
|
|
128
|
-
} catch (error) {
|
|
129
|
-
if (error.response.status !== 200 && error.response.data.error !== "bad_verification_code") {
|
|
130
|
-
throw new Error(`Validating GitHub Credentials failed.`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
const getConfig$2 = (answers) => {
|
|
135
|
-
const { clientId, clientSecret, hasEnterprise, enterpriseInstanceUrl } = answers;
|
|
136
|
-
return {
|
|
137
|
-
auth: {
|
|
138
|
-
providers: {
|
|
139
|
-
github: {
|
|
140
|
-
development: {
|
|
141
|
-
clientId,
|
|
142
|
-
clientSecret,
|
|
143
|
-
...hasEnterprise && {
|
|
144
|
-
enterpriseInstanceUrl
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
};
|
|
152
|
-
const github$1 = async () => {
|
|
153
|
-
tasks.Task.log(`
|
|
154
|
-
To add GitHub authentication, you must create an OAuth App from the GitHub developer settings: ${chalk__default.default.blue(
|
|
155
|
-
"https://github.com/settings/developers"
|
|
156
|
-
)}
|
|
157
|
-
The Homepage URL should point to Backstage's frontend, while the Authorization callback URL will point to the auth backend.
|
|
158
|
-
|
|
159
|
-
Settings for local development:
|
|
160
|
-
${chalk__default.default.cyan(`
|
|
161
|
-
Homepage URL: http://localhost:3000
|
|
162
|
-
Authorization callback URL: http://localhost:7007/api/auth/github/handler/frame`)}
|
|
163
|
-
|
|
164
|
-
You can find the full documentation page here: ${chalk__default.default.blue(
|
|
165
|
-
"https://backstage.io/docs/auth/github/provider"
|
|
166
|
-
)}
|
|
167
|
-
`);
|
|
168
|
-
const answers = await inquirer__default.default.prompt([
|
|
169
|
-
{
|
|
170
|
-
type: "input",
|
|
171
|
-
name: "clientId",
|
|
172
|
-
message: "What is your Client Id?",
|
|
173
|
-
validate: (input) => input.length ? true : false
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
type: "input",
|
|
177
|
-
name: "clientSecret",
|
|
178
|
-
message: "What is your Client Secret?",
|
|
179
|
-
validate: (input) => input.length ? true : false
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
type: "confirm",
|
|
183
|
-
name: "hasEnterprise",
|
|
184
|
-
message: "Are you using GitHub Enterprise?"
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
type: "input",
|
|
188
|
-
name: "enterpriseInstanceUrl",
|
|
189
|
-
message: "What is your URL for GitHub Enterprise?",
|
|
190
|
-
when: ({ hasEnterprise }) => hasEnterprise,
|
|
191
|
-
validate: (input) => Boolean(new URL(input))
|
|
192
|
-
}
|
|
193
|
-
]);
|
|
194
|
-
const { clientId, clientSecret } = answers;
|
|
195
|
-
const config = getConfig$2(answers);
|
|
196
|
-
tasks.Task.log("Setting up GitHub Authentication for you...");
|
|
197
|
-
await tasks.Task.forItem(
|
|
198
|
-
"Validating",
|
|
199
|
-
"credentials",
|
|
200
|
-
async () => await validateCredentials(clientId, clientSecret)
|
|
201
|
-
);
|
|
202
|
-
await tasks.Task.forItem(
|
|
203
|
-
"Updating",
|
|
204
|
-
APP_CONFIG_FILE,
|
|
205
|
-
async () => await updateConfigFile(APP_CONFIG_FILE, config)
|
|
206
|
-
);
|
|
207
|
-
const patches = await fs__namespace.readdir(PATCH_FOLDER);
|
|
208
|
-
for (const patchFile of patches.filter((p) => p.includes("github"))) {
|
|
209
|
-
await tasks.Task.forItem("Patching", patchFile, async () => {
|
|
210
|
-
await patch(patchFile);
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
return answers;
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const getConfig$1 = (answers) => {
|
|
217
|
-
const { clientId, clientSecret, hasAudience, audience } = answers;
|
|
218
|
-
return {
|
|
219
|
-
auth: {
|
|
220
|
-
providers: {
|
|
221
|
-
gitlab: {
|
|
222
|
-
development: {
|
|
223
|
-
clientId,
|
|
224
|
-
clientSecret,
|
|
225
|
-
...hasAudience && {
|
|
226
|
-
audience
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
};
|
|
234
|
-
const gitlab = async () => {
|
|
235
|
-
tasks.Task.log(`
|
|
236
|
-
To add GitLab authentication, you must create an Application from the GitLab Settings: ${chalk__default.default.blue(
|
|
237
|
-
"https://gitlab.com/-/profile/applications"
|
|
238
|
-
)}
|
|
239
|
-
The Redirect URI should point to your Backstage backend auth handler.
|
|
240
|
-
|
|
241
|
-
Settings for local development:
|
|
242
|
-
${chalk__default.default.cyan(`
|
|
243
|
-
Name: Backstage (or your custom app name)
|
|
244
|
-
Redirect URI: http://localhost:7007/api/auth/gitlab/handler/frame
|
|
245
|
-
Scopes: read_api and read_user`)}
|
|
246
|
-
|
|
247
|
-
You can find the full documentation page here: ${chalk__default.default.blue(
|
|
248
|
-
"https://backstage.io/docs/auth/gitlab/provider"
|
|
249
|
-
)}
|
|
250
|
-
`);
|
|
251
|
-
const answers = await inquirer__default.default.prompt([
|
|
252
|
-
{
|
|
253
|
-
type: "input",
|
|
254
|
-
name: "clientId",
|
|
255
|
-
message: "What is your Application Id?",
|
|
256
|
-
validate: (input) => input.length ? true : false
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
type: "input",
|
|
260
|
-
name: "clientSecret",
|
|
261
|
-
message: "What is your Application Secret?",
|
|
262
|
-
validate: (input) => input.length ? true : false
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
type: "confirm",
|
|
266
|
-
name: "hasAudience",
|
|
267
|
-
message: "Do you have a self-hosted instance of GitLab?"
|
|
268
|
-
},
|
|
269
|
-
{
|
|
270
|
-
type: "input",
|
|
271
|
-
name: "audience",
|
|
272
|
-
message: "What is the URL for your GitLab instance?",
|
|
273
|
-
when: ({ hasAudience }) => hasAudience,
|
|
274
|
-
validate: (input) => Boolean(new URL(input))
|
|
275
|
-
}
|
|
276
|
-
]);
|
|
277
|
-
const config = getConfig$1(answers);
|
|
278
|
-
tasks.Task.log("Setting up GitLab Authentication for you...");
|
|
279
|
-
await tasks.Task.forItem(
|
|
280
|
-
"Updating",
|
|
281
|
-
APP_CONFIG_FILE,
|
|
282
|
-
async () => await updateConfigFile(APP_CONFIG_FILE, config)
|
|
283
|
-
);
|
|
284
|
-
const patches = await fs__namespace.readdir(PATCH_FOLDER);
|
|
285
|
-
for (const patchFile of patches.filter((p) => p.includes("gitlab"))) {
|
|
286
|
-
await tasks.Task.forItem("Patching", patchFile, async () => {
|
|
287
|
-
await patch(patchFile);
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
return answers;
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
async function auth() {
|
|
294
|
-
const answers = await inquirer__default.default.prompt([
|
|
295
|
-
{
|
|
296
|
-
type: "list",
|
|
297
|
-
name: "provider",
|
|
298
|
-
message: "Please select an authentication provider:",
|
|
299
|
-
choices: ["GitHub", "GitLab"]
|
|
300
|
-
}
|
|
301
|
-
]);
|
|
302
|
-
const { provider } = answers;
|
|
303
|
-
let providerAnswers;
|
|
304
|
-
switch (provider) {
|
|
305
|
-
case "GitHub": {
|
|
306
|
-
providerAnswers = await github$1();
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
case "GitLab": {
|
|
310
|
-
providerAnswers = await gitlab();
|
|
311
|
-
break;
|
|
312
|
-
}
|
|
313
|
-
default:
|
|
314
|
-
throw new Error(`Provider ${provider} not implemented yet.`);
|
|
315
|
-
}
|
|
316
|
-
tasks.Task.log();
|
|
317
|
-
tasks.Task.log(`Done setting up ${provider} Authentication!`);
|
|
318
|
-
tasks.Task.log();
|
|
319
|
-
return {
|
|
320
|
-
provider,
|
|
321
|
-
answers: providerAnswers
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const getConfig = ({
|
|
326
|
-
hasEnterprise,
|
|
327
|
-
apiBaseUrl,
|
|
328
|
-
host,
|
|
329
|
-
token
|
|
330
|
-
}) => ({
|
|
331
|
-
integrations: {
|
|
332
|
-
github: [
|
|
333
|
-
{
|
|
334
|
-
host,
|
|
335
|
-
token,
|
|
336
|
-
...hasEnterprise && {
|
|
337
|
-
apiBaseUrl
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
]
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
const github = async (providerAnswers) => {
|
|
344
|
-
const answers = await inquirer__default.default.prompt([
|
|
345
|
-
{
|
|
346
|
-
type: "confirm",
|
|
347
|
-
name: "hasEnterprise",
|
|
348
|
-
message: "Are you using GitHub Enterprise?",
|
|
349
|
-
when: () => typeof providerAnswers === "undefined"
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
type: "input",
|
|
353
|
-
name: "enterpriseInstanceUrl",
|
|
354
|
-
message: "What is your URL for GitHub Enterprise?",
|
|
355
|
-
when: ({ hasEnterprise }) => hasEnterprise,
|
|
356
|
-
validate: (input) => Boolean(new URL(input))
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
type: "input",
|
|
360
|
-
name: "apiBaseUrl",
|
|
361
|
-
message: "What is your GitHub Enterprise API path?",
|
|
362
|
-
default: "/api/v3",
|
|
363
|
-
when: ({ hasEnterprise }) => hasEnterprise || providerAnswers?.hasEnterprise
|
|
364
|
-
// TODO(tudi2d): Fetch API using OAuth Token if Auth was set up
|
|
365
|
-
}
|
|
366
|
-
]);
|
|
367
|
-
const host = new URL(
|
|
368
|
-
providerAnswers?.enterpriseInstanceUrl ?? answers?.enterpriseInstanceUrl ?? "http://github.com"
|
|
369
|
-
);
|
|
370
|
-
tasks.Task.log(`
|
|
371
|
-
To create new repositories in GitHub using Software Templates you first need to create a personal access token: ${chalk__default.default.blue(
|
|
372
|
-
`${host.origin}/settings/tokens/new`
|
|
373
|
-
)}
|
|
374
|
-
|
|
375
|
-
Select the following scopes:
|
|
376
|
-
|
|
377
|
-
Reading software components:${chalk__default.default.cyan(`
|
|
378
|
-
- "repo"`)}
|
|
379
|
-
|
|
380
|
-
Reading organization data:${chalk__default.default.cyan(`
|
|
381
|
-
- "read:org"
|
|
382
|
-
- "read:user"
|
|
383
|
-
- "user:email"`)}
|
|
384
|
-
|
|
385
|
-
Publishing software templates:${chalk__default.default.cyan(`
|
|
386
|
-
- "repo"
|
|
387
|
-
- "workflow" (if templates include GitHub workflows)
|
|
388
|
-
`)}
|
|
389
|
-
|
|
390
|
-
You can find the full documentation page here: ${chalk__default.default.blue(
|
|
391
|
-
"https://backstage.io/docs/integrations/github/locations"
|
|
392
|
-
)}
|
|
393
|
-
`);
|
|
394
|
-
const { token } = await inquirer__default.default.prompt([
|
|
395
|
-
{
|
|
396
|
-
type: "input",
|
|
397
|
-
name: "token",
|
|
398
|
-
message: "Please insert your personal access token to setup the GitHub Integration"
|
|
399
|
-
// TODO(tudi2d): validate
|
|
400
|
-
}
|
|
401
|
-
]);
|
|
402
|
-
const config = getConfig({
|
|
403
|
-
hasEnterprise: providerAnswers?.hasEnterprise ?? answers.hasEnterprise,
|
|
404
|
-
apiBaseUrl: host.origin + answers.apiBaseUrl,
|
|
405
|
-
host: host.hostname,
|
|
406
|
-
token
|
|
407
|
-
});
|
|
408
|
-
tasks.Task.log("Setting up Software Templates using GitHub integration for you...");
|
|
409
|
-
await tasks.Task.forItem(
|
|
410
|
-
"Updating",
|
|
411
|
-
APP_CONFIG_FILE,
|
|
412
|
-
async () => await updateConfigFile(APP_CONFIG_FILE, config)
|
|
413
|
-
);
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
const Integrations = ["GitHub" /* GITHUB */];
|
|
417
|
-
async function integrations(providerInfo) {
|
|
418
|
-
const answers = await inquirer__default.default.prompt([
|
|
419
|
-
{
|
|
420
|
-
type: "confirm",
|
|
421
|
-
name: "shouldUsePreviousProvider",
|
|
422
|
-
message: `Do you want to keep using ${providerInfo?.provider} as your provider when setting up Software Templates?`,
|
|
423
|
-
when: () => providerInfo?.provider && Object.values(Integrations).includes(
|
|
424
|
-
providerInfo.provider
|
|
425
|
-
)
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
// TODO(tudi2d): Let's start with one, but it should be multiple choice in the future
|
|
429
|
-
type: "list",
|
|
430
|
-
name: "integration",
|
|
431
|
-
message: "Please select an integration provider:",
|
|
432
|
-
choices: Integrations,
|
|
433
|
-
when: ({ shouldUsePreviousProvider }) => !shouldUsePreviousProvider
|
|
434
|
-
}
|
|
435
|
-
]);
|
|
436
|
-
if (answers.shouldUsePreviousProvider) {
|
|
437
|
-
answers.integration = providerInfo.provider;
|
|
438
|
-
}
|
|
439
|
-
switch (answers.integration) {
|
|
440
|
-
case "GitHub" /* GITHUB */: {
|
|
441
|
-
const providerAnswers = providerInfo?.provider === "GitHub" ? providerInfo.answers : void 0;
|
|
442
|
-
await github(providerAnswers);
|
|
443
|
-
break;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
tasks.Task.log();
|
|
447
|
-
tasks.Task.log(`Done setting up ${answers.integration} Integration!`);
|
|
448
|
-
tasks.Task.log();
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
class DefaultAnalysisOutputs {
|
|
452
|
-
#outputs = /* @__PURE__ */ new Map();
|
|
453
|
-
produce(output) {
|
|
454
|
-
this.#outputs.set(output.entity.metadata.name, output);
|
|
455
|
-
}
|
|
456
|
-
list() {
|
|
457
|
-
return Array.from(this.#outputs).map(([_, output]) => output);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
class Discovery {
|
|
462
|
-
#providers = [];
|
|
463
|
-
#analyzers = [];
|
|
464
|
-
addProvider(provider) {
|
|
465
|
-
this.#providers.push(provider);
|
|
466
|
-
}
|
|
467
|
-
addAnalyzer(analyzer) {
|
|
468
|
-
this.#analyzers.push(analyzer);
|
|
469
|
-
}
|
|
470
|
-
async run(url) {
|
|
471
|
-
tasks.Task.log(`Running discovery for ${chalk__default.default.cyan(url)}`);
|
|
472
|
-
const result = [];
|
|
473
|
-
for (const provider of this.#providers) {
|
|
474
|
-
const repositories = await provider.discover(url);
|
|
475
|
-
if (repositories && repositories.length) {
|
|
476
|
-
tasks.Task.log(
|
|
477
|
-
`Discovered ${chalk__default.default.cyan(
|
|
478
|
-
repositories.length
|
|
479
|
-
)} repositories for ${chalk__default.default.cyan(provider.name())}`
|
|
480
|
-
);
|
|
481
|
-
for (const repository of repositories) {
|
|
482
|
-
await tasks.Task.forItem("Analyzing", repository.name, async () => {
|
|
483
|
-
const output = new DefaultAnalysisOutputs();
|
|
484
|
-
for (const analyzer of this.#analyzers) {
|
|
485
|
-
await analyzer.analyzeRepository({ repository, output });
|
|
486
|
-
}
|
|
487
|
-
output.list().filter((entry) => entry.type === "entity").forEach(({ entity }) => result.push(entity));
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
tasks.Task.log(`Produced ${chalk__default.default.cyan(result.length || "no")} entities`);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
return {
|
|
494
|
-
entities: result
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
class BasicRepositoryAnalyzer {
|
|
500
|
-
name() {
|
|
501
|
-
return BasicRepositoryAnalyzer.name;
|
|
502
|
-
}
|
|
503
|
-
async analyzeRepository(options) {
|
|
504
|
-
const entity = {
|
|
505
|
-
apiVersion: "backstage.io/v1alpha1",
|
|
506
|
-
kind: "Component",
|
|
507
|
-
metadata: {
|
|
508
|
-
name: options.repository.name,
|
|
509
|
-
...options.repository.description ? { description: options.repository.description } : {}
|
|
510
|
-
},
|
|
511
|
-
spec: {
|
|
512
|
-
type: "service",
|
|
513
|
-
lifecycle: "production",
|
|
514
|
-
owner: "user:guest"
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
options.output.produce({
|
|
518
|
-
type: "entity",
|
|
519
|
-
path: "/",
|
|
520
|
-
entity
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
class PackageJsonAnalyzer {
|
|
526
|
-
name() {
|
|
527
|
-
return PackageJsonAnalyzer.name;
|
|
528
|
-
}
|
|
529
|
-
async analyzeRepository(options) {
|
|
530
|
-
const packageJson = await options.repository.file("package.json");
|
|
531
|
-
if (!packageJson) {
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
const content = await readPackageJson(packageJson);
|
|
535
|
-
if (!content) {
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
const name = sanitizeName(content?.name) ?? options.repository.name;
|
|
539
|
-
const entity = {
|
|
540
|
-
apiVersion: "backstage.io/v1alpha1",
|
|
541
|
-
kind: "Component",
|
|
542
|
-
metadata: {
|
|
543
|
-
name,
|
|
544
|
-
...options.repository.description ? { description: options.repository.description } : {},
|
|
545
|
-
tags: ["javascript"],
|
|
546
|
-
annotations: {
|
|
547
|
-
[catalogModel.ANNOTATION_SOURCE_LOCATION]: `url:${options.repository.url}`
|
|
548
|
-
}
|
|
549
|
-
},
|
|
550
|
-
spec: {
|
|
551
|
-
type: "website",
|
|
552
|
-
lifecycle: "production",
|
|
553
|
-
owner: "user:guest"
|
|
554
|
-
}
|
|
555
|
-
};
|
|
556
|
-
const decorate = options.output.list().find((entry) => entry.entity.metadata.name === name);
|
|
557
|
-
if (decorate) {
|
|
558
|
-
decorate.entity.spec = {
|
|
559
|
-
...decorate.entity.spec,
|
|
560
|
-
type: "website"
|
|
561
|
-
};
|
|
562
|
-
decorate.entity.metadata.tags = [
|
|
563
|
-
...decorate.entity.metadata.tags ?? [],
|
|
564
|
-
"javascript"
|
|
565
|
-
];
|
|
566
|
-
decorate.entity.metadata.annotations = {
|
|
567
|
-
...decorate.entity.metadata.annotations,
|
|
568
|
-
[catalogModel.ANNOTATION_SOURCE_LOCATION]: `url:${options.repository.url}`
|
|
569
|
-
};
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
options.output.produce({
|
|
573
|
-
type: "entity",
|
|
574
|
-
path: "/",
|
|
575
|
-
entity
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
const packageSchema = z__default.default.object({
|
|
580
|
-
name: z__default.default.string().optional()
|
|
581
|
-
});
|
|
582
|
-
function sanitizeName(name) {
|
|
583
|
-
return name && name !== "root" ? name.replace(/[^a-z0-9A-Z]/g, "_").substring(0, 62) : void 0;
|
|
584
|
-
}
|
|
585
|
-
async function readPackageJson(file) {
|
|
586
|
-
try {
|
|
587
|
-
const text = await file.text();
|
|
588
|
-
const result = packageSchema.safeParse(JSON.parse(text));
|
|
589
|
-
if (!result.success) {
|
|
590
|
-
return void 0;
|
|
591
|
-
}
|
|
592
|
-
return { name: result.data.name };
|
|
593
|
-
} catch (e) {
|
|
594
|
-
return void 0;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
class GithubFile {
|
|
599
|
-
#path;
|
|
600
|
-
#content;
|
|
601
|
-
constructor(path, content) {
|
|
602
|
-
this.#path = path;
|
|
603
|
-
this.#content = content;
|
|
604
|
-
}
|
|
605
|
-
get path() {
|
|
606
|
-
return this.#path;
|
|
607
|
-
}
|
|
608
|
-
async text() {
|
|
609
|
-
return this.#content;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
class GithubRepository {
|
|
614
|
-
#client;
|
|
615
|
-
#repo;
|
|
616
|
-
#org;
|
|
617
|
-
constructor(client, repo, org) {
|
|
618
|
-
this.#client = client;
|
|
619
|
-
this.#repo = repo;
|
|
620
|
-
this.#org = org;
|
|
621
|
-
}
|
|
622
|
-
get url() {
|
|
623
|
-
return this.#repo.url;
|
|
624
|
-
}
|
|
625
|
-
get name() {
|
|
626
|
-
return this.#repo.name;
|
|
627
|
-
}
|
|
628
|
-
get owner() {
|
|
629
|
-
return this.#org;
|
|
630
|
-
}
|
|
631
|
-
get description() {
|
|
632
|
-
return this.#repo.description ?? void 0;
|
|
633
|
-
}
|
|
634
|
-
async file(filename) {
|
|
635
|
-
const content = await this.#getFileContent(filename);
|
|
636
|
-
if (!content || content.isBinary || !content.text) {
|
|
637
|
-
return void 0;
|
|
638
|
-
}
|
|
639
|
-
return new GithubFile(filename, content.text ?? "");
|
|
640
|
-
}
|
|
641
|
-
async #getFileContent(filename) {
|
|
642
|
-
const query = `query RepoFiles($owner: String!, $name: String!, $expr: String!) {
|
|
643
|
-
repository(owner: $owner, name: $name) {
|
|
644
|
-
object(expression: $expr) {
|
|
645
|
-
...on Blob {
|
|
646
|
-
text
|
|
647
|
-
isBinary
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}`;
|
|
652
|
-
const response = await this.#client(
|
|
653
|
-
query,
|
|
654
|
-
{
|
|
655
|
-
name: this.#repo.name,
|
|
656
|
-
owner: this.#org,
|
|
657
|
-
expr: `HEAD:${filename}`
|
|
658
|
-
}
|
|
659
|
-
);
|
|
660
|
-
return response.repository.object;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
class GithubDiscoveryProvider {
|
|
665
|
-
#envToken;
|
|
666
|
-
#scmIntegrations;
|
|
667
|
-
#credentialsProvider;
|
|
668
|
-
static fromConfig(config) {
|
|
669
|
-
const envToken = process.env.GITHUB_TOKEN || void 0;
|
|
670
|
-
const scmIntegrations = integration.ScmIntegrations.fromConfig(config);
|
|
671
|
-
const credentialsProvider = integration.DefaultGithubCredentialsProvider.fromIntegrations(scmIntegrations);
|
|
672
|
-
return new GithubDiscoveryProvider(
|
|
673
|
-
envToken,
|
|
674
|
-
scmIntegrations,
|
|
675
|
-
credentialsProvider
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
constructor(envToken, integrations, credentialsProvider) {
|
|
679
|
-
this.#envToken = envToken;
|
|
680
|
-
this.#scmIntegrations = integrations;
|
|
681
|
-
this.#credentialsProvider = credentialsProvider;
|
|
682
|
-
}
|
|
683
|
-
name() {
|
|
684
|
-
return "GitHub";
|
|
685
|
-
}
|
|
686
|
-
async discover(url) {
|
|
687
|
-
if (!url.startsWith("https://github.com/")) {
|
|
688
|
-
return false;
|
|
689
|
-
}
|
|
690
|
-
const scmIntegration = this.#scmIntegrations.github.byUrl(url);
|
|
691
|
-
if (!scmIntegration) {
|
|
692
|
-
throw new Error(`No GitHub integration found for ${url}`);
|
|
693
|
-
}
|
|
694
|
-
const parsed = parseGitUrl__default.default(url);
|
|
695
|
-
const { name, organization } = parsed;
|
|
696
|
-
const org = organization || name;
|
|
697
|
-
const client = graphql.graphql.defaults({
|
|
698
|
-
baseUrl: scmIntegration.config.apiBaseUrl,
|
|
699
|
-
headers: await this.#getRequestHeaders(url)
|
|
700
|
-
});
|
|
701
|
-
const { repositories } = await this.#getOrganizationRepositories(
|
|
702
|
-
client,
|
|
703
|
-
org
|
|
704
|
-
);
|
|
705
|
-
return repositories.filter((repo) => repo.url.startsWith(url)).map((repo) => new GithubRepository(client, repo, org));
|
|
706
|
-
}
|
|
707
|
-
async #getRequestHeaders(url) {
|
|
708
|
-
const credentials = await this.#credentialsProvider.getCredentials({
|
|
709
|
-
url
|
|
710
|
-
});
|
|
711
|
-
if (credentials.headers) {
|
|
712
|
-
return credentials.headers;
|
|
713
|
-
} else if (credentials.token) {
|
|
714
|
-
return { authorization: `token ${credentials.token}` };
|
|
715
|
-
}
|
|
716
|
-
if (this.#envToken) {
|
|
717
|
-
return { authorization: `token ${this.#envToken}` };
|
|
718
|
-
}
|
|
719
|
-
throw new Error(
|
|
720
|
-
"No token available for GitHub, please configure your integrations or set a GITHUB_TOKEN env variable"
|
|
721
|
-
);
|
|
722
|
-
}
|
|
723
|
-
async #getOrganizationRepositories(client, org) {
|
|
724
|
-
const query = `query repositories($org: String!, $cursor: String) {
|
|
725
|
-
repositoryOwner(login: $org) {
|
|
726
|
-
login
|
|
727
|
-
repositories(first: 50, after: $cursor) {
|
|
728
|
-
nodes {
|
|
729
|
-
name
|
|
730
|
-
url
|
|
731
|
-
description
|
|
732
|
-
isArchived
|
|
733
|
-
isFork
|
|
734
|
-
}
|
|
735
|
-
pageInfo {
|
|
736
|
-
hasNextPage
|
|
737
|
-
endCursor
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
}`;
|
|
742
|
-
const result = [];
|
|
743
|
-
let cursor = void 0;
|
|
744
|
-
let hasNextPage = true;
|
|
745
|
-
while (hasNextPage) {
|
|
746
|
-
const response = await client(query, {
|
|
747
|
-
org,
|
|
748
|
-
cursor
|
|
749
|
-
});
|
|
750
|
-
const { repositories: connection } = response.repositoryOwner ?? {};
|
|
751
|
-
if (!connection) {
|
|
752
|
-
throw new Error(`Found no repositories for ${org}`);
|
|
753
|
-
}
|
|
754
|
-
for (const repository of connection.nodes ?? []) {
|
|
755
|
-
if (repository && !repository.isArchived && !repository.isFork) {
|
|
756
|
-
result.push(repository);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
cursor = connection.pageInfo.endCursor;
|
|
760
|
-
hasNextPage = connection.pageInfo.hasNextPage;
|
|
761
|
-
}
|
|
762
|
-
return {
|
|
763
|
-
repositories: result
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
class GitlabFile {
|
|
769
|
-
#path;
|
|
770
|
-
#content;
|
|
771
|
-
constructor(path, content) {
|
|
772
|
-
this.#path = path;
|
|
773
|
-
this.#content = content;
|
|
774
|
-
}
|
|
775
|
-
get path() {
|
|
776
|
-
return this.#path;
|
|
777
|
-
}
|
|
778
|
-
async text() {
|
|
779
|
-
return this.#content;
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
class GitlabProject {
|
|
784
|
-
constructor(project, apiBaseUrl, headers) {
|
|
785
|
-
this.project = project;
|
|
786
|
-
this.apiBaseUrl = apiBaseUrl;
|
|
787
|
-
this.headers = headers;
|
|
788
|
-
}
|
|
789
|
-
get url() {
|
|
790
|
-
return this.project.web_url;
|
|
791
|
-
}
|
|
792
|
-
get name() {
|
|
793
|
-
return this.project.name;
|
|
794
|
-
}
|
|
795
|
-
get owner() {
|
|
796
|
-
return this.project.owner.username;
|
|
797
|
-
}
|
|
798
|
-
get description() {
|
|
799
|
-
return this.project.description;
|
|
800
|
-
}
|
|
801
|
-
async file(filename) {
|
|
802
|
-
const mainBranch = await this.#getMainBranch();
|
|
803
|
-
const content = await this.#getFileContent(filename, mainBranch);
|
|
804
|
-
return new GitlabFile(filename, content);
|
|
805
|
-
}
|
|
806
|
-
async #getFileContent(path, mainBranch) {
|
|
807
|
-
const response = await fetch__default.default(
|
|
808
|
-
`${this.apiBaseUrl}/projects/${this.project.id}/repository/files/${path}?ref=${mainBranch}`,
|
|
809
|
-
{ headers: this.headers }
|
|
810
|
-
);
|
|
811
|
-
const { content } = await response.json();
|
|
812
|
-
return Buffer.from(content, "base64").toString("ascii");
|
|
813
|
-
}
|
|
814
|
-
async #getMainBranch() {
|
|
815
|
-
const response = await fetch__default.default(
|
|
816
|
-
`${this.apiBaseUrl}/projects/${this.project.id}/repository/branches`,
|
|
817
|
-
{ headers: this.headers }
|
|
818
|
-
);
|
|
819
|
-
const branches = await response.json();
|
|
820
|
-
return branches.find((branch) => branch.default)?.name ?? "main";
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
class GitlabDiscoveryProvider {
|
|
825
|
-
#envToken;
|
|
826
|
-
#scmIntegrations;
|
|
827
|
-
#credentialsProvider;
|
|
828
|
-
static fromConfig(config) {
|
|
829
|
-
const envToken = process.env.GITLAB_TOKEN || void 0;
|
|
830
|
-
const scmIntegrations = integration.ScmIntegrations.fromConfig(config);
|
|
831
|
-
const credentialsProvider = integration.DefaultGitlabCredentialsProvider.fromIntegrations(scmIntegrations);
|
|
832
|
-
return new GitlabDiscoveryProvider(
|
|
833
|
-
envToken,
|
|
834
|
-
scmIntegrations,
|
|
835
|
-
credentialsProvider
|
|
836
|
-
);
|
|
837
|
-
}
|
|
838
|
-
constructor(envToken, integrations, credentialsProvider) {
|
|
839
|
-
this.#envToken = envToken;
|
|
840
|
-
this.#scmIntegrations = integrations;
|
|
841
|
-
this.#credentialsProvider = credentialsProvider;
|
|
842
|
-
}
|
|
843
|
-
name() {
|
|
844
|
-
return "GitLab";
|
|
845
|
-
}
|
|
846
|
-
async discover(url) {
|
|
847
|
-
const { origin, pathname } = new URL(url);
|
|
848
|
-
const [, user] = pathname.split("/");
|
|
849
|
-
const scmIntegration = this.#scmIntegrations.gitlab.byUrl(origin);
|
|
850
|
-
if (!scmIntegration) {
|
|
851
|
-
throw new Error(`No GitLab integration found for ${origin}`);
|
|
852
|
-
}
|
|
853
|
-
const headers = await this.#getRequestHeaders(origin);
|
|
854
|
-
const response = await fetch__default.default(
|
|
855
|
-
`${scmIntegration.config.apiBaseUrl}/users/${user}/projects`,
|
|
856
|
-
{ headers }
|
|
857
|
-
);
|
|
858
|
-
if (!response.ok) {
|
|
859
|
-
throw new Error(`${response.status} ${response.statusText}`);
|
|
860
|
-
}
|
|
861
|
-
const projects = await response.json();
|
|
862
|
-
return projects.map(
|
|
863
|
-
(project) => new GitlabProject(project, scmIntegration.config.apiBaseUrl, headers)
|
|
864
|
-
);
|
|
865
|
-
}
|
|
866
|
-
async #getRequestHeaders(url) {
|
|
867
|
-
const credentials = await this.#credentialsProvider.getCredentials({
|
|
868
|
-
url
|
|
869
|
-
});
|
|
870
|
-
if (credentials.headers) {
|
|
871
|
-
return credentials.headers;
|
|
872
|
-
} else if (credentials.token) {
|
|
873
|
-
return { authorization: `Bearer ${credentials.token}` };
|
|
874
|
-
}
|
|
875
|
-
if (this.#envToken) {
|
|
876
|
-
return { authorization: `Bearer ${this.#envToken}` };
|
|
877
|
-
}
|
|
878
|
-
throw new Error(
|
|
879
|
-
"No token available for GitLab, please set a GITLAB_TOKEN env variable"
|
|
880
|
-
);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
async function discover(providerInfo) {
|
|
885
|
-
tasks.Task.log(`
|
|
886
|
-
Would you like to scan for - and create - Software Catalog entities?
|
|
887
|
-
|
|
888
|
-
You will need to select which SCM (Source Code Management) provider you are using,
|
|
889
|
-
and then which repository or organization you want to scan.
|
|
890
|
-
|
|
891
|
-
This will generate a new file in the root of your app containing discovered entities,
|
|
892
|
-
which will be included in the Software Catalog when you start up Backstage next time.
|
|
893
|
-
|
|
894
|
-
Note that this command requires an access token, which can be either added through the integration config or
|
|
895
|
-
provided as an environment variable.
|
|
896
|
-
`);
|
|
897
|
-
const answers = await inquirer__default.default.prompt([
|
|
898
|
-
{
|
|
899
|
-
type: "confirm",
|
|
900
|
-
name: "shouldContinue",
|
|
901
|
-
message: "Do you want to continue?"
|
|
902
|
-
},
|
|
903
|
-
{
|
|
904
|
-
type: "list",
|
|
905
|
-
name: "provider",
|
|
906
|
-
message: "Please select which SCM provider you want to use:",
|
|
907
|
-
choices: ["GitHub", "GitLab"],
|
|
908
|
-
default: providerInfo?.provider,
|
|
909
|
-
when: ({ shouldContinue }) => shouldContinue
|
|
910
|
-
},
|
|
911
|
-
{
|
|
912
|
-
type: "input",
|
|
913
|
-
name: "url",
|
|
914
|
-
message: `Which repository do you want to scan?`,
|
|
915
|
-
when: ({ shouldContinue }) => shouldContinue,
|
|
916
|
-
filter: (input, { provider }) => {
|
|
917
|
-
if (provider === "GitLab") {
|
|
918
|
-
return `https://gitlab.com/${input}`;
|
|
919
|
-
}
|
|
920
|
-
if (provider === "GitHub") {
|
|
921
|
-
return `https://github.com/${input}`;
|
|
922
|
-
}
|
|
923
|
-
return false;
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
]);
|
|
927
|
-
if (!answers.shouldContinue) {
|
|
928
|
-
tasks.Task.log(
|
|
929
|
-
chalk__default.default.yellow(
|
|
930
|
-
"If you change your mind, feel free to re-run this command."
|
|
931
|
-
)
|
|
932
|
-
);
|
|
933
|
-
return;
|
|
934
|
-
}
|
|
935
|
-
const { fullConfig: config$1 } = await config.loadCliConfig({ args: [] });
|
|
936
|
-
const discovery = new Discovery();
|
|
937
|
-
if (answers.provider === "GitHub") {
|
|
938
|
-
discovery.addProvider(GithubDiscoveryProvider.fromConfig(config$1));
|
|
939
|
-
}
|
|
940
|
-
if (answers.provider === "GitLab") {
|
|
941
|
-
discovery.addProvider(GitlabDiscoveryProvider.fromConfig(config$1));
|
|
942
|
-
}
|
|
943
|
-
discovery.addAnalyzer(new BasicRepositoryAnalyzer());
|
|
944
|
-
discovery.addAnalyzer(new PackageJsonAnalyzer());
|
|
945
|
-
const { entities } = await discovery.run(answers.url);
|
|
946
|
-
if (!entities.length) {
|
|
947
|
-
tasks.Task.log(
|
|
948
|
-
chalk__default.default.yellow(`
|
|
949
|
-
We could not find enough information to be able to generate any Software Catalog entities for you.
|
|
950
|
-
Perhaps you can try again with a different repository?`)
|
|
951
|
-
);
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
|
-
await tasks.Task.forItem("Creating", DISCOVERED_ENTITIES_FILE, async () => {
|
|
955
|
-
const payload = [];
|
|
956
|
-
for (const entity of entities) {
|
|
957
|
-
payload.push("---\n", yaml__default.default.stringify(entity));
|
|
958
|
-
}
|
|
959
|
-
await fs__namespace.default.writeFile(DISCOVERED_ENTITIES_FILE, payload.join(""));
|
|
960
|
-
});
|
|
961
|
-
await tasks.Task.forItem(
|
|
962
|
-
"Updating",
|
|
963
|
-
APP_CONFIG_FILE,
|
|
964
|
-
async () => await updateConfigFile(APP_CONFIG_FILE, {
|
|
965
|
-
catalog: {
|
|
966
|
-
locations: [
|
|
967
|
-
{
|
|
968
|
-
type: "file",
|
|
969
|
-
target: DISCOVERED_ENTITIES_FILE
|
|
970
|
-
}
|
|
971
|
-
]
|
|
972
|
-
}
|
|
973
|
-
})
|
|
974
|
-
);
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
async function command() {
|
|
978
|
-
const answers = await inquirer__default.default.prompt([
|
|
979
|
-
{
|
|
980
|
-
type: "confirm",
|
|
981
|
-
name: "shouldSetupAuth",
|
|
982
|
-
message: "Do you want to set up Authentication for this project?",
|
|
983
|
-
default: true
|
|
984
|
-
},
|
|
985
|
-
{
|
|
986
|
-
type: "confirm",
|
|
987
|
-
name: "shouldSetupScaffolder",
|
|
988
|
-
message: "Do you want to use Software Templates in this project?",
|
|
989
|
-
default: true
|
|
990
|
-
},
|
|
991
|
-
{
|
|
992
|
-
type: "confirm",
|
|
993
|
-
name: "shouldDiscoverEntities",
|
|
994
|
-
message: "Do you want to discover entities and add them to the Software Catalog?",
|
|
995
|
-
default: true
|
|
996
|
-
}
|
|
997
|
-
]);
|
|
998
|
-
const { shouldSetupAuth, shouldSetupScaffolder, shouldDiscoverEntities } = answers;
|
|
999
|
-
if (!shouldSetupAuth && !shouldSetupScaffolder && !shouldDiscoverEntities) {
|
|
1000
|
-
tasks.Task.log(
|
|
1001
|
-
chalk__default.default.yellow(
|
|
1002
|
-
"If you change your mind, feel free to re-run this command."
|
|
1003
|
-
)
|
|
1004
|
-
);
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
let providerInfo;
|
|
1008
|
-
if (shouldSetupAuth) {
|
|
1009
|
-
providerInfo = await auth();
|
|
1010
|
-
}
|
|
1011
|
-
if (shouldSetupScaffolder) {
|
|
1012
|
-
await integrations(providerInfo);
|
|
1013
|
-
}
|
|
1014
|
-
if (shouldDiscoverEntities) {
|
|
1015
|
-
await discover(providerInfo);
|
|
1016
|
-
}
|
|
1017
|
-
tasks.Task.log();
|
|
1018
|
-
tasks.Task.log(
|
|
1019
|
-
`You can now start your app with ${chalk__default.default.inverse(
|
|
1020
|
-
chalk__default.default.italic("yarn dev")
|
|
1021
|
-
)}`
|
|
1022
|
-
);
|
|
1023
|
-
tasks.Task.log();
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
exports.command = command;
|
|
1027
|
-
//# sourceMappingURL=index-DRp-18FB.cjs.js.map
|