@workflow-code/cli 0.1.2 → 0.1.4-20260814002
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-3ZXXRODP.js +10856 -0
- package/dist/index.js +13 -23
- package/dist/{local-6VUUVT3C.js → local-K4KPED46.js} +55 -40
- package/dist/{workspace-QUP6RU65.js → workspace-HHW34WVB.js} +833 -104
- package/package.json +23 -12
- package/dist/chunk-FYS2JH42.js +0 -31
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
__commonJS,
|
|
4
|
-
__toESM
|
|
5
|
-
|
|
4
|
+
__toESM,
|
|
5
|
+
getCurrentLocale,
|
|
6
|
+
i18n,
|
|
7
|
+
prepareCliInvocation,
|
|
8
|
+
translateWorkflowCodeError
|
|
9
|
+
} from "./chunk-3ZXXRODP.js";
|
|
6
10
|
|
|
7
|
-
//
|
|
11
|
+
// node_modules/.pnpm/ignore@7.0.6/node_modules/ignore/index.js
|
|
8
12
|
var require_ignore = __commonJS({
|
|
9
|
-
"
|
|
13
|
+
"node_modules/.pnpm/ignore@7.0.6/node_modules/ignore/index.js"(exports, module) {
|
|
10
14
|
"use strict";
|
|
11
15
|
function makeArray(subject) {
|
|
12
16
|
return Array.isArray(subject) ? subject : [subject];
|
|
@@ -465,13 +469,13 @@ var require_ignore = __commonJS({
|
|
|
465
469
|
|
|
466
470
|
// src/workspace.ts
|
|
467
471
|
import { spawn } from "child_process";
|
|
468
|
-
import { randomUUID } from "crypto";
|
|
472
|
+
import { createHash, randomUUID } from "crypto";
|
|
469
473
|
import { existsSync as existsSync2 } from "fs";
|
|
470
|
-
import { mkdir as mkdir3, readFile as readFile3, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
474
|
+
import { copyFile as copyFile2, mkdir as mkdir3, readFile as readFile3, readdir as readdir2, rename, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
471
475
|
import path4 from "path";
|
|
472
476
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
473
477
|
|
|
474
|
-
//
|
|
478
|
+
// .shared/auth-config/index.ts
|
|
475
479
|
var WORKFLOW_AUTH_FILE_NAME = "workflow-auth.json";
|
|
476
480
|
var DEFAULT_MAC_PLATFORM = "darwin";
|
|
477
481
|
var DEFAULT_WINDOWS_PLATFORM = "win32";
|
|
@@ -487,7 +491,7 @@ function normalizeWorkflowAuthConfig(value) {
|
|
|
487
491
|
return createEmptyWorkflowAuthConfig();
|
|
488
492
|
}
|
|
489
493
|
return {
|
|
490
|
-
//
|
|
494
|
+
// Accept legacy field names (remoteUrl, token) from pre-migration
|
|
491
495
|
// config files so existing users don't lose their credentials on upgrade.
|
|
492
496
|
serverUrl: readString(value.serverUrl) ?? readString(value.remoteUrl) ?? "",
|
|
493
497
|
apiKey: readString(value.apiKey) ?? readString(value.token) ?? "",
|
|
@@ -576,24 +580,29 @@ function readGlobalProcess() {
|
|
|
576
580
|
return processValue;
|
|
577
581
|
}
|
|
578
582
|
|
|
579
|
-
//
|
|
583
|
+
// .shared/workflow-ids/index.ts
|
|
580
584
|
import path from "path";
|
|
581
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;
|
|
582
586
|
function isWorkflowUuid(value) {
|
|
583
587
|
return WORKFLOW_UUID_PATTERN.test(value.trim());
|
|
584
588
|
}
|
|
585
589
|
|
|
586
|
-
//
|
|
590
|
+
// .shared/project-upload/node.ts
|
|
587
591
|
import { copyFile, lstat, mkdir, readFile, readdir } from "fs/promises";
|
|
588
592
|
import path2 from "path";
|
|
589
593
|
|
|
590
|
-
//
|
|
594
|
+
// .shared/project-upload/index.ts
|
|
591
595
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
592
596
|
|
|
593
|
-
//
|
|
597
|
+
// .shared/kanban-project/index.ts
|
|
594
598
|
var KANBAN_MANIFEST_FILE = "kanban.json";
|
|
595
599
|
var KANBAN_ENTRY_FILE = "index.html";
|
|
596
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;
|
|
597
606
|
function readPackageProjectType(value) {
|
|
598
607
|
if (!isRecord2(value)) return void 0;
|
|
599
608
|
const workflowCode = value["workflowCode"];
|
|
@@ -624,7 +633,8 @@ function readKanbanArtifactConfig(value) {
|
|
|
624
633
|
"workflowCode.kanban.entry",
|
|
625
634
|
KANBAN_ENTRY_FILE,
|
|
626
635
|
false
|
|
627
|
-
)
|
|
636
|
+
),
|
|
637
|
+
dataSources: parseKanbanDataSources(rawConfig?.["dataSources"])
|
|
628
638
|
};
|
|
629
639
|
}
|
|
630
640
|
function createKanbanArtifactEntryPath(config) {
|
|
@@ -651,11 +661,72 @@ function readKanbanRelativePath(value, field, fallback, allowDot) {
|
|
|
651
661
|
}
|
|
652
662
|
return normalized;
|
|
653
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
|
+
}
|
|
654
725
|
function isRecord2(value) {
|
|
655
726
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
656
727
|
}
|
|
657
728
|
|
|
658
|
-
//
|
|
729
|
+
// .shared/project-upload/index.ts
|
|
659
730
|
var WORKFLOW_IGNORE_FILE = ".workflowignore";
|
|
660
731
|
function hasWorkflowIgnoreRules(content) {
|
|
661
732
|
if (content === void 0) return false;
|
|
@@ -730,7 +801,7 @@ function findEffectiveWorkflowIgnoreRule(normalizedPath, ruleLines) {
|
|
|
730
801
|
return ignored ? effectiveRule : void 0;
|
|
731
802
|
}
|
|
732
803
|
|
|
733
|
-
//
|
|
804
|
+
// .shared/project-upload/node.ts
|
|
734
805
|
async function createWorkflowUploadPlan(rootDir) {
|
|
735
806
|
const projectRoot = path2.resolve(rootDir);
|
|
736
807
|
const workflowIgnorePath = path2.join(projectRoot, WORKFLOW_IGNORE_FILE);
|
|
@@ -838,6 +909,15 @@ async function readWorkflowUploadUtf8File(filePath, label) {
|
|
|
838
909
|
}
|
|
839
910
|
}
|
|
840
911
|
|
|
912
|
+
// src/workspace.ts
|
|
913
|
+
import {
|
|
914
|
+
createWorkflowProjectDependencyLocks,
|
|
915
|
+
inspectLocalWorkflowStructure,
|
|
916
|
+
isProjectGroupHostDataPath,
|
|
917
|
+
parseWorkflowProjectDependencyLocks,
|
|
918
|
+
readKanbanArtifactConfig as readKanbanArtifactConfig2
|
|
919
|
+
} from "workflow-code";
|
|
920
|
+
|
|
841
921
|
// src/auth-config.ts
|
|
842
922
|
import { readFileSync } from "fs";
|
|
843
923
|
import { mkdir as mkdir2, readFile as readFile2, rm, writeFile } from "fs/promises";
|
|
@@ -876,7 +956,7 @@ import path3 from "path";
|
|
|
876
956
|
import { parse } from "dotenv";
|
|
877
957
|
function loadCliEnv() {
|
|
878
958
|
const cliRoot = process.cwd();
|
|
879
|
-
const defaultRepoRoot =
|
|
959
|
+
const defaultRepoRoot = findWorkspaceRepositoryRoot(cliRoot) ?? cliRoot;
|
|
880
960
|
const initialRepoRoot = process.env.WORKFLOW_REPO_ROOT ? path3.resolve(process.env.WORKFLOW_REPO_ROOT) : defaultRepoRoot;
|
|
881
961
|
loadEnvFiles([
|
|
882
962
|
path3.join(cliRoot, ".env"),
|
|
@@ -887,10 +967,10 @@ function loadCliEnv() {
|
|
|
887
967
|
loadEnvFiles([path3.join(resolvedRepoRoot, ".env")]);
|
|
888
968
|
}
|
|
889
969
|
}
|
|
890
|
-
function
|
|
970
|
+
function findWorkspaceRepositoryRoot(startDir) {
|
|
891
971
|
let current = path3.resolve(startDir);
|
|
892
972
|
while (true) {
|
|
893
|
-
if (existsSync(path3.join(current, "pnpm-workspace.yaml")) && existsSync(path3.join(current, "
|
|
973
|
+
if (existsSync(path3.join(current, "pnpm-workspace.yaml")) && existsSync(path3.join(current, "package.json")) && existsSync(path3.join(current, "workspace", "package.json")) && existsSync(path3.join(current, "workspace", "workflow"))) {
|
|
894
974
|
return current;
|
|
895
975
|
}
|
|
896
976
|
const parent = path3.dirname(current);
|
|
@@ -920,7 +1000,10 @@ var DEFAULT_SERVER_URL = "http://localhost:7125";
|
|
|
920
1000
|
var CLI_MODULE_DIR = path4.dirname(fileURLToPath(import.meta.url));
|
|
921
1001
|
var DEFAULT_DEVICE_FLOW_CLIENT_NAME = "Workflow Workspace CLI";
|
|
922
1002
|
loadCliEnv();
|
|
923
|
-
async function main(argv = process.argv.slice(2)) {
|
|
1003
|
+
async function main(argv = process.argv.slice(2), options = {}) {
|
|
1004
|
+
if (options.localeInitialized !== true) {
|
|
1005
|
+
argv = prepareCliInvocation(argv).argv;
|
|
1006
|
+
}
|
|
924
1007
|
const parsed = parseArgs(argv);
|
|
925
1008
|
switch (parsed.command) {
|
|
926
1009
|
case "login":
|
|
@@ -966,13 +1049,13 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
966
1049
|
printUsage();
|
|
967
1050
|
return;
|
|
968
1051
|
default:
|
|
969
|
-
throw new Error(
|
|
1052
|
+
throw new Error(i18n.t("unknownWorkspaceCommand", { ns: "cli", command: parsed.command }));
|
|
970
1053
|
}
|
|
971
1054
|
}
|
|
972
1055
|
async function commandLogin(parsed) {
|
|
973
1056
|
const server = parsed.options.server.trim();
|
|
974
1057
|
if (server === "") {
|
|
975
|
-
throw new Error("
|
|
1058
|
+
throw new Error(i18n.t("loginRequiresServer", { ns: "cli" }));
|
|
976
1059
|
}
|
|
977
1060
|
const started = await fetchApi({
|
|
978
1061
|
server,
|
|
@@ -980,7 +1063,7 @@ async function commandLogin(parsed) {
|
|
|
980
1063
|
path: "/api/auth/device/start",
|
|
981
1064
|
method: "POST",
|
|
982
1065
|
body: {
|
|
983
|
-
//
|
|
1066
|
+
// phase 1 moves workspace login to device flow and stores a
|
|
984
1067
|
// dedicated CLI auth file instead of sharing the App config.
|
|
985
1068
|
clientName: DEFAULT_DEVICE_FLOW_CLIENT_NAME,
|
|
986
1069
|
source: "cli"
|
|
@@ -994,9 +1077,11 @@ async function commandLogin(parsed) {
|
|
|
994
1077
|
}
|
|
995
1078
|
const verificationUrl = started.data.verificationUriComplete || started.data.verificationUri;
|
|
996
1079
|
void openExternalBrowser(verificationUrl);
|
|
997
|
-
console.error(
|
|
998
|
-
|
|
999
|
-
|
|
1080
|
+
console.error(i18n.t("deviceLoginPrompt", {
|
|
1081
|
+
ns: "cli",
|
|
1082
|
+
url: verificationUrl,
|
|
1083
|
+
code: started.data.userCode
|
|
1084
|
+
}));
|
|
1000
1085
|
const completed = await waitForDeviceToken({
|
|
1001
1086
|
server,
|
|
1002
1087
|
deviceCode: started.data.deviceCode,
|
|
@@ -1048,12 +1133,26 @@ async function commandStatus() {
|
|
|
1048
1133
|
}
|
|
1049
1134
|
async function commandPack(parsed) {
|
|
1050
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
|
+
}
|
|
1051
1146
|
const archivePath = await packWorkflow(workflowName, parsed.options.output, parsed.options.path);
|
|
1052
1147
|
console.log(JSON.stringify({ archivePath }, null, 2));
|
|
1053
1148
|
}
|
|
1054
1149
|
async function commandUpload(parsed) {
|
|
1055
1150
|
requireLogin(parsed);
|
|
1056
1151
|
const workflowArg = requirePositional(parsed, 0, "workflow");
|
|
1152
|
+
if (shouldUseProjectGroupUpload(parsed.options)) {
|
|
1153
|
+
await commandProjectGroupUpload(parsed, workflowArg);
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1057
1156
|
const packageContext = await readWorkflowPackageContext(workflowArg, parsed.options.path);
|
|
1058
1157
|
let workflowId = packageContext.workflowId;
|
|
1059
1158
|
if (parsed.options.create === true) {
|
|
@@ -1065,7 +1164,7 @@ async function commandUpload(parsed) {
|
|
|
1065
1164
|
workflowId = created.workflowId;
|
|
1066
1165
|
}
|
|
1067
1166
|
if (!workflowId) {
|
|
1068
|
-
throw new Error("
|
|
1167
|
+
throw new Error(i18n.t("workflowIdRequired", { ns: "cli" }));
|
|
1069
1168
|
}
|
|
1070
1169
|
const archivePath = parsed.options.file ? path4.resolve(parsed.options.file) : await packWorkflow(workflowArg, parsed.options.output, parsed.options.path);
|
|
1071
1170
|
const uploadResult = await uploadPackage({
|
|
@@ -1109,6 +1208,101 @@ async function commandUpload(parsed) {
|
|
|
1109
1208
|
assertApiOk(publishResult);
|
|
1110
1209
|
}
|
|
1111
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
|
+
}
|
|
1112
1306
|
async function commandPreparation(parsed) {
|
|
1113
1307
|
requireLogin(parsed);
|
|
1114
1308
|
const workflowId = requirePositional(parsed, 0, "workflow");
|
|
@@ -1133,6 +1327,7 @@ async function commandRun(parsed) {
|
|
|
1133
1327
|
method: "POST",
|
|
1134
1328
|
body: {
|
|
1135
1329
|
target: parsed.options.target ?? "latest",
|
|
1330
|
+
entrypointId: parsed.options.entrypoint,
|
|
1136
1331
|
args: workflowArgs
|
|
1137
1332
|
}
|
|
1138
1333
|
});
|
|
@@ -1151,6 +1346,7 @@ async function commandDebugNode(parsed) {
|
|
|
1151
1346
|
method: "POST",
|
|
1152
1347
|
body: {
|
|
1153
1348
|
target: parsed.options.target ?? "latest",
|
|
1349
|
+
entrypointId: parsed.options.entrypoint,
|
|
1154
1350
|
args: workflowArgs
|
|
1155
1351
|
}
|
|
1156
1352
|
});
|
|
@@ -1172,7 +1368,36 @@ async function commandDownload(parsed) {
|
|
|
1172
1368
|
requireLogin(parsed);
|
|
1173
1369
|
const workflowArg = requirePositional(parsed, 0, "workflow");
|
|
1174
1370
|
const workflowId = await resolveRemoteWorkflowId(parsed);
|
|
1175
|
-
const target = parsed.options.target ?? "
|
|
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
|
+
}
|
|
1176
1401
|
const result = await fetchApi({
|
|
1177
1402
|
server: parsed.options.server,
|
|
1178
1403
|
token: parsed.options.token,
|
|
@@ -1196,6 +1421,22 @@ async function commandDownload(parsed) {
|
|
|
1196
1421
|
}
|
|
1197
1422
|
async function commandPublish(parsed) {
|
|
1198
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
|
+
}
|
|
1199
1440
|
const workflowId = await resolveRemoteWorkflowId(parsed);
|
|
1200
1441
|
const publishResult = await fetchApi({
|
|
1201
1442
|
server: parsed.options.server,
|
|
@@ -1210,6 +1451,279 @@ async function commandPublish(parsed) {
|
|
|
1210
1451
|
printApiResponse(publishResult);
|
|
1211
1452
|
assertApiOk(publishResult);
|
|
1212
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
|
+
}
|
|
1213
1727
|
async function commandHealth(parsed) {
|
|
1214
1728
|
const result = await fetchApi({
|
|
1215
1729
|
server: parsed.options.server,
|
|
@@ -1220,7 +1734,7 @@ async function commandHealth(parsed) {
|
|
|
1220
1734
|
printApiResponse(result);
|
|
1221
1735
|
assertApiOk(result);
|
|
1222
1736
|
}
|
|
1223
|
-
async function packWorkflow(workflowName, outputPath, sourcePath) {
|
|
1737
|
+
async function packWorkflow(workflowName, outputPath, sourcePath, options = {}) {
|
|
1224
1738
|
const repoRoot = resolveRepoRoot();
|
|
1225
1739
|
const workflowDir = sourcePath === void 0 ? path4.join(repoRoot, "workspace", "workflow", workflowName) : path4.resolve(sourcePath);
|
|
1226
1740
|
await readPackageJsonFile(path4.join(workflowDir, "package.json"));
|
|
@@ -1235,17 +1749,166 @@ async function packWorkflow(workflowName, outputPath, sourcePath) {
|
|
|
1235
1749
|
workspaceRoot,
|
|
1236
1750
|
workflowDir,
|
|
1237
1751
|
workflowName,
|
|
1238
|
-
stagingRoot: path4.join(stagingDir, "workspace", "workflow")
|
|
1752
|
+
stagingRoot: path4.join(stagingDir, "workspace", "workflow"),
|
|
1753
|
+
projectGroupSafe: options.projectGroupSafe === true
|
|
1239
1754
|
});
|
|
1240
1755
|
const result = await runCommand("tar", ["-czf", archivePath, "-C", stagingDir, "."], {
|
|
1241
1756
|
cwd: repoRoot
|
|
1242
1757
|
});
|
|
1243
1758
|
await rm2(path4.dirname(stagingDir), { recursive: true, force: true });
|
|
1244
1759
|
if (result.exitCode !== 0) {
|
|
1245
|
-
throw new Error(
|
|
1760
|
+
throw new Error(i18n.t("archiveCreateFailed", {
|
|
1761
|
+
ns: "cli",
|
|
1762
|
+
details: result.stderr || result.stdout
|
|
1763
|
+
}));
|
|
1246
1764
|
}
|
|
1247
1765
|
return archivePath;
|
|
1248
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
|
+
}
|
|
1249
1912
|
async function resolveRemoteWorkflowId(parsed) {
|
|
1250
1913
|
const workflowArg = requirePositional(parsed, 0, "workflow");
|
|
1251
1914
|
if (isWorkflowUuid(workflowArg)) {
|
|
@@ -1255,7 +1918,10 @@ async function resolveRemoteWorkflowId(parsed) {
|
|
|
1255
1918
|
if (packageContext.workflowId) {
|
|
1256
1919
|
return packageContext.workflowId;
|
|
1257
1920
|
}
|
|
1258
|
-
throw new Error(
|
|
1921
|
+
throw new Error(i18n.t("workflowNotBound", {
|
|
1922
|
+
ns: "cli",
|
|
1923
|
+
workflow: workflowArg
|
|
1924
|
+
}));
|
|
1259
1925
|
}
|
|
1260
1926
|
async function readWorkflowPackageContext(workflowArg, sourcePath) {
|
|
1261
1927
|
const repoRoot = resolveRepoRoot();
|
|
@@ -1264,12 +1930,14 @@ async function readWorkflowPackageContext(workflowArg, sourcePath) {
|
|
|
1264
1930
|
const packageJson = await readPackageJsonFile(packageJsonPath);
|
|
1265
1931
|
const workflowId = typeof packageJson.id === "string" && isWorkflowUuid(packageJson.id) ? packageJson.id.trim() : void 0;
|
|
1266
1932
|
const workflowName = readRequiredPackageString(packageJson, "name");
|
|
1933
|
+
const projectType = inspectLocalWorkflowStructure({ workflowDir }).projectType;
|
|
1267
1934
|
return {
|
|
1268
1935
|
workflowArg,
|
|
1269
1936
|
workflowDir,
|
|
1270
1937
|
packageJsonPath,
|
|
1271
1938
|
workflowId,
|
|
1272
1939
|
workflowName,
|
|
1940
|
+
projectType,
|
|
1273
1941
|
packageJson
|
|
1274
1942
|
};
|
|
1275
1943
|
}
|
|
@@ -1281,13 +1949,14 @@ async function ensureWorkflowProjectCreated(options) {
|
|
|
1281
1949
|
method: "POST",
|
|
1282
1950
|
body: {
|
|
1283
1951
|
name: options.packageContext.workflowName,
|
|
1952
|
+
projectType: options.packageContext.projectType,
|
|
1284
1953
|
...options.packageContext.workflowId ? { workflowId: options.packageContext.workflowId } : {}
|
|
1285
1954
|
}
|
|
1286
1955
|
});
|
|
1287
1956
|
if (created.errCode !== 0 || created.data === void 0) {
|
|
1288
1957
|
printApiResponse(created);
|
|
1289
1958
|
assertApiOk(created);
|
|
1290
|
-
throw new Error("
|
|
1959
|
+
throw new Error(i18n.t("projectCreationFailed", { ns: "cli" }));
|
|
1291
1960
|
}
|
|
1292
1961
|
if (!options.packageContext.workflowId || options.packageContext.workflowId !== created.data.workflowId) {
|
|
1293
1962
|
await writeWorkflowPackageId(options.packageContext.packageJsonPath, created.data.workflowId);
|
|
@@ -1298,20 +1967,20 @@ async function readPackageJsonFile(packageJsonPath) {
|
|
|
1298
1967
|
try {
|
|
1299
1968
|
const raw = JSON.parse(await readFile3(packageJsonPath, "utf8"));
|
|
1300
1969
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
1301
|
-
throw new Error(
|
|
1970
|
+
throw new Error(i18n.t("packageMustBeObject", { ns: "cli", path: packageJsonPath }));
|
|
1302
1971
|
}
|
|
1303
1972
|
return raw;
|
|
1304
1973
|
} catch (error) {
|
|
1305
1974
|
if (error instanceof Error) {
|
|
1306
1975
|
throw error;
|
|
1307
1976
|
}
|
|
1308
|
-
throw new Error(
|
|
1977
|
+
throw new Error(i18n.t("packageReadFailed", { ns: "cli", path: packageJsonPath }));
|
|
1309
1978
|
}
|
|
1310
1979
|
}
|
|
1311
1980
|
function readRequiredPackageString(packageJson, key) {
|
|
1312
1981
|
const value = packageJson[key];
|
|
1313
1982
|
if (typeof value !== "string" || value.trim() === "") {
|
|
1314
|
-
throw new Error(
|
|
1983
|
+
throw new Error(i18n.t("packageStringRequired", { ns: "cli", key }));
|
|
1315
1984
|
}
|
|
1316
1985
|
return value.trim();
|
|
1317
1986
|
}
|
|
@@ -1336,7 +2005,8 @@ async function uploadPackage(options) {
|
|
|
1336
2005
|
headers: {
|
|
1337
2006
|
authorization: `Bearer ${options.token}`,
|
|
1338
2007
|
"content-type": "application/octet-stream",
|
|
1339
|
-
"x-workflow-package-name": archiveName
|
|
2008
|
+
"x-workflow-package-name": archiveName,
|
|
2009
|
+
"x-workflow-locale": getCurrentLocale()
|
|
1340
2010
|
},
|
|
1341
2011
|
body: new Blob([archiveBuffer])
|
|
1342
2012
|
}
|
|
@@ -1356,7 +2026,11 @@ async function waitForPreparation(options) {
|
|
|
1356
2026
|
}
|
|
1357
2027
|
if (response.data.stage !== previousStage) {
|
|
1358
2028
|
previousStage = response.data.stage;
|
|
1359
|
-
process.stderr.write(
|
|
2029
|
+
process.stderr.write(`${i18n.t("preparationProgress", {
|
|
2030
|
+
ns: "cli",
|
|
2031
|
+
status: response.data.status,
|
|
2032
|
+
stage: response.data.stage
|
|
2033
|
+
})}
|
|
1360
2034
|
`);
|
|
1361
2035
|
}
|
|
1362
2036
|
if (response.data.status === "success") {
|
|
@@ -1391,6 +2065,7 @@ async function fetchApi(options) {
|
|
|
1391
2065
|
if (options.body !== void 0) {
|
|
1392
2066
|
headers["content-type"] = "application/json";
|
|
1393
2067
|
}
|
|
2068
|
+
headers["x-workflow-locale"] = getCurrentLocale();
|
|
1394
2069
|
const response = await fetch(`${trimTrailingSlash(options.server)}${options.path}`, {
|
|
1395
2070
|
method: options.method ?? "GET",
|
|
1396
2071
|
headers,
|
|
@@ -1398,8 +2073,29 @@ async function fetchApi(options) {
|
|
|
1398
2073
|
});
|
|
1399
2074
|
return response.json();
|
|
1400
2075
|
}
|
|
1401
|
-
async function copyWorkflowSource(sourceDir, targetDir) {
|
|
1402
|
-
return copyWorkflowUploadFiles(sourceDir, targetDir);
|
|
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
|
+
};
|
|
1403
2099
|
}
|
|
1404
2100
|
async function stageWorkflowWithLocalDependencies(options) {
|
|
1405
2101
|
const queue = [options.workflowName];
|
|
@@ -1412,20 +2108,25 @@ async function stageWorkflowWithLocalDependencies(options) {
|
|
|
1412
2108
|
visited.add(currentWorkflowName);
|
|
1413
2109
|
const sourceDir = currentWorkflowName === options.workflowName ? options.workflowDir : path4.join(options.workspaceRoot, currentWorkflowName);
|
|
1414
2110
|
if (!existsSync2(path4.join(sourceDir, "package.json"))) {
|
|
1415
|
-
throw new Error(
|
|
1416
|
-
|
|
1417
|
-
|
|
2111
|
+
throw new Error(i18n.t("localDependencyMissingPackage", {
|
|
2112
|
+
ns: "cli",
|
|
2113
|
+
workflow: options.workflowName,
|
|
2114
|
+
dependency: currentWorkflowName,
|
|
2115
|
+
path: sourceDir
|
|
2116
|
+
}));
|
|
1418
2117
|
}
|
|
1419
2118
|
const uploadPlan = await copyWorkflowSource(
|
|
1420
2119
|
sourceDir,
|
|
1421
|
-
path4.join(options.stagingRoot, currentWorkflowName)
|
|
2120
|
+
path4.join(options.stagingRoot, currentWorkflowName),
|
|
2121
|
+
{ projectGroupSafe: options.projectGroupSafe }
|
|
1422
2122
|
);
|
|
1423
2123
|
if (currentWorkflowName === options.workflowName && !uploadPlan.workflowIgnoreConfigured) {
|
|
1424
|
-
process.stderr.write(
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
2124
|
+
process.stderr.write(`${i18n.t("workflowIgnoreWarning", {
|
|
2125
|
+
ns: "cli",
|
|
2126
|
+
files: uploadPlan.files.length,
|
|
2127
|
+
bytes: uploadPlan.totalBytes
|
|
2128
|
+
})}
|
|
2129
|
+
`);
|
|
1429
2130
|
}
|
|
1430
2131
|
for (const dependency of await findLocalWorkflowDependencies(sourceDir)) {
|
|
1431
2132
|
if (!visited.has(dependency)) {
|
|
@@ -1460,7 +2161,7 @@ async function findLocalWorkflowDependencies(sourceDir) {
|
|
|
1460
2161
|
function normalizeWorkflowFilePath(filePath) {
|
|
1461
2162
|
const normalized = filePath.replaceAll("\\", "/").replace(/^\.\/+/, "");
|
|
1462
2163
|
if (normalized === "" || normalized.startsWith("/") || /^[a-zA-Z]:/.test(normalized) || normalized.split("/").some((part) => part === "" || part === "." || part === "..")) {
|
|
1463
|
-
throw new Error(
|
|
2164
|
+
throw new Error(i18n.t("invalidWorkflowFilePath", { ns: "cli", path: filePath }));
|
|
1464
2165
|
}
|
|
1465
2166
|
return normalized;
|
|
1466
2167
|
}
|
|
@@ -1469,28 +2170,48 @@ function decodeWorkflowFileContent(file) {
|
|
|
1469
2170
|
return file.content;
|
|
1470
2171
|
}
|
|
1471
2172
|
if (file.encoding !== "base64") {
|
|
1472
|
-
throw new Error(
|
|
2173
|
+
throw new Error(i18n.t("unsupportedWorkflowEncoding", { ns: "cli", path: file.path }));
|
|
1473
2174
|
}
|
|
1474
2175
|
const normalized = file.content.replace(/=+$/, "");
|
|
1475
2176
|
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(file.content)) {
|
|
1476
|
-
throw new Error(
|
|
2177
|
+
throw new Error(i18n.t("invalidBase64Content", { ns: "cli", path: file.path }));
|
|
1477
2178
|
}
|
|
1478
2179
|
const decoded = Buffer.from(file.content, "base64");
|
|
1479
2180
|
if (decoded.toString("base64").replace(/=+$/, "") !== normalized) {
|
|
1480
|
-
throw new Error(
|
|
2181
|
+
throw new Error(i18n.t("invalidBase64Content", { ns: "cli", path: file.path }));
|
|
1481
2182
|
}
|
|
1482
2183
|
return decoded;
|
|
1483
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
|
+
}
|
|
1484
2199
|
function parseArgs(argv) {
|
|
1485
2200
|
const [command = "", ...rest] = argv;
|
|
1486
2201
|
const positional = [];
|
|
1487
2202
|
const authConfig = readCliAuthConfigSync();
|
|
1488
2203
|
const options = {
|
|
1489
2204
|
server: process.env.WORKFLOW_SERVER_URL ?? authConfig.serverUrl ?? DEFAULT_SERVER_URL,
|
|
1490
|
-
token: process.env.WORKFLOW_SERVER_ADMIN_KEY ?? authConfig.apiKey ?? ""
|
|
2205
|
+
token: process.env.WORKFLOW_SERVER_ADMIN_KEY ?? authConfig.apiKey ?? "",
|
|
2206
|
+
dependencies: [],
|
|
2207
|
+
dataGrants: []
|
|
1491
2208
|
};
|
|
1492
2209
|
for (let index = 0; index < rest.length; index += 1) {
|
|
1493
2210
|
const arg = rest[index];
|
|
2211
|
+
if (arg === "--") {
|
|
2212
|
+
positional.push(...rest.slice(index + 1));
|
|
2213
|
+
break;
|
|
2214
|
+
}
|
|
1494
2215
|
switch (arg) {
|
|
1495
2216
|
case "--server":
|
|
1496
2217
|
options.server = requireValue(rest, index += 1, "--server");
|
|
@@ -1501,8 +2222,11 @@ function parseArgs(argv) {
|
|
|
1501
2222
|
case "--target":
|
|
1502
2223
|
options.target = requireValue(rest, index += 1, "--target");
|
|
1503
2224
|
break;
|
|
2225
|
+
case "--entrypoint":
|
|
2226
|
+
options.entrypoint = requireValue(rest, index += 1, "--entrypoint");
|
|
2227
|
+
break;
|
|
1504
2228
|
case "--version":
|
|
1505
|
-
throw new Error("
|
|
2229
|
+
throw new Error(i18n.t("versionOptionUnsupported", { ns: "cli" }));
|
|
1506
2230
|
case "--release-log":
|
|
1507
2231
|
options.releaseLog = requireValue(rest, index += 1, "--release-log");
|
|
1508
2232
|
break;
|
|
@@ -1521,10 +2245,25 @@ function parseArgs(argv) {
|
|
|
1521
2245
|
case "--no-wait":
|
|
1522
2246
|
options.noWait = true;
|
|
1523
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;
|
|
1524
2263
|
case "--source-mode": {
|
|
1525
2264
|
const value = requireValue(rest, index += 1, "--source-mode");
|
|
1526
2265
|
if (value !== "bundled" && value !== "source") {
|
|
1527
|
-
throw new Error(
|
|
2266
|
+
throw new Error(i18n.t("invalidSourceMode", { ns: "cli" }));
|
|
1528
2267
|
}
|
|
1529
2268
|
options.sourceMode = value;
|
|
1530
2269
|
break;
|
|
@@ -1535,7 +2274,31 @@ function parseArgs(argv) {
|
|
|
1535
2274
|
}
|
|
1536
2275
|
}
|
|
1537
2276
|
if (command === "upload" && options.noWait === true && options.releaseLog !== void 0) {
|
|
1538
|
-
throw new Error("
|
|
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" }));
|
|
1539
2302
|
}
|
|
1540
2303
|
return {
|
|
1541
2304
|
command,
|
|
@@ -1545,20 +2308,20 @@ function parseArgs(argv) {
|
|
|
1545
2308
|
}
|
|
1546
2309
|
function requireLogin(parsed) {
|
|
1547
2310
|
if (parsed.options.server.trim() === "" || parsed.options.token.trim() === "") {
|
|
1548
|
-
throw new Error("
|
|
2311
|
+
throw new Error(i18n.t("notLoggedIn", { ns: "cli" }));
|
|
1549
2312
|
}
|
|
1550
2313
|
}
|
|
1551
2314
|
function requirePositional(parsed, index, name) {
|
|
1552
2315
|
const value = parsed.positional[index];
|
|
1553
2316
|
if (value === void 0 || value.trim() === "") {
|
|
1554
|
-
throw new Error(
|
|
2317
|
+
throw new Error(i18n.t("missingPositional", { ns: "cli", name }));
|
|
1555
2318
|
}
|
|
1556
2319
|
return value;
|
|
1557
2320
|
}
|
|
1558
2321
|
function requireValue(argv, index, name) {
|
|
1559
2322
|
const value = argv[index];
|
|
1560
2323
|
if (value === void 0 || value.trim() === "") {
|
|
1561
|
-
throw new Error(
|
|
2324
|
+
throw new Error(i18n.t("missingOptionValue", { ns: "cli", name }));
|
|
1562
2325
|
}
|
|
1563
2326
|
return value;
|
|
1564
2327
|
}
|
|
@@ -1603,67 +2366,28 @@ function assertApiOk(response) {
|
|
|
1603
2366
|
function formatForbiddenMessage(response) {
|
|
1604
2367
|
const message = response.errMessage.toLowerCase();
|
|
1605
2368
|
if (message.includes("blocked from running")) {
|
|
1606
|
-
return "
|
|
2369
|
+
return i18n.t("forbiddenBlocked", { ns: "cli" });
|
|
1607
2370
|
}
|
|
1608
2371
|
if (message.includes("permission denied")) {
|
|
1609
|
-
return "
|
|
2372
|
+
return i18n.t("forbiddenNoPermission", { ns: "cli" });
|
|
1610
2373
|
}
|
|
1611
|
-
return
|
|
2374
|
+
return i18n.t("forbiddenFallback", { ns: "cli", message: response.errMessage });
|
|
1612
2375
|
}
|
|
1613
2376
|
function printUsage() {
|
|
1614
|
-
console.log(
|
|
1615
|
-
"Usage: workflow-code workspace <command> [args]",
|
|
1616
|
-
"",
|
|
1617
|
-
"Local repo wrapper:",
|
|
1618
|
-
" pnpm -C workspace workspace <command> [args]",
|
|
1619
|
-
"",
|
|
1620
|
-
"Commands:",
|
|
1621
|
-
" login --server <url>",
|
|
1622
|
-
" logout",
|
|
1623
|
-
" status",
|
|
1624
|
-
" health",
|
|
1625
|
-
" pack <workflow> [--path <workflow-dir>] [--output <file.tgz>]",
|
|
1626
|
-
" upload <workflow> [--path <workflow-dir>] [--file <archive>] [--release-log <text>] [--source-mode bundled|source] [--no-wait] [--create]",
|
|
1627
|
-
" preparation <workflow-id> <job-id>",
|
|
1628
|
-
" download <workflow> [--target draft|latest|version] [--path <dir>]",
|
|
1629
|
-
" publish <workflow> [--release-log <text>] [--source-mode bundled|source]",
|
|
1630
|
-
" run <workflow> [...args] [--target draft|latest|version]",
|
|
1631
|
-
" debug-node <workflow> <node> [...args] [--target draft|latest|version]",
|
|
1632
|
-
" versions <workflow>",
|
|
1633
|
-
"",
|
|
1634
|
-
"Options:",
|
|
1635
|
-
" --server <url> Defaults to WORKFLOW_SERVER_URL, saved CLI auth, or http://localhost:7125",
|
|
1636
|
-
" --token <token> Optional manual override. Defaults to WORKFLOW_SERVER_ADMIN_KEY or saved CLI auth API key"
|
|
1637
|
-
].join("\n"));
|
|
2377
|
+
console.log(i18n.t("workspaceHelp", { ns: "cli" }));
|
|
1638
2378
|
}
|
|
1639
2379
|
function resolveRepoRoot(startDir = process.cwd()) {
|
|
1640
2380
|
if (process.env.WORKFLOW_REPO_ROOT !== void 0) {
|
|
1641
2381
|
return path4.resolve(process.env.WORKFLOW_REPO_ROOT);
|
|
1642
2382
|
}
|
|
1643
2383
|
for (const candidate of [startDir, CLI_MODULE_DIR]) {
|
|
1644
|
-
const repoRoot =
|
|
2384
|
+
const repoRoot = findWorkspaceRepositoryRoot(candidate);
|
|
1645
2385
|
if (repoRoot !== void 0) {
|
|
1646
2386
|
return repoRoot;
|
|
1647
2387
|
}
|
|
1648
2388
|
}
|
|
1649
2389
|
return process.cwd();
|
|
1650
2390
|
}
|
|
1651
|
-
function findRepoRoot2(startDir) {
|
|
1652
|
-
let current = path4.resolve(startDir);
|
|
1653
|
-
while (true) {
|
|
1654
|
-
if (isRepoRoot(current)) {
|
|
1655
|
-
return current;
|
|
1656
|
-
}
|
|
1657
|
-
const parent = path4.dirname(current);
|
|
1658
|
-
if (parent === current) {
|
|
1659
|
-
return void 0;
|
|
1660
|
-
}
|
|
1661
|
-
current = parent;
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
function isRepoRoot(candidate) {
|
|
1665
|
-
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"));
|
|
1666
|
-
}
|
|
1667
2391
|
function trimTrailingSlash(value) {
|
|
1668
2392
|
return value.replace(/\/+$/, "");
|
|
1669
2393
|
}
|
|
@@ -1731,7 +2455,7 @@ function readRetryAfterSeconds(data) {
|
|
|
1731
2455
|
}
|
|
1732
2456
|
if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL(path4.resolve(process.argv[1])).href) {
|
|
1733
2457
|
main().catch((error) => {
|
|
1734
|
-
console.error(
|
|
2458
|
+
console.error(translateWorkflowCodeError(error));
|
|
1735
2459
|
process.exitCode = 1;
|
|
1736
2460
|
});
|
|
1737
2461
|
}
|
|
@@ -1742,13 +2466,18 @@ export {
|
|
|
1742
2466
|
formatForbiddenMessage,
|
|
1743
2467
|
getPreparation,
|
|
1744
2468
|
main,
|
|
2469
|
+
materializeProjectGroupDownload,
|
|
1745
2470
|
normalizeWorkflowFilePath,
|
|
2471
|
+
packProjectGroup,
|
|
1746
2472
|
packWorkflow,
|
|
1747
2473
|
parseArgs,
|
|
1748
2474
|
readPackageJsonFile,
|
|
2475
|
+
readProjectGroupContexts,
|
|
1749
2476
|
readWorkflowPackageContext,
|
|
2477
|
+
resolveExplicitDataGrants,
|
|
1750
2478
|
resolveRemoteWorkflowId,
|
|
1751
2479
|
resolveRepoRoot,
|
|
2480
|
+
shouldUseProjectGroupUpload,
|
|
1752
2481
|
stageWorkflowWithLocalDependencies,
|
|
1753
2482
|
waitForDeviceToken,
|
|
1754
2483
|
waitForPreparation,
|