@workflow-code/cli 0.1.1 → 0.1.3

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.
@@ -0,0 +1,2501 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ __commonJS,
4
+ __toESM,
5
+ getCurrentLocale,
6
+ i18n,
7
+ prepareCliInvocation,
8
+ translateWorkflowCodeError
9
+ } from "./chunk-ONAQ5F5R.js";
10
+
11
+ // ../../node_modules/.pnpm/ignore@7.0.6/node_modules/ignore/index.js
12
+ var require_ignore = __commonJS({
13
+ "../../node_modules/.pnpm/ignore@7.0.6/node_modules/ignore/index.js"(exports, module) {
14
+ "use strict";
15
+ function makeArray(subject) {
16
+ return Array.isArray(subject) ? subject : [subject];
17
+ }
18
+ var UNDEFINED = void 0;
19
+ var EMPTY = "";
20
+ var SPACE = " ";
21
+ var ESCAPE = "\\";
22
+ var REGEX_TEST_BLANK_LINE = /^\s+$/;
23
+ var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
24
+ var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
25
+ var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
26
+ var REGEX_SPLITALL_CRLF = /\r?\n/g;
27
+ var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
28
+ var REGEX_TEST_TRAILING_SLASH = /\/$/;
29
+ var SLASH = "/";
30
+ var TMP_KEY_IGNORE = "node-ignore";
31
+ if (typeof Symbol !== "undefined") {
32
+ TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
33
+ }
34
+ var KEY_IGNORE = TMP_KEY_IGNORE;
35
+ var define = (object, key, value) => {
36
+ Object.defineProperty(object, key, { value });
37
+ return value;
38
+ };
39
+ var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
40
+ var RETURN_FALSE = () => false;
41
+ var sanitizeRange = (range) => range.replace(
42
+ REGEX_REGEXP_RANGE,
43
+ (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
44
+ );
45
+ var negateRange = (range) => range.startsWith("!") || range.startsWith("\\^") ? `^${range.slice(range[0] === "!" ? 1 : 2)}` : range;
46
+ var cleanRangeBackSlash = (slashes) => {
47
+ const { length } = slashes;
48
+ return slashes.slice(0, length - length % 2);
49
+ };
50
+ var REPLACERS = [
51
+ [
52
+ // Remove BOM
53
+ // TODO:
54
+ // Other similar zero-width characters?
55
+ /^\uFEFF/,
56
+ () => EMPTY
57
+ ],
58
+ // > Trailing spaces are ignored unless they are quoted with backslash ("\")
59
+ [
60
+ // (a\ ) -> (a )
61
+ // (a ) -> (a)
62
+ // (a ) -> (a)
63
+ // (a \ ) -> (a )
64
+ /((?:\\\\)*?)(\\?\s+)$/,
65
+ (_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
66
+ ],
67
+ // Replace (\ ) with ' '
68
+ // (\ ) -> ' '
69
+ // (\\ ) -> '\\ '
70
+ // (\\\ ) -> '\\ '
71
+ [
72
+ /(\\+?)\s/g,
73
+ (_, m1) => {
74
+ const { length } = m1;
75
+ return m1.slice(0, length - length % 2) + SPACE;
76
+ }
77
+ ],
78
+ // Escape metacharacters
79
+ // which is written down by users but means special for regular expressions.
80
+ // > There are 12 characters with special meanings:
81
+ // > - the backslash \,
82
+ // > - the caret ^,
83
+ // > - the dollar sign $,
84
+ // > - the period or dot .,
85
+ // > - the vertical bar or pipe symbol |,
86
+ // > - the question mark ?,
87
+ // > - the asterisk or star *,
88
+ // > - the plus sign +,
89
+ // > - the opening parenthesis (,
90
+ // > - the closing parenthesis ),
91
+ // > - and the opening square bracket [,
92
+ // > - the opening curly brace {,
93
+ // > These special characters are often called "metacharacters".
94
+ [
95
+ /[\\$.|*+(){^]/g,
96
+ (match) => `\\${match}`
97
+ ],
98
+ [
99
+ // > a question mark (?) matches a single character
100
+ /(?!\\)\?/g,
101
+ () => "[^/]"
102
+ ],
103
+ // leading slash
104
+ [
105
+ // > A leading slash matches the beginning of the pathname.
106
+ // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
107
+ // A leading slash matches the beginning of the pathname
108
+ /^\//,
109
+ () => "^"
110
+ ],
111
+ // replace special metacharacter slash after the leading slash
112
+ [
113
+ /\//g,
114
+ () => "\\/"
115
+ ],
116
+ [
117
+ // > A leading "**" followed by a slash means match in all directories.
118
+ // > For example, "**/foo" matches file or directory "foo" anywhere,
119
+ // > the same as pattern "foo".
120
+ // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
121
+ // > under directory "foo".
122
+ // Notice that the '*'s have been replaced as '\\*'
123
+ /^\^*(?:\\\*\\\*\\\/)+/,
124
+ // '**/foo' <-> 'foo'
125
+ () => "^(?:.*\\/)?"
126
+ ],
127
+ // starting
128
+ [
129
+ // there will be no leading '/'
130
+ // (which has been replaced by section "leading slash")
131
+ // If starts with '**', adding a '^' to the regular expression also works
132
+ /^(?=[^^])/,
133
+ function startingReplacer() {
134
+ return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
135
+ }
136
+ ],
137
+ // two globstars
138
+ [
139
+ // Use lookahead assertions so that we could match more than one `'/**'`
140
+ /\\\/\\\*\\\*(?=\\\/|$)/g,
141
+ // Zero, one or several directories
142
+ // should not use '*', or it will be replaced by the next replacer
143
+ // Check if it is not the last `'/**'`
144
+ (_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
145
+ ],
146
+ // normal intermediate wildcards
147
+ [
148
+ // Never replace escaped '*'
149
+ // ignore rule '\*' will match the path '*'
150
+ // 'abc.*/' -> go
151
+ // 'abc.*' -> skip this rule,
152
+ // coz trailing single wildcard will be handed by [trailing wildcard]
153
+ /(^|[^\\]+)(\\\*)+(?=.+)/g,
154
+ // '*.js' matches '.js'
155
+ // '*.js' doesn't match 'abc'
156
+ (_, p1, p2) => {
157
+ const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
158
+ return p1 + unescaped;
159
+ }
160
+ ],
161
+ [
162
+ // unescape, revert step 3 except for back slash
163
+ // For example, if a user escape a '\\*',
164
+ // after step 3, the result will be '\\\\\\*'
165
+ /\\\\\\(?=[$.|*+(){^])/g,
166
+ () => ESCAPE
167
+ ],
168
+ [
169
+ // '\\\\' -> '\\'
170
+ /\\\\/g,
171
+ () => ESCAPE
172
+ ],
173
+ [
174
+ // > The range notation, e.g. [a-zA-Z],
175
+ // > can be used to match one of the characters in a range.
176
+ // `\` is escaped by step 3
177
+ /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
178
+ (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${negateRange(sanitizeRange(range))}${endEscape}]` : "[]" : "[]"
179
+ ],
180
+ // ending
181
+ [
182
+ // 'js' will not match 'js.'
183
+ // 'ab' will not match 'abc'
184
+ /(?:[^*])$/,
185
+ // WTF!
186
+ // https://git-scm.com/docs/gitignore
187
+ // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
188
+ // which re-fixes #24, #38
189
+ // > If there is a separator at the end of the pattern then the pattern
190
+ // > will only match directories, otherwise the pattern can match both
191
+ // > files and directories.
192
+ // 'js*' will not match 'a.js'
193
+ // 'js/' will not match 'a.js'
194
+ // 'js' will match 'a.js' and 'a.js/'
195
+ (match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
196
+ ]
197
+ ];
198
+ var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
199
+ var MODE_IGNORE = "regex";
200
+ var MODE_CHECK_IGNORE = "checkRegex";
201
+ var UNDERSCORE = "_";
202
+ var TRAILING_WILD_CARD_REPLACERS = {
203
+ [MODE_IGNORE](_, p1) {
204
+ const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
205
+ return `${prefix}(?=$|\\/$)`;
206
+ },
207
+ [MODE_CHECK_IGNORE](_, p1) {
208
+ const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
209
+ return `${prefix}(?=$|\\/$)`;
210
+ }
211
+ };
212
+ var makeRegexPrefix = (pattern) => REPLACERS.reduce(
213
+ (prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
214
+ pattern
215
+ );
216
+ var isString = (subject) => typeof subject === "string";
217
+ var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
218
+ var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
219
+ var IgnoreRule = class {
220
+ constructor(pattern, mark, body, ignoreCase, negative, prefix) {
221
+ this.pattern = pattern;
222
+ this.mark = mark;
223
+ this.negative = negative;
224
+ define(this, "body", body);
225
+ define(this, "ignoreCase", ignoreCase);
226
+ define(this, "regexPrefix", prefix);
227
+ }
228
+ get regex() {
229
+ const key = UNDERSCORE + MODE_IGNORE;
230
+ if (this[key]) {
231
+ return this[key];
232
+ }
233
+ return this._make(MODE_IGNORE, key);
234
+ }
235
+ get checkRegex() {
236
+ const key = UNDERSCORE + MODE_CHECK_IGNORE;
237
+ if (this[key]) {
238
+ return this[key];
239
+ }
240
+ return this._make(MODE_CHECK_IGNORE, key);
241
+ }
242
+ _make(mode, key) {
243
+ const str = this.regexPrefix.replace(
244
+ REGEX_REPLACE_TRAILING_WILDCARD,
245
+ // It does not need to bind pattern
246
+ TRAILING_WILD_CARD_REPLACERS[mode]
247
+ );
248
+ const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
249
+ return define(this, key, regex);
250
+ }
251
+ };
252
+ var createRule = ({
253
+ pattern,
254
+ mark
255
+ }, ignoreCase) => {
256
+ let negative = false;
257
+ let body = pattern;
258
+ if (body.indexOf("!") === 0) {
259
+ negative = true;
260
+ body = body.substr(1);
261
+ }
262
+ body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
263
+ const regexPrefix = makeRegexPrefix(body);
264
+ return new IgnoreRule(
265
+ pattern,
266
+ mark,
267
+ body,
268
+ ignoreCase,
269
+ negative,
270
+ regexPrefix
271
+ );
272
+ };
273
+ var RuleManager = class {
274
+ constructor(ignoreCase) {
275
+ this._ignoreCase = ignoreCase;
276
+ this._rules = [];
277
+ }
278
+ _add(pattern) {
279
+ if (pattern && pattern[KEY_IGNORE]) {
280
+ this._rules = this._rules.concat(pattern._rules._rules);
281
+ this._added = true;
282
+ return;
283
+ }
284
+ if (isString(pattern)) {
285
+ pattern = {
286
+ pattern
287
+ };
288
+ }
289
+ if (checkPattern(pattern.pattern)) {
290
+ const rule = createRule(pattern, this._ignoreCase);
291
+ this._added = true;
292
+ this._rules.push(rule);
293
+ }
294
+ }
295
+ // @param {Array<string> | string | Ignore} pattern
296
+ add(pattern) {
297
+ this._added = false;
298
+ makeArray(
299
+ isString(pattern) ? splitPattern(pattern) : pattern
300
+ ).forEach(this._add, this);
301
+ return this._added;
302
+ }
303
+ // Test one single path without recursively checking parent directories
304
+ //
305
+ // - checkUnignored `boolean` whether should check if the path is unignored,
306
+ // setting `checkUnignored` to `false` could reduce additional
307
+ // path matching.
308
+ // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
309
+ // @returns {TestResult} true if a file is ignored
310
+ test(path5, checkUnignored, mode) {
311
+ let ignored = false;
312
+ let unignored = false;
313
+ let matchedRule;
314
+ this._rules.forEach((rule) => {
315
+ const { negative } = rule;
316
+ if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
317
+ return;
318
+ }
319
+ const matched = rule[mode].test(path5);
320
+ if (!matched) {
321
+ return;
322
+ }
323
+ ignored = !negative;
324
+ unignored = negative;
325
+ matchedRule = negative ? UNDEFINED : rule;
326
+ });
327
+ const ret = {
328
+ ignored,
329
+ unignored
330
+ };
331
+ if (matchedRule) {
332
+ ret.rule = matchedRule;
333
+ }
334
+ return ret;
335
+ }
336
+ };
337
+ var throwError = (message, Ctor) => {
338
+ throw new Ctor(message);
339
+ };
340
+ var checkPath = (path5, originalPath, doThrow) => {
341
+ if (!isString(path5)) {
342
+ return doThrow(
343
+ `path must be a string, but got \`${originalPath}\``,
344
+ TypeError
345
+ );
346
+ }
347
+ if (!path5) {
348
+ return doThrow(`path must not be empty`, TypeError);
349
+ }
350
+ if (checkPath.isNotRelative(path5)) {
351
+ const r = "`path.relative()`d";
352
+ return doThrow(
353
+ `path should be a ${r} string, but got "${originalPath}"`,
354
+ RangeError
355
+ );
356
+ }
357
+ return true;
358
+ };
359
+ var isNotRelative = (path5) => REGEX_TEST_INVALID_PATH.test(path5);
360
+ checkPath.isNotRelative = isNotRelative;
361
+ checkPath.convert = (p) => p;
362
+ var Ignore = class {
363
+ constructor({
364
+ ignorecase = true,
365
+ ignoreCase = ignorecase,
366
+ allowRelativePaths = false
367
+ } = {}) {
368
+ define(this, KEY_IGNORE, true);
369
+ this._rules = new RuleManager(ignoreCase);
370
+ this._strictPathCheck = !allowRelativePaths;
371
+ this._initCache();
372
+ }
373
+ _initCache() {
374
+ this._ignoreCache = /* @__PURE__ */ Object.create(null);
375
+ this._testCache = /* @__PURE__ */ Object.create(null);
376
+ }
377
+ add(pattern) {
378
+ if (this._rules.add(pattern)) {
379
+ this._initCache();
380
+ }
381
+ return this;
382
+ }
383
+ // legacy
384
+ addPattern(pattern) {
385
+ return this.add(pattern);
386
+ }
387
+ // @returns {TestResult}
388
+ _test(originalPath, cache, checkUnignored, slices) {
389
+ const path5 = originalPath && checkPath.convert(originalPath);
390
+ checkPath(
391
+ path5,
392
+ originalPath,
393
+ this._strictPathCheck ? throwError : RETURN_FALSE
394
+ );
395
+ return this._t(path5, cache, checkUnignored, slices);
396
+ }
397
+ checkIgnore(path5) {
398
+ if (!REGEX_TEST_TRAILING_SLASH.test(path5)) {
399
+ return this.test(path5);
400
+ }
401
+ const slices = path5.split(SLASH).filter(Boolean);
402
+ slices.pop();
403
+ if (slices.length) {
404
+ const parent = this._t(
405
+ slices.join(SLASH) + SLASH,
406
+ this._testCache,
407
+ true,
408
+ slices
409
+ );
410
+ if (parent.ignored) {
411
+ return parent;
412
+ }
413
+ }
414
+ return this._rules.test(path5, false, MODE_CHECK_IGNORE);
415
+ }
416
+ _t(path5, cache, checkUnignored, slices) {
417
+ if (path5 in cache) {
418
+ return cache[path5];
419
+ }
420
+ if (!slices) {
421
+ slices = path5.split(SLASH).filter(Boolean);
422
+ }
423
+ slices.pop();
424
+ if (!slices.length) {
425
+ return cache[path5] = this._rules.test(path5, checkUnignored, MODE_IGNORE);
426
+ }
427
+ const parent = this._t(
428
+ slices.join(SLASH) + SLASH,
429
+ cache,
430
+ checkUnignored,
431
+ slices
432
+ );
433
+ return cache[path5] = parent.ignored ? parent : this._rules.test(path5, checkUnignored, MODE_IGNORE);
434
+ }
435
+ ignores(path5) {
436
+ return this._test(path5, this._ignoreCache, false).ignored;
437
+ }
438
+ createFilter() {
439
+ return (path5) => !this.ignores(path5);
440
+ }
441
+ filter(paths) {
442
+ return makeArray(paths).filter(this.createFilter());
443
+ }
444
+ // @returns {TestResult}
445
+ test(path5) {
446
+ return this._test(path5, this._testCache, true);
447
+ }
448
+ };
449
+ var factory = (options) => new Ignore(options);
450
+ var isPathValid = (path5) => checkPath(path5 && checkPath.convert(path5), path5, RETURN_FALSE);
451
+ var setupWindows = () => {
452
+ const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
453
+ checkPath.convert = makePosix;
454
+ const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
455
+ checkPath.isNotRelative = (path5) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path5) || isNotRelative(path5);
456
+ };
457
+ if (
458
+ // Detect `process` so that it can run in browsers.
459
+ typeof process !== "undefined" && process.platform === "win32"
460
+ ) {
461
+ setupWindows();
462
+ }
463
+ module.exports = factory;
464
+ factory.default = factory;
465
+ module.exports.isPathValid = isPathValid;
466
+ define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
467
+ }
468
+ });
469
+
470
+ // src/workspace.ts
471
+ import { spawn } from "child_process";
472
+ import { createHash, randomUUID } from "crypto";
473
+ import { existsSync as existsSync2 } from "fs";
474
+ import { copyFile as copyFile2, mkdir as mkdir3, readFile as readFile3, readdir as readdir2, rename, rm as rm2, writeFile as writeFile2 } from "fs/promises";
475
+ import path4 from "path";
476
+ import { fileURLToPath, pathToFileURL } from "url";
477
+
478
+ // ../../shared/auth-config/index.ts
479
+ var WORKFLOW_AUTH_FILE_NAME = "workflow-auth.json";
480
+ var DEFAULT_MAC_PLATFORM = "darwin";
481
+ var DEFAULT_WINDOWS_PLATFORM = "win32";
482
+ var DEFAULT_OTHER_PLATFORM = "linux";
483
+ function createEmptyWorkflowAuthConfig() {
484
+ return {
485
+ serverUrl: "",
486
+ apiKey: ""
487
+ };
488
+ }
489
+ function normalizeWorkflowAuthConfig(value) {
490
+ if (!isRecord(value)) {
491
+ return createEmptyWorkflowAuthConfig();
492
+ }
493
+ return {
494
+ // issue #90 Accept legacy field names (remoteUrl, token) from pre-migration
495
+ // config files so existing users don't lose their credentials on upgrade.
496
+ serverUrl: readString(value.serverUrl) ?? readString(value.remoteUrl) ?? "",
497
+ apiKey: readString(value.apiKey) ?? readString(value.token) ?? "",
498
+ expiresAt: readString(value.expiresAt),
499
+ user: isRecord(value.user) ? {
500
+ userId: readString(value.user.userId) ?? "",
501
+ email: readString(value.user.email) ?? "",
502
+ displayName: readString(value.user.displayName) ?? "",
503
+ avatarUrl: readString(value.user.avatarUrl)
504
+ } : void 0,
505
+ source: value.source === "device_flow" || value.source === "manual" || value.source === "password" ? value.source : void 0
506
+ };
507
+ }
508
+ function isWorkflowAuthExpiringSoon(expiresAt, options = {}) {
509
+ if (!expiresAt) {
510
+ return false;
511
+ }
512
+ const expiresAtMs = new Date(expiresAt).getTime();
513
+ if (!Number.isFinite(expiresAtMs)) {
514
+ return false;
515
+ }
516
+ const now = options.now ?? Date.now();
517
+ const thresholdMs = options.thresholdMs ?? 1e3 * 60 * 60 * 24 * 3;
518
+ return expiresAtMs - now <= thresholdMs;
519
+ }
520
+ function resolveWorkflowCliAuthDir(env = readProcessEnv()) {
521
+ if (readString(env.WORKFLOW_CLI_AUTH_DIR)) {
522
+ return normalizePath(env.WORKFLOW_CLI_AUTH_DIR);
523
+ }
524
+ if (readPlatform(env) === DEFAULT_WINDOWS_PLATFORM) {
525
+ return joinPath(
526
+ env.APPDATA ?? joinPath(resolveHomeDir(env), "AppData", "Roaming"),
527
+ "workflow-code"
528
+ );
529
+ }
530
+ if (readPlatform(env) === DEFAULT_MAC_PLATFORM) {
531
+ return joinPath(resolveHomeDir(env), "Library", "Application Support", "workflow-code");
532
+ }
533
+ return joinPath(
534
+ env.XDG_CONFIG_HOME ?? joinPath(resolveHomeDir(env), ".config"),
535
+ "workflow-code"
536
+ );
537
+ }
538
+ function resolveWorkflowCliAuthFilePath(env = readProcessEnv()) {
539
+ return joinPath(resolveWorkflowCliAuthDir(env), WORKFLOW_AUTH_FILE_NAME);
540
+ }
541
+ function resolveHomeDir(env) {
542
+ return env.HOME ?? "";
543
+ }
544
+ function readPlatform(env) {
545
+ return env.platform ?? readProcessPlatform();
546
+ }
547
+ function isRecord(value) {
548
+ return typeof value === "object" && value !== null;
549
+ }
550
+ function readString(value) {
551
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
552
+ }
553
+ function joinPath(...parts) {
554
+ return normalizePath(parts.filter((part) => part !== "").join("/"));
555
+ }
556
+ function normalizePath(value) {
557
+ const normalized = value.replaceAll("\\", "/");
558
+ if (/^[A-Za-z]:\//.test(normalized)) {
559
+ const drive = normalized.slice(0, 2);
560
+ const rest = normalized.slice(2).replace(/\/+/g, "/");
561
+ return `${drive}${rest.startsWith("/") ? rest : `/${rest}`}`;
562
+ }
563
+ return normalized.replace(/\/+/g, "/");
564
+ }
565
+ function readProcessEnv() {
566
+ const processValue = readGlobalProcess();
567
+ return {
568
+ ...processValue?.env ?? {},
569
+ platform: processValue?.platform ?? DEFAULT_OTHER_PLATFORM
570
+ };
571
+ }
572
+ function readProcessPlatform() {
573
+ return readGlobalProcess()?.platform ?? DEFAULT_OTHER_PLATFORM;
574
+ }
575
+ function readGlobalProcess() {
576
+ const processValue = Reflect.get(globalThis, "process");
577
+ if (typeof processValue !== "object" || processValue === null) {
578
+ return void 0;
579
+ }
580
+ return processValue;
581
+ }
582
+
583
+ // ../../shared/workflow-ids/index.ts
584
+ import path from "path";
585
+ var WORKFLOW_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
586
+ function isWorkflowUuid(value) {
587
+ return WORKFLOW_UUID_PATTERN.test(value.trim());
588
+ }
589
+
590
+ // ../../shared/project-upload/node.ts
591
+ import { copyFile, lstat, mkdir, readFile, readdir } from "fs/promises";
592
+ import path2 from "path";
593
+
594
+ // ../../shared/project-upload/index.ts
595
+ var import_ignore = __toESM(require_ignore(), 1);
596
+
597
+ // ../../shared/kanban-project/index.ts
598
+ var KANBAN_MANIFEST_FILE = "kanban.json";
599
+ var KANBAN_ENTRY_FILE = "index.html";
600
+ var KANBAN_ARTIFACT_DIR = ".";
601
+ var KANBAN_DATA_SOURCE_ID_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
602
+ var KANBAN_WORKFLOW_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
603
+ var MAX_KANBAN_DATA_SOURCES = 32;
604
+ var MAX_KANBAN_DATA_SOURCE_PREFIXES = 32;
605
+ var MAX_KANBAN_DATA_SOURCE_KEY_LENGTH = 1024;
606
+ function readPackageProjectType(value) {
607
+ if (!isRecord2(value)) return void 0;
608
+ const workflowCode = value["workflowCode"];
609
+ if (!isRecord2(workflowCode)) return void 0;
610
+ return workflowCode["projectType"] === "kanban" ? "kanban" : void 0;
611
+ }
612
+ function readKanbanArtifactConfig(value) {
613
+ if (!isRecord2(value)) {
614
+ throw new Error("package.json root must be an object.");
615
+ }
616
+ const workflowCode = value["workflowCode"];
617
+ if (!isRecord2(workflowCode)) {
618
+ throw new Error("package.json workflowCode must be an object for a Kanban project.");
619
+ }
620
+ const rawConfig = workflowCode["kanban"];
621
+ if (rawConfig !== void 0 && !isRecord2(rawConfig)) {
622
+ throw new Error("package.json workflowCode.kanban must be an object.");
623
+ }
624
+ return {
625
+ artifactDir: readKanbanRelativePath(
626
+ rawConfig?.["artifactDir"],
627
+ "workflowCode.kanban.artifactDir",
628
+ KANBAN_ARTIFACT_DIR,
629
+ true
630
+ ),
631
+ entry: readKanbanRelativePath(
632
+ rawConfig?.["entry"],
633
+ "workflowCode.kanban.entry",
634
+ KANBAN_ENTRY_FILE,
635
+ false
636
+ ),
637
+ dataSources: parseKanbanDataSources(rawConfig?.["dataSources"])
638
+ };
639
+ }
640
+ function createKanbanArtifactEntryPath(config) {
641
+ return config.artifactDir === KANBAN_ARTIFACT_DIR ? config.entry : `${config.artifactDir}/${config.entry}`;
642
+ }
643
+ function readKanbanRelativePath(value, field, fallback, allowDot) {
644
+ const raw = value === void 0 ? fallback : value;
645
+ if (typeof raw !== "string" || raw.trim() === "") {
646
+ throw new Error(`package.json ${field} must be a non-empty string.`);
647
+ }
648
+ const normalized = raw.trim().replaceAll("\\", "/").replace(/\/+$/, "");
649
+ if (allowDot && normalized === ".") return normalized;
650
+ if (normalized === "" || normalized === "." || normalized.startsWith("/") || normalized.startsWith("//") || /^[a-zA-Z]:/.test(normalized) || normalized.includes("\0") || normalized.split("/").some((part) => part === "" || part === "." || part === "..")) {
651
+ throw new Error(`package.json ${field} must be a safe project-relative path.`);
652
+ }
653
+ const reservedDirectory = normalized.split("/").find((part) => [".git", ".hg", ".svn", "node_modules"].includes(part.toLowerCase()));
654
+ if (reservedDirectory !== void 0) {
655
+ throw new Error(
656
+ `package.json ${field} must not use reserved directory ${JSON.stringify(reservedDirectory)}.`
657
+ );
658
+ }
659
+ if (!allowDot && !/\.html?$/i.test(normalized)) {
660
+ throw new Error(`package.json ${field} must point to an HTML file.`);
661
+ }
662
+ return normalized;
663
+ }
664
+ function parseKanbanDataSources(value) {
665
+ if (value === void 0) return [];
666
+ if (!Array.isArray(value)) {
667
+ throw new Error("package.json workflowCode.kanban.dataSources must be an array.");
668
+ }
669
+ if (value.length > MAX_KANBAN_DATA_SOURCES) {
670
+ throw new Error(`package.json workflowCode.kanban.dataSources supports at most ${MAX_KANBAN_DATA_SOURCES} items.`);
671
+ }
672
+ const ids = /* @__PURE__ */ new Set();
673
+ return value.map((item, index) => {
674
+ if (!isRecord2(item)) {
675
+ throw new Error(`package.json workflowCode.kanban.dataSources[${index}] must be an object.`);
676
+ }
677
+ const supportedFields = /* @__PURE__ */ new Set(["id", "kind", "projectId", "keyPrefixes"]);
678
+ if (Object.keys(item).some((field) => !supportedFields.has(field))) {
679
+ throw new Error(`package.json workflowCode.kanban.dataSources[${index}] contains unsupported fields.`);
680
+ }
681
+ const id = readNonEmptyString(item["id"], `workflowCode.kanban.dataSources[${index}].id`);
682
+ if (!KANBAN_DATA_SOURCE_ID_PATTERN.test(id)) {
683
+ throw new Error(`package.json workflowCode.kanban.dataSources[${index}].id is invalid.`);
684
+ }
685
+ if (ids.has(id)) {
686
+ throw new Error(`package.json workflowCode.kanban.dataSources contains duplicate id "${id}".`);
687
+ }
688
+ ids.add(id);
689
+ if (item["kind"] !== "workflow-kv") {
690
+ throw new Error(`package.json workflowCode.kanban.dataSources[${index}].kind is unsupported.`);
691
+ }
692
+ const projectId = readNonEmptyString(
693
+ item["projectId"],
694
+ `workflowCode.kanban.dataSources[${index}].projectId`
695
+ );
696
+ if (!KANBAN_WORKFLOW_UUID_PATTERN.test(projectId)) {
697
+ throw new Error(`package.json workflowCode.kanban.dataSources[${index}].projectId must be a UUID.`);
698
+ }
699
+ const rawPrefixes = item["keyPrefixes"];
700
+ if (!Array.isArray(rawPrefixes) || rawPrefixes.length === 0 || rawPrefixes.length > MAX_KANBAN_DATA_SOURCE_PREFIXES) {
701
+ throw new Error(
702
+ `package.json workflowCode.kanban.dataSources[${index}].keyPrefixes must contain 1 to ${MAX_KANBAN_DATA_SOURCE_PREFIXES} items.`
703
+ );
704
+ }
705
+ const keyPrefixes = rawPrefixes.map((prefix, prefixIndex) => {
706
+ const normalized = readNonEmptyString(
707
+ prefix,
708
+ `workflowCode.kanban.dataSources[${index}].keyPrefixes[${prefixIndex}]`
709
+ );
710
+ if (normalized.length > MAX_KANBAN_DATA_SOURCE_KEY_LENGTH || normalized.includes("\0")) {
711
+ throw new Error(`package.json workflowCode.kanban.dataSources[${index}].keyPrefixes[${prefixIndex}] is invalid.`);
712
+ }
713
+ return normalized;
714
+ });
715
+ if (new Set(keyPrefixes).size !== keyPrefixes.length) {
716
+ throw new Error(`package.json workflowCode.kanban.dataSources[${index}].keyPrefixes contains duplicates.`);
717
+ }
718
+ return { id, kind: "workflow-kv", projectId, keyPrefixes };
719
+ });
720
+ }
721
+ function readNonEmptyString(value, field) {
722
+ if (typeof value !== "string" || value.trim() === "") throw new Error(`kanban.json ${field} must be a non-empty string.`);
723
+ return value.trim();
724
+ }
725
+ function isRecord2(value) {
726
+ return typeof value === "object" && value !== null && !Array.isArray(value);
727
+ }
728
+
729
+ // ../../shared/project-upload/index.ts
730
+ var WORKFLOW_IGNORE_FILE = ".workflowignore";
731
+ function hasWorkflowIgnoreRules(content) {
732
+ if (content === void 0) return false;
733
+ return content.split(/\r?\n/).some((line) => {
734
+ const trimmed = line.trim();
735
+ return trimmed !== "" && !trimmed.startsWith("#");
736
+ });
737
+ }
738
+ function resolveWorkflowUploadRequiredPaths(packageJson, availablePaths) {
739
+ const normalizedAvailablePaths = new Set(
740
+ Array.from(availablePaths, (filePath) => normalizeWorkflowUploadPath(filePath))
741
+ );
742
+ if (readPackageProjectType(packageJson) === "kanban") {
743
+ const artifact = readKanbanArtifactConfig(packageJson);
744
+ return [
745
+ "package.json",
746
+ createKanbanArtifactEntryPath(artifact),
747
+ ...normalizedAvailablePaths.has(KANBAN_MANIFEST_FILE) ? [KANBAN_MANIFEST_FILE] : []
748
+ ];
749
+ }
750
+ return ["package.json", "index.ts"];
751
+ }
752
+ function createWorkflowUploadMatcher(content) {
753
+ const ruleLines = content === void 0 ? [] : content.split(/\r?\n/);
754
+ const matcher = (0, import_ignore.default)().add(ruleLines);
755
+ return {
756
+ ignores(relativePath, options) {
757
+ const normalized = normalizeWorkflowUploadPath(relativePath, options);
758
+ return normalized === WORKFLOW_IGNORE_FILE ? false : matcher.ignores(normalized);
759
+ },
760
+ explain(relativePath, options) {
761
+ const normalized = normalizeWorkflowUploadPath(relativePath, options);
762
+ if (normalized === WORKFLOW_IGNORE_FILE) return void 0;
763
+ return findEffectiveWorkflowIgnoreRule(normalized, ruleLines);
764
+ },
765
+ assertRequiredPaths(relativePaths) {
766
+ for (const relativePath of relativePaths) {
767
+ const normalized = normalizeWorkflowUploadPath(relativePath);
768
+ if (!matcher.ignores(normalized)) continue;
769
+ const rule = findEffectiveWorkflowIgnoreRule(normalized, ruleLines);
770
+ const suffix = rule === void 0 ? "" : ` by rule ${JSON.stringify(rule)}`;
771
+ throw new Error(
772
+ `Required upload path ${JSON.stringify(normalized)} is excluded by ${WORKFLOW_IGNORE_FILE}${suffix}.`
773
+ );
774
+ }
775
+ }
776
+ };
777
+ }
778
+ function normalizeWorkflowUploadPath(relativePath, options = {}) {
779
+ const normalized = relativePath.replaceAll("\\", "/").replace(/^\.\/+/, "");
780
+ const withoutTrailingSlash = normalized.replace(/\/+$/, "");
781
+ if (withoutTrailingSlash === "" || withoutTrailingSlash.startsWith("/") || /^[a-zA-Z]:/.test(withoutTrailingSlash) || withoutTrailingSlash.includes("\0") || withoutTrailingSlash.split("/").some((part) => part === "" || part === "." || part === "..")) {
782
+ throw new Error(`Invalid workflow upload path ${JSON.stringify(relativePath)}.`);
783
+ }
784
+ return options.directory === true ? `${withoutTrailingSlash}/` : withoutTrailingSlash;
785
+ }
786
+ function createWorkflowUploadPortablePathKey(relativePath) {
787
+ return normalizeWorkflowUploadPath(relativePath).normalize("NFC").toLowerCase();
788
+ }
789
+ function findEffectiveWorkflowIgnoreRule(normalizedPath, ruleLines) {
790
+ const matcher = (0, import_ignore.default)();
791
+ let ignored = false;
792
+ let effectiveRule;
793
+ for (const ruleLine of ruleLines) {
794
+ matcher.add(ruleLine);
795
+ const nextIgnored = matcher.ignores(normalizedPath);
796
+ if (nextIgnored !== ignored) {
797
+ ignored = nextIgnored;
798
+ effectiveRule = ruleLine;
799
+ }
800
+ }
801
+ return ignored ? effectiveRule : void 0;
802
+ }
803
+
804
+ // ../../shared/project-upload/node.ts
805
+ async function createWorkflowUploadPlan(rootDir) {
806
+ const projectRoot = path2.resolve(rootDir);
807
+ const workflowIgnorePath = path2.join(projectRoot, WORKFLOW_IGNORE_FILE);
808
+ const workflowIgnoreStat = await lstat(workflowIgnorePath).catch(() => void 0);
809
+ if (workflowIgnoreStat?.isSymbolicLink()) {
810
+ throw new Error(`${WORKFLOW_IGNORE_FILE} must be a regular file.`);
811
+ }
812
+ const workflowIgnorePresent = workflowIgnoreStat?.isFile() === true;
813
+ if (workflowIgnoreStat !== void 0 && !workflowIgnorePresent) {
814
+ throw new Error(`${WORKFLOW_IGNORE_FILE} must be a regular file.`);
815
+ }
816
+ const workflowIgnoreContent = workflowIgnorePresent ? await readWorkflowUploadUtf8File(workflowIgnorePath, WORKFLOW_IGNORE_FILE) : void 0;
817
+ const matcher = createWorkflowUploadMatcher(
818
+ workflowIgnoreContent
819
+ );
820
+ const files = [];
821
+ const availableRegularPaths = /* @__PURE__ */ new Set();
822
+ const portablePaths = /* @__PURE__ */ new Map();
823
+ const ignoredPaths = [];
824
+ const walk = async (currentDir) => {
825
+ const entries = await readdir(currentDir, { withFileTypes: true });
826
+ for (const entry of entries) {
827
+ const absolutePath = path2.join(currentDir, entry.name);
828
+ const relativePath = normalizeWorkflowUploadPath(path2.relative(projectRoot, absolutePath));
829
+ const portablePath = createWorkflowUploadPortablePathKey(relativePath);
830
+ const conflictingPath = portablePaths.get(portablePath);
831
+ if (conflictingPath !== void 0 && conflictingPath !== relativePath) {
832
+ throw new Error(
833
+ `Workflow upload paths ${JSON.stringify(conflictingPath)} and ${JSON.stringify(relativePath)} conflict on a case-insensitive filesystem.`
834
+ );
835
+ }
836
+ portablePaths.set(portablePath, relativePath);
837
+ if (entry.isDirectory()) {
838
+ if (matcher.ignores(relativePath, { directory: true })) {
839
+ ignoredPaths.push(`${relativePath}/`);
840
+ continue;
841
+ }
842
+ await walk(absolutePath);
843
+ continue;
844
+ }
845
+ if (!entry.isFile()) {
846
+ throw new Error(`Workflow upload path ${JSON.stringify(relativePath)} must be a regular file or directory.`);
847
+ }
848
+ availableRegularPaths.add(relativePath);
849
+ if (matcher.ignores(relativePath)) {
850
+ ignoredPaths.push(relativePath);
851
+ continue;
852
+ }
853
+ const fileStat = await lstat(absolutePath);
854
+ files.push({ path: relativePath, absolutePath, size: fileStat.size });
855
+ }
856
+ };
857
+ await walk(projectRoot);
858
+ files.sort((left, right) => left.path.localeCompare(right.path));
859
+ ignoredPaths.sort((left, right) => left.localeCompare(right));
860
+ if (!availableRegularPaths.has("package.json")) {
861
+ throw new Error("Workflow package must contain package.json.");
862
+ }
863
+ const packageJson = parsePackageJson(await readWorkflowUploadUtf8File(
864
+ path2.join(projectRoot, "package.json"),
865
+ "package.json"
866
+ ));
867
+ const requiredPaths = resolveWorkflowUploadRequiredPaths(
868
+ packageJson,
869
+ availableRegularPaths
870
+ );
871
+ matcher.assertRequiredPaths(requiredPaths);
872
+ for (const requiredPath of requiredPaths) {
873
+ if (!availableRegularPaths.has(requiredPath)) {
874
+ throw new Error(`Workflow package must contain required upload path ${JSON.stringify(requiredPath)}.`);
875
+ }
876
+ }
877
+ return {
878
+ workflowIgnorePresent,
879
+ workflowIgnoreConfigured: hasWorkflowIgnoreRules(workflowIgnoreContent),
880
+ files,
881
+ ignoredPaths,
882
+ totalBytes: files.reduce((total, file) => total + file.size, 0),
883
+ requiredPaths
884
+ };
885
+ }
886
+ async function copyWorkflowUploadFiles(sourceDir, targetDir) {
887
+ const plan = await createWorkflowUploadPlan(sourceDir);
888
+ await mkdir(targetDir, { recursive: true });
889
+ for (const file of plan.files) {
890
+ const targetPath = path2.join(targetDir, ...file.path.split("/"));
891
+ await mkdir(path2.dirname(targetPath), { recursive: true });
892
+ await copyFile(file.absolutePath, targetPath);
893
+ }
894
+ return plan;
895
+ }
896
+ function parsePackageJson(content) {
897
+ try {
898
+ return JSON.parse(content);
899
+ } catch {
900
+ throw new Error("Workflow package.json is invalid JSON.");
901
+ }
902
+ }
903
+ async function readWorkflowUploadUtf8File(filePath, label) {
904
+ const content = await readFile(filePath);
905
+ try {
906
+ return new TextDecoder("utf-8", { fatal: true }).decode(content);
907
+ } catch {
908
+ throw new Error(`Workflow ${label} must be a UTF-8 text file.`);
909
+ }
910
+ }
911
+
912
+ // src/workspace.ts
913
+ import {
914
+ createWorkflowProjectDependencyLocks,
915
+ inspectLocalWorkflowStructure,
916
+ isProjectGroupHostDataPath,
917
+ parseWorkflowProjectDependencyLocks,
918
+ readKanbanArtifactConfig as readKanbanArtifactConfig2
919
+ } from "workflow-code";
920
+
921
+ // src/auth-config.ts
922
+ import { readFileSync } from "fs";
923
+ import { mkdir as mkdir2, readFile as readFile2, rm, writeFile } from "fs/promises";
924
+ async function readCliAuthConfig(env = process.env) {
925
+ try {
926
+ const raw = await readFile2(resolveWorkflowCliAuthFilePath(env), "utf8");
927
+ return normalizeWorkflowAuthConfig(JSON.parse(raw));
928
+ } catch {
929
+ return createEmptyWorkflowAuthConfig();
930
+ }
931
+ }
932
+ function readCliAuthConfigSync(env = process.env) {
933
+ try {
934
+ const raw = readFileSync(resolveWorkflowCliAuthFilePath(env), "utf8");
935
+ return normalizeWorkflowAuthConfig(JSON.parse(raw));
936
+ } catch {
937
+ return createEmptyWorkflowAuthConfig();
938
+ }
939
+ }
940
+ async function writeCliAuthConfig(config, env = process.env) {
941
+ await mkdir2(resolveWorkflowCliAuthDir(env), { recursive: true });
942
+ await writeFile(
943
+ resolveWorkflowCliAuthFilePath(env),
944
+ `${JSON.stringify(normalizeWorkflowAuthConfig(config), null, 2)}
945
+ `,
946
+ "utf8"
947
+ );
948
+ }
949
+ async function clearCliAuthConfig(env = process.env) {
950
+ await rm(resolveWorkflowCliAuthFilePath(env), { force: true });
951
+ }
952
+
953
+ // src/env.ts
954
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
955
+ import path3 from "path";
956
+ import { parse } from "dotenv";
957
+ function loadCliEnv() {
958
+ const cliRoot = process.cwd();
959
+ const defaultRepoRoot = findRepoRoot(cliRoot) ?? cliRoot;
960
+ const initialRepoRoot = process.env.WORKFLOW_REPO_ROOT ? path3.resolve(process.env.WORKFLOW_REPO_ROOT) : defaultRepoRoot;
961
+ loadEnvFiles([
962
+ path3.join(cliRoot, ".env"),
963
+ path3.join(initialRepoRoot, ".env")
964
+ ]);
965
+ const resolvedRepoRoot = process.env.WORKFLOW_REPO_ROOT ? path3.resolve(process.env.WORKFLOW_REPO_ROOT) : initialRepoRoot;
966
+ if (resolvedRepoRoot !== initialRepoRoot) {
967
+ loadEnvFiles([path3.join(resolvedRepoRoot, ".env")]);
968
+ }
969
+ }
970
+ function findRepoRoot(startDir) {
971
+ let current = path3.resolve(startDir);
972
+ while (true) {
973
+ if (existsSync(path3.join(current, "pnpm-workspace.yaml")) && existsSync(path3.join(current, "packages", "cli", "package.json"))) {
974
+ return current;
975
+ }
976
+ const parent = path3.dirname(current);
977
+ if (parent === current) return void 0;
978
+ current = parent;
979
+ }
980
+ }
981
+ function loadEnvFiles(filePaths) {
982
+ for (const filePath of unique(filePaths)) {
983
+ if (!existsSync(filePath)) {
984
+ continue;
985
+ }
986
+ const values = parse(readFileSync2(filePath));
987
+ for (const [key, value] of Object.entries(values)) {
988
+ if (process.env[key] === void 0) {
989
+ process.env[key] = value;
990
+ }
991
+ }
992
+ }
993
+ }
994
+ function unique(values) {
995
+ return [...new Set(values)];
996
+ }
997
+
998
+ // src/workspace.ts
999
+ var DEFAULT_SERVER_URL = "http://localhost:7125";
1000
+ var CLI_MODULE_DIR = path4.dirname(fileURLToPath(import.meta.url));
1001
+ var DEFAULT_DEVICE_FLOW_CLIENT_NAME = "Workflow Workspace CLI";
1002
+ loadCliEnv();
1003
+ async function main(argv = process.argv.slice(2), options = {}) {
1004
+ if (options.localeInitialized !== true) {
1005
+ argv = prepareCliInvocation(argv).argv;
1006
+ }
1007
+ const parsed = parseArgs(argv);
1008
+ switch (parsed.command) {
1009
+ case "login":
1010
+ await commandLogin(parsed);
1011
+ return;
1012
+ case "logout":
1013
+ await commandLogout();
1014
+ return;
1015
+ case "status":
1016
+ await commandStatus();
1017
+ return;
1018
+ case "pack":
1019
+ await commandPack(parsed);
1020
+ return;
1021
+ case "upload":
1022
+ await commandUpload(parsed);
1023
+ return;
1024
+ case "preparation":
1025
+ await commandPreparation(parsed);
1026
+ return;
1027
+ case "run":
1028
+ await commandRun(parsed);
1029
+ return;
1030
+ case "debug-node":
1031
+ await commandDebugNode(parsed);
1032
+ return;
1033
+ case "download":
1034
+ await commandDownload(parsed);
1035
+ return;
1036
+ case "publish":
1037
+ await commandPublish(parsed);
1038
+ return;
1039
+ case "versions":
1040
+ await commandVersions(parsed);
1041
+ return;
1042
+ case "health":
1043
+ await commandHealth(parsed);
1044
+ return;
1045
+ case "":
1046
+ case "help":
1047
+ case "--help":
1048
+ case "-h":
1049
+ printUsage();
1050
+ return;
1051
+ default:
1052
+ throw new Error(i18n.t("unknownWorkspaceCommand", { ns: "cli", command: parsed.command }));
1053
+ }
1054
+ }
1055
+ async function commandLogin(parsed) {
1056
+ const server = parsed.options.server.trim();
1057
+ if (server === "") {
1058
+ throw new Error(i18n.t("loginRequiresServer", { ns: "cli" }));
1059
+ }
1060
+ const started = await fetchApi({
1061
+ server,
1062
+ token: "",
1063
+ path: "/api/auth/device/start",
1064
+ method: "POST",
1065
+ body: {
1066
+ // issue #93 phase 1 moves workspace login to device flow and stores a
1067
+ // dedicated CLI auth file instead of sharing the App config.
1068
+ clientName: DEFAULT_DEVICE_FLOW_CLIENT_NAME,
1069
+ source: "cli"
1070
+ },
1071
+ auth: false
1072
+ });
1073
+ if (started.errCode !== 0 || started.data === void 0) {
1074
+ printApiResponse(started);
1075
+ assertApiOk(started);
1076
+ return;
1077
+ }
1078
+ const verificationUrl = started.data.verificationUriComplete || started.data.verificationUri;
1079
+ void openExternalBrowser(verificationUrl);
1080
+ console.error(i18n.t("deviceLoginPrompt", {
1081
+ ns: "cli",
1082
+ url: verificationUrl,
1083
+ code: started.data.userCode
1084
+ }));
1085
+ const completed = await waitForDeviceToken({
1086
+ server,
1087
+ deviceCode: started.data.deviceCode,
1088
+ intervalSeconds: started.data.intervalSeconds,
1089
+ expiresAt: started.data.expiresAt
1090
+ });
1091
+ if (completed.errCode !== 0 || completed.data === void 0) {
1092
+ printApiResponse(completed);
1093
+ assertApiOk(completed);
1094
+ return;
1095
+ }
1096
+ await writeCliAuthConfig({
1097
+ serverUrl: server,
1098
+ apiKey: completed.data.accessToken,
1099
+ expiresAt: completed.data.expiresAt ?? completed.data.apiKey.expiresAt,
1100
+ user: completed.data.user,
1101
+ source: "device_flow"
1102
+ });
1103
+ printApiResponse({
1104
+ errCode: 0,
1105
+ errMessage: "",
1106
+ data: {
1107
+ loggedIn: true,
1108
+ serverUrl: server,
1109
+ user: completed.data.user,
1110
+ expiresAt: completed.data.expiresAt ?? completed.data.apiKey.expiresAt,
1111
+ apiKeyPrefix: completed.data.apiKey.prefix
1112
+ }
1113
+ });
1114
+ }
1115
+ async function commandLogout() {
1116
+ await clearCliAuthConfig();
1117
+ printApiResponse({ errCode: 0, errMessage: "", data: { loggedIn: false } });
1118
+ }
1119
+ async function commandStatus() {
1120
+ const authConfig = await readCliAuthConfig();
1121
+ printApiResponse({
1122
+ errCode: 0,
1123
+ errMessage: "",
1124
+ data: {
1125
+ loggedIn: authConfig.serverUrl.trim() !== "" && authConfig.apiKey.trim() !== "",
1126
+ serverUrl: authConfig.serverUrl,
1127
+ user: authConfig.user,
1128
+ expiresAt: authConfig.expiresAt,
1129
+ expiringSoon: isWorkflowAuthExpiringSoon(authConfig.expiresAt),
1130
+ source: authConfig.source ?? null
1131
+ }
1132
+ });
1133
+ }
1134
+ async function commandPack(parsed) {
1135
+ const workflowName = requirePositional(parsed, 0, "workflow");
1136
+ if (parsed.options.projectGroup === true || parsed.options.publishGroup === true || parsed.options.dependencies.length > 0 || parsed.options.dataGrants.length > 0) {
1137
+ const packed = await packProjectGroup({
1138
+ workflowArg: workflowName,
1139
+ sourcePath: parsed.options.path,
1140
+ outputPath: parsed.options.output,
1141
+ dependencyOptions: parsed.options.dependencies
1142
+ });
1143
+ console.log(JSON.stringify({ archivePath: packed.archivePath, manifest: packed.manifest }, null, 2));
1144
+ return;
1145
+ }
1146
+ const archivePath = await packWorkflow(workflowName, parsed.options.output, parsed.options.path);
1147
+ console.log(JSON.stringify({ archivePath }, null, 2));
1148
+ }
1149
+ async function commandUpload(parsed) {
1150
+ requireLogin(parsed);
1151
+ const workflowArg = requirePositional(parsed, 0, "workflow");
1152
+ if (shouldUseProjectGroupUpload(parsed.options)) {
1153
+ await commandProjectGroupUpload(parsed, workflowArg);
1154
+ return;
1155
+ }
1156
+ const packageContext = await readWorkflowPackageContext(workflowArg, parsed.options.path);
1157
+ let workflowId = packageContext.workflowId;
1158
+ if (parsed.options.create === true) {
1159
+ const created = await ensureWorkflowProjectCreated({
1160
+ packageContext,
1161
+ server: parsed.options.server,
1162
+ token: parsed.options.token
1163
+ });
1164
+ workflowId = created.workflowId;
1165
+ }
1166
+ if (!workflowId) {
1167
+ throw new Error(i18n.t("workflowIdRequired", { ns: "cli" }));
1168
+ }
1169
+ const archivePath = parsed.options.file ? path4.resolve(parsed.options.file) : await packWorkflow(workflowArg, parsed.options.output, parsed.options.path);
1170
+ const uploadResult = await uploadPackage({
1171
+ workflowId,
1172
+ archivePath,
1173
+ server: parsed.options.server,
1174
+ token: parsed.options.token
1175
+ });
1176
+ if (uploadResult.errCode !== 0 || uploadResult.data === void 0) {
1177
+ printApiResponse(uploadResult);
1178
+ assertApiOk(uploadResult);
1179
+ return;
1180
+ }
1181
+ if (parsed.options.noWait === true) {
1182
+ printApiResponse(uploadResult);
1183
+ return;
1184
+ }
1185
+ const preparationResult = await waitForPreparation({
1186
+ workflowId,
1187
+ jobId: uploadResult.data.jobId,
1188
+ server: parsed.options.server,
1189
+ token: parsed.options.token
1190
+ });
1191
+ printApiResponse(preparationResult);
1192
+ assertApiOk(preparationResult);
1193
+ if (preparationResult.errCode !== 0) {
1194
+ return;
1195
+ }
1196
+ if (parsed.options.releaseLog !== void 0) {
1197
+ const publishResult = await fetchApi({
1198
+ server: parsed.options.server,
1199
+ token: parsed.options.token,
1200
+ path: `/api/workflows/${encodeURIComponent(workflowId)}/publish`,
1201
+ method: "POST",
1202
+ body: {
1203
+ releaseLog: parsed.options.releaseLog,
1204
+ sourceMode: parsed.options.sourceMode ?? "bundled"
1205
+ }
1206
+ });
1207
+ printApiResponse(publishResult);
1208
+ assertApiOk(publishResult);
1209
+ }
1210
+ }
1211
+ function shouldUseProjectGroupUpload(options) {
1212
+ return options.projectGroup === true || options.publishGroup === true || options.dependencies.length > 0 || options.dataGrants.length > 0;
1213
+ }
1214
+ async function commandProjectGroupUpload(parsed, workflowArg, forcePublish = false) {
1215
+ if (parsed.options.file !== void 0) {
1216
+ throw new Error(i18n.t("projectGroupUploadFileUnsupported", { ns: "cli" }));
1217
+ }
1218
+ let rootContext = await readWorkflowPackageContext(workflowArg, parsed.options.path);
1219
+ if (parsed.options.create === true || rootContext.workflowId !== void 0) {
1220
+ await ensureWorkflowProjectCreated({
1221
+ packageContext: rootContext,
1222
+ server: parsed.options.server,
1223
+ token: parsed.options.token
1224
+ });
1225
+ rootContext = await readWorkflowPackageContext(workflowArg, parsed.options.path);
1226
+ }
1227
+ if (!rootContext.workflowId) {
1228
+ throw new Error(i18n.t("projectGroupUploadRootIdRequired", { ns: "cli" }));
1229
+ }
1230
+ const contexts = await readProjectGroupContexts({
1231
+ workflowArg,
1232
+ sourcePath: parsed.options.path,
1233
+ dependencyOptions: parsed.options.dependencies
1234
+ });
1235
+ for (const dependency of contexts.dependencies) {
1236
+ await ensureWorkflowProjectCreated({
1237
+ packageContext: dependency,
1238
+ server: parsed.options.server,
1239
+ token: parsed.options.token
1240
+ });
1241
+ }
1242
+ const packed = await packProjectGroup({
1243
+ workflowArg,
1244
+ sourcePath: parsed.options.path,
1245
+ outputPath: parsed.options.output,
1246
+ dependencyOptions: parsed.options.dependencies,
1247
+ contexts
1248
+ });
1249
+ const planned = await fetchApi({
1250
+ server: parsed.options.server,
1251
+ token: parsed.options.token,
1252
+ path: "/api/deployments",
1253
+ method: "POST",
1254
+ body: {
1255
+ manifest: packed.manifest,
1256
+ releaseLog: parsed.options.releaseLog
1257
+ }
1258
+ });
1259
+ if (planned.errCode !== 0 || planned.data === void 0) {
1260
+ printApiResponse(planned);
1261
+ assertApiOk(planned);
1262
+ return;
1263
+ }
1264
+ const prepared = await uploadProjectGroupPackage({
1265
+ deploymentId: planned.data.id,
1266
+ archivePath: packed.archivePath,
1267
+ server: parsed.options.server,
1268
+ token: parsed.options.token
1269
+ });
1270
+ if (prepared.errCode !== 0 || prepared.data === void 0) {
1271
+ printApiResponse(prepared);
1272
+ assertApiOk(prepared);
1273
+ return;
1274
+ }
1275
+ const grantsByProject = resolveExplicitDataGrants(
1276
+ packed.manifest.dependencies,
1277
+ parsed.options.dataGrants
1278
+ );
1279
+ for (const [sourceProjectId, prefixes] of grantsByProject) {
1280
+ const grant = await fetchApi({
1281
+ server: parsed.options.server,
1282
+ token: parsed.options.token,
1283
+ path: `/api/workflows/${encodeURIComponent(sourceProjectId)}/data-source-grants/${encodeURIComponent(packed.manifest.rootProjectId)}`,
1284
+ method: "PUT",
1285
+ body: { keyPrefixes: prefixes }
1286
+ });
1287
+ if (grant.errCode !== 0) {
1288
+ printApiResponse(grant);
1289
+ assertApiOk(grant);
1290
+ return;
1291
+ }
1292
+ }
1293
+ if (!forcePublish && parsed.options.releaseLog === void 0 && parsed.options.publishGroup !== true) {
1294
+ printApiResponse(prepared);
1295
+ return;
1296
+ }
1297
+ const published = await fetchApi({
1298
+ server: parsed.options.server,
1299
+ token: parsed.options.token,
1300
+ path: `/api/deployments/${encodeURIComponent(planned.data.id)}/publish`,
1301
+ method: "POST"
1302
+ });
1303
+ printApiResponse(published);
1304
+ assertApiOk(published);
1305
+ }
1306
+ async function commandPreparation(parsed) {
1307
+ requireLogin(parsed);
1308
+ const workflowId = requirePositional(parsed, 0, "workflow");
1309
+ const jobId = requirePositional(parsed, 1, "jobId");
1310
+ const result = await getPreparation({
1311
+ workflowId,
1312
+ jobId,
1313
+ server: parsed.options.server,
1314
+ token: parsed.options.token
1315
+ });
1316
+ printApiResponse(result);
1317
+ assertApiOk(result);
1318
+ }
1319
+ async function commandRun(parsed) {
1320
+ requireLogin(parsed);
1321
+ const workflowId = await resolveRemoteWorkflowId(parsed);
1322
+ const workflowArgs = parsed.positional.slice(1);
1323
+ const result = await fetchApi({
1324
+ server: parsed.options.server,
1325
+ token: parsed.options.token,
1326
+ path: `/api/workflows/${encodeURIComponent(workflowId)}/run`,
1327
+ method: "POST",
1328
+ body: {
1329
+ target: parsed.options.target ?? "latest",
1330
+ entrypointId: parsed.options.entrypoint,
1331
+ args: workflowArgs
1332
+ }
1333
+ });
1334
+ printApiResponse(result);
1335
+ assertApiOk(result);
1336
+ }
1337
+ async function commandDebugNode(parsed) {
1338
+ requireLogin(parsed);
1339
+ const workflowId = await resolveRemoteWorkflowId(parsed);
1340
+ const nodeName = requirePositional(parsed, 1, "node");
1341
+ const workflowArgs = parsed.positional.slice(2);
1342
+ const result = await fetchApi({
1343
+ server: parsed.options.server,
1344
+ token: parsed.options.token,
1345
+ path: `/api/workflows/${encodeURIComponent(workflowId)}/debug/nodes/${encodeURIComponent(nodeName)}`,
1346
+ method: "POST",
1347
+ body: {
1348
+ target: parsed.options.target ?? "latest",
1349
+ entrypointId: parsed.options.entrypoint,
1350
+ args: workflowArgs
1351
+ }
1352
+ });
1353
+ printApiResponse(result);
1354
+ assertApiOk(result);
1355
+ }
1356
+ async function commandVersions(parsed) {
1357
+ requireLogin(parsed);
1358
+ const workflowId = await resolveRemoteWorkflowId(parsed);
1359
+ const result = await fetchApi({
1360
+ server: parsed.options.server,
1361
+ token: parsed.options.token,
1362
+ path: `/api/workflows/${encodeURIComponent(workflowId)}/versions`
1363
+ });
1364
+ printApiResponse(result);
1365
+ assertApiOk(result);
1366
+ }
1367
+ async function commandDownload(parsed) {
1368
+ requireLogin(parsed);
1369
+ const workflowArg = requirePositional(parsed, 0, "workflow");
1370
+ const workflowId = await resolveRemoteWorkflowId(parsed);
1371
+ const target = parsed.options.target ?? "latest";
1372
+ if (target !== "draft") {
1373
+ const result2 = await fetchApi({
1374
+ server: parsed.options.server,
1375
+ token: parsed.options.token,
1376
+ path: `/api/workflows/${encodeURIComponent(workflowId)}/project-group-download?target=${encodeURIComponent(target)}`
1377
+ });
1378
+ assertApiOk(result2);
1379
+ if (result2.errCode !== 0 || result2.data === void 0) {
1380
+ printApiResponse(result2);
1381
+ return;
1382
+ }
1383
+ const repoRoot2 = resolveRepoRoot();
1384
+ const rootTargetDir = parsed.options.path ? path4.resolve(parsed.options.path) : path4.join(repoRoot2, "workspace", "workflow", workflowArg);
1385
+ const materialized = await materializeProjectGroupDownload(result2.data, rootTargetDir);
1386
+ const locallyAvailableProjectIds = new Set(materialized.map((project) => project.projectId));
1387
+ const skippedDependencies = result2.data.skippedDependencies.filter((item) => item.dependency.delivery !== "included" || !locallyAvailableProjectIds.has(item.dependency.projectId));
1388
+ const complete = !skippedDependencies.some((item) => item.dependency.delivery === "included");
1389
+ printApiResponse({
1390
+ ...result2,
1391
+ data: {
1392
+ ...result2.data,
1393
+ complete,
1394
+ copyAllowed: complete,
1395
+ skippedDependencies,
1396
+ projects: materialized
1397
+ }
1398
+ });
1399
+ return;
1400
+ }
1401
+ const result = await fetchApi({
1402
+ server: parsed.options.server,
1403
+ token: parsed.options.token,
1404
+ path: `/api/workflows/${encodeURIComponent(workflowId)}/files?target=${encodeURIComponent(target)}&includeBinary=true`
1405
+ });
1406
+ printApiResponse(result);
1407
+ assertApiOk(result);
1408
+ if (result.errCode !== 0 || result.data === void 0) {
1409
+ return;
1410
+ }
1411
+ const repoRoot = resolveRepoRoot();
1412
+ const targetDir = parsed.options.path ? path4.resolve(parsed.options.path) : path4.join(repoRoot, "workspace", "workflow", workflowArg);
1413
+ await rm2(targetDir, { recursive: true, force: true });
1414
+ await mkdir3(targetDir, { recursive: true });
1415
+ for (const file of result.data.files) {
1416
+ const safePath = normalizeWorkflowFilePath(file.path);
1417
+ const targetPath = path4.join(targetDir, safePath);
1418
+ await mkdir3(path4.dirname(targetPath), { recursive: true });
1419
+ await writeFile2(targetPath, decodeWorkflowFileContent(file));
1420
+ }
1421
+ }
1422
+ async function commandPublish(parsed) {
1423
+ requireLogin(parsed);
1424
+ if (parsed.options.deployment !== void 0) {
1425
+ const publishResult2 = await fetchApi({
1426
+ server: parsed.options.server,
1427
+ token: parsed.options.token,
1428
+ path: `/api/deployments/${encodeURIComponent(parsed.options.deployment)}/publish`,
1429
+ method: "POST"
1430
+ });
1431
+ printApiResponse(publishResult2);
1432
+ assertApiOk(publishResult2);
1433
+ return;
1434
+ }
1435
+ if (shouldUseProjectGroupUpload(parsed.options)) {
1436
+ const workflowArg = requirePositional(parsed, 0, "workflow");
1437
+ await commandProjectGroupUpload(parsed, workflowArg, true);
1438
+ return;
1439
+ }
1440
+ const workflowId = await resolveRemoteWorkflowId(parsed);
1441
+ const publishResult = await fetchApi({
1442
+ server: parsed.options.server,
1443
+ token: parsed.options.token,
1444
+ path: `/api/workflows/${encodeURIComponent(workflowId)}/publish`,
1445
+ method: "POST",
1446
+ body: {
1447
+ releaseLog: parsed.options.releaseLog,
1448
+ sourceMode: parsed.options.sourceMode ?? "bundled"
1449
+ }
1450
+ });
1451
+ printApiResponse(publishResult);
1452
+ assertApiOk(publishResult);
1453
+ }
1454
+ async function materializeProjectGroupDownload(download, rootTargetDir) {
1455
+ if (!isWorkflowUuid(download.rootProjectId)) {
1456
+ throw new Error(i18n.t("projectGroupDownloadInvalidRootId", { ns: "cli" }));
1457
+ }
1458
+ const dependencies = parseWorkflowProjectDependencyLocks(download.dependencies, { published: true });
1459
+ const includedDependencies = dependencies.filter((dependency) => dependency.delivery === "included");
1460
+ const includedProjectIds = new Set(includedDependencies.map((dependency) => dependency.projectId));
1461
+ const rootProjects = download.projects.filter((project) => project.role === "root");
1462
+ if (rootProjects.length !== 1 || rootProjects[0]?.projectId !== download.rootProjectId) {
1463
+ throw new Error(i18n.t("projectGroupDownloadRootCountInvalid", { ns: "cli" }));
1464
+ }
1465
+ const rootProject = rootProjects[0];
1466
+ if (rootProject.version !== download.rootVersion) {
1467
+ throw new Error(i18n.t("projectGroupDownloadRootVersionMismatch", { ns: "cli" }));
1468
+ }
1469
+ const rootPackageFile = rootProject.files.find((file) => normalizeWorkflowFilePath(file.path) === "package.json");
1470
+ if (rootPackageFile === void 0 || rootPackageFile.encoding === "base64") {
1471
+ throw new Error(i18n.t("projectGroupDownloadRootPackageUtf8", { ns: "cli" }));
1472
+ }
1473
+ let rootPackageJson;
1474
+ try {
1475
+ rootPackageJson = JSON.parse(rootPackageFile.content);
1476
+ } catch {
1477
+ throw new Error(i18n.t("projectGroupDownloadRootPackageInvalidJson", { ns: "cli" }));
1478
+ }
1479
+ const declaredSources = readKanbanArtifactConfig2(rootPackageJson).dataSources;
1480
+ const selfReference = declaredSources.find((source) => source.projectId === download.rootProjectId);
1481
+ if (selfReference !== void 0) {
1482
+ throw new Error(i18n.t("projectGroupDownloadSelfReference", {
1483
+ ns: "cli",
1484
+ sourceId: JSON.stringify(selfReference.id)
1485
+ }));
1486
+ }
1487
+ if (declaredSources.length !== dependencies.length || declaredSources.some((source) => {
1488
+ const dependency = dependencies.find((candidate) => candidate.sourceId === source.id);
1489
+ return dependency === void 0 || dependency.kind !== source.kind || dependency.projectId !== source.projectId || JSON.stringify(dependency.keyPrefixes) !== JSON.stringify(source.keyPrefixes);
1490
+ })) {
1491
+ throw new Error(i18n.t("projectGroupDownloadLockMismatch", { ns: "cli" }));
1492
+ }
1493
+ const responseProjectIds = /* @__PURE__ */ new Set();
1494
+ for (const project of download.projects) {
1495
+ if (!isWorkflowUuid(project.projectId) || responseProjectIds.has(project.projectId)) {
1496
+ throw new Error(i18n.t("projectGroupDownloadInvalidOrDuplicateProjectId", {
1497
+ ns: "cli",
1498
+ projectId: JSON.stringify(project.projectId)
1499
+ }));
1500
+ }
1501
+ responseProjectIds.add(project.projectId);
1502
+ const filePaths = /* @__PURE__ */ new Set();
1503
+ for (const file of project.files) {
1504
+ const safePath = normalizeWorkflowFilePath(file.path);
1505
+ if (isProjectGroupHostDataPath(safePath)) {
1506
+ throw new Error(i18n.t("projectGroupDownloadForbiddenHostData", {
1507
+ ns: "cli",
1508
+ path: JSON.stringify(file.path)
1509
+ }));
1510
+ }
1511
+ const fileKey = createPortableLocalPathKey(safePath);
1512
+ if (filePaths.has(fileKey)) {
1513
+ throw new Error(i18n.t("projectGroupDownloadDuplicateFile", {
1514
+ ns: "cli",
1515
+ path: JSON.stringify(safePath)
1516
+ }));
1517
+ }
1518
+ filePaths.add(fileKey);
1519
+ }
1520
+ const packageFile = project.files.find((file) => normalizeWorkflowFilePath(file.path) === "package.json");
1521
+ if (packageFile === void 0 || packageFile.encoding === "base64") {
1522
+ throw new Error(i18n.t("projectGroupDownloadPackageUtf8", { ns: "cli", role: project.role }));
1523
+ }
1524
+ let packageJson;
1525
+ try {
1526
+ packageJson = JSON.parse(packageFile.content);
1527
+ } catch {
1528
+ throw new Error(i18n.t("projectGroupDownloadPackageInvalidJson", { ns: "cli", role: project.role }));
1529
+ }
1530
+ if (packageJson["id"] !== project.projectId) {
1531
+ throw new Error(i18n.t("projectGroupDownloadedPackageIdMismatch", {
1532
+ ns: "cli",
1533
+ projectId: JSON.stringify(project.projectId)
1534
+ }));
1535
+ }
1536
+ if (hashWorkflowSourceFiles(project.files) !== project.sourceHash) {
1537
+ throw new Error(i18n.t("projectGroupDownloadSourceHashMismatch", {
1538
+ ns: "cli",
1539
+ role: project.role,
1540
+ projectId: JSON.stringify(project.projectId)
1541
+ }));
1542
+ }
1543
+ if (project.role === "root" && project.sourceId !== void 0) {
1544
+ throw new Error(i18n.t("projectGroupDownloadRootSourceIdForbidden", { ns: "cli" }));
1545
+ }
1546
+ if (project.role === "dependency") {
1547
+ const matchingDependencies = includedDependencies.filter((dependency) => dependency.projectId === project.projectId && dependency.version === project.version && dependency.sourceHash === project.sourceHash);
1548
+ if (matchingDependencies.length === 0 || project.sourceId !== void 0 && !matchingDependencies.some((dependency) => dependency.sourceId === project.sourceId)) {
1549
+ throw new Error(i18n.t("projectGroupDownloadUndeclaredDependency", {
1550
+ ns: "cli",
1551
+ projectId: JSON.stringify(project.projectId)
1552
+ }));
1553
+ }
1554
+ }
1555
+ }
1556
+ const parentDir = path4.dirname(rootTargetDir);
1557
+ const existingById = await findSiblingProjectsById(parentDir);
1558
+ const materialized = [];
1559
+ const pending = [];
1560
+ const targetKeys = /* @__PURE__ */ new Set();
1561
+ const orderedProjects = [...download.projects].sort((left, right) => left.role === right.role ? left.projectId.localeCompare(right.projectId) : left.role === "root" ? -1 : 1);
1562
+ for (const project of orderedProjects) {
1563
+ const existingPath = existingById.get(project.projectId);
1564
+ if (existingPath !== void 0) {
1565
+ await assertDownloadedProjectRole(existingPath, project.projectId, project.role);
1566
+ materialized.push({
1567
+ ...project.sourceId === void 0 ? {} : { sourceId: project.sourceId },
1568
+ projectId: project.projectId,
1569
+ projectName: project.projectName,
1570
+ role: project.role,
1571
+ path: existingPath,
1572
+ alreadyAvailable: true
1573
+ });
1574
+ continue;
1575
+ }
1576
+ const targetDir = project.role === "root" ? rootTargetDir : path4.join(parentDir, createProjectDirectoryName(project.projectName, project.projectId));
1577
+ if (existsSync2(targetDir)) {
1578
+ throw new Error(i18n.t("projectGroupDownloadDirectoryProjectMismatch", {
1579
+ ns: "cli",
1580
+ projectName: JSON.stringify(project.projectName),
1581
+ path: JSON.stringify(targetDir)
1582
+ }));
1583
+ }
1584
+ const targetKey = createPortableLocalPathKey(path4.resolve(targetDir));
1585
+ if (targetKeys.has(targetKey)) {
1586
+ throw new Error(i18n.t("projectGroupDownloadDirectoryCollision", {
1587
+ ns: "cli",
1588
+ projectName: JSON.stringify(project.projectName),
1589
+ path: JSON.stringify(targetDir)
1590
+ }));
1591
+ }
1592
+ targetKeys.add(targetKey);
1593
+ pending.push({ project, targetDir });
1594
+ }
1595
+ for (const projectId of includedProjectIds) {
1596
+ if (responseProjectIds.has(projectId)) continue;
1597
+ const existingPath = existingById.get(projectId);
1598
+ if (existingPath === void 0) continue;
1599
+ const packageJson = await assertDownloadedProjectRole(existingPath, projectId, "dependency");
1600
+ materialized.push({
1601
+ projectId,
1602
+ projectName: readRequiredPackageString(packageJson, "name"),
1603
+ role: "dependency",
1604
+ path: existingPath,
1605
+ alreadyAvailable: true
1606
+ });
1607
+ }
1608
+ await mkdir3(parentDir, { recursive: true });
1609
+ const stagingRoot = path4.join(parentDir, `.workflow-download-group-${randomUUID()}`);
1610
+ const staged = [];
1611
+ const moved = [];
1612
+ try {
1613
+ await mkdir3(stagingRoot, { recursive: false });
1614
+ for (const item of pending) {
1615
+ const { project, targetDir } = item;
1616
+ const stagingDir = path4.join(stagingRoot, project.projectId);
1617
+ await mkdir3(stagingDir, { recursive: true });
1618
+ for (const file of project.files) {
1619
+ const safePath = normalizeWorkflowFilePath(file.path);
1620
+ const targetPath = path4.join(stagingDir, ...safePath.split("/"));
1621
+ await mkdir3(path4.dirname(targetPath), { recursive: true });
1622
+ await writeFile2(targetPath, decodeWorkflowFileContent(file));
1623
+ }
1624
+ const packageJson = await readPackageJsonFile(path4.join(stagingDir, "package.json"));
1625
+ if (packageJson["id"] !== project.projectId) {
1626
+ throw new Error(i18n.t("projectGroupDownloadedPackageIdMismatch", {
1627
+ ns: "cli",
1628
+ projectId: JSON.stringify(project.projectId)
1629
+ }));
1630
+ }
1631
+ await assertDownloadedProjectRole(stagingDir, project.projectId, project.role, packageJson);
1632
+ staged.push({ project, targetDir, stagingDir });
1633
+ }
1634
+ for (const item of staged) {
1635
+ await rename(item.stagingDir, item.targetDir);
1636
+ moved.push(item.targetDir);
1637
+ existingById.set(item.project.projectId, item.targetDir);
1638
+ materialized.push({
1639
+ ...item.project.sourceId === void 0 ? {} : { sourceId: item.project.sourceId },
1640
+ projectId: item.project.projectId,
1641
+ projectName: item.project.projectName,
1642
+ role: item.project.role,
1643
+ path: item.targetDir,
1644
+ alreadyAvailable: false
1645
+ });
1646
+ }
1647
+ } catch (error) {
1648
+ for (const targetDir of moved) {
1649
+ await rm2(targetDir, { recursive: true, force: true }).catch(() => void 0);
1650
+ }
1651
+ throw error;
1652
+ } finally {
1653
+ await rm2(stagingRoot, { recursive: true, force: true }).catch(() => void 0);
1654
+ }
1655
+ return materialized.sort((left, right) => left.role === right.role ? left.projectId.localeCompare(right.projectId) : left.role === "root" ? -1 : 1);
1656
+ }
1657
+ async function assertDownloadedProjectRole(projectDir, projectId, role, packageJson) {
1658
+ const metadata = packageJson ?? await readPackageJsonFile(path4.join(projectDir, "package.json"));
1659
+ if (metadata["id"] !== projectId) {
1660
+ throw new Error(i18n.t("projectGroupDownloadedPackageIdMismatch", {
1661
+ ns: "cli",
1662
+ projectId: JSON.stringify(projectId)
1663
+ }));
1664
+ }
1665
+ const projectType = inspectLocalWorkflowStructure({ workflowDir: projectDir }).projectType;
1666
+ const expectedProjectType = role === "root" ? "kanban" : "workflow";
1667
+ if (projectType !== expectedProjectType) {
1668
+ throw new Error(i18n.t("projectGroupDownloadedProjectTypeMismatch", {
1669
+ ns: "cli",
1670
+ projectId: JSON.stringify(projectId),
1671
+ projectType: expectedProjectType
1672
+ }));
1673
+ }
1674
+ return metadata;
1675
+ }
1676
+ function createPortableLocalPathKey(filePath) {
1677
+ return filePath.normalize("NFC").toLowerCase();
1678
+ }
1679
+ function resolveExplicitDataGrants(dependencies, sourceIds) {
1680
+ const selected = /* @__PURE__ */ new Set();
1681
+ for (const sourceId of sourceIds) {
1682
+ if (selected.has(sourceId)) {
1683
+ throw new Error(i18n.t("duplicateGrantDataAccessSource", {
1684
+ ns: "cli",
1685
+ sourceId: JSON.stringify(sourceId)
1686
+ }));
1687
+ }
1688
+ selected.add(sourceId);
1689
+ }
1690
+ const grants = /* @__PURE__ */ new Map();
1691
+ for (const sourceId of selected) {
1692
+ const dependency = dependencies.find((candidate) => candidate.sourceId === sourceId);
1693
+ if (dependency === void 0) {
1694
+ throw new Error(i18n.t("unknownGrantDataAccessSource", {
1695
+ ns: "cli",
1696
+ sourceId: JSON.stringify(sourceId)
1697
+ }));
1698
+ }
1699
+ grants.set(dependency.projectId, [
1700
+ ...grants.get(dependency.projectId) ?? [],
1701
+ ...dependency.keyPrefixes
1702
+ ]);
1703
+ }
1704
+ for (const [projectId, prefixes] of grants) {
1705
+ grants.set(projectId, [...new Set(prefixes)]);
1706
+ }
1707
+ return grants;
1708
+ }
1709
+ async function findSiblingProjectsById(parentDir) {
1710
+ const projects = /* @__PURE__ */ new Map();
1711
+ const entries = await readdir2(parentDir, { withFileTypes: true }).catch(() => []);
1712
+ for (const entry of entries) {
1713
+ if (!entry.isDirectory()) continue;
1714
+ const projectDir = path4.join(parentDir, entry.name);
1715
+ const packageJson = await readPackageJsonFile(path4.join(projectDir, "package.json")).catch(() => void 0);
1716
+ const projectId = packageJson?.["id"];
1717
+ if (typeof projectId === "string" && isWorkflowUuid(projectId) && !projects.has(projectId)) {
1718
+ projects.set(projectId, projectDir);
1719
+ }
1720
+ }
1721
+ return projects;
1722
+ }
1723
+ function createProjectDirectoryName(projectName, projectId) {
1724
+ const normalized = projectName.normalize("NFC").replace(/^@/, "").replace(/[\\/:*?"<>|\u0000-\u001f]/g, "-").replace(/^\.+|\.+$/g, "").trim();
1725
+ return normalized === "" ? projectId : normalized;
1726
+ }
1727
+ async function commandHealth(parsed) {
1728
+ const result = await fetchApi({
1729
+ server: parsed.options.server,
1730
+ token: "",
1731
+ path: "/health",
1732
+ auth: false
1733
+ });
1734
+ printApiResponse(result);
1735
+ assertApiOk(result);
1736
+ }
1737
+ async function packWorkflow(workflowName, outputPath, sourcePath, options = {}) {
1738
+ const repoRoot = resolveRepoRoot();
1739
+ const workflowDir = sourcePath === void 0 ? path4.join(repoRoot, "workspace", "workflow", workflowName) : path4.resolve(sourcePath);
1740
+ await readPackageJsonFile(path4.join(workflowDir, "package.json"));
1741
+ const packsDir = process.env.WORKFLOW_WORKSPACE_PACKS_DIR ? path4.resolve(process.env.WORKFLOW_WORKSPACE_PACKS_DIR) : path4.join(repoRoot, "workspace", ".packs");
1742
+ const stagingDir = path4.join(packsDir, "tmp", randomUUID(), workflowName);
1743
+ const archivePath = outputPath ? path4.resolve(outputPath) : path4.join(packsDir, `${workflowName}-${createTimestamp()}.tgz`);
1744
+ const workspaceRoot = sourcePath === void 0 ? path4.join(repoRoot, "workspace", "workflow") : path4.dirname(workflowDir);
1745
+ await rm2(path4.dirname(stagingDir), { recursive: true, force: true });
1746
+ await mkdir3(path4.join(stagingDir, "workspace", "workflow"), { recursive: true });
1747
+ await mkdir3(path4.dirname(archivePath), { recursive: true });
1748
+ await stageWorkflowWithLocalDependencies({
1749
+ workspaceRoot,
1750
+ workflowDir,
1751
+ workflowName,
1752
+ stagingRoot: path4.join(stagingDir, "workspace", "workflow"),
1753
+ projectGroupSafe: options.projectGroupSafe === true
1754
+ });
1755
+ const result = await runCommand("tar", ["-czf", archivePath, "-C", stagingDir, "."], {
1756
+ cwd: repoRoot
1757
+ });
1758
+ await rm2(path4.dirname(stagingDir), { recursive: true, force: true });
1759
+ if (result.exitCode !== 0) {
1760
+ throw new Error(i18n.t("archiveCreateFailed", {
1761
+ ns: "cli",
1762
+ details: result.stderr || result.stdout
1763
+ }));
1764
+ }
1765
+ return archivePath;
1766
+ }
1767
+ async function readProjectGroupContexts(options) {
1768
+ const root = await readWorkflowPackageContext(options.workflowArg, options.sourcePath);
1769
+ if (root.projectType !== "kanban") {
1770
+ throw new Error(i18n.t("projectGroupRootMustBeKanban", { ns: "cli" }));
1771
+ }
1772
+ if (!root.workflowId) {
1773
+ throw new Error(i18n.t("projectGroupRootIdRequired", { ns: "cli" }));
1774
+ }
1775
+ const dataSources = readKanbanArtifactConfig2(root.packageJson).dataSources;
1776
+ const selfReference = dataSources.find((source) => source.projectId === root.workflowId);
1777
+ if (selfReference !== void 0) {
1778
+ throw new Error(i18n.t("projectGroupSelfReference", {
1779
+ ns: "cli",
1780
+ sourceId: JSON.stringify(selfReference.id)
1781
+ }));
1782
+ }
1783
+ const dependencyPaths = /* @__PURE__ */ new Map();
1784
+ for (const option of options.dependencyOptions) {
1785
+ const separator = option.indexOf("=");
1786
+ const sourceId = separator < 1 ? "" : option.slice(0, separator).trim();
1787
+ const dependencyPath = separator < 0 ? "" : option.slice(separator + 1).trim();
1788
+ if (sourceId === "" || dependencyPath === "") {
1789
+ throw new Error(i18n.t("invalidDependencyOption", {
1790
+ ns: "cli",
1791
+ option: JSON.stringify(option)
1792
+ }));
1793
+ }
1794
+ if (dependencyPaths.has(sourceId)) {
1795
+ throw new Error(i18n.t("duplicateDependencySource", {
1796
+ ns: "cli",
1797
+ sourceId: JSON.stringify(sourceId)
1798
+ }));
1799
+ }
1800
+ dependencyPaths.set(sourceId, path4.resolve(dependencyPath));
1801
+ }
1802
+ const locks = createWorkflowProjectDependencyLocks(
1803
+ dataSources,
1804
+ [...dependencyPaths.keys()].map((sourceId) => ({ sourceId, delivery: "included" }))
1805
+ );
1806
+ const dependenciesByProject = /* @__PURE__ */ new Map();
1807
+ for (const [sourceId, dependencyPath] of dependencyPaths) {
1808
+ const source = dataSources.find((candidate) => candidate.id === sourceId);
1809
+ const context = await readWorkflowPackageContext(sourceId, dependencyPath);
1810
+ if (context.projectType !== "workflow") {
1811
+ throw new Error(i18n.t("dependencyMustBeWorkflow", {
1812
+ ns: "cli",
1813
+ sourceId: JSON.stringify(sourceId)
1814
+ }));
1815
+ }
1816
+ if (context.workflowId !== source.projectId) {
1817
+ throw new Error(i18n.t("dependencyPackageIdMismatch", {
1818
+ ns: "cli",
1819
+ sourceId: JSON.stringify(sourceId),
1820
+ projectId: JSON.stringify(source.projectId)
1821
+ }));
1822
+ }
1823
+ const existing = dependenciesByProject.get(source.projectId);
1824
+ if (existing !== void 0 && existing.workflowDir !== context.workflowDir) {
1825
+ throw new Error(i18n.t("dependencyProjectPathConflict", {
1826
+ ns: "cli",
1827
+ projectId: JSON.stringify(source.projectId)
1828
+ }));
1829
+ }
1830
+ dependenciesByProject.set(source.projectId, context);
1831
+ }
1832
+ return { root, dependencies: [...dependenciesByProject.values()], locks };
1833
+ }
1834
+ async function packProjectGroup(options) {
1835
+ const contexts = options.contexts ?? await readProjectGroupContexts(options);
1836
+ const repoRoot = resolveRepoRoot();
1837
+ const packsDir = process.env.WORKFLOW_WORKSPACE_PACKS_DIR ? path4.resolve(process.env.WORKFLOW_WORKSPACE_PACKS_DIR) : path4.join(repoRoot, "workspace", ".packs");
1838
+ const groupRoot = path4.join(packsDir, "tmp", `project-group-${randomUUID()}`);
1839
+ const projectsDir = path4.join(groupRoot, "projects");
1840
+ const archivePath = options.outputPath ? path4.resolve(options.outputPath) : path4.join(packsDir, `project-group-${createTimestamp()}.tgz`);
1841
+ await mkdir3(projectsDir, { recursive: true });
1842
+ await mkdir3(path4.dirname(archivePath), { recursive: true });
1843
+ const rootArchivePath = `projects/${contexts.root.workflowId}.tgz`;
1844
+ const projects = [{
1845
+ projectId: contexts.root.workflowId,
1846
+ role: "root",
1847
+ archivePath: rootArchivePath
1848
+ }];
1849
+ try {
1850
+ await packWorkflow(
1851
+ contexts.root.workflowArg,
1852
+ path4.join(groupRoot, ...rootArchivePath.split("/")),
1853
+ contexts.root.workflowDir,
1854
+ { projectGroupSafe: true }
1855
+ );
1856
+ for (const dependency of contexts.dependencies) {
1857
+ const dependencyArchivePath = `projects/${dependency.workflowId}.tgz`;
1858
+ projects.push({
1859
+ projectId: dependency.workflowId,
1860
+ role: "dependency",
1861
+ archivePath: dependencyArchivePath
1862
+ });
1863
+ await packWorkflow(
1864
+ dependency.workflowArg,
1865
+ path4.join(groupRoot, ...dependencyArchivePath.split("/")),
1866
+ dependency.workflowDir,
1867
+ { projectGroupSafe: true }
1868
+ );
1869
+ }
1870
+ const manifest = {
1871
+ version: 1,
1872
+ rootProjectId: contexts.root.workflowId,
1873
+ projects,
1874
+ dependencies: contexts.locks
1875
+ };
1876
+ await writeFile2(path4.join(groupRoot, "project-group.json"), `${JSON.stringify(manifest, null, 2)}
1877
+ `);
1878
+ const result = await runCommand("tar", ["-czf", archivePath, "-C", groupRoot, "."], { cwd: repoRoot });
1879
+ if (result.exitCode !== 0) {
1880
+ throw new Error(i18n.t("projectGroupArchiveCreateFailed", {
1881
+ ns: "cli",
1882
+ details: result.stderr || result.stdout
1883
+ }));
1884
+ }
1885
+ return {
1886
+ archivePath,
1887
+ manifest,
1888
+ root: contexts.root,
1889
+ dependencies: contexts.dependencies
1890
+ };
1891
+ } finally {
1892
+ await rm2(groupRoot, { recursive: true, force: true });
1893
+ }
1894
+ }
1895
+ async function uploadProjectGroupPackage(options) {
1896
+ const archiveBuffer = await readFile3(options.archivePath);
1897
+ const archiveName = path4.basename(options.archivePath);
1898
+ const response = await fetch(
1899
+ `${trimTrailingSlash(options.server)}/api/deployments/${encodeURIComponent(options.deploymentId)}/package?fileName=${encodeURIComponent(archiveName)}`,
1900
+ {
1901
+ method: "POST",
1902
+ headers: {
1903
+ authorization: `Bearer ${options.token}`,
1904
+ "content-type": "application/octet-stream",
1905
+ "x-workflow-package-name": archiveName
1906
+ },
1907
+ body: new Blob([archiveBuffer])
1908
+ }
1909
+ );
1910
+ return response.json();
1911
+ }
1912
+ async function resolveRemoteWorkflowId(parsed) {
1913
+ const workflowArg = requirePositional(parsed, 0, "workflow");
1914
+ if (isWorkflowUuid(workflowArg)) {
1915
+ return workflowArg;
1916
+ }
1917
+ const packageContext = await readWorkflowPackageContext(workflowArg, parsed.options.path);
1918
+ if (packageContext.workflowId) {
1919
+ return packageContext.workflowId;
1920
+ }
1921
+ throw new Error(i18n.t("workflowNotBound", {
1922
+ ns: "cli",
1923
+ workflow: workflowArg
1924
+ }));
1925
+ }
1926
+ async function readWorkflowPackageContext(workflowArg, sourcePath) {
1927
+ const repoRoot = resolveRepoRoot();
1928
+ const workflowDir = sourcePath === void 0 ? path4.join(repoRoot, "workspace", "workflow", workflowArg) : path4.resolve(sourcePath);
1929
+ const packageJsonPath = path4.join(workflowDir, "package.json");
1930
+ const packageJson = await readPackageJsonFile(packageJsonPath);
1931
+ const workflowId = typeof packageJson.id === "string" && isWorkflowUuid(packageJson.id) ? packageJson.id.trim() : void 0;
1932
+ const workflowName = readRequiredPackageString(packageJson, "name");
1933
+ const projectType = inspectLocalWorkflowStructure({ workflowDir }).projectType;
1934
+ return {
1935
+ workflowArg,
1936
+ workflowDir,
1937
+ packageJsonPath,
1938
+ workflowId,
1939
+ workflowName,
1940
+ projectType,
1941
+ packageJson
1942
+ };
1943
+ }
1944
+ async function ensureWorkflowProjectCreated(options) {
1945
+ const created = await fetchApi({
1946
+ server: options.server,
1947
+ token: options.token,
1948
+ path: "/api/workflows",
1949
+ method: "POST",
1950
+ body: {
1951
+ name: options.packageContext.workflowName,
1952
+ projectType: options.packageContext.projectType,
1953
+ ...options.packageContext.workflowId ? { workflowId: options.packageContext.workflowId } : {}
1954
+ }
1955
+ });
1956
+ if (created.errCode !== 0 || created.data === void 0) {
1957
+ printApiResponse(created);
1958
+ assertApiOk(created);
1959
+ throw new Error(i18n.t("projectCreationFailed", { ns: "cli" }));
1960
+ }
1961
+ if (!options.packageContext.workflowId || options.packageContext.workflowId !== created.data.workflowId) {
1962
+ await writeWorkflowPackageId(options.packageContext.packageJsonPath, created.data.workflowId);
1963
+ }
1964
+ return created.data;
1965
+ }
1966
+ async function readPackageJsonFile(packageJsonPath) {
1967
+ try {
1968
+ const raw = JSON.parse(await readFile3(packageJsonPath, "utf8"));
1969
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1970
+ throw new Error(i18n.t("packageMustBeObject", { ns: "cli", path: packageJsonPath }));
1971
+ }
1972
+ return raw;
1973
+ } catch (error) {
1974
+ if (error instanceof Error) {
1975
+ throw error;
1976
+ }
1977
+ throw new Error(i18n.t("packageReadFailed", { ns: "cli", path: packageJsonPath }));
1978
+ }
1979
+ }
1980
+ function readRequiredPackageString(packageJson, key) {
1981
+ const value = packageJson[key];
1982
+ if (typeof value !== "string" || value.trim() === "") {
1983
+ throw new Error(i18n.t("packageStringRequired", { ns: "cli", key }));
1984
+ }
1985
+ return value.trim();
1986
+ }
1987
+ async function writeWorkflowPackageId(packageJsonPath, workflowId) {
1988
+ const packageJson = await readPackageJsonFile(packageJsonPath);
1989
+ await writeFile2(
1990
+ packageJsonPath,
1991
+ `${JSON.stringify({
1992
+ ...packageJson,
1993
+ id: workflowId
1994
+ }, null, 2)}
1995
+ `
1996
+ );
1997
+ }
1998
+ async function uploadPackage(options) {
1999
+ const archiveBuffer = await readFile3(options.archivePath);
2000
+ const archiveName = path4.basename(options.archivePath);
2001
+ const response = await fetch(
2002
+ `${trimTrailingSlash(options.server)}/api/workflows/${encodeURIComponent(options.workflowId)}/package?fileName=${encodeURIComponent(archiveName)}`,
2003
+ {
2004
+ method: "POST",
2005
+ headers: {
2006
+ authorization: `Bearer ${options.token}`,
2007
+ "content-type": "application/octet-stream",
2008
+ "x-workflow-package-name": archiveName,
2009
+ "x-workflow-locale": getCurrentLocale()
2010
+ },
2011
+ body: new Blob([archiveBuffer])
2012
+ }
2013
+ );
2014
+ return response.json();
2015
+ }
2016
+ async function waitForPreparation(options) {
2017
+ let previousStage = "";
2018
+ while (true) {
2019
+ const response = await fetchApi({
2020
+ server: options.server,
2021
+ token: options.token,
2022
+ path: `/api/workflows/${encodeURIComponent(options.workflowId)}/preparations/${encodeURIComponent(options.jobId)}`
2023
+ });
2024
+ if (response.errCode !== 0 || response.data === void 0) {
2025
+ return response;
2026
+ }
2027
+ if (response.data.stage !== previousStage) {
2028
+ previousStage = response.data.stage;
2029
+ process.stderr.write(`${i18n.t("preparationProgress", {
2030
+ ns: "cli",
2031
+ status: response.data.status,
2032
+ stage: response.data.stage
2033
+ })}
2034
+ `);
2035
+ }
2036
+ if (response.data.status === "success") {
2037
+ return {
2038
+ errCode: 0,
2039
+ errMessage: "",
2040
+ data: response.data.result
2041
+ };
2042
+ }
2043
+ if (response.data.status === "failed") {
2044
+ return {
2045
+ errCode: 500,
2046
+ errMessage: response.data.failureReason ?? `Preparation failed during ${response.data.stage}.`,
2047
+ data: response.data
2048
+ };
2049
+ }
2050
+ await delay(options.pollIntervalMs ?? 1e3);
2051
+ }
2052
+ }
2053
+ async function getPreparation(options) {
2054
+ return fetchApi({
2055
+ server: options.server,
2056
+ token: options.token,
2057
+ path: `/api/workflows/${encodeURIComponent(options.workflowId)}/preparations/${encodeURIComponent(options.jobId)}`
2058
+ });
2059
+ }
2060
+ async function fetchApi(options) {
2061
+ const headers = {};
2062
+ if (options.auth !== false) {
2063
+ headers.authorization = `Bearer ${options.token}`;
2064
+ }
2065
+ if (options.body !== void 0) {
2066
+ headers["content-type"] = "application/json";
2067
+ }
2068
+ headers["x-workflow-locale"] = getCurrentLocale();
2069
+ const response = await fetch(`${trimTrailingSlash(options.server)}${options.path}`, {
2070
+ method: options.method ?? "GET",
2071
+ headers,
2072
+ body: options.body === void 0 ? void 0 : JSON.stringify(options.body)
2073
+ });
2074
+ return response.json();
2075
+ }
2076
+ async function copyWorkflowSource(sourceDir, targetDir, options = {}) {
2077
+ if (options.projectGroupSafe !== true) return copyWorkflowUploadFiles(sourceDir, targetDir);
2078
+ const plan = await createWorkflowUploadPlan(sourceDir);
2079
+ const files = plan.files.filter((file) => !isProjectGroupHostDataPath(file.path));
2080
+ const forbiddenRequiredPath = plan.requiredPaths.find((requiredPath) => isProjectGroupHostDataPath(requiredPath));
2081
+ if (forbiddenRequiredPath !== void 0) {
2082
+ throw new Error(i18n.t("projectGroupRequiredPathForbidden", {
2083
+ ns: "cli",
2084
+ path: JSON.stringify(forbiddenRequiredPath)
2085
+ }));
2086
+ }
2087
+ await mkdir3(targetDir, { recursive: true });
2088
+ for (const file of files) {
2089
+ const targetPath = path4.join(targetDir, ...file.path.split("/"));
2090
+ await mkdir3(path4.dirname(targetPath), { recursive: true });
2091
+ await copyFile2(file.absolutePath, targetPath);
2092
+ }
2093
+ return {
2094
+ ...plan,
2095
+ files,
2096
+ ignoredPaths: [...plan.ignoredPaths, ...plan.files.filter((file) => isProjectGroupHostDataPath(file.path)).map((file) => file.path)].sort((left, right) => left.localeCompare(right)),
2097
+ totalBytes: files.reduce((total, file) => total + file.size, 0)
2098
+ };
2099
+ }
2100
+ async function stageWorkflowWithLocalDependencies(options) {
2101
+ const queue = [options.workflowName];
2102
+ const visited = /* @__PURE__ */ new Set();
2103
+ while (queue.length > 0) {
2104
+ const currentWorkflowName = queue.shift();
2105
+ if (!currentWorkflowName || visited.has(currentWorkflowName)) {
2106
+ continue;
2107
+ }
2108
+ visited.add(currentWorkflowName);
2109
+ const sourceDir = currentWorkflowName === options.workflowName ? options.workflowDir : path4.join(options.workspaceRoot, currentWorkflowName);
2110
+ if (!existsSync2(path4.join(sourceDir, "package.json"))) {
2111
+ throw new Error(i18n.t("localDependencyMissingPackage", {
2112
+ ns: "cli",
2113
+ workflow: options.workflowName,
2114
+ dependency: currentWorkflowName,
2115
+ path: sourceDir
2116
+ }));
2117
+ }
2118
+ const uploadPlan = await copyWorkflowSource(
2119
+ sourceDir,
2120
+ path4.join(options.stagingRoot, currentWorkflowName),
2121
+ { projectGroupSafe: options.projectGroupSafe }
2122
+ );
2123
+ if (currentWorkflowName === options.workflowName && !uploadPlan.workflowIgnoreConfigured) {
2124
+ process.stderr.write(`${i18n.t("workflowIgnoreWarning", {
2125
+ ns: "cli",
2126
+ files: uploadPlan.files.length,
2127
+ bytes: uploadPlan.totalBytes
2128
+ })}
2129
+ `);
2130
+ }
2131
+ for (const dependency of await findLocalWorkflowDependencies(sourceDir)) {
2132
+ if (!visited.has(dependency)) {
2133
+ queue.push(dependency);
2134
+ }
2135
+ }
2136
+ }
2137
+ }
2138
+ async function findLocalWorkflowDependencies(sourceDir) {
2139
+ const discovered = /* @__PURE__ */ new Set();
2140
+ for (const fileName of ["index.ts", "interface.ts", "globals.d.ts"]) {
2141
+ const filePath = path4.join(sourceDir, fileName);
2142
+ if (!existsSync2(filePath)) {
2143
+ continue;
2144
+ }
2145
+ const source = await readFile3(filePath, "utf8");
2146
+ for (const match of source.matchAll(/(?:from\s+|reference path=)\"(\.\.\/([^/\"']+)(?:\/[^\"']*)?)\"/g)) {
2147
+ const workflowName = match[2]?.trim();
2148
+ if (workflowName) {
2149
+ discovered.add(workflowName);
2150
+ }
2151
+ }
2152
+ for (const match of source.matchAll(/(?:from\s+|reference path=)'(\.\.\/([^/'"]+)(?:\/[^'"]*)?)'/g)) {
2153
+ const workflowName = match[2]?.trim();
2154
+ if (workflowName) {
2155
+ discovered.add(workflowName);
2156
+ }
2157
+ }
2158
+ }
2159
+ return [...discovered].sort((left, right) => left.localeCompare(right));
2160
+ }
2161
+ function normalizeWorkflowFilePath(filePath) {
2162
+ const normalized = filePath.replaceAll("\\", "/").replace(/^\.\/+/, "");
2163
+ if (normalized === "" || normalized.startsWith("/") || /^[a-zA-Z]:/.test(normalized) || normalized.split("/").some((part) => part === "" || part === "." || part === "..")) {
2164
+ throw new Error(i18n.t("invalidWorkflowFilePath", { ns: "cli", path: filePath }));
2165
+ }
2166
+ return normalized;
2167
+ }
2168
+ function decodeWorkflowFileContent(file) {
2169
+ if (file.encoding === void 0) {
2170
+ return file.content;
2171
+ }
2172
+ if (file.encoding !== "base64") {
2173
+ throw new Error(i18n.t("unsupportedWorkflowEncoding", { ns: "cli", path: file.path }));
2174
+ }
2175
+ const normalized = file.content.replace(/=+$/, "");
2176
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(file.content)) {
2177
+ throw new Error(i18n.t("invalidBase64Content", { ns: "cli", path: file.path }));
2178
+ }
2179
+ const decoded = Buffer.from(file.content, "base64");
2180
+ if (decoded.toString("base64").replace(/=+$/, "") !== normalized) {
2181
+ throw new Error(i18n.t("invalidBase64Content", { ns: "cli", path: file.path }));
2182
+ }
2183
+ return decoded;
2184
+ }
2185
+ function hashWorkflowSourceFiles(files) {
2186
+ const hash = createHash("sha256");
2187
+ const normalizedFiles = files.map((file) => ({
2188
+ file,
2189
+ path: normalizeWorkflowFilePath(file.path)
2190
+ })).sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
2191
+ for (const item of normalizedFiles) {
2192
+ hash.update(item.path);
2193
+ hash.update("\0");
2194
+ hash.update(decodeWorkflowFileContent(item.file));
2195
+ hash.update("\0");
2196
+ }
2197
+ return hash.digest("hex");
2198
+ }
2199
+ function parseArgs(argv) {
2200
+ const [command = "", ...rest] = argv;
2201
+ const positional = [];
2202
+ const authConfig = readCliAuthConfigSync();
2203
+ const options = {
2204
+ server: process.env.WORKFLOW_SERVER_URL ?? authConfig.serverUrl ?? DEFAULT_SERVER_URL,
2205
+ token: process.env.WORKFLOW_SERVER_ADMIN_KEY ?? authConfig.apiKey ?? "",
2206
+ dependencies: [],
2207
+ dataGrants: []
2208
+ };
2209
+ for (let index = 0; index < rest.length; index += 1) {
2210
+ const arg = rest[index];
2211
+ if (arg === "--") {
2212
+ positional.push(...rest.slice(index + 1));
2213
+ break;
2214
+ }
2215
+ switch (arg) {
2216
+ case "--server":
2217
+ options.server = requireValue(rest, index += 1, "--server");
2218
+ break;
2219
+ case "--token":
2220
+ options.token = requireValue(rest, index += 1, "--token");
2221
+ break;
2222
+ case "--target":
2223
+ options.target = requireValue(rest, index += 1, "--target");
2224
+ break;
2225
+ case "--entrypoint":
2226
+ options.entrypoint = requireValue(rest, index += 1, "--entrypoint");
2227
+ break;
2228
+ case "--version":
2229
+ throw new Error(i18n.t("versionOptionUnsupported", { ns: "cli" }));
2230
+ case "--release-log":
2231
+ options.releaseLog = requireValue(rest, index += 1, "--release-log");
2232
+ break;
2233
+ case "--file":
2234
+ options.file = requireValue(rest, index += 1, "--file");
2235
+ break;
2236
+ case "--output":
2237
+ options.output = requireValue(rest, index += 1, "--output");
2238
+ break;
2239
+ case "--path":
2240
+ options.path = requireValue(rest, index += 1, "--path");
2241
+ break;
2242
+ case "--create":
2243
+ options.create = true;
2244
+ break;
2245
+ case "--no-wait":
2246
+ options.noWait = true;
2247
+ break;
2248
+ case "--dependency":
2249
+ options.dependencies.push(requireValue(rest, index += 1, "--dependency"));
2250
+ break;
2251
+ case "--grant-data-access":
2252
+ options.dataGrants.push(requireValue(rest, index += 1, "--grant-data-access"));
2253
+ break;
2254
+ case "--project-group":
2255
+ options.projectGroup = true;
2256
+ break;
2257
+ case "--publish-group":
2258
+ options.publishGroup = true;
2259
+ break;
2260
+ case "--deployment":
2261
+ options.deployment = requireValue(rest, index += 1, "--deployment");
2262
+ break;
2263
+ case "--source-mode": {
2264
+ const value = requireValue(rest, index += 1, "--source-mode");
2265
+ if (value !== "bundled" && value !== "source") {
2266
+ throw new Error(i18n.t("invalidSourceMode", { ns: "cli" }));
2267
+ }
2268
+ options.sourceMode = value;
2269
+ break;
2270
+ }
2271
+ default:
2272
+ positional.push(arg);
2273
+ break;
2274
+ }
2275
+ }
2276
+ if (command === "upload" && options.noWait === true && options.releaseLog !== void 0) {
2277
+ throw new Error(i18n.t("noWaitReleaseLogConflict", { ns: "cli" }));
2278
+ }
2279
+ if (options.noWait === true && command !== "upload") {
2280
+ throw new Error(i18n.t("noWaitCommandUnsupported", { ns: "cli" }));
2281
+ }
2282
+ if (command === "upload" && options.noWait === true && shouldUseProjectGroupUpload(options)) {
2283
+ throw new Error(i18n.t("noWaitProjectGroupUnsupported", { ns: "cli" }));
2284
+ }
2285
+ if (options.dependencies.length > 0 && command !== "pack" && command !== "upload" && command !== "publish") {
2286
+ throw new Error(i18n.t("dependencyOptionCommandUnsupported", { ns: "cli" }));
2287
+ }
2288
+ if (options.dataGrants.length > 0 && command !== "upload" && command !== "publish") {
2289
+ throw new Error(i18n.t("grantDataAccessCommandUnsupported", { ns: "cli" }));
2290
+ }
2291
+ if (options.deployment !== void 0 && command !== "publish") {
2292
+ throw new Error(i18n.t("deploymentCommandUnsupported", { ns: "cli" }));
2293
+ }
2294
+ if (options.publishGroup === true && command !== "upload") {
2295
+ throw new Error(i18n.t("publishGroupCommandUnsupported", { ns: "cli" }));
2296
+ }
2297
+ if (options.projectGroup === true && command !== "pack" && command !== "upload" && command !== "publish") {
2298
+ throw new Error(i18n.t("projectGroupCommandUnsupported", { ns: "cli" }));
2299
+ }
2300
+ if (options.deployment !== void 0 && (options.projectGroup === true || options.dependencies.length > 0 || options.dataGrants.length > 0 || options.path !== void 0 || options.file !== void 0 || options.output !== void 0 || options.create === true || options.releaseLog !== void 0 || options.sourceMode !== void 0)) {
2301
+ throw new Error(i18n.t("deploymentOptionsConflict", { ns: "cli" }));
2302
+ }
2303
+ return {
2304
+ command,
2305
+ positional,
2306
+ options
2307
+ };
2308
+ }
2309
+ function requireLogin(parsed) {
2310
+ if (parsed.options.server.trim() === "" || parsed.options.token.trim() === "") {
2311
+ throw new Error(i18n.t("notLoggedIn", { ns: "cli" }));
2312
+ }
2313
+ }
2314
+ function requirePositional(parsed, index, name) {
2315
+ const value = parsed.positional[index];
2316
+ if (value === void 0 || value.trim() === "") {
2317
+ throw new Error(i18n.t("missingPositional", { ns: "cli", name }));
2318
+ }
2319
+ return value;
2320
+ }
2321
+ function requireValue(argv, index, name) {
2322
+ const value = argv[index];
2323
+ if (value === void 0 || value.trim() === "") {
2324
+ throw new Error(i18n.t("missingOptionValue", { ns: "cli", name }));
2325
+ }
2326
+ return value;
2327
+ }
2328
+ function runCommand(command, args, options = {}) {
2329
+ return new Promise((resolve) => {
2330
+ const child = spawn(command, args, {
2331
+ cwd: options.cwd,
2332
+ stdio: ["ignore", "pipe", "pipe"]
2333
+ });
2334
+ const stdout = [];
2335
+ const stderr = [];
2336
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
2337
+ child.stderr.on("data", (chunk) => stderr.push(chunk));
2338
+ child.on("error", (error) => {
2339
+ resolve({
2340
+ exitCode: 1,
2341
+ stdout: Buffer.concat(stdout).toString("utf8"),
2342
+ stderr: `${Buffer.concat(stderr).toString("utf8")}${error.message}`
2343
+ });
2344
+ });
2345
+ child.on("close", (exitCode) => {
2346
+ resolve({
2347
+ exitCode: exitCode ?? 1,
2348
+ stdout: Buffer.concat(stdout).toString("utf8"),
2349
+ stderr: Buffer.concat(stderr).toString("utf8")
2350
+ });
2351
+ });
2352
+ });
2353
+ }
2354
+ function printApiResponse(response) {
2355
+ console.log(JSON.stringify(response, null, 2));
2356
+ }
2357
+ function assertApiOk(response) {
2358
+ if (response.errCode !== 0) {
2359
+ if (response.errCode === 403) {
2360
+ process.stderr.write(`${formatForbiddenMessage(response)}
2361
+ `);
2362
+ }
2363
+ process.exitCode = 1;
2364
+ }
2365
+ }
2366
+ function formatForbiddenMessage(response) {
2367
+ const message = response.errMessage.toLowerCase();
2368
+ if (message.includes("blocked from running")) {
2369
+ return i18n.t("forbiddenBlocked", { ns: "cli" });
2370
+ }
2371
+ if (message.includes("permission denied")) {
2372
+ return i18n.t("forbiddenNoPermission", { ns: "cli" });
2373
+ }
2374
+ return i18n.t("forbiddenFallback", { ns: "cli", message: response.errMessage });
2375
+ }
2376
+ function printUsage() {
2377
+ console.log(i18n.t("workspaceHelp", { ns: "cli" }));
2378
+ }
2379
+ function resolveRepoRoot(startDir = process.cwd()) {
2380
+ if (process.env.WORKFLOW_REPO_ROOT !== void 0) {
2381
+ return path4.resolve(process.env.WORKFLOW_REPO_ROOT);
2382
+ }
2383
+ for (const candidate of [startDir, CLI_MODULE_DIR]) {
2384
+ const repoRoot = findRepoRoot2(candidate);
2385
+ if (repoRoot !== void 0) {
2386
+ return repoRoot;
2387
+ }
2388
+ }
2389
+ return process.cwd();
2390
+ }
2391
+ function findRepoRoot2(startDir) {
2392
+ let current = path4.resolve(startDir);
2393
+ while (true) {
2394
+ if (isRepoRoot(current)) {
2395
+ return current;
2396
+ }
2397
+ const parent = path4.dirname(current);
2398
+ if (parent === current) {
2399
+ return void 0;
2400
+ }
2401
+ current = parent;
2402
+ }
2403
+ }
2404
+ function isRepoRoot(candidate) {
2405
+ return existsSync2(path4.join(candidate, "pnpm-workspace.yaml")) && existsSync2(path4.join(candidate, "package.json")) && existsSync2(path4.join(candidate, "workspace", "package.json")) && existsSync2(path4.join(candidate, "packages", "cli", "package.json"));
2406
+ }
2407
+ function trimTrailingSlash(value) {
2408
+ return value.replace(/\/+$/, "");
2409
+ }
2410
+ function createTimestamp() {
2411
+ const date = /* @__PURE__ */ new Date();
2412
+ const pad = (value) => String(value).padStart(2, "0");
2413
+ return [
2414
+ date.getUTCFullYear(),
2415
+ pad(date.getUTCMonth() + 1),
2416
+ pad(date.getUTCDate()),
2417
+ "-",
2418
+ pad(date.getUTCHours()),
2419
+ pad(date.getUTCMinutes()),
2420
+ pad(date.getUTCSeconds())
2421
+ ].join("");
2422
+ }
2423
+ async function waitForDeviceToken(options) {
2424
+ const expiresAtMs = new Date(options.expiresAt).getTime();
2425
+ const pollMs = Math.max(1e3, options.intervalSeconds * 1e3);
2426
+ while (!Number.isFinite(expiresAtMs) || Date.now() < expiresAtMs) {
2427
+ const response = await fetchApi({
2428
+ server: options.server,
2429
+ token: "",
2430
+ path: "/api/auth/device/token",
2431
+ method: "POST",
2432
+ body: { deviceCode: options.deviceCode },
2433
+ auth: false
2434
+ });
2435
+ if (response.errCode === 0) {
2436
+ return response;
2437
+ }
2438
+ if (response.errMessage === "slow_down") {
2439
+ const retryAfterSeconds = readRetryAfterSeconds(response.data) ?? Math.max(options.intervalSeconds * 2, 5);
2440
+ await delay(retryAfterSeconds * 1e3);
2441
+ continue;
2442
+ }
2443
+ if (response.errMessage !== "authorization_pending") {
2444
+ return response;
2445
+ }
2446
+ await delay(pollMs);
2447
+ }
2448
+ return {
2449
+ errCode: 409,
2450
+ errMessage: "expired_token"
2451
+ };
2452
+ }
2453
+ async function openExternalBrowser(url) {
2454
+ const commands = process.platform === "darwin" ? [["open", url]] : process.platform === "win32" ? [["cmd", "/c", "start", "", url]] : [["xdg-open", url]];
2455
+ for (const [command, ...args] of commands) {
2456
+ const result = await runCommand(command, args);
2457
+ if (result.exitCode === 0) {
2458
+ return;
2459
+ }
2460
+ }
2461
+ }
2462
+ function delay(ms) {
2463
+ return new Promise((resolve) => setTimeout(resolve, ms));
2464
+ }
2465
+ function readRetryAfterSeconds(data) {
2466
+ if (typeof data !== "object" || data === null) {
2467
+ return void 0;
2468
+ }
2469
+ const value = data.retryAfterSeconds;
2470
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
2471
+ }
2472
+ if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL(path4.resolve(process.argv[1])).href) {
2473
+ main().catch((error) => {
2474
+ console.error(translateWorkflowCodeError(error));
2475
+ process.exitCode = 1;
2476
+ });
2477
+ }
2478
+ export {
2479
+ decodeWorkflowFileContent,
2480
+ ensureWorkflowProjectCreated,
2481
+ findLocalWorkflowDependencies,
2482
+ formatForbiddenMessage,
2483
+ getPreparation,
2484
+ main,
2485
+ materializeProjectGroupDownload,
2486
+ normalizeWorkflowFilePath,
2487
+ packProjectGroup,
2488
+ packWorkflow,
2489
+ parseArgs,
2490
+ readPackageJsonFile,
2491
+ readProjectGroupContexts,
2492
+ readWorkflowPackageContext,
2493
+ resolveExplicitDataGrants,
2494
+ resolveRemoteWorkflowId,
2495
+ resolveRepoRoot,
2496
+ shouldUseProjectGroupUpload,
2497
+ stageWorkflowWithLocalDependencies,
2498
+ waitForDeviceToken,
2499
+ waitForPreparation,
2500
+ writeWorkflowPackageId
2501
+ };