Skip to content

Commit 4029b16

Browse files
committed
feat: RHIDP-1502 add method for janus-cli package metadata
Signed-off-by: Nick Boldt <[email protected]> experimenting with a new packages/cli/src/commands/metadata/index.ts command Signed-off-by: Nick Boldt <[email protected]> lint changes and fix to readme Signed-off-by: Nick Boldt <[email protected]> implement metadata generation from CODEOWNERS and some hardcoded values Signed-off-by: Nick Boldt <[email protected]> implement metadata generation from CODEOWNERS and some hardcoded values Signed-off-by: Nick Boldt <[email protected]> quieter output; updated sample; regen yarn lock Signed-off-by: Nick Boldt <[email protected]> quieter output; updated sample; regen yarn lock Signed-off-by: Nick Boldt <[email protected]> move hardcoded defaults into cmdline options; support running in root folder against a specific plugins/* folder Signed-off-by: Nick Boldt <[email protected]> change description and instuctions Signed-off-by: Nick Boldt <[email protected]>
1 parent 59276df commit 4029b16

File tree

7 files changed

+4670
-3681
lines changed

7 files changed

+4670
-3681
lines changed

packages/cli/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ To run the cli in watch mode, use `yarn start <args>`. For example `yarn start l
1919
To try out the command locally, you can execute the following from the parent directory of this repo:
2020

2121
```bash
22-
./backstage/packages/cli/bin/janus-cli --help
22+
./packages/cli/bin/janus-cli --help
2323
```
2424

2525
## Documentation

packages/cli/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"bfj": "^8.0.0",
5353
"chalk": "^4.0.0",
5454
"chokidar": "^3.3.1",
55+
"codeowners": "^5.1.1",
5556
"commander": "^9.1.0",
5657
"css-loader": "^6.5.1",
5758
"esbuild": "^0.19.0",
@@ -65,6 +66,7 @@
6566
"handlebars": "^4.7.7",
6667
"html-webpack-plugin": "^5.3.1",
6768
"inquirer": "^8.2.0",
69+
"is-native-module": "^1.1.3",
6870
"lodash": "^4.17.21",
6971
"mini-css-extract-plugin": "^2.4.2",
7072
"node-libs-browser": "^2.2.1",
@@ -87,8 +89,7 @@
8789
"webpack": "^5.89.0",
8890
"webpack-dev-server": "^4.15.1",
8991
"yml-loader": "^2.1.0",
90-
"yn": "^4.0.0",
91-
"is-native-module": "^1.1.3"
92+
"yn": "^4.0.0"
9293
},
9394
"devDependencies": {
9495
"@backstage/backend-common": "0.21.7",
@@ -103,6 +104,8 @@
103104
"@backstage/plugin-scaffolder-node": "0.4.3",
104105
"@backstage/test-utils": "1.5.4",
105106
"@backstage/theme": "0.5.3",
107+
"@ianvs/prettier-plugin-sort-imports": "^4.2.1",
108+
"@spotify/prettier-config": "^15.0.0",
106109
"@types/express": "4.17.20",
107110
"@types/fs-extra": "9.0.1",
108111
"@types/inquirer": "8.1.3",

packages/cli/src/commands/index.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const configOption = [
2727
Array<string>(),
2828
] as const;
2929

30+
const indent = ""
3031
export function registerScriptCommand(program: Command) {
3132
const command = program
3233
.command('package [command]')
@@ -143,6 +144,32 @@ export function registerScriptCommand(program: Command) {
143144
.command('schema')
144145
.description('Print configuration schema for a package')
145146
.action(lazy(() => import('./schema').then(m => m.default)));
147+
148+
command
149+
.command('metadata')
150+
.description('Add metadata to a package.json file')
151+
.option(
152+
'--dir <path/to/folder>',
153+
'Folder in which to make changes to package.json, if not the current directory',
154+
'./',
155+
)
156+
.option('--author <author>', 'Set author', 'Red Hat')
157+
.option('--license <license>', 'Set license', 'Apache-2.0')
158+
.option('--homepage <homepage>', 'Set homepage', 'https://red.ht/rhdh')
159+
.option(
160+
'--bugs <bugs>',
161+
'Set issue tracker URL',
162+
'https://github.com/janus-idp/backstage-plugins/issues',
163+
)
164+
.option(
165+
'--keywords <unique,keywords,to,add>',
166+
'Add or replace keywords; there can be only one `support:` or `lifecycle:` value,\n ' +
167+
'but unlimited other keywords can be added. To remove values, manually edit package.json\n\n ' +
168+
'Valid values for support: alpha, beta, tech-preview, or production.\n ' +
169+
'Valid values for lifecycle: active, maintenance, deprecated, inactive, retired.\n ',
170+
'support:production,lifecycle:active',
171+
)
172+
.action(lazy(() => import('./metadata').then(m => m.command)));
146173
}
147174

148175
export function registerCommands(program: Command) {
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// test with ./packages/cli/bin/janus-cli package metadata --help
2+
3+
import { OptionValues } from 'commander';
4+
import _ from 'lodash';
5+
6+
import * as fs from 'fs';
7+
import { execSync } from 'node:child_process';
8+
import { readFileSync, writeFileSync } from 'node:fs';
9+
import { join } from 'node:path';
10+
11+
export async function command(opts: OptionValues): Promise<void> {
12+
updatePackageMetadata(opts);
13+
}
14+
15+
interface Repository {
16+
type: string;
17+
url: string;
18+
directory: string;
19+
}
20+
21+
interface packageJSON {
22+
name: string;
23+
version: string;
24+
main: string;
25+
types: string;
26+
license: string;
27+
author: string;
28+
configschema: string;
29+
30+
homepage: URL;
31+
bugs: URL;
32+
33+
sideEffects: boolean;
34+
35+
publishConfig: {};
36+
backstage: {
37+
role: string;
38+
'supported-versions': string;
39+
};
40+
scripts: {};
41+
dependencies: {};
42+
peerDependencies: {};
43+
devDependencies: {};
44+
scalprum: {};
45+
repository: Repository;
46+
47+
files: string[];
48+
maintainers: string[];
49+
keywords: string[];
50+
}
51+
52+
const path = {
53+
/**
54+
* @method resolveRelativeFromAbsolute resolves a relative path from an absolute path
55+
* @param {String} DotDotPath relative path
56+
* @returns {String} resolved absolutePath
57+
*/
58+
resolveRelativeFromAbsolute(DotDotPath: String) {
59+
const pathsArray = DotDotPath.replaceAll(/[\/|\\]/g, '/').split('/');
60+
const map = pathsArray.reduce(
61+
(acc, e) => acc.set(e, (acc.get(e) || 0) + 1),
62+
new Map(),
63+
);
64+
// console.log(` @ ${DotDotPath} => ${pathsArray} (${map.get("..")} backs)`)
65+
const rootdir = pathsArray.slice(0, -(map.get('..') * 2)).join('/');
66+
// console.log(` @ root dir: ${rootdir}`)
67+
const relativeDir = process.cwd().replaceAll(`${rootdir}/`, '');
68+
return [rootdir, relativeDir];
69+
},
70+
};
71+
72+
function findCodeowners(element: String) {
73+
if (fs.existsSync(`${element}/.github/CODEOWNERS`)) {
74+
return element; // found the root dir
75+
}
76+
return findCodeowners(`${element}/..`);
77+
}
78+
79+
export function updatePackageMetadata(opts: OptionValues) {
80+
// load the package.json from the specified (or current) folder
81+
const workingDir = `${process.cwd()}/${opts.dir}`;
82+
console.log(`Updating ${workingDir} / package.json`);
83+
84+
// compute the root dir and relative path to the current dir
85+
const [rootdir, relative_path] = opts.dir
86+
? [process.cwd(), opts.dir]
87+
: path.resolveRelativeFromAbsolute(findCodeowners(`${process.cwd()}`));
88+
// console.log(` @ rootdir = ${rootdir}, relative_path = ${relative_path}`)
89+
90+
const packageJSONPath = join(workingDir, 'package.json');
91+
const packageJSON = JSON.parse(
92+
readFileSync(packageJSONPath, 'utf8'),
93+
) as packageJSON;
94+
95+
/* now let's change some values */
96+
97+
// 1. add backstage version matching the current value of backstage.json in this repo
98+
if (fs.existsSync(join(rootdir, '/backstage.json'))) {
99+
packageJSON.backstage['supported-versions'] = JSON.parse(
100+
readFileSync(join(rootdir, '/backstage.json'), 'utf8'),
101+
).version;
102+
// console.log(packageJSON.backstage)
103+
}
104+
105+
// 2. set up repository values and the current path as repo.directory
106+
const repo = {} as Repository;
107+
repo.type = 'git';
108+
repo.url = execSync('git config --get remote.origin.url')
109+
.toString()
110+
.replaceAll('[email protected]:', 'https://github.com/')
111+
.replaceAll('.git', '')
112+
.trim();
113+
repo.directory = relative_path;
114+
packageJSON.repository = repo;
115+
116+
// 3. load owners from CODEOWNERS file, using this package.json's repository.directory field to compute maintainer groups
117+
let owners = [];
118+
if (packageJSON.repository.directory) {
119+
const Codeowners = require('codeowners');
120+
const repos = new Codeowners();
121+
owners = repos.getOwner(relative_path);
122+
} else {
123+
console.log(
124+
` ! Could not load .github/CODEOWNERS file, so cannot update maintainers in package.json`,
125+
);
126+
}
127+
packageJSON.maintainers = owners;
128+
129+
// 4. set some hardcoded values based on commandline flags
130+
packageJSON.author = opts.author;
131+
packageJSON.license = opts.license;
132+
packageJSON.homepage = new URL(opts.homepage);
133+
packageJSON.bugs = new URL(opts.bugs);
134+
135+
// eslint-disable-next-line prefer-const
136+
let newKeywords = opts.keywords.split(',');
137+
// if already have keywords, replace lifecycle and support with new values (if defined)
138+
if (packageJSON.keywords.length > 0) {
139+
for (let i = 0; i < packageJSON.keywords.length; i++) {
140+
// can only have ONE lifecycle and one support keyword, so remove replace any existing values
141+
if (
142+
packageJSON.keywords[i].startsWith('lifecycle:') ||
143+
packageJSON.keywords[i].startsWith('support:')
144+
) {
145+
for (let j = 0; j < newKeywords.length; j++) {
146+
if (
147+
newKeywords[j].startsWith('lifecycle:') ||
148+
newKeywords[j].startsWith('support:')
149+
) {
150+
// replace existing; remove from array
151+
packageJSON.keywords[i] = newKeywords[j];
152+
newKeywords.splice(j, 1);
153+
}
154+
}
155+
}
156+
}
157+
}
158+
// add in the remaining keywords + dedupe
159+
for (let j = 0; j < newKeywords.length; j++) {
160+
packageJSON.keywords.push(newKeywords[j]);
161+
}
162+
packageJSON.keywords = _.uniq(packageJSON.keywords);
163+
164+
/* all done! */
165+
166+
// write changes to file
167+
writeFileSync(packageJSONPath, JSON.stringify(packageJSON, null, 2), 'utf8');
168+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright 2023 The Backstage Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
export { command } from './command';

plugins/argocd/package.json

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44
"main": "src/index.ts",
55
"types": "src/index.ts",
66
"license": "Apache-2.0",
7+
"author": "Red Hat",
8+
"maintainers": [
9+
"@janus-idp/rhtap"
10+
],
711
"publishConfig": {
812
"access": "public",
913
"main": "dist/index.esm.js",
1014
"types": "dist/index.d.ts"
1115
},
1216
"backstage": {
13-
"role": "frontend-plugin"
17+
"role": "frontend-plugin",
18+
"supported-versions": "1.25.2"
1419
},
1520
"sideEffects": false,
1621
"scripts": {
@@ -81,8 +86,12 @@
8186
},
8287
"keywords": [
8388
"backstage",
84-
"plugin"
89+
"plugin",
90+
"support:production",
91+
"lifecycle:active",
92+
"usage:ci-cd",
93+
"usage:integration"
8594
],
86-
"homepage": "https://janus-idp.io/",
95+
"homepage": "https://red.ht/rhdh",
8796
"bugs": "https://github.com/janus-idp/backstage-plugins/issues"
8897
}

0 commit comments

Comments
 (0)