neonctl 1.27.6 → 1.29.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/.bump +1 -0
- package/.editorconfig +7 -0
- package/.eslintrc.cjs +15 -0
- package/.github/workflows/commitlint.yml +46 -0
- package/.github/workflows/pr.yml +25 -0
- package/.github/workflows/release.yml +30 -0
- package/.husky/commit-msg +4 -0
- package/.husky/pre-commit +4 -0
- package/.nvmrc +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc.json +3 -0
- package/.releaserc.json +47 -0
- package/LICENSE +202 -0
- package/commitlint.config.cjs +7 -0
- package/generateOptionsFromSpec.ts +68 -0
- package/jest/setup.js +5 -0
- package/jest.config.ts +199 -0
- package/mocks/bin/psql.cjs +9 -0
- package/mocks/main/projects/GET.js +27 -0
- package/mocks/main/projects/POST.js +22 -0
- package/mocks/main/projects/shared/GET.js +16 -0
- package/mocks/main/projects/test/DELETE.json +7 -0
- package/mocks/main/projects/test/GET.json +13 -0
- package/mocks/main/projects/test/PATCH.js +18 -0
- package/mocks/main/projects/test/branches/GET.json +25 -0
- package/mocks/main/projects/test/branches/POST.js +83 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/DELETE.json +7 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/GET.json +9 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/PATCH.js +14 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/databases/GET.json +6 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/databases/POST.js +13 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/databases/test_db/DELETE.json +6 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/endpoints/GET.json +26 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/endpoints/POST.json +6 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/roles/GET.json +3 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/roles/POST.js +14 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/roles/test_role/DELETE.json +6 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/roles/test_role/reveal_password/GET.json +3 -0
- package/mocks/main/projects/test/branches/br-cloudy-branch-12345678/set_as_primary/POST.json +9 -0
- package/mocks/main/projects/test/branches/br-numbered-branch-123456/GET.json +10 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/DELETE.json +7 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/GET.json +10 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/PATCH.js +14 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/databases/GET.json +6 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/databases/POST.js +13 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/databases/test_db/DELETE.json +6 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/endpoints/GET.json +26 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/endpoints/POST.json +6 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/restore/POST.js +16 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/roles/GET.json +3 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/roles/POST.js +14 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/roles/test_role/DELETE.json +6 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/roles/test_role/reveal_password/GET.json +3 -0
- package/mocks/main/projects/test/branches/br-sunny-branch-123456/set_as_primary/POST.json +9 -0
- package/mocks/main/projects/test/endpoints/GET.json +9 -0
- package/mocks/main/projects/test/endpoints/POST.js +32 -0
- package/mocks/main/projects/test/endpoints/test_endpoint_id/DELETE.json +7 -0
- package/mocks/main/projects/test/endpoints/test_endpoint_id/GET.json +9 -0
- package/mocks/main/projects/test/endpoints/test_endpoint_id/PATCH.js +17 -0
- package/mocks/main/projects/test/operations/GET.json +22 -0
- package/mocks/main/users/me/GET.json +5 -0
- package/mocks/restore/projects/test/branches/GET.json +21 -0
- package/mocks/restore/projects/test/branches/br-another-branch-123456/GET.json +6 -0
- package/mocks/restore/projects/test/branches/br-another-branch-123456/restore/POST.js +13 -0
- package/mocks/restore/projects/test/branches/br-any-branch-123456/GET.json +6 -0
- package/mocks/restore/projects/test/branches/br-parent-tots-123456/GET.json +7 -0
- package/mocks/restore/projects/test/branches/br-parent-tots-123456/restore/POST.js +14 -0
- package/mocks/restore/projects/test/branches/br-self-tolsn-123456/GET.json +6 -0
- package/mocks/restore/projects/test/branches/br-self-tolsn-123456/restore/POST.js +15 -0
- package/mocks/single_project/projects/GET.json +10 -0
- package/mocks/single_project/projects/test-project-123456/GET.json +14 -0
- package/mocks/single_project/projects/test-project-123456/branches/GET.json +11 -0
- package/mocks/single_project/projects/test-project-123456/branches/br-main-branch-123456/databases/GET.json +3 -0
- package/mocks/single_project/projects/test-project-123456/branches/br-main-branch-123456/endpoints/GET.json +10 -0
- package/mocks/single_project/projects/test-project-123456/branches/br-main-branch-123456/roles/GET.json +3 -0
- package/mocks/single_project/projects/test-project-123456/branches/br-main-branch-123456/roles/test_role/reveal_password/GET.json +3 -0
- package/package.json +7 -6
- package/pkg.js +45 -3
- package/rollup.config.js +20 -0
- package/snapshots/commands/branches.test.snap +221 -0
- package/snapshots/commands/connection_string.test.snap +70 -0
- package/snapshots/commands/databases.test.snap +20 -0
- package/snapshots/commands/ip_allow.test.snap +55 -0
- package/snapshots/commands/operations.test.snap +17 -0
- package/snapshots/commands/projects.test.snap +141 -0
- package/snapshots/commands/roles.test.snap +19 -0
- package/snapshots/commands/set_context.test.snap +30 -0
- package/snapshots/writer.test.snap +60 -0
- package/snapshotsResolver.cjs +32 -0
- package/src/analytics.ts +95 -0
- package/src/api.ts +44 -0
- package/src/auth.ts +137 -0
- package/{cli.js → src/cli.ts} +1 -0
- package/src/commands/auth.test.ts +62 -0
- package/src/commands/auth.ts +148 -0
- package/src/commands/branches.test.ts +354 -0
- package/src/commands/branches.ts +451 -0
- package/src/commands/connection_string.test.ts +250 -0
- package/src/commands/connection_string.ts +210 -0
- package/src/commands/databases.test.ts +55 -0
- package/src/commands/databases.ts +129 -0
- package/src/commands/help.test.ts +13 -0
- package/{commands/index.js → src/commands/index.ts} +11 -10
- package/src/commands/ip_allow.test.ts +86 -0
- package/src/commands/ip_allow.ts +202 -0
- package/src/commands/operations.test.ts +13 -0
- package/src/commands/operations.ts +41 -0
- package/src/commands/projects.test.ts +147 -0
- package/src/commands/projects.ts +275 -0
- package/src/commands/roles.test.ts +46 -0
- package/src/commands/roles.ts +100 -0
- package/src/commands/set_context.test.ts +64 -0
- package/src/commands/set_context.ts +27 -0
- package/src/commands/user.ts +21 -0
- package/src/config.ts +22 -0
- package/src/context.ts +61 -0
- package/src/env.ts +7 -0
- package/src/errors.ts +24 -0
- package/src/help.ts +185 -0
- package/src/index.ts +180 -0
- package/src/log.ts +16 -0
- package/src/parameters.gen.ts +332 -0
- package/src/pkg.ts +9 -0
- package/src/test_utils/mock_server.ts +27 -0
- package/src/test_utils/oauth_server.ts +10 -0
- package/src/test_utils/test_cli_command.ts +117 -0
- package/src/types.ts +25 -0
- package/src/utils/enrichers.ts +73 -0
- package/src/utils/formats.test.ts +41 -0
- package/src/utils/formats.ts +11 -0
- package/src/utils/middlewares.ts +23 -0
- package/src/utils/point_in_time.ts +86 -0
- package/src/utils/psql.ts +29 -0
- package/src/utils/string.ts +8 -0
- package/src/utils/ui.ts +64 -0
- package/src/writer.test.ts +98 -0
- package/src/writer.ts +131 -0
- package/tsconfig.json +17 -0
- package/analytics.js +0 -78
- package/api.js +0 -35
- package/auth.js +0 -101
- package/commands/auth.js +0 -102
- package/commands/auth.test.js +0 -42
- package/commands/branches.js +0 -311
- package/commands/branches.test.js +0 -321
- package/commands/connection_string.js +0 -137
- package/commands/connection_string.test.js +0 -204
- package/commands/databases.js +0 -79
- package/commands/databases.test.js +0 -51
- package/commands/help.test.js +0 -11
- package/commands/ip_allow.js +0 -135
- package/commands/ip_allow.test.js +0 -78
- package/commands/operations.js +0 -28
- package/commands/operations.test.js +0 -11
- package/commands/projects.js +0 -156
- package/commands/projects.test.js +0 -116
- package/commands/roles.js +0 -57
- package/commands/roles.test.js +0 -42
- package/commands/set_context.js +0 -22
- package/commands/set_context.test.js +0 -53
- package/commands/user.js +0 -15
- package/config.js +0 -11
- package/context.js +0 -48
- package/env.js +0 -6
- package/errors.js +0 -16
- package/help.js +0 -146
- package/index.js +0 -168
- package/log.js +0 -15
- package/parameters.gen.js +0 -302
- package/test_utils/mock_server.js +0 -16
- package/test_utils/oauth_server.js +0 -9
- package/test_utils/test_cli_command.js +0 -80
- package/types.js +0 -1
- package/utils/enrichers.js +0 -49
- package/utils/formats.js +0 -5
- package/utils/formats.test.js +0 -32
- package/utils/middlewares.js +0 -20
- package/utils/point_in_time.js +0 -44
- package/utils/psql.js +0 -24
- package/utils/string.js +0 -5
- package/utils/ui.js +0 -59
- package/writer.js +0 -87
- package/writer.test.js +0 -86
- /package/{callback.html → src/callback.html} +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
import { testCliCommand } from '../test_utils/test_cli_command.js';
|
|
4
|
+
|
|
5
|
+
describe('roles', () => {
|
|
6
|
+
testCliCommand({
|
|
7
|
+
name: 'list',
|
|
8
|
+
args: ['roles', 'list', '--project-id', 'test', '--branch', 'test_branch'],
|
|
9
|
+
expected: {
|
|
10
|
+
snapshot: true,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
testCliCommand({
|
|
15
|
+
name: 'create',
|
|
16
|
+
args: [
|
|
17
|
+
'roles',
|
|
18
|
+
'create',
|
|
19
|
+
'--project-id',
|
|
20
|
+
'test',
|
|
21
|
+
'--branch',
|
|
22
|
+
'test_branch',
|
|
23
|
+
'--name',
|
|
24
|
+
'test_role',
|
|
25
|
+
],
|
|
26
|
+
expected: {
|
|
27
|
+
snapshot: true,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
testCliCommand({
|
|
32
|
+
name: 'delete',
|
|
33
|
+
args: [
|
|
34
|
+
'roles',
|
|
35
|
+
'delete',
|
|
36
|
+
'test_role',
|
|
37
|
+
'--project-id',
|
|
38
|
+
'test',
|
|
39
|
+
'--branch',
|
|
40
|
+
'test_branch',
|
|
41
|
+
],
|
|
42
|
+
expected: {
|
|
43
|
+
snapshot: true,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import yargs from 'yargs';
|
|
2
|
+
import { retryOnLock } from '../api.js';
|
|
3
|
+
import { branchIdFromProps, fillSingleProject } from '../utils/enrichers.js';
|
|
4
|
+
|
|
5
|
+
import { BranchScopeProps } from '../types.js';
|
|
6
|
+
import { writer } from '../writer.js';
|
|
7
|
+
|
|
8
|
+
const ROLES_FIELDS = ['name', 'created_at'] as const;
|
|
9
|
+
|
|
10
|
+
export const command = 'roles';
|
|
11
|
+
export const describe = 'Manage roles';
|
|
12
|
+
export const aliases = ['role'];
|
|
13
|
+
export const builder = (argv: yargs.Argv) =>
|
|
14
|
+
argv
|
|
15
|
+
.usage('$0 roles <sub-command> [options]')
|
|
16
|
+
.options({
|
|
17
|
+
'project-id': {
|
|
18
|
+
describe: 'Project ID',
|
|
19
|
+
type: 'string',
|
|
20
|
+
},
|
|
21
|
+
branch: {
|
|
22
|
+
describe: 'Branch ID or name',
|
|
23
|
+
type: 'string',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
.middleware(fillSingleProject as any)
|
|
27
|
+
.command(
|
|
28
|
+
'list',
|
|
29
|
+
'List roles',
|
|
30
|
+
(yargs) => yargs,
|
|
31
|
+
async (args) => await list(args as any),
|
|
32
|
+
)
|
|
33
|
+
.command(
|
|
34
|
+
'create',
|
|
35
|
+
'Create a role',
|
|
36
|
+
(yargs) =>
|
|
37
|
+
yargs.options({
|
|
38
|
+
name: {
|
|
39
|
+
describe: 'Role name',
|
|
40
|
+
type: 'string',
|
|
41
|
+
demandOption: true,
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
async (args) => await create(args as any),
|
|
45
|
+
)
|
|
46
|
+
.command(
|
|
47
|
+
'delete <role>',
|
|
48
|
+
'Delete a role',
|
|
49
|
+
(yargs) => yargs,
|
|
50
|
+
async (args) => await deleteRole(args as any),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
export const handler = (args: yargs.Argv) => {
|
|
54
|
+
return args;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const list = async (props: BranchScopeProps) => {
|
|
58
|
+
const branchId = await branchIdFromProps(props);
|
|
59
|
+
const { data } = await props.apiClient.listProjectBranchRoles(
|
|
60
|
+
props.projectId,
|
|
61
|
+
branchId,
|
|
62
|
+
);
|
|
63
|
+
writer(props).end(data.roles, {
|
|
64
|
+
fields: ROLES_FIELDS,
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const create = async (
|
|
69
|
+
props: BranchScopeProps & {
|
|
70
|
+
name: string;
|
|
71
|
+
},
|
|
72
|
+
) => {
|
|
73
|
+
const branchId = await branchIdFromProps(props);
|
|
74
|
+
const { data } = await retryOnLock(() =>
|
|
75
|
+
props.apiClient.createProjectBranchRole(props.projectId, branchId, {
|
|
76
|
+
role: {
|
|
77
|
+
name: props.name,
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
writer(props).end(data.role, {
|
|
82
|
+
fields: ROLES_FIELDS,
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const deleteRole = async (
|
|
87
|
+
props: BranchScopeProps & { role: string },
|
|
88
|
+
) => {
|
|
89
|
+
const branchId = await branchIdFromProps(props);
|
|
90
|
+
const { data } = await retryOnLock(() =>
|
|
91
|
+
props.apiClient.deleteProjectBranchRole(
|
|
92
|
+
props.projectId,
|
|
93
|
+
branchId,
|
|
94
|
+
props.role,
|
|
95
|
+
),
|
|
96
|
+
);
|
|
97
|
+
writer(props).end(data.role, {
|
|
98
|
+
fields: ROLES_FIELDS,
|
|
99
|
+
});
|
|
100
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { tmpdir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { afterAll, describe } from '@jest/globals';
|
|
5
|
+
import { testCliCommand } from '../test_utils/test_cli_command';
|
|
6
|
+
|
|
7
|
+
const CONTEXT_FILE = join(tmpdir(), `neon_${Date.now()}`);
|
|
8
|
+
|
|
9
|
+
describe('set_context', () => {
|
|
10
|
+
afterAll(() => {
|
|
11
|
+
rmSync(CONTEXT_FILE);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('should set the context', () => {
|
|
15
|
+
testCliCommand({
|
|
16
|
+
name: 'set-context',
|
|
17
|
+
args: [
|
|
18
|
+
'set-context',
|
|
19
|
+
'--project-id',
|
|
20
|
+
'test',
|
|
21
|
+
'--context-file',
|
|
22
|
+
CONTEXT_FILE,
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
testCliCommand({
|
|
27
|
+
name: 'list branches selecting project from the context',
|
|
28
|
+
args: ['branches', 'list', '--context-file', CONTEXT_FILE],
|
|
29
|
+
expected: {
|
|
30
|
+
snapshot: true,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const overrideContextFile = join(
|
|
35
|
+
tmpdir(),
|
|
36
|
+
`neon_override_ctx_${Date.now()}`,
|
|
37
|
+
);
|
|
38
|
+
testCliCommand({
|
|
39
|
+
name: 'get branch id overrides context set branch',
|
|
40
|
+
before: async () => {
|
|
41
|
+
writeFileSync(
|
|
42
|
+
overrideContextFile,
|
|
43
|
+
JSON.stringify({
|
|
44
|
+
projectId: 'test',
|
|
45
|
+
branchId: 'br-cloudy-branch-12345678',
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
},
|
|
49
|
+
after: async () => {
|
|
50
|
+
rmSync(overrideContextFile);
|
|
51
|
+
},
|
|
52
|
+
args: [
|
|
53
|
+
'branches',
|
|
54
|
+
'get',
|
|
55
|
+
'br-sunny-branch-123456',
|
|
56
|
+
'--context-file',
|
|
57
|
+
overrideContextFile,
|
|
58
|
+
],
|
|
59
|
+
expected: {
|
|
60
|
+
snapshot: true,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import yargs from 'yargs';
|
|
2
|
+
import { Context, updateContextFile } from '../context.js';
|
|
3
|
+
import { branchIdFromProps } from '../utils/enrichers.js';
|
|
4
|
+
import { BranchScopeProps } from '../types.js';
|
|
5
|
+
|
|
6
|
+
export const command = 'set-context';
|
|
7
|
+
export const describe = 'Set the current context';
|
|
8
|
+
export const builder = (argv: yargs.Argv) =>
|
|
9
|
+
argv.usage('$0 set-context [options]').options({
|
|
10
|
+
'project-id': {
|
|
11
|
+
describe: 'Project ID',
|
|
12
|
+
type: 'string',
|
|
13
|
+
},
|
|
14
|
+
branch: {
|
|
15
|
+
describe: 'Branch ID or name',
|
|
16
|
+
type: 'string',
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const handler = async (props: BranchScopeProps) => {
|
|
21
|
+
const branchId = await branchIdFromProps(props);
|
|
22
|
+
const context: Context = {
|
|
23
|
+
projectId: props.projectId,
|
|
24
|
+
branchId,
|
|
25
|
+
};
|
|
26
|
+
updateContextFile(props.contextFile, context);
|
|
27
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import yargs from 'yargs';
|
|
2
|
+
|
|
3
|
+
import { CommonProps } from '../types.js';
|
|
4
|
+
import { writer } from '../writer.js';
|
|
5
|
+
|
|
6
|
+
export const command = 'me';
|
|
7
|
+
export const describe = 'Show current user';
|
|
8
|
+
export const builder = (yargs: yargs.Argv) =>
|
|
9
|
+
yargs.option('context-file', {
|
|
10
|
+
hidden: true,
|
|
11
|
+
});
|
|
12
|
+
export const handler = async (args: CommonProps) => {
|
|
13
|
+
await me(args);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const me = async (props: CommonProps) => {
|
|
17
|
+
const { data } = await props.apiClient.getCurrentUserInfo();
|
|
18
|
+
writer(props).end(data, {
|
|
19
|
+
fields: ['login', 'email', 'name', 'projects_limit'],
|
|
20
|
+
});
|
|
21
|
+
};
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import yargs from 'yargs';
|
|
5
|
+
|
|
6
|
+
import { isCi } from './env.js';
|
|
7
|
+
|
|
8
|
+
export const CREDENTIALS_FILE = 'credentials.json';
|
|
9
|
+
|
|
10
|
+
export const defaultDir = join(
|
|
11
|
+
process.env.XDG_CONFIG_HOME || join(homedir(), '.config'),
|
|
12
|
+
'neonctl',
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const ensureConfigDir = async ({
|
|
16
|
+
'config-dir': configDir,
|
|
17
|
+
'force-auth': forceAuth,
|
|
18
|
+
}: yargs.Arguments<{ 'config-dir': string }>) => {
|
|
19
|
+
if (!existsSync(configDir) && (!isCi() || forceAuth)) {
|
|
20
|
+
mkdirSync(configDir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
};
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { accessSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { normalize, resolve } from 'node:path';
|
|
4
|
+
import yargs from 'yargs';
|
|
5
|
+
|
|
6
|
+
export type Context = {
|
|
7
|
+
projectId?: string;
|
|
8
|
+
branchId?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const CONTEXT_FILE = '.neon';
|
|
12
|
+
const CHECK_FILES = [CONTEXT_FILE, 'package.json', '.git'];
|
|
13
|
+
|
|
14
|
+
const wrapWithContextFile = (dir: string) => resolve(dir, CONTEXT_FILE);
|
|
15
|
+
|
|
16
|
+
export const currentContextFile = () => {
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
let currentDir = cwd;
|
|
19
|
+
const root = normalize('/');
|
|
20
|
+
const home = homedir();
|
|
21
|
+
while (currentDir !== root && currentDir !== home) {
|
|
22
|
+
for (const file of CHECK_FILES) {
|
|
23
|
+
try {
|
|
24
|
+
accessSync(resolve(currentDir, file));
|
|
25
|
+
return wrapWithContextFile(currentDir);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
// ignore
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
currentDir = resolve(currentDir, '..');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return wrapWithContextFile(cwd);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const readContextFile = (file: string): Context => {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(readFileSync(file, 'utf-8'));
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const enrichFromContext = (
|
|
45
|
+
args: yargs.Arguments<{ contextFile: string }>,
|
|
46
|
+
) => {
|
|
47
|
+
if (args._[0] === 'set-context') {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const context = readContextFile(args.contextFile);
|
|
51
|
+
if (!args.branch && !args.id && !args.name) {
|
|
52
|
+
args.branch = context.branchId;
|
|
53
|
+
}
|
|
54
|
+
if (!args.projectId) {
|
|
55
|
+
args.projectId = context.projectId;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const updateContextFile = (file: string, context: Context) => {
|
|
60
|
+
writeFileSync(file, JSON.stringify(context, null, 2));
|
|
61
|
+
};
|
package/src/env.ts
ADDED
package/src/errors.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type ErrorCode =
|
|
2
|
+
| 'REQUEST_TIMEOUT'
|
|
3
|
+
| 'AUTH_FAILED'
|
|
4
|
+
| 'API_ERROR'
|
|
5
|
+
| 'UNKNOWN_COMMAND'
|
|
6
|
+
| 'MISSING_ARGUMENT'
|
|
7
|
+
| 'UNKNOWN_ERROR';
|
|
8
|
+
|
|
9
|
+
const ERROR_MATCHERS = [
|
|
10
|
+
[/^Unknown command: (.*)$/, 'UNKNOWN_COMMAND'],
|
|
11
|
+
[/^Missing required argument: (.*)$/, 'MISSING_ARGUMENT'],
|
|
12
|
+
] as const;
|
|
13
|
+
export const matchErrorCode = (message?: string): ErrorCode => {
|
|
14
|
+
if (!message) {
|
|
15
|
+
return 'UNKNOWN_ERROR';
|
|
16
|
+
}
|
|
17
|
+
for (const [matcher, code] of ERROR_MATCHERS) {
|
|
18
|
+
const match = message.match(matcher);
|
|
19
|
+
if (match) {
|
|
20
|
+
return code;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return 'UNKNOWN_ERROR';
|
|
24
|
+
};
|
package/src/help.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import yargs from 'yargs';
|
|
2
|
+
import cliui from 'cliui';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
consumeBlockIfMatches,
|
|
7
|
+
consumeNextMatching,
|
|
8
|
+
drawPointer,
|
|
9
|
+
splitColumns,
|
|
10
|
+
} from './utils/ui.js';
|
|
11
|
+
|
|
12
|
+
// target width for the leftmost column
|
|
13
|
+
const SPACE_WIDTH = 20;
|
|
14
|
+
|
|
15
|
+
const formatHelp = (help: string) => {
|
|
16
|
+
const lines = help.split('\n');
|
|
17
|
+
const result = [] as string[];
|
|
18
|
+
// full command, like `neonctl projects list`
|
|
19
|
+
const topLevelCommand = consumeNextMatching(lines, /^.*/);
|
|
20
|
+
|
|
21
|
+
if (topLevelCommand) {
|
|
22
|
+
result.push(
|
|
23
|
+
chalk.bold(
|
|
24
|
+
topLevelCommand.replace('[options]', chalk.reset.green('[options]')),
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
result.push('');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// commands description block
|
|
31
|
+
// example command to see: neonctl projects
|
|
32
|
+
const commandsBlock = consumeBlockIfMatches(lines, /^Commands:/);
|
|
33
|
+
if (commandsBlock.length > 0) {
|
|
34
|
+
const header = commandsBlock.shift() as string;
|
|
35
|
+
result.push(header);
|
|
36
|
+
const ui = cliui({
|
|
37
|
+
width: 0,
|
|
38
|
+
});
|
|
39
|
+
commandsBlock.forEach((line) => {
|
|
40
|
+
if (line.match(/^\s{3,}/)) {
|
|
41
|
+
ui.div(
|
|
42
|
+
{
|
|
43
|
+
text: '',
|
|
44
|
+
width: SPACE_WIDTH,
|
|
45
|
+
padding: [0, 0, 0, 0],
|
|
46
|
+
},
|
|
47
|
+
{ text: line.trim(), padding: [0, 0, 0, 0] },
|
|
48
|
+
);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const [command, description] = splitColumns(line);
|
|
53
|
+
|
|
54
|
+
// patch the previous command if it was multiline
|
|
55
|
+
if (!description && ui.rows.length > 1) {
|
|
56
|
+
ui.rows[ui.rows.length - 2][0].text += command;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
ui.div(chalk.cyan(command));
|
|
61
|
+
ui.div(
|
|
62
|
+
{
|
|
63
|
+
text: chalk.gray(drawPointer(SPACE_WIDTH)),
|
|
64
|
+
width: SPACE_WIDTH,
|
|
65
|
+
padding: [0, 0, 0, 0],
|
|
66
|
+
},
|
|
67
|
+
{ text: description, padding: [0, 0, 0, 2] },
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
result.push(ui.toString());
|
|
71
|
+
result.push('');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// positional args block
|
|
75
|
+
// example command to see: neonctl branches rename
|
|
76
|
+
const positionalsBlock = consumeBlockIfMatches(lines, /Positionals:/);
|
|
77
|
+
if (positionalsBlock.length > 0) {
|
|
78
|
+
const header = positionalsBlock.shift() as string;
|
|
79
|
+
result.push(header);
|
|
80
|
+
const ui = cliui({
|
|
81
|
+
width: 0,
|
|
82
|
+
});
|
|
83
|
+
positionalsBlock.forEach((line) => {
|
|
84
|
+
const [positional, description] = splitColumns(line);
|
|
85
|
+
ui.div(
|
|
86
|
+
{
|
|
87
|
+
text: positional,
|
|
88
|
+
width: SPACE_WIDTH,
|
|
89
|
+
padding: [0, 2, 0, 0],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
text: description,
|
|
93
|
+
padding: [0, 0, 0, 0],
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
result.push(ui.toString());
|
|
98
|
+
result.push('');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// command description
|
|
102
|
+
// example command to see: neonctl projects list
|
|
103
|
+
const descritpionBlock = consumeBlockIfMatches(lines, /^(?!.*options:)/i);
|
|
104
|
+
if (descritpionBlock.length > 0) {
|
|
105
|
+
result.push(...descritpionBlock);
|
|
106
|
+
result.push('');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
while (true) {
|
|
110
|
+
// there are two options blocks: global and specific
|
|
111
|
+
// example to see both: neonctl projects create
|
|
112
|
+
const optionsBlock = consumeBlockIfMatches(lines, /.*options:/i);
|
|
113
|
+
if (optionsBlock.length === 0) {
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
result.push(optionsBlock.shift() as string);
|
|
117
|
+
optionsBlock.forEach((line) => {
|
|
118
|
+
const [option, description] = splitColumns(line);
|
|
119
|
+
const ui = cliui({
|
|
120
|
+
width: 0,
|
|
121
|
+
});
|
|
122
|
+
if (option.startsWith('-')) {
|
|
123
|
+
ui.div({
|
|
124
|
+
text: chalk.green(option),
|
|
125
|
+
padding: [0, 0, 0, 0],
|
|
126
|
+
});
|
|
127
|
+
ui.div(
|
|
128
|
+
{
|
|
129
|
+
text: chalk.gray(drawPointer(SPACE_WIDTH)),
|
|
130
|
+
width: SPACE_WIDTH,
|
|
131
|
+
padding: [0, 2, 0, 0],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
text: chalk.rgb(210, 210, 210)(description ?? ''),
|
|
135
|
+
padding: [0, 0, 0, 0],
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
} else {
|
|
139
|
+
ui.div(
|
|
140
|
+
{
|
|
141
|
+
padding: [0, 0, 0, 0],
|
|
142
|
+
text: '',
|
|
143
|
+
width: SPACE_WIDTH,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
text: chalk.rgb(210, 210, 210)(option),
|
|
147
|
+
padding: [0, 0, 0, 0],
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
result.push(ui.toString());
|
|
153
|
+
});
|
|
154
|
+
result.push('');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const exampleBlock = consumeBlockIfMatches(lines, /Examples:/);
|
|
158
|
+
if (exampleBlock.length > 0) {
|
|
159
|
+
result.push(exampleBlock.shift() as string);
|
|
160
|
+
const ui = cliui({
|
|
161
|
+
width: 0,
|
|
162
|
+
});
|
|
163
|
+
for (const line of exampleBlock) {
|
|
164
|
+
const [command, description] = splitColumns(line);
|
|
165
|
+
ui.div({
|
|
166
|
+
text: chalk.bold(command),
|
|
167
|
+
padding: [0, 0, 0, 0],
|
|
168
|
+
});
|
|
169
|
+
ui.div({
|
|
170
|
+
text: chalk.reset(description),
|
|
171
|
+
padding: [0, 0, 0, 2],
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
result.push(ui.toString());
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return [...result, ...lines];
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const showHelp = async (argv: yargs.Argv) => {
|
|
181
|
+
// add wrap to ensure that there are no line breaks
|
|
182
|
+
const help = await argv.getHelp();
|
|
183
|
+
process.stderr.write(formatHelp(help).join('\n') + '\n');
|
|
184
|
+
process.exit(0);
|
|
185
|
+
};
|