@asyncapi/cli 4.0.1 → 4.1.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/LICENSE CHANGED
@@ -198,4 +198,4 @@
198
198
  distributed under the License is distributed on an "AS IS" BASIS,
199
199
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
200
  See the License for the specific language governing permissions and
201
- limitations under the License.
201
+ limitations under the License.
@@ -0,0 +1,13 @@
1
+ import Command from '../../../internal/base';
2
+ export default class AuthAdd extends Command {
3
+ static description: string;
4
+ static args: {
5
+ pattern: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
+ token: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ 'auth-type': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ header: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const core_1 = require("@oclif/core");
5
+ const base_1 = tslib_1.__importDefault(require("../../../internal/base"));
6
+ const picocolors_1 = require("picocolors");
7
+ const config_service_1 = require("../../../../../domains/services/config.service");
8
+ class AuthAdd extends base_1.default {
9
+ run() {
10
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
11
+ const { args, flags } = yield this.parse(AuthAdd);
12
+ const isEnvVar = args.token.startsWith('$');
13
+ const tokenValue = isEnvVar ? args.token.slice(1) : args.token;
14
+ // Parse headers into an object
15
+ const headers = {};
16
+ if (flags.header) {
17
+ for (const headerEntry of flags.header) {
18
+ const [key, value] = headerEntry.split('=');
19
+ if (key && value) {
20
+ headers[key.trim()] = value.trim();
21
+ }
22
+ else {
23
+ this.warn(`⚠️ Ignored invalid header format: ${headerEntry}`);
24
+ }
25
+ }
26
+ }
27
+ const entry = {
28
+ pattern: args.pattern,
29
+ token: tokenValue,
30
+ authType: flags['auth-type'] || 'Bearer',
31
+ headers: Object.keys(headers).length ? headers : undefined,
32
+ };
33
+ try {
34
+ yield config_service_1.ConfigService.addAuthEntry(entry);
35
+ this.log(`✅ Auth config added for ${(0, picocolors_1.blueBright)(args.pattern)} using ${isEnvVar ? `env var (${tokenValue})` : 'raw token'} with auth type ${(0, picocolors_1.blueBright)(entry.authType || 'Bearer')}`);
36
+ if (entry.headers) {
37
+ this.log(`Headers: ${JSON.stringify(entry.headers, null, 2)}`);
38
+ }
39
+ }
40
+ catch (err) {
41
+ this.error(`❌ Failed to add auth config: ${err.message}`);
42
+ }
43
+ });
44
+ }
45
+ }
46
+ AuthAdd.description = 'Add an authentication config for resolving $ref files requiring HTTP Authorization.';
47
+ AuthAdd.args = {
48
+ pattern: core_1.Args.string({
49
+ required: true,
50
+ description: 'Glob pattern for matching protected URLs (e.g. github.com/org/repo/**/*.*)',
51
+ }),
52
+ token: core_1.Args.string({
53
+ required: true,
54
+ description: 'Authentication token or environment variable reference (prefix with $, e.g. $GITHUB_TOKEN)',
55
+ }),
56
+ };
57
+ AuthAdd.flags = {
58
+ 'auth-type': core_1.Flags.string({
59
+ char: 'a',
60
+ description: 'Authentication type (default is "Bearer")',
61
+ }),
62
+ header: core_1.Flags.string({
63
+ char: 'h',
64
+ description: 'Additional header in key=value format; can be used multiple times',
65
+ multiple: true,
66
+ }),
67
+ };
68
+ exports.default = AuthAdd;
@@ -0,0 +1,42 @@
1
+ export interface AuthEntry {
2
+ pattern: string;
3
+ token: string;
4
+ authType?: string;
5
+ headers?: Record<string, string>;
6
+ }
7
+ export interface AuthResult {
8
+ token: string;
9
+ authType: string;
10
+ headers: Record<string, string>;
11
+ }
12
+ interface Config {
13
+ auth?: AuthEntry[];
14
+ }
15
+ export declare class ConfigService {
16
+ /**
17
+ * Load config file (~/.asyncapi/config.json)
18
+ */
19
+ static loadConfig(): Promise<Config>;
20
+ /**
21
+ * Save config back to file
22
+ */
23
+ static saveConfig(config: Config): Promise<void>;
24
+ /**
25
+ * Add a new auth entry
26
+ */
27
+ static addAuthEntry(entry: AuthEntry): Promise<void>;
28
+ /**
29
+ * Reads auth config from ~/.asyncapi/config.json and
30
+ * returns auth info matching the given URL, or null if no match.
31
+ *
32
+ * @param url - URL to match against auth patterns
33
+ * @returns Auth info or null if no match found
34
+ */
35
+ static getAuthForUrl(url: string): Promise<AuthResult | null>;
36
+ /**
37
+ * Convert wildcard pattern (*, **) to RegExp matching start of string
38
+ * @param pattern - wildcard pattern
39
+ */
40
+ private static wildcardToRegex;
41
+ }
42
+ export {};
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfigService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const path_1 = tslib_1.__importDefault(require("path"));
6
+ const os_1 = tslib_1.__importDefault(require("os"));
7
+ const fs_1 = require("fs");
8
+ const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.asyncapi');
9
+ const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
10
+ class ConfigService {
11
+ /**
12
+ * Load config file (~/.asyncapi/config.json)
13
+ */
14
+ static loadConfig() {
15
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
16
+ try {
17
+ const content = yield fs_1.promises.readFile(CONFIG_FILE, 'utf8');
18
+ return JSON.parse(content);
19
+ }
20
+ catch (err) {
21
+ if (err.code === 'ENOENT') {
22
+ return {}; // no config yet
23
+ }
24
+ throw new Error(`Error reading config file: ${err.message}`);
25
+ }
26
+ });
27
+ }
28
+ /**
29
+ * Save config back to file
30
+ */
31
+ static saveConfig(config) {
32
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
33
+ yield fs_1.promises.mkdir(CONFIG_DIR, { recursive: true });
34
+ yield fs_1.promises.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
35
+ });
36
+ }
37
+ /**
38
+ * Add a new auth entry
39
+ */
40
+ static addAuthEntry(entry) {
41
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
42
+ const config = yield this.loadConfig();
43
+ if (!config.auth) {
44
+ config.auth = [];
45
+ }
46
+ config.auth.push(entry);
47
+ yield this.saveConfig(config);
48
+ });
49
+ }
50
+ /**
51
+ * Reads auth config from ~/.asyncapi/config.json and
52
+ * returns auth info matching the given URL, or null if no match.
53
+ *
54
+ * @param url - URL to match against auth patterns
55
+ * @returns Auth info or null if no match found
56
+ */
57
+ static getAuthForUrl(url) {
58
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
59
+ const config = yield this.loadConfig();
60
+ if (!config.auth || !Array.isArray(config.auth)) {
61
+ console.warn('⚠️ No valid "auth" array found in config');
62
+ return null;
63
+ }
64
+ for (const entry of config.auth) {
65
+ try {
66
+ const regex = this.wildcardToRegex(entry.pattern);
67
+ if (regex.test(url)) {
68
+ return {
69
+ token: entry.token,
70
+ authType: entry.authType || 'Bearer',
71
+ headers: entry.headers || {}
72
+ };
73
+ }
74
+ }
75
+ catch (err) {
76
+ console.warn(`⚠️ Invalid pattern "${entry.pattern}": ${err.message}`);
77
+ }
78
+ }
79
+ return null;
80
+ });
81
+ }
82
+ /**
83
+ * Convert wildcard pattern (*, **) to RegExp matching start of string
84
+ * @param pattern - wildcard pattern
85
+ */
86
+ static wildcardToRegex(pattern) {
87
+ const escaped = pattern.replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&');
88
+ const regexStr = escaped
89
+ .replace(/\*\*/g, '.*')
90
+ .replace(/\*/g, '[^/]*');
91
+ // eslint-disable-next-line security/detect-non-literal-regexp
92
+ return new RegExp(`^${regexStr}`);
93
+ }
94
+ }
95
+ exports.ConfigService = ConfigService;
@@ -14,6 +14,89 @@ const chalk_1 = require("chalk");
14
14
  const fs_1 = require("fs");
15
15
  const path_1 = tslib_1.__importDefault(require("path"));
16
16
  const scoreCalculator_1 = require("../../utils/scoreCalculator");
17
+ const config_service_1 = require("./config.service");
18
+ /**
19
+ * Helper function to validate if a URL is a GitHub blob URL
20
+ */
21
+ const isValidGitHubBlobUrl = (url) => {
22
+ try {
23
+ const parsedUrl = new URL(url);
24
+ return (parsedUrl.hostname === 'github.com' &&
25
+ parsedUrl.pathname.split('/')[3] === 'blob');
26
+ }
27
+ catch (error) {
28
+ return false;
29
+ }
30
+ };
31
+ /**
32
+ * Convert GitHub web URL to API URL
33
+ */
34
+ const convertGitHubWebUrl = (url) => {
35
+ // Remove fragment from URL before processing
36
+ const urlWithoutFragment = url.split('#')[0];
37
+ // Handle GitHub web URLs like: https://github.com/owner/repo/blob/branch/path
38
+ // eslint-disable-next-line no-useless-escape
39
+ const githubWebPattern = /^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/blob\/([^\/]+)\/(.+)$/;
40
+ const match = urlWithoutFragment.match(githubWebPattern);
41
+ if (match) {
42
+ const [, owner, repo, branch, filePath] = match;
43
+ return `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}`;
44
+ }
45
+ return url;
46
+ };
47
+ /**
48
+ * Custom resolver for private repositories
49
+ */
50
+ const createHttpWithAuthResolver = () => ({
51
+ schema: 'https',
52
+ order: 1,
53
+ read: (uri) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
54
+ let url = uri.toString();
55
+ // Default headers
56
+ const headers = {
57
+ 'User-Agent': 'AsyncAPI-CLI',
58
+ };
59
+ const authInfo = yield config_service_1.ConfigService.getAuthForUrl(url);
60
+ if (isValidGitHubBlobUrl(url)) {
61
+ url = convertGitHubWebUrl(url);
62
+ }
63
+ if (authInfo) {
64
+ headers['Authorization'] = `${authInfo.authType} ${authInfo.token}`;
65
+ Object.assign(headers, authInfo.headers); // merge custom headers
66
+ }
67
+ if (url.includes('api.github.com')) {
68
+ headers['Accept'] = 'application/vnd.github.v3+json';
69
+ const res = yield fetch(url, { headers });
70
+ if (!res.ok) {
71
+ throw new Error(`Failed to fetch GitHub API URL: ${url} - ${res.statusText}`);
72
+ }
73
+ const fileInfo = (yield res.json());
74
+ if (fileInfo.download_url) {
75
+ const contentRes = yield fetch(fileInfo.download_url, { headers });
76
+ if (!contentRes.ok) {
77
+ throw new Error(`Failed to fetch content from download URL: ${fileInfo.download_url} - ${contentRes.statusText}`);
78
+ }
79
+ return yield contentRes.text();
80
+ }
81
+ throw new Error(`No download URL found in GitHub API response for: ${url}`);
82
+ }
83
+ else if (url.includes('raw.githubusercontent.com')) {
84
+ headers['Accept'] = 'application/vnd.github.v3.raw';
85
+ const res = yield fetch(url, { headers });
86
+ if (!res.ok) {
87
+ throw new Error(`Failed to fetch GitHub URL: ${url} - ${res.statusText}`);
88
+ }
89
+ return yield res.text();
90
+ }
91
+ else {
92
+ const res = yield fetch(url, { headers });
93
+ if (!res.ok) {
94
+ throw new Error(`Failed to fetch URL: ${url} - ${res.statusText}`);
95
+ }
96
+ return yield res.text();
97
+ }
98
+ }),
99
+ });
17
100
  const { writeFile } = fs_1.promises;
18
101
  var ValidationStatus;
19
102
  (function (ValidationStatus) {
@@ -40,11 +123,17 @@ const validFormats = [
40
123
  ];
41
124
  class ValidationService extends base_service_1.BaseService {
42
125
  constructor(parserOptions = {}) {
43
- var _a;
126
+ var _a, _b, _c;
44
127
  super();
45
- this.parser = new cjs_1.Parser(Object.assign(Object.assign({}, parserOptions), { __unstable: Object.assign({ resolver: {
128
+ // Create parser with custom GitHub resolver
129
+ const customParserOptions = Object.assign(Object.assign({}, parserOptions), { __unstable: Object.assign({ resolver: {
46
130
  cache: false,
47
- } }, (_a = parserOptions.__unstable) === null || _a === void 0 ? void 0 : _a.resolver) }));
131
+ resolvers: [
132
+ createHttpWithAuthResolver(),
133
+ ...(((_b = (_a = parserOptions.__unstable) === null || _a === void 0 ? void 0 : _a.resolver) === null || _b === void 0 ? void 0 : _b.resolvers) || [])
134
+ ],
135
+ } }, (_c = parserOptions.__unstable) === null || _c === void 0 ? void 0 : _c.resolver) });
136
+ this.parser = new cjs_1.Parser(customParserOptions);
48
137
  this.parser.registerSchemaParser((0, openapi_schema_parser_1.OpenAPISchemaParser)());
49
138
  this.parser.registerSchemaParser((0, raml_dt_schema_parser_1.RamlDTSchemaParser)());
50
139
  this.parser.registerSchemaParser((0, avro_schema_parser_1.AvroSchemaParser)());
@@ -130,6 +219,7 @@ class ValidationService extends base_service_1.BaseService {
130
219
  __unstable: {
131
220
  resolver: {
132
221
  cache: false,
222
+ resolvers: [createHttpWithAuthResolver()],
133
223
  },
134
224
  },
135
225
  });
@@ -1798,6 +1798,57 @@
1798
1798
  "studio.js"
1799
1799
  ]
1800
1800
  },
1801
+ "config:auth:add": {
1802
+ "aliases": [],
1803
+ "args": {
1804
+ "pattern": {
1805
+ "description": "Glob pattern for matching protected URLs (e.g. github.com/org/repo/**/*.*)",
1806
+ "name": "pattern",
1807
+ "required": true
1808
+ },
1809
+ "token": {
1810
+ "description": "Authentication token or environment variable reference (prefix with $, e.g. $GITHUB_TOKEN)",
1811
+ "name": "token",
1812
+ "required": true
1813
+ }
1814
+ },
1815
+ "description": "Add an authentication config for resolving $ref files requiring HTTP Authorization.",
1816
+ "flags": {
1817
+ "auth-type": {
1818
+ "char": "a",
1819
+ "description": "Authentication type (default is \"Bearer\")",
1820
+ "name": "auth-type",
1821
+ "hasDynamicHelp": false,
1822
+ "multiple": false,
1823
+ "type": "option"
1824
+ },
1825
+ "header": {
1826
+ "char": "h",
1827
+ "description": "Additional header in key=value format; can be used multiple times",
1828
+ "name": "header",
1829
+ "hasDynamicHelp": false,
1830
+ "multiple": true,
1831
+ "type": "option"
1832
+ }
1833
+ },
1834
+ "hasDynamicHelp": false,
1835
+ "hiddenAliases": [],
1836
+ "id": "config:auth:add",
1837
+ "pluginAlias": "@asyncapi/cli",
1838
+ "pluginName": "@asyncapi/cli",
1839
+ "pluginType": "core",
1840
+ "strict": true,
1841
+ "isESM": false,
1842
+ "relativePath": [
1843
+ "lib",
1844
+ "apps",
1845
+ "cli",
1846
+ "commands",
1847
+ "config",
1848
+ "auth",
1849
+ "add.js"
1850
+ ]
1851
+ },
1801
1852
  "config:context:add": {
1802
1853
  "aliases": [],
1803
1854
  "args": {
@@ -2088,5 +2139,5 @@
2088
2139
  ]
2089
2140
  }
2090
2141
  },
2091
- "version": "4.0.1"
2142
+ "version": "4.1.1"
2092
2143
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@asyncapi/cli",
3
3
  "description": "All in one CLI for all AsyncAPI tools",
4
- "version": "4.0.1",
4
+ "version": "4.1.1",
5
5
  "author": "@asyncapi",
6
6
  "bin": {
7
7
  "asyncapi": "./bin/run_bin"