neonctl 0.5.4 → 0.7.0-test.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/api.js +3 -0
- package/auth.js +90 -0
- package/cli.js +2 -0
- package/commands/auth.js +87 -0
- package/commands/index.js +4 -0
- package/commands/projects.js +68 -0
- package/commands/users.js +12 -0
- package/config.js +9 -0
- package/index.js +76 -0
- package/log.js +9 -0
- package/package.json +63 -48
- package/parameters.gen.js +64 -0
- package/types.js +1 -0
- package/utils.js +20 -0
- package/writer.js +33 -0
- package/src/api/gateway.js +0 -72
- package/src/api/projects.js +0 -13
- package/src/api/users.js +0 -6
- package/src/auth.js +0 -106
- package/src/cli.js +0 -4
- package/src/commands/auth.js +0 -113
- package/src/commands/projects.js +0 -22
- package/src/commands/users.js +0 -20
- package/src/config.js +0 -25
- package/src/index.js +0 -127
- package/src/log.js +0 -12
- package/src/types.js +0 -2
- package/src/writer.js +0 -29
- /package/{src/callback.html → callback.html} +0 -0
package/api.js
ADDED
package/auth.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { custom, generators, Issuer } from 'openid-client';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { createReadStream } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import open from 'open';
|
|
6
|
+
import { log } from './log.js';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
// oauth server timeouts
|
|
9
|
+
const SERVER_TIMEOUT = 10000;
|
|
10
|
+
// where to wait for incoming redirect request from oauth server to arrive
|
|
11
|
+
const REDIRECT_URI = (port) => `http://127.0.0.1:${port}/callback`;
|
|
12
|
+
// These scopes cannot be cancelled, they are always needed.
|
|
13
|
+
const ALWAYS_PRESENT_SCOPES = ['openid', 'offline', 'offline_access'];
|
|
14
|
+
const NEONCTL_SCOPES = [
|
|
15
|
+
...ALWAYS_PRESENT_SCOPES,
|
|
16
|
+
'urn:neoncloud:projects:create',
|
|
17
|
+
'urn:neoncloud:projects:read',
|
|
18
|
+
'urn:neoncloud:projects:update',
|
|
19
|
+
'urn:neoncloud:projects:delete',
|
|
20
|
+
];
|
|
21
|
+
export const defaultClientID = 'neonctl';
|
|
22
|
+
custom.setHttpOptionsDefaults({
|
|
23
|
+
timeout: SERVER_TIMEOUT,
|
|
24
|
+
});
|
|
25
|
+
export const refreshToken = async ({ oauthHost, clientId }, tokenSet) => {
|
|
26
|
+
log.info('Discovering oauth server');
|
|
27
|
+
const issuer = await Issuer.discover(oauthHost);
|
|
28
|
+
const neonOAuthClient = new issuer.Client({
|
|
29
|
+
token_endpoint_auth_method: 'none',
|
|
30
|
+
client_id: clientId,
|
|
31
|
+
response_types: ['code'],
|
|
32
|
+
});
|
|
33
|
+
return await neonOAuthClient.refresh(tokenSet);
|
|
34
|
+
};
|
|
35
|
+
export const auth = async ({ oauthHost, clientId }) => {
|
|
36
|
+
log.info('Discovering oauth server');
|
|
37
|
+
const issuer = await Issuer.discover(oauthHost);
|
|
38
|
+
//
|
|
39
|
+
// Start HTTP server and wait till /callback is hit
|
|
40
|
+
//
|
|
41
|
+
const server = createServer();
|
|
42
|
+
server.listen(0, function () {
|
|
43
|
+
log.info(`Listening on port ${this.address().port}`);
|
|
44
|
+
});
|
|
45
|
+
const listen_port = server.address().port;
|
|
46
|
+
const neonOAuthClient = new issuer.Client({
|
|
47
|
+
token_endpoint_auth_method: 'none',
|
|
48
|
+
client_id: clientId,
|
|
49
|
+
redirect_uris: [REDIRECT_URI(listen_port)],
|
|
50
|
+
response_types: ['code'],
|
|
51
|
+
});
|
|
52
|
+
// https://datatracker.ietf.org/doc/html/rfc6819#section-4.4.1.8
|
|
53
|
+
const state = generators.state();
|
|
54
|
+
// we store the code_verifier in memory
|
|
55
|
+
const codeVerifier = generators.codeVerifier();
|
|
56
|
+
const codeChallenge = generators.codeChallenge(codeVerifier);
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
server.on('request', async (request, response) => {
|
|
59
|
+
//
|
|
60
|
+
// Wait for callback and follow oauth flow.
|
|
61
|
+
//
|
|
62
|
+
if (!request.url?.startsWith('/callback')) {
|
|
63
|
+
response.writeHead(404);
|
|
64
|
+
response.end();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
log.info(`Callback received: ${request.url}`);
|
|
68
|
+
const params = neonOAuthClient.callbackParams(request);
|
|
69
|
+
const tokenSet = await neonOAuthClient.callback(REDIRECT_URI(listen_port), params, {
|
|
70
|
+
code_verifier: codeVerifier,
|
|
71
|
+
state,
|
|
72
|
+
});
|
|
73
|
+
response.writeHead(200, { 'Content-Type': 'text/html' });
|
|
74
|
+
createReadStream(join(fileURLToPath(new URL('.', import.meta.url)), './callback.html')).pipe(response);
|
|
75
|
+
resolve(tokenSet);
|
|
76
|
+
server.close();
|
|
77
|
+
});
|
|
78
|
+
//
|
|
79
|
+
// Open browser to let user authenticate
|
|
80
|
+
//
|
|
81
|
+
const scopes = clientId == defaultClientID ? NEONCTL_SCOPES : ALWAYS_PRESENT_SCOPES;
|
|
82
|
+
const authUrl = neonOAuthClient.authorizationUrl({
|
|
83
|
+
scope: scopes.join(' '),
|
|
84
|
+
state,
|
|
85
|
+
code_challenge: codeChallenge,
|
|
86
|
+
code_challenge_method: 'S256',
|
|
87
|
+
});
|
|
88
|
+
open(authUrl);
|
|
89
|
+
});
|
|
90
|
+
};
|
package/cli.js
ADDED
package/commands/auth.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { TokenSet } from 'openid-client';
|
|
4
|
+
import { auth, refreshToken } from '../auth.js';
|
|
5
|
+
import { log } from '../log.js';
|
|
6
|
+
import { getApiClient } from '../api.js';
|
|
7
|
+
const CREDENTIALS_FILE = 'credentials.json';
|
|
8
|
+
export const command = 'auth';
|
|
9
|
+
export const describe = 'Authenticate';
|
|
10
|
+
export const builder = (yargs) => yargs;
|
|
11
|
+
export const handler = async (args) => {
|
|
12
|
+
await authFlow(args);
|
|
13
|
+
};
|
|
14
|
+
export const authFlow = async ({ configDir, oauthHost, clientId, }) => {
|
|
15
|
+
if (!clientId) {
|
|
16
|
+
throw new Error('Missing client id');
|
|
17
|
+
}
|
|
18
|
+
const tokenSet = await auth({
|
|
19
|
+
oauthHost: oauthHost,
|
|
20
|
+
clientId: clientId,
|
|
21
|
+
});
|
|
22
|
+
const credentialsPath = join(configDir, CREDENTIALS_FILE);
|
|
23
|
+
updateCredentialsFile(credentialsPath, JSON.stringify(tokenSet));
|
|
24
|
+
log.info(`Saved credentials to ${credentialsPath}`);
|
|
25
|
+
log.info('Auth complete');
|
|
26
|
+
return tokenSet.access_token || '';
|
|
27
|
+
};
|
|
28
|
+
// updateCredentialsFile correctly sets needed permissions for the credentials file
|
|
29
|
+
function updateCredentialsFile(path, contents) {
|
|
30
|
+
writeFileSync(path, contents, {
|
|
31
|
+
mode: 0o700,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export const ensureAuth = async (props) => {
|
|
35
|
+
if (props._.length === 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (props.apiKey || props._[0] === 'auth') {
|
|
39
|
+
props.apiClient = getApiClient({
|
|
40
|
+
apiKey: props.apiKey,
|
|
41
|
+
apiHost: props.apiHost,
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const credentialsPath = join(props.configDir, CREDENTIALS_FILE);
|
|
46
|
+
if (existsSync(credentialsPath)) {
|
|
47
|
+
try {
|
|
48
|
+
const tokenSetContents = await JSON.parse(readFileSync(credentialsPath, 'utf8'));
|
|
49
|
+
const tokenSet = new TokenSet(tokenSetContents);
|
|
50
|
+
if (tokenSet.expired()) {
|
|
51
|
+
log.info('using refresh token to update access token');
|
|
52
|
+
const refreshedTokenSet = await refreshToken({
|
|
53
|
+
oauthHost: props.oauthHost,
|
|
54
|
+
clientId: props.clientId,
|
|
55
|
+
}, tokenSet);
|
|
56
|
+
props.apiKey = refreshedTokenSet.access_token || 'UNKNOWN';
|
|
57
|
+
props.apiClient = getApiClient({
|
|
58
|
+
apiKey: props.apiKey,
|
|
59
|
+
apiHost: props.apiHost,
|
|
60
|
+
});
|
|
61
|
+
updateCredentialsFile(credentialsPath, JSON.stringify(refreshedTokenSet));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const token = tokenSet.access_token || 'UNKNOWN';
|
|
65
|
+
props.apiKey = token;
|
|
66
|
+
props.apiClient = getApiClient({
|
|
67
|
+
apiKey: props.apiKey,
|
|
68
|
+
apiHost: props.apiHost,
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
if (e.code !== 'ENOENT') {
|
|
74
|
+
// not a "file does not exist" error
|
|
75
|
+
throw e;
|
|
76
|
+
}
|
|
77
|
+
props.apiKey = await authFlow(props);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
props.apiKey = await authFlow(props);
|
|
82
|
+
}
|
|
83
|
+
props.apiClient = getApiClient({
|
|
84
|
+
apiKey: props.apiKey,
|
|
85
|
+
apiHost: props.apiHost,
|
|
86
|
+
});
|
|
87
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { projectCreateRequest } from '../parameters.gen.js';
|
|
2
|
+
import { writeOut } from '../writer.js';
|
|
3
|
+
const PROJECT_FIELDS = ['id', 'name', 'region_id', 'created_at'];
|
|
4
|
+
export const command = 'projects [command]';
|
|
5
|
+
export const describe = 'Manage projects';
|
|
6
|
+
export const builder = (yargs) => {
|
|
7
|
+
return yargs
|
|
8
|
+
.demandCommand(1, '')
|
|
9
|
+
.fail((msg, err, yargs) => {
|
|
10
|
+
yargs.showHelp();
|
|
11
|
+
process.exit(1);
|
|
12
|
+
})
|
|
13
|
+
.usage('usage: $0 projects <cmd> [args]')
|
|
14
|
+
.command('list', 'List projects', (yargs) => yargs, async (args) => {
|
|
15
|
+
await list(args);
|
|
16
|
+
})
|
|
17
|
+
.command('create', 'Create a project', (yargs) => yargs.options(projectCreateRequest), async (args) => {
|
|
18
|
+
await create(args);
|
|
19
|
+
})
|
|
20
|
+
.command('update', 'Update a project', (yargs) => yargs
|
|
21
|
+
.option('project.id', {
|
|
22
|
+
describe: 'Project ID',
|
|
23
|
+
type: 'string',
|
|
24
|
+
demandOption: true,
|
|
25
|
+
})
|
|
26
|
+
.options(projectCreateRequest), async (args) => {
|
|
27
|
+
await update(args);
|
|
28
|
+
})
|
|
29
|
+
.command('delete', 'Delete a project', (yargs) => yargs.options({
|
|
30
|
+
'project.id': {
|
|
31
|
+
describe: 'Project ID',
|
|
32
|
+
type: 'string',
|
|
33
|
+
demandOption: true,
|
|
34
|
+
},
|
|
35
|
+
}), async (args) => {
|
|
36
|
+
await deleteProject(args);
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
export const handler = (args) => {
|
|
40
|
+
return args;
|
|
41
|
+
};
|
|
42
|
+
const list = async (props) => {
|
|
43
|
+
writeOut(props)((await props.apiClient.listProjects({})).data.projects, {
|
|
44
|
+
fields: PROJECT_FIELDS,
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const create = async (props) => {
|
|
48
|
+
if (props.project == null) {
|
|
49
|
+
const inquirer = await import('inquirer');
|
|
50
|
+
const answers = await inquirer.default.prompt([
|
|
51
|
+
{ name: 'name', message: 'Project name', type: 'input' },
|
|
52
|
+
]);
|
|
53
|
+
props.project = answers;
|
|
54
|
+
}
|
|
55
|
+
writeOut(props)((await props.apiClient.createProject({
|
|
56
|
+
project: props.project,
|
|
57
|
+
})).data.project, { fields: PROJECT_FIELDS });
|
|
58
|
+
};
|
|
59
|
+
const deleteProject = async (props) => {
|
|
60
|
+
writeOut(props)((await props.apiClient.deleteProject(props.project.id)).data.project, {
|
|
61
|
+
fields: PROJECT_FIELDS,
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
const update = async (props) => {
|
|
65
|
+
writeOut(props)((await props.apiClient.updateProject(props.project.id, {
|
|
66
|
+
project: props.project,
|
|
67
|
+
})).data.project, { fields: PROJECT_FIELDS });
|
|
68
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { writeOut } from '../writer.js';
|
|
2
|
+
export const command = 'me';
|
|
3
|
+
export const describe = 'Show current user';
|
|
4
|
+
export const builder = (yargs) => yargs;
|
|
5
|
+
export const handler = async (args) => {
|
|
6
|
+
await me(args);
|
|
7
|
+
};
|
|
8
|
+
const me = async (props) => {
|
|
9
|
+
writeOut(props)((await props.apiClient.getCurrentUserInfo()).data, {
|
|
10
|
+
fields: ['login', 'email', 'name', 'projects_limit'],
|
|
11
|
+
});
|
|
12
|
+
};
|
package/config.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
4
|
+
export const defaultDir = join(homedir(), process.env.XDG_CONFIG_HOME || '.config', 'neonctl');
|
|
5
|
+
export const ensureConfigDir = async ({ 'config-dir': configDir, }) => {
|
|
6
|
+
if (!existsSync(configDir)) {
|
|
7
|
+
mkdirSync(configDir);
|
|
8
|
+
}
|
|
9
|
+
};
|
package/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import yargs from 'yargs';
|
|
2
|
+
import { hideBin } from 'yargs/helpers';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { ensureAuth } from './commands/auth.js';
|
|
5
|
+
import { defaultDir, ensureConfigDir } from './config.js';
|
|
6
|
+
import { log } from './log.js';
|
|
7
|
+
import { defaultClientID } from './auth.js';
|
|
8
|
+
import { isApiError } from './api.js';
|
|
9
|
+
import { fillInArgs } from './utils.js';
|
|
10
|
+
import commands from './commands/index.js';
|
|
11
|
+
const pkg = JSON.parse(readFileSync('package.json', 'utf-8'));
|
|
12
|
+
const builder = yargs(hideBin(process.argv))
|
|
13
|
+
.scriptName(pkg.name)
|
|
14
|
+
.usage('usage: $0 <cmd> [args]')
|
|
15
|
+
.help()
|
|
16
|
+
.option('output', {
|
|
17
|
+
describe: 'Set output format',
|
|
18
|
+
type: 'string',
|
|
19
|
+
choices: ['json', 'yaml', 'table'],
|
|
20
|
+
default: 'table',
|
|
21
|
+
})
|
|
22
|
+
.option('api-host', {
|
|
23
|
+
describe: 'The API host',
|
|
24
|
+
default: 'https://console.neon.tech/api/v2',
|
|
25
|
+
})
|
|
26
|
+
// Setup config directory
|
|
27
|
+
.option('config-dir', {
|
|
28
|
+
describe: 'Path to config directory',
|
|
29
|
+
type: 'string',
|
|
30
|
+
default: defaultDir,
|
|
31
|
+
})
|
|
32
|
+
.middleware(ensureConfigDir)
|
|
33
|
+
// Auth flow
|
|
34
|
+
.option('oauth-host', {
|
|
35
|
+
description: 'URL to Neon OAUTH host',
|
|
36
|
+
default: 'https://oauth2.neon.tech',
|
|
37
|
+
})
|
|
38
|
+
.option('client-id', {
|
|
39
|
+
description: 'OAuth client id',
|
|
40
|
+
type: 'string',
|
|
41
|
+
default: defaultClientID,
|
|
42
|
+
})
|
|
43
|
+
.option('api-key', {
|
|
44
|
+
describe: 'API key',
|
|
45
|
+
type: 'string',
|
|
46
|
+
default: '',
|
|
47
|
+
})
|
|
48
|
+
.option('apiClient', {
|
|
49
|
+
hidden: true,
|
|
50
|
+
coerce: (v) => v,
|
|
51
|
+
default: true,
|
|
52
|
+
})
|
|
53
|
+
.middleware((args) => fillInArgs(args), true)
|
|
54
|
+
.middleware(ensureAuth)
|
|
55
|
+
.command(commands)
|
|
56
|
+
.fail(async (msg, err) => {
|
|
57
|
+
if (isApiError(err)) {
|
|
58
|
+
if (err.response.status === 401) {
|
|
59
|
+
log.error('Authentication failed, please run `neonctl auth`');
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
log.error('%d: %s\n%s', err.response.status, err.response.statusText, err.response.data?.message);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
log.error(msg || err?.message);
|
|
67
|
+
}
|
|
68
|
+
process.exit(1);
|
|
69
|
+
});
|
|
70
|
+
(async () => {
|
|
71
|
+
const args = await builder.argv;
|
|
72
|
+
if (args._.length === 0) {
|
|
73
|
+
builder.showHelp();
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
})();
|
package/log.js
ADDED
package/package.json
CHANGED
|
@@ -1,50 +1,65 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
2
|
+
"name": "neonctl",
|
|
3
|
+
"repository": {
|
|
4
|
+
"type": "git",
|
|
5
|
+
"url": "git@github.com:neondatabase/neonctl.git"
|
|
6
|
+
},
|
|
7
|
+
"type": "module",
|
|
8
|
+
"version": "0.7.0-test.1",
|
|
9
|
+
"description": "CLI tool for NeonDB Cloud management",
|
|
10
|
+
"main": "index.js",
|
|
11
|
+
"author": "NeonDB",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"private": false,
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"neonctl": "cli.js"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@semantic-release/git": "^10.0.1",
|
|
22
|
+
"@types/cli-table": "^0.3.0",
|
|
23
|
+
"@types/inquirer": "^9.0.3",
|
|
24
|
+
"@types/node": "^18.7.13",
|
|
25
|
+
"@types/yargs": "^17.0.24",
|
|
26
|
+
"@typescript-eslint/eslint-plugin": "^5.34.0",
|
|
27
|
+
"@typescript-eslint/parser": "^5.34.0",
|
|
28
|
+
"eslint": "^8.22.0",
|
|
29
|
+
"husky": "^8.0.1",
|
|
30
|
+
"lint-staged": "^13.0.3",
|
|
31
|
+
"prettier": "^2.7.1",
|
|
32
|
+
"semantic-release": "^21.0.2",
|
|
33
|
+
"ts-morph": "^18.0.0",
|
|
34
|
+
"ts-node": "^10.9.1",
|
|
35
|
+
"typescript": "^4.7.4"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@neondatabase/api-client": "^0.1.0",
|
|
39
|
+
"axios": "^1.4.0",
|
|
40
|
+
"cli-table": "^0.3.11",
|
|
41
|
+
"inquirer": "^9.2.6",
|
|
42
|
+
"open": "^8.4.0",
|
|
43
|
+
"openid-client": "^5.1.9",
|
|
44
|
+
"yaml": "^2.1.1",
|
|
45
|
+
"yargs": "^17.7.2"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"registry": "https://registry.npmjs.org/"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"watch": "tsc --watch",
|
|
53
|
+
"lint": "tsc --noEmit && eslint src --ext .ts",
|
|
54
|
+
"build": "npm run generateParams && npm run clean && tsc && cp src/*.html dist/ && cp package.json ./dist",
|
|
55
|
+
"clean": "rm -rf dist",
|
|
56
|
+
"generateParams": "ts-node --esm generateParams.ts",
|
|
57
|
+
"start": "node src/index.js"
|
|
58
|
+
},
|
|
59
|
+
"lint-staged": {
|
|
60
|
+
"*.ts": [
|
|
61
|
+
"eslint --cache --fix",
|
|
62
|
+
"prettier --write"
|
|
63
|
+
]
|
|
64
|
+
}
|
|
50
65
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// FILE IS GENERATED, DO NOT EDIT
|
|
2
|
+
export const projectCreateRequest = {
|
|
3
|
+
'project.settings.quota.active_time_seconds': {
|
|
4
|
+
type: 'number',
|
|
5
|
+
description: "The total amount of wall-clock time allowed to be spent by project's compute endpoints.",
|
|
6
|
+
},
|
|
7
|
+
'project.settings.quota.compute_time_seconds': {
|
|
8
|
+
type: 'number',
|
|
9
|
+
description: "The total amount of CPU seconds allowed to be spent by project's compute endpoints.",
|
|
10
|
+
},
|
|
11
|
+
'project.settings.quota.written_data_bytes': {
|
|
12
|
+
type: 'number',
|
|
13
|
+
description: "Total amount of data written to all project's branches.",
|
|
14
|
+
},
|
|
15
|
+
'project.settings.quota.data_transfer_bytes': {
|
|
16
|
+
type: 'number',
|
|
17
|
+
description: "Total amount of data transferred from all project's branches using proxy.",
|
|
18
|
+
},
|
|
19
|
+
'project.settings.quota.logical_size_bytes': {
|
|
20
|
+
type: 'number',
|
|
21
|
+
description: "Limit on the logical size of every project's branch.",
|
|
22
|
+
},
|
|
23
|
+
'project.name': {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: "The project name",
|
|
26
|
+
},
|
|
27
|
+
'project.branch.name': {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: "The branch name. If not specified, the default branch name will be used.",
|
|
30
|
+
},
|
|
31
|
+
'project.branch.role_name': {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: "The role name. If not specified, the default role name will be used.",
|
|
34
|
+
},
|
|
35
|
+
'project.branch.database_name': {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: "The database name. If not specified, the default database name will be used.",
|
|
38
|
+
},
|
|
39
|
+
'project.autoscaling_limit_min_cu': {
|
|
40
|
+
type: 'number',
|
|
41
|
+
description: "The minimum number of CPU units",
|
|
42
|
+
},
|
|
43
|
+
'project.autoscaling_limit_max_cu': {
|
|
44
|
+
type: 'number',
|
|
45
|
+
description: "The maximum number of CPU units",
|
|
46
|
+
},
|
|
47
|
+
'project.provisioner': {
|
|
48
|
+
type: 'string',
|
|
49
|
+
choices: ["k8s-pod", "k8s-neonvm", "docker"],
|
|
50
|
+
description: "The Neon compute provisioner.",
|
|
51
|
+
},
|
|
52
|
+
'project.region_id': {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: "The region identifier. See [the documentation](https://neon.tech/docs/introduction/regions) for the list of supported regions.",
|
|
55
|
+
},
|
|
56
|
+
'project.pg_version': {
|
|
57
|
+
type: 'number',
|
|
58
|
+
description: "The major PostgreSQL version number. Currently supported version are `14` and `15`.",
|
|
59
|
+
},
|
|
60
|
+
'project.store_passwords': {
|
|
61
|
+
type: 'boolean',
|
|
62
|
+
description: "Whether or not passwords are stored for roles in the Neon project. Storing passwords facilitates access to Neon features that require authorization.",
|
|
63
|
+
},
|
|
64
|
+
};
|
package/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/utils.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This middleware is needed to fill in the args for nested objects,
|
|
3
|
+
* so that required arguments would work
|
|
4
|
+
* otherwise yargs just throws an error
|
|
5
|
+
*/
|
|
6
|
+
export const fillInArgs = (args, currentArgs = args, acc = []) => {
|
|
7
|
+
Object.entries(currentArgs).forEach(([k, v]) => {
|
|
8
|
+
if (k === '_') {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
// check if the value is an Object
|
|
12
|
+
if (typeof v === 'object' && v !== null) {
|
|
13
|
+
fillInArgs(args, v, [...acc, k]);
|
|
14
|
+
}
|
|
15
|
+
else if (acc.length > 0) {
|
|
16
|
+
// if it's not an object, and we have a path, fill it in
|
|
17
|
+
args[acc.join('.') + '.' + k] = v;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
};
|
package/writer.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import YAML from 'yaml';
|
|
2
|
+
import Table from 'cli-table';
|
|
3
|
+
// Allow PIPE to finish reading before the end of the output.
|
|
4
|
+
process.stdout.on('error', function (err) {
|
|
5
|
+
if (err.code == 'EPIPE') {
|
|
6
|
+
process.exit(0);
|
|
7
|
+
}
|
|
8
|
+
});
|
|
9
|
+
export const writeOut = (props) => (data, config) => {
|
|
10
|
+
if (props.output == 'yaml') {
|
|
11
|
+
process.stdout.write(YAML.stringify(data, null, 2));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (props.output == 'json') {
|
|
15
|
+
process.stdout.write(JSON.stringify(data, null, 2));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const arrayData = Array.isArray(data) ? data : [data];
|
|
19
|
+
const table = new Table({
|
|
20
|
+
style: {
|
|
21
|
+
head: ['green'],
|
|
22
|
+
},
|
|
23
|
+
head: config.fields.map((field) => field
|
|
24
|
+
.split('_')
|
|
25
|
+
.map((word) => word[0].toUpperCase() + word.slice(1))
|
|
26
|
+
.join(' ')),
|
|
27
|
+
});
|
|
28
|
+
arrayData.forEach((item) => {
|
|
29
|
+
table.push(config.fields.map((field) => item[field] ?? ''));
|
|
30
|
+
});
|
|
31
|
+
process.stdout.write(table.toString());
|
|
32
|
+
process.stdout.write('\n');
|
|
33
|
+
};
|
package/src/api/gateway.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.apiCall = exports.ApiError = void 0;
|
|
13
|
-
const node_https_1 = require("node:https");
|
|
14
|
-
class ApiError extends Error {
|
|
15
|
-
constructor(response) {
|
|
16
|
-
super(response.statusMessage);
|
|
17
|
-
this.response = response;
|
|
18
|
-
}
|
|
19
|
-
getApiError() {
|
|
20
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
-
return new Promise((resolve, reject) => {
|
|
22
|
-
const data = [];
|
|
23
|
-
this.response
|
|
24
|
-
.on('data', (chunk) => {
|
|
25
|
-
data.push(chunk);
|
|
26
|
-
})
|
|
27
|
-
.on('end', () => {
|
|
28
|
-
try {
|
|
29
|
-
const json = JSON.parse(data.join(''));
|
|
30
|
-
resolve((json === null || json === void 0 ? void 0 : json.message) ||
|
|
31
|
-
`${this.response.statusCode}:${this.message}:${data.join('')}`);
|
|
32
|
-
}
|
|
33
|
-
catch (e) {
|
|
34
|
-
reject(e);
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
.on('error', (e) => {
|
|
38
|
-
reject(e);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
exports.ApiError = ApiError;
|
|
45
|
-
const apiCall = ({ apiHost, token, path, body, method = 'GET', }) => new Promise((resolve, reject) => {
|
|
46
|
-
const req = (0, node_https_1.request)(`${apiHost}/api/v1/${path}`, {
|
|
47
|
-
headers: {
|
|
48
|
-
Authorization: `Bearer ${token}`,
|
|
49
|
-
Accept: 'application/json',
|
|
50
|
-
'Content-Type': 'application/json',
|
|
51
|
-
},
|
|
52
|
-
method,
|
|
53
|
-
}, (res) => {
|
|
54
|
-
if (!res.statusCode || res.statusCode >= 400) {
|
|
55
|
-
reject(new ApiError(res));
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
let result = '';
|
|
59
|
-
res.on('data', (data) => {
|
|
60
|
-
result += data;
|
|
61
|
-
});
|
|
62
|
-
res.on('end', () => {
|
|
63
|
-
resolve(JSON.parse(result));
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
req.on('error', reject);
|
|
67
|
-
if (body) {
|
|
68
|
-
req.write(JSON.stringify(body));
|
|
69
|
-
}
|
|
70
|
-
req.end();
|
|
71
|
-
});
|
|
72
|
-
exports.apiCall = apiCall;
|
package/src/api/projects.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createProject = exports.listProjects = void 0;
|
|
4
|
-
const gateway_1 = require("./gateway");
|
|
5
|
-
const listProjects = (props) => (0, gateway_1.apiCall)(Object.assign(Object.assign({}, props), { path: 'projects', method: 'GET' }));
|
|
6
|
-
exports.listProjects = listProjects;
|
|
7
|
-
const createProject = (props) => (0, gateway_1.apiCall)(Object.assign(Object.assign({}, props), { body: {
|
|
8
|
-
project: {
|
|
9
|
-
settings: props.settings,
|
|
10
|
-
name: props.name,
|
|
11
|
-
},
|
|
12
|
-
}, path: 'projects', method: 'POST' }));
|
|
13
|
-
exports.createProject = createProject;
|
package/src/api/users.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.apiMe = void 0;
|
|
4
|
-
const gateway_1 = require("./gateway");
|
|
5
|
-
const apiMe = (props) => (0, gateway_1.apiCall)(Object.assign(Object.assign({}, props), { path: 'users/me', method: 'GET' }));
|
|
6
|
-
exports.apiMe = apiMe;
|
package/src/auth.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.auth = exports.refreshToken = void 0;
|
|
16
|
-
const openid_client_1 = require("openid-client");
|
|
17
|
-
const node_http_1 = require("node:http");
|
|
18
|
-
const node_fs_1 = require("node:fs");
|
|
19
|
-
const node_path_1 = require("node:path");
|
|
20
|
-
const open_1 = __importDefault(require("open"));
|
|
21
|
-
const log_1 = require("./log");
|
|
22
|
-
// oauth server timeouts
|
|
23
|
-
const SERVER_TIMEOUT = 10000;
|
|
24
|
-
// where to wait for incoming redirect request from oauth server to arrive
|
|
25
|
-
const REDIRECT_URI = (port) => `http://127.0.0.1:${port}/callback`;
|
|
26
|
-
// These scopes cannot be cancelled, they are always needed.
|
|
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'],
|
|
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* () {
|
|
51
|
-
log_1.log.info('Discovering oauth server');
|
|
52
|
-
const issuer = yield openid_client_1.Issuer.discover(oauthHost);
|
|
53
|
-
//
|
|
54
|
-
// Start HTTP server and wait till /callback is hit
|
|
55
|
-
//
|
|
56
|
-
const server = (0, node_http_1.createServer)();
|
|
57
|
-
server.listen(0, function () {
|
|
58
|
-
log_1.log.info(`Listening on port ${this.address().port}`);
|
|
59
|
-
});
|
|
60
|
-
const listen_port = server.address().port;
|
|
61
|
-
const neonOAuthClient = new issuer.Client({
|
|
62
|
-
token_endpoint_auth_method: 'none',
|
|
63
|
-
client_id: clientId,
|
|
64
|
-
redirect_uris: [REDIRECT_URI(listen_port)],
|
|
65
|
-
response_types: ['code'],
|
|
66
|
-
});
|
|
67
|
-
// https://datatracker.ietf.org/doc/html/rfc6819#section-4.4.1.8
|
|
68
|
-
const state = openid_client_1.generators.state();
|
|
69
|
-
// we store the code_verifier in memory
|
|
70
|
-
const codeVerifier = openid_client_1.generators.codeVerifier();
|
|
71
|
-
const codeChallenge = openid_client_1.generators.codeChallenge(codeVerifier);
|
|
72
|
-
return new Promise((resolve) => {
|
|
73
|
-
server.on('request', (request, response) => __awaiter(void 0, void 0, void 0, function* () {
|
|
74
|
-
var _a;
|
|
75
|
-
//
|
|
76
|
-
// Wait for callback and follow oauth flow.
|
|
77
|
-
//
|
|
78
|
-
if (!((_a = request.url) === null || _a === void 0 ? void 0 : _a.startsWith('/callback'))) {
|
|
79
|
-
response.writeHead(404);
|
|
80
|
-
response.end();
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
log_1.log.info(`Callback received: ${request.url}`);
|
|
84
|
-
const params = neonOAuthClient.callbackParams(request);
|
|
85
|
-
const tokenSet = yield neonOAuthClient.callback(REDIRECT_URI(listen_port), params, {
|
|
86
|
-
code_verifier: codeVerifier,
|
|
87
|
-
state,
|
|
88
|
-
});
|
|
89
|
-
response.writeHead(200, { 'Content-Type': 'text/html' });
|
|
90
|
-
(0, node_fs_1.createReadStream)((0, node_path_1.join)(__dirname, './callback.html')).pipe(response);
|
|
91
|
-
resolve(tokenSet);
|
|
92
|
-
server.close();
|
|
93
|
-
}));
|
|
94
|
-
//
|
|
95
|
-
// Open browser to let user authenticate
|
|
96
|
-
//
|
|
97
|
-
const authUrl = neonOAuthClient.authorizationUrl({
|
|
98
|
-
scope: DEFAULT_SCOPES.join(' '),
|
|
99
|
-
state,
|
|
100
|
-
code_challenge: codeChallenge,
|
|
101
|
-
code_challenge_method: 'S256',
|
|
102
|
-
});
|
|
103
|
-
(0, open_1.default)(authUrl);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
exports.auth = auth;
|
package/src/cli.js
DELETED
package/src/commands/auth.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
-
});
|
|
33
|
-
};
|
|
34
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
-
exports.ensureAuth = exports.authFlow = void 0;
|
|
36
|
-
const node_path_1 = require("node:path");
|
|
37
|
-
const node_fs_1 = require("node:fs");
|
|
38
|
-
const openid_client_1 = require("openid-client");
|
|
39
|
-
const auth_1 = require("../auth");
|
|
40
|
-
const log_1 = require("../log");
|
|
41
|
-
const users_1 = require("../api/users");
|
|
42
|
-
const gateway_1 = require("../api/gateway");
|
|
43
|
-
const CREDENTIALS_FILE = 'credentials.json';
|
|
44
|
-
const authFlow = ({ 'config-dir': configDir, 'oauth-host': oauthHost, 'client-id': clientId, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
|
-
if (!clientId) {
|
|
46
|
-
throw new Error('Missing client id');
|
|
47
|
-
}
|
|
48
|
-
const tokenSet = yield (0, auth_1.auth)({
|
|
49
|
-
oauthHost: oauthHost,
|
|
50
|
-
clientId: clientId,
|
|
51
|
-
});
|
|
52
|
-
const credentialsPath = (0, node_path_1.join)(configDir, CREDENTIALS_FILE);
|
|
53
|
-
updateCredentialsFile(credentialsPath, JSON.stringify(tokenSet));
|
|
54
|
-
log_1.log.info(`Saved credentials to ${credentialsPath}`);
|
|
55
|
-
log_1.log.info('Auth complete');
|
|
56
|
-
return tokenSet.access_token || '';
|
|
57
|
-
});
|
|
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
|
-
}
|
|
77
|
-
const ensureAuth = (props) => __awaiter(void 0, void 0, void 0, function* () {
|
|
78
|
-
if (props.token || props._[0] === 'auth') {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const credentialsPath = (0, node_path_1.join)(props['config-dir'], CREDENTIALS_FILE);
|
|
82
|
-
if ((0, node_fs_1.existsSync)(credentialsPath)) {
|
|
83
|
-
try {
|
|
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 });
|
|
98
|
-
props.token = token;
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
catch (e) {
|
|
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);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
props.token = yield (0, exports.authFlow)(props);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
exports.ensureAuth = ensureAuth;
|
package/src/commands/projects.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.create = exports.list = void 0;
|
|
13
|
-
const projects_1 = require("../api/projects");
|
|
14
|
-
const writer_1 = require("../writer");
|
|
15
|
-
const list = (props) => __awaiter(void 0, void 0, void 0, function* () {
|
|
16
|
-
(0, writer_1.writeOut)(props)(yield (0, projects_1.listProjects)(props), { fields: [] });
|
|
17
|
-
});
|
|
18
|
-
exports.list = list;
|
|
19
|
-
const create = (props) => __awaiter(void 0, void 0, void 0, function* () {
|
|
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'] });
|
|
21
|
-
});
|
|
22
|
-
exports.create = create;
|
package/src/commands/users.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.me = void 0;
|
|
13
|
-
const users_1 = require("../api/users");
|
|
14
|
-
const writer_1 = require("../writer");
|
|
15
|
-
const me = (props) => __awaiter(void 0, void 0, void 0, function* () {
|
|
16
|
-
(0, writer_1.writeOut)(props)(yield (0, users_1.apiMe)(props), {
|
|
17
|
-
fields: ['login', 'email', 'name', 'projects_limit'],
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
exports.me = me;
|
package/src/config.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.ensureConfigDir = exports.defaultDir = void 0;
|
|
13
|
-
const node_path_1 = require("node:path");
|
|
14
|
-
const node_os_1 = require("node:os");
|
|
15
|
-
const node_fs_1 = require("node:fs");
|
|
16
|
-
const DIR_NAME = '.neonctl';
|
|
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;
|
|
20
|
-
const ensureConfigDir = ({ 'config-dir': configDir, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
21
|
-
if (!(0, node_fs_1.existsSync)(configDir)) {
|
|
22
|
-
(0, node_fs_1.mkdirSync)(configDir);
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
exports.ensureConfigDir = ensureConfigDir;
|
package/src/index.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
-
});
|
|
33
|
-
};
|
|
34
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
-
};
|
|
37
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
-
const yargs = __importStar(require("yargs"));
|
|
39
|
-
const package_json_1 = __importDefault(require("../package.json"));
|
|
40
|
-
const gateway_1 = require("./api/gateway");
|
|
41
|
-
const auth_1 = require("./commands/auth");
|
|
42
|
-
const config_1 = require("./config");
|
|
43
|
-
const log_1 = require("./log");
|
|
44
|
-
const showHelpMiddleware = (argv) => {
|
|
45
|
-
if (argv._.length === 1) {
|
|
46
|
-
yargs.showHelp();
|
|
47
|
-
process.exit(0);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const builder = yargs
|
|
51
|
-
.scriptName(package_json_1.default.name)
|
|
52
|
-
.usage('usage: $0 <cmd> [args]')
|
|
53
|
-
.help()
|
|
54
|
-
.option('json', {
|
|
55
|
-
describe: 'Set output format to JSON',
|
|
56
|
-
type: 'boolean',
|
|
57
|
-
})
|
|
58
|
-
.option('api-host', {
|
|
59
|
-
describe: 'The API host',
|
|
60
|
-
default: 'https://console.neon.tech',
|
|
61
|
-
})
|
|
62
|
-
// Setup config directory
|
|
63
|
-
.option('config-dir', {
|
|
64
|
-
describe: 'Path to config directory',
|
|
65
|
-
type: 'string',
|
|
66
|
-
default: config_1.defaultDir,
|
|
67
|
-
})
|
|
68
|
-
.middleware(config_1.ensureConfigDir)
|
|
69
|
-
// Auth flow
|
|
70
|
-
.option('oauth-host', {
|
|
71
|
-
description: 'URL to Neon OAUTH host',
|
|
72
|
-
default: 'https://oauth2.neon.tech',
|
|
73
|
-
})
|
|
74
|
-
.option('client-id', {
|
|
75
|
-
description: 'OAuth client id',
|
|
76
|
-
type: 'string',
|
|
77
|
-
default: 'neonctl',
|
|
78
|
-
})
|
|
79
|
-
.command('auth', 'Authenticate user', (yargs) => yargs, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
80
|
-
(yield Promise.resolve().then(() => __importStar(require('./commands/auth')))).authFlow(args);
|
|
81
|
-
}))
|
|
82
|
-
// Ensure auth token
|
|
83
|
-
.option('token', {
|
|
84
|
-
describe: 'Auth token',
|
|
85
|
-
type: 'string',
|
|
86
|
-
default: '',
|
|
87
|
-
})
|
|
88
|
-
.command('me', 'Get user info', (yargs) => yargs.middleware(auth_1.ensureAuth), (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
89
|
-
yield (yield Promise.resolve().then(() => __importStar(require('./commands/users')))).me(args);
|
|
90
|
-
}))
|
|
91
|
-
.command('projects', 'Manage projects', (yargs) => __awaiter(void 0, void 0, void 0, function* () {
|
|
92
|
-
yargs
|
|
93
|
-
.usage('usage: $0 projects <cmd> [args]')
|
|
94
|
-
// .command(
|
|
95
|
-
// 'list',
|
|
96
|
-
// 'List projects',
|
|
97
|
-
// (yargs) => yargs,
|
|
98
|
-
// async (args) => {
|
|
99
|
-
// await (await import('./commands/projects')).list(args);
|
|
100
|
-
// }
|
|
101
|
-
// )
|
|
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* () {
|
|
106
|
-
yield (yield Promise.resolve().then(() => __importStar(require('./commands/projects')))).create(args);
|
|
107
|
-
}))
|
|
108
|
-
.middleware(showHelpMiddleware)
|
|
109
|
-
.middleware(auth_1.ensureAuth);
|
|
110
|
-
}))
|
|
111
|
-
.strict()
|
|
112
|
-
.fail((msg, err) => __awaiter(void 0, void 0, void 0, function* () {
|
|
113
|
-
if (err instanceof gateway_1.ApiError) {
|
|
114
|
-
log_1.log.error(yield err.getApiError());
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
log_1.log.error(msg || err.message);
|
|
118
|
-
}
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}));
|
|
121
|
-
(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
122
|
-
const args = yield builder.argv;
|
|
123
|
-
if (args._.length === 0) {
|
|
124
|
-
yargs.showHelp();
|
|
125
|
-
process.exit(0);
|
|
126
|
-
}
|
|
127
|
-
}))();
|
package/src/log.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.log = void 0;
|
|
4
|
-
const node_util_1 = require("node:util");
|
|
5
|
-
exports.log = {
|
|
6
|
-
info: (...args) => {
|
|
7
|
-
process.stderr.write(`INFO: ${(0, node_util_1.format)(...args)}\n`);
|
|
8
|
-
},
|
|
9
|
-
error: (...args) => {
|
|
10
|
-
process.stderr.write(`ERROR: ${(0, node_util_1.format)(...args)}\n`);
|
|
11
|
-
},
|
|
12
|
-
};
|
package/src/types.js
DELETED
package/src/writer.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
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;
|
|
File without changes
|