@tailor-platform/sdk 2.0.0-next.5 → 2.0.0-next.7
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/CHANGELOG.md +85 -0
- package/dist/{application-Bf8wxOi6.mjs → application-GzW98_Xr.mjs} +377 -1418
- package/dist/application-GzW98_Xr.mjs.map +1 -0
- package/dist/application-OM0taSPn.mjs +3 -0
- package/dist/cli/commands/deploy/deploy.d.mts +8 -3
- package/dist/cli/commands/generate/options.d.mts +0 -1
- package/dist/cli/commands/generate/service.d.mts +1 -1
- package/dist/cli/commands/staticwebsite/deploy.d.mts +19 -0
- package/dist/cli/commands/workspace/create.d.mts +3 -0
- package/dist/cli/commands/workspace/list.d.mts +1 -0
- package/dist/cli/commands/workspace/transform.d.mts +2 -0
- package/dist/cli/index.mjs +781 -1553
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +7 -3
- package/dist/cli/lib.mjs +49 -3
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/shared/config-loader.d.mts +1 -1
- package/dist/cli/shared/readonly-guard.d.mts +25 -0
- package/dist/cli/shared/tailordb-namespaces.d.mts +37 -0
- package/dist/cli/shared/workspace-name.d.mts +1 -0
- package/dist/completion/zsh-worker.zsh +15 -303
- package/dist/configure/config/types.d.mts +5 -1
- package/dist/configure/index.mjs +20 -19
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/auth/types.d.mts +18 -23
- package/dist/configure/services/executor/executor.d.mts +2 -13
- package/dist/configure/services/executor/operation.d.mts +8 -3
- package/dist/configure/services/tailordb/schema.d.mts +1 -7
- package/dist/configure/services/tailordb/types.d.mts +1 -1
- package/dist/configure/services/workflow/execution-policy.d.mts +2 -2
- package/dist/configure/services/workflow/job.d.mts +10 -10
- package/dist/configure/services/workflow/workflow.d.mts +6 -4
- package/dist/configure/types/type.d.mts +1 -9
- package/dist/{crashreport-D3RjuSYb.mjs → crashreport-0ioNRZ4K.mjs} +1 -1
- package/dist/{crashreport-Cg-pqRJV.mjs → crashreport-CxHne11E.mjs} +4 -3
- package/dist/{crashreport-Cg-pqRJV.mjs.map → crashreport-CxHne11E.mjs.map} +1 -1
- package/dist/errors-DfnF4ibq.mjs +100 -0
- package/dist/errors-DfnF4ibq.mjs.map +1 -0
- package/dist/{globals-BjhJJ2dN.mjs → globals-CBZ0egXT.mjs} +13 -10
- package/dist/globals-CBZ0egXT.mjs.map +1 -0
- package/dist/{logger-BEiZZ3qT.mjs → logger-BwS4ppwO.mjs} +3 -3
- package/dist/logger-BwS4ppwO.mjs.map +1 -0
- package/dist/parser/service/tailordb/type-source.d.mts +10 -0
- package/dist/{register-ts-hook-BhzVkej0.mjs → register-ts-hook-DvEs6YsL.mjs} +2571 -1636
- package/dist/register-ts-hook-DvEs6YsL.mjs.map +1 -0
- package/dist/{registry-BSbq6SPo.mjs → registry-i7EdJ-D5.mjs} +10 -10
- package/dist/registry-i7EdJ-D5.mjs.map +1 -0
- package/dist/runtime/field-parse.d.mts +10 -0
- package/dist/runtime/globals.d.mts +3 -3
- package/dist/runtime/index.mjs +1 -1
- package/dist/runtime/workflow.d.mts +18 -33
- package/dist/runtime/workflow.mjs +1 -1
- package/dist/{schema-iCd1ugGp.mjs → schema--xYWRGfe.mjs} +42 -24
- package/dist/schema--xYWRGfe.mjs.map +1 -0
- package/dist/{secret-file-aoNXJmKa.mjs → secret-file-C3J4Gy8r.mjs} +2 -27
- package/dist/secret-file-C3J4Gy8r.mjs.map +1 -0
- package/dist/{secretmanager-Bd45j7an.mjs → secretmanager-IY4UvinW.mjs} +42 -9
- package/dist/secretmanager-IY4UvinW.mjs.map +1 -0
- package/dist/service-BJAQ70e5.mjs +3 -0
- package/dist/{service-DeZeYa06.mjs → service-DaeJlBoH.mjs} +3 -3
- package/dist/{service-DeZeYa06.mjs.map → service-DaeJlBoH.mjs.map} +1 -1
- package/dist/{service-CzZEVrp9.mjs → service-Dmxa2I4i.mjs} +64 -28
- package/dist/service-Dmxa2I4i.mjs.map +1 -0
- package/dist/service_pb-BoLM0X4K.mjs +167 -0
- package/dist/service_pb-BoLM0X4K.mjs.map +1 -0
- package/dist/{service_pb-DZkfjB_6.mjs → service_pb-r1T7kpJc.mjs} +1 -1
- package/dist/types/executor.generated.d.mts +60 -42
- package/dist/user-agent-Bgsszb5I.mjs +28 -0
- package/dist/user-agent-Bgsszb5I.mjs.map +1 -0
- package/dist/utils/test/index.mjs +11 -11
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/utils/test/mock.d.mts +6 -6
- package/dist/vitest/environment.mjs +1 -1
- package/dist/vitest/index.d.mts +8 -8
- package/dist/vitest/index.mjs +613 -227
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mock.d.mts +8 -8
- package/dist/vitest/mocks/aigateway.d.mts +14 -4
- package/dist/vitest/mocks/authconnection.d.mts +14 -4
- package/dist/vitest/mocks/file.d.mts +23 -6
- package/dist/vitest/mocks/iconv.d.mts +28 -4
- package/dist/vitest/mocks/idp.d.mts +25 -9
- package/dist/vitest/mocks/secretmanager.d.mts +13 -4
- package/dist/vitest/mocks/tailordb.d.mts +48 -7
- package/dist/vitest/mocks/workflow.d.mts +59 -33
- package/dist/vitest/setup.mjs +2 -2
- package/dist/vitest/workflow-local.d.mts +2 -2
- package/dist/workflow-CsBvRE3g.mjs +34 -0
- package/dist/workflow-CsBvRE3g.mjs.map +1 -0
- package/dist/{service_pb-Bk7dL-yT.mjs → workspace_resource_pb-Db3fv68L.mjs} +47 -165
- package/dist/workspace_resource_pb-Db3fv68L.mjs.map +1 -0
- package/docs/cli/application.md +63 -15
- package/docs/cli/function.md +2 -2
- package/docs/cli/tailordb.md +8 -140
- package/docs/cli/workspace.md +6 -4
- package/docs/cli-reference.md +11 -8
- package/docs/configuration.md +2 -0
- package/docs/github-actions.md +8 -0
- package/docs/migration/v2.md +169 -11
- package/docs/runtime.md +5 -6
- package/docs/services/auth.md +7 -7
- package/docs/services/executor.md +5 -0
- package/docs/services/tailordb.md +44 -0
- package/docs/services/workflow.md +43 -43
- package/docs/testing.md +112 -96
- package/package.json +2 -5
- package/dist/application-Bf8wxOi6.mjs.map +0 -1
- package/dist/application-CUkjzfij.mjs +0 -3
- package/dist/cli/erd-viewer-assets/app.js +0 -1424
- package/dist/cli/erd-viewer-assets/index.html +0 -77
- package/dist/cli/erd-viewer-assets/serve.json +0 -13
- package/dist/cli/erd-viewer-assets/styles.css +0 -1036
- package/dist/configure/types/field-runtime.d.mts +0 -16
- package/dist/errors-118wUDRr.mjs +0 -42
- package/dist/errors-118wUDRr.mjs.map +0 -1
- package/dist/globals-BjhJJ2dN.mjs.map +0 -1
- package/dist/logger-BEiZZ3qT.mjs.map +0 -1
- package/dist/register-ts-hook-BhzVkej0.mjs.map +0 -1
- package/dist/registry-BSbq6SPo.mjs.map +0 -1
- package/dist/resource_pb-BRv7AMXK.mjs +0 -49
- package/dist/resource_pb-BRv7AMXK.mjs.map +0 -1
- package/dist/schema-iCd1ugGp.mjs.map +0 -1
- package/dist/secret-file-aoNXJmKa.mjs.map +0 -1
- package/dist/secretmanager-Bd45j7an.mjs.map +0 -1
- package/dist/service-BT0GPd01.mjs +0 -3
- package/dist/service-CzZEVrp9.mjs.map +0 -1
- package/dist/service_pb-Bk7dL-yT.mjs.map +0 -1
- package/dist/workflow-GNuSnsTx.mjs +0 -22
- package/dist/workflow-GNuSnsTx.mjs.map +0 -1
|
@@ -1,699 +1,1269 @@
|
|
|
1
|
-
import { t as db } from "./schema
|
|
2
|
-
import {
|
|
1
|
+
import { t as db } from "./schema--xYWRGfe.mjs";
|
|
2
|
+
import { $ as CreateAuthIDPConfigRequestSchema, A as CreatePipelineResolverRequestSchema, At as TenantProviderConfig_TenantProviderType, B as IdPPermissionOperator, Bt as CreateAIGatewayRequestSchema, C as UpdateStaticWebsiteRequestSchema, Ct as AuthOAuth2Client_ClientType, D as CreateSecretManagerVaultRequestSchema, Dt as AuthSCIMAttribute_Uniqueness, E as CreateSecretManagerSecretRequestSchema, Et as AuthSCIMAttribute_Type, F as PipelineResolver_OperationType, Ft as GetApplicationSchemaHealthResponse_ApplicationSchemaHealthStatus, G as CreateExecutorExecutorRequestSchema, Gt as ConditionSchema, H as FunctionExecution_Status, I as CreateIdPServiceRequestSchema, It as UpdateApplicationRequestSchema, J as ExecutorJobStatus, Jt as PageDirection, K as UpdateExecutorExecutorRequestSchema, Kt as Condition_Operator, L as UpdateIdPServiceRequestSchema, M as UpdatePipelineResolverRequestSchema, N as UpdatePipelineServiceRequestSchema, O as UpdateSecretManagerSecretRequestSchema, Ot as AuthSCIMConfig_AuthorizationType, Pt as CreateApplicationRequestSchema, Q as CreateAuthHookRequestSchema, Rt as ApplicationSchemaUpdateAttemptStatus, S as CreateStaticWebsiteRequestSchema, St as AuthInvokerSchema, Tt as AuthSCIMAttribute_Mutability, V as IdPPermissionPermit, Vt as UpdateAIGatewayRequestSchema, X as ExecutorTriggerType, Y as ExecutorTargetType, Z as CreateAuthConnectionRequestSchema, _ as TailorDBGQLPermission_Permit, a as CreateWorkflowRequestSchema, at as CreateTenantConfigRequestSchema, b as TailorDBType_PermitAction, bt as AuthHookPoint, ct as UpdateAuthHookRequestSchema, d as CreateTailorDBServiceRequestSchema, dt as UpdateAuthOAuth2ClientRequestSchema, et as CreateAuthMachineUserRequestSchema, f as CreateTailorDBTypeRequestSchema, ft as UpdateAuthSCIMConfigRequestSchema, g as TailorDBGQLPermission_Operator, gt as UpdateUserProfileConfigRequestSchema, h as TailorDBGQLPermission_Action, ht as UpdateTenantConfigRequestSchema, i as CreateWorkflowJobFunctionRequestSchema, it as CreateAuthServiceRequestSchema, j as CreatePipelineServiceRequestSchema, jt as UserProfileProviderConfig_UserProfileProviderType, l as WorkflowExecution_Status, lt as UpdateAuthIDPConfigRequestSchema, mt as UpdateAuthServiceRequestSchema, nt as CreateAuthSCIMConfigRequestSchema, o as UpdateWorkflowJobFunctionExecutionPolicyRequestSchema, ot as CreateUserProfileConfigRequestSchema, p as UpdateTailorDBTypeRequestSchema, pt as UpdateAuthSCIMResourceRequestSchema, qt as FilterSchema, r as CreateWorkflowJobFunctionExecutionPolicyRequestSchema, rt as CreateAuthSCIMResourceRequestSchema, s as UpdateWorkflowRequestSchema, st as UpdateAuthConnectionRequestSchema, t as WorkspacePlatformUserRole, tt as CreateAuthOAuth2ClientRequestSchema, u as WorkflowJobExecution_Status, ut as UpdateAuthMachineUserRequestSchema, v as TailorDBType_Permission_Operator, vt as AuthConnection_Status, wt as AuthOAuth2Client_GrantType, x as AddCustomDomainRequestSchema, xt as AuthIDPConfig_AuthType, y as TailorDBType_Permission_Permit, yt as AuthConnection_Type, z as IdPLang, zt as Subgraph_ServiceType } from "./workspace_resource_pb-Db3fv68L.mjs";
|
|
3
3
|
import { t as assertDefined } from "./assert-DBxo8jPo.mjs";
|
|
4
|
-
import { a as parseBoolean, i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-
|
|
5
|
-
import {
|
|
6
|
-
import { c as stripTailorDBTypeBuilderHelpers, h as loadFilesWithIgnores, t as createExecutorService, u as TailorDBTypeSchema } from "./service-
|
|
4
|
+
import { a as parseBoolean, i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-BwS4ppwO.mjs";
|
|
5
|
+
import { A as fetchAllTolerant, B as initOperatorClient, C as createBundleCache, E as hashFile, F as getConsoleBaseUrl, G as byName, H as normalizeBaseUrl, I as getOAuth2ClientId, K as LOG_LEVELS, L as getOrNull, M as fetchPaged, O as defaultPlatformBaseUrl, P as fetchUserInfo, R as getPlatformBaseUrl, S as hasGenerationHooks, T as hashContent, U as rememberPlatformConfigForToken, V as isDefaultPlatform, W as resolveStaticWebsiteUrls, _ as platformBundleDefinePlugin, f as buildExecutorArgsExpr, g as stringifyFunction, h as assertUniqueTailorDBTypeNamesWithExternal, i as resolverBundleKey, j as fetchMachineUserToken, k as fetchAll, m as assertUniqueLocalTailorDBTypeNames, n as generatePluginFilesIfNeeded, o as getApplicationAuthNamespace, p as buildResolverOperationHookExpr, r as loadApplication, s as WorkflowJobFunctionExecutionPolicySchema, t as defineApplication, u as HTTP_METHODS, w as getDistDir, x as getPluginGenerationDependencies, z as initOAuth2Client } from "./application-GzW98_Xr.mjs";
|
|
6
|
+
import { c as stripTailorDBTypeBuilderHelpers, g as functionSchema, h as loadFilesWithIgnores, t as createExecutorService, u as TailorDBTypeSchema } from "./service-Dmxa2I4i.mjs";
|
|
7
7
|
import { t as multiline } from "./multiline-sfHpTZZK.mjs";
|
|
8
8
|
import { t as readPackageJson } from "./package-json-8b0O9TlX.mjs";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { t as userAgent } from "./user-agent-Bgsszb5I.mjs";
|
|
10
|
+
import { r as isCLIError, t as createCLIError } from "./errors-DfnF4ibq.mjs";
|
|
11
|
+
import { n as tightenSecretFilePermissions, r as writeSecretFile } from "./secret-file-C3J4Gy8r.mjs";
|
|
12
12
|
import { r as withSpan } from "./telemetry-BRVdwh14.mjs";
|
|
13
13
|
import * as mod from "node:module";
|
|
14
14
|
import * as path from "pathe";
|
|
15
|
+
import { basename, dirname, join, resolve } from "pathe";
|
|
15
16
|
import { resolveTSConfig } from "pkg-types";
|
|
16
17
|
import { arg, createDefineCommand, defineCommand, runCommand } from "politty";
|
|
17
18
|
import { z } from "zod";
|
|
18
|
-
import {
|
|
19
|
-
import * as fs$
|
|
19
|
+
import { create, fromJson, toJson } from "@bufbuild/protobuf";
|
|
20
|
+
import * as fs$2 from "node:fs";
|
|
20
21
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { parseEnv } from "node:util";
|
|
21
23
|
import { ValueSchema, timestampDate } from "@bufbuild/protobuf/wkt";
|
|
22
24
|
import { formatDistanceToNowStrict } from "date-fns";
|
|
23
25
|
import { getBorderCharacters, table } from "table";
|
|
24
26
|
import { pathToFileURL } from "node:url";
|
|
27
|
+
import { parseYAML, stringifyYAML } from "confbox";
|
|
25
28
|
import { findUpSync } from "find-up-simple";
|
|
29
|
+
import { lt } from "semver";
|
|
26
30
|
import { xdgConfig } from "xdg-basedir";
|
|
27
31
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
28
32
|
import pLimit from "p-limit";
|
|
29
33
|
import * as crypto$1 from "node:crypto";
|
|
30
|
-
import { createHash } from "node:crypto";
|
|
34
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
31
35
|
import { ExitPromptError } from "@inquirer/core";
|
|
32
|
-
import { confirm, input, password } from "@inquirer/prompts";
|
|
36
|
+
import { confirm, input, password, select } from "@inquirer/prompts";
|
|
33
37
|
import { isCI } from "std-env";
|
|
34
38
|
import * as rolldown from "rolldown";
|
|
35
|
-
import * as fs from "node:fs/promises";
|
|
36
|
-
import { glob } from "node:fs/promises";
|
|
37
39
|
import { parseSync } from "oxc-parser";
|
|
38
40
|
import * as inflection from "inflection";
|
|
39
41
|
import { tmpdir } from "node:os";
|
|
40
42
|
import { pathToString } from "@bufbuild/protobuf/reflect";
|
|
41
43
|
import { createValidator } from "@bufbuild/protovalidate";
|
|
44
|
+
import * as fs from "node:fs/promises";
|
|
45
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
42
46
|
import { setTimeout as setTimeout$1 } from "timers/promises";
|
|
43
47
|
import { setTimeout as setTimeout$2 } from "node:timers/promises";
|
|
44
48
|
import { spawn } from "node:child_process";
|
|
45
|
-
import
|
|
46
|
-
import
|
|
49
|
+
import * as fs$1 from "fs";
|
|
50
|
+
import { lookup } from "mime-types";
|
|
47
51
|
import { createPrompt } from "@toiroakr/read-multiline";
|
|
48
52
|
import { astVisitor, parse } from "pgsql-ast-parser";
|
|
49
53
|
import { parse as parse$1 } from "@0no-co/graphql.web";
|
|
50
54
|
|
|
51
|
-
//#region src/cli/shared/
|
|
55
|
+
//#region src/cli/shared/args.ts
|
|
56
|
+
const unitToMs = {
|
|
57
|
+
ms: 1,
|
|
58
|
+
s: 1e3,
|
|
59
|
+
m: 60 * 1e3
|
|
60
|
+
};
|
|
61
|
+
const durationPattern = /^(\d+)(ms|s|m)$/;
|
|
52
62
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* Parent commands with only `subCommands` can use `defineCommand` from politty directly.
|
|
63
|
+
* Schema for duration string validation (e.g., "3s", "500ms", "1m")
|
|
64
|
+
* Only validates format; use parseDuration() to convert to milliseconds
|
|
56
65
|
*/
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
const durationArg = z.string().refine((val) => durationPattern.test(val), { message: "Invalid duration format. Expected format: '3s', '500ms', '1m'" }).refine((val) => {
|
|
67
|
+
const match = val.match(durationPattern);
|
|
68
|
+
if (!match) return false;
|
|
69
|
+
const digits = match[1];
|
|
70
|
+
return digits !== void 0 && parseInt(digits, 10) > 0;
|
|
71
|
+
}, { message: "Duration must be greater than 0" });
|
|
61
72
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* 1. `opts.profile` (CLI flag)
|
|
66
|
-
* 2. `process.env.TAILOR_PLATFORM_PROFILE`
|
|
67
|
-
*
|
|
68
|
-
* If neither is set, no profile is in scope so the call is allowed. This is
|
|
69
|
-
* intentional: `TAILOR_PLATFORM_TOKEN` direct access (CI / machine user) and
|
|
70
|
-
* `--workspace-id` without a profile are out-of-band paths whose authorization
|
|
71
|
-
* is governed by the bearer token itself, not by the local profile flag.
|
|
72
|
-
*
|
|
73
|
-
* If the resolved profile cannot be found in the config, this function returns
|
|
74
|
-
* silently and lets downstream loaders surface the not-found error.
|
|
75
|
-
* @param opts - Options
|
|
76
|
-
* @param opts.profile - Optional explicit profile name from command args
|
|
73
|
+
* Parse a validated duration string into milliseconds
|
|
74
|
+
* @param duration - Duration string (e.g., "3s", "500ms", "1m")
|
|
75
|
+
* @returns Duration in milliseconds
|
|
77
76
|
*/
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
throw createCLIError({
|
|
84
|
-
code: "PROFILE_READONLY",
|
|
85
|
-
message: `Profile "${profileName}" is read-only.`,
|
|
86
|
-
details: "This profile blocks platform-state mutations (apply, create/update/delete, deploy, etc.). Application-data operations remain available because their permissions are governed by the machine user.",
|
|
87
|
-
suggestion: `Use a different profile, unset TAILOR_PLATFORM_PROFILE, or run 'tailor profile update ${profileName} --permission write'.`
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
//#endregion
|
|
92
|
-
//#region src/cli/commands/api/api-call.ts
|
|
93
|
-
function hasEnvAccessToken() {
|
|
94
|
-
const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
|
|
95
|
-
return Boolean(envToken);
|
|
77
|
+
function parseDuration(duration) {
|
|
78
|
+
const match = assertDefined(duration.match(durationPattern), `invalid duration format: ${duration}`);
|
|
79
|
+
const value = parseInt(assertDefined(match[1], "duration digits group missing"), 10);
|
|
80
|
+
const unit = assertDefined(match[2], "duration unit group missing");
|
|
81
|
+
return value * unitToMs[unit];
|
|
96
82
|
}
|
|
97
83
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* @param options - API call options (profile, endpoint, body)
|
|
101
|
-
* @returns Response status and data
|
|
84
|
+
* Schema for positive integer validation (from string input)
|
|
85
|
+
* Transforms the string to a number
|
|
102
86
|
*/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
status: response.status,
|
|
128
|
-
data
|
|
129
|
-
};
|
|
87
|
+
const positiveIntArg = z.coerce.number().int().positive();
|
|
88
|
+
/**
|
|
89
|
+
* Schema for non-negative integer validation (from string input).
|
|
90
|
+
* Accepts 0 (used for `--limit 0` to disable the limit).
|
|
91
|
+
*/
|
|
92
|
+
const nonNegativeIntArg = z.coerce.number().int().nonnegative();
|
|
93
|
+
/**
|
|
94
|
+
* Schema for sort order (`asc` or `desc`).
|
|
95
|
+
*/
|
|
96
|
+
const orderArg = z.enum(["asc", "desc"]);
|
|
97
|
+
/**
|
|
98
|
+
* Translate a CLI `--order` value into the proto `PageDirection` enum.
|
|
99
|
+
* Returns `undefined` when the user did not specify an order so that
|
|
100
|
+
* callers can omit the field and fall back to the server default.
|
|
101
|
+
* @param order - Order string from CLI args (`"asc"` | `"desc"` | undefined)
|
|
102
|
+
* @returns PageDirection, or undefined when `order` is undefined
|
|
103
|
+
*/
|
|
104
|
+
function toPageDirection(order) {
|
|
105
|
+
if (order === void 0) return void 0;
|
|
106
|
+
return order === "asc" ? PageDirection.ASC : PageDirection.DESC;
|
|
107
|
+
}
|
|
108
|
+
function hasMachineUserFlag(argv) {
|
|
109
|
+
return argv.slice(0, argv.indexOf("--") === -1 ? argv.length : argv.indexOf("--")).some((token) => token === "-m" || token.startsWith("-m=") || token === "--machine-user" || token.startsWith("--machine-user=") || token === "--machineUser" || token.startsWith("--machineUser=") || token === "--machineuser" || token.startsWith("--machineuser="));
|
|
130
110
|
}
|
|
131
|
-
|
|
132
|
-
//#endregion
|
|
133
|
-
//#region src/cli/commands/api/proto-reflect.ts
|
|
134
|
-
const UNARY_METHODS = OperatorService.methods.filter((m) => m.methodKind === "unary");
|
|
135
|
-
function listMethodNames() {
|
|
136
|
-
return UNARY_METHODS.map((m) => m.name).toSorted();
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Returns every accepted form of the `endpoint` positional — bare method name
|
|
140
|
-
* and fully-qualified `service/Method`. politty's expand completion keys its
|
|
141
|
-
* static table by the literal endpoint string, so both forms must appear in
|
|
142
|
-
* `choices` or the user typing the fully-qualified name gets no `--field`
|
|
143
|
-
* completion candidates back at TAB time.
|
|
144
|
-
* @returns Sorted list of accepted endpoint values
|
|
145
|
-
*/
|
|
146
|
-
function listMethodChoices() {
|
|
147
|
-
return UNARY_METHODS.flatMap((m) => [m.name, `${OperatorService.typeName}/${m.name}`]).toSorted();
|
|
148
|
-
}
|
|
149
|
-
function getMethodDescriptor(methodName) {
|
|
150
|
-
return UNARY_METHODS.find((m) => m.name === methodName);
|
|
151
|
-
}
|
|
152
|
-
function extractMethodName(endpoint) {
|
|
153
|
-
if (!endpoint.includes("/")) return endpoint;
|
|
154
|
-
return endpoint.split("/").pop() ?? endpoint;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Returns the nested message descriptor when the field is a message, a list of
|
|
158
|
-
* messages, or a map with message values. Otherwise returns undefined.
|
|
159
|
-
* @param field - The proto field descriptor to inspect
|
|
160
|
-
* @returns The nested message descriptor, or undefined when the field isn't message-shaped
|
|
161
|
-
*/
|
|
162
|
-
function nestedMessage(field) {
|
|
163
|
-
if (field.fieldKind === "message") return field.message;
|
|
164
|
-
if (field.fieldKind === "list" && field.listKind === "message") return field.message;
|
|
165
|
-
if (field.fieldKind === "map" && field.mapKind === "message") return field.message;
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Most `google.protobuf.*` well-known types serialize as scalars in proto JSON
|
|
169
|
-
* (Timestamp/Duration → RFC3339/duration string, FieldMask → comma-joined
|
|
170
|
-
* string, *Value wrappers → underlying primitive, etc.), so we must not let
|
|
171
|
-
* `--field` drill into their internal fields. Treating them as leaves keeps
|
|
172
|
-
* the completion and the body shape consistent with what the server expects.
|
|
173
|
-
* @param message - The proto message descriptor to inspect
|
|
174
|
-
* @returns True when the message is a well-known type
|
|
175
|
-
*/
|
|
176
|
-
function isWellKnownType(message) {
|
|
177
|
-
return message.typeName.startsWith("google.protobuf.");
|
|
178
|
-
}
|
|
179
|
-
const UNREPRESENTABLE_WELL_KNOWN_TYPES = /* @__PURE__ */ new Set([
|
|
180
|
-
"google.protobuf.Struct",
|
|
181
|
-
"google.protobuf.Value",
|
|
182
|
-
"google.protobuf.ListValue",
|
|
183
|
-
"google.protobuf.NullValue",
|
|
184
|
-
"google.protobuf.Any",
|
|
185
|
-
"google.protobuf.Empty"
|
|
186
|
-
]);
|
|
187
111
|
/**
|
|
188
|
-
*
|
|
189
|
-
* @
|
|
112
|
+
* Resolve whether a parsed machine user value came from an explicit CLI option or env fallback.
|
|
113
|
+
* @param machineUser - Parsed machine user value
|
|
114
|
+
* @param argv - Raw CLI argv, excluding the executable and script path
|
|
115
|
+
* @param options - Source resolution options
|
|
116
|
+
* @returns Machine user input source, or undefined when no value was parsed
|
|
190
117
|
*/
|
|
191
|
-
function
|
|
192
|
-
|
|
118
|
+
function resolveMachineUserInputSource(machineUser, argv = process.argv.slice(2), options = {}) {
|
|
119
|
+
if (machineUser === void 0) return void 0;
|
|
120
|
+
if (options.valueIsExplicit) return "option";
|
|
121
|
+
if (hasMachineUserFlag(argv)) return "option";
|
|
122
|
+
return process.env.TAILOR_PLATFORM_MACHINE_USER_NAME === machineUser ? "env" : "option";
|
|
193
123
|
}
|
|
194
124
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* prefix-filter at TAB time. Used by the `expand` completion variant so no
|
|
198
|
-
* Node process is spawned per keystroke.
|
|
199
|
-
*
|
|
200
|
-
* For each leaf field, emits `key=` (key completion). Enum leaves additionally
|
|
201
|
-
* emit `key=ENUM_VALUE` per value, bool leaves emit `key=true` and
|
|
202
|
-
* `key=false`. For nested messages, emits `key.` (drill-down) and recurses.
|
|
125
|
+
* Load env files from parsed arguments.
|
|
126
|
+
* Processes --env-file first, then --env-file-if-exists.
|
|
203
127
|
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* @
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const fullKey = prefix + field.localName;
|
|
219
|
-
if (field.fieldKind === "message") {
|
|
220
|
-
if (isUnrepresentableWellKnownType(field.message)) continue;
|
|
221
|
-
if (!isWellKnownType(field.message)) {
|
|
222
|
-
const nested = field.message;
|
|
223
|
-
candidates.push({
|
|
224
|
-
value: `${fullKey}.`,
|
|
225
|
-
description: `${fullKey} (message)`
|
|
226
|
-
});
|
|
227
|
-
if (!visited.has(nested)) walk(nested, `${fullKey}.`);
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
128
|
+
* Follows Node.js --env-file behavior:
|
|
129
|
+
* - Variables already set in the environment are NOT overwritten
|
|
130
|
+
* - Variables from later files override those from earlier files
|
|
131
|
+
* @param envFiles - Required env file path(s) that must exist
|
|
132
|
+
* @param envFilesIfExists - Optional env file path(s) that are loaded if they exist
|
|
133
|
+
*/
|
|
134
|
+
function loadEnvFiles(envFiles, envFilesIfExists) {
|
|
135
|
+
const originalEnvKeys = new Set(Object.keys(process.env));
|
|
136
|
+
const load = (files, required) => {
|
|
137
|
+
for (const file of [files ?? []].flat()) {
|
|
138
|
+
const envPath = path.resolve(process.cwd(), file);
|
|
139
|
+
if (!fs$2.existsSync(envPath)) {
|
|
140
|
+
if (required) throw new Error(`Environment file not found: ${envPath}`);
|
|
141
|
+
continue;
|
|
230
142
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (field.fieldKind === "enum") for (const v of field.enum.values) candidates.push({
|
|
236
|
-
value: `${fullKey}=${v.name}`,
|
|
237
|
-
description: v.name
|
|
238
|
-
});
|
|
239
|
-
else if (field.fieldKind === "scalar" && field.scalar === ScalarType.BOOL) {
|
|
240
|
-
candidates.push({
|
|
241
|
-
value: `${fullKey}=true`,
|
|
242
|
-
description: "true"
|
|
243
|
-
});
|
|
244
|
-
candidates.push({
|
|
245
|
-
value: `${fullKey}=false`,
|
|
246
|
-
description: "false"
|
|
247
|
-
});
|
|
143
|
+
const parsed = parseEnv(fs$2.readFileSync(envPath, "utf-8"));
|
|
144
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
145
|
+
if (originalEnvKeys.has(key)) continue;
|
|
146
|
+
process.env[key] = value;
|
|
248
147
|
}
|
|
249
148
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return candidates;
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Resolves the leaf `DescField` at a dotted `--field` path within an input
|
|
257
|
-
* message tree. Returns undefined when any segment is missing or when a
|
|
258
|
-
* non-leaf segment isn't a singular message — callers fall back to treating
|
|
259
|
-
* the value as a string in that case.
|
|
260
|
-
* @param input - The RPC input message descriptor
|
|
261
|
-
* @param path - Dot-split path segments to follow (e.g. ["application", "name"])
|
|
262
|
-
* @returns The leaf field descriptor, or undefined when the path doesn't resolve
|
|
263
|
-
*/
|
|
264
|
-
function resolveLeafField(input, path) {
|
|
265
|
-
let message = input;
|
|
266
|
-
for (let i = 0; i < path.length; i++) {
|
|
267
|
-
const field = message.fields.find((f) => f.localName === path[i]);
|
|
268
|
-
if (!field) return void 0;
|
|
269
|
-
if (i === path.length - 1) {
|
|
270
|
-
if (field.fieldKind === "message" && isUnrepresentableWellKnownType(field.message)) return;
|
|
271
|
-
return field;
|
|
272
|
-
}
|
|
273
|
-
if (field.fieldKind !== "message") return void 0;
|
|
274
|
-
if (isWellKnownType(field.message)) return void 0;
|
|
275
|
-
message = field.message;
|
|
276
|
-
}
|
|
149
|
+
};
|
|
150
|
+
load(envFiles, true);
|
|
151
|
+
load(envFilesIfExists, false);
|
|
277
152
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
function enumOf(field) {
|
|
309
|
-
if (field.fieldKind === "enum") return field.enum;
|
|
310
|
-
if (field.fieldKind === "list" && field.listKind === "enum") return field.enum;
|
|
311
|
-
}
|
|
312
|
-
function describeFieldType(field) {
|
|
313
|
-
switch (field.fieldKind) {
|
|
314
|
-
case "scalar": return scalarLabel(field.scalar);
|
|
315
|
-
case "enum": return enumLabel(field.enum);
|
|
316
|
-
case "message": return shortName(field.message.typeName);
|
|
317
|
-
case "list": {
|
|
318
|
-
let inner;
|
|
319
|
-
if (field.listKind === "scalar") inner = scalarLabel(field.scalar);
|
|
320
|
-
else if (field.listKind === "enum") inner = enumLabel(field.enum);
|
|
321
|
-
else inner = shortName(field.message.typeName);
|
|
322
|
-
return `repeated ${inner}`;
|
|
153
|
+
/**
|
|
154
|
+
* Common arguments for all CLI commands
|
|
155
|
+
*
|
|
156
|
+
* NOTE: --env-file and --env-file-if-exists collide with Node.js flags due to a bug
|
|
157
|
+
* (https://github.com/nodejs/node/issues/54232). Node.js parses these even after the
|
|
158
|
+
* script path, causing warnings (twice due to tsx loader).
|
|
159
|
+
*/
|
|
160
|
+
const commonArgs = {
|
|
161
|
+
"env-file": arg(z.string().optional(), {
|
|
162
|
+
alias: "e",
|
|
163
|
+
description: "Path to the environment file (error if not found)",
|
|
164
|
+
completion: {
|
|
165
|
+
type: "file",
|
|
166
|
+
matcher: [".env.*", ".env"]
|
|
167
|
+
}
|
|
168
|
+
}),
|
|
169
|
+
"env-file-if-exists": arg(z.string().optional(), {
|
|
170
|
+
description: "Path to the environment file (ignored if not found)",
|
|
171
|
+
completion: {
|
|
172
|
+
type: "file",
|
|
173
|
+
matcher: [".env.*", ".env"]
|
|
174
|
+
},
|
|
175
|
+
effect: (_value, { args }) => {
|
|
176
|
+
loadEnvFiles(args["env-file"], args["env-file-if-exists"]);
|
|
177
|
+
}
|
|
178
|
+
}),
|
|
179
|
+
verbose: arg(z.boolean().default(false), {
|
|
180
|
+
description: "Enable verbose logging",
|
|
181
|
+
effect: (value) => {
|
|
182
|
+
verboseMode = value;
|
|
323
183
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
return `map<${keyType}, ${valType}>`;
|
|
184
|
+
}),
|
|
185
|
+
json: arg(z.boolean().default(false), {
|
|
186
|
+
alias: "j",
|
|
187
|
+
description: "Output as JSON",
|
|
188
|
+
effect: (value) => {
|
|
189
|
+
logger.jsonMode = value;
|
|
331
190
|
}
|
|
332
|
-
|
|
191
|
+
})
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Arguments for commands that require workspace context
|
|
195
|
+
*/
|
|
196
|
+
const workspaceArgs = {
|
|
197
|
+
"workspace-id": arg(z.string().optional(), {
|
|
198
|
+
alias: "w",
|
|
199
|
+
description: "Workspace ID",
|
|
200
|
+
env: "TAILOR_PLATFORM_WORKSPACE_ID",
|
|
201
|
+
completion: { type: "none" }
|
|
202
|
+
}),
|
|
203
|
+
profile: arg(z.string().optional(), {
|
|
204
|
+
alias: "p",
|
|
205
|
+
description: "Workspace profile",
|
|
206
|
+
env: "TAILOR_PLATFORM_PROFILE",
|
|
207
|
+
completion: { type: "none" }
|
|
208
|
+
})
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Shared config arg for commands that accept a config file path
|
|
212
|
+
*/
|
|
213
|
+
const configArg = { config: arg(z.string().default("tailor.config.ts"), {
|
|
214
|
+
alias: "c",
|
|
215
|
+
description: "Path to Tailor config file",
|
|
216
|
+
env: "TAILOR_CONFIG_PATH",
|
|
217
|
+
completion: {
|
|
218
|
+
type: "file",
|
|
219
|
+
extensions: ["ts"]
|
|
333
220
|
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
221
|
+
}) };
|
|
222
|
+
/**
|
|
223
|
+
* Shared config arg for commands that accept one or more comma-separated config file paths
|
|
224
|
+
*/
|
|
225
|
+
const multiConfigArg = { config: arg(z.string().default("tailor.config.ts"), {
|
|
226
|
+
alias: "c",
|
|
227
|
+
description: "Path to SDK config file. Use comma-separated paths to deploy multiple apps together.",
|
|
228
|
+
env: "TAILOR_PLATFORM_SDK_CONFIG_PATH",
|
|
229
|
+
completion: {
|
|
230
|
+
type: "file",
|
|
231
|
+
extensions: ["ts"]
|
|
232
|
+
}
|
|
233
|
+
}) };
|
|
234
|
+
/**
|
|
235
|
+
* Arguments for commands that interact with deployed resources (includes config)
|
|
236
|
+
*/
|
|
237
|
+
const deploymentArgs = {
|
|
238
|
+
...workspaceArgs,
|
|
239
|
+
...configArg
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Arguments for commands that require confirmation
|
|
243
|
+
*/
|
|
244
|
+
const confirmationArgs = { yes: arg(z.boolean().default(false), {
|
|
245
|
+
alias: "y",
|
|
246
|
+
description: "Skip confirmation prompts"
|
|
247
|
+
}) };
|
|
248
|
+
/**
|
|
249
|
+
* Arguments for commands that require organization context
|
|
250
|
+
*/
|
|
251
|
+
const organizationArgs = { "organization-id": arg(z.string(), {
|
|
252
|
+
alias: "o",
|
|
253
|
+
description: "Organization ID",
|
|
254
|
+
env: "TAILOR_PLATFORM_ORGANIZATION_ID",
|
|
255
|
+
completion: { type: "none" }
|
|
256
|
+
}) };
|
|
257
|
+
/**
|
|
258
|
+
* Arguments for list commands that accept `--order` / `--limit`. Sort
|
|
259
|
+
* order defaults to `desc` (newest first) because most callers want the
|
|
260
|
+
* latest items; pass `--order asc` to opt in to ascending order. The
|
|
261
|
+
* limit is unbounded by default so existing invocations keep returning
|
|
262
|
+
* every item; pass `--limit N` to cap the result size.
|
|
263
|
+
* @param defaultOrder - Default value for `--order` (defaults to `"desc"`)
|
|
264
|
+
* @returns Argument shape suitable for spreading into a command schema
|
|
265
|
+
*/
|
|
266
|
+
const paginationArgs = (defaultOrder = "desc") => ({
|
|
267
|
+
order: arg(orderArg.default(defaultOrder), { description: "Sort order (asc or desc)" }),
|
|
268
|
+
limit: arg(nonNegativeIntArg.optional(), {
|
|
269
|
+
alias: "l",
|
|
270
|
+
description: "Maximum number of items to return (0 or omit: unlimited)"
|
|
271
|
+
})
|
|
272
|
+
});
|
|
273
|
+
/**
|
|
274
|
+
* Arguments for time-series log list commands. Defaults to newest-first
|
|
275
|
+
* (`desc`) and a 50-item cap so that listing stays responsive on busy
|
|
276
|
+
* workspaces. Pass `--limit 0` to disable the cap and fetch all entries.
|
|
277
|
+
*/
|
|
278
|
+
const pagedLogArgs = {
|
|
279
|
+
order: arg(orderArg.default("desc"), { description: "Sort order (asc or desc)" }),
|
|
280
|
+
limit: arg(nonNegativeIntArg.default(50), {
|
|
281
|
+
alias: "l",
|
|
282
|
+
description: "Maximum number of items to return (0: unlimited)"
|
|
283
|
+
})
|
|
284
|
+
};
|
|
285
|
+
/**
|
|
286
|
+
* Arguments for commands that require folder context
|
|
287
|
+
*/
|
|
288
|
+
const folderArgs = { "folder-id": arg(z.string(), {
|
|
289
|
+
alias: "f",
|
|
290
|
+
description: "Folder ID",
|
|
291
|
+
env: "TAILOR_PLATFORM_FOLDER_ID",
|
|
292
|
+
completion: { type: "none" }
|
|
293
|
+
}) };
|
|
294
|
+
let verboseMode = false;
|
|
295
|
+
/**
|
|
296
|
+
* Returns whether verbose mode is enabled.
|
|
297
|
+
* Used by the global cleanup handler which doesn't have access to parsed args.
|
|
298
|
+
* @returns Whether verbose mode is enabled
|
|
299
|
+
*/
|
|
300
|
+
function isVerbose() {
|
|
301
|
+
return verboseMode;
|
|
397
302
|
}
|
|
398
303
|
|
|
399
304
|
//#endregion
|
|
400
|
-
//#region src/cli/
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
desc: "Show fields of GetApplicationRequest."
|
|
408
|
-
}, {
|
|
409
|
-
cmd: "CreateExecutorExecutor",
|
|
410
|
-
desc: "Inspect a deeply nested input with `(oneof config)` annotations."
|
|
411
|
-
}],
|
|
412
|
-
args: z.strictObject({ endpoint: arg(z.string(), {
|
|
413
|
-
positional: true,
|
|
414
|
-
description: "API endpoint to inspect (e.g., 'GetApplication' or 'tailor.v1.OperatorService/GetApplication').",
|
|
415
|
-
completion: { custom: { choices: listMethodNames() } }
|
|
416
|
-
}) }),
|
|
417
|
-
run: (args) => {
|
|
418
|
-
const methodName = extractMethodName(args.endpoint);
|
|
419
|
-
const method = getMethodDescriptor(methodName);
|
|
420
|
-
if (!method) throw createCLIError({
|
|
421
|
-
message: `unknown method: ${methodName}`,
|
|
422
|
-
suggestion: "Run `tailor api list` to see available methods.",
|
|
423
|
-
command: "api inspect"
|
|
424
|
-
});
|
|
425
|
-
if (logger.jsonMode) logger.out(renderInspectJson(method));
|
|
426
|
-
else logger.out(renderInspectText(method));
|
|
427
|
-
}
|
|
428
|
-
});
|
|
305
|
+
//#region src/cli/shared/command.ts
|
|
306
|
+
/**
|
|
307
|
+
* defineCommand with global args type (CommonArgsType).
|
|
308
|
+
* Use this for leaf commands with `run` to get type-safe access to global args.
|
|
309
|
+
* Parent commands with only `subCommands` can use `defineCommand` from politty directly.
|
|
310
|
+
*/
|
|
311
|
+
const defineAppCommand = createDefineCommand();
|
|
429
312
|
|
|
430
313
|
//#endregion
|
|
431
|
-
//#region src/
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
314
|
+
//#region src/parser/app-config/schema.ts
|
|
315
|
+
const envValueSchema = z.union([
|
|
316
|
+
z.string(),
|
|
317
|
+
z.number(),
|
|
318
|
+
z.boolean()
|
|
319
|
+
]);
|
|
320
|
+
const LogLevelSchema = z.enum(LOG_LEVELS);
|
|
321
|
+
const logLevelSchema = z.string().refine((value) => LogLevelSchema.safeParse(value.trim().toUpperCase()).success, { message: `'logLevel' must be one of: ${LOG_LEVELS.join(", ")}.` });
|
|
322
|
+
/**
|
|
323
|
+
* Structural validation schema for `defineConfig({...})`. Validates only
|
|
324
|
+
* top-level fields with platform-side constraints (notably `id`); fields
|
|
325
|
+
* that carry SDK builder objects (`auth`, `idp`, `db`, ...) are accepted
|
|
326
|
+
* as opaque values, since their internal shapes are validated by their
|
|
327
|
+
* own factory functions and parser-level schemas.
|
|
328
|
+
*
|
|
329
|
+
* The `id` is auto-managed by `deploy` and stored as a plain UUID. A
|
|
330
|
+
* label-compatible prefix is added at the metadata boundary, so user-facing
|
|
331
|
+
* configs only need to carry a UUID.
|
|
332
|
+
*/
|
|
333
|
+
const AppConfigSchema = z.strictObject({
|
|
334
|
+
id: z.uuid({ message: "'id' must be a UUID." }).optional(),
|
|
335
|
+
name: z.string().min(1, { message: "'name' must be a non-empty string." }),
|
|
336
|
+
env: z.record(z.string(), envValueSchema).optional(),
|
|
337
|
+
cors: z.array(z.string()).optional(),
|
|
338
|
+
allowedIpAddresses: z.array(z.string()).optional(),
|
|
339
|
+
disableIntrospection: z.boolean().optional(),
|
|
340
|
+
inlineSourcemap: z.boolean().optional(),
|
|
341
|
+
logLevel: logLevelSchema.optional(),
|
|
342
|
+
db: z.unknown().optional(),
|
|
343
|
+
resolver: z.unknown().optional(),
|
|
344
|
+
idp: z.unknown().optional(),
|
|
345
|
+
auth: z.unknown().optional(),
|
|
346
|
+
executor: z.unknown().optional(),
|
|
347
|
+
workflow: z.unknown().optional(),
|
|
348
|
+
httpAdapter: z.unknown().optional(),
|
|
349
|
+
staticWebsites: z.unknown().optional(),
|
|
350
|
+
aiGateways: z.unknown().optional(),
|
|
351
|
+
secrets: z.unknown().optional()
|
|
442
352
|
});
|
|
443
353
|
|
|
444
354
|
//#endregion
|
|
445
|
-
//#region src/
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
355
|
+
//#region src/parser/plugin-config/schema.ts
|
|
356
|
+
const PluginConfigSchema = z.looseObject({
|
|
357
|
+
id: z.string(),
|
|
358
|
+
description: z.string(),
|
|
359
|
+
importPath: z.string().optional(),
|
|
360
|
+
pluginConfig: z.unknown().optional(),
|
|
361
|
+
typeConfigRequired: z.union([z.boolean(), functionSchema]).optional(),
|
|
362
|
+
onTypeLoaded: functionSchema.optional(),
|
|
363
|
+
onNamespaceLoaded: functionSchema.optional(),
|
|
364
|
+
onTailorDBReady: functionSchema.optional(),
|
|
365
|
+
onResolverReady: functionSchema.optional(),
|
|
366
|
+
onExecutorReady: functionSchema.optional()
|
|
367
|
+
}).refine((p) => {
|
|
368
|
+
return !(p.onTypeLoaded || p.onNamespaceLoaded) || !!p.importPath;
|
|
369
|
+
}, { message: "importPath is required when plugin has definition-time hooks (onTypeLoaded/onNamespaceLoaded)" }).transform((plugin) => plugin);
|
|
370
|
+
|
|
371
|
+
//#endregion
|
|
372
|
+
//#region src/cli/shared/token-store.ts
|
|
373
|
+
const SERVICE_NAME = "tailor-platform-cli";
|
|
374
|
+
let entryClass;
|
|
375
|
+
async function getEntryClass() {
|
|
376
|
+
if (entryClass !== void 0) return entryClass;
|
|
377
|
+
try {
|
|
378
|
+
const mod = await import("@napi-rs/keyring");
|
|
379
|
+
const probe = new mod.Entry(SERVICE_NAME, "__probe__");
|
|
380
|
+
probe.setPassword("probe");
|
|
381
|
+
probe.deletePassword();
|
|
382
|
+
entryClass = mod.Entry;
|
|
383
|
+
} catch {
|
|
384
|
+
logger.warn("System keyring is not available. Tokens will be stored in the config file. Set TAILOR_PLATFORM_TOKEN environment variable for CI environments.");
|
|
385
|
+
entryClass = false;
|
|
461
386
|
}
|
|
387
|
+
return entryClass;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Check whether the OS keyring is available and functional.
|
|
391
|
+
* @returns true if keyring is available
|
|
392
|
+
*/
|
|
393
|
+
async function isKeyringAvailable() {
|
|
394
|
+
return await getEntryClass() !== false;
|
|
462
395
|
}
|
|
463
396
|
/**
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
* @param body - Raw body string
|
|
468
|
-
* @returns Parsed object or undefined
|
|
397
|
+
* Load tokens from the OS keyring for a given account.
|
|
398
|
+
* @param account - User identifier (e.g. email or client ID)
|
|
399
|
+
* @returns Token data or undefined if not found or keyring unavailable
|
|
469
400
|
*/
|
|
470
|
-
function
|
|
471
|
-
|
|
401
|
+
async function loadKeyringTokens(account) {
|
|
402
|
+
const Entry = await getEntryClass();
|
|
403
|
+
if (!Entry) return void 0;
|
|
472
404
|
try {
|
|
473
|
-
|
|
405
|
+
const raw = new Entry(SERVICE_NAME, account).getPassword();
|
|
406
|
+
if (raw === null) return void 0;
|
|
407
|
+
return JSON.parse(raw);
|
|
474
408
|
} catch {
|
|
475
409
|
return;
|
|
476
410
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
*
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
"
|
|
546
|
-
|
|
547
|
-
"prototype"
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Save tokens to the OS keyring for a given account.
|
|
414
|
+
* @param account - User identifier (e.g. email or client ID)
|
|
415
|
+
* @param tokens - Token data to store
|
|
416
|
+
*/
|
|
417
|
+
async function saveKeyringTokens(account, tokens) {
|
|
418
|
+
const Entry = await getEntryClass();
|
|
419
|
+
if (!Entry) return;
|
|
420
|
+
new Entry(SERVICE_NAME, account).setPassword(JSON.stringify(tokens));
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Delete tokens from the OS keyring for a given account.
|
|
424
|
+
* @param account - User identifier (e.g. email or client ID)
|
|
425
|
+
*/
|
|
426
|
+
async function deleteKeyringTokens(account) {
|
|
427
|
+
const Entry = await getEntryClass();
|
|
428
|
+
if (!Entry) return;
|
|
429
|
+
try {
|
|
430
|
+
new Entry(SERVICE_NAME, account).deletePassword();
|
|
431
|
+
} catch {}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/cli/shared/context.ts
|
|
436
|
+
const pfProfileSchema = z.object({
|
|
437
|
+
user: z.string(),
|
|
438
|
+
workspace_id: z.string(),
|
|
439
|
+
readonly: z.boolean().optional(),
|
|
440
|
+
machine_user: z.string().optional(),
|
|
441
|
+
machine_user_override: z.enum(["allow", "deny"]).optional(),
|
|
442
|
+
platform_url: z.url().optional(),
|
|
443
|
+
oauth2_client_id: z.string().optional(),
|
|
444
|
+
console_url: z.url().optional()
|
|
445
|
+
});
|
|
446
|
+
const pfUserSchemaV1 = z.object({
|
|
447
|
+
access_token: z.string(),
|
|
448
|
+
refresh_token: z.string().optional(),
|
|
449
|
+
token_expires_at: z.string()
|
|
450
|
+
});
|
|
451
|
+
const pfUserKeyringSchema = z.object({
|
|
452
|
+
storage: z.literal("keyring"),
|
|
453
|
+
token_expires_at: z.string()
|
|
454
|
+
});
|
|
455
|
+
const pfUserFileSchema = z.object({
|
|
456
|
+
storage: z.literal("file"),
|
|
457
|
+
token_expires_at: z.string(),
|
|
458
|
+
access_token: z.string(),
|
|
459
|
+
refresh_token: z.string().optional()
|
|
460
|
+
});
|
|
461
|
+
const pfUserSchemaV2 = z.discriminatedUnion("storage", [pfUserKeyringSchema, pfUserFileSchema]);
|
|
462
|
+
const pfUserKeyringSchemaV3 = pfUserKeyringSchema.extend({ email: z.string().optional() });
|
|
463
|
+
const pfUserFileSchemaV3 = pfUserFileSchema.extend({ email: z.string().optional() });
|
|
464
|
+
const pfUserSchemaV3 = z.discriminatedUnion("storage", [pfUserKeyringSchemaV3, pfUserFileSchemaV3]);
|
|
465
|
+
const pfConfigSchemaV1 = z.object({
|
|
466
|
+
version: z.literal(1),
|
|
467
|
+
users: z.partialRecord(z.string(), pfUserSchemaV1),
|
|
468
|
+
profiles: z.partialRecord(z.string(), pfProfileSchema),
|
|
469
|
+
current_user: z.string().nullable()
|
|
470
|
+
});
|
|
471
|
+
const V2_CONFIG_VERSION = 2;
|
|
472
|
+
const LATEST_CONFIG_VERSION = 3;
|
|
473
|
+
const V2_MIN_SDK_VERSION = "1.29.0";
|
|
474
|
+
const V3_MIN_SDK_VERSION = "2.0.0";
|
|
475
|
+
const semverSchema = z.templateLiteral([
|
|
476
|
+
z.number().int(),
|
|
477
|
+
".",
|
|
478
|
+
z.number().int(),
|
|
479
|
+
".",
|
|
480
|
+
z.number().int()
|
|
548
481
|
]);
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
482
|
+
const pfConfigSchemaV2 = z.object({
|
|
483
|
+
version: z.literal(V2_CONFIG_VERSION),
|
|
484
|
+
min_sdk_version: semverSchema,
|
|
485
|
+
latest_version: z.number().int().optional(),
|
|
486
|
+
latest_min_sdk_version: semverSchema.optional(),
|
|
487
|
+
users: z.partialRecord(z.string(), pfUserSchemaV2),
|
|
488
|
+
profiles: z.partialRecord(z.string(), pfProfileSchema),
|
|
489
|
+
current_user: z.string().nullable()
|
|
490
|
+
});
|
|
491
|
+
const pfConfigSchemaV3 = z.object({
|
|
492
|
+
version: z.literal(LATEST_CONFIG_VERSION),
|
|
493
|
+
min_sdk_version: semverSchema,
|
|
494
|
+
latest_version: z.number().int().optional(),
|
|
495
|
+
latest_min_sdk_version: semverSchema.optional(),
|
|
496
|
+
users: z.partialRecord(z.string(), pfUserSchemaV3),
|
|
497
|
+
profiles: z.partialRecord(z.string(), pfProfileSchema),
|
|
498
|
+
current_user: z.string().nullable()
|
|
499
|
+
});
|
|
500
|
+
function platformConfigPath() {
|
|
501
|
+
if (!xdgConfig) throw new Error("User home directory not found");
|
|
502
|
+
return path.join(xdgConfig, "tailor-platform", "config.yaml");
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Convert stored profile platform fields to platform client settings.
|
|
506
|
+
* @param profile - Profile platform settings
|
|
507
|
+
* @returns Platform client settings
|
|
508
|
+
*/
|
|
509
|
+
function platformConfigFromProfile(profile) {
|
|
510
|
+
const config = {
|
|
511
|
+
...profile?.platform_url ? { platformUrl: profile.platform_url } : {},
|
|
512
|
+
...profile?.oauth2_client_id ? { oauth2ClientId: profile.oauth2_client_id } : {},
|
|
513
|
+
...profile?.console_url ? { consoleUrl: profile.console_url } : {}
|
|
514
|
+
};
|
|
515
|
+
return Object.keys(config).length > 0 ? config : void 0;
|
|
516
|
+
}
|
|
517
|
+
function platformUserKey(user, config) {
|
|
518
|
+
const platformUrl = getPlatformBaseUrl(config);
|
|
519
|
+
if (platformUrl === normalizeBaseUrl("https://api.tailor.tech")) return user;
|
|
520
|
+
return `${platformUrl}|${user}`;
|
|
521
|
+
}
|
|
522
|
+
function userFromPlatformUserKey(userKey, config) {
|
|
523
|
+
const platformPrefix = `${getPlatformBaseUrl(config)}|`;
|
|
524
|
+
return userKey.startsWith(platformPrefix) ? userKey.slice(platformPrefix.length) : userKey;
|
|
525
|
+
}
|
|
526
|
+
function canUseLegacyUserKey(platformUrl) {
|
|
527
|
+
try {
|
|
528
|
+
return getPlatformBaseUrl() === platformUrl;
|
|
529
|
+
} catch {
|
|
530
|
+
return false;
|
|
565
531
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
return
|
|
532
|
+
}
|
|
533
|
+
function findUserByEmail(users, email, platformConfig) {
|
|
534
|
+
const platformUrl = getPlatformBaseUrl(platformConfig);
|
|
535
|
+
const platformPrefix = `${platformUrl}|`;
|
|
536
|
+
const defaultPlatform = platformUrl === normalizeBaseUrl(defaultPlatformBaseUrl);
|
|
537
|
+
return Object.entries(users).find(([key, entry]) => {
|
|
538
|
+
if (entry?.email !== email) return false;
|
|
539
|
+
return defaultPlatform ? !key.includes("|") : key.startsWith(platformPrefix);
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
function findUserEntry(config, user, platformConfig, opts = {}) {
|
|
543
|
+
const userKey = platformUserKey(user, platformConfig);
|
|
544
|
+
const userEntry = config.users[userKey];
|
|
545
|
+
if (userEntry) return {
|
|
546
|
+
userKey,
|
|
547
|
+
userEntry
|
|
548
|
+
};
|
|
549
|
+
const emailMatch = findUserByEmail(config.users, user, platformConfig);
|
|
550
|
+
if (emailMatch?.[1]) return {
|
|
551
|
+
userKey: emailMatch[0],
|
|
552
|
+
userEntry: emailMatch[1]
|
|
553
|
+
};
|
|
554
|
+
const platformUrl = getPlatformBaseUrl(platformConfig);
|
|
555
|
+
if (userKey !== user && (opts.allowLegacyUserKey === false || !canUseLegacyUserKey(platformUrl))) return {
|
|
556
|
+
userKey,
|
|
557
|
+
userEntry
|
|
558
|
+
};
|
|
559
|
+
const legacyEntry = config.users[user];
|
|
560
|
+
return legacyEntry ? {
|
|
561
|
+
userKey: user,
|
|
562
|
+
userEntry: legacyEntry
|
|
563
|
+
} : {
|
|
564
|
+
userKey,
|
|
565
|
+
userEntry
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Resolve the config user key that would be used for a user on the selected platform.
|
|
570
|
+
* @param config - Platform config
|
|
571
|
+
* @param user - User name
|
|
572
|
+
* @param platformConfig - Optional platform connection settings
|
|
573
|
+
* @param opts - Token lookup options
|
|
574
|
+
* @returns Resolved config user key
|
|
575
|
+
*/
|
|
576
|
+
function resolveUserTokenKey(config, user, platformConfig, opts) {
|
|
577
|
+
return findUserEntry(config, user, platformConfig, opts).userKey;
|
|
578
|
+
}
|
|
579
|
+
function resolveConfigUser(config, user, platformConfig, opts) {
|
|
580
|
+
const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
|
|
581
|
+
return userEntry ? userFromPlatformUserKey(userKey, platformConfig) : void 0;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Check whether tokens are registered for a user on the selected platform.
|
|
585
|
+
* @param config - Platform config
|
|
586
|
+
* @param user - User name
|
|
587
|
+
* @param platformConfig - Optional platform connection settings
|
|
588
|
+
* @param opts - Token lookup options
|
|
589
|
+
* @returns True when the user has a registered token entry
|
|
590
|
+
*/
|
|
591
|
+
function hasUserTokenEntry(config, user, platformConfig, opts) {
|
|
592
|
+
return findUserEntry(config, user, platformConfig, opts).userEntry !== void 0;
|
|
593
|
+
}
|
|
594
|
+
function hasUserKeyForName(users, user) {
|
|
595
|
+
return users[user] !== void 0 || Object.keys(users).some((key) => key.endsWith(`|${user}`));
|
|
596
|
+
}
|
|
597
|
+
function hasUserEmailEntry(users, user) {
|
|
598
|
+
return Object.values(users).some((entry) => entry?.email === user);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Check whether any platform has tokens registered for a user.
|
|
602
|
+
* @param config - Platform config
|
|
603
|
+
* @param user - User name
|
|
604
|
+
* @returns True when the user has a token entry for any platform
|
|
605
|
+
*/
|
|
606
|
+
function hasAnyUserTokenEntry(config, user) {
|
|
607
|
+
return hasUserKeyForName(config.users, user) || hasUserEmailEntry(config.users, user);
|
|
608
|
+
}
|
|
609
|
+
function hasCurrentUserEntry(users, currentUser) {
|
|
610
|
+
return hasUserKeyForName(users, currentUser);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Migrate a v1 config to v2.
|
|
614
|
+
* Tokens are kept in the config file (storage: "file") during migration.
|
|
615
|
+
* They will be moved to the OS keyring on next login or token refresh.
|
|
616
|
+
* @param v1Config - v1 configuration to migrate
|
|
617
|
+
* @returns Migrated v2 configuration
|
|
618
|
+
*/
|
|
619
|
+
function migrateV1ToV2(v1Config) {
|
|
620
|
+
const users = {};
|
|
621
|
+
for (const [name, v1User] of Object.entries(v1Config.users)) {
|
|
622
|
+
if (!v1User) continue;
|
|
623
|
+
users[name] = {
|
|
624
|
+
access_token: v1User.access_token,
|
|
625
|
+
refresh_token: v1User.refresh_token,
|
|
626
|
+
token_expires_at: v1User.token_expires_at,
|
|
627
|
+
storage: "file"
|
|
628
|
+
};
|
|
573
629
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
630
|
+
return {
|
|
631
|
+
version: V2_CONFIG_VERSION,
|
|
632
|
+
min_sdk_version: V2_MIN_SDK_VERSION,
|
|
633
|
+
users,
|
|
634
|
+
profiles: v1Config.profiles,
|
|
635
|
+
current_user: v1Config.current_user
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function inferEmailFromUserId(user) {
|
|
639
|
+
return z.email().safeParse(user).success ? user : void 0;
|
|
640
|
+
}
|
|
641
|
+
function migrateV2ToV3(v2Config) {
|
|
642
|
+
const users = {};
|
|
643
|
+
for (const [user, entry] of Object.entries(v2Config.users)) {
|
|
644
|
+
if (!entry) continue;
|
|
645
|
+
const email = inferEmailFromUserId(user);
|
|
646
|
+
users[user] = {
|
|
647
|
+
...entry,
|
|
648
|
+
...email ? { email } : {}
|
|
649
|
+
};
|
|
581
650
|
}
|
|
582
651
|
return {
|
|
583
|
-
|
|
584
|
-
|
|
652
|
+
version: LATEST_CONFIG_VERSION,
|
|
653
|
+
min_sdk_version: V3_MIN_SDK_VERSION,
|
|
654
|
+
users,
|
|
655
|
+
profiles: v2Config.profiles,
|
|
656
|
+
current_user: v2Config.current_user
|
|
585
657
|
};
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
+
}
|
|
659
|
+
function migrateV1ToV3(v1Config) {
|
|
660
|
+
return migrateV2ToV3(migrateV1ToV2(v1Config));
|
|
661
|
+
}
|
|
662
|
+
function formatUnknownError(error) {
|
|
663
|
+
return error instanceof Error ? error.message : String(error);
|
|
664
|
+
}
|
|
665
|
+
async function trySaveTokensInKeyring(user, tokens) {
|
|
666
|
+
if (!await isKeyringAvailable()) return false;
|
|
667
|
+
try {
|
|
668
|
+
await saveKeyringTokens(user, tokens);
|
|
669
|
+
return true;
|
|
670
|
+
} catch (error) {
|
|
671
|
+
logger.warn(`System keyring failed to store credentials. Tokens will be stored in the config file. ${formatUnknownError(error)}`);
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
async function warnIfNewerConfigAvailable(config) {
|
|
676
|
+
if (!config.latest_min_sdk_version) return;
|
|
677
|
+
if (lt((await readPackageJson()).version ?? "0.0.0", config.latest_min_sdk_version)) logger.warn(multiline`
|
|
678
|
+
A newer config version (${String(config.latest_version)}) is available.
|
|
679
|
+
Please update your SDK to >= ${config.latest_min_sdk_version}: pnpm update @tailor-platform/sdk
|
|
680
|
+
`);
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Read Tailor Platform CLI configuration, migrating from v1 if necessary.
|
|
684
|
+
* @returns Parsed platform configuration
|
|
685
|
+
*/
|
|
686
|
+
async function readPlatformConfig() {
|
|
687
|
+
const configPath = platformConfigPath();
|
|
688
|
+
if (!fs$2.existsSync(configPath)) {
|
|
689
|
+
const config = {
|
|
690
|
+
version: LATEST_CONFIG_VERSION,
|
|
691
|
+
min_sdk_version: V3_MIN_SDK_VERSION,
|
|
692
|
+
users: {},
|
|
693
|
+
profiles: {},
|
|
694
|
+
current_user: null
|
|
695
|
+
};
|
|
696
|
+
writePlatformConfig(config);
|
|
697
|
+
return config;
|
|
698
|
+
}
|
|
699
|
+
const rawConfig = parseYAML(fs$2.readFileSync(configPath, "utf-8"));
|
|
700
|
+
tightenSecretFilePermissions(configPath);
|
|
701
|
+
const version = rawConfig != null && typeof rawConfig === "object" && "version" in rawConfig ? rawConfig.version : void 0;
|
|
702
|
+
if (typeof version === "number" && version > LATEST_CONFIG_VERSION) {
|
|
703
|
+
const minSdk = "min_sdk_version" in rawConfig ? String(rawConfig.min_sdk_version) : void 0;
|
|
704
|
+
const updateHint = minSdk ? `Please update your SDK to >= ${minSdk}: pnpm update @tailor-platform/sdk` : "Please update your SDK: pnpm update @tailor-platform/sdk";
|
|
705
|
+
throw new Error(multiline`
|
|
706
|
+
Config file uses version ${String(version)}, but this SDK only supports up to version ${String(LATEST_CONFIG_VERSION)}.
|
|
707
|
+
${updateHint}
|
|
708
|
+
`);
|
|
709
|
+
}
|
|
710
|
+
const v3Result = pfConfigSchemaV3.safeParse(rawConfig);
|
|
711
|
+
if (v3Result.success) {
|
|
712
|
+
await warnIfNewerConfigAvailable(v3Result.data);
|
|
713
|
+
return v3Result.data;
|
|
714
|
+
}
|
|
715
|
+
const v2Result = pfConfigSchemaV2.safeParse(rawConfig);
|
|
716
|
+
if (v2Result.success) {
|
|
717
|
+
await warnIfNewerConfigAvailable(v2Result.data);
|
|
718
|
+
return migrateV2ToV3(v2Result.data);
|
|
719
|
+
}
|
|
720
|
+
const v1Result = pfConfigSchemaV1.safeParse(rawConfig);
|
|
721
|
+
if (v1Result.success) return migrateV1ToV3(v1Result.data);
|
|
722
|
+
throw new Error(multiline`
|
|
723
|
+
Failed to parse config file at ${configPath}.
|
|
724
|
+
The file may be corrupted or created by an incompatible SDK version.
|
|
725
|
+
`);
|
|
726
|
+
}
|
|
727
|
+
function toV1ForDisk(config) {
|
|
728
|
+
const users = {};
|
|
729
|
+
for (const [name, entry] of Object.entries(config.users)) {
|
|
730
|
+
if (!entry || entry.storage === "keyring") continue;
|
|
731
|
+
users[name] = {
|
|
732
|
+
access_token: entry.access_token,
|
|
733
|
+
refresh_token: entry.refresh_token,
|
|
734
|
+
token_expires_at: entry.token_expires_at
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
const currentUser = config.current_user && hasCurrentUserEntry(users, config.current_user) ? config.current_user : null;
|
|
738
|
+
return {
|
|
739
|
+
version: 1,
|
|
740
|
+
users,
|
|
741
|
+
profiles: config.profiles,
|
|
742
|
+
current_user: currentUser
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function hasProfilePlatformSettings(config) {
|
|
746
|
+
return Object.values(config.profiles).some((profile) => profile?.platform_url !== void 0 || profile?.oauth2_client_id !== void 0 || profile?.console_url !== void 0);
|
|
747
|
+
}
|
|
748
|
+
function hasScopedUserKeys(config) {
|
|
749
|
+
return Object.keys(config.users).some((userKey) => userKey.includes("|"));
|
|
750
|
+
}
|
|
751
|
+
function hasUserEmailMetadata(config) {
|
|
752
|
+
return Object.values(config.users).some((user) => user != null && "email" in user && user.email !== void 0);
|
|
753
|
+
}
|
|
754
|
+
function toLatestForDisk(config) {
|
|
755
|
+
return {
|
|
756
|
+
...config.version === 1 ? migrateV1ToV2(config) : config,
|
|
757
|
+
version: LATEST_CONFIG_VERSION,
|
|
758
|
+
min_sdk_version: V3_MIN_SDK_VERSION
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Write Tailor Platform CLI configuration to disk.
|
|
763
|
+
* By default, file-backed configs without newer fields are converted to V1 for
|
|
764
|
+
* backward compatibility, so an older SDK can still read the file. Configs
|
|
765
|
+
* containing a keyring user are kept in V2 or later because the keyring storage
|
|
766
|
+
* variant is not representable in V1. Configs containing profile-level Platform
|
|
767
|
+
* settings, platform-scoped user tokens, canonical user IDs, or email metadata
|
|
768
|
+
* are written in the latest min-SDK-gated format because older SDKs would
|
|
769
|
+
* silently drop or misread those settings.
|
|
770
|
+
*
|
|
771
|
+
* The config file may contain access/refresh tokens when the OS keyring is
|
|
772
|
+
* unavailable, so it is written via {@link writeSecretFile} so other users
|
|
773
|
+
* on the host cannot read it.
|
|
774
|
+
* @param config - Platform configuration to write
|
|
775
|
+
*/
|
|
776
|
+
function writePlatformConfig(config) {
|
|
777
|
+
const configPath = platformConfigPath();
|
|
778
|
+
const hasKeyringUser = config.version !== 1 && Object.values(config.users).some((u) => u?.storage === "keyring");
|
|
779
|
+
writeSecretFile(configPath, stringifyYAML(config.version === LATEST_CONFIG_VERSION || hasProfilePlatformSettings(config) || hasScopedUserKeys(config) || hasUserEmailMetadata(config) ? toLatestForDisk(config) : config.version === V2_CONFIG_VERSION && !hasKeyringUser ? toV1ForDisk(config) : config));
|
|
780
|
+
}
|
|
781
|
+
function validateUUID(value, source) {
|
|
782
|
+
const result = z.uuid().safeParse(value);
|
|
783
|
+
if (!result.success) throw new Error(`Invalid value from ${source}: must be a valid UUID`);
|
|
784
|
+
return result.data;
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Load workspace ID from command options, environment variables, or platform config.
|
|
788
|
+
* In CLI context, env fallback is also handled by politty's arg env option.
|
|
789
|
+
* Priority: opts/workspaceId > env/workspaceId > opts/profile > error
|
|
790
|
+
* @param opts - Workspace and profile options
|
|
791
|
+
* @returns Resolved workspace ID
|
|
792
|
+
*/
|
|
793
|
+
async function loadWorkspaceId(opts) {
|
|
794
|
+
const workspaceId = await tryLoadWorkspaceId(opts);
|
|
795
|
+
if (workspaceId) return workspaceId;
|
|
796
|
+
throw new Error(multiline`
|
|
797
|
+
Workspace ID not found.
|
|
798
|
+
Please specify workspace ID via --workspace-id option or TAILOR_PLATFORM_WORKSPACE_ID environment variable.
|
|
799
|
+
`);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Load a workspace ID when one is explicitly configured.
|
|
803
|
+
* @param opts - Workspace and profile options
|
|
804
|
+
* @returns Resolved workspace ID, or undefined when no source is configured
|
|
805
|
+
*/
|
|
806
|
+
async function tryLoadWorkspaceId(opts) {
|
|
807
|
+
const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
808
|
+
if (opts?.workspaceId !== void 0) return validateUUID(opts.workspaceId, "--workspace-id option");
|
|
809
|
+
if (process.env.TAILOR_PLATFORM_WORKSPACE_ID) return validateUUID(process.env.TAILOR_PLATFORM_WORKSPACE_ID, "TAILOR_PLATFORM_WORKSPACE_ID environment variable");
|
|
810
|
+
if (profile) {
|
|
811
|
+
const wsId = (await readPlatformConfig()).profiles[profile]?.workspace_id;
|
|
812
|
+
if (!wsId) throw new Error(`Profile "${profile}" not found`);
|
|
813
|
+
return validateUUID(wsId, `profile "${profile}"`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Load machine user name from command options, environment variables, or platform config.
|
|
818
|
+
* In CLI context, env fallback is also handled by politty's arg env option.
|
|
819
|
+
* Priority: opts/machineUser > env/TAILOR_PLATFORM_MACHINE_USER_NAME > opts/profile (profile default) > undefined.
|
|
820
|
+
* An explicitly empty `opts.machineUser` is rejected with a CLIError (`MACHINE_USER_NAME_EMPTY`) rather than falling back to the env var or profile default.
|
|
821
|
+
* When the active profile has `machine_user_override: "deny"`, an explicit value that differs from the profile's machine user throws a CLIError with code `PROFILE_MACHINE_USER_OVERRIDE_DENIED`.
|
|
822
|
+
* @param opts - Machine user and profile options
|
|
823
|
+
* @returns Resolved machine user name, or undefined if not set
|
|
824
|
+
*/
|
|
825
|
+
async function loadMachineUserName(opts) {
|
|
826
|
+
if (opts?.machineUser === "") throw createCLIError({
|
|
827
|
+
code: "MACHINE_USER_NAME_EMPTY",
|
|
828
|
+
message: "Machine user name cannot be empty.",
|
|
829
|
+
suggestion: "Pass a non-empty machine user name, or omit the option to use the environment variable or profile default."
|
|
830
|
+
});
|
|
831
|
+
const envMachineUser = process.env.TAILOR_PLATFORM_MACHINE_USER_NAME || void 0;
|
|
832
|
+
const explicitMachineUser = opts?.machineUser ? {
|
|
833
|
+
source: opts.machineUserSource ?? "option",
|
|
834
|
+
value: opts.machineUser
|
|
835
|
+
} : envMachineUser ? {
|
|
836
|
+
source: "env",
|
|
837
|
+
value: envMachineUser
|
|
838
|
+
} : void 0;
|
|
839
|
+
const explicit = explicitMachineUser?.value;
|
|
840
|
+
const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
841
|
+
if (!profile) return explicit;
|
|
842
|
+
const entry = (await readPlatformConfig()).profiles[profile];
|
|
843
|
+
if (!entry) {
|
|
844
|
+
if (explicit) return explicit;
|
|
845
|
+
throw new Error(`Profile "${profile}" not found`);
|
|
846
|
+
}
|
|
847
|
+
if (entry.machine_user && entry.machine_user_override === "deny") {
|
|
848
|
+
if (explicit && explicit !== entry.machine_user) {
|
|
849
|
+
const details = explicitMachineUser.source === "env" ? `The machine user is being set to "${explicit}" via the TAILOR_PLATFORM_MACHINE_USER_NAME environment variable, which conflicts with this profile's pinned machine user "${entry.machine_user}".` : `This profile fixes the machine user to "${entry.machine_user}" for application-data commands.`;
|
|
850
|
+
throw createCLIError({
|
|
851
|
+
code: "PROFILE_MACHINE_USER_OVERRIDE_DENIED",
|
|
852
|
+
message: `Profile "${profile}" denies overriding the machine user.`,
|
|
853
|
+
details,
|
|
854
|
+
suggestion: `Omit the machine user option, unset TAILOR_PLATFORM_MACHINE_USER_NAME, or run 'tailor profile update ${profile} --machine-user-override allow'.`
|
|
855
|
+
});
|
|
658
856
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
857
|
+
return entry.machine_user;
|
|
858
|
+
}
|
|
859
|
+
return explicit || entry.machine_user;
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Load access token from environment variables, command options, or platform config.
|
|
863
|
+
* In CLI context, profile env fallback is also handled by politty's arg env option.
|
|
864
|
+
* Priority: env/TAILOR_PLATFORM_TOKEN > env/TAILOR_TOKEN (deprecated) > opts/profile > env/profile > config/currentUser > error
|
|
865
|
+
* @param opts - Profile options
|
|
866
|
+
* @returns Resolved access token
|
|
867
|
+
*/
|
|
868
|
+
async function loadAccessToken(opts) {
|
|
869
|
+
const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
870
|
+
const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
|
|
871
|
+
if (envToken && !process.env.TAILOR_PLATFORM_TOKEN) logger.warn("TAILOR_TOKEN is deprecated. Please use TAILOR_PLATFORM_TOKEN instead.");
|
|
872
|
+
if (envToken) {
|
|
873
|
+
rememberPlatformConfigForToken(envToken, await loadPlatformClientConfig({
|
|
874
|
+
profile,
|
|
875
|
+
allowMissingProfile: true
|
|
876
|
+
}));
|
|
877
|
+
return envToken;
|
|
878
|
+
}
|
|
879
|
+
const pfConfig = await readPlatformConfig();
|
|
880
|
+
const profileEntry = profile ? pfConfig.profiles[profile] : void 0;
|
|
881
|
+
if (profile && !profileEntry) throw new Error(`Profile "${profile}" not found`);
|
|
882
|
+
const user = profileEntry?.user ?? pfConfig.current_user;
|
|
883
|
+
if (!user) throw new Error(multiline`
|
|
884
|
+
Tailor Platform token not found.
|
|
885
|
+
Please specify token via TAILOR_PLATFORM_TOKEN environment variable or login using 'tailor login' command.
|
|
886
|
+
`);
|
|
887
|
+
return (await fetchLatestToken(pfConfig, user, profileEntry ? platformConfigFromProfile(profileEntry) : void 0)).accessToken;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Load platform connection settings from the active profile.
|
|
891
|
+
* @param opts - Profile options
|
|
892
|
+
* @returns Resolved platform connection settings, or undefined for the default environment
|
|
893
|
+
*/
|
|
894
|
+
async function loadPlatformClientConfig(opts) {
|
|
895
|
+
const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
896
|
+
if (!profile) return;
|
|
897
|
+
let pfConfig;
|
|
898
|
+
try {
|
|
899
|
+
pfConfig = await readPlatformConfig();
|
|
900
|
+
} catch (error) {
|
|
901
|
+
if (opts?.allowMissingProfile) return;
|
|
902
|
+
throw error;
|
|
903
|
+
}
|
|
904
|
+
const profileEntry = pfConfig.profiles[profile];
|
|
905
|
+
if (!profileEntry) {
|
|
906
|
+
if (opts?.allowMissingProfile) return;
|
|
907
|
+
throw new Error(`Profile "${profile}" not found`);
|
|
908
|
+
}
|
|
909
|
+
return platformConfigFromProfile(profileEntry);
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Load the Tailor Platform Console base URL from environment variables or the active profile.
|
|
913
|
+
* @param opts - Profile options
|
|
914
|
+
* @returns Resolved Console base URL
|
|
915
|
+
*/
|
|
916
|
+
async function loadConsoleBaseUrl(opts) {
|
|
917
|
+
return getConsoleBaseUrl(await loadPlatformClientConfig(opts));
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Resolve the actual token values for a user, reading from keyring or config as appropriate.
|
|
921
|
+
* @param userEntry - User entry from the config
|
|
922
|
+
* @param user - User identifier
|
|
923
|
+
* @param label - User-facing identifier used in error messages
|
|
924
|
+
* @returns Access token and optional refresh token
|
|
925
|
+
*/
|
|
926
|
+
async function resolveTokens(userEntry, user, label = user) {
|
|
927
|
+
if (userEntry.storage === "keyring") {
|
|
928
|
+
const tokens = await loadKeyringTokens(user);
|
|
929
|
+
if (!tokens) throw new Error(multiline`
|
|
930
|
+
Credentials not found in OS keyring for "${label}".
|
|
931
|
+
Please run 'tailor login' and try again.
|
|
932
|
+
`);
|
|
933
|
+
return tokens;
|
|
934
|
+
}
|
|
935
|
+
return {
|
|
936
|
+
accessToken: userEntry.access_token,
|
|
937
|
+
refreshToken: userEntry.refresh_token
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Save tokens for a user, writing to keyring by default when available.
|
|
942
|
+
* @param config - Platform config
|
|
943
|
+
* @param user - User identifier
|
|
944
|
+
* @param tokens - Token data to save
|
|
945
|
+
* @param tokens.accessToken - Access token to save
|
|
946
|
+
* @param tokens.refreshToken - Optional refresh token to save
|
|
947
|
+
* @param expiresAt - Token expiration date
|
|
948
|
+
* @param opts - Optional platform and user metadata
|
|
949
|
+
*/
|
|
950
|
+
async function saveUserTokens(config, user, tokens, expiresAt, opts = {}) {
|
|
951
|
+
const userKey = platformUserKey(user, opts.platformConfig);
|
|
952
|
+
const email = opts.email ?? config.users[userKey]?.email;
|
|
953
|
+
if (await trySaveTokensInKeyring(userKey, tokens)) config.users[userKey] = {
|
|
954
|
+
token_expires_at: expiresAt,
|
|
955
|
+
storage: "keyring",
|
|
956
|
+
...email ? { email } : {}
|
|
957
|
+
};
|
|
958
|
+
else {
|
|
959
|
+
if (config.users[userKey]?.storage === "keyring") await deleteKeyringTokens(userKey);
|
|
960
|
+
config.users[userKey] = {
|
|
961
|
+
access_token: tokens.accessToken,
|
|
962
|
+
refresh_token: tokens.refreshToken,
|
|
963
|
+
token_expires_at: expiresAt,
|
|
964
|
+
storage: "file",
|
|
965
|
+
...email ? { email } : {}
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Delete tokens for a user from keyring if applicable.
|
|
971
|
+
* @param config - Platform config
|
|
972
|
+
* @param user - User identifier
|
|
973
|
+
* @param platformConfig - Optional platform connection settings
|
|
974
|
+
* @param opts - Token lookup options
|
|
975
|
+
*/
|
|
976
|
+
async function deleteUserTokens(config, user, platformConfig, opts) {
|
|
977
|
+
const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
|
|
978
|
+
if (userEntry?.storage === "keyring") await deleteKeyringTokens(userKey);
|
|
979
|
+
delete config.users[userKey];
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Resolve stored tokens for a user on the selected platform.
|
|
983
|
+
* @param config - Platform config
|
|
984
|
+
* @param user - User name
|
|
985
|
+
* @param platformConfig - Optional platform connection settings
|
|
986
|
+
* @param opts - Token lookup options
|
|
987
|
+
* @returns Stored user entry and token values, or undefined when the user is not logged in
|
|
988
|
+
*/
|
|
989
|
+
async function loadStoredUserTokens(config, user, platformConfig, opts) {
|
|
990
|
+
const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
|
|
991
|
+
if (!userEntry) return void 0;
|
|
992
|
+
return {
|
|
993
|
+
userEntry,
|
|
994
|
+
...await resolveTokens(userEntry, userKey, user)
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
function updateUserReferences(config, fromUser, toUser) {
|
|
998
|
+
if (fromUser === toUser) return;
|
|
999
|
+
if (config.current_user === fromUser) config.current_user = toUser;
|
|
1000
|
+
for (const profile of Object.values(config.profiles)) if (profile?.user === fromUser) profile.user = toUser;
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Remove a legacy alias after a canonical user ID has been written.
|
|
1004
|
+
* @param config - Platform config
|
|
1005
|
+
* @param legacyUser - Previous user key
|
|
1006
|
+
* @param canonicalUser - Canonical user key
|
|
1007
|
+
* @param platformConfig - Platform settings for scoped token storage
|
|
1008
|
+
*/
|
|
1009
|
+
async function removeLegacyUserAlias(config, legacyUser, canonicalUser, platformConfig) {
|
|
1010
|
+
if (legacyUser === canonicalUser) return;
|
|
1011
|
+
updateUserReferences(config, legacyUser, canonicalUser);
|
|
1012
|
+
const canonicalKey = platformUserKey(canonicalUser, platformConfig);
|
|
1013
|
+
const legacyKeys = /* @__PURE__ */ new Set([legacyUser, platformUserKey(legacyUser, platformConfig)]);
|
|
1014
|
+
for (const legacyKey of legacyKeys) {
|
|
1015
|
+
if (legacyKey === canonicalKey) continue;
|
|
1016
|
+
if (config.users[legacyKey]?.storage === "keyring") await deleteKeyringTokens(legacyKey);
|
|
1017
|
+
delete config.users[legacyKey];
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
function shouldResolveSubjectOnRefresh(user, userEntry) {
|
|
1021
|
+
return Boolean(userEntry.email || inferEmailFromUserId(user));
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Fetch the latest access token, refreshing if necessary.
|
|
1025
|
+
* @param config - Platform config
|
|
1026
|
+
* @param user - User identifier
|
|
1027
|
+
* @param platformConfig - Optional platform connection settings
|
|
1028
|
+
* @returns Latest access token and the canonical user ID it is stored under
|
|
1029
|
+
* (the resolved subject when a legacy email key was migrated during refresh,
|
|
1030
|
+
* otherwise the matching config user)
|
|
1031
|
+
*/
|
|
1032
|
+
async function fetchLatestToken(config, user, platformConfig) {
|
|
1033
|
+
const { userKey: storedUser, userEntry } = findUserEntry(config, user, platformConfig);
|
|
1034
|
+
if (!userEntry) throw new Error(multiline`
|
|
1035
|
+
User "${user}" not found.
|
|
1036
|
+
Please verify your user name and login using 'tailor login' command.
|
|
1037
|
+
`);
|
|
1038
|
+
const storedConfigUser = userFromPlatformUserKey(storedUser, platformConfig);
|
|
1039
|
+
const tokens = await resolveTokens(userEntry, storedUser, user);
|
|
1040
|
+
if (new Date(userEntry.token_expires_at) > /* @__PURE__ */ new Date()) {
|
|
1041
|
+
rememberPlatformConfigForToken(tokens.accessToken, platformConfig);
|
|
1042
|
+
return {
|
|
1043
|
+
accessToken: tokens.accessToken,
|
|
1044
|
+
user: storedConfigUser
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
if (!tokens.refreshToken) throw new Error(multiline`
|
|
1048
|
+
Token expired.
|
|
1049
|
+
Please run 'tailor login' and try again.
|
|
1050
|
+
`);
|
|
1051
|
+
const client = initOAuth2Client(platformConfig);
|
|
1052
|
+
let resp;
|
|
1053
|
+
try {
|
|
1054
|
+
resp = await client.refreshToken({
|
|
1055
|
+
accessToken: tokens.accessToken,
|
|
1056
|
+
refreshToken: tokens.refreshToken,
|
|
1057
|
+
expiresAt: Date.parse(userEntry.token_expires_at)
|
|
1058
|
+
});
|
|
1059
|
+
} catch {
|
|
1060
|
+
throw new Error(multiline`
|
|
1061
|
+
Failed to refresh token. Your session may have expired.
|
|
1062
|
+
Please run 'tailor login' and try again.
|
|
1063
|
+
`);
|
|
1064
|
+
}
|
|
1065
|
+
const newExpiresAt = new Date(assertDefined(resp.expiresAt, "token refresh response missing expiresAt")).toISOString();
|
|
1066
|
+
let resolvedUser = storedConfigUser;
|
|
1067
|
+
const previousEmail = userEntry.email ?? inferEmailFromUserId(user) ?? inferEmailFromUserId(storedConfigUser);
|
|
1068
|
+
let email = previousEmail;
|
|
1069
|
+
if (shouldResolveSubjectOnRefresh(user, userEntry) || shouldResolveSubjectOnRefresh(storedConfigUser, userEntry)) try {
|
|
1070
|
+
const userInfo = await fetchUserInfo(resp.accessToken, platformConfig);
|
|
1071
|
+
resolvedUser = userInfo.sub;
|
|
1072
|
+
email = userInfo.email;
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
logger.debug(`Failed to resolve refreshed token user info: ${String(error)}`);
|
|
1075
|
+
}
|
|
1076
|
+
await saveUserTokens(config, resolvedUser, {
|
|
1077
|
+
accessToken: resp.accessToken,
|
|
1078
|
+
refreshToken: resp.refreshToken ?? void 0
|
|
1079
|
+
}, newExpiresAt, {
|
|
1080
|
+
platformConfig,
|
|
1081
|
+
email
|
|
1082
|
+
});
|
|
1083
|
+
await removeLegacyUserAlias(config, user, resolvedUser, platformConfig);
|
|
1084
|
+
if (storedUser !== platformUserKey(resolvedUser, platformConfig)) {
|
|
1085
|
+
if (config.users[storedUser]?.storage === "keyring") await deleteKeyringTokens(storedUser);
|
|
1086
|
+
delete config.users[storedUser];
|
|
1087
|
+
}
|
|
1088
|
+
if (previousEmail && email && previousEmail !== email) logger.info(`Updated local user email from "${previousEmail}" to "${email}".`);
|
|
1089
|
+
writePlatformConfig(config);
|
|
1090
|
+
rememberPlatformConfigForToken(resp.accessToken, platformConfig);
|
|
1091
|
+
return {
|
|
1092
|
+
accessToken: resp.accessToken,
|
|
1093
|
+
user: resolvedUser
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
const DEFAULT_CONFIG_FILENAME = "tailor.config.ts";
|
|
1097
|
+
/**
|
|
1098
|
+
* Load config path from command options, environment variables, or search parent directories.
|
|
1099
|
+
* In CLI context, env fallback is also handled by politty's arg env option.
|
|
1100
|
+
* Priority: opts/config > env/config > search parent directories
|
|
1101
|
+
* @param configPath - Optional explicit config path
|
|
1102
|
+
* @returns Resolved config path or undefined
|
|
1103
|
+
*/
|
|
1104
|
+
function loadConfigPath(configPath) {
|
|
1105
|
+
if (configPath) return configPath;
|
|
1106
|
+
if (process.env.TAILOR_CONFIG_PATH) return process.env.TAILOR_CONFIG_PATH;
|
|
1107
|
+
return findUpSync(DEFAULT_CONFIG_FILENAME);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
//#endregion
|
|
1111
|
+
//#region src/cli/shared/mock.ts
|
|
1112
|
+
/**
|
|
1113
|
+
* Install a stub `globalThis.tailordb` so that user code loaded by the CLI
|
|
1114
|
+
* (e.g. via `createGetDB` in `@tailor-platform/sdk/kysely`) can reference
|
|
1115
|
+
* `tailordb.Client` without hitting a `ReferenceError`. The CLI never
|
|
1116
|
+
* actually executes the user code paths that issue queries, so a no-op
|
|
1117
|
+
* client suffices.
|
|
1118
|
+
*
|
|
1119
|
+
* Exposed as a function (rather than a top-level statement) so that
|
|
1120
|
+
* `package.json#sideEffects` can keep the file marked side-effect-free
|
|
1121
|
+
* without bundlers eliminating the install step.
|
|
1122
|
+
*/
|
|
1123
|
+
function installCliTailordbStub() {
|
|
1124
|
+
globalThis.tailordb = { Client: class {
|
|
1125
|
+
constructor(_config) {}
|
|
1126
|
+
async connect() {}
|
|
1127
|
+
async end() {}
|
|
1128
|
+
async queryObject() {
|
|
1129
|
+
return {};
|
|
677
1130
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
1131
|
+
} };
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
//#endregion
|
|
1135
|
+
//#region src/cli/shared/config-loader.ts
|
|
1136
|
+
/**
|
|
1137
|
+
* Load Tailor configuration file and associated plugins.
|
|
1138
|
+
* @param configPath - Optional explicit config path
|
|
1139
|
+
* @param options - Optional module import behavior.
|
|
1140
|
+
* @returns Loaded config, plugins, and config path
|
|
1141
|
+
*/
|
|
1142
|
+
async function loadConfig(configPath, options = {}) {
|
|
1143
|
+
installCliTailordbStub();
|
|
1144
|
+
const foundPath = loadConfigPath(configPath);
|
|
1145
|
+
if (!foundPath) throw new Error("Configuration file not found: tailor.config.ts not found in current or parent directories");
|
|
1146
|
+
const resolvedPath = path.resolve(process.cwd(), foundPath);
|
|
1147
|
+
if (!fs$2.existsSync(resolvedPath)) throw new Error(`Configuration file not found: ${configPath}`);
|
|
1148
|
+
const configUrl = pathToFileURL(resolvedPath);
|
|
1149
|
+
if (options.importNonce) configUrl.searchParams.set("tailorImportNonce", options.importNonce);
|
|
1150
|
+
const configModule = await import(configUrl.href);
|
|
1151
|
+
if (!configModule || !configModule.default) throw new Error("Invalid Tailor config module: default export not found");
|
|
1152
|
+
const validated = AppConfigSchema.safeParse(configModule.default);
|
|
1153
|
+
if (!validated.success) {
|
|
1154
|
+
const issues = validated.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
1155
|
+
throw new Error(`Invalid Tailor config in ${resolvedPath}:\n${issues}`);
|
|
1156
|
+
}
|
|
1157
|
+
const allPlugins = [];
|
|
1158
|
+
for (const value of Object.values(configModule)) if (Array.isArray(value)) {
|
|
1159
|
+
const pluginParsed = value.reduce((acc, item) => {
|
|
1160
|
+
if (!acc.success) return acc;
|
|
1161
|
+
const result = PluginConfigSchema.safeParse(item);
|
|
1162
|
+
if (result.success) acc.items.push(result.data);
|
|
1163
|
+
else acc.success = false;
|
|
1164
|
+
return acc;
|
|
1165
|
+
}, {
|
|
1166
|
+
success: true,
|
|
1167
|
+
items: []
|
|
682
1168
|
});
|
|
683
|
-
|
|
1169
|
+
if (pluginParsed.success && pluginParsed.items.length > 0) allPlugins.push(...pluginParsed.items);
|
|
684
1170
|
}
|
|
685
|
-
|
|
1171
|
+
return {
|
|
1172
|
+
config: {
|
|
1173
|
+
...configModule.default,
|
|
1174
|
+
path: resolvedPath
|
|
1175
|
+
},
|
|
1176
|
+
plugins: allPlugins
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
//#endregion
|
|
1181
|
+
//#region src/cli/shared/readonly-guard.ts
|
|
1182
|
+
/**
|
|
1183
|
+
* Throw a CLIError if the active profile has `readonly: true`.
|
|
1184
|
+
*
|
|
1185
|
+
* Resolves the active profile in this order:
|
|
1186
|
+
* 1. `opts.profile` (CLI flag)
|
|
1187
|
+
* 2. `process.env.TAILOR_PLATFORM_PROFILE`
|
|
1188
|
+
*
|
|
1189
|
+
* If neither is set, no profile is in scope so the call is allowed. This is
|
|
1190
|
+
* intentional: `TAILOR_PLATFORM_TOKEN` direct access (CI / machine user) and
|
|
1191
|
+
* `--workspace-id` without a profile are out-of-band paths whose authorization
|
|
1192
|
+
* is governed by the bearer token itself, not by the local profile flag.
|
|
1193
|
+
*
|
|
1194
|
+
* If the resolved profile cannot be found in the config, this function returns
|
|
1195
|
+
* silently and lets downstream loaders surface the not-found error.
|
|
1196
|
+
* @param opts - Options
|
|
1197
|
+
* @param opts.profile - Optional explicit profile name from command args
|
|
1198
|
+
*/
|
|
1199
|
+
async function assertWritable(opts) {
|
|
1200
|
+
const profileName = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
1201
|
+
if (!profileName) return;
|
|
1202
|
+
const profile = (await readPlatformConfig()).profiles[profileName];
|
|
1203
|
+
if (!profile || profile.readonly !== true) return;
|
|
1204
|
+
throw createCLIError({
|
|
1205
|
+
code: "PROFILE_READONLY",
|
|
1206
|
+
message: `Profile "${profileName}" is read-only.`,
|
|
1207
|
+
details: "This profile blocks platform-state mutations (apply, create/update/delete, deploy, etc.). Application-data operations remain available because their permissions are governed by the machine user.",
|
|
1208
|
+
suggestion: `Use a different profile, unset TAILOR_PLATFORM_PROFILE, or run 'tailor profile update ${profileName} --permission write'.`
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
//#endregion
|
|
1213
|
+
//#region src/cli/commands/api/api-call.ts
|
|
1214
|
+
function hasEnvAccessToken() {
|
|
1215
|
+
const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
|
|
1216
|
+
return Boolean(envToken);
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Call Tailor Platform API endpoints directly.
|
|
1220
|
+
* If the endpoint doesn't contain "/", it defaults to `tailor.v1.OperatorService/{endpoint}`.
|
|
1221
|
+
* @param options - API call options (profile, endpoint, body)
|
|
1222
|
+
* @returns Response status and data
|
|
1223
|
+
*/
|
|
1224
|
+
async function apiCall(options) {
|
|
1225
|
+
const accessToken = await loadAccessToken({ profile: options.profile });
|
|
1226
|
+
let platformConfig;
|
|
1227
|
+
try {
|
|
1228
|
+
platformConfig = await loadPlatformClientConfig({ profile: options.profile });
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
if (!hasEnvAccessToken()) throw error;
|
|
1231
|
+
}
|
|
1232
|
+
let endpointPath;
|
|
1233
|
+
if (options.endpoint.includes("/")) endpointPath = options.endpoint;
|
|
1234
|
+
else endpointPath = `tailor.v1.OperatorService/${options.endpoint}`;
|
|
1235
|
+
const url = new URL(endpointPath, getPlatformBaseUrl(platformConfig));
|
|
1236
|
+
const response = await fetch(url.toString(), {
|
|
1237
|
+
method: "POST",
|
|
1238
|
+
headers: {
|
|
1239
|
+
"Content-Type": "application/json",
|
|
1240
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1241
|
+
"User-Agent": await userAgent()
|
|
1242
|
+
},
|
|
1243
|
+
body: options.body ?? "{}"
|
|
1244
|
+
});
|
|
1245
|
+
const data = await response.json();
|
|
1246
|
+
if (!response.ok) throw new Error(`API call failed (${response.status}): ${JSON.stringify(data)}`);
|
|
1247
|
+
return {
|
|
1248
|
+
status: response.status,
|
|
1249
|
+
data
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
686
1252
|
|
|
687
1253
|
//#endregion
|
|
688
1254
|
//#region src/cli/shared/prompt.ts
|
|
1255
|
+
function canPrompt() {
|
|
1256
|
+
return !isCI && process.stdin.isTTY === true && process.stdout.isTTY === true && !logger.jsonMode;
|
|
1257
|
+
}
|
|
689
1258
|
/**
|
|
690
1259
|
* Wraps a prompt function with CI guard and cancellation handling.
|
|
691
1260
|
* @param fn - A prompt function from `@inquirer/prompts`
|
|
1261
|
+
* @param unavailableMessage - Message used when interactive input is unavailable
|
|
692
1262
|
* @returns A wrapped function that throws in CI and exits on cancel
|
|
693
1263
|
*/
|
|
694
|
-
function withGuard(fn) {
|
|
1264
|
+
function withGuard(fn, unavailableMessage) {
|
|
695
1265
|
return async (...args) => {
|
|
696
|
-
if (
|
|
1266
|
+
if (!canPrompt()) throw new CIPromptError(unavailableMessage);
|
|
697
1267
|
try {
|
|
698
1268
|
return await fn(...args);
|
|
699
1269
|
} catch (error) {
|
|
@@ -703,9 +1273,10 @@ function withGuard(fn) {
|
|
|
703
1273
|
};
|
|
704
1274
|
}
|
|
705
1275
|
const prompt = {
|
|
706
|
-
confirm: withGuard(confirm),
|
|
1276
|
+
confirm: withGuard(confirm, "Interactive confirmations are not available in this environment. Use --yes to skip confirmation prompts, or provide the required options explicitly."),
|
|
707
1277
|
text: withGuard(input),
|
|
708
|
-
password: withGuard(password)
|
|
1278
|
+
password: withGuard(password),
|
|
1279
|
+
select: withGuard(select)
|
|
709
1280
|
};
|
|
710
1281
|
|
|
711
1282
|
//#endregion
|
|
@@ -750,7 +1321,7 @@ function createCacheStore(config) {
|
|
|
750
1321
|
}
|
|
751
1322
|
function loadManifest() {
|
|
752
1323
|
try {
|
|
753
|
-
const raw = fs$
|
|
1324
|
+
const raw = fs$2.readFileSync(manifestPath(), "utf-8");
|
|
754
1325
|
const result = cacheManifestSchema.safeParse(JSON.parse(raw));
|
|
755
1326
|
if (!result.success) {
|
|
756
1327
|
cachedManifest = void 0;
|
|
@@ -777,15 +1348,15 @@ function createCacheStore(config) {
|
|
|
777
1348
|
return cachedManifest;
|
|
778
1349
|
}
|
|
779
1350
|
function saveManifest(manifest) {
|
|
780
|
-
fs$
|
|
1351
|
+
fs$2.mkdirSync(config.cacheDir, { recursive: true });
|
|
781
1352
|
const target = manifestPath();
|
|
782
1353
|
const tmpFile = path.join(config.cacheDir, `.manifest.${process.pid}.tmp`);
|
|
783
1354
|
try {
|
|
784
|
-
fs$
|
|
785
|
-
fs$
|
|
1355
|
+
fs$2.writeFileSync(tmpFile, JSON.stringify(manifest, null, 2), "utf-8");
|
|
1356
|
+
fs$2.renameSync(tmpFile, target);
|
|
786
1357
|
} catch (e) {
|
|
787
1358
|
try {
|
|
788
|
-
fs$
|
|
1359
|
+
fs$2.rmSync(tmpFile, { force: true });
|
|
789
1360
|
} catch {}
|
|
790
1361
|
throw e;
|
|
791
1362
|
}
|
|
@@ -804,19 +1375,19 @@ function createCacheStore(config) {
|
|
|
804
1375
|
}
|
|
805
1376
|
function storeBundleContent(cacheKey, content) {
|
|
806
1377
|
const dir = bundlesDir();
|
|
807
|
-
fs$
|
|
808
|
-
fs$
|
|
1378
|
+
fs$2.mkdirSync(dir, { recursive: true });
|
|
1379
|
+
fs$2.writeFileSync(bundlePath(cacheKey), content, "utf-8");
|
|
809
1380
|
}
|
|
810
1381
|
function restoreBundleContent(cacheKey) {
|
|
811
1382
|
try {
|
|
812
|
-
return fs$
|
|
1383
|
+
return fs$2.readFileSync(bundlePath(cacheKey), "utf-8");
|
|
813
1384
|
} catch (e) {
|
|
814
1385
|
if (e.code === "ENOENT") return void 0;
|
|
815
1386
|
throw e;
|
|
816
1387
|
}
|
|
817
1388
|
}
|
|
818
1389
|
function clean() {
|
|
819
|
-
fs$
|
|
1390
|
+
fs$2.rmSync(config.cacheDir, {
|
|
820
1391
|
recursive: true,
|
|
821
1392
|
force: true
|
|
822
1393
|
});
|
|
@@ -910,7 +1481,7 @@ function extractAttributesFromConfig(config) {
|
|
|
910
1481
|
* @returns Generated type definition source
|
|
911
1482
|
*/
|
|
912
1483
|
function generateTypeDefinition(attributes, attributeList, env, machineUserNames, idpNames, connectionNames, aiGatewayNames) {
|
|
913
|
-
const attributeFields = attributes ? Object.entries(attributes).map(([key,
|
|
1484
|
+
const attributeFields = attributes ? Object.entries(attributes).map(([key, { type, optional }]) => ` ${key}${optional ? "?" : ""}: ${type};`).join("\n") : "";
|
|
914
1485
|
const attributesBody = !attributes || Object.keys(attributes).length === 0 ? "{}" : `{
|
|
915
1486
|
${attributeFields}
|
|
916
1487
|
}`;
|
|
@@ -973,12 +1544,15 @@ function collectAttributesFromConfig(config) {
|
|
|
973
1544
|
const inferAttributeType = (field) => {
|
|
974
1545
|
const type = field?.type;
|
|
975
1546
|
const metadata = field?.metadata;
|
|
976
|
-
if (!metadata) return "string";
|
|
1547
|
+
if (!metadata) return { type: "string" };
|
|
977
1548
|
let typeStr = "string";
|
|
978
1549
|
if (type === "boolean") typeStr = "boolean";
|
|
979
1550
|
else if (type === "enum" && metadata.allowedValues) typeStr = metadata.allowedValues.map((v) => `"${v.value}"`).join(" | ");
|
|
980
1551
|
if (metadata.array) typeStr = typeStr.includes(" | ") ? `(${typeStr})[]` : `${typeStr}[]`;
|
|
981
|
-
return
|
|
1552
|
+
return {
|
|
1553
|
+
type: typeStr,
|
|
1554
|
+
optional: metadata.required === false
|
|
1555
|
+
};
|
|
982
1556
|
};
|
|
983
1557
|
if ("userProfile" in auth) {
|
|
984
1558
|
const userProfile = auth.userProfile;
|
|
@@ -1057,8 +1631,8 @@ async function generateUserTypes(options) {
|
|
|
1057
1631
|
if (env) logger.debug(`Extracted Env: ${JSON.stringify(env)}`);
|
|
1058
1632
|
const typeDefContent = generateTypeDefinition(attributes, attributeList, env, machineUserNames, idpNames, connectionNames, aiGatewayNames);
|
|
1059
1633
|
const outputPath = resolveTypeDefinitionPath(configPath);
|
|
1060
|
-
fs$
|
|
1061
|
-
fs$
|
|
1634
|
+
fs$2.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
1635
|
+
fs$2.writeFileSync(outputPath, typeDefContent);
|
|
1062
1636
|
const relativePath = path.relative(process.cwd(), outputPath);
|
|
1063
1637
|
logger.newline();
|
|
1064
1638
|
logger.success(`Generated type definitions: ${relativePath}`, { mode: "plain" });
|
|
@@ -2559,7 +3133,7 @@ async function applyAuthConnections(client, result, phase) {
|
|
|
2559
3133
|
|
|
2560
3134
|
//#endregion
|
|
2561
3135
|
//#region src/cli/commands/deploy/function-registry.ts
|
|
2562
|
-
const CHUNK_SIZE = 64 * 1024;
|
|
3136
|
+
const CHUNK_SIZE$1 = 64 * 1024;
|
|
2563
3137
|
/**
|
|
2564
3138
|
* Compute SHA-256 content hash for a script string.
|
|
2565
3139
|
* @param content - Script content to hash
|
|
@@ -2836,9 +3410,9 @@ async function uploadFunctionScript(client, workspaceId, entry, isCreate) {
|
|
|
2836
3410
|
case: "info",
|
|
2837
3411
|
value: info
|
|
2838
3412
|
} };
|
|
2839
|
-
for (let i = 0; i < buffer.length; i += CHUNK_SIZE) yield { payload: {
|
|
3413
|
+
for (let i = 0; i < buffer.length; i += CHUNK_SIZE$1) yield { payload: {
|
|
2840
3414
|
case: "chunk",
|
|
2841
|
-
value: buffer.subarray(i, Math.min(i + CHUNK_SIZE, buffer.length))
|
|
3415
|
+
value: buffer.subarray(i, Math.min(i + CHUNK_SIZE$1, buffer.length))
|
|
2842
3416
|
} };
|
|
2843
3417
|
}
|
|
2844
3418
|
await client.createFunctionRegistry(createStream());
|
|
@@ -2849,9 +3423,9 @@ async function uploadFunctionScript(client, workspaceId, entry, isCreate) {
|
|
|
2849
3423
|
case: "info",
|
|
2850
3424
|
value: info
|
|
2851
3425
|
} };
|
|
2852
|
-
for (let i = 0; i < buffer.length; i += CHUNK_SIZE) yield { payload: {
|
|
3426
|
+
for (let i = 0; i < buffer.length; i += CHUNK_SIZE$1) yield { payload: {
|
|
2853
3427
|
case: "chunk",
|
|
2854
|
-
value: buffer.subarray(i, Math.min(i + CHUNK_SIZE, buffer.length))
|
|
3428
|
+
value: buffer.subarray(i, Math.min(i + CHUNK_SIZE$1, buffer.length))
|
|
2855
3429
|
} };
|
|
2856
3430
|
}
|
|
2857
3431
|
await client.updateFunctionRegistry(updateStream());
|
|
@@ -5239,7 +5813,7 @@ function protoExecutor(context, executor) {
|
|
|
5239
5813
|
case: "workflow",
|
|
5240
5814
|
value: {
|
|
5241
5815
|
workflowName: target.workflowName,
|
|
5242
|
-
variables: target.args ? typeof target.args === "function" ? { expr: `(${stringifyFunction(target.args)})(${argsExpr})` } : { expr: JSON.stringify(target.args) } : void 0,
|
|
5816
|
+
variables: target.args !== void 0 ? typeof target.args === "function" ? { expr: `(${stringifyFunction(target.args)})(${argsExpr})` } : { expr: JSON.stringify(target.args) } : void 0,
|
|
5243
5817
|
invoker: normalizeInvoker(target.invoker, authNamespace, invokerContext)
|
|
5244
5818
|
}
|
|
5245
5819
|
} };
|
|
@@ -6928,7 +7502,7 @@ function createSnapshotFromLocalTypes(types, namespace) {
|
|
|
6928
7502
|
* @returns {NormalizedSchemaSnapshot} Loaded normalized schema snapshot
|
|
6929
7503
|
*/
|
|
6930
7504
|
function loadSnapshot(filePath) {
|
|
6931
|
-
const content = fs$
|
|
7505
|
+
const content = fs$2.readFileSync(filePath, "utf-8");
|
|
6932
7506
|
let raw;
|
|
6933
7507
|
try {
|
|
6934
7508
|
raw = JSON.parse(content);
|
|
@@ -6946,7 +7520,7 @@ function loadSnapshot(filePath) {
|
|
|
6946
7520
|
* @returns {MigrationDiff} Loaded migration diff
|
|
6947
7521
|
*/
|
|
6948
7522
|
function loadDiff(filePath) {
|
|
6949
|
-
const content = fs$
|
|
7523
|
+
const content = fs$2.readFileSync(filePath, "utf-8");
|
|
6950
7524
|
let raw;
|
|
6951
7525
|
try {
|
|
6952
7526
|
raw = JSON.parse(content);
|
|
@@ -6969,8 +7543,8 @@ function loadDiff(filePath) {
|
|
|
6969
7543
|
* @returns {Array<{number: number, type: "schema" | "diff", path: string}>} Migration files sorted by number
|
|
6970
7544
|
*/
|
|
6971
7545
|
function getMigrationFiles(migrationsDir) {
|
|
6972
|
-
if (!fs$
|
|
6973
|
-
const entries = fs$
|
|
7546
|
+
if (!fs$2.existsSync(migrationsDir)) return [];
|
|
7547
|
+
const entries = fs$2.readdirSync(migrationsDir, { withFileTypes: true });
|
|
6974
7548
|
const migrations = [];
|
|
6975
7549
|
for (const entry of entries) {
|
|
6976
7550
|
if (!entry.isDirectory()) continue;
|
|
@@ -6978,13 +7552,13 @@ function getMigrationFiles(migrationsDir) {
|
|
|
6978
7552
|
const num = parseInt(entry.name, 10);
|
|
6979
7553
|
const migrationDir = path.join(migrationsDir, entry.name);
|
|
6980
7554
|
const schemaPath = path.join(migrationDir, SCHEMA_FILE_NAME);
|
|
6981
|
-
if (fs$
|
|
7555
|
+
if (fs$2.existsSync(schemaPath)) migrations.push({
|
|
6982
7556
|
number: num,
|
|
6983
7557
|
type: "schema",
|
|
6984
7558
|
path: schemaPath
|
|
6985
7559
|
});
|
|
6986
7560
|
const diffPath = path.join(migrationDir, DIFF_FILE_NAME);
|
|
6987
|
-
if (fs$
|
|
7561
|
+
if (fs$2.existsSync(diffPath)) migrations.push({
|
|
6988
7562
|
number: num,
|
|
6989
7563
|
type: "diff",
|
|
6990
7564
|
path: diffPath
|
|
@@ -7750,7 +8324,7 @@ function compareLocalTypesWithSnapshot(snapshot, localTypes, namespace) {
|
|
|
7750
8324
|
*/
|
|
7751
8325
|
function validateMigrationFiles(migrationsDir) {
|
|
7752
8326
|
const errors = [];
|
|
7753
|
-
if (!fs$
|
|
8327
|
+
if (!fs$2.existsSync(migrationsDir)) return errors;
|
|
7754
8328
|
const migrationFiles = getMigrationFiles(migrationsDir);
|
|
7755
8329
|
if (migrationFiles.length === 0) return errors;
|
|
7756
8330
|
const schemaFiles = [];
|
|
@@ -8987,7 +9561,7 @@ function handleOptionalToRequiredError(error, messages) {
|
|
|
8987
9561
|
*/
|
|
8988
9562
|
async function bundleMigrationScript(sourceFile, namespace, migrationNumber, env = {}) {
|
|
8989
9563
|
const outputDir = path.resolve(getDistDir(), "migrations");
|
|
8990
|
-
fs$
|
|
9564
|
+
fs$2.mkdirSync(outputDir, { recursive: true });
|
|
8991
9565
|
const entryPath = path.join(outputDir, `migration_${namespace}_${migrationNumber}.entry.js`);
|
|
8992
9566
|
const entryContent = multiline`
|
|
8993
9567
|
import { main as _migrationMain } from "${path.resolve(sourceFile).replace(/\\/g, "/")}";
|
|
@@ -9009,7 +9583,7 @@ async function bundleMigrationScript(sourceFile, namespace, migrationNumber, env
|
|
|
9009
9583
|
return { success: true };
|
|
9010
9584
|
}
|
|
9011
9585
|
`;
|
|
9012
|
-
fs$
|
|
9586
|
+
fs$2.writeFileSync(entryPath, entryContent);
|
|
9013
9587
|
let tsconfig;
|
|
9014
9588
|
try {
|
|
9015
9589
|
tsconfig = await resolveTSConfig();
|
|
@@ -9320,10 +9894,10 @@ async function detectPendingMigrations(client, workspaceId, namespacesWithMigrat
|
|
|
9320
9894
|
for (const file of migrationFiles) {
|
|
9321
9895
|
if (file.number <= currentMigration) continue;
|
|
9322
9896
|
const diffPath = getMigrationFilePath(migrationsDir, file.number, "diff");
|
|
9323
|
-
if (!fs$
|
|
9897
|
+
if (!fs$2.existsSync(diffPath)) continue;
|
|
9324
9898
|
const diff = loadDiff(diffPath);
|
|
9325
9899
|
const scriptPath = getMigrationFilePath(migrationsDir, file.number, "migrate");
|
|
9326
|
-
const hasScript = fs$
|
|
9900
|
+
const hasScript = fs$2.existsSync(scriptPath);
|
|
9327
9901
|
if (diff.requiresMigrationScript && !hasScript) {
|
|
9328
9902
|
logger.warn(`Migration ${namespace}/${file.number} requires a script but migrate.ts not found`);
|
|
9329
9903
|
continue;
|
|
@@ -11205,7 +11779,7 @@ function findIdProperty(obj) {
|
|
|
11205
11779
|
* @returns Resolved id and whether it was newly injected, or null if skipped
|
|
11206
11780
|
*/
|
|
11207
11781
|
async function ensureConfigId(configPath) {
|
|
11208
|
-
const source = await fs$
|
|
11782
|
+
const source = await fs$2.promises.readFile(configPath, "utf-8");
|
|
11209
11783
|
const { program } = parseSync(configPath, source);
|
|
11210
11784
|
const calls = [];
|
|
11211
11785
|
findDefineConfigCalls(program, calls);
|
|
@@ -11227,7 +11801,7 @@ async function ensureConfigId(configPath) {
|
|
|
11227
11801
|
}
|
|
11228
11802
|
const id = crypto.randomUUID();
|
|
11229
11803
|
const newSource = insertIdProperty(source, configObj, id);
|
|
11230
|
-
await fs$
|
|
11804
|
+
await fs$2.promises.writeFile(configPath, newSource, "utf-8");
|
|
11231
11805
|
logger.info(`Generated app id and wrote to ${configPath}: ${id}`);
|
|
11232
11806
|
return {
|
|
11233
11807
|
id,
|
|
@@ -11243,7 +11817,7 @@ async function ensureConfigId(configPath) {
|
|
|
11243
11817
|
* @returns The existing id (or null when absent), or null for wrapper configs
|
|
11244
11818
|
*/
|
|
11245
11819
|
async function readConfigId(configPath) {
|
|
11246
|
-
const { program } = parseSync(configPath, await fs$
|
|
11820
|
+
const { program } = parseSync(configPath, await fs$2.promises.readFile(configPath, "utf-8"));
|
|
11247
11821
|
const calls = [];
|
|
11248
11822
|
findDefineConfigCalls(program, calls);
|
|
11249
11823
|
if (calls.length === 0) return null;
|
|
@@ -11497,188 +12071,1014 @@ async function validatePlan(input) {
|
|
|
11497
12071
|
violations
|
|
11498
12072
|
});
|
|
11499
12073
|
}
|
|
11500
|
-
function replaces(schema, kind, items) {
|
|
11501
|
-
validateItems({
|
|
11502
|
-
validator,
|
|
11503
|
-
schema,
|
|
11504
|
-
kind,
|
|
11505
|
-
action: "replace",
|
|
11506
|
-
items,
|
|
11507
|
-
requestKey: "createRequest",
|
|
11508
|
-
violations
|
|
12074
|
+
function replaces(schema, kind, items) {
|
|
12075
|
+
validateItems({
|
|
12076
|
+
validator,
|
|
12077
|
+
schema,
|
|
12078
|
+
kind,
|
|
12079
|
+
action: "replace",
|
|
12080
|
+
items,
|
|
12081
|
+
requestKey: "createRequest",
|
|
12082
|
+
violations
|
|
12083
|
+
});
|
|
12084
|
+
}
|
|
12085
|
+
function inPlaceReplaces(schema, kind, items) {
|
|
12086
|
+
validateItems({
|
|
12087
|
+
validator,
|
|
12088
|
+
schema,
|
|
12089
|
+
kind,
|
|
12090
|
+
action: "replace",
|
|
12091
|
+
items,
|
|
12092
|
+
requestKey: "updateRequest",
|
|
12093
|
+
violations
|
|
12094
|
+
});
|
|
12095
|
+
}
|
|
12096
|
+
creates(CreateTailorDBServiceRequestSchema, "TailorDB service", tailorDB.changeSet.service.creates);
|
|
12097
|
+
creates(CreateTailorDBTypeRequestSchema, "TailorDB type", tailorDB.changeSet.type.creates);
|
|
12098
|
+
updates(UpdateTailorDBTypeRequestSchema, "TailorDB type", tailorDB.changeSet.type.updates);
|
|
12099
|
+
creates(CreateStaticWebsiteRequestSchema, "StaticWebsite", staticWebsite.changeSet.creates);
|
|
12100
|
+
updates(UpdateStaticWebsiteRequestSchema, "StaticWebsite", staticWebsite.changeSet.updates);
|
|
12101
|
+
creates(AddCustomDomainRequestSchema, "StaticWebsite custom domain", staticWebsite.customDomainChangeSet.creates);
|
|
12102
|
+
creates(CreateAIGatewayRequestSchema, "AIGateway", aiGateway.changeSet.creates.map((item) => ({
|
|
12103
|
+
name: item.name,
|
|
12104
|
+
request: {
|
|
12105
|
+
...item.request,
|
|
12106
|
+
cors: void 0
|
|
12107
|
+
}
|
|
12108
|
+
})));
|
|
12109
|
+
updates(UpdateAIGatewayRequestSchema, "AIGateway", aiGateway.changeSet.updates.map((item) => ({
|
|
12110
|
+
name: item.name,
|
|
12111
|
+
request: {
|
|
12112
|
+
...item.request,
|
|
12113
|
+
cors: void 0
|
|
12114
|
+
}
|
|
12115
|
+
})));
|
|
12116
|
+
const placeholderOriginReplacement = "https://placeholder.invalid";
|
|
12117
|
+
const substituteIdpReturnOrigins = (item) => {
|
|
12118
|
+
const request = item.request;
|
|
12119
|
+
const origins = request.userAuthPolicy?.allowedReturnOrigins;
|
|
12120
|
+
if (!Array.isArray(origins) || origins.length === 0) return item;
|
|
12121
|
+
const substituted = origins.map((origin) => typeof origin === "string" && /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]:url$/.test(origin) ? placeholderOriginReplacement : origin);
|
|
12122
|
+
return {
|
|
12123
|
+
...item,
|
|
12124
|
+
request: {
|
|
12125
|
+
...request,
|
|
12126
|
+
userAuthPolicy: {
|
|
12127
|
+
...request.userAuthPolicy,
|
|
12128
|
+
allowedReturnOrigins: substituted
|
|
12129
|
+
}
|
|
12130
|
+
}
|
|
12131
|
+
};
|
|
12132
|
+
};
|
|
12133
|
+
creates(CreateIdPServiceRequestSchema, "IdP service", idp.changeSet.service.creates.map(substituteIdpReturnOrigins));
|
|
12134
|
+
updates(UpdateIdPServiceRequestSchema, "IdP service", idp.changeSet.service.updates.map(substituteIdpReturnOrigins));
|
|
12135
|
+
const idpClientVaultItems = [...idp.changeSet.client.creates.map((c) => ({
|
|
12136
|
+
clientName: c.request.client?.name ?? "",
|
|
12137
|
+
namespaceName: c.request.namespaceName ?? "",
|
|
12138
|
+
workspaceId: c.request.workspaceId ?? ""
|
|
12139
|
+
})), ...idp.changeSet.client.updates.map((u) => ({
|
|
12140
|
+
clientName: u.name,
|
|
12141
|
+
namespaceName: u.namespaceName,
|
|
12142
|
+
workspaceId: u.workspaceId
|
|
12143
|
+
}))];
|
|
12144
|
+
creates(CreateSecretManagerVaultRequestSchema, "IdP client secret", idpClientVaultItems.map((item) => ({
|
|
12145
|
+
name: item.clientName,
|
|
12146
|
+
request: {
|
|
12147
|
+
workspaceId: item.workspaceId,
|
|
12148
|
+
secretmanagerVaultName: idpClientVaultName(item.namespaceName, item.clientName)
|
|
12149
|
+
}
|
|
12150
|
+
})));
|
|
12151
|
+
creates(CreateSecretManagerSecretRequestSchema, "IdP client secret", idpClientVaultItems.map((item) => ({
|
|
12152
|
+
name: item.clientName,
|
|
12153
|
+
request: {
|
|
12154
|
+
workspaceId: item.workspaceId,
|
|
12155
|
+
secretmanagerVaultName: idpClientVaultName(item.namespaceName, item.clientName),
|
|
12156
|
+
secretmanagerSecretName: idpClientSecretName(item.namespaceName, item.clientName)
|
|
12157
|
+
}
|
|
12158
|
+
})));
|
|
12159
|
+
creates(CreateAuthServiceRequestSchema, "Auth service", auth.changeSet.service.creates);
|
|
12160
|
+
updates(UpdateAuthServiceRequestSchema, "Auth service", auth.changeSet.service.updates);
|
|
12161
|
+
creates(CreateAuthIDPConfigRequestSchema, "Auth IDP config", auth.changeSet.idpConfig.creates);
|
|
12162
|
+
updates(UpdateAuthIDPConfigRequestSchema, "Auth IDP config", auth.changeSet.idpConfig.updates);
|
|
12163
|
+
creates(CreateUserProfileConfigRequestSchema, "Auth user profile config", auth.changeSet.userProfileConfig.creates);
|
|
12164
|
+
updates(UpdateUserProfileConfigRequestSchema, "Auth user profile config", auth.changeSet.userProfileConfig.updates);
|
|
12165
|
+
creates(CreateTenantConfigRequestSchema, "Auth tenant config", auth.changeSet.tenantConfig.creates);
|
|
12166
|
+
updates(UpdateTenantConfigRequestSchema, "Auth tenant config", auth.changeSet.tenantConfig.updates);
|
|
12167
|
+
creates(CreateAuthMachineUserRequestSchema, "Auth machine user", auth.changeSet.machineUser.creates);
|
|
12168
|
+
updates(UpdateAuthMachineUserRequestSchema, "Auth machine user", auth.changeSet.machineUser.updates);
|
|
12169
|
+
creates(CreateAuthHookRequestSchema, "Auth hook", auth.changeSet.authHook.creates);
|
|
12170
|
+
updates(UpdateAuthHookRequestSchema, "Auth hook", auth.changeSet.authHook.updates);
|
|
12171
|
+
creates(CreateAuthSCIMConfigRequestSchema, "Auth SCIM config", auth.changeSet.scim.creates);
|
|
12172
|
+
updates(UpdateAuthSCIMConfigRequestSchema, "Auth SCIM config", auth.changeSet.scim.updates);
|
|
12173
|
+
creates(CreateAuthSCIMResourceRequestSchema, "Auth SCIM resource", auth.changeSet.scimResource.creates);
|
|
12174
|
+
updates(UpdateAuthSCIMResourceRequestSchema, "Auth SCIM resource", auth.changeSet.scimResource.updates);
|
|
12175
|
+
creates(CreateAuthOAuth2ClientRequestSchema, "OAuth2 client", auth.changeSet.oauth2Client.creates);
|
|
12176
|
+
updates(UpdateAuthOAuth2ClientRequestSchema, "OAuth2 client", auth.changeSet.oauth2Client.updates);
|
|
12177
|
+
replaces(CreateAuthOAuth2ClientRequestSchema, "OAuth2 client", auth.changeSet.oauth2Client.replaces);
|
|
12178
|
+
creates(CreatePipelineServiceRequestSchema, "Pipeline service", pipeline.changeSet.service.creates);
|
|
12179
|
+
updates(UpdatePipelineServiceRequestSchema, "Pipeline service", pipeline.changeSet.service.updates);
|
|
12180
|
+
creates(CreatePipelineResolverRequestSchema, "Resolver", pipeline.changeSet.resolver.creates);
|
|
12181
|
+
updates(UpdatePipelineResolverRequestSchema, "Resolver", pipeline.changeSet.resolver.updates);
|
|
12182
|
+
creates(CreateExecutorExecutorRequestSchema, "Executor", executor.changeSet.creates);
|
|
12183
|
+
updates(UpdateExecutorExecutorRequestSchema, "Executor", executor.changeSet.updates);
|
|
12184
|
+
creates(CreateWorkflowRequestSchema, "Workflow", workflow.changeSet.creates.map((item) => ({
|
|
12185
|
+
name: item.name,
|
|
12186
|
+
request: buildWorkflowValidationShape(item.workspaceId, item.workflow)
|
|
12187
|
+
})));
|
|
12188
|
+
updates(UpdateWorkflowRequestSchema, "Workflow", workflow.changeSet.updates.map((item) => ({
|
|
12189
|
+
name: item.name,
|
|
12190
|
+
request: buildWorkflowValidationShape(item.workspaceId, item.workflow)
|
|
12191
|
+
})));
|
|
12192
|
+
const workflowJobNameWorkspaceId = workflow.changeSet.creates[0]?.workspaceId ?? workflow.changeSet.updates[0]?.workspaceId ?? "";
|
|
12193
|
+
if (workflowJobNameWorkspaceId) {
|
|
12194
|
+
const allJobNames = /* @__PURE__ */ new Set();
|
|
12195
|
+
for (const item of [...workflow.changeSet.creates, ...workflow.changeSet.updates]) for (const jobName of item.usedJobNames) allJobNames.add(jobName);
|
|
12196
|
+
for (const jobName of workflow.unchangedWorkflowJobNames) allJobNames.add(jobName);
|
|
12197
|
+
creates(CreateWorkflowJobFunctionRequestSchema, "Workflow job function", [...allJobNames].map((jobName) => ({
|
|
12198
|
+
name: jobName,
|
|
12199
|
+
request: {
|
|
12200
|
+
workspaceId: workflowJobNameWorkspaceId,
|
|
12201
|
+
jobFunctionName: jobName,
|
|
12202
|
+
scriptRef: jobName
|
|
12203
|
+
}
|
|
12204
|
+
})));
|
|
12205
|
+
}
|
|
12206
|
+
creates(CreateWorkflowJobFunctionExecutionPolicyRequestSchema, "Workflow execution policy", [...workflowExecutionPolicy.changeSet.creates, ...workflowExecutionPolicy.changeSet.replaces].map((item) => ({
|
|
12207
|
+
name: item.name,
|
|
12208
|
+
request: {
|
|
12209
|
+
workspaceId: item.workspaceId,
|
|
12210
|
+
executionPolicyName: item.policy.name,
|
|
12211
|
+
executionPolicyKey: toPlatformExecutionPolicyKey(item.policy),
|
|
12212
|
+
...item.policy.concurrencyPolicy && { concurrencyPolicy: { maxConcurrentExecutions: item.policy.concurrencyPolicy.maxConcurrentExecutions } }
|
|
12213
|
+
}
|
|
12214
|
+
})));
|
|
12215
|
+
updates(UpdateWorkflowJobFunctionExecutionPolicyRequestSchema, "Workflow execution policy", workflowExecutionPolicy.changeSet.updates.map((item) => ({
|
|
12216
|
+
name: item.name,
|
|
12217
|
+
request: {
|
|
12218
|
+
workspaceId: item.workspaceId,
|
|
12219
|
+
executionPolicyName: item.policy.name,
|
|
12220
|
+
...item.policy.concurrencyPolicy && { concurrencyPolicy: { maxConcurrentExecutions: item.policy.concurrencyPolicy.maxConcurrentExecutions } }
|
|
12221
|
+
}
|
|
12222
|
+
})));
|
|
12223
|
+
creates(CreateSecretManagerVaultRequestSchema, "Secret Manager vault", secretManager.vaultChangeSet.creates.map((item) => ({
|
|
12224
|
+
name: item.name,
|
|
12225
|
+
request: vaultCreateRequest(item)
|
|
12226
|
+
})));
|
|
12227
|
+
creates(CreateSecretManagerSecretRequestSchema, "Secret Manager secret", secretManager.secretChangeSet.creates.map((item) => ({
|
|
12228
|
+
name: item.name,
|
|
12229
|
+
request: secretCreateRequest(item)
|
|
12230
|
+
})));
|
|
12231
|
+
updates(UpdateSecretManagerSecretRequestSchema, "Secret Manager secret", secretManager.secretChangeSet.updates.map((item) => ({
|
|
12232
|
+
name: item.name,
|
|
12233
|
+
request: secretUpdateRequest(item)
|
|
12234
|
+
})));
|
|
12235
|
+
creates(CreateApplicationRequestSchema, "Application", app.creates.map((item) => ({
|
|
12236
|
+
name: item.name,
|
|
12237
|
+
request: {
|
|
12238
|
+
...item.request,
|
|
12239
|
+
cors: void 0
|
|
12240
|
+
}
|
|
12241
|
+
})));
|
|
12242
|
+
updates(UpdateApplicationRequestSchema, "Application", [...app.updates, ...app.unchanged].map((item) => ({
|
|
12243
|
+
name: item.name,
|
|
12244
|
+
request: {
|
|
12245
|
+
...item.request,
|
|
12246
|
+
cors: void 0
|
|
12247
|
+
}
|
|
12248
|
+
})));
|
|
12249
|
+
creates(CreateAuthConnectionRequestSchema, "Auth connection", auth.changeSet.connection.creates);
|
|
12250
|
+
inPlaceReplaces(UpdateAuthConnectionRequestSchema, "Auth connection", auth.changeSet.connection.replaces);
|
|
12251
|
+
if (violations.length === 0) return;
|
|
12252
|
+
const resourceNames = new Set(violations.map((v) => `${v.kind}:${v.name}`));
|
|
12253
|
+
logger.error(`Pre-flight validation found ${violations.length} violation(s) across ${resourceNames.size} resource(s):`);
|
|
12254
|
+
for (const v of violations) logger.log(` ${styles.resourceType(v.kind)} ${styles.resourceName(v.name)} (${v.action}) — ${styles.bold(v.fieldPath)}: ${v.message}`);
|
|
12255
|
+
throw new Error(`${violations.length} validation error(s) found in ${resourceNames.size} resource(s)`);
|
|
12256
|
+
}
|
|
12257
|
+
|
|
12258
|
+
//#endregion
|
|
12259
|
+
//#region src/cli/shared/profile-name.ts
|
|
12260
|
+
const profileNameSchema = z.string().min(1, "Profile must not be empty");
|
|
12261
|
+
|
|
12262
|
+
//#endregion
|
|
12263
|
+
//#region src/cli/shared/workspace-name.ts
|
|
12264
|
+
const workspaceNameSchema = z.string().min(3, "Name must be at least 3 characters").max(63, "Name must be at most 63 characters").regex(/^[a-z0-9-]+$/, "Name can only contain lowercase letters, numbers, and hyphens").refine((name) => !name.startsWith("-") && !name.endsWith("-"), "Name cannot start or end with a hyphen");
|
|
12265
|
+
/**
|
|
12266
|
+
* Validate a workspace name for use in an interactive prompt.
|
|
12267
|
+
* @param name - Candidate workspace name
|
|
12268
|
+
* @returns True when valid, otherwise a validation message
|
|
12269
|
+
*/
|
|
12270
|
+
function validateWorkspaceName(name) {
|
|
12271
|
+
const result = workspaceNameSchema.safeParse(name);
|
|
12272
|
+
return result.success ? true : assertDefined(result.error.issues[0], "Zod returned no issues").message;
|
|
12273
|
+
}
|
|
12274
|
+
|
|
12275
|
+
//#endregion
|
|
12276
|
+
//#region src/cli/shared/format.ts
|
|
12277
|
+
/**
|
|
12278
|
+
* Format a protobuf Timestamp to ISO string.
|
|
12279
|
+
* @param timestamp - Protobuf timestamp
|
|
12280
|
+
* @returns Date object or null if invalid
|
|
12281
|
+
*/
|
|
12282
|
+
function formatTimestamp(timestamp) {
|
|
12283
|
+
if (!timestamp) return null;
|
|
12284
|
+
const date = timestampDate(timestamp);
|
|
12285
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
12286
|
+
return date;
|
|
12287
|
+
}
|
|
12288
|
+
/**
|
|
12289
|
+
* Formats a table with consistent single-line border style.
|
|
12290
|
+
* Use this instead of importing `table` directly.
|
|
12291
|
+
* @param data - Table data
|
|
12292
|
+
* @param config - Table configuration
|
|
12293
|
+
* @returns Formatted table string
|
|
12294
|
+
*/
|
|
12295
|
+
function formatTable(data, config) {
|
|
12296
|
+
return table(data, {
|
|
12297
|
+
...config,
|
|
12298
|
+
border: getBorderCharacters("norc")
|
|
12299
|
+
});
|
|
12300
|
+
}
|
|
12301
|
+
/**
|
|
12302
|
+
* Formats a key-value table with single-line border style.
|
|
12303
|
+
* @param data - Key-value pairs
|
|
12304
|
+
* @returns Formatted key-value table string
|
|
12305
|
+
*/
|
|
12306
|
+
function formatKeyValueTable(data) {
|
|
12307
|
+
return formatTable(data, { singleLine: true });
|
|
12308
|
+
}
|
|
12309
|
+
/**
|
|
12310
|
+
* Format a Date or ISO timestamp string as a human-readable relative time.
|
|
12311
|
+
* @param value - Date object, ISO date string, or null
|
|
12312
|
+
* @returns Relative time (e.g., "5 minutes ago") or "N/A" for null/invalid
|
|
12313
|
+
*/
|
|
12314
|
+
function humanizeRelativeTime(value) {
|
|
12315
|
+
if (value === null) return "N/A";
|
|
12316
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
12317
|
+
if (Number.isNaN(date.getTime())) return typeof value === "string" ? value : "N/A";
|
|
12318
|
+
return formatDistanceToNowStrict(date, { addSuffix: true });
|
|
12319
|
+
}
|
|
12320
|
+
|
|
12321
|
+
//#endregion
|
|
12322
|
+
//#region src/cli/commands/workspace/transform.ts
|
|
12323
|
+
const workspaceInfo = (workspace, folderName) => {
|
|
12324
|
+
const info = {
|
|
12325
|
+
id: workspace.id,
|
|
12326
|
+
name: workspace.name,
|
|
12327
|
+
region: workspace.region,
|
|
12328
|
+
...workspace.organizationId ? { organizationId: workspace.organizationId } : {},
|
|
12329
|
+
...workspace.folderId ? { folderId: workspace.folderId } : {},
|
|
12330
|
+
createdAt: formatTimestamp(workspace.createTime),
|
|
12331
|
+
updatedAt: formatTimestamp(workspace.updateTime)
|
|
12332
|
+
};
|
|
12333
|
+
return folderName ? {
|
|
12334
|
+
...info,
|
|
12335
|
+
folderName
|
|
12336
|
+
} : info;
|
|
12337
|
+
};
|
|
12338
|
+
const workspaceDetails = (workspace, folderName) => {
|
|
12339
|
+
return {
|
|
12340
|
+
...workspaceInfo(workspace, folderName),
|
|
12341
|
+
deleteProtection: workspace.deleteProtection,
|
|
12342
|
+
organizationId: workspace.organizationId,
|
|
12343
|
+
folderId: workspace.folderId
|
|
12344
|
+
};
|
|
12345
|
+
};
|
|
12346
|
+
async function resolveWorkspaceFolderName(client, workspace) {
|
|
12347
|
+
if (!workspace.organizationId || !workspace.folderId) return void 0;
|
|
12348
|
+
try {
|
|
12349
|
+
return (await client.getOrganizationFolder({
|
|
12350
|
+
organizationId: workspace.organizationId,
|
|
12351
|
+
folderId: workspace.folderId
|
|
12352
|
+
})).folder?.name || void 0;
|
|
12353
|
+
} catch (error) {
|
|
12354
|
+
if (error instanceof ConnectError && (error.code === Code.NotFound || error.code === Code.PermissionDenied)) return;
|
|
12355
|
+
logger.warn(`Failed to resolve workspace folder name: ${error}`);
|
|
12356
|
+
return;
|
|
12357
|
+
}
|
|
12358
|
+
}
|
|
12359
|
+
function createWorkspaceFolderNameResolver(client) {
|
|
12360
|
+
const cache = /* @__PURE__ */ new Map();
|
|
12361
|
+
return (workspace) => {
|
|
12362
|
+
if (!workspace.organizationId || !workspace.folderId) return Promise.resolve(void 0);
|
|
12363
|
+
const cacheKey = `${workspace.organizationId}/${workspace.folderId}`;
|
|
12364
|
+
const cached = cache.get(cacheKey);
|
|
12365
|
+
if (cached) return cached;
|
|
12366
|
+
const promise = resolveWorkspaceFolderName(client, workspace);
|
|
12367
|
+
cache.set(cacheKey, promise);
|
|
12368
|
+
return promise;
|
|
12369
|
+
};
|
|
12370
|
+
}
|
|
12371
|
+
async function workspaceInfoWithFolderName(client, workspace) {
|
|
12372
|
+
const folderName = await resolveWorkspaceFolderName(client, workspace);
|
|
12373
|
+
return workspaceInfo(workspace, folderName);
|
|
12374
|
+
}
|
|
12375
|
+
async function workspaceDetailsWithFolderName(client, workspace) {
|
|
12376
|
+
const folderName = await resolveWorkspaceFolderName(client, workspace);
|
|
12377
|
+
return workspaceDetails(workspace, folderName);
|
|
12378
|
+
}
|
|
12379
|
+
async function workspaceInfosWithFolderNames(client, workspaces) {
|
|
12380
|
+
const resolveFolderName = createWorkspaceFolderNameResolver(client);
|
|
12381
|
+
const limit = pLimit(5);
|
|
12382
|
+
return Promise.all(workspaces.map((workspace) => limit(async () => workspaceInfo(workspace, await resolveFolderName(workspace)))));
|
|
12383
|
+
}
|
|
12384
|
+
function workspaceDisplayName(workspace) {
|
|
12385
|
+
return workspace.folderName ? `${workspace.folderName}/${workspace.name}` : workspace.name;
|
|
12386
|
+
}
|
|
12387
|
+
function createWorkspaceNameTransformer(nameKey, folderNameKey) {
|
|
12388
|
+
return (value, item) => {
|
|
12389
|
+
const workspace = item;
|
|
12390
|
+
const name = workspace[nameKey];
|
|
12391
|
+
const folderName = workspace[folderNameKey];
|
|
12392
|
+
if (typeof name === "string" && typeof folderName === "string") return workspaceDisplayName({
|
|
12393
|
+
name,
|
|
12394
|
+
folderName
|
|
12395
|
+
});
|
|
12396
|
+
return String(value ?? "");
|
|
12397
|
+
};
|
|
12398
|
+
}
|
|
12399
|
+
const workspaceNameTransformer = createWorkspaceNameTransformer("name", "folderName");
|
|
12400
|
+
|
|
12401
|
+
//#endregion
|
|
12402
|
+
//#region src/cli/commands/workspace/create.ts
|
|
12403
|
+
/**
|
|
12404
|
+
* Schema for workspace creation options
|
|
12405
|
+
* - name: 3-63 chars, lowercase alphanumeric and hyphens, cannot start/end with hyphen
|
|
12406
|
+
* - organizationId, folderId: optional UUIDs
|
|
12407
|
+
*/
|
|
12408
|
+
const createWorkspaceOptionsSchema = z.object({
|
|
12409
|
+
name: workspaceNameSchema,
|
|
12410
|
+
region: z.string(),
|
|
12411
|
+
deleteProtection: z.boolean().optional(),
|
|
12412
|
+
organizationId: z.uuid().optional(),
|
|
12413
|
+
folderId: z.uuid().optional(),
|
|
12414
|
+
profile: profileNameSchema.optional()
|
|
12415
|
+
});
|
|
12416
|
+
const validateRegion = async (region, client) => {
|
|
12417
|
+
const availableRegions = await client.listAvailableWorkspaceRegions({});
|
|
12418
|
+
if (!availableRegions.regions.includes(region)) throw new Error(`Region must be one of: ${availableRegions.regions.join(", ")}.`);
|
|
12419
|
+
};
|
|
12420
|
+
function profilePlatformSettings(platformConfig) {
|
|
12421
|
+
const hasOAuth2ClientId = platformConfig?.oauth2ClientId || process.env.TAILOR_PLATFORM_OAUTH2_CLIENT_ID;
|
|
12422
|
+
const hasConsoleUrl = platformConfig?.consoleUrl || process.env.TAILOR_PLATFORM_CONSOLE_URL;
|
|
12423
|
+
return {
|
|
12424
|
+
...isDefaultPlatform(platformConfig) ? {} : { platform_url: getPlatformBaseUrl(platformConfig) },
|
|
12425
|
+
...hasOAuth2ClientId ? { oauth2_client_id: getOAuth2ClientId(platformConfig) } : {},
|
|
12426
|
+
...hasConsoleUrl ? { console_url: getConsoleBaseUrl(platformConfig) } : {}
|
|
12427
|
+
};
|
|
12428
|
+
}
|
|
12429
|
+
/**
|
|
12430
|
+
* Create a new workspace with the given options.
|
|
12431
|
+
* @param options - Workspace creation options
|
|
12432
|
+
* @returns Created workspace info
|
|
12433
|
+
*/
|
|
12434
|
+
async function createWorkspace$1(options) {
|
|
12435
|
+
const validated = validateCreateWorkspaceOptions(options);
|
|
12436
|
+
const client = await initOperatorClient(await loadAccessToken({ profile: validated.profile }), await loadPlatformClientConfig({ profile: validated.profile }));
|
|
12437
|
+
await validateRegion(validated.region, client);
|
|
12438
|
+
return createValidatedWorkspaceWithClient(client, validated);
|
|
12439
|
+
}
|
|
12440
|
+
/**
|
|
12441
|
+
* Create a workspace after its local options and region have been validated.
|
|
12442
|
+
* @param client - Authenticated Operator client
|
|
12443
|
+
* @param options - Validated workspace creation options
|
|
12444
|
+
* @returns Created workspace info
|
|
12445
|
+
*/
|
|
12446
|
+
async function createValidatedWorkspaceWithClient(client, options) {
|
|
12447
|
+
return workspaceInfoWithFolderName(client, assertDefined((await client.createWorkspace({
|
|
12448
|
+
workspaceName: options.name,
|
|
12449
|
+
workspaceRegion: options.region,
|
|
12450
|
+
deleteProtection: options.deleteProtection ?? false,
|
|
12451
|
+
organizationId: options.organizationId,
|
|
12452
|
+
folderId: options.folderId
|
|
12453
|
+
})).workspace, "createWorkspace response missing workspace"));
|
|
12454
|
+
}
|
|
12455
|
+
/**
|
|
12456
|
+
* Validate workspace creation options without making API calls.
|
|
12457
|
+
* @param options - Workspace creation options
|
|
12458
|
+
* @returns Validated workspace creation options
|
|
12459
|
+
*/
|
|
12460
|
+
function validateCreateWorkspaceOptions(options) {
|
|
12461
|
+
const result = createWorkspaceOptionsSchema.safeParse(options);
|
|
12462
|
+
if (!result.success) throw new Error(assertDefined(result.error.issues[0], "Zod returned no issues").message);
|
|
12463
|
+
return result.data;
|
|
12464
|
+
}
|
|
12465
|
+
const createCommand$1 = defineAppCommand({
|
|
12466
|
+
name: "create",
|
|
12467
|
+
description: "Create a new Tailor Platform workspace.",
|
|
12468
|
+
args: z.strictObject({
|
|
12469
|
+
name: arg(z.string(), {
|
|
12470
|
+
alias: "n",
|
|
12471
|
+
description: "Workspace name"
|
|
12472
|
+
}),
|
|
12473
|
+
region: arg(z.string(), {
|
|
12474
|
+
alias: "r",
|
|
12475
|
+
description: "Workspace region (us-west, asia-northeast)"
|
|
12476
|
+
}),
|
|
12477
|
+
"delete-protection": arg(z.boolean().default(false), {
|
|
12478
|
+
alias: "d",
|
|
12479
|
+
description: "Enable delete protection"
|
|
12480
|
+
}),
|
|
12481
|
+
"organization-id": arg(z.string().optional(), {
|
|
12482
|
+
alias: "o",
|
|
12483
|
+
description: "Organization ID to workspace associate with",
|
|
12484
|
+
env: "TAILOR_PLATFORM_ORGANIZATION_ID"
|
|
12485
|
+
}),
|
|
12486
|
+
"folder-id": arg(z.string().optional(), {
|
|
12487
|
+
alias: "f",
|
|
12488
|
+
description: "Folder ID to workspace associate with",
|
|
12489
|
+
env: "TAILOR_PLATFORM_FOLDER_ID"
|
|
12490
|
+
}),
|
|
12491
|
+
"profile-name": arg(z.string().optional(), {
|
|
12492
|
+
alias: "p",
|
|
12493
|
+
description: "Profile name to create"
|
|
12494
|
+
}),
|
|
12495
|
+
profile: arg(profileNameSchema.optional(), {
|
|
12496
|
+
description: "Workspace profile used for authentication and Platform selection",
|
|
12497
|
+
env: "TAILOR_PLATFORM_PROFILE"
|
|
12498
|
+
}),
|
|
12499
|
+
"profile-user": arg(z.string().optional(), { description: "User email address or machine user client ID for the profile (defaults to current user)" }),
|
|
12500
|
+
permission: arg(z.enum(["write", "read"]).default("write"), { description: "Profile permission (requires --profile-name). 'read' blocks all write commands while the profile is active." })
|
|
12501
|
+
}),
|
|
12502
|
+
run: async (args) => {
|
|
12503
|
+
await assertWritable({ profile: args.profile });
|
|
12504
|
+
const profileName = args["profile-name"];
|
|
12505
|
+
let profileSetup;
|
|
12506
|
+
if (profileName) {
|
|
12507
|
+
const config = await readPlatformConfig();
|
|
12508
|
+
if (config.profiles[profileName]) throw new Error(`Profile "${profileName}" already exists.`);
|
|
12509
|
+
const activeProfileName = args.profile;
|
|
12510
|
+
const activeProfileEntry = activeProfileName ? config.profiles[activeProfileName] : void 0;
|
|
12511
|
+
const platformConfig = activeProfileEntry ? platformConfigFromProfile(activeProfileEntry) : void 0;
|
|
12512
|
+
const profileUser = args["profile-user"] || activeProfileEntry?.user || config.current_user;
|
|
12513
|
+
if (!profileUser) throw new Error("Current user not found. Please login or specify --profile-user to create a profile.");
|
|
12514
|
+
const resolvedProfileUser = resolveConfigUser(config, profileUser, platformConfig);
|
|
12515
|
+
if (!resolvedProfileUser) throw new Error(`User "${profileUser}" not found.\nPlease verify your user name and login using 'tailor login' command.`);
|
|
12516
|
+
profileSetup = {
|
|
12517
|
+
name: profileName,
|
|
12518
|
+
user: resolvedProfileUser,
|
|
12519
|
+
platformSettings: profilePlatformSettings(platformConfig)
|
|
12520
|
+
};
|
|
12521
|
+
}
|
|
12522
|
+
const workspace = await createWorkspace$1({
|
|
12523
|
+
name: args.name,
|
|
12524
|
+
region: args.region,
|
|
12525
|
+
deleteProtection: args["delete-protection"],
|
|
12526
|
+
organizationId: args["organization-id"],
|
|
12527
|
+
folderId: args["folder-id"],
|
|
12528
|
+
profile: args.profile
|
|
12529
|
+
});
|
|
12530
|
+
let profileInfo;
|
|
12531
|
+
if (profileSetup) {
|
|
12532
|
+
const config = await readPlatformConfig();
|
|
12533
|
+
const platformSettings = profileSetup.platformSettings;
|
|
12534
|
+
config.profiles[profileSetup.name] = {
|
|
12535
|
+
user: profileSetup.user,
|
|
12536
|
+
workspace_id: workspace.id,
|
|
12537
|
+
...args.permission === "read" ? { readonly: true } : {},
|
|
12538
|
+
...platformSettings
|
|
12539
|
+
};
|
|
12540
|
+
writePlatformConfig(config);
|
|
12541
|
+
profileInfo = {
|
|
12542
|
+
name: profileSetup.name,
|
|
12543
|
+
user: profileSetup.user,
|
|
12544
|
+
workspaceId: workspace.id,
|
|
12545
|
+
permission: args.permission,
|
|
12546
|
+
...platformSettings.platform_url ? { platformUrl: platformSettings.platform_url } : {},
|
|
12547
|
+
...platformSettings.oauth2_client_id ? { oauth2ClientId: platformSettings.oauth2_client_id } : {},
|
|
12548
|
+
...platformSettings.console_url ? { consoleUrl: platformSettings.console_url } : {}
|
|
12549
|
+
};
|
|
12550
|
+
if (!args.json) logger.success(`Profile "${profileSetup.name}" created successfully.`);
|
|
12551
|
+
}
|
|
12552
|
+
if (!args.json) logger.success(`Workspace "${workspaceDisplayName(workspace)}" created successfully.`);
|
|
12553
|
+
if (args.json && profileInfo) {
|
|
12554
|
+
logger.out({
|
|
12555
|
+
...workspace,
|
|
12556
|
+
profile: profileInfo
|
|
12557
|
+
});
|
|
12558
|
+
return;
|
|
12559
|
+
}
|
|
12560
|
+
logger.out(workspace, { display: {
|
|
12561
|
+
name: workspaceNameTransformer,
|
|
12562
|
+
folderName: null
|
|
12563
|
+
} });
|
|
12564
|
+
if (profileInfo) {
|
|
12565
|
+
logger.out("Profile:");
|
|
12566
|
+
logger.out(profileInfo);
|
|
12567
|
+
}
|
|
12568
|
+
}
|
|
12569
|
+
});
|
|
12570
|
+
|
|
12571
|
+
//#endregion
|
|
12572
|
+
//#region src/cli/commands/workspace/list.ts
|
|
12573
|
+
/**
|
|
12574
|
+
* List workspaces with an optional order and limit.
|
|
12575
|
+
* @param options - Workspace listing options
|
|
12576
|
+
* @returns List of workspaces
|
|
12577
|
+
*/
|
|
12578
|
+
async function listWorkspaces(options) {
|
|
12579
|
+
const profile = profileNameSchema.optional().parse(options?.profile);
|
|
12580
|
+
return listWorkspacesWithClient(await initOperatorClient(await loadAccessToken({ profile }), await loadPlatformClientConfig({ profile })), options);
|
|
12581
|
+
}
|
|
12582
|
+
/**
|
|
12583
|
+
* List workspaces using an existing Operator client.
|
|
12584
|
+
* @param client - Authenticated Operator client
|
|
12585
|
+
* @param options - Workspace listing options
|
|
12586
|
+
* @returns List of workspaces
|
|
12587
|
+
*/
|
|
12588
|
+
async function listWorkspacesWithClient(client, options) {
|
|
12589
|
+
const pageDirection = toPageDirection(options?.order);
|
|
12590
|
+
return workspaceInfosWithFolderNames(client, await fetchPaged(async (pageToken, pageSize) => {
|
|
12591
|
+
const { workspaces, nextPageToken } = await client.listWorkspaces({
|
|
12592
|
+
pageToken,
|
|
12593
|
+
pageSize,
|
|
12594
|
+
pageDirection
|
|
12595
|
+
});
|
|
12596
|
+
return [workspaces, nextPageToken];
|
|
12597
|
+
}, { limit: options?.limit }));
|
|
12598
|
+
}
|
|
12599
|
+
const listCommand$9 = defineAppCommand({
|
|
12600
|
+
name: "list",
|
|
12601
|
+
description: "List all Tailor Platform workspaces.",
|
|
12602
|
+
args: z.strictObject({
|
|
12603
|
+
...paginationArgs(),
|
|
12604
|
+
profile: arg(profileNameSchema.optional(), {
|
|
12605
|
+
description: "Workspace profile used for authentication and Platform selection",
|
|
12606
|
+
env: "TAILOR_PLATFORM_PROFILE"
|
|
12607
|
+
})
|
|
12608
|
+
}),
|
|
12609
|
+
run: async (args) => {
|
|
12610
|
+
const workspaces = await listWorkspaces({
|
|
12611
|
+
order: args.order,
|
|
12612
|
+
limit: args.limit,
|
|
12613
|
+
profile: args.profile
|
|
12614
|
+
});
|
|
12615
|
+
logger.out(workspaces, { display: {
|
|
12616
|
+
name: workspaceNameTransformer,
|
|
12617
|
+
folderName: null,
|
|
12618
|
+
organizationId: null,
|
|
12619
|
+
folderId: null,
|
|
12620
|
+
updatedAt: null
|
|
12621
|
+
} });
|
|
12622
|
+
}
|
|
12623
|
+
});
|
|
12624
|
+
|
|
12625
|
+
//#endregion
|
|
12626
|
+
//#region src/cli/commands/deploy/workspace-context.ts
|
|
12627
|
+
const workspaceContextSchema = z.object({
|
|
12628
|
+
version: z.literal(1),
|
|
12629
|
+
platformUrl: z.url(),
|
|
12630
|
+
applicationId: z.string().min(1).optional(),
|
|
12631
|
+
workspaceId: z.uuid()
|
|
12632
|
+
});
|
|
12633
|
+
function defaultConfigPath() {
|
|
12634
|
+
return resolve(process.cwd(), "tailor.config.ts");
|
|
12635
|
+
}
|
|
12636
|
+
function contextPath(configPath) {
|
|
12637
|
+
return join(dirname(configPath), getDistDir(), `${basename(configPath)}.context.json`);
|
|
12638
|
+
}
|
|
12639
|
+
/**
|
|
12640
|
+
* Load the project workspace context when it belongs to the current platform.
|
|
12641
|
+
* Missing, malformed, and cross-platform state is ignored.
|
|
12642
|
+
* @param platformUrl - Current Platform API base URL
|
|
12643
|
+
* @param configPath - Configuration file whose workspace selection is loaded
|
|
12644
|
+
* @param applicationId - Application identity that must match the saved context
|
|
12645
|
+
* @returns Valid context for the current platform, or undefined
|
|
12646
|
+
*/
|
|
12647
|
+
async function loadWorkspaceContext(platformUrl, configPath = defaultConfigPath(), applicationId) {
|
|
12648
|
+
let contents;
|
|
12649
|
+
try {
|
|
12650
|
+
contents = await readFile(contextPath(configPath), "utf8");
|
|
12651
|
+
} catch (error) {
|
|
12652
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
|
|
12653
|
+
throw error;
|
|
12654
|
+
}
|
|
12655
|
+
let value;
|
|
12656
|
+
try {
|
|
12657
|
+
value = JSON.parse(contents);
|
|
12658
|
+
} catch {
|
|
12659
|
+
return;
|
|
12660
|
+
}
|
|
12661
|
+
const result = workspaceContextSchema.safeParse(value);
|
|
12662
|
+
if (!result.success || result.data.platformUrl !== platformUrl || applicationId !== void 0 && result.data.applicationId !== applicationId) return;
|
|
12663
|
+
return result.data;
|
|
12664
|
+
}
|
|
12665
|
+
/**
|
|
12666
|
+
* Persist the selected workspace as project-local SDK state.
|
|
12667
|
+
* @param context - Workspace context to persist
|
|
12668
|
+
* @param configPath - Configuration file whose workspace selection is persisted
|
|
12669
|
+
* @param applicationId - Application identity stored with the workspace selection
|
|
12670
|
+
*/
|
|
12671
|
+
async function saveWorkspaceContext(context, configPath = defaultConfigPath(), applicationId) {
|
|
12672
|
+
const validated = workspaceContextSchema.parse({
|
|
12673
|
+
...context,
|
|
12674
|
+
...applicationId === void 0 ? {} : { applicationId }
|
|
12675
|
+
});
|
|
12676
|
+
const stateDirectory = join(dirname(configPath), getDistDir());
|
|
12677
|
+
const targetPath = contextPath(configPath);
|
|
12678
|
+
const serialized = `${JSON.stringify(validated, null, 2)}\n`;
|
|
12679
|
+
try {
|
|
12680
|
+
if (await readFile(targetPath, "utf8") === serialized) return;
|
|
12681
|
+
} catch {}
|
|
12682
|
+
const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
12683
|
+
await mkdir(stateDirectory, { recursive: true });
|
|
12684
|
+
try {
|
|
12685
|
+
await writeFile(temporaryPath, serialized, { mode: 384 });
|
|
12686
|
+
await rename(temporaryPath, targetPath);
|
|
12687
|
+
} catch (error) {
|
|
12688
|
+
await rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
12689
|
+
throw error;
|
|
12690
|
+
}
|
|
12691
|
+
}
|
|
12692
|
+
|
|
12693
|
+
//#endregion
|
|
12694
|
+
//#region src/cli/commands/deploy/workspace.ts
|
|
12695
|
+
function suggestedWorkspaceName() {
|
|
12696
|
+
const name = basename(process.cwd()).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, 63).replace(/-+$/, "");
|
|
12697
|
+
return validateWorkspaceName(name) === true ? name : "my-workspace";
|
|
12698
|
+
}
|
|
12699
|
+
function executableAction(args) {
|
|
12700
|
+
return {
|
|
12701
|
+
command: "tailor-sdk",
|
|
12702
|
+
args
|
|
12703
|
+
};
|
|
12704
|
+
}
|
|
12705
|
+
function deployArgs(options) {
|
|
12706
|
+
return options.deployArgs ?? ["deploy"];
|
|
12707
|
+
}
|
|
12708
|
+
function workspaceCommandArgs(options) {
|
|
12709
|
+
return options.workspaceCommandArgs ?? [];
|
|
12710
|
+
}
|
|
12711
|
+
function createDeployArgs(options, name, region) {
|
|
12712
|
+
return [
|
|
12713
|
+
...deployArgs(options),
|
|
12714
|
+
"--create-workspace",
|
|
12715
|
+
"--workspace-name",
|
|
12716
|
+
name,
|
|
12717
|
+
"--workspace-region",
|
|
12718
|
+
region,
|
|
12719
|
+
...options.organizationId ? ["--organization-id", options.organizationId] : [],
|
|
12720
|
+
...options.folderId ? ["--folder-id", options.folderId] : []
|
|
12721
|
+
];
|
|
12722
|
+
}
|
|
12723
|
+
function selectDeployArgs(options) {
|
|
12724
|
+
return [
|
|
12725
|
+
...deployArgs(options),
|
|
12726
|
+
"--workspace-id",
|
|
12727
|
+
"<workspace-id>"
|
|
12728
|
+
];
|
|
12729
|
+
}
|
|
12730
|
+
function workspaceLabel(workspace) {
|
|
12731
|
+
const organization = workspace.organizationId ?? "personal";
|
|
12732
|
+
return `${workspaceDisplayName(workspace)} (${workspace.region}, org: ${organization}, id: ${workspace.id})`;
|
|
12733
|
+
}
|
|
12734
|
+
function workspaceMatchesRequestedIdentity(workspace, options) {
|
|
12735
|
+
return options.workspaceName === workspace.name && options.workspaceRegion === workspace.region && options.organizationId === workspace.organizationId && options.folderId === workspace.folderId;
|
|
12736
|
+
}
|
|
12737
|
+
function workspaceIdentity(workspace) {
|
|
12738
|
+
const { id, name, region, organizationId, folderId } = workspace;
|
|
12739
|
+
return {
|
|
12740
|
+
id,
|
|
12741
|
+
name,
|
|
12742
|
+
region,
|
|
12743
|
+
organizationId,
|
|
12744
|
+
folderId
|
|
12745
|
+
};
|
|
12746
|
+
}
|
|
12747
|
+
function projectContextTargets(contextTargets) {
|
|
12748
|
+
if (!contextTargets) return void 0;
|
|
12749
|
+
return [...new Map(contextTargets.map((target) => [target.configPath, target])).values()];
|
|
12750
|
+
}
|
|
12751
|
+
async function loadProjectContexts(platformUrl, contextTargets) {
|
|
12752
|
+
const targets = projectContextTargets(contextTargets);
|
|
12753
|
+
if (!targets || targets.length === 0) {
|
|
12754
|
+
const context = await loadWorkspaceContext(platformUrl);
|
|
12755
|
+
return context ? [context] : [];
|
|
12756
|
+
}
|
|
12757
|
+
return (await Promise.all(targets.map(({ configPath, applicationId }) => loadWorkspaceContext(platformUrl, configPath, applicationId)))).filter((context) => context !== void 0);
|
|
12758
|
+
}
|
|
12759
|
+
async function persistWorkspaceContext(context, contextTargets) {
|
|
12760
|
+
const targets = projectContextTargets(contextTargets);
|
|
12761
|
+
if (!targets || targets.length === 0) {
|
|
12762
|
+
await saveWorkspaceContext(context);
|
|
12763
|
+
return;
|
|
12764
|
+
}
|
|
12765
|
+
const failures = (await Promise.allSettled(targets.map(({ configPath, applicationId }) => saveWorkspaceContext(context, configPath, applicationId)))).filter((result) => result.status === "rejected");
|
|
12766
|
+
if (failures.length > 0) throw new Error(failures.map(({ reason }) => reason instanceof Error ? reason.message : String(reason)).join("; "));
|
|
12767
|
+
}
|
|
12768
|
+
async function rememberWorkspaceContext(context, options, failurePolicy = "error-on-partial") {
|
|
12769
|
+
if (options.dryRun) return;
|
|
12770
|
+
try {
|
|
12771
|
+
await persistWorkspaceContext(context, options.contextTargets);
|
|
12772
|
+
} catch (error) {
|
|
12773
|
+
if ((projectContextTargets(options.contextTargets)?.length ?? 0) > 1 && failurePolicy === "error-on-partial") throw createCLIError({
|
|
12774
|
+
code: "WORKSPACE_CONTEXT_SAVE_FAILED",
|
|
12775
|
+
message: "The workspace selection could not be saved for every configuration file.",
|
|
12776
|
+
details: error instanceof Error ? error.message : String(error),
|
|
12777
|
+
suggestion: "Fix project state permissions, then rerun deploy with the workspace ID.",
|
|
12778
|
+
next: executableAction([
|
|
12779
|
+
...deployArgs(options),
|
|
12780
|
+
"--workspace-id",
|
|
12781
|
+
context.workspaceId
|
|
12782
|
+
]),
|
|
12783
|
+
context: {
|
|
12784
|
+
workspaceId: context.workspaceId,
|
|
12785
|
+
configPaths: options.contextTargets?.map(({ configPath }) => configPath)
|
|
12786
|
+
}
|
|
12787
|
+
});
|
|
12788
|
+
logger.warn(`Could not save the project workspace selection (${error instanceof Error ? error.message : String(error)}). Continue with --workspace-id ${context.workspaceId} on later runs.`);
|
|
12789
|
+
}
|
|
12790
|
+
}
|
|
12791
|
+
async function useWorkspace(client, platformUrl, workspace, options, failurePolicy = "error-on-partial") {
|
|
12792
|
+
await rememberWorkspaceContext({
|
|
12793
|
+
version: 1,
|
|
12794
|
+
platformUrl,
|
|
12795
|
+
workspaceId: workspace.id
|
|
12796
|
+
}, options, failurePolicy);
|
|
12797
|
+
logger.info(`Using workspace: ${workspaceLabel(workspace)}`);
|
|
12798
|
+
return {
|
|
12799
|
+
client,
|
|
12800
|
+
workspaceId: workspace.id
|
|
12801
|
+
};
|
|
12802
|
+
}
|
|
12803
|
+
function useRememberedWorkspace(client, workspace) {
|
|
12804
|
+
logger.warn(`Using saved workspace selection: ${workspaceLabel(workspace)}`);
|
|
12805
|
+
return {
|
|
12806
|
+
client,
|
|
12807
|
+
workspaceId: workspace.id
|
|
12808
|
+
};
|
|
12809
|
+
}
|
|
12810
|
+
const createNewWorkspaceSelection = "create-new-workspace";
|
|
12811
|
+
async function chooseWorkspace(client, platformUrl, workspaces, options) {
|
|
12812
|
+
const workspaceId = await prompt.select({
|
|
12813
|
+
message: "Select a workspace",
|
|
12814
|
+
choices: [...workspaces.map((workspace) => ({
|
|
12815
|
+
name: workspaceLabel(workspace),
|
|
12816
|
+
value: workspace.id
|
|
12817
|
+
})), {
|
|
12818
|
+
name: "Create new workspace",
|
|
12819
|
+
value: createNewWorkspaceSelection
|
|
12820
|
+
}]
|
|
12821
|
+
});
|
|
12822
|
+
if (workspaceId === createNewWorkspaceSelection) return createWorkspaceForDeploy(client, platformUrl, options);
|
|
12823
|
+
const workspace = workspaces.find(({ id }) => id === workspaceId);
|
|
12824
|
+
if (!workspace) throw new Error("Selected workspace was not found");
|
|
12825
|
+
return useWorkspace(client, platformUrl, workspace, options);
|
|
12826
|
+
}
|
|
12827
|
+
function invalidCreateOptionsError(options, name, region, details) {
|
|
12828
|
+
return createCLIError({
|
|
12829
|
+
code: "WORKSPACE_CREATE_OPTIONS_INVALID",
|
|
12830
|
+
message: "Workspace creation options are invalid.",
|
|
12831
|
+
details,
|
|
12832
|
+
suggestion: "Correct the workspace creation options and rerun deploy.",
|
|
12833
|
+
next: executableAction(createDeployArgs(options, name, region))
|
|
12834
|
+
});
|
|
12835
|
+
}
|
|
12836
|
+
async function createWorkspace(client, platformUrl, options, availableRegions, validatedOptions) {
|
|
12837
|
+
let name = options.workspaceName;
|
|
12838
|
+
let region = options.workspaceRegion;
|
|
12839
|
+
const interactive = canPrompt();
|
|
12840
|
+
if (interactive) {
|
|
12841
|
+
name ??= await prompt.text({
|
|
12842
|
+
message: "Workspace name",
|
|
12843
|
+
default: suggestedWorkspaceName(),
|
|
12844
|
+
validate: validateWorkspaceName
|
|
12845
|
+
});
|
|
12846
|
+
region ??= await prompt.select({
|
|
12847
|
+
message: "Workspace region",
|
|
12848
|
+
choices: availableRegions.map((value) => ({
|
|
12849
|
+
name: value,
|
|
12850
|
+
value
|
|
12851
|
+
}))
|
|
12852
|
+
});
|
|
12853
|
+
} else {
|
|
12854
|
+
const missingOptions = [...name === void 0 ? ["--workspace-name"] : [], ...region === void 0 ? ["--workspace-region"] : []];
|
|
12855
|
+
if (missingOptions.length > 0) throw createCLIError({
|
|
12856
|
+
code: "WORKSPACE_CREATE_OPTIONS_REQUIRED",
|
|
12857
|
+
message: "Workspace creation requires a name and region in non-interactive mode.",
|
|
12858
|
+
suggestion: "Provide every workspace creation option and rerun deploy.",
|
|
12859
|
+
next: executableAction(createDeployArgs(options, name ?? "<name>", region ?? "<region>")),
|
|
12860
|
+
context: { missingOptions }
|
|
12861
|
+
});
|
|
12862
|
+
}
|
|
12863
|
+
if (!name || !region) throw new Error("Workspace creation options were not resolved");
|
|
12864
|
+
let validated = validatedOptions;
|
|
12865
|
+
if (!validated) {
|
|
12866
|
+
try {
|
|
12867
|
+
validated = validateCreateWorkspaceOptions({
|
|
12868
|
+
name,
|
|
12869
|
+
region,
|
|
12870
|
+
organizationId: options.organizationId,
|
|
12871
|
+
folderId: options.folderId
|
|
12872
|
+
});
|
|
12873
|
+
} catch (error) {
|
|
12874
|
+
throw invalidCreateOptionsError(options, name, region, error instanceof Error ? error.message : String(error));
|
|
12875
|
+
}
|
|
12876
|
+
if (!availableRegions.includes(region)) throw invalidCreateOptionsError(options, name, region, `Region must be one of: ${availableRegions.join(", ")}.`);
|
|
12877
|
+
}
|
|
12878
|
+
if (interactive) {
|
|
12879
|
+
const scope = [options.organizationId ? `organization: ${options.organizationId}` : void 0, options.folderId ? `folder: ${options.folderId}` : void 0].filter((value) => value !== void 0);
|
|
12880
|
+
if (!await prompt.confirm({
|
|
12881
|
+
message: `Create workspace "${name}" in ${region}${scope.length > 0 ? ` (${scope.join(", ")})` : ""}?`,
|
|
12882
|
+
default: true
|
|
12883
|
+
})) throw createCLIError({
|
|
12884
|
+
code: "WORKSPACE_CREATION_CANCELLED",
|
|
12885
|
+
message: "Workspace creation was cancelled."
|
|
11509
12886
|
});
|
|
11510
12887
|
}
|
|
11511
|
-
|
|
11512
|
-
|
|
11513
|
-
|
|
11514
|
-
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
|
|
12888
|
+
let workspace;
|
|
12889
|
+
try {
|
|
12890
|
+
workspace = await createValidatedWorkspaceWithClient(client, validated);
|
|
12891
|
+
} catch (error) {
|
|
12892
|
+
if (error instanceof ConnectError && ![
|
|
12893
|
+
Code.Canceled,
|
|
12894
|
+
Code.Unknown,
|
|
12895
|
+
Code.DeadlineExceeded,
|
|
12896
|
+
Code.Aborted,
|
|
12897
|
+
Code.Internal,
|
|
12898
|
+
Code.Unavailable,
|
|
12899
|
+
Code.DataLoss
|
|
12900
|
+
].includes(error.code)) throw error;
|
|
12901
|
+
throw createCLIError({
|
|
12902
|
+
code: "WORKSPACE_CREATION_FAILED",
|
|
12903
|
+
message: "Workspace creation did not complete successfully.",
|
|
12904
|
+
details: error instanceof Error ? error.message : String(error),
|
|
12905
|
+
suggestion: "The outcome may be uncertain. List workspaces before retrying creation to avoid duplicates.",
|
|
12906
|
+
next: executableAction([
|
|
12907
|
+
"workspace",
|
|
12908
|
+
"list",
|
|
12909
|
+
...workspaceCommandArgs(options),
|
|
12910
|
+
"--json"
|
|
12911
|
+
])
|
|
11520
12912
|
});
|
|
11521
12913
|
}
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
}
|
|
11534
|
-
})));
|
|
11535
|
-
updates(UpdateAIGatewayRequestSchema, "AIGateway", aiGateway.changeSet.updates.map((item) => ({
|
|
11536
|
-
name: item.name,
|
|
11537
|
-
request: {
|
|
11538
|
-
...item.request,
|
|
11539
|
-
cors: void 0
|
|
11540
|
-
}
|
|
11541
|
-
})));
|
|
11542
|
-
const placeholderOriginReplacement = "https://placeholder.invalid";
|
|
11543
|
-
const substituteIdpReturnOrigins = (item) => {
|
|
11544
|
-
const request = item.request;
|
|
11545
|
-
const origins = request.userAuthPolicy?.allowedReturnOrigins;
|
|
11546
|
-
if (!Array.isArray(origins) || origins.length === 0) return item;
|
|
11547
|
-
const substituted = origins.map((origin) => typeof origin === "string" && /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]:url$/.test(origin) ? placeholderOriginReplacement : origin);
|
|
11548
|
-
return {
|
|
11549
|
-
...item,
|
|
11550
|
-
request: {
|
|
11551
|
-
...request,
|
|
11552
|
-
userAuthPolicy: {
|
|
11553
|
-
...request.userAuthPolicy,
|
|
11554
|
-
allowedReturnOrigins: substituted
|
|
11555
|
-
}
|
|
11556
|
-
}
|
|
11557
|
-
};
|
|
12914
|
+
await rememberWorkspaceContext({
|
|
12915
|
+
version: 1,
|
|
12916
|
+
platformUrl,
|
|
12917
|
+
workspaceId: workspace.id
|
|
12918
|
+
}, options);
|
|
12919
|
+
logger.success(`Created workspace: ${workspaceLabel(workspace)}`);
|
|
12920
|
+
logger.info(`Reuse this workspace with: tailor-sdk deploy --workspace-id ${workspace.id}`);
|
|
12921
|
+
logger.info(`Or set TAILOR_PLATFORM_WORKSPACE_ID=${workspace.id}.`);
|
|
12922
|
+
return {
|
|
12923
|
+
client,
|
|
12924
|
+
workspaceId: workspace.id
|
|
11558
12925
|
};
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
const
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
12926
|
+
}
|
|
12927
|
+
async function createWorkspaceForDeploy(client, platformUrl, options, requestedRegions, validatedOptions) {
|
|
12928
|
+
const regions = requestedRegions ?? (await client.listAvailableWorkspaceRegions({})).regions;
|
|
12929
|
+
if (options.dryRun) throw createCLIError({
|
|
12930
|
+
code: "WORKSPACE_CREATION_DISABLED_IN_DRY_RUN",
|
|
12931
|
+
message: "Dry-run cannot create the workspace required to build a deployment plan.",
|
|
12932
|
+
suggestion: "Create a workspace explicitly, then rerun the same dry-run with its workspace ID.",
|
|
12933
|
+
context: { availableRegions: regions }
|
|
12934
|
+
});
|
|
12935
|
+
return createWorkspace(client, platformUrl, options, regions, validatedOptions);
|
|
12936
|
+
}
|
|
12937
|
+
/**
|
|
12938
|
+
* Resolve or provision the workspace used by deploy.
|
|
12939
|
+
* Explicit configuration wins over project context and account discovery.
|
|
12940
|
+
* @param options - Deploy workspace selection and creation options
|
|
12941
|
+
* @returns Authenticated client and resolved workspace ID
|
|
12942
|
+
*/
|
|
12943
|
+
async function resolveDeployWorkspace(options = {}) {
|
|
12944
|
+
const createOptionNames = [options.workspaceName !== void 0 ? "--workspace-name" : void 0, options.workspaceRegion !== void 0 ? "--workspace-region" : void 0].filter((name) => name !== void 0);
|
|
12945
|
+
if (!options.createWorkspace && createOptionNames.length > 0) throw createCLIError({
|
|
12946
|
+
code: "WORKSPACE_CREATE_FLAG_REQUIRED",
|
|
12947
|
+
message: "Workspace creation options require --create-workspace.",
|
|
12948
|
+
suggestion: "Add --create-workspace or remove the workspace creation options.",
|
|
12949
|
+
next: executableAction(createDeployArgs(options, options.workspaceName ?? "<name>", options.workspaceRegion ?? "<region>")),
|
|
12950
|
+
context: { options: createOptionNames }
|
|
12951
|
+
});
|
|
12952
|
+
if (options.createWorkspace && options.workspaceName !== void 0) {
|
|
12953
|
+
const nameValidation = validateWorkspaceName(options.workspaceName);
|
|
12954
|
+
if (nameValidation !== true) throw invalidCreateOptionsError(options, options.workspaceName, options.workspaceRegion ?? "<region>", nameValidation);
|
|
12955
|
+
}
|
|
12956
|
+
if (options.createWorkspace && options.workspaceRegion === "") throw invalidCreateOptionsError(options, options.workspaceName ?? "<name>", options.workspaceRegion, "Region must not be empty.");
|
|
12957
|
+
const explicitWorkspaceId = await tryLoadWorkspaceId({
|
|
12958
|
+
workspaceId: options.workspaceId,
|
|
12959
|
+
profile: options.profile
|
|
12960
|
+
});
|
|
12961
|
+
const accessToken = await loadAccessToken({ profile: options.profile });
|
|
12962
|
+
const platformConfig = await loadPlatformClientConfig({
|
|
12963
|
+
profile: options.profile,
|
|
12964
|
+
allowMissingProfile: explicitWorkspaceId !== void 0
|
|
12965
|
+
});
|
|
12966
|
+
const platformUrl = getPlatformBaseUrl(platformConfig);
|
|
12967
|
+
const client = await initOperatorClient(accessToken, platformConfig);
|
|
12968
|
+
if (explicitWorkspaceId) {
|
|
12969
|
+
let response;
|
|
12970
|
+
try {
|
|
12971
|
+
response = await client.getWorkspace({ workspaceId: explicitWorkspaceId });
|
|
12972
|
+
} catch (error) {
|
|
12973
|
+
if (error instanceof ConnectError && error.code === Code.NotFound) throw createCLIError({
|
|
12974
|
+
code: "WORKSPACE_NOT_FOUND",
|
|
12975
|
+
message: `Workspace "${explicitWorkspaceId}" was not found.`
|
|
12976
|
+
});
|
|
12977
|
+
throw error;
|
|
11583
12978
|
}
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
12979
|
+
if (!response.workspace) throw createCLIError({
|
|
12980
|
+
code: "WORKSPACE_NOT_FOUND",
|
|
12981
|
+
message: `Workspace "${explicitWorkspaceId}" was not found.`
|
|
12982
|
+
});
|
|
12983
|
+
return useWorkspace(client, platformUrl, workspaceInfo(response.workspace), options, "warn");
|
|
12984
|
+
}
|
|
12985
|
+
let requestedCreateOptions;
|
|
12986
|
+
if (options.createWorkspace && options.workspaceName !== void 0 && options.workspaceRegion !== void 0) try {
|
|
12987
|
+
requestedCreateOptions = validateCreateWorkspaceOptions({
|
|
12988
|
+
name: options.workspaceName,
|
|
12989
|
+
region: options.workspaceRegion,
|
|
12990
|
+
organizationId: options.organizationId,
|
|
12991
|
+
folderId: options.folderId
|
|
12992
|
+
});
|
|
12993
|
+
} catch (error) {
|
|
12994
|
+
throw invalidCreateOptionsError(options, options.workspaceName, options.workspaceRegion, error instanceof Error ? error.message : String(error));
|
|
12995
|
+
}
|
|
12996
|
+
const [contexts, workspaces] = await Promise.all([loadProjectContexts(platformUrl, options.contextTargets), listWorkspacesWithClient(client)]);
|
|
12997
|
+
const interactive = canPrompt();
|
|
12998
|
+
const contextWorkspaceIds = new Set(contexts.map(({ workspaceId }) => workspaceId));
|
|
12999
|
+
const linkedWorkspace = contextWorkspaceIds.size === 1 ? workspaces.find(({ id }) => contextWorkspaceIds.has(id)) : void 0;
|
|
13000
|
+
if (linkedWorkspace && (!options.createWorkspace || workspaceMatchesRequestedIdentity(linkedWorkspace, options))) {
|
|
13001
|
+
const contextTargetCount = projectContextTargets(options.contextTargets)?.length ?? 1;
|
|
13002
|
+
return contexts.length === contextTargetCount ? useRememberedWorkspace(client, linkedWorkspace) : useWorkspace(client, platformUrl, linkedWorkspace, options);
|
|
13003
|
+
}
|
|
13004
|
+
const onlyWorkspace = workspaces.length === 1 ? workspaces[0] : void 0;
|
|
13005
|
+
const canReuseOnlyWorkspace = onlyWorkspace !== void 0 && requestedCreateOptions !== void 0 && workspaceMatchesRequestedIdentity(onlyWorkspace, options);
|
|
13006
|
+
let requestedRegions;
|
|
13007
|
+
if (requestedCreateOptions && !canReuseOnlyWorkspace) {
|
|
13008
|
+
requestedRegions = await client.listAvailableWorkspaceRegions({});
|
|
13009
|
+
if (!requestedRegions.regions.includes(requestedCreateOptions.region)) throw invalidCreateOptionsError(options, requestedCreateOptions.name, requestedCreateOptions.region, `Region must be one of: ${requestedRegions.regions.join(", ")}.`);
|
|
13010
|
+
}
|
|
13011
|
+
if (workspaces.length === 0 && (interactive || options.createWorkspace || contexts.length === 0)) {
|
|
13012
|
+
const regions = requestedRegions?.regions ?? (await client.listAvailableWorkspaceRegions({})).regions;
|
|
13013
|
+
if (options.dryRun || interactive || options.createWorkspace) return createWorkspaceForDeploy(client, platformUrl, options, regions, requestedCreateOptions);
|
|
13014
|
+
throw createCLIError({
|
|
13015
|
+
code: "WORKSPACE_NOT_FOUND",
|
|
13016
|
+
message: "No workspaces are available for this account.",
|
|
13017
|
+
suggestion: "Create one during deploy by providing the workspace name and one of the available regions.",
|
|
13018
|
+
next: executableAction(createDeployArgs(options, "<name>", "<region>")),
|
|
13019
|
+
context: { availableRegions: regions }
|
|
13020
|
+
});
|
|
13021
|
+
}
|
|
13022
|
+
if (contextWorkspaceIds.size > 1) {
|
|
13023
|
+
const canReplaceStaleContexts = options.createWorkspace === true && options.workspaceName !== void 0 && options.workspaceRegion !== void 0 && workspaces.length === 0;
|
|
13024
|
+
if (!interactive && !canReplaceStaleContexts) throw createCLIError({
|
|
13025
|
+
code: "WORKSPACE_CONTEXT_CONFLICT",
|
|
13026
|
+
message: "The deployed configuration files are linked to different workspaces.",
|
|
13027
|
+
suggestion: "Choose one workspace explicitly for the combined deployment.",
|
|
13028
|
+
next: executableAction(selectDeployArgs(options)),
|
|
13029
|
+
context: { savedWorkspaceIds: [...contextWorkspaceIds] }
|
|
13030
|
+
});
|
|
13031
|
+
if (workspaces.length > 0 && !options.createWorkspace) return chooseWorkspace(client, platformUrl, workspaces, options);
|
|
13032
|
+
}
|
|
13033
|
+
if (!linkedWorkspace && contexts.length > 0) {
|
|
13034
|
+
const explicitlyEnsuringSingleTarget = options.createWorkspace === true && options.workspaceName !== void 0 && options.workspaceRegion !== void 0 && workspaces.length <= 1;
|
|
13035
|
+
if (!interactive && !explicitlyEnsuringSingleTarget) throw createCLIError({
|
|
13036
|
+
code: "WORKSPACE_CONTEXT_STALE",
|
|
13037
|
+
message: "The saved project workspace is no longer available.",
|
|
13038
|
+
suggestion: "Choose an available workspace explicitly before deploying.",
|
|
13039
|
+
next: executableAction(selectDeployArgs(options)),
|
|
13040
|
+
context: {
|
|
13041
|
+
savedWorkspaceIds: [...contextWorkspaceIds],
|
|
13042
|
+
workspaces: workspaces.map(workspaceIdentity)
|
|
11629
13043
|
}
|
|
11630
|
-
})
|
|
13044
|
+
});
|
|
13045
|
+
if (workspaces.length > 0 && interactive && !options.createWorkspace) return chooseWorkspace(client, platformUrl, workspaces, options);
|
|
13046
|
+
}
|
|
13047
|
+
if (workspaces.length === 1) {
|
|
13048
|
+
const [workspace] = workspaces;
|
|
13049
|
+
if (!workspace) throw new Error("Workspace discovery returned an invalid result");
|
|
13050
|
+
if (options.createWorkspace && !workspaceMatchesRequestedIdentity(workspace, options)) throw createCLIError({
|
|
13051
|
+
code: "WORKSPACE_CREATE_CONFLICT",
|
|
13052
|
+
message: "The existing workspace does not match the requested workspace.",
|
|
13053
|
+
suggestion: "Use the existing workspace, or run the explicit workspace create command to create another one.",
|
|
13054
|
+
next: executableAction([
|
|
13055
|
+
"workspace",
|
|
13056
|
+
"create",
|
|
13057
|
+
...workspaceCommandArgs(options),
|
|
13058
|
+
...options.workspaceCommandJson ? ["--json"] : [],
|
|
13059
|
+
"--name",
|
|
13060
|
+
options.workspaceName ?? "<name>",
|
|
13061
|
+
"--region",
|
|
13062
|
+
options.workspaceRegion ?? "<region>",
|
|
13063
|
+
...options.organizationId ? ["--organization-id", options.organizationId] : [],
|
|
13064
|
+
...options.folderId ? ["--folder-id", options.folderId] : []
|
|
13065
|
+
]),
|
|
13066
|
+
context: { existingWorkspace: workspaceIdentity(workspace) }
|
|
13067
|
+
});
|
|
13068
|
+
if (interactive && !options.createWorkspace) return chooseWorkspace(client, platformUrl, workspaces, options);
|
|
13069
|
+
return useWorkspace(client, platformUrl, workspace, options);
|
|
13070
|
+
}
|
|
13071
|
+
if (workspaces.length > 1) {
|
|
13072
|
+
if (!interactive) throw createCLIError({
|
|
13073
|
+
code: "WORKSPACE_SELECTION_REQUIRED",
|
|
13074
|
+
message: "Multiple workspaces are available.",
|
|
13075
|
+
suggestion: "Choose one explicitly for non-interactive deployment.",
|
|
13076
|
+
next: executableAction(selectDeployArgs(options)),
|
|
13077
|
+
context: { workspaces: workspaces.map(workspaceIdentity) }
|
|
13078
|
+
});
|
|
13079
|
+
return chooseWorkspace(client, platformUrl, workspaces, options);
|
|
11631
13080
|
}
|
|
11632
|
-
|
|
11633
|
-
name: item.name,
|
|
11634
|
-
request: {
|
|
11635
|
-
workspaceId: item.workspaceId,
|
|
11636
|
-
executionPolicyName: item.policy.name,
|
|
11637
|
-
executionPolicyKey: toPlatformExecutionPolicyKey(item.policy),
|
|
11638
|
-
...item.policy.concurrencyPolicy && { concurrencyPolicy: { maxConcurrentExecutions: item.policy.concurrencyPolicy.maxConcurrentExecutions } }
|
|
11639
|
-
}
|
|
11640
|
-
})));
|
|
11641
|
-
updates(UpdateWorkflowJobFunctionExecutionPolicyRequestSchema, "Workflow execution policy", workflowExecutionPolicy.changeSet.updates.map((item) => ({
|
|
11642
|
-
name: item.name,
|
|
11643
|
-
request: {
|
|
11644
|
-
workspaceId: item.workspaceId,
|
|
11645
|
-
executionPolicyName: item.policy.name,
|
|
11646
|
-
...item.policy.concurrencyPolicy && { concurrencyPolicy: { maxConcurrentExecutions: item.policy.concurrencyPolicy.maxConcurrentExecutions } }
|
|
11647
|
-
}
|
|
11648
|
-
})));
|
|
11649
|
-
creates(CreateSecretManagerVaultRequestSchema, "Secret Manager vault", secretManager.vaultChangeSet.creates.map((item) => ({
|
|
11650
|
-
name: item.name,
|
|
11651
|
-
request: vaultCreateRequest(item)
|
|
11652
|
-
})));
|
|
11653
|
-
creates(CreateSecretManagerSecretRequestSchema, "Secret Manager secret", secretManager.secretChangeSet.creates.map((item) => ({
|
|
11654
|
-
name: item.name,
|
|
11655
|
-
request: secretCreateRequest(item)
|
|
11656
|
-
})));
|
|
11657
|
-
updates(UpdateSecretManagerSecretRequestSchema, "Secret Manager secret", secretManager.secretChangeSet.updates.map((item) => ({
|
|
11658
|
-
name: item.name,
|
|
11659
|
-
request: secretUpdateRequest(item)
|
|
11660
|
-
})));
|
|
11661
|
-
creates(CreateApplicationRequestSchema, "Application", app.creates.map((item) => ({
|
|
11662
|
-
name: item.name,
|
|
11663
|
-
request: {
|
|
11664
|
-
...item.request,
|
|
11665
|
-
cors: void 0
|
|
11666
|
-
}
|
|
11667
|
-
})));
|
|
11668
|
-
updates(UpdateApplicationRequestSchema, "Application", [...app.updates, ...app.unchanged].map((item) => ({
|
|
11669
|
-
name: item.name,
|
|
11670
|
-
request: {
|
|
11671
|
-
...item.request,
|
|
11672
|
-
cors: void 0
|
|
11673
|
-
}
|
|
11674
|
-
})));
|
|
11675
|
-
creates(CreateAuthConnectionRequestSchema, "Auth connection", auth.changeSet.connection.creates);
|
|
11676
|
-
inPlaceReplaces(UpdateAuthConnectionRequestSchema, "Auth connection", auth.changeSet.connection.replaces);
|
|
11677
|
-
if (violations.length === 0) return;
|
|
11678
|
-
const resourceNames = new Set(violations.map((v) => `${v.kind}:${v.name}`));
|
|
11679
|
-
logger.error(`Pre-flight validation found ${violations.length} violation(s) across ${resourceNames.size} resource(s):`);
|
|
11680
|
-
for (const v of violations) logger.log(` ${styles.resourceType(v.kind)} ${styles.resourceName(v.name)} (${v.action}) — ${styles.bold(v.fieldPath)}: ${v.message}`);
|
|
11681
|
-
throw new Error(`${violations.length} validation error(s) found in ${resourceNames.size} resource(s)`);
|
|
13081
|
+
throw new Error("Workspace discovery returned an invalid result");
|
|
11682
13082
|
}
|
|
11683
13083
|
|
|
11684
13084
|
//#endregion
|
|
@@ -12098,19 +13498,40 @@ function parseDeployConfigPaths(configPath) {
|
|
|
12098
13498
|
if (configPaths.some((entry) => entry.length === 0)) throw new Error("--config must contain one or more non-empty config paths.");
|
|
12099
13499
|
return configPaths;
|
|
12100
13500
|
}
|
|
13501
|
+
function recoveryEnvironmentArgs(options, cliContext) {
|
|
13502
|
+
return [
|
|
13503
|
+
...cliContext?.envFile ? ["--env-file", path.resolve(process.cwd(), cliContext.envFile)] : [],
|
|
13504
|
+
...cliContext?.envFileIfExists ? ["--env-file-if-exists", path.resolve(process.cwd(), cliContext.envFileIfExists)] : [],
|
|
13505
|
+
...options?.profile ? ["--profile", options.profile] : []
|
|
13506
|
+
];
|
|
13507
|
+
}
|
|
13508
|
+
function recoveryOutputArgs(cliContext) {
|
|
13509
|
+
return [...cliContext?.verbose ? ["--verbose"] : [], ...cliContext?.json || logger.jsonMode ? ["--json"] : []];
|
|
13510
|
+
}
|
|
13511
|
+
function retryDeployArgs(options, configPaths, cliContext) {
|
|
13512
|
+
return [
|
|
13513
|
+
"deploy",
|
|
13514
|
+
"--config",
|
|
13515
|
+
configPaths.join(","),
|
|
13516
|
+
...recoveryEnvironmentArgs(options, cliContext),
|
|
13517
|
+
...options?.dryRun ? ["--dry-run"] : [],
|
|
13518
|
+
...options?.yes ? ["--yes"] : [],
|
|
13519
|
+
...options?.noSchemaCheck ? ["--no-schema-check"] : [],
|
|
13520
|
+
...options?.noValidate ? ["--no-validate"] : [],
|
|
13521
|
+
...options?.noCache ? ["--no-cache"] : [],
|
|
13522
|
+
...options?.cleanCache ? ["--clean-cache"] : [],
|
|
13523
|
+
...recoveryOutputArgs(cliContext)
|
|
13524
|
+
];
|
|
13525
|
+
}
|
|
13526
|
+
function workspaceRecoveryArgs(options, cliContext) {
|
|
13527
|
+
return [...recoveryEnvironmentArgs(options, cliContext), ...cliContext?.verbose ? ["--verbose"] : []];
|
|
13528
|
+
}
|
|
12101
13529
|
async function buildDeploymentTarget(params) {
|
|
12102
|
-
const { configPath, dryRun, buildOnly, noCache, packageVersion, cacheDir } = params;
|
|
12103
|
-
const { config, plugins } =
|
|
12104
|
-
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
if (fs$1.existsSync(resolvedPath)) await ensureConfigIdForDeploy({
|
|
12108
|
-
configPath: resolvedPath,
|
|
12109
|
-
dryRun,
|
|
12110
|
-
buildOnly
|
|
12111
|
-
});
|
|
12112
|
-
}
|
|
12113
|
-
return loadConfig(configPath);
|
|
13530
|
+
const { configPath, loadedConfig, dryRun, buildOnly, noCache, packageVersion, cacheDir } = params;
|
|
13531
|
+
const { config, plugins } = loadedConfig ?? await loadDeployConfig({
|
|
13532
|
+
configPath,
|
|
13533
|
+
dryRun,
|
|
13534
|
+
buildOnly
|
|
12114
13535
|
});
|
|
12115
13536
|
const configDir = path.dirname(config.path);
|
|
12116
13537
|
const lockfilePath = findUpSync("pnpm-lock.yaml", { cwd: configDir }) ?? findUpSync("package-lock.json", { cwd: configDir }) ?? findUpSync("yarn.lock", { cwd: configDir }) ?? findUpSync("bun.lock", { cwd: configDir });
|
|
@@ -12151,11 +13572,27 @@ async function buildDeploymentTarget(params) {
|
|
|
12151
13572
|
bundledScripts
|
|
12152
13573
|
};
|
|
12153
13574
|
}
|
|
13575
|
+
async function loadDeployConfig(params) {
|
|
13576
|
+
const { configPath, dryRun, buildOnly } = params;
|
|
13577
|
+
return withSpan("build.loadConfig", async () => {
|
|
13578
|
+
const foundPath = loadConfigPath(configPath);
|
|
13579
|
+
if (foundPath) {
|
|
13580
|
+
const resolvedPath = path.resolve(process.cwd(), foundPath);
|
|
13581
|
+
if (fs$2.existsSync(resolvedPath)) await ensureConfigIdForDeploy({
|
|
13582
|
+
configPath: resolvedPath,
|
|
13583
|
+
dryRun,
|
|
13584
|
+
buildOnly
|
|
13585
|
+
});
|
|
13586
|
+
}
|
|
13587
|
+
return loadConfig(configPath);
|
|
13588
|
+
});
|
|
13589
|
+
}
|
|
12154
13590
|
async function buildDeploymentTargets(params) {
|
|
12155
|
-
const { configPaths, buildTarget = buildDeploymentTarget, ...targetParams } = params;
|
|
12156
|
-
return Promise.all(configPaths.map((configPath) => buildTarget({
|
|
13591
|
+
const { configPaths, loadedConfigs, buildTarget = buildDeploymentTarget, ...targetParams } = params;
|
|
13592
|
+
return Promise.all(configPaths.map((configPath, index) => buildTarget({
|
|
12157
13593
|
...targetParams,
|
|
12158
|
-
configPath
|
|
13594
|
+
configPath,
|
|
13595
|
+
loadedConfig: loadedConfigs?.[index]
|
|
12159
13596
|
})));
|
|
12160
13597
|
}
|
|
12161
13598
|
function setBundledScript(target, name, code, kind) {
|
|
@@ -12734,45 +14171,65 @@ async function validateDeploymentPlans(deployments) {
|
|
|
12734
14171
|
}
|
|
12735
14172
|
/**
|
|
12736
14173
|
* Deploy the configured application to the Tailor platform.
|
|
12737
|
-
* @param options -
|
|
14174
|
+
* @param options - Deploy execution options
|
|
14175
|
+
* @param cliContext - Global CLI arguments to preserve in recovery actions
|
|
12738
14176
|
* @returns Promise that resolves to `{ bundledScripts }` when `buildOnly` is true, otherwise void
|
|
12739
14177
|
*/
|
|
12740
|
-
async function
|
|
14178
|
+
async function deployInternal(options, cliContext) {
|
|
12741
14179
|
return withSpan("deploy", async (rootSpan) => {
|
|
12742
14180
|
rootSpan.setAttribute("deploy.dry_run", options?.dryRun ?? false);
|
|
12743
14181
|
const configPaths = parseDeployConfigPaths(options?.configPath);
|
|
12744
|
-
const
|
|
12745
|
-
|
|
12746
|
-
|
|
14182
|
+
const dryRun = options?.dryRun ?? false;
|
|
14183
|
+
const buildOnly = options?.buildOnly ?? parseBoolean(process.env.TAILOR_DEPLOY_BUILD_ONLY) === true;
|
|
14184
|
+
const preflightConfigs = buildOnly ? [] : await withSpan("config.preflight", () => Promise.all(configPaths.map((configPath) => loadDeployConfig({
|
|
14185
|
+
configPath,
|
|
14186
|
+
dryRun,
|
|
14187
|
+
buildOnly
|
|
14188
|
+
}))));
|
|
14189
|
+
const resolvedConfigPaths = preflightConfigs.map(({ config }) => config.path);
|
|
14190
|
+
const workspaceContextTargets = preflightConfigs.map(({ config }) => ({
|
|
14191
|
+
configPath: config.path,
|
|
14192
|
+
applicationId: config.id ?? `name:${config.name}`
|
|
14193
|
+
}));
|
|
14194
|
+
const workspace = buildOnly ? void 0 : await resolveDeployWorkspace({
|
|
14195
|
+
workspaceId: options?.workspaceId,
|
|
14196
|
+
profile: options?.profile,
|
|
14197
|
+
createWorkspace: options?.createWorkspace,
|
|
14198
|
+
workspaceName: options?.workspaceName,
|
|
14199
|
+
workspaceRegion: options?.workspaceRegion,
|
|
14200
|
+
organizationId: options?.organizationId,
|
|
14201
|
+
folderId: options?.folderId,
|
|
14202
|
+
dryRun,
|
|
14203
|
+
contextTargets: workspaceContextTargets,
|
|
14204
|
+
deployArgs: retryDeployArgs(options, resolvedConfigPaths, cliContext),
|
|
14205
|
+
workspaceCommandArgs: workspaceRecoveryArgs(options, cliContext),
|
|
14206
|
+
workspaceCommandJson: cliContext?.json || logger.jsonMode
|
|
14207
|
+
});
|
|
14208
|
+
const targets = await withSpan("build", async () => {
|
|
12747
14209
|
const noCache = options?.noCache ?? false;
|
|
12748
14210
|
const packageJson = await readPackageJson();
|
|
12749
14211
|
const cacheDir = path.resolve(getDistDir(), "cache");
|
|
12750
14212
|
if (options?.cleanCache) {
|
|
12751
|
-
fs$
|
|
14213
|
+
fs$2.rmSync(cacheDir, {
|
|
12752
14214
|
recursive: true,
|
|
12753
14215
|
force: true
|
|
12754
14216
|
});
|
|
12755
14217
|
logger.info("Bundle cache cleaned");
|
|
12756
14218
|
}
|
|
12757
|
-
return {
|
|
12758
|
-
|
|
12759
|
-
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
buildOnly
|
|
12767
|
-
};
|
|
14219
|
+
return await buildDeploymentTargets({
|
|
14220
|
+
configPaths,
|
|
14221
|
+
loadedConfigs: preflightConfigs,
|
|
14222
|
+
dryRun,
|
|
14223
|
+
buildOnly,
|
|
14224
|
+
noCache,
|
|
14225
|
+
packageVersion: packageJson.version ?? "unknown",
|
|
14226
|
+
cacheDir
|
|
14227
|
+
});
|
|
12768
14228
|
});
|
|
12769
14229
|
if (buildOnly) return { bundledScripts: mergeBundledScripts(targets) };
|
|
12770
|
-
assertUniqueGlobalResourceNames(targets);
|
|
12771
|
-
|
|
12772
|
-
const workspaceId =
|
|
12773
|
-
workspaceId: options?.workspaceId,
|
|
12774
|
-
profile: options?.profile
|
|
12775
|
-
});
|
|
14230
|
+
assertUniqueGlobalResourceNames(targets);
|
|
14231
|
+
if (!workspace) throw new Error("Workspace was not resolved");
|
|
14232
|
+
const { client, workspaceId } = workspace;
|
|
12776
14233
|
rootSpan.setAttribute("app.name", targets.map((target) => target.application.name).join(","));
|
|
12777
14234
|
rootSpan.setAttribute("workspace.id", workspaceId);
|
|
12778
14235
|
const deployments = await planDeploymentTargets({
|
|
@@ -12782,7 +14239,6 @@ async function deploy(options) {
|
|
|
12782
14239
|
workspaceId,
|
|
12783
14240
|
noSchemaCheck: options?.noSchemaCheck
|
|
12784
14241
|
});
|
|
12785
|
-
const dryRun = options?.dryRun ?? false;
|
|
12786
14242
|
const yes = options?.yes ?? false;
|
|
12787
14243
|
dropCrossDeploymentManagedDeletes(deployments);
|
|
12788
14244
|
await withSpan("confirm", async () => {
|
|
@@ -12806,6 +14262,23 @@ async function deploy(options) {
|
|
|
12806
14262
|
else logger.success("Successfully applied changes.");
|
|
12807
14263
|
});
|
|
12808
14264
|
}
|
|
14265
|
+
/**
|
|
14266
|
+
* Deploy using the programmatic CLI API.
|
|
14267
|
+
* @param options - Deploy execution options
|
|
14268
|
+
* @returns Deploy result
|
|
14269
|
+
*/
|
|
14270
|
+
function deploy(options) {
|
|
14271
|
+
return deployInternal(options);
|
|
14272
|
+
}
|
|
14273
|
+
/**
|
|
14274
|
+
* Deploy from the command adapter while preserving global CLI arguments in recovery actions.
|
|
14275
|
+
* @param options - Deploy execution options
|
|
14276
|
+
* @param cliContext - Global CLI arguments already applied by the command runner
|
|
14277
|
+
* @returns Deploy result
|
|
14278
|
+
*/
|
|
14279
|
+
function deployFromCLI(options, cliContext) {
|
|
14280
|
+
return deployInternal(options, cliContext);
|
|
14281
|
+
}
|
|
12809
14282
|
|
|
12810
14283
|
//#endregion
|
|
12811
14284
|
//#region src/cli/commands/executor/status.ts
|
|
@@ -13195,52 +14668,6 @@ const getCommand$6 = defineAppCommand({
|
|
|
13195
14668
|
}
|
|
13196
14669
|
});
|
|
13197
14670
|
|
|
13198
|
-
//#endregion
|
|
13199
|
-
//#region src/cli/shared/format.ts
|
|
13200
|
-
/**
|
|
13201
|
-
* Format a protobuf Timestamp to ISO string.
|
|
13202
|
-
* @param timestamp - Protobuf timestamp
|
|
13203
|
-
* @returns Date object or null if invalid
|
|
13204
|
-
*/
|
|
13205
|
-
function formatTimestamp(timestamp) {
|
|
13206
|
-
if (!timestamp) return null;
|
|
13207
|
-
const date = timestampDate(timestamp);
|
|
13208
|
-
if (Number.isNaN(date.getTime())) return null;
|
|
13209
|
-
return date;
|
|
13210
|
-
}
|
|
13211
|
-
/**
|
|
13212
|
-
* Formats a table with consistent single-line border style.
|
|
13213
|
-
* Use this instead of importing `table` directly.
|
|
13214
|
-
* @param data - Table data
|
|
13215
|
-
* @param config - Table configuration
|
|
13216
|
-
* @returns Formatted table string
|
|
13217
|
-
*/
|
|
13218
|
-
function formatTable(data, config) {
|
|
13219
|
-
return table(data, {
|
|
13220
|
-
...config,
|
|
13221
|
-
border: getBorderCharacters("norc")
|
|
13222
|
-
});
|
|
13223
|
-
}
|
|
13224
|
-
/**
|
|
13225
|
-
* Formats a key-value table with single-line border style.
|
|
13226
|
-
* @param data - Key-value pairs
|
|
13227
|
-
* @returns Formatted key-value table string
|
|
13228
|
-
*/
|
|
13229
|
-
function formatKeyValueTable(data) {
|
|
13230
|
-
return formatTable(data, { singleLine: true });
|
|
13231
|
-
}
|
|
13232
|
-
/**
|
|
13233
|
-
* Format a Date or ISO timestamp string as a human-readable relative time.
|
|
13234
|
-
* @param value - Date object, ISO date string, or null
|
|
13235
|
-
* @returns Relative time (e.g., "5 minutes ago") or "N/A" for null/invalid
|
|
13236
|
-
*/
|
|
13237
|
-
function humanizeRelativeTime(value) {
|
|
13238
|
-
if (value === null) return "N/A";
|
|
13239
|
-
const date = value instanceof Date ? value : new Date(value);
|
|
13240
|
-
if (Number.isNaN(date.getTime())) return typeof value === "string" ? value : "N/A";
|
|
13241
|
-
return formatDistanceToNowStrict(date, { addSuffix: true });
|
|
13242
|
-
}
|
|
13243
|
-
|
|
13244
14671
|
//#endregion
|
|
13245
14672
|
//#region src/cli/shared/function-execution.ts
|
|
13246
14673
|
/**
|
|
@@ -14657,7 +16084,7 @@ async function listExecutors(options) {
|
|
|
14657
16084
|
return [executors, nextPageToken];
|
|
14658
16085
|
}, { limit: options?.limit })).map((e) => toExecutorListInfo(e));
|
|
14659
16086
|
}
|
|
14660
|
-
const listCommand$
|
|
16087
|
+
const listCommand$8 = defineAppCommand({
|
|
14661
16088
|
name: "list",
|
|
14662
16089
|
description: "List all executors",
|
|
14663
16090
|
args: z.strictObject({
|
|
@@ -15082,7 +16509,7 @@ async function listFunctionRegistries(options) {
|
|
|
15082
16509
|
}
|
|
15083
16510
|
}, { limit })).map(functionRegistryInfo);
|
|
15084
16511
|
}
|
|
15085
|
-
const listCommand$
|
|
16512
|
+
const listCommand$7 = defineAppCommand({
|
|
15086
16513
|
name: "list",
|
|
15087
16514
|
description: "List function registries in a workspace",
|
|
15088
16515
|
args: z.strictObject({
|
|
@@ -15106,387 +16533,6 @@ const listCommand$8 = defineAppCommand({
|
|
|
15106
16533
|
}
|
|
15107
16534
|
});
|
|
15108
16535
|
|
|
15109
|
-
//#endregion
|
|
15110
|
-
//#region src/cli/commands/generate/watch/index.ts
|
|
15111
|
-
/**
|
|
15112
|
-
* Error codes.
|
|
15113
|
-
*/
|
|
15114
|
-
const WatcherErrorCode = {
|
|
15115
|
-
DEPENDENCY_ANALYSIS_FAILED: "DEPENDENCY_ANALYSIS_FAILED",
|
|
15116
|
-
FILE_WATCH_FAILED: "FILE_WATCH_FAILED",
|
|
15117
|
-
CIRCULAR_DEPENDENCY_DETECTED: "CIRCULAR_DEPENDENCY_DETECTED",
|
|
15118
|
-
INVALID_WATCH_GROUP: "INVALID_WATCH_GROUP",
|
|
15119
|
-
MADGE_INITIALIZATION_FAILED: "MADGE_INITIALIZATION_FAILED"
|
|
15120
|
-
};
|
|
15121
|
-
/**
|
|
15122
|
-
* Watcher-specific error.
|
|
15123
|
-
*/
|
|
15124
|
-
var WatcherError = class extends Error {
|
|
15125
|
-
code;
|
|
15126
|
-
filePath;
|
|
15127
|
-
originalError;
|
|
15128
|
-
constructor(message, code, filePath, originalError) {
|
|
15129
|
-
super(message);
|
|
15130
|
-
this.code = code;
|
|
15131
|
-
this.filePath = filePath;
|
|
15132
|
-
this.originalError = originalError;
|
|
15133
|
-
this.name = "WatcherError";
|
|
15134
|
-
}
|
|
15135
|
-
};
|
|
15136
|
-
/**
|
|
15137
|
-
* Creates a dependency graph manager.
|
|
15138
|
-
* @param options - Options for madge
|
|
15139
|
-
* @returns DependencyGraphManager instance
|
|
15140
|
-
*/
|
|
15141
|
-
function createDependencyGraphManager(options = {}) {
|
|
15142
|
-
const graph = /* @__PURE__ */ new Map();
|
|
15143
|
-
let madgeInstance = null;
|
|
15144
|
-
let madgeLoader = null;
|
|
15145
|
-
function getMadgeLoader() {
|
|
15146
|
-
if (madgeLoader) return madgeLoader;
|
|
15147
|
-
const defaultExport = madgeModule.default;
|
|
15148
|
-
if (typeof defaultExport === "function") {
|
|
15149
|
-
madgeLoader = defaultExport;
|
|
15150
|
-
return madgeLoader;
|
|
15151
|
-
}
|
|
15152
|
-
if (typeof madgeModule === "function") {
|
|
15153
|
-
madgeLoader = madgeModule;
|
|
15154
|
-
return madgeLoader;
|
|
15155
|
-
}
|
|
15156
|
-
throw new WatcherError("Failed to initialize madge analyzer: module did not export a callable function.", WatcherErrorCode.MADGE_INITIALIZATION_FAILED);
|
|
15157
|
-
}
|
|
15158
|
-
function traverseDependents(filePath, visited) {
|
|
15159
|
-
if (visited.has(filePath)) return [];
|
|
15160
|
-
visited.add(filePath);
|
|
15161
|
-
const node = graph.get(filePath);
|
|
15162
|
-
if (!node) return [];
|
|
15163
|
-
const result = [];
|
|
15164
|
-
for (const dependent of node.dependents) {
|
|
15165
|
-
result.push(dependent);
|
|
15166
|
-
result.push(...traverseDependents(dependent, visited));
|
|
15167
|
-
}
|
|
15168
|
-
return result;
|
|
15169
|
-
}
|
|
15170
|
-
function traverseDependencies(filePath, visited) {
|
|
15171
|
-
if (visited.has(filePath)) return [];
|
|
15172
|
-
visited.add(filePath);
|
|
15173
|
-
const node = graph.get(filePath);
|
|
15174
|
-
if (!node) return [];
|
|
15175
|
-
const result = [];
|
|
15176
|
-
for (const dependency of node.dependencies) {
|
|
15177
|
-
result.push(dependency);
|
|
15178
|
-
result.push(...traverseDependencies(dependency, visited));
|
|
15179
|
-
}
|
|
15180
|
-
return result;
|
|
15181
|
-
}
|
|
15182
|
-
function addNode(filePath) {
|
|
15183
|
-
const absolutePath = path.resolve(filePath);
|
|
15184
|
-
if (!graph.has(absolutePath)) graph.set(absolutePath, {
|
|
15185
|
-
filePath: absolutePath,
|
|
15186
|
-
dependencies: /* @__PURE__ */ new Set(),
|
|
15187
|
-
dependents: /* @__PURE__ */ new Set()
|
|
15188
|
-
});
|
|
15189
|
-
}
|
|
15190
|
-
function removeNode(filePath) {
|
|
15191
|
-
const absolutePath = path.resolve(filePath);
|
|
15192
|
-
const node = graph.get(absolutePath);
|
|
15193
|
-
if (!node) return;
|
|
15194
|
-
for (const dep of node.dependencies) {
|
|
15195
|
-
const depNode = graph.get(dep);
|
|
15196
|
-
if (depNode) depNode.dependents.delete(absolutePath);
|
|
15197
|
-
}
|
|
15198
|
-
for (const dependent of node.dependents) {
|
|
15199
|
-
const dependentNode = graph.get(dependent);
|
|
15200
|
-
if (dependentNode) dependentNode.dependencies.delete(absolutePath);
|
|
15201
|
-
}
|
|
15202
|
-
graph.delete(absolutePath);
|
|
15203
|
-
}
|
|
15204
|
-
function findCircularDependencies() {
|
|
15205
|
-
if (!madgeInstance) return [];
|
|
15206
|
-
try {
|
|
15207
|
-
return madgeInstance.circular();
|
|
15208
|
-
} catch (error) {
|
|
15209
|
-
logger.warn(`Failed to detect circular dependencies: ${String(error)}`);
|
|
15210
|
-
return [];
|
|
15211
|
-
}
|
|
15212
|
-
}
|
|
15213
|
-
return {
|
|
15214
|
-
async buildGraph(filePaths) {
|
|
15215
|
-
try {
|
|
15216
|
-
if (filePaths.length === 0) return;
|
|
15217
|
-
madgeInstance = await getMadgeLoader()(filePaths, {
|
|
15218
|
-
fileExtensions: ["ts", "js"],
|
|
15219
|
-
excludeRegExp: [/node_modules/],
|
|
15220
|
-
baseDir: ".",
|
|
15221
|
-
...options
|
|
15222
|
-
});
|
|
15223
|
-
const dependencyObj = madgeInstance.obj();
|
|
15224
|
-
graph.clear();
|
|
15225
|
-
for (const filePath of filePaths) addNode(filePath);
|
|
15226
|
-
for (const [filePath, dependencies] of Object.entries(dependencyObj)) {
|
|
15227
|
-
const absoluteFilePath = path.resolve(".", filePath);
|
|
15228
|
-
const node = graph.get(absoluteFilePath);
|
|
15229
|
-
if (!node) continue;
|
|
15230
|
-
for (const dep of dependencies) {
|
|
15231
|
-
const absoluteDepPath = path.resolve(".", dep);
|
|
15232
|
-
node.dependencies.add(absoluteDepPath);
|
|
15233
|
-
const depNode = graph.get(absoluteDepPath);
|
|
15234
|
-
if (depNode) depNode.dependents.add(absoluteFilePath);
|
|
15235
|
-
}
|
|
15236
|
-
}
|
|
15237
|
-
} catch (error) {
|
|
15238
|
-
if (error instanceof WatcherError) throw error;
|
|
15239
|
-
throw new WatcherError(`Failed to build dependency graph: ${error instanceof Error ? error.message : String(error)}`, WatcherErrorCode.DEPENDENCY_ANALYSIS_FAILED, void 0, error instanceof Error ? error : void 0);
|
|
15240
|
-
}
|
|
15241
|
-
},
|
|
15242
|
-
getDependents(filePath) {
|
|
15243
|
-
const visited = /* @__PURE__ */ new Set();
|
|
15244
|
-
return traverseDependents(path.resolve(filePath), visited);
|
|
15245
|
-
},
|
|
15246
|
-
getDependencies(filePath) {
|
|
15247
|
-
const visited = /* @__PURE__ */ new Set();
|
|
15248
|
-
return traverseDependencies(path.resolve(filePath), visited);
|
|
15249
|
-
},
|
|
15250
|
-
findCircularDependencies,
|
|
15251
|
-
addNode,
|
|
15252
|
-
removeNode,
|
|
15253
|
-
getGraphStats() {
|
|
15254
|
-
let edgeCount = 0;
|
|
15255
|
-
for (const node of graph.values()) edgeCount += node.dependencies.size;
|
|
15256
|
-
return {
|
|
15257
|
-
nodeCount: graph.size,
|
|
15258
|
-
edgeCount,
|
|
15259
|
-
circularDependencyCount: findCircularDependencies().length
|
|
15260
|
-
};
|
|
15261
|
-
}
|
|
15262
|
-
};
|
|
15263
|
-
}
|
|
15264
|
-
/**
|
|
15265
|
-
* Creates a dependency watcher.
|
|
15266
|
-
* @param options - Watcher options
|
|
15267
|
-
* @returns DependencyWatcher instance
|
|
15268
|
-
*/
|
|
15269
|
-
function createDependencyWatcher(options = {}) {
|
|
15270
|
-
let chokidarWatcher = null;
|
|
15271
|
-
const watchGroups = /* @__PURE__ */ new Map();
|
|
15272
|
-
const dependencyGraphManager = createDependencyGraphManager(options.madgeOptions);
|
|
15273
|
-
let errorCallback = null;
|
|
15274
|
-
const debounceTimers = /* @__PURE__ */ new Map();
|
|
15275
|
-
let isInitialized = false;
|
|
15276
|
-
const dependencyCache = /* @__PURE__ */ new Map();
|
|
15277
|
-
const maxCacheSize = 1e3;
|
|
15278
|
-
let signalHandlersRegistered = false;
|
|
15279
|
-
let restartCallback = null;
|
|
15280
|
-
function validateWatchGroup(groupId, patterns) {
|
|
15281
|
-
if (!groupId || typeof groupId !== "string") throw new WatcherError("Group ID must be a non-empty string", WatcherErrorCode.INVALID_WATCH_GROUP);
|
|
15282
|
-
if (!Array.isArray(patterns) || patterns.length === 0) throw new WatcherError("Patterns must be a non-empty array", WatcherErrorCode.INVALID_WATCH_GROUP);
|
|
15283
|
-
if (watchGroups.has(groupId)) throw new WatcherError(`Watch group with ID '${groupId}' already exists`, WatcherErrorCode.INVALID_WATCH_GROUP);
|
|
15284
|
-
}
|
|
15285
|
-
function handleError(error) {
|
|
15286
|
-
logger.error(`[DependencyWatcher] ${error.message} (code: ${error.code}, filePath: ${error.filePath})`);
|
|
15287
|
-
if (errorCallback) errorCallback(error);
|
|
15288
|
-
}
|
|
15289
|
-
function setCacheValue(key, value) {
|
|
15290
|
-
if (dependencyCache.size >= maxCacheSize) {
|
|
15291
|
-
const firstKey = dependencyCache.keys().next().value;
|
|
15292
|
-
if (firstKey) dependencyCache.delete(firstKey);
|
|
15293
|
-
}
|
|
15294
|
-
dependencyCache.set(key, value);
|
|
15295
|
-
}
|
|
15296
|
-
function findAffectedFiles(changedFile) {
|
|
15297
|
-
return dependencyGraphManager.getDependents(changedFile);
|
|
15298
|
-
}
|
|
15299
|
-
function findAffectedGroups(affectedFiles) {
|
|
15300
|
-
logger.debug(`Finding affected groups for files: ${affectedFiles.join(", ")}`);
|
|
15301
|
-
const affectedGroupsSet = /* @__PURE__ */ new Set();
|
|
15302
|
-
for (const [groupId, group] of watchGroups) for (const affectedFile of affectedFiles) if (group.files.has(affectedFile)) {
|
|
15303
|
-
logger.debug(`Group ${groupId} is affected by file: ${affectedFile}`);
|
|
15304
|
-
affectedGroupsSet.add(groupId);
|
|
15305
|
-
break;
|
|
15306
|
-
}
|
|
15307
|
-
return Array.from(affectedGroupsSet);
|
|
15308
|
-
}
|
|
15309
|
-
function calculateImpact(filePath) {
|
|
15310
|
-
const cacheKey = `impact:${filePath}`;
|
|
15311
|
-
let affectedFiles = dependencyCache.get(cacheKey);
|
|
15312
|
-
if (!affectedFiles) {
|
|
15313
|
-
affectedFiles = findAffectedFiles(filePath);
|
|
15314
|
-
setCacheValue(cacheKey, affectedFiles);
|
|
15315
|
-
}
|
|
15316
|
-
const allAffectedFiles = [filePath, ...affectedFiles];
|
|
15317
|
-
return {
|
|
15318
|
-
changedFile: filePath,
|
|
15319
|
-
affectedFiles: allAffectedFiles,
|
|
15320
|
-
affectedGroups: findAffectedGroups(allAffectedFiles)
|
|
15321
|
-
};
|
|
15322
|
-
}
|
|
15323
|
-
async function updateDependencyGraph() {
|
|
15324
|
-
const allFiles = [];
|
|
15325
|
-
for (const group of watchGroups.values()) allFiles.push(...Array.from(group.files));
|
|
15326
|
-
await dependencyGraphManager.buildGraph(allFiles);
|
|
15327
|
-
dependencyCache.clear();
|
|
15328
|
-
if (options.detectCircularDependencies) {
|
|
15329
|
-
const circularDeps = dependencyGraphManager.findCircularDependencies();
|
|
15330
|
-
if (circularDeps.length > 0) logger.warn(`Circular dependencies detected: ${JSON.stringify(circularDeps)}`);
|
|
15331
|
-
}
|
|
15332
|
-
}
|
|
15333
|
-
async function handleFileChange(event, filePath) {
|
|
15334
|
-
try {
|
|
15335
|
-
const absolutePath = path.resolve(filePath);
|
|
15336
|
-
if (event === "unlink") dependencyGraphManager.removeNode(absolutePath);
|
|
15337
|
-
else {
|
|
15338
|
-
dependencyGraphManager.addNode(absolutePath);
|
|
15339
|
-
if (event === "change") await updateDependencyGraph();
|
|
15340
|
-
}
|
|
15341
|
-
dependencyCache.clear();
|
|
15342
|
-
const impactResult = calculateImpact(absolutePath);
|
|
15343
|
-
if (impactResult.affectedGroups.length > 0) {
|
|
15344
|
-
logger.info("File change detected, restarting watch process...", { mode: "stream" });
|
|
15345
|
-
logger.info(`Changed file: ${absolutePath}`, { mode: "stream" });
|
|
15346
|
-
logger.info(`Affected groups: ${impactResult.affectedGroups.join(", ")}`, { mode: "stream" });
|
|
15347
|
-
if (restartCallback) restartCallback();
|
|
15348
|
-
} else logger.debug(`No affected groups found for file: ${absolutePath}`);
|
|
15349
|
-
} catch (error) {
|
|
15350
|
-
handleError(new WatcherError(`Failed to handle file change: ${error instanceof Error ? error.message : String(error)}`, WatcherErrorCode.DEPENDENCY_ANALYSIS_FAILED, filePath, error instanceof Error ? error : void 0));
|
|
15351
|
-
}
|
|
15352
|
-
}
|
|
15353
|
-
function debounceFileChange(event, filePath) {
|
|
15354
|
-
const key = `${event}:${filePath}`;
|
|
15355
|
-
if (debounceTimers.has(key)) clearTimeout(debounceTimers.get(key));
|
|
15356
|
-
const timer = setTimeout(() => {
|
|
15357
|
-
handleFileChange(event, filePath);
|
|
15358
|
-
debounceTimers.delete(key);
|
|
15359
|
-
}, options.debounceTime || 100);
|
|
15360
|
-
debounceTimers.set(key, timer);
|
|
15361
|
-
}
|
|
15362
|
-
async function stop() {
|
|
15363
|
-
if (chokidarWatcher) {
|
|
15364
|
-
await chokidarWatcher.close();
|
|
15365
|
-
chokidarWatcher = null;
|
|
15366
|
-
}
|
|
15367
|
-
for (const timer of debounceTimers.values()) clearTimeout(timer);
|
|
15368
|
-
debounceTimers.clear();
|
|
15369
|
-
removeSignalHandlers();
|
|
15370
|
-
isInitialized = false;
|
|
15371
|
-
}
|
|
15372
|
-
function setupSignalHandlers() {
|
|
15373
|
-
if (signalHandlersRegistered) return;
|
|
15374
|
-
const handleSignal = async () => {
|
|
15375
|
-
try {
|
|
15376
|
-
await stop();
|
|
15377
|
-
logger.info("Watcher stopped successfully");
|
|
15378
|
-
process.exit(0);
|
|
15379
|
-
} catch (error) {
|
|
15380
|
-
logger.error(`Error during shutdown: ${String(error)}`);
|
|
15381
|
-
process.exit(0);
|
|
15382
|
-
}
|
|
15383
|
-
};
|
|
15384
|
-
process.on("SIGINT", () => handleSignal());
|
|
15385
|
-
process.on("SIGTERM", () => handleSignal());
|
|
15386
|
-
signalHandlersRegistered = true;
|
|
15387
|
-
}
|
|
15388
|
-
function removeSignalHandlers() {
|
|
15389
|
-
if (!signalHandlersRegistered) return;
|
|
15390
|
-
process.removeAllListeners("SIGINT");
|
|
15391
|
-
process.removeAllListeners("SIGTERM");
|
|
15392
|
-
signalHandlersRegistered = false;
|
|
15393
|
-
}
|
|
15394
|
-
async function initialize() {
|
|
15395
|
-
if (isInitialized) return;
|
|
15396
|
-
try {
|
|
15397
|
-
chokidarWatcher = watch([], {
|
|
15398
|
-
ignored: /node_modules/,
|
|
15399
|
-
persistent: true,
|
|
15400
|
-
ignoreInitial: true,
|
|
15401
|
-
usePolling: false,
|
|
15402
|
-
awaitWriteFinish: {
|
|
15403
|
-
stabilityThreshold: 100,
|
|
15404
|
-
pollInterval: 100
|
|
15405
|
-
},
|
|
15406
|
-
...options.chokidarOptions
|
|
15407
|
-
});
|
|
15408
|
-
chokidarWatcher.on("add", (filePath) => {
|
|
15409
|
-
logger.debug(`File added: ${filePath}`);
|
|
15410
|
-
debounceFileChange("add", filePath);
|
|
15411
|
-
});
|
|
15412
|
-
chokidarWatcher.on("change", (filePath) => {
|
|
15413
|
-
logger.debug(`File changed: ${filePath}`);
|
|
15414
|
-
debounceFileChange("change", filePath);
|
|
15415
|
-
});
|
|
15416
|
-
chokidarWatcher.on("unlink", (filePath) => {
|
|
15417
|
-
logger.debug(`File removed: ${filePath}`);
|
|
15418
|
-
debounceFileChange("unlink", filePath);
|
|
15419
|
-
});
|
|
15420
|
-
chokidarWatcher.on("error", (error) => {
|
|
15421
|
-
logger.error(`Watcher error: ${error instanceof Error ? error.message : String(error)}`, { mode: "stream" });
|
|
15422
|
-
handleError(new WatcherError(`File watcher error: ${error instanceof Error ? error.message : String(error)}`, WatcherErrorCode.FILE_WATCH_FAILED, void 0, error instanceof Error ? error : void 0));
|
|
15423
|
-
});
|
|
15424
|
-
setupSignalHandlers();
|
|
15425
|
-
isInitialized = true;
|
|
15426
|
-
} catch (error) {
|
|
15427
|
-
throw new WatcherError(`Failed to initialize watcher: ${error instanceof Error ? error.message : String(error)}`, WatcherErrorCode.FILE_WATCH_FAILED, void 0, error instanceof Error ? error : void 0);
|
|
15428
|
-
}
|
|
15429
|
-
}
|
|
15430
|
-
return {
|
|
15431
|
-
initialize,
|
|
15432
|
-
async addWatchGroup(groupId, patterns) {
|
|
15433
|
-
validateWatchGroup(groupId, patterns);
|
|
15434
|
-
if (!isInitialized) await initialize();
|
|
15435
|
-
const files = /* @__PURE__ */ new Set();
|
|
15436
|
-
for (const pattern of patterns) {
|
|
15437
|
-
logger.log(`${styles.dim(`Watch pattern for`)} ${styles.dim(groupId + ":")} ${path.relative(process.cwd(), pattern)}`);
|
|
15438
|
-
for await (const file of glob(pattern)) files.add(path.resolve(file));
|
|
15439
|
-
}
|
|
15440
|
-
const watchGroup = {
|
|
15441
|
-
id: groupId,
|
|
15442
|
-
patterns,
|
|
15443
|
-
files
|
|
15444
|
-
};
|
|
15445
|
-
watchGroups.set(groupId, watchGroup);
|
|
15446
|
-
if (chokidarWatcher) {
|
|
15447
|
-
const filePaths = Array.from(files);
|
|
15448
|
-
chokidarWatcher.add(filePaths);
|
|
15449
|
-
}
|
|
15450
|
-
await updateDependencyGraph();
|
|
15451
|
-
},
|
|
15452
|
-
async removeWatchGroup(groupId) {
|
|
15453
|
-
const watchGroup = watchGroups.get(groupId);
|
|
15454
|
-
if (!watchGroup) return;
|
|
15455
|
-
if (chokidarWatcher) chokidarWatcher.unwatch(watchGroup.patterns);
|
|
15456
|
-
for (const filePath of watchGroup.files) dependencyGraphManager.removeNode(filePath);
|
|
15457
|
-
watchGroups.delete(groupId);
|
|
15458
|
-
dependencyCache.clear();
|
|
15459
|
-
},
|
|
15460
|
-
async start() {
|
|
15461
|
-
if (!isInitialized) await initialize();
|
|
15462
|
-
await updateDependencyGraph();
|
|
15463
|
-
},
|
|
15464
|
-
stop,
|
|
15465
|
-
onError(callback) {
|
|
15466
|
-
errorCallback = callback;
|
|
15467
|
-
},
|
|
15468
|
-
updateDependencyGraph,
|
|
15469
|
-
calculateImpact,
|
|
15470
|
-
detectCircularDependencies() {
|
|
15471
|
-
return dependencyGraphManager.findCircularDependencies();
|
|
15472
|
-
},
|
|
15473
|
-
getWatchStatus() {
|
|
15474
|
-
let fileCount = 0;
|
|
15475
|
-
for (const group of watchGroups.values()) fileCount += group.files.size;
|
|
15476
|
-
const stats = dependencyGraphManager.getGraphStats();
|
|
15477
|
-
return {
|
|
15478
|
-
isWatching: isInitialized && chokidarWatcher !== null,
|
|
15479
|
-
groupCount: watchGroups.size,
|
|
15480
|
-
fileCount,
|
|
15481
|
-
dependencyNodeCount: stats.nodeCount
|
|
15482
|
-
};
|
|
15483
|
-
},
|
|
15484
|
-
setRestartCallback(callback) {
|
|
15485
|
-
restartCallback = callback;
|
|
15486
|
-
}
|
|
15487
|
-
};
|
|
15488
|
-
}
|
|
15489
|
-
|
|
15490
16536
|
//#endregion
|
|
15491
16537
|
//#region src/cli/commands/generate/service.ts
|
|
15492
16538
|
/**
|
|
@@ -15500,13 +16546,12 @@ function createDependencyWatcher(options = {}) {
|
|
|
15500
16546
|
function createGenerationManager(params) {
|
|
15501
16547
|
const { application, config, pluginManager } = params;
|
|
15502
16548
|
const baseDir = path.join(getDistDir(), "generated");
|
|
15503
|
-
fs$
|
|
16549
|
+
fs$2.mkdirSync(baseDir, { recursive: true });
|
|
15504
16550
|
const services = {
|
|
15505
16551
|
tailordb: {},
|
|
15506
16552
|
resolver: {},
|
|
15507
16553
|
executor: {}
|
|
15508
16554
|
};
|
|
15509
|
-
let watcher = null;
|
|
15510
16555
|
const generationPlugins = pluginManager?.getPluginsWithGenerationHooks() ?? [];
|
|
15511
16556
|
function getAuthInput() {
|
|
15512
16557
|
const authService = application.authService;
|
|
@@ -15598,24 +16643,20 @@ function createGenerationManager(params) {
|
|
|
15598
16643
|
* Each hook runs at its natural pipeline phase, ensuring outputs from earlier
|
|
15599
16644
|
* phases are available when later phases load resolvers/executors.
|
|
15600
16645
|
* @param hookName - Name of the hook to call
|
|
15601
|
-
* @param watch - Whether running in watch mode (suppresses throws)
|
|
15602
16646
|
*/
|
|
15603
|
-
async function runPluginHook(hookName
|
|
16647
|
+
async function runPluginHook(hookName) {
|
|
15604
16648
|
const plugins = generationPlugins.filter((p) => p[hookName] != null);
|
|
15605
16649
|
if (plugins.length === 0) return;
|
|
15606
|
-
const
|
|
16650
|
+
const failures = (await Promise.allSettled(plugins.map(async (plugin) => {
|
|
15607
16651
|
try {
|
|
15608
16652
|
await runPluginPhaseHook(plugin, hookName);
|
|
15609
16653
|
} catch (error) {
|
|
15610
16654
|
logger.error(`Error processing plugin ${styles.bold(plugin.id)} (${hookName})`);
|
|
15611
16655
|
logger.error(String(error));
|
|
15612
|
-
|
|
16656
|
+
throw error;
|
|
15613
16657
|
}
|
|
15614
|
-
}));
|
|
15615
|
-
if (
|
|
15616
|
-
const failures = results.filter((r) => r.status === "rejected");
|
|
15617
|
-
if (failures.length > 0) throw new AggregateError(failures.map((f) => f.reason));
|
|
15618
|
-
}
|
|
16658
|
+
}))).filter((r) => r.status === "rejected");
|
|
16659
|
+
if (failures.length > 0) throw new AggregateError(failures.map((f) => f.reason));
|
|
15619
16660
|
}
|
|
15620
16661
|
/**
|
|
15621
16662
|
* Write generated files to disk.
|
|
@@ -15624,14 +16665,14 @@ function createGenerationManager(params) {
|
|
|
15624
16665
|
*/
|
|
15625
16666
|
async function writeGeneratedFiles(sourceId, result) {
|
|
15626
16667
|
await Promise.all(result.files.map(async (file) => {
|
|
15627
|
-
fs$
|
|
16668
|
+
fs$2.mkdirSync(path.dirname(file.path), { recursive: true });
|
|
15628
16669
|
return new Promise((resolve, reject) => {
|
|
15629
|
-
if (file.skipIfExists && fs$
|
|
16670
|
+
if (file.skipIfExists && fs$2.existsSync(file.path)) {
|
|
15630
16671
|
const relativePath = path.relative(process.cwd(), file.path);
|
|
15631
16672
|
logger.debug(`${sourceId} | skip existing: ${relativePath}`);
|
|
15632
16673
|
return resolve();
|
|
15633
16674
|
}
|
|
15634
|
-
fs$
|
|
16675
|
+
fs$2.writeFile(file.path, file.content, (err) => {
|
|
15635
16676
|
if (err) {
|
|
15636
16677
|
const relativePath = path.relative(process.cwd(), file.path);
|
|
15637
16678
|
logger.error(`Error writing file ${styles.bold(relativePath)}`);
|
|
@@ -15640,7 +16681,7 @@ function createGenerationManager(params) {
|
|
|
15640
16681
|
} else {
|
|
15641
16682
|
const relativePath = path.relative(process.cwd(), file.path);
|
|
15642
16683
|
logger.log(`${sourceId} | generate: ${styles.success(relativePath)}`);
|
|
15643
|
-
if (file.executable) fs$
|
|
16684
|
+
if (file.executable) fs$2.chmod(file.path, 493, (chmodErr) => {
|
|
15644
16685
|
if (chmodErr) {
|
|
15645
16686
|
const relativePath = path.relative(process.cwd(), file.path);
|
|
15646
16687
|
logger.error(`Error setting executable permission on ${styles.bold(relativePath)}`);
|
|
@@ -15654,35 +16695,11 @@ function createGenerationManager(params) {
|
|
|
15654
16695
|
});
|
|
15655
16696
|
}));
|
|
15656
16697
|
}
|
|
15657
|
-
async function restartWatchProcess() {
|
|
15658
|
-
logger.newline();
|
|
15659
|
-
logger.info("Restarting watch process to clear module cache...", { mode: "stream" });
|
|
15660
|
-
logger.newline();
|
|
15661
|
-
if (watcher) await watcher.stop();
|
|
15662
|
-
const args = process.argv.slice(2);
|
|
15663
|
-
const env = {
|
|
15664
|
-
...process.env,
|
|
15665
|
-
__TAILOR_WATCH_GENERATION: (parseInt(process.env.__TAILOR_WATCH_GENERATION || "0", 10) + 1).toString()
|
|
15666
|
-
};
|
|
15667
|
-
const child = spawn(assertDefined(process.argv[0], "argv[0] missing"), [assertDefined(process.argv[1], "argv[1] missing"), ...args], {
|
|
15668
|
-
stdio: "inherit",
|
|
15669
|
-
env,
|
|
15670
|
-
detached: false
|
|
15671
|
-
});
|
|
15672
|
-
const forwardSignal = (signal) => {
|
|
15673
|
-
child.kill(signal);
|
|
15674
|
-
};
|
|
15675
|
-
process.on("SIGINT", forwardSignal);
|
|
15676
|
-
process.on("SIGTERM", forwardSignal);
|
|
15677
|
-
child.on("exit", (code) => {
|
|
15678
|
-
process.exit(code || 0);
|
|
15679
|
-
});
|
|
15680
|
-
}
|
|
15681
16698
|
return {
|
|
15682
16699
|
application,
|
|
15683
16700
|
baseDir,
|
|
15684
16701
|
services,
|
|
15685
|
-
async generate(
|
|
16702
|
+
async generate() {
|
|
15686
16703
|
logger.newline();
|
|
15687
16704
|
logger.log(`Generation for application: ${styles.highlight(application.config.name)}`);
|
|
15688
16705
|
const app = application;
|
|
@@ -15702,7 +16719,7 @@ function createGenerationManager(params) {
|
|
|
15702
16719
|
} catch (error) {
|
|
15703
16720
|
logger.error(`Error loading types for TailorDB service ${styles.bold(namespace)}`);
|
|
15704
16721
|
logger.error(String(error));
|
|
15705
|
-
|
|
16722
|
+
throw error;
|
|
15706
16723
|
}
|
|
15707
16724
|
});
|
|
15708
16725
|
}
|
|
@@ -15711,14 +16728,17 @@ function createGenerationManager(params) {
|
|
|
15711
16728
|
} catch (error) {
|
|
15712
16729
|
logger.error("Error validating TailorDB type names");
|
|
15713
16730
|
logger.error(String(error));
|
|
15714
|
-
|
|
16731
|
+
throw error;
|
|
15715
16732
|
}
|
|
15716
16733
|
});
|
|
15717
16734
|
const { pluginExecutorFiles, executorService } = await withSpan("generate.pluginFiles", async () => {
|
|
15718
16735
|
const pluginExecutorFiles = generatePluginFilesIfNeeded(pluginManager, app.tailorDBServices, config.path);
|
|
15719
16736
|
return {
|
|
15720
16737
|
pluginExecutorFiles,
|
|
15721
|
-
executorService: app.executorService ?? (pluginExecutorFiles.length > 0 ? createExecutorService({
|
|
16738
|
+
executorService: app.executorService ?? (pluginExecutorFiles.length > 0 ? createExecutorService({
|
|
16739
|
+
config: { files: [] },
|
|
16740
|
+
baseDir: path.dirname(config.path)
|
|
16741
|
+
}) : void 0)
|
|
15722
16742
|
};
|
|
15723
16743
|
});
|
|
15724
16744
|
if (app.authService) {
|
|
@@ -15728,7 +16748,7 @@ function createGenerationManager(params) {
|
|
|
15728
16748
|
if (app.tailorDBServices.length > 0 || pluginExecutorFiles.length > 0) logger.newline();
|
|
15729
16749
|
if (generationPlugins.some((p) => p.onTailorDBReady != null)) {
|
|
15730
16750
|
await withSpan("generate.onTailorDBReady", async () => {
|
|
15731
|
-
await runPluginHook("onTailorDBReady"
|
|
16751
|
+
await runPluginHook("onTailorDBReady");
|
|
15732
16752
|
});
|
|
15733
16753
|
logger.newline();
|
|
15734
16754
|
}
|
|
@@ -15746,14 +16766,14 @@ function createGenerationManager(params) {
|
|
|
15746
16766
|
} catch (error) {
|
|
15747
16767
|
logger.error(`Error loading resolvers for Resolver service ${styles.bold(namespace)}`);
|
|
15748
16768
|
logger.error(String(error));
|
|
15749
|
-
|
|
16769
|
+
throw error;
|
|
15750
16770
|
}
|
|
15751
16771
|
});
|
|
15752
16772
|
}
|
|
15753
16773
|
});
|
|
15754
16774
|
if (generationPlugins.some((p) => p.onResolverReady != null)) {
|
|
15755
16775
|
await withSpan("generate.onResolversReady", async () => {
|
|
15756
|
-
await runPluginHook("onResolverReady"
|
|
16776
|
+
await runPluginHook("onResolverReady");
|
|
15757
16777
|
});
|
|
15758
16778
|
logger.newline();
|
|
15759
16779
|
}
|
|
@@ -15769,42 +16789,23 @@ function createGenerationManager(params) {
|
|
|
15769
16789
|
});
|
|
15770
16790
|
if (generationPlugins.some((p) => p.onExecutorReady != null)) {
|
|
15771
16791
|
await withSpan("generate.onExecutorsReady", async () => {
|
|
15772
|
-
await runPluginHook("onExecutorReady"
|
|
16792
|
+
await runPluginHook("onExecutorReady");
|
|
15773
16793
|
});
|
|
15774
16794
|
logger.newline();
|
|
15775
16795
|
}
|
|
15776
|
-
},
|
|
15777
|
-
async watch() {
|
|
15778
|
-
watcher = createDependencyWatcher();
|
|
15779
|
-
watcher.setRestartCallback(() => {
|
|
15780
|
-
restartWatchProcess();
|
|
15781
|
-
});
|
|
15782
|
-
await watcher.addWatchGroup("Config", [config.path]);
|
|
15783
|
-
const app = application;
|
|
15784
|
-
for (const db of app.tailorDBServices) {
|
|
15785
|
-
const dbNamespace = db.namespace;
|
|
15786
|
-
await watcher.addWatchGroup(`TailorDB/${dbNamespace}`, db.config.files);
|
|
15787
|
-
}
|
|
15788
|
-
for (const resolverService of app.resolverServices) {
|
|
15789
|
-
const resolverNamespace = resolverService.namespace;
|
|
15790
|
-
await watcher.addWatchGroup(`Resolver/${resolverNamespace}`, resolverService["config"].files);
|
|
15791
|
-
}
|
|
15792
|
-
await new Promise(() => {});
|
|
15793
16796
|
}
|
|
15794
16797
|
};
|
|
15795
16798
|
}
|
|
15796
16799
|
/**
|
|
15797
16800
|
* Run code generation using the Tailor configuration.
|
|
15798
16801
|
* @param options - Generation options
|
|
15799
|
-
* @returns Promise that resolves when generation
|
|
16802
|
+
* @returns Promise that resolves when generation completes
|
|
15800
16803
|
*/
|
|
15801
16804
|
async function generate$1(options) {
|
|
15802
16805
|
return withSpan("generate", async (rootSpan) => {
|
|
15803
16806
|
const { config, plugins } = await withSpan("generate.loadConfig", async () => {
|
|
15804
16807
|
return loadConfig(options?.configPath);
|
|
15805
16808
|
});
|
|
15806
|
-
const watch = options?.watch ?? false;
|
|
15807
|
-
rootSpan.setAttribute("generate.watch", watch);
|
|
15808
16809
|
await withSpan("generate.generateUserTypes", async () => generateUserTypes({
|
|
15809
16810
|
config,
|
|
15810
16811
|
configPath: config.path
|
|
@@ -15816,13 +16817,11 @@ async function generate$1(options) {
|
|
|
15816
16817
|
pluginManager
|
|
15817
16818
|
});
|
|
15818
16819
|
rootSpan.setAttribute("app.name", application.config.name);
|
|
15819
|
-
|
|
16820
|
+
await createGenerationManager({
|
|
15820
16821
|
application,
|
|
15821
16822
|
config,
|
|
15822
16823
|
pluginManager
|
|
15823
|
-
});
|
|
15824
|
-
await manager.generate(watch);
|
|
15825
|
-
if (watch) await manager.watch();
|
|
16824
|
+
}).generate();
|
|
15826
16825
|
});
|
|
15827
16826
|
}
|
|
15828
16827
|
|
|
@@ -15872,7 +16871,7 @@ async function listMachineUsers(options) {
|
|
|
15872
16871
|
return [machineUsers, nextPageToken];
|
|
15873
16872
|
}, { limit: options?.limit })).map(machineUserInfo);
|
|
15874
16873
|
}
|
|
15875
|
-
const listCommand$
|
|
16874
|
+
const listCommand$6 = defineAppCommand({
|
|
15876
16875
|
name: "list",
|
|
15877
16876
|
description: "List all machine users in the application.",
|
|
15878
16877
|
args: z.strictObject({
|
|
@@ -16088,7 +17087,7 @@ async function listOAuth2Clients(options) {
|
|
|
16088
17087
|
return [oauth2Clients, nextPageToken];
|
|
16089
17088
|
}, { limit: options?.limit })).map(toOAuth2ClientInfo);
|
|
16090
17089
|
}
|
|
16091
|
-
const listCommand$
|
|
17090
|
+
const listCommand$5 = defineAppCommand({
|
|
16092
17091
|
name: "list",
|
|
16093
17092
|
description: "List all OAuth2 clients in the application.",
|
|
16094
17093
|
args: z.strictObject({
|
|
@@ -16158,7 +17157,7 @@ async function createFolder(options) {
|
|
|
16158
17157
|
if (!response.folder) throw new Error("Failed to create folder.");
|
|
16159
17158
|
return folderInfo(response.folder);
|
|
16160
17159
|
}
|
|
16161
|
-
const createCommand
|
|
17160
|
+
const createCommand = defineAppCommand({
|
|
16162
17161
|
name: "create",
|
|
16163
17162
|
description: "Create a new folder in an organization.",
|
|
16164
17163
|
args: z.strictObject({
|
|
@@ -16306,7 +17305,7 @@ async function listFolders(options) {
|
|
|
16306
17305
|
return [response.folders, response.nextPageToken];
|
|
16307
17306
|
}, { limit })).map(folderListInfo);
|
|
16308
17307
|
}
|
|
16309
|
-
const listCommand$
|
|
17308
|
+
const listCommand$4 = defineAppCommand({
|
|
16310
17309
|
name: "list",
|
|
16311
17310
|
description: "List folders in an organization.",
|
|
16312
17311
|
args: z.strictObject({
|
|
@@ -16415,7 +17414,7 @@ async function listOrganizations(options) {
|
|
|
16415
17414
|
if (limit !== void 0) return results.slice(0, limit);
|
|
16416
17415
|
return results;
|
|
16417
17416
|
}
|
|
16418
|
-
const listCommand$
|
|
17417
|
+
const listCommand$3 = defineAppCommand({
|
|
16419
17418
|
name: "list",
|
|
16420
17419
|
description: "List organizations you belong to.",
|
|
16421
17420
|
args: z.strictObject({ limit: arg(positiveIntArg.optional(), {
|
|
@@ -16726,7 +17725,7 @@ const removeCommand$1 = defineAppCommand({
|
|
|
16726
17725
|
//#region src/cli/shared/beta.ts
|
|
16727
17726
|
/**
|
|
16728
17727
|
* Warn that a feature is in beta.
|
|
16729
|
-
* @param {string} featureName - Name of the beta feature (e.g., "tailordb
|
|
17728
|
+
* @param {string} featureName - Name of the beta feature (e.g., "tailordb migration")
|
|
16730
17729
|
*/
|
|
16731
17730
|
function logBetaWarning(featureName) {
|
|
16732
17731
|
logger.warn(`The '${featureName}' command is a beta feature and may introduce breaking changes in future releases.`);
|
|
@@ -16757,89 +17756,11 @@ function extractAllNamespaces(config) {
|
|
|
16757
17756
|
function extractOwnedNamespaces(config) {
|
|
16758
17757
|
const namespaces = /* @__PURE__ */ new Set();
|
|
16759
17758
|
if (config.db) for (const [namespaceName, nsConfig] of Object.entries(config.db)) {
|
|
16760
|
-
if ("external" in nsConfig) continue;
|
|
16761
|
-
namespaces.add(namespaceName);
|
|
16762
|
-
}
|
|
16763
|
-
return Array.from(namespaces);
|
|
16764
|
-
}
|
|
16765
|
-
|
|
16766
|
-
//#endregion
|
|
16767
|
-
//#region src/cli/commands/workspace/transform.ts
|
|
16768
|
-
const workspaceInfo = (workspace, folderName) => {
|
|
16769
|
-
const info = {
|
|
16770
|
-
id: workspace.id,
|
|
16771
|
-
name: workspace.name,
|
|
16772
|
-
region: workspace.region,
|
|
16773
|
-
createdAt: formatTimestamp(workspace.createTime),
|
|
16774
|
-
updatedAt: formatTimestamp(workspace.updateTime)
|
|
16775
|
-
};
|
|
16776
|
-
return folderName ? {
|
|
16777
|
-
...info,
|
|
16778
|
-
folderName
|
|
16779
|
-
} : info;
|
|
16780
|
-
};
|
|
16781
|
-
const workspaceDetails = (workspace, folderName) => {
|
|
16782
|
-
return {
|
|
16783
|
-
...workspaceInfo(workspace, folderName),
|
|
16784
|
-
deleteProtection: workspace.deleteProtection,
|
|
16785
|
-
organizationId: workspace.organizationId,
|
|
16786
|
-
folderId: workspace.folderId
|
|
16787
|
-
};
|
|
16788
|
-
};
|
|
16789
|
-
async function resolveWorkspaceFolderName(client, workspace) {
|
|
16790
|
-
if (!workspace.organizationId || !workspace.folderId) return void 0;
|
|
16791
|
-
try {
|
|
16792
|
-
return (await client.getOrganizationFolder({
|
|
16793
|
-
organizationId: workspace.organizationId,
|
|
16794
|
-
folderId: workspace.folderId
|
|
16795
|
-
})).folder?.name || void 0;
|
|
16796
|
-
} catch (error) {
|
|
16797
|
-
if (error instanceof ConnectError && (error.code === Code.NotFound || error.code === Code.PermissionDenied)) return;
|
|
16798
|
-
logger.warn(`Failed to resolve workspace folder name: ${error}`);
|
|
16799
|
-
return;
|
|
16800
|
-
}
|
|
16801
|
-
}
|
|
16802
|
-
function createWorkspaceFolderNameResolver(client) {
|
|
16803
|
-
const cache = /* @__PURE__ */ new Map();
|
|
16804
|
-
return (workspace) => {
|
|
16805
|
-
if (!workspace.organizationId || !workspace.folderId) return Promise.resolve(void 0);
|
|
16806
|
-
const cacheKey = `${workspace.organizationId}/${workspace.folderId}`;
|
|
16807
|
-
const cached = cache.get(cacheKey);
|
|
16808
|
-
if (cached) return cached;
|
|
16809
|
-
const promise = resolveWorkspaceFolderName(client, workspace);
|
|
16810
|
-
cache.set(cacheKey, promise);
|
|
16811
|
-
return promise;
|
|
16812
|
-
};
|
|
16813
|
-
}
|
|
16814
|
-
async function workspaceInfoWithFolderName(client, workspace) {
|
|
16815
|
-
const folderName = await resolveWorkspaceFolderName(client, workspace);
|
|
16816
|
-
return workspaceInfo(workspace, folderName);
|
|
16817
|
-
}
|
|
16818
|
-
async function workspaceDetailsWithFolderName(client, workspace) {
|
|
16819
|
-
const folderName = await resolveWorkspaceFolderName(client, workspace);
|
|
16820
|
-
return workspaceDetails(workspace, folderName);
|
|
16821
|
-
}
|
|
16822
|
-
async function workspaceInfosWithFolderNames(client, workspaces) {
|
|
16823
|
-
const resolveFolderName = createWorkspaceFolderNameResolver(client);
|
|
16824
|
-
const limit = pLimit(5);
|
|
16825
|
-
return Promise.all(workspaces.map((workspace) => limit(async () => workspaceInfo(workspace, await resolveFolderName(workspace)))));
|
|
16826
|
-
}
|
|
16827
|
-
function workspaceDisplayName(workspace) {
|
|
16828
|
-
return workspace.folderName ? `${workspace.folderName}/${workspace.name}` : workspace.name;
|
|
16829
|
-
}
|
|
16830
|
-
function createWorkspaceNameTransformer(nameKey, folderNameKey) {
|
|
16831
|
-
return (value, item) => {
|
|
16832
|
-
const workspace = item;
|
|
16833
|
-
const name = workspace[nameKey];
|
|
16834
|
-
const folderName = workspace[folderNameKey];
|
|
16835
|
-
if (typeof name === "string" && typeof folderName === "string") return workspaceDisplayName({
|
|
16836
|
-
name,
|
|
16837
|
-
folderName
|
|
16838
|
-
});
|
|
16839
|
-
return String(value ?? "");
|
|
16840
|
-
};
|
|
17759
|
+
if ("external" in nsConfig) continue;
|
|
17760
|
+
namespaces.add(namespaceName);
|
|
17761
|
+
}
|
|
17762
|
+
return Array.from(namespaces);
|
|
16841
17763
|
}
|
|
16842
|
-
const workspaceNameTransformer = createWorkspaceNameTransformer("name", "folderName");
|
|
16843
17764
|
|
|
16844
17765
|
//#endregion
|
|
16845
17766
|
//#region src/cli/commands/show.ts
|
|
@@ -16925,6 +17846,203 @@ const showCommand = defineAppCommand({
|
|
|
16925
17846
|
}
|
|
16926
17847
|
});
|
|
16927
17848
|
|
|
17849
|
+
//#endregion
|
|
17850
|
+
//#region src/cli/shared/progress.ts
|
|
17851
|
+
/**
|
|
17852
|
+
* Create a simple progress reporter that writes updates to stderr.
|
|
17853
|
+
* @param label - Label to prefix progress output
|
|
17854
|
+
* @param total - Total number of steps
|
|
17855
|
+
* @returns Progress helpers
|
|
17856
|
+
*/
|
|
17857
|
+
function createProgress(label, total) {
|
|
17858
|
+
let current = 0;
|
|
17859
|
+
const update = () => {
|
|
17860
|
+
current += 1;
|
|
17861
|
+
const percent = Math.round(current / total * 100);
|
|
17862
|
+
process.stderr.write(`\r${label} ${current}/${total} (${percent}%)`);
|
|
17863
|
+
};
|
|
17864
|
+
const finish = () => {
|
|
17865
|
+
process.stderr.write("\n");
|
|
17866
|
+
};
|
|
17867
|
+
return {
|
|
17868
|
+
update,
|
|
17869
|
+
finish
|
|
17870
|
+
};
|
|
17871
|
+
}
|
|
17872
|
+
/**
|
|
17873
|
+
* Wrap a promise with a timeout, rejecting if the timeout elapses first.
|
|
17874
|
+
* @template T
|
|
17875
|
+
* @param p - Promise to await
|
|
17876
|
+
* @param ms - Timeout in milliseconds
|
|
17877
|
+
* @param message - Error message on timeout
|
|
17878
|
+
* @returns Result of the original promise if it completes in time
|
|
17879
|
+
*/
|
|
17880
|
+
async function withTimeout(p, ms, message) {
|
|
17881
|
+
return await Promise.race([p, setTimeout$2(ms).then(() => {
|
|
17882
|
+
throw new Error(message);
|
|
17883
|
+
})]);
|
|
17884
|
+
}
|
|
17885
|
+
|
|
17886
|
+
//#endregion
|
|
17887
|
+
//#region src/cli/commands/staticwebsite/deploy.ts
|
|
17888
|
+
const CHUNK_SIZE = 64 * 1024;
|
|
17889
|
+
const IGNORED_FILES = /* @__PURE__ */ new Set([
|
|
17890
|
+
".DS_Store",
|
|
17891
|
+
"thumbs.db",
|
|
17892
|
+
"desktop.ini"
|
|
17893
|
+
]);
|
|
17894
|
+
function shouldIgnoreFile(filePath) {
|
|
17895
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
17896
|
+
return IGNORED_FILES.has(fileName);
|
|
17897
|
+
}
|
|
17898
|
+
/**
|
|
17899
|
+
* Deploy a static website by creating a deployment, uploading files, and publishing it.
|
|
17900
|
+
* @param client - Operator client instance
|
|
17901
|
+
* @param workspaceId - Workspace ID
|
|
17902
|
+
* @param name - Static website name
|
|
17903
|
+
* @param distDir - Directory containing static site files
|
|
17904
|
+
* @param showProgress - Whether to show upload progress
|
|
17905
|
+
* @returns Deployment result with URL and skipped files
|
|
17906
|
+
*/
|
|
17907
|
+
async function deployStaticWebsite(client, workspaceId, name, distDir, showProgress = true) {
|
|
17908
|
+
const { deploymentId } = await client.createDeployment({
|
|
17909
|
+
workspaceId,
|
|
17910
|
+
name
|
|
17911
|
+
});
|
|
17912
|
+
if (!deploymentId) throw new Error("createDeployment returned empty deploymentId");
|
|
17913
|
+
const skippedFiles = await uploadDirectory(client, workspaceId, deploymentId, distDir, showProgress);
|
|
17914
|
+
const { url } = await client.publishDeployment({
|
|
17915
|
+
workspaceId,
|
|
17916
|
+
deploymentId
|
|
17917
|
+
});
|
|
17918
|
+
if (!url) throw new Error("publishDeployment returned empty url");
|
|
17919
|
+
return {
|
|
17920
|
+
url,
|
|
17921
|
+
skippedFiles
|
|
17922
|
+
};
|
|
17923
|
+
}
|
|
17924
|
+
async function uploadDirectory(client, workspaceId, deploymentId, rootDir, showProgress) {
|
|
17925
|
+
const files = await collectFiles(rootDir);
|
|
17926
|
+
if (files.length === 0) {
|
|
17927
|
+
logger.warn(`No files found under ${rootDir}`);
|
|
17928
|
+
return [];
|
|
17929
|
+
}
|
|
17930
|
+
const limit = pLimit(5);
|
|
17931
|
+
const total = files.length;
|
|
17932
|
+
const progress = showProgress ? createProgress("Uploading files", total) : void 0;
|
|
17933
|
+
const skippedFiles = [];
|
|
17934
|
+
await Promise.all(files.map((relativePath) => limit(async () => {
|
|
17935
|
+
await uploadSingleFile(client, workspaceId, deploymentId, rootDir, relativePath, skippedFiles);
|
|
17936
|
+
if (progress) progress.update();
|
|
17937
|
+
})));
|
|
17938
|
+
if (progress) progress.finish();
|
|
17939
|
+
return skippedFiles;
|
|
17940
|
+
}
|
|
17941
|
+
/**
|
|
17942
|
+
* Recursively collect all deployable files under the given directory.
|
|
17943
|
+
* @param rootDir - Root directory to scan
|
|
17944
|
+
* @param currentDir - Current relative directory (for recursion)
|
|
17945
|
+
* @returns List of file paths relative to rootDir
|
|
17946
|
+
*/
|
|
17947
|
+
async function collectFiles(rootDir, currentDir = "") {
|
|
17948
|
+
const dirPath = path.join(rootDir, currentDir);
|
|
17949
|
+
const entries = await fs$1.promises.readdir(dirPath, { withFileTypes: true });
|
|
17950
|
+
const files = [];
|
|
17951
|
+
for (const entry of entries) {
|
|
17952
|
+
const rel = path.join(currentDir, entry.name);
|
|
17953
|
+
if (entry.isDirectory()) {
|
|
17954
|
+
const sub = await collectFiles(rootDir, rel);
|
|
17955
|
+
files.push(...sub);
|
|
17956
|
+
} else if (entry.isFile() && !entry.isSymbolicLink() && !shouldIgnoreFile(rel)) files.push(rel);
|
|
17957
|
+
}
|
|
17958
|
+
return files;
|
|
17959
|
+
}
|
|
17960
|
+
async function uploadSingleFile(client, workspaceId, deploymentId, rootDir, filePath, skippedFiles) {
|
|
17961
|
+
const absPath = path.join(rootDir, filePath);
|
|
17962
|
+
const mime = lookup(filePath);
|
|
17963
|
+
if (!mime) {
|
|
17964
|
+
skippedFiles.push(`${filePath} (unsupported content type; no MIME mapping found)`);
|
|
17965
|
+
return;
|
|
17966
|
+
}
|
|
17967
|
+
const contentType = mime;
|
|
17968
|
+
const readStream = fs$1.createReadStream(absPath, { highWaterMark: CHUNK_SIZE });
|
|
17969
|
+
async function* requestStream() {
|
|
17970
|
+
yield { payload: {
|
|
17971
|
+
case: "initialMetadata",
|
|
17972
|
+
value: {
|
|
17973
|
+
workspaceId,
|
|
17974
|
+
deploymentId,
|
|
17975
|
+
filePath,
|
|
17976
|
+
contentType
|
|
17977
|
+
}
|
|
17978
|
+
} };
|
|
17979
|
+
for await (const chunk of readStream) yield { payload: {
|
|
17980
|
+
case: "chunkData",
|
|
17981
|
+
value: chunk
|
|
17982
|
+
} };
|
|
17983
|
+
}
|
|
17984
|
+
async function uploadWithLogging() {
|
|
17985
|
+
try {
|
|
17986
|
+
await client.uploadFile(requestStream());
|
|
17987
|
+
} catch (error) {
|
|
17988
|
+
if (error instanceof ConnectError && error.code === Code.InvalidArgument) {
|
|
17989
|
+
skippedFiles.push(`${filePath} (server rejected file as invalid: ${error.message})`);
|
|
17990
|
+
return;
|
|
17991
|
+
}
|
|
17992
|
+
throw error;
|
|
17993
|
+
}
|
|
17994
|
+
}
|
|
17995
|
+
await withTimeout(uploadWithLogging(), 2 * 6e4, `Upload timed out for "${filePath}"`);
|
|
17996
|
+
}
|
|
17997
|
+
/**
|
|
17998
|
+
* Log skipped files after a deployment, including reasons for skipping.
|
|
17999
|
+
* @param skippedFiles - List of skipped file descriptions
|
|
18000
|
+
*/
|
|
18001
|
+
function logSkippedFiles(skippedFiles) {
|
|
18002
|
+
if (skippedFiles.length === 0) return;
|
|
18003
|
+
logger.warn("Deployment completed, but some files failed to upload. These files may have unsupported content types or other validation issues. Please review the list below:");
|
|
18004
|
+
for (const file of skippedFiles) logger.log(` - ${file}`);
|
|
18005
|
+
}
|
|
18006
|
+
const deployCommand = defineAppCommand({
|
|
18007
|
+
name: "deploy",
|
|
18008
|
+
description: "Deploy a static website from a local build directory.",
|
|
18009
|
+
args: z.strictObject({
|
|
18010
|
+
...workspaceArgs,
|
|
18011
|
+
name: arg(z.string(), {
|
|
18012
|
+
alias: "n",
|
|
18013
|
+
description: "Static website name"
|
|
18014
|
+
}),
|
|
18015
|
+
dir: arg(z.string(), {
|
|
18016
|
+
alias: "d",
|
|
18017
|
+
description: "Path to the static website files",
|
|
18018
|
+
completion: { type: "directory" }
|
|
18019
|
+
})
|
|
18020
|
+
}),
|
|
18021
|
+
run: async (args) => {
|
|
18022
|
+
await assertWritable({ profile: args.profile });
|
|
18023
|
+
logger.info(`Deploying static website "${args.name}" from directory: ${args.dir}`);
|
|
18024
|
+
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
18025
|
+
const name = args.name;
|
|
18026
|
+
const dir = path.resolve(process.cwd(), args.dir);
|
|
18027
|
+
const workspaceId = await loadWorkspaceId({
|
|
18028
|
+
workspaceId: args["workspace-id"],
|
|
18029
|
+
profile: args.profile
|
|
18030
|
+
});
|
|
18031
|
+
if (!fs$1.existsSync(dir) || !fs$1.statSync(dir).isDirectory()) throw new Error(`Directory not found or not a directory: ${dir}`);
|
|
18032
|
+
const { url, skippedFiles } = await withTimeout(deployStaticWebsite(client, workspaceId, name, dir, !args.json), 10 * 6e4, "Deployment timed out after 10 minutes.");
|
|
18033
|
+
if (args.json) logger.out({
|
|
18034
|
+
name,
|
|
18035
|
+
workspaceId,
|
|
18036
|
+
url,
|
|
18037
|
+
skippedFiles
|
|
18038
|
+
});
|
|
18039
|
+
else {
|
|
18040
|
+
logger.success(`Static website "${name}" deployed successfully. URL: ${url}`);
|
|
18041
|
+
logSkippedFiles(skippedFiles);
|
|
18042
|
+
}
|
|
18043
|
+
}
|
|
18044
|
+
});
|
|
18045
|
+
|
|
16928
18046
|
//#endregion
|
|
16929
18047
|
//#region src/cli/shared/editor.ts
|
|
16930
18048
|
const DEFAULT_EDITOR = "editor";
|
|
@@ -17481,7 +18599,7 @@ function generateChangeScript(change) {
|
|
|
17481
18599
|
* @returns {Promise<void>}
|
|
17482
18600
|
*/
|
|
17483
18601
|
async function handleInitOption(namespaces, skipConfirmation) {
|
|
17484
|
-
const existingDirs = namespaces.filter(({ migrationsDir }) => fs$
|
|
18602
|
+
const existingDirs = namespaces.filter(({ migrationsDir }) => fs$2.existsSync(migrationsDir));
|
|
17485
18603
|
if (existingDirs.length === 0) {
|
|
17486
18604
|
logger.info("No existing migration directories found.");
|
|
17487
18605
|
return;
|
|
@@ -17531,7 +18649,7 @@ async function generate(options) {
|
|
|
17531
18649
|
if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
|
|
17532
18650
|
let pluginManager;
|
|
17533
18651
|
if (plugins.length > 0) pluginManager = new PluginManager(plugins);
|
|
17534
|
-
const { defineApplication } = await import("./application-
|
|
18652
|
+
const { defineApplication } = await import("./application-OM0taSPn.mjs");
|
|
17535
18653
|
const application = defineApplication({
|
|
17536
18654
|
config,
|
|
17537
18655
|
pluginManager
|
|
@@ -17891,7 +19009,7 @@ async function listWorkflows(options) {
|
|
|
17891
19009
|
return [workflows, nextPageToken];
|
|
17892
19010
|
}, { limit: options?.limit })).map(toWorkflowListInfo);
|
|
17893
19011
|
}
|
|
17894
|
-
const listCommand$
|
|
19012
|
+
const listCommand$2 = defineAppCommand({
|
|
17895
19013
|
name: "list",
|
|
17896
19014
|
description: "List all workflows in the workspace.",
|
|
17897
19015
|
args: z.strictObject({
|
|
@@ -18224,7 +19342,7 @@ async function listApps(options) {
|
|
|
18224
19342
|
return [applications, nextPageToken];
|
|
18225
19343
|
}, { limit })).map(appInfo);
|
|
18226
19344
|
}
|
|
18227
|
-
const listCommand$
|
|
19345
|
+
const listCommand$1 = defineAppCommand({
|
|
18228
19346
|
name: "list",
|
|
18229
19347
|
description: "List applications in a workspace",
|
|
18230
19348
|
args: z.strictObject({
|
|
@@ -18247,153 +19365,6 @@ const listCommand$2 = defineAppCommand({
|
|
|
18247
19365
|
}
|
|
18248
19366
|
});
|
|
18249
19367
|
|
|
18250
|
-
//#endregion
|
|
18251
|
-
//#region src/cli/commands/workspace/create.ts
|
|
18252
|
-
/**
|
|
18253
|
-
* Schema for workspace creation options
|
|
18254
|
-
* - name: 3-63 chars, lowercase alphanumeric and hyphens, cannot start/end with hyphen
|
|
18255
|
-
* - organizationId, folderId: optional UUIDs
|
|
18256
|
-
*/
|
|
18257
|
-
const createWorkspaceOptionsSchema = z.object({
|
|
18258
|
-
name: z.string().min(3, "Name must be at least 3 characters").max(63, "Name must be at most 63 characters").regex(/^[a-z0-9-]+$/, "Name can only contain lowercase letters, numbers, and hyphens").refine((n) => !n.startsWith("-") && !n.endsWith("-"), "Name cannot start or end with a hyphen"),
|
|
18259
|
-
region: z.string(),
|
|
18260
|
-
deleteProtection: z.boolean().optional(),
|
|
18261
|
-
organizationId: z.uuid().optional(),
|
|
18262
|
-
folderId: z.uuid().optional()
|
|
18263
|
-
});
|
|
18264
|
-
const validateRegion = async (region, client) => {
|
|
18265
|
-
const availableRegions = await client.listAvailableWorkspaceRegions({});
|
|
18266
|
-
if (!availableRegions.regions.includes(region)) throw new Error(`Region must be one of: ${availableRegions.regions.join(", ")}.`);
|
|
18267
|
-
};
|
|
18268
|
-
function profilePlatformSettings(platformConfig) {
|
|
18269
|
-
const hasOAuth2ClientId = platformConfig?.oauth2ClientId || process.env.TAILOR_PLATFORM_OAUTH2_CLIENT_ID;
|
|
18270
|
-
const hasConsoleUrl = platformConfig?.consoleUrl || process.env.TAILOR_PLATFORM_CONSOLE_URL;
|
|
18271
|
-
return {
|
|
18272
|
-
...isDefaultPlatform(platformConfig) ? {} : { platform_url: getPlatformBaseUrl(platformConfig) },
|
|
18273
|
-
...hasOAuth2ClientId ? { oauth2_client_id: getOAuth2ClientId(platformConfig) } : {},
|
|
18274
|
-
...hasConsoleUrl ? { console_url: getConsoleBaseUrl(platformConfig) } : {}
|
|
18275
|
-
};
|
|
18276
|
-
}
|
|
18277
|
-
/**
|
|
18278
|
-
* Create a new workspace with the given options.
|
|
18279
|
-
* @param options - Workspace creation options
|
|
18280
|
-
* @returns Created workspace info
|
|
18281
|
-
*/
|
|
18282
|
-
async function createWorkspace(options) {
|
|
18283
|
-
const result = createWorkspaceOptionsSchema.safeParse(options);
|
|
18284
|
-
if (!result.success) throw new Error(assertDefined(result.error.issues[0], "Zod returned no issues").message);
|
|
18285
|
-
const validated = result.data;
|
|
18286
|
-
const client = await initOperatorClient(await loadAccessToken());
|
|
18287
|
-
await validateRegion(validated.region, client);
|
|
18288
|
-
return workspaceInfoWithFolderName(client, assertDefined((await client.createWorkspace({
|
|
18289
|
-
workspaceName: validated.name,
|
|
18290
|
-
workspaceRegion: validated.region,
|
|
18291
|
-
deleteProtection: validated.deleteProtection ?? false,
|
|
18292
|
-
organizationId: validated.organizationId,
|
|
18293
|
-
folderId: validated.folderId
|
|
18294
|
-
})).workspace, "createWorkspace response missing workspace"));
|
|
18295
|
-
}
|
|
18296
|
-
const createCommand = defineAppCommand({
|
|
18297
|
-
name: "create",
|
|
18298
|
-
description: "Create a new Tailor Platform workspace.",
|
|
18299
|
-
args: z.strictObject({
|
|
18300
|
-
name: arg(z.string(), {
|
|
18301
|
-
alias: "n",
|
|
18302
|
-
description: "Workspace name"
|
|
18303
|
-
}),
|
|
18304
|
-
region: arg(z.string(), {
|
|
18305
|
-
alias: "r",
|
|
18306
|
-
description: "Workspace region (us-west, asia-northeast)"
|
|
18307
|
-
}),
|
|
18308
|
-
"delete-protection": arg(z.boolean().default(false), {
|
|
18309
|
-
alias: "d",
|
|
18310
|
-
description: "Enable delete protection"
|
|
18311
|
-
}),
|
|
18312
|
-
"organization-id": arg(z.string().optional(), {
|
|
18313
|
-
alias: "o",
|
|
18314
|
-
description: "Organization ID to workspace associate with",
|
|
18315
|
-
env: "TAILOR_PLATFORM_ORGANIZATION_ID"
|
|
18316
|
-
}),
|
|
18317
|
-
"folder-id": arg(z.string().optional(), {
|
|
18318
|
-
alias: "f",
|
|
18319
|
-
description: "Folder ID to workspace associate with",
|
|
18320
|
-
env: "TAILOR_PLATFORM_FOLDER_ID"
|
|
18321
|
-
}),
|
|
18322
|
-
"profile-name": arg(z.string().optional(), {
|
|
18323
|
-
alias: "p",
|
|
18324
|
-
description: "Profile name to create"
|
|
18325
|
-
}),
|
|
18326
|
-
"profile-user": arg(z.string().optional(), { description: "User email address or machine user client ID for the profile (defaults to current user)" }),
|
|
18327
|
-
permission: arg(z.enum(["write", "read"]).default("write"), { description: "Profile permission (requires --profile-name). 'read' blocks all write commands while the profile is active." })
|
|
18328
|
-
}),
|
|
18329
|
-
run: async (args) => {
|
|
18330
|
-
await assertWritable();
|
|
18331
|
-
const profileName = args["profile-name"];
|
|
18332
|
-
let profileSetup;
|
|
18333
|
-
if (profileName) {
|
|
18334
|
-
const config = await readPlatformConfig();
|
|
18335
|
-
if (config.profiles[profileName]) throw new Error(`Profile "${profileName}" already exists.`);
|
|
18336
|
-
const activeProfileName = process.env.TAILOR_PLATFORM_PROFILE;
|
|
18337
|
-
const activeProfileEntry = activeProfileName ? config.profiles[activeProfileName] : void 0;
|
|
18338
|
-
const platformConfig = activeProfileEntry ? platformConfigFromProfile(activeProfileEntry) : void 0;
|
|
18339
|
-
const profileUser = args["profile-user"] || activeProfileEntry?.user || config.current_user;
|
|
18340
|
-
if (!profileUser) throw new Error("Current user not found. Please login or specify --profile-user to create a profile.");
|
|
18341
|
-
const resolvedProfileUser = resolveConfigUser(config, profileUser, platformConfig);
|
|
18342
|
-
if (!resolvedProfileUser) throw new Error(`User "${profileUser}" not found.\nPlease verify your user name and login using 'tailor login' command.`);
|
|
18343
|
-
profileSetup = {
|
|
18344
|
-
name: profileName,
|
|
18345
|
-
user: resolvedProfileUser,
|
|
18346
|
-
platformSettings: profilePlatformSettings(platformConfig)
|
|
18347
|
-
};
|
|
18348
|
-
}
|
|
18349
|
-
const workspace = await createWorkspace({
|
|
18350
|
-
name: args.name,
|
|
18351
|
-
region: args.region,
|
|
18352
|
-
deleteProtection: args["delete-protection"],
|
|
18353
|
-
organizationId: args["organization-id"],
|
|
18354
|
-
folderId: args["folder-id"]
|
|
18355
|
-
});
|
|
18356
|
-
let profileInfo;
|
|
18357
|
-
if (profileSetup) {
|
|
18358
|
-
const config = await readPlatformConfig();
|
|
18359
|
-
const platformSettings = profileSetup.platformSettings;
|
|
18360
|
-
config.profiles[profileSetup.name] = {
|
|
18361
|
-
user: profileSetup.user,
|
|
18362
|
-
workspace_id: workspace.id,
|
|
18363
|
-
...args.permission === "read" ? { readonly: true } : {},
|
|
18364
|
-
...platformSettings
|
|
18365
|
-
};
|
|
18366
|
-
writePlatformConfig(config);
|
|
18367
|
-
profileInfo = {
|
|
18368
|
-
name: profileSetup.name,
|
|
18369
|
-
user: profileSetup.user,
|
|
18370
|
-
workspaceId: workspace.id,
|
|
18371
|
-
permission: args.permission,
|
|
18372
|
-
...platformSettings.platform_url ? { platformUrl: platformSettings.platform_url } : {},
|
|
18373
|
-
...platformSettings.oauth2_client_id ? { oauth2ClientId: platformSettings.oauth2_client_id } : {},
|
|
18374
|
-
...platformSettings.console_url ? { consoleUrl: platformSettings.console_url } : {}
|
|
18375
|
-
};
|
|
18376
|
-
if (!args.json) logger.success(`Profile "${profileSetup.name}" created successfully.`);
|
|
18377
|
-
}
|
|
18378
|
-
if (!args.json) logger.success(`Workspace "${workspaceDisplayName(workspace)}" created successfully.`);
|
|
18379
|
-
if (args.json && profileInfo) {
|
|
18380
|
-
logger.out({
|
|
18381
|
-
...workspace,
|
|
18382
|
-
profile: profileInfo
|
|
18383
|
-
});
|
|
18384
|
-
return;
|
|
18385
|
-
}
|
|
18386
|
-
logger.out(workspace, { display: {
|
|
18387
|
-
name: workspaceNameTransformer,
|
|
18388
|
-
folderName: null
|
|
18389
|
-
} });
|
|
18390
|
-
if (profileInfo) {
|
|
18391
|
-
logger.out("Profile:");
|
|
18392
|
-
logger.out(profileInfo);
|
|
18393
|
-
}
|
|
18394
|
-
}
|
|
18395
|
-
});
|
|
18396
|
-
|
|
18397
19368
|
//#endregion
|
|
18398
19369
|
//#region src/cli/commands/workspace/delete.ts
|
|
18399
19370
|
const deleteWorkspaceOptionsSchema = z.object({ workspaceId: z.uuid({ message: "workspace-id must be a valid UUID" }) });
|
|
@@ -18507,42 +19478,6 @@ const getCommand = defineAppCommand({
|
|
|
18507
19478
|
}
|
|
18508
19479
|
});
|
|
18509
19480
|
|
|
18510
|
-
//#endregion
|
|
18511
|
-
//#region src/cli/commands/workspace/list.ts
|
|
18512
|
-
/**
|
|
18513
|
-
* List workspaces with an optional order and limit.
|
|
18514
|
-
* @param options - Workspace listing options
|
|
18515
|
-
* @returns List of workspaces
|
|
18516
|
-
*/
|
|
18517
|
-
async function listWorkspaces(options) {
|
|
18518
|
-
const client = await initOperatorClient(await loadAccessToken());
|
|
18519
|
-
const pageDirection = toPageDirection(options?.order);
|
|
18520
|
-
return workspaceInfosWithFolderNames(client, await fetchPaged(async (pageToken, pageSize) => {
|
|
18521
|
-
const { workspaces, nextPageToken } = await client.listWorkspaces({
|
|
18522
|
-
pageToken,
|
|
18523
|
-
pageSize,
|
|
18524
|
-
pageDirection
|
|
18525
|
-
});
|
|
18526
|
-
return [workspaces, nextPageToken];
|
|
18527
|
-
}, { limit: options?.limit }));
|
|
18528
|
-
}
|
|
18529
|
-
const listCommand$1 = defineAppCommand({
|
|
18530
|
-
name: "list",
|
|
18531
|
-
description: "List all Tailor Platform workspaces.",
|
|
18532
|
-
args: z.strictObject({ ...paginationArgs() }),
|
|
18533
|
-
run: async (args) => {
|
|
18534
|
-
const workspaces = await listWorkspaces({
|
|
18535
|
-
order: args.order,
|
|
18536
|
-
limit: args.limit
|
|
18537
|
-
});
|
|
18538
|
-
logger.out(workspaces, { display: {
|
|
18539
|
-
name: workspaceNameTransformer,
|
|
18540
|
-
folderName: null,
|
|
18541
|
-
updatedAt: null
|
|
18542
|
-
} });
|
|
18543
|
-
}
|
|
18544
|
-
});
|
|
18545
|
-
|
|
18546
19481
|
//#endregion
|
|
18547
19482
|
//#region src/cli/commands/workspace/restore.ts
|
|
18548
19483
|
const restoreWorkspaceOptionsSchema = z.object({ workspaceId: z.uuid({ message: "workspace-id must be a valid UUID" }) });
|
|
@@ -18922,10 +19857,10 @@ function createGqlEntry() {
|
|
|
18922
19857
|
*/
|
|
18923
19858
|
async function bundleQueryScript(engine) {
|
|
18924
19859
|
const outputDir = path.resolve(getDistDir(), "query");
|
|
18925
|
-
fs$
|
|
19860
|
+
fs$2.mkdirSync(outputDir, { recursive: true });
|
|
18926
19861
|
const entryPath = path.join(outputDir, `query_${engine}.entry.ts`);
|
|
18927
19862
|
const entryContent = engine === "sql" ? createSqlEntry() : createGqlEntry();
|
|
18928
|
-
fs$
|
|
19863
|
+
fs$2.writeFileSync(entryPath, entryContent);
|
|
18929
19864
|
let tsconfig;
|
|
18930
19865
|
try {
|
|
18931
19866
|
tsconfig = await resolveTSConfig();
|
|
@@ -19193,7 +20128,7 @@ async function loadTypeFieldOrder(config, namespace) {
|
|
|
19193
20128
|
const fieldOrder = /* @__PURE__ */ new Map();
|
|
19194
20129
|
const dbConfig = config.db?.[namespace];
|
|
19195
20130
|
if (!dbConfig || !("files" in dbConfig) || dbConfig.files.length === 0) return fieldOrder;
|
|
19196
|
-
const typeFiles = loadFilesWithIgnores(dbConfig);
|
|
20131
|
+
const typeFiles = loadFilesWithIgnores(dbConfig, path.dirname(config.path));
|
|
19197
20132
|
await Promise.all(typeFiles.map(async (typeFile) => {
|
|
19198
20133
|
try {
|
|
19199
20134
|
const module = await import(pathToFileURL(typeFile).href);
|
|
@@ -19809,5 +20744,5 @@ async function registerTsHook(tsHookUrl) {
|
|
|
19809
20744
|
}
|
|
19810
20745
|
|
|
19811
20746
|
//#endregion
|
|
19812
|
-
export {
|
|
19813
|
-
//# sourceMappingURL=register-ts-hook-
|
|
20747
|
+
export { updateFolder as $, saveUserTokens as $n, bundleMigrationScript as $t, generate as A, sdkNameLabelKey as An, listExecutorJobs as At, extractOwnedNamespaces as B, hasUserTokenEntry as Bn, getCommand$6 as Bt, waitWorkflowExecution as C, formatMigrationNumber as Cn, triggerCommand as Ct, listWorkflows as D, hasChanges as Dn, getExecutorJob as Dt, listCommand$2 as E, formatMigrationDiff as En, listExecutors as Et, openInConfiguredEditor as F, assertWritable as Fn, getWorkflow as Ft, updateOrganization as G, loadPlatformClientConfig as Gn, listWorkspaces as Gt, remove as H, loadConfigPath as Hn, deploy as Ht, deployCommand as I, loadConfig as In, executionsCommand as It, listCommand$3 as J, platformConfigFromProfile as Jn, formatKeyValueTable as Jt, organizationTree as K, loadStoredUserTokens as Kn, createCommand$1 as Kt, deployStaticWebsite as L, deleteUserTokens as Ln, getWorkflowExecution as Lt, generateMigrationScript as M, generateUserTypes as Mn, startCommand as Mt, writeDbTypesFile as N, prompt as Nn, startWorkflow as Nt, truncate as O, getNamespacesWithMigrations as On, getExecutorWaitFailureMessage as Ot, getConfiguredEditorCommand as P, apiCall as Pn, getCommand$5 as Pt, updateCommand$2 as Q, resolveUserTokenKey as Qn, waitForExecution as Qt, show as R, fetchLatestToken as Rn, listWorkflowExecutions as Rt, waitCommand as S, reconstructSnapshotFromMigrations as Sn, webhookCommand as St, resumeWorkflow as T, formatDiffSummary as Tn, listCommand$8 as Tt, removeCommand$1 as U, loadConsoleBaseUrl as Un, deployFromCLI as Ut, logBetaWarning as V, loadAccessToken as Vn, getExecutor as Vt, updateCommand$1 as W, loadMachineUserName as Wn, listCommand$9 as Wt, getCommand$1 as X, removeLegacyUserAlias as Xn, ensureConfigId as Xt, listOrganizations as Y, readPlatformConfig as Yn, workspaceNameSchema as Yt, getOrganization as Z, resolveConfigUser as Zn, executeScript as Zt, deleteWorkspace as _, getMigrationFilePath as _n, listCommand$7 as _t, updateUser as a, protoGqlPermission as an, deploymentArgs as ar, deleteFolder as at, getAppHealth as b, isValidMigrationNumber as bn, getFunctionRegistry as bt, listCommand as c, INITIAL_SCHEMA_NUMBER as cn, pagedLogArgs as cr, listCommand$5 as ct, inviteUser as d, assertValidMigrationFiles as dn, toPageDirection as dr, getOAuth2Client as dt, MIGRATION_LABEL_KEY as en, writePlatformConfig as er, listCommand$4 as et, restoreCommand as f, compareLocalTypesWithSnapshot as fn, workspaceArgs as fr, getMachineUserToken as ft, deleteCommand as g, getMigrationDirPath as gn, generate$1 as gt, getWorkspace as h, getLatestMigrationNumber as hn, listMachineUsers as ht, updateCommand as i, generateAllTypeManifestsFromSnapshot as in, confirmationArgs as ir, deleteCommand$1 as it, generateCommand as j, PluginManager as jn, watchExecutorJob as jt, truncateCommand as k, resourceTrn as kn, jobsCommand as kt, listUsers as l, MIGRATE_FILE_NAME as ln, paginationArgs as lr, listOAuth2Clients as lt, getCommand as m, createSnapshotFromLocalTypes as mn, listCommand$6 as mt, query as n, parseMigrationLabelNumber as nn, commonArgs as nr, getCommand$2 as nt, removeCommand as o, DB_TYPES_FILE_NAME as on, isVerbose as or, createCommand as ot, restoreWorkspace as p, compareSnapshots as pn, tokenCommand as pt, treeCommand as q, loadWorkspaceId as qn, createWorkspace$1 as qt, queryCommand as r, compareSnapshotWithRemote as rn, configArg as rr, getFolder as rt, removeUser as s, DIFF_FILE_NAME as sn, multiConfigArg as sr, createFolder as st, registerTsHook as t, handleOptionalToRequiredError as tn, defineAppCommand as tr, listFolders as tt, inviteCommand as u, SCHEMA_FILE_NAME as un, resolveMachineUserInputSource as ur, getCommand$3 as ut, listApps as v, getMigrationFiles as vn, listFunctionRegistries as vt, resumeCommand as w, parseMigrationNumberArg as wn, triggerExecutor as wt, healthCommand as x, loadDiff as xn, listWebhookExecutors as xt, listCommand$1 as y, getNextMigrationNumber as yn, getCommand$4 as yt, showCommand as z, hasAnyUserTokenEntry as zn, functionExecutionStatusToString as zt };
|
|
20748
|
+
//# sourceMappingURL=register-ts-hook-DvEs6YsL.mjs.map
|