@tailor-platform/sdk 2.0.0-next.4 → 2.0.0-next.6
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 +135 -0
- package/dist/{application-BX13RArL.mjs → application-BJXRpQj5.mjs} +602 -1842
- package/dist/application-BJXRpQj5.mjs.map +1 -0
- package/dist/application-BV-AXawv.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/options.d.mts +0 -1
- package/dist/cli/commands/generate/service.d.mts +6 -2
- 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/staticwebsite/deploy.d.mts +19 -0
- package/dist/cli/commands/tailordb/migrate/config.d.mts +0 -1
- package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +16 -3
- package/dist/cli/commands/tailordb/migrate/generate.d.mts +0 -1
- package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +6 -0
- 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 +6 -3
- package/dist/cli/index.mjs +871 -1577
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +10 -4
- package/dist/cli/lib.mjs +50 -4
- 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 +1 -2
- package/dist/cli/shared/context.d.mts +2 -1
- package/dist/cli/shared/readonly-guard.d.mts +25 -0
- package/dist/cli/shared/script-executor.d.mts +0 -1
- package/dist/cli/shared/seed-chunker.d.mts +7 -3
- package/dist/cli/shared/tailordb-namespaces.d.mts +37 -0
- package/dist/cli/shared/type-generator.d.mts +0 -1
- package/dist/cli/shared/workspace-name.d.mts +1 -0
- package/dist/completion/zsh-worker.zsh +15 -303
- package/dist/configure/config/index.d.mts +0 -1
- package/dist/configure/config/types.d.mts +2 -2
- package/dist/configure/index.d.mts +14 -4
- package/dist/configure/index.mjs +201 -6
- 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 +15 -14
- package/dist/configure/services/executor/executor.d.mts +2 -13
- package/dist/configure/services/executor/index.d.mts +1 -0
- package/dist/configure/services/executor/operation.d.mts +9 -4
- 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 +1 -0
- 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 +86 -56
- package/dist/configure/services/tailordb/types.d.mts +66 -17
- package/dist/configure/services/workflow/execution-policy.d.mts +2 -3
- 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 +1 -1
- package/dist/configure/types/field.d.mts +1 -2
- package/dist/configure/types/field.types.d.mts +6 -45
- package/dist/configure/types/index.d.mts +1 -0
- package/dist/configure/types/type.d.mts +2 -11
- package/dist/{crashreport-D3RjuSYb.mjs → crashreport-0ioNRZ4K.mjs} +1 -1
- package/dist/{crashreport-Cg-pqRJV.mjs → crashreport-CxHne11E.mjs} +4 -3
- package/dist/{crashreport-Cg-pqRJV.mjs.map → crashreport-CxHne11E.mjs.map} +1 -1
- package/dist/errors-DfnF4ibq.mjs +100 -0
- package/dist/errors-DfnF4ibq.mjs.map +1 -0
- package/dist/{globals-BjhJJ2dN.mjs → globals-D-YbJKW-.mjs} +16 -10
- package/dist/globals-D-YbJKW-.mjs.map +1 -0
- package/dist/kysely/index.d.mts +3 -4
- package/dist/{kysely-type-D-zflMGf.mjs → kysely-type-DqIRgDYR.mjs} +4 -3
- package/dist/kysely-type-DqIRgDYR.mjs.map +1 -0
- package/dist/{logger-BEiZZ3qT.mjs → logger-BwS4ppwO.mjs} +3 -3
- package/dist/logger-BwS4ppwO.mjs.map +1 -0
- package/dist/parser/service/auth/index.d.mts +1 -1
- package/dist/parser/service/auth/schema.d.mts +2 -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/type-source.d.mts +10 -0
- package/dist/parser/service/tailordb/types.d.mts +6 -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/kysely-type/index.mjs +1 -1
- package/dist/plugin/builtin/seed/index.d.mts +0 -1
- package/dist/plugin/get-generated-type.d.mts +0 -1
- package/dist/plugin/manager.d.mts +2 -1
- package/dist/plugin/types.d.mts +0 -1
- package/dist/plugin/with-context.d.mts +0 -1
- package/dist/{register-ts-hook-BM1caLT0.mjs → register-ts-hook-DL31O2W9.mjs} +3076 -1744
- package/dist/register-ts-hook-DL31O2W9.mjs.map +1 -0
- package/dist/{registry-BSbq6SPo.mjs → registry-CC3CbQiF.mjs} +3 -3
- package/dist/registry-CC3CbQiF.mjs.map +1 -0
- package/dist/runtime/aigateway.d.mts +0 -1
- package/dist/runtime/authconnection.d.mts +0 -1
- package/dist/runtime/context.d.mts +0 -1
- package/dist/runtime/field-parse.d.mts +10 -0
- package/dist/runtime/globals.d.mts +5 -3
- package/dist/runtime/index.d.mts +0 -1
- package/dist/runtime/index.mjs +1 -1
- package/dist/runtime/types.d.mts +9 -4
- package/dist/runtime/workflow.d.mts +70 -10
- package/dist/runtime/workflow.mjs +1 -1
- package/dist/{schema-C4LYbXVG.mjs → schema--xYWRGfe.mjs} +272 -51
- package/dist/schema--xYWRGfe.mjs.map +1 -0
- package/dist/{secret-file-aoNXJmKa.mjs → secret-file-C3J4Gy8r.mjs} +2 -27
- package/dist/secret-file-C3J4Gy8r.mjs.map +1 -0
- package/dist/{secretmanager-Bd45j7an.mjs → secretmanager-IY4UvinW.mjs} +42 -9
- package/dist/secretmanager-IY4UvinW.mjs.map +1 -0
- package/dist/seed/index.d.mts +3 -2
- package/dist/{service-ufH7sS8o.mjs → service-CnHz9rwz.mjs} +47 -20
- package/dist/service-CnHz9rwz.mjs.map +1 -0
- package/dist/{service-DeZeYa06.mjs → service-DaeJlBoH.mjs} +3 -3
- package/dist/{service-DeZeYa06.mjs.map → service-DaeJlBoH.mjs.map} +1 -1
- package/dist/service-nU6ITOHL.mjs +3 -0
- 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/types/aigateway.generated.d.mts +5 -2
- package/dist/types/auth-connection.generated.d.mts +24 -11
- package/dist/types/auth.generated.d.mts +115 -84
- package/dist/types/executor.generated.d.mts +133 -123
- package/dist/types/helpers.d.mts +7 -6
- 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 +26 -11
- package/dist/types/staticwebsite.generated.d.mts +7 -3
- package/dist/types/tailordb.generated.d.mts +57 -29
- package/dist/types/workflow.generated.d.mts +9 -4
- package/dist/user-agent-Bgsszb5I.mjs +28 -0
- package/dist/user-agent-Bgsszb5I.mjs.map +1 -0
- package/dist/utils/test/index.d.mts +1 -2
- package/dist/utils/test/index.mjs +36 -10
- package/dist/utils/test/index.mjs.map +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 +591 -173
- 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 +23 -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 +72 -21
- package/dist/vitest/setup.mjs +2 -2
- package/dist/vitest/workflow-local.d.mts +1 -0
- package/dist/workflow-9kHGKxF2.mjs +64 -0
- package/dist/workflow-9kHGKxF2.mjs.map +1 -0
- 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 +61 -15
- package/docs/cli/setup.md +1 -1
- package/docs/cli/tailordb.md +8 -140
- package/docs/cli/workspace.md +6 -4
- package/docs/cli-reference.md +18 -14
- package/docs/github-actions.md +8 -0
- package/docs/migration/v2.md +247 -0
- package/docs/runtime.md +5 -6
- package/docs/services/aigateway.md +2 -0
- package/docs/services/executor.md +5 -0
- package/docs/services/tailordb.md +144 -68
- package/docs/services/workflow.md +3 -3
- package/docs/testing.md +105 -89
- package/package.json +22 -25
- package/dist/application-BO4ZF8wP.mjs +0 -3
- package/dist/application-BX13RArL.mjs.map +0 -1
- package/dist/cli/erd-viewer-assets/app.js +0 -1424
- package/dist/cli/erd-viewer-assets/index.html +0 -77
- package/dist/cli/erd-viewer-assets/serve.json +0 -13
- package/dist/cli/erd-viewer-assets/styles.css +0 -1036
- package/dist/cli/shared/trigger-context.d.mts +0 -1
- package/dist/configure/types/field-runtime.d.mts +0 -17
- package/dist/errors-118wUDRr.mjs +0 -42
- package/dist/errors-118wUDRr.mjs.map +0 -1
- package/dist/field-runtime-CAnh_oow.mjs +0 -183
- package/dist/field-runtime-CAnh_oow.mjs.map +0 -1
- package/dist/globals-BjhJJ2dN.mjs.map +0 -1
- package/dist/kysely-type-D-zflMGf.mjs.map +0 -1
- package/dist/logger-BEiZZ3qT.mjs.map +0 -1
- package/dist/register-ts-hook-BM1caLT0.mjs.map +0 -1
- package/dist/registry-BSbq6SPo.mjs.map +0 -1
- package/dist/resource_pb-BRv7AMXK.mjs +0 -49
- package/dist/resource_pb-BRv7AMXK.mjs.map +0 -1
- package/dist/schema-C4LYbXVG.mjs.map +0 -1
- package/dist/secret-file-aoNXJmKa.mjs.map +0 -1
- package/dist/secretmanager-Bd45j7an.mjs.map +0 -1
- package/dist/service-CkIwEctX.mjs +0 -3
- package/dist/service-ufH7sS8o.mjs.map +0 -1
- package/dist/service_pb-B5w9rjPY.mjs.map +0 -1
- package/dist/types-C-lQ4se3.mjs +0 -4
- package/dist/types-CXF2OYdR.mjs +0 -199
- package/dist/types-CXF2OYdR.mjs.map +0 -1
- package/dist/workflow-GNuSnsTx.mjs +0 -22
- package/dist/workflow-GNuSnsTx.mjs.map +0 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { r as parseInputFields } from "../schema--xYWRGfe.mjs";
|
|
3
|
+
import { St as AuthInvokerSchema, T as CustomDomainStatus, U as FunctionExecution_Type, kt as PATScope } from "../workspace_resource_pb-Db3fv68L.mjs";
|
|
2
4
|
import { t as assertDefined } from "../assert-DBxo8jPo.mjs";
|
|
3
|
-
import { n as logger, r as styles } from "../logger-
|
|
4
|
-
import { $ as
|
|
5
|
-
import {
|
|
6
|
-
import { i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-
|
|
5
|
+
import { n as logger, r as styles } from "../logger-BwS4ppwO.mjs";
|
|
6
|
+
import { $n as saveUserTokens, An as sdkNameLabelKey, B as extractOwnedNamespaces, Bn as hasUserTokenEntry, Bt as getCommand$1, Cn as formatMigrationNumber, Ct as triggerCommand, Dn as hasChanges, E as listCommand$14, En as formatMigrationDiff, F as openInConfiguredEditor, Fn as assertWritable, Gn as loadPlatformClientConfig, Hn as loadConfigPath, I as deployCommand$1, In as loadConfig, It as executionsCommand, J as listCommand$13, Jn as platformConfigFromProfile, Jt as formatKeyValueTable, Kn as loadStoredUserTokens, Kt as createCommand$4, Ln as deleteUserTokens, M as generateMigrationScript, Mt as startCommand, N as writeDbTypesFile, Nn as prompt, On as getNamespacesWithMigrations, P as getConfiguredEditorCommand, Pn as apiCall, Pt as getCommand$6, Q as updateCommand$2, Qn as resolveUserTokenKey, Rn as fetchLatestToken, S as waitCommand, Sn as reconstructSnapshotFromMigrations, St as webhookCommand, Tt as listCommand$8, U as removeCommand$1, Un as loadConsoleBaseUrl, Ut as deployFromCLI, V as logBetaWarning, Vn as loadAccessToken, W as updateCommand$3, Wn as loadMachineUserName, Wt as listCommand$17, X as getCommand$5, Xn as removeLegacyUserAlias, Xt as ensureConfigId, Yn as readPlatformConfig, Yt as workspaceNameSchema, Zn as resolveConfigUser, Zt as executeScript, _n as getMigrationFilePath, _t as listCommand$9, an as protoGqlPermission, ar as deploymentArgs, bn as isValidMigrationNumber, c as listCommand$16, cn as INITIAL_SCHEMA_NUMBER, cr as pagedLogArgs, ct as listCommand$11, dn as assertValidMigrationFiles, dr as toPageDirection, en as MIGRATION_LABEL_KEY, er as writePlatformConfig, et as listCommand$12, f as restoreCommand, fn as compareLocalTypesWithSnapshot, fr as workspaceArgs, g as deleteCommand$5, gt as generate, hn as getLatestMigrationNumber, i as updateCommand$4, in as generateAllTypeManifestsFromSnapshot, ir as confirmationArgs, it as deleteCommand$4, j as generateCommand$1, jn as PluginManager, k as truncateCommand, kn as resourceTrn, kt as jobsCommand, lr as paginationArgs, m as getCommand$7, mn as createSnapshotFromLocalTypes, mt as listCommand$10, nn as parseMigrationLabelNumber, nr as commonArgs, nt as getCommand$4, o as removeCommand, or as isVerbose, ot as createCommand$3, pt as tokenCommand$1, q as treeCommand, qn as loadWorkspaceId, r as queryCommand, rn as compareSnapshotWithRemote, rr as configArg, sr as multiConfigArg, t as registerTsHook, tn as handleOptionalToRequiredError, tr as defineAppCommand, u as inviteCommand, ur as resolveMachineUserInputSource, ut as getCommand$3, vn as getMigrationFiles, w as resumeCommand, wn as parseMigrationNumberArg, x as healthCommand, xn as loadDiff, y as listCommand$15, yt as getCommand$2, z as showCommand, zn as hasAnyUserTokenEntry, zt as functionExecutionStatusToString } from "../register-ts-hook-DL31O2W9.mjs";
|
|
7
|
+
import { A as fetchAllTolerant, B as initOperatorClient, D as closeConnectionPool, I as getOAuth2ClientId, L as getOrNull, M as fetchPaged, N as fetchPlatformMachineUserToken, O as defaultPlatformBaseUrl, P as fetchUserInfo, R as getPlatformBaseUrl, V as isDefaultPlatform, _ as platformBundleDefinePlugin, a as resolveInlineSourcemap, b as resolveBundleLogLevel, c as WorkflowJobSchema, d as INVOKER_EXPR, k as fetchAll, l as ResolverSchema, v as composeFunctionTreeshakeOptions, w as getDistDir, y as createLogLevelTreeshakeOptions, z as initOAuth2Client } from "../application-BJXRpQj5.mjs";
|
|
8
|
+
import { i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-CnHz9rwz.mjs";
|
|
7
9
|
import { t as multiline } from "../multiline-sfHpTZZK.mjs";
|
|
8
10
|
import { t as readPackageJson } from "../package-json-8b0O9TlX.mjs";
|
|
9
|
-
import {
|
|
10
|
-
import { n as isCLIError } from "../errors-
|
|
11
|
-
import {
|
|
12
|
-
import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-
|
|
13
|
-
import { t as isPluginGeneratedType } from "../type-source-DH_LH20p.mjs";
|
|
11
|
+
import { t as userAgent } from "../user-agent-Bgsszb5I.mjs";
|
|
12
|
+
import { n as errorToJson, r as isCLIError, t as createCLIError } from "../errors-DfnF4ibq.mjs";
|
|
13
|
+
import { t as OperatorService } from "../service_pb-BoLM0X4K.mjs";
|
|
14
|
+
import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-CxHne11E.mjs";
|
|
14
15
|
import * as path from "pathe";
|
|
15
16
|
import { dirname, resolve } from "pathe";
|
|
16
17
|
import { resolvePackageJSON, resolveTSConfig } from "pkg-types";
|
|
@@ -18,29 +19,578 @@ import { arg, defineCommand, runCommand, runMain } from "politty";
|
|
|
18
19
|
import { withCompletionCommand } from "politty/completion";
|
|
19
20
|
import { withSkillCommand } from "politty/skill";
|
|
20
21
|
import { z } from "zod";
|
|
21
|
-
import { create } from "@bufbuild/protobuf";
|
|
22
|
-
import * as fs
|
|
22
|
+
import { ScalarType, create } from "@bufbuild/protobuf";
|
|
23
|
+
import * as fs from "node:fs";
|
|
23
24
|
import { accessSync, constants, readdirSync } from "node:fs";
|
|
24
25
|
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
25
|
-
import {
|
|
26
|
+
import { pathToFileURL } from "node:url";
|
|
26
27
|
import { generateCodeVerifier } from "@badgateway/oauth2-client";
|
|
27
28
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
28
|
-
import pLimit from "p-limit";
|
|
29
29
|
import * as crypto from "node:crypto";
|
|
30
30
|
import { createHash } from "node:crypto";
|
|
31
31
|
import * as http from "node:http";
|
|
32
32
|
import open from "open";
|
|
33
33
|
import * as rolldown from "rolldown";
|
|
34
|
-
import * as fsPromises from "node:fs/promises";
|
|
35
|
-
import { glob } from "node:fs/promises";
|
|
36
34
|
import * as os from "node:os";
|
|
37
|
-
import
|
|
35
|
+
import * as fsPromises from "node:fs/promises";
|
|
38
36
|
import { TraceMap, generatedPositionFor, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
39
37
|
import { spawn, spawnSync } from "node:child_process";
|
|
40
|
-
import { watch } from "chokidar";
|
|
41
|
-
import * as fs from "fs";
|
|
42
|
-
import { lookup } from "mime-types";
|
|
43
38
|
|
|
39
|
+
//#region src/cli/commands/api/proto-reflect.ts
|
|
40
|
+
const UNARY_METHODS = OperatorService.methods.filter((m) => m.methodKind === "unary");
|
|
41
|
+
function listMethodNames() {
|
|
42
|
+
return UNARY_METHODS.map((m) => m.name).toSorted();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Returns every accepted form of the `endpoint` positional — bare method name
|
|
46
|
+
* and fully-qualified `service/Method`. politty's expand completion keys its
|
|
47
|
+
* static table by the literal endpoint string, so both forms must appear in
|
|
48
|
+
* `choices` or the user typing the fully-qualified name gets no `--field`
|
|
49
|
+
* completion candidates back at TAB time.
|
|
50
|
+
* @returns Sorted list of accepted endpoint values
|
|
51
|
+
*/
|
|
52
|
+
function listMethodChoices() {
|
|
53
|
+
return UNARY_METHODS.flatMap((m) => [m.name, `${OperatorService.typeName}/${m.name}`]).toSorted();
|
|
54
|
+
}
|
|
55
|
+
function getMethodDescriptor(methodName) {
|
|
56
|
+
return UNARY_METHODS.find((m) => m.name === methodName);
|
|
57
|
+
}
|
|
58
|
+
function extractMethodName(endpoint) {
|
|
59
|
+
if (!endpoint.includes("/")) return endpoint;
|
|
60
|
+
return endpoint.split("/").pop() ?? endpoint;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns the nested message descriptor when the field is a message, a list of
|
|
64
|
+
* messages, or a map with message values. Otherwise returns undefined.
|
|
65
|
+
* @param field - The proto field descriptor to inspect
|
|
66
|
+
* @returns The nested message descriptor, or undefined when the field isn't message-shaped
|
|
67
|
+
*/
|
|
68
|
+
function nestedMessage(field) {
|
|
69
|
+
if (field.fieldKind === "message") return field.message;
|
|
70
|
+
if (field.fieldKind === "list" && field.listKind === "message") return field.message;
|
|
71
|
+
if (field.fieldKind === "map" && field.mapKind === "message") return field.message;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Most `google.protobuf.*` well-known types serialize as scalars in proto JSON
|
|
75
|
+
* (Timestamp/Duration → RFC3339/duration string, FieldMask → comma-joined
|
|
76
|
+
* string, *Value wrappers → underlying primitive, etc.), so we must not let
|
|
77
|
+
* `--field` drill into their internal fields. Treating them as leaves keeps
|
|
78
|
+
* the completion and the body shape consistent with what the server expects.
|
|
79
|
+
* @param message - The proto message descriptor to inspect
|
|
80
|
+
* @returns True when the message is a well-known type
|
|
81
|
+
*/
|
|
82
|
+
function isWellKnownType(message) {
|
|
83
|
+
return message.typeName.startsWith("google.protobuf.");
|
|
84
|
+
}
|
|
85
|
+
const UNREPRESENTABLE_WELL_KNOWN_TYPES = /* @__PURE__ */ new Set([
|
|
86
|
+
"google.protobuf.Struct",
|
|
87
|
+
"google.protobuf.Value",
|
|
88
|
+
"google.protobuf.ListValue",
|
|
89
|
+
"google.protobuf.NullValue",
|
|
90
|
+
"google.protobuf.Any",
|
|
91
|
+
"google.protobuf.Empty"
|
|
92
|
+
]);
|
|
93
|
+
/**
|
|
94
|
+
* @param message - The proto message descriptor to inspect
|
|
95
|
+
* @returns True when the message is a well-known type that `--field` cannot represent
|
|
96
|
+
*/
|
|
97
|
+
function isUnrepresentableWellKnownType(message) {
|
|
98
|
+
return UNREPRESENTABLE_WELL_KNOWN_TYPES.has(message.typeName);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Pre-enumerates every `--field` completion candidate for `methodName`,
|
|
102
|
+
* walking the input message tree to produce a flat list that the shell can
|
|
103
|
+
* prefix-filter at TAB time. Used by the `expand` completion variant so no
|
|
104
|
+
* Node process is spawned per keystroke.
|
|
105
|
+
*
|
|
106
|
+
* For each leaf field, emits `key=` (key completion). Enum leaves additionally
|
|
107
|
+
* emit `key=ENUM_VALUE` per value, bool leaves emit `key=true` and
|
|
108
|
+
* `key=false`. For nested messages, emits `key.` (drill-down) and recurses.
|
|
109
|
+
*
|
|
110
|
+
* Returns an empty array when `methodName` is unknown so the `expand`
|
|
111
|
+
* generator stays exception-free at script-generation time.
|
|
112
|
+
* @param methodName - Name of the unary RPC whose input message is being walked
|
|
113
|
+
* @returns Flat list of `{ value, description }` candidates
|
|
114
|
+
*/
|
|
115
|
+
function enumerateAllFieldCompletions(methodName) {
|
|
116
|
+
const method = getMethodDescriptor(methodName);
|
|
117
|
+
if (!method) return [];
|
|
118
|
+
const candidates = [];
|
|
119
|
+
const visited = /* @__PURE__ */ new Set();
|
|
120
|
+
function walk(message, prefix) {
|
|
121
|
+
visited.add(message);
|
|
122
|
+
for (const field of message.fields) {
|
|
123
|
+
if (field.fieldKind === "list" || field.fieldKind === "map") continue;
|
|
124
|
+
const fullKey = prefix + field.localName;
|
|
125
|
+
if (field.fieldKind === "message") {
|
|
126
|
+
if (isUnrepresentableWellKnownType(field.message)) continue;
|
|
127
|
+
if (!isWellKnownType(field.message)) {
|
|
128
|
+
const nested = field.message;
|
|
129
|
+
candidates.push({
|
|
130
|
+
value: `${fullKey}.`,
|
|
131
|
+
description: `${fullKey} (message)`
|
|
132
|
+
});
|
|
133
|
+
if (!visited.has(nested)) walk(nested, `${fullKey}.`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
candidates.push({
|
|
138
|
+
value: `${fullKey}=`,
|
|
139
|
+
description: `Set ${fullKey}`
|
|
140
|
+
});
|
|
141
|
+
if (field.fieldKind === "enum") for (const v of field.enum.values) candidates.push({
|
|
142
|
+
value: `${fullKey}=${v.name}`,
|
|
143
|
+
description: v.name
|
|
144
|
+
});
|
|
145
|
+
else if (field.fieldKind === "scalar" && field.scalar === ScalarType.BOOL) {
|
|
146
|
+
candidates.push({
|
|
147
|
+
value: `${fullKey}=true`,
|
|
148
|
+
description: "true"
|
|
149
|
+
});
|
|
150
|
+
candidates.push({
|
|
151
|
+
value: `${fullKey}=false`,
|
|
152
|
+
description: "false"
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
visited.delete(message);
|
|
157
|
+
}
|
|
158
|
+
walk(method.input, "");
|
|
159
|
+
return candidates;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Resolves the leaf `DescField` at a dotted `--field` path within an input
|
|
163
|
+
* message tree. Returns undefined when any segment is missing or when a
|
|
164
|
+
* non-leaf segment isn't a singular message — callers fall back to treating
|
|
165
|
+
* the value as a string in that case.
|
|
166
|
+
* @param input - The RPC input message descriptor
|
|
167
|
+
* @param path - Dot-split path segments to follow (e.g. ["application", "name"])
|
|
168
|
+
* @returns The leaf field descriptor, or undefined when the path doesn't resolve
|
|
169
|
+
*/
|
|
170
|
+
function resolveLeafField(input, path) {
|
|
171
|
+
let message = input;
|
|
172
|
+
for (let i = 0; i < path.length; i++) {
|
|
173
|
+
const field = message.fields.find((f) => f.localName === path[i]);
|
|
174
|
+
if (!field) return void 0;
|
|
175
|
+
if (i === path.length - 1) {
|
|
176
|
+
if (field.fieldKind === "message" && isUnrepresentableWellKnownType(field.message)) return;
|
|
177
|
+
return field;
|
|
178
|
+
}
|
|
179
|
+
if (field.fieldKind !== "message") return void 0;
|
|
180
|
+
if (isWellKnownType(field.message)) return void 0;
|
|
181
|
+
message = field.message;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/cli/commands/api/render.ts
|
|
187
|
+
const SCALAR_LABEL = {
|
|
188
|
+
[ScalarType.DOUBLE]: "double",
|
|
189
|
+
[ScalarType.FLOAT]: "float",
|
|
190
|
+
[ScalarType.INT64]: "int64",
|
|
191
|
+
[ScalarType.UINT64]: "uint64",
|
|
192
|
+
[ScalarType.INT32]: "int32",
|
|
193
|
+
[ScalarType.FIXED64]: "fixed64",
|
|
194
|
+
[ScalarType.FIXED32]: "fixed32",
|
|
195
|
+
[ScalarType.BOOL]: "bool",
|
|
196
|
+
[ScalarType.STRING]: "string",
|
|
197
|
+
[ScalarType.BYTES]: "bytes",
|
|
198
|
+
[ScalarType.UINT32]: "uint32",
|
|
199
|
+
[ScalarType.SFIXED32]: "sfixed32",
|
|
200
|
+
[ScalarType.SFIXED64]: "sfixed64",
|
|
201
|
+
[ScalarType.SINT32]: "sint32",
|
|
202
|
+
[ScalarType.SINT64]: "sint64"
|
|
203
|
+
};
|
|
204
|
+
function shortName(typeName) {
|
|
205
|
+
const dot = typeName.lastIndexOf(".");
|
|
206
|
+
return dot < 0 ? typeName : typeName.slice(dot + 1);
|
|
207
|
+
}
|
|
208
|
+
function scalarLabel(scalar) {
|
|
209
|
+
return SCALAR_LABEL[scalar] ?? `scalar(${scalar})`;
|
|
210
|
+
}
|
|
211
|
+
function enumLabel(enumDesc) {
|
|
212
|
+
return `enum ${shortName(enumDesc.typeName)}`;
|
|
213
|
+
}
|
|
214
|
+
function enumOf(field) {
|
|
215
|
+
if (field.fieldKind === "enum") return field.enum;
|
|
216
|
+
if (field.fieldKind === "list" && field.listKind === "enum") return field.enum;
|
|
217
|
+
}
|
|
218
|
+
function describeFieldType(field) {
|
|
219
|
+
switch (field.fieldKind) {
|
|
220
|
+
case "scalar": return scalarLabel(field.scalar);
|
|
221
|
+
case "enum": return enumLabel(field.enum);
|
|
222
|
+
case "message": return shortName(field.message.typeName);
|
|
223
|
+
case "list": {
|
|
224
|
+
let inner;
|
|
225
|
+
if (field.listKind === "scalar") inner = scalarLabel(field.scalar);
|
|
226
|
+
else if (field.listKind === "enum") inner = enumLabel(field.enum);
|
|
227
|
+
else inner = shortName(field.message.typeName);
|
|
228
|
+
return `repeated ${inner}`;
|
|
229
|
+
}
|
|
230
|
+
case "map": {
|
|
231
|
+
const keyType = scalarLabel(field.mapKey);
|
|
232
|
+
let valType;
|
|
233
|
+
if (field.mapKind === "scalar") valType = scalarLabel(field.scalar);
|
|
234
|
+
else if (field.mapKind === "enum") valType = enumLabel(field.enum);
|
|
235
|
+
else valType = shortName(field.message.typeName);
|
|
236
|
+
return `map<${keyType}, ${valType}>`;
|
|
237
|
+
}
|
|
238
|
+
default: return "<unknown>";
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function fieldToJson(field, visited) {
|
|
242
|
+
const json = {
|
|
243
|
+
name: field.localName,
|
|
244
|
+
protoName: field.name,
|
|
245
|
+
type: describeFieldType(field),
|
|
246
|
+
fieldKind: field.fieldKind,
|
|
247
|
+
repeated: field.fieldKind === "list"
|
|
248
|
+
};
|
|
249
|
+
if (field.oneof) json.oneof = field.oneof.name;
|
|
250
|
+
const fieldEnum = enumOf(field);
|
|
251
|
+
if (fieldEnum) json.enumValues = fieldEnum.values.map((v) => v.name);
|
|
252
|
+
const nested = nestedMessage(field);
|
|
253
|
+
if (nested && !visited.has(nested)) {
|
|
254
|
+
visited.add(nested);
|
|
255
|
+
json.message = {
|
|
256
|
+
typeName: nested.typeName,
|
|
257
|
+
fields: nested.fields.map((f) => fieldToJson(f, visited))
|
|
258
|
+
};
|
|
259
|
+
visited.delete(nested);
|
|
260
|
+
} else if (nested) json.message = {
|
|
261
|
+
typeName: nested.typeName,
|
|
262
|
+
fields: [],
|
|
263
|
+
recursive: true
|
|
264
|
+
};
|
|
265
|
+
return json;
|
|
266
|
+
}
|
|
267
|
+
function renderInspectJson(method) {
|
|
268
|
+
const visited = /* @__PURE__ */ new Set([method.input]);
|
|
269
|
+
return {
|
|
270
|
+
method: method.name,
|
|
271
|
+
input: {
|
|
272
|
+
typeName: method.input.typeName,
|
|
273
|
+
fields: method.input.fields.map((f) => fieldToJson(f, visited))
|
|
274
|
+
},
|
|
275
|
+
output: { typeName: method.output.typeName }
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function renderFieldText(field, indent, visited) {
|
|
279
|
+
const lines = [];
|
|
280
|
+
const oneofTag = field.oneof ? ` (oneof ${field.oneof.name})` : "";
|
|
281
|
+
lines.push(`${indent}${field.localName}: ${describeFieldType(field)}${oneofTag}`);
|
|
282
|
+
const fieldEnum = enumOf(field);
|
|
283
|
+
if (fieldEnum) {
|
|
284
|
+
const values = fieldEnum.values.map((v) => v.name).join(", ");
|
|
285
|
+
lines.push(`${indent} values: ${values}`);
|
|
286
|
+
}
|
|
287
|
+
const nested = nestedMessage(field);
|
|
288
|
+
if (nested && !visited.has(nested)) {
|
|
289
|
+
visited.add(nested);
|
|
290
|
+
for (const sub of nested.fields) lines.push(...renderFieldText(sub, `${indent} `, visited));
|
|
291
|
+
visited.delete(nested);
|
|
292
|
+
} else if (nested) lines.push(`${indent} …(recursive ${nested.typeName})`);
|
|
293
|
+
return lines;
|
|
294
|
+
}
|
|
295
|
+
function renderInspectText(method) {
|
|
296
|
+
const lines = [];
|
|
297
|
+
lines.push(`${method.name}`);
|
|
298
|
+
lines.push(` request: ${method.input.typeName}`);
|
|
299
|
+
const visited = /* @__PURE__ */ new Set([method.input]);
|
|
300
|
+
for (const f of method.input.fields) lines.push(...renderFieldText(f, " ", visited));
|
|
301
|
+
lines.push(` response: ${method.output.typeName}`);
|
|
302
|
+
return lines.join("\n");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/cli/commands/api/inspect.ts
|
|
307
|
+
const inspectCommand = defineAppCommand({
|
|
308
|
+
name: "inspect",
|
|
309
|
+
description: "Print the input message tree of an OperatorService endpoint.",
|
|
310
|
+
notes: "Combine with the global `--json` flag for a machine-readable descriptor. Recursive type references and `oneof` membership are annotated. Use `tailor api list` to discover endpoint names.",
|
|
311
|
+
examples: [{
|
|
312
|
+
cmd: "GetApplication",
|
|
313
|
+
desc: "Show fields of GetApplicationRequest."
|
|
314
|
+
}, {
|
|
315
|
+
cmd: "CreateExecutorExecutor",
|
|
316
|
+
desc: "Inspect a deeply nested input with `(oneof config)` annotations."
|
|
317
|
+
}],
|
|
318
|
+
args: z.strictObject({ endpoint: arg(z.string(), {
|
|
319
|
+
positional: true,
|
|
320
|
+
description: "API endpoint to inspect (e.g., 'GetApplication' or 'tailor.v1.OperatorService/GetApplication').",
|
|
321
|
+
completion: { custom: { choices: listMethodNames() } }
|
|
322
|
+
}) }),
|
|
323
|
+
run: (args) => {
|
|
324
|
+
const methodName = extractMethodName(args.endpoint);
|
|
325
|
+
const method = getMethodDescriptor(methodName);
|
|
326
|
+
if (!method) throw createCLIError({
|
|
327
|
+
message: `unknown method: ${methodName}`,
|
|
328
|
+
suggestion: "Run `tailor api list` to see available methods.",
|
|
329
|
+
command: "api inspect"
|
|
330
|
+
});
|
|
331
|
+
if (logger.jsonMode) logger.out(renderInspectJson(method));
|
|
332
|
+
else logger.out(renderInspectText(method));
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
//#endregion
|
|
337
|
+
//#region src/cli/commands/api/list.ts
|
|
338
|
+
const listCommand$7 = defineAppCommand({
|
|
339
|
+
name: "list",
|
|
340
|
+
description: "List all invocable OperatorService methods.",
|
|
341
|
+
notes: "Only single-request (non-streaming) methods are listed, because the CLI issues a single JSON request and reads one JSON response.",
|
|
342
|
+
args: z.strictObject({}),
|
|
343
|
+
run: () => {
|
|
344
|
+
const names = listMethodNames();
|
|
345
|
+
if (logger.jsonMode) logger.out(names);
|
|
346
|
+
else for (const name of names) logger.out(name);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/cli/commands/api/index.ts
|
|
352
|
+
function resolveNamespaceName(methodName, config) {
|
|
353
|
+
if (/Auth|Tenant|UserProfile/.test(methodName)) return config.auth?.name;
|
|
354
|
+
if (/IdP/.test(methodName)) {
|
|
355
|
+
if (config.idp?.length === 1) return assertDefined(config.idp[0], "idp config missing").name;
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (/TailorDB/.test(methodName)) {
|
|
359
|
+
const keys = Object.keys(config.db ?? {});
|
|
360
|
+
if (keys.length === 1) return keys[0];
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (/Pipeline/.test(methodName)) {
|
|
364
|
+
const keys = Object.keys(config.resolver ?? {});
|
|
365
|
+
if (keys.length === 1) return keys[0];
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Parse a JSON body string as a plain object for field injection.
|
|
371
|
+
* Returns undefined for invalid JSON or non-object values (null, arrays, primitives),
|
|
372
|
+
* so callers can fall back to sending the raw string unchanged.
|
|
373
|
+
* @param body - Raw body string
|
|
374
|
+
* @returns Parsed object or undefined
|
|
375
|
+
*/
|
|
376
|
+
function parseBodyAsObject(body) {
|
|
377
|
+
let parsed;
|
|
378
|
+
try {
|
|
379
|
+
parsed = JSON.parse(body);
|
|
380
|
+
} catch {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
|
|
384
|
+
return parsed;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Set a dotted path on a body object, replacing non-object intermediates as
|
|
388
|
+
* needed. `--field` takes precedence over `--body`, so collisions overwrite.
|
|
389
|
+
* @param obj - The body object to mutate
|
|
390
|
+
* @param path - Dot-split path segments (e.g. ["application", "name"])
|
|
391
|
+
* @param value - Value to assign at the leaf
|
|
392
|
+
*/
|
|
393
|
+
function setNestedPath(obj, path, value) {
|
|
394
|
+
let cursor = obj;
|
|
395
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
396
|
+
const key = assertDefined(path[i], "path segment missing");
|
|
397
|
+
const next = cursor[key];
|
|
398
|
+
if (typeof next !== "object" || next === null || Array.isArray(next)) cursor[key] = {};
|
|
399
|
+
cursor = cursor[key];
|
|
400
|
+
}
|
|
401
|
+
cursor[assertDefined(path[path.length - 1], "path last segment missing")] = value;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Coerces a raw `--field` string value to match the leaf proto type so the
|
|
405
|
+
* JSON body sends a properly typed value. Today we only coerce bool — the
|
|
406
|
+
* completion candidates explicitly suggest `=true`/`=false`, so sending the
|
|
407
|
+
* literal string "true" would be a visible mismatch. Other scalars are left
|
|
408
|
+
* as strings: proto JSON accepts string forms for ints/floats/int64/etc.,
|
|
409
|
+
* and we'd rather pass through what the user typed than silently coerce.
|
|
410
|
+
* @param field - The resolved leaf field descriptor, or undefined when the path didn't resolve
|
|
411
|
+
* @param raw - The raw string value after `=`
|
|
412
|
+
* @returns The value to write into the body object
|
|
413
|
+
*/
|
|
414
|
+
function coerceFieldValue(field, raw) {
|
|
415
|
+
if (field && field.fieldKind === "scalar" && field.scalar === ScalarType.BOOL) {
|
|
416
|
+
if (raw === "true") return true;
|
|
417
|
+
if (raw === "false") return false;
|
|
418
|
+
throw new Error(`Invalid value for bool field: '${raw}'. Expected 'true' or 'false'.`);
|
|
419
|
+
}
|
|
420
|
+
return raw;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Collapse top-level body keys that address a known input field by its
|
|
424
|
+
* protobuf name (snake_case) or JSON name to the field's `localName`. protojson
|
|
425
|
+
* accepts every alias, so converging them here lets the presence checks below
|
|
426
|
+
* reason in a single namespace — otherwise a `--body` written as
|
|
427
|
+
* `{"workspace_id": ...}` slips past the camelCase check and we inject a second
|
|
428
|
+
* `workspaceId`, which the server rejects as a duplicate field. When both forms
|
|
429
|
+
* are present the canonical key wins and the alias is dropped.
|
|
430
|
+
* @param body - The parsed body object to mutate
|
|
431
|
+
* @param fields - The target endpoint's input fields
|
|
432
|
+
* @returns Whether any key was rewritten
|
|
433
|
+
*/
|
|
434
|
+
function normalizeBodyFieldKeys(body, fields) {
|
|
435
|
+
const aliasToLocal = /* @__PURE__ */ new Map();
|
|
436
|
+
for (const f of fields) {
|
|
437
|
+
aliasToLocal.set(f.name, f.localName);
|
|
438
|
+
aliasToLocal.set(f.jsonName, f.localName);
|
|
439
|
+
}
|
|
440
|
+
let changed = false;
|
|
441
|
+
for (const key of Object.keys(body)) {
|
|
442
|
+
const local = aliasToLocal.get(key);
|
|
443
|
+
if (!local || local === key) continue;
|
|
444
|
+
if (!Object.hasOwn(body, local)) body[local] = body[key];
|
|
445
|
+
delete body[key];
|
|
446
|
+
changed = true;
|
|
447
|
+
}
|
|
448
|
+
return changed;
|
|
449
|
+
}
|
|
450
|
+
const FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set([
|
|
451
|
+
"__proto__",
|
|
452
|
+
"constructor",
|
|
453
|
+
"prototype"
|
|
454
|
+
]);
|
|
455
|
+
const fieldArg = z.string().transform((val, ctx) => {
|
|
456
|
+
const eq = val.indexOf("=");
|
|
457
|
+
if (eq < 0) {
|
|
458
|
+
ctx.addIssue({
|
|
459
|
+
code: z.ZodIssueCode.custom,
|
|
460
|
+
message: `Invalid field format: '${val}'. Expected format: 'key=value' or 'a.b.c=value'`
|
|
461
|
+
});
|
|
462
|
+
return z.NEVER;
|
|
463
|
+
}
|
|
464
|
+
const key = val.slice(0, eq);
|
|
465
|
+
if (key.length === 0) {
|
|
466
|
+
ctx.addIssue({
|
|
467
|
+
code: z.ZodIssueCode.custom,
|
|
468
|
+
message: "Field key cannot be empty"
|
|
469
|
+
});
|
|
470
|
+
return z.NEVER;
|
|
471
|
+
}
|
|
472
|
+
const segments = key.split(".");
|
|
473
|
+
if (segments.some((seg) => seg.length === 0)) {
|
|
474
|
+
ctx.addIssue({
|
|
475
|
+
code: z.ZodIssueCode.custom,
|
|
476
|
+
message: `Invalid field key: '${key}'. Dotted segments cannot be empty`
|
|
477
|
+
});
|
|
478
|
+
return z.NEVER;
|
|
479
|
+
}
|
|
480
|
+
const forbidden = segments.find((seg) => FORBIDDEN_SEGMENTS.has(seg));
|
|
481
|
+
if (forbidden) {
|
|
482
|
+
ctx.addIssue({
|
|
483
|
+
code: z.ZodIssueCode.custom,
|
|
484
|
+
message: `Invalid field key: '${key}'. Segment '${forbidden}' is not allowed.`
|
|
485
|
+
});
|
|
486
|
+
return z.NEVER;
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
path: segments,
|
|
490
|
+
value: val.slice(eq + 1)
|
|
491
|
+
};
|
|
492
|
+
});
|
|
493
|
+
const apiCommand = defineAppCommand({
|
|
494
|
+
name: "api",
|
|
495
|
+
description: "Call Tailor Platform API endpoints directly.",
|
|
496
|
+
notes: `Use \`tailor api list\` to enumerate invocable methods and \`tailor api inspect <endpoint>\` to print an endpoint's input message tree (combine with \`--json\` for machine-readable output).
|
|
497
|
+
|
|
498
|
+
The request body is inferred from the target endpoint's request schema, and commonly required fields are auto-injected so they can be omitted from \`--body\`:
|
|
499
|
+
|
|
500
|
+
- \`workspaceId\` — resolved from \`-w\` / \`TAILOR_PLATFORM_WORKSPACE_ID\` / the selected profile.
|
|
501
|
+
- \`namespaceName\` — resolved from \`tailor.config.ts\` based on the endpoint's service:
|
|
502
|
+
- Auth / Tenant / UserProfile endpoints use \`auth.name\`.
|
|
503
|
+
- IdP / TailorDB / Pipeline endpoints use the sole configured namespace when exactly one is defined.
|
|
504
|
+
|
|
505
|
+
Values already present in \`--body\` are never overridden. If a value cannot be resolved (e.g. no config found), injection is silently skipped and the server-side validation error takes precedence.
|
|
506
|
+
|
|
507
|
+
Use \`--field key=value\` (repeatable) to set request body fields without writing JSON. Dotted keys (e.g. \`application.name=foo\`) build nested objects. \`--field\` overrides matching fields in \`--body\` and tab-completes from the endpoint's request schema.`,
|
|
508
|
+
examples: [
|
|
509
|
+
{
|
|
510
|
+
cmd: "GetApplication -b '{\"applicationName\":\"app-1\"}'",
|
|
511
|
+
desc: "Call an endpoint; workspaceId is auto-injected."
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
cmd: "GetApplication -f applicationName=app-1",
|
|
515
|
+
desc: "Same as above, using --field instead of --body."
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
cmd: "list",
|
|
519
|
+
desc: "List all invocable OperatorService methods."
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
cmd: "inspect GetApplication",
|
|
523
|
+
desc: "Show the input message tree for an endpoint."
|
|
524
|
+
}
|
|
525
|
+
],
|
|
526
|
+
subCommands: {
|
|
527
|
+
list: listCommand$7,
|
|
528
|
+
inspect: inspectCommand
|
|
529
|
+
},
|
|
530
|
+
args: z.strictObject({
|
|
531
|
+
...workspaceArgs,
|
|
532
|
+
...configArg,
|
|
533
|
+
body: arg(z.string().default("{}"), {
|
|
534
|
+
alias: "b",
|
|
535
|
+
description: "Request body as JSON."
|
|
536
|
+
}),
|
|
537
|
+
field: arg(fieldArg.array().optional(), {
|
|
538
|
+
alias: "f",
|
|
539
|
+
description: "Set a body field as `key=value` (repeatable; dotted keys nest). Overrides --body.",
|
|
540
|
+
completion: { custom: { expand: {
|
|
541
|
+
dependsOn: ["endpoint"],
|
|
542
|
+
enumerate: ({ endpoint }) => enumerateAllFieldCompletions(extractMethodName(endpoint ?? ""))
|
|
543
|
+
} } }
|
|
544
|
+
}),
|
|
545
|
+
endpoint: arg(z.string(), {
|
|
546
|
+
positional: true,
|
|
547
|
+
description: "API endpoint to call (e.g., 'GetApplication' or 'tailor.v1.OperatorService/GetApplication').",
|
|
548
|
+
completion: { custom: { choices: listMethodChoices() } }
|
|
549
|
+
})
|
|
550
|
+
}),
|
|
551
|
+
run: async (args) => {
|
|
552
|
+
await assertWritable({ profile: args.profile });
|
|
553
|
+
const methodName = extractMethodName(args.endpoint);
|
|
554
|
+
const method = getMethodDescriptor(methodName);
|
|
555
|
+
const parsedBody = parseBodyAsObject(args.body);
|
|
556
|
+
let mutated = false;
|
|
557
|
+
if (args.field && args.field.length > 0) {
|
|
558
|
+
if (!parsedBody) throw new Error("--field requires --body to be a JSON object (or omitted).");
|
|
559
|
+
for (const f of args.field) {
|
|
560
|
+
const leaf = method ? resolveLeafField(method.input, f.path) : void 0;
|
|
561
|
+
setNestedPath(parsedBody, f.path, coerceFieldValue(leaf, f.value));
|
|
562
|
+
}
|
|
563
|
+
mutated = true;
|
|
564
|
+
}
|
|
565
|
+
if (parsedBody && method) {
|
|
566
|
+
if (normalizeBodyFieldKeys(parsedBody, method.input.fields)) mutated = true;
|
|
567
|
+
const fieldNames = method.input.fields.map((f) => f.localName);
|
|
568
|
+
if (fieldNames.includes("workspaceId") && !Object.hasOwn(parsedBody, "workspaceId")) try {
|
|
569
|
+
parsedBody.workspaceId = await loadWorkspaceId({
|
|
570
|
+
workspaceId: args["workspace-id"],
|
|
571
|
+
profile: args.profile
|
|
572
|
+
});
|
|
573
|
+
mutated = true;
|
|
574
|
+
} catch {}
|
|
575
|
+
if (fieldNames.includes("namespaceName") && !Object.hasOwn(parsedBody, "namespaceName")) try {
|
|
576
|
+
const { config } = await loadConfig(args.config);
|
|
577
|
+
const ns = resolveNamespaceName(methodName, config);
|
|
578
|
+
if (ns) {
|
|
579
|
+
parsedBody.namespaceName = ns;
|
|
580
|
+
mutated = true;
|
|
581
|
+
}
|
|
582
|
+
} catch {}
|
|
583
|
+
}
|
|
584
|
+
const result = await apiCall({
|
|
585
|
+
profile: args.profile,
|
|
586
|
+
endpoint: args.endpoint,
|
|
587
|
+
body: mutated && parsedBody ? JSON.stringify(parsedBody) : args.body
|
|
588
|
+
});
|
|
589
|
+
logger.out(JSON.stringify(result.data, null, 2));
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
//#endregion
|
|
44
594
|
//#region src/cli/commands/auth/token.ts
|
|
45
595
|
const tokenCommand = defineAppCommand({
|
|
46
596
|
name: "token",
|
|
@@ -396,7 +946,7 @@ const listCommand$6 = defineAppCommand({
|
|
|
396
946
|
}
|
|
397
947
|
let entries;
|
|
398
948
|
try {
|
|
399
|
-
entries = fs
|
|
949
|
+
entries = fs.readdirSync(config.localDir);
|
|
400
950
|
} catch {
|
|
401
951
|
logger.info("No crash reports found.");
|
|
402
952
|
if (jsonOutput) logger.out([]);
|
|
@@ -436,7 +986,7 @@ const sendCommand = defineAppCommand({
|
|
|
436
986
|
run: async (args) => {
|
|
437
987
|
let content;
|
|
438
988
|
try {
|
|
439
|
-
content = fs
|
|
989
|
+
content = fs.readFileSync(args.file, "utf-8");
|
|
440
990
|
} catch {
|
|
441
991
|
logger.error(`Crash report file not found: ${args.file}`);
|
|
442
992
|
process.exit(1);
|
|
@@ -491,13 +1041,24 @@ const crashReportCommand = defineCommand({
|
|
|
491
1041
|
|
|
492
1042
|
//#endregion
|
|
493
1043
|
//#region src/cli/commands/deploy/index.ts
|
|
494
|
-
const deployCommand
|
|
1044
|
+
const deployCommand = defineAppCommand({
|
|
495
1045
|
name: "deploy",
|
|
496
1046
|
description: "Deploy your application by applying the Tailor configuration.",
|
|
497
1047
|
args: z.strictObject({
|
|
498
1048
|
...workspaceArgs,
|
|
499
1049
|
...multiConfigArg,
|
|
500
1050
|
...confirmationArgs,
|
|
1051
|
+
"create-workspace": arg(z.boolean().optional(), { description: "Create a workspace when the account has none" }),
|
|
1052
|
+
"workspace-name": arg(z.string().optional(), { description: "Name for a workspace created during deploy" }),
|
|
1053
|
+
"workspace-region": arg(z.string().optional(), { description: "Region for a workspace created during deploy" }),
|
|
1054
|
+
"organization-id": arg(z.string().optional(), {
|
|
1055
|
+
description: "Organization ID for a workspace created during deploy",
|
|
1056
|
+
env: "TAILOR_PLATFORM_ORGANIZATION_ID"
|
|
1057
|
+
}),
|
|
1058
|
+
"folder-id": arg(z.string().optional(), {
|
|
1059
|
+
description: "Folder ID for a workspace created during deploy",
|
|
1060
|
+
env: "TAILOR_PLATFORM_FOLDER_ID"
|
|
1061
|
+
}),
|
|
501
1062
|
"dry-run": arg(z.boolean().optional(), {
|
|
502
1063
|
alias: "d",
|
|
503
1064
|
description: "Run the command without making any changes"
|
|
@@ -511,16 +1072,26 @@ const deployCommand$1 = defineAppCommand({
|
|
|
511
1072
|
await assertWritable({ profile: args.profile });
|
|
512
1073
|
const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
|
|
513
1074
|
await initTelemetry();
|
|
514
|
-
await
|
|
1075
|
+
await deployFromCLI({
|
|
515
1076
|
workspaceId: args["workspace-id"],
|
|
516
1077
|
profile: args.profile,
|
|
517
1078
|
configPath: args.config,
|
|
518
1079
|
dryRun: args["dry-run"],
|
|
519
1080
|
yes: args.yes,
|
|
1081
|
+
createWorkspace: args["create-workspace"],
|
|
1082
|
+
workspaceName: args["workspace-name"],
|
|
1083
|
+
workspaceRegion: args["workspace-region"],
|
|
1084
|
+
organizationId: args["organization-id"],
|
|
1085
|
+
folderId: args["folder-id"],
|
|
520
1086
|
noSchemaCheck: args["no-schema-check"],
|
|
521
1087
|
noValidate: args["no-validate"],
|
|
522
1088
|
noCache: args["no-cache"],
|
|
523
1089
|
cleanCache: args["clean-cache"]
|
|
1090
|
+
}, {
|
|
1091
|
+
envFile: args["env-file"],
|
|
1092
|
+
envFileIfExists: args["env-file-if-exists"],
|
|
1093
|
+
verbose: args.verbose,
|
|
1094
|
+
json: args.json
|
|
524
1095
|
});
|
|
525
1096
|
}
|
|
526
1097
|
});
|
|
@@ -533,12 +1104,12 @@ const executorCommand = defineCommand({
|
|
|
533
1104
|
subCommands: {
|
|
534
1105
|
trigger: triggerCommand,
|
|
535
1106
|
jobs: jobsCommand,
|
|
536
|
-
list: listCommand$
|
|
1107
|
+
list: listCommand$8,
|
|
537
1108
|
get: getCommand$1,
|
|
538
1109
|
webhook: webhookCommand
|
|
539
1110
|
},
|
|
540
1111
|
async run() {
|
|
541
|
-
await runCommand(listCommand$
|
|
1112
|
+
await runCommand(listCommand$8, []);
|
|
542
1113
|
}
|
|
543
1114
|
});
|
|
544
1115
|
|
|
@@ -1119,12 +1690,12 @@ async function bundleForTestRun(options) {
|
|
|
1119
1690
|
const inlineSourcemap = resolveInlineSourcemap(options.inlineSourcemap);
|
|
1120
1691
|
const bundleLogLevel = resolveBundleLogLevel(options.logLevel);
|
|
1121
1692
|
const outputDir = path.resolve(getDistDir(), "test-run");
|
|
1122
|
-
fs
|
|
1693
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
1123
1694
|
const baseName = `test-run--${detected.name}`;
|
|
1124
1695
|
const scriptName = `${baseName}.js`;
|
|
1125
1696
|
const entryPath = path.join(outputDir, `${baseName}.entry.js`);
|
|
1126
1697
|
const entryContent = generateEntry(detected, sourceFile, env, machineUser, workspaceId);
|
|
1127
|
-
fs
|
|
1698
|
+
fs.writeFileSync(entryPath, entryContent);
|
|
1128
1699
|
let tsconfig;
|
|
1129
1700
|
try {
|
|
1130
1701
|
tsconfig = await resolveTSConfig();
|
|
@@ -1273,8 +1844,11 @@ async function detectFunctionType(options) {
|
|
|
1273
1844
|
const rawInput = module.default.input;
|
|
1274
1845
|
let inputSchema;
|
|
1275
1846
|
if (rawInput) {
|
|
1276
|
-
const
|
|
1277
|
-
inputSchema =
|
|
1847
|
+
const fields = rawInput;
|
|
1848
|
+
inputSchema = { parse: (args) => parseInputFields({
|
|
1849
|
+
...args,
|
|
1850
|
+
fields
|
|
1851
|
+
}) };
|
|
1278
1852
|
}
|
|
1279
1853
|
return {
|
|
1280
1854
|
type: "resolver",
|
|
@@ -1420,7 +1994,7 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
|
|
|
1420
1994
|
run: async (args) => {
|
|
1421
1995
|
const jsonOutput = logger.jsonMode;
|
|
1422
1996
|
const filePath = path.resolve(args.file);
|
|
1423
|
-
if (!fs
|
|
1997
|
+
if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
1424
1998
|
const { config } = await loadConfig(args.config);
|
|
1425
1999
|
const authNamespace = resolveAuthNamespace(config.auth);
|
|
1426
2000
|
const machineUserName = await resolveMachineUserName({
|
|
@@ -1449,7 +2023,7 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
|
|
|
1449
2023
|
let functionName;
|
|
1450
2024
|
if (isPreBundled) {
|
|
1451
2025
|
scriptName = path.basename(filePath);
|
|
1452
|
-
bundledCode = fs
|
|
2026
|
+
bundledCode = fs.readFileSync(filePath, "utf-8");
|
|
1453
2027
|
logger.info(`Using pre-bundled script ${styles.bold(scriptName)}`);
|
|
1454
2028
|
} else {
|
|
1455
2029
|
logger.info(`Detecting function type from ${styles.path(relativePath)}`);
|
|
@@ -1603,12 +2177,12 @@ const functionCommand = defineCommand({
|
|
|
1603
2177
|
description: "Manage functions",
|
|
1604
2178
|
subCommands: {
|
|
1605
2179
|
get: getCommand$2,
|
|
1606
|
-
list: listCommand$
|
|
2180
|
+
list: listCommand$9,
|
|
1607
2181
|
logs: logsCommand,
|
|
1608
2182
|
"test-run": testRunCommand
|
|
1609
2183
|
},
|
|
1610
2184
|
async run() {
|
|
1611
|
-
await runCommand(listCommand$
|
|
2185
|
+
await runCommand(listCommand$9, []);
|
|
1612
2186
|
}
|
|
1613
2187
|
});
|
|
1614
2188
|
|
|
@@ -1617,23 +2191,14 @@ const functionCommand = defineCommand({
|
|
|
1617
2191
|
const generateCommand = defineAppCommand({
|
|
1618
2192
|
name: "generate",
|
|
1619
2193
|
description: "Generate files using Tailor configuration.",
|
|
1620
|
-
args: z.strictObject({
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
}),
|
|
1625
|
-
watch: arg(z.boolean().default(false), {
|
|
1626
|
-
alias: "W",
|
|
1627
|
-
description: "Watch for type/resolver changes and regenerate"
|
|
1628
|
-
})
|
|
1629
|
-
}),
|
|
2194
|
+
args: z.strictObject({ config: arg(z.string().default("tailor.config.ts"), {
|
|
2195
|
+
alias: "c",
|
|
2196
|
+
description: "Path to SDK config file"
|
|
2197
|
+
}) }),
|
|
1630
2198
|
run: async (args) => {
|
|
1631
2199
|
const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
|
|
1632
2200
|
await initTelemetry();
|
|
1633
|
-
await generate({
|
|
1634
|
-
configPath: args.config,
|
|
1635
|
-
watch: args.watch
|
|
1636
|
-
});
|
|
2201
|
+
await generate({ configPath: args.config });
|
|
1637
2202
|
}
|
|
1638
2203
|
});
|
|
1639
2204
|
|
|
@@ -1692,8 +2257,47 @@ const redirectUri = `http://localhost:${redirectPort}/callback`;
|
|
|
1692
2257
|
function randomState() {
|
|
1693
2258
|
return crypto.randomBytes(32).toString("base64url");
|
|
1694
2259
|
}
|
|
1695
|
-
function
|
|
1696
|
-
if (args.profile
|
|
2260
|
+
function getProfileUserMismatch(args, authenticatedUser, authenticatedSubject) {
|
|
2261
|
+
if (!args.profile || !args.profileUser || authenticatedUser === args.profileUser || authenticatedSubject === args.profileUser) return;
|
|
2262
|
+
return {
|
|
2263
|
+
profile: args.profile,
|
|
2264
|
+
oldUser: args.profileUser,
|
|
2265
|
+
authenticatedUser,
|
|
2266
|
+
loginMode: args.loginMode ?? "user"
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
function quoteCommandArg(value, platform = process.platform) {
|
|
2270
|
+
if (platform === "win32") {
|
|
2271
|
+
if (/^[A-Za-z0-9_./:@+=,-]+$/.test(value)) return value;
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
2275
|
+
}
|
|
2276
|
+
function profileUpdateCommand(mismatch) {
|
|
2277
|
+
const profileArg = quoteCommandArg(mismatch.profile);
|
|
2278
|
+
const userArg = quoteCommandArg(mismatch.authenticatedUser);
|
|
2279
|
+
if (profileArg && userArg) return `tailor-sdk profile update --user ${userArg} -- ${profileArg}`;
|
|
2280
|
+
return [
|
|
2281
|
+
"tailor-sdk profile update --user <authenticated-user> -- <profile>",
|
|
2282
|
+
`profile = ${JSON.stringify(mismatch.profile)}`,
|
|
2283
|
+
`authenticated user = ${JSON.stringify(mismatch.authenticatedUser)}`
|
|
2284
|
+
].join("\n");
|
|
2285
|
+
}
|
|
2286
|
+
function retryInstruction(mismatch) {
|
|
2287
|
+
if (mismatch.loginMode === "machine-user") return "Then retry the original machine-user login command.";
|
|
2288
|
+
const profileArg = quoteCommandArg(mismatch.profile);
|
|
2289
|
+
if (!profileArg) return "Then retry the browser login with the same profile value.";
|
|
2290
|
+
return `Then run:\n tailor-sdk login --profile ${profileArg}`;
|
|
2291
|
+
}
|
|
2292
|
+
function profileUserMismatchError(mismatch) {
|
|
2293
|
+
const updateCommand = profileUpdateCommand(mismatch);
|
|
2294
|
+
const nextStep = retryInstruction(mismatch);
|
|
2295
|
+
return new Error(multiline`
|
|
2296
|
+
Profile "${mismatch.profile}" is configured for "${mismatch.oldUser}", but login authenticated "${mismatch.authenticatedUser}".
|
|
2297
|
+
The authenticated user has been saved. To use it with this profile, run:
|
|
2298
|
+
${updateCommand}
|
|
2299
|
+
${nextStep}
|
|
2300
|
+
`);
|
|
1697
2301
|
}
|
|
1698
2302
|
function shouldUpdateCurrentUser(profile, platformConfig) {
|
|
1699
2303
|
if (!profile) return true;
|
|
@@ -1713,7 +2317,6 @@ const startAuthServer = async (args = {}) => {
|
|
|
1713
2317
|
codeVerifier
|
|
1714
2318
|
});
|
|
1715
2319
|
const userInfo = await fetchUserInfo(tokens.accessToken, args.platformConfig);
|
|
1716
|
-
assertProfileLoginUser(args, userInfo.email, userInfo.sub);
|
|
1717
2320
|
const pfConfig = await readPlatformConfig();
|
|
1718
2321
|
await saveUserTokens(pfConfig, userInfo.sub, {
|
|
1719
2322
|
accessToken: tokens.accessToken,
|
|
@@ -1722,6 +2325,11 @@ const startAuthServer = async (args = {}) => {
|
|
|
1722
2325
|
platformConfig: args.platformConfig,
|
|
1723
2326
|
email: userInfo.email
|
|
1724
2327
|
});
|
|
2328
|
+
const mismatch = getProfileUserMismatch(args, userInfo.email, userInfo.sub);
|
|
2329
|
+
if (mismatch) {
|
|
2330
|
+
writePlatformConfig(pfConfig);
|
|
2331
|
+
throw profileUserMismatchError(mismatch);
|
|
2332
|
+
}
|
|
1725
2333
|
if (args.updateCurrentUser ?? true) pfConfig.current_user = userInfo.sub;
|
|
1726
2334
|
await removeLegacyUserAlias(pfConfig, userInfo.email, userInfo.sub, args.platformConfig);
|
|
1727
2335
|
writePlatformConfig(pfConfig);
|
|
@@ -1765,11 +2373,15 @@ const startAuthServer = async (args = {}) => {
|
|
|
1765
2373
|
});
|
|
1766
2374
|
};
|
|
1767
2375
|
async function loginAsMachineUser(args) {
|
|
1768
|
-
assertProfileLoginUser(args, args.clientId);
|
|
1769
2376
|
const clientSecret = args.clientSecret ?? await prompt.password({ message: "Client secret" });
|
|
1770
2377
|
const tokens = await fetchPlatformMachineUserToken(args.clientId, clientSecret, args.platformConfig);
|
|
1771
2378
|
const pfConfig = await readPlatformConfig();
|
|
1772
2379
|
await saveUserTokens(pfConfig, args.clientId, { accessToken: tokens.accessToken }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString(), { platformConfig: args.platformConfig });
|
|
2380
|
+
const mismatch = getProfileUserMismatch(args, args.clientId);
|
|
2381
|
+
if (mismatch) {
|
|
2382
|
+
writePlatformConfig(pfConfig);
|
|
2383
|
+
throw profileUserMismatchError(mismatch);
|
|
2384
|
+
}
|
|
1773
2385
|
if (args.updateCurrentUser ?? true) pfConfig.current_user = args.clientId;
|
|
1774
2386
|
writePlatformConfig(pfConfig);
|
|
1775
2387
|
}
|
|
@@ -1801,31 +2413,36 @@ const loginCommand = defineAppCommand({
|
|
|
1801
2413
|
})
|
|
1802
2414
|
}).describe("Machine User Login")]),
|
|
1803
2415
|
run: async (args) => {
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
2416
|
+
try {
|
|
2417
|
+
let platformConfig;
|
|
2418
|
+
let profileUser;
|
|
2419
|
+
if ("profile" in args && args.profile) {
|
|
2420
|
+
const profileEntry = (await readPlatformConfig()).profiles[args.profile];
|
|
2421
|
+
if (!profileEntry) throw new Error(`Profile "${args.profile}" not found`);
|
|
2422
|
+
platformConfig = platformConfigFromProfile(profileEntry);
|
|
2423
|
+
profileUser = profileEntry.user;
|
|
2424
|
+
}
|
|
2425
|
+
const updateCurrentUser = shouldUpdateCurrentUser(args.profile, platformConfig);
|
|
2426
|
+
if ("machine-user" in args) await loginAsMachineUser({
|
|
2427
|
+
clientId: args.clientId,
|
|
2428
|
+
clientSecret: args.clientSecret,
|
|
2429
|
+
profile: args.profile,
|
|
2430
|
+
profileUser,
|
|
2431
|
+
platformConfig,
|
|
2432
|
+
updateCurrentUser,
|
|
2433
|
+
loginMode: "machine-user"
|
|
2434
|
+
});
|
|
2435
|
+
else await startAuthServer({
|
|
2436
|
+
profile: args.profile,
|
|
2437
|
+
profileUser,
|
|
2438
|
+
platformConfig,
|
|
2439
|
+
updateCurrentUser,
|
|
2440
|
+
loginMode: "user"
|
|
2441
|
+
});
|
|
2442
|
+
logger.success("Successfully logged in to Tailor Platform.");
|
|
2443
|
+
} finally {
|
|
2444
|
+
await closeConnectionPool();
|
|
1811
2445
|
}
|
|
1812
|
-
const updateCurrentUser = shouldUpdateCurrentUser(args.profile, platformConfig);
|
|
1813
|
-
if ("machine-user" in args) await loginAsMachineUser({
|
|
1814
|
-
clientId: args.clientId,
|
|
1815
|
-
clientSecret: args.clientSecret,
|
|
1816
|
-
profile: args.profile,
|
|
1817
|
-
profileUser,
|
|
1818
|
-
platformConfig,
|
|
1819
|
-
updateCurrentUser
|
|
1820
|
-
});
|
|
1821
|
-
else await startAuthServer({
|
|
1822
|
-
profile: args.profile,
|
|
1823
|
-
profileUser,
|
|
1824
|
-
platformConfig,
|
|
1825
|
-
updateCurrentUser
|
|
1826
|
-
});
|
|
1827
|
-
logger.success("Successfully logged in to Tailor Platform.");
|
|
1828
|
-
await closeConnectionPool();
|
|
1829
2446
|
}
|
|
1830
2447
|
});
|
|
1831
2448
|
|
|
@@ -1892,11 +2509,11 @@ const machineuserCommand = defineCommand({
|
|
|
1892
2509
|
name: "machineuser",
|
|
1893
2510
|
description: "Manage machine users in your Tailor Platform application.",
|
|
1894
2511
|
subCommands: {
|
|
1895
|
-
list: listCommand$
|
|
2512
|
+
list: listCommand$10,
|
|
1896
2513
|
token: tokenCommand$1
|
|
1897
2514
|
},
|
|
1898
2515
|
async run() {
|
|
1899
|
-
await runCommand(listCommand$
|
|
2516
|
+
await runCommand(listCommand$10, []);
|
|
1900
2517
|
}
|
|
1901
2518
|
});
|
|
1902
2519
|
|
|
@@ -1906,11 +2523,11 @@ const oauth2clientCommand = defineCommand({
|
|
|
1906
2523
|
name: "oauth2client",
|
|
1907
2524
|
description: "Manage OAuth2 clients in your Tailor Platform application.",
|
|
1908
2525
|
subCommands: {
|
|
1909
|
-
list: listCommand$
|
|
2526
|
+
list: listCommand$11,
|
|
1910
2527
|
get: getCommand$3
|
|
1911
2528
|
},
|
|
1912
2529
|
async run() {
|
|
1913
|
-
await runCommand(listCommand$
|
|
2530
|
+
await runCommand(listCommand$11, []);
|
|
1914
2531
|
}
|
|
1915
2532
|
});
|
|
1916
2533
|
|
|
@@ -1967,7 +2584,7 @@ const folderCommand = defineCommand({
|
|
|
1967
2584
|
create: createCommand$3,
|
|
1968
2585
|
delete: deleteCommand$4,
|
|
1969
2586
|
get: getCommand$4,
|
|
1970
|
-
list: listCommand$
|
|
2587
|
+
list: listCommand$12,
|
|
1971
2588
|
update: updateCommand$2
|
|
1972
2589
|
}
|
|
1973
2590
|
});
|
|
@@ -1980,12 +2597,12 @@ const organizationCommand = defineCommand({
|
|
|
1980
2597
|
subCommands: {
|
|
1981
2598
|
folder: folderCommand,
|
|
1982
2599
|
get: getCommand$5,
|
|
1983
|
-
list: listCommand$
|
|
2600
|
+
list: listCommand$13,
|
|
1984
2601
|
tree: treeCommand,
|
|
1985
2602
|
update: updateCommand$3
|
|
1986
2603
|
},
|
|
1987
2604
|
async run() {
|
|
1988
|
-
await runCommand(listCommand$
|
|
2605
|
+
await runCommand(listCommand$13, []);
|
|
1989
2606
|
}
|
|
1990
2607
|
});
|
|
1991
2608
|
|
|
@@ -2033,6 +2650,12 @@ const BUILTIN_COMMAND_NAMES = [
|
|
|
2033
2650
|
|
|
2034
2651
|
//#endregion
|
|
2035
2652
|
//#region src/cli/shared/plugin.ts
|
|
2653
|
+
/**
|
|
2654
|
+
* npm packages known to provide a plugin slug, keyed by the slug the
|
|
2655
|
+
* dispatcher resolves (command path joined with `-`). Used to suggest an
|
|
2656
|
+
* install command when the plugin executable is not found.
|
|
2657
|
+
*/
|
|
2658
|
+
const KNOWN_PLUGIN_PACKAGES = { "tailordb-erd": "@tailor-platform/sdk-tailordb-erd-plugin" };
|
|
2036
2659
|
/** PATH entry separator (`;` on Windows, `:` elsewhere). */
|
|
2037
2660
|
const pathDelimiter = process.platform === "win32" ? ";" : ":";
|
|
2038
2661
|
/**
|
|
@@ -2210,17 +2833,7 @@ async function resolveActiveUser(profile) {
|
|
|
2210
2833
|
*/
|
|
2211
2834
|
async function buildPluginEnv(options = {}) {
|
|
2212
2835
|
const { profile } = options;
|
|
2213
|
-
|
|
2214
|
-
try {
|
|
2215
|
-
platformConfig = await loadPlatformClientConfig({
|
|
2216
|
-
profile,
|
|
2217
|
-
allowMissingProfile: true
|
|
2218
|
-
});
|
|
2219
|
-
} catch {}
|
|
2220
|
-
const env = {
|
|
2221
|
-
TAILOR_PLATFORM_URL: getPlatformBaseUrl(platformConfig),
|
|
2222
|
-
TAILOR_PLATFORM_OAUTH2_CLIENT_ID: getOAuth2ClientId(platformConfig)
|
|
2223
|
-
};
|
|
2836
|
+
const env = {};
|
|
2224
2837
|
const binPath = process.argv[1];
|
|
2225
2838
|
if (binPath) env.TAILOR_BIN = binPath;
|
|
2226
2839
|
try {
|
|
@@ -2229,6 +2842,16 @@ async function buildPluginEnv(options = {}) {
|
|
|
2229
2842
|
} catch {}
|
|
2230
2843
|
const configPath = loadConfigPath();
|
|
2231
2844
|
if (configPath) env.TAILOR_CONFIG_PATH = configPath;
|
|
2845
|
+
if (options.skipPlatformContext) return env;
|
|
2846
|
+
let platformConfig;
|
|
2847
|
+
try {
|
|
2848
|
+
platformConfig = await loadPlatformClientConfig({
|
|
2849
|
+
profile,
|
|
2850
|
+
allowMissingProfile: true
|
|
2851
|
+
});
|
|
2852
|
+
} catch {}
|
|
2853
|
+
env.TAILOR_PLATFORM_URL = getPlatformBaseUrl(platformConfig);
|
|
2854
|
+
env.TAILOR_PLATFORM_OAUTH2_CLIENT_ID = getOAuth2ClientId(platformConfig);
|
|
2232
2855
|
try {
|
|
2233
2856
|
env.TAILOR_PLATFORM_WORKSPACE_ID = await loadWorkspaceId({ profile });
|
|
2234
2857
|
} catch {}
|
|
@@ -2300,21 +2923,72 @@ function buildSpawnTarget(pluginPath, args) {
|
|
|
2300
2923
|
};
|
|
2301
2924
|
}
|
|
2302
2925
|
/**
|
|
2926
|
+
* Check whether args forwarded to a plugin carry an explicit env-file flag.
|
|
2927
|
+
* Scanning stops at a `--` terminator. The check is presence-only:
|
|
2928
|
+
* `--env-file-if-exists` counts even when the named file does not exist, so
|
|
2929
|
+
* no platform context is injected in that case either.
|
|
2930
|
+
* @param args - Args forwarded to the plugin
|
|
2931
|
+
* @returns Whether an `--env-file`/`-e`/`--env-file-if-exists` flag is present
|
|
2932
|
+
*/
|
|
2933
|
+
function hasEnvFileFlag(args) {
|
|
2934
|
+
for (const token of args) {
|
|
2935
|
+
if (token === "--") return false;
|
|
2936
|
+
if (token === "--env-file" || token === "-e" || token === "--env-file-if-exists") return true;
|
|
2937
|
+
if (token.startsWith("--env-file=") || token.startsWith("-e=") || token.startsWith("--env-file-if-exists=")) return true;
|
|
2938
|
+
}
|
|
2939
|
+
return false;
|
|
2940
|
+
}
|
|
2941
|
+
/**
|
|
2942
|
+
* Extract an explicit `--profile`/`-p` value from args forwarded to a plugin,
|
|
2943
|
+
* so the injected platform context matches the profile the plugin will use.
|
|
2944
|
+
* Scanning stops at a `--` terminator; the last occurrence wins.
|
|
2945
|
+
* @param args - Args forwarded to the plugin
|
|
2946
|
+
* @returns The explicit profile value, or undefined when not present
|
|
2947
|
+
*/
|
|
2948
|
+
function explicitProfileFromArgs(args) {
|
|
2949
|
+
let profile;
|
|
2950
|
+
for (let i = 0; i < args.length; i++) {
|
|
2951
|
+
const token = args[i];
|
|
2952
|
+
if (token === void 0 || token === "--") break;
|
|
2953
|
+
if (token === "--profile" || token === "-p") {
|
|
2954
|
+
const next = args[i + 1];
|
|
2955
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
2956
|
+
profile = next;
|
|
2957
|
+
i++;
|
|
2958
|
+
}
|
|
2959
|
+
continue;
|
|
2960
|
+
}
|
|
2961
|
+
if (token.startsWith("--profile=")) profile = token.slice(10);
|
|
2962
|
+
else if (token.startsWith("-p=")) profile = token.slice(3);
|
|
2963
|
+
}
|
|
2964
|
+
return profile || void 0;
|
|
2965
|
+
}
|
|
2966
|
+
/**
|
|
2967
|
+
* Build the plugin slug from the known command path plus the unknown name,
|
|
2968
|
+
* so `tailor tailordb erd` resolves `tailor-tailordb-erd`.
|
|
2969
|
+
* @param commandPath - Parent command path preceding the unknown subcommand
|
|
2970
|
+
* @param name - Unknown subcommand name
|
|
2971
|
+
* @returns The plugin slug
|
|
2972
|
+
*/
|
|
2973
|
+
function pluginSlug(commandPath, name) {
|
|
2974
|
+
return [...commandPath ?? [], name].join("-");
|
|
2975
|
+
}
|
|
2976
|
+
/**
|
|
2303
2977
|
* Resolve and execute a plugin, forwarding stdio and propagating its exit code.
|
|
2304
2978
|
* @param params - Dispatch parameters
|
|
2305
|
-
* @param params.name - Plugin name (without the `<cli>-` prefix)
|
|
2306
|
-
* @param params.args - Args to forward to the plugin
|
|
2307
|
-
* @param params.cliName - The host CLI name
|
|
2308
|
-
* @param params.profile - Active profile name, if any
|
|
2309
2979
|
* @returns The plugin's exit code, or undefined when no matching plugin exists
|
|
2310
2980
|
*/
|
|
2311
2981
|
async function dispatchPlugin(params) {
|
|
2312
|
-
const slug =
|
|
2982
|
+
const slug = pluginSlug(params.commandPath, params.name);
|
|
2313
2983
|
const plugin = resolvePlugin(slug, params.cliName);
|
|
2314
2984
|
if (!plugin) return;
|
|
2985
|
+
const profile = explicitProfileFromArgs(params.args) ?? params.profile;
|
|
2315
2986
|
const env = {
|
|
2316
2987
|
...process.env,
|
|
2317
|
-
...await buildPluginEnv({
|
|
2988
|
+
...await buildPluginEnv({
|
|
2989
|
+
profile,
|
|
2990
|
+
skipPlatformContext: hasEnvFileFlag(params.args)
|
|
2991
|
+
})
|
|
2318
2992
|
};
|
|
2319
2993
|
const { command, args, shell } = buildSpawnTarget(plugin.path, params.args);
|
|
2320
2994
|
return await new Promise((resolve) => {
|
|
@@ -2333,6 +3007,27 @@ async function dispatchPlugin(params) {
|
|
|
2333
3007
|
});
|
|
2334
3008
|
});
|
|
2335
3009
|
}
|
|
3010
|
+
/**
|
|
3011
|
+
* Dispatch a plugin, printing an install hint when the subcommand maps to a
|
|
3012
|
+
* known plugin package that is not installed.
|
|
3013
|
+
* @param params - Dispatch parameters
|
|
3014
|
+
* @returns The plugin's exit code (1 after an install hint), or undefined when
|
|
3015
|
+
* the subcommand matches no plugin and no known package
|
|
3016
|
+
*/
|
|
3017
|
+
async function dispatchPluginWithInstallHint(params) {
|
|
3018
|
+
const exitCode = await dispatchPlugin(params);
|
|
3019
|
+
if (exitCode !== void 0) return exitCode;
|
|
3020
|
+
const knownPackage = KNOWN_PLUGIN_PACKAGES[pluginSlug(params.commandPath, params.name)];
|
|
3021
|
+
if (!knownPackage) return;
|
|
3022
|
+
const fullCommand = [
|
|
3023
|
+
params.cliName,
|
|
3024
|
+
...params.commandPath ?? [],
|
|
3025
|
+
params.name
|
|
3026
|
+
].join(" ");
|
|
3027
|
+
logger.error(`"${fullCommand}" is provided by the ${knownPackage} CLI plugin, which is not installed.`);
|
|
3028
|
+
logger.info(`Install it with: npm install -D ${knownPackage}`);
|
|
3029
|
+
return 1;
|
|
3030
|
+
}
|
|
2336
3031
|
|
|
2337
3032
|
//#endregion
|
|
2338
3033
|
//#region src/cli/commands/plugin/list.ts
|
|
@@ -3135,10 +3830,10 @@ function lockPath(outputDir) {
|
|
|
3135
3830
|
*/
|
|
3136
3831
|
function readLock(outputDir) {
|
|
3137
3832
|
const file = lockPath(outputDir);
|
|
3138
|
-
if (!fs
|
|
3833
|
+
if (!fs.existsSync(file)) return null;
|
|
3139
3834
|
let parsed;
|
|
3140
3835
|
try {
|
|
3141
|
-
parsed = JSON.parse(fs
|
|
3836
|
+
parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
3142
3837
|
} catch (cause) {
|
|
3143
3838
|
throw new Error(`${LOCK_FILENAME} is not valid JSON. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor.lock) and re-run setup.`, { cause });
|
|
3144
3839
|
}
|
|
@@ -3154,8 +3849,8 @@ function readLock(outputDir) {
|
|
|
3154
3849
|
*/
|
|
3155
3850
|
function writeLock(outputDir, lock) {
|
|
3156
3851
|
const file = lockPath(outputDir);
|
|
3157
|
-
fs
|
|
3158
|
-
fs
|
|
3852
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
3853
|
+
fs.writeFileSync(file, `${JSON.stringify(lock, null, 2)}\n`, "utf-8");
|
|
3159
3854
|
}
|
|
3160
3855
|
/**
|
|
3161
3856
|
* Find a lock target by identity. Targets are identified by (kind,
|
|
@@ -3171,23 +3866,23 @@ function findTarget(lock, kind, workspaceName) {
|
|
|
3171
3866
|
|
|
3172
3867
|
//#endregion
|
|
3173
3868
|
//#region src/cli/commands/setup/action.yml
|
|
3174
|
-
var action_default = "# __HEADER__\nname: __WORKSPACE_NAME__\ndescription: >-\n Deploy __WORKSPACE_NAME__ to Tailor Platform.\n Checkout and dependency installation are expected to be handled by the caller.\n\ninputs:\n workspace-id:\n description: Tailor Platform workspace id\n required: true\n name:\n description: Workspace name (used in Slack notifications)\n required: true\n working-directory:\n description: App directory relative to the repository root\n required: false\n default: \".\"\n config:\n description: SDK config path, or comma-separated config paths for multi-config deploys\n required: false\n default: \"tailor.config.ts\"\n deploy:\n description: Whether to run the Tailor deploy step after build steps\n required: false\n default: \"true\"\n build-site:\n description: Whether to run the static website build slot when present\n required: false\n default: \"true\"\n platform-client-id:\n description: Machine-user client id\n required: true\n platform-client-secret:\n description: Machine-user client secret\n required: true\n slack-token:\n description: Slack bot token\n required: false\n slack-channel-id:\n description: Slack channel id\n required: false\n user-mapping:\n description: JSON string mapping GitHub usernames to Slack user IDs\n required: false\n package-manager:\n description: Package manager used for dependency installation (pnpm, npm, yarn, bun)\n required: false\n default: pnpm\n\noutputs:\n app-url:\n description: Deployed application URL\n value: ${{ steps.tailor-apply.outputs.app-url }}\n\nruns:\n using: composite\n steps:\n # __STATIC_WEBSITE_BUILD_START__\n - id: build-site\n if: inputs.build-site == 'true'\n shell: bash\n run: |\n # Replace with your static website build command.\n # Example: pnpm --filter <package-name> run build:static\n # Note: this run: command is user-editable. Custom build commands are preserved across\n # reruns and are not treated as drift by setup check. Other edits to this file are flagged\n # as drift and will be overwritten if you re-run setup action with --force.\n true\n # __STATIC_WEBSITE_BUILD_END__\n - id: tailor-apply\n if: inputs.deploy == 'true'\n uses: tailor-platform/actions/deploy@
|
|
3869
|
+
var action_default = "# __HEADER__\nname: __WORKSPACE_NAME__\ndescription: >-\n Deploy __WORKSPACE_NAME__ to Tailor Platform.\n Checkout and dependency installation are expected to be handled by the caller.\n\ninputs:\n workspace-id:\n description: Tailor Platform workspace id\n required: true\n name:\n description: Workspace name (used in Slack notifications)\n required: true\n working-directory:\n description: App directory relative to the repository root\n required: false\n default: \".\"\n config:\n description: SDK config path, or comma-separated config paths for multi-config deploys\n required: false\n default: \"tailor.config.ts\"\n deploy:\n description: Whether to run the Tailor deploy step after build steps\n required: false\n default: \"true\"\n build-site:\n description: Whether to run the static website build slot when present\n required: false\n default: \"true\"\n platform-client-id:\n description: Machine-user client id\n required: true\n platform-client-secret:\n description: Machine-user client secret\n required: true\n slack-token:\n description: Slack bot token\n required: false\n slack-channel-id:\n description: Slack channel id\n required: false\n user-mapping:\n description: JSON string mapping GitHub usernames to Slack user IDs\n required: false\n package-manager:\n description: Package manager used for dependency installation (pnpm, npm, yarn, bun)\n required: false\n default: pnpm\n\noutputs:\n app-url:\n description: Deployed application URL\n value: ${{ steps.tailor-apply.outputs.app-url }}\n\nruns:\n using: composite\n steps:\n # __STATIC_WEBSITE_BUILD_START__\n - id: build-site\n if: inputs.build-site == 'true'\n shell: bash\n run: |\n # Replace with your static website build command.\n # Example: pnpm --filter <package-name> run build:static\n # Note: this run: command is user-editable. Custom build commands are preserved across\n # reruns and are not treated as drift by setup check. Other edits to this file are flagged\n # as drift and will be overwritten if you re-run setup action with --force.\n true\n # __STATIC_WEBSITE_BUILD_END__\n - id: tailor-apply\n if: inputs.deploy == 'true'\n uses: tailor-platform/actions/deploy@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n env:\n TAILOR_PLATFORM_SDK_CONFIG_PATH: ${{ inputs.config }}\n with:\n workspace-id: ${{ inputs.workspace-id }}\n working-directory: ${{ inputs.working-directory }}\n package-manager: ${{ inputs.package-manager }}\n platform-client-id: ${{ inputs.platform-client-id }}\n platform-client-secret: ${{ inputs.platform-client-secret }}\n - id: tailor-notify\n if: failure() && inputs.slack-token != '' && inputs.slack-channel-id != ''\n uses: tailor-platform/actions/notify@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n provider: slack\n status: failure\n workspace-name: ${{ inputs.name }}\n slack-token: ${{ inputs.slack-token }}\n slack-channel-id: ${{ inputs.slack-channel-id }}\n user-mapping: ${{ inputs.user-mapping }}\n";
|
|
3175
3870
|
|
|
3176
3871
|
//#endregion
|
|
3177
3872
|
//#region src/cli/commands/setup/branch.workflow.yml
|
|
3178
|
-
var branch_workflow_default = "# __HEADER__\nname: Tailor (__WORKSPACE_NAME__)\n\non:\n # __PULL_REQUEST_START__\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n # __PULL_REQUEST_END__\n push:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n workflow_dispatch:\n # __DISPATCH_INPUTS_START__\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n # __DISPATCH_INPUTS_END__\n\npermissions:\n contents: read\n\njobs:\n # __PLAN_JOB_START__\n tailor-plan:\n if: >-\n github.event_name == 'pull_request' ||\n (github.event_name == 'workflow_dispatch' && inputs['dry-run'])\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-plan-__WORKSPACE_NAME__-${{ github.event.pull_request.number || github.run_id }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_START__\n - id: tailor-seed-validate\n uses: tailor-platform/actions/seed-validate@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_END__\n - id: tailor-drift-check\n uses: tailor-platform/actions/drift-check@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __MIGRATION_DRIFT_CHECK_START__\n - id: tailor-migration-drift-check\n uses: tailor-platform/actions/migration-drift-check@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __MIGRATION_DRIFT_CHECK_END__\n - id: tailor-plan\n # Fork PRs cannot read secrets; the checks above still run for them.\n if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork\n uses: tailor-platform/actions/plan@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n # __PLAN_JOB_END__\n # __ERD_PREVIEW_JOB_START__\n tailor-erd-preview-matrix:\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n contents: read\n outputs:\n namespaces: ${{ steps.tailor-erd-preview-matrix.outputs.namespaces }}\n base-app-dir: ${{ steps.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n persist-credentials: false\n - id: tailor-checkout-base\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n repository: ${{ github.event.pull_request.base.repo.full_name }}\n ref: ${{ github.event.pull_request.base.sha }}\n path: .tailor-erd-base\n persist-credentials: false\n - id: tailor-erd-preview-matrix\n shell: bash\n env:\n APP_DIR: \"__APP_DIR__\"\n WORKSPACE_NAME: \"__WORKSPACE_NAME__\"\n run: |\n set -euo pipefail\n node <<'NODE'\n const fs = require(\"node:fs\");\n\n const appDir = process.env.APP_DIR || \".\";\n const workspaceName = process.env.WORKSPACE_NAME;\n const output = process.env.GITHUB_OUTPUT;\n const dirPattern = /^[A-Za-z0-9._/-]+$/;\n const namespacePattern = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\n function lockTarget(file) {\n try {\n const lock = JSON.parse(fs.readFileSync(file, \"utf8\"));\n return lock.targets?.find(\n (candidate) =>\n candidate.kind === \"branch\" &&\n candidate.workspaceName === workspaceName,\n );\n } catch (error) {\n if (error && error.code === \"ENOENT\") return undefined;\n throw error;\n }\n }\n\n function namespacesFromTarget(target, label) {\n const namespaces =\n target?.inputs?.erdPreview === true && Array.isArray(target.inputs?.erdNamespaces)\n ? target.inputs.erdNamespaces\n : [];\n for (const namespace of namespaces) {\n if (typeof namespace !== \"string\" || !namespacePattern.test(namespace)) {\n throw new Error(`Invalid ERD namespace in ${label}: ${String(namespace)}`);\n }\n }\n return namespaces;\n }\n\n function normalizeDir(value, label) {\n const raw = typeof value === \"string\" && value.length > 0 ? value : appDir;\n const normalized =\n raw.replaceAll(\"\\\\\", \"/\").replace(/\\/{2,}/g, \"/\").replace(/^\\.\\//, \"\").replace(/\\/$/, \"\") ||\n \".\";\n if (\n !dirPattern.test(normalized) ||\n normalized.startsWith(\"/\") ||\n normalized.split(\"/\").includes(\"..\")\n ) {\n throw new Error(`Invalid ERD app dir in ${label}: ${raw}`);\n }\n return normalized;\n }\n\n const headTarget = lockTarget(\".github/tailor.lock\");\n const baseTarget = lockTarget(\".tailor-erd-base/.github/tailor.lock\");\n const baseAppDir = normalizeDir(baseTarget?.inputs?.dir, \"base setup lock\");\n const namespaces = [\n ...new Set([\n ...namespacesFromTarget(headTarget, \"head setup lock\"),\n ...namespacesFromTarget(baseTarget, \"base setup lock\"),\n ]),\n ].sort((a, b) => a.localeCompare(b));\n\n if (namespaces.length === 0) {\n console.error(\"No ERD preview namespaces found in the head or base setup lock.\");\n process.exit(1);\n }\n\n fs.appendFileSync(output, `namespaces=${JSON.stringify(namespaces)}\\n`);\n fs.appendFileSync(output, `base-app-dir=${baseAppDir}\\n`);\n NODE\n\n tailor-erd-preview:\n needs: tailor-erd-preview-matrix\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n strategy:\n fail-fast: false\n matrix:\n namespace: ${{ fromJSON(needs.tailor-erd-preview-matrix.outputs.namespaces) }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n persist-credentials: false\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-checkout-base\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n repository: ${{ github.event.pull_request.base.repo.full_name }}\n ref: ${{ github.event.pull_request.base.sha }}\n path: .tailor-erd-base\n persist-credentials: false\n - id: tailor-detect-base-package-manager\n shell: bash\n run: |\n set -euo pipefail\n cd .tailor-erd-base\n base_package_manager=\"$(\n if [ -f pnpm-lock.yaml ]; then\n echo pnpm\n elif [ -f yarn.lock ]; then\n echo yarn\n elif [ -f bun.lockb ] || [ -f bun.lock ]; then\n echo bun\n elif [ -f package-lock.json ]; then\n echo npm\n else\n echo \"__PACKAGE_MANAGER__\"\n fi\n )\"\n echo \"package-manager=$base_package_manager\" >> \"$GITHUB_OUTPUT\"\n if [ \"$base_package_manager\" = \"pnpm\" ]; then\n base_pnpm_version=\"$(node <<'NODE'\n const fs = require(\"node:fs\");\n\n const fallback = \"10\";\n\n function readManifest(file) {\n try {\n return JSON.parse(fs.readFileSync(file, \"utf8\"));\n } catch {\n return {};\n }\n }\n\n function pnpmVersion(manifest) {\n const packageManager = manifest.packageManager;\n if (typeof packageManager === \"string\" && packageManager.startsWith(\"pnpm@\")) {\n return packageManager.slice(\"pnpm@\".length).split(\"+\")[0];\n }\n\n const devPackageManager = manifest.devEngines?.packageManager;\n if (devPackageManager?.name === \"pnpm\" && devPackageManager.version) {\n return devPackageManager.version;\n }\n\n return undefined;\n }\n\n process.stdout.write(\n pnpmVersion(readManifest(\"package.json\")) ?? fallback,\n );\n NODE\n )\"\n echo \"pnpm-version=$base_pnpm_version\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-setup-base-pnpm\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'pnpm'\n uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9\n with:\n version: ${{ steps.tailor-detect-base-package-manager.outputs['pnpm-version'] }}\n package_json_file: .tailor-erd-base/package.json\n run_install: false\n - id: tailor-setup-base-node\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] != 'bun'\n uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\n with:\n node-version-file: .tailor-erd-base/package.json\n - id: tailor-setup-base-yarn\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'yarn'\n shell: bash\n working-directory: .tailor-erd-base\n run: |\n set -euo pipefail\n corepack enable\n - id: tailor-setup-base-bun\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'bun'\n uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0\n - id: tailor-install-base\n shell: bash\n working-directory: .tailor-erd-base\n env:\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n run: |\n set -euo pipefail\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) pnpm install --frozen-lockfile ;;\n npm) npm ci ;;\n yarn)\n if grep -q '^__metadata:' yarn.lock; then\n yarn install --immutable\n else\n yarn install --frozen-lockfile\n fi\n ;;\n bun) bun install --frozen-lockfile ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n - id: tailor-restore-head-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-build-erd-preview\n shell: bash\n env:\n APP_DIR: \"__APP_DIR__\"\n BASE_APP_DIR: ${{ needs.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n NAMESPACE: ${{ matrix.namespace }}\n run: |\n set -euo pipefail\n\n run_tailor_cli() {\n case \"__PACKAGE_MANAGER__\" in\n pnpm) pnpm exec tailor \"$@\" ;;\n npm) npx tailor \"$@\" ;;\n yarn) yarn tailor \"$@\" ;;\n bun) bunx tailor \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n }\n\n run_head_node() {\n case \"__PACKAGE_MANAGER__\" in\n pnpm) pnpm exec node \"$@\" ;;\n npm) node \"$@\" ;;\n yarn) yarn node \"$@\" ;;\n bun) node \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n }\n\n tailor_cli_bin=\"$(\n run_head_node - <<'NODE'\n const path = require(\"node:path\");\n const { createRequire } = require(\"node:module\");\n const appDir = process.env.APP_DIR || \".\";\n const githubWorkspace = process.env.GITHUB_WORKSPACE;\n if (!githubWorkspace) {\n throw new Error(\"GITHUB_WORKSPACE is required.\");\n }\n\n let lastError;\n for (const manifestPath of [\n path.join(githubWorkspace, appDir, \"package.json\"),\n path.join(githubWorkspace, \"package.json\"),\n ]) {\n try {\n const requireFromManifest = createRequire(manifestPath);\n process.stdout.write(\n path.join(\n path.dirname(requireFromManifest.resolve(\"@tailor-platform/sdk/cli\")),\n \"index.mjs\",\n ),\n );\n process.exit(0);\n } catch (error) {\n lastError = error;\n }\n }\n throw lastError || new Error(\"Failed to resolve @tailor-platform/sdk/cli.\");\n NODE\n )\"\n\n run_head_tailor_cli_bin() {\n local command_cwd=\"$1\"\n shift\n\n run_head_cli_env() {\n env TAILOR_CLI_BIN=\"$tailor_cli_bin\" TAILOR_CLI_CWD=\"$command_cwd\" \"$@\"\n }\n\n (\n cd \"$GITHUB_WORKSPACE\" || exit 1\n case \"__PACKAGE_MANAGER__\" in\n pnpm) run_head_cli_env pnpm exec node \"$head_cli_runner\" \"$@\" ;;\n npm) run_head_cli_env node \"$head_cli_runner\" \"$@\" ;;\n yarn) run_head_cli_env yarn node \"$head_cli_runner\" \"$@\" ;;\n bun) run_head_cli_env bun \"$head_cli_runner\" \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n )\n }\n\n run_base_tailor_cli_bin() {\n local command_cwd=\"$1\"\n shift\n\n run_head_cli_env() {\n env TAILOR_CLI_BIN=\"$tailor_cli_bin\" TAILOR_CLI_CWD=\"$command_cwd\" \"$@\"\n }\n\n (\n cd \"$command_cwd\" || exit 1\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) run_head_cli_env pnpm exec node \"$head_cli_runner\" \"$@\" ;;\n npm) run_head_cli_env node \"$head_cli_runner\" \"$@\" ;;\n yarn) run_head_cli_env yarn node \"$head_cli_runner\" \"$@\" ;;\n bun) run_head_cli_env bun \"$head_cli_runner\" \"$@\" ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n )\n }\n\n head_output=\"$RUNNER_TEMP/tailor-erd/head\"\n base_output=\"$RUNNER_TEMP/tailor-erd/base\"\n preview_output=\"$RUNNER_TEMP/tailor-erd/preview\"\n dts_output=\"$RUNNER_TEMP/tailor-erd/dts\"\n head_cli_runner=\"$RUNNER_TEMP/tailor-erd/run-head-cli.mjs\"\n head_log=\"$RUNNER_TEMP/tailor-erd/head-$NAMESPACE.log\"\n base_log=\"$RUNNER_TEMP/tailor-erd/base-$NAMESPACE.log\"\n rm -rf \"$head_output\" \"$base_output\" \"$preview_output\" \"$dts_output\"\n mkdir -p \"$head_output\" \"$base_output\" \"$preview_output\" \"$dts_output\"\n cat > \"$head_cli_runner\" <<'NODE'\n import { pathToFileURL } from \"node:url\";\n\n const cliBin = process.env.TAILOR_CLI_BIN;\n const commandCwd = process.env.TAILOR_CLI_CWD;\n if (!cliBin || !commandCwd) {\n throw new Error(\"TAILOR_CLI_BIN and TAILOR_CLI_CWD are required.\");\n }\n\n process.chdir(commandCwd);\n process.argv = [process.argv[0], cliBin, ...process.argv.slice(2)];\n await import(pathToFileURL(cliBin).href);\n NODE\n\n head_config=\"$GITHUB_WORKSPACE/$APP_DIR/tailor.config.ts\"\n base_config=\"$GITHUB_WORKSPACE/.tailor-erd-base/$BASE_APP_DIR/tailor.config.ts\"\n head_html=\"$head_output/$NAMESPACE/dist/index.html\"\n base_html=\"$base_output/$NAMESPACE/dist/index.html\"\n preview_html=\"$preview_output/$NAMESPACE-viewer.html\"\n\n head_missing=\"false\"\n if [ ! -f \"$head_config\" ]; then\n echo \"Head ERD config not found; rendering base objects as removed.\"\n head_missing=\"true\"\n elif ! (\n cd \"$GITHUB_WORKSPACE/$APP_DIR\" || exit 1\n TAILOR_PLATFORM_SDK_DTS_PATH=\"$dts_output/head-$NAMESPACE.d.ts\" \\\n run_tailor_cli tailordb erd export --config \"$head_config\" --namespace \"$NAMESPACE\" --output \"$head_output\"\n ) >\"$head_log\" 2>&1; then\n if grep -q 'not found in local config.db' \"$head_log\"; then\n cat \"$head_log\"\n echo \"Head ERD namespace '$NAMESPACE' not found; rendering base objects as removed.\"\n head_missing=\"true\"\n else\n cat \"$head_log\"\n exit 1\n fi\n elif [ -s \"$head_log\" ]; then\n cat \"$head_log\"\n fi\n\n base_missing=\"false\"\n if [ ! -f \"$base_config\" ]; then\n echo \"Base ERD config not found; rendering current objects as added.\"\n base_missing=\"true\"\n elif ! (\n TAILOR_PLATFORM_SDK_DTS_PATH=\"$dts_output/base-$NAMESPACE.d.ts\" \\\n run_base_tailor_cli_bin \"$GITHUB_WORKSPACE/.tailor-erd-base/$BASE_APP_DIR\" tailordb erd export --config \"$base_config\" --namespace \"$NAMESPACE\" --output \"$base_output\"\n ) >\"$base_log\" 2>&1; then\n if grep -q 'not found in local config.db' \"$base_log\"; then\n cat \"$base_log\"\n echo \"Base ERD namespace '$NAMESPACE' not found; rendering current objects as added.\"\n base_missing=\"true\"\n else\n cat \"$base_log\"\n exit 1\n fi\n elif [ -s \"$base_log\" ]; then\n cat \"$base_log\"\n fi\n\n if [ \"$head_missing\" = \"true\" ] && [ \"$base_missing\" = \"true\" ]; then\n echo \"::error::ERD namespace '$NAMESPACE' was not found in head or base.\"\n exit 1\n fi\n if [ \"$head_missing\" = \"false\" ] && [ ! -f \"$head_html\" ]; then\n echo \"::error::Head ERD viewer was not generated at $head_html\"\n exit 1\n fi\n if [ \"$base_missing\" = \"false\" ] && [ ! -f \"$base_html\" ]; then\n echo \"::error::Base ERD viewer was not generated at $base_html\"\n exit 1\n fi\n\n diff_args=(--namespace \"$NAMESPACE\" --output \"$preview_html\")\n if [ \"$head_missing\" = \"false\" ]; then\n diff_args+=(--head-html \"$head_html\")\n fi\n if [ \"$base_missing\" = \"false\" ]; then\n diff_args+=(--base-html \"$base_html\")\n fi\n run_head_tailor_cli_bin \"$GITHUB_WORKSPACE\" tailordb erd diff \"${diff_args[@]}\"\n - id: tailor-upload-erd-viewer\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: ${{ matrix.namespace }}-viewer.html\n path: ${{ runner.temp }}/tailor-erd/preview/${{ matrix.namespace }}-viewer.html\n if-no-files-found: error\n archive: false\n retention-days: 7\n\n # __ERD_PREVIEW_JOB_END__\n # __ERD_PREVIEW_COMMENT_JOB_START__\n tailor-erd-preview-comment:\n needs: tailor-erd-preview\n if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n actions: read\n pull-requests: write\n steps:\n - id: tailor-comment-erd-preview\n env:\n GH_TOKEN: ${{ github.token }}\n REPO: ${{ github.repository }}\n RUN_ID: ${{ github.run_id }}\n SERVER: ${{ github.server_url }}\n PR_NUMBER: ${{ github.event.pull_request.number }}\n BASE_REF: ${{ github.event.pull_request.base.ref }}\n HEAD_SHA: ${{ github.event.pull_request.head.sha }}\n MARKER: \"<!-- tailor-erd-preview: __WORKSPACE_NAME__ -->\"\n run: |\n set -euo pipefail\n\n current_head=$(gh api \"repos/$REPO/pulls/$PR_NUMBER\" --jq '.head.sha')\n if [ \"$current_head\" != \"$HEAD_SHA\" ]; then\n echo \"Skipping stale ERD preview comment for $HEAD_SHA; current head is $current_head.\"\n exit 0\n fi\n\n body=\"$MARKER\"$'\\n'\"### TailorDB ERD preview (__WORKSPACE_NAME__)\"$'\\n\\n'\n body+=\"Self-contained ERD viewer HTML artifacts for this pull request. Each viewer can switch between the current schema and a diff against base branch \\`$BASE_REF\\`.\"$'\\n'\n\n while IFS=$'\\t' read -r name id; do\n [ -z \"$name\" ] && continue\n if [[ \"$name\" != *-viewer.html ]]; then\n continue\n fi\n ns=${name%-viewer.html}\n label=\"$ns viewer\"\n body+=$'\\n'\"- **$label**: [$name]($SERVER/$REPO/actions/runs/$RUN_ID/artifacts/$id)\"\n done < <(gh api \"repos/$REPO/actions/runs/$RUN_ID/artifacts\" --paginate \\\n --jq '.artifacts[] | select(.name | endswith(\"-viewer.html\")) | [.name, (.id|tostring)] | @tsv' | sort)\n\n existing=$(gh api \"repos/$REPO/issues/$PR_NUMBER/comments\" --paginate \\\n --jq 'map(select(.body | contains(env.MARKER))) | .[0].id // empty')\n if [ -n \"$existing\" ]; then\n gh api --method PATCH \"repos/$REPO/issues/comments/$existing\" -f body=\"$body\" >/dev/null\n else\n gh api --method POST \"repos/$REPO/issues/$PR_NUMBER/comments\" -f body=\"$body\" >/dev/null\n fi\n\n # __ERD_PREVIEW_COMMENT_JOB_END__\n tailor-deploy:\n # __DEPLOY_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n environment: __ENVIRONMENT__\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n # __SEED_DATA_START__\n - id: seed-data\n if: false\n shell: bash\n # __WORKING_DIRECTORY__\n run: |\n # Uncomment and implement to seed data on deploy.\n # Run after apply so the schema is ready.\n # Example: node scripts/seed.mjs\n true\n # __SEED_DATA_END__\n - id: slack-prereq\n if: always()\n shell: bash\n env:\n SLACK_TOKEN: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n SLACK_CHANNEL_ID: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n run: |\n if [[ -n \"$SLACK_TOKEN\" && -n \"$SLACK_CHANNEL_ID\" ]]; then\n echo \"ready=true\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-notify\n if: always() && steps.slack-prereq.outputs.ready == 'true'\n uses: tailor-platform/actions/notify@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\n with:\n provider: slack\n status: ${{ job.status }}\n workspace-name: __WORKSPACE_NAME__\n slack-token: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n slack-channel-id: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n # user-mapping: ${{ vars.TAILOR_SLACK_USER_MAPPING }}\n";
|
|
3873
|
+
var branch_workflow_default = "# __HEADER__\nname: Tailor (__WORKSPACE_NAME__)\n\non:\n # __PULL_REQUEST_START__\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n # __PULL_REQUEST_END__\n push:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n workflow_dispatch:\n # __DISPATCH_INPUTS_START__\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n # __DISPATCH_INPUTS_END__\n\npermissions:\n contents: read\n\njobs:\n # __PLAN_JOB_START__\n tailor-plan:\n if: >-\n github.event_name == 'pull_request' ||\n (github.event_name == 'workflow_dispatch' && inputs['dry-run'])\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-plan-__WORKSPACE_NAME__-${{ github.event.pull_request.number || github.run_id }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_START__\n - id: tailor-seed-validate\n uses: tailor-platform/actions/seed-validate@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_END__\n - id: tailor-drift-check\n uses: tailor-platform/actions/drift-check@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __MIGRATION_DRIFT_CHECK_START__\n - id: tailor-migration-drift-check\n uses: tailor-platform/actions/migration-drift-check@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __MIGRATION_DRIFT_CHECK_END__\n - id: tailor-plan\n # Fork PRs cannot read secrets; the checks above still run for them.\n if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork\n uses: tailor-platform/actions/plan@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n # __PLAN_JOB_END__\n # __ERD_PREVIEW_JOB_START__\n tailor-erd-preview-matrix:\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n contents: read\n outputs:\n namespaces: ${{ steps.tailor-erd-preview-matrix.outputs.namespaces }}\n base-app-dir: ${{ steps.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n persist-credentials: false\n - id: tailor-checkout-base\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n repository: ${{ github.event.pull_request.base.repo.full_name }}\n ref: ${{ github.event.pull_request.base.sha }}\n path: .tailor-erd-base\n persist-credentials: false\n - id: tailor-erd-preview-matrix\n shell: bash\n env:\n APP_DIR: \"__APP_DIR__\"\n WORKSPACE_NAME: \"__WORKSPACE_NAME__\"\n run: |\n set -euo pipefail\n node <<'NODE'\n const fs = require(\"node:fs\");\n\n const appDir = process.env.APP_DIR || \".\";\n const workspaceName = process.env.WORKSPACE_NAME;\n const output = process.env.GITHUB_OUTPUT;\n const dirPattern = /^[A-Za-z0-9._/-]+$/;\n const namespacePattern = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\n function lockTarget(file) {\n try {\n const lock = JSON.parse(fs.readFileSync(file, \"utf8\"));\n return lock.targets?.find(\n (candidate) =>\n candidate.kind === \"branch\" &&\n candidate.workspaceName === workspaceName,\n );\n } catch (error) {\n if (error && error.code === \"ENOENT\") return undefined;\n throw error;\n }\n }\n\n function namespacesFromTarget(target, label) {\n const namespaces =\n target?.inputs?.erdPreview === true && Array.isArray(target.inputs?.erdNamespaces)\n ? target.inputs.erdNamespaces\n : [];\n for (const namespace of namespaces) {\n if (typeof namespace !== \"string\" || !namespacePattern.test(namespace)) {\n throw new Error(`Invalid ERD namespace in ${label}: ${String(namespace)}`);\n }\n }\n return namespaces;\n }\n\n function normalizeDir(value, label) {\n const raw = typeof value === \"string\" && value.length > 0 ? value : appDir;\n const normalized =\n raw.replaceAll(\"\\\\\", \"/\").replace(/\\/{2,}/g, \"/\").replace(/^\\.\\//, \"\").replace(/\\/$/, \"\") ||\n \".\";\n if (\n !dirPattern.test(normalized) ||\n normalized.startsWith(\"/\") ||\n normalized.split(\"/\").includes(\"..\")\n ) {\n throw new Error(`Invalid ERD app dir in ${label}: ${raw}`);\n }\n return normalized;\n }\n\n const headTarget = lockTarget(\".github/tailor.lock\");\n const baseTarget = lockTarget(\".tailor-erd-base/.github/tailor.lock\");\n const baseAppDir = normalizeDir(baseTarget?.inputs?.dir, \"base setup lock\");\n const namespaces = [\n ...new Set([\n ...namespacesFromTarget(headTarget, \"head setup lock\"),\n ...namespacesFromTarget(baseTarget, \"base setup lock\"),\n ]),\n ].sort((a, b) => a.localeCompare(b));\n\n if (namespaces.length === 0) {\n console.error(\"No ERD preview namespaces found in the head or base setup lock.\");\n process.exit(1);\n }\n\n fs.appendFileSync(output, `namespaces=${JSON.stringify(namespaces)}\\n`);\n fs.appendFileSync(output, `base-app-dir=${baseAppDir}\\n`);\n NODE\n\n tailor-erd-preview:\n needs: tailor-erd-preview-matrix\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n strategy:\n fail-fast: false\n matrix:\n namespace: ${{ fromJSON(needs.tailor-erd-preview-matrix.outputs.namespaces) }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n persist-credentials: false\n - id: tailor-setup\n uses: tailor-platform/actions/setup@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-checkout-base\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n repository: ${{ github.event.pull_request.base.repo.full_name }}\n ref: ${{ github.event.pull_request.base.sha }}\n path: .tailor-erd-base\n persist-credentials: false\n - id: tailor-detect-base-package-manager\n shell: bash\n run: |\n set -euo pipefail\n cd .tailor-erd-base\n base_package_manager=\"$(\n if [ -f pnpm-lock.yaml ]; then\n echo pnpm\n elif [ -f yarn.lock ]; then\n echo yarn\n elif [ -f bun.lockb ] || [ -f bun.lock ]; then\n echo bun\n elif [ -f package-lock.json ]; then\n echo npm\n else\n echo \"__PACKAGE_MANAGER__\"\n fi\n )\"\n echo \"package-manager=$base_package_manager\" >> \"$GITHUB_OUTPUT\"\n if [ \"$base_package_manager\" = \"pnpm\" ]; then\n base_pnpm_version=\"$(node <<'NODE'\n const fs = require(\"node:fs\");\n\n const fallback = \"10\";\n\n function readManifest(file) {\n try {\n return JSON.parse(fs.readFileSync(file, \"utf8\"));\n } catch {\n return {};\n }\n }\n\n function pnpmVersion(manifest) {\n const packageManager = manifest.packageManager;\n if (typeof packageManager === \"string\" && packageManager.startsWith(\"pnpm@\")) {\n return packageManager.slice(\"pnpm@\".length).split(\"+\")[0];\n }\n\n const devPackageManager = manifest.devEngines?.packageManager;\n if (devPackageManager?.name === \"pnpm\" && devPackageManager.version) {\n return devPackageManager.version;\n }\n\n return undefined;\n }\n\n process.stdout.write(\n pnpmVersion(readManifest(\"package.json\")) ?? fallback,\n );\n NODE\n )\"\n echo \"pnpm-version=$base_pnpm_version\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-setup-base-pnpm\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'pnpm'\n uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9\n with:\n version: ${{ steps.tailor-detect-base-package-manager.outputs['pnpm-version'] }}\n package_json_file: .tailor-erd-base/package.json\n run_install: false\n - id: tailor-setup-base-node\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] != 'bun'\n uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7\n with:\n node-version-file: .tailor-erd-base/package.json\n - id: tailor-setup-base-yarn\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'yarn'\n shell: bash\n working-directory: .tailor-erd-base\n run: |\n set -euo pipefail\n corepack enable\n - id: tailor-setup-base-bun\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'bun'\n uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0\n - id: tailor-install-base\n shell: bash\n working-directory: .tailor-erd-base\n env:\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n run: |\n set -euo pipefail\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) pnpm install --frozen-lockfile ;;\n npm) npm ci ;;\n yarn)\n if grep -q '^__metadata:' yarn.lock; then\n yarn install --immutable\n else\n yarn install --frozen-lockfile\n fi\n ;;\n bun) bun install --frozen-lockfile ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n - id: tailor-restore-head-setup\n uses: tailor-platform/actions/setup@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-build-erd-preview\n shell: bash\n env:\n APP_DIR: \"__APP_DIR__\"\n BASE_APP_DIR: ${{ needs.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n NAMESPACE: ${{ matrix.namespace }}\n run: |\n set -euo pipefail\n\n run_tailor_cli() {\n case \"__PACKAGE_MANAGER__\" in\n pnpm) pnpm exec tailor \"$@\" ;;\n npm) npx tailor \"$@\" ;;\n yarn) yarn tailor \"$@\" ;;\n bun) bunx tailor \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n }\n\n run_head_node() {\n case \"__PACKAGE_MANAGER__\" in\n pnpm) pnpm exec node \"$@\" ;;\n npm) node \"$@\" ;;\n yarn) yarn node \"$@\" ;;\n bun) node \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n }\n\n tailor_cli_bin=\"$(\n run_head_node - <<'NODE'\n const path = require(\"node:path\");\n const { createRequire } = require(\"node:module\");\n const appDir = process.env.APP_DIR || \".\";\n const githubWorkspace = process.env.GITHUB_WORKSPACE;\n if (!githubWorkspace) {\n throw new Error(\"GITHUB_WORKSPACE is required.\");\n }\n\n let lastError;\n for (const manifestPath of [\n path.join(githubWorkspace, appDir, \"package.json\"),\n path.join(githubWorkspace, \"package.json\"),\n ]) {\n try {\n const requireFromManifest = createRequire(manifestPath);\n process.stdout.write(\n path.join(\n path.dirname(requireFromManifest.resolve(\"@tailor-platform/sdk/cli\")),\n \"index.mjs\",\n ),\n );\n process.exit(0);\n } catch (error) {\n lastError = error;\n }\n }\n throw lastError || new Error(\"Failed to resolve @tailor-platform/sdk/cli.\");\n NODE\n )\"\n\n run_head_tailor_cli_bin() {\n local command_cwd=\"$1\"\n shift\n\n run_head_cli_env() {\n env TAILOR_CLI_BIN=\"$tailor_cli_bin\" TAILOR_CLI_CWD=\"$command_cwd\" \"$@\"\n }\n\n (\n cd \"$GITHUB_WORKSPACE\" || exit 1\n case \"__PACKAGE_MANAGER__\" in\n pnpm) run_head_cli_env pnpm exec node \"$head_cli_runner\" \"$@\" ;;\n npm) run_head_cli_env node \"$head_cli_runner\" \"$@\" ;;\n yarn) run_head_cli_env yarn node \"$head_cli_runner\" \"$@\" ;;\n bun) run_head_cli_env bun \"$head_cli_runner\" \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n )\n }\n\n run_base_tailor_cli_bin() {\n local command_cwd=\"$1\"\n shift\n\n run_head_cli_env() {\n env TAILOR_CLI_BIN=\"$tailor_cli_bin\" TAILOR_CLI_CWD=\"$command_cwd\" \"$@\"\n }\n\n (\n cd \"$command_cwd\" || exit 1\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) run_head_cli_env pnpm exec node \"$head_cli_runner\" \"$@\" ;;\n npm) run_head_cli_env node \"$head_cli_runner\" \"$@\" ;;\n yarn) run_head_cli_env yarn node \"$head_cli_runner\" \"$@\" ;;\n bun) run_head_cli_env bun \"$head_cli_runner\" \"$@\" ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n )\n }\n\n head_output=\"$RUNNER_TEMP/tailor-erd/head\"\n base_output=\"$RUNNER_TEMP/tailor-erd/base\"\n preview_output=\"$RUNNER_TEMP/tailor-erd/preview\"\n dts_output=\"$RUNNER_TEMP/tailor-erd/dts\"\n head_cli_runner=\"$RUNNER_TEMP/tailor-erd/run-head-cli.mjs\"\n head_log=\"$RUNNER_TEMP/tailor-erd/head-$NAMESPACE.log\"\n base_log=\"$RUNNER_TEMP/tailor-erd/base-$NAMESPACE.log\"\n rm -rf \"$head_output\" \"$base_output\" \"$preview_output\" \"$dts_output\"\n mkdir -p \"$head_output\" \"$base_output\" \"$preview_output\" \"$dts_output\"\n cat > \"$head_cli_runner\" <<'NODE'\n import { pathToFileURL } from \"node:url\";\n\n const cliBin = process.env.TAILOR_CLI_BIN;\n const commandCwd = process.env.TAILOR_CLI_CWD;\n if (!cliBin || !commandCwd) {\n throw new Error(\"TAILOR_CLI_BIN and TAILOR_CLI_CWD are required.\");\n }\n\n process.chdir(commandCwd);\n process.argv = [process.argv[0], cliBin, ...process.argv.slice(2)];\n await import(pathToFileURL(cliBin).href);\n NODE\n\n head_config=\"$GITHUB_WORKSPACE/$APP_DIR/tailor.config.ts\"\n base_config=\"$GITHUB_WORKSPACE/.tailor-erd-base/$BASE_APP_DIR/tailor.config.ts\"\n head_html=\"$head_output/$NAMESPACE/dist/index.html\"\n base_html=\"$base_output/$NAMESPACE/dist/index.html\"\n preview_html=\"$preview_output/$NAMESPACE-viewer.html\"\n\n head_missing=\"false\"\n if [ ! -f \"$head_config\" ]; then\n echo \"Head ERD config not found; rendering base objects as removed.\"\n head_missing=\"true\"\n elif ! (\n cd \"$GITHUB_WORKSPACE/$APP_DIR\" || exit 1\n TAILOR_PLATFORM_SDK_DTS_PATH=\"$dts_output/head-$NAMESPACE.d.ts\" \\\n run_tailor_cli tailordb erd export --config \"$head_config\" --namespace \"$NAMESPACE\" --output \"$head_output\"\n ) >\"$head_log\" 2>&1; then\n if grep -q 'not found in local config.db' \"$head_log\"; then\n cat \"$head_log\"\n echo \"Head ERD namespace '$NAMESPACE' not found; rendering base objects as removed.\"\n head_missing=\"true\"\n else\n cat \"$head_log\"\n exit 1\n fi\n elif [ -s \"$head_log\" ]; then\n cat \"$head_log\"\n fi\n\n base_missing=\"false\"\n if [ ! -f \"$base_config\" ]; then\n echo \"Base ERD config not found; rendering current objects as added.\"\n base_missing=\"true\"\n elif ! (\n TAILOR_PLATFORM_SDK_DTS_PATH=\"$dts_output/base-$NAMESPACE.d.ts\" \\\n run_base_tailor_cli_bin \"$GITHUB_WORKSPACE/.tailor-erd-base/$BASE_APP_DIR\" tailordb erd export --config \"$base_config\" --namespace \"$NAMESPACE\" --output \"$base_output\"\n ) >\"$base_log\" 2>&1; then\n if grep -q 'not found in local config.db' \"$base_log\"; then\n cat \"$base_log\"\n echo \"Base ERD namespace '$NAMESPACE' not found; rendering current objects as added.\"\n base_missing=\"true\"\n else\n cat \"$base_log\"\n exit 1\n fi\n elif [ -s \"$base_log\" ]; then\n cat \"$base_log\"\n fi\n\n if [ \"$head_missing\" = \"true\" ] && [ \"$base_missing\" = \"true\" ]; then\n echo \"::error::ERD namespace '$NAMESPACE' was not found in head or base.\"\n exit 1\n fi\n if [ \"$head_missing\" = \"false\" ] && [ ! -f \"$head_html\" ]; then\n echo \"::error::Head ERD viewer was not generated at $head_html\"\n exit 1\n fi\n if [ \"$base_missing\" = \"false\" ] && [ ! -f \"$base_html\" ]; then\n echo \"::error::Base ERD viewer was not generated at $base_html\"\n exit 1\n fi\n\n diff_args=(--namespace \"$NAMESPACE\" --output \"$preview_html\")\n if [ \"$head_missing\" = \"false\" ]; then\n diff_args+=(--head-html \"$head_html\")\n fi\n if [ \"$base_missing\" = \"false\" ]; then\n diff_args+=(--base-html \"$base_html\")\n fi\n run_head_tailor_cli_bin \"$GITHUB_WORKSPACE\" tailordb erd diff \"${diff_args[@]}\"\n - id: tailor-upload-erd-viewer\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: ${{ matrix.namespace }}-viewer.html\n path: ${{ runner.temp }}/tailor-erd/preview/${{ matrix.namespace }}-viewer.html\n if-no-files-found: error\n archive: false\n retention-days: 7\n\n # __ERD_PREVIEW_JOB_END__\n # __ERD_PREVIEW_COMMENT_JOB_START__\n tailor-erd-preview-comment:\n needs: tailor-erd-preview\n if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n actions: read\n pull-requests: write\n steps:\n - id: tailor-comment-erd-preview\n env:\n GH_TOKEN: ${{ github.token }}\n REPO: ${{ github.repository }}\n RUN_ID: ${{ github.run_id }}\n SERVER: ${{ github.server_url }}\n PR_NUMBER: ${{ github.event.pull_request.number }}\n BASE_REF: ${{ github.event.pull_request.base.ref }}\n HEAD_SHA: ${{ github.event.pull_request.head.sha }}\n MARKER: \"<!-- tailor-erd-preview: __WORKSPACE_NAME__ -->\"\n run: |\n set -euo pipefail\n\n current_head=$(gh api \"repos/$REPO/pulls/$PR_NUMBER\" --jq '.head.sha')\n if [ \"$current_head\" != \"$HEAD_SHA\" ]; then\n echo \"Skipping stale ERD preview comment for $HEAD_SHA; current head is $current_head.\"\n exit 0\n fi\n\n body=\"$MARKER\"$'\\n'\"### TailorDB ERD preview (__WORKSPACE_NAME__)\"$'\\n\\n'\n body+=\"Self-contained ERD viewer HTML artifacts for this pull request. Each viewer can switch between the current schema and a diff against base branch \\`$BASE_REF\\`.\"$'\\n'\n\n while IFS=$'\\t' read -r name id; do\n [ -z \"$name\" ] && continue\n if [[ \"$name\" != *-viewer.html ]]; then\n continue\n fi\n ns=${name%-viewer.html}\n label=\"$ns viewer\"\n body+=$'\\n'\"- **$label**: [$name]($SERVER/$REPO/actions/runs/$RUN_ID/artifacts/$id)\"\n done < <(gh api \"repos/$REPO/actions/runs/$RUN_ID/artifacts\" --paginate \\\n --jq '.artifacts[] | select(.name | endswith(\"-viewer.html\")) | [.name, (.id|tostring)] | @tsv' | sort)\n\n existing=$(gh api \"repos/$REPO/issues/$PR_NUMBER/comments\" --paginate \\\n --jq 'map(select(.body | contains(env.MARKER))) | .[0].id // empty')\n if [ -n \"$existing\" ]; then\n gh api --method PATCH \"repos/$REPO/issues/comments/$existing\" -f body=\"$body\" >/dev/null\n else\n gh api --method POST \"repos/$REPO/issues/$PR_NUMBER/comments\" -f body=\"$body\" >/dev/null\n fi\n\n # __ERD_PREVIEW_COMMENT_JOB_END__\n tailor-deploy:\n # __DEPLOY_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n environment: __ENVIRONMENT__\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n # __SEED_DATA_START__\n - id: seed-data\n if: false\n shell: bash\n # __WORKING_DIRECTORY__\n run: |\n # Uncomment and implement to seed data on deploy.\n # Run after apply so the schema is ready.\n # Example: node scripts/seed.mjs\n true\n # __SEED_DATA_END__\n - id: slack-prereq\n if: always()\n shell: bash\n env:\n SLACK_TOKEN: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n SLACK_CHANNEL_ID: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n run: |\n if [[ -n \"$SLACK_TOKEN\" && -n \"$SLACK_CHANNEL_ID\" ]]; then\n echo \"ready=true\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-notify\n if: always() && steps.slack-prereq.outputs.ready == 'true'\n uses: tailor-platform/actions/notify@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n provider: slack\n status: ${{ job.status }}\n workspace-name: __WORKSPACE_NAME__\n slack-token: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n slack-channel-id: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n # user-mapping: ${{ vars.TAILOR_SLACK_USER_MAPPING }}\n";
|
|
3179
3874
|
|
|
3180
3875
|
//#endregion
|
|
3181
3876
|
//#region src/cli/commands/setup/coordinate.workflow.yml
|
|
3182
|
-
var coordinate_workflow_default = "# __HEADER__\nname: Tailor Coordinator (__WORKSPACE_NAME__)\n\non:\n # __PULL_REQUEST_START__\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PULL_REQUEST_END__\n push:\n # __PUSH_BRANCHES_START__\n branches: [\"__BRANCH__\"]\n # __PUSH_BRANCHES_END__\n # __PUSH_TAGS_START__\n tags: [__TAG_PATTERN__]\n # __PUSH_TAGS_END__\n workflow_dispatch:\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n\npermissions:\n contents: read\n\njobs:\n # __TAG_GUARD_JOB_START__\n tailor-tag-guard:\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n contents: read\n outputs:\n on-branch: ${{ steps.tailor-tag-guard.outputs.on-branch }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@
|
|
3877
|
+
var coordinate_workflow_default = "# __HEADER__\nname: Tailor Coordinator (__WORKSPACE_NAME__)\n\non:\n # __PULL_REQUEST_START__\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PULL_REQUEST_END__\n push:\n # __PUSH_BRANCHES_START__\n branches: [\"__BRANCH__\"]\n # __PUSH_BRANCHES_END__\n # __PUSH_TAGS_START__\n tags: [__TAG_PATTERN__]\n # __PUSH_TAGS_END__\n workflow_dispatch:\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n\npermissions:\n contents: read\n\njobs:\n # __TAG_GUARD_JOB_START__\n tailor-tag-guard:\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n contents: read\n outputs:\n on-branch: ${{ steps.tailor-tag-guard.outputs.on-branch }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n target-branch: __BRANCH__\n # __TAG_GUARD_JOB_END__\n\n tailor-plan:\n # __PLAN_NEEDS__\n # __PLAN_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 60\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-plan-__WORKSPACE_NAME__-${{ github.event.pull_request.number || github.run_id }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: ./.github/actions/tailor-setup\n # __PER_APP_PLAN_STEPS__\n\n tailor-deploy:\n # __DEPLOY_NEEDS__\n # __DEPLOY_IF__\n # __DEPLOY_ENVIRONMENT__\n runs-on: ubuntu-latest\n timeout-minutes: 60\n permissions:\n contents: read\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: ./.github/actions/tailor-setup\n # __PER_APP_DEPLOY_STEPS__\n";
|
|
3183
3878
|
|
|
3184
3879
|
//#endregion
|
|
3185
3880
|
//#region src/cli/commands/setup/preview.workflow.yml
|
|
3186
|
-
var preview_workflow_default = "# __HEADER__\nname: Tailor Preview (__WORKSPACE_NAME__)\n\non:\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PR_TYPES__\n # To deploy only for PRs that carry the `tailor:preview` label instead of all PRs,\n # re-run: tailor setup preview --require-preview-label\n # __PATHS__\n\npermissions:\n contents: read\n pull-requests: write\n\njobs:\n tailor-preview-deploy:\n # __DEPLOY_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-preview-__WORKSPACE_NAME__-${{ github.event.pull_request.number }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@
|
|
3881
|
+
var preview_workflow_default = "# __HEADER__\nname: Tailor Preview (__WORKSPACE_NAME__)\n\non:\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PR_TYPES__\n # To deploy only for PRs that carry the `tailor:preview` label instead of all PRs,\n # re-run: tailor setup preview --require-preview-label\n # __PATHS__\n\npermissions:\n contents: read\n pull-requests: write\n\njobs:\n tailor-preview-deploy:\n # __DEPLOY_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-preview-__WORKSPACE_NAME__-${{ github.event.pull_request.number }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n - id: tailor-preview-deploy\n uses: tailor-platform/actions/preview-deploy@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n workspace-name-prefix: __WORKSPACE_NAME__\n region: \"__REGION__\"\n organization-id: ${{ vars.TAILOR_PLATFORM_ORGANIZATION_ID }}\n folder-id: ${{ vars.TAILOR_PLATFORM_FOLDER_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n - id: tailor-preview-comment\n if: always()\n uses: tailor-platform/actions/preview-comment@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n workspace-id: ${{ steps.tailor-preview-deploy.outputs.workspace-id }}\n workspace-name: ${{ steps.tailor-preview-deploy.outputs.workspace-name }}\n status: ${{ job.status == 'success' && 'success' || 'failure' }}\n app-url: ${{ steps.tailor-preview-deploy.outputs.app-url }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n\n tailor-preview-cleanup:\n # __CLEANUP_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 15\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-preview-cleanup\n uses: tailor-platform/actions/preview-cleanup@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n workspace-name-prefix: __WORKSPACE_NAME__\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n";
|
|
3187
3882
|
|
|
3188
3883
|
//#endregion
|
|
3189
3884
|
//#region src/cli/commands/setup/tag.workflow.yml
|
|
3190
|
-
var tag_workflow_default = "# __HEADER__\nname: Tailor (__WORKSPACE_NAME__)\n\non:\n push:\n tags: [\"__TAG_PATTERN__\"]\n workflow_dispatch:\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n\npermissions:\n contents: read\n\njobs:\n # __TAG_GUARD_JOB_START__\n tailor-tag-guard:\n runs-on: ubuntu-latest\n timeout-minutes: 10\n permissions:\n contents: read\n outputs:\n on-branch: ${{ steps.tailor-tag-guard.outputs.on-branch }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@
|
|
3885
|
+
var tag_workflow_default = "# __HEADER__\nname: Tailor (__WORKSPACE_NAME__)\n\non:\n push:\n tags: [\"__TAG_PATTERN__\"]\n workflow_dispatch:\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n\npermissions:\n contents: read\n\njobs:\n # __TAG_GUARD_JOB_START__\n tailor-tag-guard:\n runs-on: ubuntu-latest\n timeout-minutes: 10\n permissions:\n contents: read\n outputs:\n on-branch: ${{ steps.tailor-tag-guard.outputs.on-branch }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n target-branch: \"__BRANCH__\"\n # __TAG_GUARD_JOB_END__\n tailor-plan:\n # __PLAN_NEEDS__\n # __PLAN_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_START__\n - id: tailor-seed-validate\n uses: tailor-platform/actions/seed-validate@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_END__\n - id: tailor-drift-check\n uses: tailor-platform/actions/drift-check@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __MIGRATION_DRIFT_CHECK_START__\n - id: tailor-migration-drift-check\n uses: tailor-platform/actions/migration-drift-check@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __MIGRATION_DRIFT_CHECK_END__\n - id: tailor-plan\n uses: tailor-platform/actions/plan@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n\n tailor-deploy:\n needs: tailor-plan\n if: ${{ !(github.event_name == 'workflow_dispatch' && inputs['dry-run']) }}\n environment: __ENVIRONMENT__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n # __SEED_DATA_START__\n - id: seed-data\n if: false\n shell: bash\n # __WORKING_DIRECTORY__\n run: |\n # Uncomment and implement to seed data on deploy.\n # Run after apply so the schema is ready.\n # Example: node scripts/seed.mjs\n true\n # __SEED_DATA_END__\n - id: slack-prereq\n if: always()\n shell: bash\n env:\n SLACK_TOKEN: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n SLACK_CHANNEL_ID: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n run: |\n if [[ -n \"$SLACK_TOKEN\" && -n \"$SLACK_CHANNEL_ID\" ]]; then\n echo \"ready=true\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-notify\n if: always() && steps.slack-prereq.outputs.ready == 'true'\n uses: tailor-platform/actions/notify@a3c3032e14277e81039eb26ae1a425adb22eb532 # v1.7.0\n with:\n provider: slack\n status: ${{ job.status }}\n workspace-name: __WORKSPACE_NAME__\n slack-token: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n slack-channel-id: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n # user-mapping: ${{ vars.TAILOR_SLACK_USER_MAPPING }}\n";
|
|
3191
3886
|
|
|
3192
3887
|
//#endregion
|
|
3193
3888
|
//#region src/cli/commands/setup/templates.ts
|
|
@@ -3206,10 +3901,10 @@ const HEADER = `# Generated by \`tailor setup\` — managed by the Tailor SDK.
|
|
|
3206
3901
|
* @returns Detected package manager, defaults to npm
|
|
3207
3902
|
*/
|
|
3208
3903
|
function detectPackageManager(dir) {
|
|
3209
|
-
if (fs
|
|
3210
|
-
if (fs
|
|
3211
|
-
if (fs
|
|
3212
|
-
if (fs
|
|
3904
|
+
if (fs.existsSync(path.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
3905
|
+
if (fs.existsSync(path.join(dir, "yarn.lock"))) return "yarn";
|
|
3906
|
+
if (fs.existsSync(path.join(dir, "bun.lockb")) || fs.existsSync(path.join(dir, "bun.lock"))) return "bun";
|
|
3907
|
+
if (fs.existsSync(path.join(dir, "package-lock.json"))) return "npm";
|
|
3213
3908
|
return "npm";
|
|
3214
3909
|
}
|
|
3215
3910
|
function block(content, name, keep) {
|
|
@@ -3357,7 +4052,8 @@ function renderActionWorkflow(params) {
|
|
|
3357
4052
|
generatedIds: ["tailor-apply", "tailor-notify"]
|
|
3358
4053
|
};
|
|
3359
4054
|
}
|
|
3360
|
-
const ACTIONS_SHA = "
|
|
4055
|
+
const ACTIONS_SHA = "a3c3032e14277e81039eb26ae1a425adb22eb532";
|
|
4056
|
+
const ACTIONS_VERSION = "v1.7.0";
|
|
3361
4057
|
/**
|
|
3362
4058
|
* Render the coordinator workflow that orchestrates per-app composite actions.
|
|
3363
4059
|
*
|
|
@@ -3399,7 +4095,7 @@ function renderCoordinateWorkflow(params) {
|
|
|
3399
4095
|
}
|
|
3400
4096
|
const driftCheckStep = [
|
|
3401
4097
|
`- id: tailor-drift-check`,
|
|
3402
|
-
` uses: tailor-platform/actions/drift-check@${ACTIONS_SHA} #
|
|
4098
|
+
` uses: tailor-platform/actions/drift-check@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
|
|
3403
4099
|
` with:`,
|
|
3404
4100
|
` package-manager: ${packageManager}`
|
|
3405
4101
|
].join("\n");
|
|
@@ -3416,7 +4112,7 @@ function renderCoordinateWorkflow(params) {
|
|
|
3416
4112
|
return [
|
|
3417
4113
|
`- id: tailor-plan-${group.id}`,
|
|
3418
4114
|
` if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork`,
|
|
3419
|
-
` uses: tailor-platform/actions/plan@${ACTIONS_SHA} #
|
|
4115
|
+
` uses: tailor-platform/actions/plan@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
|
|
3420
4116
|
` with:`,
|
|
3421
4117
|
` workspace-id: \${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}`,
|
|
3422
4118
|
` package-manager: ${packageManager}${wdLine}`,
|
|
@@ -3429,7 +4125,7 @@ function renderCoordinateWorkflow(params) {
|
|
|
3429
4125
|
const multiConfigPlanStep = (group) => [
|
|
3430
4126
|
`- id: tailor-plan-${group.id}`,
|
|
3431
4127
|
` if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork`,
|
|
3432
|
-
` uses: tailor-platform/actions/plan@${ACTIONS_SHA} #
|
|
4128
|
+
` uses: tailor-platform/actions/plan@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
|
|
3433
4129
|
` env:`,
|
|
3434
4130
|
` TAILOR_PLATFORM_SDK_CONFIG_PATH: ${groupConfigPath(group)}`,
|
|
3435
4131
|
` with:`,
|
|
@@ -3446,7 +4142,7 @@ function renderCoordinateWorkflow(params) {
|
|
|
3446
4142
|
const wdLine = wd ? `\n working-directory: ${wd}` : "";
|
|
3447
4143
|
return [
|
|
3448
4144
|
`- id: tailor-generate-check-${app.name}`,
|
|
3449
|
-
` uses: tailor-platform/actions/generate-check@${ACTIONS_SHA} #
|
|
4145
|
+
` uses: tailor-platform/actions/generate-check@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
|
|
3450
4146
|
` with:`,
|
|
3451
4147
|
` package-manager: ${packageManager}${wdLine}`
|
|
3452
4148
|
];
|
|
@@ -3541,11 +4237,11 @@ function renderTailorSetupAction(params) {
|
|
|
3541
4237
|
`runs:`,
|
|
3542
4238
|
` using: composite`,
|
|
3543
4239
|
` steps:`,
|
|
3544
|
-
` - uses: tailor-platform/actions/setup@${ACTIONS_SHA} #
|
|
4240
|
+
` - uses: tailor-platform/actions/setup@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
|
|
3545
4241
|
` with:`,
|
|
3546
4242
|
` package-manager: ${packageManager}`,
|
|
3547
4243
|
` # Add custom steps here (e.g. private registry authentication)`,
|
|
3548
|
-
` - uses: tailor-platform/actions/install@${ACTIONS_SHA} #
|
|
4244
|
+
` - uses: tailor-platform/actions/install@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
|
|
3549
4245
|
` with:`,
|
|
3550
4246
|
` package-manager: ${packageManager}`,
|
|
3551
4247
|
` # editable: install-command:`,
|
|
@@ -3594,9 +4290,8 @@ async function defaultLoadHasStaticWebsites$1(configPath) {
|
|
|
3594
4290
|
const { config } = await loadConfig(configPath);
|
|
3595
4291
|
return (config.staticWebsites?.length ?? 0) > 0;
|
|
3596
4292
|
}
|
|
3597
|
-
const WORKSPACE_NAME_RE = /^[a-z0-9-]+$/;
|
|
3598
4293
|
function validateWorkspaceName(name) {
|
|
3599
|
-
if (
|
|
4294
|
+
if (!workspaceNameSchema.safeParse(name).success) throw new Error(`Invalid workspace name "${name}". Names must be 3-63 characters of lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen. Pass a valid name with --name.`);
|
|
3600
4295
|
}
|
|
3601
4296
|
const BRANCH_RE = /^[A-Za-z0-9._/-]+$/;
|
|
3602
4297
|
const TAG_PATTERN_RE = /^[A-Za-z0-9._/*?![\]-]+$/;
|
|
@@ -3639,13 +4334,13 @@ function resolveConfigPath(outputDir, dir) {
|
|
|
3639
4334
|
const appDir = path.resolve(outputDir, dir);
|
|
3640
4335
|
const rel = path.relative(outputDir, appDir);
|
|
3641
4336
|
if (path.isAbsolute(dir) || escapesRoot$1(rel)) throw new Error(`--dir must be a relative path inside the repository (got "${dir}").`);
|
|
3642
|
-
if (fs
|
|
3643
|
-
const realAppDir = path.normalize(fs
|
|
3644
|
-
const realOutputDir = path.normalize(fs
|
|
4337
|
+
if (fs.existsSync(appDir)) {
|
|
4338
|
+
const realAppDir = path.normalize(fs.realpathSync(appDir));
|
|
4339
|
+
const realOutputDir = path.normalize(fs.realpathSync(outputDir));
|
|
3645
4340
|
if (escapesRoot$1(path.relative(realOutputDir, realAppDir))) throw new Error(`--dir must resolve to a directory inside the repository (got "${dir}", which links outside it).`);
|
|
3646
4341
|
}
|
|
3647
4342
|
const configPath = path.join(appDir, "tailor.config.ts");
|
|
3648
|
-
if (!fs
|
|
4343
|
+
if (!fs.existsSync(configPath)) throw new Error(`tailor.config.ts not found at ${configPath}. Run this from your SDK project root, or pass the app directory with --dir.`);
|
|
3649
4344
|
return configPath;
|
|
3650
4345
|
}
|
|
3651
4346
|
/**
|
|
@@ -3881,8 +4576,8 @@ async function setupTarget(options) {
|
|
|
3881
4576
|
file: resolved.file
|
|
3882
4577
|
});
|
|
3883
4578
|
const existing = findTarget(lock, resolved.kind, resolved.workspaceName);
|
|
3884
|
-
const fileExists = fs
|
|
3885
|
-
const currentContent = fileExists ? fs
|
|
4579
|
+
const fileExists = fs.existsSync(absFile);
|
|
4580
|
+
const currentContent = fileExists ? fs.readFileSync(absFile, "utf-8") : null;
|
|
3886
4581
|
const normalize = resolved.kind === "action" ? normalizeActionContent : void 0;
|
|
3887
4582
|
const decision = decideAction({
|
|
3888
4583
|
existing,
|
|
@@ -3900,8 +4595,8 @@ async function setupTarget(options) {
|
|
|
3900
4595
|
const existingBody = extractBuildSiteRunBody(currentContent);
|
|
3901
4596
|
if (existingBody !== null) contentToWrite = injectBuildSiteRunBody(contentToWrite, existingBody);
|
|
3902
4597
|
}
|
|
3903
|
-
fs
|
|
3904
|
-
fs
|
|
4598
|
+
fs.mkdirSync(path.dirname(absFile), { recursive: true });
|
|
4599
|
+
fs.writeFileSync(absFile, contentToWrite, "utf-8");
|
|
3905
4600
|
const newTarget = {
|
|
3906
4601
|
kind: resolved.kind,
|
|
3907
4602
|
workspaceName: resolved.workspaceName,
|
|
@@ -4005,21 +4700,21 @@ async function setupCoordinate(options) {
|
|
|
4005
4700
|
file
|
|
4006
4701
|
});
|
|
4007
4702
|
const existing = findTarget(lock, "coordinate", coordinatorName);
|
|
4008
|
-
const fileExists = fs
|
|
4703
|
+
const fileExists = fs.existsSync(absFile);
|
|
4009
4704
|
const decision = decideAction({
|
|
4010
4705
|
existing,
|
|
4011
4706
|
fileExists,
|
|
4012
|
-
currentContent: fileExists ? fs
|
|
4707
|
+
currentContent: fileExists ? fs.readFileSync(absFile, "utf-8") : null,
|
|
4013
4708
|
force
|
|
4014
4709
|
});
|
|
4015
4710
|
if (decision.action === "conflict") throw new Error(`${file}: ${decision.reason}`);
|
|
4016
|
-
fs
|
|
4017
|
-
fs
|
|
4711
|
+
fs.mkdirSync(path.dirname(absFile), { recursive: true });
|
|
4712
|
+
fs.writeFileSync(absFile, render.content, "utf-8");
|
|
4018
4713
|
const tailorSetupFile = ".github/actions/tailor-setup/action.yml";
|
|
4019
4714
|
const absTailorSetupFile = path.join(outputDir, tailorSetupFile);
|
|
4020
|
-
if (!fs
|
|
4021
|
-
fs
|
|
4022
|
-
fs
|
|
4715
|
+
if (!fs.existsSync(absTailorSetupFile)) {
|
|
4716
|
+
fs.mkdirSync(path.dirname(absTailorSetupFile), { recursive: true });
|
|
4717
|
+
fs.writeFileSync(absTailorSetupFile, renderTailorSetupAction({ packageManager }), "utf-8");
|
|
4023
4718
|
logger.success(`Generated ${styles.path(tailorSetupFile)}`);
|
|
4024
4719
|
}
|
|
4025
4720
|
const newTarget = {
|
|
@@ -4146,13 +4841,13 @@ function resolveWithinRoot(outputDir, relPath) {
|
|
|
4146
4841
|
const abs = path.join(outputDir, relPath);
|
|
4147
4842
|
if (escapesRoot(path.relative(outputDir, abs))) return null;
|
|
4148
4843
|
try {
|
|
4149
|
-
if (escapesRoot(path.relative(fs
|
|
4844
|
+
if (escapesRoot(path.relative(fs.realpathSync(outputDir), fs.realpathSync(abs)))) return null;
|
|
4150
4845
|
} catch {}
|
|
4151
4846
|
return abs;
|
|
4152
4847
|
}
|
|
4153
4848
|
function readHash(absFile, normalize) {
|
|
4154
4849
|
try {
|
|
4155
|
-
const content = fs
|
|
4850
|
+
const content = fs.readFileSync(absFile, "utf-8");
|
|
4156
4851
|
return hashContent(normalize ? normalize(content) : content);
|
|
4157
4852
|
} catch {
|
|
4158
4853
|
return null;
|
|
@@ -4193,7 +4888,7 @@ async function checkGitHub(options) {
|
|
|
4193
4888
|
}
|
|
4194
4889
|
const slackToken = Boolean(process.env["TAILOR_SLACK_BOT_TOKEN"]);
|
|
4195
4890
|
if (slackToken !== Boolean(process.env["TAILOR_SLACK_CHANNEL_ID"])) throw new Error(`Slack is partially configured: ${slackToken ? "TAILOR_SLACK_BOT_TOKEN is set" : "TAILOR_SLACK_CHANNEL_ID is set"} but not both. TAILOR_SLACK_BOT_TOKEN (secret) and TAILOR_SLACK_CHANNEL_ID (variable) must be set together, or neither should be set.`);
|
|
4196
|
-
const exists = options.configExistsAt ?? ((p) => fs
|
|
4891
|
+
const exists = options.configExistsAt ?? ((p) => fs.existsSync(p));
|
|
4197
4892
|
const defaultBranch = detectDefaultBranchSafe(outputDir, options.gitRunner);
|
|
4198
4893
|
const loadErdNamespaces = options.loadErdNamespaces ?? defaultLoadErdNamespaces;
|
|
4199
4894
|
const loadHasMigrations = options.loadHasMigrations ?? defaultLoadHasMigrations;
|
|
@@ -4282,10 +4977,10 @@ async function setupDelete(options) {
|
|
|
4282
4977
|
const remaining = [...lock.targets];
|
|
4283
4978
|
try {
|
|
4284
4979
|
for (const { relPath, absPath, target } of matches) {
|
|
4285
|
-
fs
|
|
4980
|
+
fs.rmSync(absPath, { force: true });
|
|
4286
4981
|
if (target.kind === "action") {
|
|
4287
4982
|
const dir = path.dirname(absPath);
|
|
4288
|
-
if (fs
|
|
4983
|
+
if (fs.existsSync(dir) && fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);
|
|
4289
4984
|
}
|
|
4290
4985
|
const index = remaining.findIndex((t) => targetKey(t) === targetKey(target));
|
|
4291
4986
|
if (index !== -1) remaining.splice(index, 1);
|
|
@@ -4489,203 +5184,6 @@ const setupCommand = defineCommand({
|
|
|
4489
5184
|
}
|
|
4490
5185
|
});
|
|
4491
5186
|
|
|
4492
|
-
//#endregion
|
|
4493
|
-
//#region src/cli/shared/progress.ts
|
|
4494
|
-
/**
|
|
4495
|
-
* Create a simple progress reporter that writes updates to stderr.
|
|
4496
|
-
* @param label - Label to prefix progress output
|
|
4497
|
-
* @param total - Total number of steps
|
|
4498
|
-
* @returns Progress helpers
|
|
4499
|
-
*/
|
|
4500
|
-
function createProgress(label, total) {
|
|
4501
|
-
let current = 0;
|
|
4502
|
-
const update = () => {
|
|
4503
|
-
current += 1;
|
|
4504
|
-
const percent = Math.round(current / total * 100);
|
|
4505
|
-
process.stderr.write(`\r${label} ${current}/${total} (${percent}%)`);
|
|
4506
|
-
};
|
|
4507
|
-
const finish = () => {
|
|
4508
|
-
process.stderr.write("\n");
|
|
4509
|
-
};
|
|
4510
|
-
return {
|
|
4511
|
-
update,
|
|
4512
|
-
finish
|
|
4513
|
-
};
|
|
4514
|
-
}
|
|
4515
|
-
/**
|
|
4516
|
-
* Wrap a promise with a timeout, rejecting if the timeout elapses first.
|
|
4517
|
-
* @template T
|
|
4518
|
-
* @param p - Promise to await
|
|
4519
|
-
* @param ms - Timeout in milliseconds
|
|
4520
|
-
* @param message - Error message on timeout
|
|
4521
|
-
* @returns Result of the original promise if it completes in time
|
|
4522
|
-
*/
|
|
4523
|
-
async function withTimeout(p, ms, message) {
|
|
4524
|
-
return await Promise.race([p, setTimeout$1(ms).then(() => {
|
|
4525
|
-
throw new Error(message);
|
|
4526
|
-
})]);
|
|
4527
|
-
}
|
|
4528
|
-
|
|
4529
|
-
//#endregion
|
|
4530
|
-
//#region src/cli/commands/staticwebsite/deploy.ts
|
|
4531
|
-
const CHUNK_SIZE = 64 * 1024;
|
|
4532
|
-
const IGNORED_FILES = /* @__PURE__ */ new Set([
|
|
4533
|
-
".DS_Store",
|
|
4534
|
-
"thumbs.db",
|
|
4535
|
-
"desktop.ini"
|
|
4536
|
-
]);
|
|
4537
|
-
function shouldIgnoreFile(filePath) {
|
|
4538
|
-
const fileName = path.basename(filePath).toLowerCase();
|
|
4539
|
-
return IGNORED_FILES.has(fileName);
|
|
4540
|
-
}
|
|
4541
|
-
/**
|
|
4542
|
-
* Deploy a static website by creating a deployment, uploading files, and publishing it.
|
|
4543
|
-
* @param client - Operator client instance
|
|
4544
|
-
* @param workspaceId - Workspace ID
|
|
4545
|
-
* @param name - Static website name
|
|
4546
|
-
* @param distDir - Directory containing static site files
|
|
4547
|
-
* @param showProgress - Whether to show upload progress
|
|
4548
|
-
* @returns Deployment result with URL and skipped files
|
|
4549
|
-
*/
|
|
4550
|
-
async function deployStaticWebsite(client, workspaceId, name, distDir, showProgress = true) {
|
|
4551
|
-
const { deploymentId } = await client.createDeployment({
|
|
4552
|
-
workspaceId,
|
|
4553
|
-
name
|
|
4554
|
-
});
|
|
4555
|
-
if (!deploymentId) throw new Error("createDeployment returned empty deploymentId");
|
|
4556
|
-
const skippedFiles = await uploadDirectory(client, workspaceId, deploymentId, distDir, showProgress);
|
|
4557
|
-
const { url } = await client.publishDeployment({
|
|
4558
|
-
workspaceId,
|
|
4559
|
-
deploymentId
|
|
4560
|
-
});
|
|
4561
|
-
if (!url) throw new Error("publishDeployment returned empty url");
|
|
4562
|
-
return {
|
|
4563
|
-
url,
|
|
4564
|
-
skippedFiles
|
|
4565
|
-
};
|
|
4566
|
-
}
|
|
4567
|
-
async function uploadDirectory(client, workspaceId, deploymentId, rootDir, showProgress) {
|
|
4568
|
-
const files = await collectFiles(rootDir);
|
|
4569
|
-
if (files.length === 0) {
|
|
4570
|
-
logger.warn(`No files found under ${rootDir}`);
|
|
4571
|
-
return [];
|
|
4572
|
-
}
|
|
4573
|
-
const limit = pLimit(5);
|
|
4574
|
-
const total = files.length;
|
|
4575
|
-
const progress = showProgress ? createProgress("Uploading files", total) : void 0;
|
|
4576
|
-
const skippedFiles = [];
|
|
4577
|
-
await Promise.all(files.map((relativePath) => limit(async () => {
|
|
4578
|
-
await uploadSingleFile(client, workspaceId, deploymentId, rootDir, relativePath, skippedFiles);
|
|
4579
|
-
if (progress) progress.update();
|
|
4580
|
-
})));
|
|
4581
|
-
if (progress) progress.finish();
|
|
4582
|
-
return skippedFiles;
|
|
4583
|
-
}
|
|
4584
|
-
/**
|
|
4585
|
-
* Recursively collect all deployable files under the given directory.
|
|
4586
|
-
* @param rootDir - Root directory to scan
|
|
4587
|
-
* @param currentDir - Current relative directory (for recursion)
|
|
4588
|
-
* @returns List of file paths relative to rootDir
|
|
4589
|
-
*/
|
|
4590
|
-
async function collectFiles(rootDir, currentDir = "") {
|
|
4591
|
-
const dirPath = path.join(rootDir, currentDir);
|
|
4592
|
-
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
|
4593
|
-
const files = [];
|
|
4594
|
-
for (const entry of entries) {
|
|
4595
|
-
const rel = path.join(currentDir, entry.name);
|
|
4596
|
-
if (entry.isDirectory()) {
|
|
4597
|
-
const sub = await collectFiles(rootDir, rel);
|
|
4598
|
-
files.push(...sub);
|
|
4599
|
-
} else if (entry.isFile() && !entry.isSymbolicLink() && !shouldIgnoreFile(rel)) files.push(rel);
|
|
4600
|
-
}
|
|
4601
|
-
return files;
|
|
4602
|
-
}
|
|
4603
|
-
async function uploadSingleFile(client, workspaceId, deploymentId, rootDir, filePath, skippedFiles) {
|
|
4604
|
-
const absPath = path.join(rootDir, filePath);
|
|
4605
|
-
const mime = lookup(filePath);
|
|
4606
|
-
if (!mime) {
|
|
4607
|
-
skippedFiles.push(`${filePath} (unsupported content type; no MIME mapping found)`);
|
|
4608
|
-
return;
|
|
4609
|
-
}
|
|
4610
|
-
const contentType = mime;
|
|
4611
|
-
const readStream = fs.createReadStream(absPath, { highWaterMark: CHUNK_SIZE });
|
|
4612
|
-
async function* requestStream() {
|
|
4613
|
-
yield { payload: {
|
|
4614
|
-
case: "initialMetadata",
|
|
4615
|
-
value: {
|
|
4616
|
-
workspaceId,
|
|
4617
|
-
deploymentId,
|
|
4618
|
-
filePath,
|
|
4619
|
-
contentType
|
|
4620
|
-
}
|
|
4621
|
-
} };
|
|
4622
|
-
for await (const chunk of readStream) yield { payload: {
|
|
4623
|
-
case: "chunkData",
|
|
4624
|
-
value: chunk
|
|
4625
|
-
} };
|
|
4626
|
-
}
|
|
4627
|
-
async function uploadWithLogging() {
|
|
4628
|
-
try {
|
|
4629
|
-
await client.uploadFile(requestStream());
|
|
4630
|
-
} catch (error) {
|
|
4631
|
-
if (error instanceof ConnectError && error.code === Code.InvalidArgument) {
|
|
4632
|
-
skippedFiles.push(`${filePath} (server rejected file as invalid: ${error.message})`);
|
|
4633
|
-
return;
|
|
4634
|
-
}
|
|
4635
|
-
throw error;
|
|
4636
|
-
}
|
|
4637
|
-
}
|
|
4638
|
-
await withTimeout(uploadWithLogging(), 2 * 6e4, `Upload timed out for "${filePath}"`);
|
|
4639
|
-
}
|
|
4640
|
-
/**
|
|
4641
|
-
* Log skipped files after a deployment, including reasons for skipping.
|
|
4642
|
-
* @param skippedFiles - List of skipped file descriptions
|
|
4643
|
-
*/
|
|
4644
|
-
function logSkippedFiles(skippedFiles) {
|
|
4645
|
-
if (skippedFiles.length === 0) return;
|
|
4646
|
-
logger.warn("Deployment completed, but some files failed to upload. These files may have unsupported content types or other validation issues. Please review the list below:");
|
|
4647
|
-
for (const file of skippedFiles) logger.log(` - ${file}`);
|
|
4648
|
-
}
|
|
4649
|
-
const deployCommand = defineAppCommand({
|
|
4650
|
-
name: "deploy",
|
|
4651
|
-
description: "Deploy a static website from a local build directory.",
|
|
4652
|
-
args: z.strictObject({
|
|
4653
|
-
...workspaceArgs,
|
|
4654
|
-
name: arg(z.string(), {
|
|
4655
|
-
alias: "n",
|
|
4656
|
-
description: "Static website name"
|
|
4657
|
-
}),
|
|
4658
|
-
dir: arg(z.string(), {
|
|
4659
|
-
alias: "d",
|
|
4660
|
-
description: "Path to the static website files",
|
|
4661
|
-
completion: { type: "directory" }
|
|
4662
|
-
})
|
|
4663
|
-
}),
|
|
4664
|
-
run: async (args) => {
|
|
4665
|
-
await assertWritable({ profile: args.profile });
|
|
4666
|
-
logger.info(`Deploying static website "${args.name}" from directory: ${args.dir}`);
|
|
4667
|
-
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
4668
|
-
const name = args.name;
|
|
4669
|
-
const dir = path.resolve(process.cwd(), args.dir);
|
|
4670
|
-
const workspaceId = await loadWorkspaceId({
|
|
4671
|
-
workspaceId: args["workspace-id"],
|
|
4672
|
-
profile: args.profile
|
|
4673
|
-
});
|
|
4674
|
-
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) throw new Error(`Directory not found or not a directory: ${dir}`);
|
|
4675
|
-
const { url, skippedFiles } = await withTimeout(deployStaticWebsite(client, workspaceId, name, dir, !args.json), 10 * 6e4, "Deployment timed out after 10 minutes.");
|
|
4676
|
-
if (args.json) logger.out({
|
|
4677
|
-
name,
|
|
4678
|
-
workspaceId,
|
|
4679
|
-
url,
|
|
4680
|
-
skippedFiles
|
|
4681
|
-
});
|
|
4682
|
-
else {
|
|
4683
|
-
logger.success(`Static website "${name}" deployed successfully. URL: ${url}`);
|
|
4684
|
-
logSkippedFiles(skippedFiles);
|
|
4685
|
-
}
|
|
4686
|
-
}
|
|
4687
|
-
});
|
|
4688
|
-
|
|
4689
5187
|
//#endregion
|
|
4690
5188
|
//#region src/cli/commands/staticwebsite/domain/status.ts
|
|
4691
5189
|
const statusLabels = {
|
|
@@ -4896,7 +5394,7 @@ const staticwebsiteCommand = defineCommand({
|
|
|
4896
5394
|
name: "staticwebsite",
|
|
4897
5395
|
description: "Manage static websites in your workspace.",
|
|
4898
5396
|
subCommands: {
|
|
4899
|
-
deploy: deployCommand,
|
|
5397
|
+
deploy: deployCommand$1,
|
|
4900
5398
|
domain: domainCommand,
|
|
4901
5399
|
list: listCommand$2,
|
|
4902
5400
|
get: getCommand
|
|
@@ -4906,1201 +5404,6 @@ const staticwebsiteCommand = defineCommand({
|
|
|
4906
5404
|
}
|
|
4907
5405
|
});
|
|
4908
5406
|
|
|
4909
|
-
//#endregion
|
|
4910
|
-
//#region src/cli/commands/tailordb/erd/local-schema.ts
|
|
4911
|
-
/**
|
|
4912
|
-
* Resolve TailorDB namespaces that need local type loading for ERD generation.
|
|
4913
|
-
* @param config - Loaded Tailor config.
|
|
4914
|
-
* @param options - Namespace selection options.
|
|
4915
|
-
* @returns Namespace names to load, or undefined to load all owned namespaces.
|
|
4916
|
-
*/
|
|
4917
|
-
function resolveLocalErdSchemaNamespaces(config, options) {
|
|
4918
|
-
if (options.namespaces) return options.namespaces;
|
|
4919
|
-
if (!options.requireErdSite) return;
|
|
4920
|
-
return Object.entries(config.db ?? {}).flatMap(([namespace, dbConfig]) => "external" in dbConfig || !dbConfig.erdSite ? [] : [namespace]);
|
|
4921
|
-
}
|
|
4922
|
-
/**
|
|
4923
|
-
* Load local TailorDB namespaces exactly as SDK generation/deploy sees them.
|
|
4924
|
-
* @param options - Local schema loading options.
|
|
4925
|
-
* @returns Loaded TailorDB namespace data.
|
|
4926
|
-
*/
|
|
4927
|
-
async function loadLocalErdSchema(options) {
|
|
4928
|
-
const { config, plugins } = await loadConfig(options.configPath);
|
|
4929
|
-
await generateUserTypes({
|
|
4930
|
-
config,
|
|
4931
|
-
configPath: config.path
|
|
4932
|
-
});
|
|
4933
|
-
const application = defineApplication({
|
|
4934
|
-
config,
|
|
4935
|
-
pluginManager: plugins.length > 0 ? new PluginManager(plugins) : void 0
|
|
4936
|
-
});
|
|
4937
|
-
const namespaceNames = resolveLocalErdSchemaNamespaces(config, {
|
|
4938
|
-
namespaces: options.namespaces,
|
|
4939
|
-
requireErdSite: options.requireErdSite
|
|
4940
|
-
});
|
|
4941
|
-
const namespaceFilter = namespaceNames ? new Set(namespaceNames) : void 0;
|
|
4942
|
-
const services = namespaceFilter ? application.tailorDBServices.filter((db) => namespaceFilter.has(db.namespace)) : application.tailorDBServices;
|
|
4943
|
-
if (namespaceFilter && services.length !== namespaceFilter.size) {
|
|
4944
|
-
const available = application.tailorDBServices.map((db) => db.namespace).join(", ");
|
|
4945
|
-
const requested = [...namespaceFilter].join(", ");
|
|
4946
|
-
throw new Error(`TailorDB namespace "${requested}" not found in local config.db.` + (available ? ` Available owned namespaces: ${available}` : ""));
|
|
4947
|
-
}
|
|
4948
|
-
const namespaces = [];
|
|
4949
|
-
for (const db of services) {
|
|
4950
|
-
await db.loadTypes();
|
|
4951
|
-
await db.processNamespacePlugins();
|
|
4952
|
-
namespaces.push({
|
|
4953
|
-
namespace: db.namespace,
|
|
4954
|
-
types: { ...db.types },
|
|
4955
|
-
sourceInfo: new Map(Object.entries(db.typeSourceInfo)),
|
|
4956
|
-
pluginAttachments: db.pluginAttachments
|
|
4957
|
-
});
|
|
4958
|
-
}
|
|
4959
|
-
return {
|
|
4960
|
-
config,
|
|
4961
|
-
namespaces
|
|
4962
|
-
};
|
|
4963
|
-
}
|
|
4964
|
-
|
|
4965
|
-
//#endregion
|
|
4966
|
-
//#region src/cli/commands/tailordb/erd/schema.ts
|
|
4967
|
-
const CLEAN_ROOM_NOTES = [
|
|
4968
|
-
"Generated by a TailorDB-specific viewer implementation.",
|
|
4969
|
-
"The implementation is based on TailorDB schema contracts, public Liam documentation, Liam CLI help, and black-box generated-output observation.",
|
|
4970
|
-
"It does not copy Liam source code, generated JavaScript/CSS, parser internals, or layout internals."
|
|
4971
|
-
];
|
|
4972
|
-
function buildRevision(schema) {
|
|
4973
|
-
return hashContent$1(JSON.stringify(schema)).slice(0, 16);
|
|
4974
|
-
}
|
|
4975
|
-
function toTypeSource(source) {
|
|
4976
|
-
if (!source) return void 0;
|
|
4977
|
-
if (isPluginGeneratedType(source)) return {
|
|
4978
|
-
kind: "plugin",
|
|
4979
|
-
exportName: source.exportName,
|
|
4980
|
-
pluginId: source.pluginId,
|
|
4981
|
-
pluginImportPath: source.pluginImportPath,
|
|
4982
|
-
originalExportName: source.originalExportName,
|
|
4983
|
-
generatedTypeKind: source.generatedTypeKind,
|
|
4984
|
-
namespace: source.namespace
|
|
4985
|
-
};
|
|
4986
|
-
return {
|
|
4987
|
-
kind: "user",
|
|
4988
|
-
exportName: source.exportName
|
|
4989
|
-
};
|
|
4990
|
-
}
|
|
4991
|
-
function toRelationships(relationships) {
|
|
4992
|
-
return Object.entries(relationships).toSorted(([a], [b]) => a.localeCompare(b)).map(([name, relationship]) => ({
|
|
4993
|
-
name,
|
|
4994
|
-
targetType: relationship.targetType,
|
|
4995
|
-
targetField: relationship.targetField,
|
|
4996
|
-
sourceField: relationship.sourceField,
|
|
4997
|
-
isArray: relationship.isArray,
|
|
4998
|
-
...relationship.description && { description: relationship.description }
|
|
4999
|
-
}));
|
|
5000
|
-
}
|
|
5001
|
-
function toIndexes(type) {
|
|
5002
|
-
return Object.entries(type.indexes ?? {}).toSorted(([a], [b]) => a.localeCompare(b)).map(([name, index]) => ({
|
|
5003
|
-
name,
|
|
5004
|
-
fields: [...index.fields],
|
|
5005
|
-
unique: index.unique === true
|
|
5006
|
-
}));
|
|
5007
|
-
}
|
|
5008
|
-
function toColumnRelation(options) {
|
|
5009
|
-
const { fieldConfig, parsedField } = options;
|
|
5010
|
-
if (!fieldConfig.foreignKey || !fieldConfig.foreignKeyType) return;
|
|
5011
|
-
const required = fieldConfig.required !== false;
|
|
5012
|
-
const kind = parsedField?.relation || fieldConfig.rawRelation ? "relation" : "foreignKey";
|
|
5013
|
-
return {
|
|
5014
|
-
targetTable: fieldConfig.foreignKeyType,
|
|
5015
|
-
targetColumn: fieldConfig.foreignKeyField || "id",
|
|
5016
|
-
kind,
|
|
5017
|
-
required,
|
|
5018
|
-
...fieldConfig.rawRelation?.type && { relationType: fieldConfig.rawRelation.type },
|
|
5019
|
-
...parsedField?.relation?.forwardName && { forwardName: parsedField.relation.forwardName },
|
|
5020
|
-
...parsedField?.relation?.backwardName && { backwardName: parsedField.relation.backwardName }
|
|
5021
|
-
};
|
|
5022
|
-
}
|
|
5023
|
-
function toColumn(options) {
|
|
5024
|
-
const { fieldName, fieldConfig } = options;
|
|
5025
|
-
const indexNames = options.indexEntries.filter(([, index]) => index.fields.includes(fieldName)).map(([name]) => name);
|
|
5026
|
-
const uniqueIndexNames = options.indexEntries.filter(([, index]) => index.unique && index.fields.includes(fieldName)).map(([name]) => name);
|
|
5027
|
-
const enumValues = fieldConfig.allowedValues?.map((value) => value.value) ?? [];
|
|
5028
|
-
const enumValueDescriptions = Object.fromEntries((fieldConfig.allowedValues ?? []).flatMap((value) => value.description ? [[value.value, value.description]] : []));
|
|
5029
|
-
const nestedFields = Object.entries(fieldConfig.fields ?? {}).map(([nestedName, nestedConfig]) => toColumn({
|
|
5030
|
-
fieldName: nestedName,
|
|
5031
|
-
fieldConfig: nestedConfig,
|
|
5032
|
-
indexEntries: []
|
|
5033
|
-
}));
|
|
5034
|
-
const relation = toColumnRelation(options);
|
|
5035
|
-
return {
|
|
5036
|
-
name: fieldName,
|
|
5037
|
-
type: fieldConfig.type || "string",
|
|
5038
|
-
required: fieldConfig.required !== false,
|
|
5039
|
-
array: fieldConfig.array === true,
|
|
5040
|
-
...fieldConfig.description && { description: fieldConfig.description },
|
|
5041
|
-
...fieldConfig.unique && { unique: true },
|
|
5042
|
-
...(fieldConfig.index || indexNames.length > 0) && { index: true },
|
|
5043
|
-
...indexNames.length > 0 && { indexNames },
|
|
5044
|
-
...uniqueIndexNames.length > 0 && { uniqueIndexNames },
|
|
5045
|
-
...enumValues.length > 0 && { enumValues },
|
|
5046
|
-
...Object.keys(enumValueDescriptions).length > 0 && { enumValueDescriptions },
|
|
5047
|
-
...fieldConfig.vector && { vector: true },
|
|
5048
|
-
...fieldConfig.serial && { serial: { ...fieldConfig.serial } },
|
|
5049
|
-
...fieldConfig.scale !== void 0 && { scale: fieldConfig.scale },
|
|
5050
|
-
...fieldConfig.validate?.length && { validations: fieldConfig.validate.length },
|
|
5051
|
-
...fieldConfig.hooks && { hooks: {
|
|
5052
|
-
...fieldConfig.hooks.create && { create: true },
|
|
5053
|
-
...fieldConfig.hooks.update && { update: true }
|
|
5054
|
-
} },
|
|
5055
|
-
...nestedFields.length > 0 && { fields: nestedFields },
|
|
5056
|
-
...relation && { relation }
|
|
5057
|
-
};
|
|
5058
|
-
}
|
|
5059
|
-
function toTable(type, source) {
|
|
5060
|
-
const indexes = toIndexes(type);
|
|
5061
|
-
const indexEntries = indexes.map((index) => [index.name, index]);
|
|
5062
|
-
const fieldColumns = Object.entries(type.fields).filter(([fieldName]) => fieldName !== "id").map(([fieldName, field]) => toColumn({
|
|
5063
|
-
fieldName,
|
|
5064
|
-
fieldConfig: field.config,
|
|
5065
|
-
parsedField: field,
|
|
5066
|
-
indexEntries
|
|
5067
|
-
}));
|
|
5068
|
-
const typeSource = toTypeSource(source);
|
|
5069
|
-
return {
|
|
5070
|
-
name: type.name,
|
|
5071
|
-
pluralForm: type.pluralForm,
|
|
5072
|
-
...type.description && { description: type.description },
|
|
5073
|
-
...typeSource && { source: typeSource },
|
|
5074
|
-
columns: [{
|
|
5075
|
-
name: "id",
|
|
5076
|
-
type: "uuid",
|
|
5077
|
-
required: true,
|
|
5078
|
-
array: false,
|
|
5079
|
-
primaryKey: true,
|
|
5080
|
-
unique: true
|
|
5081
|
-
}, ...fieldColumns],
|
|
5082
|
-
indexes,
|
|
5083
|
-
forwardRelationships: toRelationships(type.forwardRelationships),
|
|
5084
|
-
backwardRelationships: toRelationships(type.backwardRelationships)
|
|
5085
|
-
};
|
|
5086
|
-
}
|
|
5087
|
-
function toRelation(sourceTable, field) {
|
|
5088
|
-
const relation = toColumnRelation({
|
|
5089
|
-
fieldName: field.name,
|
|
5090
|
-
fieldConfig: field.config,
|
|
5091
|
-
parsedField: field,
|
|
5092
|
-
indexEntries: []
|
|
5093
|
-
});
|
|
5094
|
-
if (!relation) return void 0;
|
|
5095
|
-
return {
|
|
5096
|
-
name: `${sourceTable}.${field.name}->${relation.targetTable}.${relation.targetColumn}`,
|
|
5097
|
-
sourceTable,
|
|
5098
|
-
sourceColumns: [field.name],
|
|
5099
|
-
targetTable: relation.targetTable,
|
|
5100
|
-
targetColumns: [relation.targetColumn],
|
|
5101
|
-
required: relation.required,
|
|
5102
|
-
unique: field.config.unique === true || field.relation?.unique === true,
|
|
5103
|
-
kind: relation.kind,
|
|
5104
|
-
...relation.relationType && { relationType: relation.relationType },
|
|
5105
|
-
...relation.forwardName && { forwardName: relation.forwardName },
|
|
5106
|
-
...relation.backwardName && { backwardName: relation.backwardName }
|
|
5107
|
-
};
|
|
5108
|
-
}
|
|
5109
|
-
function buildRelations(types) {
|
|
5110
|
-
const relations = [];
|
|
5111
|
-
for (const type of Object.values(types)) for (const field of Object.values(type.fields)) {
|
|
5112
|
-
const relation = toRelation(type.name, field);
|
|
5113
|
-
if (relation) relations.push(relation);
|
|
5114
|
-
}
|
|
5115
|
-
return relations.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
5116
|
-
}
|
|
5117
|
-
/**
|
|
5118
|
-
* Build the TailorDB ERD viewer schema for one namespace.
|
|
5119
|
-
* @param options - Schema build options.
|
|
5120
|
-
* @returns TailorDB ERD viewer schema.
|
|
5121
|
-
*/
|
|
5122
|
-
function buildTailorDbErdSchema(options) {
|
|
5123
|
-
const { namespaceData } = options;
|
|
5124
|
-
const tables = Object.values(namespaceData.types).toSorted((a, b) => a.name.localeCompare(b.name)).map((type) => toTable(type, namespaceData.sourceInfo.get(type.name)));
|
|
5125
|
-
const schemaWithoutRevision = {
|
|
5126
|
-
version: 1,
|
|
5127
|
-
namespace: namespaceData.namespace,
|
|
5128
|
-
source: options.source ?? "local",
|
|
5129
|
-
cleanRoom: {
|
|
5130
|
-
implementation: "tailor",
|
|
5131
|
-
notes: CLEAN_ROOM_NOTES
|
|
5132
|
-
},
|
|
5133
|
-
tables,
|
|
5134
|
-
relations: buildRelations(namespaceData.types)
|
|
5135
|
-
};
|
|
5136
|
-
return {
|
|
5137
|
-
...schemaWithoutRevision,
|
|
5138
|
-
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5139
|
-
revision: buildRevision(schemaWithoutRevision)
|
|
5140
|
-
};
|
|
5141
|
-
}
|
|
5142
|
-
|
|
5143
|
-
//#endregion
|
|
5144
|
-
//#region src/cli/commands/tailordb/erd/utils.ts
|
|
5145
|
-
/**
|
|
5146
|
-
* Initialize shared ERD command behavior.
|
|
5147
|
-
*/
|
|
5148
|
-
function initErdCommand() {
|
|
5149
|
-
logBetaWarning("tailordb erd");
|
|
5150
|
-
}
|
|
5151
|
-
/**
|
|
5152
|
-
* Initialize platform context for ERD deployment.
|
|
5153
|
-
* @param args - CLI arguments.
|
|
5154
|
-
* @returns Initialized deploy context.
|
|
5155
|
-
*/
|
|
5156
|
-
async function initErdDeployContext(args) {
|
|
5157
|
-
initErdCommand();
|
|
5158
|
-
return {
|
|
5159
|
-
client: await initOperatorClient(await loadAccessToken({ profile: args.profile })),
|
|
5160
|
-
workspaceId: await loadWorkspaceId({
|
|
5161
|
-
workspaceId: args.workspaceId,
|
|
5162
|
-
profile: args.profile
|
|
5163
|
-
})
|
|
5164
|
-
};
|
|
5165
|
-
}
|
|
5166
|
-
|
|
5167
|
-
//#endregion
|
|
5168
|
-
//#region src/cli/commands/tailordb/erd/viewer.ts
|
|
5169
|
-
const VIEWER_ASSETS_DIR = "erd-viewer-assets";
|
|
5170
|
-
const STYLES_LINK = "<link rel=\"stylesheet\" href=\"./styles.css\" />";
|
|
5171
|
-
const APP_SCRIPT = "<script src=\"./app.js\" type=\"module\"><\/script>";
|
|
5172
|
-
function escapeHtml(value) {
|
|
5173
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
5174
|
-
}
|
|
5175
|
-
function assetDirCandidates() {
|
|
5176
|
-
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
5177
|
-
return [
|
|
5178
|
-
path.join(currentDir, "viewer-assets"),
|
|
5179
|
-
path.join(currentDir, VIEWER_ASSETS_DIR),
|
|
5180
|
-
path.join(currentDir, "commands", "tailordb", "erd", VIEWER_ASSETS_DIR),
|
|
5181
|
-
path.resolve(process.cwd(), "packages/sdk/src/cli/commands/tailordb/erd/viewer-assets")
|
|
5182
|
-
];
|
|
5183
|
-
}
|
|
5184
|
-
/**
|
|
5185
|
-
* Resolve the packaged ERD viewer asset directory.
|
|
5186
|
-
* @returns Absolute path to the viewer asset directory.
|
|
5187
|
-
*/
|
|
5188
|
-
function resolveViewerAssetsDir() {
|
|
5189
|
-
for (const candidate of assetDirCandidates()) if (fs$1.existsSync(path.join(candidate, "index.html"))) return candidate;
|
|
5190
|
-
throw new Error(`ERD viewer assets not found. Checked: ${assetDirCandidates().join(", ")}`);
|
|
5191
|
-
}
|
|
5192
|
-
/**
|
|
5193
|
-
* Build the self-contained ERD viewer HTML document. CSS, JS, and the schema
|
|
5194
|
-
* are inlined as separately extractable blocks: a `<style>` element, a
|
|
5195
|
-
* `<script type="module">`, and a `<script type="application/json"
|
|
5196
|
-
* id="erd-schema">` data block. This renders without any sibling asset files
|
|
5197
|
-
* and lets external tooling (including the ERD diff command) pull out the
|
|
5198
|
-
* schema via `JSON.parse`.
|
|
5199
|
-
* @param options - Viewer build options.
|
|
5200
|
-
* @returns The self-contained HTML document.
|
|
5201
|
-
*/
|
|
5202
|
-
function buildViewerHtml(options) {
|
|
5203
|
-
const assetsDir = resolveViewerAssetsDir();
|
|
5204
|
-
const html = fs$1.readFileSync(path.join(assetsDir, "index.html"), "utf8");
|
|
5205
|
-
const css = fs$1.readFileSync(path.join(assetsDir, "styles.css"), "utf8");
|
|
5206
|
-
const appJs = fs$1.readFileSync(path.join(assetsDir, "app.js"), "utf8");
|
|
5207
|
-
if (!html.includes(STYLES_LINK) || !html.includes(APP_SCRIPT)) throw new Error("ERD viewer index.html is missing expected asset references for inlining.");
|
|
5208
|
-
const embedScript = `<script type="application/json" id="erd-schema">${JSON.stringify(options.schema).replaceAll("<", "\\u003c")}<\/script>`;
|
|
5209
|
-
const currentSchemaScript = options.currentSchema === void 0 ? "" : `\n <script type="application/json" id="erd-current-schema">${JSON.stringify(options.currentSchema).replaceAll("<", "\\u003c")}<\/script>`;
|
|
5210
|
-
const diffScript = options.diff === void 0 ? "" : `\n <script type="application/json" id="erd-diff">${JSON.stringify(options.diff).replaceAll("<", "\\u003c")}<\/script>`;
|
|
5211
|
-
const inlineScript = `<script type="module">\n${appJs.replace(/<\/script/gi, "<\\/script")}\n<\/script>`;
|
|
5212
|
-
return html.replace("<title>TailorDB ERD</title>", () => `<title>${escapeHtml(options.title ?? "TailorDB ERD")}</title>`).replace(STYLES_LINK, () => `<style>\n${css}\n</style>`).replace(APP_SCRIPT, () => `${embedScript}${currentSchemaScript}${diffScript}\n ${inlineScript}`);
|
|
5213
|
-
}
|
|
5214
|
-
/**
|
|
5215
|
-
* Write the self-contained TailorDB ERD viewer to `<distDir>/index.html`.
|
|
5216
|
-
* @param options - Viewer dist write options.
|
|
5217
|
-
*/
|
|
5218
|
-
function writeViewerDist(options) {
|
|
5219
|
-
fs$1.rmSync(options.distDir, {
|
|
5220
|
-
recursive: true,
|
|
5221
|
-
force: true
|
|
5222
|
-
});
|
|
5223
|
-
fs$1.mkdirSync(options.distDir, { recursive: true });
|
|
5224
|
-
fs$1.writeFileSync(path.join(options.distDir, "index.html"), buildViewerHtml({ schema: options.schema }), "utf8");
|
|
5225
|
-
}
|
|
5226
|
-
|
|
5227
|
-
//#endregion
|
|
5228
|
-
//#region src/cli/commands/tailordb/erd/export.ts
|
|
5229
|
-
const DEFAULT_ERD_BASE_DIR$1 = ".tailor/erd";
|
|
5230
|
-
function getErdSite(context, namespace) {
|
|
5231
|
-
const dbConfig = context.config.db?.[namespace];
|
|
5232
|
-
if (!dbConfig || "external" in dbConfig) return;
|
|
5233
|
-
return dbConfig.erdSite;
|
|
5234
|
-
}
|
|
5235
|
-
function resolveExplicitTarget(options) {
|
|
5236
|
-
const namespaceData = options.context.namespaces.find((candidate) => candidate.namespace === options.namespace);
|
|
5237
|
-
if (!namespaceData) {
|
|
5238
|
-
const available = options.context.namespaces.map((candidate) => candidate.namespace).join(", ");
|
|
5239
|
-
throw new Error(`TailorDB namespace "${options.namespace}" not found in local config.db.` + (available ? ` Available owned namespaces: ${available}` : ""));
|
|
5240
|
-
}
|
|
5241
|
-
const erdSite = getErdSite(options.context, namespaceData.namespace);
|
|
5242
|
-
if (options.requireErdSite && !erdSite) throw new Error(`No erdSite configured for namespace "${namespaceData.namespace}". Add erdSite: "<static-website-name>" to db.${namespaceData.namespace} in tailor.config.ts.`);
|
|
5243
|
-
return toTarget(options.outputDir, namespaceData, erdSite);
|
|
5244
|
-
}
|
|
5245
|
-
function resolveAllTargets(options) {
|
|
5246
|
-
const namespaces = options.context.namespaces.filter((namespaceData) => !options.requireErdSite || getErdSite(options.context, namespaceData.namespace));
|
|
5247
|
-
if (namespaces.length === 0) throw new Error(options.requireErdSite ? "No namespaces with erdSite configured found. Add erdSite: \"<static-website-name>\" to db.<namespace> in tailor.config.ts." : "No TailorDB namespaces found in config. Please define db services in tailor.config.ts.");
|
|
5248
|
-
logger.info(`Found ${namespaces.length} namespace(s)${options.requireErdSite ? " with erdSite configured" : ""}.`);
|
|
5249
|
-
return namespaces.map((namespaceData) => toTarget(options.outputDir, namespaceData, getErdSite(options.context, namespaceData.namespace)));
|
|
5250
|
-
}
|
|
5251
|
-
function toTarget(outputDir, namespaceData, erdSite) {
|
|
5252
|
-
return {
|
|
5253
|
-
namespaceData,
|
|
5254
|
-
erdSite,
|
|
5255
|
-
distDir: path.join(outputDir, namespaceData.namespace, "dist")
|
|
5256
|
-
};
|
|
5257
|
-
}
|
|
5258
|
-
function resolveTargets(options) {
|
|
5259
|
-
if (options.namespace) return [resolveExplicitTarget(options)];
|
|
5260
|
-
return resolveAllTargets(options);
|
|
5261
|
-
}
|
|
5262
|
-
function prepareErdBuild(target) {
|
|
5263
|
-
writeViewerDist({
|
|
5264
|
-
schema: buildTailorDbErdSchema({ namespaceData: target.namespaceData }),
|
|
5265
|
-
distDir: target.distDir
|
|
5266
|
-
});
|
|
5267
|
-
const relativePath = path.relative(process.cwd(), target.distDir);
|
|
5268
|
-
logger.success(`Built ERD to ${relativePath}`);
|
|
5269
|
-
return {
|
|
5270
|
-
namespace: target.namespaceData.namespace,
|
|
5271
|
-
erdSite: target.erdSite,
|
|
5272
|
-
distDir: target.distDir
|
|
5273
|
-
};
|
|
5274
|
-
}
|
|
5275
|
-
/**
|
|
5276
|
-
* Prepare TailorDB ERD static viewer builds for one or more namespaces.
|
|
5277
|
-
* @param options - Build options.
|
|
5278
|
-
* @returns Build results by namespace.
|
|
5279
|
-
*/
|
|
5280
|
-
async function prepareErdBuilds(options) {
|
|
5281
|
-
return prepareErdBuildsFromContext({
|
|
5282
|
-
context: await loadLocalErdSchema({
|
|
5283
|
-
configPath: options.configPath,
|
|
5284
|
-
namespaces: options.namespace ? [options.namespace] : void 0,
|
|
5285
|
-
requireErdSite: options.requireErdSite
|
|
5286
|
-
}),
|
|
5287
|
-
namespace: options.namespace,
|
|
5288
|
-
outputDir: options.outputDir,
|
|
5289
|
-
requireErdSite: options.requireErdSite
|
|
5290
|
-
});
|
|
5291
|
-
}
|
|
5292
|
-
/**
|
|
5293
|
-
* Prepare TailorDB ERD static viewer builds from an already loaded schema context.
|
|
5294
|
-
* @param options - Build options.
|
|
5295
|
-
* @returns Build results by namespace.
|
|
5296
|
-
*/
|
|
5297
|
-
function prepareErdBuildsFromContext(options) {
|
|
5298
|
-
const outputDir = path.resolve(process.cwd(), options.outputDir ?? DEFAULT_ERD_BASE_DIR$1);
|
|
5299
|
-
return resolveTargets({
|
|
5300
|
-
context: options.context,
|
|
5301
|
-
namespace: options.namespace,
|
|
5302
|
-
outputDir,
|
|
5303
|
-
requireErdSite: options.requireErdSite
|
|
5304
|
-
}).map((target) => prepareErdBuild(target));
|
|
5305
|
-
}
|
|
5306
|
-
const erdExportCommand = defineAppCommand({
|
|
5307
|
-
name: "export",
|
|
5308
|
-
description: "Export TailorDB ERD static viewer from local TailorDB schema.",
|
|
5309
|
-
args: z.strictObject({
|
|
5310
|
-
...configArg,
|
|
5311
|
-
namespace: arg(z.string().optional(), {
|
|
5312
|
-
alias: "n",
|
|
5313
|
-
description: "TailorDB namespace name (optional if only one namespace is defined in config)"
|
|
5314
|
-
}),
|
|
5315
|
-
output: arg(z.string().default(DEFAULT_ERD_BASE_DIR$1), {
|
|
5316
|
-
alias: "o",
|
|
5317
|
-
description: "Output directory path for TailorDB ERD viewer files (writes to `<outputDir>/<namespace>/dist`)",
|
|
5318
|
-
completion: { type: "directory" }
|
|
5319
|
-
})
|
|
5320
|
-
}),
|
|
5321
|
-
run: async (args) => {
|
|
5322
|
-
initErdCommand();
|
|
5323
|
-
const results = await prepareErdBuilds({
|
|
5324
|
-
configPath: args.config,
|
|
5325
|
-
namespace: args.namespace,
|
|
5326
|
-
outputDir: args.output
|
|
5327
|
-
});
|
|
5328
|
-
logger.newline();
|
|
5329
|
-
if (args.json) logger.out(results.map((result) => ({
|
|
5330
|
-
namespace: result.namespace,
|
|
5331
|
-
distDir: result.distDir
|
|
5332
|
-
})));
|
|
5333
|
-
else for (const result of results) {
|
|
5334
|
-
logger.out(`Exported ERD for namespace "${result.namespace}"`);
|
|
5335
|
-
logger.out(` - ERD viewer: ${path.join(result.distDir, "index.html")}`);
|
|
5336
|
-
}
|
|
5337
|
-
}
|
|
5338
|
-
});
|
|
5339
|
-
|
|
5340
|
-
//#endregion
|
|
5341
|
-
//#region src/cli/commands/tailordb/erd/deploy.ts
|
|
5342
|
-
const erdDeployCommand = defineAppCommand({
|
|
5343
|
-
name: "deploy",
|
|
5344
|
-
description: "Deploy ERD static website for TailorDB namespace(s).",
|
|
5345
|
-
args: z.strictObject({
|
|
5346
|
-
...deploymentArgs,
|
|
5347
|
-
namespace: arg(z.string().optional(), {
|
|
5348
|
-
alias: "n",
|
|
5349
|
-
description: "TailorDB namespace name (optional - deploys all namespaces with erdSite if omitted)"
|
|
5350
|
-
})
|
|
5351
|
-
}),
|
|
5352
|
-
run: async (args) => {
|
|
5353
|
-
await assertWritable({ profile: args.profile });
|
|
5354
|
-
const { client, workspaceId } = await initErdDeployContext(args);
|
|
5355
|
-
const buildResults = await prepareErdBuilds({
|
|
5356
|
-
configPath: args.config,
|
|
5357
|
-
namespace: args.namespace,
|
|
5358
|
-
requireErdSite: true
|
|
5359
|
-
});
|
|
5360
|
-
const deployResults = await Promise.all(buildResults.map(async (result) => {
|
|
5361
|
-
if (!result.erdSite) throw new Error(`No erdSite configured for namespace "${result.namespace}". Add erdSite: "<static-website-name>" to db.${result.namespace} in tailor.config.ts.`);
|
|
5362
|
-
if (!args.json) logger.info(`Deploying ERD for namespace "${result.namespace}" to site "${result.erdSite}"...`);
|
|
5363
|
-
const { url, skippedFiles } = await deployStaticWebsite(client, workspaceId, result.erdSite, result.distDir, !args.json);
|
|
5364
|
-
return {
|
|
5365
|
-
namespace: result.namespace,
|
|
5366
|
-
erdSite: result.erdSite,
|
|
5367
|
-
url,
|
|
5368
|
-
skippedFiles
|
|
5369
|
-
};
|
|
5370
|
-
}));
|
|
5371
|
-
logger.newline();
|
|
5372
|
-
if (args.json) logger.out(deployResults);
|
|
5373
|
-
else for (const result of deployResults) {
|
|
5374
|
-
logSkippedFiles(result.skippedFiles);
|
|
5375
|
-
logger.newline();
|
|
5376
|
-
logger.success(`ERD site "${result.erdSite}" deployed successfully.`);
|
|
5377
|
-
logger.out(result.url);
|
|
5378
|
-
}
|
|
5379
|
-
}
|
|
5380
|
-
});
|
|
5381
|
-
|
|
5382
|
-
//#endregion
|
|
5383
|
-
//#region src/cli/commands/tailordb/erd/diff.ts
|
|
5384
|
-
const SCHEMA_BLOCK_PATTERN = /<script type="application\/json" id="erd-schema">([\s\S]*?)<\/script>/;
|
|
5385
|
-
function stableValue(value) {
|
|
5386
|
-
if (Array.isArray(value)) return value.map((item) => stableValue(item));
|
|
5387
|
-
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, stableValue(item)]));
|
|
5388
|
-
return value;
|
|
5389
|
-
}
|
|
5390
|
-
function stableJson(value) {
|
|
5391
|
-
return JSON.stringify(stableValue(value));
|
|
5392
|
-
}
|
|
5393
|
-
function changedFields(base, head) {
|
|
5394
|
-
return [.../* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(head)])].toSorted((a, b) => a.localeCompare(b)).filter((key) => stableJson(base[key]) !== stableJson(head[key]));
|
|
5395
|
-
}
|
|
5396
|
-
function detailForChangedFields(fields) {
|
|
5397
|
-
return `Changed fields: ${fields.join(", ")}`;
|
|
5398
|
-
}
|
|
5399
|
-
function mapByName(items) {
|
|
5400
|
-
return new Map(items.map((item) => [item.name, item]));
|
|
5401
|
-
}
|
|
5402
|
-
function compareCommonNamed(baseItems, headItems, addChange) {
|
|
5403
|
-
const baseByName = mapByName(baseItems);
|
|
5404
|
-
const headByName = mapByName(headItems);
|
|
5405
|
-
const commonNames = [...baseByName.keys()].filter((name) => headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
|
|
5406
|
-
for (const name of commonNames) {
|
|
5407
|
-
const base = baseByName.get(name);
|
|
5408
|
-
const head = headByName.get(name);
|
|
5409
|
-
if (!base || !head) continue;
|
|
5410
|
-
const fields = changedFields(base, head);
|
|
5411
|
-
if (fields.length > 0) addChange(name, fields);
|
|
5412
|
-
}
|
|
5413
|
-
}
|
|
5414
|
-
function removedNames(baseItems, headItems) {
|
|
5415
|
-
const headByName = mapByName(headItems);
|
|
5416
|
-
return baseItems.map((item) => item.name).filter((name) => !headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
|
|
5417
|
-
}
|
|
5418
|
-
function addedNames(baseItems, headItems) {
|
|
5419
|
-
const baseByName = mapByName(baseItems);
|
|
5420
|
-
return headItems.map((item) => item.name).filter((name) => !baseByName.has(name)).toSorted((a, b) => a.localeCompare(b));
|
|
5421
|
-
}
|
|
5422
|
-
function tableMetadata(table) {
|
|
5423
|
-
return {
|
|
5424
|
-
backwardRelationships: table.backwardRelationships,
|
|
5425
|
-
description: table.description,
|
|
5426
|
-
forwardRelationships: table.forwardRelationships,
|
|
5427
|
-
pluralForm: table.pluralForm,
|
|
5428
|
-
source: table.source
|
|
5429
|
-
};
|
|
5430
|
-
}
|
|
5431
|
-
function pushChange(changes, change) {
|
|
5432
|
-
changes.push(change);
|
|
5433
|
-
}
|
|
5434
|
-
function diffColumns(changes, tableName, baseColumns, headColumns) {
|
|
5435
|
-
compareCommonNamed(baseColumns, headColumns, (name, fields) => {
|
|
5436
|
-
pushChange(changes, {
|
|
5437
|
-
action: "changed",
|
|
5438
|
-
entity: "column",
|
|
5439
|
-
path: `${tableName}.${name}`,
|
|
5440
|
-
detail: detailForChangedFields(fields)
|
|
5441
|
-
});
|
|
5442
|
-
});
|
|
5443
|
-
for (const name of removedNames(baseColumns, headColumns)) pushChange(changes, {
|
|
5444
|
-
action: "removed",
|
|
5445
|
-
entity: "column",
|
|
5446
|
-
path: `${tableName}.${name}`,
|
|
5447
|
-
detail: "Column removed"
|
|
5448
|
-
});
|
|
5449
|
-
for (const name of addedNames(baseColumns, headColumns)) pushChange(changes, {
|
|
5450
|
-
action: "added",
|
|
5451
|
-
entity: "column",
|
|
5452
|
-
path: `${tableName}.${name}`,
|
|
5453
|
-
detail: "Column added"
|
|
5454
|
-
});
|
|
5455
|
-
}
|
|
5456
|
-
function diffIndexes(changes, tableName, baseIndexes, headIndexes) {
|
|
5457
|
-
compareCommonNamed(baseIndexes, headIndexes, (name, fields) => {
|
|
5458
|
-
pushChange(changes, {
|
|
5459
|
-
action: "changed",
|
|
5460
|
-
entity: "index",
|
|
5461
|
-
path: `${tableName}.${name}`,
|
|
5462
|
-
detail: detailForChangedFields(fields)
|
|
5463
|
-
});
|
|
5464
|
-
});
|
|
5465
|
-
for (const name of removedNames(baseIndexes, headIndexes)) pushChange(changes, {
|
|
5466
|
-
action: "removed",
|
|
5467
|
-
entity: "index",
|
|
5468
|
-
path: `${tableName}.${name}`,
|
|
5469
|
-
detail: "Index removed"
|
|
5470
|
-
});
|
|
5471
|
-
for (const name of addedNames(baseIndexes, headIndexes)) pushChange(changes, {
|
|
5472
|
-
action: "added",
|
|
5473
|
-
entity: "index",
|
|
5474
|
-
path: `${tableName}.${name}`,
|
|
5475
|
-
detail: "Index added"
|
|
5476
|
-
});
|
|
5477
|
-
}
|
|
5478
|
-
function diffTables(changes, baseTables, headTables) {
|
|
5479
|
-
const baseByName = mapByName(baseTables);
|
|
5480
|
-
const headByName = mapByName(headTables);
|
|
5481
|
-
for (const name of removedNames(baseTables, headTables)) pushChange(changes, {
|
|
5482
|
-
action: "removed",
|
|
5483
|
-
entity: "table",
|
|
5484
|
-
path: name,
|
|
5485
|
-
detail: "Table removed"
|
|
5486
|
-
});
|
|
5487
|
-
for (const name of addedNames(baseTables, headTables)) pushChange(changes, {
|
|
5488
|
-
action: "added",
|
|
5489
|
-
entity: "table",
|
|
5490
|
-
path: name,
|
|
5491
|
-
detail: "Table added"
|
|
5492
|
-
});
|
|
5493
|
-
const commonNames = [...baseByName.keys()].filter((name) => headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
|
|
5494
|
-
for (const name of commonNames) {
|
|
5495
|
-
const base = baseByName.get(name);
|
|
5496
|
-
const head = headByName.get(name);
|
|
5497
|
-
if (!base || !head) continue;
|
|
5498
|
-
const tableFields = changedFields(tableMetadata(base), tableMetadata(head));
|
|
5499
|
-
if (tableFields.length > 0) pushChange(changes, {
|
|
5500
|
-
action: "changed",
|
|
5501
|
-
entity: "table",
|
|
5502
|
-
path: name,
|
|
5503
|
-
detail: detailForChangedFields(tableFields)
|
|
5504
|
-
});
|
|
5505
|
-
diffColumns(changes, name, base.columns, head.columns);
|
|
5506
|
-
diffIndexes(changes, name, base.indexes, head.indexes);
|
|
5507
|
-
}
|
|
5508
|
-
}
|
|
5509
|
-
function diffRelations(changes, baseRelations, headRelations) {
|
|
5510
|
-
compareCommonNamed(baseRelations, headRelations, (name, fields) => {
|
|
5511
|
-
pushChange(changes, {
|
|
5512
|
-
action: "changed",
|
|
5513
|
-
entity: "relation",
|
|
5514
|
-
path: name,
|
|
5515
|
-
detail: detailForChangedFields(fields)
|
|
5516
|
-
});
|
|
5517
|
-
});
|
|
5518
|
-
for (const name of removedNames(baseRelations, headRelations)) pushChange(changes, {
|
|
5519
|
-
action: "removed",
|
|
5520
|
-
entity: "relation",
|
|
5521
|
-
path: name,
|
|
5522
|
-
detail: "Relation removed"
|
|
5523
|
-
});
|
|
5524
|
-
for (const name of addedNames(baseRelations, headRelations)) pushChange(changes, {
|
|
5525
|
-
action: "added",
|
|
5526
|
-
entity: "relation",
|
|
5527
|
-
path: name,
|
|
5528
|
-
detail: "Relation added"
|
|
5529
|
-
});
|
|
5530
|
-
}
|
|
5531
|
-
function summarize(changes) {
|
|
5532
|
-
return {
|
|
5533
|
-
added: changes.filter((change) => change.action === "added").length,
|
|
5534
|
-
changed: changes.filter((change) => change.action === "changed").length,
|
|
5535
|
-
removed: changes.filter((change) => change.action === "removed").length
|
|
5536
|
-
};
|
|
5537
|
-
}
|
|
5538
|
-
function extractEmbeddedErdSchema(html) {
|
|
5539
|
-
const match = SCHEMA_BLOCK_PATTERN.exec(html);
|
|
5540
|
-
if (!match?.[1]) throw new Error("ERD schema block not found.");
|
|
5541
|
-
try {
|
|
5542
|
-
return JSON.parse(match[1]);
|
|
5543
|
-
} catch (error) {
|
|
5544
|
-
throw new Error(`Failed to parse ERD schema block: ${String(error)}`, { cause: error });
|
|
5545
|
-
}
|
|
5546
|
-
}
|
|
5547
|
-
function createEmptyErdSchema(options) {
|
|
5548
|
-
return {
|
|
5549
|
-
version: 1,
|
|
5550
|
-
namespace: options.namespace,
|
|
5551
|
-
generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
5552
|
-
revision: options.revision,
|
|
5553
|
-
source: "local",
|
|
5554
|
-
cleanRoom: {
|
|
5555
|
-
implementation: "tailor",
|
|
5556
|
-
notes: ["Synthetic empty schema used for ERD diff generation."]
|
|
5557
|
-
},
|
|
5558
|
-
tables: [],
|
|
5559
|
-
relations: []
|
|
5560
|
-
};
|
|
5561
|
-
}
|
|
5562
|
-
function buildErdSchemaDiff(options) {
|
|
5563
|
-
const { base, head } = options;
|
|
5564
|
-
if (base.namespace !== head.namespace) throw new Error(`Cannot diff ERD schemas from different namespaces: ${base.namespace}, ${head.namespace}`);
|
|
5565
|
-
const changes = [];
|
|
5566
|
-
diffTables(changes, base.tables, head.tables);
|
|
5567
|
-
diffRelations(changes, base.relations, head.relations);
|
|
5568
|
-
const summary = summarize(changes);
|
|
5569
|
-
return {
|
|
5570
|
-
namespace: head.namespace,
|
|
5571
|
-
baseRevision: base.revision,
|
|
5572
|
-
headRevision: head.revision,
|
|
5573
|
-
changed: changes.length > 0,
|
|
5574
|
-
summary,
|
|
5575
|
-
changes
|
|
5576
|
-
};
|
|
5577
|
-
}
|
|
5578
|
-
function mergeNamedByHead(baseItems, headItems) {
|
|
5579
|
-
const headByName = mapByName(headItems);
|
|
5580
|
-
const removedItems = baseItems.filter((item) => !headByName.has(item.name)).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
5581
|
-
return [...headItems, ...removedItems];
|
|
5582
|
-
}
|
|
5583
|
-
function mergeDiffViewerTable(baseTable, headTable) {
|
|
5584
|
-
return {
|
|
5585
|
-
...headTable,
|
|
5586
|
-
columns: mergeNamedByHead(baseTable.columns, headTable.columns),
|
|
5587
|
-
indexes: mergeNamedByHead(baseTable.indexes, headTable.indexes)
|
|
5588
|
-
};
|
|
5589
|
-
}
|
|
5590
|
-
function mergeDiffViewerTables(baseTables, headTables) {
|
|
5591
|
-
const baseByName = mapByName(baseTables);
|
|
5592
|
-
const headByName = mapByName(headTables);
|
|
5593
|
-
const merged = headTables.map((headTable) => {
|
|
5594
|
-
const baseTable = baseByName.get(headTable.name);
|
|
5595
|
-
return baseTable ? mergeDiffViewerTable(baseTable, headTable) : headTable;
|
|
5596
|
-
});
|
|
5597
|
-
const removedTables = baseTables.filter((table) => !headByName.has(table.name)).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
5598
|
-
return [...merged, ...removedTables];
|
|
5599
|
-
}
|
|
5600
|
-
/**
|
|
5601
|
-
* Build the schema rendered by the diff viewer. The ordinary ERD viewer only
|
|
5602
|
-
* knows how to draw objects present in its schema, so the diff schema keeps
|
|
5603
|
-
* base-only tables, columns, indexes, and relations visible for removal
|
|
5604
|
-
* highlighting while preferring head-side metadata for unchanged objects.
|
|
5605
|
-
* @param options - Base and head schemas to merge for diff rendering.
|
|
5606
|
-
* @returns A TailorDB ERD schema suitable for the visual diff viewer.
|
|
5607
|
-
*/
|
|
5608
|
-
function buildErdDiffViewerSchema(options) {
|
|
5609
|
-
const { base, head } = options;
|
|
5610
|
-
if (base.namespace !== head.namespace) throw new Error(`Cannot diff ERD schemas from different namespaces: ${base.namespace}, ${head.namespace}`);
|
|
5611
|
-
return {
|
|
5612
|
-
...head,
|
|
5613
|
-
tables: mergeDiffViewerTables(base.tables, head.tables),
|
|
5614
|
-
relations: mergeNamedByHead(base.relations, head.relations)
|
|
5615
|
-
};
|
|
5616
|
-
}
|
|
5617
|
-
function renderErdDiffHtml(options) {
|
|
5618
|
-
return buildViewerHtml({
|
|
5619
|
-
schema: options.schema,
|
|
5620
|
-
currentSchema: options.currentSchema,
|
|
5621
|
-
diff: options.diff,
|
|
5622
|
-
title: `TailorDB ERD diff - ${options.diff.namespace}`
|
|
5623
|
-
});
|
|
5624
|
-
}
|
|
5625
|
-
|
|
5626
|
-
//#endregion
|
|
5627
|
-
//#region src/cli/commands/tailordb/erd/diff-command.ts
|
|
5628
|
-
function readSchema(filePath) {
|
|
5629
|
-
if (!filePath) return void 0;
|
|
5630
|
-
return extractEmbeddedErdSchema(fs$1.readFileSync(filePath, "utf8"));
|
|
5631
|
-
}
|
|
5632
|
-
function resolveNamespace(options) {
|
|
5633
|
-
const namespace = options.namespace ?? options.head?.namespace ?? options.base?.namespace;
|
|
5634
|
-
if (!namespace) throw new Error("Missing --namespace when one side of the ERD diff is omitted.");
|
|
5635
|
-
if (options.base && options.base.namespace !== namespace) throw new Error(`Base ERD namespace "${options.base.namespace}" does not match "${namespace}".`);
|
|
5636
|
-
if (options.head && options.head.namespace !== namespace) throw new Error(`Head ERD namespace "${options.head.namespace}" does not match "${namespace}".`);
|
|
5637
|
-
return namespace;
|
|
5638
|
-
}
|
|
5639
|
-
function writeFile(filePath, content) {
|
|
5640
|
-
fs$1.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
5641
|
-
fs$1.writeFileSync(filePath, content, "utf8");
|
|
5642
|
-
}
|
|
5643
|
-
function writeErdDiff(options) {
|
|
5644
|
-
const base = readSchema(options.baseHtml);
|
|
5645
|
-
const head = readSchema(options.headHtml);
|
|
5646
|
-
if (!base && !head) throw new Error("At least one of --base-html or --head-html is required.");
|
|
5647
|
-
const namespace = resolveNamespace({
|
|
5648
|
-
namespace: options.namespace,
|
|
5649
|
-
base,
|
|
5650
|
-
head
|
|
5651
|
-
});
|
|
5652
|
-
const baseSchema = base ?? createEmptyErdSchema({
|
|
5653
|
-
namespace,
|
|
5654
|
-
revision: "missing-base"
|
|
5655
|
-
});
|
|
5656
|
-
const headSchema = head ?? createEmptyErdSchema({
|
|
5657
|
-
namespace,
|
|
5658
|
-
revision: "missing-head"
|
|
5659
|
-
});
|
|
5660
|
-
const diff = buildErdSchemaDiff({
|
|
5661
|
-
base: baseSchema,
|
|
5662
|
-
head: headSchema
|
|
5663
|
-
});
|
|
5664
|
-
const viewerSchema = buildErdDiffViewerSchema({
|
|
5665
|
-
base: baseSchema,
|
|
5666
|
-
head: headSchema
|
|
5667
|
-
});
|
|
5668
|
-
writeFile(options.outputHtml, renderErdDiffHtml({
|
|
5669
|
-
schema: viewerSchema,
|
|
5670
|
-
currentSchema: headSchema,
|
|
5671
|
-
diff
|
|
5672
|
-
}));
|
|
5673
|
-
if (options.outputJson) writeFile(options.outputJson, `${JSON.stringify(diff, null, 2)}\n`);
|
|
5674
|
-
return {
|
|
5675
|
-
namespace,
|
|
5676
|
-
outputHtml: options.outputHtml,
|
|
5677
|
-
outputJson: options.outputJson,
|
|
5678
|
-
diff
|
|
5679
|
-
};
|
|
5680
|
-
}
|
|
5681
|
-
const erdDiffCommand = defineAppCommand({
|
|
5682
|
-
name: "diff",
|
|
5683
|
-
description: "Render TailorDB ERD schema diff HTML from exported ERD viewers.",
|
|
5684
|
-
args: z.strictObject({
|
|
5685
|
-
"base-html": arg(z.string().optional(), {
|
|
5686
|
-
description: "Base ERD viewer HTML file",
|
|
5687
|
-
completion: {
|
|
5688
|
-
type: "file",
|
|
5689
|
-
matcher: [".html"]
|
|
5690
|
-
}
|
|
5691
|
-
}),
|
|
5692
|
-
"head-html": arg(z.string().optional(), {
|
|
5693
|
-
description: "Head ERD viewer HTML file",
|
|
5694
|
-
completion: {
|
|
5695
|
-
type: "file",
|
|
5696
|
-
matcher: [".html"]
|
|
5697
|
-
}
|
|
5698
|
-
}),
|
|
5699
|
-
namespace: arg(z.string().optional(), {
|
|
5700
|
-
alias: "n",
|
|
5701
|
-
description: "TailorDB namespace name (defaults to the provided ERD schema namespace)"
|
|
5702
|
-
}),
|
|
5703
|
-
output: arg(z.string().min(1), {
|
|
5704
|
-
alias: "o",
|
|
5705
|
-
description: "Output ERD diff HTML file",
|
|
5706
|
-
completion: {
|
|
5707
|
-
type: "file",
|
|
5708
|
-
matcher: [".html"]
|
|
5709
|
-
}
|
|
5710
|
-
}),
|
|
5711
|
-
"output-json": arg(z.string().optional(), {
|
|
5712
|
-
description: "Optional output JSON file for the computed diff",
|
|
5713
|
-
completion: {
|
|
5714
|
-
type: "file",
|
|
5715
|
-
matcher: [".json"]
|
|
5716
|
-
}
|
|
5717
|
-
})
|
|
5718
|
-
}),
|
|
5719
|
-
run: (args) => {
|
|
5720
|
-
initErdCommand();
|
|
5721
|
-
const result = writeErdDiff({
|
|
5722
|
-
baseHtml: args["base-html"],
|
|
5723
|
-
headHtml: args["head-html"],
|
|
5724
|
-
namespace: args.namespace,
|
|
5725
|
-
outputHtml: args.output,
|
|
5726
|
-
outputJson: args["output-json"]
|
|
5727
|
-
});
|
|
5728
|
-
if (args.json) logger.out({
|
|
5729
|
-
namespace: result.namespace,
|
|
5730
|
-
outputHtml: result.outputHtml,
|
|
5731
|
-
outputJson: result.outputJson,
|
|
5732
|
-
summary: result.diff.summary
|
|
5733
|
-
});
|
|
5734
|
-
else logger.success(`Wrote ERD diff to ${path.relative(process.cwd(), result.outputHtml)}`);
|
|
5735
|
-
}
|
|
5736
|
-
});
|
|
5737
|
-
|
|
5738
|
-
//#endregion
|
|
5739
|
-
//#region src/cli/commands/tailordb/erd/serve.ts
|
|
5740
|
-
const DEFAULT_ERD_BASE_DIR = ".tailor/erd";
|
|
5741
|
-
const LOCAL_HOST = "127.0.0.1";
|
|
5742
|
-
const GLOB_CHARS = /[*?[\]{}()!+@]/;
|
|
5743
|
-
function formatServeCommand(namespace) {
|
|
5744
|
-
return `tailor tailordb erd serve --namespace ${namespace}`;
|
|
5745
|
-
}
|
|
5746
|
-
function getCacheControl(filePath) {
|
|
5747
|
-
return filePath.endsWith(".html") || filePath.endsWith(".json") ? "no-cache" : "public, max-age=3600";
|
|
5748
|
-
}
|
|
5749
|
-
function resolveRequestPath(distDir, requestUrl) {
|
|
5750
|
-
const url = new URL(requestUrl ?? "/", "http://localhost");
|
|
5751
|
-
let pathname;
|
|
5752
|
-
try {
|
|
5753
|
-
pathname = decodeURIComponent(url.pathname);
|
|
5754
|
-
} catch {
|
|
5755
|
-
return;
|
|
5756
|
-
}
|
|
5757
|
-
if (pathname === "/" || pathname.endsWith("/")) pathname = path.join(pathname, "index.html");
|
|
5758
|
-
const root = path.resolve(distDir);
|
|
5759
|
-
const filePath = path.resolve(root, `.${pathname}`);
|
|
5760
|
-
if (filePath !== root && !filePath.startsWith(`${root}${path.sep}`)) return;
|
|
5761
|
-
return filePath;
|
|
5762
|
-
}
|
|
5763
|
-
function openStaticFile(filePath) {
|
|
5764
|
-
let fd;
|
|
5765
|
-
try {
|
|
5766
|
-
fd = fs$1.openSync(filePath, "r");
|
|
5767
|
-
if (!fs$1.fstatSync(fd).isFile()) {
|
|
5768
|
-
fs$1.closeSync(fd);
|
|
5769
|
-
return;
|
|
5770
|
-
}
|
|
5771
|
-
return {
|
|
5772
|
-
filePath,
|
|
5773
|
-
fd
|
|
5774
|
-
};
|
|
5775
|
-
} catch {
|
|
5776
|
-
if (fd !== void 0) fs$1.closeSync(fd);
|
|
5777
|
-
return;
|
|
5778
|
-
}
|
|
5779
|
-
}
|
|
5780
|
-
function serveFile(distDir, req, res) {
|
|
5781
|
-
const filePath = resolveRequestPath(distDir, req.url);
|
|
5782
|
-
if (!filePath) {
|
|
5783
|
-
res.writeHead(403);
|
|
5784
|
-
res.end("Forbidden");
|
|
5785
|
-
return;
|
|
5786
|
-
}
|
|
5787
|
-
const fallbackPath = path.join(distDir, "index.html");
|
|
5788
|
-
const target = openStaticFile(filePath) ?? openStaticFile(fallbackPath);
|
|
5789
|
-
if (!target) {
|
|
5790
|
-
res.writeHead(503, {
|
|
5791
|
-
"Content-Type": "text/plain; charset=utf-8",
|
|
5792
|
-
"Cache-Control": "no-cache",
|
|
5793
|
-
"Retry-After": "1"
|
|
5794
|
-
});
|
|
5795
|
-
res.end("ERD build is refreshing. Please retry.");
|
|
5796
|
-
return;
|
|
5797
|
-
}
|
|
5798
|
-
const mimeType = lookup(target.filePath) || "application/octet-stream";
|
|
5799
|
-
const stream = fs$1.createReadStream(target.filePath, {
|
|
5800
|
-
fd: target.fd,
|
|
5801
|
-
autoClose: true
|
|
5802
|
-
});
|
|
5803
|
-
stream.on("error", () => {
|
|
5804
|
-
if (!res.headersSent) {
|
|
5805
|
-
res.writeHead(503, {
|
|
5806
|
-
"Content-Type": "text/plain; charset=utf-8",
|
|
5807
|
-
"Cache-Control": "no-cache",
|
|
5808
|
-
"Retry-After": "1"
|
|
5809
|
-
});
|
|
5810
|
-
res.end("ERD build is refreshing. Please retry.");
|
|
5811
|
-
return;
|
|
5812
|
-
}
|
|
5813
|
-
res.destroy();
|
|
5814
|
-
});
|
|
5815
|
-
res.writeHead(200, {
|
|
5816
|
-
"Content-Type": mimeType,
|
|
5817
|
-
"Cache-Control": getCacheControl(target.filePath)
|
|
5818
|
-
});
|
|
5819
|
-
stream.pipe(res);
|
|
5820
|
-
}
|
|
5821
|
-
async function startStaticServer(options) {
|
|
5822
|
-
const server = http.createServer((req, res) => {
|
|
5823
|
-
serveFile(options.distDir, req, res);
|
|
5824
|
-
});
|
|
5825
|
-
return await new Promise((resolve, reject) => {
|
|
5826
|
-
server.once("error", reject);
|
|
5827
|
-
server.listen(options.port, LOCAL_HOST, () => {
|
|
5828
|
-
server.off("error", reject);
|
|
5829
|
-
const address = server.address();
|
|
5830
|
-
if (!address || typeof address === "string") {
|
|
5831
|
-
reject(/* @__PURE__ */ new Error("Failed to determine ERD server address."));
|
|
5832
|
-
return;
|
|
5833
|
-
}
|
|
5834
|
-
resolve({
|
|
5835
|
-
server,
|
|
5836
|
-
url: `http://${LOCAL_HOST}:${address.port}`
|
|
5837
|
-
});
|
|
5838
|
-
});
|
|
5839
|
-
});
|
|
5840
|
-
}
|
|
5841
|
-
function getWatchPatterns(config, results) {
|
|
5842
|
-
const namespaces = new Set(results.map((result) => result.namespace));
|
|
5843
|
-
const patterns = [config.path];
|
|
5844
|
-
for (const namespace of namespaces) {
|
|
5845
|
-
const dbConfig = config.db?.[namespace];
|
|
5846
|
-
if (dbConfig && !("external" in dbConfig)) patterns.push(...dbConfig.files);
|
|
5847
|
-
}
|
|
5848
|
-
return [...new Set(patterns)];
|
|
5849
|
-
}
|
|
5850
|
-
function hasGlobPattern(pattern) {
|
|
5851
|
-
return GLOB_CHARS.test(pattern);
|
|
5852
|
-
}
|
|
5853
|
-
function globBaseDir(pattern) {
|
|
5854
|
-
const absolutePattern = path.resolve(pattern);
|
|
5855
|
-
const parsed = path.parse(absolutePattern);
|
|
5856
|
-
const relativePattern = absolutePattern.slice(parsed.root.length);
|
|
5857
|
-
const literalParts = [];
|
|
5858
|
-
for (const part of relativePattern.split(path.sep)) {
|
|
5859
|
-
if (!part || GLOB_CHARS.test(part)) break;
|
|
5860
|
-
literalParts.push(part);
|
|
5861
|
-
}
|
|
5862
|
-
const literalPath = literalParts.length > 0 ? path.join(parsed.root, ...literalParts) : parsed.root;
|
|
5863
|
-
if (!literalPath || literalPath === parsed.root) return parsed.root || process.cwd();
|
|
5864
|
-
if (!fs$1.existsSync(literalPath)) return path.dirname(literalPath);
|
|
5865
|
-
return fs$1.statSync(literalPath).isDirectory() ? literalPath : path.dirname(literalPath);
|
|
5866
|
-
}
|
|
5867
|
-
async function expandWatchPattern(pattern) {
|
|
5868
|
-
if (!hasGlobPattern(pattern)) return [path.resolve(pattern)];
|
|
5869
|
-
const paths = /* @__PURE__ */ new Set();
|
|
5870
|
-
for await (const file of glob(pattern)) paths.add(path.resolve(file));
|
|
5871
|
-
const baseDir = globBaseDir(pattern);
|
|
5872
|
-
if (fs$1.existsSync(baseDir) && fs$1.statSync(baseDir).isDirectory()) paths.add(baseDir);
|
|
5873
|
-
return [...paths];
|
|
5874
|
-
}
|
|
5875
|
-
async function resolveWatchPathsFromConfig(config, results) {
|
|
5876
|
-
const paths = /* @__PURE__ */ new Set();
|
|
5877
|
-
for (const pattern of getWatchPatterns(config, results)) for (const watchPath of await expandWatchPattern(pattern)) paths.add(watchPath);
|
|
5878
|
-
return [...paths];
|
|
5879
|
-
}
|
|
5880
|
-
async function resolveWatchPaths(context, results) {
|
|
5881
|
-
return await resolveWatchPathsFromConfig(context.config, results);
|
|
5882
|
-
}
|
|
5883
|
-
function parseFreshErdExportResults(stdout) {
|
|
5884
|
-
const lines = stdout.trim().split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
5885
|
-
for (const line of lines.toReversed()) try {
|
|
5886
|
-
const parsed = JSON.parse(line);
|
|
5887
|
-
if (!Array.isArray(parsed)) continue;
|
|
5888
|
-
return parsed.map((entry) => {
|
|
5889
|
-
const result = entry;
|
|
5890
|
-
if (typeof result.namespace !== "string" || typeof result.distDir !== "string") throw new Error("Invalid ERD export JSON output.");
|
|
5891
|
-
return {
|
|
5892
|
-
namespace: result.namespace,
|
|
5893
|
-
distDir: result.distDir
|
|
5894
|
-
};
|
|
5895
|
-
});
|
|
5896
|
-
} catch {
|
|
5897
|
-
continue;
|
|
5898
|
-
}
|
|
5899
|
-
throw new Error("Failed to parse ERD export JSON output.");
|
|
5900
|
-
}
|
|
5901
|
-
function freshErdExportArgs(options) {
|
|
5902
|
-
const cliEntry = process.argv[1];
|
|
5903
|
-
if (!cliEntry) throw new Error("Cannot rebuild ERD schema in a fresh process: CLI entrypoint is unavailable.");
|
|
5904
|
-
const args = [
|
|
5905
|
-
cliEntry,
|
|
5906
|
-
"tailordb",
|
|
5907
|
-
"erd",
|
|
5908
|
-
"export",
|
|
5909
|
-
"--output",
|
|
5910
|
-
options.outputDir,
|
|
5911
|
-
"--json"
|
|
5912
|
-
];
|
|
5913
|
-
if (options.configPath) args.push("--config", options.configPath);
|
|
5914
|
-
if (options.namespace) args.push("--namespace", options.namespace);
|
|
5915
|
-
return args;
|
|
5916
|
-
}
|
|
5917
|
-
async function runFreshErdExport(options) {
|
|
5918
|
-
return await new Promise((resolve, reject) => {
|
|
5919
|
-
const child = spawn(process.execPath, freshErdExportArgs(options), {
|
|
5920
|
-
cwd: process.cwd(),
|
|
5921
|
-
env: process.env,
|
|
5922
|
-
stdio: [
|
|
5923
|
-
"ignore",
|
|
5924
|
-
"pipe",
|
|
5925
|
-
"pipe"
|
|
5926
|
-
]
|
|
5927
|
-
});
|
|
5928
|
-
let stdout = "";
|
|
5929
|
-
let stderr = "";
|
|
5930
|
-
child.stdout.setEncoding("utf8");
|
|
5931
|
-
child.stderr.setEncoding("utf8");
|
|
5932
|
-
child.stdout.on("data", (chunk) => {
|
|
5933
|
-
stdout += chunk;
|
|
5934
|
-
});
|
|
5935
|
-
child.stderr.on("data", (chunk) => {
|
|
5936
|
-
stderr += chunk;
|
|
5937
|
-
});
|
|
5938
|
-
child.on("error", reject);
|
|
5939
|
-
child.on("close", (code, signal) => {
|
|
5940
|
-
if (code !== 0) {
|
|
5941
|
-
const detail = stderr.trim() || signal || `exit code ${code}`;
|
|
5942
|
-
reject(/* @__PURE__ */ new Error(`Fresh ERD export failed: ${detail}`));
|
|
5943
|
-
return;
|
|
5944
|
-
}
|
|
5945
|
-
try {
|
|
5946
|
-
resolve(parseFreshErdExportResults(stdout));
|
|
5947
|
-
} catch (error) {
|
|
5948
|
-
reject(error);
|
|
5949
|
-
}
|
|
5950
|
-
});
|
|
5951
|
-
});
|
|
5952
|
-
}
|
|
5953
|
-
function selectPrimaryResult(results) {
|
|
5954
|
-
const [primary, ...rest] = results;
|
|
5955
|
-
if (!primary) throw new Error("No ERD build results found.");
|
|
5956
|
-
logger.info(`Serving ERD for namespace "${primary.namespace}".`);
|
|
5957
|
-
if (rest.length > 0) {
|
|
5958
|
-
const commands = rest.map((result) => ` - ${formatServeCommand(result.namespace)}`).join("\n");
|
|
5959
|
-
logger.warn(`Multiple namespaces found. To serve another namespace, run:\n${commands}`);
|
|
5960
|
-
}
|
|
5961
|
-
return primary;
|
|
5962
|
-
}
|
|
5963
|
-
async function createErdWatcher(options) {
|
|
5964
|
-
let rebuilding = false;
|
|
5965
|
-
let pending = false;
|
|
5966
|
-
let watchPaths = await resolveWatchPaths(options.initialContext, options.initialResults);
|
|
5967
|
-
let importNonce = 0;
|
|
5968
|
-
const watcher = watch(watchPaths, {
|
|
5969
|
-
ignored: /node_modules/,
|
|
5970
|
-
ignoreInitial: true,
|
|
5971
|
-
awaitWriteFinish: {
|
|
5972
|
-
stabilityThreshold: 100,
|
|
5973
|
-
pollInterval: 100
|
|
5974
|
-
}
|
|
5975
|
-
});
|
|
5976
|
-
async function rebuild() {
|
|
5977
|
-
if (rebuilding) {
|
|
5978
|
-
pending = true;
|
|
5979
|
-
return;
|
|
5980
|
-
}
|
|
5981
|
-
rebuilding = true;
|
|
5982
|
-
try {
|
|
5983
|
-
const results = await runFreshErdExport({
|
|
5984
|
-
configPath: options.configPath,
|
|
5985
|
-
namespace: options.namespace,
|
|
5986
|
-
outputDir: options.outputDir
|
|
5987
|
-
});
|
|
5988
|
-
const { config } = await loadConfig(options.configPath, { importNonce: String(importNonce += 1) });
|
|
5989
|
-
const nextWatchPaths = await resolveWatchPathsFromConfig(config, results);
|
|
5990
|
-
watcher.unwatch(watchPaths);
|
|
5991
|
-
watcher.add(nextWatchPaths);
|
|
5992
|
-
watchPaths = nextWatchPaths;
|
|
5993
|
-
logger.success(`Rebuilt ERD schema (${results.map((result) => result.namespace).join(", ")})`, { mode: "stream" });
|
|
5994
|
-
} catch (error) {
|
|
5995
|
-
logger.error("Failed to rebuild ERD schema. Serving the last successful build.", { mode: "stream" });
|
|
5996
|
-
logger.error(String(error));
|
|
5997
|
-
} finally {
|
|
5998
|
-
rebuilding = false;
|
|
5999
|
-
if (pending) {
|
|
6000
|
-
pending = false;
|
|
6001
|
-
await rebuild();
|
|
6002
|
-
}
|
|
6003
|
-
}
|
|
6004
|
-
}
|
|
6005
|
-
let debounceTimer;
|
|
6006
|
-
const scheduleRebuild = (changedPath) => {
|
|
6007
|
-
logger.info(`Schema source changed: ${path.relative(process.cwd(), changedPath)}`, { mode: "stream" });
|
|
6008
|
-
if (debounceTimer) clearTimeout(debounceTimer);
|
|
6009
|
-
debounceTimer = setTimeout(() => {
|
|
6010
|
-
rebuild();
|
|
6011
|
-
}, 150);
|
|
6012
|
-
};
|
|
6013
|
-
watcher.on("add", scheduleRebuild);
|
|
6014
|
-
watcher.on("change", scheduleRebuild);
|
|
6015
|
-
watcher.on("unlink", scheduleRebuild);
|
|
6016
|
-
watcher.on("error", (error) => {
|
|
6017
|
-
logger.error(`ERD watcher error: ${String(error)}`, { mode: "stream" });
|
|
6018
|
-
});
|
|
6019
|
-
return watcher;
|
|
6020
|
-
}
|
|
6021
|
-
async function waitForShutdown(server, watcher) {
|
|
6022
|
-
return await new Promise((resolve) => {
|
|
6023
|
-
const shutdown = () => {
|
|
6024
|
-
watcher.close().finally(() => {
|
|
6025
|
-
server.close(() => {
|
|
6026
|
-
logger.info("ERD server stopped.");
|
|
6027
|
-
resolve();
|
|
6028
|
-
});
|
|
6029
|
-
});
|
|
6030
|
-
};
|
|
6031
|
-
process.once("SIGINT", shutdown);
|
|
6032
|
-
process.once("SIGTERM", shutdown);
|
|
6033
|
-
});
|
|
6034
|
-
}
|
|
6035
|
-
const erdServeCommand = defineAppCommand({
|
|
6036
|
-
name: "serve",
|
|
6037
|
-
description: "Generate and serve TailorDB ERD locally with watch reload. (beta)",
|
|
6038
|
-
args: z.strictObject({
|
|
6039
|
-
...configArg,
|
|
6040
|
-
namespace: arg(z.string().optional(), {
|
|
6041
|
-
alias: "n",
|
|
6042
|
-
description: "TailorDB namespace name (uses first namespace in config if not specified)"
|
|
6043
|
-
}),
|
|
6044
|
-
port: arg(z.coerce.number().int().min(0).max(65535).default(0), { description: "Local server port (0 selects a free port)" }),
|
|
6045
|
-
open: arg(z.boolean().default(false), { description: "Open the ERD viewer in the default browser" })
|
|
6046
|
-
}),
|
|
6047
|
-
run: async (args) => {
|
|
6048
|
-
initErdCommand();
|
|
6049
|
-
const outputDir = path.resolve(process.cwd(), DEFAULT_ERD_BASE_DIR);
|
|
6050
|
-
const context = await loadLocalErdSchema({
|
|
6051
|
-
configPath: args.config,
|
|
6052
|
-
namespaces: args.namespace ? [args.namespace] : void 0
|
|
6053
|
-
});
|
|
6054
|
-
const results = prepareErdBuildsFromContext({
|
|
6055
|
-
context,
|
|
6056
|
-
namespace: args.namespace,
|
|
6057
|
-
outputDir
|
|
6058
|
-
});
|
|
6059
|
-
const primary = selectPrimaryResult(results);
|
|
6060
|
-
const { server, url } = await startStaticServer({
|
|
6061
|
-
distDir: primary.distDir,
|
|
6062
|
-
port: args.port
|
|
6063
|
-
});
|
|
6064
|
-
const watchUrl = `${url}/?watch=1`;
|
|
6065
|
-
const watcher = await createErdWatcher({
|
|
6066
|
-
configPath: args.config,
|
|
6067
|
-
namespace: args.namespace,
|
|
6068
|
-
outputDir,
|
|
6069
|
-
initialContext: context,
|
|
6070
|
-
initialResults: results
|
|
6071
|
-
});
|
|
6072
|
-
logger.newline();
|
|
6073
|
-
if (args.json) logger.out({
|
|
6074
|
-
namespace: primary.namespace,
|
|
6075
|
-
url: watchUrl,
|
|
6076
|
-
distDir: primary.distDir
|
|
6077
|
-
});
|
|
6078
|
-
else {
|
|
6079
|
-
logger.success("ERD server started.");
|
|
6080
|
-
logger.out(watchUrl);
|
|
6081
|
-
}
|
|
6082
|
-
if (args.open) try {
|
|
6083
|
-
await open(watchUrl);
|
|
6084
|
-
} catch {
|
|
6085
|
-
logger.warn("Failed to open browser automatically. Please open the URL above manually.");
|
|
6086
|
-
}
|
|
6087
|
-
await waitForShutdown(server, watcher);
|
|
6088
|
-
}
|
|
6089
|
-
});
|
|
6090
|
-
|
|
6091
|
-
//#endregion
|
|
6092
|
-
//#region src/cli/commands/tailordb/erd/index.ts
|
|
6093
|
-
const erdCommand = defineCommand({
|
|
6094
|
-
name: "erd",
|
|
6095
|
-
description: "Generate TailorDB ERD viewer artifacts from local TailorDB schema. (beta)",
|
|
6096
|
-
subCommands: {
|
|
6097
|
-
export: erdExportCommand,
|
|
6098
|
-
diff: erdDiffCommand,
|
|
6099
|
-
serve: erdServeCommand,
|
|
6100
|
-
deploy: erdDeployCommand
|
|
6101
|
-
}
|
|
6102
|
-
});
|
|
6103
|
-
|
|
6104
5407
|
//#endregion
|
|
6105
5408
|
//#region src/cli/commands/tailordb/migrate/script.ts
|
|
6106
5409
|
/**
|
|
@@ -6125,9 +5428,9 @@ async function script(options) {
|
|
|
6125
5428
|
const targetNamespace = resolveTargetNamespace(namespacesWithMigrations, options.namespace);
|
|
6126
5429
|
const { migrationsDir } = assertDefined(namespacesWithMigrations.find((ns) => ns.namespace === targetNamespace), "namespace with migrations not found");
|
|
6127
5430
|
const diffPath = getMigrationFilePath(migrationsDir, migrationNumber, "diff");
|
|
6128
|
-
if (!fs
|
|
5431
|
+
if (!fs.existsSync(diffPath)) throw new Error(`Migration ${options.number} not found in ${migrationsDir}. Expected ${diffPath}.`);
|
|
6129
5432
|
const migratePath = getMigrationFilePath(migrationsDir, migrationNumber, "migrate");
|
|
6130
|
-
if (fs
|
|
5433
|
+
if (fs.existsSync(migratePath)) throw new Error(`Migration script already exists at ${migratePath}.`);
|
|
6131
5434
|
const diff = loadDiff(diffPath);
|
|
6132
5435
|
const previousSnapshot = reconstructSnapshotFromMigrations(migrationsDir, migrationNumber - 1);
|
|
6133
5436
|
if (!previousSnapshot) throw new Error(`Could not reconstruct previous schema for migration ${options.number}. Make sure migration ${0} exists.`);
|
|
@@ -6312,7 +5615,7 @@ async function collectMigrationStatuses(options) {
|
|
|
6312
5615
|
const pendingMigrations = getMigrationFiles(migrationsDir).map((f) => f.number).filter((n, i, arr) => arr.indexOf(n) === i).toSorted((a, b) => a - b).filter((n) => n > currentMigration).map((num) => {
|
|
6313
5616
|
const diffPath = getMigrationFilePath(migrationsDir, num, "diff");
|
|
6314
5617
|
let description;
|
|
6315
|
-
if (fs
|
|
5618
|
+
if (fs.existsSync(diffPath)) try {
|
|
6316
5619
|
description = loadDiff(diffPath).description;
|
|
6317
5620
|
} catch {}
|
|
6318
5621
|
return {
|
|
@@ -6427,7 +5730,7 @@ async function fetchRemoteTypes(client, workspaceId, namespace) {
|
|
|
6427
5730
|
async function assertMigrationsReproduceLocalTypes(loaded, target) {
|
|
6428
5731
|
const { config, plugins } = loaded;
|
|
6429
5732
|
const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
|
|
6430
|
-
const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-
|
|
5733
|
+
const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-BV-AXawv.mjs");
|
|
6431
5734
|
const application = defineApplication({
|
|
6432
5735
|
config,
|
|
6433
5736
|
pluginManager
|
|
@@ -6439,7 +5742,7 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
|
|
|
6439
5742
|
await service.processNamespacePlugins();
|
|
6440
5743
|
}
|
|
6441
5744
|
const pluginExecutorFiles = generatePluginFilesIfNeeded(pluginManager, application.tailorDBServices, config.path);
|
|
6442
|
-
const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-
|
|
5745
|
+
const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-nU6ITOHL.mjs")).createExecutorService({ config: { files: [] } }) : void 0);
|
|
6443
5746
|
await executorService?.loadExecutors();
|
|
6444
5747
|
if (pluginExecutorFiles.length > 0) await executorService?.loadPluginExecutorFiles([...pluginExecutorFiles]);
|
|
6445
5748
|
const executorUsedTypes = /* @__PURE__ */ new Set();
|
|
@@ -6684,10 +5987,10 @@ const migrationCommand = defineCommand({
|
|
|
6684
5987
|
const tailordbCommand = defineCommand({
|
|
6685
5988
|
name: "tailordb",
|
|
6686
5989
|
description: "Manage TailorDB tables and data.",
|
|
5990
|
+
notes: "The `tailordb erd` commands are provided by the @tailor-platform/sdk-tailordb-erd-plugin CLI plugin.",
|
|
6687
5991
|
subCommands: {
|
|
6688
5992
|
truncate: truncateCommand,
|
|
6689
|
-
migration: migrationCommand
|
|
6690
|
-
erd: erdCommand
|
|
5993
|
+
migration: migrationCommand
|
|
6691
5994
|
}
|
|
6692
5995
|
});
|
|
6693
5996
|
|
|
@@ -6710,7 +6013,7 @@ const upgradeCommand = defineAppCommand({
|
|
|
6710
6013
|
run: async (args) => {
|
|
6711
6014
|
const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
|
|
6712
6015
|
await initTelemetry();
|
|
6713
|
-
const { upgrade } = await import("../service-
|
|
6016
|
+
const { upgrade } = await import("../service-DaeJlBoH.mjs");
|
|
6714
6017
|
await upgrade({
|
|
6715
6018
|
from: args.from,
|
|
6716
6019
|
dryRun: args["dry-run"],
|
|
@@ -7055,7 +6358,7 @@ const workflowCommand = defineCommand({
|
|
|
7055
6358
|
name: "workflow",
|
|
7056
6359
|
description: "Manage workflows and workflow executions.",
|
|
7057
6360
|
subCommands: {
|
|
7058
|
-
list: listCommand$
|
|
6361
|
+
list: listCommand$14,
|
|
7059
6362
|
get: getCommand$6,
|
|
7060
6363
|
start: startCommand,
|
|
7061
6364
|
wait: waitCommand,
|
|
@@ -7063,7 +6366,7 @@ const workflowCommand = defineCommand({
|
|
|
7063
6366
|
resume: resumeCommand
|
|
7064
6367
|
},
|
|
7065
6368
|
async run() {
|
|
7066
|
-
await runCommand(listCommand$
|
|
6369
|
+
await runCommand(listCommand$14, []);
|
|
7067
6370
|
}
|
|
7068
6371
|
});
|
|
7069
6372
|
|
|
@@ -7074,10 +6377,10 @@ const appCommand = defineCommand({
|
|
|
7074
6377
|
description: "Manage workspace applications",
|
|
7075
6378
|
subCommands: {
|
|
7076
6379
|
health: healthCommand,
|
|
7077
|
-
list: listCommand$
|
|
6380
|
+
list: listCommand$15
|
|
7078
6381
|
},
|
|
7079
6382
|
async run() {
|
|
7080
|
-
await runCommand(listCommand$
|
|
6383
|
+
await runCommand(listCommand$15, []);
|
|
7081
6384
|
}
|
|
7082
6385
|
});
|
|
7083
6386
|
|
|
@@ -7088,12 +6391,12 @@ const userCommand = defineCommand({
|
|
|
7088
6391
|
description: "Manage workspace users",
|
|
7089
6392
|
subCommands: {
|
|
7090
6393
|
invite: inviteCommand,
|
|
7091
|
-
list: listCommand$
|
|
6394
|
+
list: listCommand$16,
|
|
7092
6395
|
remove: removeCommand,
|
|
7093
6396
|
update: updateCommand$4
|
|
7094
6397
|
},
|
|
7095
6398
|
async run() {
|
|
7096
|
-
await runCommand(listCommand$
|
|
6399
|
+
await runCommand(listCommand$16, []);
|
|
7097
6400
|
}
|
|
7098
6401
|
});
|
|
7099
6402
|
|
|
@@ -7107,12 +6410,12 @@ const workspaceCommand = defineCommand({
|
|
|
7107
6410
|
create: createCommand$4,
|
|
7108
6411
|
delete: deleteCommand$5,
|
|
7109
6412
|
get: getCommand$7,
|
|
7110
|
-
list: listCommand$
|
|
6413
|
+
list: listCommand$17,
|
|
7111
6414
|
restore: restoreCommand,
|
|
7112
6415
|
user: userCommand
|
|
7113
6416
|
},
|
|
7114
6417
|
async run() {
|
|
7115
|
-
await runCommand(listCommand$
|
|
6418
|
+
await runCommand(listCommand$17, []);
|
|
7116
6419
|
}
|
|
7117
6420
|
});
|
|
7118
6421
|
|
|
@@ -7124,37 +6427,22 @@ const packageJson = await readPackageJson();
|
|
|
7124
6427
|
const cliName = Object.keys(packageJson.bin ?? {})[0] || "tailor";
|
|
7125
6428
|
const packageName = packageJson.name ?? "@tailor-platform/sdk";
|
|
7126
6429
|
const bundledSkillsDir = resolve(dirname(await resolvePackageJSON(import.meta.url)), "agent-skills");
|
|
7127
|
-
function replaceCommandArgs(command, removals = []) {
|
|
7128
|
-
const args = command.args;
|
|
7129
|
-
if (!args?.shape) return command;
|
|
7130
|
-
const shape = { ...args.shape };
|
|
7131
|
-
for (const name of removals) delete shape[name];
|
|
7132
|
-
return {
|
|
7133
|
-
...command,
|
|
7134
|
-
args: z.strictObject({ ...shape })
|
|
7135
|
-
};
|
|
7136
|
-
}
|
|
7137
|
-
function removeCommandAliases(command) {
|
|
7138
|
-
const next = { ...command };
|
|
7139
|
-
delete next.aliases;
|
|
7140
|
-
return next;
|
|
7141
|
-
}
|
|
7142
6430
|
function alignSkillCommand(command) {
|
|
7143
6431
|
const subCommands = command.subCommands ?? {};
|
|
7144
6432
|
const add = {
|
|
7145
|
-
...
|
|
6433
|
+
...subCommands.add,
|
|
7146
6434
|
description: "Install Tailor SDK agent skills."
|
|
7147
6435
|
};
|
|
7148
6436
|
const list = {
|
|
7149
|
-
...
|
|
6437
|
+
...subCommands.list,
|
|
7150
6438
|
description: "List Tailor SDK agent skills."
|
|
7151
6439
|
};
|
|
7152
6440
|
const remove = {
|
|
7153
|
-
...
|
|
6441
|
+
...subCommands.remove,
|
|
7154
6442
|
description: "Remove installed Tailor SDK agent skills."
|
|
7155
6443
|
};
|
|
7156
6444
|
const sync = {
|
|
7157
|
-
...
|
|
6445
|
+
...subCommands.sync,
|
|
7158
6446
|
description: "Remove and reinstall Tailor SDK agent skills."
|
|
7159
6447
|
};
|
|
7160
6448
|
return {
|
|
@@ -7183,7 +6471,7 @@ Run \`${cliName} plugin list\` to see which plugins are installed and where they
|
|
|
7183
6471
|
auth: authCommand,
|
|
7184
6472
|
authconnection: authconnectionCommand,
|
|
7185
6473
|
crashreport: crashReportCommand,
|
|
7186
|
-
deploy: deployCommand
|
|
6474
|
+
deploy: deployCommand,
|
|
7187
6475
|
executor: executorCommand,
|
|
7188
6476
|
function: functionCommand,
|
|
7189
6477
|
generate: generateCommand,
|
|
@@ -7212,21 +6500,26 @@ Run \`${cliName} plugin list\` to see which plugins are installed and where they
|
|
|
7212
6500
|
sourceDir: bundledSkillsDir,
|
|
7213
6501
|
package: packageName,
|
|
7214
6502
|
mode: "copy",
|
|
7215
|
-
descriptionAppend: false
|
|
6503
|
+
descriptionAppend: false,
|
|
6504
|
+
globalArgs: z.object(commonArgs),
|
|
6505
|
+
commandMap: {
|
|
6506
|
+
add: ["add"],
|
|
6507
|
+
remove: ["remove"]
|
|
6508
|
+
},
|
|
6509
|
+
unknownKeys: "strict"
|
|
7216
6510
|
});
|
|
7217
|
-
const commandWithSkillsSubCommands = commandWithSkills.subCommands ?? {};
|
|
7218
6511
|
const mainCommand = withCompletionCommand({
|
|
7219
6512
|
...commandWithSkills,
|
|
7220
6513
|
subCommands: {
|
|
7221
|
-
...
|
|
7222
|
-
skills: alignSkillCommand(
|
|
6514
|
+
...commandWithSkills.subCommands,
|
|
6515
|
+
skills: alignSkillCommand(commandWithSkills.subCommands.skills)
|
|
7223
6516
|
}
|
|
7224
6517
|
});
|
|
7225
6518
|
runMain(mainCommand, {
|
|
7226
6519
|
version: packageJson.version,
|
|
7227
6520
|
globalArgs: z.object(commonArgs),
|
|
7228
6521
|
displayErrors: false,
|
|
7229
|
-
onUnknownSubcommand: ({ commandPath, name, args }) =>
|
|
6522
|
+
onUnknownSubcommand: ({ commandPath, name, args }) => dispatchPluginWithInstallHint({
|
|
7230
6523
|
commandPath,
|
|
7231
6524
|
name,
|
|
7232
6525
|
args,
|
|
@@ -7235,7 +6528,8 @@ runMain(mainCommand, {
|
|
|
7235
6528
|
}),
|
|
7236
6529
|
cleanup: async ({ error }) => {
|
|
7237
6530
|
if (error) {
|
|
7238
|
-
if (
|
|
6531
|
+
if (logger.jsonMode) logger.log(JSON.stringify(errorToJson(error, { includeStack: isVerbose() })));
|
|
6532
|
+
else if (isCLIError(error)) {
|
|
7239
6533
|
logger.log(error.format());
|
|
7240
6534
|
if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
|
|
7241
6535
|
} else if (error instanceof Error) {
|
|
@@ -7243,7 +6537,7 @@ runMain(mainCommand, {
|
|
|
7243
6537
|
if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
|
|
7244
6538
|
} else logger.error(`Unknown error: ${error}`);
|
|
7245
6539
|
if (!isCLIError(error) && (!(error instanceof Error) || error instanceof TypeError || error instanceof RangeError)) {
|
|
7246
|
-
const { reportCrash } = await import("../crashreport-
|
|
6540
|
+
const { reportCrash } = await import("../crashreport-0ioNRZ4K.mjs");
|
|
7247
6541
|
await reportCrash(error, "handledError");
|
|
7248
6542
|
}
|
|
7249
6543
|
}
|