@tailor-platform/sdk 1.76.1 → 1.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/dist/{application-BUfYmf0O.mjs → application-CUxVA7Fs.mjs} +378 -1679
- package/dist/application-CUxVA7Fs.mjs.map +1 -0
- package/dist/application-Dp_-ioiP.mjs +3 -0
- package/dist/cli/cache/bundle-cache.d.mts +2 -1
- package/dist/cli/cache/store.d.mts +1 -1
- package/dist/cli/commands/api/index.d.mts +1 -0
- package/dist/cli/commands/deploy/aigateway.d.mts +5 -1
- package/dist/cli/commands/deploy/application.d.mts +7 -1
- package/dist/cli/commands/deploy/apply-phases.d.mts +14 -1
- package/dist/cli/commands/deploy/auth.d.mts +5 -1
- package/dist/cli/commands/deploy/deploy.d.mts +13 -3
- package/dist/cli/commands/deploy/executor.d.mts +7 -1
- package/dist/cli/commands/deploy/function-registry.d.mts +5 -0
- package/dist/cli/commands/deploy/idp.d.mts +5 -1
- package/dist/cli/commands/deploy/resolver.d.mts +5 -1
- package/dist/cli/commands/deploy/secret-manager.d.mts +5 -1
- package/dist/cli/commands/deploy/staticwebsite.d.mts +5 -1
- package/dist/cli/commands/deploy/tailordb/index.d.mts +8 -1
- package/dist/cli/commands/deploy/types.d.mts +3 -1
- package/dist/cli/commands/deploy/workflow-execution-policy.d.mts +3 -1
- package/dist/cli/commands/deploy/workflow.d.mts +4 -1
- package/dist/cli/commands/executor/get.d.mts +0 -1
- package/dist/cli/commands/executor/jobs.d.mts +0 -1
- package/dist/cli/commands/executor/list.d.mts +0 -1
- package/dist/cli/commands/executor/transform.d.mts +1 -0
- package/dist/cli/commands/executor/trigger.d.mts +0 -1
- package/dist/cli/commands/executor/webhook.d.mts +0 -1
- package/dist/cli/commands/function/get.d.mts +0 -1
- package/dist/cli/commands/function/list.d.mts +0 -1
- package/dist/cli/commands/function/transform.d.mts +1 -0
- package/dist/cli/commands/generate/service.d.mts +5 -1
- package/dist/cli/commands/generate/types.d.mts +3 -1
- package/dist/cli/commands/machineuser/list.d.mts +0 -1
- package/dist/cli/commands/machineuser/token.d.mts +0 -1
- package/dist/cli/commands/oauth2client/get.d.mts +0 -1
- package/dist/cli/commands/oauth2client/list.d.mts +0 -1
- package/dist/cli/commands/oauth2client/transform.d.mts +1 -0
- package/dist/cli/commands/organization/folder/create.d.mts +0 -1
- package/dist/cli/commands/organization/folder/delete.d.mts +0 -1
- package/dist/cli/commands/organization/folder/get.d.mts +0 -1
- package/dist/cli/commands/organization/folder/list.d.mts +0 -1
- package/dist/cli/commands/organization/folder/update.d.mts +0 -1
- package/dist/cli/commands/organization/get.d.mts +0 -1
- package/dist/cli/commands/organization/list.d.mts +0 -1
- package/dist/cli/commands/organization/transform.d.mts +2 -0
- package/dist/cli/commands/organization/tree.d.mts +0 -1
- package/dist/cli/commands/organization/update.d.mts +0 -1
- package/dist/cli/commands/remove.d.mts +0 -1
- package/dist/cli/commands/show.d.mts +9 -4
- package/dist/cli/commands/tailordb/migrate/config.d.mts +0 -1
- package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +0 -1
- package/dist/cli/commands/tailordb/migrate/generate.d.mts +0 -1
- package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +1 -1
- package/dist/cli/commands/tailordb/truncate.d.mts +0 -1
- package/dist/cli/commands/workflow/executions.d.mts +0 -1
- package/dist/cli/commands/workflow/get.d.mts +2 -1
- package/dist/cli/commands/workflow/list.d.mts +0 -1
- package/dist/cli/commands/workflow/resume.d.mts +0 -1
- package/dist/cli/commands/workflow/start.d.mts +0 -1
- package/dist/cli/commands/workflow/status.d.mts +1 -0
- package/dist/cli/commands/workflow/transform.d.mts +1 -0
- package/dist/cli/commands/workflow/wait.d.mts +0 -1
- package/dist/cli/commands/workflow/waiter.d.mts +1 -1
- package/dist/cli/commands/workspace/app/health.d.mts +0 -1
- package/dist/cli/commands/workspace/app/list.d.mts +0 -1
- package/dist/cli/commands/workspace/app/transform.d.mts +2 -0
- package/dist/cli/commands/workspace/create.d.mts +3 -1
- package/dist/cli/commands/workspace/delete.d.mts +0 -1
- package/dist/cli/commands/workspace/get.d.mts +0 -1
- package/dist/cli/commands/workspace/list.d.mts +1 -1
- package/dist/cli/commands/workspace/restore.d.mts +0 -1
- package/dist/cli/commands/workspace/transform.d.mts +4 -0
- package/dist/cli/commands/workspace/user/invite.d.mts +0 -1
- package/dist/cli/commands/workspace/user/list.d.mts +0 -1
- package/dist/cli/commands/workspace/user/remove.d.mts +0 -1
- package/dist/cli/commands/workspace/user/transform.d.mts +1 -0
- package/dist/cli/commands/workspace/user/update.d.mts +0 -1
- package/dist/cli/index.d.mts +3 -1
- package/dist/cli/index.mjs +660 -56
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +4 -2
- package/dist/cli/lib.mjs +3 -3
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/query/index.d.mts +0 -1
- package/dist/cli/services/application.d.mts +12 -1
- package/dist/cli/services/auth/service.d.mts +2 -0
- package/dist/cli/services/executor/service.d.mts +1 -1
- package/dist/cli/services/http-adapter/bundler.d.mts +3 -1
- package/dist/cli/services/http-adapter/service.d.mts +2 -1
- package/dist/cli/services/resolver/service.d.mts +1 -1
- package/dist/cli/services/tailordb/service.d.mts +3 -1
- package/dist/cli/services/workflow/bundler.d.mts +2 -1
- package/dist/cli/services/workflow/service.d.mts +1 -1
- package/dist/cli/shared/args.d.mts +1 -1
- package/dist/cli/shared/client.d.mts +1 -1
- package/dist/cli/shared/config-loader.d.mts +0 -1
- package/dist/cli/shared/context.d.mts +2 -1
- package/dist/cli/shared/script-executor.d.mts +0 -1
- package/dist/cli/shared/seed-chunker.d.mts +7 -3
- package/dist/cli/shared/type-generator.d.mts +0 -1
- package/dist/cli/shared/workspace-name.d.mts +1 -0
- package/dist/cli/skills.mjs +1 -1
- package/dist/completion/zsh-worker.zsh +18 -1
- package/dist/configure/config/index.d.mts +0 -1
- package/dist/configure/config/types.d.mts +2 -2
- package/dist/configure/index.d.mts +2 -1
- package/dist/configure/index.mjs +248 -4
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/aigateway/index.d.mts +0 -1
- package/dist/configure/services/aigateway/types.d.mts +0 -1
- package/dist/configure/services/auth/index.d.mts +0 -1
- package/dist/configure/services/auth/types.d.mts +14 -13
- package/dist/configure/services/executor/executor.d.mts +0 -1
- package/dist/configure/services/executor/index.d.mts +1 -0
- package/dist/configure/services/executor/operation.d.mts +1 -2
- package/dist/configure/services/executor/trigger/event.d.mts +0 -1
- package/dist/configure/services/executor/trigger/index.d.mts +0 -1
- package/dist/configure/services/executor/trigger/schedule.d.mts +0 -1
- package/dist/configure/services/executor/trigger/webhook.d.mts +0 -1
- package/dist/configure/services/http-adapter/http-adapter.d.mts +1 -2
- package/dist/configure/services/idp/index.d.mts +1 -5
- package/dist/configure/services/idp/permission.d.mts +4 -5
- package/dist/configure/services/idp/types.d.mts +0 -1
- package/dist/configure/services/index.d.mts +5 -0
- package/dist/configure/services/resolver/resolver.d.mts +0 -1
- package/dist/configure/services/resolver/types.d.mts +0 -1
- package/dist/configure/services/secrets/index.d.mts +0 -1
- package/dist/configure/services/secrets/types.d.mts +0 -1
- package/dist/configure/services/staticwebsite/index.d.mts +0 -1
- package/dist/configure/services/staticwebsite/types.d.mts +0 -1
- package/dist/configure/services/tailordb/permission.d.mts +4 -5
- package/dist/configure/services/tailordb/schema.d.mts +21 -8
- package/dist/configure/services/tailordb/types.d.mts +4 -5
- package/dist/configure/services/workflow/execution-policy.d.mts +0 -1
- package/dist/configure/services/workflow/execution-policy.types.d.mts +0 -1
- package/dist/configure/services/workflow/index.d.mts +1 -0
- package/dist/configure/services/workflow/job.d.mts +0 -1
- package/dist/configure/services/workflow/wait-point.d.mts +0 -1
- package/dist/configure/services/workflow/workflow.d.mts +0 -1
- package/dist/configure/types/field-runtime.d.mts +1 -1
- package/dist/configure/types/field.d.mts +1 -2
- package/dist/configure/types/field.types.d.mts +1 -2
- package/dist/configure/types/type.d.mts +1 -1
- package/dist/configure/user.d.mts +0 -1
- package/dist/{crashreport-ayWANEZ6.mjs → crashreport-BNWUM6Kf.mjs} +4 -3
- package/dist/{crashreport-ayWANEZ6.mjs.map → crashreport-BNWUM6Kf.mjs.map} +1 -1
- package/dist/{crashreport-Dy4ZzSkd.mjs → crashreport-BafWvjjX.mjs} +1 -1
- package/dist/errors-DVFS0AZk.mjs +100 -0
- package/dist/errors-DVFS0AZk.mjs.map +1 -0
- package/dist/{globals-CcU1ONiK.mjs → globals-CvizbGLY.mjs} +2 -2
- package/dist/{globals-CcU1ONiK.mjs.map → globals-CvizbGLY.mjs.map} +1 -1
- package/dist/kysely/index.d.mts +3 -4
- package/dist/{logger-BEiZZ3qT.mjs → logger-BwS4ppwO.mjs} +3 -3
- package/dist/logger-BwS4ppwO.mjs.map +1 -0
- package/dist/parser/service/auth/index.d.mts +1 -1
- package/dist/parser/service/auth/schema.d.mts +1 -0
- package/dist/parser/service/http-adapter/index.d.mts +1 -1
- package/dist/parser/service/idp/types.d.mts +1 -0
- package/dist/parser/service/tailordb/types.d.mts +0 -1
- package/dist/plugin/builtin/enum-constants/index.d.mts +0 -1
- package/dist/plugin/builtin/file-utils/index.d.mts +0 -1
- package/dist/plugin/builtin/kysely-type/index.d.mts +0 -1
- package/dist/plugin/builtin/seed/index.d.mts +0 -1
- package/dist/plugin/builtin/seed/index.mjs +1 -1
- package/dist/plugin/get-generated-type.d.mts +0 -1
- package/dist/plugin/manager.d.mts +3 -1
- package/dist/plugin/types.d.mts +0 -1
- package/dist/plugin/with-context.d.mts +0 -1
- package/dist/{registry-NfSW0BRo.mjs → registry-CYBRvfaU.mjs} +4 -49
- package/dist/registry-CYBRvfaU.mjs.map +1 -0
- package/dist/runtime/aigateway.d.mts +0 -2
- package/dist/runtime/authconnection.d.mts +0 -2
- package/dist/runtime/globals.d.mts +0 -1
- package/dist/runtime/index.d.mts +0 -1
- package/dist/runtime/types.d.mts +16 -7
- package/dist/{runtime-_vUgjiqT.mjs → runtime-BD2vYJFT.mjs} +2382 -1167
- package/dist/runtime-BD2vYJFT.mjs.map +1 -0
- package/dist/{schema-DrVyN-LP.mjs → schema-U6DxoZ8D.mjs} +204 -3
- package/dist/schema-U6DxoZ8D.mjs.map +1 -0
- package/dist/{secret-file-VSVGy1V0.mjs → secret-file-DBqZhjFQ.mjs} +2 -27
- package/dist/{secret-file-VSVGy1V0.mjs.map → secret-file-DBqZhjFQ.mjs.map} +1 -1
- package/dist/{secretmanager-Bd45j7an.mjs → secretmanager-IY4UvinW.mjs} +42 -9
- package/dist/secretmanager-IY4UvinW.mjs.map +1 -0
- package/dist/seed/index.d.mts +3 -2
- package/dist/{seed-fm0LeYP2.mjs → seed-DFFigL8V.mjs} +3 -13
- package/dist/seed-DFFigL8V.mjs.map +1 -0
- package/dist/service-CghpdV-9.mjs +3 -0
- package/dist/{service-DBFZQloR.mjs → service-Cp0I7ZFe.mjs} +3 -3
- package/dist/{service-DBFZQloR.mjs.map → service-Cp0I7ZFe.mjs.map} +1 -1
- package/dist/{service-BCRJ-3NV.mjs → service-D12iQGcS.mjs} +2 -2
- package/dist/{service-BCRJ-3NV.mjs.map → service-D12iQGcS.mjs.map} +1 -1
- package/dist/service_pb-BoLM0X4K.mjs +167 -0
- package/dist/service_pb-BoLM0X4K.mjs.map +1 -0
- package/dist/{service_pb-B2FF59sP.mjs → service_pb-r1T7kpJc.mjs} +1 -1
- package/dist/tailor-proto/src/tailor/v1/aigateway_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/aigateway_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/application_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/application_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/auth_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/auth_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/events_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/events_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/executor_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/executor_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/function_registry_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/http_adapter_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/idp_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/idp_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/metadata_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/metadata_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/meter_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/pipeline_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/pipeline_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/secret_manager_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/secret_manager_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/stateflow_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/stateflow_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/staticwebsite_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/staticwebsite_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/tailordb_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/tailordb_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/telemetryrouter_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/telemetryrouter_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/workflow_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/workflow_resource_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/workspace_pb.d.mts +0 -1
- package/dist/tailor-proto/src/tailor/v1/workspace_resource_pb.d.mts +0 -1
- package/dist/test-env-key-DuZycyWM.mjs +49 -0
- package/dist/test-env-key-DuZycyWM.mjs.map +1 -0
- package/dist/type-source-DH_LH20p.mjs +13 -0
- package/dist/type-source-DH_LH20p.mjs.map +1 -0
- package/dist/types/aigateway.generated.d.mts +5 -2
- package/dist/types/auth-connection.generated.d.mts +11 -5
- package/dist/types/auth.generated.d.mts +118 -85
- package/dist/types/executor.generated.d.mts +76 -84
- package/dist/types/helpers.d.mts +5 -5
- package/dist/types/http-adapter.generated.d.mts +11 -5
- package/dist/types/idp.generated.d.mts +536 -524
- package/dist/types/resolver.generated.d.mts +24 -10
- package/dist/types/staticwebsite.generated.d.mts +7 -3
- package/dist/types/tailordb.generated.d.mts +55 -29
- package/dist/types/workflow.generated.d.mts +9 -4
- package/dist/user-agent-DX2Jnw0-.mjs +28 -0
- package/dist/user-agent-DX2Jnw0-.mjs.map +1 -0
- package/dist/utils/test/index.d.mts +0 -1
- package/dist/utils/test/index.mjs +1 -1
- package/dist/utils/test/mock.d.mts +0 -1
- package/dist/vitest/environment.mjs +1 -1
- package/dist/vitest/index.d.mts +9 -9
- package/dist/vitest/index.mjs +600 -185
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mock-kysely.d.mts +0 -1
- package/dist/vitest/mock.d.mts +8 -8
- package/dist/vitest/mocks/aigateway.d.mts +16 -6
- package/dist/vitest/mocks/authconnection.d.mts +16 -6
- package/dist/vitest/mocks/file.d.mts +24 -6
- package/dist/vitest/mocks/iconv.d.mts +28 -4
- package/dist/vitest/mocks/idp.d.mts +26 -10
- package/dist/vitest/mocks/secretmanager.d.mts +16 -5
- package/dist/vitest/mocks/tailordb.d.mts +54 -10
- package/dist/vitest/mocks/workflow.d.mts +48 -11
- package/dist/vitest/setup.mjs +2 -2
- package/dist/{service_pb-B5w9rjPY.mjs → workspace_resource_pb-Db3fv68L.mjs} +57 -165
- package/dist/workspace_resource_pb-Db3fv68L.mjs.map +1 -0
- package/docs/cli/application.md +58 -11
- package/docs/cli/setup.md +1 -1
- package/docs/cli/workspace.md +6 -4
- package/docs/cli-reference.md +12 -8
- package/docs/runtime.md +3 -4
- package/docs/services/aigateway.md +2 -0
- package/docs/services/tailordb.md +52 -1
- package/docs/services/workflow.md +1 -1
- package/docs/testing.md +103 -89
- package/package.json +16 -16
- package/dist/application-BUfYmf0O.mjs.map +0 -1
- package/dist/application-DbvD5UUz.mjs +0 -3
- package/dist/cli/shared/trigger-context.d.mts +0 -1
- package/dist/errors-Dtf2WPaW.mjs +0 -42
- package/dist/errors-Dtf2WPaW.mjs.map +0 -1
- package/dist/field-runtime-9bfXBAOK.mjs +0 -183
- package/dist/field-runtime-9bfXBAOK.mjs.map +0 -1
- package/dist/job-D-PbD1P3.mjs +0 -55
- package/dist/job-D-PbD1P3.mjs.map +0 -1
- package/dist/logger-BEiZZ3qT.mjs.map +0 -1
- package/dist/registry-NfSW0BRo.mjs.map +0 -1
- package/dist/resource_pb-BRv7AMXK.mjs +0 -49
- package/dist/resource_pb-BRv7AMXK.mjs.map +0 -1
- package/dist/runtime-_vUgjiqT.mjs.map +0 -1
- package/dist/schema-DrVyN-LP.mjs.map +0 -1
- package/dist/secretmanager-Bd45j7an.mjs.map +0 -1
- package/dist/seed-fm0LeYP2.mjs.map +0 -1
- package/dist/service-CPMlIg3X.mjs +0 -3
- package/dist/service_pb-B5w9rjPY.mjs.map +0 -1
- package/dist/types-BwzB2okW.mjs +0 -199
- package/dist/types-BwzB2okW.mjs.map +0 -1
- package/dist/types-KlawuVKa.mjs +0 -4
|
@@ -1,39 +1,48 @@
|
|
|
1
|
-
import { t as db } from "./schema-
|
|
2
|
-
import {
|
|
1
|
+
import { t as db } from "./schema-U6DxoZ8D.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 { o as loadFilesWithIgnores, t as createExecutorService } 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, R as getPlatformBaseUrl, S as hasGenerationHooks, T as hashContent, U as rememberPlatformConfigForToken, V as isDefaultPlatform, W as resolveStaticWebsiteUrls, _ as platformBundleDefinePlugin, d as buildExecutorArgsExpr, f as buildResolverOperationHookExpr, g as stringifyFunction, h as TailorDBTypeSchema, i as resolverBundleKey, j as fetchMachineUserToken, k as fetchAll, l as HTTP_METHODS, m as assertUniqueTailorDBTypeNamesWithExternal, n as generatePluginFilesIfNeeded, o as WorkflowJobFunctionExecutionPolicySchema, p as assertUniqueLocalTailorDBTypeNames, r as loadApplication, t as defineApplication, w as getDistDir, x as getPluginGenerationDependencies, z as initOAuth2Client } from "./application-CUxVA7Fs.mjs";
|
|
6
|
+
import { o as loadFilesWithIgnores, s as functionSchema, t as createExecutorService } from "./service-D12iQGcS.mjs";
|
|
7
|
+
import { n as enumConstantsPlugin, t as EnumConstantsGeneratorID } from "./enum-constants-j9QBF0cB.mjs";
|
|
7
8
|
import { t as multiline } from "./multiline-sfHpTZZK.mjs";
|
|
9
|
+
import { n as fileUtilsPlugin, t as FileUtilsGeneratorID } from "./file-utils-DcyIPFQh.mjs";
|
|
10
|
+
import { n as kyselyTypePlugin, t as KyselyGeneratorID } from "./kysely-type-D-zflMGf.mjs";
|
|
11
|
+
import { n as seedPlugin, t as SeedGeneratorID } from "./seed-DFFigL8V.mjs";
|
|
8
12
|
import { t as readPackageJson } from "./package-json-8b0O9TlX.mjs";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
13
|
+
import { t as userAgent } from "./user-agent-DX2Jnw0-.mjs";
|
|
14
|
+
import { r as isCLIError, t as createCLIError } from "./errors-DVFS0AZk.mjs";
|
|
15
|
+
import { n as tightenSecretFilePermissions, r as writeSecretFile } from "./secret-file-DBqZhjFQ.mjs";
|
|
12
16
|
import { r as withSpan } from "./telemetry-CdqJEzkj.mjs";
|
|
13
17
|
import { arg, createDefineCommand, defineCommand, runCommand } from "politty";
|
|
14
18
|
import { z } from "zod";
|
|
15
|
-
import {
|
|
19
|
+
import { create, fromJson, toJson } from "@bufbuild/protobuf";
|
|
16
20
|
import * as fs$1 from "node:fs";
|
|
17
|
-
import {
|
|
21
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { parseEnv } from "node:util";
|
|
18
23
|
import { ValueSchema, timestampDate } from "@bufbuild/protobuf/wkt";
|
|
19
24
|
import * as path from "pathe";
|
|
25
|
+
import { basename, dirname, join, resolve } from "pathe";
|
|
20
26
|
import { formatDistanceToNowStrict } from "date-fns";
|
|
21
27
|
import { getBorderCharacters, table } from "table";
|
|
22
28
|
import { pathToFileURL } from "node:url";
|
|
29
|
+
import * as os from "node:os";
|
|
23
30
|
import { tmpdir } from "node:os";
|
|
31
|
+
import { parseTOML, parseYAML, stringifyYAML } from "confbox";
|
|
24
32
|
import { findUpSync } from "find-up-simple";
|
|
33
|
+
import { lt } from "semver";
|
|
25
34
|
import { xdgConfig } from "xdg-basedir";
|
|
26
35
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
27
36
|
import pLimit from "p-limit";
|
|
28
37
|
import { resolveTSConfig } from "pkg-types";
|
|
29
38
|
import * as crypto$1 from "node:crypto";
|
|
30
|
-
import { createHash } from "node:crypto";
|
|
39
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
31
40
|
import { ExitPromptError } from "@inquirer/core";
|
|
32
|
-
import { confirm, input, password } from "@inquirer/prompts";
|
|
41
|
+
import { confirm, input, password, select } from "@inquirer/prompts";
|
|
33
42
|
import { isCI } from "std-env";
|
|
34
43
|
import * as rolldown from "rolldown";
|
|
35
44
|
import * as fs from "node:fs/promises";
|
|
36
|
-
import { glob } from "node:fs/promises";
|
|
45
|
+
import { glob, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
37
46
|
import { parseSync } from "oxc-parser";
|
|
38
47
|
import * as inflection from "inflection";
|
|
39
48
|
import { pathToString } from "@bufbuild/protobuf/reflect";
|
|
@@ -47,652 +56,1200 @@ import { createPrompt } from "@toiroakr/read-multiline";
|
|
|
47
56
|
import { astVisitor, parse } from "pgsql-ast-parser";
|
|
48
57
|
import { parse as parse$1 } from "@0no-co/graphql.web";
|
|
49
58
|
|
|
50
|
-
//#region src/cli/shared/
|
|
59
|
+
//#region src/cli/shared/args.ts
|
|
60
|
+
const unitToMs = {
|
|
61
|
+
ms: 1,
|
|
62
|
+
s: 1e3,
|
|
63
|
+
m: 60 * 1e3
|
|
64
|
+
};
|
|
65
|
+
const durationPattern = /^(\d+)(ms|s|m)$/;
|
|
51
66
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* Parent commands with only `subCommands` can use `defineCommand` from politty directly.
|
|
67
|
+
* Schema for duration string validation (e.g., "3s", "500ms", "1m")
|
|
68
|
+
* Only validates format; use parseDuration() to convert to milliseconds
|
|
55
69
|
*/
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
const durationArg = z.string().refine((val) => durationPattern.test(val), { message: "Invalid duration format. Expected format: '3s', '500ms', '1m'" }).refine((val) => {
|
|
71
|
+
const match = val.match(durationPattern);
|
|
72
|
+
if (!match) return false;
|
|
73
|
+
const digits = match[1];
|
|
74
|
+
return digits !== void 0 && parseInt(digits, 10) > 0;
|
|
75
|
+
}, { message: "Duration must be greater than 0" });
|
|
60
76
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* 1. `opts.profile` (CLI flag)
|
|
65
|
-
* 2. `process.env.TAILOR_PLATFORM_PROFILE`
|
|
66
|
-
*
|
|
67
|
-
* If neither is set, no profile is in scope so the call is allowed. This is
|
|
68
|
-
* intentional: `TAILOR_PLATFORM_TOKEN` direct access (CI / machine user) and
|
|
69
|
-
* `--workspace-id` without a profile are out-of-band paths whose authorization
|
|
70
|
-
* is governed by the bearer token itself, not by the local profile flag.
|
|
71
|
-
*
|
|
72
|
-
* If the resolved profile cannot be found in the config, this function returns
|
|
73
|
-
* silently and lets downstream loaders surface the not-found error.
|
|
74
|
-
* @param opts - Options
|
|
75
|
-
* @param opts.profile - Optional explicit profile name from command args
|
|
77
|
+
* Parse a validated duration string into milliseconds
|
|
78
|
+
* @param duration - Duration string (e.g., "3s", "500ms", "1m")
|
|
79
|
+
* @returns Duration in milliseconds
|
|
76
80
|
*/
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
throw createCLIError({
|
|
83
|
-
code: "PROFILE_READONLY",
|
|
84
|
-
message: `Profile "${profileName}" is read-only.`,
|
|
85
|
-
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.",
|
|
86
|
-
suggestion: `Use a different profile, unset TAILOR_PLATFORM_PROFILE, or run 'tailor-sdk profile update ${profileName} --permission write'.`
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
//#endregion
|
|
91
|
-
//#region src/cli/commands/api/api-call.ts
|
|
92
|
-
function hasEnvAccessToken() {
|
|
93
|
-
const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
|
|
94
|
-
return Boolean(envToken);
|
|
81
|
+
function parseDuration(duration) {
|
|
82
|
+
const match = assertDefined(duration.match(durationPattern), `invalid duration format: ${duration}`);
|
|
83
|
+
const value = parseInt(assertDefined(match[1], "duration digits group missing"), 10);
|
|
84
|
+
const unit = assertDefined(match[2], "duration unit group missing");
|
|
85
|
+
return value * unitToMs[unit];
|
|
95
86
|
}
|
|
96
87
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* @param options - API call options (profile, endpoint, body)
|
|
100
|
-
* @returns Response status and data
|
|
88
|
+
* Schema for positive integer validation (from string input)
|
|
89
|
+
* Transforms the string to a number
|
|
101
90
|
*/
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
status: response.status,
|
|
127
|
-
data
|
|
128
|
-
};
|
|
91
|
+
const positiveIntArg = z.coerce.number().int().positive();
|
|
92
|
+
/**
|
|
93
|
+
* Schema for non-negative integer validation (from string input).
|
|
94
|
+
* Accepts 0 (used for `--limit 0` to disable the limit).
|
|
95
|
+
*/
|
|
96
|
+
const nonNegativeIntArg = z.coerce.number().int().nonnegative();
|
|
97
|
+
/**
|
|
98
|
+
* Schema for sort order (`asc` or `desc`).
|
|
99
|
+
*/
|
|
100
|
+
const orderArg = z.enum(["asc", "desc"]);
|
|
101
|
+
/**
|
|
102
|
+
* Translate a CLI `--order` value into the proto `PageDirection` enum.
|
|
103
|
+
* Returns `undefined` when the user did not specify an order so that
|
|
104
|
+
* callers can omit the field and fall back to the server default.
|
|
105
|
+
* @param order - Order string from CLI args (`"asc"` | `"desc"` | undefined)
|
|
106
|
+
* @returns PageDirection, or undefined when `order` is undefined
|
|
107
|
+
*/
|
|
108
|
+
function toPageDirection(order) {
|
|
109
|
+
if (order === void 0) return void 0;
|
|
110
|
+
return order === "asc" ? PageDirection.ASC : PageDirection.DESC;
|
|
111
|
+
}
|
|
112
|
+
function hasMachineUserFlag(argv) {
|
|
113
|
+
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="));
|
|
129
114
|
}
|
|
130
|
-
|
|
131
|
-
//#endregion
|
|
132
|
-
//#region src/cli/commands/api/proto-reflect.ts
|
|
133
|
-
const UNARY_METHODS = OperatorService.methods.filter((m) => m.methodKind === "unary");
|
|
134
|
-
function listMethodNames() {
|
|
135
|
-
return UNARY_METHODS.map((m) => m.name).toSorted();
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Returns every accepted form of the `endpoint` positional — bare method name
|
|
139
|
-
* and fully-qualified `service/Method`. politty's expand completion keys its
|
|
140
|
-
* static table by the literal endpoint string, so both forms must appear in
|
|
141
|
-
* `choices` or the user typing the fully-qualified name gets no `--field`
|
|
142
|
-
* completion candidates back at TAB time.
|
|
143
|
-
* @returns Sorted list of accepted endpoint values
|
|
144
|
-
*/
|
|
145
|
-
function listMethodChoices() {
|
|
146
|
-
return UNARY_METHODS.flatMap((m) => [m.name, `${OperatorService.typeName}/${m.name}`]).toSorted();
|
|
147
|
-
}
|
|
148
|
-
function getMethodDescriptor(methodName) {
|
|
149
|
-
return UNARY_METHODS.find((m) => m.name === methodName);
|
|
150
|
-
}
|
|
151
|
-
function extractMethodName(endpoint) {
|
|
152
|
-
if (!endpoint.includes("/")) return endpoint;
|
|
153
|
-
return endpoint.split("/").pop() ?? endpoint;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Returns the nested message descriptor when the field is a message, a list of
|
|
157
|
-
* messages, or a map with message values. Otherwise returns undefined.
|
|
158
|
-
* @param field - The proto field descriptor to inspect
|
|
159
|
-
* @returns The nested message descriptor, or undefined when the field isn't message-shaped
|
|
160
|
-
*/
|
|
161
|
-
function nestedMessage(field) {
|
|
162
|
-
if (field.fieldKind === "message") return field.message;
|
|
163
|
-
if (field.fieldKind === "list" && field.listKind === "message") return field.message;
|
|
164
|
-
if (field.fieldKind === "map" && field.mapKind === "message") return field.message;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Most `google.protobuf.*` well-known types serialize as scalars in proto JSON
|
|
168
|
-
* (Timestamp/Duration → RFC3339/duration string, FieldMask → comma-joined
|
|
169
|
-
* string, *Value wrappers → underlying primitive, etc.), so we must not let
|
|
170
|
-
* `--field` drill into their internal fields. Treating them as leaves keeps
|
|
171
|
-
* the completion and the body shape consistent with what the server expects.
|
|
172
|
-
* @param message - The proto message descriptor to inspect
|
|
173
|
-
* @returns True when the message is a well-known type
|
|
174
|
-
*/
|
|
175
|
-
function isWellKnownType(message) {
|
|
176
|
-
return message.typeName.startsWith("google.protobuf.");
|
|
177
|
-
}
|
|
178
|
-
const UNREPRESENTABLE_WELL_KNOWN_TYPES = /* @__PURE__ */ new Set([
|
|
179
|
-
"google.protobuf.Struct",
|
|
180
|
-
"google.protobuf.Value",
|
|
181
|
-
"google.protobuf.ListValue",
|
|
182
|
-
"google.protobuf.NullValue",
|
|
183
|
-
"google.protobuf.Any",
|
|
184
|
-
"google.protobuf.Empty"
|
|
185
|
-
]);
|
|
186
115
|
/**
|
|
187
|
-
*
|
|
188
|
-
* @
|
|
116
|
+
* Resolve whether a parsed machine user value came from an explicit CLI option or env fallback.
|
|
117
|
+
* @param machineUser - Parsed machine user value
|
|
118
|
+
* @param argv - Raw CLI argv, excluding the executable and script path
|
|
119
|
+
* @param options - Source resolution options
|
|
120
|
+
* @returns Machine user input source, or undefined when no value was parsed
|
|
189
121
|
*/
|
|
190
|
-
function
|
|
191
|
-
|
|
122
|
+
function resolveMachineUserInputSource(machineUser, argv = process.argv.slice(2), options = {}) {
|
|
123
|
+
if (machineUser === void 0) return void 0;
|
|
124
|
+
if (options.valueIsExplicit) return "option";
|
|
125
|
+
if (hasMachineUserFlag(argv)) return "option";
|
|
126
|
+
return process.env.TAILOR_PLATFORM_MACHINE_USER_NAME === machineUser ? "env" : "option";
|
|
192
127
|
}
|
|
193
128
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* prefix-filter at TAB time. Used by the `expand` completion variant so no
|
|
197
|
-
* Node process is spawned per keystroke.
|
|
198
|
-
*
|
|
199
|
-
* For each leaf field, emits `key=` (key completion). Enum leaves additionally
|
|
200
|
-
* emit `key=ENUM_VALUE` per value, bool leaves emit `key=true` and
|
|
201
|
-
* `key=false`. For nested messages, emits `key.` (drill-down) and recurses.
|
|
129
|
+
* Load env files from parsed arguments.
|
|
130
|
+
* Processes --env-file first, then --env-file-if-exists.
|
|
202
131
|
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* @
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const fullKey = prefix + field.localName;
|
|
218
|
-
if (field.fieldKind === "message") {
|
|
219
|
-
if (isUnrepresentableWellKnownType(field.message)) continue;
|
|
220
|
-
if (!isWellKnownType(field.message)) {
|
|
221
|
-
const nested = field.message;
|
|
222
|
-
candidates.push({
|
|
223
|
-
value: `${fullKey}.`,
|
|
224
|
-
description: `${fullKey} (message)`
|
|
225
|
-
});
|
|
226
|
-
if (!visited.has(nested)) walk(nested, `${fullKey}.`);
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
132
|
+
* Follows Node.js --env-file behavior:
|
|
133
|
+
* - Variables already set in the environment are NOT overwritten
|
|
134
|
+
* - Variables from later files override those from earlier files
|
|
135
|
+
* @param envFiles - Required env file path(s) that must exist
|
|
136
|
+
* @param envFilesIfExists - Optional env file path(s) that are loaded if they exist
|
|
137
|
+
*/
|
|
138
|
+
function loadEnvFiles(envFiles, envFilesIfExists) {
|
|
139
|
+
const originalEnvKeys = new Set(Object.keys(process.env));
|
|
140
|
+
const load = (files, required) => {
|
|
141
|
+
for (const file of [files ?? []].flat()) {
|
|
142
|
+
const envPath = path.resolve(process.cwd(), file);
|
|
143
|
+
if (!fs$1.existsSync(envPath)) {
|
|
144
|
+
if (required) throw new Error(`Environment file not found: ${envPath}`);
|
|
145
|
+
continue;
|
|
229
146
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (field.fieldKind === "enum") for (const v of field.enum.values) candidates.push({
|
|
235
|
-
value: `${fullKey}=${v.name}`,
|
|
236
|
-
description: v.name
|
|
237
|
-
});
|
|
238
|
-
else if (field.fieldKind === "scalar" && field.scalar === ScalarType.BOOL) {
|
|
239
|
-
candidates.push({
|
|
240
|
-
value: `${fullKey}=true`,
|
|
241
|
-
description: "true"
|
|
242
|
-
});
|
|
243
|
-
candidates.push({
|
|
244
|
-
value: `${fullKey}=false`,
|
|
245
|
-
description: "false"
|
|
246
|
-
});
|
|
147
|
+
const parsed = parseEnv(fs$1.readFileSync(envPath, "utf-8"));
|
|
148
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
149
|
+
if (originalEnvKeys.has(key)) continue;
|
|
150
|
+
process.env[key] = value;
|
|
247
151
|
}
|
|
248
152
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return candidates;
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Resolves the leaf `DescField` at a dotted `--field` path within an input
|
|
256
|
-
* message tree. Returns undefined when any segment is missing or when a
|
|
257
|
-
* non-leaf segment isn't a singular message — callers fall back to treating
|
|
258
|
-
* the value as a string in that case.
|
|
259
|
-
* @param input - The RPC input message descriptor
|
|
260
|
-
* @param path - Dot-split path segments to follow (e.g. ["application", "name"])
|
|
261
|
-
* @returns The leaf field descriptor, or undefined when the path doesn't resolve
|
|
262
|
-
*/
|
|
263
|
-
function resolveLeafField(input, path) {
|
|
264
|
-
let message = input;
|
|
265
|
-
for (let i = 0; i < path.length; i++) {
|
|
266
|
-
const field = message.fields.find((f) => f.localName === path[i]);
|
|
267
|
-
if (!field) return void 0;
|
|
268
|
-
if (i === path.length - 1) {
|
|
269
|
-
if (field.fieldKind === "message" && isUnrepresentableWellKnownType(field.message)) return;
|
|
270
|
-
return field;
|
|
271
|
-
}
|
|
272
|
-
if (field.fieldKind !== "message") return void 0;
|
|
273
|
-
if (isWellKnownType(field.message)) return void 0;
|
|
274
|
-
message = field.message;
|
|
275
|
-
}
|
|
153
|
+
};
|
|
154
|
+
load(envFiles, true);
|
|
155
|
+
load(envFilesIfExists, false);
|
|
276
156
|
}
|
|
277
|
-
|
|
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
|
-
function enumOf(field) {
|
|
308
|
-
if (field.fieldKind === "enum") return field.enum;
|
|
309
|
-
if (field.fieldKind === "list" && field.listKind === "enum") return field.enum;
|
|
310
|
-
}
|
|
311
|
-
function describeFieldType(field) {
|
|
312
|
-
switch (field.fieldKind) {
|
|
313
|
-
case "scalar": return scalarLabel(field.scalar);
|
|
314
|
-
case "enum": return enumLabel(field.enum);
|
|
315
|
-
case "message": return shortName(field.message.typeName);
|
|
316
|
-
case "list": {
|
|
317
|
-
let inner;
|
|
318
|
-
if (field.listKind === "scalar") inner = scalarLabel(field.scalar);
|
|
319
|
-
else if (field.listKind === "enum") inner = enumLabel(field.enum);
|
|
320
|
-
else inner = shortName(field.message.typeName);
|
|
321
|
-
return `repeated ${inner}`;
|
|
157
|
+
/**
|
|
158
|
+
* Common arguments for all CLI commands
|
|
159
|
+
*
|
|
160
|
+
* NOTE: --env-file and --env-file-if-exists collide with Node.js flags due to a bug
|
|
161
|
+
* (https://github.com/nodejs/node/issues/54232). Node.js parses these even after the
|
|
162
|
+
* script path, causing warnings (twice due to tsx loader).
|
|
163
|
+
*/
|
|
164
|
+
const commonArgs = {
|
|
165
|
+
"env-file": arg(z.string().optional(), {
|
|
166
|
+
alias: "e",
|
|
167
|
+
description: "Path to the environment file (error if not found)",
|
|
168
|
+
completion: {
|
|
169
|
+
type: "file",
|
|
170
|
+
matcher: [".env.*", ".env"]
|
|
171
|
+
}
|
|
172
|
+
}),
|
|
173
|
+
"env-file-if-exists": arg(z.string().optional(), {
|
|
174
|
+
description: "Path to the environment file (ignored if not found)",
|
|
175
|
+
completion: {
|
|
176
|
+
type: "file",
|
|
177
|
+
matcher: [".env.*", ".env"]
|
|
178
|
+
},
|
|
179
|
+
effect: (_value, { args }) => {
|
|
180
|
+
loadEnvFiles(args["env-file"], args["env-file-if-exists"]);
|
|
181
|
+
}
|
|
182
|
+
}),
|
|
183
|
+
verbose: arg(z.boolean().default(false), {
|
|
184
|
+
description: "Enable verbose logging",
|
|
185
|
+
effect: (value) => {
|
|
186
|
+
verboseMode = value;
|
|
322
187
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return `map<${keyType}, ${valType}>`;
|
|
188
|
+
}),
|
|
189
|
+
json: arg(z.boolean().default(false), {
|
|
190
|
+
alias: "j",
|
|
191
|
+
description: "Output as JSON",
|
|
192
|
+
effect: (value) => {
|
|
193
|
+
logger.jsonMode = value;
|
|
330
194
|
}
|
|
331
|
-
|
|
195
|
+
})
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Arguments for commands that require workspace context
|
|
199
|
+
*/
|
|
200
|
+
const workspaceArgs = {
|
|
201
|
+
"workspace-id": arg(z.string().optional(), {
|
|
202
|
+
alias: "w",
|
|
203
|
+
description: "Workspace ID",
|
|
204
|
+
env: "TAILOR_PLATFORM_WORKSPACE_ID",
|
|
205
|
+
completion: { type: "none" }
|
|
206
|
+
}),
|
|
207
|
+
profile: arg(z.string().optional(), {
|
|
208
|
+
alias: "p",
|
|
209
|
+
description: "Workspace profile",
|
|
210
|
+
env: "TAILOR_PLATFORM_PROFILE",
|
|
211
|
+
completion: { type: "none" }
|
|
212
|
+
})
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Shared config arg for commands that accept a config file path
|
|
216
|
+
*/
|
|
217
|
+
const configArg = { config: arg(z.string().default("tailor.config.ts"), {
|
|
218
|
+
alias: "c",
|
|
219
|
+
description: "Path to SDK config file",
|
|
220
|
+
env: "TAILOR_PLATFORM_SDK_CONFIG_PATH",
|
|
221
|
+
completion: {
|
|
222
|
+
type: "file",
|
|
223
|
+
extensions: ["ts"]
|
|
332
224
|
}
|
|
333
|
-
}
|
|
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
|
-
|
|
225
|
+
}) };
|
|
226
|
+
/**
|
|
227
|
+
* Shared config arg for commands that accept one or more comma-separated config file paths
|
|
228
|
+
*/
|
|
229
|
+
const multiConfigArg = { config: arg(z.string().default("tailor.config.ts"), {
|
|
230
|
+
alias: "c",
|
|
231
|
+
description: "Path to SDK config file. Use comma-separated paths to deploy multiple apps together.",
|
|
232
|
+
env: "TAILOR_PLATFORM_SDK_CONFIG_PATH",
|
|
233
|
+
completion: {
|
|
234
|
+
type: "file",
|
|
235
|
+
extensions: ["ts"]
|
|
236
|
+
}
|
|
237
|
+
}) };
|
|
238
|
+
/**
|
|
239
|
+
* Arguments for commands that interact with deployed resources (includes config)
|
|
240
|
+
*/
|
|
241
|
+
const deploymentArgs = {
|
|
242
|
+
...workspaceArgs,
|
|
243
|
+
...configArg
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Arguments for commands that require confirmation
|
|
247
|
+
*/
|
|
248
|
+
const confirmationArgs = { yes: arg(z.boolean().default(false), {
|
|
249
|
+
alias: "y",
|
|
250
|
+
description: "Skip confirmation prompts"
|
|
251
|
+
}) };
|
|
252
|
+
/**
|
|
253
|
+
* Arguments for commands that require organization context
|
|
254
|
+
*/
|
|
255
|
+
const organizationArgs = { "organization-id": arg(z.string(), {
|
|
256
|
+
alias: "o",
|
|
257
|
+
description: "Organization ID",
|
|
258
|
+
env: "TAILOR_PLATFORM_ORGANIZATION_ID",
|
|
259
|
+
completion: { type: "none" }
|
|
260
|
+
}) };
|
|
261
|
+
/**
|
|
262
|
+
* Arguments for list commands that accept `--order` / `--limit`. Sort
|
|
263
|
+
* order defaults to `desc` (newest first) because most callers want the
|
|
264
|
+
* latest items; pass `--order asc` to opt in to ascending order. The
|
|
265
|
+
* limit is unbounded by default so existing invocations keep returning
|
|
266
|
+
* every item; pass `--limit N` to cap the result size.
|
|
267
|
+
* @param defaultOrder - Default value for `--order` (defaults to `"desc"`)
|
|
268
|
+
* @returns Argument shape suitable for spreading into a command schema
|
|
269
|
+
*/
|
|
270
|
+
const paginationArgs = (defaultOrder = "desc") => ({
|
|
271
|
+
order: arg(orderArg.default(defaultOrder), { description: "Sort order (asc or desc)" }),
|
|
272
|
+
limit: arg(nonNegativeIntArg.optional(), {
|
|
273
|
+
alias: "l",
|
|
274
|
+
description: "Maximum number of items to return (0 or omit: unlimited)"
|
|
275
|
+
})
|
|
276
|
+
});
|
|
277
|
+
/**
|
|
278
|
+
* Arguments for time-series log list commands. Defaults to newest-first
|
|
279
|
+
* (`desc`) and a 50-item cap so that listing stays responsive on busy
|
|
280
|
+
* workspaces. Pass `--limit 0` to disable the cap and fetch all entries.
|
|
281
|
+
*/
|
|
282
|
+
const pagedLogArgs = {
|
|
283
|
+
order: arg(orderArg.default("desc"), { description: "Sort order (asc or desc)" }),
|
|
284
|
+
limit: arg(nonNegativeIntArg.default(50), {
|
|
285
|
+
alias: "l",
|
|
286
|
+
description: "Maximum number of items to return (0: unlimited)"
|
|
287
|
+
})
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* Arguments for commands that require folder context
|
|
291
|
+
*/
|
|
292
|
+
const folderArgs = { "folder-id": arg(z.string(), {
|
|
293
|
+
alias: "f",
|
|
294
|
+
description: "Folder ID",
|
|
295
|
+
env: "TAILOR_PLATFORM_FOLDER_ID",
|
|
296
|
+
completion: { type: "none" }
|
|
297
|
+
}) };
|
|
298
|
+
let verboseMode = false;
|
|
299
|
+
/**
|
|
300
|
+
* Returns whether verbose mode is enabled.
|
|
301
|
+
* Used by the global cleanup handler which doesn't have access to parsed args.
|
|
302
|
+
* @returns Whether verbose mode is enabled
|
|
303
|
+
*/
|
|
304
|
+
function isVerbose() {
|
|
305
|
+
return verboseMode;
|
|
396
306
|
}
|
|
397
307
|
|
|
398
308
|
//#endregion
|
|
399
|
-
//#region src/cli/
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
309
|
+
//#region src/cli/shared/command.ts
|
|
310
|
+
/**
|
|
311
|
+
* defineCommand with global args type (CommonArgsType).
|
|
312
|
+
* Use this for leaf commands with `run` to get type-safe access to global args.
|
|
313
|
+
* Parent commands with only `subCommands` can use `defineCommand` from politty directly.
|
|
314
|
+
*/
|
|
315
|
+
const defineAppCommand = createDefineCommand();
|
|
316
|
+
|
|
317
|
+
//#endregion
|
|
318
|
+
//#region src/parser/app-config/schema.ts
|
|
319
|
+
const envValueSchema = z.union([
|
|
320
|
+
z.string(),
|
|
321
|
+
z.number(),
|
|
322
|
+
z.boolean()
|
|
323
|
+
]);
|
|
324
|
+
const LogLevelSchema = z.enum(LOG_LEVELS);
|
|
325
|
+
const logLevelSchema = z.string().refine((value) => LogLevelSchema.safeParse(value.trim().toUpperCase()).success, { message: `'logLevel' must be one of: ${LOG_LEVELS.join(", ")}.` });
|
|
326
|
+
/**
|
|
327
|
+
* Structural validation schema for `defineConfig({...})`. Validates only
|
|
328
|
+
* top-level fields with platform-side constraints (notably `id`); fields
|
|
329
|
+
* that carry SDK builder objects (`auth`, `idp`, `db`, ...) are accepted
|
|
330
|
+
* as opaque values, since their internal shapes are validated by their
|
|
331
|
+
* own factory functions and parser-level schemas.
|
|
332
|
+
*
|
|
333
|
+
* The `id` is auto-managed by `deploy` and stored as a plain UUID. A
|
|
334
|
+
* label-compatible prefix is added at the metadata boundary, so user-facing
|
|
335
|
+
* configs only need to carry a UUID.
|
|
336
|
+
*/
|
|
337
|
+
const AppConfigSchema = z.object({
|
|
338
|
+
id: z.uuid({ message: "'id' must be a UUID." }).optional(),
|
|
339
|
+
name: z.string().min(1, { message: "'name' must be a non-empty string." }),
|
|
340
|
+
env: z.record(z.string(), envValueSchema).optional(),
|
|
341
|
+
cors: z.array(z.string()).optional(),
|
|
342
|
+
allowedIpAddresses: z.array(z.string()).optional(),
|
|
343
|
+
disableIntrospection: z.boolean().optional(),
|
|
344
|
+
inlineSourcemap: z.boolean().optional(),
|
|
345
|
+
logLevel: logLevelSchema.optional(),
|
|
346
|
+
db: z.unknown().optional(),
|
|
347
|
+
resolver: z.unknown().optional(),
|
|
348
|
+
idp: z.unknown().optional(),
|
|
349
|
+
auth: z.unknown().optional(),
|
|
350
|
+
executor: z.unknown().optional(),
|
|
351
|
+
workflow: z.unknown().optional(),
|
|
352
|
+
httpAdapter: z.unknown().optional(),
|
|
353
|
+
staticWebsites: z.unknown().optional(),
|
|
354
|
+
aiGateways: z.unknown().optional(),
|
|
355
|
+
secrets: z.unknown().optional()
|
|
427
356
|
});
|
|
428
357
|
|
|
429
358
|
//#endregion
|
|
430
|
-
//#region src/
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
359
|
+
//#region src/parser/generator-config/schema.ts
|
|
360
|
+
const DependencyKindSchema = z.enum([
|
|
361
|
+
"tailordb",
|
|
362
|
+
"resolver",
|
|
363
|
+
"executor"
|
|
364
|
+
]);
|
|
365
|
+
const KyselyTypeConfigSchema = z.tuple([z.literal("@tailor-platform/kysely-type"), z.object({ distPath: z.string() })]);
|
|
366
|
+
const SeedConfigSchema = z.tuple([z.literal("@tailor-platform/seed"), z.object({
|
|
367
|
+
distPath: z.string(),
|
|
368
|
+
machineUserName: z.string().optional(),
|
|
369
|
+
disableIdpUserSync: z.object({
|
|
370
|
+
userToIdp: z.boolean().optional(),
|
|
371
|
+
idpToUser: z.boolean().optional()
|
|
372
|
+
}).optional()
|
|
373
|
+
})]);
|
|
374
|
+
const EnumConstantsConfigSchema = z.tuple([z.literal("@tailor-platform/enum-constants"), z.object({ distPath: z.string() })]);
|
|
375
|
+
const FileUtilsConfigSchema = z.tuple([z.literal("@tailor-platform/file-utils"), z.object({ distPath: z.string() })]);
|
|
376
|
+
const CodeGeneratorSchema = z.object({
|
|
377
|
+
id: z.string(),
|
|
378
|
+
description: z.string(),
|
|
379
|
+
dependencies: z.array(DependencyKindSchema),
|
|
380
|
+
processType: z.function().optional(),
|
|
381
|
+
processResolver: z.function().optional(),
|
|
382
|
+
processExecutor: z.function().optional(),
|
|
383
|
+
processTailorDBNamespace: z.function().optional(),
|
|
384
|
+
processResolverNamespace: z.function().optional(),
|
|
385
|
+
aggregate: z.function({ output: z.any() })
|
|
441
386
|
});
|
|
387
|
+
const BaseGeneratorConfigSchema = z.union([
|
|
388
|
+
KyselyTypeConfigSchema,
|
|
389
|
+
SeedConfigSchema,
|
|
390
|
+
EnumConstantsConfigSchema,
|
|
391
|
+
FileUtilsConfigSchema,
|
|
392
|
+
CodeGeneratorSchema
|
|
393
|
+
]);
|
|
442
394
|
|
|
443
395
|
//#endregion
|
|
444
|
-
//#region src/
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
396
|
+
//#region src/parser/plugin-config/schema.ts
|
|
397
|
+
const PluginConfigSchema = z.object({
|
|
398
|
+
id: z.string(),
|
|
399
|
+
description: z.string(),
|
|
400
|
+
importPath: z.string().optional(),
|
|
401
|
+
pluginConfig: z.unknown().optional(),
|
|
402
|
+
typeConfigRequired: z.union([z.boolean(), functionSchema]).optional(),
|
|
403
|
+
onTypeLoaded: functionSchema.optional(),
|
|
404
|
+
onNamespaceLoaded: functionSchema.optional(),
|
|
405
|
+
onTailorDBReady: functionSchema.optional(),
|
|
406
|
+
onResolverReady: functionSchema.optional(),
|
|
407
|
+
onExecutorReady: functionSchema.optional()
|
|
408
|
+
}).passthrough().refine((p) => {
|
|
409
|
+
return !(p.onTypeLoaded || p.onNamespaceLoaded) || !!p.importPath;
|
|
410
|
+
}, { message: "importPath is required when plugin has definition-time hooks (onTypeLoaded/onNamespaceLoaded)" }).transform((plugin) => plugin);
|
|
411
|
+
|
|
412
|
+
//#endregion
|
|
413
|
+
//#region src/plugin/builtin/registry.ts
|
|
414
|
+
const builtinPlugins = /* @__PURE__ */ new Map([
|
|
415
|
+
[KyselyGeneratorID, (options) => kyselyTypePlugin(options)],
|
|
416
|
+
[SeedGeneratorID, (options) => seedPlugin(options)],
|
|
417
|
+
[EnumConstantsGeneratorID, (options) => enumConstantsPlugin(options)],
|
|
418
|
+
[FileUtilsGeneratorID, (options) => fileUtilsPlugin(options)]
|
|
419
|
+
]);
|
|
420
|
+
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region src/cli/shared/token-store.ts
|
|
423
|
+
const SERVICE_NAME = "tailor-platform-cli";
|
|
424
|
+
let entryClass;
|
|
425
|
+
async function getEntryClass() {
|
|
426
|
+
if (entryClass !== void 0) return entryClass;
|
|
427
|
+
try {
|
|
428
|
+
const mod = await import("@napi-rs/keyring");
|
|
429
|
+
const probe = new mod.Entry(SERVICE_NAME, "__probe__");
|
|
430
|
+
probe.setPassword("probe");
|
|
431
|
+
probe.deletePassword();
|
|
432
|
+
entryClass = mod.Entry;
|
|
433
|
+
} catch {
|
|
434
|
+
logger.warn("System keyring is not available. Tokens will be stored in the config file. Set TAILOR_PLATFORM_TOKEN environment variable for CI environments.");
|
|
435
|
+
entryClass = false;
|
|
460
436
|
}
|
|
437
|
+
return entryClass;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Check whether the OS keyring is available and functional.
|
|
441
|
+
* @returns true if keyring is available
|
|
442
|
+
*/
|
|
443
|
+
async function isKeyringAvailable() {
|
|
444
|
+
return await getEntryClass() !== false;
|
|
461
445
|
}
|
|
462
446
|
/**
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
* @param body - Raw body string
|
|
467
|
-
* @returns Parsed object or undefined
|
|
447
|
+
* Load tokens from the OS keyring for a given account.
|
|
448
|
+
* @param account - User identifier (e.g. email or client ID)
|
|
449
|
+
* @returns Token data or undefined if not found or keyring unavailable
|
|
468
450
|
*/
|
|
469
|
-
function
|
|
470
|
-
|
|
451
|
+
async function loadKeyringTokens(account) {
|
|
452
|
+
const Entry = await getEntryClass();
|
|
453
|
+
if (!Entry) return void 0;
|
|
471
454
|
try {
|
|
472
|
-
|
|
455
|
+
const raw = new Entry(SERVICE_NAME, account).getPassword();
|
|
456
|
+
if (raw === null) return void 0;
|
|
457
|
+
return JSON.parse(raw);
|
|
473
458
|
} catch {
|
|
474
459
|
return;
|
|
475
460
|
}
|
|
476
|
-
|
|
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
|
-
const FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set([
|
|
544
|
-
"__proto__",
|
|
545
|
-
"constructor",
|
|
546
|
-
"prototype"
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Save tokens to the OS keyring for a given account.
|
|
464
|
+
* @param account - User identifier (e.g. email or client ID)
|
|
465
|
+
* @param tokens - Token data to store
|
|
466
|
+
*/
|
|
467
|
+
async function saveKeyringTokens(account, tokens) {
|
|
468
|
+
const Entry = await getEntryClass();
|
|
469
|
+
if (!Entry) return;
|
|
470
|
+
new Entry(SERVICE_NAME, account).setPassword(JSON.stringify(tokens));
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Delete tokens from the OS keyring for a given account.
|
|
474
|
+
* @param account - User identifier (e.g. email or client ID)
|
|
475
|
+
*/
|
|
476
|
+
async function deleteKeyringTokens(account) {
|
|
477
|
+
const Entry = await getEntryClass();
|
|
478
|
+
if (!Entry) return;
|
|
479
|
+
try {
|
|
480
|
+
new Entry(SERVICE_NAME, account).deletePassword();
|
|
481
|
+
} catch {}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region src/cli/shared/context.ts
|
|
486
|
+
const pfProfileSchema = z.object({
|
|
487
|
+
user: z.string(),
|
|
488
|
+
workspace_id: z.string(),
|
|
489
|
+
readonly: z.boolean().optional(),
|
|
490
|
+
machine_user: z.string().optional(),
|
|
491
|
+
machine_user_override: z.enum(["allow", "deny"]).optional(),
|
|
492
|
+
platform_url: z.url().optional(),
|
|
493
|
+
oauth2_client_id: z.string().optional(),
|
|
494
|
+
console_url: z.url().optional()
|
|
495
|
+
});
|
|
496
|
+
const pfUserSchemaV1 = z.object({
|
|
497
|
+
access_token: z.string(),
|
|
498
|
+
refresh_token: z.string().optional(),
|
|
499
|
+
token_expires_at: z.string()
|
|
500
|
+
});
|
|
501
|
+
const pfUserKeyringSchema = z.object({
|
|
502
|
+
storage: z.literal("keyring"),
|
|
503
|
+
token_expires_at: z.string()
|
|
504
|
+
});
|
|
505
|
+
const pfUserFileSchema = z.object({
|
|
506
|
+
storage: z.literal("file"),
|
|
507
|
+
token_expires_at: z.string(),
|
|
508
|
+
access_token: z.string(),
|
|
509
|
+
refresh_token: z.string().optional()
|
|
510
|
+
});
|
|
511
|
+
const pfUserSchemaV2 = z.discriminatedUnion("storage", [pfUserKeyringSchema, pfUserFileSchema]);
|
|
512
|
+
const pfConfigSchemaV1 = z.object({
|
|
513
|
+
version: z.literal(1),
|
|
514
|
+
users: z.partialRecord(z.string(), pfUserSchemaV1),
|
|
515
|
+
profiles: z.partialRecord(z.string(), pfProfileSchema),
|
|
516
|
+
current_user: z.string().nullable()
|
|
517
|
+
});
|
|
518
|
+
const V2_CONFIG_VERSION = 2;
|
|
519
|
+
const LATEST_CONFIG_VERSION = 3;
|
|
520
|
+
const V2_MIN_SDK_VERSION = "1.29.0";
|
|
521
|
+
const V3_MIN_SDK_VERSION = "1.70.0";
|
|
522
|
+
const semverSchema = z.templateLiteral([
|
|
523
|
+
z.number().int(),
|
|
524
|
+
".",
|
|
525
|
+
z.number().int(),
|
|
526
|
+
".",
|
|
527
|
+
z.number().int()
|
|
547
528
|
]);
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
529
|
+
const pfConfigSchema = z.object({
|
|
530
|
+
version: z.union([z.literal(V2_CONFIG_VERSION), z.literal(LATEST_CONFIG_VERSION)]),
|
|
531
|
+
min_sdk_version: semverSchema,
|
|
532
|
+
latest_version: z.number().int().optional(),
|
|
533
|
+
latest_min_sdk_version: semverSchema.optional(),
|
|
534
|
+
users: z.partialRecord(z.string(), pfUserSchemaV2),
|
|
535
|
+
profiles: z.partialRecord(z.string(), pfProfileSchema),
|
|
536
|
+
current_user: z.string().nullable()
|
|
537
|
+
});
|
|
538
|
+
function platformConfigPath() {
|
|
539
|
+
if (!xdgConfig) throw new Error("User home directory not found");
|
|
540
|
+
return path.join(xdgConfig, "tailor-platform", "config.yaml");
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Convert stored profile platform fields to platform client settings.
|
|
544
|
+
* @param profile - Profile platform settings
|
|
545
|
+
* @returns Platform client settings
|
|
546
|
+
*/
|
|
547
|
+
function platformConfigFromProfile(profile) {
|
|
548
|
+
const config = {
|
|
549
|
+
...profile?.platform_url ? { platformUrl: profile.platform_url } : {},
|
|
550
|
+
...profile?.oauth2_client_id ? { oauth2ClientId: profile.oauth2_client_id } : {},
|
|
551
|
+
...profile?.console_url ? { consoleUrl: profile.console_url } : {}
|
|
552
|
+
};
|
|
553
|
+
return Object.keys(config).length > 0 ? config : void 0;
|
|
554
|
+
}
|
|
555
|
+
function platformUserKey(user, config) {
|
|
556
|
+
const platformUrl = getPlatformBaseUrl(config);
|
|
557
|
+
if (platformUrl === normalizeBaseUrl("https://api.tailor.tech")) return user;
|
|
558
|
+
return `${platformUrl}|${user}`;
|
|
559
|
+
}
|
|
560
|
+
function canUseLegacyUserKey(platformUrl) {
|
|
561
|
+
try {
|
|
562
|
+
return getPlatformBaseUrl() === platformUrl;
|
|
563
|
+
} catch {
|
|
564
|
+
return false;
|
|
556
565
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
566
|
+
}
|
|
567
|
+
function findUserEntry(config, user, platformConfig, opts = {}) {
|
|
568
|
+
const userKey = platformUserKey(user, platformConfig);
|
|
569
|
+
const userEntry = config.users[userKey];
|
|
570
|
+
if (userEntry) return {
|
|
571
|
+
userKey,
|
|
572
|
+
userEntry
|
|
573
|
+
};
|
|
574
|
+
const platformUrl = getPlatformBaseUrl(platformConfig);
|
|
575
|
+
if (userKey !== user && (opts.allowLegacyUserKey === false || !canUseLegacyUserKey(platformUrl))) return {
|
|
576
|
+
userKey,
|
|
577
|
+
userEntry
|
|
578
|
+
};
|
|
579
|
+
const legacyEntry = config.users[user];
|
|
580
|
+
return legacyEntry ? {
|
|
581
|
+
userKey: user,
|
|
582
|
+
userEntry: legacyEntry
|
|
583
|
+
} : {
|
|
584
|
+
userKey,
|
|
585
|
+
userEntry
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Resolve the config user key that would be used for a user on the selected platform.
|
|
590
|
+
* @param config - Platform config
|
|
591
|
+
* @param user - User name
|
|
592
|
+
* @param platformConfig - Optional platform connection settings
|
|
593
|
+
* @param opts - Token lookup options
|
|
594
|
+
* @returns Resolved config user key
|
|
595
|
+
*/
|
|
596
|
+
function resolveUserTokenKey(config, user, platformConfig, opts) {
|
|
597
|
+
return findUserEntry(config, user, platformConfig, opts).userKey;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Check whether tokens are registered for a user on the selected platform.
|
|
601
|
+
* @param config - Platform config
|
|
602
|
+
* @param user - User name
|
|
603
|
+
* @param platformConfig - Optional platform connection settings
|
|
604
|
+
* @param opts - Token lookup options
|
|
605
|
+
* @returns True when the user has a registered token entry
|
|
606
|
+
*/
|
|
607
|
+
function hasUserTokenEntry(config, user, platformConfig, opts) {
|
|
608
|
+
return findUserEntry(config, user, platformConfig, opts).userEntry !== void 0;
|
|
609
|
+
}
|
|
610
|
+
function hasUserKeyForName(users, user) {
|
|
611
|
+
return users[user] !== void 0 || Object.keys(users).some((key) => key.endsWith(`|${user}`));
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Check whether any platform has tokens registered for a user.
|
|
615
|
+
* @param config - Platform config
|
|
616
|
+
* @param user - User name
|
|
617
|
+
* @returns True when the user has a token entry for any platform
|
|
618
|
+
*/
|
|
619
|
+
function hasAnyUserTokenEntry(config, user) {
|
|
620
|
+
return hasUserKeyForName(config.users, user);
|
|
621
|
+
}
|
|
622
|
+
function hasCurrentUserEntry(users, currentUser) {
|
|
623
|
+
return hasUserKeyForName(users, currentUser);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Migrate a v1 config to v2.
|
|
627
|
+
* Tokens are kept in the config file (storage: "file") during migration.
|
|
628
|
+
* They will be moved to the OS keyring on next login or token refresh.
|
|
629
|
+
* @param v1Config - v1 configuration to migrate
|
|
630
|
+
* @returns Migrated v2 configuration
|
|
631
|
+
*/
|
|
632
|
+
function migrateV1ToV2(v1Config) {
|
|
633
|
+
const users = {};
|
|
634
|
+
for (const [name, v1User] of Object.entries(v1Config.users)) {
|
|
635
|
+
if (!v1User) continue;
|
|
636
|
+
users[name] = {
|
|
637
|
+
access_token: v1User.access_token,
|
|
638
|
+
refresh_token: v1User.refresh_token,
|
|
639
|
+
token_expires_at: v1User.token_expires_at,
|
|
640
|
+
storage: "file"
|
|
641
|
+
};
|
|
564
642
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
643
|
+
return {
|
|
644
|
+
version: V2_CONFIG_VERSION,
|
|
645
|
+
min_sdk_version: V2_MIN_SDK_VERSION,
|
|
646
|
+
users,
|
|
647
|
+
profiles: v1Config.profiles,
|
|
648
|
+
current_user: v1Config.current_user
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
async function warnIfNewerConfigAvailable(config) {
|
|
652
|
+
if (!config.latest_min_sdk_version) return;
|
|
653
|
+
if (lt((await readPackageJson()).version ?? "0.0.0", config.latest_min_sdk_version)) logger.warn(multiline`
|
|
654
|
+
A newer config version (${String(config.latest_version)}) is available.
|
|
655
|
+
Please update your SDK to >= ${config.latest_min_sdk_version}: pnpm update @tailor-platform/sdk
|
|
656
|
+
`);
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Read Tailor Platform CLI configuration, migrating from tailorctl or v1 if necessary.
|
|
660
|
+
* @returns Parsed platform configuration
|
|
661
|
+
*/
|
|
662
|
+
async function readPlatformConfig() {
|
|
663
|
+
const configPath = platformConfigPath();
|
|
664
|
+
if (!fs$1.existsSync(configPath)) {
|
|
665
|
+
logger.warn(`Config not found at ${configPath}, migrating from tailorctl config...`);
|
|
666
|
+
const tcConfig = readTailorctlConfig();
|
|
667
|
+
const v1Config = tcConfig ? fromTailorctlConfig(tcConfig) : {
|
|
668
|
+
version: 1,
|
|
669
|
+
users: {},
|
|
670
|
+
profiles: {},
|
|
671
|
+
current_user: null
|
|
672
|
+
};
|
|
673
|
+
writePlatformConfig(v1Config);
|
|
674
|
+
return migrateV1ToV2(v1Config);
|
|
675
|
+
}
|
|
676
|
+
const rawConfig = parseYAML(fs$1.readFileSync(configPath, "utf-8"));
|
|
677
|
+
tightenSecretFilePermissions(configPath);
|
|
678
|
+
const version = rawConfig != null && typeof rawConfig === "object" && "version" in rawConfig ? rawConfig.version : void 0;
|
|
679
|
+
if (typeof version === "number" && version > LATEST_CONFIG_VERSION) {
|
|
680
|
+
const minSdk = "min_sdk_version" in rawConfig ? String(rawConfig.min_sdk_version) : void 0;
|
|
681
|
+
const updateHint = minSdk ? `Please update your SDK to >= ${minSdk}: pnpm update @tailor-platform/sdk` : "Please update your SDK: pnpm update @tailor-platform/sdk";
|
|
682
|
+
throw new Error(multiline`
|
|
683
|
+
Config file uses version ${String(version)}, but this SDK only supports up to version ${String(LATEST_CONFIG_VERSION)}.
|
|
684
|
+
${updateHint}
|
|
685
|
+
`);
|
|
572
686
|
}
|
|
573
|
-
const
|
|
574
|
-
if (
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
687
|
+
const configResult = pfConfigSchema.safeParse(rawConfig);
|
|
688
|
+
if (configResult.success) {
|
|
689
|
+
await warnIfNewerConfigAvailable(configResult.data);
|
|
690
|
+
return configResult.data;
|
|
691
|
+
}
|
|
692
|
+
const v1Result = pfConfigSchemaV1.safeParse(rawConfig);
|
|
693
|
+
if (v1Result.success) return migrateV1ToV2(v1Result.data);
|
|
694
|
+
throw new Error(multiline`
|
|
695
|
+
Failed to parse config file at ${configPath}.
|
|
696
|
+
The file may be corrupted or created by an incompatible SDK version.
|
|
697
|
+
`);
|
|
698
|
+
}
|
|
699
|
+
function toV1ForDisk(config) {
|
|
700
|
+
const users = {};
|
|
701
|
+
for (const [name, entry] of Object.entries(config.users)) {
|
|
702
|
+
if (!entry || entry.storage === "keyring") continue;
|
|
703
|
+
users[name] = {
|
|
704
|
+
access_token: entry.access_token,
|
|
705
|
+
refresh_token: entry.refresh_token,
|
|
706
|
+
token_expires_at: entry.token_expires_at
|
|
707
|
+
};
|
|
580
708
|
}
|
|
709
|
+
const currentUser = config.current_user && hasCurrentUserEntry(users, config.current_user) ? config.current_user : null;
|
|
581
710
|
return {
|
|
582
|
-
|
|
583
|
-
|
|
711
|
+
version: 1,
|
|
712
|
+
users,
|
|
713
|
+
profiles: config.profiles,
|
|
714
|
+
current_user: currentUser
|
|
584
715
|
};
|
|
585
|
-
}
|
|
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
|
-
|
|
716
|
+
}
|
|
717
|
+
function hasProfilePlatformSettings(config) {
|
|
718
|
+
return Object.values(config.profiles).some((profile) => profile?.platform_url !== void 0 || profile?.oauth2_client_id !== void 0 || profile?.console_url !== void 0);
|
|
719
|
+
}
|
|
720
|
+
function hasScopedUserKeys(config) {
|
|
721
|
+
return Object.keys(config.users).some((userKey) => userKey.includes("|"));
|
|
722
|
+
}
|
|
723
|
+
function toLatestForDisk(config) {
|
|
724
|
+
return {
|
|
725
|
+
...config.version === 1 ? migrateV1ToV2(config) : config,
|
|
726
|
+
version: LATEST_CONFIG_VERSION,
|
|
727
|
+
min_sdk_version: V3_MIN_SDK_VERSION
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Write Tailor Platform CLI configuration to disk.
|
|
732
|
+
* By default, file-backed configs without newer fields are converted to V1 for
|
|
733
|
+
* backward compatibility, so an older SDK can still read the file. Configs
|
|
734
|
+
* containing a keyring user are kept in V2 or later because the keyring storage
|
|
735
|
+
* variant is not representable in V1. Configs containing profile-level Platform
|
|
736
|
+
* settings or platform-scoped user tokens are written in the latest
|
|
737
|
+
* min-SDK-gated format because older SDKs would silently drop or misread those
|
|
738
|
+
* settings.
|
|
739
|
+
* Set TAILOR_USE_KEYRING to write the current in-memory format unconditionally.
|
|
740
|
+
*
|
|
741
|
+
* The config file may contain access/refresh tokens when the OS keyring is
|
|
742
|
+
* unavailable, so it is written via {@link writeSecretFile} so other users
|
|
743
|
+
* on the host cannot read it.
|
|
744
|
+
* @param config - Platform configuration to write
|
|
745
|
+
*/
|
|
746
|
+
function writePlatformConfig(config) {
|
|
747
|
+
const configPath = platformConfigPath();
|
|
748
|
+
const hasKeyringUser = config.version !== 1 && Object.values(config.users).some((u) => u?.storage === "keyring");
|
|
749
|
+
writeSecretFile(configPath, stringifyYAML(hasProfilePlatformSettings(config) || hasScopedUserKeys(config) ? toLatestForDisk(config) : config.version !== 1 && !process.env.TAILOR_USE_KEYRING && !hasKeyringUser ? toV1ForDisk(config) : config));
|
|
750
|
+
}
|
|
751
|
+
const tcContextConfigSchema = z.object({
|
|
752
|
+
username: z.string().optional(),
|
|
753
|
+
controlplaneaccesstoken: z.string().optional(),
|
|
754
|
+
controlplanerefreshtoken: z.string().optional(),
|
|
755
|
+
controlplanetokenexpiresat: z.string().optional(),
|
|
756
|
+
workspaceid: z.string().optional()
|
|
757
|
+
});
|
|
758
|
+
const tcConfigSchema = z.object({ global: z.object({ context: z.string().optional() }).optional() }).catchall(tcContextConfigSchema.optional());
|
|
759
|
+
function readTailorctlConfig() {
|
|
760
|
+
const configPath = path.join(os.homedir(), ".tailorctl", "config");
|
|
761
|
+
if (!fs$1.existsSync(configPath)) return;
|
|
762
|
+
const rawConfig = parseTOML(fs$1.readFileSync(configPath, "utf-8"));
|
|
763
|
+
return tcConfigSchema.parse(rawConfig);
|
|
764
|
+
}
|
|
765
|
+
function fromTailorctlConfig(config) {
|
|
766
|
+
const users = {};
|
|
767
|
+
const profiles = {};
|
|
768
|
+
let currentUser = null;
|
|
769
|
+
const currentContext = config.global?.context || "default";
|
|
770
|
+
for (const [key, val] of Object.entries(config)) {
|
|
771
|
+
if (key === "global") continue;
|
|
772
|
+
const context = val;
|
|
773
|
+
if (!context.username || !context.controlplaneaccesstoken || !context.controlplanerefreshtoken || !context.controlplanetokenexpiresat || !context.workspaceid) continue;
|
|
774
|
+
if (key === currentContext) currentUser = context.username;
|
|
775
|
+
profiles[key] = {
|
|
776
|
+
user: context.username,
|
|
777
|
+
workspace_id: context.workspaceid
|
|
778
|
+
};
|
|
779
|
+
const user = users[context.username];
|
|
780
|
+
if (!user || new Date(user.token_expires_at) < new Date(context.controlplanetokenexpiresat)) users[context.username] = {
|
|
781
|
+
access_token: context.controlplaneaccesstoken,
|
|
782
|
+
refresh_token: context.controlplanerefreshtoken,
|
|
783
|
+
token_expires_at: context.controlplanetokenexpiresat
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
return {
|
|
787
|
+
version: 1,
|
|
788
|
+
users,
|
|
789
|
+
profiles,
|
|
790
|
+
current_user: currentUser
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
function validateUUID(value, source) {
|
|
794
|
+
const result = z.uuid().safeParse(value);
|
|
795
|
+
if (!result.success) throw new Error(`Invalid value from ${source}: must be a valid UUID`);
|
|
796
|
+
return result.data;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Load workspace ID from command options, environment variables, or platform config.
|
|
800
|
+
* In CLI context, env fallback is also handled by politty's arg env option.
|
|
801
|
+
* Priority: opts/workspaceId > env/workspaceId > opts/profile > error
|
|
802
|
+
* @param opts - Workspace and profile options
|
|
803
|
+
* @returns Resolved workspace ID
|
|
804
|
+
*/
|
|
805
|
+
async function loadWorkspaceId(opts) {
|
|
806
|
+
const workspaceId = await tryLoadWorkspaceId(opts);
|
|
807
|
+
if (workspaceId) return workspaceId;
|
|
808
|
+
throw new Error(multiline`
|
|
809
|
+
Workspace ID not found.
|
|
810
|
+
Please specify workspace ID via --workspace-id option or TAILOR_PLATFORM_WORKSPACE_ID environment variable.
|
|
811
|
+
`);
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Load a workspace ID when one is explicitly configured.
|
|
815
|
+
* @param opts - Workspace and profile options
|
|
816
|
+
* @returns Resolved workspace ID, or undefined when no source is configured
|
|
817
|
+
*/
|
|
818
|
+
async function tryLoadWorkspaceId(opts) {
|
|
819
|
+
const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
820
|
+
if (opts?.workspaceId !== void 0) return validateUUID(opts.workspaceId, "--workspace-id option");
|
|
821
|
+
if (process.env.TAILOR_PLATFORM_WORKSPACE_ID) return validateUUID(process.env.TAILOR_PLATFORM_WORKSPACE_ID, "TAILOR_PLATFORM_WORKSPACE_ID environment variable");
|
|
822
|
+
if (profile) {
|
|
823
|
+
const wsId = (await readPlatformConfig()).profiles[profile]?.workspace_id;
|
|
824
|
+
if (!wsId) throw new Error(`Profile "${profile}" not found`);
|
|
825
|
+
return validateUUID(wsId, `profile "${profile}"`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Load machine user name from command options, environment variables, or platform config.
|
|
830
|
+
* In CLI context, env fallback is also handled by politty's arg env option.
|
|
831
|
+
* Priority: opts/machineUser > env/TAILOR_PLATFORM_MACHINE_USER_NAME > opts/profile (profile default) > undefined.
|
|
832
|
+
* 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.
|
|
833
|
+
* 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`.
|
|
834
|
+
* @param opts - Machine user and profile options
|
|
835
|
+
* @returns Resolved machine user name, or undefined if not set
|
|
836
|
+
*/
|
|
837
|
+
async function loadMachineUserName(opts) {
|
|
838
|
+
if (opts?.machineUser === "") throw createCLIError({
|
|
839
|
+
code: "MACHINE_USER_NAME_EMPTY",
|
|
840
|
+
message: "Machine user name cannot be empty.",
|
|
841
|
+
suggestion: "Pass a non-empty machine user name, or omit the option to use the environment variable or profile default."
|
|
842
|
+
});
|
|
843
|
+
const envMachineUser = process.env.TAILOR_PLATFORM_MACHINE_USER_NAME || void 0;
|
|
844
|
+
const explicitMachineUser = opts?.machineUser ? {
|
|
845
|
+
source: opts.machineUserSource ?? "option",
|
|
846
|
+
value: opts.machineUser
|
|
847
|
+
} : envMachineUser ? {
|
|
848
|
+
source: "env",
|
|
849
|
+
value: envMachineUser
|
|
850
|
+
} : void 0;
|
|
851
|
+
const explicit = explicitMachineUser?.value;
|
|
852
|
+
const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
853
|
+
if (!profile) return explicit;
|
|
854
|
+
const entry = (await readPlatformConfig()).profiles[profile];
|
|
855
|
+
if (!entry) {
|
|
856
|
+
if (explicit) return explicit;
|
|
857
|
+
throw new Error(`Profile "${profile}" not found`);
|
|
858
|
+
}
|
|
859
|
+
if (entry.machine_user && entry.machine_user_override === "deny") {
|
|
860
|
+
if (explicit && explicit !== entry.machine_user) {
|
|
861
|
+
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.`;
|
|
862
|
+
throw createCLIError({
|
|
863
|
+
code: "PROFILE_MACHINE_USER_OVERRIDE_DENIED",
|
|
864
|
+
message: `Profile "${profile}" denies overriding the machine user.`,
|
|
865
|
+
details,
|
|
866
|
+
suggestion: `Omit the machine user option, unset TAILOR_PLATFORM_MACHINE_USER_NAME, or run 'tailor-sdk profile update ${profile} --machine-user-override allow'.`
|
|
867
|
+
});
|
|
617
868
|
}
|
|
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
|
-
|
|
869
|
+
return entry.machine_user;
|
|
870
|
+
}
|
|
871
|
+
return explicit || entry.machine_user;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Load access token from environment variables, command options, or platform config.
|
|
875
|
+
* In CLI context, profile env fallback is also handled by politty's arg env option.
|
|
876
|
+
* Priority: env/TAILOR_PLATFORM_TOKEN > env/TAILOR_TOKEN (deprecated) > opts/profile > env/profile > config/currentUser > error
|
|
877
|
+
* @param opts - Profile options
|
|
878
|
+
* @returns Resolved access token
|
|
879
|
+
*/
|
|
880
|
+
async function loadAccessToken(opts) {
|
|
881
|
+
const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
882
|
+
const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
|
|
883
|
+
if (envToken && !process.env.TAILOR_PLATFORM_TOKEN) logger.warn("TAILOR_TOKEN is deprecated. Please use TAILOR_PLATFORM_TOKEN instead.");
|
|
884
|
+
if (envToken) {
|
|
885
|
+
rememberPlatformConfigForToken(envToken, await loadPlatformClientConfig({
|
|
886
|
+
profile,
|
|
887
|
+
allowMissingProfile: true
|
|
888
|
+
}));
|
|
889
|
+
return envToken;
|
|
890
|
+
}
|
|
891
|
+
const pfConfig = await readPlatformConfig();
|
|
892
|
+
const profileEntry = profile ? pfConfig.profiles[profile] : void 0;
|
|
893
|
+
if (profile && !profileEntry) throw new Error(`Profile "${profile}" not found`);
|
|
894
|
+
const user = profileEntry?.user ?? pfConfig.current_user;
|
|
895
|
+
if (!user) throw new Error(multiline`
|
|
896
|
+
Tailor Platform token not found.
|
|
897
|
+
Please specify token via TAILOR_PLATFORM_TOKEN environment variable or login using 'tailor-sdk login' command.
|
|
898
|
+
`);
|
|
899
|
+
return await fetchLatestToken(pfConfig, user, profileEntry ? platformConfigFromProfile(profileEntry) : void 0);
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Load platform connection settings from the active profile.
|
|
903
|
+
* @param opts - Profile options
|
|
904
|
+
* @returns Resolved platform connection settings, or undefined for the default environment
|
|
905
|
+
*/
|
|
906
|
+
async function loadPlatformClientConfig(opts) {
|
|
907
|
+
const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
908
|
+
if (!profile) return;
|
|
909
|
+
let pfConfig;
|
|
910
|
+
try {
|
|
911
|
+
pfConfig = await readPlatformConfig();
|
|
912
|
+
} catch (error) {
|
|
913
|
+
if (opts?.allowMissingProfile) return;
|
|
914
|
+
throw error;
|
|
915
|
+
}
|
|
916
|
+
const profileEntry = pfConfig.profiles[profile];
|
|
917
|
+
if (!profileEntry) {
|
|
918
|
+
if (opts?.allowMissingProfile) return;
|
|
919
|
+
throw new Error(`Profile "${profile}" not found`);
|
|
920
|
+
}
|
|
921
|
+
return platformConfigFromProfile(profileEntry);
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Load the Tailor Platform Console base URL from environment variables or the active profile.
|
|
925
|
+
* @param opts - Profile options
|
|
926
|
+
* @returns Resolved Console base URL
|
|
927
|
+
*/
|
|
928
|
+
async function loadConsoleBaseUrl(opts) {
|
|
929
|
+
return getConsoleBaseUrl(await loadPlatformClientConfig(opts));
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Resolve the actual token values for a user, reading from keyring or config as appropriate.
|
|
933
|
+
* @param userEntry - User entry from the config
|
|
934
|
+
* @param user - User identifier
|
|
935
|
+
* @param label - User-facing identifier used in error messages
|
|
936
|
+
* @returns Access token and optional refresh token
|
|
937
|
+
*/
|
|
938
|
+
async function resolveTokens(userEntry, user, label = user) {
|
|
939
|
+
if (userEntry.storage === "keyring") {
|
|
940
|
+
const tokens = await loadKeyringTokens(user);
|
|
941
|
+
if (!tokens) throw new Error(multiline`
|
|
942
|
+
Credentials not found in OS keyring for "${label}".
|
|
943
|
+
Please run 'tailor-sdk login' and try again.
|
|
944
|
+
`);
|
|
945
|
+
return tokens;
|
|
946
|
+
}
|
|
947
|
+
return {
|
|
948
|
+
accessToken: userEntry.access_token,
|
|
949
|
+
refreshToken: userEntry.refresh_token
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Save tokens for a user, writing to keyring or config as appropriate.
|
|
954
|
+
* @param config - Platform config
|
|
955
|
+
* @param user - User identifier
|
|
956
|
+
* @param tokens - Token data to save
|
|
957
|
+
* @param tokens.accessToken - Access token to persist
|
|
958
|
+
* @param tokens.refreshToken - Refresh token to persist when available
|
|
959
|
+
* @param expiresAt - Token expiration date
|
|
960
|
+
* @param platformConfig - Optional platform connection settings
|
|
961
|
+
*/
|
|
962
|
+
async function saveUserTokens(config, user, tokens, expiresAt, platformConfig) {
|
|
963
|
+
const userKey = platformUserKey(user, platformConfig);
|
|
964
|
+
if (process.env.TAILOR_USE_KEYRING && await isKeyringAvailable()) {
|
|
965
|
+
await saveKeyringTokens(userKey, tokens);
|
|
966
|
+
config.users[userKey] = {
|
|
967
|
+
token_expires_at: expiresAt,
|
|
968
|
+
storage: "keyring"
|
|
969
|
+
};
|
|
970
|
+
} else config.users[userKey] = {
|
|
971
|
+
access_token: tokens.accessToken,
|
|
972
|
+
refresh_token: tokens.refreshToken,
|
|
973
|
+
token_expires_at: expiresAt,
|
|
974
|
+
storage: "file"
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Delete tokens for a user from keyring if applicable.
|
|
979
|
+
* @param config - Platform config
|
|
980
|
+
* @param user - User identifier
|
|
981
|
+
* @param platformConfig - Optional platform connection settings
|
|
982
|
+
* @param opts - Token lookup options
|
|
983
|
+
*/
|
|
984
|
+
async function deleteUserTokens(config, user, platformConfig, opts) {
|
|
985
|
+
const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
|
|
986
|
+
if (userEntry?.storage === "keyring") await deleteKeyringTokens(userKey);
|
|
987
|
+
delete config.users[userKey];
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Resolve stored tokens for a user on the selected platform.
|
|
991
|
+
* @param config - Platform config
|
|
992
|
+
* @param user - User name
|
|
993
|
+
* @param platformConfig - Optional platform connection settings
|
|
994
|
+
* @param opts - Token lookup options
|
|
995
|
+
* @returns Stored user entry and token values, or undefined when the user is not logged in
|
|
996
|
+
*/
|
|
997
|
+
async function loadStoredUserTokens(config, user, platformConfig, opts) {
|
|
998
|
+
const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
|
|
999
|
+
if (!userEntry) return void 0;
|
|
1000
|
+
return {
|
|
1001
|
+
userEntry,
|
|
1002
|
+
...await resolveTokens(userEntry, userKey, user)
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Fetch the latest access token, refreshing if necessary.
|
|
1007
|
+
* @param config - Platform config
|
|
1008
|
+
* @param user - User name
|
|
1009
|
+
* @param platformConfig - Optional platform connection settings
|
|
1010
|
+
* @returns Latest access token
|
|
1011
|
+
*/
|
|
1012
|
+
async function fetchLatestToken(config, user, platformConfig) {
|
|
1013
|
+
const { userKey, userEntry } = findUserEntry(config, user, platformConfig);
|
|
1014
|
+
if (!userEntry) throw new Error(multiline`
|
|
1015
|
+
User "${user}" not found.
|
|
1016
|
+
Please verify your user name and login using 'tailor-sdk login' command.
|
|
1017
|
+
`);
|
|
1018
|
+
const tokens = await resolveTokens(userEntry, userKey, user);
|
|
1019
|
+
if (new Date(userEntry.token_expires_at) > /* @__PURE__ */ new Date()) {
|
|
1020
|
+
rememberPlatformConfigForToken(tokens.accessToken, platformConfig);
|
|
1021
|
+
return tokens.accessToken;
|
|
1022
|
+
}
|
|
1023
|
+
if (!tokens.refreshToken) throw new Error(multiline`
|
|
1024
|
+
Token expired.
|
|
1025
|
+
Please run 'tailor-sdk login' and try again.
|
|
1026
|
+
`);
|
|
1027
|
+
const client = initOAuth2Client(platformConfig);
|
|
1028
|
+
let resp;
|
|
1029
|
+
try {
|
|
1030
|
+
resp = await client.refreshToken({
|
|
1031
|
+
accessToken: tokens.accessToken,
|
|
1032
|
+
refreshToken: tokens.refreshToken,
|
|
1033
|
+
expiresAt: Date.parse(userEntry.token_expires_at)
|
|
1034
|
+
});
|
|
1035
|
+
} catch {
|
|
1036
|
+
throw new Error(multiline`
|
|
1037
|
+
Failed to refresh token. Your session may have expired.
|
|
1038
|
+
Please run 'tailor-sdk login' and try again.
|
|
1039
|
+
`);
|
|
1040
|
+
}
|
|
1041
|
+
const newExpiresAt = new Date(assertDefined(resp.expiresAt, "token refresh response missing expiresAt")).toISOString();
|
|
1042
|
+
await saveUserTokens(config, user, {
|
|
1043
|
+
accessToken: resp.accessToken,
|
|
1044
|
+
refreshToken: resp.refreshToken ?? void 0
|
|
1045
|
+
}, newExpiresAt, platformConfig);
|
|
1046
|
+
if (userKey !== platformUserKey(user, platformConfig)) {
|
|
1047
|
+
if (userEntry.storage === "keyring") await deleteKeyringTokens(userKey);
|
|
1048
|
+
delete config.users[userKey];
|
|
1049
|
+
}
|
|
1050
|
+
writePlatformConfig(config);
|
|
1051
|
+
rememberPlatformConfigForToken(resp.accessToken, platformConfig);
|
|
1052
|
+
return resp.accessToken;
|
|
1053
|
+
}
|
|
1054
|
+
const DEFAULT_CONFIG_FILENAME = "tailor.config.ts";
|
|
1055
|
+
/**
|
|
1056
|
+
* Load config path from command options, environment variables, or search parent directories.
|
|
1057
|
+
* In CLI context, env fallback is also handled by politty's arg env option.
|
|
1058
|
+
* Priority: opts/config > env/config > search parent directories
|
|
1059
|
+
* @param configPath - Optional explicit config path
|
|
1060
|
+
* @returns Resolved config path or undefined
|
|
1061
|
+
*/
|
|
1062
|
+
function loadConfigPath(configPath) {
|
|
1063
|
+
if (configPath) return configPath;
|
|
1064
|
+
if (process.env.TAILOR_PLATFORM_SDK_CONFIG_PATH) return process.env.TAILOR_PLATFORM_SDK_CONFIG_PATH;
|
|
1065
|
+
return findUpSync(DEFAULT_CONFIG_FILENAME);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
//#endregion
|
|
1069
|
+
//#region src/cli/shared/mock.ts
|
|
1070
|
+
/**
|
|
1071
|
+
* Install a stub `globalThis.tailordb` so that user code loaded by the CLI
|
|
1072
|
+
* (e.g. via `createGetDB` in `@tailor-platform/sdk/kysely`) can reference
|
|
1073
|
+
* `tailordb.Client` without hitting a `ReferenceError`. The CLI never
|
|
1074
|
+
* actually executes the user code paths that issue queries, so a no-op
|
|
1075
|
+
* client suffices.
|
|
1076
|
+
*
|
|
1077
|
+
* Exposed as a function (rather than a top-level statement) so that
|
|
1078
|
+
* `package.json#sideEffects` can keep the file marked side-effect-free
|
|
1079
|
+
* without bundlers eliminating the install step.
|
|
1080
|
+
*/
|
|
1081
|
+
function installCliTailordbStub() {
|
|
1082
|
+
globalThis.tailordb = { Client: class {
|
|
1083
|
+
constructor(_config) {}
|
|
1084
|
+
async connect() {}
|
|
1085
|
+
async end() {}
|
|
1086
|
+
async queryObject() {
|
|
1087
|
+
return {};
|
|
657
1088
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
1089
|
+
} };
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
//#endregion
|
|
1093
|
+
//#region src/cli/shared/config-loader.ts
|
|
1094
|
+
const GeneratorConfigSchema = CodeGeneratorSchema.brand("CodeGenerator");
|
|
1095
|
+
/**
|
|
1096
|
+
* Load Tailor configuration file and associated generators and plugins.
|
|
1097
|
+
* @param configPath - Optional explicit config path
|
|
1098
|
+
* @param options - Optional module import behavior.
|
|
1099
|
+
* @returns Loaded config, generators, plugins, and config path
|
|
1100
|
+
*/
|
|
1101
|
+
async function loadConfig(configPath, options = {}) {
|
|
1102
|
+
installCliTailordbStub();
|
|
1103
|
+
const foundPath = loadConfigPath(configPath);
|
|
1104
|
+
if (!foundPath) throw new Error("Configuration file not found: tailor.config.ts not found in current or parent directories");
|
|
1105
|
+
const resolvedPath = path.resolve(process.cwd(), foundPath);
|
|
1106
|
+
if (!fs$1.existsSync(resolvedPath)) throw new Error(`Configuration file not found: ${configPath}`);
|
|
1107
|
+
const configUrl = pathToFileURL(resolvedPath);
|
|
1108
|
+
if (options.importNonce) configUrl.searchParams.set("tailorImportNonce", options.importNonce);
|
|
1109
|
+
const configModule = await import(configUrl.href);
|
|
1110
|
+
if (!configModule || !configModule.default) throw new Error("Invalid Tailor config module: default export not found");
|
|
1111
|
+
const validated = AppConfigSchema.safeParse(configModule.default);
|
|
1112
|
+
if (!validated.success) {
|
|
1113
|
+
const issues = validated.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
1114
|
+
throw new Error(`Invalid Tailor config in ${resolvedPath}:\n${issues}`);
|
|
1115
|
+
}
|
|
1116
|
+
const allGenerators = [];
|
|
1117
|
+
const allPlugins = [];
|
|
1118
|
+
for (const value of Object.values(configModule)) if (Array.isArray(value)) {
|
|
1119
|
+
const generatorParsed = value.reduce((acc, item) => {
|
|
1120
|
+
if (!acc.success) return acc;
|
|
1121
|
+
const baseResult = BaseGeneratorConfigSchema.safeParse(item);
|
|
1122
|
+
if (baseResult.success && Array.isArray(baseResult.data)) {
|
|
1123
|
+
const [id, options] = baseResult.data;
|
|
1124
|
+
const pluginFactory = builtinPlugins.get(id);
|
|
1125
|
+
if (pluginFactory) {
|
|
1126
|
+
acc.convertedPlugins.push(pluginFactory(options));
|
|
1127
|
+
return acc;
|
|
674
1128
|
}
|
|
675
|
-
}
|
|
1129
|
+
}
|
|
1130
|
+
const result = GeneratorConfigSchema.safeParse(item);
|
|
1131
|
+
if (result.success) acc.items.push(result.data);
|
|
1132
|
+
else acc.success = false;
|
|
1133
|
+
return acc;
|
|
1134
|
+
}, {
|
|
1135
|
+
success: true,
|
|
1136
|
+
items: [],
|
|
1137
|
+
convertedPlugins: []
|
|
1138
|
+
});
|
|
1139
|
+
if (generatorParsed.success && (generatorParsed.items.length > 0 || generatorParsed.convertedPlugins.length > 0)) {
|
|
1140
|
+
allGenerators.push(...generatorParsed.items);
|
|
1141
|
+
allPlugins.push(...generatorParsed.convertedPlugins);
|
|
1142
|
+
continue;
|
|
676
1143
|
}
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
1144
|
+
const pluginParsed = value.reduce((acc, item) => {
|
|
1145
|
+
if (!acc.success) return acc;
|
|
1146
|
+
const result = PluginConfigSchema.safeParse(item);
|
|
1147
|
+
if (result.success) acc.items.push(result.data);
|
|
1148
|
+
else acc.success = false;
|
|
1149
|
+
return acc;
|
|
1150
|
+
}, {
|
|
1151
|
+
success: true,
|
|
1152
|
+
items: []
|
|
681
1153
|
});
|
|
682
|
-
|
|
1154
|
+
if (pluginParsed.success && pluginParsed.items.length > 0) allPlugins.push(...pluginParsed.items);
|
|
683
1155
|
}
|
|
684
|
-
|
|
1156
|
+
return {
|
|
1157
|
+
config: {
|
|
1158
|
+
...configModule.default,
|
|
1159
|
+
path: resolvedPath
|
|
1160
|
+
},
|
|
1161
|
+
generators: allGenerators,
|
|
1162
|
+
plugins: allPlugins
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
//#endregion
|
|
1167
|
+
//#region src/cli/shared/readonly-guard.ts
|
|
1168
|
+
/**
|
|
1169
|
+
* Throw a CLIError if the active profile has `readonly: true`.
|
|
1170
|
+
*
|
|
1171
|
+
* Resolves the active profile in this order:
|
|
1172
|
+
* 1. `opts.profile` (CLI flag)
|
|
1173
|
+
* 2. `process.env.TAILOR_PLATFORM_PROFILE`
|
|
1174
|
+
*
|
|
1175
|
+
* If neither is set, no profile is in scope so the call is allowed. This is
|
|
1176
|
+
* intentional: `TAILOR_PLATFORM_TOKEN` direct access (CI / machine user) and
|
|
1177
|
+
* `--workspace-id` without a profile are out-of-band paths whose authorization
|
|
1178
|
+
* is governed by the bearer token itself, not by the local profile flag.
|
|
1179
|
+
*
|
|
1180
|
+
* If the resolved profile cannot be found in the config, this function returns
|
|
1181
|
+
* silently and lets downstream loaders surface the not-found error.
|
|
1182
|
+
* @param opts - Options
|
|
1183
|
+
* @param opts.profile - Optional explicit profile name from command args
|
|
1184
|
+
*/
|
|
1185
|
+
async function assertWritable(opts) {
|
|
1186
|
+
const profileName = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
1187
|
+
if (!profileName) return;
|
|
1188
|
+
const profile = (await readPlatformConfig()).profiles[profileName];
|
|
1189
|
+
if (!profile || profile.readonly !== true) return;
|
|
1190
|
+
throw createCLIError({
|
|
1191
|
+
code: "PROFILE_READONLY",
|
|
1192
|
+
message: `Profile "${profileName}" is read-only.`,
|
|
1193
|
+
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.",
|
|
1194
|
+
suggestion: `Use a different profile, unset TAILOR_PLATFORM_PROFILE, or run 'tailor-sdk profile update ${profileName} --permission write'.`
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
//#endregion
|
|
1199
|
+
//#region src/cli/commands/api/api-call.ts
|
|
1200
|
+
function hasEnvAccessToken() {
|
|
1201
|
+
const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
|
|
1202
|
+
return Boolean(envToken);
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Call Tailor Platform API endpoints directly.
|
|
1206
|
+
* If the endpoint doesn't contain "/", it defaults to `tailor.v1.OperatorService/{endpoint}`.
|
|
1207
|
+
* @param options - API call options (profile, endpoint, body)
|
|
1208
|
+
* @returns Response status and data
|
|
1209
|
+
*/
|
|
1210
|
+
async function apiCall(options) {
|
|
1211
|
+
const accessToken = await loadAccessToken({ profile: options.profile });
|
|
1212
|
+
let platformConfig;
|
|
1213
|
+
try {
|
|
1214
|
+
platformConfig = await loadPlatformClientConfig({ profile: options.profile });
|
|
1215
|
+
} catch (error) {
|
|
1216
|
+
if (!hasEnvAccessToken()) throw error;
|
|
1217
|
+
}
|
|
1218
|
+
let endpointPath;
|
|
1219
|
+
if (options.endpoint.includes("/")) endpointPath = options.endpoint;
|
|
1220
|
+
else endpointPath = `tailor.v1.OperatorService/${options.endpoint}`;
|
|
1221
|
+
const url = new URL(endpointPath, getPlatformBaseUrl(platformConfig));
|
|
1222
|
+
const response = await fetch(url.toString(), {
|
|
1223
|
+
method: "POST",
|
|
1224
|
+
headers: {
|
|
1225
|
+
"Content-Type": "application/json",
|
|
1226
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1227
|
+
"User-Agent": await userAgent()
|
|
1228
|
+
},
|
|
1229
|
+
body: options.body ?? "{}"
|
|
1230
|
+
});
|
|
1231
|
+
const data = await response.json();
|
|
1232
|
+
if (!response.ok) throw new Error(`API call failed (${response.status}): ${JSON.stringify(data)}`);
|
|
1233
|
+
return {
|
|
1234
|
+
status: response.status,
|
|
1235
|
+
data
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
685
1238
|
|
|
686
1239
|
//#endregion
|
|
687
1240
|
//#region src/cli/shared/prompt.ts
|
|
1241
|
+
function canPrompt() {
|
|
1242
|
+
return !isCI && process.stdin.isTTY === true && process.stdout.isTTY === true && !logger.jsonMode;
|
|
1243
|
+
}
|
|
688
1244
|
/**
|
|
689
1245
|
* Wraps a prompt function with CI guard and cancellation handling.
|
|
690
1246
|
* @param fn - A prompt function from `@inquirer/prompts`
|
|
1247
|
+
* @param unavailableMessage - Message used when interactive input is unavailable
|
|
691
1248
|
* @returns A wrapped function that throws in CI and exits on cancel
|
|
692
1249
|
*/
|
|
693
|
-
function withGuard(fn) {
|
|
1250
|
+
function withGuard(fn, unavailableMessage) {
|
|
694
1251
|
return async (...args) => {
|
|
695
|
-
if (
|
|
1252
|
+
if (!canPrompt()) throw new CIPromptError(unavailableMessage);
|
|
696
1253
|
try {
|
|
697
1254
|
return await fn(...args);
|
|
698
1255
|
} catch (error) {
|
|
@@ -702,9 +1259,10 @@ function withGuard(fn) {
|
|
|
702
1259
|
};
|
|
703
1260
|
}
|
|
704
1261
|
const prompt = {
|
|
705
|
-
confirm: withGuard(confirm),
|
|
1262
|
+
confirm: withGuard(confirm, "Interactive confirmations are not available in this environment. Use --yes to skip confirmation prompts, or provide the required options explicitly."),
|
|
706
1263
|
text: withGuard(input),
|
|
707
|
-
password: withGuard(password)
|
|
1264
|
+
password: withGuard(password),
|
|
1265
|
+
select: withGuard(select)
|
|
708
1266
|
};
|
|
709
1267
|
|
|
710
1268
|
//#endregion
|
|
@@ -2296,35 +2854,59 @@ const SecretsStateSchema = z.object({
|
|
|
2296
2854
|
vaults: z.record(z.string(), z.record(z.string(), z.string())),
|
|
2297
2855
|
connections: z.record(z.string(), z.string()).optional()
|
|
2298
2856
|
});
|
|
2857
|
+
const PersistedSecretsStateSchema = z.object({
|
|
2858
|
+
version: z.literal(1),
|
|
2859
|
+
workspaceId: z.string(),
|
|
2860
|
+
applicationKey: z.string(),
|
|
2861
|
+
state: SecretsStateSchema
|
|
2862
|
+
});
|
|
2299
2863
|
/**
|
|
2300
|
-
* Get the file path for
|
|
2301
|
-
* @
|
|
2864
|
+
* Get the file path for one workspace and application's secrets state JSON.
|
|
2865
|
+
* @param scope - Workspace and application identity for the deployment
|
|
2866
|
+
* @returns Absolute path to the scoped state file
|
|
2302
2867
|
*/
|
|
2303
|
-
function getSecretsStatePath() {
|
|
2304
|
-
|
|
2868
|
+
function getSecretsStatePath(scope) {
|
|
2869
|
+
const scopeHash = hashValue(JSON.stringify([scope.workspaceId, applicationStateKey(scope)]));
|
|
2870
|
+
return path.join(getDistDir(), "secrets-state", `${scopeHash}.json`);
|
|
2305
2871
|
}
|
|
2306
|
-
|
|
2307
|
-
* Load secrets hash state from disk.
|
|
2308
|
-
* @returns Persisted state, or empty state if file is missing or corrupted
|
|
2309
|
-
*/
|
|
2310
|
-
function loadSecretsState() {
|
|
2311
|
-
const filePath = getSecretsStatePath();
|
|
2312
|
-
if (!existsSync(filePath)) return { vaults: {} };
|
|
2872
|
+
function loadPersistedSecretsState(scope) {
|
|
2313
2873
|
try {
|
|
2314
|
-
const raw = readFileSync(
|
|
2315
|
-
|
|
2874
|
+
const raw = readFileSync(getSecretsStatePath(scope), "utf-8");
|
|
2875
|
+
const persistedState = PersistedSecretsStateSchema.parse(JSON.parse(raw));
|
|
2876
|
+
if (persistedState.workspaceId !== scope.workspaceId || persistedState.applicationKey !== applicationStateKey(scope)) return;
|
|
2877
|
+
return persistedState;
|
|
2316
2878
|
} catch {
|
|
2317
|
-
return
|
|
2879
|
+
return;
|
|
2318
2880
|
}
|
|
2319
2881
|
}
|
|
2882
|
+
function applicationStateKey(scope) {
|
|
2883
|
+
if (!scope.applicationId) throw new Error(`Application "${scope.applicationName}" has no stable id for secrets state`);
|
|
2884
|
+
return `id:${scope.applicationId}`;
|
|
2885
|
+
}
|
|
2886
|
+
/**
|
|
2887
|
+
* Load secrets hash state for one workspace and application from disk.
|
|
2888
|
+
* @param scope - Workspace and application identity for the deployment
|
|
2889
|
+
* @returns Persisted state, or empty state if the scope is missing or the file is invalid
|
|
2890
|
+
*/
|
|
2891
|
+
function loadSecretsState(scope) {
|
|
2892
|
+
if (!scope.applicationId) return { vaults: {} };
|
|
2893
|
+
return loadPersistedSecretsState(scope)?.state ?? { vaults: {} };
|
|
2894
|
+
}
|
|
2320
2895
|
/**
|
|
2321
|
-
* Save secrets hash state to disk.
|
|
2896
|
+
* Save secrets hash state for one workspace and application to disk.
|
|
2897
|
+
* @param scope - Workspace and application identity for the deployment
|
|
2322
2898
|
* @param state - The secrets state to persist
|
|
2323
2899
|
*/
|
|
2324
|
-
function saveSecretsState(state) {
|
|
2325
|
-
|
|
2900
|
+
function saveSecretsState(scope, state) {
|
|
2901
|
+
if (!scope.applicationId) return;
|
|
2902
|
+
const filePath = getSecretsStatePath(scope);
|
|
2326
2903
|
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
2327
|
-
writeFileSync(filePath, JSON.stringify(
|
|
2904
|
+
writeFileSync(filePath, JSON.stringify({
|
|
2905
|
+
version: 1,
|
|
2906
|
+
workspaceId: scope.workspaceId,
|
|
2907
|
+
applicationKey: applicationStateKey(scope),
|
|
2908
|
+
state
|
|
2909
|
+
}, null, 2), "utf-8");
|
|
2328
2910
|
}
|
|
2329
2911
|
/**
|
|
2330
2912
|
* Compute SHA-256 hex digest of a value.
|
|
@@ -2390,6 +2972,11 @@ function buildUpdateMask(existing, desired, secretChanged) {
|
|
|
2390
2972
|
* @returns Planned changes for auth connections
|
|
2391
2973
|
*/
|
|
2392
2974
|
async function planAuthConnections(client, workspaceId, appName, appId, auths) {
|
|
2975
|
+
const stateScope = {
|
|
2976
|
+
workspaceId,
|
|
2977
|
+
applicationId: appId,
|
|
2978
|
+
applicationName: appName
|
|
2979
|
+
};
|
|
2393
2980
|
const changeSet = createChangeSet("Auth connections");
|
|
2394
2981
|
const conflicts = [];
|
|
2395
2982
|
const unmanaged = [];
|
|
@@ -2413,7 +3000,7 @@ async function planAuthConnections(client, workspaceId, appName, appId, auths) {
|
|
|
2413
3000
|
allLabels: metadata?.labels
|
|
2414
3001
|
};
|
|
2415
3002
|
}));
|
|
2416
|
-
const state = loadSecretsState();
|
|
3003
|
+
const state = loadSecretsState(stateScope);
|
|
2417
3004
|
for (const [name, config] of Object.entries(desiredConnections)) {
|
|
2418
3005
|
const existing = existingConnections[name];
|
|
2419
3006
|
const metaRequest = await buildMetaRequest({
|
|
@@ -2474,7 +3061,8 @@ async function planAuthConnections(client, workspaceId, appName, appId, auths) {
|
|
|
2474
3061
|
changeSet,
|
|
2475
3062
|
conflicts,
|
|
2476
3063
|
unmanaged,
|
|
2477
|
-
resourceOwners
|
|
3064
|
+
resourceOwners,
|
|
3065
|
+
stateScope
|
|
2478
3066
|
};
|
|
2479
3067
|
}
|
|
2480
3068
|
/**
|
|
@@ -2484,7 +3072,7 @@ async function planAuthConnections(client, workspaceId, appName, appId, auths) {
|
|
|
2484
3072
|
* @param phase - Apply phase
|
|
2485
3073
|
*/
|
|
2486
3074
|
async function applyAuthConnections(client, result, phase) {
|
|
2487
|
-
const { changeSet } = result;
|
|
3075
|
+
const { changeSet, stateScope } = result;
|
|
2488
3076
|
if (phase === "create-update") {
|
|
2489
3077
|
await Promise.all(changeSet.creates.map(async (create) => {
|
|
2490
3078
|
await client.createAuthConnection(create.request);
|
|
@@ -2498,26 +3086,29 @@ async function applyAuthConnections(client, result, phase) {
|
|
|
2498
3086
|
await Promise.all(changeSet.updates.map(async (update) => {
|
|
2499
3087
|
await client.setMetadata(update.metaRequest);
|
|
2500
3088
|
}));
|
|
2501
|
-
const
|
|
2502
|
-
if (
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
3089
|
+
const secretReplaces = changeSet.replaces.filter((replace) => replace.updateRequest.updateMask?.paths?.includes("oauth2.client_secret"));
|
|
3090
|
+
if (changeSet.creates.length > 0 || secretReplaces.length > 0) {
|
|
3091
|
+
const state = loadSecretsState(stateScope);
|
|
3092
|
+
if (!state.connections) state.connections = {};
|
|
3093
|
+
for (const create of changeSet.creates) {
|
|
3094
|
+
const conn = create.request.connection;
|
|
3095
|
+
if (conn?.config?.case === "oauth2") state.connections[create.name] = hashValue(conn.config.value.clientSecret ?? "");
|
|
3096
|
+
}
|
|
3097
|
+
for (const replace of secretReplaces) {
|
|
3098
|
+
const conn = replace.updateRequest.connection;
|
|
3099
|
+
if (conn?.config?.case === "oauth2") state.connections[replace.name] = hashValue(conn.config.value.clientSecret ?? "");
|
|
3100
|
+
}
|
|
3101
|
+
saveSecretsState(stateScope, state);
|
|
2510
3102
|
}
|
|
2511
|
-
saveSecretsState(state);
|
|
2512
3103
|
} else {
|
|
2513
3104
|
await Promise.all(changeSet.deletes.map(async (del) => {
|
|
2514
3105
|
await client.deleteAuthConnection(del.request);
|
|
2515
3106
|
}));
|
|
2516
3107
|
if (changeSet.deletes.length > 0) {
|
|
2517
|
-
const state = loadSecretsState();
|
|
3108
|
+
const state = loadSecretsState(stateScope);
|
|
2518
3109
|
if (state.connections) {
|
|
2519
3110
|
for (const del of changeSet.deletes) delete state.connections[del.name];
|
|
2520
|
-
saveSecretsState(state);
|
|
3111
|
+
saveSecretsState(stateScope, state);
|
|
2521
3112
|
}
|
|
2522
3113
|
}
|
|
2523
3114
|
}
|
|
@@ -3680,7 +4271,10 @@ async function applyAuth(client, result, phase = "create-update") {
|
|
|
3680
4271
|
await Promise.all([...changeSet.machineUser.creates.map((create) => client.createAuthMachineUser(create.request)), ...changeSet.machineUser.updates.map((update) => client.updateAuthMachineUser(update.request))]);
|
|
3681
4272
|
};
|
|
3682
4273
|
const applyCreateUpdateDependents = async () => {
|
|
3683
|
-
await applyAuthConnections(client, {
|
|
4274
|
+
await applyAuthConnections(client, {
|
|
4275
|
+
changeSet: changeSet.connection,
|
|
4276
|
+
stateScope: result.connectionStateScope
|
|
4277
|
+
}, "create-update");
|
|
3684
4278
|
await Promise.all([...changeSet.idpConfig.creates.map(async (create) => {
|
|
3685
4279
|
if (create.idpConfig.kind === "BuiltInIdP") assertDefined(create.request.idpConfig, "request missing idpConfig").config = await protoBuiltinIdPConfig(client, assertDefined(create.request.workspaceId, "request missing workspaceId"), create.idpConfig);
|
|
3686
4280
|
return client.createAuthIDPConfig(create.request);
|
|
@@ -3723,7 +4317,10 @@ async function applyAuth(client, result, phase = "create-update") {
|
|
|
3723
4317
|
await Promise.all(changeSet.tenantConfig.deletes.map((del) => client.deleteTenantConfig(del.request)));
|
|
3724
4318
|
await Promise.all(changeSet.userProfileConfig.deletes.map((del) => client.deleteUserProfileConfig(del.request)));
|
|
3725
4319
|
await Promise.all(changeSet.idpConfig.deletes.map((del) => client.deleteAuthIDPConfig(del.request)));
|
|
3726
|
-
await applyAuthConnections(client, {
|
|
4320
|
+
await applyAuthConnections(client, {
|
|
4321
|
+
changeSet: changeSet.connection,
|
|
4322
|
+
stateScope: result.connectionStateScope
|
|
4323
|
+
}, "delete-resources");
|
|
3727
4324
|
} else if (phase === "delete-services") await Promise.all(changeSet.service.deletes.map((del) => client.deleteAuthService(del.request)));
|
|
3728
4325
|
}
|
|
3729
4326
|
/**
|
|
@@ -3767,7 +4364,8 @@ async function planAuth(context) {
|
|
|
3767
4364
|
},
|
|
3768
4365
|
conflicts: [...conflicts, ...connectionResult.conflicts],
|
|
3769
4366
|
unmanaged: [...unmanaged, ...connectionResult.unmanaged],
|
|
3770
|
-
resourceOwners: /* @__PURE__ */ new Set([...resourceOwners, ...connectionResult.resourceOwners])
|
|
4367
|
+
resourceOwners: /* @__PURE__ */ new Set([...resourceOwners, ...connectionResult.resourceOwners]),
|
|
4368
|
+
connectionStateScope: connectionResult.stateScope
|
|
3771
4369
|
};
|
|
3772
4370
|
}
|
|
3773
4371
|
async function planServices$2(client, workspaceId, appName, appId, auths, forceApplyAll = false) {
|
|
@@ -5643,7 +6241,12 @@ async function planSecretManager(context) {
|
|
|
5643
6241
|
getName: (resource) => resource.name,
|
|
5644
6242
|
getTrn: (name) => resourceTrn(workspaceId, "vault", name)
|
|
5645
6243
|
});
|
|
5646
|
-
const
|
|
6244
|
+
const stateScope = {
|
|
6245
|
+
workspaceId,
|
|
6246
|
+
applicationId: application.id,
|
|
6247
|
+
applicationName: application.name
|
|
6248
|
+
};
|
|
6249
|
+
const state = loadSecretsState(stateScope);
|
|
5647
6250
|
const skippedSecrets = [];
|
|
5648
6251
|
await Promise.all(secretVaults.map(async (vault) => {
|
|
5649
6252
|
const vaultName = vault.vaultName;
|
|
@@ -5752,7 +6355,8 @@ async function planSecretManager(context) {
|
|
|
5752
6355
|
skippedSecrets,
|
|
5753
6356
|
conflicts,
|
|
5754
6357
|
unmanaged,
|
|
5755
|
-
resourceOwners
|
|
6358
|
+
resourceOwners,
|
|
6359
|
+
stateScope
|
|
5756
6360
|
};
|
|
5757
6361
|
}
|
|
5758
6362
|
/**
|
|
@@ -5760,11 +6364,11 @@ async function planSecretManager(context) {
|
|
|
5760
6364
|
* @param client - Operator client instance
|
|
5761
6365
|
* @param result - Planned secret changes
|
|
5762
6366
|
* @param phase - Apply phase
|
|
5763
|
-
* @param application - Application
|
|
6367
|
+
* @param application - Application used for ownership metadata and hash state persistence
|
|
5764
6368
|
* @returns Promise that resolves when secret changes are applied
|
|
5765
6369
|
*/
|
|
5766
6370
|
async function applySecretManager(client, result, phase = "create-update", application) {
|
|
5767
|
-
const { vaultChangeSet, secretChangeSet } = result;
|
|
6371
|
+
const { vaultChangeSet, secretChangeSet, stateScope } = result;
|
|
5768
6372
|
if (phase === "create-update") {
|
|
5769
6373
|
await Promise.all(vaultChangeSet.creates.map(async (create) => {
|
|
5770
6374
|
await client.createSecretManagerVault(vaultCreateRequest(create));
|
|
@@ -5787,13 +6391,14 @@ async function applySecretManager(client, result, phase = "create-update", appli
|
|
|
5787
6391
|
}));
|
|
5788
6392
|
await Promise.all(secretChangeSet.creates.map((create) => client.createSecretManagerSecret(secretCreateRequest(create))));
|
|
5789
6393
|
await Promise.all(secretChangeSet.updates.map((update) => client.updateSecretManagerSecret(secretUpdateRequest(update))));
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
6394
|
+
const secretHashUpdates = [...secretChangeSet.creates, ...secretChangeSet.updates];
|
|
6395
|
+
if (application && secretHashUpdates.length > 0) {
|
|
6396
|
+
const state = loadSecretsState(stateScope);
|
|
6397
|
+
for (const secret of secretHashUpdates) {
|
|
6398
|
+
if (!Object.hasOwn(state.vaults, secret.vaultName)) state.vaults[secret.vaultName] = {};
|
|
6399
|
+
assertDefined(state.vaults[secret.vaultName], "vault state entry missing")[secret.secretName] = hashValue(secret.value);
|
|
5795
6400
|
}
|
|
5796
|
-
saveSecretsState(state);
|
|
6401
|
+
saveSecretsState(stateScope, state);
|
|
5797
6402
|
}
|
|
5798
6403
|
} else {
|
|
5799
6404
|
await Promise.all(secretChangeSet.deletes.map((del) => client.deleteSecretManagerSecret({
|
|
@@ -5806,13 +6411,13 @@ async function applySecretManager(client, result, phase = "create-update", appli
|
|
|
5806
6411
|
secretmanagerVaultName: del.name
|
|
5807
6412
|
})));
|
|
5808
6413
|
if (secretChangeSet.deletes.length > 0 || vaultChangeSet.deletes.length > 0) {
|
|
5809
|
-
const state = loadSecretsState();
|
|
6414
|
+
const state = loadSecretsState(stateScope);
|
|
5810
6415
|
for (const del of secretChangeSet.deletes) if (Object.hasOwn(state.vaults, del.vaultName)) {
|
|
5811
6416
|
delete assertDefined(state.vaults[del.vaultName], "vault state entry missing")[del.secretName];
|
|
5812
6417
|
if (Object.keys(assertDefined(state.vaults[del.vaultName], "vault state entry missing")).length === 0) delete state.vaults[del.vaultName];
|
|
5813
6418
|
}
|
|
5814
6419
|
for (const del of vaultChangeSet.deletes) delete state.vaults[del.name];
|
|
5815
|
-
saveSecretsState(state);
|
|
6420
|
+
saveSecretsState(stateScope, state);
|
|
5816
6421
|
}
|
|
5817
6422
|
}
|
|
5818
6423
|
}
|
|
@@ -10311,11 +10916,11 @@ async function registerJobFunctions(client, changeSet, appName, appId, unchanged
|
|
|
10311
10916
|
const allUsedJobNames = /* @__PURE__ */ new Set();
|
|
10312
10917
|
unchangedWorkflowJobNames.forEach((jobName) => allUsedJobNames.add(jobName));
|
|
10313
10918
|
for (const item of [...changeSet.creates, ...changeSet.updates]) for (const jobName of item.usedJobNames) allUsedJobNames.add(jobName);
|
|
10314
|
-
const existingJobFunctions = await fetchAll(async (pageToken,
|
|
10919
|
+
const existingJobFunctions = await fetchAll(async (pageToken, maxPageSize) => {
|
|
10315
10920
|
const response = await client.listWorkflowJobFunctions({
|
|
10316
10921
|
workspaceId,
|
|
10317
10922
|
pageToken,
|
|
10318
|
-
pageSize:
|
|
10923
|
+
pageSize: maxPageSize
|
|
10319
10924
|
});
|
|
10320
10925
|
return [response.jobFunctions.map((j) => j.name), response.nextPageToken];
|
|
10321
10926
|
});
|
|
@@ -10495,11 +11100,11 @@ async function planWorkflow(client, workspaceId, appName, appId, workflows, main
|
|
|
10495
11100
|
}
|
|
10496
11101
|
async function planWorkflowJobFunctionDeletes(params) {
|
|
10497
11102
|
const { client, workspaceId, appName, appId, retainedWorkflowJobNames } = params;
|
|
10498
|
-
const existingJobFunctions = await fetchAll(async (pageToken,
|
|
11103
|
+
const existingJobFunctions = await fetchAll(async (pageToken, maxPageSize) => {
|
|
10499
11104
|
const response = await client.listWorkflowJobFunctions({
|
|
10500
11105
|
workspaceId,
|
|
10501
11106
|
pageToken,
|
|
10502
|
-
pageSize:
|
|
11107
|
+
pageSize: maxPageSize
|
|
10503
11108
|
});
|
|
10504
11109
|
return [response.jobFunctions.map((jobFunction) => jobFunction.name), response.nextPageToken];
|
|
10505
11110
|
});
|
|
@@ -11118,13 +11723,14 @@ function validateItems(params) {
|
|
|
11118
11723
|
*
|
|
11119
11724
|
* Collections not validated: idp client, tailorDB gqlPermission, functionRegistry — no
|
|
11120
11725
|
* buf.validate annotations.
|
|
11121
|
-
* Application cors and IdP userAuthPolicy.allowedReturnOrigins receive
|
|
11122
|
-
* handling: static-website URL placeholders are resolved at apply time, so the
|
|
11726
|
+
* Application cors, AIGateway cors, and IdP userAuthPolicy.allowedReturnOrigins receive
|
|
11727
|
+
* special handling: static-website URL placeholders are resolved at apply time, so the
|
|
11123
11728
|
* relevant origin/URL constraints would false-positive on `<name>:url` entries
|
|
11124
|
-
* here. Application cors
|
|
11125
|
-
*
|
|
11126
|
-
*
|
|
11127
|
-
*
|
|
11729
|
+
* here. Application cors and AIGateway cors are dropped entirely, forgoing their
|
|
11730
|
+
* per-item URL/origin constraints for the whole list rather than substituting
|
|
11731
|
+
* placeholders; IdP `allowedReturnOrigins` instead substitutes placeholder entries
|
|
11732
|
+
* with a dummy origin so the per-item regex and the cross-field `enable_mfa requires
|
|
11733
|
+
* ≥1 origin` rule still get exercised on the rest of the payload.
|
|
11128
11734
|
* Workflow jobFunctions map excluded: versions are registered at apply time (registerJobFunctions)
|
|
11129
11735
|
* and the map field carries no min_items constraint. Job names are validated separately via
|
|
11130
11736
|
* CreateWorkflowJobFunctionRequestSchema using usedJobNames from the workflow change set.
|
|
@@ -11134,7 +11740,7 @@ function validateItems(params) {
|
|
|
11134
11740
|
* @param input - Plan results from the plan phase
|
|
11135
11741
|
*/
|
|
11136
11742
|
async function validatePlan(input) {
|
|
11137
|
-
const { tailorDB, staticWebsite, idp, auth, pipeline, app, executor, workflow, workflowExecutionPolicy, secretManager } = input;
|
|
11743
|
+
const { tailorDB, staticWebsite, aiGateway, idp, auth, pipeline, app, executor, workflow, workflowExecutionPolicy, secretManager } = input;
|
|
11138
11744
|
const validator = createValidator();
|
|
11139
11745
|
const violations = [];
|
|
11140
11746
|
function creates(schema, kind, items) {
|
|
@@ -11187,6 +11793,20 @@ async function validatePlan(input) {
|
|
|
11187
11793
|
creates(CreateStaticWebsiteRequestSchema, "StaticWebsite", staticWebsite.changeSet.creates);
|
|
11188
11794
|
updates(UpdateStaticWebsiteRequestSchema, "StaticWebsite", staticWebsite.changeSet.updates);
|
|
11189
11795
|
creates(AddCustomDomainRequestSchema, "StaticWebsite custom domain", staticWebsite.customDomainChangeSet.creates);
|
|
11796
|
+
creates(CreateAIGatewayRequestSchema, "AIGateway", aiGateway.changeSet.creates.map((item) => ({
|
|
11797
|
+
name: item.name,
|
|
11798
|
+
request: {
|
|
11799
|
+
...item.request,
|
|
11800
|
+
cors: void 0
|
|
11801
|
+
}
|
|
11802
|
+
})));
|
|
11803
|
+
updates(UpdateAIGatewayRequestSchema, "AIGateway", aiGateway.changeSet.updates.map((item) => ({
|
|
11804
|
+
name: item.name,
|
|
11805
|
+
request: {
|
|
11806
|
+
...item.request,
|
|
11807
|
+
cors: void 0
|
|
11808
|
+
}
|
|
11809
|
+
})));
|
|
11190
11810
|
const placeholderOriginReplacement = "https://placeholder.invalid";
|
|
11191
11811
|
const substituteIdpReturnOrigins = (item) => {
|
|
11192
11812
|
const request = item.request;
|
|
@@ -11329,6 +11949,831 @@ async function validatePlan(input) {
|
|
|
11329
11949
|
throw new Error(`${violations.length} validation error(s) found in ${resourceNames.size} resource(s)`);
|
|
11330
11950
|
}
|
|
11331
11951
|
|
|
11952
|
+
//#endregion
|
|
11953
|
+
//#region src/cli/shared/profile-name.ts
|
|
11954
|
+
const profileNameSchema = z.string().min(1, "Profile must not be empty");
|
|
11955
|
+
|
|
11956
|
+
//#endregion
|
|
11957
|
+
//#region src/cli/shared/workspace-name.ts
|
|
11958
|
+
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");
|
|
11959
|
+
/**
|
|
11960
|
+
* Validate a workspace name for use in an interactive prompt.
|
|
11961
|
+
* @param name - Candidate workspace name
|
|
11962
|
+
* @returns True when valid, otherwise a validation message
|
|
11963
|
+
*/
|
|
11964
|
+
function validateWorkspaceName(name) {
|
|
11965
|
+
const result = workspaceNameSchema.safeParse(name);
|
|
11966
|
+
return result.success ? true : assertDefined(result.error.issues[0], "Zod returned no issues").message;
|
|
11967
|
+
}
|
|
11968
|
+
|
|
11969
|
+
//#endregion
|
|
11970
|
+
//#region src/cli/shared/format.ts
|
|
11971
|
+
/**
|
|
11972
|
+
* Format a protobuf Timestamp to ISO string.
|
|
11973
|
+
* @param timestamp - Protobuf timestamp
|
|
11974
|
+
* @returns Date object or null if invalid
|
|
11975
|
+
*/
|
|
11976
|
+
function formatTimestamp(timestamp) {
|
|
11977
|
+
if (!timestamp) return null;
|
|
11978
|
+
const date = timestampDate(timestamp);
|
|
11979
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
11980
|
+
return date;
|
|
11981
|
+
}
|
|
11982
|
+
/**
|
|
11983
|
+
* Formats a table with consistent single-line border style.
|
|
11984
|
+
* Use this instead of importing `table` directly.
|
|
11985
|
+
* @param data - Table data
|
|
11986
|
+
* @param config - Table configuration
|
|
11987
|
+
* @returns Formatted table string
|
|
11988
|
+
*/
|
|
11989
|
+
function formatTable(data, config) {
|
|
11990
|
+
return table(data, {
|
|
11991
|
+
...config,
|
|
11992
|
+
border: getBorderCharacters("norc")
|
|
11993
|
+
});
|
|
11994
|
+
}
|
|
11995
|
+
/**
|
|
11996
|
+
* Formats a key-value table with single-line border style.
|
|
11997
|
+
* @param data - Key-value pairs
|
|
11998
|
+
* @returns Formatted key-value table string
|
|
11999
|
+
*/
|
|
12000
|
+
function formatKeyValueTable(data) {
|
|
12001
|
+
return formatTable(data, { singleLine: true });
|
|
12002
|
+
}
|
|
12003
|
+
/**
|
|
12004
|
+
* Format a Date or ISO timestamp string as a human-readable relative time.
|
|
12005
|
+
* @param value - Date object, ISO date string, or null
|
|
12006
|
+
* @returns Relative time (e.g., "5 minutes ago") or "N/A" for null/invalid
|
|
12007
|
+
*/
|
|
12008
|
+
function humanizeRelativeTime(value) {
|
|
12009
|
+
if (value === null) return "N/A";
|
|
12010
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
12011
|
+
if (Number.isNaN(date.getTime())) return typeof value === "string" ? value : "N/A";
|
|
12012
|
+
return formatDistanceToNowStrict(date, { addSuffix: true });
|
|
12013
|
+
}
|
|
12014
|
+
|
|
12015
|
+
//#endregion
|
|
12016
|
+
//#region src/cli/commands/workspace/transform.ts
|
|
12017
|
+
const workspaceInfo = (workspace, folderName) => {
|
|
12018
|
+
const info = {
|
|
12019
|
+
id: workspace.id,
|
|
12020
|
+
name: workspace.name,
|
|
12021
|
+
region: workspace.region,
|
|
12022
|
+
...workspace.organizationId ? { organizationId: workspace.organizationId } : {},
|
|
12023
|
+
...workspace.folderId ? { folderId: workspace.folderId } : {},
|
|
12024
|
+
createdAt: formatTimestamp(workspace.createTime),
|
|
12025
|
+
updatedAt: formatTimestamp(workspace.updateTime)
|
|
12026
|
+
};
|
|
12027
|
+
return folderName ? {
|
|
12028
|
+
...info,
|
|
12029
|
+
folderName
|
|
12030
|
+
} : info;
|
|
12031
|
+
};
|
|
12032
|
+
const workspaceDetails = (workspace, folderName) => {
|
|
12033
|
+
return {
|
|
12034
|
+
...workspaceInfo(workspace, folderName),
|
|
12035
|
+
deleteProtection: workspace.deleteProtection,
|
|
12036
|
+
organizationId: workspace.organizationId,
|
|
12037
|
+
folderId: workspace.folderId
|
|
12038
|
+
};
|
|
12039
|
+
};
|
|
12040
|
+
async function resolveWorkspaceFolderName(client, workspace) {
|
|
12041
|
+
if (!workspace.organizationId || !workspace.folderId) return void 0;
|
|
12042
|
+
try {
|
|
12043
|
+
return (await client.getOrganizationFolder({
|
|
12044
|
+
organizationId: workspace.organizationId,
|
|
12045
|
+
folderId: workspace.folderId
|
|
12046
|
+
})).folder?.name || void 0;
|
|
12047
|
+
} catch (error) {
|
|
12048
|
+
if (error instanceof ConnectError && (error.code === Code.NotFound || error.code === Code.PermissionDenied)) return;
|
|
12049
|
+
logger.warn(`Failed to resolve workspace folder name: ${error}`);
|
|
12050
|
+
return;
|
|
12051
|
+
}
|
|
12052
|
+
}
|
|
12053
|
+
function createWorkspaceFolderNameResolver(client) {
|
|
12054
|
+
const cache = /* @__PURE__ */ new Map();
|
|
12055
|
+
return (workspace) => {
|
|
12056
|
+
if (!workspace.organizationId || !workspace.folderId) return Promise.resolve(void 0);
|
|
12057
|
+
const cacheKey = `${workspace.organizationId}/${workspace.folderId}`;
|
|
12058
|
+
const cached = cache.get(cacheKey);
|
|
12059
|
+
if (cached) return cached;
|
|
12060
|
+
const promise = resolveWorkspaceFolderName(client, workspace);
|
|
12061
|
+
cache.set(cacheKey, promise);
|
|
12062
|
+
return promise;
|
|
12063
|
+
};
|
|
12064
|
+
}
|
|
12065
|
+
async function workspaceInfoWithFolderName(client, workspace) {
|
|
12066
|
+
const folderName = await resolveWorkspaceFolderName(client, workspace);
|
|
12067
|
+
return workspaceInfo(workspace, folderName);
|
|
12068
|
+
}
|
|
12069
|
+
async function workspaceDetailsWithFolderName(client, workspace) {
|
|
12070
|
+
const folderName = await resolveWorkspaceFolderName(client, workspace);
|
|
12071
|
+
return workspaceDetails(workspace, folderName);
|
|
12072
|
+
}
|
|
12073
|
+
async function workspaceInfosWithFolderNames(client, workspaces) {
|
|
12074
|
+
const resolveFolderName = createWorkspaceFolderNameResolver(client);
|
|
12075
|
+
const limit = pLimit(5);
|
|
12076
|
+
return Promise.all(workspaces.map((workspace) => limit(async () => workspaceInfo(workspace, await resolveFolderName(workspace)))));
|
|
12077
|
+
}
|
|
12078
|
+
function workspaceDisplayName(workspace) {
|
|
12079
|
+
return workspace.folderName ? `${workspace.folderName}/${workspace.name}` : workspace.name;
|
|
12080
|
+
}
|
|
12081
|
+
function createWorkspaceNameTransformer(nameKey, folderNameKey) {
|
|
12082
|
+
return (value, item) => {
|
|
12083
|
+
const workspace = item;
|
|
12084
|
+
const name = workspace[nameKey];
|
|
12085
|
+
const folderName = workspace[folderNameKey];
|
|
12086
|
+
if (typeof name === "string" && typeof folderName === "string") return workspaceDisplayName({
|
|
12087
|
+
name,
|
|
12088
|
+
folderName
|
|
12089
|
+
});
|
|
12090
|
+
return String(value ?? "");
|
|
12091
|
+
};
|
|
12092
|
+
}
|
|
12093
|
+
const workspaceNameTransformer = createWorkspaceNameTransformer("name", "folderName");
|
|
12094
|
+
|
|
12095
|
+
//#endregion
|
|
12096
|
+
//#region src/cli/commands/workspace/create.ts
|
|
12097
|
+
/**
|
|
12098
|
+
* Schema for workspace creation options
|
|
12099
|
+
* - name: 3-63 chars, lowercase alphanumeric and hyphens, cannot start/end with hyphen
|
|
12100
|
+
* - organizationId, folderId: optional UUIDs
|
|
12101
|
+
*/
|
|
12102
|
+
const createWorkspaceOptionsSchema = z.object({
|
|
12103
|
+
name: workspaceNameSchema,
|
|
12104
|
+
region: z.string(),
|
|
12105
|
+
deleteProtection: z.boolean().optional(),
|
|
12106
|
+
organizationId: z.uuid().optional(),
|
|
12107
|
+
folderId: z.uuid().optional(),
|
|
12108
|
+
profile: profileNameSchema.optional()
|
|
12109
|
+
});
|
|
12110
|
+
const validateRegion = async (region, client) => {
|
|
12111
|
+
const availableRegions = await client.listAvailableWorkspaceRegions({});
|
|
12112
|
+
if (!availableRegions.regions.includes(region)) throw new Error(`Region must be one of: ${availableRegions.regions.join(", ")}.`);
|
|
12113
|
+
};
|
|
12114
|
+
function profilePlatformSettings(platformConfig) {
|
|
12115
|
+
const hasOAuth2ClientId = platformConfig?.oauth2ClientId || process.env.TAILOR_PLATFORM_OAUTH2_CLIENT_ID;
|
|
12116
|
+
const hasConsoleUrl = platformConfig?.consoleUrl || process.env.TAILOR_PLATFORM_CONSOLE_URL;
|
|
12117
|
+
return {
|
|
12118
|
+
...isDefaultPlatform(platformConfig) ? {} : { platform_url: getPlatformBaseUrl(platformConfig) },
|
|
12119
|
+
...hasOAuth2ClientId ? { oauth2_client_id: getOAuth2ClientId(platformConfig) } : {},
|
|
12120
|
+
...hasConsoleUrl ? { console_url: getConsoleBaseUrl(platformConfig) } : {}
|
|
12121
|
+
};
|
|
12122
|
+
}
|
|
12123
|
+
/**
|
|
12124
|
+
* Create a new workspace with the given options.
|
|
12125
|
+
* @param options - Workspace creation options
|
|
12126
|
+
* @returns Created workspace info
|
|
12127
|
+
*/
|
|
12128
|
+
async function createWorkspace$1(options) {
|
|
12129
|
+
const validated = validateCreateWorkspaceOptions(options);
|
|
12130
|
+
const client = await initOperatorClient(await loadAccessToken({ profile: validated.profile }), await loadPlatformClientConfig({ profile: validated.profile }));
|
|
12131
|
+
await validateRegion(validated.region, client);
|
|
12132
|
+
return createValidatedWorkspaceWithClient(client, validated);
|
|
12133
|
+
}
|
|
12134
|
+
/**
|
|
12135
|
+
* Create a workspace after its local options and region have been validated.
|
|
12136
|
+
* @param client - Authenticated Operator client
|
|
12137
|
+
* @param options - Validated workspace creation options
|
|
12138
|
+
* @returns Created workspace info
|
|
12139
|
+
*/
|
|
12140
|
+
async function createValidatedWorkspaceWithClient(client, options) {
|
|
12141
|
+
return workspaceInfoWithFolderName(client, assertDefined((await client.createWorkspace({
|
|
12142
|
+
workspaceName: options.name,
|
|
12143
|
+
workspaceRegion: options.region,
|
|
12144
|
+
deleteProtection: options.deleteProtection ?? false,
|
|
12145
|
+
organizationId: options.organizationId,
|
|
12146
|
+
folderId: options.folderId
|
|
12147
|
+
})).workspace, "createWorkspace response missing workspace"));
|
|
12148
|
+
}
|
|
12149
|
+
/**
|
|
12150
|
+
* Validate workspace creation options without making API calls.
|
|
12151
|
+
* @param options - Workspace creation options
|
|
12152
|
+
* @returns Validated workspace creation options
|
|
12153
|
+
*/
|
|
12154
|
+
function validateCreateWorkspaceOptions(options) {
|
|
12155
|
+
const result = createWorkspaceOptionsSchema.safeParse(options);
|
|
12156
|
+
if (!result.success) throw new Error(assertDefined(result.error.issues[0], "Zod returned no issues").message);
|
|
12157
|
+
return result.data;
|
|
12158
|
+
}
|
|
12159
|
+
const createCommand$1 = defineAppCommand({
|
|
12160
|
+
name: "create",
|
|
12161
|
+
description: "Create a new Tailor Platform workspace.",
|
|
12162
|
+
args: z.object({
|
|
12163
|
+
name: arg(z.string(), {
|
|
12164
|
+
alias: "n",
|
|
12165
|
+
description: "Workspace name"
|
|
12166
|
+
}),
|
|
12167
|
+
region: arg(z.string(), {
|
|
12168
|
+
alias: "r",
|
|
12169
|
+
description: "Workspace region (us-west, asia-northeast)"
|
|
12170
|
+
}),
|
|
12171
|
+
"delete-protection": arg(z.boolean().default(false), {
|
|
12172
|
+
alias: "d",
|
|
12173
|
+
description: "Enable delete protection"
|
|
12174
|
+
}),
|
|
12175
|
+
"organization-id": arg(z.string().optional(), {
|
|
12176
|
+
alias: "o",
|
|
12177
|
+
description: "Organization ID to workspace associate with",
|
|
12178
|
+
env: "TAILOR_PLATFORM_ORGANIZATION_ID"
|
|
12179
|
+
}),
|
|
12180
|
+
"folder-id": arg(z.string().optional(), {
|
|
12181
|
+
alias: "f",
|
|
12182
|
+
description: "Folder ID to workspace associate with",
|
|
12183
|
+
env: "TAILOR_PLATFORM_FOLDER_ID"
|
|
12184
|
+
}),
|
|
12185
|
+
"profile-name": arg(z.string().optional(), {
|
|
12186
|
+
alias: "p",
|
|
12187
|
+
description: "Profile name to create"
|
|
12188
|
+
}),
|
|
12189
|
+
profile: arg(profileNameSchema.optional(), {
|
|
12190
|
+
description: "Workspace profile used for authentication and Platform selection",
|
|
12191
|
+
env: "TAILOR_PLATFORM_PROFILE"
|
|
12192
|
+
}),
|
|
12193
|
+
"profile-user": arg(z.string().optional(), { description: "User email for the profile (defaults to current user)" }),
|
|
12194
|
+
permission: arg(z.enum(["write", "read"]).default("write"), { description: "Profile permission (requires --profile-name). 'read' blocks all write commands while the profile is active." })
|
|
12195
|
+
}).strict(),
|
|
12196
|
+
run: async (args) => {
|
|
12197
|
+
await assertWritable({ profile: args.profile });
|
|
12198
|
+
const profileName = args["profile-name"];
|
|
12199
|
+
let profileSetup;
|
|
12200
|
+
if (profileName) {
|
|
12201
|
+
const config = await readPlatformConfig();
|
|
12202
|
+
if (config.profiles[profileName]) throw new Error(`Profile "${profileName}" already exists.`);
|
|
12203
|
+
const activeProfileName = args.profile;
|
|
12204
|
+
const activeProfileEntry = activeProfileName ? config.profiles[activeProfileName] : void 0;
|
|
12205
|
+
const platformConfig = activeProfileEntry ? platformConfigFromProfile(activeProfileEntry) : void 0;
|
|
12206
|
+
const profileUser = args["profile-user"] || activeProfileEntry?.user || config.current_user;
|
|
12207
|
+
if (!profileUser) throw new Error("Current user not found. Please login or specify --profile-user to create a profile.");
|
|
12208
|
+
if (!hasUserTokenEntry(config, profileUser, platformConfig)) throw new Error(`User "${profileUser}" not found.\nPlease verify your user name and login using 'tailor-sdk login' command.`);
|
|
12209
|
+
profileSetup = {
|
|
12210
|
+
name: profileName,
|
|
12211
|
+
user: profileUser,
|
|
12212
|
+
platformSettings: profilePlatformSettings(platformConfig)
|
|
12213
|
+
};
|
|
12214
|
+
}
|
|
12215
|
+
const workspace = await createWorkspace$1({
|
|
12216
|
+
name: args.name,
|
|
12217
|
+
region: args.region,
|
|
12218
|
+
deleteProtection: args["delete-protection"],
|
|
12219
|
+
organizationId: args["organization-id"],
|
|
12220
|
+
folderId: args["folder-id"],
|
|
12221
|
+
profile: args.profile
|
|
12222
|
+
});
|
|
12223
|
+
let profileInfo;
|
|
12224
|
+
if (profileSetup) {
|
|
12225
|
+
const config = await readPlatformConfig();
|
|
12226
|
+
const platformSettings = profileSetup.platformSettings;
|
|
12227
|
+
config.profiles[profileSetup.name] = {
|
|
12228
|
+
user: profileSetup.user,
|
|
12229
|
+
workspace_id: workspace.id,
|
|
12230
|
+
...args.permission === "read" ? { readonly: true } : {},
|
|
12231
|
+
...platformSettings
|
|
12232
|
+
};
|
|
12233
|
+
writePlatformConfig(config);
|
|
12234
|
+
profileInfo = {
|
|
12235
|
+
name: profileSetup.name,
|
|
12236
|
+
user: profileSetup.user,
|
|
12237
|
+
workspaceId: workspace.id,
|
|
12238
|
+
permission: args.permission,
|
|
12239
|
+
...platformSettings.platform_url ? { platformUrl: platformSettings.platform_url } : {},
|
|
12240
|
+
...platformSettings.oauth2_client_id ? { oauth2ClientId: platformSettings.oauth2_client_id } : {},
|
|
12241
|
+
...platformSettings.console_url ? { consoleUrl: platformSettings.console_url } : {}
|
|
12242
|
+
};
|
|
12243
|
+
if (!args.json) logger.success(`Profile "${profileSetup.name}" created successfully.`);
|
|
12244
|
+
}
|
|
12245
|
+
if (!args.json) logger.success(`Workspace "${workspaceDisplayName(workspace)}" created successfully.`);
|
|
12246
|
+
if (args.json && profileInfo) {
|
|
12247
|
+
logger.out({
|
|
12248
|
+
...workspace,
|
|
12249
|
+
profile: profileInfo
|
|
12250
|
+
});
|
|
12251
|
+
return;
|
|
12252
|
+
}
|
|
12253
|
+
logger.out(workspace, { display: {
|
|
12254
|
+
name: workspaceNameTransformer,
|
|
12255
|
+
folderName: null
|
|
12256
|
+
} });
|
|
12257
|
+
if (profileInfo) {
|
|
12258
|
+
logger.out("Profile:");
|
|
12259
|
+
logger.out(profileInfo);
|
|
12260
|
+
}
|
|
12261
|
+
}
|
|
12262
|
+
});
|
|
12263
|
+
|
|
12264
|
+
//#endregion
|
|
12265
|
+
//#region src/cli/commands/workspace/list.ts
|
|
12266
|
+
/**
|
|
12267
|
+
* List workspaces with an optional order and limit.
|
|
12268
|
+
* @param options - Workspace listing options
|
|
12269
|
+
* @returns List of workspaces
|
|
12270
|
+
*/
|
|
12271
|
+
async function listWorkspaces(options) {
|
|
12272
|
+
const profile = profileNameSchema.optional().parse(options?.profile);
|
|
12273
|
+
return listWorkspacesWithClient(await initOperatorClient(await loadAccessToken({ profile }), await loadPlatformClientConfig({ profile })), options);
|
|
12274
|
+
}
|
|
12275
|
+
/**
|
|
12276
|
+
* List workspaces using an existing Operator client.
|
|
12277
|
+
* @param client - Authenticated Operator client
|
|
12278
|
+
* @param options - Workspace listing options
|
|
12279
|
+
* @returns List of workspaces
|
|
12280
|
+
*/
|
|
12281
|
+
async function listWorkspacesWithClient(client, options) {
|
|
12282
|
+
const pageDirection = toPageDirection(options?.order);
|
|
12283
|
+
return workspaceInfosWithFolderNames(client, await fetchPaged(async (pageToken, pageSize) => {
|
|
12284
|
+
const { workspaces, nextPageToken } = await client.listWorkspaces({
|
|
12285
|
+
pageToken,
|
|
12286
|
+
pageSize,
|
|
12287
|
+
pageDirection
|
|
12288
|
+
});
|
|
12289
|
+
return [workspaces, nextPageToken];
|
|
12290
|
+
}, { limit: options?.limit }));
|
|
12291
|
+
}
|
|
12292
|
+
const listCommand$9 = defineAppCommand({
|
|
12293
|
+
name: "list",
|
|
12294
|
+
description: "List all Tailor Platform workspaces.",
|
|
12295
|
+
args: z.object({
|
|
12296
|
+
...paginationArgs(),
|
|
12297
|
+
profile: arg(profileNameSchema.optional(), {
|
|
12298
|
+
description: "Workspace profile used for authentication and Platform selection",
|
|
12299
|
+
env: "TAILOR_PLATFORM_PROFILE"
|
|
12300
|
+
})
|
|
12301
|
+
}).strict(),
|
|
12302
|
+
run: async (args) => {
|
|
12303
|
+
const workspaces = await listWorkspaces({
|
|
12304
|
+
order: args.order,
|
|
12305
|
+
limit: args.limit,
|
|
12306
|
+
profile: args.profile
|
|
12307
|
+
});
|
|
12308
|
+
logger.out(workspaces, { display: {
|
|
12309
|
+
name: workspaceNameTransformer,
|
|
12310
|
+
folderName: null,
|
|
12311
|
+
organizationId: null,
|
|
12312
|
+
folderId: null,
|
|
12313
|
+
updatedAt: null
|
|
12314
|
+
} });
|
|
12315
|
+
}
|
|
12316
|
+
});
|
|
12317
|
+
|
|
12318
|
+
//#endregion
|
|
12319
|
+
//#region src/cli/commands/deploy/workspace-context.ts
|
|
12320
|
+
const workspaceContextSchema = z.object({
|
|
12321
|
+
version: z.literal(1),
|
|
12322
|
+
platformUrl: z.url(),
|
|
12323
|
+
applicationId: z.string().min(1).optional(),
|
|
12324
|
+
workspaceId: z.uuid()
|
|
12325
|
+
});
|
|
12326
|
+
function defaultConfigPath() {
|
|
12327
|
+
return resolve(process.cwd(), "tailor.config.ts");
|
|
12328
|
+
}
|
|
12329
|
+
function contextPath(configPath) {
|
|
12330
|
+
return join(dirname(configPath), ".tailor-sdk", `${basename(configPath)}.context.json`);
|
|
12331
|
+
}
|
|
12332
|
+
/**
|
|
12333
|
+
* Load the project workspace context when it belongs to the current platform.
|
|
12334
|
+
* Missing, malformed, and cross-platform state is ignored.
|
|
12335
|
+
* @param platformUrl - Current Platform API base URL
|
|
12336
|
+
* @param configPath - Configuration file whose workspace selection is loaded
|
|
12337
|
+
* @param applicationId - Application identity that must match the saved context
|
|
12338
|
+
* @returns Valid context for the current platform, or undefined
|
|
12339
|
+
*/
|
|
12340
|
+
async function loadWorkspaceContext(platformUrl, configPath = defaultConfigPath(), applicationId) {
|
|
12341
|
+
let contents;
|
|
12342
|
+
try {
|
|
12343
|
+
contents = await readFile(contextPath(configPath), "utf8");
|
|
12344
|
+
} catch (error) {
|
|
12345
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
|
|
12346
|
+
throw error;
|
|
12347
|
+
}
|
|
12348
|
+
let value;
|
|
12349
|
+
try {
|
|
12350
|
+
value = JSON.parse(contents);
|
|
12351
|
+
} catch {
|
|
12352
|
+
return;
|
|
12353
|
+
}
|
|
12354
|
+
const result = workspaceContextSchema.safeParse(value);
|
|
12355
|
+
if (!result.success || result.data.platformUrl !== platformUrl || applicationId !== void 0 && result.data.applicationId !== applicationId) return;
|
|
12356
|
+
return result.data;
|
|
12357
|
+
}
|
|
12358
|
+
/**
|
|
12359
|
+
* Persist the selected workspace as project-local SDK state.
|
|
12360
|
+
* @param context - Workspace context to persist
|
|
12361
|
+
* @param configPath - Configuration file whose workspace selection is persisted
|
|
12362
|
+
* @param applicationId - Application identity stored with the workspace selection
|
|
12363
|
+
*/
|
|
12364
|
+
async function saveWorkspaceContext(context, configPath = defaultConfigPath(), applicationId) {
|
|
12365
|
+
const validated = workspaceContextSchema.parse({
|
|
12366
|
+
...context,
|
|
12367
|
+
...applicationId === void 0 ? {} : { applicationId }
|
|
12368
|
+
});
|
|
12369
|
+
const stateDirectory = join(dirname(configPath), ".tailor-sdk");
|
|
12370
|
+
const targetPath = contextPath(configPath);
|
|
12371
|
+
const serialized = `${JSON.stringify(validated, null, 2)}\n`;
|
|
12372
|
+
try {
|
|
12373
|
+
if (await readFile(targetPath, "utf8") === serialized) return;
|
|
12374
|
+
} catch {}
|
|
12375
|
+
const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
12376
|
+
await mkdir(stateDirectory, { recursive: true });
|
|
12377
|
+
try {
|
|
12378
|
+
await writeFile(temporaryPath, serialized, { mode: 384 });
|
|
12379
|
+
await rename(temporaryPath, targetPath);
|
|
12380
|
+
} catch (error) {
|
|
12381
|
+
await rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
12382
|
+
throw error;
|
|
12383
|
+
}
|
|
12384
|
+
}
|
|
12385
|
+
|
|
12386
|
+
//#endregion
|
|
12387
|
+
//#region src/cli/commands/deploy/workspace.ts
|
|
12388
|
+
function suggestedWorkspaceName() {
|
|
12389
|
+
const name = basename(process.cwd()).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, 63).replace(/-+$/, "");
|
|
12390
|
+
return validateWorkspaceName(name) === true ? name : "my-workspace";
|
|
12391
|
+
}
|
|
12392
|
+
function executableAction(args) {
|
|
12393
|
+
return {
|
|
12394
|
+
command: "tailor-sdk",
|
|
12395
|
+
args
|
|
12396
|
+
};
|
|
12397
|
+
}
|
|
12398
|
+
function deployArgs(options) {
|
|
12399
|
+
return options.deployArgs ?? ["deploy"];
|
|
12400
|
+
}
|
|
12401
|
+
function workspaceCommandArgs(options) {
|
|
12402
|
+
return options.workspaceCommandArgs ?? [];
|
|
12403
|
+
}
|
|
12404
|
+
function createDeployArgs(options, name, region) {
|
|
12405
|
+
return [
|
|
12406
|
+
...deployArgs(options),
|
|
12407
|
+
"--create-workspace",
|
|
12408
|
+
"--workspace-name",
|
|
12409
|
+
name,
|
|
12410
|
+
"--workspace-region",
|
|
12411
|
+
region,
|
|
12412
|
+
...options.organizationId ? ["--organization-id", options.organizationId] : [],
|
|
12413
|
+
...options.folderId ? ["--folder-id", options.folderId] : []
|
|
12414
|
+
];
|
|
12415
|
+
}
|
|
12416
|
+
function selectDeployArgs(options) {
|
|
12417
|
+
return [
|
|
12418
|
+
...deployArgs(options),
|
|
12419
|
+
"--workspace-id",
|
|
12420
|
+
"<workspace-id>"
|
|
12421
|
+
];
|
|
12422
|
+
}
|
|
12423
|
+
function workspaceLabel(workspace) {
|
|
12424
|
+
const organization = workspace.organizationId ?? "personal";
|
|
12425
|
+
return `${workspaceDisplayName(workspace)} (${workspace.region}, org: ${organization}, id: ${workspace.id})`;
|
|
12426
|
+
}
|
|
12427
|
+
function workspaceMatchesRequestedIdentity(workspace, options) {
|
|
12428
|
+
return options.workspaceName === workspace.name && options.workspaceRegion === workspace.region && options.organizationId === workspace.organizationId && options.folderId === workspace.folderId;
|
|
12429
|
+
}
|
|
12430
|
+
function workspaceIdentity(workspace) {
|
|
12431
|
+
const { id, name, region, organizationId, folderId } = workspace;
|
|
12432
|
+
return {
|
|
12433
|
+
id,
|
|
12434
|
+
name,
|
|
12435
|
+
region,
|
|
12436
|
+
organizationId,
|
|
12437
|
+
folderId
|
|
12438
|
+
};
|
|
12439
|
+
}
|
|
12440
|
+
function projectContextTargets(contextTargets) {
|
|
12441
|
+
if (!contextTargets) return void 0;
|
|
12442
|
+
return [...new Map(contextTargets.map((target) => [target.configPath, target])).values()];
|
|
12443
|
+
}
|
|
12444
|
+
async function loadProjectContexts(platformUrl, contextTargets) {
|
|
12445
|
+
const targets = projectContextTargets(contextTargets);
|
|
12446
|
+
if (!targets || targets.length === 0) {
|
|
12447
|
+
const context = await loadWorkspaceContext(platformUrl);
|
|
12448
|
+
return context ? [context] : [];
|
|
12449
|
+
}
|
|
12450
|
+
return (await Promise.all(targets.map(({ configPath, applicationId }) => loadWorkspaceContext(platformUrl, configPath, applicationId)))).filter((context) => context !== void 0);
|
|
12451
|
+
}
|
|
12452
|
+
async function persistWorkspaceContext(context, contextTargets) {
|
|
12453
|
+
const targets = projectContextTargets(contextTargets);
|
|
12454
|
+
if (!targets || targets.length === 0) {
|
|
12455
|
+
await saveWorkspaceContext(context);
|
|
12456
|
+
return;
|
|
12457
|
+
}
|
|
12458
|
+
const failures = (await Promise.allSettled(targets.map(({ configPath, applicationId }) => saveWorkspaceContext(context, configPath, applicationId)))).filter((result) => result.status === "rejected");
|
|
12459
|
+
if (failures.length > 0) throw new Error(failures.map(({ reason }) => reason instanceof Error ? reason.message : String(reason)).join("; "));
|
|
12460
|
+
}
|
|
12461
|
+
async function rememberWorkspaceContext(context, options, failurePolicy = "error-on-partial") {
|
|
12462
|
+
if (options.dryRun) return;
|
|
12463
|
+
try {
|
|
12464
|
+
await persistWorkspaceContext(context, options.contextTargets);
|
|
12465
|
+
} catch (error) {
|
|
12466
|
+
if ((projectContextTargets(options.contextTargets)?.length ?? 0) > 1 && failurePolicy === "error-on-partial") throw createCLIError({
|
|
12467
|
+
code: "WORKSPACE_CONTEXT_SAVE_FAILED",
|
|
12468
|
+
message: "The workspace selection could not be saved for every configuration file.",
|
|
12469
|
+
details: error instanceof Error ? error.message : String(error),
|
|
12470
|
+
suggestion: "Fix project state permissions, then rerun deploy with the workspace ID.",
|
|
12471
|
+
next: executableAction([
|
|
12472
|
+
...deployArgs(options),
|
|
12473
|
+
"--workspace-id",
|
|
12474
|
+
context.workspaceId
|
|
12475
|
+
]),
|
|
12476
|
+
context: {
|
|
12477
|
+
workspaceId: context.workspaceId,
|
|
12478
|
+
configPaths: options.contextTargets?.map(({ configPath }) => configPath)
|
|
12479
|
+
}
|
|
12480
|
+
});
|
|
12481
|
+
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.`);
|
|
12482
|
+
}
|
|
12483
|
+
}
|
|
12484
|
+
async function useWorkspace(client, platformUrl, workspace, options, failurePolicy = "error-on-partial") {
|
|
12485
|
+
await rememberWorkspaceContext({
|
|
12486
|
+
version: 1,
|
|
12487
|
+
platformUrl,
|
|
12488
|
+
workspaceId: workspace.id
|
|
12489
|
+
}, options, failurePolicy);
|
|
12490
|
+
logger.info(`Using workspace: ${workspaceLabel(workspace)}`);
|
|
12491
|
+
return {
|
|
12492
|
+
client,
|
|
12493
|
+
workspaceId: workspace.id
|
|
12494
|
+
};
|
|
12495
|
+
}
|
|
12496
|
+
function useRememberedWorkspace(client, workspace) {
|
|
12497
|
+
logger.warn(`Using saved workspace selection: ${workspaceLabel(workspace)}`);
|
|
12498
|
+
return {
|
|
12499
|
+
client,
|
|
12500
|
+
workspaceId: workspace.id
|
|
12501
|
+
};
|
|
12502
|
+
}
|
|
12503
|
+
const createNewWorkspaceSelection = "create-new-workspace";
|
|
12504
|
+
async function chooseWorkspace(client, platformUrl, workspaces, options) {
|
|
12505
|
+
const workspaceId = await prompt.select({
|
|
12506
|
+
message: "Select a workspace",
|
|
12507
|
+
choices: [...workspaces.map((workspace) => ({
|
|
12508
|
+
name: workspaceLabel(workspace),
|
|
12509
|
+
value: workspace.id
|
|
12510
|
+
})), {
|
|
12511
|
+
name: "Create new workspace",
|
|
12512
|
+
value: createNewWorkspaceSelection
|
|
12513
|
+
}]
|
|
12514
|
+
});
|
|
12515
|
+
if (workspaceId === createNewWorkspaceSelection) return createWorkspaceForDeploy(client, platformUrl, options);
|
|
12516
|
+
const workspace = workspaces.find(({ id }) => id === workspaceId);
|
|
12517
|
+
if (!workspace) throw new Error("Selected workspace was not found");
|
|
12518
|
+
return useWorkspace(client, platformUrl, workspace, options);
|
|
12519
|
+
}
|
|
12520
|
+
function invalidCreateOptionsError(options, name, region, details) {
|
|
12521
|
+
return createCLIError({
|
|
12522
|
+
code: "WORKSPACE_CREATE_OPTIONS_INVALID",
|
|
12523
|
+
message: "Workspace creation options are invalid.",
|
|
12524
|
+
details,
|
|
12525
|
+
suggestion: "Correct the workspace creation options and rerun deploy.",
|
|
12526
|
+
next: executableAction(createDeployArgs(options, name, region))
|
|
12527
|
+
});
|
|
12528
|
+
}
|
|
12529
|
+
async function createWorkspace(client, platformUrl, options, availableRegions, validatedOptions) {
|
|
12530
|
+
let name = options.workspaceName;
|
|
12531
|
+
let region = options.workspaceRegion;
|
|
12532
|
+
const interactive = canPrompt();
|
|
12533
|
+
if (interactive) {
|
|
12534
|
+
name ??= await prompt.text({
|
|
12535
|
+
message: "Workspace name",
|
|
12536
|
+
default: suggestedWorkspaceName(),
|
|
12537
|
+
validate: validateWorkspaceName
|
|
12538
|
+
});
|
|
12539
|
+
region ??= await prompt.select({
|
|
12540
|
+
message: "Workspace region",
|
|
12541
|
+
choices: availableRegions.map((value) => ({
|
|
12542
|
+
name: value,
|
|
12543
|
+
value
|
|
12544
|
+
}))
|
|
12545
|
+
});
|
|
12546
|
+
} else {
|
|
12547
|
+
const missingOptions = [...name === void 0 ? ["--workspace-name"] : [], ...region === void 0 ? ["--workspace-region"] : []];
|
|
12548
|
+
if (missingOptions.length > 0) throw createCLIError({
|
|
12549
|
+
code: "WORKSPACE_CREATE_OPTIONS_REQUIRED",
|
|
12550
|
+
message: "Workspace creation requires a name and region in non-interactive mode.",
|
|
12551
|
+
suggestion: "Provide every workspace creation option and rerun deploy.",
|
|
12552
|
+
next: executableAction(createDeployArgs(options, name ?? "<name>", region ?? "<region>")),
|
|
12553
|
+
context: { missingOptions }
|
|
12554
|
+
});
|
|
12555
|
+
}
|
|
12556
|
+
if (!name || !region) throw new Error("Workspace creation options were not resolved");
|
|
12557
|
+
let validated = validatedOptions;
|
|
12558
|
+
if (!validated) {
|
|
12559
|
+
try {
|
|
12560
|
+
validated = validateCreateWorkspaceOptions({
|
|
12561
|
+
name,
|
|
12562
|
+
region,
|
|
12563
|
+
organizationId: options.organizationId,
|
|
12564
|
+
folderId: options.folderId
|
|
12565
|
+
});
|
|
12566
|
+
} catch (error) {
|
|
12567
|
+
throw invalidCreateOptionsError(options, name, region, error instanceof Error ? error.message : String(error));
|
|
12568
|
+
}
|
|
12569
|
+
if (!availableRegions.includes(region)) throw invalidCreateOptionsError(options, name, region, `Region must be one of: ${availableRegions.join(", ")}.`);
|
|
12570
|
+
}
|
|
12571
|
+
if (interactive) {
|
|
12572
|
+
const scope = [options.organizationId ? `organization: ${options.organizationId}` : void 0, options.folderId ? `folder: ${options.folderId}` : void 0].filter((value) => value !== void 0);
|
|
12573
|
+
if (!await prompt.confirm({
|
|
12574
|
+
message: `Create workspace "${name}" in ${region}${scope.length > 0 ? ` (${scope.join(", ")})` : ""}?`,
|
|
12575
|
+
default: true
|
|
12576
|
+
})) throw createCLIError({
|
|
12577
|
+
code: "WORKSPACE_CREATION_CANCELLED",
|
|
12578
|
+
message: "Workspace creation was cancelled."
|
|
12579
|
+
});
|
|
12580
|
+
}
|
|
12581
|
+
let workspace;
|
|
12582
|
+
try {
|
|
12583
|
+
workspace = await createValidatedWorkspaceWithClient(client, validated);
|
|
12584
|
+
} catch (error) {
|
|
12585
|
+
if (error instanceof ConnectError && ![
|
|
12586
|
+
Code.Canceled,
|
|
12587
|
+
Code.Unknown,
|
|
12588
|
+
Code.DeadlineExceeded,
|
|
12589
|
+
Code.Aborted,
|
|
12590
|
+
Code.Internal,
|
|
12591
|
+
Code.Unavailable,
|
|
12592
|
+
Code.DataLoss
|
|
12593
|
+
].includes(error.code)) throw error;
|
|
12594
|
+
throw createCLIError({
|
|
12595
|
+
code: "WORKSPACE_CREATION_FAILED",
|
|
12596
|
+
message: "Workspace creation did not complete successfully.",
|
|
12597
|
+
details: error instanceof Error ? error.message : String(error),
|
|
12598
|
+
suggestion: "The outcome may be uncertain. List workspaces before retrying creation to avoid duplicates.",
|
|
12599
|
+
next: executableAction([
|
|
12600
|
+
"workspace",
|
|
12601
|
+
"list",
|
|
12602
|
+
...workspaceCommandArgs(options),
|
|
12603
|
+
"--json"
|
|
12604
|
+
])
|
|
12605
|
+
});
|
|
12606
|
+
}
|
|
12607
|
+
await rememberWorkspaceContext({
|
|
12608
|
+
version: 1,
|
|
12609
|
+
platformUrl,
|
|
12610
|
+
workspaceId: workspace.id
|
|
12611
|
+
}, options);
|
|
12612
|
+
logger.success(`Created workspace: ${workspaceLabel(workspace)}`);
|
|
12613
|
+
logger.info(`Reuse this workspace with: tailor-sdk deploy --workspace-id ${workspace.id}`);
|
|
12614
|
+
logger.info(`Or set TAILOR_PLATFORM_WORKSPACE_ID=${workspace.id}.`);
|
|
12615
|
+
return {
|
|
12616
|
+
client,
|
|
12617
|
+
workspaceId: workspace.id
|
|
12618
|
+
};
|
|
12619
|
+
}
|
|
12620
|
+
async function createWorkspaceForDeploy(client, platformUrl, options, requestedRegions, validatedOptions) {
|
|
12621
|
+
const regions = requestedRegions ?? (await client.listAvailableWorkspaceRegions({})).regions;
|
|
12622
|
+
if (options.dryRun) throw createCLIError({
|
|
12623
|
+
code: "WORKSPACE_CREATION_DISABLED_IN_DRY_RUN",
|
|
12624
|
+
message: "Dry-run cannot create the workspace required to build a deployment plan.",
|
|
12625
|
+
suggestion: "Create a workspace explicitly, then rerun the same dry-run with its workspace ID.",
|
|
12626
|
+
context: { availableRegions: regions }
|
|
12627
|
+
});
|
|
12628
|
+
return createWorkspace(client, platformUrl, options, regions, validatedOptions);
|
|
12629
|
+
}
|
|
12630
|
+
/**
|
|
12631
|
+
* Resolve or provision the workspace used by deploy.
|
|
12632
|
+
* Explicit configuration wins over project context and account discovery.
|
|
12633
|
+
* @param options - Deploy workspace selection and creation options
|
|
12634
|
+
* @returns Authenticated client and resolved workspace ID
|
|
12635
|
+
*/
|
|
12636
|
+
async function resolveDeployWorkspace(options = {}) {
|
|
12637
|
+
const createOptionNames = [options.workspaceName !== void 0 ? "--workspace-name" : void 0, options.workspaceRegion !== void 0 ? "--workspace-region" : void 0].filter((name) => name !== void 0);
|
|
12638
|
+
if (!options.createWorkspace && createOptionNames.length > 0) throw createCLIError({
|
|
12639
|
+
code: "WORKSPACE_CREATE_FLAG_REQUIRED",
|
|
12640
|
+
message: "Workspace creation options require --create-workspace.",
|
|
12641
|
+
suggestion: "Add --create-workspace or remove the workspace creation options.",
|
|
12642
|
+
next: executableAction(createDeployArgs(options, options.workspaceName ?? "<name>", options.workspaceRegion ?? "<region>")),
|
|
12643
|
+
context: { options: createOptionNames }
|
|
12644
|
+
});
|
|
12645
|
+
if (options.createWorkspace && options.workspaceName !== void 0) {
|
|
12646
|
+
const nameValidation = validateWorkspaceName(options.workspaceName);
|
|
12647
|
+
if (nameValidation !== true) throw invalidCreateOptionsError(options, options.workspaceName, options.workspaceRegion ?? "<region>", nameValidation);
|
|
12648
|
+
}
|
|
12649
|
+
if (options.createWorkspace && options.workspaceRegion === "") throw invalidCreateOptionsError(options, options.workspaceName ?? "<name>", options.workspaceRegion, "Region must not be empty.");
|
|
12650
|
+
const explicitWorkspaceId = await tryLoadWorkspaceId({
|
|
12651
|
+
workspaceId: options.workspaceId,
|
|
12652
|
+
profile: options.profile
|
|
12653
|
+
});
|
|
12654
|
+
const accessToken = await loadAccessToken({ profile: options.profile });
|
|
12655
|
+
const platformConfig = await loadPlatformClientConfig({
|
|
12656
|
+
profile: options.profile,
|
|
12657
|
+
allowMissingProfile: explicitWorkspaceId !== void 0
|
|
12658
|
+
});
|
|
12659
|
+
const platformUrl = getPlatformBaseUrl(platformConfig);
|
|
12660
|
+
const client = await initOperatorClient(accessToken, platformConfig);
|
|
12661
|
+
if (explicitWorkspaceId) {
|
|
12662
|
+
let response;
|
|
12663
|
+
try {
|
|
12664
|
+
response = await client.getWorkspace({ workspaceId: explicitWorkspaceId });
|
|
12665
|
+
} catch (error) {
|
|
12666
|
+
if (error instanceof ConnectError && error.code === Code.NotFound) throw createCLIError({
|
|
12667
|
+
code: "WORKSPACE_NOT_FOUND",
|
|
12668
|
+
message: `Workspace "${explicitWorkspaceId}" was not found.`
|
|
12669
|
+
});
|
|
12670
|
+
throw error;
|
|
12671
|
+
}
|
|
12672
|
+
if (!response.workspace) throw createCLIError({
|
|
12673
|
+
code: "WORKSPACE_NOT_FOUND",
|
|
12674
|
+
message: `Workspace "${explicitWorkspaceId}" was not found.`
|
|
12675
|
+
});
|
|
12676
|
+
return useWorkspace(client, platformUrl, workspaceInfo(response.workspace), options, "warn");
|
|
12677
|
+
}
|
|
12678
|
+
let requestedCreateOptions;
|
|
12679
|
+
if (options.createWorkspace && options.workspaceName !== void 0 && options.workspaceRegion !== void 0) try {
|
|
12680
|
+
requestedCreateOptions = validateCreateWorkspaceOptions({
|
|
12681
|
+
name: options.workspaceName,
|
|
12682
|
+
region: options.workspaceRegion,
|
|
12683
|
+
organizationId: options.organizationId,
|
|
12684
|
+
folderId: options.folderId
|
|
12685
|
+
});
|
|
12686
|
+
} catch (error) {
|
|
12687
|
+
throw invalidCreateOptionsError(options, options.workspaceName, options.workspaceRegion, error instanceof Error ? error.message : String(error));
|
|
12688
|
+
}
|
|
12689
|
+
const [contexts, workspaces] = await Promise.all([loadProjectContexts(platformUrl, options.contextTargets), listWorkspacesWithClient(client)]);
|
|
12690
|
+
const interactive = canPrompt();
|
|
12691
|
+
const contextWorkspaceIds = new Set(contexts.map(({ workspaceId }) => workspaceId));
|
|
12692
|
+
const linkedWorkspace = contextWorkspaceIds.size === 1 ? workspaces.find(({ id }) => contextWorkspaceIds.has(id)) : void 0;
|
|
12693
|
+
if (linkedWorkspace && (!options.createWorkspace || workspaceMatchesRequestedIdentity(linkedWorkspace, options))) {
|
|
12694
|
+
const contextTargetCount = projectContextTargets(options.contextTargets)?.length ?? 1;
|
|
12695
|
+
return contexts.length === contextTargetCount ? useRememberedWorkspace(client, linkedWorkspace) : useWorkspace(client, platformUrl, linkedWorkspace, options);
|
|
12696
|
+
}
|
|
12697
|
+
const onlyWorkspace = workspaces.length === 1 ? workspaces[0] : void 0;
|
|
12698
|
+
const canReuseOnlyWorkspace = onlyWorkspace !== void 0 && requestedCreateOptions !== void 0 && workspaceMatchesRequestedIdentity(onlyWorkspace, options);
|
|
12699
|
+
let requestedRegions;
|
|
12700
|
+
if (requestedCreateOptions && !canReuseOnlyWorkspace) {
|
|
12701
|
+
requestedRegions = await client.listAvailableWorkspaceRegions({});
|
|
12702
|
+
if (!requestedRegions.regions.includes(requestedCreateOptions.region)) throw invalidCreateOptionsError(options, requestedCreateOptions.name, requestedCreateOptions.region, `Region must be one of: ${requestedRegions.regions.join(", ")}.`);
|
|
12703
|
+
}
|
|
12704
|
+
if (workspaces.length === 0 && (interactive || options.createWorkspace || contexts.length === 0)) {
|
|
12705
|
+
const regions = requestedRegions?.regions ?? (await client.listAvailableWorkspaceRegions({})).regions;
|
|
12706
|
+
if (options.dryRun || interactive || options.createWorkspace) return createWorkspaceForDeploy(client, platformUrl, options, regions, requestedCreateOptions);
|
|
12707
|
+
throw createCLIError({
|
|
12708
|
+
code: "WORKSPACE_NOT_FOUND",
|
|
12709
|
+
message: "No workspaces are available for this account.",
|
|
12710
|
+
suggestion: "Create one during deploy by providing the workspace name and one of the available regions.",
|
|
12711
|
+
next: executableAction(createDeployArgs(options, "<name>", "<region>")),
|
|
12712
|
+
context: { availableRegions: regions }
|
|
12713
|
+
});
|
|
12714
|
+
}
|
|
12715
|
+
if (contextWorkspaceIds.size > 1) {
|
|
12716
|
+
const canReplaceStaleContexts = options.createWorkspace === true && options.workspaceName !== void 0 && options.workspaceRegion !== void 0 && workspaces.length === 0;
|
|
12717
|
+
if (!interactive && !canReplaceStaleContexts) throw createCLIError({
|
|
12718
|
+
code: "WORKSPACE_CONTEXT_CONFLICT",
|
|
12719
|
+
message: "The deployed configuration files are linked to different workspaces.",
|
|
12720
|
+
suggestion: "Choose one workspace explicitly for the combined deployment.",
|
|
12721
|
+
next: executableAction(selectDeployArgs(options)),
|
|
12722
|
+
context: { savedWorkspaceIds: [...contextWorkspaceIds] }
|
|
12723
|
+
});
|
|
12724
|
+
if (workspaces.length > 0 && !options.createWorkspace) return chooseWorkspace(client, platformUrl, workspaces, options);
|
|
12725
|
+
}
|
|
12726
|
+
if (!linkedWorkspace && contexts.length > 0) {
|
|
12727
|
+
const explicitlyEnsuringSingleTarget = options.createWorkspace === true && options.workspaceName !== void 0 && options.workspaceRegion !== void 0 && workspaces.length <= 1;
|
|
12728
|
+
if (!interactive && !explicitlyEnsuringSingleTarget) throw createCLIError({
|
|
12729
|
+
code: "WORKSPACE_CONTEXT_STALE",
|
|
12730
|
+
message: "The saved project workspace is no longer available.",
|
|
12731
|
+
suggestion: "Choose an available workspace explicitly before deploying.",
|
|
12732
|
+
next: executableAction(selectDeployArgs(options)),
|
|
12733
|
+
context: {
|
|
12734
|
+
savedWorkspaceIds: [...contextWorkspaceIds],
|
|
12735
|
+
workspaces: workspaces.map(workspaceIdentity)
|
|
12736
|
+
}
|
|
12737
|
+
});
|
|
12738
|
+
if (workspaces.length > 0 && interactive && !options.createWorkspace) return chooseWorkspace(client, platformUrl, workspaces, options);
|
|
12739
|
+
}
|
|
12740
|
+
if (workspaces.length === 1) {
|
|
12741
|
+
const [workspace] = workspaces;
|
|
12742
|
+
if (!workspace) throw new Error("Workspace discovery returned an invalid result");
|
|
12743
|
+
if (options.createWorkspace && !workspaceMatchesRequestedIdentity(workspace, options)) throw createCLIError({
|
|
12744
|
+
code: "WORKSPACE_CREATE_CONFLICT",
|
|
12745
|
+
message: "The existing workspace does not match the requested workspace.",
|
|
12746
|
+
suggestion: "Use the existing workspace, or run the explicit workspace create command to create another one.",
|
|
12747
|
+
next: executableAction([
|
|
12748
|
+
"workspace",
|
|
12749
|
+
"create",
|
|
12750
|
+
...workspaceCommandArgs(options),
|
|
12751
|
+
...options.workspaceCommandJson ? ["--json"] : [],
|
|
12752
|
+
"--name",
|
|
12753
|
+
options.workspaceName ?? "<name>",
|
|
12754
|
+
"--region",
|
|
12755
|
+
options.workspaceRegion ?? "<region>",
|
|
12756
|
+
...options.organizationId ? ["--organization-id", options.organizationId] : [],
|
|
12757
|
+
...options.folderId ? ["--folder-id", options.folderId] : []
|
|
12758
|
+
]),
|
|
12759
|
+
context: { existingWorkspace: workspaceIdentity(workspace) }
|
|
12760
|
+
});
|
|
12761
|
+
if (interactive && !options.createWorkspace) return chooseWorkspace(client, platformUrl, workspaces, options);
|
|
12762
|
+
return useWorkspace(client, platformUrl, workspace, options);
|
|
12763
|
+
}
|
|
12764
|
+
if (workspaces.length > 1) {
|
|
12765
|
+
if (!interactive) throw createCLIError({
|
|
12766
|
+
code: "WORKSPACE_SELECTION_REQUIRED",
|
|
12767
|
+
message: "Multiple workspaces are available.",
|
|
12768
|
+
suggestion: "Choose one explicitly for non-interactive deployment.",
|
|
12769
|
+
next: executableAction(selectDeployArgs(options)),
|
|
12770
|
+
context: { workspaces: workspaces.map(workspaceIdentity) }
|
|
12771
|
+
});
|
|
12772
|
+
return chooseWorkspace(client, platformUrl, workspaces, options);
|
|
12773
|
+
}
|
|
12774
|
+
throw new Error("Workspace discovery returned an invalid result");
|
|
12775
|
+
}
|
|
12776
|
+
|
|
11332
12777
|
//#endregion
|
|
11333
12778
|
//#region src/cli/commands/deploy/deploy.ts
|
|
11334
12779
|
/**
|
|
@@ -11746,19 +13191,40 @@ function parseDeployConfigPaths(configPath) {
|
|
|
11746
13191
|
if (configPaths.some((entry) => entry.length === 0)) throw new Error("--config must contain one or more non-empty config paths.");
|
|
11747
13192
|
return configPaths;
|
|
11748
13193
|
}
|
|
13194
|
+
function recoveryEnvironmentArgs(options, cliContext) {
|
|
13195
|
+
return [
|
|
13196
|
+
...cliContext?.envFile ? ["--env-file", path.resolve(process.cwd(), cliContext.envFile)] : [],
|
|
13197
|
+
...cliContext?.envFileIfExists ? ["--env-file-if-exists", path.resolve(process.cwd(), cliContext.envFileIfExists)] : [],
|
|
13198
|
+
...options?.profile ? ["--profile", options.profile] : []
|
|
13199
|
+
];
|
|
13200
|
+
}
|
|
13201
|
+
function recoveryOutputArgs(cliContext) {
|
|
13202
|
+
return [...cliContext?.verbose ? ["--verbose"] : [], ...cliContext?.json || logger.jsonMode ? ["--json"] : []];
|
|
13203
|
+
}
|
|
13204
|
+
function retryDeployArgs(options, configPaths, cliContext) {
|
|
13205
|
+
return [
|
|
13206
|
+
"deploy",
|
|
13207
|
+
"--config",
|
|
13208
|
+
configPaths.join(","),
|
|
13209
|
+
...recoveryEnvironmentArgs(options, cliContext),
|
|
13210
|
+
...options?.dryRun ? ["--dry-run"] : [],
|
|
13211
|
+
...options?.yes ? ["--yes"] : [],
|
|
13212
|
+
...options?.noSchemaCheck ? ["--no-schema-check"] : [],
|
|
13213
|
+
...options?.noValidate ? ["--no-validate"] : [],
|
|
13214
|
+
...options?.noCache ? ["--no-cache"] : [],
|
|
13215
|
+
...options?.cleanCache ? ["--clean-cache"] : [],
|
|
13216
|
+
...recoveryOutputArgs(cliContext)
|
|
13217
|
+
];
|
|
13218
|
+
}
|
|
13219
|
+
function workspaceRecoveryArgs(options, cliContext) {
|
|
13220
|
+
return [...recoveryEnvironmentArgs(options, cliContext), ...cliContext?.verbose ? ["--verbose"] : []];
|
|
13221
|
+
}
|
|
11749
13222
|
async function buildDeploymentTarget(params) {
|
|
11750
|
-
const { configPath, dryRun, buildOnly, noCache, packageVersion, cacheDir } = params;
|
|
11751
|
-
const { config, plugins } =
|
|
11752
|
-
|
|
11753
|
-
|
|
11754
|
-
|
|
11755
|
-
if (fs$1.existsSync(resolvedPath)) await ensureConfigIdForDeploy({
|
|
11756
|
-
configPath: resolvedPath,
|
|
11757
|
-
dryRun,
|
|
11758
|
-
buildOnly
|
|
11759
|
-
});
|
|
11760
|
-
}
|
|
11761
|
-
return loadConfig(configPath);
|
|
13223
|
+
const { configPath, loadedConfig, dryRun, buildOnly, noCache, packageVersion, cacheDir } = params;
|
|
13224
|
+
const { config, plugins } = loadedConfig ?? await loadDeployConfig({
|
|
13225
|
+
configPath,
|
|
13226
|
+
dryRun,
|
|
13227
|
+
buildOnly
|
|
11762
13228
|
});
|
|
11763
13229
|
const configDir = path.dirname(config.path);
|
|
11764
13230
|
const lockfilePath = findUpSync("pnpm-lock.yaml", { cwd: configDir }) ?? findUpSync("package-lock.json", { cwd: configDir }) ?? findUpSync("yarn.lock", { cwd: configDir }) ?? findUpSync("bun.lock", { cwd: configDir });
|
|
@@ -11799,11 +13265,27 @@ async function buildDeploymentTarget(params) {
|
|
|
11799
13265
|
bundledScripts
|
|
11800
13266
|
};
|
|
11801
13267
|
}
|
|
13268
|
+
async function loadDeployConfig(params) {
|
|
13269
|
+
const { configPath, dryRun, buildOnly } = params;
|
|
13270
|
+
return withSpan("build.loadConfig", async () => {
|
|
13271
|
+
const foundPath = loadConfigPath(configPath);
|
|
13272
|
+
if (foundPath) {
|
|
13273
|
+
const resolvedPath = path.resolve(process.cwd(), foundPath);
|
|
13274
|
+
if (fs$1.existsSync(resolvedPath)) await ensureConfigIdForDeploy({
|
|
13275
|
+
configPath: resolvedPath,
|
|
13276
|
+
dryRun,
|
|
13277
|
+
buildOnly
|
|
13278
|
+
});
|
|
13279
|
+
}
|
|
13280
|
+
return loadConfig(configPath);
|
|
13281
|
+
});
|
|
13282
|
+
}
|
|
11802
13283
|
async function buildDeploymentTargets(params) {
|
|
11803
|
-
const { configPaths, buildTarget = buildDeploymentTarget, ...targetParams } = params;
|
|
11804
|
-
return Promise.all(configPaths.map((configPath) => buildTarget({
|
|
13284
|
+
const { configPaths, loadedConfigs, buildTarget = buildDeploymentTarget, ...targetParams } = params;
|
|
13285
|
+
return Promise.all(configPaths.map((configPath, index) => buildTarget({
|
|
11805
13286
|
...targetParams,
|
|
11806
|
-
configPath
|
|
13287
|
+
configPath,
|
|
13288
|
+
loadedConfig: loadedConfigs?.[index]
|
|
11807
13289
|
})));
|
|
11808
13290
|
}
|
|
11809
13291
|
function setBundledScript(target, name, code, kind) {
|
|
@@ -11972,50 +13454,25 @@ function deploymentPlanResults(deployment) {
|
|
|
11972
13454
|
const { application: _application, ...results } = deployment;
|
|
11973
13455
|
return results;
|
|
11974
13456
|
}
|
|
13457
|
+
/**
|
|
13458
|
+
* Every `PlanResults` entry except `app` carries ownership-tracking fields;
|
|
13459
|
+
* deriving the list from `results` itself (instead of naming each key) keeps
|
|
13460
|
+
* these collectors in sync with `PlannedDeployment` as resource types are added.
|
|
13461
|
+
* @param results - Plan results for a single deployment
|
|
13462
|
+
* @returns The ownership-tracking plan entries, `app` excluded
|
|
13463
|
+
*/
|
|
13464
|
+
function ownershipTrackedPlans(results) {
|
|
13465
|
+
const { app: _app, ...rest } = results;
|
|
13466
|
+
return Object.values(rest);
|
|
13467
|
+
}
|
|
11975
13468
|
function collectOwnerConflicts(results) {
|
|
11976
|
-
return
|
|
11977
|
-
...results.functionRegistry.conflicts,
|
|
11978
|
-
...results.tailorDB.conflicts,
|
|
11979
|
-
...results.staticWebsite.conflicts,
|
|
11980
|
-
...results.aiGateway.conflicts,
|
|
11981
|
-
...results.idp.conflicts,
|
|
11982
|
-
...results.auth.conflicts,
|
|
11983
|
-
...results.pipeline.conflicts,
|
|
11984
|
-
...results.executor.conflicts,
|
|
11985
|
-
...results.workflow.conflicts,
|
|
11986
|
-
...results.workflowExecutionPolicy.conflicts,
|
|
11987
|
-
...results.secretManager.conflicts
|
|
11988
|
-
];
|
|
13469
|
+
return ownershipTrackedPlans(results).flatMap((plan) => plan.conflicts);
|
|
11989
13470
|
}
|
|
11990
13471
|
function collectUnmanagedResources(results) {
|
|
11991
|
-
return
|
|
11992
|
-
...results.functionRegistry.unmanaged,
|
|
11993
|
-
...results.tailorDB.unmanaged,
|
|
11994
|
-
...results.staticWebsite.unmanaged,
|
|
11995
|
-
...results.aiGateway.unmanaged,
|
|
11996
|
-
...results.idp.unmanaged,
|
|
11997
|
-
...results.auth.unmanaged,
|
|
11998
|
-
...results.pipeline.unmanaged,
|
|
11999
|
-
...results.executor.unmanaged,
|
|
12000
|
-
...results.workflow.unmanaged,
|
|
12001
|
-
...results.workflowExecutionPolicy.unmanaged,
|
|
12002
|
-
...results.secretManager.unmanaged
|
|
12003
|
-
];
|
|
13472
|
+
return ownershipTrackedPlans(results).flatMap((plan) => plan.unmanaged);
|
|
12004
13473
|
}
|
|
12005
13474
|
function collectResourceOwners(results) {
|
|
12006
|
-
return
|
|
12007
|
-
...results.functionRegistry.resourceOwners,
|
|
12008
|
-
...results.tailorDB.resourceOwners,
|
|
12009
|
-
...results.staticWebsite.resourceOwners,
|
|
12010
|
-
...results.aiGateway.resourceOwners,
|
|
12011
|
-
...results.idp.resourceOwners,
|
|
12012
|
-
...results.auth.resourceOwners,
|
|
12013
|
-
...results.pipeline.resourceOwners,
|
|
12014
|
-
...results.executor.resourceOwners,
|
|
12015
|
-
...results.workflow.resourceOwners,
|
|
12016
|
-
...results.workflowExecutionPolicy.resourceOwners,
|
|
12017
|
-
...results.secretManager.resourceOwners
|
|
12018
|
-
]);
|
|
13475
|
+
return new Set(ownershipTrackedPlans(results).flatMap((plan) => [...plan.resourceOwners]));
|
|
12019
13476
|
}
|
|
12020
13477
|
function collectImportantResourceDeletions(results) {
|
|
12021
13478
|
const importantDeletions = [];
|
|
@@ -12407,16 +13864,41 @@ async function validateDeploymentPlans(deployments) {
|
|
|
12407
13864
|
}
|
|
12408
13865
|
/**
|
|
12409
13866
|
* Deploy the configured application to the Tailor platform.
|
|
12410
|
-
* @param options -
|
|
13867
|
+
* @param options - Deploy execution options
|
|
13868
|
+
* @param cliContext - Global CLI arguments to preserve in recovery actions
|
|
12411
13869
|
* @returns Promise that resolves to `{ bundledScripts }` when `buildOnly` is true, otherwise void
|
|
12412
13870
|
*/
|
|
12413
|
-
async function
|
|
13871
|
+
async function deployInternal(options, cliContext) {
|
|
12414
13872
|
return withSpan("deploy", async (rootSpan) => {
|
|
12415
13873
|
rootSpan.setAttribute("deploy.dry_run", options?.dryRun ?? false);
|
|
12416
13874
|
const configPaths = parseDeployConfigPaths(options?.configPath);
|
|
12417
|
-
const
|
|
12418
|
-
|
|
12419
|
-
|
|
13875
|
+
const dryRun = options?.dryRun ?? false;
|
|
13876
|
+
const buildOnly = options?.buildOnly ?? parseBoolean(process.env.TAILOR_PLATFORM_SDK_BUILD_ONLY) === true;
|
|
13877
|
+
const preflightConfigs = buildOnly ? [] : await withSpan("config.preflight", () => Promise.all(configPaths.map((configPath) => loadDeployConfig({
|
|
13878
|
+
configPath,
|
|
13879
|
+
dryRun,
|
|
13880
|
+
buildOnly
|
|
13881
|
+
}))));
|
|
13882
|
+
const resolvedConfigPaths = preflightConfigs.map(({ config }) => config.path);
|
|
13883
|
+
const workspaceContextTargets = preflightConfigs.map(({ config }) => ({
|
|
13884
|
+
configPath: config.path,
|
|
13885
|
+
applicationId: config.id ?? `name:${config.name}`
|
|
13886
|
+
}));
|
|
13887
|
+
const workspace = buildOnly ? void 0 : await resolveDeployWorkspace({
|
|
13888
|
+
workspaceId: options?.workspaceId,
|
|
13889
|
+
profile: options?.profile,
|
|
13890
|
+
createWorkspace: options?.createWorkspace,
|
|
13891
|
+
workspaceName: options?.workspaceName,
|
|
13892
|
+
workspaceRegion: options?.workspaceRegion,
|
|
13893
|
+
organizationId: options?.organizationId,
|
|
13894
|
+
folderId: options?.folderId,
|
|
13895
|
+
dryRun,
|
|
13896
|
+
contextTargets: workspaceContextTargets,
|
|
13897
|
+
deployArgs: retryDeployArgs(options, resolvedConfigPaths, cliContext),
|
|
13898
|
+
workspaceCommandArgs: workspaceRecoveryArgs(options, cliContext),
|
|
13899
|
+
workspaceCommandJson: cliContext?.json || logger.jsonMode
|
|
13900
|
+
});
|
|
13901
|
+
const targets = await withSpan("build", async () => {
|
|
12420
13902
|
const noCache = options?.noCache ?? false;
|
|
12421
13903
|
const packageJson = await readPackageJson();
|
|
12422
13904
|
const cacheDir = path.resolve(getDistDir(), "cache");
|
|
@@ -12427,25 +13909,20 @@ async function deploy(options) {
|
|
|
12427
13909
|
});
|
|
12428
13910
|
logger.info("Bundle cache cleaned");
|
|
12429
13911
|
}
|
|
12430
|
-
return {
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
buildOnly
|
|
12440
|
-
};
|
|
13912
|
+
return await buildDeploymentTargets({
|
|
13913
|
+
configPaths,
|
|
13914
|
+
loadedConfigs: preflightConfigs,
|
|
13915
|
+
dryRun,
|
|
13916
|
+
buildOnly,
|
|
13917
|
+
noCache,
|
|
13918
|
+
packageVersion: packageJson.version ?? "unknown",
|
|
13919
|
+
cacheDir
|
|
13920
|
+
});
|
|
12441
13921
|
});
|
|
12442
13922
|
if (buildOnly) return { bundledScripts: mergeBundledScripts(targets) };
|
|
12443
13923
|
assertUniqueGlobalResourceNames(targets);
|
|
12444
|
-
|
|
12445
|
-
const workspaceId =
|
|
12446
|
-
workspaceId: options?.workspaceId,
|
|
12447
|
-
profile: options?.profile
|
|
12448
|
-
});
|
|
13924
|
+
if (!workspace) throw new Error("Workspace was not resolved");
|
|
13925
|
+
const { client, workspaceId } = workspace;
|
|
12449
13926
|
rootSpan.setAttribute("app.name", targets.map((target) => target.application.name).join(","));
|
|
12450
13927
|
rootSpan.setAttribute("workspace.id", workspaceId);
|
|
12451
13928
|
const deployments = await planDeploymentTargets({
|
|
@@ -12455,7 +13932,6 @@ async function deploy(options) {
|
|
|
12455
13932
|
workspaceId,
|
|
12456
13933
|
noSchemaCheck: options?.noSchemaCheck
|
|
12457
13934
|
});
|
|
12458
|
-
const dryRun = options?.dryRun ?? false;
|
|
12459
13935
|
const yes = options?.yes ?? false;
|
|
12460
13936
|
dropCrossDeploymentManagedDeletes(deployments);
|
|
12461
13937
|
await withSpan("confirm", async () => {
|
|
@@ -12479,6 +13955,23 @@ async function deploy(options) {
|
|
|
12479
13955
|
else logger.success("Successfully applied changes.");
|
|
12480
13956
|
});
|
|
12481
13957
|
}
|
|
13958
|
+
/**
|
|
13959
|
+
* Deploy using the programmatic CLI API.
|
|
13960
|
+
* @param options - Deploy execution options
|
|
13961
|
+
* @returns Deploy result
|
|
13962
|
+
*/
|
|
13963
|
+
function deploy(options) {
|
|
13964
|
+
return deployInternal(options);
|
|
13965
|
+
}
|
|
13966
|
+
/**
|
|
13967
|
+
* Deploy from the command adapter while preserving global CLI arguments in recovery actions.
|
|
13968
|
+
* @param options - Deploy execution options
|
|
13969
|
+
* @param cliContext - Global CLI arguments already applied by the command runner
|
|
13970
|
+
* @returns Deploy result
|
|
13971
|
+
*/
|
|
13972
|
+
function deployFromCLI(options, cliContext) {
|
|
13973
|
+
return deployInternal(options, cliContext);
|
|
13974
|
+
}
|
|
12482
13975
|
|
|
12483
13976
|
//#endregion
|
|
12484
13977
|
//#region src/cli/commands/executor/status.ts
|
|
@@ -12868,52 +14361,6 @@ const getCommand$6 = defineAppCommand({
|
|
|
12868
14361
|
}
|
|
12869
14362
|
});
|
|
12870
14363
|
|
|
12871
|
-
//#endregion
|
|
12872
|
-
//#region src/cli/shared/format.ts
|
|
12873
|
-
/**
|
|
12874
|
-
* Format a protobuf Timestamp to ISO string.
|
|
12875
|
-
* @param timestamp - Protobuf timestamp
|
|
12876
|
-
* @returns Date object or null if invalid
|
|
12877
|
-
*/
|
|
12878
|
-
function formatTimestamp(timestamp) {
|
|
12879
|
-
if (!timestamp) return null;
|
|
12880
|
-
const date = timestampDate(timestamp);
|
|
12881
|
-
if (Number.isNaN(date.getTime())) return null;
|
|
12882
|
-
return date;
|
|
12883
|
-
}
|
|
12884
|
-
/**
|
|
12885
|
-
* Formats a table with consistent single-line border style.
|
|
12886
|
-
* Use this instead of importing `table` directly.
|
|
12887
|
-
* @param data - Table data
|
|
12888
|
-
* @param config - Table configuration
|
|
12889
|
-
* @returns Formatted table string
|
|
12890
|
-
*/
|
|
12891
|
-
function formatTable(data, config) {
|
|
12892
|
-
return table(data, {
|
|
12893
|
-
...config,
|
|
12894
|
-
border: getBorderCharacters("norc")
|
|
12895
|
-
});
|
|
12896
|
-
}
|
|
12897
|
-
/**
|
|
12898
|
-
* Formats a key-value table with single-line border style.
|
|
12899
|
-
* @param data - Key-value pairs
|
|
12900
|
-
* @returns Formatted key-value table string
|
|
12901
|
-
*/
|
|
12902
|
-
function formatKeyValueTable(data) {
|
|
12903
|
-
return formatTable(data, { singleLine: true });
|
|
12904
|
-
}
|
|
12905
|
-
/**
|
|
12906
|
-
* Format a Date or ISO timestamp string as a human-readable relative time.
|
|
12907
|
-
* @param value - Date object, ISO date string, or null
|
|
12908
|
-
* @returns Relative time (e.g., "5 minutes ago") or "N/A" for null/invalid
|
|
12909
|
-
*/
|
|
12910
|
-
function humanizeRelativeTime(value) {
|
|
12911
|
-
if (value === null) return "N/A";
|
|
12912
|
-
const date = value instanceof Date ? value : new Date(value);
|
|
12913
|
-
if (Number.isNaN(date.getTime())) return typeof value === "string" ? value : "N/A";
|
|
12914
|
-
return formatDistanceToNowStrict(date, { addSuffix: true });
|
|
12915
|
-
}
|
|
12916
|
-
|
|
12917
14364
|
//#endregion
|
|
12918
14365
|
//#region src/cli/shared/function-execution.ts
|
|
12919
14366
|
/**
|
|
@@ -14312,7 +15759,7 @@ async function listExecutors(options) {
|
|
|
14312
15759
|
return [executors, nextPageToken];
|
|
14313
15760
|
}, { limit: options?.limit })).map((e) => toExecutorListInfo(e));
|
|
14314
15761
|
}
|
|
14315
|
-
const listCommand$
|
|
15762
|
+
const listCommand$8 = defineAppCommand({
|
|
14316
15763
|
name: "list",
|
|
14317
15764
|
description: "List all executors",
|
|
14318
15765
|
args: z.object({
|
|
@@ -14737,7 +16184,7 @@ async function listFunctionRegistries(options) {
|
|
|
14737
16184
|
}
|
|
14738
16185
|
}, { limit })).map(functionRegistryInfo);
|
|
14739
16186
|
}
|
|
14740
|
-
const listCommand$
|
|
16187
|
+
const listCommand$7 = defineAppCommand({
|
|
14741
16188
|
name: "list",
|
|
14742
16189
|
description: "List function registries in a workspace",
|
|
14743
16190
|
args: z.object({
|
|
@@ -15682,7 +17129,7 @@ async function listMachineUsers(options) {
|
|
|
15682
17129
|
return [machineUsers, nextPageToken];
|
|
15683
17130
|
}, { limit: options?.limit })).map(machineUserInfo);
|
|
15684
17131
|
}
|
|
15685
|
-
const listCommand$
|
|
17132
|
+
const listCommand$6 = defineAppCommand({
|
|
15686
17133
|
name: "list",
|
|
15687
17134
|
description: "List all machine users in the application.",
|
|
15688
17135
|
args: z.object({
|
|
@@ -15898,7 +17345,7 @@ async function listOAuth2Clients(options) {
|
|
|
15898
17345
|
return [oauth2Clients, nextPageToken];
|
|
15899
17346
|
}, { limit: options?.limit })).map(toOAuth2ClientInfo);
|
|
15900
17347
|
}
|
|
15901
|
-
const listCommand$
|
|
17348
|
+
const listCommand$5 = defineAppCommand({
|
|
15902
17349
|
name: "list",
|
|
15903
17350
|
description: "List all OAuth2 clients in the application.",
|
|
15904
17351
|
args: z.object({
|
|
@@ -15968,7 +17415,7 @@ async function createFolder(options) {
|
|
|
15968
17415
|
if (!response.folder) throw new Error("Failed to create folder.");
|
|
15969
17416
|
return folderInfo(response.folder);
|
|
15970
17417
|
}
|
|
15971
|
-
const createCommand
|
|
17418
|
+
const createCommand = defineAppCommand({
|
|
15972
17419
|
name: "create",
|
|
15973
17420
|
description: "Create a new folder in an organization.",
|
|
15974
17421
|
args: z.object({
|
|
@@ -16116,7 +17563,7 @@ async function listFolders(options) {
|
|
|
16116
17563
|
return [response.folders, response.nextPageToken];
|
|
16117
17564
|
}, { limit })).map(folderListInfo);
|
|
16118
17565
|
}
|
|
16119
|
-
const listCommand$
|
|
17566
|
+
const listCommand$4 = defineAppCommand({
|
|
16120
17567
|
name: "list",
|
|
16121
17568
|
description: "List folders in an organization.",
|
|
16122
17569
|
args: z.object({
|
|
@@ -16225,7 +17672,7 @@ async function listOrganizations(options) {
|
|
|
16225
17672
|
if (limit !== void 0) return results.slice(0, limit);
|
|
16226
17673
|
return results;
|
|
16227
17674
|
}
|
|
16228
|
-
const listCommand$
|
|
17675
|
+
const listCommand$3 = defineAppCommand({
|
|
16229
17676
|
name: "list",
|
|
16230
17677
|
description: "List organizations you belong to.",
|
|
16231
17678
|
args: z.object({ limit: arg(positiveIntArg.optional(), {
|
|
@@ -16430,62 +17877,67 @@ async function execRemove(client, workspaceId, application, config, confirm) {
|
|
|
16430
17877
|
forRemoval: true,
|
|
16431
17878
|
config
|
|
16432
17879
|
};
|
|
16433
|
-
const
|
|
16434
|
-
|
|
16435
|
-
|
|
16436
|
-
|
|
16437
|
-
|
|
16438
|
-
|
|
16439
|
-
|
|
16440
|
-
|
|
16441
|
-
|
|
16442
|
-
|
|
16443
|
-
|
|
17880
|
+
const plans = {
|
|
17881
|
+
tailorDB: await planTailorDB(ctx),
|
|
17882
|
+
staticWebsite: await planStaticWebsite(ctx),
|
|
17883
|
+
aiGateway: await planAIGateway(ctx),
|
|
17884
|
+
idp: await planIdP(ctx),
|
|
17885
|
+
auth: await planAuth(ctx),
|
|
17886
|
+
pipeline: await planPipeline(ctx),
|
|
17887
|
+
app: await planApplication(ctx),
|
|
17888
|
+
executor: await planExecutor(ctx),
|
|
17889
|
+
workflow: await planWorkflow(client, workspaceId, application.name, application.id, {}, {}),
|
|
17890
|
+
workflowExecutionPolicy: await planWorkflowJobFunctionExecutionPolicy(client, workspaceId, application.name, application.id, {}),
|
|
17891
|
+
functionRegistry: await planFunctionRegistry(client, workspaceId, application.name, application.id, []),
|
|
17892
|
+
secretManager: await planSecretManager(ctx)
|
|
17893
|
+
};
|
|
16444
17894
|
const removeLines = [
|
|
16445
|
-
...functionRegistry.changeSet.lines(),
|
|
16446
|
-
...staticWebsite.changeSet.lines(),
|
|
16447
|
-
...aiGateway.changeSet.lines(),
|
|
16448
|
-
...app.lines(),
|
|
16449
|
-
...tailorDB.changeSet.service.lines(),
|
|
16450
|
-
...tailorDB.changeSet.type.lines(),
|
|
16451
|
-
...tailorDB.changeSet.gqlPermission.lines(),
|
|
16452
|
-
...pipeline.changeSet.service.lines(),
|
|
16453
|
-
...pipeline.changeSet.resolver.lines(),
|
|
16454
|
-
...executor.changeSet.lines(),
|
|
16455
|
-
...workflow.changeSet.lines(),
|
|
16456
|
-
...
|
|
16457
|
-
...idp.changeSet.
|
|
16458
|
-
...
|
|
16459
|
-
...auth.changeSet.
|
|
16460
|
-
...auth.changeSet.
|
|
16461
|
-
...auth.changeSet.
|
|
16462
|
-
...auth.changeSet.
|
|
16463
|
-
...auth.changeSet.
|
|
16464
|
-
...auth.changeSet.
|
|
16465
|
-
...auth.changeSet.
|
|
16466
|
-
...auth.changeSet.
|
|
16467
|
-
...auth.changeSet.
|
|
16468
|
-
...
|
|
16469
|
-
...secretManager.
|
|
17895
|
+
...plans.functionRegistry.changeSet.lines(),
|
|
17896
|
+
...plans.staticWebsite.changeSet.lines(),
|
|
17897
|
+
...plans.aiGateway.changeSet.lines(),
|
|
17898
|
+
...plans.app.lines(),
|
|
17899
|
+
...plans.tailorDB.changeSet.service.lines(),
|
|
17900
|
+
...plans.tailorDB.changeSet.type.lines(),
|
|
17901
|
+
...plans.tailorDB.changeSet.gqlPermission.lines(),
|
|
17902
|
+
...plans.pipeline.changeSet.service.lines(),
|
|
17903
|
+
...plans.pipeline.changeSet.resolver.lines(),
|
|
17904
|
+
...plans.executor.changeSet.lines(),
|
|
17905
|
+
...plans.workflow.changeSet.lines(),
|
|
17906
|
+
...plans.workflowExecutionPolicy.changeSet.lines(),
|
|
17907
|
+
...plans.idp.changeSet.service.lines(),
|
|
17908
|
+
...plans.idp.changeSet.client.lines(),
|
|
17909
|
+
...plans.auth.changeSet.service.lines(),
|
|
17910
|
+
...plans.auth.changeSet.idpConfig.lines(),
|
|
17911
|
+
...plans.auth.changeSet.userProfileConfig.lines(),
|
|
17912
|
+
...plans.auth.changeSet.tenantConfig.lines(),
|
|
17913
|
+
...plans.auth.changeSet.machineUser.lines(),
|
|
17914
|
+
...plans.auth.changeSet.oauth2Client.lines(),
|
|
17915
|
+
...plans.auth.changeSet.authHook.lines(),
|
|
17916
|
+
...plans.auth.changeSet.scim.lines(),
|
|
17917
|
+
...plans.auth.changeSet.scimResource.lines(),
|
|
17918
|
+
...plans.auth.changeSet.connection.lines(),
|
|
17919
|
+
...plans.secretManager.vaultChangeSet.lines(),
|
|
17920
|
+
...plans.secretManager.secretChangeSet.lines()
|
|
16470
17921
|
];
|
|
16471
17922
|
if (removeLines.length > 0) logger.log(removeLines.join("\n"));
|
|
16472
|
-
if (tailorDB.changeSet.service.deletes.length === 0 && staticWebsite.changeSet.deletes.length === 0 && aiGateway.changeSet.deletes.length === 0 && idp.changeSet.service.deletes.length === 0 && auth.changeSet.service.deletes.length === 0 && pipeline.changeSet.service.deletes.length === 0 && app.deletes.length === 0 && executor.changeSet.deletes.length === 0 && workflow.changeSet.deletes.length === 0 && functionRegistry.changeSet.deletes.length === 0 && secretManager.vaultChangeSet.deletes.length === 0 && secretManager.secretChangeSet.deletes.length === 0) return;
|
|
17923
|
+
if (plans.tailorDB.changeSet.service.deletes.length === 0 && plans.staticWebsite.changeSet.deletes.length === 0 && plans.aiGateway.changeSet.deletes.length === 0 && plans.idp.changeSet.service.deletes.length === 0 && plans.auth.changeSet.service.deletes.length === 0 && plans.pipeline.changeSet.service.deletes.length === 0 && plans.app.deletes.length === 0 && plans.executor.changeSet.deletes.length === 0 && plans.workflow.changeSet.deletes.length === 0 && plans.workflowExecutionPolicy.changeSet.deletes.length === 0 && plans.functionRegistry.changeSet.deletes.length === 0 && plans.secretManager.vaultChangeSet.deletes.length === 0 && plans.secretManager.secretChangeSet.deletes.length === 0) return;
|
|
16473
17924
|
if (confirm) await confirm();
|
|
16474
|
-
await applyWorkflow(client, workflow, "delete");
|
|
16475
|
-
await
|
|
16476
|
-
await
|
|
16477
|
-
await
|
|
16478
|
-
await
|
|
16479
|
-
await
|
|
16480
|
-
await applyPipeline(client, pipeline, "delete-
|
|
16481
|
-
await
|
|
16482
|
-
await applyAuth(client, auth, "delete-
|
|
16483
|
-
await
|
|
16484
|
-
await applyIdP(client, idp, "delete-
|
|
16485
|
-
await
|
|
16486
|
-
await applyTailorDB(client, tailorDB, "delete-
|
|
16487
|
-
await
|
|
16488
|
-
await
|
|
17925
|
+
await applyWorkflow(client, plans.workflow, "delete");
|
|
17926
|
+
await applyWorkflowJobFunctionExecutionPolicy(client, plans.workflowExecutionPolicy, "delete");
|
|
17927
|
+
await applyExecutor(client, plans.executor, "delete");
|
|
17928
|
+
await applyStaticWebsite(client, plans.staticWebsite, "delete");
|
|
17929
|
+
await applyAIGateway(client, plans.aiGateway, "delete");
|
|
17930
|
+
await applyApplication(client, plans.app, "delete");
|
|
17931
|
+
await applyPipeline(client, plans.pipeline, "delete-resources");
|
|
17932
|
+
await applyPipeline(client, plans.pipeline, "delete-services");
|
|
17933
|
+
await applyAuth(client, plans.auth, "delete-resources");
|
|
17934
|
+
await applyAuth(client, plans.auth, "delete-services");
|
|
17935
|
+
await applyIdP(client, plans.idp, "delete-resources");
|
|
17936
|
+
await applyIdP(client, plans.idp, "delete-services");
|
|
17937
|
+
await applyTailorDB(client, plans.tailorDB, "delete-resources");
|
|
17938
|
+
await applyTailorDB(client, plans.tailorDB, "delete-services");
|
|
17939
|
+
await applyFunctionRegistry(client, workspaceId, plans.functionRegistry, "delete");
|
|
17940
|
+
await applySecretManager(client, plans.secretManager, "delete");
|
|
16489
17941
|
}
|
|
16490
17942
|
/**
|
|
16491
17943
|
* Remove all resources managed by the current application.
|
|
@@ -16510,141 +17962,63 @@ const removeCommand$1 = defineAppCommand({
|
|
|
16510
17962
|
profile: args.profile,
|
|
16511
17963
|
configPath: args.config
|
|
16512
17964
|
});
|
|
16513
|
-
logger.info(`Planning removal of resources managed by "${application.name}"...`);
|
|
16514
|
-
logger.newline();
|
|
16515
|
-
await execRemove(client, workspaceId, application, config, async () => {
|
|
16516
|
-
if (!args.yes) {
|
|
16517
|
-
if (!await prompt.confirm({
|
|
16518
|
-
message: "Are you sure you want to remove all resources?",
|
|
16519
|
-
default: false
|
|
16520
|
-
})) throw new Error(multiline`
|
|
16521
|
-
Remove cancelled. No resources were deleted.
|
|
16522
|
-
To override, run again and confirm, or use --yes flag.
|
|
16523
|
-
`);
|
|
16524
|
-
} else logger.success("Removing all resources (--yes flag specified)...");
|
|
16525
|
-
});
|
|
16526
|
-
logger.success(`Successfully removed all resources managed by "${application.name}".`);
|
|
16527
|
-
}
|
|
16528
|
-
});
|
|
16529
|
-
|
|
16530
|
-
//#endregion
|
|
16531
|
-
//#region src/cli/shared/beta.ts
|
|
16532
|
-
/**
|
|
16533
|
-
* Warn that a feature is in beta.
|
|
16534
|
-
* @param {string} featureName - Name of the beta feature (e.g., "tailordb erd", "tailordb migration")
|
|
16535
|
-
*/
|
|
16536
|
-
function logBetaWarning(featureName) {
|
|
16537
|
-
logger.warn(`The '${featureName}' command is a beta feature and may introduce breaking changes in future releases.`);
|
|
16538
|
-
logger.newline();
|
|
16539
|
-
}
|
|
16540
|
-
|
|
16541
|
-
//#endregion
|
|
16542
|
-
//#region src/cli/shared/config.ts
|
|
16543
|
-
/**
|
|
16544
|
-
* Extracts every namespace key declared under `config.db`, including those
|
|
16545
|
-
* declared with `{ external: true }`.
|
|
16546
|
-
* @param config - Loaded application configuration.
|
|
16547
|
-
* @returns Namespace names in insertion order.
|
|
16548
|
-
*/
|
|
16549
|
-
function extractAllNamespaces(config) {
|
|
16550
|
-
const namespaces = /* @__PURE__ */ new Set();
|
|
16551
|
-
if (config.db) for (const namespaceName of Object.keys(config.db)) namespaces.add(namespaceName);
|
|
16552
|
-
return Array.from(namespaces);
|
|
16553
|
-
}
|
|
16554
|
-
/**
|
|
16555
|
-
* Extracts namespace keys under `config.db` that this app owns
|
|
16556
|
-
* (i.e. not declared with `{ external: true }`). Use this for destructive
|
|
16557
|
-
* operations like `tailordb truncate --all` to avoid touching namespaces
|
|
16558
|
-
* owned by other apps.
|
|
16559
|
-
* @param config - Loaded application configuration.
|
|
16560
|
-
* @returns Owned namespace names in insertion order.
|
|
16561
|
-
*/
|
|
16562
|
-
function extractOwnedNamespaces(config) {
|
|
16563
|
-
const namespaces = /* @__PURE__ */ new Set();
|
|
16564
|
-
if (config.db) for (const [namespaceName, nsConfig] of Object.entries(config.db)) {
|
|
16565
|
-
if ("external" in nsConfig) continue;
|
|
16566
|
-
namespaces.add(namespaceName);
|
|
16567
|
-
}
|
|
16568
|
-
return Array.from(namespaces);
|
|
16569
|
-
}
|
|
16570
|
-
|
|
16571
|
-
//#endregion
|
|
16572
|
-
//#region src/cli/commands/workspace/transform.ts
|
|
16573
|
-
const workspaceInfo = (workspace, folderName) => {
|
|
16574
|
-
const info = {
|
|
16575
|
-
id: workspace.id,
|
|
16576
|
-
name: workspace.name,
|
|
16577
|
-
region: workspace.region,
|
|
16578
|
-
createdAt: formatTimestamp(workspace.createTime),
|
|
16579
|
-
updatedAt: formatTimestamp(workspace.updateTime)
|
|
16580
|
-
};
|
|
16581
|
-
return folderName ? {
|
|
16582
|
-
...info,
|
|
16583
|
-
folderName
|
|
16584
|
-
} : info;
|
|
16585
|
-
};
|
|
16586
|
-
const workspaceDetails = (workspace, folderName) => {
|
|
16587
|
-
return {
|
|
16588
|
-
...workspaceInfo(workspace, folderName),
|
|
16589
|
-
deleteProtection: workspace.deleteProtection,
|
|
16590
|
-
organizationId: workspace.organizationId,
|
|
16591
|
-
folderId: workspace.folderId
|
|
16592
|
-
};
|
|
16593
|
-
};
|
|
16594
|
-
async function resolveWorkspaceFolderName(client, workspace) {
|
|
16595
|
-
if (!workspace.organizationId || !workspace.folderId) return void 0;
|
|
16596
|
-
try {
|
|
16597
|
-
return (await client.getOrganizationFolder({
|
|
16598
|
-
organizationId: workspace.organizationId,
|
|
16599
|
-
folderId: workspace.folderId
|
|
16600
|
-
})).folder?.name || void 0;
|
|
16601
|
-
} catch (error) {
|
|
16602
|
-
if (error instanceof ConnectError && (error.code === Code.NotFound || error.code === Code.PermissionDenied)) return;
|
|
16603
|
-
logger.warn(`Failed to resolve workspace folder name: ${error}`);
|
|
16604
|
-
return;
|
|
16605
|
-
}
|
|
16606
|
-
}
|
|
16607
|
-
function createWorkspaceFolderNameResolver(client) {
|
|
16608
|
-
const cache = /* @__PURE__ */ new Map();
|
|
16609
|
-
return (workspace) => {
|
|
16610
|
-
if (!workspace.organizationId || !workspace.folderId) return Promise.resolve(void 0);
|
|
16611
|
-
const cacheKey = `${workspace.organizationId}/${workspace.folderId}`;
|
|
16612
|
-
const cached = cache.get(cacheKey);
|
|
16613
|
-
if (cached) return cached;
|
|
16614
|
-
const promise = resolveWorkspaceFolderName(client, workspace);
|
|
16615
|
-
cache.set(cacheKey, promise);
|
|
16616
|
-
return promise;
|
|
16617
|
-
};
|
|
16618
|
-
}
|
|
16619
|
-
async function workspaceInfoWithFolderName(client, workspace) {
|
|
16620
|
-
const folderName = await resolveWorkspaceFolderName(client, workspace);
|
|
16621
|
-
return workspaceInfo(workspace, folderName);
|
|
16622
|
-
}
|
|
16623
|
-
async function workspaceDetailsWithFolderName(client, workspace) {
|
|
16624
|
-
const folderName = await resolveWorkspaceFolderName(client, workspace);
|
|
16625
|
-
return workspaceDetails(workspace, folderName);
|
|
16626
|
-
}
|
|
16627
|
-
async function workspaceInfosWithFolderNames(client, workspaces) {
|
|
16628
|
-
const resolveFolderName = createWorkspaceFolderNameResolver(client);
|
|
16629
|
-
const limit = pLimit(5);
|
|
16630
|
-
return Promise.all(workspaces.map((workspace) => limit(async () => workspaceInfo(workspace, await resolveFolderName(workspace)))));
|
|
17965
|
+
logger.info(`Planning removal of resources managed by "${application.name}"...`);
|
|
17966
|
+
logger.newline();
|
|
17967
|
+
await execRemove(client, workspaceId, application, config, async () => {
|
|
17968
|
+
if (!args.yes) {
|
|
17969
|
+
if (!await prompt.confirm({
|
|
17970
|
+
message: "Are you sure you want to remove all resources?",
|
|
17971
|
+
default: false
|
|
17972
|
+
})) throw new Error(multiline`
|
|
17973
|
+
Remove cancelled. No resources were deleted.
|
|
17974
|
+
To override, run again and confirm, or use --yes flag.
|
|
17975
|
+
`);
|
|
17976
|
+
} else logger.success("Removing all resources (--yes flag specified)...");
|
|
17977
|
+
});
|
|
17978
|
+
logger.success(`Successfully removed all resources managed by "${application.name}".`);
|
|
17979
|
+
}
|
|
17980
|
+
});
|
|
17981
|
+
|
|
17982
|
+
//#endregion
|
|
17983
|
+
//#region src/cli/shared/beta.ts
|
|
17984
|
+
/**
|
|
17985
|
+
* Warn that a feature is in beta.
|
|
17986
|
+
* @param {string} featureName - Name of the beta feature (e.g., "tailordb erd", "tailordb migration")
|
|
17987
|
+
*/
|
|
17988
|
+
function logBetaWarning(featureName) {
|
|
17989
|
+
logger.warn(`The '${featureName}' command is a beta feature and may introduce breaking changes in future releases.`);
|
|
17990
|
+
logger.newline();
|
|
16631
17991
|
}
|
|
16632
|
-
|
|
16633
|
-
|
|
17992
|
+
|
|
17993
|
+
//#endregion
|
|
17994
|
+
//#region src/cli/shared/config.ts
|
|
17995
|
+
/**
|
|
17996
|
+
* Extracts every namespace key declared under `config.db`, including those
|
|
17997
|
+
* declared with `{ external: true }`.
|
|
17998
|
+
* @param config - Loaded application configuration.
|
|
17999
|
+
* @returns Namespace names in insertion order.
|
|
18000
|
+
*/
|
|
18001
|
+
function extractAllNamespaces(config) {
|
|
18002
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
18003
|
+
if (config.db) for (const namespaceName of Object.keys(config.db)) namespaces.add(namespaceName);
|
|
18004
|
+
return Array.from(namespaces);
|
|
16634
18005
|
}
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
18006
|
+
/**
|
|
18007
|
+
* Extracts namespace keys under `config.db` that this app owns
|
|
18008
|
+
* (i.e. not declared with `{ external: true }`). Use this for destructive
|
|
18009
|
+
* operations like `tailordb truncate --all` to avoid touching namespaces
|
|
18010
|
+
* owned by other apps.
|
|
18011
|
+
* @param config - Loaded application configuration.
|
|
18012
|
+
* @returns Owned namespace names in insertion order.
|
|
18013
|
+
*/
|
|
18014
|
+
function extractOwnedNamespaces(config) {
|
|
18015
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
18016
|
+
if (config.db) for (const [namespaceName, nsConfig] of Object.entries(config.db)) {
|
|
18017
|
+
if ("external" in nsConfig) continue;
|
|
18018
|
+
namespaces.add(namespaceName);
|
|
18019
|
+
}
|
|
18020
|
+
return Array.from(namespaces);
|
|
16646
18021
|
}
|
|
16647
|
-
const workspaceNameTransformer = createWorkspaceNameTransformer("name", "folderName");
|
|
16648
18022
|
|
|
16649
18023
|
//#endregion
|
|
16650
18024
|
//#region src/cli/commands/show.ts
|
|
@@ -16661,10 +18035,27 @@ function applicationInfo(app) {
|
|
|
16661
18035
|
updatedAt: app.updateTime ? timestampDate(app.updateTime) : null
|
|
16662
18036
|
};
|
|
16663
18037
|
}
|
|
18038
|
+
async function fetchAIGateways(client, workspaceId, names) {
|
|
18039
|
+
return (await Promise.all(names.map(async (name) => {
|
|
18040
|
+
try {
|
|
18041
|
+
const { aigateway } = await client.getAIGateway({
|
|
18042
|
+
workspaceId,
|
|
18043
|
+
aigatewayName: name
|
|
18044
|
+
});
|
|
18045
|
+
return aigateway ? {
|
|
18046
|
+
name: aigateway.name,
|
|
18047
|
+
url: aigateway.url
|
|
18048
|
+
} : void 0;
|
|
18049
|
+
} catch (error) {
|
|
18050
|
+
if (error instanceof ConnectError && error.code === Code.NotFound) return;
|
|
18051
|
+
throw error;
|
|
18052
|
+
}
|
|
18053
|
+
}))).filter((gateway) => gateway !== void 0);
|
|
18054
|
+
}
|
|
16664
18055
|
/**
|
|
16665
18056
|
* Show applied application information for the current workspace.
|
|
16666
18057
|
* @param options - Show options
|
|
16667
|
-
* @returns
|
|
18058
|
+
* @returns Deployed application, workspace, and AI Gateway information
|
|
16668
18059
|
*/
|
|
16669
18060
|
async function show(options) {
|
|
16670
18061
|
const client = await initOperatorClient(await loadAccessToken({ profile: options?.profile }));
|
|
@@ -16673,10 +18064,15 @@ async function show(options) {
|
|
|
16673
18064
|
profile: options?.profile
|
|
16674
18065
|
});
|
|
16675
18066
|
const { config } = await loadConfig(options?.configPath);
|
|
16676
|
-
const
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
18067
|
+
const aiGatewayNames = config.aiGateways?.length ? [...new Set(config.aiGateways.map((gateway) => gateway.name))] : [];
|
|
18068
|
+
const [workspaceResp, resp, aiGateways] = await Promise.all([
|
|
18069
|
+
client.getWorkspace({ workspaceId }),
|
|
18070
|
+
client.getApplication({
|
|
18071
|
+
workspaceId,
|
|
18072
|
+
applicationName: config.name
|
|
18073
|
+
}),
|
|
18074
|
+
fetchAIGateways(client, workspaceId, aiGatewayNames)
|
|
18075
|
+
]);
|
|
16680
18076
|
const { name, ...appInfo } = applicationInfo(assertDefined(resp.application, `application "${config.name}" not found in workspace`));
|
|
16681
18077
|
const workspace = workspaceResp.workspace;
|
|
16682
18078
|
const workspaceFolderName = workspace ? await resolveWorkspaceFolderName(client, workspace) : "";
|
|
@@ -16686,7 +18082,8 @@ async function show(options) {
|
|
|
16686
18082
|
workspaceName: workspace?.name ?? "",
|
|
16687
18083
|
...workspaceFolderName ? { workspaceFolderName } : {},
|
|
16688
18084
|
workspaceRegion: workspace?.region ?? "",
|
|
16689
|
-
...appInfo
|
|
18085
|
+
...appInfo,
|
|
18086
|
+
aiGateways
|
|
16690
18087
|
};
|
|
16691
18088
|
}
|
|
16692
18089
|
const showWorkspaceNameTransformer = createWorkspaceNameTransformer("workspaceName", "workspaceFolderName");
|
|
@@ -17293,7 +18690,7 @@ async function generate(options) {
|
|
|
17293
18690
|
if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
|
|
17294
18691
|
let pluginManager;
|
|
17295
18692
|
if (plugins.length > 0) pluginManager = new PluginManager(plugins);
|
|
17296
|
-
const { defineApplication } = await import("./application-
|
|
18693
|
+
const { defineApplication } = await import("./application-Dp_-ioiP.mjs");
|
|
17297
18694
|
const application = defineApplication({
|
|
17298
18695
|
config,
|
|
17299
18696
|
pluginManager
|
|
@@ -17653,7 +19050,7 @@ async function listWorkflows(options) {
|
|
|
17653
19050
|
return [workflows, nextPageToken];
|
|
17654
19051
|
}, { limit: options?.limit })).map(toWorkflowListInfo);
|
|
17655
19052
|
}
|
|
17656
|
-
const listCommand$
|
|
19053
|
+
const listCommand$2 = defineAppCommand({
|
|
17657
19054
|
name: "list",
|
|
17658
19055
|
description: "List all workflows in the workspace.",
|
|
17659
19056
|
args: z.object({
|
|
@@ -17986,7 +19383,7 @@ async function listApps(options) {
|
|
|
17986
19383
|
return [applications, nextPageToken];
|
|
17987
19384
|
}, { limit })).map(appInfo);
|
|
17988
19385
|
}
|
|
17989
|
-
const listCommand$
|
|
19386
|
+
const listCommand$1 = defineAppCommand({
|
|
17990
19387
|
name: "list",
|
|
17991
19388
|
description: "List applications in a workspace",
|
|
17992
19389
|
args: z.object({
|
|
@@ -18009,152 +19406,6 @@ const listCommand$2 = defineAppCommand({
|
|
|
18009
19406
|
}
|
|
18010
19407
|
});
|
|
18011
19408
|
|
|
18012
|
-
//#endregion
|
|
18013
|
-
//#region src/cli/commands/workspace/create.ts
|
|
18014
|
-
/**
|
|
18015
|
-
* Schema for workspace creation options
|
|
18016
|
-
* - name: 3-63 chars, lowercase alphanumeric and hyphens, cannot start/end with hyphen
|
|
18017
|
-
* - organizationId, folderId: optional UUIDs
|
|
18018
|
-
*/
|
|
18019
|
-
const createWorkspaceOptionsSchema = z.object({
|
|
18020
|
-
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"),
|
|
18021
|
-
region: z.string(),
|
|
18022
|
-
deleteProtection: z.boolean().optional(),
|
|
18023
|
-
organizationId: z.uuid().optional(),
|
|
18024
|
-
folderId: z.uuid().optional()
|
|
18025
|
-
});
|
|
18026
|
-
const validateRegion = async (region, client) => {
|
|
18027
|
-
const availableRegions = await client.listAvailableWorkspaceRegions({});
|
|
18028
|
-
if (!availableRegions.regions.includes(region)) throw new Error(`Region must be one of: ${availableRegions.regions.join(", ")}.`);
|
|
18029
|
-
};
|
|
18030
|
-
function profilePlatformSettings(platformConfig) {
|
|
18031
|
-
const hasOAuth2ClientId = platformConfig?.oauth2ClientId || process.env.TAILOR_PLATFORM_OAUTH2_CLIENT_ID;
|
|
18032
|
-
const hasConsoleUrl = platformConfig?.consoleUrl || process.env.TAILOR_PLATFORM_CONSOLE_URL;
|
|
18033
|
-
return {
|
|
18034
|
-
...isDefaultPlatform(platformConfig) ? {} : { platform_url: getPlatformBaseUrl(platformConfig) },
|
|
18035
|
-
...hasOAuth2ClientId ? { oauth2_client_id: getOAuth2ClientId(platformConfig) } : {},
|
|
18036
|
-
...hasConsoleUrl ? { console_url: getConsoleBaseUrl(platformConfig) } : {}
|
|
18037
|
-
};
|
|
18038
|
-
}
|
|
18039
|
-
/**
|
|
18040
|
-
* Create a new workspace with the given options.
|
|
18041
|
-
* @param options - Workspace creation options
|
|
18042
|
-
* @returns Created workspace info
|
|
18043
|
-
*/
|
|
18044
|
-
async function createWorkspace(options) {
|
|
18045
|
-
const result = createWorkspaceOptionsSchema.safeParse(options);
|
|
18046
|
-
if (!result.success) throw new Error(assertDefined(result.error.issues[0], "Zod returned no issues").message);
|
|
18047
|
-
const validated = result.data;
|
|
18048
|
-
const client = await initOperatorClient(await loadAccessToken());
|
|
18049
|
-
await validateRegion(validated.region, client);
|
|
18050
|
-
return workspaceInfoWithFolderName(client, assertDefined((await client.createWorkspace({
|
|
18051
|
-
workspaceName: validated.name,
|
|
18052
|
-
workspaceRegion: validated.region,
|
|
18053
|
-
deleteProtection: validated.deleteProtection ?? false,
|
|
18054
|
-
organizationId: validated.organizationId,
|
|
18055
|
-
folderId: validated.folderId
|
|
18056
|
-
})).workspace, "createWorkspace response missing workspace"));
|
|
18057
|
-
}
|
|
18058
|
-
const createCommand = defineAppCommand({
|
|
18059
|
-
name: "create",
|
|
18060
|
-
description: "Create a new Tailor Platform workspace.",
|
|
18061
|
-
args: z.object({
|
|
18062
|
-
name: arg(z.string(), {
|
|
18063
|
-
alias: "n",
|
|
18064
|
-
description: "Workspace name"
|
|
18065
|
-
}),
|
|
18066
|
-
region: arg(z.string(), {
|
|
18067
|
-
alias: "r",
|
|
18068
|
-
description: "Workspace region (us-west, asia-northeast)"
|
|
18069
|
-
}),
|
|
18070
|
-
"delete-protection": arg(z.boolean().default(false), {
|
|
18071
|
-
alias: "d",
|
|
18072
|
-
description: "Enable delete protection"
|
|
18073
|
-
}),
|
|
18074
|
-
"organization-id": arg(z.string().optional(), {
|
|
18075
|
-
alias: "o",
|
|
18076
|
-
description: "Organization ID to workspace associate with",
|
|
18077
|
-
env: "TAILOR_PLATFORM_ORGANIZATION_ID"
|
|
18078
|
-
}),
|
|
18079
|
-
"folder-id": arg(z.string().optional(), {
|
|
18080
|
-
alias: "f",
|
|
18081
|
-
description: "Folder ID to workspace associate with",
|
|
18082
|
-
env: "TAILOR_PLATFORM_FOLDER_ID"
|
|
18083
|
-
}),
|
|
18084
|
-
"profile-name": arg(z.string().optional(), {
|
|
18085
|
-
alias: "p",
|
|
18086
|
-
description: "Profile name to create"
|
|
18087
|
-
}),
|
|
18088
|
-
"profile-user": arg(z.string().optional(), { description: "User email for the profile (defaults to current user)" }),
|
|
18089
|
-
permission: arg(z.enum(["write", "read"]).default("write"), { description: "Profile permission (requires --profile-name). 'read' blocks all write commands while the profile is active." })
|
|
18090
|
-
}).strict(),
|
|
18091
|
-
run: async (args) => {
|
|
18092
|
-
await assertWritable();
|
|
18093
|
-
const profileName = args["profile-name"];
|
|
18094
|
-
let profileSetup;
|
|
18095
|
-
if (profileName) {
|
|
18096
|
-
const config = await readPlatformConfig();
|
|
18097
|
-
if (config.profiles[profileName]) throw new Error(`Profile "${profileName}" already exists.`);
|
|
18098
|
-
const activeProfileName = process.env.TAILOR_PLATFORM_PROFILE;
|
|
18099
|
-
const activeProfileEntry = activeProfileName ? config.profiles[activeProfileName] : void 0;
|
|
18100
|
-
const platformConfig = activeProfileEntry ? platformConfigFromProfile(activeProfileEntry) : void 0;
|
|
18101
|
-
const profileUser = args["profile-user"] || activeProfileEntry?.user || config.current_user;
|
|
18102
|
-
if (!profileUser) throw new Error("Current user not found. Please login or specify --profile-user to create a profile.");
|
|
18103
|
-
if (!hasUserTokenEntry(config, profileUser, platformConfig)) throw new Error(`User "${profileUser}" not found.\nPlease verify your user name and login using 'tailor-sdk login' command.`);
|
|
18104
|
-
profileSetup = {
|
|
18105
|
-
name: profileName,
|
|
18106
|
-
user: profileUser,
|
|
18107
|
-
platformSettings: profilePlatformSettings(platformConfig)
|
|
18108
|
-
};
|
|
18109
|
-
}
|
|
18110
|
-
const workspace = await createWorkspace({
|
|
18111
|
-
name: args.name,
|
|
18112
|
-
region: args.region,
|
|
18113
|
-
deleteProtection: args["delete-protection"],
|
|
18114
|
-
organizationId: args["organization-id"],
|
|
18115
|
-
folderId: args["folder-id"]
|
|
18116
|
-
});
|
|
18117
|
-
let profileInfo;
|
|
18118
|
-
if (profileSetup) {
|
|
18119
|
-
const config = await readPlatformConfig();
|
|
18120
|
-
const platformSettings = profileSetup.platformSettings;
|
|
18121
|
-
config.profiles[profileSetup.name] = {
|
|
18122
|
-
user: profileSetup.user,
|
|
18123
|
-
workspace_id: workspace.id,
|
|
18124
|
-
...args.permission === "read" ? { readonly: true } : {},
|
|
18125
|
-
...platformSettings
|
|
18126
|
-
};
|
|
18127
|
-
writePlatformConfig(config);
|
|
18128
|
-
profileInfo = {
|
|
18129
|
-
name: profileSetup.name,
|
|
18130
|
-
user: profileSetup.user,
|
|
18131
|
-
workspaceId: workspace.id,
|
|
18132
|
-
permission: args.permission,
|
|
18133
|
-
...platformSettings.platform_url ? { platformUrl: platformSettings.platform_url } : {},
|
|
18134
|
-
...platformSettings.oauth2_client_id ? { oauth2ClientId: platformSettings.oauth2_client_id } : {},
|
|
18135
|
-
...platformSettings.console_url ? { consoleUrl: platformSettings.console_url } : {}
|
|
18136
|
-
};
|
|
18137
|
-
if (!args.json) logger.success(`Profile "${profileSetup.name}" created successfully.`);
|
|
18138
|
-
}
|
|
18139
|
-
if (!args.json) logger.success(`Workspace "${workspaceDisplayName(workspace)}" created successfully.`);
|
|
18140
|
-
if (args.json && profileInfo) {
|
|
18141
|
-
logger.out({
|
|
18142
|
-
...workspace,
|
|
18143
|
-
profile: profileInfo
|
|
18144
|
-
});
|
|
18145
|
-
return;
|
|
18146
|
-
}
|
|
18147
|
-
logger.out(workspace, { display: {
|
|
18148
|
-
name: workspaceNameTransformer,
|
|
18149
|
-
folderName: null
|
|
18150
|
-
} });
|
|
18151
|
-
if (profileInfo) {
|
|
18152
|
-
logger.out("Profile:");
|
|
18153
|
-
logger.out(profileInfo);
|
|
18154
|
-
}
|
|
18155
|
-
}
|
|
18156
|
-
});
|
|
18157
|
-
|
|
18158
19409
|
//#endregion
|
|
18159
19410
|
//#region src/cli/commands/workspace/delete.ts
|
|
18160
19411
|
const deleteWorkspaceOptionsSchema = z.object({ workspaceId: z.uuid({ message: "workspace-id must be a valid UUID" }) });
|
|
@@ -18268,42 +19519,6 @@ const getCommand = defineAppCommand({
|
|
|
18268
19519
|
}
|
|
18269
19520
|
});
|
|
18270
19521
|
|
|
18271
|
-
//#endregion
|
|
18272
|
-
//#region src/cli/commands/workspace/list.ts
|
|
18273
|
-
/**
|
|
18274
|
-
* List workspaces with an optional order and limit.
|
|
18275
|
-
* @param options - Workspace listing options
|
|
18276
|
-
* @returns List of workspaces
|
|
18277
|
-
*/
|
|
18278
|
-
async function listWorkspaces(options) {
|
|
18279
|
-
const client = await initOperatorClient(await loadAccessToken());
|
|
18280
|
-
const pageDirection = toPageDirection(options?.order);
|
|
18281
|
-
return workspaceInfosWithFolderNames(client, await fetchPaged(async (pageToken, pageSize) => {
|
|
18282
|
-
const { workspaces, nextPageToken } = await client.listWorkspaces({
|
|
18283
|
-
pageToken,
|
|
18284
|
-
pageSize,
|
|
18285
|
-
pageDirection
|
|
18286
|
-
});
|
|
18287
|
-
return [workspaces, nextPageToken];
|
|
18288
|
-
}, { limit: options?.limit }));
|
|
18289
|
-
}
|
|
18290
|
-
const listCommand$1 = defineAppCommand({
|
|
18291
|
-
name: "list",
|
|
18292
|
-
description: "List all Tailor Platform workspaces.",
|
|
18293
|
-
args: z.object({ ...paginationArgs() }).strict(),
|
|
18294
|
-
run: async (args) => {
|
|
18295
|
-
const workspaces = await listWorkspaces({
|
|
18296
|
-
order: args.order,
|
|
18297
|
-
limit: args.limit
|
|
18298
|
-
});
|
|
18299
|
-
logger.out(workspaces, { display: {
|
|
18300
|
-
name: workspaceNameTransformer,
|
|
18301
|
-
folderName: null,
|
|
18302
|
-
updatedAt: null
|
|
18303
|
-
} });
|
|
18304
|
-
}
|
|
18305
|
-
});
|
|
18306
|
-
|
|
18307
19522
|
//#endregion
|
|
18308
19523
|
//#region src/cli/commands/workspace/restore.ts
|
|
18309
19524
|
const restoreWorkspaceOptionsSchema = z.object({ workspaceId: z.uuid({ message: "workspace-id must be a valid UUID" }) });
|
|
@@ -19560,5 +20775,5 @@ function isDeno() {
|
|
|
19560
20775
|
}
|
|
19561
20776
|
|
|
19562
20777
|
//#endregion
|
|
19563
|
-
export {
|
|
19564
|
-
//# sourceMappingURL=runtime-
|
|
20778
|
+
export { listFolders as $, deploymentArgs as $n, handleOptionalToRequiredError as $t, generate as A, generateUserTypes as An, startCommand as At, remove as B, loadConsoleBaseUrl as Bn, deploy as Bt, waitWorkflowExecution as C, formatDiffSummary as Cn, listCommand$8 as Ct, listWorkflows as D, resourceTrn as Dn, jobsCommand as Dt, listCommand$2 as E, getNamespacesWithMigrations as En, getExecutorWaitFailureMessage as Et, openInConfiguredEditor as F, deleteUserTokens as Fn, getWorkflowExecution as Ft, treeCommand as G, readPlatformConfig as Gn, createWorkspace$1 as Gt, updateCommand$1 as H, loadStoredUserTokens as Hn, listCommand$9 as Ht, show as I, fetchLatestToken as In, listWorkflowExecutions as It, getCommand$1 as J, writePlatformConfig as Jn, ensureConfigId as Jt, listCommand$3 as K, resolveUserTokenKey as Kn, formatKeyValueTable as Kt, showCommand as L, hasAnyUserTokenEntry as Ln, functionExecutionStatusToString as Lt, generateMigrationScript as M, apiCall as Mn, getCommand$5 as Mt, writeDbTypesFile as N, assertWritable as Nn, getWorkflow as Nt, truncate as O, sdkNameLabelKey as On, listExecutorJobs as Ot, getConfiguredEditorCommand as P, loadConfig as Pn, executionsCommand as Pt, listCommand$4 as Q, confirmationArgs as Qn, MIGRATION_LABEL_KEY as Qt, extractOwnedNamespaces as R, hasUserTokenEntry as Rn, getCommand$6 as Rt, waitCommand as S, parseMigrationNumberArg as Sn, triggerExecutor as St, resumeWorkflow as T, hasChanges as Tn, getExecutorJob as Tt, updateOrganization as U, loadWorkspaceId as Un, listWorkspaces as Ut, removeCommand$1 as V, loadMachineUserName as Vn, deployFromCLI as Vt, organizationTree as W, platformConfigFromProfile as Wn, createCommand$1 as Wt, updateCommand$2 as X, commonArgs as Xn, waitForExecution as Xt, getOrganization as Y, defineAppCommand as Yn, executeScript as Yt, updateFolder as Z, configArg as Zn, bundleMigrationScript as Zt, deleteWorkspace as _, getNextMigrationNumber as _n, getCommand$4 as _t, updateUser as a, DIFF_FILE_NAME as an, toPageDirection as ar, createFolder as at, getAppHealth as b, reconstructSnapshotFromMigrations as bn, webhookCommand as bt, listCommand as c, SCHEMA_FILE_NAME as cn, getCommand$3 as ct, inviteUser as d, compareSnapshots as dn, tokenCommand as dt, parseMigrationLabelNumber as en, isVerbose as er, getCommand$2 as et, restoreCommand as f, createSnapshotFromLocalTypes as fn, listCommand$6 as ft, deleteCommand as g, getMigrationFiles as gn, listFunctionRegistries as gt, getWorkspace as h, getMigrationFilePath as hn, listCommand$7 as ht, updateCommand as i, DB_TYPES_FILE_NAME as in, resolveMachineUserInputSource as ir, createCommand as it, generateCommand as j, prompt as jn, startWorkflow as jt, truncateCommand as k, PluginManager as kn, watchExecutorJob as kt, listUsers as l, assertValidMigrationFiles as ln, getOAuth2Client as lt, getCommand as m, getMigrationDirPath as mn, generate$1 as mt, query as n, generateAllTypeManifestsFromSnapshot as nn, pagedLogArgs as nr, deleteCommand$1 as nt, removeCommand as o, INITIAL_SCHEMA_NUMBER as on, workspaceArgs as or, listCommand$5 as ot, restoreWorkspace as p, getLatestMigrationNumber as pn, listMachineUsers as pt, listOrganizations as q, saveUserTokens as qn, workspaceNameSchema as qt, queryCommand as r, protoGqlPermission as rn, paginationArgs as rr, deleteFolder as rt, removeUser as s, MIGRATE_FILE_NAME as sn, listOAuth2Clients as st, isNativeTypeScriptRuntime as t, compareSnapshotWithRemote as tn, multiConfigArg as tr, getFolder as tt, inviteCommand as u, compareLocalTypesWithSnapshot as un, getMachineUserToken as ut, listApps as v, isValidMigrationNumber as vn, getFunctionRegistry as vt, resumeCommand as w, formatMigrationDiff as wn, listExecutors as wt, healthCommand as x, formatMigrationNumber as xn, triggerCommand as xt, listCommand$1 as y, loadDiff as yn, listWebhookExecutors as yt, logBetaWarning as z, loadAccessToken as zn, getExecutor as zt };
|
|
20779
|
+
//# sourceMappingURL=runtime-BD2vYJFT.mjs.map
|