bdy 1.23.14-dev-target-commands → 1.23.14-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 (46) hide show
  1. package/distTs/README.md +181 -0
  2. package/distTs/package.json +3 -3
  3. package/distTs/src/agent/manager.js +9 -2
  4. package/distTs/src/api/client.js +22 -11
  5. package/distTs/src/command/api/request.js +5 -4
  6. package/distTs/src/command/environment/delete.js +2 -1
  7. package/distTs/src/command/environment/resolve.js +21 -4
  8. package/distTs/src/command/environment/update.js +2 -1
  9. package/distTs/src/command/pre.js +9 -8
  10. package/distTs/src/command/project/get.js +18 -0
  11. package/distTs/src/command/project/git/credential.js +6 -4
  12. package/distTs/src/command/project/set.js +31 -0
  13. package/distTs/src/command/sandbox/exec/command.js +1 -1
  14. package/distTs/src/command/sandbox/exec/logs.js +1 -1
  15. package/distTs/src/command/sandbox/get/yaml.js +30 -0
  16. package/distTs/src/command/target/delete.js +2 -1
  17. package/distTs/src/command/target/exec/command.js +3 -5
  18. package/distTs/src/command/target/scope.js +37 -7
  19. package/distTs/src/command/target/update.js +2 -1
  20. package/distTs/src/command/version.js +1 -1
  21. package/distTs/src/command/vt/scrape.js +193 -0
  22. package/distTs/src/index.js +14 -0
  23. package/distTs/src/logger.js +7 -1
  24. package/distTs/src/output/pipeline.js +5 -3
  25. package/distTs/src/output.js +12 -3
  26. package/distTs/src/texts.js +8 -9
  27. package/distTs/src/tunnel/server/sftp.js +24 -5
  28. package/distTs/src/tunnel/ssh/client.js +35 -4
  29. package/distTs/src/utils.js +49 -14
  30. package/package.json +3 -3
  31. package/distTs/detect-rules.json +0 -351
  32. package/distTs/src/command/pipeline/run/apply.js +0 -62
  33. package/distTs/src/command/yaml/actions/detect.js +0 -268
  34. package/distTs/src/command/yaml/actions/info.js +0 -56
  35. package/distTs/src/command/yaml/actions/list.js +0 -70
  36. package/distTs/src/command/yaml/actions/schema.js +0 -104
  37. package/distTs/src/command/yaml/actions.js +0 -13
  38. package/distTs/src/command/yaml/agents.js +0 -88
  39. package/distTs/src/command/yaml/cache.js +0 -98
  40. package/distTs/src/command/yaml/init.js +0 -110
  41. package/distTs/src/command/yaml/pipeline.js +0 -42
  42. package/distTs/src/command/yaml/render.js +0 -83
  43. package/distTs/src/command/yaml/schemaUtils.js +0 -139
  44. package/distTs/src/command/yaml/validate.js +0 -259
  45. package/distTs/src/command/yaml.js +0 -18
  46. package/distTs/src/diskCache.js +0 -82
@@ -0,0 +1,181 @@
1
+ # Buddy CLI (`bdy`)
2
+
3
+ Work seamlessly with [Buddy](https://buddy.works) from the command line.
4
+
5
+ `bdy` is Buddy's official command-line interface for managing CI/CD pipelines, agents, tests, artifacts, sandboxes, distributions, and more — straight from your terminal.
6
+
7
+ 📚 Full documentation: https://buddy.works/docs/cli/getting-started
8
+
9
+ ## Installation
10
+
11
+ ### npm (recommended)
12
+
13
+ Requires Node.js 14+.
14
+
15
+ ```bash
16
+ # macOS / Linux
17
+ sudo npm i -g bdy
18
+
19
+ # Windows
20
+ npm i -g bdy
21
+ ```
22
+
23
+ ### Homebrew (macOS)
24
+
25
+ ```bash
26
+ brew tap buddy/bdy
27
+ brew install bdy
28
+ ```
29
+
30
+ ### APT (Linux x64 / ARM64)
31
+
32
+ ```bash
33
+ sudo apt-get update && sudo apt-get install -y software-properties-common
34
+ sudo gpg --homedir /tmp --no-default-keyring --keyring /usr/share/keyrings/buddy.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys eb39332e766364ca6220e8dc631c5a16310cc0ad
35
+ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/buddy.gpg] https://es.buddy.works/bdy/apt-repo prod main" | sudo tee /etc/apt/sources.list.d/buddy.list > /dev/null
36
+ sudo apt-get update && sudo apt-get install -y bdy
37
+ ```
38
+
39
+ > On ARM64, replace `arch=amd64` with `arch=arm64` in the `echo` command above.
40
+
41
+ ### Chocolatey (Windows)
42
+
43
+ ```powershell
44
+ choco install bdy --pre
45
+ ```
46
+
47
+ ### Direct download
48
+
49
+ Prebuilt binaries are available for macOS (Apple Silicon), Linux (x64/ARM64), and Windows (x64) on the [installation page](https://buddy.works/docs/cli/getting-started).
50
+
51
+ ### Verify & update
52
+
53
+ ```bash
54
+ bdy version # show current and latest version
55
+ sudo npm i -g bdy # update (or use your package manager's upgrade command)
56
+ ```
57
+
58
+ ## Authentication
59
+
60
+ A [Buddy account](https://buddy.works) is required to use the CLI.
61
+
62
+ Log in interactively:
63
+
64
+ ```bash
65
+ bdy login
66
+ ```
67
+
68
+ New to Buddy? Create an account from the CLI:
69
+
70
+ ```bash
71
+ bdy register
72
+ ```
73
+
74
+ ### Linking a project
75
+
76
+ Once logged in, link a directory to a Buddy project:
77
+
78
+ ```bash
79
+ mkdir my-proj
80
+ cd my-proj
81
+ bdy proj link
82
+ ```
83
+
84
+ After linking, every `bdy` command run from this directory is executed against the linked project — no need to pass the project name each time.
85
+
86
+ Check who you're logged in as, or log out:
87
+
88
+ ```bash
89
+ bdy whoami
90
+ bdy logout
91
+ ```
92
+
93
+ ### Tokens & non-interactive login
94
+
95
+ For CI/CD pipelines and non-browser environments, authenticate with a [personal access token](https://buddy.works/docs/api/getting-started/oauth2/personal-access-token):
96
+
97
+ ```bash
98
+ bdy login --token <token>
99
+ ```
100
+
101
+ The login command accepts the following options:
102
+
103
+ | Option | Description | Environment variable |
104
+ | --- | --- | --- |
105
+ | `--token <token>` | Personal access token | `BUDDY_TOKEN` |
106
+ | `--api <url>` | API URL for On-Premises installations | `BUDDY_API_ENDPOINT` |
107
+ | `--region <region>` | Region: `us`, `eu`, or `as` | `BUDDY_REGION` |
108
+ | `-w, --workspace <domain>` | Workspace URL handle | `BUDDY_WORKSPACE` |
109
+
110
+ Each option can be supplied as a flag or via its environment variable — handy for pipelines and headless setups:
111
+
112
+ ```bash
113
+ export BUDDY_TOKEN=<token>
114
+ export BUDDY_WORKSPACE=<your-workspace>
115
+ bdy login
116
+ ```
117
+
118
+ ## Agent integration (plugin & skills)
119
+
120
+ The [Buddy Plugin](https://github.com/buddy/buddy-plugin) lets coding agents deploy applications, expose local services, and manage infrastructure on Buddy. It bundles a consolidated skill covering sandboxes, artifacts, tunnels, domains, distributions, and pipelines, plus two commands:
121
+
122
+ - `/deploy [name] [path]` — deploy a static site or server application
123
+ - `/expose [port]` — open a tunnel to a locally running service
124
+
125
+ Make sure the CLI is installed first (`sudo npm install -g bdy`), then:
126
+
127
+ **Claude Code** — install the plugin (commands + skills):
128
+
129
+ ```bash
130
+ claude plugin marketplace add buddy/buddy-plugin
131
+ claude plugin install buddy@buddy-plugin
132
+ ```
133
+
134
+ **Other agents** — install the skills only:
135
+
136
+ ```bash
137
+ npx skills add buddy/buddy-plugin
138
+ ```
139
+
140
+ Then authenticate and link your project:
141
+
142
+ ```bash
143
+ bdy login
144
+ cd your-project
145
+ bdy proj link
146
+ ```
147
+
148
+ The plugin auto-detects your project type: static sites generate versioned artifacts with public URLs, while server applications deploy to sandboxes with HTTPS endpoints. Monorepos can deploy multiple applications, each getting its own URL and endpoint.
149
+
150
+ ## Commands
151
+
152
+ | Command | Description |
153
+ | --- | --- |
154
+ | `bdy login` / `sign-in` | Log in to Buddy |
155
+ | `bdy register` / `sign-up` | Register a new Buddy account |
156
+ | `bdy whoami` | Check login information |
157
+ | `bdy logout` | Log out from Buddy |
158
+ | `bdy workspace` / `ws` | Manage workspaces |
159
+ | `bdy project` / `proj` | Manage projects |
160
+ | `bdy pipeline` / `pip` | Interact with the pipeline service |
161
+ | `bdy artifact` / `art` | Interact with the artifact service |
162
+ | `bdy sandbox` / `sb` | Interact with sandboxes |
163
+ | `bdy agent` | Install and run `bdy` as an OS service (Windows, macOS, Linux) |
164
+ | `bdy tunnel` | Manage tunnel configuration |
165
+ | `bdy crawl` | Manage web crawls (Markdown, HTML, PNG/JPEG screenshots; suite linking) |
166
+ | `bdy tests` | Manage unit tests and visual regression (Storybook, URL captures, runners) |
167
+ | `bdy domain` | Manage zones used in distribution routes |
168
+ | `bdy distro` | Manage distributions |
169
+ | `bdy api` | Contact the Buddy API directly |
170
+ | `bdy update` / `version` | Show version and update the CLI |
171
+
172
+ Run any command with `-h` / `--help` for detailed usage:
173
+
174
+ ```bash
175
+ bdy --help
176
+ bdy pipeline --help
177
+ ```
178
+
179
+ ## Feedback
180
+
181
+ Found a bug or have a feature request? Let us know through [Buddy support](https://buddy.works/support) or the [documentation](https://buddy.works/docs/cli/getting-started).
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bdy",
3
3
  "preferGlobal": false,
4
- "version": "1.23.14-dev-target-commands",
4
+ "version": "1.23.14-dev",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "homepage": "https://buddy.works/docs/cli",
@@ -53,7 +53,7 @@
53
53
  "@fastify/cors": "11.2.0",
54
54
  "@inquirer/prompts": "8.5.2",
55
55
  "@modelcontextprotocol/sdk": "1.30.0",
56
- "@puppeteer/browsers": "2.13.0",
56
+ "@puppeteer/browsers": "3.2.0",
57
57
  "@scalar/json-magic": "0.12.16",
58
58
  "@scalar/openapi-parser": "0.28.7",
59
59
  "@scalar/openapi-types": "0.9.1",
@@ -81,7 +81,7 @@
81
81
  "picomatch": "4.0.4",
82
82
  "pino": "10.3.1",
83
83
  "punycode": "2.3.1",
84
- "puppeteer-core": "23.11.1",
84
+ "puppeteer-core": "25.6.0",
85
85
  "range-parser": "1.2.1",
86
86
  "socket.io-client": "4.8.3",
87
87
  "ssh2": "1.17.0",
@@ -161,6 +161,10 @@ class AgentManagerClass {
161
161
  }
162
162
  const current = (0, utils_1.getVersion)();
163
163
  const latest = await (0, utils_1.getLatestVersion)();
164
+ // Equality only, deliberately not "is latest newer": this runs on an explicit remote
165
+ // update action, whose meaning is "match what the channel publishes". Refusing an
166
+ // older latest would take away the only rollback the fleet has - moving the channel
167
+ // pointer back after a bad release.
164
168
  if (!latest || latest === '0.0.0' || latest === current) {
165
169
  logger_1.default.info((0, texts_1.LOG_AGENT_UPDATE_NOT_NEEDED)(current));
166
170
  // Nothing to do is not a failure, and showing it as one would make an update of
@@ -174,12 +178,15 @@ class AgentManagerClass {
174
178
  // - this is the value that stays behind if the restart never lands.
175
179
  this.agent?.reportUpdateStatus(tunnel_1.TUNNEL_AGENT_UPDATE_STATUS.STARTED);
176
180
  this.agent?.flushUpdateStatus();
177
- const target = latest.match(/^\d+\.\d+\.\d+/);
181
+ // getLatestVersion already refuses a version it cannot parse, so this is belt and
182
+ // braces - kept because the download url is built from the triple, and shipping a
183
+ // malformed one would fail much further away from the cause.
184
+ const target = (0, utils_1.parseVersion)(latest);
178
185
  if (!target) {
179
186
  logger_1.default.error(texts_1.LOG_AGENT_UPDATE_FAILED);
180
187
  return report(tunnel_1.TUNNEL_AGENT_UPDATE_STATUS.FAILED);
181
188
  }
182
- const swapped = await this.system.swapBinaryToVersion(target[0]);
189
+ const swapped = await this.system.swapBinaryToVersion(target.version);
183
190
  if (!swapped) {
184
191
  logger_1.default.error((0, texts_1.LOG_AGENT_UPDATE_BINARY_INVALID)(latest));
185
192
  return report(tunnel_1.TUNNEL_AGENT_UPDATE_STATUS.FAILED);
@@ -86,9 +86,8 @@ class ApiClient {
86
86
  }
87
87
  return false;
88
88
  }
89
- async request({ method = 'GET', path, query, body = null, headers = {}, parseResponseBody = false, rawResponseBody = false, rawResponse = false, httpUrlEncoded = false, tryRefreshingToken = true, }) {
90
- if (!headers)
91
- headers = {};
89
+ async request(args) {
90
+ const { method = 'GET', path, query, body = null, headers = {}, parseResponseBody = false, rawResponseBody = false, rawResponse = false, httpUrlEncoded = false, tryRefreshingToken = true, } = args;
92
91
  if (this.token && !headers.authorization) {
93
92
  headers.authorization = `Bearer ${this.token}`;
94
93
  }
@@ -176,15 +175,12 @@ class ApiClient {
176
175
  const success = await this.tryRefreshToken();
177
176
  if (success) {
178
177
  delete headers.authorization;
178
+ // Retry with the complete original args (an explicit field list
179
+ // once silently dropped `query` here); only the mutated headers
180
+ // and the refresh guard are overridden.
179
181
  return await this.request({
180
- method,
181
- path,
182
- body,
182
+ ...args,
183
183
  headers,
184
- parseResponseBody,
185
- rawResponseBody,
186
- rawResponse,
187
- httpUrlEncoded,
188
184
  tryRefreshingToken: false,
189
185
  });
190
186
  }
@@ -253,6 +249,12 @@ class ApiClient {
253
249
  }
254
250
  }
255
251
  else if (status === 204) {
252
+ // A no-content success still honors the raw-output contracts: callers
253
+ // stream the (empty) body instead of tripping over a null.
254
+ if (rawResponse)
255
+ return response;
256
+ if (rawResponseBody)
257
+ return responseBody;
256
258
  await responseBody.dump();
257
259
  return null;
258
260
  }
@@ -1251,11 +1253,20 @@ class ApiClient {
1251
1253
  query += '&';
1252
1254
  query += encodeURIComponent(key) + '=' + encodeURIComponent(value);
1253
1255
  });
1254
- return await this.request({
1256
+ const resolved = await this.request({
1255
1257
  method: 'GET',
1256
1258
  path: `/workspaces/${encodeURIComponent(workspace)}/identifiers${query}`,
1257
1259
  parseResponseBody: true,
1258
1260
  });
1261
+ // A queried project must itself resolve: for some resources
1262
+ // (environments, artifacts, distributions) the endpoint otherwise falls
1263
+ // back to the workspace resource of the same identifier, silently
1264
+ // answering for a different scope than asked (TARGET_API_REQUIREMENTS.md
1265
+ // p6). project_identifier is present whenever the project exists.
1266
+ if (params.project && !resolved.project_identifier) {
1267
+ throw new Error(texts_1.ERR_PROJECT_NOT_FOUND);
1268
+ }
1269
+ return resolved;
1259
1270
  }
1260
1271
  async resolveIdentifiers(workspace, params) {
1261
1272
  let query = '';
@@ -159,14 +159,15 @@ const request = async (method, url, options) => {
159
159
  output_1.default.exitNormal('');
160
160
  }
161
161
  else {
162
- // setEncoding keeps multibyte characters intact across chunk boundaries,
163
- // and newLine=false keeps chunk boundaries out of the payload
162
+ // setEncoding keeps multibyte characters intact across chunk boundaries;
163
+ // the response is a 1:1 payload, so it bypasses terminal-kit (whose
164
+ // formatting corrupts % and ^ sequences, common in URL-encoded data)
164
165
  body.setEncoding('utf8');
165
166
  body.on('data', (data) => {
166
- output_1.default.normal(data, false);
167
+ output_1.default.data(data, false);
167
168
  });
168
169
  body.on('close', () => {
169
- output_1.default.normal('');
170
+ output_1.default.data('');
170
171
  output_1.default.exitNormal('');
171
172
  });
172
173
  }
@@ -18,7 +18,8 @@ commandEnvironmentDelete.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_ENVI
18
18
  commandEnvironmentDelete.action(async (identifier, options) => {
19
19
  const workspace = input_1.default.restApiWorkspace(options.workspace);
20
20
  const client = input_1.default.restApiTokenClient();
21
- const environment_id = await (0, resolve_1.resolveEnvironmentId)(client, workspace, identifier, options);
21
+ const environment_id = await (0, resolve_1.resolveEnvironmentId)(client, workspace, identifier, options,
22
+ /* strictProject */ true);
22
23
  const confirmed = options.force ||
23
24
  (await output_1.default.confirm((0, texts_1.TXT_ENVIRONMENT_DELETE_CONFIRM)(identifier)));
24
25
  if (!confirmed)
@@ -11,12 +11,29 @@ const input_1 = __importDefault(require("../../input"));
11
11
  // project context from flags/env -> /identifiers -> hash id, exiting on a
12
12
  // miss. Without a project the identifier resolves as a workspace
13
13
  // environment; with one, along the project line.
14
- const resolveEnvironmentId = async (client, workspace, identifier, options) => {
14
+ //
15
+ // strictProject is for mutating commands (update/delete): an explicit -p is
16
+ // a statement "the environment of this project", so the resolved environment
17
+ // must actually live there. Without it /identifiers silently falls back to
18
+ // the workspace environment of the same identifier and the write lands on a
19
+ // workspace-wide resource. Reads keep the fallback - that is how workspace
20
+ // environments stay addressable from inside a project context.
21
+ const resolveEnvironmentId = async (client, workspace, identifier, options, strictProject = false) => {
15
22
  const project = input_1.default.restApiProject(options.project, true);
16
- const { environment_id } = await client.getEnvironmentByIdentifier(workspace, identifier, project);
17
- if (!environment_id) {
23
+ const resolved = await client.getEnvironmentByIdentifier(workspace, identifier, project);
24
+ // A nonexistent project is rejected by the API client
25
+ // (getResourceByIdentifier); here only the environment itself can miss.
26
+ if (!resolved.environment_id) {
18
27
  output_1.default.exitError(texts_1.ERR_ENVIRONMENT_NOT_FOUND);
19
28
  }
20
- return environment_id;
29
+ if (strictProject && options.project) {
30
+ const environment = await client.getEnvironment(workspace, resolved.environment_id);
31
+ // a workspace environment carries no project, so this also fails the
32
+ // workspace-fallback case
33
+ if (environment.project?.name !== resolved.project_identifier) {
34
+ output_1.default.exitError(texts_1.ERR_ENVIRONMENT_NOT_FOUND);
35
+ }
36
+ }
37
+ return resolved.environment_id;
21
38
  };
22
39
  exports.resolveEnvironmentId = resolveEnvironmentId;
@@ -52,7 +52,8 @@ commandEnvironmentUpdate.action(async (identifier, options) => {
52
52
  output_1.default.exitError(texts_1.ERR_COMMAND_NO_UPDATE);
53
53
  }
54
54
  const client = input_1.default.restApiTokenClient();
55
- const environment_id = await (0, resolve_1.resolveEnvironmentId)(client, workspace, identifier, options);
55
+ const environment_id = await (0, resolve_1.resolveEnvironmentId)(client, workspace, identifier, options,
56
+ /* strictProject */ true);
56
57
  const result = await client.updateEnvironment(workspace, environment_id, body);
57
58
  output_1.default.exitSuccess((0, texts_1.TXT_ENVIRONMENT_UPDATED)(result.identifier, result.html_url));
58
59
  });
@@ -46,14 +46,15 @@ const commandPre = async (_, command) => {
46
46
  if (!output_1.default.isTTY() || command.hideVersionUpdate || ['json', 'jsonl'].includes(command.opts()?.format)) {
47
47
  return;
48
48
  }
49
- const newCli = !!command.latestVersion && command.currentVersion !== command.latestVersion;
50
- const newAgent = !!command.agentStatus && !!command.latestVersion &&
51
- command.agentStatus.version !== command.latestVersion;
52
- if ((0, utils_1.isDocker)() && newCli && command.latestVersion) {
53
- output_1.default.newCliDockerVersion(command.latestVersion);
54
- }
55
- else if (newCli && command.latestVersion) {
56
- output_1.default.newCliVersion(command.latestVersion);
49
+ // No truthiness guards on the versions: an empty or unparsable one is never newer
50
+ const latest = command.latestVersion || '';
51
+ const newCli = (0, utils_1.isVersionNewer)(latest, command.currentVersion || '');
52
+ const newAgent = !!command.agentStatus && (0, utils_1.isVersionNewer)(latest, command.agentStatus.version);
53
+ if ((0, utils_1.isDocker)() && newCli) {
54
+ output_1.default.newCliDockerVersion(latest);
55
+ }
56
+ else if (newCli) {
57
+ output_1.default.newCliVersion(latest);
57
58
  }
58
59
  else if (newAgent) {
59
60
  output_1.default.newAgentVersion();
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const cfg_1 = __importDefault(require("../../tunnel/cfg"));
7
+ const output_1 = __importDefault(require("../../output"));
8
+ const texts_1 = require("../../texts");
9
+ const utils_1 = require("../../utils");
10
+ const commandProjectGet = (0, utils_1.newCommand)('get', texts_1.DESC_COMMAND_PROJECT_GET);
11
+ commandProjectGet.action(async () => {
12
+ const project = cfg_1.default.getProject();
13
+ if (!project) {
14
+ output_1.default.exitError(texts_1.TXT_PROJECT_NONE);
15
+ }
16
+ output_1.default.exitNormal(project);
17
+ });
18
+ exports.default = commandProjectGet;
@@ -60,10 +60,12 @@ commandProjectGitCredential.action(async (action) => {
60
60
  projects.projects[0].html_url.includes(input.host);
61
61
  }
62
62
  if (myHost) {
63
- output_1.default.normal(`protocol=${input.protocol || 'https'}`);
64
- output_1.default.normal(`host=${input.host}`);
65
- output_1.default.normal(`username=${client.token}`);
66
- output_1.default.normal(`password=`);
63
+ // git credential protocol output - a machine-read payload, so it
64
+ // bypasses terminal-kit formatting
65
+ output_1.default.data(`protocol=${input.protocol || 'https'}`);
66
+ output_1.default.data(`host=${input.host}`);
67
+ output_1.default.data(`username=${client.token}`);
68
+ output_1.default.data(`password=`);
67
69
  }
68
70
  }
69
71
  catch {
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const cfg_1 = __importDefault(require("../../tunnel/cfg"));
7
+ const output_1 = __importDefault(require("../../output"));
8
+ const texts_1 = require("../../texts");
9
+ const utils_1 = require("../../utils");
10
+ const input_1 = __importDefault(require("../../input"));
11
+ const commandProjectSet = (0, utils_1.newCommand)('set', texts_1.DESC_COMMAND_PROJECT_SET);
12
+ commandProjectSet.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
13
+ commandProjectSet.argument('[project]', texts_1.ARG_COMMAND_PROJECT_NAME);
14
+ commandProjectSet.action(async (project, options) => {
15
+ output_1.default.handleSignals();
16
+ const workspace = input_1.default.restApiWorkspace(options.workspace);
17
+ const client = input_1.default.restApiTokenClient();
18
+ if (project) {
19
+ await client.getProject(workspace, project);
20
+ }
21
+ else {
22
+ const response = await client.getProjects(workspace);
23
+ project = await output_1.default.selectProject(response.projects);
24
+ }
25
+ cfg_1.default.setProject(project);
26
+ if (!project)
27
+ output_1.default.exitSuccess(texts_1.TXT_PROJECT_SET_CLEARED);
28
+ else
29
+ output_1.default.exitSuccess((0, texts_1.TXT_PROJECT_SET_SUCCESS)(project));
30
+ });
31
+ exports.default = commandProjectSet;
@@ -43,7 +43,7 @@ commandSandboxExecCommand.action(async (identifier, command, options) => {
43
43
  str.forEach((s) => {
44
44
  const json = JSON.parse(s);
45
45
  if (json.data)
46
- output_1.default.normal(json.data);
46
+ output_1.default.data(json.data);
47
47
  });
48
48
  }
49
49
  catch {
@@ -31,7 +31,7 @@ commandSandboxExecLogs.action(async (identifier, commandId, options) => {
31
31
  str.forEach((s) => {
32
32
  const json = JSON.parse(s);
33
33
  if (json.data)
34
- output_1.default.normal(json.data);
34
+ output_1.default.data(json.data);
35
35
  });
36
36
  }
37
37
  catch {
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const utils_1 = require("../../../utils");
7
+ const texts_1 = require("../../../texts");
8
+ const input_1 = __importDefault(require("../../../input"));
9
+ const output_1 = __importDefault(require("../../../output"));
10
+ const commandSandboxGetYaml = (0, utils_1.newCommand)('yaml', texts_1.DESC_COMMAND_SANDBOX_GET_YAML);
11
+ commandSandboxGetYaml.hideVersionUpdate = true;
12
+ commandSandboxGetYaml.alias('yml');
13
+ commandSandboxGetYaml.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
14
+ commandSandboxGetYaml.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
15
+ commandSandboxGetYaml.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
16
+ commandSandboxGetYaml.action(async (identifier, options) => {
17
+ const workspace = input_1.default.restApiWorkspace(options.workspace);
18
+ const project = input_1.default.restApiProject(options.project);
19
+ const client = input_1.default.restApiTokenClient();
20
+ let result = await client.listSandboxes(workspace, project);
21
+ const sandboxes = result.sandboxes || [];
22
+ const found = sandboxes.find((s) => s.identifier === identifier);
23
+ if (!found) {
24
+ output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
25
+ }
26
+ const sandboxId = found.id;
27
+ result = await client.getSandboxYaml(workspace, sandboxId);
28
+ output_1.default.exitNormal(Buffer.from(result.yaml, 'base64').toString('utf8'));
29
+ });
30
+ exports.default = commandSandboxGetYaml;
@@ -20,7 +20,8 @@ commandTargetDelete.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_TARGET_DE
20
20
  commandTargetDelete.action(async (identifier, options) => {
21
21
  const workspace = input_1.default.restApiWorkspace(options.workspace);
22
22
  const client = input_1.default.restApiTokenClient();
23
- const target_id = await (0, scope_1.resolveTargetId)(client, workspace, identifier, options);
23
+ const target_id = await (0, scope_1.resolveTargetId)(client, workspace, identifier, options,
24
+ /* strictLine */ true);
24
25
  const confirmed = options.force || (await output_1.default.confirm((0, texts_1.TXT_TARGET_DELETE_CONFIRM)(identifier)));
25
26
  if (!confirmed)
26
27
  output_1.default.exitNormal();
@@ -17,7 +17,6 @@ commandTargetExecCommand.option('-p, --project <name>', texts_1.OPTION_REST_API_
17
17
  commandTargetExecCommand.option('--pipeline <identifier>', texts_1.OPTION_TARGET_PIPELINE);
18
18
  commandTargetExecCommand.option('--environment <identifier>', texts_1.OPTION_TARGET_ENVIRONMENT);
19
19
  commandTargetExecCommand.option('--async', texts_1.OPTION_TARGET_EXEC_ASYNC);
20
- commandTargetExecCommand.option('--wait', texts_1.OPTION_TARGET_EXEC_WAIT);
21
20
  commandTargetExecCommand.option('--file <path>', texts_1.OPTION_TARGET_EXEC_FILE);
22
21
  commandTargetExecCommand.option('--format <text|json>', texts_1.OPTION_FORMAT);
23
22
  commandTargetExecCommand.argument('<identifier>', texts_1.OPTION_TARGET_IDENTIFIER);
@@ -42,15 +41,14 @@ commandTargetExecCommand.action(async (identifier, command, options) => {
42
41
  }
43
42
  const workspace = input_1.default.restApiWorkspace(options.workspace);
44
43
  const client = input_1.default.restApiTokenClient();
45
- const target_id = await (0, scope_1.resolveTargetId)(client, workspace, identifier, options);
44
+ const target_id = await (0, scope_1.resolveTargetId)(client, workspace, identifier, options,
45
+ /* strictLine */ true);
46
46
  let exec = await client.executeTargetCommand(workspace, target_id, {
47
47
  command: cmd,
48
48
  });
49
49
  const execId = exec.execution_id;
50
50
  const json = options.format === 'json';
51
- // --wait is deprecated (waiting is the default) but still forces sync
52
- // when combined with --async.
53
- if (options.async && !options.wait) {
51
+ if (options.async) {
54
52
  if (json) {
55
53
  output_1.default.json(exec);
56
54
  }
@@ -10,14 +10,42 @@ const output_1 = __importDefault(require("../../output"));
10
10
  const input_1 = __importDefault(require("../../input"));
11
11
  // Shared resolve preamble for commands taking a target identifier: line
12
12
  // context from flags/env -> /identifiers -> hash id, exiting on a miss.
13
- const resolveTargetId = async (client, workspace, identifier, options) => {
13
+ //
14
+ // strictLine is for mutating commands (update/delete/exec command): an
15
+ // explicit line flag is a statement "the target of this project/pipeline/
16
+ // environment", yet /identifiers falls back to the workspace target of the
17
+ // same identifier when the requested line exists but does not hold it - the
18
+ // operation would silently land on a workspace-wide resource. Reads keep
19
+ // the fallback (workspace targets stay addressable from a line context).
20
+ const resolveTargetId = async (client, workspace, identifier, options, strictLine = false) => {
14
21
  const project = input_1.default.restApiProject(options.project, true);
15
22
  const line = input_1.default.targetLine(project, options.pipeline, options.environment);
16
- const { target_id } = await client.getTargetByIdentifier(workspace, identifier, line);
17
- if (!target_id) {
23
+ const resolved = await client.getTargetByIdentifier(workspace, identifier, line);
24
+ if (!resolved.target_id) {
18
25
  output_1.default.exitError(texts_1.ERR_TARGET_NOT_FOUND);
19
26
  }
20
- return target_id;
27
+ if (strictLine) {
28
+ const requested = options.pipeline
29
+ ? utils_1.TARGET_SCOPE.PIPELINE
30
+ : options.environment
31
+ ? utils_1.TARGET_SCOPE.ENVIRONMENT
32
+ : options.project
33
+ ? utils_1.TARGET_SCOPE.PROJECT
34
+ : null;
35
+ if (requested) {
36
+ const target = await client.getTarget(workspace, resolved.target_id);
37
+ const matches = requested === utils_1.TARGET_SCOPE.PIPELINE
38
+ ? String(target.pipeline?.id) === String(resolved.pipeline_id)
39
+ : requested === utils_1.TARGET_SCOPE.ENVIRONMENT
40
+ ? target.environment?.id === resolved.environment_id
41
+ : (0, exports.targetScopeOf)(target) === utils_1.TARGET_SCOPE.PROJECT &&
42
+ target.project?.name === resolved.project_identifier;
43
+ if (!matches) {
44
+ output_1.default.exitError(texts_1.ERR_TARGET_NOT_FOUND);
45
+ }
46
+ }
47
+ }
48
+ return resolved.target_id;
21
49
  };
22
50
  exports.resolveTargetId = resolveTargetId;
23
51
  // Lines the CLI requests per log fetch.
@@ -95,10 +123,12 @@ const resolveTargetScope = async (client, workspace, project, scope, options) =>
95
123
  };
96
124
  if (project)
97
125
  query.project = project;
98
- const { environment_id } = await client.getResourceByIdentifier(workspace, query);
99
- if (!environment_id)
126
+ const resolved = await client.getResourceByIdentifier(workspace, query);
127
+ // a nonexistent project is rejected by getResourceByIdentifier itself
128
+ if (!resolved.environment_id) {
100
129
  output_1.default.exitError(texts_1.ERR_TARGET_ENVIRONMENT_NOT_FOUND);
101
- params.environmentId = environment_id;
130
+ }
131
+ params.environmentId = resolved.environment_id;
102
132
  }
103
133
  else if (scope === utils_1.TARGET_SCOPE.PROJECT) {
104
134
  params.project = project;
@@ -21,7 +21,8 @@ commandTargetUpdate.action(async (identifier, options) => {
21
21
  const yaml = input_1.default.restApiYaml(options.yaml);
22
22
  const workspace = input_1.default.restApiWorkspace(options.workspace);
23
23
  const client = input_1.default.restApiTokenClient();
24
- const target_id = await (0, scope_1.resolveTargetId)(client, workspace, identifier, options);
24
+ const target_id = await (0, scope_1.resolveTargetId)(client, workspace, identifier, options,
25
+ /* strictLine */ true);
25
26
  const body = {
26
27
  yaml: Buffer.from(yaml, 'utf8').toString('base64'),
27
28
  };
@@ -78,7 +78,7 @@ commandVersion.action(async () => {
78
78
  const currVersion = commandVersion.currentVersion || '';
79
79
  const lastVersion = commandVersion.latestVersion || '';
80
80
  const env = commandVersion.env || '';
81
- if (currVersion !== lastVersion) {
81
+ if ((0, utils_1.isVersionNewer)(lastVersion, currVersion)) {
82
82
  const platform = (0, utils_1.getPlatform)();
83
83
  const isMac = platform === 'darwin';
84
84
  const isWin = platform === 'win32';