neonctl 0.3.3 → 0.5.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neonctl",
3
- "version": "0.3.3",
3
+ "version": "0.5.4",
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,17 @@
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 && cd dist && npm publish"
42
+ "publizh": "npm run build && npm publish ./dist && git push"
43
+ },
44
+ "lint-staged": {
45
+ "*.ts": [
46
+ "eslint --cache --fix",
47
+ "prettier --write"
48
+ ]
38
49
  }
39
50
  }
@@ -60,7 +60,7 @@ const apiCall = ({ apiHost, token, path, body, method = 'GET', }) => new Promise
60
60
  result += data;
61
61
  });
62
62
  res.on('end', () => {
63
- resolve(result);
63
+ resolve(JSON.parse(result));
64
64
  });
65
65
  });
66
66
  req.on('error', reject);
@@ -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 = ['openid', 'offline', 'urn:neoncloud:projects:create'];
28
- const auth = ({ oauthHost, clientId }) => __awaiter(void 0, void 0, void 0, function* () {
29
- openid_client_1.custom.setHttpOptionsDefaults({
30
- timeout: SERVER_TIMEOUT,
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
  //
@@ -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
- (0, node_fs_1.writeFileSync)(credentialsPath, JSON.stringify(tokenSet));
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,11 +81,28 @@ 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 token = (yield Promise.resolve().then(() => __importStar(require(credentialsPath)))).access_token;
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) {
102
+ if (e.code !== 'ENOENT') {
103
+ // not a "file does not exist" error
104
+ throw e;
105
+ }
68
106
  props.token = yield (0, exports.authFlow)(props);
69
107
  }
70
108
  }
@@ -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
- process.stdout.write(yield (0, projects_1.listProjects)(props));
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
- process.stdout.write(yield (0, projects_1.createProject)(Object.assign(Object.assign({}, props), { settings: {} })));
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;
@@ -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
- process.stdout.write(yield (0, users_1.apiMe)(props));
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
- exports.defaultDir = (0, node_path_1.join)(process.cwd(), DIR_NAME);
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, (args) => __awaiter(void 0, void 0, void 0, function* () {
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;