bdy 1.23.17-dev → 1.23.18-dev

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 (43) hide show
  1. package/distTs/package.json +1 -1
  2. package/distTs/src/api/client.js +49 -10
  3. package/distTs/src/command/environment/delete.js +1 -2
  4. package/distTs/src/command/environment/get.js +1 -2
  5. package/distTs/src/command/environment/update.js +1 -2
  6. package/distTs/src/command/sandbox/app/add.js +4 -9
  7. package/distTs/src/command/sandbox/app/list.js +3 -8
  8. package/distTs/src/command/sandbox/app/logs.js +3 -8
  9. package/distTs/src/command/sandbox/app/remove.js +4 -9
  10. package/distTs/src/command/sandbox/app/start.js +3 -8
  11. package/distTs/src/command/sandbox/app/status.js +3 -8
  12. package/distTs/src/command/sandbox/app/stop.js +3 -8
  13. package/distTs/src/command/sandbox/cp.js +10 -18
  14. package/distTs/src/command/sandbox/create.js +26 -6
  15. package/distTs/src/command/sandbox/destroy.js +3 -8
  16. package/distTs/src/command/sandbox/endpoint/add.js +3 -7
  17. package/distTs/src/command/sandbox/endpoint/delete.js +4 -9
  18. package/distTs/src/command/sandbox/endpoint/get.js +16 -12
  19. package/distTs/src/command/sandbox/endpoint/list.js +3 -8
  20. package/distTs/src/command/sandbox/exec/command.js +5 -10
  21. package/distTs/src/command/sandbox/exec/kill.js +3 -8
  22. package/distTs/src/command/sandbox/exec/list.js +3 -8
  23. package/distTs/src/command/sandbox/exec/logs.js +4 -9
  24. package/distTs/src/command/sandbox/exec/status.js +3 -8
  25. package/distTs/src/command/sandbox/get.js +7 -9
  26. package/distTs/src/command/sandbox/list.js +5 -4
  27. package/distTs/src/command/sandbox/logs.js +3 -8
  28. package/distTs/src/command/sandbox/restart.js +4 -9
  29. package/distTs/src/command/sandbox/snapshot/create.js +4 -9
  30. package/distTs/src/command/sandbox/snapshot/delete.js +4 -9
  31. package/distTs/src/command/sandbox/snapshot/get.js +4 -9
  32. package/distTs/src/command/sandbox/snapshot/list.js +7 -9
  33. package/distTs/src/command/sandbox/ssh.js +7 -10
  34. package/distTs/src/command/sandbox/start.js +4 -9
  35. package/distTs/src/command/sandbox/status.js +3 -8
  36. package/distTs/src/command/sandbox/stop.js +4 -9
  37. package/distTs/src/command/sandbox/update.js +7 -12
  38. package/distTs/src/command/sandbox/yaml.js +5 -8
  39. package/distTs/src/input.js +85 -1
  40. package/distTs/src/texts.js +12 -9
  41. package/distTs/src/types/distro.js +1 -0
  42. package/distTs/src/utils.js +18 -4
  43. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bdy",
3
3
  "preferGlobal": false,
4
- "version": "1.23.17-dev",
4
+ "version": "1.23.18-dev",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "homepage": "https://buddy.works/docs/cli",
@@ -384,7 +384,7 @@ class ApiClient {
384
384
  async getTunnelToken(workspace, project) {
385
385
  const query = {};
386
386
  if (project)
387
- query.project_name = encodeURIComponent(project);
387
+ query.project_name = project;
388
388
  return await this.request({
389
389
  method: 'GET',
390
390
  path: `/workspaces/${encodeURIComponent(workspace)}/tunnels/token`,
@@ -791,17 +791,27 @@ class ApiClient {
791
791
  }
792
792
  // Sandbox methods
793
793
  async createSandbox(workspace, project, body) {
794
+ const query = {};
795
+ if (project)
796
+ query.project_name = project;
794
797
  return await this.request({
795
798
  method: 'POST',
796
- path: `/workspaces/${encodeURIComponent(workspace)}/sandboxes?project_name=${encodeURIComponent(project)}`,
799
+ path: `/workspaces/${encodeURIComponent(workspace)}/sandboxes`,
800
+ query,
797
801
  body,
798
802
  parseResponseBody: true,
799
803
  });
800
804
  }
801
- async createSandboxByYaml(workspace, project, body) {
805
+ async createSandboxByYaml(workspace, project, body, environmentId) {
806
+ const query = {};
807
+ if (project)
808
+ query.project_name = project;
809
+ if (environmentId)
810
+ query.environment_id = environmentId;
802
811
  return await this.request({
803
812
  method: 'POST',
804
- path: `/workspaces/${encodeURIComponent(workspace)}/sandboxes/yaml?project_name=${encodeURIComponent(project)}`,
813
+ path: `/workspaces/${encodeURIComponent(workspace)}/sandboxes/yaml`,
814
+ query,
805
815
  body,
806
816
  parseResponseBody: true,
807
817
  });
@@ -822,7 +832,10 @@ class ApiClient {
822
832
  });
823
833
  }
824
834
  async sandboxDownloadFile(workspace, sandboxId, remotePath) {
825
- const encodedRemotePath = remotePath.split('/').map(encodeURIComponent).join('/');
835
+ const encodedRemotePath = remotePath
836
+ .split('/')
837
+ .map(encodeURIComponent)
838
+ .join('/');
826
839
  return await this.request({
827
840
  method: 'GET',
828
841
  path: `/workspaces/${encodeURIComponent(workspace)}/sandboxes/${encodeURIComponent(sandboxId)}/download${encodedRemotePath}`,
@@ -837,7 +850,10 @@ class ApiClient {
837
850
  const formData = new undici_1.FormData();
838
851
  const file = await node_fs_1.default.openAsBlob(localPath);
839
852
  formData.set('file', file);
840
- const encodedRemotePath = remotePath.split('/').map(encodeURIComponent).join('/');
853
+ const encodedRemotePath = remotePath
854
+ .split('/')
855
+ .map(encodeURIComponent)
856
+ .join('/');
841
857
  return await this.request({
842
858
  method: 'POST',
843
859
  path: `/workspaces/${encodeURIComponent(workspace)}/sandboxes/${encodeURIComponent(sandboxId)}/content/upload${encodedRemotePath}`,
@@ -846,13 +862,34 @@ class ApiClient {
846
862
  parseResponseBody: true,
847
863
  });
848
864
  }
849
- async listSandboxes(workspace, project) {
865
+ /**
866
+ * With no filter the endpoint returns workspace-scoped sandboxes only - it is
867
+ * not a workspace-wide listing, so a project- or environment-scoped sandbox is
868
+ * reachable only through its own filter.
869
+ */
870
+ async listSandboxes(workspace, project, environmentId) {
871
+ const query = {};
872
+ if (project)
873
+ query.project_name = project;
874
+ if (environmentId)
875
+ query.environment_id = environmentId;
850
876
  return await this.request({
851
877
  method: 'GET',
852
- path: `/workspaces/${encodeURIComponent(workspace)}/sandboxes?project_name=${encodeURIComponent(project)}`,
878
+ path: `/workspaces/${encodeURIComponent(workspace)}/sandboxes`,
879
+ query,
853
880
  parseResponseBody: true,
854
881
  });
855
882
  }
883
+ /**
884
+ * Resolve an identifier to an id by listing its scope, for the scopes
885
+ * `/identifiers?sandbox=` cannot answer (it resolves project-scoped sandboxes
886
+ * only, and needs `project` to do it).
887
+ */
888
+ async findSandboxByIdentifier(workspace, identifier, project, environmentId) {
889
+ const result = await this.listSandboxes(workspace, project, environmentId);
890
+ const sandboxes = result.sandboxes || [];
891
+ return sandboxes.find((s) => s.identifier === identifier)?.id;
892
+ }
856
893
  async getSandbox(workspace, sandboxId) {
857
894
  return await this.request({
858
895
  method: 'GET',
@@ -1062,10 +1099,12 @@ class ApiClient {
1062
1099
  parseResponseBody: true,
1063
1100
  });
1064
1101
  }
1065
- async listProjectSnapshots(workspace, project) {
1102
+ async listSnapshots(workspace, project, environmentId) {
1066
1103
  const query = {};
1067
1104
  if (project)
1068
- query.project_name = encodeURIComponent(project);
1105
+ query.project_name = project;
1106
+ if (environmentId)
1107
+ query.environment_id = environmentId;
1069
1108
  return await this.request({
1070
1109
  method: 'GET',
1071
1110
  path: `/workspaces/${encodeURIComponent(workspace)}/sandboxes/snapshots`,
@@ -7,7 +7,6 @@ const utils_1 = require("../../utils");
7
7
  const texts_1 = require("../../texts");
8
8
  const output_1 = __importDefault(require("../../output"));
9
9
  const input_1 = __importDefault(require("../../input"));
10
- const resolve_1 = require("./resolve");
11
10
  const commandEnvironmentDelete = (0, utils_1.newCommand)('delete', texts_1.DESC_COMMAND_ENVIRONMENT_DELETE);
12
11
  commandEnvironmentDelete.alias('rm');
13
12
  commandEnvironmentDelete.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
@@ -18,7 +17,7 @@ commandEnvironmentDelete.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_ENVI
18
17
  commandEnvironmentDelete.action(async (identifier, options) => {
19
18
  const workspace = input_1.default.restApiWorkspace(options.workspace);
20
19
  const client = input_1.default.restApiTokenClient();
21
- const environment_id = await (0, resolve_1.resolveEnvironmentId)(client, workspace, identifier, options,
20
+ const environment_id = await input_1.default.environmentId(client, workspace, identifier, options,
22
21
  /* strictProject */ true);
23
22
  const confirmed = options.force ||
24
23
  (await output_1.default.confirm((0, texts_1.TXT_ENVIRONMENT_DELETE_CONFIRM)(identifier)));
@@ -7,7 +7,6 @@ const utils_1 = require("../../utils");
7
7
  const texts_1 = require("../../texts");
8
8
  const output_1 = __importDefault(require("../../output"));
9
9
  const input_1 = __importDefault(require("../../input"));
10
- const resolve_1 = require("./resolve");
11
10
  const commandEnvironmentGet = (0, utils_1.newCommand)('get', texts_1.DESC_COMMAND_ENVIRONMENT_GET);
12
11
  commandEnvironmentGet.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
13
12
  commandEnvironmentGet.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
@@ -17,7 +16,7 @@ commandEnvironmentGet.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_ENVIRON
17
16
  commandEnvironmentGet.action(async (identifier, options) => {
18
17
  const workspace = input_1.default.restApiWorkspace(options.workspace);
19
18
  const client = input_1.default.restApiTokenClient();
20
- const environment_id = await (0, resolve_1.resolveEnvironmentId)(client, workspace, identifier, options);
19
+ const environment_id = await input_1.default.environmentId(client, workspace, identifier, options);
21
20
  const environment = await client.getEnvironment(workspace, environment_id);
22
21
  if (options.format === 'json') {
23
22
  output_1.default.json(environment);
@@ -7,7 +7,6 @@ const utils_1 = require("../../utils");
7
7
  const texts_1 = require("../../texts");
8
8
  const input_1 = __importDefault(require("../../input"));
9
9
  const output_1 = __importDefault(require("../../output"));
10
- const resolve_1 = require("./resolve");
11
10
  const commandEnvironmentUpdate = (0, utils_1.newCommand)('update', texts_1.DESC_COMMAND_ENVIRONMENT_UPDATE);
12
11
  commandEnvironmentUpdate.alias('edit');
13
12
  commandEnvironmentUpdate.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
@@ -52,7 +51,7 @@ commandEnvironmentUpdate.action(async (identifier, options) => {
52
51
  output_1.default.exitError(texts_1.ERR_COMMAND_NO_UPDATE);
53
52
  }
54
53
  const client = input_1.default.restApiTokenClient();
55
- const environment_id = await (0, resolve_1.resolveEnvironmentId)(client, workspace, identifier, options,
54
+ const environment_id = await input_1.default.environmentId(client, workspace, identifier, options,
56
55
  /* strictProject */ true);
57
56
  const result = await client.updateEnvironment(workspace, environment_id, body);
58
57
  output_1.default.exitSuccess((0, texts_1.TXT_ENVIRONMENT_UPDATED)(result.identifier, result.html_url));
@@ -8,23 +8,18 @@ const texts_1 = require("../../../texts");
8
8
  const input_1 = __importDefault(require("../../../input"));
9
9
  const output_1 = __importDefault(require("../../../output"));
10
10
  const commandSandboxAppAdd = (0, utils_1.newCommand)('add', texts_1.DESC_COMMAND_SANDBOX_APP_ADD);
11
- commandSandboxAppAdd.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
12
- commandSandboxAppAdd.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
11
+ (0, utils_1.sandboxScopeOptions)(commandSandboxAppAdd);
13
12
  commandSandboxAppAdd.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
14
13
  commandSandboxAppAdd.argument('<app-command>', texts_1.OPTION_SANDBOX_APP_COMMAND);
15
14
  commandSandboxAppAdd.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_SANDBOX_APP_ADD}`);
16
15
  commandSandboxAppAdd.action(async (identifier, app, options) => {
17
16
  const workspace = input_1.default.restApiWorkspace(options.workspace);
18
- const project = input_1.default.restApiProject(options.project);
19
17
  const client = input_1.default.restApiTokenClient();
20
- const { sandbox_id } = await client.getSandboxByIdentifier(workspace, project, identifier);
21
- if (!sandbox_id) {
22
- output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
23
- }
24
- const sandbox = await client.getSandbox(workspace, sandbox_id);
18
+ const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
19
+ const sandbox = await client.getSandbox(workspace, sandboxId);
25
20
  const apps = sandbox.apps || [];
26
21
  apps.push(app);
27
- await client.updateSandbox(workspace, sandbox_id, {
22
+ await client.updateSandbox(workspace, sandboxId, {
28
23
  apps,
29
24
  });
30
25
  output_1.default.exitSuccess(texts_1.TXT_SANDBOX_APP_ADDED);
@@ -9,20 +9,15 @@ const input_1 = __importDefault(require("../../../input"));
9
9
  const output_1 = __importDefault(require("../../../output"));
10
10
  const commandSandboxAppList = (0, utils_1.newCommand)('list', texts_1.DESC_COMMAND_SANDBOX_APP_LIST);
11
11
  commandSandboxAppList.alias('ls');
12
- commandSandboxAppList.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
13
- commandSandboxAppList.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
12
+ (0, utils_1.sandboxScopeOptions)(commandSandboxAppList);
14
13
  commandSandboxAppList.option('--format <text|json>', texts_1.OPTION_FORMAT);
15
14
  commandSandboxAppList.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
16
15
  commandSandboxAppList.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_SANDBOX_APP_LIST}`);
17
16
  commandSandboxAppList.action(async (identifier, options) => {
18
17
  const workspace = input_1.default.restApiWorkspace(options.workspace);
19
- const project = input_1.default.restApiProject(options.project);
20
18
  const client = input_1.default.restApiTokenClient();
21
- const { sandbox_id } = await client.getSandboxByIdentifier(workspace, project, identifier);
22
- if (!sandbox_id) {
23
- output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
24
- }
25
- const sandbox = await client.getSandbox(workspace, sandbox_id);
19
+ const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
20
+ const sandbox = await client.getSandbox(workspace, sandboxId);
26
21
  // A sandbox with no apps has the field omitted altogether.
27
22
  const apps = sandbox.apps || [];
28
23
  if (options.format === 'json') {
@@ -9,8 +9,7 @@ const input_1 = __importDefault(require("../../../input"));
9
9
  const output_1 = __importDefault(require("../../../output"));
10
10
  const commandSandboxAppLogs = (0, utils_1.newCommand)('logs', texts_1.DESC_COMMAND_SANDBOX_APP_LOGS);
11
11
  commandSandboxAppLogs.hideVersionUpdate = true;
12
- commandSandboxAppLogs.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
13
- commandSandboxAppLogs.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
12
+ (0, utils_1.sandboxScopeOptions)(commandSandboxAppLogs);
14
13
  commandSandboxAppLogs.option('-l, --limit <lines>', texts_1.OPTION_SANDBOX_LOGS_LIMIT);
15
14
  commandSandboxAppLogs.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
16
15
  commandSandboxAppLogs.argument('<app-id>', texts_1.OPTION_SANDBOX_APP_ID);
@@ -31,13 +30,9 @@ const fetchLogsWithLimit = async (client, workspace, sandboxId, appId, limit) =>
31
30
  };
32
31
  commandSandboxAppLogs.action(async (identifier, appId, options) => {
33
32
  const workspace = input_1.default.restApiWorkspace(options.workspace);
34
- const project = input_1.default.restApiProject(options.project);
35
33
  const client = input_1.default.restApiTokenClient();
36
- const { sandbox_id } = await client.getSandboxByIdentifier(workspace, project, identifier);
37
- if (!sandbox_id) {
38
- output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
39
- }
40
- const logs = await fetchLogsWithLimit(client, workspace, sandbox_id, appId, options.limit || 100);
34
+ const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
35
+ const logs = await fetchLogsWithLimit(client, workspace, sandboxId, appId, options.limit || 100);
41
36
  output_1.default.exitNormal(logs);
42
37
  });
43
38
  exports.default = commandSandboxAppLogs;
@@ -9,22 +9,17 @@ const input_1 = __importDefault(require("../../../input"));
9
9
  const output_1 = __importDefault(require("../../../output"));
10
10
  const commandSandboxAppRemove = (0, utils_1.newCommand)('remove', texts_1.DESC_COMMAND_SANDBOX_APP_REMOVE);
11
11
  commandSandboxAppRemove.alias('rm');
12
- commandSandboxAppRemove.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
13
- commandSandboxAppRemove.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
12
+ (0, utils_1.sandboxScopeOptions)(commandSandboxAppRemove);
14
13
  commandSandboxAppRemove.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
15
14
  commandSandboxAppRemove.argument('<app-id>', texts_1.OPTION_SANDBOX_APP_ID);
16
15
  commandSandboxAppRemove.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_SANDBOX_APP_REMOVE}`);
17
16
  commandSandboxAppRemove.action(async (identifier, appId, options) => {
18
17
  const workspace = input_1.default.restApiWorkspace(options.workspace);
19
- const project = input_1.default.restApiProject(options.project);
20
18
  const client = input_1.default.restApiTokenClient();
21
- const { sandbox_id } = await client.getSandboxByIdentifier(workspace, project, identifier);
22
- if (!sandbox_id) {
23
- output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
24
- }
25
- const sandbox = await client.getSandbox(workspace, sandbox_id);
19
+ const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
20
+ const sandbox = await client.getSandbox(workspace, sandboxId);
26
21
  const apps = (sandbox.apps || []).filter((app) => app.id !== appId);
27
- await client.updateSandbox(workspace, sandbox_id, {
22
+ await client.updateSandbox(workspace, sandboxId, {
28
23
  apps,
29
24
  });
30
25
  output_1.default.exitSuccess(texts_1.TXT_SANDBOX_APP_REMOVED);
@@ -8,20 +8,15 @@ const texts_1 = require("../../../texts");
8
8
  const input_1 = __importDefault(require("../../../input"));
9
9
  const output_1 = __importDefault(require("../../../output"));
10
10
  const commandSandboxAppStart = (0, utils_1.newCommand)('start', texts_1.DESC_COMMAND_SANDBOX_APP_START);
11
- commandSandboxAppStart.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
12
- commandSandboxAppStart.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
11
+ (0, utils_1.sandboxScopeOptions)(commandSandboxAppStart);
13
12
  commandSandboxAppStart.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
14
13
  commandSandboxAppStart.argument('<app-id>', texts_1.OPTION_SANDBOX_APP_ID);
15
14
  commandSandboxAppStart.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_SANDBOX_APP_START}`);
16
15
  commandSandboxAppStart.action(async (identifier, appId, options) => {
17
16
  const workspace = input_1.default.restApiWorkspace(options.workspace);
18
- const project = input_1.default.restApiProject(options.project);
19
17
  const client = input_1.default.restApiTokenClient();
20
- const { sandbox_id } = await client.getSandboxByIdentifier(workspace, project, identifier);
21
- if (!sandbox_id) {
22
- output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
23
- }
24
- await client.startSandboxApp(workspace, sandbox_id, appId);
18
+ const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
19
+ await client.startSandboxApp(workspace, sandboxId, appId);
25
20
  output_1.default.exitSuccess(texts_1.TXT_SANDBOX_APP_STARTED);
26
21
  });
27
22
  exports.default = commandSandboxAppStart;
@@ -8,21 +8,16 @@ const texts_1 = require("../../../texts");
8
8
  const input_1 = __importDefault(require("../../../input"));
9
9
  const output_1 = __importDefault(require("../../../output"));
10
10
  const commandSandboxAppStatus = (0, utils_1.newCommand)('status', texts_1.DESC_COMMAND_SANDBOX_APP_STATUS);
11
- commandSandboxAppStatus.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
12
- commandSandboxAppStatus.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
11
+ (0, utils_1.sandboxScopeOptions)(commandSandboxAppStatus);
13
12
  commandSandboxAppStatus.option('--format <text|json>', texts_1.OPTION_FORMAT);
14
13
  commandSandboxAppStatus.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
15
14
  commandSandboxAppStatus.argument('<app-id>', texts_1.OPTION_SANDBOX_APP_ID);
16
15
  commandSandboxAppStatus.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_SANDBOX_APP_STATUS}`);
17
16
  commandSandboxAppStatus.action(async (identifier, appId, options) => {
18
17
  const workspace = input_1.default.restApiWorkspace(options.workspace);
19
- const project = input_1.default.restApiProject(options.project);
20
18
  const client = input_1.default.restApiTokenClient();
21
- const { sandbox_id } = await client.getSandboxByIdentifier(workspace, project, identifier);
22
- if (!sandbox_id) {
23
- output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
24
- }
25
- const { apps, } = await client.getSandbox(workspace, sandbox_id);
19
+ const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
20
+ const { apps, } = await client.getSandbox(workspace, sandboxId);
26
21
  const app = (apps || []).find((app) => app.id === appId);
27
22
  if (!app) {
28
23
  output_1.default.exitError(texts_1.ERR_SANDBOX_APP_NOT_FOUND);
@@ -8,20 +8,15 @@ const texts_1 = require("../../../texts");
8
8
  const input_1 = __importDefault(require("../../../input"));
9
9
  const output_1 = __importDefault(require("../../../output"));
10
10
  const commandSandboxAppStop = (0, utils_1.newCommand)('stop', texts_1.DESC_COMMAND_SANDBOX_APP_STOP);
11
- commandSandboxAppStop.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
12
- commandSandboxAppStop.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
11
+ (0, utils_1.sandboxScopeOptions)(commandSandboxAppStop);
13
12
  commandSandboxAppStop.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
14
13
  commandSandboxAppStop.argument('<app-id>', texts_1.OPTION_SANDBOX_APP_ID);
15
14
  commandSandboxAppStop.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_SANDBOX_APP_STOP}`);
16
15
  commandSandboxAppStop.action(async (identifier, appId, options) => {
17
16
  const workspace = input_1.default.restApiWorkspace(options.workspace);
18
- const project = input_1.default.restApiProject(options.project);
19
17
  const client = input_1.default.restApiTokenClient();
20
- const { sandbox_id } = await client.getSandboxByIdentifier(workspace, project, identifier);
21
- if (!sandbox_id) {
22
- output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
23
- }
24
- await client.stopSandboxApp(workspace, sandbox_id, appId);
18
+ const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
19
+ await client.stopSandboxApp(workspace, sandboxId, appId);
25
20
  output_1.default.exitSuccess(texts_1.TXT_SANDBOX_APP_STOPPED);
26
21
  });
27
22
  exports.default = commandSandboxAppStop;
@@ -12,8 +12,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
12
12
  const promises_1 = __importDefault(require("node:stream/promises"));
13
13
  const picomatch_1 = __importDefault(require("picomatch"));
14
14
  const commandSandboxCp = (0, utils_1.newCommand)('cp', texts_1.DESC_COMMAND_SANDBOX_CP);
15
- commandSandboxCp.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
16
- commandSandboxCp.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
15
+ (0, utils_1.sandboxScopeOptions)(commandSandboxCp);
17
16
  commandSandboxCp.option('-m, --merge', texts_1.OPTION_SANDBOX_CP_DOWNLOAD_MERGE);
18
17
  commandSandboxCp.option('-r, --replace', texts_1.OPTION_SANDBOX_CP_DOWNLOAD_REPLACE);
19
18
  commandSandboxCp.option('-u, --user <user>', texts_1.OPTION_SANDBOX_CP_USER);
@@ -21,21 +20,18 @@ commandSandboxCp.option('--ignore <patterns...>', texts_1.OPTION_SANDBOX_CP_IGNO
21
20
  commandSandboxCp.argument('<source>', texts_1.OPTION_SANDBOX_CP_SOURCE);
22
21
  commandSandboxCp.argument('<destination>', texts_1.OPTION_SANDBOX_CP_DEST);
23
22
  commandSandboxCp.addHelpText('after', texts_1.EXAMPLE_SANDBOX_CP);
24
- const upload = async (client, workspace, project, source, destination, ignore, userName) => {
23
+ const upload = async (client, workspace, options, source, destination, ignore, userName) => {
25
24
  const { fdir } = require('fdir');
26
25
  const { sourcePath, sourceStats } = input_1.default.restApiSandboxUploadSourcePath(source);
27
26
  const { identifier, remotePath, isRemoteDir } = input_1.default.restApiSandboxUploadDestinationPath(destination);
28
- const { sandbox_id } = await client.getSandboxByIdentifier(workspace, project, identifier);
29
- if (!sandbox_id) {
30
- output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
31
- }
27
+ const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
32
28
  const isIgnored = ignore.length ? (0, picomatch_1.default)(ignore, { dot: true }) : null;
33
29
  if (sourceStats.isFile()) {
34
30
  // Single file copy
35
31
  const name = (0, path_1.basename)(sourcePath);
36
32
  output_1.default.normal((0, texts_1.TXT_SANDBOX_CP_PROGRESS)(1, 1, name));
37
33
  const remoteFile = isRemoteDir ? (0, path_1.join)(remotePath, name) : remotePath;
38
- await client.sandboxUploadFile(workspace, sandbox_id, remoteFile, sourcePath, userName);
34
+ await client.sandboxUploadFile(workspace, sandboxId, remoteFile, sourcePath, userName);
39
35
  output_1.default.exitSuccess((0, texts_1.TXT_SANDBOX_CP_DONE)(1));
40
36
  }
41
37
  else if (sourceStats.isDirectory()) {
@@ -57,7 +53,7 @@ const upload = async (client, workspace, project, source, destination, ignore, u
57
53
  const remoteFile = (0, path_1.join)(baseRemoteDir, relativePath).replace(/\\/g, '/');
58
54
  output_1.default.clearPreviousLine();
59
55
  output_1.default.normal((0, texts_1.TXT_SANDBOX_CP_PROGRESS)(i + 1, files.length, relativePath));
60
- await client.sandboxUploadFile(workspace, sandbox_id, remoteFile, localFile, userName);
56
+ await client.sandboxUploadFile(workspace, sandboxId, remoteFile, localFile, userName);
61
57
  }
62
58
  output_1.default.exitSuccess((0, texts_1.TXT_SANDBOX_CP_DONE)(files.length));
63
59
  }
@@ -65,18 +61,15 @@ const upload = async (client, workspace, project, source, destination, ignore, u
65
61
  output_1.default.exitError((0, texts_1.ERR_SANDBOX_CP_SOURCE_NOT_FOUND)(source));
66
62
  }
67
63
  };
68
- const download = async (client, workspace, project, source, destination, merge, replace) => {
64
+ const download = async (client, workspace, options, source, destination, merge, replace) => {
69
65
  const { v4 } = require('uuid');
70
66
  const ArchiverExtract = require('../../unzipper').default;
71
67
  const { UNZIPPER_FORMAT } = require('../../unzipper');
72
68
  const { destPath, isDestPathDir } = input_1.default.restApiSandboxDownloadDestinationPath(destination, merge, replace);
73
69
  const { sourcePath, identifier } = input_1.default.restApiSandboxDownloadSourcePath(source);
74
- const { sandbox_id } = await client.getSandboxByIdentifier(workspace, project, identifier);
75
- if (!sandbox_id) {
76
- output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
77
- }
70
+ const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
78
71
  output_1.default.normal(texts_1.TXT_SANDBOX_CP_DOWNLOAD, false);
79
- const { body, headers } = await client.sandboxDownloadFile(workspace, sandbox_id, sourcePath);
72
+ const { body, headers } = await client.sandboxDownloadFile(workspace, sandboxId, sourcePath);
80
73
  const isFile = headers['content-type'] === 'application/octet-stream';
81
74
  const name = (0, path_1.basename)(sourcePath);
82
75
  if (isFile) {
@@ -121,14 +114,13 @@ const download = async (client, workspace, project, source, destination, merge,
121
114
  };
122
115
  commandSandboxCp.action(async (source, destination, options) => {
123
116
  const workspace = input_1.default.restApiWorkspace(options.workspace);
124
- const project = input_1.default.restApiProject(options.project);
125
117
  const client = input_1.default.restApiTokenClient();
126
118
  const isUpload = input_1.default.restApiSandboxIsUpload(destination);
127
119
  if (isUpload) {
128
- await upload(client, workspace, project, source, destination, options.ignore || [], options.user);
120
+ await upload(client, workspace, options, source, destination, options.ignore || [], options.user);
129
121
  }
130
122
  else {
131
- await download(client, workspace, project, source, destination, !!options.merge, !!options.replace);
123
+ await download(client, workspace, options, source, destination, !!options.merge, !!options.replace);
132
124
  }
133
125
  });
134
126
  exports.default = commandSandboxCp;
@@ -7,6 +7,7 @@ const utils_1 = require("../../utils");
7
7
  const texts_1 = require("../../texts");
8
8
  const output_1 = __importDefault(require("../../output"));
9
9
  const input_1 = __importDefault(require("../../input"));
10
+ const distro_1 = require("../../types/distro");
10
11
  const VALID_RESOURCES = [
11
12
  '1x2',
12
13
  '2x4',
@@ -23,8 +24,7 @@ const VALID_RESOURCES = [
23
24
  ];
24
25
  const commandSandboxCreate = (0, utils_1.newCommand)('create', texts_1.DESC_COMMAND_SANDBOX_CREATE);
25
26
  commandSandboxCreate.alias('add');
26
- commandSandboxCreate.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
27
- commandSandboxCreate.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
27
+ (0, utils_1.sandboxScopeOptions)(commandSandboxCreate);
28
28
  commandSandboxCreate.option('-n, --name <name>', texts_1.OPTION_SANDBOX_NAME);
29
29
  commandSandboxCreate.option('--note <note>', texts_1.OPTION_SANDBOX_NOTE);
30
30
  commandSandboxCreate.option('--agent-note <note>', texts_1.OPTION_SANDBOX_AGENT_NOTE);
@@ -53,11 +53,24 @@ commandSandboxCreate.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_SANDBOX_
53
53
  commandSandboxCreate.action(async (options) => {
54
54
  const humanId = require('human-id').default;
55
55
  const workspace = input_1.default.restApiWorkspace(options.workspace);
56
- const project = input_1.default.restApiProject(options.project);
56
+ const project = input_1.default.restApiProject(options.project, true);
57
+ const scope = input_1.default.scope(project, options.scope, options.environment, true);
57
58
  const client = input_1.default.restApiTokenClient();
58
59
  if (options.resources && !VALID_RESOURCES.includes(options.resources)) {
59
60
  output_1.default.exitError((0, texts_1.ERR_SANDBOX_INVALID_RESOURCES)(options.resources));
60
61
  }
62
+ // A snapshot brings its own OS, so the two are alternatives - saying both is
63
+ // a mistake worth reporting rather than silently dropping --os.
64
+ if (options.snapshot && options.os) {
65
+ output_1.default.exitError(texts_1.ERR_SANDBOX_SNAPSHOT_OS_CONFLICT);
66
+ }
67
+ // An `environment` key inside a YAML definition is ignored by the API, so both
68
+ // paths carry the environment the same way: the YAML endpoint as
69
+ // `?environment_id=`, the JSON one in the body.
70
+ let environmentId = null;
71
+ if (scope === distro_1.SCOPE.ENVIRONMENT) {
72
+ environmentId = await input_1.default.environmentId(client, workspace, options.environment, options);
73
+ }
61
74
  let result;
62
75
  if (options.yaml) {
63
76
  const yaml = input_1.default.restApiYaml(options.yaml);
@@ -65,7 +78,7 @@ commandSandboxCreate.action(async (options) => {
65
78
  yaml: Buffer.from(yaml, 'utf8').toString('base64'),
66
79
  };
67
80
  output_1.default.normal((0, texts_1.TXT_SANDBOX_CREATING)());
68
- result = await client.createSandboxByYaml(workspace, project, body);
81
+ result = await client.createSandboxByYaml(workspace, scope === distro_1.SCOPE.PROJECT ? project : null, body, environmentId);
69
82
  }
70
83
  else {
71
84
  const timestamp = Date.now().toString(36);
@@ -79,8 +92,15 @@ commandSandboxCreate.action(async (options) => {
79
92
  if (options.fetch) {
80
93
  body.fetch = input_1.default.sandboxFetch(options.fetch);
81
94
  }
95
+ if (environmentId) {
96
+ body.environment = {
97
+ id: environmentId,
98
+ };
99
+ }
82
100
  if (options.snapshot) {
83
- const snapshotsResult = await client.listProjectSnapshots(workspace, project);
101
+ // The snapshot has to come from the scope the sandbox is created in, so
102
+ // the listing filter mirrors the create call below.
103
+ const snapshotsResult = await client.listSnapshots(workspace, scope === distro_1.SCOPE.PROJECT ? project : null, environmentId);
84
104
  const snapshots = snapshotsResult.snapshots || [];
85
105
  const foundSnapshot = snapshots.find((s) => s.name === options.snapshot);
86
106
  if (!foundSnapshot) {
@@ -123,7 +143,7 @@ commandSandboxCreate.action(async (options) => {
123
143
  body.app_dir = options.appDir;
124
144
  }
125
145
  output_1.default.normal((0, texts_1.TXT_SANDBOX_CREATING)(body.name, body.identifier));
126
- result = await client.createSandbox(workspace, project, body);
146
+ result = await client.createSandbox(workspace, scope === distro_1.SCOPE.PROJECT ? project : null, body);
127
147
  }
128
148
  const sandboxId = result.id;
129
149
  if (options.waitForRunning) {
@@ -9,23 +9,18 @@ const output_1 = __importDefault(require("../../output"));
9
9
  const input_1 = __importDefault(require("../../input"));
10
10
  const commandSandboxDestroy = (0, utils_1.newCommand)('destroy', texts_1.DESC_COMMAND_SANDBOX_DESTROY);
11
11
  commandSandboxDestroy.aliases(['delete', 'rm']);
12
- commandSandboxDestroy.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
13
- commandSandboxDestroy.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
12
+ (0, utils_1.sandboxScopeOptions)(commandSandboxDestroy);
14
13
  commandSandboxDestroy.option('-f, --force', texts_1.OPTION_CONFIRM_FORCE);
15
14
  commandSandboxDestroy.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
16
15
  commandSandboxDestroy.action(async (identifier, options) => {
17
16
  const workspace = input_1.default.restApiWorkspace(options.workspace);
18
- const project = input_1.default.restApiProject(options.project);
19
17
  const client = input_1.default.restApiTokenClient();
20
- const { sandbox_id } = await client.getSandboxByIdentifier(workspace, project, identifier);
21
- if (!sandbox_id) {
22
- output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
23
- }
18
+ const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
24
19
  const confirmed = options.force ||
25
20
  (await output_1.default.confirm((0, texts_1.TXT_SANDBOX_DESTROY_CONFIRM)(identifier)));
26
21
  if (!confirmed)
27
22
  output_1.default.exitNormal();
28
- await client.deleteSandbox(workspace, sandbox_id);
23
+ await client.deleteSandbox(workspace, sandboxId);
29
24
  output_1.default.exitSuccess((0, texts_1.TXT_SANDBOX_DESTROYED)(identifier));
30
25
  });
31
26
  exports.default = commandSandboxDestroy;
@@ -19,13 +19,9 @@ commandSandboxEndpointAdd.action(async (identifier, options) => {
19
19
  options.name = name;
20
20
  }
21
21
  const workspace = input_1.default.restApiWorkspace(options.workspace);
22
- const project = input_1.default.restApiProject(options.project);
23
22
  const client = input_1.default.restApiTokenClient();
24
- const { sandbox_id } = await client.getSandboxByIdentifier(workspace, project, identifier);
25
- if (!sandbox_id) {
26
- output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
27
- }
28
- const sandbox = await client.getSandbox(workspace, sandbox_id);
23
+ const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
24
+ const sandbox = await client.getSandbox(workspace, sandboxId);
29
25
  const endpoints = sandbox.endpoints || [];
30
26
  const existing = endpoints.find((ep) => ep.name === name);
31
27
  if (existing) {
@@ -33,7 +29,7 @@ commandSandboxEndpointAdd.action(async (identifier, options) => {
33
29
  }
34
30
  const endpoint = input_1.default.prepareSandboxEndpoint(options);
35
31
  const updatedEndpoints = [...endpoints, endpoint];
36
- const updatedSandbox = await client.updateSandbox(workspace, sandbox_id, {
32
+ const updatedSandbox = await client.updateSandbox(workspace, sandboxId, {
37
33
  endpoints: updatedEndpoints,
38
34
  });
39
35
  const addedEndpoint = (updatedSandbox.endpoints || []).find((ep) => ep.name === name);
@@ -9,21 +9,16 @@ const output_1 = __importDefault(require("../../../output"));
9
9
  const input_1 = __importDefault(require("../../../input"));
10
10
  const commandSandboxEndpointDelete = (0, utils_1.newCommand)('delete', texts_1.DESC_COMMAND_SANDBOX_ENDPOINT_DELETE);
11
11
  commandSandboxEndpointDelete.alias('rm');
12
- commandSandboxEndpointDelete.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
13
- commandSandboxEndpointDelete.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
12
+ (0, utils_1.sandboxScopeOptions)(commandSandboxEndpointDelete);
14
13
  commandSandboxEndpointDelete.option('-f, --force', texts_1.OPTION_CONFIRM_FORCE);
15
14
  commandSandboxEndpointDelete.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
16
15
  commandSandboxEndpointDelete.argument('<endpoint-name>', texts_1.OPTION_SANDBOX_ENDPOINT_NAME_ARG);
17
16
  commandSandboxEndpointDelete.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_SANDBOX_ENDPOINT_DELETE}`);
18
17
  commandSandboxEndpointDelete.action(async (identifier, endpointName, options) => {
19
18
  const workspace = input_1.default.restApiWorkspace(options.workspace);
20
- const project = input_1.default.restApiProject(options.project);
21
19
  const client = input_1.default.restApiTokenClient();
22
- const { sandbox_id } = await client.getSandboxByIdentifier(workspace, project, identifier);
23
- if (!sandbox_id) {
24
- output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
25
- }
26
- const sandbox = await client.getSandbox(workspace, sandbox_id);
20
+ const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
21
+ const sandbox = await client.getSandbox(workspace, sandboxId);
27
22
  const endpoints = sandbox.endpoints || [];
28
23
  const endpointIndex = endpoints.findIndex((ep) => ep.name === endpointName);
29
24
  if (endpointIndex === -1) {
@@ -34,7 +29,7 @@ commandSandboxEndpointDelete.action(async (identifier, endpointName, options) =>
34
29
  (await output_1.default.confirm((0, texts_1.TXT_SANDBOX_ENDPOINT_DELETE_CONFIRM)(identifier, endpointName)));
35
30
  if (!confirmed)
36
31
  output_1.default.exitNormal();
37
- await client.updateSandbox(workspace, sandbox_id, {
32
+ await client.updateSandbox(workspace, sandboxId, {
38
33
  endpoints: updatedEndpoints,
39
34
  });
40
35
  output_1.default.exitSuccess((0, texts_1.TXT_SANDBOX_ENDPOINT_DELETED)(endpointName));