@asyncapi/cli 4.0.1 → 4.1.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.
- package/LICENSE +1 -1
- package/lib/apps/cli/commands/config/auth/add.d.ts +13 -0
- package/lib/apps/cli/commands/config/auth/add.js +68 -0
- package/lib/domains/services/config.service.d.ts +42 -0
- package/lib/domains/services/config.service.js +95 -0
- package/lib/domains/services/validation.service.js +93 -3
- package/oclif.manifest.json +52 -1
- package/package.json +1 -1
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
|
-
|
|
128
|
+
// Create parser with custom GitHub resolver
|
|
129
|
+
const customParserOptions = Object.assign(Object.assign({}, parserOptions), { __unstable: Object.assign({ resolver: {
|
|
46
130
|
cache: false,
|
|
47
|
-
|
|
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
|
});
|
package/oclif.manifest.json
CHANGED
|
@@ -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
|
|
2142
|
+
"version": "4.1.0"
|
|
2092
2143
|
}
|