@tenderprompt/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/tender.js +30 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +4169 -0
- package/dist/local-files.d.ts +5 -0
- package/dist/local-files.js +46 -0
- package/package.json +34 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { validateTenderApp, } from "@tenderprompt/tender-app-validator";
|
|
8
|
+
import { collectTenderAppFiles } from "./local-files.js";
|
|
9
|
+
class TenderCliUsageError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "TenderCliUsageError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const DEFAULT_PROFILE_NAME = "default";
|
|
16
|
+
const DEFAULT_BASE_URL = "https://app.tenderprompt.com";
|
|
17
|
+
const ARTIFACT_SOURCE_EXPORT_MAX_FILES = 1000;
|
|
18
|
+
const ARTIFACT_SOURCE_EXPORT_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
19
|
+
function rootHelp() {
|
|
20
|
+
return `Usage: tender <command> [options]
|
|
21
|
+
|
|
22
|
+
Commands:
|
|
23
|
+
tender auth login --device
|
|
24
|
+
Authorize this machine with a browser-approved device flow
|
|
25
|
+
tender auth status Show whether a Tender API token is available
|
|
26
|
+
tender artifacts list List artifacts visible to the current token
|
|
27
|
+
tender artifacts create --name <name>
|
|
28
|
+
Create a new artifact in the current account
|
|
29
|
+
tender artifacts update <artifact-id> --name <name>
|
|
30
|
+
Update artifact metadata
|
|
31
|
+
tender artifacts init <artifact-id>
|
|
32
|
+
Bootstrap a checkout for local agent work
|
|
33
|
+
tender artifacts validate <artifact-id>
|
|
34
|
+
Validate the server-side artifact workspace
|
|
35
|
+
tender artifacts build <artifact-id>
|
|
36
|
+
Build the current artifact workspace
|
|
37
|
+
tender artifacts preview rebuild <artifact-id>
|
|
38
|
+
Rebuild the preview from the current workspace
|
|
39
|
+
tender artifacts preview status <artifact-id>
|
|
40
|
+
Show the latest or commit-pinned preview job
|
|
41
|
+
tender artifacts preview watch <artifact-id>
|
|
42
|
+
Stream or print lifecycle events for a preview job
|
|
43
|
+
tender artifacts publish <artifact-id>
|
|
44
|
+
Publish after explicit confirmation
|
|
45
|
+
tender artifacts publish status <artifact-id>
|
|
46
|
+
Show publish job status by job id
|
|
47
|
+
tender artifacts publish watch <artifact-id>
|
|
48
|
+
Stream or print lifecycle events for a publish job
|
|
49
|
+
tender artifacts context fetch <artifact-id>
|
|
50
|
+
Fetch managed agent context into a checkout
|
|
51
|
+
tender artifacts context status <artifact-id>
|
|
52
|
+
Report whether managed context is stale
|
|
53
|
+
tender artifacts context refresh <artifact-id>
|
|
54
|
+
Refresh managed context files
|
|
55
|
+
tender artifacts git setup <artifact-id>
|
|
56
|
+
Configure a checkout to push to the artifact remote
|
|
57
|
+
tender artifacts doctor Validate a local Tender App checkout
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
|
|
61
|
+
tender auth status --json
|
|
62
|
+
tender artifacts list --json
|
|
63
|
+
tender artifacts create --name "Exit Intent Widget" --json
|
|
64
|
+
tender artifacts update artifact_123 --name "Sale Widget" --json
|
|
65
|
+
tender artifacts init artifact_123 --dir ./widget --json
|
|
66
|
+
tender artifacts validate artifact_123 --json
|
|
67
|
+
tender artifacts build artifact_123 --commit $(git rev-parse HEAD) --json
|
|
68
|
+
tender artifacts preview rebuild artifact_123 --commit $(git rev-parse HEAD) --stream --json
|
|
69
|
+
tender artifacts preview status artifact_123 --commit $(git rev-parse HEAD) --json
|
|
70
|
+
tender artifacts preview watch artifact_123 --job job_123 --stream --json
|
|
71
|
+
tender artifacts publish artifact_123 --commit $(git rev-parse HEAD) --confirm publish --stream --json
|
|
72
|
+
tender artifacts context fetch artifact_123 --dir ./widget --dry-run --json
|
|
73
|
+
tender artifacts context status artifact_123 --dir ./widget --json
|
|
74
|
+
tender artifacts context refresh artifact_123 --dir ./widget --dry-run --json
|
|
75
|
+
tender artifacts git setup artifact_123 --dir ./widget --json
|
|
76
|
+
tender artifacts doctor --dir ./widget
|
|
77
|
+
tender artifacts doctor --dir ./widget --json`;
|
|
78
|
+
}
|
|
79
|
+
function authHelp() {
|
|
80
|
+
return `Usage: tender auth <command> [options]
|
|
81
|
+
|
|
82
|
+
Commands:
|
|
83
|
+
tender auth login --device Authorize the CLI with a browser-approved device flow
|
|
84
|
+
tender auth status Show token source, profile, account, and expiry metadata
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
|
|
88
|
+
tender auth login --device --profile publish --artifact artifact_123 --ttl 7d --save-profile publish
|
|
89
|
+
tender auth status --json`;
|
|
90
|
+
}
|
|
91
|
+
function authLoginHelp() {
|
|
92
|
+
return `Usage: tender auth login --device [--base-url <url>] [--profile <edit-preview|publish|create>] [--artifact <artifact-id>] [--ttl <7d|14d|30d>] [--save-profile <name>] [--no-poll] [--json]
|
|
93
|
+
|
|
94
|
+
Options:
|
|
95
|
+
--device Use the OAuth device-code flow.
|
|
96
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL or https://app.tenderprompt.com.
|
|
97
|
+
--profile <value> Token permission profile: edit-preview, publish, or create. Defaults to edit-preview.
|
|
98
|
+
--artifact <artifact-id> Scope the token to one artifact. Required for normal edit-preview agent work.
|
|
99
|
+
--ttl <value> Token lifetime: 7d, 14d, or 30d. Defaults to 7d.
|
|
100
|
+
--save-profile <name> Local config profile name. Defaults to default.
|
|
101
|
+
--no-poll Print the verification URL and exit without waiting for approval.
|
|
102
|
+
--json Emit stable JSON for coding agents.
|
|
103
|
+
|
|
104
|
+
Examples:
|
|
105
|
+
tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
|
|
106
|
+
tender auth login --device --profile publish --artifact artifact_123 --ttl 7d --save-profile publish --json
|
|
107
|
+
tender auth login --device --profile create --ttl 7d --save-profile account`;
|
|
108
|
+
}
|
|
109
|
+
function authStatusHelp() {
|
|
110
|
+
return `Usage: tender auth status [--profile <name>] [--json]
|
|
111
|
+
|
|
112
|
+
Options:
|
|
113
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
114
|
+
--json Emit stable JSON for coding agents.
|
|
115
|
+
|
|
116
|
+
Examples:
|
|
117
|
+
tender auth status
|
|
118
|
+
tender auth status --profile publish --json`;
|
|
119
|
+
}
|
|
120
|
+
function artifactsHelp() {
|
|
121
|
+
return `Usage: tender artifacts <command> [options]
|
|
122
|
+
|
|
123
|
+
Commands:
|
|
124
|
+
tender artifacts list List artifacts visible to the current token
|
|
125
|
+
tender artifacts create --name <name>
|
|
126
|
+
Create a new artifact in the current account
|
|
127
|
+
tender artifacts update <artifact-id> --name <name>
|
|
128
|
+
Update artifact metadata
|
|
129
|
+
tender artifacts init <artifact-id>
|
|
130
|
+
Bootstrap context files and the tender Git remote
|
|
131
|
+
tender artifacts validate <artifact-id>
|
|
132
|
+
Validate the server-side artifact workspace
|
|
133
|
+
tender artifacts build <artifact-id>
|
|
134
|
+
Build the current artifact workspace
|
|
135
|
+
tender artifacts preview rebuild <artifact-id>
|
|
136
|
+
Rebuild the preview and optionally stream lifecycle events
|
|
137
|
+
tender artifacts preview status <artifact-id>
|
|
138
|
+
Show preview job status by latest, commit, or job id
|
|
139
|
+
tender artifacts preview watch <artifact-id>
|
|
140
|
+
Replay lifecycle events by latest, commit, or job id
|
|
141
|
+
tender artifacts publish <artifact-id>
|
|
142
|
+
Publish after dry-run or explicit confirmation
|
|
143
|
+
tender artifacts publish status <artifact-id>
|
|
144
|
+
Show publish job status by job id
|
|
145
|
+
tender artifacts publish watch <artifact-id>
|
|
146
|
+
Replay lifecycle events for a publish job id
|
|
147
|
+
tender artifacts context fetch <artifact-id>
|
|
148
|
+
Fetch AGENTS.md, skills, and Tender App scaffold files
|
|
149
|
+
tender artifacts context status <artifact-id>
|
|
150
|
+
Show managed context freshness for a checkout
|
|
151
|
+
tender artifacts context refresh <artifact-id>
|
|
152
|
+
Refresh managed context files in a checkout
|
|
153
|
+
tender artifacts git setup <artifact-id>
|
|
154
|
+
Configure the tender Git remote and credential helper
|
|
155
|
+
tender artifacts doctor Validate local context, TypeScript scaffold, and Tender App rules
|
|
156
|
+
tender artifacts git remote <artifact-id>
|
|
157
|
+
Show Tender-hosted Git remote metadata
|
|
158
|
+
|
|
159
|
+
Examples:
|
|
160
|
+
tender artifacts list --json
|
|
161
|
+
tender artifacts create --name "Exit Intent Widget" --json
|
|
162
|
+
tender artifacts update artifact_123 --name "Sale Widget" --json
|
|
163
|
+
tender artifacts init artifact_123 --dir ./widget --json
|
|
164
|
+
tender artifacts validate artifact_123 --json
|
|
165
|
+
tender artifacts build artifact_123 --commit $(git rev-parse HEAD) --json
|
|
166
|
+
tender artifacts preview rebuild artifact_123 --commit $(git rev-parse HEAD) --stream --json
|
|
167
|
+
tender artifacts preview status artifact_123 --commit $(git rev-parse HEAD) --json
|
|
168
|
+
tender artifacts preview watch artifact_123 --job job_123 --stream --json
|
|
169
|
+
tender artifacts publish artifact_123 --dry-run --json
|
|
170
|
+
tender artifacts publish status artifact_123 --job job_123 --json
|
|
171
|
+
tender artifacts publish watch artifact_123 --job job_123 --stream --json
|
|
172
|
+
tender artifacts context fetch artifact_123 --dir ./widget --dry-run --json
|
|
173
|
+
tender artifacts context status artifact_123 --dir ./widget --json
|
|
174
|
+
tender artifacts context refresh artifact_123 --dir ./widget --dry-run --json
|
|
175
|
+
tender artifacts git setup artifact_123 --dir ./widget --json
|
|
176
|
+
tender artifacts doctor --dir ./widget
|
|
177
|
+
tender artifacts doctor --json
|
|
178
|
+
tender artifacts git remote artifact_123 --ttl 7d --json`;
|
|
179
|
+
}
|
|
180
|
+
function artifactsListHelp() {
|
|
181
|
+
return `Usage: tender artifacts list [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
182
|
+
|
|
183
|
+
Options:
|
|
184
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
185
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
186
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
187
|
+
--json Emit stable JSON for coding agents.
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
tender artifacts list --json
|
|
191
|
+
tender artifacts list --profile account --json
|
|
192
|
+
TENDER_API_TOKEN=tpat_... tender artifacts list --base-url https://app.tenderprompt.com --json`;
|
|
193
|
+
}
|
|
194
|
+
function artifactsCreateHelp() {
|
|
195
|
+
return `Usage: tender artifacts create --name <name> [--target-pack <id>] [--target-pack-version <version>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
196
|
+
|
|
197
|
+
Options:
|
|
198
|
+
--name <name> Artifact name.
|
|
199
|
+
--target-pack <id> Target pack id. Defaults to the API default.
|
|
200
|
+
--target-pack-version <value> Target pack version. Defaults to the API default.
|
|
201
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
202
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
203
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
204
|
+
--json Emit stable JSON for coding agents.
|
|
205
|
+
|
|
206
|
+
Examples:
|
|
207
|
+
tender artifacts create --name "Exit Intent Widget" --json
|
|
208
|
+
tender artifacts create --name "Exit Intent Widget" --profile account --json`;
|
|
209
|
+
}
|
|
210
|
+
function artifactsUpdateHelp() {
|
|
211
|
+
return `Usage: tender artifacts update <artifact-id> --name <name> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
212
|
+
|
|
213
|
+
Options:
|
|
214
|
+
--name <name> New artifact name.
|
|
215
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
216
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
217
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
218
|
+
--json Emit stable JSON for coding agents.
|
|
219
|
+
|
|
220
|
+
Examples:
|
|
221
|
+
tender artifacts update artifact_123 --name "Sale Widget" --json`;
|
|
222
|
+
}
|
|
223
|
+
function artifactsInitHelp() {
|
|
224
|
+
return `Usage: tender artifacts init <artifact-id> [--dir <path>] [--remote <name>] [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--dry-run] [--force] [--json]
|
|
225
|
+
|
|
226
|
+
Options:
|
|
227
|
+
--dir <path> Local checkout directory to create/update. Defaults to current directory.
|
|
228
|
+
--remote <name> Git remote name to add or update. Defaults to tender.
|
|
229
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
230
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
231
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
232
|
+
--ttl <value> Deprecated for Tender-hosted Git remotes; accepted for compatibility.
|
|
233
|
+
--dry-run Show file and Git actions without writing.
|
|
234
|
+
--force Overwrite conflicting managed context files.
|
|
235
|
+
--json Emit stable JSON for coding agents.
|
|
236
|
+
|
|
237
|
+
Examples:
|
|
238
|
+
tender artifacts init artifact_123 --dir ./widget --dry-run --json
|
|
239
|
+
tender artifacts init artifact_123 --dir ./widget --json
|
|
240
|
+
tender artifacts init artifact_123 --dir ./widget --force --json`;
|
|
241
|
+
}
|
|
242
|
+
function artifactsContextFetchHelp() {
|
|
243
|
+
return `Usage: tender artifacts context fetch <artifact-id> [--dir <path>] [--base-url <url>] [--token <token>] [--profile <name>] [--dry-run] [--force] [--json]
|
|
244
|
+
|
|
245
|
+
Options:
|
|
246
|
+
--dir <path> Local checkout to update. Defaults to current directory.
|
|
247
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
248
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
249
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
250
|
+
--dry-run Show create/overwrite/conflict actions without writing files.
|
|
251
|
+
--force Overwrite conflicting managed context files.
|
|
252
|
+
--json Emit stable JSON for coding agents.
|
|
253
|
+
|
|
254
|
+
Examples:
|
|
255
|
+
tender artifacts context fetch artifact_123 --dir ./widget --dry-run --json
|
|
256
|
+
tender artifacts context fetch artifact_123 --dir ./widget --force
|
|
257
|
+
tender artifacts context fetch artifact_123 --profile agent --json`;
|
|
258
|
+
}
|
|
259
|
+
function artifactsContextStatusHelp() {
|
|
260
|
+
return `Usage: tender artifacts context status <artifact-id> [--dir <path>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
261
|
+
|
|
262
|
+
Options:
|
|
263
|
+
--dir <path> Local checkout to inspect. Defaults to current directory.
|
|
264
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
265
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
266
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
267
|
+
--json Emit stable JSON for coding agents.
|
|
268
|
+
|
|
269
|
+
Examples:
|
|
270
|
+
tender artifacts context status artifact_123 --dir ./widget --json`;
|
|
271
|
+
}
|
|
272
|
+
function artifactsContextRefreshHelp() {
|
|
273
|
+
return `Usage: tender artifacts context refresh <artifact-id> [--dir <path>] [--base-url <url>] [--token <token>] [--profile <name>] [--dry-run] [--force] [--json]
|
|
274
|
+
|
|
275
|
+
Options:
|
|
276
|
+
--dir <path> Local checkout to update. Defaults to current directory.
|
|
277
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
278
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
279
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
280
|
+
--dry-run Show create/overwrite/conflict actions without writing files.
|
|
281
|
+
--force Overwrite conflicting managed context files.
|
|
282
|
+
--json Emit stable JSON for coding agents.
|
|
283
|
+
|
|
284
|
+
Examples:
|
|
285
|
+
tender artifacts context refresh artifact_123 --dir ./widget --dry-run --json
|
|
286
|
+
tender artifacts context refresh artifact_123 --dir ./widget --force`;
|
|
287
|
+
}
|
|
288
|
+
function doctorHelp() {
|
|
289
|
+
return `Usage: tender artifacts doctor [--dir <path>] [--json]
|
|
290
|
+
|
|
291
|
+
Options:
|
|
292
|
+
--dir <path> Local Tender App checkout to inspect. Defaults to current directory.
|
|
293
|
+
--json Emit stable JSON for coding agents.
|
|
294
|
+
|
|
295
|
+
Examples:
|
|
296
|
+
tender artifacts doctor --dir ./widget
|
|
297
|
+
tender artifacts doctor --dir ./widget --json`;
|
|
298
|
+
}
|
|
299
|
+
function gitRemoteHelp() {
|
|
300
|
+
return `Usage: tender artifacts git remote <artifact-id> [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--json]
|
|
301
|
+
|
|
302
|
+
Options:
|
|
303
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
304
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
305
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
306
|
+
--ttl <value> Deprecated for Tender-hosted Git remotes; accepted for compatibility.
|
|
307
|
+
--json Emit stable JSON for coding agents.
|
|
308
|
+
|
|
309
|
+
Examples:
|
|
310
|
+
tender artifacts git remote artifact_123 --ttl 7d --json
|
|
311
|
+
tender artifacts git remote artifact_123 --profile publish --ttl 7d --json
|
|
312
|
+
TENDER_API_TOKEN=tp_... tender artifacts git remote artifact_123 --base-url https://app.tenderprompt.com --json`;
|
|
313
|
+
}
|
|
314
|
+
function gitSetupHelp() {
|
|
315
|
+
return `Usage: tender artifacts git setup <artifact-id> [--dir <path>] [--remote <name>] [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--dry-run] [--json]
|
|
316
|
+
|
|
317
|
+
Options:
|
|
318
|
+
--dir <path> Local Git checkout to configure. Defaults to current directory.
|
|
319
|
+
--remote <name> Git remote name to add or update. Defaults to tender.
|
|
320
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
321
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
322
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
323
|
+
--ttl <value> Deprecated for Tender-hosted Git remotes; accepted for compatibility.
|
|
324
|
+
--dry-run Show Git commands without running them.
|
|
325
|
+
--json Emit stable JSON for coding agents.
|
|
326
|
+
|
|
327
|
+
Examples:
|
|
328
|
+
tender artifacts git setup artifact_123 --dir ./widget --json
|
|
329
|
+
tender artifacts git setup artifact_123 --dir ./widget --remote tender --ttl 7d
|
|
330
|
+
tender artifacts git setup artifact_123 --dir ./widget --dry-run --json`;
|
|
331
|
+
}
|
|
332
|
+
function gitCredentialHelp() {
|
|
333
|
+
return `Usage: tender artifacts git credential <artifact-id> [get|store|erase] [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--remote-url <url>]
|
|
334
|
+
|
|
335
|
+
This command is intended for Git's credential.helper protocol. It prints a
|
|
336
|
+
Tender API-token username/password pair only for matching get requests. Do not
|
|
337
|
+
run it manually unless you are debugging Git credential-helper behavior.
|
|
338
|
+
|
|
339
|
+
Examples:
|
|
340
|
+
tender artifacts git credential artifact_123 get
|
|
341
|
+
tender artifacts git credential artifact_123 get --remote-url https://app.tenderprompt.com/git/artifacts/artifact_123.git`;
|
|
342
|
+
}
|
|
343
|
+
function previewRebuildHelp() {
|
|
344
|
+
return `Usage: tender artifacts preview rebuild <artifact-id> [--commit <sha>] [--base-url <url>] [--token <token>] [--profile <name>] [--stream] [--json]
|
|
345
|
+
|
|
346
|
+
Options:
|
|
347
|
+
--commit <sha> Rebuild preview only if the artifact workspace is at this Git commit.
|
|
348
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
349
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
350
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
351
|
+
--stream Request lifecycle events from the API.
|
|
352
|
+
--json Emit stable JSON for coding agents.
|
|
353
|
+
|
|
354
|
+
Examples:
|
|
355
|
+
tender artifacts preview rebuild artifact_123 --commit $(git rev-parse HEAD) --stream --json
|
|
356
|
+
tender artifacts preview rebuild artifact_123 --json`;
|
|
357
|
+
}
|
|
358
|
+
function validateHelp() {
|
|
359
|
+
return `Usage: tender artifacts validate <artifact-id> [--base-url <url>] [--token <token>] [--profile <name>] [--stream] [--json]
|
|
360
|
+
|
|
361
|
+
Options:
|
|
362
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
363
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
364
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
365
|
+
--stream Request lifecycle events from the API.
|
|
366
|
+
--json Emit stable JSON for coding agents.
|
|
367
|
+
|
|
368
|
+
Examples:
|
|
369
|
+
tender artifacts validate artifact_123 --json
|
|
370
|
+
tender artifacts validate artifact_123 --stream --json`;
|
|
371
|
+
}
|
|
372
|
+
function buildHelp() {
|
|
373
|
+
return `Usage: tender artifacts build <artifact-id> [--commit <sha>] [--base-url <url>] [--token <token>] [--profile <name>] [--stream] [--json]
|
|
374
|
+
|
|
375
|
+
Options:
|
|
376
|
+
--commit <sha> Build only if the artifact workspace is at this Git commit.
|
|
377
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
378
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
379
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
380
|
+
--stream Request lifecycle events from the API.
|
|
381
|
+
--json Emit stable JSON for coding agents.
|
|
382
|
+
|
|
383
|
+
Examples:
|
|
384
|
+
tender artifacts build artifact_123 --commit $(git rev-parse HEAD) --json
|
|
385
|
+
tender artifacts build artifact_123 --stream --json`;
|
|
386
|
+
}
|
|
387
|
+
function previewStatusHelp() {
|
|
388
|
+
return `Usage: tender artifacts preview status <artifact-id> [--commit <sha> | --job <job-id>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
389
|
+
|
|
390
|
+
Options:
|
|
391
|
+
--commit <sha> Show the latest preview job for one artifact Git commit.
|
|
392
|
+
--job <job-id> Show one lifecycle job directly.
|
|
393
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
394
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
395
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
396
|
+
--json Emit stable JSON for coding agents.
|
|
397
|
+
|
|
398
|
+
Examples:
|
|
399
|
+
tender artifacts preview status artifact_123 --commit $(git rev-parse HEAD) --json
|
|
400
|
+
tender artifacts preview status artifact_123 --job job_123 --json`;
|
|
401
|
+
}
|
|
402
|
+
function previewWatchHelp() {
|
|
403
|
+
return `Usage: tender artifacts preview watch <artifact-id> [--commit <sha> | --job <job-id>] [--base-url <url>] [--token <token>] [--profile <name>] [--stream] [--json]
|
|
404
|
+
|
|
405
|
+
Options:
|
|
406
|
+
--commit <sha> Watch the latest preview job for one artifact Git commit.
|
|
407
|
+
--job <job-id> Watch one lifecycle job directly.
|
|
408
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
409
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
410
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
411
|
+
--stream Request Server-Sent Events from the API.
|
|
412
|
+
--json Emit lifecycle events as JSON lines.
|
|
413
|
+
|
|
414
|
+
Examples:
|
|
415
|
+
tender artifacts preview watch artifact_123 --commit $(git rev-parse HEAD) --stream --json
|
|
416
|
+
tender artifacts preview watch artifact_123 --job job_123 --stream --json`;
|
|
417
|
+
}
|
|
418
|
+
function publishHelp() {
|
|
419
|
+
return `Usage: tender artifacts publish <artifact-id> (--dry-run | --confirm publish) [--commit <sha>] [--message <text>] [--base-url <url>] [--token <token>] [--profile <name>] [--stream] [--json]
|
|
420
|
+
|
|
421
|
+
Options:
|
|
422
|
+
--dry-run Show the publish request without calling the API.
|
|
423
|
+
--confirm publish Required to call the production-facing publish API.
|
|
424
|
+
--commit <sha> Publish only if the artifact workspace is at this Git commit.
|
|
425
|
+
--message <text> Human audit message returned in CLI output.
|
|
426
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
427
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
428
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
429
|
+
--stream Request lifecycle events from the API.
|
|
430
|
+
--json Emit stable JSON for coding agents.
|
|
431
|
+
|
|
432
|
+
Examples:
|
|
433
|
+
tender artifacts publish artifact_123 --dry-run --json
|
|
434
|
+
tender artifacts publish artifact_123 --commit $(git rev-parse HEAD) --confirm publish --stream --json`;
|
|
435
|
+
}
|
|
436
|
+
function publishStatusHelp() {
|
|
437
|
+
return `Usage: tender artifacts publish status <artifact-id> --job <job-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
438
|
+
|
|
439
|
+
Options:
|
|
440
|
+
--job <job-id> Show one publish lifecycle job directly.
|
|
441
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
442
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
443
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
444
|
+
--json Emit stable JSON for coding agents.
|
|
445
|
+
|
|
446
|
+
Examples:
|
|
447
|
+
tender artifacts publish status artifact_123 --job job_123 --json`;
|
|
448
|
+
}
|
|
449
|
+
function publishWatchHelp() {
|
|
450
|
+
return `Usage: tender artifacts publish watch <artifact-id> --job <job-id> [--base-url <url>] [--token <token>] [--profile <name>] [--stream] [--json]
|
|
451
|
+
|
|
452
|
+
Options:
|
|
453
|
+
--job <job-id> Watch one publish lifecycle job directly.
|
|
454
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
455
|
+
--token <token> Tender API token. Defaults to TENDER_API_TOKEN or the saved profile token.
|
|
456
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
457
|
+
--stream Request Server-Sent Events from the API.
|
|
458
|
+
--json Emit lifecycle events as JSON lines.
|
|
459
|
+
|
|
460
|
+
Examples:
|
|
461
|
+
tender artifacts publish watch artifact_123 --job job_123 --stream --json`;
|
|
462
|
+
}
|
|
463
|
+
function normalizeBaseUrl(value) {
|
|
464
|
+
return (value ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
465
|
+
}
|
|
466
|
+
function readDefaultBaseUrl(env) {
|
|
467
|
+
return normalizeBaseUrl(env.TENDER_BASE_URL ?? DEFAULT_BASE_URL);
|
|
468
|
+
}
|
|
469
|
+
function readConfigPath(env) {
|
|
470
|
+
return env.TENDER_CONFIG
|
|
471
|
+
? path.resolve(env.TENDER_CONFIG)
|
|
472
|
+
: path.join(os.homedir(), ".tender", "config.json");
|
|
473
|
+
}
|
|
474
|
+
function isMissingFile(error) {
|
|
475
|
+
return (error instanceof Error &&
|
|
476
|
+
error.code === "ENOENT");
|
|
477
|
+
}
|
|
478
|
+
function normalizeStoredProfile(value) {
|
|
479
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
const record = value;
|
|
483
|
+
if (typeof record.token !== "string" || !record.token) {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
baseUrl: typeof record.baseUrl === "string"
|
|
488
|
+
? normalizeBaseUrl(record.baseUrl)
|
|
489
|
+
: DEFAULT_BASE_URL,
|
|
490
|
+
token: record.token,
|
|
491
|
+
tokenPrefix: typeof record.tokenPrefix === "string" ? record.tokenPrefix : undefined,
|
|
492
|
+
tenantId: typeof record.tenantId === "string" ? record.tenantId : undefined,
|
|
493
|
+
artifactId: typeof record.artifactId === "string" || record.artifactId === null
|
|
494
|
+
? record.artifactId
|
|
495
|
+
: undefined,
|
|
496
|
+
expiresAt: typeof record.expiresAt === "number" ? record.expiresAt : undefined,
|
|
497
|
+
permissions: Array.isArray(record.permissions)
|
|
498
|
+
? record.permissions.filter((permission) => typeof permission === "string")
|
|
499
|
+
: undefined,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function normalizeConfig(value) {
|
|
503
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
504
|
+
return {};
|
|
505
|
+
}
|
|
506
|
+
const record = value;
|
|
507
|
+
const profiles = {};
|
|
508
|
+
if (record.profiles &&
|
|
509
|
+
typeof record.profiles === "object" &&
|
|
510
|
+
!Array.isArray(record.profiles)) {
|
|
511
|
+
for (const [name, profile] of Object.entries(record.profiles)) {
|
|
512
|
+
const normalized = normalizeStoredProfile(profile);
|
|
513
|
+
if (normalized) {
|
|
514
|
+
profiles[name] = normalized;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return {
|
|
519
|
+
defaultProfile: typeof record.defaultProfile === "string" && record.defaultProfile
|
|
520
|
+
? record.defaultProfile
|
|
521
|
+
: undefined,
|
|
522
|
+
profiles: Object.keys(profiles).length > 0 ? profiles : undefined,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
async function readConfig(env) {
|
|
526
|
+
try {
|
|
527
|
+
const raw = await readFile(readConfigPath(env), "utf8");
|
|
528
|
+
return normalizeConfig(JSON.parse(raw));
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
if (isMissingFile(error)) {
|
|
532
|
+
return {};
|
|
533
|
+
}
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
async function writeConfig(env, config) {
|
|
538
|
+
const configPath = readConfigPath(env);
|
|
539
|
+
const tempPath = `${configPath}.tmp`;
|
|
540
|
+
await mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
541
|
+
await writeFile(tempPath, `${JSON.stringify(config, null, 2)}\n`, {
|
|
542
|
+
mode: 0o600,
|
|
543
|
+
});
|
|
544
|
+
await rename(tempPath, configPath);
|
|
545
|
+
}
|
|
546
|
+
async function resolveApiCredentials(input) {
|
|
547
|
+
if (input.flagToken) {
|
|
548
|
+
return {
|
|
549
|
+
baseUrl: input.baseUrl ?? readDefaultBaseUrl(input.env),
|
|
550
|
+
token: input.flagToken,
|
|
551
|
+
source: "flag",
|
|
552
|
+
profileName: null,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
if (input.env.TENDER_API_TOKEN) {
|
|
556
|
+
return {
|
|
557
|
+
baseUrl: input.baseUrl ?? readDefaultBaseUrl(input.env),
|
|
558
|
+
token: input.env.TENDER_API_TOKEN,
|
|
559
|
+
source: "env",
|
|
560
|
+
profileName: null,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
const config = await readConfig(input.env);
|
|
564
|
+
const profileName = input.profileName ?? config.defaultProfile ?? DEFAULT_PROFILE_NAME;
|
|
565
|
+
const profile = config.profiles?.[profileName];
|
|
566
|
+
if (!profile) {
|
|
567
|
+
throw new TenderCliUsageError("Tender API token is required. Run `tender auth login --device`, set TENDER_API_TOKEN, or pass --token <token>.");
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
baseUrl: input.baseUrl ?? profile.baseUrl ?? readDefaultBaseUrl(input.env),
|
|
571
|
+
token: profile.token,
|
|
572
|
+
source: "config",
|
|
573
|
+
profileName,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function parseProfileName(value, flag) {
|
|
577
|
+
if (!value || value.startsWith("-")) {
|
|
578
|
+
throw new TenderCliUsageError(`${flag} requires a profile name.`);
|
|
579
|
+
}
|
|
580
|
+
return value;
|
|
581
|
+
}
|
|
582
|
+
function parseBaseUrlFlag(args, index) {
|
|
583
|
+
const value = args[index + 1];
|
|
584
|
+
if (!value || value.startsWith("-")) {
|
|
585
|
+
throw new TenderCliUsageError("--base-url requires a URL.");
|
|
586
|
+
}
|
|
587
|
+
return normalizeBaseUrl(value);
|
|
588
|
+
}
|
|
589
|
+
function parseTokenFlag(args, index) {
|
|
590
|
+
const value = args[index + 1];
|
|
591
|
+
if (!value || value.startsWith("-")) {
|
|
592
|
+
throw new TenderCliUsageError("--token requires a token.");
|
|
593
|
+
}
|
|
594
|
+
return value;
|
|
595
|
+
}
|
|
596
|
+
function parseTtl(value) {
|
|
597
|
+
if (value !== "7d" && value !== "14d" && value !== "30d") {
|
|
598
|
+
throw new TenderCliUsageError("--ttl must be one of 7d, 14d, or 30d.");
|
|
599
|
+
}
|
|
600
|
+
return value;
|
|
601
|
+
}
|
|
602
|
+
function parseGitRemoteArgs(args) {
|
|
603
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
604
|
+
return { command: "help", topic: "artifacts git remote" };
|
|
605
|
+
}
|
|
606
|
+
const artifactId = args[0];
|
|
607
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
608
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts git remote <artifact-id> --json");
|
|
609
|
+
}
|
|
610
|
+
let baseUrl = null;
|
|
611
|
+
let flagToken = null;
|
|
612
|
+
let profileName = null;
|
|
613
|
+
let ttl = "7d";
|
|
614
|
+
let json = false;
|
|
615
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
616
|
+
const arg = args[index];
|
|
617
|
+
if (arg === "--json") {
|
|
618
|
+
json = true;
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
if (arg === "--base-url") {
|
|
622
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
623
|
+
index += 1;
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
if (arg === "--token") {
|
|
627
|
+
flagToken = parseTokenFlag(args, index);
|
|
628
|
+
index += 1;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (arg === "--profile") {
|
|
632
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
633
|
+
index += 1;
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (arg === "--ttl") {
|
|
637
|
+
ttl = parseTtl(args[index + 1]);
|
|
638
|
+
index += 1;
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
throw new TenderCliUsageError(`Unknown git remote option: ${arg}`);
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
command: "artifacts git remote",
|
|
645
|
+
artifactId,
|
|
646
|
+
baseUrl,
|
|
647
|
+
flagToken,
|
|
648
|
+
profileName,
|
|
649
|
+
ttl,
|
|
650
|
+
json,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function parseRemoteName(value) {
|
|
654
|
+
if (!value || value.startsWith("-")) {
|
|
655
|
+
throw new TenderCliUsageError("--remote requires a remote name.");
|
|
656
|
+
}
|
|
657
|
+
if (!/^[A-Za-z0-9._-]+$/.test(value)) {
|
|
658
|
+
throw new TenderCliUsageError("--remote must contain only letters, numbers, dots, underscores, or dashes.");
|
|
659
|
+
}
|
|
660
|
+
return value;
|
|
661
|
+
}
|
|
662
|
+
function parseGitSetupArgs(args) {
|
|
663
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
664
|
+
return { command: "help", topic: "artifacts git setup" };
|
|
665
|
+
}
|
|
666
|
+
const artifactId = args[0];
|
|
667
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
668
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts git setup <artifact-id> --dir ./widget --json");
|
|
669
|
+
}
|
|
670
|
+
let dir = ".";
|
|
671
|
+
let remoteName = "tender";
|
|
672
|
+
let baseUrl = null;
|
|
673
|
+
let flagToken = null;
|
|
674
|
+
let profileName = null;
|
|
675
|
+
let ttl = "7d";
|
|
676
|
+
let dryRun = false;
|
|
677
|
+
let json = false;
|
|
678
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
679
|
+
const arg = args[index];
|
|
680
|
+
if (arg === "--json") {
|
|
681
|
+
json = true;
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
if (arg === "--dry-run") {
|
|
685
|
+
dryRun = true;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (arg === "--dir") {
|
|
689
|
+
const value = args[index + 1];
|
|
690
|
+
if (!value || value.startsWith("-")) {
|
|
691
|
+
throw new TenderCliUsageError("--dir requires a path.");
|
|
692
|
+
}
|
|
693
|
+
dir = value;
|
|
694
|
+
index += 1;
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
if (arg === "--remote") {
|
|
698
|
+
remoteName = parseRemoteName(args[index + 1]);
|
|
699
|
+
index += 1;
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (arg === "--base-url") {
|
|
703
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
704
|
+
index += 1;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (arg === "--token") {
|
|
708
|
+
flagToken = parseTokenFlag(args, index);
|
|
709
|
+
index += 1;
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (arg === "--profile") {
|
|
713
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
714
|
+
index += 1;
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
if (arg === "--ttl") {
|
|
718
|
+
ttl = parseTtl(args[index + 1]);
|
|
719
|
+
index += 1;
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
throw new TenderCliUsageError(`Unknown git setup option: ${arg}`);
|
|
723
|
+
}
|
|
724
|
+
return {
|
|
725
|
+
command: "artifacts git setup",
|
|
726
|
+
artifactId,
|
|
727
|
+
dir,
|
|
728
|
+
remoteName,
|
|
729
|
+
baseUrl,
|
|
730
|
+
flagToken,
|
|
731
|
+
profileName,
|
|
732
|
+
ttl,
|
|
733
|
+
dryRun,
|
|
734
|
+
json,
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
function parseGitCredentialOperation(value) {
|
|
738
|
+
if (value === undefined || value.startsWith("-")) {
|
|
739
|
+
return { operation: "get", consumed: false };
|
|
740
|
+
}
|
|
741
|
+
if (value === "get") {
|
|
742
|
+
return { operation: "get", consumed: true };
|
|
743
|
+
}
|
|
744
|
+
if (value === "store") {
|
|
745
|
+
return { operation: "store", consumed: true };
|
|
746
|
+
}
|
|
747
|
+
if (value === "erase") {
|
|
748
|
+
return { operation: "erase", consumed: true };
|
|
749
|
+
}
|
|
750
|
+
throw new TenderCliUsageError("credential operation must be get, store, or erase.");
|
|
751
|
+
}
|
|
752
|
+
function isGitCredentialOperation(value) {
|
|
753
|
+
return value === "get" || value === "store" || value === "erase";
|
|
754
|
+
}
|
|
755
|
+
function parseGitCredentialArgs(args) {
|
|
756
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
757
|
+
return { command: "help", topic: "artifacts git credential" };
|
|
758
|
+
}
|
|
759
|
+
const artifactId = args[0];
|
|
760
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
761
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts git credential <artifact-id> get");
|
|
762
|
+
}
|
|
763
|
+
const parsedOperation = parseGitCredentialOperation(args[1]);
|
|
764
|
+
let operation = parsedOperation.operation;
|
|
765
|
+
let operationConsumed = parsedOperation.consumed;
|
|
766
|
+
let baseUrl = null;
|
|
767
|
+
let flagToken = null;
|
|
768
|
+
let profileName = null;
|
|
769
|
+
let ttl = "7d";
|
|
770
|
+
let remoteUrl = null;
|
|
771
|
+
let json = false;
|
|
772
|
+
for (let index = parsedOperation.consumed ? 2 : 1; index < args.length; index += 1) {
|
|
773
|
+
const arg = args[index];
|
|
774
|
+
if (isGitCredentialOperation(arg)) {
|
|
775
|
+
if (operationConsumed) {
|
|
776
|
+
throw new TenderCliUsageError("credential operation can only be provided once.");
|
|
777
|
+
}
|
|
778
|
+
operation = arg;
|
|
779
|
+
operationConsumed = true;
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
if (arg === "--json") {
|
|
783
|
+
json = true;
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
if (arg === "--base-url") {
|
|
787
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
788
|
+
index += 1;
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (arg === "--token") {
|
|
792
|
+
flagToken = parseTokenFlag(args, index);
|
|
793
|
+
index += 1;
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
if (arg === "--profile") {
|
|
797
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
798
|
+
index += 1;
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
if (arg === "--ttl") {
|
|
802
|
+
ttl = parseTtl(args[index + 1]);
|
|
803
|
+
index += 1;
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
if (arg === "--remote-url") {
|
|
807
|
+
const value = args[index + 1];
|
|
808
|
+
if (!value || value.startsWith("-")) {
|
|
809
|
+
throw new TenderCliUsageError("--remote-url requires a URL.");
|
|
810
|
+
}
|
|
811
|
+
remoteUrl = value;
|
|
812
|
+
index += 1;
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
throw new TenderCliUsageError(`Unknown git credential option: ${arg}`);
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
command: "artifacts git credential",
|
|
819
|
+
artifactId,
|
|
820
|
+
operation,
|
|
821
|
+
baseUrl,
|
|
822
|
+
flagToken,
|
|
823
|
+
profileName,
|
|
824
|
+
ttl,
|
|
825
|
+
remoteUrl,
|
|
826
|
+
json,
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
function parseValidateArgs(args) {
|
|
830
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
831
|
+
return { command: "help", topic: "artifacts validate" };
|
|
832
|
+
}
|
|
833
|
+
const artifactId = args[0];
|
|
834
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
835
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts validate <artifact-id> --json");
|
|
836
|
+
}
|
|
837
|
+
let baseUrl = null;
|
|
838
|
+
let flagToken = null;
|
|
839
|
+
let profileName = null;
|
|
840
|
+
let stream = false;
|
|
841
|
+
let json = false;
|
|
842
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
843
|
+
const arg = args[index];
|
|
844
|
+
if (arg === "--json") {
|
|
845
|
+
json = true;
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (arg === "--stream") {
|
|
849
|
+
stream = true;
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (arg === "--base-url") {
|
|
853
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
854
|
+
index += 1;
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
if (arg === "--token") {
|
|
858
|
+
flagToken = parseTokenFlag(args, index);
|
|
859
|
+
index += 1;
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
if (arg === "--profile") {
|
|
863
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
864
|
+
index += 1;
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
throw new TenderCliUsageError(`Unknown validate option: ${arg}`);
|
|
868
|
+
}
|
|
869
|
+
return {
|
|
870
|
+
command: "artifacts validate",
|
|
871
|
+
artifactId,
|
|
872
|
+
baseUrl,
|
|
873
|
+
flagToken,
|
|
874
|
+
profileName,
|
|
875
|
+
stream,
|
|
876
|
+
json,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function parseBuildArgs(args) {
|
|
880
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
881
|
+
return { command: "help", topic: "artifacts build" };
|
|
882
|
+
}
|
|
883
|
+
const artifactId = args[0];
|
|
884
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
885
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts build <artifact-id> --json");
|
|
886
|
+
}
|
|
887
|
+
let baseUrl = null;
|
|
888
|
+
let flagToken = null;
|
|
889
|
+
let profileName = null;
|
|
890
|
+
let commitSha = null;
|
|
891
|
+
let stream = false;
|
|
892
|
+
let json = false;
|
|
893
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
894
|
+
const arg = args[index];
|
|
895
|
+
if (arg === "--json") {
|
|
896
|
+
json = true;
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
if (arg === "--stream") {
|
|
900
|
+
stream = true;
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
if (arg === "--commit") {
|
|
904
|
+
const value = args[index + 1];
|
|
905
|
+
if (!value || value.startsWith("-")) {
|
|
906
|
+
throw new TenderCliUsageError("--commit requires a Git commit SHA.");
|
|
907
|
+
}
|
|
908
|
+
commitSha = value;
|
|
909
|
+
index += 1;
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
if (arg === "--base-url") {
|
|
913
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
914
|
+
index += 1;
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
if (arg === "--token") {
|
|
918
|
+
flagToken = parseTokenFlag(args, index);
|
|
919
|
+
index += 1;
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
if (arg === "--profile") {
|
|
923
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
924
|
+
index += 1;
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
throw new TenderCliUsageError(`Unknown build option: ${arg}`);
|
|
928
|
+
}
|
|
929
|
+
return {
|
|
930
|
+
command: "artifacts build",
|
|
931
|
+
artifactId,
|
|
932
|
+
commitSha,
|
|
933
|
+
baseUrl,
|
|
934
|
+
flagToken,
|
|
935
|
+
profileName,
|
|
936
|
+
stream,
|
|
937
|
+
json,
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function parsePreviewRebuildArgs(args) {
|
|
941
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
942
|
+
return { command: "help", topic: "artifacts preview rebuild" };
|
|
943
|
+
}
|
|
944
|
+
const artifactId = args[0];
|
|
945
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
946
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts preview rebuild <artifact-id> --stream --json");
|
|
947
|
+
}
|
|
948
|
+
let baseUrl = null;
|
|
949
|
+
let flagToken = null;
|
|
950
|
+
let profileName = null;
|
|
951
|
+
let commitSha = null;
|
|
952
|
+
let stream = false;
|
|
953
|
+
let json = false;
|
|
954
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
955
|
+
const arg = args[index];
|
|
956
|
+
if (arg === "--json") {
|
|
957
|
+
json = true;
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
if (arg === "--stream") {
|
|
961
|
+
stream = true;
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
if (arg === "--commit") {
|
|
965
|
+
const value = args[index + 1];
|
|
966
|
+
if (!value || value.startsWith("-")) {
|
|
967
|
+
throw new TenderCliUsageError("--commit requires a Git commit SHA.");
|
|
968
|
+
}
|
|
969
|
+
commitSha = value;
|
|
970
|
+
index += 1;
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
if (arg === "--base-url") {
|
|
974
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
975
|
+
index += 1;
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (arg === "--token") {
|
|
979
|
+
flagToken = parseTokenFlag(args, index);
|
|
980
|
+
index += 1;
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
if (arg === "--profile") {
|
|
984
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
985
|
+
index += 1;
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
throw new TenderCliUsageError(`Unknown preview rebuild option: ${arg}`);
|
|
989
|
+
}
|
|
990
|
+
return {
|
|
991
|
+
command: "artifacts preview rebuild",
|
|
992
|
+
artifactId,
|
|
993
|
+
commitSha,
|
|
994
|
+
baseUrl,
|
|
995
|
+
flagToken,
|
|
996
|
+
profileName,
|
|
997
|
+
stream,
|
|
998
|
+
json,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function readPreviewLookupOptions(input) {
|
|
1002
|
+
let baseUrl = null;
|
|
1003
|
+
let flagToken = null;
|
|
1004
|
+
let profileName = null;
|
|
1005
|
+
let commitSha = null;
|
|
1006
|
+
let jobId = null;
|
|
1007
|
+
let stream = false;
|
|
1008
|
+
let json = false;
|
|
1009
|
+
for (let index = input.startIndex; index < input.args.length; index += 1) {
|
|
1010
|
+
const arg = input.args[index];
|
|
1011
|
+
if (arg === "--json") {
|
|
1012
|
+
json = true;
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
if (arg === "--stream") {
|
|
1016
|
+
stream = true;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
if (arg === "--commit") {
|
|
1020
|
+
const value = input.args[index + 1];
|
|
1021
|
+
if (!value || value.startsWith("-")) {
|
|
1022
|
+
throw new TenderCliUsageError("--commit requires a commit SHA.");
|
|
1023
|
+
}
|
|
1024
|
+
commitSha = value;
|
|
1025
|
+
index += 1;
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
if (arg === "--job") {
|
|
1029
|
+
const value = input.args[index + 1];
|
|
1030
|
+
if (!value || value.startsWith("-")) {
|
|
1031
|
+
throw new TenderCliUsageError("--job requires a lifecycle job id.");
|
|
1032
|
+
}
|
|
1033
|
+
jobId = value;
|
|
1034
|
+
index += 1;
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
if (arg === "--base-url") {
|
|
1038
|
+
baseUrl = parseBaseUrlFlag(input.args, index);
|
|
1039
|
+
index += 1;
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
if (arg === "--token") {
|
|
1043
|
+
flagToken = parseTokenFlag(input.args, index);
|
|
1044
|
+
index += 1;
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
if (arg === "--profile") {
|
|
1048
|
+
profileName = parseProfileName(input.args[index + 1], "--profile");
|
|
1049
|
+
index += 1;
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
throw new TenderCliUsageError(`Unknown ${input.commandName} option: ${arg}`);
|
|
1053
|
+
}
|
|
1054
|
+
if (commitSha && jobId) {
|
|
1055
|
+
throw new TenderCliUsageError("--commit and --job cannot be used together.");
|
|
1056
|
+
}
|
|
1057
|
+
return {
|
|
1058
|
+
baseUrl,
|
|
1059
|
+
flagToken,
|
|
1060
|
+
profileName,
|
|
1061
|
+
commitSha,
|
|
1062
|
+
jobId,
|
|
1063
|
+
stream,
|
|
1064
|
+
json,
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function parsePreviewStatusArgs(args) {
|
|
1068
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1069
|
+
return { command: "help", topic: "artifacts preview status" };
|
|
1070
|
+
}
|
|
1071
|
+
const artifactId = args[0];
|
|
1072
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
1073
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts preview status <artifact-id> --commit <sha> --json");
|
|
1074
|
+
}
|
|
1075
|
+
const options = readPreviewLookupOptions({
|
|
1076
|
+
args,
|
|
1077
|
+
startIndex: 1,
|
|
1078
|
+
commandName: "preview status",
|
|
1079
|
+
});
|
|
1080
|
+
if (options.stream) {
|
|
1081
|
+
throw new TenderCliUsageError("preview status does not support --stream. Use tender artifacts preview watch.");
|
|
1082
|
+
}
|
|
1083
|
+
return {
|
|
1084
|
+
command: "artifacts preview status",
|
|
1085
|
+
artifactId,
|
|
1086
|
+
commitSha: options.commitSha,
|
|
1087
|
+
jobId: options.jobId,
|
|
1088
|
+
baseUrl: options.baseUrl,
|
|
1089
|
+
flagToken: options.flagToken,
|
|
1090
|
+
profileName: options.profileName,
|
|
1091
|
+
json: options.json,
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
function parsePreviewWatchArgs(args) {
|
|
1095
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1096
|
+
return { command: "help", topic: "artifacts preview watch" };
|
|
1097
|
+
}
|
|
1098
|
+
const artifactId = args[0];
|
|
1099
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
1100
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts preview watch <artifact-id> --job <job-id> --stream --json");
|
|
1101
|
+
}
|
|
1102
|
+
const options = readPreviewLookupOptions({
|
|
1103
|
+
args,
|
|
1104
|
+
startIndex: 1,
|
|
1105
|
+
commandName: "preview watch",
|
|
1106
|
+
});
|
|
1107
|
+
return {
|
|
1108
|
+
command: "artifacts preview watch",
|
|
1109
|
+
artifactId,
|
|
1110
|
+
commitSha: options.commitSha,
|
|
1111
|
+
jobId: options.jobId,
|
|
1112
|
+
baseUrl: options.baseUrl,
|
|
1113
|
+
flagToken: options.flagToken,
|
|
1114
|
+
profileName: options.profileName,
|
|
1115
|
+
stream: options.stream,
|
|
1116
|
+
json: options.json,
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
function parsePublishArgs(args) {
|
|
1120
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1121
|
+
return { command: "help", topic: "artifacts publish" };
|
|
1122
|
+
}
|
|
1123
|
+
const artifactId = args[0];
|
|
1124
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
1125
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts publish <artifact-id> --dry-run --json");
|
|
1126
|
+
}
|
|
1127
|
+
let baseUrl = null;
|
|
1128
|
+
let flagToken = null;
|
|
1129
|
+
let profileName = null;
|
|
1130
|
+
let confirm = null;
|
|
1131
|
+
let dryRun = false;
|
|
1132
|
+
let stream = false;
|
|
1133
|
+
let commitSha = null;
|
|
1134
|
+
let message = null;
|
|
1135
|
+
let json = false;
|
|
1136
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
1137
|
+
const arg = args[index];
|
|
1138
|
+
if (arg === "--json") {
|
|
1139
|
+
json = true;
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
if (arg === "--dry-run") {
|
|
1143
|
+
dryRun = true;
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
if (arg === "--stream") {
|
|
1147
|
+
stream = true;
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
if (arg === "--confirm") {
|
|
1151
|
+
const value = args[index + 1];
|
|
1152
|
+
if (!value || value.startsWith("-")) {
|
|
1153
|
+
throw new TenderCliUsageError("--confirm requires the value publish.");
|
|
1154
|
+
}
|
|
1155
|
+
confirm = value;
|
|
1156
|
+
index += 1;
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
if (arg === "--message") {
|
|
1160
|
+
const value = args[index + 1];
|
|
1161
|
+
if (!value || value.startsWith("-")) {
|
|
1162
|
+
throw new TenderCliUsageError("--message requires text.");
|
|
1163
|
+
}
|
|
1164
|
+
message = value;
|
|
1165
|
+
index += 1;
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
if (arg === "--commit") {
|
|
1169
|
+
const value = args[index + 1];
|
|
1170
|
+
if (!value || value.startsWith("-")) {
|
|
1171
|
+
throw new TenderCliUsageError("--commit requires a commit SHA.");
|
|
1172
|
+
}
|
|
1173
|
+
commitSha = value;
|
|
1174
|
+
index += 1;
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
if (arg === "--base-url") {
|
|
1178
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
1179
|
+
index += 1;
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
if (arg === "--token") {
|
|
1183
|
+
flagToken = parseTokenFlag(args, index);
|
|
1184
|
+
index += 1;
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
if (arg === "--profile") {
|
|
1188
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
1189
|
+
index += 1;
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
throw new TenderCliUsageError(`Unknown publish option: ${arg}`);
|
|
1193
|
+
}
|
|
1194
|
+
if (!dryRun && confirm !== "publish") {
|
|
1195
|
+
throw new TenderCliUsageError("publish requires --dry-run or --confirm publish.");
|
|
1196
|
+
}
|
|
1197
|
+
return {
|
|
1198
|
+
command: "artifacts publish",
|
|
1199
|
+
artifactId,
|
|
1200
|
+
commitSha,
|
|
1201
|
+
baseUrl,
|
|
1202
|
+
flagToken,
|
|
1203
|
+
profileName,
|
|
1204
|
+
confirm,
|
|
1205
|
+
dryRun,
|
|
1206
|
+
stream,
|
|
1207
|
+
message,
|
|
1208
|
+
json,
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
function readPublishJobLookupOptions(input) {
|
|
1212
|
+
let baseUrl = null;
|
|
1213
|
+
let flagToken = null;
|
|
1214
|
+
let profileName = null;
|
|
1215
|
+
let jobId = null;
|
|
1216
|
+
let stream = false;
|
|
1217
|
+
let json = false;
|
|
1218
|
+
for (let index = input.startIndex; index < input.args.length; index += 1) {
|
|
1219
|
+
const arg = input.args[index];
|
|
1220
|
+
if (arg === "--json") {
|
|
1221
|
+
json = true;
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
if (arg === "--stream") {
|
|
1225
|
+
stream = true;
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
if (arg === "--job") {
|
|
1229
|
+
const value = input.args[index + 1];
|
|
1230
|
+
if (!value || value.startsWith("-")) {
|
|
1231
|
+
throw new TenderCliUsageError("--job requires a lifecycle job id.");
|
|
1232
|
+
}
|
|
1233
|
+
jobId = value;
|
|
1234
|
+
index += 1;
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
if (arg === "--base-url") {
|
|
1238
|
+
baseUrl = parseBaseUrlFlag(input.args, index);
|
|
1239
|
+
index += 1;
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
if (arg === "--token") {
|
|
1243
|
+
flagToken = parseTokenFlag(input.args, index);
|
|
1244
|
+
index += 1;
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
if (arg === "--profile") {
|
|
1248
|
+
profileName = parseProfileName(input.args[index + 1], "--profile");
|
|
1249
|
+
index += 1;
|
|
1250
|
+
continue;
|
|
1251
|
+
}
|
|
1252
|
+
throw new TenderCliUsageError(`Unknown ${input.commandName} option: ${arg}`);
|
|
1253
|
+
}
|
|
1254
|
+
if (!jobId) {
|
|
1255
|
+
throw new TenderCliUsageError("--job is required for publish job polling.");
|
|
1256
|
+
}
|
|
1257
|
+
return {
|
|
1258
|
+
baseUrl,
|
|
1259
|
+
flagToken,
|
|
1260
|
+
profileName,
|
|
1261
|
+
jobId,
|
|
1262
|
+
stream,
|
|
1263
|
+
json,
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
function parsePublishStatusArgs(args) {
|
|
1267
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1268
|
+
return { command: "help", topic: "artifacts publish status" };
|
|
1269
|
+
}
|
|
1270
|
+
const artifactId = args[0];
|
|
1271
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
1272
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts publish status <artifact-id> --job <job-id> --json");
|
|
1273
|
+
}
|
|
1274
|
+
const options = readPublishJobLookupOptions({
|
|
1275
|
+
args,
|
|
1276
|
+
startIndex: 1,
|
|
1277
|
+
commandName: "publish status",
|
|
1278
|
+
});
|
|
1279
|
+
if (options.stream) {
|
|
1280
|
+
throw new TenderCliUsageError("publish status does not support --stream. Use tender artifacts publish watch.");
|
|
1281
|
+
}
|
|
1282
|
+
return {
|
|
1283
|
+
command: "artifacts publish status",
|
|
1284
|
+
artifactId,
|
|
1285
|
+
jobId: options.jobId,
|
|
1286
|
+
baseUrl: options.baseUrl,
|
|
1287
|
+
flagToken: options.flagToken,
|
|
1288
|
+
profileName: options.profileName,
|
|
1289
|
+
json: options.json,
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
function parsePublishWatchArgs(args) {
|
|
1293
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1294
|
+
return { command: "help", topic: "artifacts publish watch" };
|
|
1295
|
+
}
|
|
1296
|
+
const artifactId = args[0];
|
|
1297
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
1298
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts publish watch <artifact-id> --job <job-id> --stream --json");
|
|
1299
|
+
}
|
|
1300
|
+
const options = readPublishJobLookupOptions({
|
|
1301
|
+
args,
|
|
1302
|
+
startIndex: 1,
|
|
1303
|
+
commandName: "publish watch",
|
|
1304
|
+
});
|
|
1305
|
+
return {
|
|
1306
|
+
command: "artifacts publish watch",
|
|
1307
|
+
artifactId,
|
|
1308
|
+
jobId: options.jobId,
|
|
1309
|
+
baseUrl: options.baseUrl,
|
|
1310
|
+
flagToken: options.flagToken,
|
|
1311
|
+
profileName: options.profileName,
|
|
1312
|
+
stream: options.stream,
|
|
1313
|
+
json: options.json,
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
function parseArtifactsListArgs(args) {
|
|
1317
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1318
|
+
return { command: "help", topic: "artifacts list" };
|
|
1319
|
+
}
|
|
1320
|
+
let baseUrl = null;
|
|
1321
|
+
let flagToken = null;
|
|
1322
|
+
let profileName = null;
|
|
1323
|
+
let json = false;
|
|
1324
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1325
|
+
const arg = args[index];
|
|
1326
|
+
if (arg === "--json") {
|
|
1327
|
+
json = true;
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
if (arg === "--base-url") {
|
|
1331
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
1332
|
+
index += 1;
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
if (arg === "--token") {
|
|
1336
|
+
flagToken = parseTokenFlag(args, index);
|
|
1337
|
+
index += 1;
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
if (arg === "--profile") {
|
|
1341
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
1342
|
+
index += 1;
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
throw new TenderCliUsageError(`Unknown artifacts list option: ${arg}`);
|
|
1346
|
+
}
|
|
1347
|
+
return {
|
|
1348
|
+
command: "artifacts list",
|
|
1349
|
+
baseUrl,
|
|
1350
|
+
flagToken,
|
|
1351
|
+
profileName,
|
|
1352
|
+
json,
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
function parseArtifactsCreateArgs(args) {
|
|
1356
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1357
|
+
return { command: "help", topic: "artifacts create" };
|
|
1358
|
+
}
|
|
1359
|
+
let name = "";
|
|
1360
|
+
let targetPackId = null;
|
|
1361
|
+
let targetPackVersion = null;
|
|
1362
|
+
let baseUrl = null;
|
|
1363
|
+
let flagToken = null;
|
|
1364
|
+
let profileName = null;
|
|
1365
|
+
let json = false;
|
|
1366
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1367
|
+
const arg = args[index];
|
|
1368
|
+
if (arg === "--json") {
|
|
1369
|
+
json = true;
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
if (arg === "--name") {
|
|
1373
|
+
const value = args[index + 1];
|
|
1374
|
+
if (!value || value.startsWith("-")) {
|
|
1375
|
+
throw new TenderCliUsageError("--name requires an artifact name.");
|
|
1376
|
+
}
|
|
1377
|
+
name = value;
|
|
1378
|
+
index += 1;
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
if (arg === "--target-pack") {
|
|
1382
|
+
const value = args[index + 1];
|
|
1383
|
+
if (!value || value.startsWith("-")) {
|
|
1384
|
+
throw new TenderCliUsageError("--target-pack requires a target pack id.");
|
|
1385
|
+
}
|
|
1386
|
+
targetPackId = value;
|
|
1387
|
+
index += 1;
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
if (arg === "--target-pack-version") {
|
|
1391
|
+
const value = args[index + 1];
|
|
1392
|
+
if (!value || value.startsWith("-")) {
|
|
1393
|
+
throw new TenderCliUsageError("--target-pack-version requires a version.");
|
|
1394
|
+
}
|
|
1395
|
+
targetPackVersion = value;
|
|
1396
|
+
index += 1;
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1399
|
+
if (arg === "--base-url") {
|
|
1400
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
1401
|
+
index += 1;
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
if (arg === "--token") {
|
|
1405
|
+
flagToken = parseTokenFlag(args, index);
|
|
1406
|
+
index += 1;
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
if (arg === "--profile") {
|
|
1410
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
1411
|
+
index += 1;
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
throw new TenderCliUsageError(`Unknown artifacts create option: ${arg}`);
|
|
1415
|
+
}
|
|
1416
|
+
if (!name.trim()) {
|
|
1417
|
+
throw new TenderCliUsageError("--name is required. Example: tender artifacts create --name \"Exit Intent Widget\" --json");
|
|
1418
|
+
}
|
|
1419
|
+
return {
|
|
1420
|
+
command: "artifacts create",
|
|
1421
|
+
name: name.trim(),
|
|
1422
|
+
targetPackId,
|
|
1423
|
+
targetPackVersion,
|
|
1424
|
+
baseUrl,
|
|
1425
|
+
flagToken,
|
|
1426
|
+
profileName,
|
|
1427
|
+
json,
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
function parseArtifactsUpdateArgs(args) {
|
|
1431
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1432
|
+
return { command: "help", topic: "artifacts update" };
|
|
1433
|
+
}
|
|
1434
|
+
const artifactId = args[0];
|
|
1435
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
1436
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts update <artifact-id> --name \"Sale Widget\" --json");
|
|
1437
|
+
}
|
|
1438
|
+
let name = "";
|
|
1439
|
+
let baseUrl = null;
|
|
1440
|
+
let flagToken = null;
|
|
1441
|
+
let profileName = null;
|
|
1442
|
+
let json = false;
|
|
1443
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
1444
|
+
const arg = args[index];
|
|
1445
|
+
if (arg === "--json") {
|
|
1446
|
+
json = true;
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
if (arg === "--name") {
|
|
1450
|
+
const value = args[index + 1];
|
|
1451
|
+
if (!value || value.startsWith("-")) {
|
|
1452
|
+
throw new TenderCliUsageError("--name requires an artifact name.");
|
|
1453
|
+
}
|
|
1454
|
+
name = value;
|
|
1455
|
+
index += 1;
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
if (arg === "--base-url") {
|
|
1459
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
1460
|
+
index += 1;
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
if (arg === "--token") {
|
|
1464
|
+
flagToken = parseTokenFlag(args, index);
|
|
1465
|
+
index += 1;
|
|
1466
|
+
continue;
|
|
1467
|
+
}
|
|
1468
|
+
if (arg === "--profile") {
|
|
1469
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
1470
|
+
index += 1;
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
throw new TenderCliUsageError(`Unknown artifacts update option: ${arg}`);
|
|
1474
|
+
}
|
|
1475
|
+
if (!name.trim()) {
|
|
1476
|
+
throw new TenderCliUsageError("--name is required. Example: tender artifacts update artifact_123 --name \"Sale Widget\" --json");
|
|
1477
|
+
}
|
|
1478
|
+
return {
|
|
1479
|
+
command: "artifacts update",
|
|
1480
|
+
artifactId,
|
|
1481
|
+
name: name.trim(),
|
|
1482
|
+
baseUrl,
|
|
1483
|
+
flagToken,
|
|
1484
|
+
profileName,
|
|
1485
|
+
json,
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
function parseArtifactsInitArgs(args) {
|
|
1489
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1490
|
+
return { command: "help", topic: "artifacts init" };
|
|
1491
|
+
}
|
|
1492
|
+
const artifactId = args[0];
|
|
1493
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
1494
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts init <artifact-id> --dir ./widget --json");
|
|
1495
|
+
}
|
|
1496
|
+
let dir = ".";
|
|
1497
|
+
let remoteName = "tender";
|
|
1498
|
+
let baseUrl = null;
|
|
1499
|
+
let flagToken = null;
|
|
1500
|
+
let profileName = null;
|
|
1501
|
+
let ttl = "7d";
|
|
1502
|
+
let dryRun = false;
|
|
1503
|
+
let force = false;
|
|
1504
|
+
let json = false;
|
|
1505
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
1506
|
+
const arg = args[index];
|
|
1507
|
+
if (arg === "--json") {
|
|
1508
|
+
json = true;
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
if (arg === "--dry-run") {
|
|
1512
|
+
dryRun = true;
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
if (arg === "--force") {
|
|
1516
|
+
force = true;
|
|
1517
|
+
continue;
|
|
1518
|
+
}
|
|
1519
|
+
if (arg === "--dir") {
|
|
1520
|
+
const value = args[index + 1];
|
|
1521
|
+
if (!value || value.startsWith("-")) {
|
|
1522
|
+
throw new TenderCliUsageError("--dir requires a path.");
|
|
1523
|
+
}
|
|
1524
|
+
dir = value;
|
|
1525
|
+
index += 1;
|
|
1526
|
+
continue;
|
|
1527
|
+
}
|
|
1528
|
+
if (arg === "--remote") {
|
|
1529
|
+
remoteName = parseRemoteName(args[index + 1]);
|
|
1530
|
+
index += 1;
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1533
|
+
if (arg === "--base-url") {
|
|
1534
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
1535
|
+
index += 1;
|
|
1536
|
+
continue;
|
|
1537
|
+
}
|
|
1538
|
+
if (arg === "--token") {
|
|
1539
|
+
flagToken = parseTokenFlag(args, index);
|
|
1540
|
+
index += 1;
|
|
1541
|
+
continue;
|
|
1542
|
+
}
|
|
1543
|
+
if (arg === "--profile") {
|
|
1544
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
1545
|
+
index += 1;
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
if (arg === "--ttl") {
|
|
1549
|
+
ttl = parseTtl(args[index + 1]);
|
|
1550
|
+
index += 1;
|
|
1551
|
+
continue;
|
|
1552
|
+
}
|
|
1553
|
+
throw new TenderCliUsageError(`Unknown artifacts init option: ${arg}`);
|
|
1554
|
+
}
|
|
1555
|
+
return {
|
|
1556
|
+
command: "artifacts init",
|
|
1557
|
+
artifactId,
|
|
1558
|
+
dir,
|
|
1559
|
+
remoteName,
|
|
1560
|
+
baseUrl,
|
|
1561
|
+
flagToken,
|
|
1562
|
+
profileName,
|
|
1563
|
+
ttl,
|
|
1564
|
+
dryRun,
|
|
1565
|
+
force,
|
|
1566
|
+
json,
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
function parseContextFetchArgs(args) {
|
|
1570
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1571
|
+
return { command: "help", topic: "artifacts context fetch" };
|
|
1572
|
+
}
|
|
1573
|
+
const artifactId = args[0];
|
|
1574
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
1575
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts context fetch <artifact-id> --dir ./widget --json");
|
|
1576
|
+
}
|
|
1577
|
+
let dir = ".";
|
|
1578
|
+
let baseUrl = null;
|
|
1579
|
+
let flagToken = null;
|
|
1580
|
+
let profileName = null;
|
|
1581
|
+
let dryRun = false;
|
|
1582
|
+
let force = false;
|
|
1583
|
+
let json = false;
|
|
1584
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
1585
|
+
const arg = args[index];
|
|
1586
|
+
if (arg === "--json") {
|
|
1587
|
+
json = true;
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
if (arg === "--dry-run") {
|
|
1591
|
+
dryRun = true;
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
if (arg === "--force") {
|
|
1595
|
+
force = true;
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
if (arg === "--dir") {
|
|
1599
|
+
const value = args[index + 1];
|
|
1600
|
+
if (!value || value.startsWith("-")) {
|
|
1601
|
+
throw new TenderCliUsageError("--dir requires a path.");
|
|
1602
|
+
}
|
|
1603
|
+
dir = value;
|
|
1604
|
+
index += 1;
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1607
|
+
if (arg === "--base-url") {
|
|
1608
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
1609
|
+
index += 1;
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
if (arg === "--token") {
|
|
1613
|
+
flagToken = parseTokenFlag(args, index);
|
|
1614
|
+
index += 1;
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
if (arg === "--profile") {
|
|
1618
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
1619
|
+
index += 1;
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
throw new TenderCliUsageError(`Unknown context fetch option: ${arg}`);
|
|
1623
|
+
}
|
|
1624
|
+
return {
|
|
1625
|
+
command: "artifacts context fetch",
|
|
1626
|
+
artifactId,
|
|
1627
|
+
dir,
|
|
1628
|
+
baseUrl,
|
|
1629
|
+
flagToken,
|
|
1630
|
+
profileName,
|
|
1631
|
+
dryRun,
|
|
1632
|
+
force,
|
|
1633
|
+
json,
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function parseContextStatusArgs(args) {
|
|
1637
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1638
|
+
return { command: "help", topic: "artifacts context status" };
|
|
1639
|
+
}
|
|
1640
|
+
const artifactId = args[0];
|
|
1641
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
1642
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts context status <artifact-id> --dir ./widget --json");
|
|
1643
|
+
}
|
|
1644
|
+
let dir = ".";
|
|
1645
|
+
let baseUrl = null;
|
|
1646
|
+
let flagToken = null;
|
|
1647
|
+
let profileName = null;
|
|
1648
|
+
let json = false;
|
|
1649
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
1650
|
+
const arg = args[index];
|
|
1651
|
+
if (arg === "--json") {
|
|
1652
|
+
json = true;
|
|
1653
|
+
continue;
|
|
1654
|
+
}
|
|
1655
|
+
if (arg === "--dir") {
|
|
1656
|
+
const value = args[index + 1];
|
|
1657
|
+
if (!value || value.startsWith("-")) {
|
|
1658
|
+
throw new TenderCliUsageError("--dir requires a path.");
|
|
1659
|
+
}
|
|
1660
|
+
dir = value;
|
|
1661
|
+
index += 1;
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
if (arg === "--base-url") {
|
|
1665
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
1666
|
+
index += 1;
|
|
1667
|
+
continue;
|
|
1668
|
+
}
|
|
1669
|
+
if (arg === "--token") {
|
|
1670
|
+
flagToken = parseTokenFlag(args, index);
|
|
1671
|
+
index += 1;
|
|
1672
|
+
continue;
|
|
1673
|
+
}
|
|
1674
|
+
if (arg === "--profile") {
|
|
1675
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
1676
|
+
index += 1;
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
throw new TenderCliUsageError(`Unknown context status option: ${arg}`);
|
|
1680
|
+
}
|
|
1681
|
+
return {
|
|
1682
|
+
command: "artifacts context status",
|
|
1683
|
+
artifactId,
|
|
1684
|
+
dir,
|
|
1685
|
+
baseUrl,
|
|
1686
|
+
flagToken,
|
|
1687
|
+
profileName,
|
|
1688
|
+
json,
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
function parseContextRefreshArgs(args) {
|
|
1692
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1693
|
+
return { command: "help", topic: "artifacts context refresh" };
|
|
1694
|
+
}
|
|
1695
|
+
const artifactId = args[0];
|
|
1696
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
1697
|
+
throw new TenderCliUsageError("artifact id is required. Example: tender artifacts context refresh <artifact-id> --dir ./widget --dry-run --json");
|
|
1698
|
+
}
|
|
1699
|
+
let dir = ".";
|
|
1700
|
+
let baseUrl = null;
|
|
1701
|
+
let flagToken = null;
|
|
1702
|
+
let profileName = null;
|
|
1703
|
+
let dryRun = false;
|
|
1704
|
+
let force = false;
|
|
1705
|
+
let json = false;
|
|
1706
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
1707
|
+
const arg = args[index];
|
|
1708
|
+
if (arg === "--json") {
|
|
1709
|
+
json = true;
|
|
1710
|
+
continue;
|
|
1711
|
+
}
|
|
1712
|
+
if (arg === "--dry-run") {
|
|
1713
|
+
dryRun = true;
|
|
1714
|
+
continue;
|
|
1715
|
+
}
|
|
1716
|
+
if (arg === "--force") {
|
|
1717
|
+
force = true;
|
|
1718
|
+
continue;
|
|
1719
|
+
}
|
|
1720
|
+
if (arg === "--dir") {
|
|
1721
|
+
const value = args[index + 1];
|
|
1722
|
+
if (!value || value.startsWith("-")) {
|
|
1723
|
+
throw new TenderCliUsageError("--dir requires a path.");
|
|
1724
|
+
}
|
|
1725
|
+
dir = value;
|
|
1726
|
+
index += 1;
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
if (arg === "--base-url") {
|
|
1730
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
1731
|
+
index += 1;
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
if (arg === "--token") {
|
|
1735
|
+
flagToken = parseTokenFlag(args, index);
|
|
1736
|
+
index += 1;
|
|
1737
|
+
continue;
|
|
1738
|
+
}
|
|
1739
|
+
if (arg === "--profile") {
|
|
1740
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
1741
|
+
index += 1;
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1744
|
+
throw new TenderCliUsageError(`Unknown context refresh option: ${arg}`);
|
|
1745
|
+
}
|
|
1746
|
+
return {
|
|
1747
|
+
command: "artifacts context refresh",
|
|
1748
|
+
artifactId,
|
|
1749
|
+
dir,
|
|
1750
|
+
baseUrl,
|
|
1751
|
+
flagToken,
|
|
1752
|
+
profileName,
|
|
1753
|
+
dryRun,
|
|
1754
|
+
force,
|
|
1755
|
+
json,
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
function parseTokenProfile(value) {
|
|
1759
|
+
if (value === "edit-preview" ||
|
|
1760
|
+
value === "publish" ||
|
|
1761
|
+
value === "create") {
|
|
1762
|
+
return value;
|
|
1763
|
+
}
|
|
1764
|
+
throw new TenderCliUsageError("--profile must be one of edit-preview, publish, or create.");
|
|
1765
|
+
}
|
|
1766
|
+
function parsePositiveIntegerFlag(value, flag) {
|
|
1767
|
+
const parsed = Number(value);
|
|
1768
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
1769
|
+
throw new TenderCliUsageError(`${flag} requires a positive integer.`);
|
|
1770
|
+
}
|
|
1771
|
+
return parsed;
|
|
1772
|
+
}
|
|
1773
|
+
function parseAuthLoginArgs(args) {
|
|
1774
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1775
|
+
return { command: "help", topic: "auth login" };
|
|
1776
|
+
}
|
|
1777
|
+
let device = false;
|
|
1778
|
+
let baseUrl = null;
|
|
1779
|
+
let tokenProfile = "edit-preview";
|
|
1780
|
+
let saveProfile = DEFAULT_PROFILE_NAME;
|
|
1781
|
+
let artifactId = null;
|
|
1782
|
+
let ttl = "7d";
|
|
1783
|
+
let json = false;
|
|
1784
|
+
let noPoll = false;
|
|
1785
|
+
let pollTimeoutSeconds = 600;
|
|
1786
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1787
|
+
const arg = args[index];
|
|
1788
|
+
if (arg === "--device") {
|
|
1789
|
+
device = true;
|
|
1790
|
+
continue;
|
|
1791
|
+
}
|
|
1792
|
+
if (arg === "--json") {
|
|
1793
|
+
json = true;
|
|
1794
|
+
continue;
|
|
1795
|
+
}
|
|
1796
|
+
if (arg === "--no-poll") {
|
|
1797
|
+
noPoll = true;
|
|
1798
|
+
continue;
|
|
1799
|
+
}
|
|
1800
|
+
if (arg === "--base-url") {
|
|
1801
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
1802
|
+
index += 1;
|
|
1803
|
+
continue;
|
|
1804
|
+
}
|
|
1805
|
+
if (arg === "--profile") {
|
|
1806
|
+
tokenProfile = parseTokenProfile(args[index + 1]);
|
|
1807
|
+
index += 1;
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
if (arg === "--save-profile") {
|
|
1811
|
+
saveProfile = parseProfileName(args[index + 1], "--save-profile");
|
|
1812
|
+
index += 1;
|
|
1813
|
+
continue;
|
|
1814
|
+
}
|
|
1815
|
+
if (arg === "--artifact") {
|
|
1816
|
+
artifactId = parseProfileName(args[index + 1], "--artifact");
|
|
1817
|
+
index += 1;
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1820
|
+
if (arg === "--ttl") {
|
|
1821
|
+
ttl = parseTtl(args[index + 1]);
|
|
1822
|
+
index += 1;
|
|
1823
|
+
continue;
|
|
1824
|
+
}
|
|
1825
|
+
if (arg === "--poll-timeout") {
|
|
1826
|
+
pollTimeoutSeconds = parsePositiveIntegerFlag(args[index + 1], "--poll-timeout");
|
|
1827
|
+
index += 1;
|
|
1828
|
+
continue;
|
|
1829
|
+
}
|
|
1830
|
+
throw new TenderCliUsageError(`Unknown auth login option: ${arg}`);
|
|
1831
|
+
}
|
|
1832
|
+
if (!device) {
|
|
1833
|
+
throw new TenderCliUsageError("auth login currently requires --device. Example: tender auth login --device --profile edit-preview --artifact artifact_123");
|
|
1834
|
+
}
|
|
1835
|
+
if (tokenProfile === "create" && artifactId) {
|
|
1836
|
+
throw new TenderCliUsageError("--profile create must be account-scoped; remove --artifact.");
|
|
1837
|
+
}
|
|
1838
|
+
return {
|
|
1839
|
+
command: "auth login",
|
|
1840
|
+
baseUrl,
|
|
1841
|
+
device,
|
|
1842
|
+
tokenProfile,
|
|
1843
|
+
saveProfile,
|
|
1844
|
+
artifactId,
|
|
1845
|
+
ttl,
|
|
1846
|
+
json,
|
|
1847
|
+
noPoll,
|
|
1848
|
+
pollTimeoutSeconds,
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
function parseAuthStatusArgs(args) {
|
|
1852
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1853
|
+
return { command: "help", topic: "auth status" };
|
|
1854
|
+
}
|
|
1855
|
+
let profileName = null;
|
|
1856
|
+
let json = false;
|
|
1857
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1858
|
+
const arg = args[index];
|
|
1859
|
+
if (arg === "--json") {
|
|
1860
|
+
json = true;
|
|
1861
|
+
continue;
|
|
1862
|
+
}
|
|
1863
|
+
if (arg === "--profile") {
|
|
1864
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
1865
|
+
index += 1;
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
throw new TenderCliUsageError(`Unknown auth status option: ${arg}`);
|
|
1869
|
+
}
|
|
1870
|
+
return {
|
|
1871
|
+
command: "auth status",
|
|
1872
|
+
profileName,
|
|
1873
|
+
json,
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
function parseTenderArgs(args) {
|
|
1877
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
1878
|
+
return { command: "help", topic: "root" };
|
|
1879
|
+
}
|
|
1880
|
+
if (args[0] === "auth") {
|
|
1881
|
+
if (args[1] === undefined || args[1] === "--help" || args[1] === "-h") {
|
|
1882
|
+
return { command: "help", topic: "auth" };
|
|
1883
|
+
}
|
|
1884
|
+
if (args[1] === "login") {
|
|
1885
|
+
return parseAuthLoginArgs(args.slice(2));
|
|
1886
|
+
}
|
|
1887
|
+
if (args[1] === "status") {
|
|
1888
|
+
return parseAuthStatusArgs(args.slice(2));
|
|
1889
|
+
}
|
|
1890
|
+
throw new TenderCliUsageError(`Unknown auth command: ${args[1]}`);
|
|
1891
|
+
}
|
|
1892
|
+
if (args[0] !== "artifacts") {
|
|
1893
|
+
throw new TenderCliUsageError(`Unknown command: ${args[0]}`);
|
|
1894
|
+
}
|
|
1895
|
+
if (args[1] === undefined || args[1] === "--help" || args[1] === "-h") {
|
|
1896
|
+
return { command: "help", topic: "artifacts" };
|
|
1897
|
+
}
|
|
1898
|
+
if (args[1] !== "doctor") {
|
|
1899
|
+
if (args[1] === "list") {
|
|
1900
|
+
return parseArtifactsListArgs(args.slice(2));
|
|
1901
|
+
}
|
|
1902
|
+
if (args[1] === "create") {
|
|
1903
|
+
return parseArtifactsCreateArgs(args.slice(2));
|
|
1904
|
+
}
|
|
1905
|
+
if (args[1] === "update") {
|
|
1906
|
+
return parseArtifactsUpdateArgs(args.slice(2));
|
|
1907
|
+
}
|
|
1908
|
+
if (args[1] === "init") {
|
|
1909
|
+
return parseArtifactsInitArgs(args.slice(2));
|
|
1910
|
+
}
|
|
1911
|
+
if (args[1] === "context" && args[2] === "fetch") {
|
|
1912
|
+
return parseContextFetchArgs(args.slice(3));
|
|
1913
|
+
}
|
|
1914
|
+
if (args[1] === "context" && args[2] === "status") {
|
|
1915
|
+
return parseContextStatusArgs(args.slice(3));
|
|
1916
|
+
}
|
|
1917
|
+
if (args[1] === "context" && args[2] === "refresh") {
|
|
1918
|
+
return parseContextRefreshArgs(args.slice(3));
|
|
1919
|
+
}
|
|
1920
|
+
if (args[1] === "context" &&
|
|
1921
|
+
(args[2] === undefined || args[2] === "--help" || args[2] === "-h")) {
|
|
1922
|
+
return { command: "help", topic: "artifacts" };
|
|
1923
|
+
}
|
|
1924
|
+
if (args[1] === "git" && args[2] === "remote") {
|
|
1925
|
+
return parseGitRemoteArgs(args.slice(3));
|
|
1926
|
+
}
|
|
1927
|
+
if (args[1] === "git" && args[2] === "setup") {
|
|
1928
|
+
return parseGitSetupArgs(args.slice(3));
|
|
1929
|
+
}
|
|
1930
|
+
if (args[1] === "git" && args[2] === "credential") {
|
|
1931
|
+
return parseGitCredentialArgs(args.slice(3));
|
|
1932
|
+
}
|
|
1933
|
+
if (args[1] === "validate") {
|
|
1934
|
+
return parseValidateArgs(args.slice(2));
|
|
1935
|
+
}
|
|
1936
|
+
if (args[1] === "build") {
|
|
1937
|
+
return parseBuildArgs(args.slice(2));
|
|
1938
|
+
}
|
|
1939
|
+
if (args[1] === "preview" && args[2] === "rebuild") {
|
|
1940
|
+
return parsePreviewRebuildArgs(args.slice(3));
|
|
1941
|
+
}
|
|
1942
|
+
if (args[1] === "preview" && args[2] === "status") {
|
|
1943
|
+
return parsePreviewStatusArgs(args.slice(3));
|
|
1944
|
+
}
|
|
1945
|
+
if (args[1] === "preview" && args[2] === "watch") {
|
|
1946
|
+
return parsePreviewWatchArgs(args.slice(3));
|
|
1947
|
+
}
|
|
1948
|
+
if (args[1] === "preview" &&
|
|
1949
|
+
(args[2] === undefined || args[2] === "--help" || args[2] === "-h")) {
|
|
1950
|
+
return { command: "help", topic: "artifacts" };
|
|
1951
|
+
}
|
|
1952
|
+
if (args[1] === "publish" && args[2] === "status") {
|
|
1953
|
+
return parsePublishStatusArgs(args.slice(3));
|
|
1954
|
+
}
|
|
1955
|
+
if (args[1] === "publish" && args[2] === "watch") {
|
|
1956
|
+
return parsePublishWatchArgs(args.slice(3));
|
|
1957
|
+
}
|
|
1958
|
+
if (args[1] === "publish") {
|
|
1959
|
+
return parsePublishArgs(args.slice(2));
|
|
1960
|
+
}
|
|
1961
|
+
if (args[1] === "git" && (args[2] === undefined || args[2] === "--help" || args[2] === "-h")) {
|
|
1962
|
+
return { command: "help", topic: "artifacts" };
|
|
1963
|
+
}
|
|
1964
|
+
throw new TenderCliUsageError(`Unknown artifacts command: ${args[1]}`);
|
|
1965
|
+
}
|
|
1966
|
+
const rest = args.slice(2);
|
|
1967
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
1968
|
+
return { command: "help", topic: "artifacts doctor" };
|
|
1969
|
+
}
|
|
1970
|
+
let dir = ".";
|
|
1971
|
+
let json = false;
|
|
1972
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
1973
|
+
const arg = rest[index];
|
|
1974
|
+
if (arg === "--json") {
|
|
1975
|
+
json = true;
|
|
1976
|
+
continue;
|
|
1977
|
+
}
|
|
1978
|
+
if (arg === "--dir") {
|
|
1979
|
+
const value = rest[index + 1];
|
|
1980
|
+
if (!value) {
|
|
1981
|
+
throw new TenderCliUsageError("--dir requires a path.");
|
|
1982
|
+
}
|
|
1983
|
+
dir = value;
|
|
1984
|
+
index += 1;
|
|
1985
|
+
continue;
|
|
1986
|
+
}
|
|
1987
|
+
throw new TenderCliUsageError(`Unknown doctor option: ${arg}`);
|
|
1988
|
+
}
|
|
1989
|
+
return {
|
|
1990
|
+
command: "artifacts doctor",
|
|
1991
|
+
dir,
|
|
1992
|
+
json,
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
function formatDoctorHuman(result) {
|
|
1996
|
+
if (result.diagnostics.length === 0) {
|
|
1997
|
+
return result.ok
|
|
1998
|
+
? "Tender artifacts doctor passed."
|
|
1999
|
+
: "Tender artifacts doctor failed.";
|
|
2000
|
+
}
|
|
2001
|
+
return result.diagnostics
|
|
2002
|
+
.map((diagnostic) => {
|
|
2003
|
+
const location = [
|
|
2004
|
+
diagnostic.path,
|
|
2005
|
+
diagnostic.line !== undefined ? diagnostic.line : null,
|
|
2006
|
+
diagnostic.column !== undefined ? diagnostic.column : null,
|
|
2007
|
+
]
|
|
2008
|
+
.filter((part) => part !== null && part !== undefined)
|
|
2009
|
+
.join(":");
|
|
2010
|
+
return [
|
|
2011
|
+
diagnostic.severity.toUpperCase(),
|
|
2012
|
+
diagnostic.code,
|
|
2013
|
+
location,
|
|
2014
|
+
diagnostic.message,
|
|
2015
|
+
diagnostic.suggestion,
|
|
2016
|
+
]
|
|
2017
|
+
.filter(Boolean)
|
|
2018
|
+
.join(" - ");
|
|
2019
|
+
})
|
|
2020
|
+
.join("\n");
|
|
2021
|
+
}
|
|
2022
|
+
async function runDoctor(command, io) {
|
|
2023
|
+
const root = path.resolve(command.dir);
|
|
2024
|
+
const files = await collectTenderAppFiles({ root, current: root });
|
|
2025
|
+
const result = validateTenderApp({
|
|
2026
|
+
files,
|
|
2027
|
+
mode: "local",
|
|
2028
|
+
});
|
|
2029
|
+
if (command.json) {
|
|
2030
|
+
io.stdout(JSON.stringify({
|
|
2031
|
+
ok: result.ok,
|
|
2032
|
+
command: command.command,
|
|
2033
|
+
dir: root,
|
|
2034
|
+
diagnostics: result.diagnostics,
|
|
2035
|
+
}, null, 2));
|
|
2036
|
+
}
|
|
2037
|
+
else {
|
|
2038
|
+
io.stdout(formatDoctorHuman(result));
|
|
2039
|
+
}
|
|
2040
|
+
return result.ok ? 0 : 1;
|
|
2041
|
+
}
|
|
2042
|
+
async function parseJsonResponse(response) {
|
|
2043
|
+
return (await response.json().catch(() => null));
|
|
2044
|
+
}
|
|
2045
|
+
function splitScope(scope) {
|
|
2046
|
+
return scope
|
|
2047
|
+
? scope.split(/\s+/).filter((permission) => permission.length > 0)
|
|
2048
|
+
: [];
|
|
2049
|
+
}
|
|
2050
|
+
function formatExpiry(value) {
|
|
2051
|
+
return value ? new Date(value).toISOString() : null;
|
|
2052
|
+
}
|
|
2053
|
+
function parseSseLifecycleEvents(text) {
|
|
2054
|
+
const events = [];
|
|
2055
|
+
for (const block of text.split(/\n\n+/)) {
|
|
2056
|
+
const event = parseSseLifecycleBlock(block);
|
|
2057
|
+
if (event === null) {
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
2060
|
+
events.push(event);
|
|
2061
|
+
}
|
|
2062
|
+
return events;
|
|
2063
|
+
}
|
|
2064
|
+
function parseSseLifecycleBlock(block) {
|
|
2065
|
+
const dataLines = block
|
|
2066
|
+
.split(/\r?\n/)
|
|
2067
|
+
.filter((line) => line.startsWith("data:"))
|
|
2068
|
+
.map((line) => line.slice("data:".length).trim());
|
|
2069
|
+
if (dataLines.length === 0) {
|
|
2070
|
+
return null;
|
|
2071
|
+
}
|
|
2072
|
+
try {
|
|
2073
|
+
return JSON.parse(dataLines.join("\n"));
|
|
2074
|
+
}
|
|
2075
|
+
catch {
|
|
2076
|
+
return { raw: dataLines.join("\n") };
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
function findSseBlockSeparator(buffer) {
|
|
2080
|
+
const unixIndex = buffer.indexOf("\n\n");
|
|
2081
|
+
const windowsIndex = buffer.indexOf("\r\n\r\n");
|
|
2082
|
+
if (unixIndex === -1 && windowsIndex === -1) {
|
|
2083
|
+
return null;
|
|
2084
|
+
}
|
|
2085
|
+
if (windowsIndex !== -1 && (unixIndex === -1 || windowsIndex < unixIndex)) {
|
|
2086
|
+
return { index: windowsIndex, length: 4 };
|
|
2087
|
+
}
|
|
2088
|
+
return { index: unixIndex, length: 2 };
|
|
2089
|
+
}
|
|
2090
|
+
function emitParsedSseBlock(input) {
|
|
2091
|
+
const event = parseSseLifecycleBlock(input.block);
|
|
2092
|
+
if (event === null) {
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
2095
|
+
if (input.json) {
|
|
2096
|
+
input.io.stdout(JSON.stringify(event));
|
|
2097
|
+
}
|
|
2098
|
+
else {
|
|
2099
|
+
input.io.stdout(input.block.trim());
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
async function emitSseLifecycleStream(input) {
|
|
2103
|
+
if (!input.response.body) {
|
|
2104
|
+
const text = await input.response.text();
|
|
2105
|
+
const events = parseSseLifecycleEvents(text);
|
|
2106
|
+
if (input.json) {
|
|
2107
|
+
for (const event of events) {
|
|
2108
|
+
input.io.stdout(JSON.stringify(event));
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
else {
|
|
2112
|
+
input.io.stdout(text.trim());
|
|
2113
|
+
}
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
const reader = input.response.body.getReader();
|
|
2117
|
+
const decoder = new TextDecoder();
|
|
2118
|
+
let buffer = "";
|
|
2119
|
+
while (true) {
|
|
2120
|
+
const result = await reader.read();
|
|
2121
|
+
if (result.done) {
|
|
2122
|
+
break;
|
|
2123
|
+
}
|
|
2124
|
+
buffer += decoder.decode(result.value, { stream: true });
|
|
2125
|
+
let separator = findSseBlockSeparator(buffer);
|
|
2126
|
+
while (separator !== null) {
|
|
2127
|
+
const block = buffer.slice(0, separator.index);
|
|
2128
|
+
buffer = buffer.slice(separator.index + separator.length);
|
|
2129
|
+
emitParsedSseBlock({
|
|
2130
|
+
block,
|
|
2131
|
+
io: input.io,
|
|
2132
|
+
json: input.json,
|
|
2133
|
+
});
|
|
2134
|
+
separator = findSseBlockSeparator(buffer);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
buffer += decoder.decode();
|
|
2138
|
+
const remaining = buffer.trim();
|
|
2139
|
+
if (remaining) {
|
|
2140
|
+
emitParsedSseBlock({
|
|
2141
|
+
block: remaining,
|
|
2142
|
+
io: input.io,
|
|
2143
|
+
json: input.json,
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
function readObject(value) {
|
|
2148
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
2149
|
+
? value
|
|
2150
|
+
: null;
|
|
2151
|
+
}
|
|
2152
|
+
function readObjectString(value, key) {
|
|
2153
|
+
const record = readObject(value);
|
|
2154
|
+
const field = record?.[key];
|
|
2155
|
+
return typeof field === "string" && field.trim() ? field : null;
|
|
2156
|
+
}
|
|
2157
|
+
function readPreviewUrlFromJob(job) {
|
|
2158
|
+
return readObjectString(job?.result, "previewUrl");
|
|
2159
|
+
}
|
|
2160
|
+
function readPublishedUrlFromJob(job) {
|
|
2161
|
+
return readObjectString(job?.result, "publishedUrl");
|
|
2162
|
+
}
|
|
2163
|
+
function normalizeContextFilePath(filePath) {
|
|
2164
|
+
const normalized = path.posix
|
|
2165
|
+
.normalize(filePath.replaceAll("\\", "/"))
|
|
2166
|
+
.replace(/^\/+/, "");
|
|
2167
|
+
if (!normalized ||
|
|
2168
|
+
normalized === "." ||
|
|
2169
|
+
normalized.startsWith("../") ||
|
|
2170
|
+
normalized.includes("/../")) {
|
|
2171
|
+
return null;
|
|
2172
|
+
}
|
|
2173
|
+
return normalized;
|
|
2174
|
+
}
|
|
2175
|
+
function resolveContextOutputPath(root, relativePath) {
|
|
2176
|
+
const absolutePath = path.resolve(root, relativePath);
|
|
2177
|
+
const relativeFromRoot = path.relative(root, absolutePath);
|
|
2178
|
+
if (relativeFromRoot === "" ||
|
|
2179
|
+
relativeFromRoot.startsWith("..") ||
|
|
2180
|
+
path.isAbsolute(relativeFromRoot)) {
|
|
2181
|
+
return null;
|
|
2182
|
+
}
|
|
2183
|
+
return absolutePath;
|
|
2184
|
+
}
|
|
2185
|
+
async function readExistingFile(pathname) {
|
|
2186
|
+
try {
|
|
2187
|
+
return await readFile(pathname, "utf8");
|
|
2188
|
+
}
|
|
2189
|
+
catch (error) {
|
|
2190
|
+
if (isMissingFile(error)) {
|
|
2191
|
+
return null;
|
|
2192
|
+
}
|
|
2193
|
+
throw error;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
async function planContextFiles(input) {
|
|
2197
|
+
const planned = [];
|
|
2198
|
+
for (const file of input.files) {
|
|
2199
|
+
const relativePath = normalizeContextFilePath(file.path);
|
|
2200
|
+
if (!relativePath) {
|
|
2201
|
+
throw new Error(`Context file path is unsafe: ${file.path}`);
|
|
2202
|
+
}
|
|
2203
|
+
const absolutePath = resolveContextOutputPath(input.root, relativePath);
|
|
2204
|
+
if (!absolutePath) {
|
|
2205
|
+
throw new Error(`Context file path escapes the target directory: ${file.path}`);
|
|
2206
|
+
}
|
|
2207
|
+
const existing = await readExistingFile(absolutePath);
|
|
2208
|
+
let action;
|
|
2209
|
+
if (existing === null) {
|
|
2210
|
+
action = "create";
|
|
2211
|
+
}
|
|
2212
|
+
else if (existing === file.content) {
|
|
2213
|
+
action = "unchanged";
|
|
2214
|
+
}
|
|
2215
|
+
else {
|
|
2216
|
+
action = input.force ? "overwrite" : "conflict";
|
|
2217
|
+
}
|
|
2218
|
+
planned.push({
|
|
2219
|
+
path: relativePath,
|
|
2220
|
+
absolutePath,
|
|
2221
|
+
sha256: file.sha256,
|
|
2222
|
+
action,
|
|
2223
|
+
content: file.content,
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
return planned.sort((left, right) => left.path.localeCompare(right.path));
|
|
2227
|
+
}
|
|
2228
|
+
async function requestArtifactWorkspaceFileList(input) {
|
|
2229
|
+
const params = new URLSearchParams({ path: input.pathname });
|
|
2230
|
+
const response = await input.fetcher(`${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/files?${params.toString()}`, {
|
|
2231
|
+
headers: {
|
|
2232
|
+
authorization: `Bearer ${input.token}`,
|
|
2233
|
+
},
|
|
2234
|
+
});
|
|
2235
|
+
const payload = await parseJsonResponse(response);
|
|
2236
|
+
if (!response.ok || !payload?.ok || !Array.isArray(payload.files)) {
|
|
2237
|
+
throw new Error(payload?.message ??
|
|
2238
|
+
payload?.reasonCode ??
|
|
2239
|
+
`Artifact file list request failed with HTTP ${response.status}.`);
|
|
2240
|
+
}
|
|
2241
|
+
return payload.files;
|
|
2242
|
+
}
|
|
2243
|
+
async function requestArtifactWorkspaceFileContent(input) {
|
|
2244
|
+
const params = new URLSearchParams({ path: input.pathname });
|
|
2245
|
+
const response = await input.fetcher(`${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/file-content?${params.toString()}`, {
|
|
2246
|
+
headers: {
|
|
2247
|
+
authorization: `Bearer ${input.token}`,
|
|
2248
|
+
},
|
|
2249
|
+
});
|
|
2250
|
+
const payload = await parseJsonResponse(response);
|
|
2251
|
+
if (!response.ok || !payload?.ok || !payload.file) {
|
|
2252
|
+
throw new Error(payload?.message ??
|
|
2253
|
+
payload?.reasonCode ??
|
|
2254
|
+
`Artifact file content request failed with HTTP ${response.status}.`);
|
|
2255
|
+
}
|
|
2256
|
+
return payload.file.content;
|
|
2257
|
+
}
|
|
2258
|
+
async function collectArtifactWorkspaceSourceFiles(input) {
|
|
2259
|
+
const files = [];
|
|
2260
|
+
const queue = ["/"];
|
|
2261
|
+
const visited = new Set();
|
|
2262
|
+
while (queue.length > 0) {
|
|
2263
|
+
const current = queue.shift() ?? "/";
|
|
2264
|
+
if (visited.has(current)) {
|
|
2265
|
+
continue;
|
|
2266
|
+
}
|
|
2267
|
+
visited.add(current);
|
|
2268
|
+
const entries = await requestArtifactWorkspaceFileList({
|
|
2269
|
+
...input,
|
|
2270
|
+
pathname: current,
|
|
2271
|
+
});
|
|
2272
|
+
for (const entry of entries) {
|
|
2273
|
+
if (files.length >= ARTIFACT_SOURCE_EXPORT_MAX_FILES) {
|
|
2274
|
+
throw new Error("artifact_source_export_file_limit_exceeded");
|
|
2275
|
+
}
|
|
2276
|
+
const normalizedPath = normalizeContextFilePath(entry.path);
|
|
2277
|
+
if (!normalizedPath) {
|
|
2278
|
+
continue;
|
|
2279
|
+
}
|
|
2280
|
+
if (entry.type === "directory") {
|
|
2281
|
+
queue.push(entry.path);
|
|
2282
|
+
continue;
|
|
2283
|
+
}
|
|
2284
|
+
if (entry.type !== "file") {
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
if (typeof entry.size === "number" &&
|
|
2288
|
+
entry.size > ARTIFACT_SOURCE_EXPORT_MAX_FILE_BYTES) {
|
|
2289
|
+
throw new Error(`artifact_source_file_too_large:${normalizedPath}`);
|
|
2290
|
+
}
|
|
2291
|
+
const content = await requestArtifactWorkspaceFileContent({
|
|
2292
|
+
...input,
|
|
2293
|
+
pathname: entry.path,
|
|
2294
|
+
});
|
|
2295
|
+
files.push({
|
|
2296
|
+
path: normalizedPath,
|
|
2297
|
+
content,
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
return files.sort((left, right) => left.path.localeCompare(right.path));
|
|
2302
|
+
}
|
|
2303
|
+
async function planSourceFiles(input) {
|
|
2304
|
+
const planned = [];
|
|
2305
|
+
for (const file of input.files) {
|
|
2306
|
+
const absolutePath = resolveContextOutputPath(input.root, file.path);
|
|
2307
|
+
if (!absolutePath) {
|
|
2308
|
+
throw new Error(`Source file path escapes the target directory: ${file.path}`);
|
|
2309
|
+
}
|
|
2310
|
+
const existing = await readExistingFile(absolutePath);
|
|
2311
|
+
let action;
|
|
2312
|
+
if (existing === null) {
|
|
2313
|
+
action = "create";
|
|
2314
|
+
}
|
|
2315
|
+
else if (existing === file.content) {
|
|
2316
|
+
action = "unchanged";
|
|
2317
|
+
}
|
|
2318
|
+
else {
|
|
2319
|
+
action = input.force ? "overwrite" : "conflict";
|
|
2320
|
+
}
|
|
2321
|
+
planned.push({
|
|
2322
|
+
path: file.path,
|
|
2323
|
+
absolutePath,
|
|
2324
|
+
action,
|
|
2325
|
+
content: file.content,
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
return planned.sort((left, right) => left.path.localeCompare(right.path));
|
|
2329
|
+
}
|
|
2330
|
+
async function runAuthStatus(command, io, runtime) {
|
|
2331
|
+
const env = runtime.env ?? process.env;
|
|
2332
|
+
if (env.TENDER_API_TOKEN) {
|
|
2333
|
+
const result = {
|
|
2334
|
+
ok: true,
|
|
2335
|
+
command: command.command,
|
|
2336
|
+
source: "env",
|
|
2337
|
+
profile: null,
|
|
2338
|
+
baseUrl: readDefaultBaseUrl(env),
|
|
2339
|
+
tokenAvailable: true,
|
|
2340
|
+
};
|
|
2341
|
+
if (command.json) {
|
|
2342
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
2343
|
+
}
|
|
2344
|
+
else {
|
|
2345
|
+
io.stdout([
|
|
2346
|
+
"authenticated: true",
|
|
2347
|
+
"source: TENDER_API_TOKEN",
|
|
2348
|
+
`base_url: ${result.baseUrl}`,
|
|
2349
|
+
].join("\n"));
|
|
2350
|
+
}
|
|
2351
|
+
return 0;
|
|
2352
|
+
}
|
|
2353
|
+
const config = await readConfig(env);
|
|
2354
|
+
const profileName = command.profileName ?? config.defaultProfile ?? DEFAULT_PROFILE_NAME;
|
|
2355
|
+
const profile = config.profiles?.[profileName] ?? null;
|
|
2356
|
+
const result = {
|
|
2357
|
+
ok: Boolean(profile),
|
|
2358
|
+
command: command.command,
|
|
2359
|
+
source: profile ? "config" : null,
|
|
2360
|
+
profile: profileName,
|
|
2361
|
+
configPath: readConfigPath(env),
|
|
2362
|
+
baseUrl: profile?.baseUrl ?? null,
|
|
2363
|
+
tokenAvailable: Boolean(profile?.token),
|
|
2364
|
+
tokenPrefix: profile?.tokenPrefix ?? null,
|
|
2365
|
+
tenantId: profile?.tenantId ?? null,
|
|
2366
|
+
artifactId: profile?.artifactId ?? null,
|
|
2367
|
+
permissions: profile?.permissions ?? [],
|
|
2368
|
+
expiresAt: formatExpiry(profile?.expiresAt),
|
|
2369
|
+
};
|
|
2370
|
+
if (command.json) {
|
|
2371
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
2372
|
+
}
|
|
2373
|
+
else if (profile) {
|
|
2374
|
+
io.stdout([
|
|
2375
|
+
"authenticated: true",
|
|
2376
|
+
`source: config:${profileName}`,
|
|
2377
|
+
`base_url: ${profile.baseUrl}`,
|
|
2378
|
+
`token_prefix: ${profile.tokenPrefix ?? "(unknown)"}`,
|
|
2379
|
+
`tenant_id: ${profile.tenantId ?? "(unknown)"}`,
|
|
2380
|
+
`artifact_id: ${profile.artifactId ?? "(account)"}`,
|
|
2381
|
+
`permissions: ${(profile.permissions ?? []).join(" ") || "(unknown)"}`,
|
|
2382
|
+
`expires_at: ${formatExpiry(profile.expiresAt) ?? "(unknown)"}`,
|
|
2383
|
+
].join("\n"));
|
|
2384
|
+
}
|
|
2385
|
+
else {
|
|
2386
|
+
io.stdout([
|
|
2387
|
+
"authenticated: false",
|
|
2388
|
+
`profile: ${profileName}`,
|
|
2389
|
+
"next: tender auth login --device --profile edit-preview --artifact <artifact-id>",
|
|
2390
|
+
].join("\n"));
|
|
2391
|
+
}
|
|
2392
|
+
return profile ? 0 : 1;
|
|
2393
|
+
}
|
|
2394
|
+
async function runAuthLogin(command, io, runtime) {
|
|
2395
|
+
const env = runtime.env ?? process.env;
|
|
2396
|
+
const fetcher = runtime.fetcher ?? fetch;
|
|
2397
|
+
const sleeper = runtime.sleeper ??
|
|
2398
|
+
((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
2399
|
+
const now = runtime.now ?? Date.now;
|
|
2400
|
+
const baseUrl = command.baseUrl ?? readDefaultBaseUrl(env);
|
|
2401
|
+
const deviceResponse = await fetcher(`${baseUrl}/oauth/device/code`, {
|
|
2402
|
+
method: "POST",
|
|
2403
|
+
headers: {
|
|
2404
|
+
"content-type": "application/json",
|
|
2405
|
+
},
|
|
2406
|
+
body: JSON.stringify({
|
|
2407
|
+
profile: command.tokenProfile,
|
|
2408
|
+
ttl: command.ttl,
|
|
2409
|
+
artifactId: command.artifactId,
|
|
2410
|
+
}),
|
|
2411
|
+
});
|
|
2412
|
+
const devicePayload = await parseJsonResponse(deviceResponse);
|
|
2413
|
+
if (!deviceResponse.ok ||
|
|
2414
|
+
!devicePayload?.device_code ||
|
|
2415
|
+
!devicePayload.user_code ||
|
|
2416
|
+
!devicePayload.verification_uri_complete) {
|
|
2417
|
+
throw new Error(devicePayload?.error_description ??
|
|
2418
|
+
devicePayload?.error ??
|
|
2419
|
+
`Device authorization failed with HTTP ${deviceResponse.status}.`);
|
|
2420
|
+
}
|
|
2421
|
+
const pendingResult = {
|
|
2422
|
+
ok: true,
|
|
2423
|
+
command: command.command,
|
|
2424
|
+
baseUrl,
|
|
2425
|
+
profile: command.tokenProfile,
|
|
2426
|
+
saveProfile: command.saveProfile,
|
|
2427
|
+
userCode: devicePayload.user_code,
|
|
2428
|
+
verificationUri: devicePayload.verification_uri,
|
|
2429
|
+
verificationUriComplete: devicePayload.verification_uri_complete,
|
|
2430
|
+
expiresIn: devicePayload.expires_in ?? null,
|
|
2431
|
+
interval: devicePayload.interval ?? null,
|
|
2432
|
+
};
|
|
2433
|
+
if (command.noPoll) {
|
|
2434
|
+
if (command.json) {
|
|
2435
|
+
io.stdout(JSON.stringify(pendingResult, null, 2));
|
|
2436
|
+
}
|
|
2437
|
+
else {
|
|
2438
|
+
io.stdout([
|
|
2439
|
+
`verification_url: ${devicePayload.verification_uri_complete}`,
|
|
2440
|
+
`user_code: ${devicePayload.user_code}`,
|
|
2441
|
+
].join("\n"));
|
|
2442
|
+
}
|
|
2443
|
+
return 0;
|
|
2444
|
+
}
|
|
2445
|
+
if (!command.json) {
|
|
2446
|
+
io.stdout([
|
|
2447
|
+
`Open this URL to approve Tender CLI access: ${devicePayload.verification_uri_complete}`,
|
|
2448
|
+
`User code: ${devicePayload.user_code}`,
|
|
2449
|
+
].join("\n"));
|
|
2450
|
+
}
|
|
2451
|
+
let intervalSeconds = Math.max(1, devicePayload.interval ?? 5);
|
|
2452
|
+
const expiresAtMs = now() + Math.max(1, devicePayload.expires_in ?? 600) * 1000;
|
|
2453
|
+
let attemptsRemaining = Math.max(1, Math.ceil(command.pollTimeoutSeconds / intervalSeconds));
|
|
2454
|
+
while (attemptsRemaining > 0 && now() < expiresAtMs) {
|
|
2455
|
+
attemptsRemaining -= 1;
|
|
2456
|
+
const tokenResponse = await fetcher(`${baseUrl}/oauth/token`, {
|
|
2457
|
+
method: "POST",
|
|
2458
|
+
headers: {
|
|
2459
|
+
"content-type": "application/json",
|
|
2460
|
+
},
|
|
2461
|
+
body: JSON.stringify({
|
|
2462
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
2463
|
+
device_code: devicePayload.device_code,
|
|
2464
|
+
}),
|
|
2465
|
+
});
|
|
2466
|
+
const tokenPayload = await parseJsonResponse(tokenResponse);
|
|
2467
|
+
if (tokenResponse.ok && tokenPayload?.access_token) {
|
|
2468
|
+
const config = await readConfig(env);
|
|
2469
|
+
const profiles = config.profiles ?? {};
|
|
2470
|
+
profiles[command.saveProfile] = {
|
|
2471
|
+
baseUrl,
|
|
2472
|
+
token: tokenPayload.access_token,
|
|
2473
|
+
tokenPrefix: tokenPayload.token_prefix,
|
|
2474
|
+
tenantId: tokenPayload.tenant_id,
|
|
2475
|
+
artifactId: tokenPayload.artifact_id,
|
|
2476
|
+
expiresAt: tokenPayload.expires_at,
|
|
2477
|
+
permissions: splitScope(tokenPayload.scope),
|
|
2478
|
+
};
|
|
2479
|
+
await writeConfig(env, {
|
|
2480
|
+
...config,
|
|
2481
|
+
defaultProfile: config.defaultProfile ?? command.saveProfile,
|
|
2482
|
+
profiles,
|
|
2483
|
+
});
|
|
2484
|
+
const result = {
|
|
2485
|
+
ok: true,
|
|
2486
|
+
command: command.command,
|
|
2487
|
+
baseUrl,
|
|
2488
|
+
profile: command.tokenProfile,
|
|
2489
|
+
savedProfile: command.saveProfile,
|
|
2490
|
+
tokenPrefix: tokenPayload.token_prefix ?? null,
|
|
2491
|
+
tenantId: tokenPayload.tenant_id ?? null,
|
|
2492
|
+
artifactId: tokenPayload.artifact_id ?? null,
|
|
2493
|
+
permissions: splitScope(tokenPayload.scope),
|
|
2494
|
+
expiresAt: formatExpiry(tokenPayload.expires_at),
|
|
2495
|
+
configPath: readConfigPath(env),
|
|
2496
|
+
};
|
|
2497
|
+
if (command.json) {
|
|
2498
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
2499
|
+
}
|
|
2500
|
+
else {
|
|
2501
|
+
io.stdout([
|
|
2502
|
+
"authenticated: true",
|
|
2503
|
+
`profile: ${command.saveProfile}`,
|
|
2504
|
+
`tenant_id: ${result.tenantId ?? "(unknown)"}`,
|
|
2505
|
+
`artifact_id: ${result.artifactId ?? "(account)"}`,
|
|
2506
|
+
`permissions: ${result.permissions.join(" ") || "(unknown)"}`,
|
|
2507
|
+
`expires_at: ${result.expiresAt ?? "(unknown)"}`,
|
|
2508
|
+
].join("\n"));
|
|
2509
|
+
}
|
|
2510
|
+
return 0;
|
|
2511
|
+
}
|
|
2512
|
+
if (tokenPayload?.error === "authorization_pending") {
|
|
2513
|
+
await sleeper(intervalSeconds * 1000);
|
|
2514
|
+
continue;
|
|
2515
|
+
}
|
|
2516
|
+
if (tokenPayload?.error === "slow_down") {
|
|
2517
|
+
intervalSeconds += 5;
|
|
2518
|
+
await sleeper(intervalSeconds * 1000);
|
|
2519
|
+
continue;
|
|
2520
|
+
}
|
|
2521
|
+
throw new Error(tokenPayload?.error_description ??
|
|
2522
|
+
tokenPayload?.error ??
|
|
2523
|
+
`Token exchange failed with HTTP ${tokenResponse.status}.`);
|
|
2524
|
+
}
|
|
2525
|
+
throw new Error("Timed out waiting for device approval.");
|
|
2526
|
+
}
|
|
2527
|
+
async function requestAgentContext(input) {
|
|
2528
|
+
const response = await input.fetcher(`${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/agent-context`, {
|
|
2529
|
+
headers: {
|
|
2530
|
+
authorization: `Bearer ${input.token}`,
|
|
2531
|
+
},
|
|
2532
|
+
});
|
|
2533
|
+
const payload = await parseJsonResponse(response);
|
|
2534
|
+
if (!response.ok || !payload?.ok || !payload.files || !payload.revision) {
|
|
2535
|
+
throw new Error(payload?.message ??
|
|
2536
|
+
payload?.reasonCode ??
|
|
2537
|
+
`Artifact context request failed with HTTP ${response.status}.`);
|
|
2538
|
+
}
|
|
2539
|
+
return {
|
|
2540
|
+
...payload,
|
|
2541
|
+
artifactId: payload.artifactId ?? input.artifactId,
|
|
2542
|
+
revision: payload.revision,
|
|
2543
|
+
files: payload.files,
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
async function runContextFetch(command, io, runtime) {
|
|
2547
|
+
const credentials = await resolveApiCredentials({
|
|
2548
|
+
baseUrl: command.baseUrl,
|
|
2549
|
+
flagToken: command.flagToken,
|
|
2550
|
+
profileName: command.profileName,
|
|
2551
|
+
env: runtime.env ?? process.env,
|
|
2552
|
+
});
|
|
2553
|
+
const fetcher = runtime.fetcher ?? fetch;
|
|
2554
|
+
const payload = await requestAgentContext({
|
|
2555
|
+
artifactId: command.artifactId,
|
|
2556
|
+
baseUrl: credentials.baseUrl,
|
|
2557
|
+
token: credentials.token,
|
|
2558
|
+
fetcher,
|
|
2559
|
+
});
|
|
2560
|
+
const root = path.resolve(command.dir);
|
|
2561
|
+
const plannedFiles = await planContextFiles({
|
|
2562
|
+
root,
|
|
2563
|
+
files: payload.files,
|
|
2564
|
+
force: command.force,
|
|
2565
|
+
});
|
|
2566
|
+
const conflicts = plannedFiles.filter((file) => file.action === "conflict");
|
|
2567
|
+
const writableFiles = plannedFiles.filter((file) => file.action === "create" || file.action === "overwrite");
|
|
2568
|
+
if (conflicts.length === 0 && !command.dryRun) {
|
|
2569
|
+
for (const file of writableFiles) {
|
|
2570
|
+
await mkdir(path.dirname(file.absolutePath), { recursive: true });
|
|
2571
|
+
await writeFile(file.absolutePath, file.content);
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
const result = {
|
|
2575
|
+
ok: conflicts.length === 0,
|
|
2576
|
+
command: command.command,
|
|
2577
|
+
baseUrl: credentials.baseUrl,
|
|
2578
|
+
authSource: credentials.source,
|
|
2579
|
+
profile: credentials.profileName,
|
|
2580
|
+
artifactId: payload.artifactId ?? command.artifactId,
|
|
2581
|
+
revision: payload.revision,
|
|
2582
|
+
dir: root,
|
|
2583
|
+
dryRun: command.dryRun,
|
|
2584
|
+
force: command.force,
|
|
2585
|
+
summary: {
|
|
2586
|
+
create: plannedFiles.filter((file) => file.action === "create").length,
|
|
2587
|
+
overwrite: plannedFiles.filter((file) => file.action === "overwrite").length,
|
|
2588
|
+
unchanged: plannedFiles.filter((file) => file.action === "unchanged").length,
|
|
2589
|
+
conflict: conflicts.length,
|
|
2590
|
+
},
|
|
2591
|
+
files: plannedFiles.map((file) => ({
|
|
2592
|
+
path: file.path,
|
|
2593
|
+
sha256: file.sha256,
|
|
2594
|
+
action: file.action,
|
|
2595
|
+
})),
|
|
2596
|
+
};
|
|
2597
|
+
if (command.json) {
|
|
2598
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
2599
|
+
}
|
|
2600
|
+
else if (conflicts.length > 0) {
|
|
2601
|
+
io.stdout([
|
|
2602
|
+
`context_revision: ${payload.revision}`,
|
|
2603
|
+
`conflicts: ${conflicts.length}`,
|
|
2604
|
+
...conflicts.map((file) => `conflict: ${file.path}`),
|
|
2605
|
+
"next: rerun with --force to overwrite managed context files.",
|
|
2606
|
+
].join("\n"));
|
|
2607
|
+
}
|
|
2608
|
+
else {
|
|
2609
|
+
io.stdout([
|
|
2610
|
+
`context_revision: ${payload.revision}`,
|
|
2611
|
+
`created: ${result.summary.create}`,
|
|
2612
|
+
`overwritten: ${result.summary.overwrite}`,
|
|
2613
|
+
`unchanged: ${result.summary.unchanged}`,
|
|
2614
|
+
command.dryRun ? "dry_run: true" : `dir: ${root}`,
|
|
2615
|
+
].join("\n"));
|
|
2616
|
+
}
|
|
2617
|
+
return conflicts.length === 0 ? 0 : 1;
|
|
2618
|
+
}
|
|
2619
|
+
async function runContextStatus(command, io, runtime) {
|
|
2620
|
+
const credentials = await resolveApiCredentials({
|
|
2621
|
+
baseUrl: command.baseUrl,
|
|
2622
|
+
flagToken: command.flagToken,
|
|
2623
|
+
profileName: command.profileName,
|
|
2624
|
+
env: runtime.env ?? process.env,
|
|
2625
|
+
});
|
|
2626
|
+
const payload = await requestAgentContext({
|
|
2627
|
+
artifactId: command.artifactId,
|
|
2628
|
+
baseUrl: credentials.baseUrl,
|
|
2629
|
+
token: credentials.token,
|
|
2630
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
2631
|
+
});
|
|
2632
|
+
const root = path.resolve(command.dir);
|
|
2633
|
+
const plannedFiles = await planContextFiles({
|
|
2634
|
+
root,
|
|
2635
|
+
files: payload.files,
|
|
2636
|
+
force: false,
|
|
2637
|
+
});
|
|
2638
|
+
const summary = {
|
|
2639
|
+
create: plannedFiles.filter((file) => file.action === "create").length,
|
|
2640
|
+
overwrite: 0,
|
|
2641
|
+
unchanged: plannedFiles.filter((file) => file.action === "unchanged").length,
|
|
2642
|
+
conflict: plannedFiles.filter((file) => file.action === "conflict").length,
|
|
2643
|
+
};
|
|
2644
|
+
const stale = summary.create > 0 || summary.conflict > 0;
|
|
2645
|
+
const result = {
|
|
2646
|
+
ok: true,
|
|
2647
|
+
command: command.command,
|
|
2648
|
+
baseUrl: credentials.baseUrl,
|
|
2649
|
+
authSource: credentials.source,
|
|
2650
|
+
profile: credentials.profileName,
|
|
2651
|
+
artifactId: payload.artifactId ?? command.artifactId,
|
|
2652
|
+
revision: payload.revision,
|
|
2653
|
+
dir: root,
|
|
2654
|
+
status: stale ? "stale" : "up_to_date",
|
|
2655
|
+
stale,
|
|
2656
|
+
summary,
|
|
2657
|
+
files: plannedFiles.map((file) => ({
|
|
2658
|
+
path: file.path,
|
|
2659
|
+
sha256: file.sha256,
|
|
2660
|
+
action: file.action,
|
|
2661
|
+
})),
|
|
2662
|
+
};
|
|
2663
|
+
if (command.json) {
|
|
2664
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
2665
|
+
}
|
|
2666
|
+
else {
|
|
2667
|
+
io.stdout([
|
|
2668
|
+
`context_revision: ${payload.revision}`,
|
|
2669
|
+
`status: ${result.status}`,
|
|
2670
|
+
`missing: ${summary.create}`,
|
|
2671
|
+
`conflicts: ${summary.conflict}`,
|
|
2672
|
+
].join("\n"));
|
|
2673
|
+
}
|
|
2674
|
+
return stale ? 1 : 0;
|
|
2675
|
+
}
|
|
2676
|
+
async function runArtifactsList(command, io, runtime) {
|
|
2677
|
+
const credentials = await resolveApiCredentials({
|
|
2678
|
+
baseUrl: command.baseUrl,
|
|
2679
|
+
flagToken: command.flagToken,
|
|
2680
|
+
profileName: command.profileName,
|
|
2681
|
+
env: runtime.env ?? process.env,
|
|
2682
|
+
});
|
|
2683
|
+
const fetcher = runtime.fetcher ?? fetch;
|
|
2684
|
+
const response = await fetcher(`${credentials.baseUrl}/api/v1/artifacts`, {
|
|
2685
|
+
headers: {
|
|
2686
|
+
authorization: `Bearer ${credentials.token}`,
|
|
2687
|
+
},
|
|
2688
|
+
});
|
|
2689
|
+
const payload = await parseJsonResponse(response);
|
|
2690
|
+
if (!response.ok || !payload?.ok || !payload.artifacts) {
|
|
2691
|
+
throw new Error(payload?.message ??
|
|
2692
|
+
payload?.reasonCode ??
|
|
2693
|
+
`Artifact list request failed with HTTP ${response.status}.`);
|
|
2694
|
+
}
|
|
2695
|
+
const result = {
|
|
2696
|
+
ok: true,
|
|
2697
|
+
command: command.command,
|
|
2698
|
+
baseUrl: credentials.baseUrl,
|
|
2699
|
+
authSource: credentials.source,
|
|
2700
|
+
profile: credentials.profileName,
|
|
2701
|
+
tenantId: payload.tenantId ?? null,
|
|
2702
|
+
artifactBoundary: payload.artifactBoundary ?? null,
|
|
2703
|
+
artifacts: payload.artifacts,
|
|
2704
|
+
};
|
|
2705
|
+
if (command.json) {
|
|
2706
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
2707
|
+
}
|
|
2708
|
+
else if (payload.artifacts.length === 0) {
|
|
2709
|
+
io.stdout("No artifacts found.");
|
|
2710
|
+
}
|
|
2711
|
+
else {
|
|
2712
|
+
io.stdout(payload.artifacts
|
|
2713
|
+
.map((artifact) => [
|
|
2714
|
+
artifact.artifactId,
|
|
2715
|
+
artifact.name,
|
|
2716
|
+
artifact.status,
|
|
2717
|
+
artifact.previewUrl ?? "",
|
|
2718
|
+
].join("\t"))
|
|
2719
|
+
.join("\n"));
|
|
2720
|
+
}
|
|
2721
|
+
return 0;
|
|
2722
|
+
}
|
|
2723
|
+
async function runArtifactsCreate(command, io, runtime) {
|
|
2724
|
+
const credentials = await resolveApiCredentials({
|
|
2725
|
+
baseUrl: command.baseUrl,
|
|
2726
|
+
flagToken: command.flagToken,
|
|
2727
|
+
profileName: command.profileName,
|
|
2728
|
+
env: runtime.env ?? process.env,
|
|
2729
|
+
});
|
|
2730
|
+
const fetcher = runtime.fetcher ?? fetch;
|
|
2731
|
+
const body = {
|
|
2732
|
+
name: command.name,
|
|
2733
|
+
};
|
|
2734
|
+
if (command.targetPackId) {
|
|
2735
|
+
body.targetPackId = command.targetPackId;
|
|
2736
|
+
}
|
|
2737
|
+
if (command.targetPackVersion) {
|
|
2738
|
+
body.targetPackVersion = command.targetPackVersion;
|
|
2739
|
+
}
|
|
2740
|
+
const response = await fetcher(`${credentials.baseUrl}/api/v1/artifacts`, {
|
|
2741
|
+
method: "POST",
|
|
2742
|
+
headers: {
|
|
2743
|
+
authorization: `Bearer ${credentials.token}`,
|
|
2744
|
+
"content-type": "application/json",
|
|
2745
|
+
},
|
|
2746
|
+
body: JSON.stringify(body),
|
|
2747
|
+
});
|
|
2748
|
+
const payload = await parseJsonResponse(response);
|
|
2749
|
+
if (!response.ok || !payload?.ok || !payload.artifact) {
|
|
2750
|
+
throw new Error(payload?.message ??
|
|
2751
|
+
payload?.reasonCode ??
|
|
2752
|
+
`Artifact create request failed with HTTP ${response.status}.`);
|
|
2753
|
+
}
|
|
2754
|
+
const result = {
|
|
2755
|
+
ok: true,
|
|
2756
|
+
command: command.command,
|
|
2757
|
+
baseUrl: credentials.baseUrl,
|
|
2758
|
+
authSource: credentials.source,
|
|
2759
|
+
profile: credentials.profileName,
|
|
2760
|
+
tenantId: payload.tenantId ?? null,
|
|
2761
|
+
artifact: payload.artifact,
|
|
2762
|
+
next: {
|
|
2763
|
+
contextFetch: `tender artifacts context fetch ${payload.artifact.artifactId} --dir .`,
|
|
2764
|
+
gitSetup: `tender artifacts git setup ${payload.artifact.artifactId} --dir .`,
|
|
2765
|
+
},
|
|
2766
|
+
};
|
|
2767
|
+
if (command.json) {
|
|
2768
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
2769
|
+
}
|
|
2770
|
+
else {
|
|
2771
|
+
io.stdout([
|
|
2772
|
+
`artifact_id: ${payload.artifact.artifactId}`,
|
|
2773
|
+
`name: ${payload.artifact.name}`,
|
|
2774
|
+
`preview_url: ${payload.artifact.previewUrl ?? ""}`,
|
|
2775
|
+
`next: ${result.next.contextFetch}`,
|
|
2776
|
+
].join("\n"));
|
|
2777
|
+
}
|
|
2778
|
+
return 0;
|
|
2779
|
+
}
|
|
2780
|
+
async function runArtifactsUpdate(command, io, runtime) {
|
|
2781
|
+
const credentials = await resolveApiCredentials({
|
|
2782
|
+
baseUrl: command.baseUrl,
|
|
2783
|
+
flagToken: command.flagToken,
|
|
2784
|
+
profileName: command.profileName,
|
|
2785
|
+
env: runtime.env ?? process.env,
|
|
2786
|
+
});
|
|
2787
|
+
const fetcher = runtime.fetcher ?? fetch;
|
|
2788
|
+
const response = await fetcher(`${credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(command.artifactId)}`, {
|
|
2789
|
+
method: "PATCH",
|
|
2790
|
+
headers: {
|
|
2791
|
+
authorization: `Bearer ${credentials.token}`,
|
|
2792
|
+
"content-type": "application/json",
|
|
2793
|
+
},
|
|
2794
|
+
body: JSON.stringify({ name: command.name }),
|
|
2795
|
+
});
|
|
2796
|
+
const payload = await parseJsonResponse(response);
|
|
2797
|
+
if (!response.ok || !payload?.ok || !payload.artifact) {
|
|
2798
|
+
throw new Error(payload?.message ??
|
|
2799
|
+
payload?.reasonCode ??
|
|
2800
|
+
`Artifact update request failed with HTTP ${response.status}.`);
|
|
2801
|
+
}
|
|
2802
|
+
const result = {
|
|
2803
|
+
ok: true,
|
|
2804
|
+
command: command.command,
|
|
2805
|
+
baseUrl: credentials.baseUrl,
|
|
2806
|
+
authSource: credentials.source,
|
|
2807
|
+
profile: credentials.profileName,
|
|
2808
|
+
tenantId: payload.tenantId ?? null,
|
|
2809
|
+
artifact: payload.artifact,
|
|
2810
|
+
};
|
|
2811
|
+
if (command.json) {
|
|
2812
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
2813
|
+
}
|
|
2814
|
+
else {
|
|
2815
|
+
io.stdout([
|
|
2816
|
+
`artifact_id: ${payload.artifact.artifactId}`,
|
|
2817
|
+
`name: ${payload.artifact.name}`,
|
|
2818
|
+
].join("\n"));
|
|
2819
|
+
}
|
|
2820
|
+
return 0;
|
|
2821
|
+
}
|
|
2822
|
+
function defaultCommandRunner(command, args, options) {
|
|
2823
|
+
return new Promise((resolve, reject) => {
|
|
2824
|
+
const child = spawn(command, args, {
|
|
2825
|
+
cwd: options.cwd,
|
|
2826
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2827
|
+
});
|
|
2828
|
+
const stdout = [];
|
|
2829
|
+
const stderr = [];
|
|
2830
|
+
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
2831
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
2832
|
+
child.on("error", reject);
|
|
2833
|
+
child.on("close", (exitCode) => {
|
|
2834
|
+
resolve({
|
|
2835
|
+
exitCode: exitCode ?? 1,
|
|
2836
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
2837
|
+
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
2838
|
+
});
|
|
2839
|
+
});
|
|
2840
|
+
});
|
|
2841
|
+
}
|
|
2842
|
+
async function runGitCommand(input) {
|
|
2843
|
+
const runner = input.runtime.commandRunner ?? defaultCommandRunner;
|
|
2844
|
+
const result = await runner("git", input.args, { cwd: input.cwd });
|
|
2845
|
+
if (!input.allowFailure && result.exitCode !== 0) {
|
|
2846
|
+
throw new Error([
|
|
2847
|
+
`git ${input.args.join(" ")} failed with exit code ${result.exitCode}.`,
|
|
2848
|
+
result.stderr.trim() || result.stdout.trim(),
|
|
2849
|
+
]
|
|
2850
|
+
.filter(Boolean)
|
|
2851
|
+
.join("\n"));
|
|
2852
|
+
}
|
|
2853
|
+
return result;
|
|
2854
|
+
}
|
|
2855
|
+
function shellQuote(value) {
|
|
2856
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
2857
|
+
}
|
|
2858
|
+
function createCredentialHelperCommand(input) {
|
|
2859
|
+
const parts = [
|
|
2860
|
+
"tender",
|
|
2861
|
+
"artifacts",
|
|
2862
|
+
"git",
|
|
2863
|
+
"credential",
|
|
2864
|
+
shellQuote(input.artifactId),
|
|
2865
|
+
"--base-url",
|
|
2866
|
+
shellQuote(input.baseUrl),
|
|
2867
|
+
"--ttl",
|
|
2868
|
+
shellQuote(input.ttl),
|
|
2869
|
+
"--remote-url",
|
|
2870
|
+
shellQuote(input.remoteUrl),
|
|
2871
|
+
];
|
|
2872
|
+
if (input.profileName) {
|
|
2873
|
+
parts.push("--profile", shellQuote(input.profileName));
|
|
2874
|
+
}
|
|
2875
|
+
return `!${parts.join(" ")}`;
|
|
2876
|
+
}
|
|
2877
|
+
function createGitSetupCommandPlan(input) {
|
|
2878
|
+
return [
|
|
2879
|
+
["git", "rev-parse", "--is-inside-work-tree"],
|
|
2880
|
+
["git", "remote", "get-url", input.remoteName],
|
|
2881
|
+
["git", "remote", "add-or-set-url", input.remoteName, input.remoteUrl],
|
|
2882
|
+
[
|
|
2883
|
+
"git",
|
|
2884
|
+
"config",
|
|
2885
|
+
"--local",
|
|
2886
|
+
"--unset-all",
|
|
2887
|
+
"credential.helper",
|
|
2888
|
+
],
|
|
2889
|
+
[
|
|
2890
|
+
"git",
|
|
2891
|
+
"config",
|
|
2892
|
+
"--local",
|
|
2893
|
+
"--add",
|
|
2894
|
+
"credential.helper",
|
|
2895
|
+
"",
|
|
2896
|
+
],
|
|
2897
|
+
[
|
|
2898
|
+
"git",
|
|
2899
|
+
"config",
|
|
2900
|
+
"--local",
|
|
2901
|
+
"--add",
|
|
2902
|
+
"credential.helper",
|
|
2903
|
+
input.credentialHelper,
|
|
2904
|
+
],
|
|
2905
|
+
["git", "config", "--local", "credential.useHttpPath", "true"],
|
|
2906
|
+
];
|
|
2907
|
+
}
|
|
2908
|
+
async function applyGitSetup(input) {
|
|
2909
|
+
await runGitCommand({
|
|
2910
|
+
runtime: input.runtime,
|
|
2911
|
+
cwd: input.root,
|
|
2912
|
+
args: ["rev-parse", "--is-inside-work-tree"],
|
|
2913
|
+
});
|
|
2914
|
+
const existingRemote = await runGitCommand({
|
|
2915
|
+
runtime: input.runtime,
|
|
2916
|
+
cwd: input.root,
|
|
2917
|
+
args: ["remote", "get-url", input.remoteName],
|
|
2918
|
+
allowFailure: true,
|
|
2919
|
+
});
|
|
2920
|
+
if (existingRemote.exitCode === 0) {
|
|
2921
|
+
await runGitCommand({
|
|
2922
|
+
runtime: input.runtime,
|
|
2923
|
+
cwd: input.root,
|
|
2924
|
+
args: ["remote", "set-url", input.remoteName, input.remoteUrl],
|
|
2925
|
+
});
|
|
2926
|
+
}
|
|
2927
|
+
else {
|
|
2928
|
+
await runGitCommand({
|
|
2929
|
+
runtime: input.runtime,
|
|
2930
|
+
cwd: input.root,
|
|
2931
|
+
args: ["remote", "add", input.remoteName, input.remoteUrl],
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
await runGitCommand({
|
|
2935
|
+
runtime: input.runtime,
|
|
2936
|
+
cwd: input.root,
|
|
2937
|
+
args: ["config", "--local", "--unset-all", "credential.helper"],
|
|
2938
|
+
allowFailure: true,
|
|
2939
|
+
});
|
|
2940
|
+
await runGitCommand({
|
|
2941
|
+
runtime: input.runtime,
|
|
2942
|
+
cwd: input.root,
|
|
2943
|
+
args: ["config", "--local", "--add", "credential.helper", ""],
|
|
2944
|
+
});
|
|
2945
|
+
await runGitCommand({
|
|
2946
|
+
runtime: input.runtime,
|
|
2947
|
+
cwd: input.root,
|
|
2948
|
+
args: [
|
|
2949
|
+
"config",
|
|
2950
|
+
"--local",
|
|
2951
|
+
"--add",
|
|
2952
|
+
"credential.helper",
|
|
2953
|
+
input.credentialHelper,
|
|
2954
|
+
],
|
|
2955
|
+
});
|
|
2956
|
+
await runGitCommand({
|
|
2957
|
+
runtime: input.runtime,
|
|
2958
|
+
cwd: input.root,
|
|
2959
|
+
args: ["config", "--local", "credential.useHttpPath", "true"],
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
async function maybeCheckoutInitialArtifactSource(input) {
|
|
2963
|
+
const localHead = await runGitCommand({
|
|
2964
|
+
runtime: input.runtime,
|
|
2965
|
+
cwd: input.root,
|
|
2966
|
+
args: ["rev-parse", "--verify", "HEAD"],
|
|
2967
|
+
allowFailure: true,
|
|
2968
|
+
});
|
|
2969
|
+
if (localHead.exitCode === 0) {
|
|
2970
|
+
return {
|
|
2971
|
+
attempted: false,
|
|
2972
|
+
checkedOut: false,
|
|
2973
|
+
skippedReason: "local_head_exists",
|
|
2974
|
+
fetchExitCode: null,
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
const fetchResult = await runGitCommand({
|
|
2978
|
+
runtime: input.runtime,
|
|
2979
|
+
cwd: input.root,
|
|
2980
|
+
args: ["fetch", input.remoteName, input.defaultBranch],
|
|
2981
|
+
allowFailure: true,
|
|
2982
|
+
});
|
|
2983
|
+
if (fetchResult.exitCode !== 0) {
|
|
2984
|
+
const missingDefaultBranch = isMissingRemoteBranchFetchFailure(fetchResult);
|
|
2985
|
+
return {
|
|
2986
|
+
attempted: true,
|
|
2987
|
+
checkedOut: false,
|
|
2988
|
+
skippedReason: missingDefaultBranch
|
|
2989
|
+
? "remote_default_branch_missing"
|
|
2990
|
+
: "remote_unavailable",
|
|
2991
|
+
fetchExitCode: fetchResult.exitCode,
|
|
2992
|
+
};
|
|
2993
|
+
}
|
|
2994
|
+
await runGitCommand({
|
|
2995
|
+
runtime: input.runtime,
|
|
2996
|
+
cwd: input.root,
|
|
2997
|
+
args: ["checkout", "-B", input.defaultBranch, "FETCH_HEAD"],
|
|
2998
|
+
});
|
|
2999
|
+
return {
|
|
3000
|
+
attempted: true,
|
|
3001
|
+
checkedOut: true,
|
|
3002
|
+
skippedReason: null,
|
|
3003
|
+
fetchExitCode: fetchResult.exitCode,
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
function isMissingRemoteBranchFetchFailure(result) {
|
|
3007
|
+
const output = `${result.stdout}\n${result.stderr}`.toLowerCase();
|
|
3008
|
+
return (output.includes("couldn't find remote ref") ||
|
|
3009
|
+
output.includes("could not find remote ref") ||
|
|
3010
|
+
(output.includes("remote ref") && output.includes("not found")) ||
|
|
3011
|
+
output.includes("repository is empty"));
|
|
3012
|
+
}
|
|
3013
|
+
async function maybeSeedInitialArtifactGitCommit(input) {
|
|
3014
|
+
if (input.sourceCheckout.skippedReason === "local_head_exists") {
|
|
3015
|
+
return {
|
|
3016
|
+
attempted: false,
|
|
3017
|
+
committed: false,
|
|
3018
|
+
pushed: false,
|
|
3019
|
+
skippedReason: "local_head_exists",
|
|
3020
|
+
commitExitCode: null,
|
|
3021
|
+
pushExitCode: null,
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
if (input.sourceCheckout.checkedOut) {
|
|
3025
|
+
return {
|
|
3026
|
+
attempted: false,
|
|
3027
|
+
committed: false,
|
|
3028
|
+
pushed: false,
|
|
3029
|
+
skippedReason: "source_checked_out",
|
|
3030
|
+
commitExitCode: null,
|
|
3031
|
+
pushExitCode: null,
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
if (input.sourceCheckout.skippedReason !== "remote_default_branch_missing") {
|
|
3035
|
+
return {
|
|
3036
|
+
attempted: false,
|
|
3037
|
+
committed: false,
|
|
3038
|
+
pushed: false,
|
|
3039
|
+
skippedReason: "remote_unavailable",
|
|
3040
|
+
commitExitCode: null,
|
|
3041
|
+
pushExitCode: null,
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
const paths = [...new Set(input.paths)].sort();
|
|
3045
|
+
if (paths.length === 0) {
|
|
3046
|
+
return {
|
|
3047
|
+
attempted: false,
|
|
3048
|
+
committed: false,
|
|
3049
|
+
pushed: false,
|
|
3050
|
+
skippedReason: "no_files_to_seed",
|
|
3051
|
+
commitExitCode: null,
|
|
3052
|
+
pushExitCode: null,
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
await runGitCommand({
|
|
3056
|
+
runtime: input.runtime,
|
|
3057
|
+
cwd: input.root,
|
|
3058
|
+
args: ["add", "--", ...paths],
|
|
3059
|
+
});
|
|
3060
|
+
const commitResult = await runGitCommand({
|
|
3061
|
+
runtime: input.runtime,
|
|
3062
|
+
cwd: input.root,
|
|
3063
|
+
args: ["commit", "-m", "Seed Tender App scaffold"],
|
|
3064
|
+
});
|
|
3065
|
+
const pushResult = await runGitCommand({
|
|
3066
|
+
runtime: input.runtime,
|
|
3067
|
+
cwd: input.root,
|
|
3068
|
+
args: ["push", input.remoteName, input.defaultBranch],
|
|
3069
|
+
});
|
|
3070
|
+
return {
|
|
3071
|
+
attempted: true,
|
|
3072
|
+
committed: true,
|
|
3073
|
+
pushed: true,
|
|
3074
|
+
skippedReason: null,
|
|
3075
|
+
commitExitCode: commitResult.exitCode,
|
|
3076
|
+
pushExitCode: pushResult.exitCode,
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
async function requestGitRemoteLease(input) {
|
|
3080
|
+
const url = `${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/git/remote?ttl=${encodeURIComponent(input.ttl)}`;
|
|
3081
|
+
const response = await input.fetcher(url, {
|
|
3082
|
+
headers: {
|
|
3083
|
+
authorization: `Bearer ${input.token}`,
|
|
3084
|
+
},
|
|
3085
|
+
});
|
|
3086
|
+
const payload = await parseJsonResponse(response);
|
|
3087
|
+
if (!response.ok || !payload?.ok || !payload.git) {
|
|
3088
|
+
throw new Error(payload?.message ??
|
|
3089
|
+
payload?.reasonCode ??
|
|
3090
|
+
`Artifact Git remote request failed with HTTP ${response.status}.`);
|
|
3091
|
+
}
|
|
3092
|
+
return payload.git;
|
|
3093
|
+
}
|
|
3094
|
+
function parseCredentialInput(stdin) {
|
|
3095
|
+
const values = new Map();
|
|
3096
|
+
for (const line of stdin.split(/\r?\n/)) {
|
|
3097
|
+
const separator = line.indexOf("=");
|
|
3098
|
+
if (separator <= 0) {
|
|
3099
|
+
continue;
|
|
3100
|
+
}
|
|
3101
|
+
values.set(line.slice(0, separator), line.slice(separator + 1));
|
|
3102
|
+
}
|
|
3103
|
+
return values;
|
|
3104
|
+
}
|
|
3105
|
+
function credentialRequestMatchesRemote(input) {
|
|
3106
|
+
if (!input.remoteUrl) {
|
|
3107
|
+
return true;
|
|
3108
|
+
}
|
|
3109
|
+
let expected;
|
|
3110
|
+
try {
|
|
3111
|
+
expected = new URL(input.remoteUrl);
|
|
3112
|
+
}
|
|
3113
|
+
catch {
|
|
3114
|
+
return false;
|
|
3115
|
+
}
|
|
3116
|
+
const credential = parseCredentialInput(input.stdin);
|
|
3117
|
+
const protocol = credential.get("protocol");
|
|
3118
|
+
const host = credential.get("host");
|
|
3119
|
+
const credentialPath = credential.get("path")?.replace(/^\/+/, "");
|
|
3120
|
+
const expectedPath = expected.pathname.replace(/^\/+/, "");
|
|
3121
|
+
if (protocol && protocol !== expected.protocol.replace(/:$/, "")) {
|
|
3122
|
+
return false;
|
|
3123
|
+
}
|
|
3124
|
+
if (host && host !== expected.host) {
|
|
3125
|
+
return false;
|
|
3126
|
+
}
|
|
3127
|
+
if (credentialPath && credentialPath !== expectedPath) {
|
|
3128
|
+
return false;
|
|
3129
|
+
}
|
|
3130
|
+
return Boolean(protocol || host || credentialPath);
|
|
3131
|
+
}
|
|
3132
|
+
async function runGitSetup(command, io, runtime) {
|
|
3133
|
+
const credentials = await resolveApiCredentials({
|
|
3134
|
+
baseUrl: command.baseUrl,
|
|
3135
|
+
flagToken: command.flagToken,
|
|
3136
|
+
profileName: command.profileName,
|
|
3137
|
+
env: runtime.env ?? process.env,
|
|
3138
|
+
});
|
|
3139
|
+
const git = await requestGitRemoteLease({
|
|
3140
|
+
artifactId: command.artifactId,
|
|
3141
|
+
ttl: command.ttl,
|
|
3142
|
+
baseUrl: credentials.baseUrl,
|
|
3143
|
+
token: credentials.token,
|
|
3144
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
3145
|
+
});
|
|
3146
|
+
const root = path.resolve(command.dir);
|
|
3147
|
+
const helper = createCredentialHelperCommand({
|
|
3148
|
+
artifactId: command.artifactId,
|
|
3149
|
+
baseUrl: credentials.baseUrl,
|
|
3150
|
+
profileName: credentials.profileName,
|
|
3151
|
+
ttl: command.ttl,
|
|
3152
|
+
remoteUrl: git.remoteUrl,
|
|
3153
|
+
});
|
|
3154
|
+
const plannedCommands = createGitSetupCommandPlan({
|
|
3155
|
+
remoteName: command.remoteName,
|
|
3156
|
+
remoteUrl: git.remoteUrl,
|
|
3157
|
+
credentialHelper: helper,
|
|
3158
|
+
});
|
|
3159
|
+
if (!command.dryRun) {
|
|
3160
|
+
await applyGitSetup({
|
|
3161
|
+
runtime,
|
|
3162
|
+
root,
|
|
3163
|
+
remoteName: command.remoteName,
|
|
3164
|
+
remoteUrl: git.remoteUrl,
|
|
3165
|
+
credentialHelper: helper,
|
|
3166
|
+
});
|
|
3167
|
+
}
|
|
3168
|
+
const result = {
|
|
3169
|
+
ok: true,
|
|
3170
|
+
command: command.command,
|
|
3171
|
+
artifactId: command.artifactId,
|
|
3172
|
+
dir: root,
|
|
3173
|
+
remoteName: command.remoteName,
|
|
3174
|
+
remoteUrl: git.remoteUrl,
|
|
3175
|
+
defaultBranch: git.defaultBranch,
|
|
3176
|
+
scope: git.scope,
|
|
3177
|
+
ttl: command.ttl,
|
|
3178
|
+
dryRun: command.dryRun,
|
|
3179
|
+
authSource: credentials.source,
|
|
3180
|
+
profile: credentials.profileName,
|
|
3181
|
+
credentialHelper: helper,
|
|
3182
|
+
commands: plannedCommands.map((parts) => parts.join(" ")),
|
|
3183
|
+
next: `git push ${command.remoteName} ${git.defaultBranch}`,
|
|
3184
|
+
};
|
|
3185
|
+
if (command.json) {
|
|
3186
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
3187
|
+
}
|
|
3188
|
+
else {
|
|
3189
|
+
io.stdout([
|
|
3190
|
+
`remote: ${command.remoteName}`,
|
|
3191
|
+
`remote_url: ${git.remoteUrl}`,
|
|
3192
|
+
`default_branch: ${git.defaultBranch}`,
|
|
3193
|
+
`next: ${result.next}`,
|
|
3194
|
+
].join("\n"));
|
|
3195
|
+
}
|
|
3196
|
+
return 0;
|
|
3197
|
+
}
|
|
3198
|
+
async function runGitCredential(command, io, runtime) {
|
|
3199
|
+
if (command.operation !== "get") {
|
|
3200
|
+
return 0;
|
|
3201
|
+
}
|
|
3202
|
+
if (!credentialRequestMatchesRemote({
|
|
3203
|
+
stdin: runtime.stdin ?? "",
|
|
3204
|
+
remoteUrl: command.remoteUrl,
|
|
3205
|
+
})) {
|
|
3206
|
+
if (command.json) {
|
|
3207
|
+
io.stdout(JSON.stringify({ ok: false, reasonCode: "remote_mismatch" }));
|
|
3208
|
+
}
|
|
3209
|
+
return 0;
|
|
3210
|
+
}
|
|
3211
|
+
const credentials = await resolveApiCredentials({
|
|
3212
|
+
baseUrl: command.baseUrl,
|
|
3213
|
+
flagToken: command.flagToken,
|
|
3214
|
+
profileName: command.profileName,
|
|
3215
|
+
env: runtime.env ?? process.env,
|
|
3216
|
+
});
|
|
3217
|
+
const git = await requestGitRemoteLease({
|
|
3218
|
+
artifactId: command.artifactId,
|
|
3219
|
+
ttl: command.ttl,
|
|
3220
|
+
baseUrl: credentials.baseUrl,
|
|
3221
|
+
token: credentials.token,
|
|
3222
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
3223
|
+
});
|
|
3224
|
+
if (command.json) {
|
|
3225
|
+
io.stdout(JSON.stringify({
|
|
3226
|
+
ok: true,
|
|
3227
|
+
command: command.command,
|
|
3228
|
+
artifactId: command.artifactId,
|
|
3229
|
+
remoteUrl: git.remoteUrl,
|
|
3230
|
+
username: git.token?.username ?? git.authentication?.username ?? "tender",
|
|
3231
|
+
password: git.token?.password ??
|
|
3232
|
+
(git.authentication?.mode === "tender-api-token"
|
|
3233
|
+
? credentials.token
|
|
3234
|
+
: ""),
|
|
3235
|
+
expiresAt: git.token?.expiresAt ?? null,
|
|
3236
|
+
}, null, 2));
|
|
3237
|
+
}
|
|
3238
|
+
else {
|
|
3239
|
+
io.stdout([
|
|
3240
|
+
`username=${git.token?.username ?? git.authentication?.username ?? "tender"}`,
|
|
3241
|
+
`password=${git.token?.password ??
|
|
3242
|
+
(git.authentication?.mode === "tender-api-token"
|
|
3243
|
+
? credentials.token
|
|
3244
|
+
: "")}`,
|
|
3245
|
+
].join("\n"));
|
|
3246
|
+
}
|
|
3247
|
+
return 0;
|
|
3248
|
+
}
|
|
3249
|
+
async function runArtifactsInit(command, io, runtime) {
|
|
3250
|
+
const credentials = await resolveApiCredentials({
|
|
3251
|
+
baseUrl: command.baseUrl,
|
|
3252
|
+
flagToken: command.flagToken,
|
|
3253
|
+
profileName: command.profileName,
|
|
3254
|
+
env: runtime.env ?? process.env,
|
|
3255
|
+
});
|
|
3256
|
+
const fetcher = runtime.fetcher ?? fetch;
|
|
3257
|
+
const context = await requestAgentContext({
|
|
3258
|
+
artifactId: command.artifactId,
|
|
3259
|
+
baseUrl: credentials.baseUrl,
|
|
3260
|
+
token: credentials.token,
|
|
3261
|
+
fetcher,
|
|
3262
|
+
});
|
|
3263
|
+
const root = path.resolve(command.dir);
|
|
3264
|
+
const git = await requestGitRemoteLease({
|
|
3265
|
+
artifactId: command.artifactId,
|
|
3266
|
+
ttl: command.ttl,
|
|
3267
|
+
baseUrl: credentials.baseUrl,
|
|
3268
|
+
token: credentials.token,
|
|
3269
|
+
fetcher,
|
|
3270
|
+
});
|
|
3271
|
+
const helper = createCredentialHelperCommand({
|
|
3272
|
+
artifactId: command.artifactId,
|
|
3273
|
+
baseUrl: credentials.baseUrl,
|
|
3274
|
+
profileName: credentials.profileName,
|
|
3275
|
+
ttl: command.ttl,
|
|
3276
|
+
remoteUrl: git.remoteUrl,
|
|
3277
|
+
});
|
|
3278
|
+
const gitCommands = createGitSetupCommandPlan({
|
|
3279
|
+
remoteName: command.remoteName,
|
|
3280
|
+
remoteUrl: git.remoteUrl,
|
|
3281
|
+
credentialHelper: helper,
|
|
3282
|
+
});
|
|
3283
|
+
let sourceCheckout = {
|
|
3284
|
+
attempted: false,
|
|
3285
|
+
checkedOut: false,
|
|
3286
|
+
skippedReason: command.dryRun ? "dry_run" : null,
|
|
3287
|
+
fetchExitCode: null,
|
|
3288
|
+
};
|
|
3289
|
+
let seedCommit = {
|
|
3290
|
+
attempted: false,
|
|
3291
|
+
committed: false,
|
|
3292
|
+
pushed: false,
|
|
3293
|
+
skippedReason: command.dryRun ? "dry_run" : null,
|
|
3294
|
+
commitExitCode: null,
|
|
3295
|
+
pushExitCode: null,
|
|
3296
|
+
};
|
|
3297
|
+
let sourceExport = {
|
|
3298
|
+
attempted: false,
|
|
3299
|
+
filesWritten: 0,
|
|
3300
|
+
skippedReason: command.dryRun ? "dry_run" : null,
|
|
3301
|
+
files: [],
|
|
3302
|
+
};
|
|
3303
|
+
let seedPaths = [];
|
|
3304
|
+
let plannedFiles = await planContextFiles({
|
|
3305
|
+
root,
|
|
3306
|
+
files: context.files,
|
|
3307
|
+
force: command.force,
|
|
3308
|
+
});
|
|
3309
|
+
if (!command.dryRun) {
|
|
3310
|
+
await mkdir(root, { recursive: true });
|
|
3311
|
+
const gitState = await runGitCommand({
|
|
3312
|
+
runtime,
|
|
3313
|
+
cwd: root,
|
|
3314
|
+
args: ["rev-parse", "--is-inside-work-tree"],
|
|
3315
|
+
allowFailure: true,
|
|
3316
|
+
});
|
|
3317
|
+
if (gitState.exitCode !== 0) {
|
|
3318
|
+
await runGitCommand({
|
|
3319
|
+
runtime,
|
|
3320
|
+
cwd: root,
|
|
3321
|
+
args: ["init", "-b", git.defaultBranch],
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
await applyGitSetup({
|
|
3325
|
+
runtime,
|
|
3326
|
+
root,
|
|
3327
|
+
remoteName: command.remoteName,
|
|
3328
|
+
remoteUrl: git.remoteUrl,
|
|
3329
|
+
credentialHelper: helper,
|
|
3330
|
+
});
|
|
3331
|
+
sourceCheckout = await maybeCheckoutInitialArtifactSource({
|
|
3332
|
+
runtime,
|
|
3333
|
+
root,
|
|
3334
|
+
remoteName: command.remoteName,
|
|
3335
|
+
defaultBranch: git.defaultBranch,
|
|
3336
|
+
});
|
|
3337
|
+
if (sourceCheckout.skippedReason === "remote_default_branch_missing") {
|
|
3338
|
+
const sourceFiles = await collectArtifactWorkspaceSourceFiles({
|
|
3339
|
+
artifactId: command.artifactId,
|
|
3340
|
+
baseUrl: credentials.baseUrl,
|
|
3341
|
+
token: credentials.token,
|
|
3342
|
+
fetcher,
|
|
3343
|
+
});
|
|
3344
|
+
const plannedSourceFiles = await planSourceFiles({
|
|
3345
|
+
root,
|
|
3346
|
+
files: sourceFiles,
|
|
3347
|
+
force: command.force,
|
|
3348
|
+
});
|
|
3349
|
+
const sourceConflicts = plannedSourceFiles.filter((file) => file.action === "conflict");
|
|
3350
|
+
sourceExport = {
|
|
3351
|
+
attempted: true,
|
|
3352
|
+
filesWritten: 0,
|
|
3353
|
+
skippedReason: plannedSourceFiles.length === 0 ? "empty_workspace" : null,
|
|
3354
|
+
files: plannedSourceFiles.map((file) => ({
|
|
3355
|
+
path: file.path,
|
|
3356
|
+
action: file.action,
|
|
3357
|
+
})),
|
|
3358
|
+
};
|
|
3359
|
+
if (sourceConflicts.length > 0) {
|
|
3360
|
+
const result = {
|
|
3361
|
+
ok: false,
|
|
3362
|
+
command: command.command,
|
|
3363
|
+
artifactId: command.artifactId,
|
|
3364
|
+
revision: context.revision,
|
|
3365
|
+
dir: root,
|
|
3366
|
+
remoteName: command.remoteName,
|
|
3367
|
+
remoteUrl: git.remoteUrl,
|
|
3368
|
+
defaultBranch: git.defaultBranch,
|
|
3369
|
+
dryRun: command.dryRun,
|
|
3370
|
+
force: command.force,
|
|
3371
|
+
authSource: credentials.source,
|
|
3372
|
+
profile: credentials.profileName,
|
|
3373
|
+
sourceCheckout,
|
|
3374
|
+
sourceExport,
|
|
3375
|
+
seedCommit,
|
|
3376
|
+
summary: {
|
|
3377
|
+
create: plannedFiles.filter((file) => file.action === "create")
|
|
3378
|
+
.length,
|
|
3379
|
+
overwrite: plannedFiles.filter((file) => file.action === "overwrite").length,
|
|
3380
|
+
unchanged: plannedFiles.filter((file) => file.action === "unchanged").length,
|
|
3381
|
+
conflict: 0,
|
|
3382
|
+
sourceConflict: sourceConflicts.length,
|
|
3383
|
+
},
|
|
3384
|
+
files: plannedFiles.map((file) => ({
|
|
3385
|
+
path: file.path,
|
|
3386
|
+
sha256: file.sha256,
|
|
3387
|
+
action: file.action,
|
|
3388
|
+
})),
|
|
3389
|
+
};
|
|
3390
|
+
if (command.json) {
|
|
3391
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
3392
|
+
}
|
|
3393
|
+
else {
|
|
3394
|
+
io.stdout([
|
|
3395
|
+
`context_revision: ${context.revision}`,
|
|
3396
|
+
`source_conflicts: ${sourceConflicts.length}`,
|
|
3397
|
+
...sourceConflicts.map((file) => `conflict: ${file.path}`),
|
|
3398
|
+
"next: rerun with --force to overwrite local source files.",
|
|
3399
|
+
].join("\n"));
|
|
3400
|
+
}
|
|
3401
|
+
return 1;
|
|
3402
|
+
}
|
|
3403
|
+
for (const file of plannedSourceFiles) {
|
|
3404
|
+
if (file.action !== "create" && file.action !== "overwrite") {
|
|
3405
|
+
continue;
|
|
3406
|
+
}
|
|
3407
|
+
await mkdir(path.dirname(file.absolutePath), { recursive: true });
|
|
3408
|
+
await writeFile(file.absolutePath, file.content);
|
|
3409
|
+
seedPaths.push(file.path);
|
|
3410
|
+
}
|
|
3411
|
+
sourceExport = {
|
|
3412
|
+
...sourceExport,
|
|
3413
|
+
filesWritten: seedPaths.length,
|
|
3414
|
+
};
|
|
3415
|
+
}
|
|
3416
|
+
else if (sourceCheckout.checkedOut) {
|
|
3417
|
+
sourceExport = {
|
|
3418
|
+
attempted: false,
|
|
3419
|
+
filesWritten: 0,
|
|
3420
|
+
skippedReason: "source_checked_out",
|
|
3421
|
+
files: [],
|
|
3422
|
+
};
|
|
3423
|
+
}
|
|
3424
|
+
else if (sourceCheckout.skippedReason === "local_head_exists") {
|
|
3425
|
+
sourceExport = {
|
|
3426
|
+
attempted: false,
|
|
3427
|
+
filesWritten: 0,
|
|
3428
|
+
skippedReason: "local_head_exists",
|
|
3429
|
+
files: [],
|
|
3430
|
+
};
|
|
3431
|
+
}
|
|
3432
|
+
else if (sourceCheckout.skippedReason === "remote_unavailable") {
|
|
3433
|
+
sourceExport = {
|
|
3434
|
+
attempted: false,
|
|
3435
|
+
filesWritten: 0,
|
|
3436
|
+
skippedReason: "remote_unavailable",
|
|
3437
|
+
files: [],
|
|
3438
|
+
};
|
|
3439
|
+
}
|
|
3440
|
+
plannedFiles = await planContextFiles({
|
|
3441
|
+
root,
|
|
3442
|
+
files: context.files,
|
|
3443
|
+
force: command.force,
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
const conflicts = plannedFiles.filter((file) => file.action === "conflict");
|
|
3447
|
+
if (conflicts.length > 0) {
|
|
3448
|
+
const result = {
|
|
3449
|
+
ok: false,
|
|
3450
|
+
command: command.command,
|
|
3451
|
+
artifactId: command.artifactId,
|
|
3452
|
+
revision: context.revision,
|
|
3453
|
+
dir: root,
|
|
3454
|
+
remoteName: command.remoteName,
|
|
3455
|
+
remoteUrl: git.remoteUrl,
|
|
3456
|
+
defaultBranch: git.defaultBranch,
|
|
3457
|
+
dryRun: command.dryRun,
|
|
3458
|
+
force: command.force,
|
|
3459
|
+
authSource: credentials.source,
|
|
3460
|
+
profile: credentials.profileName,
|
|
3461
|
+
sourceCheckout,
|
|
3462
|
+
sourceExport,
|
|
3463
|
+
seedCommit,
|
|
3464
|
+
summary: {
|
|
3465
|
+
create: plannedFiles.filter((file) => file.action === "create").length,
|
|
3466
|
+
overwrite: plannedFiles.filter((file) => file.action === "overwrite").length,
|
|
3467
|
+
unchanged: plannedFiles.filter((file) => file.action === "unchanged").length,
|
|
3468
|
+
conflict: conflicts.length,
|
|
3469
|
+
},
|
|
3470
|
+
files: plannedFiles.map((file) => ({
|
|
3471
|
+
path: file.path,
|
|
3472
|
+
sha256: file.sha256,
|
|
3473
|
+
action: file.action,
|
|
3474
|
+
})),
|
|
3475
|
+
};
|
|
3476
|
+
if (command.json) {
|
|
3477
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
3478
|
+
}
|
|
3479
|
+
else {
|
|
3480
|
+
io.stdout([
|
|
3481
|
+
`context_revision: ${context.revision}`,
|
|
3482
|
+
`conflicts: ${conflicts.length}`,
|
|
3483
|
+
...conflicts.map((file) => `conflict: ${file.path}`),
|
|
3484
|
+
"next: rerun with --force to overwrite managed context files.",
|
|
3485
|
+
].join("\n"));
|
|
3486
|
+
}
|
|
3487
|
+
return 1;
|
|
3488
|
+
}
|
|
3489
|
+
if (!command.dryRun) {
|
|
3490
|
+
for (const file of plannedFiles) {
|
|
3491
|
+
if (file.action !== "create" && file.action !== "overwrite") {
|
|
3492
|
+
continue;
|
|
3493
|
+
}
|
|
3494
|
+
await mkdir(path.dirname(file.absolutePath), { recursive: true });
|
|
3495
|
+
await writeFile(file.absolutePath, file.content);
|
|
3496
|
+
seedPaths.push(file.path);
|
|
3497
|
+
}
|
|
3498
|
+
seedCommit = await maybeSeedInitialArtifactGitCommit({
|
|
3499
|
+
runtime,
|
|
3500
|
+
root,
|
|
3501
|
+
remoteName: command.remoteName,
|
|
3502
|
+
defaultBranch: git.defaultBranch,
|
|
3503
|
+
sourceCheckout,
|
|
3504
|
+
paths: seedPaths,
|
|
3505
|
+
});
|
|
3506
|
+
}
|
|
3507
|
+
const result = {
|
|
3508
|
+
ok: true,
|
|
3509
|
+
command: command.command,
|
|
3510
|
+
artifactId: command.artifactId,
|
|
3511
|
+
revision: context.revision,
|
|
3512
|
+
dir: root,
|
|
3513
|
+
remoteName: command.remoteName,
|
|
3514
|
+
remoteUrl: git.remoteUrl,
|
|
3515
|
+
defaultBranch: git.defaultBranch,
|
|
3516
|
+
dryRun: command.dryRun,
|
|
3517
|
+
force: command.force,
|
|
3518
|
+
authSource: credentials.source,
|
|
3519
|
+
profile: credentials.profileName,
|
|
3520
|
+
sourceCheckout,
|
|
3521
|
+
sourceExport,
|
|
3522
|
+
seedCommit,
|
|
3523
|
+
summary: {
|
|
3524
|
+
create: plannedFiles.filter((file) => file.action === "create").length,
|
|
3525
|
+
overwrite: plannedFiles.filter((file) => file.action === "overwrite").length,
|
|
3526
|
+
unchanged: plannedFiles.filter((file) => file.action === "unchanged").length,
|
|
3527
|
+
conflict: 0,
|
|
3528
|
+
},
|
|
3529
|
+
files: plannedFiles.map((file) => ({
|
|
3530
|
+
path: file.path,
|
|
3531
|
+
sha256: file.sha256,
|
|
3532
|
+
action: file.action,
|
|
3533
|
+
})),
|
|
3534
|
+
commands: [
|
|
3535
|
+
`mkdir -p ${root}`,
|
|
3536
|
+
`git init -b ${git.defaultBranch} # if needed`,
|
|
3537
|
+
...gitCommands.map((parts) => parts.join(" ")),
|
|
3538
|
+
`git fetch ${command.remoteName} ${git.defaultBranch} # if no local commits`,
|
|
3539
|
+
`git checkout -B ${git.defaultBranch} FETCH_HEAD # if fetch succeeds`,
|
|
3540
|
+
],
|
|
3541
|
+
next: `git -C ${root} push ${command.remoteName} ${git.defaultBranch}`,
|
|
3542
|
+
};
|
|
3543
|
+
if (command.json) {
|
|
3544
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
3545
|
+
}
|
|
3546
|
+
else {
|
|
3547
|
+
io.stdout([
|
|
3548
|
+
`artifact_id: ${command.artifactId}`,
|
|
3549
|
+
`context_revision: ${context.revision}`,
|
|
3550
|
+
`dir: ${root}`,
|
|
3551
|
+
`remote: ${command.remoteName}`,
|
|
3552
|
+
`next: ${result.next}`,
|
|
3553
|
+
].join("\n"));
|
|
3554
|
+
}
|
|
3555
|
+
return 0;
|
|
3556
|
+
}
|
|
3557
|
+
async function postLifecycle(input) {
|
|
3558
|
+
const credentials = await resolveApiCredentials({
|
|
3559
|
+
baseUrl: input.command.baseUrl,
|
|
3560
|
+
flagToken: input.command.flagToken,
|
|
3561
|
+
profileName: input.command.profileName,
|
|
3562
|
+
env: input.runtime.env ?? process.env,
|
|
3563
|
+
});
|
|
3564
|
+
const fetcher = input.runtime.fetcher ?? fetch;
|
|
3565
|
+
const stream = input.command.stream;
|
|
3566
|
+
const url = `${credentials.baseUrl}${input.path}${stream ? "?stream=1" : ""}`;
|
|
3567
|
+
const requestBody = {};
|
|
3568
|
+
if ("commitSha" in input.command && input.command.commitSha) {
|
|
3569
|
+
requestBody.commitSha = input.command.commitSha;
|
|
3570
|
+
}
|
|
3571
|
+
if ("message" in input.command && input.command.message) {
|
|
3572
|
+
requestBody.message = input.command.message;
|
|
3573
|
+
}
|
|
3574
|
+
const response = await fetcher(url, {
|
|
3575
|
+
method: "POST",
|
|
3576
|
+
headers: {
|
|
3577
|
+
authorization: `Bearer ${credentials.token}`,
|
|
3578
|
+
accept: stream ? "text/event-stream" : "application/json",
|
|
3579
|
+
...(Object.keys(requestBody).length > 0
|
|
3580
|
+
? { "content-type": "application/json" }
|
|
3581
|
+
: {}),
|
|
3582
|
+
},
|
|
3583
|
+
body: Object.keys(requestBody).length > 0
|
|
3584
|
+
? JSON.stringify(requestBody)
|
|
3585
|
+
: undefined,
|
|
3586
|
+
});
|
|
3587
|
+
if (stream) {
|
|
3588
|
+
if (!response.ok) {
|
|
3589
|
+
throw new Error(`Artifact ${input.operation} request failed with HTTP ${response.status}.`);
|
|
3590
|
+
}
|
|
3591
|
+
await emitSseLifecycleStream({
|
|
3592
|
+
response,
|
|
3593
|
+
io: input.io,
|
|
3594
|
+
json: input.command.json,
|
|
3595
|
+
});
|
|
3596
|
+
return 0;
|
|
3597
|
+
}
|
|
3598
|
+
const payload = await parseJsonResponse(response);
|
|
3599
|
+
if (!response.ok || !payload?.ok) {
|
|
3600
|
+
throw new Error(payload?.message ??
|
|
3601
|
+
payload?.reasonCode ??
|
|
3602
|
+
`Artifact ${input.operation} request failed with HTTP ${response.status}.`);
|
|
3603
|
+
}
|
|
3604
|
+
const result = {
|
|
3605
|
+
ok: true,
|
|
3606
|
+
command: input.command.command,
|
|
3607
|
+
baseUrl: credentials.baseUrl,
|
|
3608
|
+
authSource: credentials.source,
|
|
3609
|
+
profile: credentials.profileName,
|
|
3610
|
+
artifactId: input.command.artifactId,
|
|
3611
|
+
operation: payload.operation ?? input.operation,
|
|
3612
|
+
job: payload.job ?? null,
|
|
3613
|
+
result: payload.result ?? null,
|
|
3614
|
+
};
|
|
3615
|
+
if (input.command.json) {
|
|
3616
|
+
input.io.stdout(JSON.stringify(result, null, 2));
|
|
3617
|
+
}
|
|
3618
|
+
else {
|
|
3619
|
+
input.io.stdout([
|
|
3620
|
+
`artifact_id: ${input.command.artifactId}`,
|
|
3621
|
+
`operation: ${result.operation}`,
|
|
3622
|
+
"status: completed",
|
|
3623
|
+
].join("\n"));
|
|
3624
|
+
}
|
|
3625
|
+
return 0;
|
|
3626
|
+
}
|
|
3627
|
+
async function runValidate(command, io, runtime) {
|
|
3628
|
+
return await postLifecycle({
|
|
3629
|
+
command,
|
|
3630
|
+
operation: "validate",
|
|
3631
|
+
path: `/api/v1/artifacts/${encodeURIComponent(command.artifactId)}/validate`,
|
|
3632
|
+
io,
|
|
3633
|
+
runtime,
|
|
3634
|
+
});
|
|
3635
|
+
}
|
|
3636
|
+
async function runBuild(command, io, runtime) {
|
|
3637
|
+
return await postLifecycle({
|
|
3638
|
+
command,
|
|
3639
|
+
operation: "build",
|
|
3640
|
+
path: `/api/v1/artifacts/${encodeURIComponent(command.artifactId)}/build`,
|
|
3641
|
+
io,
|
|
3642
|
+
runtime,
|
|
3643
|
+
});
|
|
3644
|
+
}
|
|
3645
|
+
async function runPreviewRebuild(command, io, runtime) {
|
|
3646
|
+
return await postLifecycle({
|
|
3647
|
+
command,
|
|
3648
|
+
operation: "preview",
|
|
3649
|
+
path: `/api/v1/artifacts/${encodeURIComponent(command.artifactId)}/preview/rebuild`,
|
|
3650
|
+
io,
|
|
3651
|
+
runtime,
|
|
3652
|
+
});
|
|
3653
|
+
}
|
|
3654
|
+
async function requestPreviewStatus(input) {
|
|
3655
|
+
if (input.command.jobId) {
|
|
3656
|
+
const response = await input.fetcher(`${input.credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.command.artifactId)}/jobs/${encodeURIComponent(input.command.jobId)}`, {
|
|
3657
|
+
headers: {
|
|
3658
|
+
authorization: `Bearer ${input.credentials.token}`,
|
|
3659
|
+
},
|
|
3660
|
+
});
|
|
3661
|
+
const payload = await parseJsonResponse(response);
|
|
3662
|
+
if (!response.ok || !payload?.ok || !payload.job) {
|
|
3663
|
+
throw new Error(payload?.message ??
|
|
3664
|
+
payload?.reasonCode ??
|
|
3665
|
+
`Preview job lookup failed with HTTP ${response.status}.`);
|
|
3666
|
+
}
|
|
3667
|
+
return {
|
|
3668
|
+
job: payload.job,
|
|
3669
|
+
preview: {
|
|
3670
|
+
artifactId: input.command.artifactId,
|
|
3671
|
+
commitSha: null,
|
|
3672
|
+
status: payload.job.status,
|
|
3673
|
+
previewUrl: readPreviewUrlFromJob(payload.job),
|
|
3674
|
+
diagnostics: [],
|
|
3675
|
+
job: payload.job,
|
|
3676
|
+
},
|
|
3677
|
+
};
|
|
3678
|
+
}
|
|
3679
|
+
const params = new URLSearchParams({ limit: "1" });
|
|
3680
|
+
if (input.command.commitSha) {
|
|
3681
|
+
params.set("commit", input.command.commitSha);
|
|
3682
|
+
}
|
|
3683
|
+
const response = await input.fetcher(`${input.credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.command.artifactId)}/previews?${params.toString()}`, {
|
|
3684
|
+
headers: {
|
|
3685
|
+
authorization: `Bearer ${input.credentials.token}`,
|
|
3686
|
+
},
|
|
3687
|
+
});
|
|
3688
|
+
const payload = await parseJsonResponse(response);
|
|
3689
|
+
if (!response.ok || !payload?.ok) {
|
|
3690
|
+
throw new Error(payload?.message ??
|
|
3691
|
+
payload?.reasonCode ??
|
|
3692
|
+
`Preview status lookup failed with HTTP ${response.status}.`);
|
|
3693
|
+
}
|
|
3694
|
+
const preview = payload.previews?.[0] ?? null;
|
|
3695
|
+
if (!preview?.job) {
|
|
3696
|
+
throw new Error(input.command.commitSha
|
|
3697
|
+
? `No preview job found for commit ${input.command.commitSha}.`
|
|
3698
|
+
: "No preview job found for this artifact.");
|
|
3699
|
+
}
|
|
3700
|
+
return {
|
|
3701
|
+
job: preview.job,
|
|
3702
|
+
preview,
|
|
3703
|
+
};
|
|
3704
|
+
}
|
|
3705
|
+
async function runPreviewStatus(command, io, runtime) {
|
|
3706
|
+
const credentials = await resolveApiCredentials({
|
|
3707
|
+
baseUrl: command.baseUrl,
|
|
3708
|
+
flagToken: command.flagToken,
|
|
3709
|
+
profileName: command.profileName,
|
|
3710
|
+
env: runtime.env ?? process.env,
|
|
3711
|
+
});
|
|
3712
|
+
const { job, preview } = await requestPreviewStatus({
|
|
3713
|
+
command,
|
|
3714
|
+
credentials,
|
|
3715
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
3716
|
+
});
|
|
3717
|
+
const result = {
|
|
3718
|
+
ok: true,
|
|
3719
|
+
command: command.command,
|
|
3720
|
+
baseUrl: credentials.baseUrl,
|
|
3721
|
+
authSource: credentials.source,
|
|
3722
|
+
profile: credentials.profileName,
|
|
3723
|
+
artifactId: command.artifactId,
|
|
3724
|
+
commitSha: preview.commitSha ?? command.commitSha,
|
|
3725
|
+
status: preview.status ?? job.status ?? null,
|
|
3726
|
+
previewUrl: preview.previewUrl ?? readPreviewUrlFromJob(job),
|
|
3727
|
+
job,
|
|
3728
|
+
};
|
|
3729
|
+
if (command.json) {
|
|
3730
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
3731
|
+
}
|
|
3732
|
+
else {
|
|
3733
|
+
io.stdout([
|
|
3734
|
+
`artifact_id: ${command.artifactId}`,
|
|
3735
|
+
`job_id: ${job.jobId}`,
|
|
3736
|
+
`status: ${result.status ?? "unknown"}`,
|
|
3737
|
+
...(result.previewUrl ? [`preview_url: ${result.previewUrl}`] : []),
|
|
3738
|
+
].join("\n"));
|
|
3739
|
+
}
|
|
3740
|
+
return 0;
|
|
3741
|
+
}
|
|
3742
|
+
async function runPreviewWatch(command, io, runtime) {
|
|
3743
|
+
const credentials = await resolveApiCredentials({
|
|
3744
|
+
baseUrl: command.baseUrl,
|
|
3745
|
+
flagToken: command.flagToken,
|
|
3746
|
+
profileName: command.profileName,
|
|
3747
|
+
env: runtime.env ?? process.env,
|
|
3748
|
+
});
|
|
3749
|
+
const { job } = await requestPreviewStatus({
|
|
3750
|
+
command,
|
|
3751
|
+
credentials,
|
|
3752
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
3753
|
+
});
|
|
3754
|
+
const stream = command.stream;
|
|
3755
|
+
const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(command.artifactId)}/jobs/${encodeURIComponent(job.jobId)}/events${stream ? "?stream=1" : ""}`, {
|
|
3756
|
+
headers: {
|
|
3757
|
+
authorization: `Bearer ${credentials.token}`,
|
|
3758
|
+
accept: stream ? "text/event-stream" : "application/json",
|
|
3759
|
+
},
|
|
3760
|
+
});
|
|
3761
|
+
if (stream) {
|
|
3762
|
+
if (!response.ok) {
|
|
3763
|
+
throw new Error(`Preview event stream failed with HTTP ${response.status}.`);
|
|
3764
|
+
}
|
|
3765
|
+
await emitSseLifecycleStream({
|
|
3766
|
+
response,
|
|
3767
|
+
io,
|
|
3768
|
+
json: command.json,
|
|
3769
|
+
});
|
|
3770
|
+
return 0;
|
|
3771
|
+
}
|
|
3772
|
+
const payload = await parseJsonResponse(response);
|
|
3773
|
+
if (!response.ok || !payload?.ok) {
|
|
3774
|
+
throw new Error(payload?.message ??
|
|
3775
|
+
payload?.reasonCode ??
|
|
3776
|
+
`Preview events lookup failed with HTTP ${response.status}.`);
|
|
3777
|
+
}
|
|
3778
|
+
const result = {
|
|
3779
|
+
ok: true,
|
|
3780
|
+
command: command.command,
|
|
3781
|
+
baseUrl: credentials.baseUrl,
|
|
3782
|
+
authSource: credentials.source,
|
|
3783
|
+
profile: credentials.profileName,
|
|
3784
|
+
artifactId: command.artifactId,
|
|
3785
|
+
job: payload.job ?? job,
|
|
3786
|
+
events: payload.events ?? [],
|
|
3787
|
+
};
|
|
3788
|
+
if (command.json) {
|
|
3789
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
3790
|
+
}
|
|
3791
|
+
else {
|
|
3792
|
+
io.stdout((result.events.length > 0
|
|
3793
|
+
? result.events.map((event) => event.phase ?? "event")
|
|
3794
|
+
: ["no events"]).join("\n"));
|
|
3795
|
+
}
|
|
3796
|
+
return 0;
|
|
3797
|
+
}
|
|
3798
|
+
async function runPublish(command, io, runtime) {
|
|
3799
|
+
if (command.dryRun) {
|
|
3800
|
+
const result = {
|
|
3801
|
+
ok: true,
|
|
3802
|
+
command: command.command,
|
|
3803
|
+
artifactId: command.artifactId,
|
|
3804
|
+
commitSha: command.commitSha,
|
|
3805
|
+
dryRun: true,
|
|
3806
|
+
confirmed: false,
|
|
3807
|
+
message: command.message,
|
|
3808
|
+
wouldRequest: {
|
|
3809
|
+
method: "POST",
|
|
3810
|
+
path: `/api/v1/artifacts/${command.artifactId}/publish`,
|
|
3811
|
+
stream: command.stream,
|
|
3812
|
+
body: {
|
|
3813
|
+
...(command.commitSha ? { commitSha: command.commitSha } : {}),
|
|
3814
|
+
...(command.message ? { message: command.message } : {}),
|
|
3815
|
+
},
|
|
3816
|
+
},
|
|
3817
|
+
};
|
|
3818
|
+
if (command.json) {
|
|
3819
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
3820
|
+
}
|
|
3821
|
+
else {
|
|
3822
|
+
io.stdout([
|
|
3823
|
+
`artifact_id: ${command.artifactId}`,
|
|
3824
|
+
"dry_run: true",
|
|
3825
|
+
"next: rerun with --confirm publish to publish.",
|
|
3826
|
+
].join("\n"));
|
|
3827
|
+
}
|
|
3828
|
+
return 0;
|
|
3829
|
+
}
|
|
3830
|
+
return await postLifecycle({
|
|
3831
|
+
command,
|
|
3832
|
+
operation: "publish",
|
|
3833
|
+
path: `/api/v1/artifacts/${encodeURIComponent(command.artifactId)}/publish`,
|
|
3834
|
+
io,
|
|
3835
|
+
runtime,
|
|
3836
|
+
});
|
|
3837
|
+
}
|
|
3838
|
+
async function requestPublishJob(input) {
|
|
3839
|
+
const response = await input.fetcher(`${input.credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.command.artifactId)}/jobs/${encodeURIComponent(input.command.jobId)}`, {
|
|
3840
|
+
headers: {
|
|
3841
|
+
authorization: `Bearer ${input.credentials.token}`,
|
|
3842
|
+
},
|
|
3843
|
+
});
|
|
3844
|
+
const payload = await parseJsonResponse(response);
|
|
3845
|
+
if (!response.ok || !payload?.ok || !payload.job) {
|
|
3846
|
+
throw new Error(payload?.message ??
|
|
3847
|
+
payload?.reasonCode ??
|
|
3848
|
+
`Publish job lookup failed with HTTP ${response.status}.`);
|
|
3849
|
+
}
|
|
3850
|
+
if (payload.job.operation && payload.job.operation !== "publish") {
|
|
3851
|
+
throw new Error(`Lifecycle job ${payload.job.jobId} is ${payload.job.operation}, not publish.`);
|
|
3852
|
+
}
|
|
3853
|
+
return payload.job;
|
|
3854
|
+
}
|
|
3855
|
+
async function runPublishStatus(command, io, runtime) {
|
|
3856
|
+
const credentials = await resolveApiCredentials({
|
|
3857
|
+
baseUrl: command.baseUrl,
|
|
3858
|
+
flagToken: command.flagToken,
|
|
3859
|
+
profileName: command.profileName,
|
|
3860
|
+
env: runtime.env ?? process.env,
|
|
3861
|
+
});
|
|
3862
|
+
const job = await requestPublishJob({
|
|
3863
|
+
command,
|
|
3864
|
+
credentials,
|
|
3865
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
3866
|
+
});
|
|
3867
|
+
const result = {
|
|
3868
|
+
ok: true,
|
|
3869
|
+
command: command.command,
|
|
3870
|
+
baseUrl: credentials.baseUrl,
|
|
3871
|
+
authSource: credentials.source,
|
|
3872
|
+
profile: credentials.profileName,
|
|
3873
|
+
artifactId: command.artifactId,
|
|
3874
|
+
status: job.status ?? null,
|
|
3875
|
+
publishedUrl: readPublishedUrlFromJob(job),
|
|
3876
|
+
job,
|
|
3877
|
+
};
|
|
3878
|
+
if (command.json) {
|
|
3879
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
3880
|
+
}
|
|
3881
|
+
else {
|
|
3882
|
+
io.stdout([
|
|
3883
|
+
`artifact_id: ${command.artifactId}`,
|
|
3884
|
+
`job_id: ${job.jobId}`,
|
|
3885
|
+
`status: ${result.status ?? "unknown"}`,
|
|
3886
|
+
...(result.publishedUrl
|
|
3887
|
+
? [`published_url: ${result.publishedUrl}`]
|
|
3888
|
+
: []),
|
|
3889
|
+
].join("\n"));
|
|
3890
|
+
}
|
|
3891
|
+
return 0;
|
|
3892
|
+
}
|
|
3893
|
+
async function runPublishWatch(command, io, runtime) {
|
|
3894
|
+
const credentials = await resolveApiCredentials({
|
|
3895
|
+
baseUrl: command.baseUrl,
|
|
3896
|
+
flagToken: command.flagToken,
|
|
3897
|
+
profileName: command.profileName,
|
|
3898
|
+
env: runtime.env ?? process.env,
|
|
3899
|
+
});
|
|
3900
|
+
const job = await requestPublishJob({
|
|
3901
|
+
command,
|
|
3902
|
+
credentials,
|
|
3903
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
3904
|
+
});
|
|
3905
|
+
const stream = command.stream;
|
|
3906
|
+
const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(command.artifactId)}/jobs/${encodeURIComponent(job.jobId)}/events${stream ? "?stream=1" : ""}`, {
|
|
3907
|
+
headers: {
|
|
3908
|
+
authorization: `Bearer ${credentials.token}`,
|
|
3909
|
+
accept: stream ? "text/event-stream" : "application/json",
|
|
3910
|
+
},
|
|
3911
|
+
});
|
|
3912
|
+
if (stream) {
|
|
3913
|
+
if (!response.ok) {
|
|
3914
|
+
throw new Error(`Publish event stream failed with HTTP ${response.status}.`);
|
|
3915
|
+
}
|
|
3916
|
+
await emitSseLifecycleStream({
|
|
3917
|
+
response,
|
|
3918
|
+
io,
|
|
3919
|
+
json: command.json,
|
|
3920
|
+
});
|
|
3921
|
+
return 0;
|
|
3922
|
+
}
|
|
3923
|
+
const payload = await parseJsonResponse(response);
|
|
3924
|
+
if (!response.ok || !payload?.ok) {
|
|
3925
|
+
throw new Error(payload?.message ??
|
|
3926
|
+
payload?.reasonCode ??
|
|
3927
|
+
`Publish events lookup failed with HTTP ${response.status}.`);
|
|
3928
|
+
}
|
|
3929
|
+
const result = {
|
|
3930
|
+
ok: true,
|
|
3931
|
+
command: command.command,
|
|
3932
|
+
baseUrl: credentials.baseUrl,
|
|
3933
|
+
authSource: credentials.source,
|
|
3934
|
+
profile: credentials.profileName,
|
|
3935
|
+
artifactId: command.artifactId,
|
|
3936
|
+
job: payload.job ?? job,
|
|
3937
|
+
events: payload.events ?? [],
|
|
3938
|
+
};
|
|
3939
|
+
if (command.json) {
|
|
3940
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
3941
|
+
}
|
|
3942
|
+
else {
|
|
3943
|
+
io.stdout((result.events.length > 0
|
|
3944
|
+
? result.events.map((event) => event.phase ?? "event")
|
|
3945
|
+
: ["no events"]).join("\n"));
|
|
3946
|
+
}
|
|
3947
|
+
return 0;
|
|
3948
|
+
}
|
|
3949
|
+
async function runGitRemote(command, io, runtime) {
|
|
3950
|
+
const credentials = await resolveApiCredentials({
|
|
3951
|
+
baseUrl: command.baseUrl,
|
|
3952
|
+
flagToken: command.flagToken,
|
|
3953
|
+
profileName: command.profileName,
|
|
3954
|
+
env: runtime.env ?? process.env,
|
|
3955
|
+
});
|
|
3956
|
+
const git = await requestGitRemoteLease({
|
|
3957
|
+
artifactId: command.artifactId,
|
|
3958
|
+
ttl: command.ttl,
|
|
3959
|
+
baseUrl: credentials.baseUrl,
|
|
3960
|
+
token: credentials.token,
|
|
3961
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
3962
|
+
});
|
|
3963
|
+
const result = {
|
|
3964
|
+
ok: true,
|
|
3965
|
+
command: command.command,
|
|
3966
|
+
artifactId: command.artifactId,
|
|
3967
|
+
baseUrl: credentials.baseUrl,
|
|
3968
|
+
authSource: credentials.source,
|
|
3969
|
+
profile: credentials.profileName,
|
|
3970
|
+
remoteUrl: git.remoteUrl,
|
|
3971
|
+
defaultBranch: git.defaultBranch,
|
|
3972
|
+
scope: git.scope,
|
|
3973
|
+
authentication: git.authentication?.mode ?? "artifacts-token",
|
|
3974
|
+
username: git.token?.username ?? git.authentication?.username ?? "tender",
|
|
3975
|
+
password: git.token?.password ?? null,
|
|
3976
|
+
expiresAt: git.token?.expiresAt ?? null,
|
|
3977
|
+
};
|
|
3978
|
+
if (command.json) {
|
|
3979
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
3980
|
+
}
|
|
3981
|
+
else {
|
|
3982
|
+
const lines = [
|
|
3983
|
+
`remote_url: ${result.remoteUrl}`,
|
|
3984
|
+
`default_branch: ${result.defaultBranch}`,
|
|
3985
|
+
`authentication: ${result.authentication}`,
|
|
3986
|
+
`username: ${result.username}`,
|
|
3987
|
+
];
|
|
3988
|
+
if (result.password) {
|
|
3989
|
+
lines.push(`password: ${result.password}`);
|
|
3990
|
+
}
|
|
3991
|
+
if (result.expiresAt) {
|
|
3992
|
+
lines.push(`expires_at: ${result.expiresAt}`);
|
|
3993
|
+
}
|
|
3994
|
+
io.stdout(lines.join("\n"));
|
|
3995
|
+
}
|
|
3996
|
+
return 0;
|
|
3997
|
+
}
|
|
3998
|
+
export async function runTenderCli(args, io = {
|
|
3999
|
+
stdout: (message) => console.log(message),
|
|
4000
|
+
stderr: (message) => console.error(message),
|
|
4001
|
+
}, runtime = {}) {
|
|
4002
|
+
try {
|
|
4003
|
+
const command = parseTenderArgs(args);
|
|
4004
|
+
if (command.command === "help") {
|
|
4005
|
+
if (command.topic === "auth login") {
|
|
4006
|
+
io.stdout(authLoginHelp());
|
|
4007
|
+
}
|
|
4008
|
+
else if (command.topic === "auth status") {
|
|
4009
|
+
io.stdout(authStatusHelp());
|
|
4010
|
+
}
|
|
4011
|
+
else if (command.topic === "auth") {
|
|
4012
|
+
io.stdout(authHelp());
|
|
4013
|
+
}
|
|
4014
|
+
else if (command.topic === "artifacts create") {
|
|
4015
|
+
io.stdout(artifactsCreateHelp());
|
|
4016
|
+
}
|
|
4017
|
+
else if (command.topic === "artifacts update") {
|
|
4018
|
+
io.stdout(artifactsUpdateHelp());
|
|
4019
|
+
}
|
|
4020
|
+
else if (command.topic === "artifacts init") {
|
|
4021
|
+
io.stdout(artifactsInitHelp());
|
|
4022
|
+
}
|
|
4023
|
+
else if (command.topic === "artifacts doctor") {
|
|
4024
|
+
io.stdout(doctorHelp());
|
|
4025
|
+
}
|
|
4026
|
+
else if (command.topic === "artifacts list") {
|
|
4027
|
+
io.stdout(artifactsListHelp());
|
|
4028
|
+
}
|
|
4029
|
+
else if (command.topic === "artifacts context fetch") {
|
|
4030
|
+
io.stdout(artifactsContextFetchHelp());
|
|
4031
|
+
}
|
|
4032
|
+
else if (command.topic === "artifacts context status") {
|
|
4033
|
+
io.stdout(artifactsContextStatusHelp());
|
|
4034
|
+
}
|
|
4035
|
+
else if (command.topic === "artifacts context refresh") {
|
|
4036
|
+
io.stdout(artifactsContextRefreshHelp());
|
|
4037
|
+
}
|
|
4038
|
+
else if (command.topic === "artifacts git setup") {
|
|
4039
|
+
io.stdout(gitSetupHelp());
|
|
4040
|
+
}
|
|
4041
|
+
else if (command.topic === "artifacts git credential") {
|
|
4042
|
+
io.stdout(gitCredentialHelp());
|
|
4043
|
+
}
|
|
4044
|
+
else if (command.topic === "artifacts git remote") {
|
|
4045
|
+
io.stdout(gitRemoteHelp());
|
|
4046
|
+
}
|
|
4047
|
+
else if (command.topic === "artifacts validate") {
|
|
4048
|
+
io.stdout(validateHelp());
|
|
4049
|
+
}
|
|
4050
|
+
else if (command.topic === "artifacts build") {
|
|
4051
|
+
io.stdout(buildHelp());
|
|
4052
|
+
}
|
|
4053
|
+
else if (command.topic === "artifacts preview rebuild") {
|
|
4054
|
+
io.stdout(previewRebuildHelp());
|
|
4055
|
+
}
|
|
4056
|
+
else if (command.topic === "artifacts preview status") {
|
|
4057
|
+
io.stdout(previewStatusHelp());
|
|
4058
|
+
}
|
|
4059
|
+
else if (command.topic === "artifacts preview watch") {
|
|
4060
|
+
io.stdout(previewWatchHelp());
|
|
4061
|
+
}
|
|
4062
|
+
else if (command.topic === "artifacts publish") {
|
|
4063
|
+
io.stdout(publishHelp());
|
|
4064
|
+
}
|
|
4065
|
+
else if (command.topic === "artifacts publish status") {
|
|
4066
|
+
io.stdout(publishStatusHelp());
|
|
4067
|
+
}
|
|
4068
|
+
else if (command.topic === "artifacts publish watch") {
|
|
4069
|
+
io.stdout(publishWatchHelp());
|
|
4070
|
+
}
|
|
4071
|
+
else if (command.topic === "artifacts") {
|
|
4072
|
+
io.stdout(artifactsHelp());
|
|
4073
|
+
}
|
|
4074
|
+
else {
|
|
4075
|
+
io.stdout(rootHelp());
|
|
4076
|
+
}
|
|
4077
|
+
return 0;
|
|
4078
|
+
}
|
|
4079
|
+
if (command.command === "auth login") {
|
|
4080
|
+
return await runAuthLogin(command, io, runtime);
|
|
4081
|
+
}
|
|
4082
|
+
if (command.command === "auth status") {
|
|
4083
|
+
return await runAuthStatus(command, io, runtime);
|
|
4084
|
+
}
|
|
4085
|
+
if (command.command === "artifacts list") {
|
|
4086
|
+
return await runArtifactsList(command, io, runtime);
|
|
4087
|
+
}
|
|
4088
|
+
if (command.command === "artifacts create") {
|
|
4089
|
+
return await runArtifactsCreate(command, io, runtime);
|
|
4090
|
+
}
|
|
4091
|
+
if (command.command === "artifacts update") {
|
|
4092
|
+
return await runArtifactsUpdate(command, io, runtime);
|
|
4093
|
+
}
|
|
4094
|
+
if (command.command === "artifacts init") {
|
|
4095
|
+
return await runArtifactsInit(command, io, runtime);
|
|
4096
|
+
}
|
|
4097
|
+
if (command.command === "artifacts context fetch") {
|
|
4098
|
+
return await runContextFetch(command, io, runtime);
|
|
4099
|
+
}
|
|
4100
|
+
if (command.command === "artifacts context status") {
|
|
4101
|
+
return await runContextStatus(command, io, runtime);
|
|
4102
|
+
}
|
|
4103
|
+
if (command.command === "artifacts context refresh") {
|
|
4104
|
+
return await runContextFetch(command, io, runtime);
|
|
4105
|
+
}
|
|
4106
|
+
if (command.command === "artifacts git setup") {
|
|
4107
|
+
return await runGitSetup(command, io, runtime);
|
|
4108
|
+
}
|
|
4109
|
+
if (command.command === "artifacts git credential") {
|
|
4110
|
+
return await runGitCredential(command, io, runtime);
|
|
4111
|
+
}
|
|
4112
|
+
if (command.command === "artifacts validate") {
|
|
4113
|
+
return await runValidate(command, io, runtime);
|
|
4114
|
+
}
|
|
4115
|
+
if (command.command === "artifacts build") {
|
|
4116
|
+
return await runBuild(command, io, runtime);
|
|
4117
|
+
}
|
|
4118
|
+
if (command.command === "artifacts preview rebuild") {
|
|
4119
|
+
return await runPreviewRebuild(command, io, runtime);
|
|
4120
|
+
}
|
|
4121
|
+
if (command.command === "artifacts preview status") {
|
|
4122
|
+
return await runPreviewStatus(command, io, runtime);
|
|
4123
|
+
}
|
|
4124
|
+
if (command.command === "artifacts preview watch") {
|
|
4125
|
+
return await runPreviewWatch(command, io, runtime);
|
|
4126
|
+
}
|
|
4127
|
+
if (command.command === "artifacts publish") {
|
|
4128
|
+
return await runPublish(command, io, runtime);
|
|
4129
|
+
}
|
|
4130
|
+
if (command.command === "artifacts publish status") {
|
|
4131
|
+
return await runPublishStatus(command, io, runtime);
|
|
4132
|
+
}
|
|
4133
|
+
if (command.command === "artifacts publish watch") {
|
|
4134
|
+
return await runPublishWatch(command, io, runtime);
|
|
4135
|
+
}
|
|
4136
|
+
if (command.command === "artifacts git remote") {
|
|
4137
|
+
return await runGitRemote(command, io, runtime);
|
|
4138
|
+
}
|
|
4139
|
+
return await runDoctor(command, io);
|
|
4140
|
+
}
|
|
4141
|
+
catch (error) {
|
|
4142
|
+
io.stderr(error instanceof Error ? error.message : String(error));
|
|
4143
|
+
io.stderr("Run `tender --help` for examples.");
|
|
4144
|
+
return error instanceof TenderCliUsageError ? 2 : 1;
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
4148
|
+
function shouldReadEntrypointStdin(args) {
|
|
4149
|
+
return (args[0] === "artifacts" &&
|
|
4150
|
+
args[1] === "git" &&
|
|
4151
|
+
args[2] === "credential");
|
|
4152
|
+
}
|
|
4153
|
+
async function readEntrypointStdin() {
|
|
4154
|
+
if (process.stdin.isTTY) {
|
|
4155
|
+
return undefined;
|
|
4156
|
+
}
|
|
4157
|
+
const chunks = [];
|
|
4158
|
+
for await (const chunk of process.stdin) {
|
|
4159
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
4160
|
+
}
|
|
4161
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
4162
|
+
}
|
|
4163
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === currentFile) {
|
|
4164
|
+
const args = process.argv.slice(2);
|
|
4165
|
+
const exitCode = await runTenderCli(args, undefined, shouldReadEntrypointStdin(args)
|
|
4166
|
+
? { stdin: await readEntrypointStdin() }
|
|
4167
|
+
: undefined);
|
|
4168
|
+
process.exit(exitCode);
|
|
4169
|
+
}
|