neonctl 0.3.2 → 0.5.3
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/package.json +14 -2
- package/src/api/gateway.js +1 -1
- package/src/api/projects.js +1 -0
- package/src/auth.js +24 -5
- package/src/commands/auth.js +42 -4
- package/src/commands/projects.js +3 -2
- package/src/commands/users.js +4 -1
- package/src/config.js +4 -1
- package/src/index.js +9 -4
- package/src/writer.js +29 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neonctl",
|
|
3
|
-
"version": "0.3
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "CLI tool for NeonDB Cloud management",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "NeonDB",
|
|
@@ -10,16 +10,20 @@
|
|
|
10
10
|
"neonctl": "src/cli.js"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
|
+
"@types/cli-table": "^0.3.0",
|
|
13
14
|
"@types/node": "^18.7.13",
|
|
14
15
|
"@types/yargs": "^17.0.11",
|
|
15
16
|
"@typescript-eslint/eslint-plugin": "^5.34.0",
|
|
16
17
|
"@typescript-eslint/parser": "^5.34.0",
|
|
17
18
|
"eslint": "^8.22.0",
|
|
19
|
+
"husky": "^8.0.1",
|
|
20
|
+
"lint-staged": "^13.0.3",
|
|
18
21
|
"prettier": "^2.7.1",
|
|
19
22
|
"ts-node": "^10.9.1",
|
|
20
23
|
"typescript": "^4.7.4"
|
|
21
24
|
},
|
|
22
25
|
"dependencies": {
|
|
26
|
+
"cli-table": "^0.3.11",
|
|
23
27
|
"open": "^8.4.0",
|
|
24
28
|
"openid-client": "^5.1.9",
|
|
25
29
|
"yargs": "^17.5.1"
|
|
@@ -30,10 +34,18 @@
|
|
|
30
34
|
},
|
|
31
35
|
"scripts": {
|
|
32
36
|
"dev": "node dev.js",
|
|
37
|
+
"lint": "eslint src --ext .ts",
|
|
33
38
|
"debug": "node --inspect-brk dev.js",
|
|
34
39
|
"build": "npm run clean && tsc && cp src/*.html dist/src/",
|
|
35
40
|
"clean": "rm -rf dist",
|
|
36
41
|
"start": "node index.js",
|
|
37
|
-
"publizh": "npm run build &&
|
|
42
|
+
"publizh": "npm run build && npm publish ./dist && git push",
|
|
43
|
+
"postinstall": "husky install"
|
|
44
|
+
},
|
|
45
|
+
"lint-staged": {
|
|
46
|
+
"*.ts": [
|
|
47
|
+
"eslint --cache --fix",
|
|
48
|
+
"prettier --write"
|
|
49
|
+
]
|
|
38
50
|
}
|
|
39
51
|
}
|
package/src/api/gateway.js
CHANGED
package/src/api/projects.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.listProjects = listProjects;
|
|
|
7
7
|
const createProject = (props) => (0, gateway_1.apiCall)(Object.assign(Object.assign({}, props), { body: {
|
|
8
8
|
project: {
|
|
9
9
|
settings: props.settings,
|
|
10
|
+
name: props.name,
|
|
10
11
|
},
|
|
11
12
|
}, path: 'projects', method: 'POST' }));
|
|
12
13
|
exports.createProject = createProject;
|
package/src/auth.js
CHANGED
|
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.auth = void 0;
|
|
15
|
+
exports.auth = exports.refreshToken = void 0;
|
|
16
16
|
const openid_client_1 = require("openid-client");
|
|
17
17
|
const node_http_1 = require("node:http");
|
|
18
18
|
const node_fs_1 = require("node:fs");
|
|
@@ -24,11 +24,30 @@ const SERVER_TIMEOUT = 10000;
|
|
|
24
24
|
// where to wait for incoming redirect request from oauth server to arrive
|
|
25
25
|
const REDIRECT_URI = (port) => `http://127.0.0.1:${port}/callback`;
|
|
26
26
|
// These scopes cannot be cancelled, they are always needed.
|
|
27
|
-
const DEFAULT_SCOPES = [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const DEFAULT_SCOPES = [
|
|
28
|
+
'openid',
|
|
29
|
+
'offline',
|
|
30
|
+
'offline_access',
|
|
31
|
+
'urn:neoncloud:projects:create',
|
|
32
|
+
'urn:neoncloud:projects:read',
|
|
33
|
+
'urn:neoncloud:projects:modify',
|
|
34
|
+
'urn:neoncloud:projects:delete',
|
|
35
|
+
];
|
|
36
|
+
openid_client_1.custom.setHttpOptionsDefaults({
|
|
37
|
+
timeout: SERVER_TIMEOUT,
|
|
38
|
+
});
|
|
39
|
+
const refreshToken = ({ oauthHost, clientId }, tokenSet) => __awaiter(void 0, void 0, void 0, function* () {
|
|
40
|
+
log_1.log.info('Discovering oauth server');
|
|
41
|
+
const issuer = yield openid_client_1.Issuer.discover(oauthHost);
|
|
42
|
+
const neonOAuthClient = new issuer.Client({
|
|
43
|
+
token_endpoint_auth_method: 'none',
|
|
44
|
+
client_id: clientId,
|
|
45
|
+
response_types: ['code'],
|
|
31
46
|
});
|
|
47
|
+
return yield neonOAuthClient.refresh(tokenSet);
|
|
48
|
+
});
|
|
49
|
+
exports.refreshToken = refreshToken;
|
|
50
|
+
const auth = ({ oauthHost, clientId }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
32
51
|
log_1.log.info('Discovering oauth server');
|
|
33
52
|
const issuer = yield openid_client_1.Issuer.discover(oauthHost);
|
|
34
53
|
//
|
package/src/commands/auth.js
CHANGED
|
@@ -35,8 +35,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
35
35
|
exports.ensureAuth = exports.authFlow = void 0;
|
|
36
36
|
const node_path_1 = require("node:path");
|
|
37
37
|
const node_fs_1 = require("node:fs");
|
|
38
|
+
const openid_client_1 = require("openid-client");
|
|
38
39
|
const auth_1 = require("../auth");
|
|
39
40
|
const log_1 = require("../log");
|
|
41
|
+
const users_1 = require("../api/users");
|
|
42
|
+
const gateway_1 = require("../api/gateway");
|
|
40
43
|
const CREDENTIALS_FILE = 'credentials.json';
|
|
41
44
|
const authFlow = ({ 'config-dir': configDir, 'oauth-host': oauthHost, 'client-id': clientId, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
45
|
if (!clientId) {
|
|
@@ -47,12 +50,30 @@ const authFlow = ({ 'config-dir': configDir, 'oauth-host': oauthHost, 'client-id
|
|
|
47
50
|
clientId: clientId,
|
|
48
51
|
});
|
|
49
52
|
const credentialsPath = (0, node_path_1.join)(configDir, CREDENTIALS_FILE);
|
|
50
|
-
(
|
|
53
|
+
updateCredentialsFile(credentialsPath, JSON.stringify(tokenSet));
|
|
51
54
|
log_1.log.info(`Saved credentials to ${credentialsPath}`);
|
|
52
55
|
log_1.log.info('Auth complete');
|
|
53
56
|
return tokenSet.access_token || '';
|
|
54
57
|
});
|
|
55
58
|
exports.authFlow = authFlow;
|
|
59
|
+
const validateToken = (props) => __awaiter(void 0, void 0, void 0, function* () {
|
|
60
|
+
try {
|
|
61
|
+
yield (0, users_1.apiMe)(props);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
if (e instanceof gateway_1.ApiError) {
|
|
65
|
+
if (e.response.statusCode === 401) {
|
|
66
|
+
throw new Error('Invalid token');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// updateCredentialsFile correctly sets needed permissions for the credentials file
|
|
72
|
+
function updateCredentialsFile(path, contents) {
|
|
73
|
+
(0, node_fs_1.writeFileSync)(path, contents, {
|
|
74
|
+
mode: 0o700,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
56
77
|
const ensureAuth = (props) => __awaiter(void 0, void 0, void 0, function* () {
|
|
57
78
|
if (props.token || props._[0] === 'auth') {
|
|
58
79
|
return;
|
|
@@ -60,16 +81,33 @@ const ensureAuth = (props) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
60
81
|
const credentialsPath = (0, node_path_1.join)(props['config-dir'], CREDENTIALS_FILE);
|
|
61
82
|
if ((0, node_fs_1.existsSync)(credentialsPath)) {
|
|
62
83
|
try {
|
|
63
|
-
const
|
|
84
|
+
const tokenSetContents = yield Promise.resolve().then(() => __importStar(require(credentialsPath)));
|
|
85
|
+
const tokenSet = new openid_client_1.TokenSet(tokenSetContents);
|
|
86
|
+
if (tokenSet.expired()) {
|
|
87
|
+
log_1.log.info('using refresh token to update access token');
|
|
88
|
+
const refreshedTokenSet = yield (0, auth_1.refreshToken)({
|
|
89
|
+
oauthHost: props['oauth-host'],
|
|
90
|
+
clientId: props['client-id'],
|
|
91
|
+
}, tokenSet);
|
|
92
|
+
props.token = refreshedTokenSet.access_token || 'UNKNOWN';
|
|
93
|
+
updateCredentialsFile(credentialsPath, JSON.stringify(refreshedTokenSet));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const token = tokenSet.access_token || 'UNKNOWN';
|
|
97
|
+
yield validateToken({ apiHost: props['api-host'], token });
|
|
64
98
|
props.token = token;
|
|
65
99
|
return;
|
|
66
100
|
}
|
|
67
101
|
catch (e) {
|
|
68
|
-
|
|
102
|
+
if (e.code !== 'ENOENT') {
|
|
103
|
+
// not a "file does not exist" error
|
|
104
|
+
throw e;
|
|
105
|
+
}
|
|
106
|
+
props.token = yield (0, exports.authFlow)(props);
|
|
69
107
|
}
|
|
70
108
|
}
|
|
71
109
|
else {
|
|
72
|
-
yield (0, exports.authFlow)(props);
|
|
110
|
+
props.token = yield (0, exports.authFlow)(props);
|
|
73
111
|
}
|
|
74
112
|
});
|
|
75
113
|
exports.ensureAuth = ensureAuth;
|
package/src/commands/projects.js
CHANGED
|
@@ -11,11 +11,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.create = exports.list = void 0;
|
|
13
13
|
const projects_1 = require("../api/projects");
|
|
14
|
+
const writer_1 = require("../writer");
|
|
14
15
|
const list = (props) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15
|
-
|
|
16
|
+
(0, writer_1.writeOut)(props)(yield (0, projects_1.listProjects)(props), { fields: [] });
|
|
16
17
|
});
|
|
17
18
|
exports.list = list;
|
|
18
19
|
const create = (props) => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
|
-
|
|
20
|
+
(0, writer_1.writeOut)(props)(yield (0, projects_1.createProject)(Object.assign(Object.assign({}, props), { settings: {}, name: props.name })), { fields: ['id', 'name', 'region_name', 'created_at'] });
|
|
20
21
|
});
|
|
21
22
|
exports.create = create;
|
package/src/commands/users.js
CHANGED
|
@@ -11,7 +11,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.me = void 0;
|
|
13
13
|
const users_1 = require("../api/users");
|
|
14
|
+
const writer_1 = require("../writer");
|
|
14
15
|
const me = (props) => __awaiter(void 0, void 0, void 0, function* () {
|
|
15
|
-
|
|
16
|
+
(0, writer_1.writeOut)(props)(yield (0, users_1.apiMe)(props), {
|
|
17
|
+
fields: ['login', 'email', 'name', 'projects_limit'],
|
|
18
|
+
});
|
|
16
19
|
});
|
|
17
20
|
exports.me = me;
|
package/src/config.js
CHANGED
|
@@ -11,9 +11,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.ensureConfigDir = exports.defaultDir = void 0;
|
|
13
13
|
const node_path_1 = require("node:path");
|
|
14
|
+
const node_os_1 = require("node:os");
|
|
14
15
|
const node_fs_1 = require("node:fs");
|
|
15
16
|
const DIR_NAME = '.neonctl';
|
|
16
|
-
|
|
17
|
+
const cwdDir = (0, node_path_1.join)(process.cwd(), DIR_NAME);
|
|
18
|
+
const homeConfigDir = (0, node_path_1.join)((0, node_os_1.homedir)(), process.env.XDG_CONFIG_HOME || '.config', DIR_NAME);
|
|
19
|
+
exports.defaultDir = (0, node_fs_1.existsSync)(cwdDir) ? cwdDir : homeConfigDir;
|
|
17
20
|
const ensureConfigDir = ({ 'config-dir': configDir, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
21
|
if (!(0, node_fs_1.existsSync)(configDir)) {
|
|
19
22
|
(0, node_fs_1.mkdirSync)(configDir);
|
package/src/index.js
CHANGED
|
@@ -44,6 +44,7 @@ const log_1 = require("./log");
|
|
|
44
44
|
const showHelpMiddleware = (argv) => {
|
|
45
45
|
if (argv._.length === 1) {
|
|
46
46
|
yargs.showHelp();
|
|
47
|
+
process.exit(0);
|
|
47
48
|
}
|
|
48
49
|
};
|
|
49
50
|
const builder = yargs
|
|
@@ -84,8 +85,7 @@ const builder = yargs
|
|
|
84
85
|
type: 'string',
|
|
85
86
|
default: '',
|
|
86
87
|
})
|
|
87
|
-
.middleware(auth_1.ensureAuth)
|
|
88
|
-
.command('me', 'Get user info', (yargs) => yargs, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
88
|
+
.command('me', 'Get user info', (yargs) => yargs.middleware(auth_1.ensureAuth), (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
89
89
|
yield (yield Promise.resolve().then(() => __importStar(require('./commands/users')))).me(args);
|
|
90
90
|
}))
|
|
91
91
|
.command('projects', 'Manage projects', (yargs) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -99,10 +99,14 @@ const builder = yargs
|
|
|
99
99
|
// await (await import('./commands/projects')).list(args);
|
|
100
100
|
// }
|
|
101
101
|
// )
|
|
102
|
-
.command('create', 'Create a project', (yargs) => yargs
|
|
102
|
+
.command('create', 'Create a project', (yargs) => yargs.option('name', {
|
|
103
|
+
describe: 'Project name',
|
|
104
|
+
type: 'string',
|
|
105
|
+
}), (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
106
|
yield (yield Promise.resolve().then(() => __importStar(require('./commands/projects')))).create(args);
|
|
104
107
|
}))
|
|
105
|
-
.middleware(showHelpMiddleware)
|
|
108
|
+
.middleware(showHelpMiddleware)
|
|
109
|
+
.middleware(auth_1.ensureAuth);
|
|
106
110
|
}))
|
|
107
111
|
.strict()
|
|
108
112
|
.fail((msg, err) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -118,5 +122,6 @@ const builder = yargs
|
|
|
118
122
|
const args = yield builder.argv;
|
|
119
123
|
if (args._.length === 0) {
|
|
120
124
|
yargs.showHelp();
|
|
125
|
+
process.exit(0);
|
|
121
126
|
}
|
|
122
127
|
}))();
|
package/src/writer.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeOut = void 0;
|
|
7
|
+
const cli_table_1 = __importDefault(require("cli-table"));
|
|
8
|
+
const writeOut = (props) => (data, config) => {
|
|
9
|
+
if (props.json) {
|
|
10
|
+
process.stdout.write(JSON.stringify(data, null, 2));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const arrayData = Array.isArray(data) ? data : [data];
|
|
14
|
+
const table = new cli_table_1.default({
|
|
15
|
+
style: {
|
|
16
|
+
head: ['green'],
|
|
17
|
+
},
|
|
18
|
+
head: config.fields.map((field) => field
|
|
19
|
+
.split('_')
|
|
20
|
+
.map((word) => word[0].toUpperCase() + word.slice(1))
|
|
21
|
+
.join(' ')),
|
|
22
|
+
});
|
|
23
|
+
arrayData.forEach((item) => {
|
|
24
|
+
table.push(config.fields.map((field) => { var _a; return (_a = item[field]) !== null && _a !== void 0 ? _a : ''; }));
|
|
25
|
+
});
|
|
26
|
+
process.stdout.write(table.toString());
|
|
27
|
+
process.stdout.write('\n');
|
|
28
|
+
};
|
|
29
|
+
exports.writeOut = writeOut;
|