@backstage/cli 0.27.1 → 0.28.0-next.0

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/cjs/{build-D9YZ-dyI.cjs.js → build-gNEKjjhr.cjs.js} +9 -8
  3. package/dist/cjs/{buildBackend-CmtimF8a.cjs.js → buildBackend-DXvz7jvh.cjs.js} +6 -6
  4. package/dist/cjs/{buildWorkspace-C4wCq5WM.cjs.js → buildWorkspace-DLfI3EjD.cjs.js} +6 -5
  5. package/dist/cjs/{bump-BQ6YRL6D.cjs.js → bump-d9dS56p9.cjs.js} +73 -9
  6. package/dist/cjs/{clean-BllZTky1.cjs.js → clean-Dpgqc3P2.cjs.js} +2 -2
  7. package/dist/cjs/{clean-CVl--Ec9.cjs.js → clean-bdpRw-oZ.cjs.js} +2 -2
  8. package/dist/cjs/{config-BMsjTUVf.cjs.js → config-nCB2LQK-.cjs.js} +2 -2
  9. package/dist/cjs/{createDistWorkspace-DxOWPD6z.cjs.js → createDistWorkspace-DKzy-L-f.cjs.js} +7 -5
  10. package/dist/cjs/{docs-DPzCP6Jp.cjs.js → docs-CDE82fWG.cjs.js} +3 -3
  11. package/dist/cjs/{fix-BM8I_DZ2.cjs.js → fix-BTjof5XT.cjs.js} +2 -2
  12. package/dist/cjs/{index-CXG8-26G.cjs.js → index-7LxfekFu.cjs.js} +14 -11
  13. package/dist/cjs/{index-BgEQ8aeF.cjs.js → index-DVDDx3pk.cjs.js} +2 -2
  14. package/dist/cjs/{index-Cw0lUK20.cjs.js → index-DsP5wbju.cjs.js} +10 -9
  15. package/dist/cjs/{index-DmUbBCFk.cjs.js → index-v57xnvgT.cjs.js} +61 -79
  16. package/dist/cjs/{info-FKrzx-9W.cjs.js → info-CB7ln5K_.cjs.js} +5 -6
  17. package/dist/cjs/{lint-6vrbdjyg.cjs.js → lint-6_0f9f26.cjs.js} +2 -2
  18. package/dist/cjs/{lint-Cm_9pg85.cjs.js → lint-DYO_SeK3.cjs.js} +2 -2
  19. package/dist/cjs/{list-deprecations-C6R3rEyS.cjs.js → list-deprecations-DFgQENam.cjs.js} +2 -2
  20. package/dist/cjs/{moduleFederation-8XXecxLD.cjs.js → moduleFederation-Dq0n1quT.cjs.js} +3 -3
  21. package/dist/cjs/{new-DJUBFwiF.cjs.js → new-C0D_k25O.cjs.js} +336 -97
  22. package/dist/cjs/{pack-BqFGqZb5.cjs.js → pack-D8AK7Uem.cjs.js} +4 -3
  23. package/dist/cjs/{packageExports-DvjdOWjC.cjs.js → packageExports-bg4mFuFZ.cjs.js} +3 -3
  24. package/dist/cjs/{packageLintConfigs-DGkvTpBd.cjs.js → packageLintConfigs-BBvQehRd.cjs.js} +3 -3
  25. package/dist/cjs/{packageRole-CHz7zkIQ.cjs.js → packageRole-CkpKfhJA.cjs.js} +2 -2
  26. package/dist/cjs/{print-CQU7JzAh.cjs.js → print-BX8-7Nzg.cjs.js} +3 -3
  27. package/dist/cjs/{productionPack-BWU8WkGs.cjs.js → productionPack-Dfl28j3c.cjs.js} +105 -4
  28. package/dist/cjs/{role-8b0z7P0n.cjs.js → role-e0emQj8q.cjs.js} +2 -2
  29. package/dist/cjs/{run-CSt1n0F1.cjs.js → run-HW3lfDbM.cjs.js} +2 -2
  30. package/dist/cjs/{schema-B4250t0W.cjs.js → schema-Cabm6I6c.cjs.js} +3 -3
  31. package/dist/cjs/{test-DVUsmgyZ.cjs.js → test-5bQM0VRL.cjs.js} +3 -3
  32. package/dist/cjs/{test-Diil1uTk.cjs.js → test-YJMOGqXG.cjs.js} +3 -3
  33. package/dist/cjs/{validate-DDIGkK2r.cjs.js → validate-DKBlShmI.cjs.js} +3 -3
  34. package/dist/cjs/{Lockfile-B4mqBkH6.cjs.js → yarn-6FNAgNBK.cjs.js} +31 -1
  35. package/dist/index.cjs.js +1 -1
  36. package/package.json +9 -8
  37. package/dist/cjs/codeowners-FKKtpciN.cjs.js +0 -91
  38. package/dist/cjs/createPlugin-Dj7O_us6.cjs.js +0 -280
  39. package/dist/cjs/diff-Drt115Zb.cjs.js +0 -436
  40. package/dist/cjs/index-DRp-18FB.cjs.js +0 -1027
  41. package/dist/cjs/install-BMA3RshT.cjs.js +0 -268
  42. package/dist/cjs/lint-Dkx_fBkS.cjs.js +0 -10
  43. package/dist/cjs/packages-Cuogjl7j.cjs.js +0 -75
  44. package/dist/cjs/tasks-DtAiMv5G.cjs.js +0 -188
  45. 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