@chabokan.net/cli 0.8.15 → 0.9.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.
Files changed (54) hide show
  1. package/README.md +520 -66
  2. package/dist/base.d.ts +30 -7
  3. package/dist/base.js +163 -14
  4. package/dist/commands/account/info.d.ts +12 -0
  5. package/dist/commands/account/info.js +40 -0
  6. package/dist/commands/account/list.d.ts +5 -1
  7. package/dist/commands/account/list.js +29 -19
  8. package/dist/commands/account/remove.d.ts +5 -1
  9. package/dist/commands/account/remove.js +23 -12
  10. package/dist/commands/account/use.d.ts +6 -2
  11. package/dist/commands/account/use.js +22 -11
  12. package/dist/commands/cloudserver/create.d.ts +40 -0
  13. package/dist/commands/cloudserver/create.js +242 -0
  14. package/dist/commands/cloudserver/delete.d.ts +14 -0
  15. package/dist/commands/cloudserver/delete.js +65 -0
  16. package/dist/commands/cloudserver/list.d.ts +12 -0
  17. package/dist/commands/cloudserver/list.js +46 -0
  18. package/dist/commands/cloudserver/restart.d.ts +13 -0
  19. package/dist/commands/cloudserver/restart.js +51 -0
  20. package/dist/commands/cloudserver/start.d.ts +13 -0
  21. package/dist/commands/cloudserver/start.js +51 -0
  22. package/dist/commands/cloudserver/stop.d.ts +13 -0
  23. package/dist/commands/cloudserver/stop.js +52 -0
  24. package/dist/commands/deploy.d.ts +13 -3
  25. package/dist/commands/deploy.js +96 -60
  26. package/dist/commands/login.d.ts +8 -4
  27. package/dist/commands/login.js +69 -41
  28. package/dist/commands/service/domain/add.d.ts +15 -0
  29. package/dist/commands/service/domain/add.js +74 -0
  30. package/dist/commands/service/domain/remove.d.ts +15 -0
  31. package/dist/commands/service/domain/remove.js +72 -0
  32. package/dist/commands/service/list.d.ts +5 -1
  33. package/dist/commands/service/list.js +29 -21
  34. package/dist/commands/service/logs.d.ts +6 -2
  35. package/dist/commands/service/logs.js +33 -30
  36. package/dist/commands/service/resize.d.ts +9 -5
  37. package/dist/commands/service/resize.js +73 -69
  38. package/dist/commands/service/restart.d.ts +6 -2
  39. package/dist/commands/service/restart.js +28 -32
  40. package/dist/commands/service/start.d.ts +6 -2
  41. package/dist/commands/service/start.js +29 -31
  42. package/dist/commands/service/stop.d.ts +6 -2
  43. package/dist/commands/service/stop.js +29 -31
  44. package/dist/commands/wallet/list.d.ts +12 -0
  45. package/dist/commands/wallet/list.js +41 -0
  46. package/dist/constants.d.ts +2 -0
  47. package/dist/constants.js +7 -2
  48. package/dist/helper.d.ts +23 -7
  49. package/dist/helper.js +294 -113
  50. package/dist/types.d.ts +28 -8
  51. package/dist/ui.d.ts +23 -0
  52. package/dist/ui.js +70 -0
  53. package/oclif.manifest.json +635 -25
  54. package/package.json +39 -35
@@ -0,0 +1,242 @@
1
+ import { Flags } from '@oclif/core';
2
+ import Command from "../../base.js";
3
+ import { handleApiError, logErrorDetails, extractRecords, isObject, stripHtml } from "../../helper.js";
4
+ import * as ui from "../../ui.js";
5
+ import inquirer from 'inquirer';
6
+ import axios from "axios";
7
+ export default class CloudserverCreate extends Command {
8
+ static description = 'create a new cloud server (VPS) — walks through provider, location, plan, OS and wallet';
9
+ static examples = [
10
+ {
11
+ description: 'Create a cloud server, choosing every option interactively',
12
+ command: '<%= config.bin %> cloudserver create',
13
+ },
14
+ {
15
+ description: 'Skip the hostname prompt',
16
+ command: '<%= config.bin %> cloudserver create --hostname my-vps',
17
+ },
18
+ ];
19
+ static flags = {
20
+ ...Command.flags,
21
+ hostname: Flags.string({ description: 'hostname for the new cloud server' }),
22
+ wallet: Flags.string({ description: 'name of the wallet to pay from' }),
23
+ };
24
+ async run() {
25
+ const { flags } = await this.parse(CloudserverCreate);
26
+ const cli = this;
27
+ await this.init_run();
28
+ if (!(await this.require_login())) {
29
+ return;
30
+ }
31
+ const cloud = await this.pick('cloud provider', 'get', 'cloudservers/cloud/', undefined, ['clouds']);
32
+ if (!cloud)
33
+ return;
34
+ const locationResult = await this.pick_location(cloud.value);
35
+ if (!locationResult)
36
+ return;
37
+ const { location, descriptionConfirmed } = locationResult;
38
+ const plan = await this.pick('plan', 'post', 'cloudservers/plans/', { cloud_location_id: location.value }, ['plans'], (r) => ({ value: String(r.id), name: `${r.cpu} CPU / ${r.ram} MB RAM / ${r.disk} GB disk — ${r.hourly_price} per hour` }));
39
+ if (!plan)
40
+ return;
41
+ const osTemplate = await this.pick('operating system', 'post', 'cloudservers/os/', { cloud_plan_id: plan.value }, ['cloud_base_templates'], (r) => ({ value: String(r.id), name: String(r.name) }));
42
+ if (!osTemplate)
43
+ return;
44
+ const osVersion = await this.pick('OS version', 'post', 'cloudservers/os/versions/', { cloud_template_name: osTemplate.name, cloud_plan_id: plan.value }, ['cloud_template_versions'], (r) => ({ value: String(r.id), name: String(r.name) }));
45
+ if (!osVersion)
46
+ return;
47
+ const hostname = await this.pick_hostname(flags.hostname);
48
+ if (!hostname)
49
+ return;
50
+ const wallet = await this.select_wallet(flags.wallet);
51
+ if (!wallet)
52
+ return;
53
+ cli.log(ui.info('About to create:'));
54
+ ui.printRecord({
55
+ Provider: cloud.name,
56
+ Location: location.name,
57
+ Plan: plan.name,
58
+ OS: `${osTemplate.name} ${osVersion.name}`,
59
+ Hostname: hostname,
60
+ Wallet: wallet.name,
61
+ });
62
+ const { confirmed } = await inquirer.prompt({
63
+ type: 'confirm',
64
+ name: 'confirmed',
65
+ message: 'This will start billing your wallet immediately. Create this cloud server?',
66
+ default: false,
67
+ });
68
+ if (!confirmed) {
69
+ cli.log(ui.info('Cancelled.'));
70
+ return;
71
+ }
72
+ try {
73
+ const { data } = await axios.post('cloudservers/create/', {
74
+ cloud: cloud.value,
75
+ cloud_location: location.value,
76
+ hostname,
77
+ cloud_plan: plan.value,
78
+ cloud_template: osTemplate.name,
79
+ cloud_template_version: osVersion.value,
80
+ wallet: wallet.id,
81
+ // Undocumented: this location's API response included a
82
+ // datacenter disclaimer under `extra_data.description`, and
83
+ // creation is rejected until it's echoed back as accepted. The
84
+ // exact field name isn't in the OpenAPI schema — reverse-engineered
85
+ // from the rejection message.
86
+ ...(descriptionConfirmed === undefined ? {} : { confirm_description: descriptionConfirmed }),
87
+ }, this.axiosConfig);
88
+ if (data.success) {
89
+ cli.log(ui.success(`Cloud server ${ui.value(hostname)} is being created.`));
90
+ cli.log(ui.hint(`Check on it with ${ui.cmd('chabok cloudserver list')}`));
91
+ }
92
+ else {
93
+ cli.log(ui.error(`Could not create the cloud server. ${data.message || 'Please try again in a moment.'}`));
94
+ }
95
+ }
96
+ catch (error) {
97
+ const errorInfo = handleApiError(error, 'Failed to create cloud server.', {
98
+ endpoint: 'cloudservers/create/',
99
+ operation: 'Create Cloudserver'
100
+ });
101
+ cli.log(ui.error(errorInfo.message));
102
+ logErrorDetails(errorInfo, cli);
103
+ }
104
+ }
105
+ /**
106
+ * Fetches locations for `cloudId` and lets the user pick one. Some
107
+ * locations (notably Iranian ones) come with a legal disclaimer under
108
+ * `extra_data.description` that the user must read and accept before the
109
+ * server can be created — this isn't documented in the OpenAPI schema at
110
+ * all, only discovered by hitting the create endpoint's rejection message.
111
+ */
112
+ async pick_location(cloudId) {
113
+ let data;
114
+ try {
115
+ ({ data } = await axios.post('cloudservers/locations/', { cloud_id: cloudId }, this.axiosConfig));
116
+ }
117
+ catch (error) {
118
+ const errorInfo = handleApiError(error, 'Failed to fetch location options.', {
119
+ endpoint: 'cloudservers/locations/',
120
+ operation: 'Fetch location',
121
+ });
122
+ this.log(ui.error(errorInfo.message));
123
+ logErrorDetails(errorInfo, this);
124
+ return undefined;
125
+ }
126
+ const records = extractRecords(data, ['locations']);
127
+ if (records.length === 0) {
128
+ this.log(ui.error('No location options are available.'));
129
+ return undefined;
130
+ }
131
+ const choices = records.map((r) => ({ value: String(r.id), name: `${r.city ?? r.region ?? r.id}${r.status ? ` (${r.status})` : ''}` }));
132
+ let location = choices[0];
133
+ if (choices.length > 1) {
134
+ const { picked } = await inquirer.prompt({
135
+ type: 'select',
136
+ message: 'Select a location:',
137
+ name: 'picked',
138
+ choices,
139
+ });
140
+ location = choices.find((c) => c.value === picked);
141
+ }
142
+ if (!location)
143
+ return undefined;
144
+ const description = isObject(data) && typeof data.extra_data === 'object' && data.extra_data !== null && 'description' in data.extra_data && typeof data.extra_data.description === 'string'
145
+ ? stripHtml(data.extra_data.description)
146
+ : undefined;
147
+ if (!description) {
148
+ return { location, descriptionConfirmed: undefined };
149
+ }
150
+ this.log(ui.info('This location comes with the following terms:'));
151
+ this.log(description);
152
+ const { confirmed } = await inquirer.prompt({
153
+ type: 'confirm',
154
+ name: 'confirmed',
155
+ message: 'Do you accept these terms?',
156
+ default: false,
157
+ });
158
+ if (!confirmed) {
159
+ this.log(ui.info('Cancelled.'));
160
+ return undefined;
161
+ }
162
+ return { location, descriptionConfirmed: true };
163
+ }
164
+ async pick_hostname(flagValue) {
165
+ let hostname = flagValue;
166
+ // Each retry depends on what the user typed (or the previous check's
167
+ // rejection), so this loop is inherently sequential.
168
+ /* eslint-disable no-await-in-loop */
169
+ while (true) {
170
+ if (!hostname) {
171
+ ({ hostname } = await inquirer.prompt({
172
+ type: 'input',
173
+ name: 'hostname',
174
+ message: 'Hostname:',
175
+ validate(input) {
176
+ return input.trim().length > 0 || 'Hostname cannot be empty';
177
+ },
178
+ }));
179
+ }
180
+ hostname = hostname.trim();
181
+ try {
182
+ const { data } = await axios.post('cloudservers/name-check/', { hostname }, this.axiosConfig);
183
+ if (data.success) {
184
+ return hostname;
185
+ }
186
+ this.log(ui.error(`${ui.value(hostname)} is not available. ${data.message || 'Choose a different hostname.'}`));
187
+ }
188
+ catch (error) {
189
+ const errorInfo = handleApiError(error, 'Failed to check hostname.', {
190
+ endpoint: 'cloudservers/name-check/',
191
+ operation: 'Check Hostname'
192
+ });
193
+ this.log(ui.error(errorInfo.message));
194
+ logErrorDetails(errorInfo, this);
195
+ return undefined;
196
+ }
197
+ hostname = undefined;
198
+ }
199
+ /* eslint-enable no-await-in-loop */
200
+ }
201
+ /**
202
+ * Fetches a list from `endpoint`, lets the user pick one, and returns its
203
+ * {value, name}. `toChoice` defaults to a generic id/name mapper, but most
204
+ * of these endpoints have a known shape (learned by hand against the live
205
+ * API), so callers pass an explicit mapper for a clearer label. `value` is
206
+ * whatever the create request needs (an id) — the choice list itself only
207
+ * ever shows `name`.
208
+ */
209
+ async pick(label, method, endpoint, body, wrapperKeys, toChoice = (r) => ({ value: String(r.id), name: String(r.name ?? r.id) })) {
210
+ let records;
211
+ try {
212
+ const { data } = method === 'get'
213
+ ? await axios.get(endpoint, this.axiosConfig)
214
+ : await axios.post(endpoint, body, this.axiosConfig);
215
+ records = extractRecords(data, wrapperKeys);
216
+ }
217
+ catch (error) {
218
+ const errorInfo = handleApiError(error, `Failed to fetch ${label} options.`, {
219
+ endpoint,
220
+ operation: `Fetch ${label}`,
221
+ });
222
+ this.log(ui.error(errorInfo.message));
223
+ logErrorDetails(errorInfo, this);
224
+ return undefined;
225
+ }
226
+ if (records.length === 0) {
227
+ this.log(ui.error(`No ${label} options are available.`));
228
+ return undefined;
229
+ }
230
+ const choices = records.map(toChoice);
231
+ if (choices.length === 1) {
232
+ return choices[0];
233
+ }
234
+ const { picked } = await inquirer.prompt({
235
+ type: 'select',
236
+ message: `Select a ${label}:`,
237
+ name: 'picked',
238
+ choices,
239
+ });
240
+ return choices.find((c) => c.value === picked);
241
+ }
242
+ }
@@ -0,0 +1,14 @@
1
+ import Command from "../../base.js";
2
+ export default class CloudserverDelete extends Command {
3
+ static description: string;
4
+ static examples: {
5
+ description: string;
6
+ command: string;
7
+ }[];
8
+ static flags: {
9
+ hostname: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,65 @@
1
+ import { Flags } from '@oclif/core';
2
+ import Command from "../../base.js";
3
+ import { handleApiError, logErrorDetails } from "../../helper.js";
4
+ import * as ui from "../../ui.js";
5
+ import inquirer from 'inquirer';
6
+ import axios from "axios";
7
+ export default class CloudserverDelete extends Command {
8
+ static description = 'permanently delete a cloud server';
9
+ static examples = [
10
+ {
11
+ description: 'Pick a cloud server from an interactive list, then confirm',
12
+ command: '<%= config.bin %> cloudserver delete',
13
+ },
14
+ {
15
+ description: 'Delete a specific cloud server without a confirmation prompt',
16
+ command: '<%= config.bin %> cloudserver delete --hostname my-vps --yes',
17
+ },
18
+ ];
19
+ static flags = {
20
+ ...Command.flags,
21
+ hostname: Flags.string({ description: 'hostname of the cloud server to delete' }),
22
+ yes: Flags.boolean({ char: 'y', description: 'skip the confirmation prompt', default: false }),
23
+ };
24
+ async run() {
25
+ const { flags } = await this.parse(CloudserverDelete);
26
+ const cli = this;
27
+ await this.init_run();
28
+ if (!(await this.require_login())) {
29
+ return;
30
+ }
31
+ const selected = await this.select_cloud_server(flags.hostname, 'Delete Cloudserver');
32
+ if (!selected) {
33
+ return;
34
+ }
35
+ if (!flags.yes) {
36
+ const { confirmed } = await inquirer.prompt({
37
+ type: 'confirm',
38
+ name: 'confirmed',
39
+ message: `Delete cloud server ${selected.hostname}? This cannot be undone.`,
40
+ default: false,
41
+ });
42
+ if (!confirmed) {
43
+ cli.log(ui.info('Cancelled.'));
44
+ return;
45
+ }
46
+ }
47
+ try {
48
+ const { data } = await axios.post('cloudservers/delete/', { cloud_server_id: selected.id }, this.axiosConfig);
49
+ if (data.success) {
50
+ cli.log(ui.success(`Cloud server ${ui.value(selected.hostname)} was deleted.`));
51
+ }
52
+ else {
53
+ cli.log(ui.error(`Could not delete ${ui.value(selected.hostname)}. ${data.message || 'Please try again in a moment.'}`));
54
+ }
55
+ }
56
+ catch (error) {
57
+ const errorInfo = handleApiError(error, 'Failed to delete cloud server.', {
58
+ endpoint: 'cloudservers/delete/',
59
+ operation: 'Delete Cloudserver'
60
+ });
61
+ cli.log(ui.error(errorInfo.message));
62
+ logErrorDetails(errorInfo, cli);
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,12 @@
1
+ import Command from "../../base.js";
2
+ export default class CloudserverList extends Command {
3
+ static description: string;
4
+ static examples: {
5
+ description: string;
6
+ command: string;
7
+ }[];
8
+ static flags: {
9
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,46 @@
1
+ import Command from "../../base.js";
2
+ import * as ui from "../../ui.js";
3
+ import { handleApiError, logErrorDetails, extractRecords } from "../../helper.js";
4
+ import axios from "axios";
5
+ export default class CloudserverList extends Command {
6
+ static description = "list the cloud servers (VPS) on your account";
7
+ static examples = [
8
+ {
9
+ description: 'List every cloud server on the active account',
10
+ command: '<%= config.bin %> cloudserver list',
11
+ },
12
+ ];
13
+ static flags = {
14
+ ...Command.flags,
15
+ };
16
+ async run() {
17
+ await this.parse(CloudserverList);
18
+ const cli = this;
19
+ await this.init_run();
20
+ if (!(await this.require_login())) {
21
+ return;
22
+ }
23
+ try {
24
+ const { data } = await axios.get('cloudservers/', this.axiosConfig);
25
+ const cloudservers = extractRecords(data, ['user_cloud_servers', 'cloudservers', 'results', 'data']);
26
+ if (cloudservers.length === 0) {
27
+ cli.log(ui.info('You have no cloud servers yet.'));
28
+ cli.log(ui.hint('Create one at https://hub.chabokan.net'));
29
+ return;
30
+ }
31
+ // The internal id is plumbing for the API, not something the user
32
+ // needs — every command refers to cloud servers by hostname instead.
33
+ const displayRecords = cloudservers.map(({ id: _id, ...rest }) => rest);
34
+ ui.printRecords(displayRecords);
35
+ cli.log(ui.hint(`Act on one with ${ui.cmd('chabok cloudserver restart')}, ${ui.cmd('start')}, ${ui.cmd('stop')} …`));
36
+ }
37
+ catch (error) {
38
+ const errorInfo = handleApiError(error, 'Failed to fetch cloud servers list.', {
39
+ endpoint: 'cloudservers/',
40
+ operation: 'List Cloudservers'
41
+ });
42
+ cli.log(ui.error(errorInfo.message));
43
+ logErrorDetails(errorInfo, cli);
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,13 @@
1
+ import Command from "../../base.js";
2
+ export default class CloudserverRestart extends Command {
3
+ static description: string;
4
+ static examples: {
5
+ description: string;
6
+ command: string;
7
+ }[];
8
+ static flags: {
9
+ hostname: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,51 @@
1
+ import { Flags } from '@oclif/core';
2
+ import Command from "../../base.js";
3
+ import { handleApiError, logErrorDetails } from "../../helper.js";
4
+ import * as ui from "../../ui.js";
5
+ import axios from "axios";
6
+ export default class CloudserverRestart extends Command {
7
+ static description = 'restart a cloud server';
8
+ static examples = [
9
+ {
10
+ description: 'Pick a cloud server from an interactive list',
11
+ command: '<%= config.bin %> cloudserver restart',
12
+ },
13
+ {
14
+ description: 'Restart a specific cloud server by hostname',
15
+ command: '<%= config.bin %> cloudserver restart --hostname my-vps',
16
+ },
17
+ ];
18
+ static flags = {
19
+ ...Command.flags,
20
+ hostname: Flags.string({ description: 'hostname of the cloud server to restart' }),
21
+ };
22
+ async run() {
23
+ const { flags } = await this.parse(CloudserverRestart);
24
+ const cli = this;
25
+ await this.init_run();
26
+ if (!(await this.require_login())) {
27
+ return;
28
+ }
29
+ const selected = await this.select_cloud_server(flags.hostname, 'Restart Cloudserver');
30
+ if (!selected) {
31
+ return;
32
+ }
33
+ try {
34
+ const { data } = await axios.post('cloudservers/restart/', { cloud_server_id: selected.id }, this.axiosConfig);
35
+ if (data.success) {
36
+ cli.log(ui.success(`Cloud server ${ui.value(selected.hostname)} is restarting.`));
37
+ }
38
+ else {
39
+ cli.log(ui.error(`Could not restart ${ui.value(selected.hostname)}. ${data.message || 'Please try again in a moment.'}`));
40
+ }
41
+ }
42
+ catch (error) {
43
+ const errorInfo = handleApiError(error, 'Failed to restart cloud server.', {
44
+ endpoint: 'cloudservers/restart/',
45
+ operation: 'Restart Cloudserver'
46
+ });
47
+ cli.log(ui.error(errorInfo.message));
48
+ logErrorDetails(errorInfo, cli);
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,13 @@
1
+ import Command from "../../base.js";
2
+ export default class CloudserverStart extends Command {
3
+ static description: string;
4
+ static examples: {
5
+ description: string;
6
+ command: string;
7
+ }[];
8
+ static flags: {
9
+ hostname: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,51 @@
1
+ import { Flags } from '@oclif/core';
2
+ import Command from "../../base.js";
3
+ import { handleApiError, logErrorDetails } from "../../helper.js";
4
+ import * as ui from "../../ui.js";
5
+ import axios from "axios";
6
+ export default class CloudserverStart extends Command {
7
+ static description = 'turn on a cloud server';
8
+ static examples = [
9
+ {
10
+ description: 'Pick a cloud server from an interactive list',
11
+ command: '<%= config.bin %> cloudserver start',
12
+ },
13
+ {
14
+ description: 'Turn on a specific cloud server by hostname',
15
+ command: '<%= config.bin %> cloudserver start --hostname my-vps',
16
+ },
17
+ ];
18
+ static flags = {
19
+ ...Command.flags,
20
+ hostname: Flags.string({ description: 'hostname of the cloud server to turn on' }),
21
+ };
22
+ async run() {
23
+ const { flags } = await this.parse(CloudserverStart);
24
+ const cli = this;
25
+ await this.init_run();
26
+ if (!(await this.require_login())) {
27
+ return;
28
+ }
29
+ const selected = await this.select_cloud_server(flags.hostname, 'Start Cloudserver');
30
+ if (!selected) {
31
+ return;
32
+ }
33
+ try {
34
+ const { data } = await axios.post('cloudservers/start/', { cloud_server_id: selected.id }, this.axiosConfig);
35
+ if (data.success) {
36
+ cli.log(ui.success(`Cloud server ${ui.value(selected.hostname)} is starting.`));
37
+ }
38
+ else {
39
+ cli.log(ui.error(`Could not start ${ui.value(selected.hostname)}. ${data.message || 'Please try again in a moment.'}`));
40
+ }
41
+ }
42
+ catch (error) {
43
+ const errorInfo = handleApiError(error, 'Failed to start cloud server.', {
44
+ endpoint: 'cloudservers/start/',
45
+ operation: 'Start Cloudserver'
46
+ });
47
+ cli.log(ui.error(errorInfo.message));
48
+ logErrorDetails(errorInfo, cli);
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,13 @@
1
+ import Command from "../../base.js";
2
+ export default class CloudserverStop extends Command {
3
+ static description: string;
4
+ static examples: {
5
+ description: string;
6
+ command: string;
7
+ }[];
8
+ static flags: {
9
+ hostname: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,52 @@
1
+ import { Flags } from '@oclif/core';
2
+ import Command from "../../base.js";
3
+ import { handleApiError, logErrorDetails } from "../../helper.js";
4
+ import * as ui from "../../ui.js";
5
+ import axios from "axios";
6
+ export default class CloudserverStop extends Command {
7
+ static description = 'turn off a cloud server';
8
+ static examples = [
9
+ {
10
+ description: 'Pick a cloud server from an interactive list',
11
+ command: '<%= config.bin %> cloudserver stop',
12
+ },
13
+ {
14
+ description: 'Turn off a specific cloud server by hostname',
15
+ command: '<%= config.bin %> cloudserver stop --hostname my-vps',
16
+ },
17
+ ];
18
+ static flags = {
19
+ ...Command.flags,
20
+ hostname: Flags.string({ description: 'hostname of the cloud server to turn off' }),
21
+ };
22
+ async run() {
23
+ const { flags } = await this.parse(CloudserverStop);
24
+ const cli = this;
25
+ await this.init_run();
26
+ if (!(await this.require_login())) {
27
+ return;
28
+ }
29
+ const selected = await this.select_cloud_server(flags.hostname, 'Stop Cloudserver');
30
+ if (!selected) {
31
+ return;
32
+ }
33
+ try {
34
+ const { data } = await axios.post('cloudservers/stop/', { cloud_server_id: selected.id }, this.axiosConfig);
35
+ if (data.success) {
36
+ cli.log(ui.success(`Cloud server ${ui.value(selected.hostname)} is stopping.`));
37
+ cli.log(ui.hint(`Bring it back with ${ui.cmd(`chabok cloudserver start --hostname ${selected.hostname}`)}`));
38
+ }
39
+ else {
40
+ cli.log(ui.error(`Could not stop ${ui.value(selected.hostname)}. ${data.message || 'Please try again in a moment.'}`));
41
+ }
42
+ }
43
+ catch (error) {
44
+ const errorInfo = handleApiError(error, 'Failed to stop cloud server.', {
45
+ endpoint: 'cloudservers/stop/',
46
+ operation: 'Stop Cloudserver'
47
+ });
48
+ cli.log(ui.error(errorInfo.message));
49
+ logErrorDetails(errorInfo, cli);
50
+ }
51
+ }
52
+ }
@@ -2,12 +2,22 @@ import Command from "../base.js";
2
2
  import type { ChabokFile } from "../types.js";
3
3
  export default class Deploy extends Command {
4
4
  static description: string;
5
+ static examples: {
6
+ description: string;
7
+ command: string;
8
+ }[];
5
9
  static flags: {
6
- path: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
7
- service: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
8
- help: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<void>;
10
+ path: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ service: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
9
13
  };
10
14
  run(): Promise<void>;
15
+ /**
16
+ * Loads chabok.json from the project root. A missing file is normal; a
17
+ * malformed one is reported rather than silently ignored, because deploying
18
+ * with the wrong options is worse than not deploying.
19
+ */
20
+ read_chabok_file(project_path: string): ChabokFile;
11
21
  prepare_archive(project_path: string): Promise<string>;
12
22
  upload(archive_path: string, selected_service: string, cli: Command, chabok_file: ChabokFile): Promise<{
13
23
  success: boolean;