neonctl 1.31.0 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/analytics.js +11 -0
- package/commands/auth.test.js +5 -10
- package/commands/bootstrap/index.js +562 -0
- package/commands/bootstrap/index.test.js +104 -0
- package/commands/bootstrap/is-folder-empty.js +56 -0
- package/commands/bootstrap/validate-pkg.js +15 -0
- package/commands/branches.js +12 -0
- package/commands/branches.test.js +63 -1
- package/commands/connection_string.test.js +1 -1
- package/commands/databases.test.js +1 -1
- package/commands/help.test.js +1 -1
- package/commands/index.js +2 -0
- package/commands/ip_allow.js +14 -1
- package/commands/ip_allow.test.js +18 -2
- package/commands/operations.test.js +1 -1
- package/commands/orgs.test.js +11 -0
- package/commands/projects.js +25 -2
- package/commands/projects.test.js +49 -1
- package/commands/roles.test.js +1 -1
- package/commands/set_context.test.js +1 -1
- package/index.js +5 -0
- package/package.json +15 -13
- package/test_utils/mock_server.js +1 -1
- package/test_utils/oauth_server.js +1 -1
- package/test_utils/test_cli_command.js +1 -1
- package/utils/compute_units.js +28 -0
- package/utils/formats.test.js +1 -1
- package/writer.test.js +1 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { test, expect, describe, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { runMockServer } from '../../test_utils/mock_server.js';
|
|
3
|
+
import { fork } from 'node:child_process';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { log } from '../../log';
|
|
6
|
+
describe('bootstrap/create-app', () => {
|
|
7
|
+
let server;
|
|
8
|
+
const mockDir = 'main';
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
server = await runMockServer(mockDir);
|
|
11
|
+
});
|
|
12
|
+
afterAll(async () => {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
server.close(() => {
|
|
15
|
+
resolve();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
// We create an app without a schema and without deploying it, as
|
|
20
|
+
// a very simple check that the CLI works. Eventually, we need
|
|
21
|
+
// to have a much more complete test suite that actually verifies
|
|
22
|
+
// that launching all different app combinations works.
|
|
23
|
+
test.skip('very simple CLI interaction test', async () => {
|
|
24
|
+
// Most of this forking code is copied from `test_cli_command.ts`.
|
|
25
|
+
const cp = fork(join(process.cwd(), './dist/index.js'), [
|
|
26
|
+
'--api-host',
|
|
27
|
+
`http://localhost:${server.address().port}`,
|
|
28
|
+
'--output',
|
|
29
|
+
'yaml',
|
|
30
|
+
'--api-key',
|
|
31
|
+
'test-key',
|
|
32
|
+
'--no-analytics',
|
|
33
|
+
'create-app',
|
|
34
|
+
], {
|
|
35
|
+
stdio: 'pipe',
|
|
36
|
+
env: {
|
|
37
|
+
PATH: `mocks/bin:${process.env.PATH}`,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
process.on('SIGINT', () => {
|
|
41
|
+
cp.kill();
|
|
42
|
+
});
|
|
43
|
+
let neonProjectCreated = false;
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
cp.stdout?.on('data', (data) => {
|
|
46
|
+
const stdout = data.toString();
|
|
47
|
+
log.info(stdout);
|
|
48
|
+
// For some unknown, weird reason, when we send TAB clicks (\t),
|
|
49
|
+
// they only affect the next question. So, we send TAB below
|
|
50
|
+
// in order to affect the answer to the following prompt, not the
|
|
51
|
+
// current one.
|
|
52
|
+
if (stdout.includes('What is your project named')) {
|
|
53
|
+
cp.stdin?.write('my-app\n');
|
|
54
|
+
}
|
|
55
|
+
else if (stdout.includes('Which package manager would you like to use')) {
|
|
56
|
+
cp.stdin?.write('\n');
|
|
57
|
+
}
|
|
58
|
+
else if (stdout.includes('What framework would you like to use')) {
|
|
59
|
+
cp.stdin?.write('\n');
|
|
60
|
+
}
|
|
61
|
+
else if (stdout.includes('What ORM would you like to use')) {
|
|
62
|
+
cp.stdin?.write('\t'); // change auth.js
|
|
63
|
+
cp.stdin?.write('\n');
|
|
64
|
+
}
|
|
65
|
+
else if (stdout.includes('What authentication framework do you want to use')) {
|
|
66
|
+
cp.stdin?.write('\n');
|
|
67
|
+
}
|
|
68
|
+
else if (stdout.includes('What Neon project would you like to use')) {
|
|
69
|
+
neonProjectCreated = true;
|
|
70
|
+
cp.stdin?.write('\t'); // change deployment
|
|
71
|
+
cp.stdin?.write('\t');
|
|
72
|
+
cp.stdin?.write('\n');
|
|
73
|
+
}
|
|
74
|
+
else if (stdout.includes('Where would you like to deploy')) {
|
|
75
|
+
cp.stdin?.write('\n');
|
|
76
|
+
cp.stdin?.write('\n');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
cp.stderr?.on('data', (data) => {
|
|
80
|
+
log.error(data.toString());
|
|
81
|
+
});
|
|
82
|
+
cp.on('error', (err) => {
|
|
83
|
+
throw err;
|
|
84
|
+
});
|
|
85
|
+
cp.on('close', (code) => {
|
|
86
|
+
// If we got to the point that a Neon project was successfully
|
|
87
|
+
// created, we consider the test run to be a success. We can't
|
|
88
|
+
// currently check that the template is properly generated, and that
|
|
89
|
+
// the project runs. We'll have to do that with containerization in
|
|
90
|
+
// the future, most likely.
|
|
91
|
+
if (neonProjectCreated) {
|
|
92
|
+
resolve();
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
expect(code).toBe(0);
|
|
96
|
+
resolve();
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
reject(err);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}, 1000 * 60 * 5);
|
|
104
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Code copied from `create-next-app`.
|
|
2
|
+
import { lstatSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
// `isFolderEmpty` checks if a folder is empty and ready to onboard a Next.js package into it.
|
|
6
|
+
// It will actually log to stdout as part of its execution.
|
|
7
|
+
export function isFolderEmpty(root, name, writeStdout) {
|
|
8
|
+
const validFiles = new Set([
|
|
9
|
+
'.DS_Store',
|
|
10
|
+
'.git',
|
|
11
|
+
'.gitattributes',
|
|
12
|
+
'.gitignore',
|
|
13
|
+
'.gitlab-ci.yml',
|
|
14
|
+
'.hg',
|
|
15
|
+
'.hgcheck',
|
|
16
|
+
'.hgignore',
|
|
17
|
+
'.idea',
|
|
18
|
+
'.npmignore',
|
|
19
|
+
'.travis.yml',
|
|
20
|
+
'LICENSE',
|
|
21
|
+
'Thumbs.db',
|
|
22
|
+
'docs',
|
|
23
|
+
'mkdocs.yml',
|
|
24
|
+
'npm-debug.log',
|
|
25
|
+
'yarn-debug.log',
|
|
26
|
+
'yarn-error.log',
|
|
27
|
+
'yarnrc.yml',
|
|
28
|
+
'.yarn',
|
|
29
|
+
]);
|
|
30
|
+
const conflicts = readdirSync(root).filter((file) => !validFiles.has(file) &&
|
|
31
|
+
// Support IntelliJ IDEA-based editors
|
|
32
|
+
!/\.iml$/.test(file));
|
|
33
|
+
if (conflicts.length > 0) {
|
|
34
|
+
writeStdout(`The directory ${chalk.green(name)} contains files that could conflict:\n`);
|
|
35
|
+
writeStdout('');
|
|
36
|
+
for (const file of conflicts) {
|
|
37
|
+
try {
|
|
38
|
+
const stats = lstatSync(join(root, file));
|
|
39
|
+
if (stats.isDirectory()) {
|
|
40
|
+
writeStdout(` ${chalk.blue(file)}/\n`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
writeStdout(` ${file}\n`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
writeStdout(` ${file}\n`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
writeStdout('\n');
|
|
51
|
+
writeStdout('Either try using a new directory name, or remove the files listed above.\n');
|
|
52
|
+
writeStdout('\n');
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Code copied from `create-next-app`.
|
|
2
|
+
import validateProjectName from 'validate-npm-package-name';
|
|
3
|
+
export function validateNpmName(name) {
|
|
4
|
+
const nameValidation = validateProjectName(name);
|
|
5
|
+
if (nameValidation.validForNewPackages) {
|
|
6
|
+
return { valid: true };
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
valid: false,
|
|
10
|
+
problems: [
|
|
11
|
+
...(nameValidation.errors || []),
|
|
12
|
+
...(nameValidation.warnings || []),
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
}
|
package/commands/branches.js
CHANGED
|
@@ -8,6 +8,7 @@ import { psql } from '../utils/psql.js';
|
|
|
8
8
|
import { parsePointInTime } from '../utils/point_in_time.js';
|
|
9
9
|
import { log } from '../log.js';
|
|
10
10
|
import { parseSchemaDiffParams, schemaDiff } from './schema_diff.js';
|
|
11
|
+
import { getComputeUnits } from '../utils/compute_units.js';
|
|
11
12
|
const BRANCH_FIELDS = [
|
|
12
13
|
'id',
|
|
13
14
|
'name',
|
|
@@ -61,6 +62,11 @@ export const builder = (argv) => argv
|
|
|
61
62
|
implies: 'compute',
|
|
62
63
|
default: 0,
|
|
63
64
|
},
|
|
65
|
+
cu: {
|
|
66
|
+
describe: 'The number of Compute Units. Could be a fixed size (e.g. "2") or a range delimited by a dash (e.g. "0.5-3").',
|
|
67
|
+
type: 'string',
|
|
68
|
+
implies: 'compute',
|
|
69
|
+
},
|
|
64
70
|
psql: {
|
|
65
71
|
type: 'boolean',
|
|
66
72
|
describe: 'Connect to a new branch via psql',
|
|
@@ -119,6 +125,10 @@ export const builder = (argv) => argv
|
|
|
119
125
|
describe: 'Type of compute to add',
|
|
120
126
|
default: EndpointType.ReadOnly,
|
|
121
127
|
},
|
|
128
|
+
cu: {
|
|
129
|
+
describe: 'The number of Compute Units. Could be a fixed size (e.g. "2") or a range delimited by a dash (e.g. "0.5-3").',
|
|
130
|
+
type: 'string',
|
|
131
|
+
},
|
|
122
132
|
}), async (args) => await addCompute(args))
|
|
123
133
|
.command('delete <id|name>', 'Delete a branch', (yargs) => yargs, async (args) => await deleteBranch(args))
|
|
124
134
|
.command('get <id|name>', 'Get a branch', (yargs) => yargs, async (args) => await get(args))
|
|
@@ -213,6 +223,7 @@ const create = async (props) => {
|
|
|
213
223
|
{
|
|
214
224
|
type: props.type,
|
|
215
225
|
suspend_timeout_seconds: props.suspendTimeout === 0 ? undefined : props.suspendTimeout,
|
|
226
|
+
...(props.cu ? getComputeUnits(props.cu) : undefined),
|
|
216
227
|
},
|
|
217
228
|
]
|
|
218
229
|
: [],
|
|
@@ -282,6 +293,7 @@ const addCompute = async (props) => {
|
|
|
282
293
|
endpoint: {
|
|
283
294
|
branch_id: branchId,
|
|
284
295
|
type: props.type,
|
|
296
|
+
...(props.cu ? getComputeUnits(props.cu) : undefined),
|
|
285
297
|
},
|
|
286
298
|
}));
|
|
287
299
|
writer(props).end(data.endpoint, {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe } from '
|
|
1
|
+
import { describe } from 'vitest';
|
|
2
2
|
import { testCliCommand } from '../test_utils/test_cli_command.js';
|
|
3
3
|
describe('branches', () => {
|
|
4
4
|
/* list */
|
|
@@ -152,6 +152,38 @@ describe('branches', () => {
|
|
|
152
152
|
snapshot: true,
|
|
153
153
|
},
|
|
154
154
|
});
|
|
155
|
+
testCliCommand({
|
|
156
|
+
name: 'create with fixed size CU',
|
|
157
|
+
args: [
|
|
158
|
+
'branches',
|
|
159
|
+
'create',
|
|
160
|
+
'--project-id',
|
|
161
|
+
'test',
|
|
162
|
+
'--name',
|
|
163
|
+
'test_branch_with_fixed_cu',
|
|
164
|
+
'--cu',
|
|
165
|
+
'2',
|
|
166
|
+
],
|
|
167
|
+
expected: {
|
|
168
|
+
snapshot: true,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
testCliCommand({
|
|
172
|
+
name: 'create with autoscaled CU',
|
|
173
|
+
args: [
|
|
174
|
+
'branches',
|
|
175
|
+
'create',
|
|
176
|
+
'--project-id',
|
|
177
|
+
'test',
|
|
178
|
+
'--name',
|
|
179
|
+
'test_branch_with_autoscaling',
|
|
180
|
+
'--cu',
|
|
181
|
+
'0.5-2',
|
|
182
|
+
],
|
|
183
|
+
expected: {
|
|
184
|
+
snapshot: true,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
155
187
|
/* delete */
|
|
156
188
|
testCliCommand({
|
|
157
189
|
name: 'delete by id',
|
|
@@ -252,6 +284,36 @@ describe('branches', () => {
|
|
|
252
284
|
snapshot: true,
|
|
253
285
|
},
|
|
254
286
|
});
|
|
287
|
+
testCliCommand({
|
|
288
|
+
name: 'add compute with fixed size CU',
|
|
289
|
+
args: [
|
|
290
|
+
'branches',
|
|
291
|
+
'add-compute',
|
|
292
|
+
'test_branch_with_fixed_cu',
|
|
293
|
+
'--project-id',
|
|
294
|
+
'test',
|
|
295
|
+
'--cu',
|
|
296
|
+
'2',
|
|
297
|
+
],
|
|
298
|
+
expected: {
|
|
299
|
+
snapshot: true,
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
testCliCommand({
|
|
303
|
+
name: 'add compute with autoscaled CU',
|
|
304
|
+
args: [
|
|
305
|
+
'branches',
|
|
306
|
+
'add-compute',
|
|
307
|
+
'test_branch_with_autoscaling',
|
|
308
|
+
'--project-id',
|
|
309
|
+
'test',
|
|
310
|
+
'--cu',
|
|
311
|
+
'0.5-2',
|
|
312
|
+
],
|
|
313
|
+
expected: {
|
|
314
|
+
snapshot: true,
|
|
315
|
+
},
|
|
316
|
+
});
|
|
255
317
|
/* reset */
|
|
256
318
|
testCliCommand({
|
|
257
319
|
name: 'reset branch to parent',
|
package/commands/help.test.js
CHANGED
package/commands/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import * as roles from './roles.js';
|
|
|
9
9
|
import * as operations from './operations.js';
|
|
10
10
|
import * as cs from './connection_string.js';
|
|
11
11
|
import * as setContext from './set_context.js';
|
|
12
|
+
import * as bootstrap from './bootstrap/index.js';
|
|
12
13
|
export default [
|
|
13
14
|
auth,
|
|
14
15
|
users,
|
|
@@ -21,4 +22,5 @@ export default [
|
|
|
21
22
|
operations,
|
|
22
23
|
cs,
|
|
23
24
|
setContext,
|
|
25
|
+
bootstrap,
|
|
24
26
|
];
|
package/commands/ip_allow.js
CHANGED
|
@@ -6,7 +6,7 @@ const IP_ALLOW_FIELDS = [
|
|
|
6
6
|
'id',
|
|
7
7
|
'name',
|
|
8
8
|
'IP_addresses',
|
|
9
|
-
'
|
|
9
|
+
'protected_branches_only',
|
|
10
10
|
];
|
|
11
11
|
export const command = 'ip-allow';
|
|
12
12
|
export const describe = 'Manage IP Allow';
|
|
@@ -30,11 +30,18 @@ export const builder = (argv) => {
|
|
|
30
30
|
type: 'string',
|
|
31
31
|
default: [],
|
|
32
32
|
array: true,
|
|
33
|
+
})
|
|
34
|
+
.options({
|
|
35
|
+
'protected-only': {
|
|
36
|
+
describe: projectUpdateRequest['project.settings.allowed_ips.protected_branches_only'].description,
|
|
37
|
+
type: 'boolean',
|
|
38
|
+
},
|
|
33
39
|
})
|
|
34
40
|
.options({
|
|
35
41
|
'primary-only': {
|
|
36
42
|
describe: projectUpdateRequest['project.settings.allowed_ips.primary_branch_only'].description,
|
|
37
43
|
type: 'boolean',
|
|
44
|
+
deprecated: 'See --protected-only',
|
|
38
45
|
},
|
|
39
46
|
}), async (args) => {
|
|
40
47
|
await add(args);
|
|
@@ -77,6 +84,9 @@ const add = async (props) => {
|
|
|
77
84
|
allowed_ips: {
|
|
78
85
|
ips: [...new Set(props.ips.concat(existingAllowedIps?.ips ?? []))],
|
|
79
86
|
primary_branch_only: props.primaryOnly ?? existingAllowedIps?.primary_branch_only ?? false,
|
|
87
|
+
protected_branches_only: props.protectedOnly ??
|
|
88
|
+
existingAllowedIps?.protected_branches_only ??
|
|
89
|
+
false,
|
|
80
90
|
},
|
|
81
91
|
};
|
|
82
92
|
const { data: response } = await props.apiClient.updateProject(props.projectId, {
|
|
@@ -97,6 +107,7 @@ const remove = async (props) => {
|
|
|
97
107
|
allowed_ips: {
|
|
98
108
|
ips: existingAllowedIps?.ips?.filter((ip) => !props.ips.includes(ip)) ?? [],
|
|
99
109
|
primary_branch_only: existingAllowedIps?.primary_branch_only ?? false,
|
|
110
|
+
protected_branches_only: existingAllowedIps?.protected_branches_only ?? false,
|
|
100
111
|
},
|
|
101
112
|
};
|
|
102
113
|
const { data: response } = await props.apiClient.updateProject(props.projectId, {
|
|
@@ -112,6 +123,7 @@ const reset = async (props) => {
|
|
|
112
123
|
allowed_ips: {
|
|
113
124
|
ips: props.ips,
|
|
114
125
|
primary_branch_only: false,
|
|
126
|
+
protected_branches_only: false,
|
|
115
127
|
},
|
|
116
128
|
};
|
|
117
129
|
const { data } = await props.apiClient.updateProject(props.projectId, {
|
|
@@ -131,5 +143,6 @@ const parse = (project) => {
|
|
|
131
143
|
name: project.name,
|
|
132
144
|
IP_addresses: ips,
|
|
133
145
|
primary_branch_only: project.settings?.allowed_ips?.primary_branch_only ?? false,
|
|
146
|
+
protected_branches_only: project.settings?.allowed_ips?.protected_branches_only ?? false,
|
|
134
147
|
};
|
|
135
148
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe } from '
|
|
1
|
+
import { describe } from 'vitest';
|
|
2
2
|
import { testCliCommand } from '../test_utils/test_cli_command.js';
|
|
3
3
|
describe('ip-allow', () => {
|
|
4
4
|
testCliCommand({
|
|
@@ -26,7 +26,7 @@ describe('ip-allow', () => {
|
|
|
26
26
|
},
|
|
27
27
|
});
|
|
28
28
|
testCliCommand({
|
|
29
|
-
name: 'Add IP allow',
|
|
29
|
+
name: 'Add IP allow - Primary',
|
|
30
30
|
args: [
|
|
31
31
|
'ip-allow',
|
|
32
32
|
'add',
|
|
@@ -40,6 +40,21 @@ describe('ip-allow', () => {
|
|
|
40
40
|
snapshot: true,
|
|
41
41
|
},
|
|
42
42
|
});
|
|
43
|
+
testCliCommand({
|
|
44
|
+
name: 'Add IP allow - Protected',
|
|
45
|
+
args: [
|
|
46
|
+
'ip-allow',
|
|
47
|
+
'add',
|
|
48
|
+
'127.0.0.1',
|
|
49
|
+
'192.168.10.1-192.168.10.15',
|
|
50
|
+
'--protected-only',
|
|
51
|
+
'--project-id',
|
|
52
|
+
'test',
|
|
53
|
+
],
|
|
54
|
+
expected: {
|
|
55
|
+
snapshot: true,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
43
58
|
testCliCommand({
|
|
44
59
|
name: 'Remove IP allow - Error',
|
|
45
60
|
args: ['ip-allow', 'remove', '--project-id', 'test'],
|
|
@@ -64,6 +79,7 @@ describe('ip-allow', () => {
|
|
|
64
79
|
name: test_project
|
|
65
80
|
IP_addresses: []
|
|
66
81
|
primary_branch_only: false
|
|
82
|
+
protected_branches_only: false
|
|
67
83
|
`,
|
|
68
84
|
stderr: `INFO: The IP allowlist has been reset. All databases on project "test_project" are now exposed to the internet`,
|
|
69
85
|
},
|
package/commands/projects.js
CHANGED
|
@@ -3,7 +3,13 @@ import { projectCreateRequest, projectUpdateRequest, } from '../parameters.gen.j
|
|
|
3
3
|
import { writer } from '../writer.js';
|
|
4
4
|
import { psql } from '../utils/psql.js';
|
|
5
5
|
import { updateContextFile } from '../context.js';
|
|
6
|
-
|
|
6
|
+
import { getComputeUnits } from '../utils/compute_units.js';
|
|
7
|
+
export const PROJECT_FIELDS = [
|
|
8
|
+
'id',
|
|
9
|
+
'name',
|
|
10
|
+
'region_id',
|
|
11
|
+
'created_at',
|
|
12
|
+
];
|
|
7
13
|
const REGIONS = [
|
|
8
14
|
'aws-us-west-2',
|
|
9
15
|
'aws-ap-southeast-1',
|
|
@@ -48,6 +54,10 @@ export const builder = (argv) => {
|
|
|
48
54
|
describe: 'Set the current context to the new project',
|
|
49
55
|
default: false,
|
|
50
56
|
},
|
|
57
|
+
cu: {
|
|
58
|
+
describe: 'The number of Compute Units. Could be a fixed size (e.g. "2") or a range delimited by a dash (e.g. "0.5-3").',
|
|
59
|
+
type: 'string',
|
|
60
|
+
},
|
|
51
61
|
}), async (args) => {
|
|
52
62
|
await create(args);
|
|
53
63
|
})
|
|
@@ -68,6 +78,10 @@ export const builder = (argv) => {
|
|
|
68
78
|
type: 'boolean',
|
|
69
79
|
deprecated: "Deprecated. Use 'ip-allow' command",
|
|
70
80
|
},
|
|
81
|
+
cu: {
|
|
82
|
+
describe: 'The number of Compute Units. Could be a fixed size (e.g. "2") or a range delimited by a dash (e.g. "0.5-3").',
|
|
83
|
+
type: 'string',
|
|
84
|
+
},
|
|
71
85
|
}), async (args) => {
|
|
72
86
|
await update(args);
|
|
73
87
|
})
|
|
@@ -130,13 +144,17 @@ const create = async (props) => {
|
|
|
130
144
|
if (props.role) {
|
|
131
145
|
project.branch.role_name = props.role;
|
|
132
146
|
}
|
|
147
|
+
if (props.cu) {
|
|
148
|
+
project.default_endpoint_settings = props.cu
|
|
149
|
+
? getComputeUnits(props.cu)
|
|
150
|
+
: undefined;
|
|
151
|
+
}
|
|
133
152
|
const { data } = await props.apiClient.createProject({
|
|
134
153
|
project,
|
|
135
154
|
});
|
|
136
155
|
if (props.setContext) {
|
|
137
156
|
updateContextFile(props.contextFile, {
|
|
138
157
|
projectId: data.project.id,
|
|
139
|
-
branchId: data.branch.id,
|
|
140
158
|
});
|
|
141
159
|
}
|
|
142
160
|
const out = writer(props);
|
|
@@ -175,6 +193,11 @@ const update = async (props) => {
|
|
|
175
193
|
},
|
|
176
194
|
};
|
|
177
195
|
}
|
|
196
|
+
if (props.cu) {
|
|
197
|
+
project.default_endpoint_settings = props.cu
|
|
198
|
+
? getComputeUnits(props.cu)
|
|
199
|
+
: undefined;
|
|
200
|
+
}
|
|
178
201
|
const { data } = await props.apiClient.updateProject(props.id, {
|
|
179
202
|
project,
|
|
180
203
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterAll, describe, expect, test } from '
|
|
1
|
+
import { afterAll, describe, expect, test } from 'vitest';
|
|
2
2
|
import { readFileSync, rmSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
@@ -73,6 +73,34 @@ describe('projects', () => {
|
|
|
73
73
|
snapshot: true,
|
|
74
74
|
},
|
|
75
75
|
});
|
|
76
|
+
testCliCommand({
|
|
77
|
+
name: 'create project with default fixed size CU',
|
|
78
|
+
args: [
|
|
79
|
+
'projects',
|
|
80
|
+
'create',
|
|
81
|
+
'--name',
|
|
82
|
+
'test_project_with_fixed_cu',
|
|
83
|
+
'--cu',
|
|
84
|
+
'2',
|
|
85
|
+
],
|
|
86
|
+
expected: {
|
|
87
|
+
snapshot: true,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
testCliCommand({
|
|
91
|
+
name: 'create project with default autoscaled CU',
|
|
92
|
+
args: [
|
|
93
|
+
'projects',
|
|
94
|
+
'create',
|
|
95
|
+
'--name',
|
|
96
|
+
'test_project_with_autoscaling',
|
|
97
|
+
'--cu',
|
|
98
|
+
'0.5-2',
|
|
99
|
+
],
|
|
100
|
+
expected: {
|
|
101
|
+
snapshot: true,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
76
104
|
afterAll(() => {
|
|
77
105
|
rmSync(CONTEXT_FILE);
|
|
78
106
|
});
|
|
@@ -122,6 +150,26 @@ describe('projects', () => {
|
|
|
122
150
|
snapshot: true,
|
|
123
151
|
},
|
|
124
152
|
});
|
|
153
|
+
testCliCommand({
|
|
154
|
+
name: 'update project with default fixed size CU',
|
|
155
|
+
args: ['projects', 'update', 'test_project_with_fixed_cu', '--cu', '2'],
|
|
156
|
+
expected: {
|
|
157
|
+
snapshot: true,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
testCliCommand({
|
|
161
|
+
name: 'update project with default autoscaled CU',
|
|
162
|
+
args: [
|
|
163
|
+
'projects',
|
|
164
|
+
'update',
|
|
165
|
+
'test_project_with_autoscaling',
|
|
166
|
+
'--cu',
|
|
167
|
+
'0.5-2',
|
|
168
|
+
],
|
|
169
|
+
expected: {
|
|
170
|
+
snapshot: true,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
125
173
|
testCliCommand({
|
|
126
174
|
name: 'get',
|
|
127
175
|
args: ['projects', 'get', 'test'],
|
package/commands/roles.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { tmpdir } from 'node:os';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { rmSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { afterAll, describe } from '
|
|
4
|
+
import { afterAll, describe } from 'vitest';
|
|
5
5
|
import { testCliCommand } from '../test_utils/test_cli_command';
|
|
6
6
|
const CONTEXT_FILE = join(tmpdir(), `neon_${Date.now()}`);
|
|
7
7
|
describe('set_context', () => {
|
package/index.js
CHANGED
|
@@ -26,11 +26,16 @@ import { matchErrorCode } from './errors.js';
|
|
|
26
26
|
import { showHelp } from './help.js';
|
|
27
27
|
import { currentContextFile, enrichFromContext } from './context.js';
|
|
28
28
|
const NO_SUBCOMMANDS_VERBS = [
|
|
29
|
+
// aliases
|
|
29
30
|
'auth',
|
|
30
31
|
'me',
|
|
32
|
+
// aliases
|
|
31
33
|
'cs',
|
|
32
34
|
'connection-string',
|
|
33
35
|
'set-context',
|
|
36
|
+
// aliases
|
|
37
|
+
'create-app',
|
|
38
|
+
'bootstrap',
|
|
34
39
|
];
|
|
35
40
|
let builder = yargs(hideBin(process.argv));
|
|
36
41
|
builder = builder
|