@tailor-platform/sdk 2.0.0-next.1 → 2.0.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +514 -2
- package/README.md +9 -9
- package/agent-skills/{tailor-sdk → tailor}/SKILL.md +3 -1
- package/dist/aigateway-B3oZZE6v.mjs +15 -0
- package/dist/aigateway-B3oZZE6v.mjs.map +1 -0
- package/dist/application-D2hSK1ZL.mjs +3 -0
- package/dist/{application-DqS1yBg3.mjs → application-eHp9jXld.mjs} +1397 -684
- package/dist/application-eHp9jXld.mjs.map +1 -0
- package/dist/{authconnection-D2MhtTN5.mjs → authconnection-BGQM8FZI.mjs} +2 -2
- package/dist/authconnection-BGQM8FZI.mjs.map +1 -0
- package/dist/cli/cache/bundle-cache.d.mts +1 -0
- package/dist/cli/cache/store.d.mts +1 -0
- package/dist/cli/cache/types.d.mts +1 -0
- package/dist/cli/commands/api/api-call.d.mts +19 -0
- package/dist/cli/commands/api/index.d.mts +3 -0
- package/dist/cli/commands/deploy/aigateway.d.mts +1 -0
- package/dist/cli/commands/deploy/application.d.mts +1 -0
- package/dist/cli/commands/deploy/apply-phases.d.mts +1 -0
- package/dist/cli/commands/deploy/auth.d.mts +1 -0
- package/dist/cli/commands/deploy/deploy.d.mts +24 -0
- package/dist/cli/commands/deploy/executor.d.mts +1 -0
- package/dist/cli/commands/deploy/function-registry-types.d.mts +12 -0
- package/dist/cli/commands/deploy/function-registry.d.mts +2 -0
- package/dist/cli/commands/deploy/idp.d.mts +1 -0
- package/dist/cli/commands/deploy/resolver.d.mts +1 -0
- package/dist/cli/commands/deploy/secret-manager.d.mts +1 -0
- package/dist/cli/commands/deploy/staticwebsite.d.mts +1 -0
- package/dist/cli/commands/deploy/tailordb/index.d.mts +1 -0
- package/dist/cli/commands/deploy/types.d.mts +1 -0
- package/dist/cli/commands/deploy/workflow.d.mts +1 -0
- package/dist/cli/commands/executor/get.d.mts +29 -0
- package/dist/cli/commands/executor/jobs.d.mts +123 -0
- package/dist/cli/commands/executor/list.d.mts +19 -0
- package/dist/cli/commands/executor/transform.d.mts +41 -0
- package/dist/cli/commands/executor/trigger.d.mts +41 -0
- package/dist/cli/commands/executor/webhook.d.mts +22 -0
- package/dist/cli/commands/function/get.d.mts +18 -0
- package/dist/cli/commands/function/list.d.mts +22 -0
- package/dist/cli/commands/function/transform.d.mts +11 -0
- package/dist/cli/commands/generate/options.d.mts +7 -0
- package/dist/cli/commands/generate/seed/bundler.d.mts +26 -0
- package/dist/cli/commands/generate/service.d.mts +11 -0
- package/dist/cli/commands/machineuser/list.d.mts +27 -0
- package/dist/cli/commands/machineuser/token.d.mts +22 -0
- package/dist/cli/commands/oauth2client/get.d.mts +18 -0
- package/dist/cli/commands/oauth2client/list.d.mts +20 -0
- package/dist/cli/commands/oauth2client/transform.d.mts +20 -0
- package/dist/cli/commands/organization/folder/create.d.mts +18 -0
- package/dist/cli/commands/organization/folder/delete.d.mts +16 -0
- package/dist/cli/commands/organization/folder/get.d.mts +17 -0
- package/dist/cli/commands/organization/folder/list.d.mts +22 -0
- package/dist/cli/commands/organization/folder/update.d.mts +18 -0
- package/dist/cli/commands/organization/get.d.mts +16 -0
- package/dist/cli/commands/organization/list.d.mts +15 -0
- package/dist/cli/commands/organization/transform.d.mts +27 -0
- package/dist/cli/commands/organization/tree.d.mts +25 -0
- package/dist/cli/commands/organization/update.d.mts +17 -0
- package/dist/cli/commands/remove.d.mts +16 -0
- package/dist/cli/commands/show.d.mts +34 -0
- package/dist/cli/commands/tailordb/migrate/bundler.d.mts +28 -0
- package/dist/cli/commands/tailordb/migrate/config.d.mts +19 -0
- package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +223 -0
- package/dist/cli/commands/tailordb/migrate/generate.d.mts +17 -0
- package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +202 -0
- package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +98 -0
- package/dist/cli/commands/tailordb/migrate/types.d.mts +7 -0
- package/dist/cli/commands/tailordb/truncate.d.mts +19 -0
- package/dist/cli/commands/workflow/executions.d.mts +75 -0
- package/dist/cli/commands/workflow/get.d.mts +29 -0
- package/dist/cli/commands/workflow/list.d.mts +19 -0
- package/dist/cli/commands/workflow/resume.d.mts +23 -0
- package/dist/cli/commands/workflow/start.d.mts +61 -0
- package/dist/cli/commands/workflow/status.d.mts +5 -0
- package/dist/cli/commands/workflow/transform.d.mts +33 -0
- package/dist/cli/commands/workflow/wait.d.mts +16 -0
- package/dist/cli/commands/workflow/waiter.d.mts +23 -0
- package/dist/cli/commands/workspace/app/health.d.mts +18 -0
- package/dist/cli/commands/workspace/app/list.d.mts +22 -0
- package/dist/cli/commands/workspace/app/transform.d.mts +18 -0
- package/dist/cli/commands/workspace/create.d.mts +25 -0
- package/dist/cli/commands/workspace/delete.d.mts +15 -0
- package/dist/cli/commands/workspace/get.d.mts +17 -0
- package/dist/cli/commands/workspace/list.d.mts +17 -0
- package/dist/cli/commands/workspace/restore.d.mts +15 -0
- package/dist/cli/commands/workspace/transform.d.mts +16 -0
- package/dist/cli/commands/workspace/user/invite.d.mts +22 -0
- package/dist/cli/commands/workspace/user/list.d.mts +22 -0
- package/dist/cli/commands/workspace/user/remove.d.mts +17 -0
- package/dist/cli/commands/workspace/user/transform.d.mts +8 -0
- package/dist/cli/commands/workspace/user/update.d.mts +22 -0
- package/dist/cli/erd-viewer-assets/app.js +279 -36
- package/dist/cli/erd-viewer-assets/index.html +4 -0
- package/dist/cli/erd-viewer-assets/styles.css +252 -5
- package/dist/cli/index.d.mts +18 -3
- package/dist/cli/index.mjs +2193 -519
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +81 -21048
- package/dist/cli/lib.mjs +8 -12
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/query/index.d.mts +35 -0
- package/dist/cli/services/application.d.mts +1 -0
- package/dist/cli/services/auth/service.d.mts +1 -0
- package/dist/cli/services/executor/service.d.mts +1 -0
- package/dist/cli/services/http-adapter/bundler.d.mts +1 -0
- package/dist/cli/services/http-adapter/service.d.mts +1 -0
- package/dist/cli/services/resolver/service.d.mts +1 -0
- package/dist/cli/services/tailordb/service.d.mts +1 -0
- package/dist/cli/services/workflow/bundler.d.mts +1 -0
- package/dist/cli/services/workflow/service.d.mts +1 -0
- package/dist/cli/shared/args.d.mts +13 -0
- package/dist/cli/shared/client.d.mts +1273 -0
- package/dist/cli/shared/config-loader.d.mts +26 -0
- package/dist/cli/shared/context.d.mts +28 -0
- package/dist/cli/shared/script-executor.d.mts +75 -0
- package/dist/cli/shared/seed-chunker.d.mts +46 -0
- package/dist/cli/shared/trigger-context.d.mts +1 -0
- package/dist/cli/shared/type-generator.d.mts +20 -0
- package/dist/cli/ts-hook.d.mts +4 -0
- package/dist/cli/ts-hook.mjs +303 -0
- package/dist/completion/zsh-worker.zsh +1577 -1098
- package/dist/configure/config/index.d.mts +21 -0
- package/dist/configure/config/types.d.mts +103 -0
- package/dist/configure/index.d.mts +122 -8
- package/dist/configure/index.mjs +74 -34
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/aigateway/index.d.mts +17 -0
- package/dist/configure/services/aigateway/types.d.mts +11 -0
- package/dist/configure/services/auth/index.d.mts +34 -0
- package/dist/configure/services/auth/types.d.mts +172 -0
- package/dist/configure/services/executor/executor.d.mts +81 -0
- package/dist/configure/services/executor/index.d.mts +8 -0
- package/dist/configure/services/executor/operation.d.mts +45 -0
- package/dist/configure/services/executor/trigger/event.d.mts +272 -0
- package/dist/configure/services/executor/trigger/index.d.mts +8 -0
- package/dist/configure/services/executor/trigger/schedule.d.mts +25 -0
- package/dist/configure/services/executor/trigger/webhook.d.mts +44 -0
- package/dist/configure/services/executor/types.d.mts +2 -0
- package/dist/configure/services/http-adapter/http-adapter.d.mts +125 -0
- package/dist/configure/services/http-adapter/index.d.mts +2 -0
- package/dist/configure/services/idp/index.d.mts +64 -0
- package/dist/configure/services/idp/permission.d.mts +80 -0
- package/dist/configure/services/idp/types.d.mts +20 -0
- package/dist/configure/services/index.d.mts +32 -0
- package/dist/configure/services/resolver/index.d.mts +5 -0
- package/dist/configure/services/resolver/resolver.d.mts +75 -0
- package/dist/configure/services/resolver/types.d.mts +6 -0
- package/dist/configure/services/secrets/index.d.mts +38 -0
- package/dist/configure/services/secrets/types.d.mts +11 -0
- package/dist/configure/services/staticwebsite/index.d.mts +19 -0
- package/dist/configure/services/staticwebsite/types.d.mts +11 -0
- package/dist/configure/services/tailordb/index.d.mts +4 -0
- package/dist/configure/services/tailordb/permission.d.mts +119 -0
- package/dist/configure/services/tailordb/schema.d.mts +456 -0
- package/dist/configure/services/tailordb/types.d.mts +149 -0
- package/dist/configure/services/workflow/index.d.mts +6 -0
- package/dist/configure/services/workflow/job.d.mts +79 -0
- package/dist/configure/services/workflow/types.d.mts +2 -0
- package/dist/configure/services/workflow/wait-point.d.mts +73 -0
- package/dist/configure/services/workflow/workflow.d.mts +54 -0
- package/dist/configure/types/aigateway-name.d.mts +12 -0
- package/dist/configure/types/connection-name.d.mts +12 -0
- package/dist/configure/types/field-runtime.d.mts +17 -0
- package/dist/configure/types/field.d.mts +9 -0
- package/dist/configure/types/field.types.d.mts +109 -0
- package/dist/configure/types/idp-name.d.mts +12 -0
- package/dist/configure/types/index.d.mts +4 -0
- package/dist/configure/types/machine-user.d.mts +2 -0
- package/dist/configure/types/type.d.mts +82 -0
- package/dist/context-Bd266-ru.mjs.map +1 -1
- package/dist/{crashreport-BsjAkFWw.mjs → crashreport-Cg-pqRJV.mjs} +9 -11
- package/dist/{crashreport-BsjAkFWw.mjs.map → crashreport-Cg-pqRJV.mjs.map} +1 -1
- package/dist/{crashreport-pr6Rhvza.mjs → crashreport-D3RjuSYb.mjs} +1 -1
- package/dist/enum-constants-j9QBF0cB.mjs.map +1 -1
- package/dist/{errors-Dtf2WPaW.mjs → errors-118wUDRr.mjs} +2 -2
- package/dist/errors-118wUDRr.mjs.map +1 -0
- package/dist/field-runtime-CAnh_oow.mjs +183 -0
- package/dist/field-runtime-CAnh_oow.mjs.map +1 -0
- package/dist/{file-_oUZo76X.mjs → file-BbdFGdMV.mjs} +2 -10
- package/dist/file-BbdFGdMV.mjs.map +1 -0
- package/dist/{file-utils-DcyIPFQh.mjs → file-utils-CYZnO1pX.mjs} +5 -5
- package/dist/file-utils-CYZnO1pX.mjs.map +1 -0
- package/dist/{globals-Crz8o65k.mjs → globals-DhwZj0RB.mjs} +5 -47
- package/dist/globals-DhwZj0RB.mjs.map +1 -0
- package/dist/{idp-BDbK5gjm.mjs → idp-Bx25ZBdN.mjs} +9 -1
- package/dist/idp-Bx25ZBdN.mjs.map +1 -0
- package/dist/{interceptor-D-q1rvRl.mjs → interceptor-e33JtsC-.mjs} +2 -2
- package/dist/interceptor-e33JtsC-.mjs.map +1 -0
- package/dist/kysely/index.d.mts +2 -3
- package/dist/{kysely-type-DR8uzZTA.mjs → kysely-type-5eAChaHO.mjs} +4 -2
- package/dist/kysely-type-5eAChaHO.mjs.map +1 -0
- package/dist/{logger-CxF-Ex5d.mjs → logger-BEiZZ3qT.mjs} +3 -3
- package/dist/{logger-CxF-Ex5d.mjs.map → logger-BEiZZ3qT.mjs.map} +1 -1
- package/dist/parser/service/auth/index.d.mts +1 -0
- package/dist/parser/service/auth/schema.d.mts +1 -0
- package/dist/parser/service/http-adapter/index.d.mts +1 -0
- package/dist/parser/service/http-adapter/schema.d.mts +1 -0
- package/dist/parser/service/idp/types.d.mts +4 -0
- package/dist/parser/service/tailordb/types.d.mts +126 -0
- package/dist/platform-serialize-RoRtBS0v.mjs +46 -0
- package/dist/platform-serialize-RoRtBS0v.mjs.map +1 -0
- package/dist/plugin/builtin/enum-constants/index.d.mts +16 -1
- package/dist/plugin/builtin/file-utils/index.d.mts +16 -1
- package/dist/plugin/builtin/file-utils/index.mjs +1 -1
- package/dist/plugin/builtin/kysely-type/index.d.mts +16 -1
- package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
- package/dist/plugin/builtin/seed/index.d.mts +45 -1
- package/dist/plugin/builtin/seed/index.mjs +1 -1
- package/dist/plugin/get-generated-type.d.mts +17 -0
- package/dist/plugin/index.d.mts +3 -117
- package/dist/plugin/index.mjs +1 -1
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/plugin/manager.d.mts +1 -0
- package/dist/plugin/types.d.mts +243 -0
- package/dist/plugin/with-context.d.mts +102 -0
- package/dist/{runtime-n9NCkjee.mjs → register-ts-hook-ZpBacA07.mjs} +4374 -2446
- package/dist/register-ts-hook-ZpBacA07.mjs.map +1 -0
- package/dist/{registry-DdsYlL_P.mjs → registry-DH4m7eYo.mjs} +4 -2
- package/dist/registry-DH4m7eYo.mjs.map +1 -0
- package/dist/{repl-editor-DmGr9zMw.mjs → repl-editor-BCozyiNq.mjs} +6 -5
- package/dist/{repl-editor-DmGr9zMw.mjs.map → repl-editor-BCozyiNq.mjs.map} +1 -1
- package/dist/runtime/aigateway.d.mts +35 -0
- package/dist/runtime/aigateway.mjs +3 -0
- package/dist/runtime/authconnection.d.mts +31 -2
- package/dist/runtime/authconnection.mjs +1 -1
- package/dist/runtime/context.d.mts +46 -2
- package/dist/runtime/file.d.mts +210 -2
- package/dist/runtime/file.mjs +2 -2
- package/dist/runtime/globals.d.mts +31 -19
- package/dist/runtime/iconv.d.mts +121 -2
- package/dist/runtime/idp.d.mts +186 -2
- package/dist/runtime/idp.mjs +1 -1
- package/dist/runtime/index.d.mts +47 -9
- package/dist/runtime/index.mjs +6 -5
- package/dist/runtime/secretmanager.d.mts +54 -2
- package/dist/runtime/types.d.mts +20 -0
- package/dist/runtime/workflow.d.mts +112 -2
- package/dist/runtime/workflow.mjs +2 -2
- package/dist/{schema-BhkpP5Hw.mjs → schema-89dYxNC2.mjs} +25 -187
- package/dist/schema-89dYxNC2.mjs.map +1 -0
- package/dist/{secret-file-DBqZhjFQ.mjs → secret-file-aoNXJmKa.mjs} +28 -3
- package/dist/secret-file-aoNXJmKa.mjs.map +1 -0
- package/dist/secretmanager-Bd45j7an.mjs +98 -0
- package/dist/secretmanager-Bd45j7an.mjs.map +1 -0
- package/dist/seed/index.d.mts +1 -2
- package/dist/{seed-jf3008-h.mjs → seed-CRSQJI_z.mjs} +16 -6
- package/dist/seed-CRSQJI_z.mjs.map +1 -0
- package/dist/service-CkIwEctX.mjs +3 -0
- package/dist/{service-DU1mVzri.mjs → service-DeZeYa06.mjs} +3 -3
- package/dist/{service-DU1mVzri.mjs.map → service-DeZeYa06.mjs.map} +1 -1
- package/dist/service-ufH7sS8o.mjs +797 -0
- package/dist/service-ufH7sS8o.mjs.map +1 -0
- package/dist/{client-z_oHGVNy.mjs → service_pb-CIrhGwHk.mjs} +38 -466
- package/dist/service_pb-CIrhGwHk.mjs.map +1 -0
- package/dist/service_pb-DjwIn4jO.mjs +3 -0
- package/dist/tailor-proto/src/tailor/v1/aigateway_pb.d.mts +204 -0
- package/dist/tailor-proto/src/tailor/v1/aigateway_resource_pb.d.mts +58 -0
- package/dist/tailor-proto/src/tailor/v1/application_pb.d.mts +316 -0
- package/dist/tailor-proto/src/tailor/v1/application_resource_pb.d.mts +161 -0
- package/dist/tailor-proto/src/tailor/v1/auth_pb.d.mts +2070 -0
- package/dist/tailor-proto/src/tailor/v1/auth_resource_pb.d.mts +861 -0
- package/dist/tailor-proto/src/tailor/v1/events_pb.d.mts +112 -0
- package/dist/tailor-proto/src/tailor/v1/events_resource_pb.d.mts +69 -0
- package/dist/tailor-proto/src/tailor/v1/executor_pb.d.mts +439 -0
- package/dist/tailor-proto/src/tailor/v1/executor_resource_pb.d.mts +564 -0
- package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +145 -0
- package/dist/tailor-proto/src/tailor/v1/function_registry_pb.d.mts +451 -0
- package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +192 -0
- package/dist/tailor-proto/src/tailor/v1/http_adapter_resource_pb.d.mts +50 -0
- package/dist/tailor-proto/src/tailor/v1/idp_pb.d.mts +407 -0
- package/dist/tailor-proto/src/tailor/v1/idp_resource_pb.d.mts +430 -0
- package/dist/tailor-proto/src/tailor/v1/metadata_pb.d.mts +73 -0
- package/dist/tailor-proto/src/tailor/v1/metadata_resource_pb.d.mts +17 -0
- package/dist/tailor-proto/src/tailor/v1/meter_pb.d.mts +309 -0
- package/dist/tailor-proto/src/tailor/v1/pipeline_pb.d.mts +591 -0
- package/dist/tailor-proto/src/tailor/v1/pipeline_resource_pb.d.mts +330 -0
- package/dist/tailor-proto/src/tailor/v1/resource_pb.d.mts +113 -0
- package/dist/tailor-proto/src/tailor/v1/secret_manager_pb.d.mts +346 -0
- package/dist/tailor-proto/src/tailor/v1/secret_manager_resource_pb.d.mts +54 -0
- package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +3576 -0
- package/dist/tailor-proto/src/tailor/v1/stateflow_pb.d.mts +187 -0
- package/dist/tailor-proto/src/tailor/v1/stateflow_resource_pb.d.mts +29 -0
- package/dist/tailor-proto/src/tailor/v1/staticwebsite_pb.d.mts +429 -0
- package/dist/tailor-proto/src/tailor/v1/staticwebsite_resource_pb.d.mts +90 -0
- package/dist/tailor-proto/src/tailor/v1/tailordb_pb.d.mts +684 -0
- package/dist/tailor-proto/src/tailor/v1/tailordb_resource_pb.d.mts +797 -0
- package/dist/tailor-proto/src/tailor/v1/telemetryrouter_pb.d.mts +330 -0
- package/dist/tailor-proto/src/tailor/v1/telemetryrouter_resource_pb.d.mts +183 -0
- package/dist/tailor-proto/src/tailor/v1/workflow_pb.d.mts +842 -0
- package/dist/tailor-proto/src/tailor/v1/workflow_resource_pb.d.mts +276 -0
- package/dist/tailor-proto/src/tailor/v1/workspace_pb.d.mts +2015 -0
- package/dist/tailor-proto/src/tailor/v1/workspace_resource_pb.d.mts +483 -0
- package/dist/{telemetry-CdqJEzkj.mjs → telemetry-BRVdwh14.mjs} +3 -3
- package/dist/telemetry-BRVdwh14.mjs.map +1 -0
- package/dist/telemetry-CkbkeJxl.mjs +3 -0
- package/dist/test-env-key-D7UkZp99.mjs +75 -0
- package/dist/test-env-key-D7UkZp99.mjs.map +1 -0
- package/dist/types/aigateway.generated.d.mts +9 -0
- package/dist/types/app-config.generated.d.mts +4 -0
- package/dist/types/auth-connection.generated.d.mts +20 -0
- package/dist/types/auth.generated.d.mts +145 -0
- package/dist/types/executor.generated.d.mts +177 -0
- package/dist/types/helpers.d.mts +39 -0
- package/dist/types/http-adapter.generated.d.mts +17 -0
- package/dist/{workflow.generated-DtQwEo-x.d.mts → types/idp.generated.d.mts} +98 -185
- package/dist/types/resolver.generated.d.mts +39 -0
- package/dist/types/secrets.generated.d.mts +14 -0
- package/dist/types/staticwebsite.generated.d.mts +10 -0
- package/dist/types/tailordb.generated.d.mts +428 -0
- package/dist/types/workflow.generated.d.mts +12 -0
- package/dist/types-C-lQ4se3.mjs +4 -0
- package/dist/types-CXF2OYdR.mjs +199 -0
- package/dist/types-CXF2OYdR.mjs.map +1 -0
- package/dist/utils/test/index.d.mts +5 -98
- package/dist/utils/test/index.mjs +4 -13
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/utils/test/mock.d.mts +87 -0
- package/dist/vitest/environment.d.mts +1 -2
- package/dist/vitest/environment.mjs +2 -2
- package/dist/vitest/environment.mjs.map +1 -1
- package/dist/vitest/index.d.mts +12 -428
- package/dist/vitest/index.mjs +741 -10
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mock-kysely.d.mts +62 -0
- package/dist/vitest/mock.d.mts +9 -0
- package/dist/vitest/mocks/aigateway.d.mts +30 -0
- package/dist/vitest/mocks/authconnection.d.mts +28 -0
- package/dist/vitest/mocks/file.d.mts +41 -0
- package/dist/vitest/mocks/iconv.d.mts +27 -0
- package/dist/vitest/mocks/idp.d.mts +44 -0
- package/dist/vitest/mocks/secretmanager.d.mts +33 -0
- package/dist/vitest/mocks/tailordb.d.mts +71 -0
- package/dist/vitest/mocks/workflow.d.mts +115 -0
- package/dist/vitest/setup.d.mts +1 -2
- package/dist/vitest/setup.mjs +2 -2
- package/dist/vitest/workflow-local.d.mts +40 -0
- package/dist/{workflow-DgemCAz3.mjs → workflow-Bo59B75H.mjs} +16 -4
- package/dist/workflow-Bo59B75H.mjs.map +1 -0
- package/docs/cli/application.md +98 -250
- package/docs/cli/auth.md +48 -292
- package/docs/cli/completion.md +1 -25
- package/docs/cli/crashreport.md +3 -61
- package/docs/cli/executor.md +72 -183
- package/docs/cli/function.md +14 -130
- package/docs/cli/organization.md +11 -221
- package/docs/cli/plugin.md +29 -0
- package/docs/cli/query.md +2 -22
- package/docs/cli/secret.md +71 -251
- package/docs/cli/setup.md +138 -40
- package/docs/cli/skills.md +50 -39
- package/docs/cli/staticwebsite.md +32 -180
- package/docs/cli/tailordb.md +97 -323
- package/docs/cli/upgrade.md +2 -22
- package/docs/cli/user.md +78 -247
- package/docs/cli/workflow.md +150 -172
- package/docs/cli/workspace.md +165 -538
- package/docs/cli-reference.md +184 -78
- package/docs/configuration.md +10 -4
- package/docs/github-actions.md +64 -24
- package/docs/migration/v2.md +735 -0
- package/docs/multi-environment.md +29 -7
- package/docs/plugin/index.md +6 -6
- package/docs/quickstart.md +8 -7
- package/docs/runtime.md +6 -2
- package/docs/services/aigateway.md +18 -2
- package/docs/services/auth.md +44 -52
- package/docs/services/executor.md +3 -3
- package/docs/services/http-adapter.md +16 -1
- package/docs/services/idp.md +55 -2
- package/docs/services/resolver.md +7 -7
- package/docs/services/secret.md +11 -11
- package/docs/services/staticwebsite.md +8 -2
- package/docs/services/tailordb-migration.md +24 -22
- package/docs/services/tailordb.md +39 -14
- package/docs/services/workflow.md +18 -18
- package/docs/testing.md +108 -50
- package/package.json +46 -35
- package/postinstall.mjs +4 -6
- package/dist/application-DB2r36Et.mjs +0 -3
- package/dist/application-DqS1yBg3.mjs.map +0 -1
- package/dist/authconnection-D2MhtTN5.mjs.map +0 -1
- package/dist/authconnection-DvUQAjQS.d.mts +0 -39
- package/dist/client-Dbohmtkv.mjs +0 -3
- package/dist/client-z_oHGVNy.mjs.map +0 -1
- package/dist/context-BuuIb8CC.d.mts +0 -68
- package/dist/errors-Dtf2WPaW.mjs.map +0 -1
- package/dist/field-DOsJCPFa.mjs +0 -22
- package/dist/field-DOsJCPFa.mjs.map +0 -1
- package/dist/file-BB8Vs9O_.d.mts +0 -250
- package/dist/file-_oUZo76X.mjs.map +0 -1
- package/dist/file-utils-DcyIPFQh.mjs.map +0 -1
- package/dist/globals-Crz8o65k.mjs.map +0 -1
- package/dist/http-adapter.generated-WgMnb7Sb.d.mts +0 -580
- package/dist/iconv-Co-TOPuH.d.mts +0 -122
- package/dist/idp-BDbK5gjm.mjs.map +0 -1
- package/dist/idp-DrhVrLmV.d.mts +0 -160
- package/dist/index-5vPyRu1y.d.mts +0 -18
- package/dist/index-B7AKc18V.d.mts +0 -47
- package/dist/index-BlpzXncY.d.mts +0 -1016
- package/dist/index-CK7u9isy.d.mts +0 -46
- package/dist/index-CNYe5lnW.d.mts +0 -18
- package/dist/index-DjUdWlzf.d.mts +0 -18
- package/dist/index-ZePLwxw7.d.mts +0 -208
- package/dist/interceptor-D-q1rvRl.mjs.map +0 -1
- package/dist/job-fuc3j1Ma.mjs +0 -53
- package/dist/job-fuc3j1Ma.mjs.map +0 -1
- package/dist/kysely-type-DR8uzZTA.mjs.map +0 -1
- package/dist/mock-BjFj5o1I.mjs +0 -804
- package/dist/mock-BjFj5o1I.mjs.map +0 -1
- package/dist/registry-DdsYlL_P.mjs.map +0 -1
- package/dist/runtime-n9NCkjee.mjs.map +0 -1
- package/dist/schema-BhkpP5Hw.mjs.map +0 -1
- package/dist/secret-file-DBqZhjFQ.mjs.map +0 -1
- package/dist/secretmanager-B3n4KHfm.d.mts +0 -55
- package/dist/seed-jf3008-h.mjs.map +0 -1
- package/dist/service-CCL8ruDf.mjs +0 -459
- package/dist/service-CCL8ruDf.mjs.map +0 -1
- package/dist/service-D6yonf2I.mjs +0 -3
- package/dist/telemetry-CdqJEzkj.mjs.map +0 -1
- package/dist/telemetry-ClwW5ohF.mjs +0 -3
- package/dist/test-env-key-D9kM6ETE.mjs +0 -49
- package/dist/test-env-key-D9kM6ETE.mjs.map +0 -1
- package/dist/types-B2RpYyA_.mjs +0 -371
- package/dist/types-B2RpYyA_.mjs.map +0 -1
- package/dist/types-ClhIrW_C.mjs +0 -4
- package/dist/types-DCUhgpyI.d.mts +0 -784
- package/dist/types-DhO_VEZd.d.mts +0 -574
- package/dist/types-DwDgacni.d.mts +0 -338
- package/dist/workflow-BbKvGLQg.d.mts +0 -96
- package/dist/workflow-DgemCAz3.mjs.map +0 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { C as CustomDomainStatus, R as FunctionExecution_Type, mt as AuthInvokerSchema, xt as PATScope } from "../service_pb-CIrhGwHk.mjs";
|
|
3
3
|
import { t as assertDefined } from "../assert-DBxo8jPo.mjs";
|
|
4
|
-
import { n as logger, r as styles } from "../logger-
|
|
5
|
-
import { $ as
|
|
6
|
-
import { A as
|
|
7
|
-
import {
|
|
4
|
+
import { n as logger, r as styles } from "../logger-BEiZZ3qT.mjs";
|
|
5
|
+
import { $t as compareSnapshotWithRemote, A as listCommand$13, An as apiCommand, Bn as pagedLogArgs, C as listCommand$14, Cn as hasChanges, Dn as PluginManager, Dt as listCommand$7, E as waitCommand, En as sdkNameLabelKey, F as generateCommand$1, Fn as configArg, G as removeCommand$1, Gt as deploy, H as extractOwnedNamespaces, Hn as toPageDirection, Ht as formatKeyValueTable, I as generateMigrationScript, In as confirmationArgs, It as getCommand$6, K as updateCommand$3, Kt as ensureConfigId, L as writeDbTypesFile, Ln as deploymentArgs, Mn as assertWritable, N as truncateCommand, Nn as defineAppCommand, O as resumeCommand, On as generateUserTypes, Pn as commonArgs, Pt as startCommand, Q as getCommand$5, Qt as parseMigrationLabelNumber, R as getConfiguredEditorCommand, Rn as isVerbose, Rt as executionsCommand, Sn as formatMigrationDiff, T as healthCommand, Tn as resourceTrn, Tt as triggerCommand, U as logBetaWarning, Un as workspaceArgs, Ut as getCommand$1, V as showCommand, Vn as paginationArgs, Vt as functionExecutionStatusToString, X as listCommand$12, Xt as MIGRATION_LABEL_KEY, Y as treeCommand, Zt as handleOptionalToRequiredError, _n as loadDiff, b as createCommand$4, bn as parseMigrationNumberArg, c as listCommand$15, cn as compareLocalTypesWithSnapshot, ct as createCommand$3, dn as getLatestMigrationNumber, en as generateAllTypeManifestsFromSnapshot, et as updateCommand$2, f as restoreCommand, ft as getCommand$3, g as getCommand$7, gn as isValidMigrationNumber, gt as listCommand$9, ht as tokenCommand$1, i as updateCommand$4, in as INITIAL_SCHEMA_NUMBER, it as getCommand$4, jt as jobsCommand, kn as prompt, m as listCommand$16, mn as getMigrationFiles, nt as listCommand$11, o as removeCommand, ot as deleteCommand$4, pn as getMigrationFilePath, qt as executeScript, r as queryCommand, sn as assertValidMigrationFiles, t as registerTsHook, tn as protoGqlPermission, u as inviteCommand, un as createSnapshotFromLocalTypes, ut as listCommand$10, v as deleteCommand$5, vn as reconstructSnapshotFromMigrations, vt as generate, wn as getNamespacesWithMigrations, wt as webhookCommand, xt as getCommand$2, yn as formatMigrationNumber, yt as listCommand$8, z as openInConfiguredEditor, zn as multiConfigArg } from "../register-ts-hook-ZpBacA07.mjs";
|
|
6
|
+
import { A as hasUserTokenEntry, B as removeLegacyUserAlias, C as getDistDir, D as deleteUserTokens, E as loadConfig, F as loadPlatformClientConfig, G as closeConnectionPool, H as resolveUserTokenKey, I as loadStoredUserTokens, J as fetchAllTolerant, K as defaultPlatformBaseUrl, L as loadWorkspaceId, M as loadConfigPath, N as loadConsoleBaseUrl, O as fetchLatestToken, P as loadMachineUserName, Q as fetchUserInfo, R as platformConfigFromProfile, U as saveUserTokens, V as resolveConfigUser, W as writePlatformConfig, X as fetchPaged, Z as fetchPlatformMachineUserToken, _ as composeFunctionTreeshakeOptions, a as resolveInlineSourcemap, at as isDefaultPlatform, c as ResolverSchema, et as getOAuth2ClientId, g as platformBundleDefinePlugin, it as initOperatorClient, j as loadAccessToken, k as hasAnyUserTokenEntry, nt as getPlatformBaseUrl, q as fetchAll, rt as initOAuth2Client, s as WorkflowJobSchema, t as defineApplication, tt as getOrNull, u as INVOKER_EXPR, v as createLogLevelTreeshakeOptions, w as hashContent$1, y as resolveBundleLogLevel, z as readPlatformConfig } from "../application-eHp9jXld.mjs";
|
|
7
|
+
import { i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-ufH7sS8o.mjs";
|
|
8
8
|
import { t as multiline } from "../multiline-sfHpTZZK.mjs";
|
|
9
9
|
import { t as readPackageJson } from "../package-json-8b0O9TlX.mjs";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { i as userAgent } from "../secret-file-aoNXJmKa.mjs";
|
|
11
|
+
import { n as isCLIError } from "../errors-118wUDRr.mjs";
|
|
12
|
+
import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-Cg-pqRJV.mjs";
|
|
12
13
|
import { t as isPluginGeneratedType } from "../type-source-DH_LH20p.mjs";
|
|
14
|
+
import * as path from "pathe";
|
|
15
|
+
import { dirname, resolve } from "pathe";
|
|
16
|
+
import { resolvePackageJSON, resolveTSConfig } from "pkg-types";
|
|
13
17
|
import { arg, defineCommand, runCommand, runMain } from "politty";
|
|
14
18
|
import { withCompletionCommand } from "politty/completion";
|
|
19
|
+
import { withSkillCommand } from "politty/skill";
|
|
15
20
|
import { z } from "zod";
|
|
16
21
|
import { create } from "@bufbuild/protobuf";
|
|
17
22
|
import * as fs$1 from "node:fs";
|
|
23
|
+
import { accessSync, constants, readdirSync } from "node:fs";
|
|
18
24
|
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
19
|
-
import * as path from "pathe";
|
|
20
|
-
import { dirname, resolve } from "pathe";
|
|
21
25
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
26
|
import { generateCodeVerifier } from "@badgateway/oauth2-client";
|
|
23
27
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
24
|
-
import
|
|
28
|
+
import pLimit from "p-limit";
|
|
25
29
|
import * as crypto from "node:crypto";
|
|
26
30
|
import { createHash } from "node:crypto";
|
|
27
31
|
import * as http from "node:http";
|
|
@@ -29,14 +33,34 @@ import open from "open";
|
|
|
29
33
|
import * as rolldown from "rolldown";
|
|
30
34
|
import * as fsPromises from "node:fs/promises";
|
|
31
35
|
import { glob } from "node:fs/promises";
|
|
32
|
-
import
|
|
36
|
+
import * as os from "node:os";
|
|
37
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
33
38
|
import { TraceMap, generatedPositionFor, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
34
39
|
import { spawn, spawnSync } from "node:child_process";
|
|
35
40
|
import { watch } from "chokidar";
|
|
36
41
|
import * as fs from "fs";
|
|
37
42
|
import { lookup } from "mime-types";
|
|
38
|
-
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
39
43
|
|
|
44
|
+
//#region src/cli/commands/auth/token.ts
|
|
45
|
+
const tokenCommand = defineAppCommand({
|
|
46
|
+
name: "token",
|
|
47
|
+
description: "Print a valid Tailor Platform access token to stdout, refreshing it first if expired.",
|
|
48
|
+
args: z.strictObject({ profile: workspaceArgs.profile }),
|
|
49
|
+
run: async ({ profile }) => {
|
|
50
|
+
const token = await loadAccessToken({ profile });
|
|
51
|
+
logger.out(token);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/cli/commands/auth/index.ts
|
|
57
|
+
const authCommand = defineCommand({
|
|
58
|
+
name: "auth",
|
|
59
|
+
description: "Authentication helpers for scripts and plugins.",
|
|
60
|
+
subCommands: { token: tokenCommand }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
40
64
|
//#region src/cli/commands/authconnection/args.ts
|
|
41
65
|
/**
|
|
42
66
|
* Arguments for identifying an auth connection
|
|
@@ -67,13 +91,13 @@ function randomState$1() {
|
|
|
67
91
|
const authorizeAuthConnectionCommand = defineAppCommand({
|
|
68
92
|
name: "authorize",
|
|
69
93
|
description: "Authorize an auth connection via OAuth2 flow.",
|
|
70
|
-
args: z.
|
|
94
|
+
args: z.strictObject({
|
|
71
95
|
...workspaceArgs,
|
|
72
96
|
...connectionNameArgs,
|
|
73
97
|
scopes: z.string().optional().default(defaultScopes).describe("OAuth2 scopes to request (comma-separated)"),
|
|
74
98
|
port: z.coerce.number().optional().default(defaultPort).describe("Local callback server port"),
|
|
75
99
|
"no-browser": z.boolean().optional().default(false).describe("Don't open browser automatically")
|
|
76
|
-
})
|
|
100
|
+
}),
|
|
77
101
|
run: async (args) => {
|
|
78
102
|
await assertWritable({ profile: args.profile });
|
|
79
103
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
@@ -147,13 +171,13 @@ const authorizeAuthConnectionCommand = defineAppCommand({
|
|
|
147
171
|
clearTimeout(timeout);
|
|
148
172
|
const code = err.code;
|
|
149
173
|
const portHint = code === "EADDRINUSE" || code === "EACCES" ? `Try a different port with --port, or authorize via the Console instead:` : `Authorize via the Console instead:`;
|
|
150
|
-
logger.warn(`Could not start the local callback server on port ${args.port}${code ? ` (${code})` : ""}.\n${portHint}\n tailor
|
|
174
|
+
logger.warn(`Could not start the local callback server on port ${args.port}${code ? ` (${code})` : ""}.\n${portHint}\n tailor authconnection open`);
|
|
151
175
|
reject(err);
|
|
152
176
|
});
|
|
153
177
|
server.listen(args.port, async () => {
|
|
154
178
|
const authorizeUrl = authUrl.toString();
|
|
155
179
|
logger.info(args["no-browser"] ? `Open this URL in your browser to authorize:\n\n${authorizeUrl}\n` : `Opening browser for authorization:\n\n${authorizeUrl}\n`);
|
|
156
|
-
logger.info("If this flow doesn't complete, you can authorize via the Console instead:\n tailor
|
|
180
|
+
logger.info("If this flow doesn't complete, you can authorize via the Console instead:\n tailor authconnection open");
|
|
157
181
|
if (!args["no-browser"]) try {
|
|
158
182
|
await open(authorizeUrl);
|
|
159
183
|
} catch {
|
|
@@ -170,11 +194,11 @@ const authorizeAuthConnectionCommand = defineAppCommand({
|
|
|
170
194
|
const deleteAuthConnectionCommand = defineAppCommand({
|
|
171
195
|
name: "delete",
|
|
172
196
|
description: "Delete an auth connection entirely.",
|
|
173
|
-
args: z.
|
|
197
|
+
args: z.strictObject({
|
|
174
198
|
...workspaceArgs,
|
|
175
199
|
...connectionNameArgs,
|
|
176
200
|
...confirmationArgs
|
|
177
|
-
})
|
|
201
|
+
}),
|
|
178
202
|
run: async (args) => {
|
|
179
203
|
await assertWritable({ profile: args.profile });
|
|
180
204
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
@@ -219,10 +243,10 @@ function connectionInfo(connection) {
|
|
|
219
243
|
const listAuthConnectionCommand = defineAppCommand({
|
|
220
244
|
name: "list",
|
|
221
245
|
description: "List all auth connections.",
|
|
222
|
-
args: z.
|
|
246
|
+
args: z.strictObject({
|
|
223
247
|
...workspaceArgs,
|
|
224
248
|
...paginationArgs()
|
|
225
|
-
})
|
|
249
|
+
}),
|
|
226
250
|
run: async (args) => {
|
|
227
251
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
228
252
|
const workspaceId = await loadWorkspaceId({
|
|
@@ -253,18 +277,21 @@ const listAuthConnectionCommand = defineAppCommand({
|
|
|
253
277
|
|
|
254
278
|
//#endregion
|
|
255
279
|
//#region src/cli/commands/authconnection/open.ts
|
|
256
|
-
const consoleBaseUrl$1 = "https://console.tailor.tech";
|
|
257
280
|
const openAuthConnectionCommand = defineAppCommand({
|
|
258
281
|
name: "open",
|
|
259
282
|
description: "Open the auth connections page in the Tailor Platform Console.",
|
|
260
|
-
args: z.
|
|
283
|
+
args: z.strictObject({ ...workspaceArgs }),
|
|
261
284
|
run: async (args) => {
|
|
262
285
|
const workspaceId = await loadWorkspaceId({
|
|
263
286
|
workspaceId: args["workspace-id"],
|
|
264
287
|
profile: args.profile
|
|
265
288
|
});
|
|
266
289
|
const consolePath = `/workspaces/${workspaceId}/settings/connections`;
|
|
267
|
-
const
|
|
290
|
+
const consoleBaseUrl = await loadConsoleBaseUrl({
|
|
291
|
+
profile: args.profile,
|
|
292
|
+
...args["workspace-id"] !== void 0 ? { allowMissingProfile: true } : {}
|
|
293
|
+
});
|
|
294
|
+
const consoleUrl = new URL(consolePath, consoleBaseUrl).toString();
|
|
268
295
|
const jsonOutput = logger.jsonMode;
|
|
269
296
|
logger.info("Opening auth connections page in Tailor Platform Console...");
|
|
270
297
|
let opened = true;
|
|
@@ -294,11 +321,11 @@ const revokeAuthConnectionCommand = defineAppCommand({
|
|
|
294
321
|
name: "revoke",
|
|
295
322
|
description: "Revoke an auth connection's tokens (keeps the connection; use 'delete' to remove it).",
|
|
296
323
|
notes: "Revoke invalidates the connection's active session and tokens but keeps the connection and its stored credentials, so it can be re-authorized later. Use `delete` to remove the connection entirely.",
|
|
297
|
-
args: z.
|
|
324
|
+
args: z.strictObject({
|
|
298
325
|
...workspaceArgs,
|
|
299
326
|
...connectionNameArgs,
|
|
300
327
|
...confirmationArgs
|
|
301
|
-
})
|
|
328
|
+
}),
|
|
302
329
|
run: async (args) => {
|
|
303
330
|
await assertWritable({ profile: args.profile });
|
|
304
331
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
@@ -355,10 +382,10 @@ function formatCrashReportFiles(files, localDir) {
|
|
|
355
382
|
path: path.join(localDir, file)
|
|
356
383
|
}));
|
|
357
384
|
}
|
|
358
|
-
const listCommand$
|
|
385
|
+
const listCommand$6 = defineAppCommand({
|
|
359
386
|
name: "list",
|
|
360
387
|
description: "List local crash report files.",
|
|
361
|
-
args: z.
|
|
388
|
+
args: z.strictObject({ ...paginationArgs() }),
|
|
362
389
|
run: async (args) => {
|
|
363
390
|
const config = parseCrashReportConfig();
|
|
364
391
|
const jsonOutput = logger.jsonMode;
|
|
@@ -398,14 +425,14 @@ const listCommand$5 = defineAppCommand({
|
|
|
398
425
|
const sendCommand = defineAppCommand({
|
|
399
426
|
name: "send",
|
|
400
427
|
description: "Submit a crash report to help improve the SDK.",
|
|
401
|
-
args: z.
|
|
428
|
+
args: z.strictObject({ file: arg(z.string(), {
|
|
402
429
|
description: "Path to the crash report file",
|
|
403
430
|
required: true,
|
|
404
431
|
completion: {
|
|
405
432
|
type: "file",
|
|
406
433
|
extensions: ["log"]
|
|
407
434
|
}
|
|
408
|
-
}) })
|
|
435
|
+
}) }),
|
|
409
436
|
run: async (args) => {
|
|
410
437
|
let content;
|
|
411
438
|
try {
|
|
@@ -454,11 +481,11 @@ const crashReportCommand = defineCommand({
|
|
|
454
481
|
name: "crashreport",
|
|
455
482
|
description: "Manage crash reports.",
|
|
456
483
|
subCommands: {
|
|
457
|
-
list: listCommand$
|
|
484
|
+
list: listCommand$6,
|
|
458
485
|
send: sendCommand
|
|
459
486
|
},
|
|
460
487
|
async run() {
|
|
461
|
-
await runCommand(listCommand$
|
|
488
|
+
await runCommand(listCommand$6, []);
|
|
462
489
|
}
|
|
463
490
|
});
|
|
464
491
|
|
|
@@ -467,8 +494,9 @@ const crashReportCommand = defineCommand({
|
|
|
467
494
|
const deployCommand$1 = defineAppCommand({
|
|
468
495
|
name: "deploy",
|
|
469
496
|
description: "Deploy your application by applying the Tailor configuration.",
|
|
470
|
-
args: z.
|
|
471
|
-
...
|
|
497
|
+
args: z.strictObject({
|
|
498
|
+
...workspaceArgs,
|
|
499
|
+
...multiConfigArg,
|
|
472
500
|
...confirmationArgs,
|
|
473
501
|
"dry-run": arg(z.boolean().optional(), {
|
|
474
502
|
alias: "d",
|
|
@@ -478,10 +506,10 @@ const deployCommand$1 = defineAppCommand({
|
|
|
478
506
|
"no-validate": arg(z.boolean().optional(), { description: "Skip client-side validation against platform resource constraints" }),
|
|
479
507
|
"no-cache": arg(z.boolean().optional(), { description: "Disable bundle caching for this run" }),
|
|
480
508
|
"clean-cache": arg(z.boolean().optional(), { description: "Clean the bundle cache before building" })
|
|
481
|
-
})
|
|
509
|
+
}),
|
|
482
510
|
run: async (args) => {
|
|
483
511
|
await assertWritable({ profile: args.profile });
|
|
484
|
-
const { initTelemetry } = await import("../telemetry-
|
|
512
|
+
const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
|
|
485
513
|
await initTelemetry();
|
|
486
514
|
await deploy({
|
|
487
515
|
workspaceId: args["workspace-id"],
|
|
@@ -505,12 +533,12 @@ const executorCommand = defineCommand({
|
|
|
505
533
|
subCommands: {
|
|
506
534
|
trigger: triggerCommand,
|
|
507
535
|
jobs: jobsCommand,
|
|
508
|
-
list: listCommand$
|
|
536
|
+
list: listCommand$7,
|
|
509
537
|
get: getCommand$1,
|
|
510
538
|
webhook: webhookCommand
|
|
511
539
|
},
|
|
512
540
|
async run() {
|
|
513
|
-
await runCommand(listCommand$
|
|
541
|
+
await runCommand(listCommand$7, []);
|
|
514
542
|
}
|
|
515
543
|
});
|
|
516
544
|
|
|
@@ -1020,14 +1048,14 @@ Stack traces are mapped only when the execution includes a content hash for the
|
|
|
1020
1048
|
desc: "Get execution details as JSON"
|
|
1021
1049
|
}
|
|
1022
1050
|
],
|
|
1023
|
-
args: z.
|
|
1051
|
+
args: z.strictObject({
|
|
1024
1052
|
...workspaceArgs,
|
|
1025
1053
|
...pagedLogArgs,
|
|
1026
1054
|
"execution-id": arg(z.string().optional(), {
|
|
1027
1055
|
positional: true,
|
|
1028
1056
|
description: "Execution ID (if provided, shows details with logs)"
|
|
1029
1057
|
})
|
|
1030
|
-
})
|
|
1058
|
+
}),
|
|
1031
1059
|
run: async (args) => {
|
|
1032
1060
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
1033
1061
|
const workspaceId = await loadWorkspaceId({
|
|
@@ -1143,21 +1171,21 @@ function generateEntry(detected, sourceFile, env, machineUser, workspaceId) {
|
|
|
1143
1171
|
export { _fn as main };
|
|
1144
1172
|
`;
|
|
1145
1173
|
case "resolver": {
|
|
1146
|
-
const
|
|
1174
|
+
const principalExpr = buildMachinePrincipalExpr(machineUser, workspaceId);
|
|
1147
1175
|
return multiline`
|
|
1148
1176
|
import _internalResolver from "${absoluteSourcePath}";
|
|
1149
1177
|
import { t } from "@tailor-platform/sdk";
|
|
1150
1178
|
|
|
1151
1179
|
const _env = ${JSON.stringify(env)};
|
|
1152
|
-
const
|
|
1180
|
+
const _caller = ${principalExpr};
|
|
1153
1181
|
|
|
1154
1182
|
const $tailor_resolver_body = async (context) => {
|
|
1155
|
-
const _invoker = ${INVOKER_EXPR};
|
|
1183
|
+
const _invoker = ${INVOKER_EXPR} ?? _caller;
|
|
1156
1184
|
if (_internalResolver.input) {
|
|
1157
1185
|
const result = t.object(_internalResolver.input).parse({
|
|
1158
1186
|
value: context,
|
|
1159
1187
|
data: context,
|
|
1160
|
-
|
|
1188
|
+
invoker: _invoker,
|
|
1161
1189
|
});
|
|
1162
1190
|
|
|
1163
1191
|
if (result.issues) {
|
|
@@ -1168,7 +1196,7 @@ function generateEntry(detected, sourceFile, env, machineUser, workspaceId) {
|
|
|
1168
1196
|
}
|
|
1169
1197
|
}
|
|
1170
1198
|
|
|
1171
|
-
const enrichedContext = { input: context, env: _env,
|
|
1199
|
+
const enrichedContext = { input: context, env: _env, caller: _caller, invoker: _invoker };
|
|
1172
1200
|
return _internalResolver.body(enrichedContext);
|
|
1173
1201
|
};
|
|
1174
1202
|
|
|
@@ -1176,15 +1204,15 @@ function generateEntry(detected, sourceFile, env, machineUser, workspaceId) {
|
|
|
1176
1204
|
`;
|
|
1177
1205
|
}
|
|
1178
1206
|
case "executor": {
|
|
1179
|
-
const
|
|
1207
|
+
const principalExpr = buildMachinePrincipalExpr(machineUser, workspaceId);
|
|
1180
1208
|
return multiline`
|
|
1181
1209
|
import _internalExecutor from "${absoluteSourcePath}";
|
|
1182
1210
|
|
|
1183
1211
|
const _env = ${JSON.stringify(env)};
|
|
1184
|
-
const _actor = ${
|
|
1212
|
+
const _actor = ${principalExpr};
|
|
1185
1213
|
|
|
1186
1214
|
const __executor_function = async (args) => {
|
|
1187
|
-
const _invoker = ${INVOKER_EXPR};
|
|
1215
|
+
const _invoker = ${INVOKER_EXPR} ?? _actor;
|
|
1188
1216
|
return _internalExecutor.operation.body({ ...args, env: _env, actor: _actor, invoker: _invoker });
|
|
1189
1217
|
};
|
|
1190
1218
|
|
|
@@ -1193,13 +1221,15 @@ function generateEntry(detected, sourceFile, env, machineUser, workspaceId) {
|
|
|
1193
1221
|
}
|
|
1194
1222
|
case "workflow-job": {
|
|
1195
1223
|
const exportName = assertDefined(detected.exportName, "workflow job export name missing");
|
|
1224
|
+
const principalExpr = buildMachinePrincipalExpr(machineUser, workspaceId);
|
|
1196
1225
|
return multiline`
|
|
1197
1226
|
import { ${exportName} } from "${absoluteSourcePath}";
|
|
1198
1227
|
|
|
1199
1228
|
const env = ${JSON.stringify(env)};
|
|
1229
|
+
const fallbackInvoker = ${principalExpr};
|
|
1200
1230
|
|
|
1201
1231
|
export async function main(input) {
|
|
1202
|
-
const invoker = ${INVOKER_EXPR};
|
|
1232
|
+
const invoker = ${INVOKER_EXPR} ?? fallbackInvoker;
|
|
1203
1233
|
return await ${exportName}.body(input, { env, invoker });
|
|
1204
1234
|
}
|
|
1205
1235
|
`;
|
|
@@ -1207,35 +1237,20 @@ function generateEntry(detected, sourceFile, env, machineUser, workspaceId) {
|
|
|
1207
1237
|
}
|
|
1208
1238
|
}
|
|
1209
1239
|
/**
|
|
1210
|
-
* Build a JSON expression for a machine user
|
|
1240
|
+
* Build a JSON expression for a machine user TailorPrincipal object.
|
|
1211
1241
|
* @param machineUser - Resolved machine user info
|
|
1212
1242
|
* @param workspaceId - Workspace ID
|
|
1213
1243
|
* @returns JSON string for the user expression
|
|
1214
1244
|
*/
|
|
1215
|
-
function
|
|
1245
|
+
function buildMachinePrincipalExpr(machineUser, workspaceId) {
|
|
1216
1246
|
return JSON.stringify({
|
|
1217
1247
|
id: machineUser.id,
|
|
1218
1248
|
type: "machine_user",
|
|
1219
1249
|
workspaceId,
|
|
1220
|
-
attributes: machineUser.attributes,
|
|
1250
|
+
attributes: machineUser.attributes ?? {},
|
|
1221
1251
|
attributeList: machineUser.attributeList
|
|
1222
1252
|
});
|
|
1223
1253
|
}
|
|
1224
|
-
/**
|
|
1225
|
-
* Build a JSON expression for a machine user TailorActor object.
|
|
1226
|
-
* @param machineUser - Resolved machine user info
|
|
1227
|
-
* @param workspaceId - Workspace ID
|
|
1228
|
-
* @returns JSON string for the actor expression
|
|
1229
|
-
*/
|
|
1230
|
-
function buildMachineActorExpr(machineUser, workspaceId) {
|
|
1231
|
-
return JSON.stringify({
|
|
1232
|
-
workspaceId,
|
|
1233
|
-
userId: machineUser.id,
|
|
1234
|
-
attributes: machineUser.attributes,
|
|
1235
|
-
attributeList: machineUser.attributeList,
|
|
1236
|
-
userType: "USER_TYPE_MACHINE_USER"
|
|
1237
|
-
});
|
|
1238
|
-
}
|
|
1239
1254
|
|
|
1240
1255
|
//#endregion
|
|
1241
1256
|
//#region src/cli/commands/function/detect.ts
|
|
@@ -1258,7 +1273,7 @@ async function detectFunctionType(options) {
|
|
|
1258
1273
|
const rawInput = module.default.input;
|
|
1259
1274
|
let inputSchema;
|
|
1260
1275
|
if (rawInput) {
|
|
1261
|
-
const { t } = await import("../types-
|
|
1276
|
+
const { t } = await import("../types-C-lQ4se3.mjs");
|
|
1262
1277
|
inputSchema = t.object(rawInput);
|
|
1263
1278
|
}
|
|
1264
1279
|
return {
|
|
@@ -1268,7 +1283,7 @@ async function detectFunctionType(options) {
|
|
|
1268
1283
|
inputSchema
|
|
1269
1284
|
};
|
|
1270
1285
|
}
|
|
1271
|
-
const executorResult = ExecutorSchema.safeParse(module.default);
|
|
1286
|
+
const executorResult = ExecutorSchema.safeParse(stripExecutorTriggerArgs(module.default));
|
|
1272
1287
|
if (executorResult.success) {
|
|
1273
1288
|
const { operation } = executorResult.data;
|
|
1274
1289
|
if (operation.kind === "function" || operation.kind === "jobFunction") return {
|
|
@@ -1350,7 +1365,7 @@ function deriveNameFromPath(filePath) {
|
|
|
1350
1365
|
//#endregion
|
|
1351
1366
|
//#region src/cli/commands/function/test-run.ts
|
|
1352
1367
|
/**
|
|
1353
|
-
* `tailor
|
|
1368
|
+
* `tailor function test-run` command
|
|
1354
1369
|
*
|
|
1355
1370
|
* Bundles and executes a function on the Tailor Platform server
|
|
1356
1371
|
* without deploying (applying) the application.
|
|
@@ -1444,11 +1459,9 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
|
|
|
1444
1459
|
functionType = detected.type;
|
|
1445
1460
|
functionName = detected.name;
|
|
1446
1461
|
logger.info(`Detected: ${styles.bold(detected.type)} ${styles.info(`"${detected.name}"`)}`);
|
|
1447
|
-
if (detected.type === "resolver" && args.arg) {
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
args.arg = void 0;
|
|
1451
|
-
} else if (detected.inputSchema) JSON.parse(args.arg);
|
|
1462
|
+
if (detected.type === "resolver" && args.arg && !detected.hasInput) {
|
|
1463
|
+
logger.warn("--arg is ignored because this resolver has no input schema. Define \"input\" in your resolver to use --arg.");
|
|
1464
|
+
args.arg = void 0;
|
|
1452
1465
|
}
|
|
1453
1466
|
logger.info("Bundling...");
|
|
1454
1467
|
({bundledCode, scriptName} = await bundleForTestRun({
|
|
@@ -1462,18 +1475,24 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
|
|
|
1462
1475
|
}));
|
|
1463
1476
|
logger.info(`Bundled as ${styles.bold(scriptName)}`);
|
|
1464
1477
|
}
|
|
1465
|
-
const
|
|
1478
|
+
const invoker = create(AuthInvokerSchema, {
|
|
1466
1479
|
namespace: authNamespace,
|
|
1467
1480
|
machineUserName: machineUser.name
|
|
1468
1481
|
});
|
|
1469
1482
|
logger.info(`Executing on workspace ${styles.dim(workspaceId)}...`);
|
|
1483
|
+
let parsedArg;
|
|
1484
|
+
if (args.arg !== void 0) try {
|
|
1485
|
+
parsedArg = JSON.parse(args.arg);
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
throw new Error(`Invalid --arg JSON: ${error instanceof Error ? error.message : error}`, { cause: error });
|
|
1488
|
+
}
|
|
1470
1489
|
const result = await executeScript({
|
|
1471
1490
|
client,
|
|
1472
1491
|
workspaceId,
|
|
1473
1492
|
name: scriptName,
|
|
1474
1493
|
code: bundledCode,
|
|
1475
|
-
arg:
|
|
1476
|
-
invoker
|
|
1494
|
+
arg: parsedArg,
|
|
1495
|
+
invoker
|
|
1477
1496
|
});
|
|
1478
1497
|
if (jsonOutput) logger.out({
|
|
1479
1498
|
success: result.success,
|
|
@@ -1539,7 +1558,7 @@ async function resolveMachineUserName(options) {
|
|
|
1539
1558
|
if (keys.length > 0) return assertDefined(keys[0], "machine user key missing");
|
|
1540
1559
|
}
|
|
1541
1560
|
}
|
|
1542
|
-
throw new Error("Machine user is required. Provide --machine-user, set TAILOR_PLATFORM_MACHINE_USER_NAME, set a profile default with 'tailor
|
|
1561
|
+
throw new Error("Machine user is required. Provide --machine-user, set TAILOR_PLATFORM_MACHINE_USER_NAME, set a profile default with 'tailor profile update <profile> --machine-user <name>', or ensure tailor.config.ts has machine users configured.");
|
|
1543
1562
|
}
|
|
1544
1563
|
/**
|
|
1545
1564
|
* Resolve full machine user info: name, id (from API), and attributes (from config).
|
|
@@ -1582,12 +1601,12 @@ const functionCommand = defineCommand({
|
|
|
1582
1601
|
description: "Manage functions",
|
|
1583
1602
|
subCommands: {
|
|
1584
1603
|
get: getCommand$2,
|
|
1585
|
-
list: listCommand$
|
|
1604
|
+
list: listCommand$8,
|
|
1586
1605
|
logs: logsCommand,
|
|
1587
1606
|
"test-run": testRunCommand
|
|
1588
1607
|
},
|
|
1589
1608
|
async run() {
|
|
1590
|
-
await runCommand(listCommand$
|
|
1609
|
+
await runCommand(listCommand$8, []);
|
|
1591
1610
|
}
|
|
1592
1611
|
});
|
|
1593
1612
|
|
|
@@ -1596,7 +1615,7 @@ const functionCommand = defineCommand({
|
|
|
1596
1615
|
const generateCommand = defineAppCommand({
|
|
1597
1616
|
name: "generate",
|
|
1598
1617
|
description: "Generate files using Tailor configuration.",
|
|
1599
|
-
args: z.
|
|
1618
|
+
args: z.strictObject({
|
|
1600
1619
|
config: arg(z.string().default("tailor.config.ts"), {
|
|
1601
1620
|
alias: "c",
|
|
1602
1621
|
description: "Path to SDK config file"
|
|
@@ -1605,9 +1624,9 @@ const generateCommand = defineAppCommand({
|
|
|
1605
1624
|
alias: "W",
|
|
1606
1625
|
description: "Watch for type/resolver changes and regenerate"
|
|
1607
1626
|
})
|
|
1608
|
-
})
|
|
1627
|
+
}),
|
|
1609
1628
|
run: async (args) => {
|
|
1610
|
-
const { initTelemetry } = await import("../telemetry-
|
|
1629
|
+
const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
|
|
1611
1630
|
await initTelemetry();
|
|
1612
1631
|
await generate({
|
|
1613
1632
|
configPath: args.config,
|
|
@@ -1634,7 +1653,7 @@ const detectPackageManager$1 = () => {
|
|
|
1634
1653
|
const initCommand = defineAppCommand({
|
|
1635
1654
|
name: "init",
|
|
1636
1655
|
description: "Initialize a new project using create-sdk.",
|
|
1637
|
-
args: z.
|
|
1656
|
+
args: z.strictObject({
|
|
1638
1657
|
name: arg(z.string().optional(), {
|
|
1639
1658
|
positional: true,
|
|
1640
1659
|
description: "Project name"
|
|
@@ -1643,7 +1662,7 @@ const initCommand = defineAppCommand({
|
|
|
1643
1662
|
alias: "t",
|
|
1644
1663
|
description: "Template name"
|
|
1645
1664
|
})
|
|
1646
|
-
})
|
|
1665
|
+
}),
|
|
1647
1666
|
run: async (args) => {
|
|
1648
1667
|
const packageJson = await readPackageJson();
|
|
1649
1668
|
const version = packageJson.version && packageJson.version !== "0.0.0" ? packageJson.version : "latest";
|
|
@@ -1671,8 +1690,15 @@ const redirectUri = `http://localhost:${redirectPort}/callback`;
|
|
|
1671
1690
|
function randomState() {
|
|
1672
1691
|
return crypto.randomBytes(32).toString("base64url");
|
|
1673
1692
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1693
|
+
function assertProfileLoginUser(args, authenticatedUser, authenticatedSubject) {
|
|
1694
|
+
if (args.profile && args.profileUser && authenticatedUser !== args.profileUser && authenticatedSubject !== args.profileUser) throw new Error(`Profile "${args.profile}" is configured for "${args.profileUser}", but login authenticated "${authenticatedUser}".`);
|
|
1695
|
+
}
|
|
1696
|
+
function shouldUpdateCurrentUser(profile, platformConfig) {
|
|
1697
|
+
if (!profile) return true;
|
|
1698
|
+
return isDefaultPlatform(platformConfig);
|
|
1699
|
+
}
|
|
1700
|
+
const startAuthServer = async (args = {}) => {
|
|
1701
|
+
const client = initOAuth2Client(args.platformConfig);
|
|
1676
1702
|
const state = randomState();
|
|
1677
1703
|
const codeVerifier = await generateCodeVerifier();
|
|
1678
1704
|
return new Promise((resolve, reject) => {
|
|
@@ -1684,14 +1710,18 @@ const startAuthServer = async () => {
|
|
|
1684
1710
|
state,
|
|
1685
1711
|
codeVerifier
|
|
1686
1712
|
});
|
|
1687
|
-
const userInfo = await fetchUserInfo(tokens.accessToken);
|
|
1713
|
+
const userInfo = await fetchUserInfo(tokens.accessToken, args.platformConfig);
|
|
1714
|
+
assertProfileLoginUser(args, userInfo.email, userInfo.sub);
|
|
1688
1715
|
const pfConfig = await readPlatformConfig();
|
|
1689
1716
|
await saveUserTokens(pfConfig, userInfo.sub, {
|
|
1690
1717
|
accessToken: tokens.accessToken,
|
|
1691
1718
|
refreshToken: tokens.refreshToken ?? void 0
|
|
1692
|
-
}, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString(), {
|
|
1693
|
-
|
|
1694
|
-
|
|
1719
|
+
}, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString(), {
|
|
1720
|
+
platformConfig: args.platformConfig,
|
|
1721
|
+
email: userInfo.email
|
|
1722
|
+
});
|
|
1723
|
+
if (args.updateCurrentUser ?? true) pfConfig.current_user = userInfo.sub;
|
|
1724
|
+
await removeLegacyUserAlias(pfConfig, userInfo.email, userInfo.sub, args.platformConfig);
|
|
1695
1725
|
writePlatformConfig(pfConfig);
|
|
1696
1726
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1697
1727
|
res.end(JSON.stringify({
|
|
@@ -1733,17 +1763,22 @@ const startAuthServer = async () => {
|
|
|
1733
1763
|
});
|
|
1734
1764
|
};
|
|
1735
1765
|
async function loginAsMachineUser(args) {
|
|
1766
|
+
assertProfileLoginUser(args, args.clientId);
|
|
1736
1767
|
const clientSecret = args.clientSecret ?? await prompt.password({ message: "Client secret" });
|
|
1737
|
-
const tokens = await fetchPlatformMachineUserToken(args.clientId, clientSecret);
|
|
1768
|
+
const tokens = await fetchPlatformMachineUserToken(args.clientId, clientSecret, args.platformConfig);
|
|
1738
1769
|
const pfConfig = await readPlatformConfig();
|
|
1739
|
-
await saveUserTokens(pfConfig, args.clientId, { accessToken: tokens.accessToken }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString());
|
|
1740
|
-
pfConfig.current_user = args.clientId;
|
|
1770
|
+
await saveUserTokens(pfConfig, args.clientId, { accessToken: tokens.accessToken }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString(), { platformConfig: args.platformConfig });
|
|
1771
|
+
if (args.updateCurrentUser ?? true) pfConfig.current_user = args.clientId;
|
|
1741
1772
|
writePlatformConfig(pfConfig);
|
|
1742
1773
|
}
|
|
1743
1774
|
const loginCommand = defineAppCommand({
|
|
1744
1775
|
name: "login",
|
|
1745
1776
|
description: "Login to Tailor Platform.",
|
|
1746
|
-
args: z.xor([z.
|
|
1777
|
+
args: z.xor([z.strictObject({ profile: arg(z.string().optional(), {
|
|
1778
|
+
alias: "p",
|
|
1779
|
+
description: "Workspace profile whose platform settings should be used for login.",
|
|
1780
|
+
env: "TAILOR_PLATFORM_PROFILE"
|
|
1781
|
+
}) }).describe("User Login"), z.strictObject({
|
|
1747
1782
|
"machine-user": arg(z.literal(true), {
|
|
1748
1783
|
description: "Login as a platform machine user.",
|
|
1749
1784
|
required: true
|
|
@@ -1756,14 +1791,37 @@ const loginCommand = defineAppCommand({
|
|
|
1756
1791
|
"client-secret": arg(z.string().optional(), {
|
|
1757
1792
|
description: "Client secret",
|
|
1758
1793
|
env: "TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET"
|
|
1794
|
+
}),
|
|
1795
|
+
profile: arg(z.string().optional(), {
|
|
1796
|
+
alias: "p",
|
|
1797
|
+
description: "Workspace profile whose platform settings should be used for login.",
|
|
1798
|
+
env: "TAILOR_PLATFORM_PROFILE"
|
|
1759
1799
|
})
|
|
1760
|
-
}).
|
|
1800
|
+
}).describe("Machine User Login")]),
|
|
1761
1801
|
run: async (args) => {
|
|
1802
|
+
let platformConfig;
|
|
1803
|
+
let profileUser;
|
|
1804
|
+
if ("profile" in args && args.profile) {
|
|
1805
|
+
const profileEntry = (await readPlatformConfig()).profiles[args.profile];
|
|
1806
|
+
if (!profileEntry) throw new Error(`Profile "${args.profile}" not found`);
|
|
1807
|
+
platformConfig = platformConfigFromProfile(profileEntry);
|
|
1808
|
+
profileUser = profileEntry.user;
|
|
1809
|
+
}
|
|
1810
|
+
const updateCurrentUser = shouldUpdateCurrentUser(args.profile, platformConfig);
|
|
1762
1811
|
if ("machine-user" in args) await loginAsMachineUser({
|
|
1763
1812
|
clientId: args.clientId,
|
|
1764
|
-
clientSecret: args.clientSecret
|
|
1813
|
+
clientSecret: args.clientSecret,
|
|
1814
|
+
profile: args.profile,
|
|
1815
|
+
profileUser,
|
|
1816
|
+
platformConfig,
|
|
1817
|
+
updateCurrentUser
|
|
1818
|
+
});
|
|
1819
|
+
else await startAuthServer({
|
|
1820
|
+
profile: args.profile,
|
|
1821
|
+
profileUser,
|
|
1822
|
+
platformConfig,
|
|
1823
|
+
updateCurrentUser
|
|
1765
1824
|
});
|
|
1766
|
-
else await startAuthServer();
|
|
1767
1825
|
logger.success("Successfully logged in to Tailor Platform.");
|
|
1768
1826
|
await closeConnectionPool();
|
|
1769
1827
|
}
|
|
@@ -1774,30 +1832,53 @@ const loginCommand = defineAppCommand({
|
|
|
1774
1832
|
const logoutCommand = defineAppCommand({
|
|
1775
1833
|
name: "logout",
|
|
1776
1834
|
description: "Logout from Tailor Platform.",
|
|
1777
|
-
args: z.
|
|
1778
|
-
|
|
1835
|
+
args: z.strictObject({ profile: arg(z.string().optional(), {
|
|
1836
|
+
alias: "p",
|
|
1837
|
+
description: "Workspace profile whose platform settings should be used for logout.",
|
|
1838
|
+
env: "TAILOR_PLATFORM_PROFILE"
|
|
1839
|
+
}) }),
|
|
1840
|
+
run: async (args) => {
|
|
1841
|
+
const profile = args.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
1779
1842
|
const pfConfig = await readPlatformConfig();
|
|
1780
|
-
const
|
|
1781
|
-
|
|
1782
|
-
|
|
1843
|
+
const profileEntry = profile ? pfConfig.profiles[profile] : void 0;
|
|
1844
|
+
if (profile && !profileEntry) throw new Error(`Profile "${profile}" not found`);
|
|
1845
|
+
const platformConfig = profileEntry ? platformConfigFromProfile(profileEntry) : void 0;
|
|
1846
|
+
const currentUser = profileEntry ? profileEntry.user : pfConfig.current_user;
|
|
1847
|
+
const deletesDefaultToken = isDefaultPlatform(platformConfig);
|
|
1848
|
+
const lookupOptions = profile ? { allowLegacyUserKey: true } : void 0;
|
|
1849
|
+
if (!currentUser) {
|
|
1783
1850
|
logger.info("You are not logged in.");
|
|
1784
1851
|
return;
|
|
1785
1852
|
}
|
|
1853
|
+
const hasDefaultUserToken = () => hasUserTokenEntry(pfConfig, currentUser, { platformUrl: defaultPlatformBaseUrl });
|
|
1854
|
+
const shouldClearCurrentUser = () => pfConfig.current_user === currentUser && (deletesDefaultToken ? !hasDefaultUserToken() : !hasAnyUserTokenEntry(pfConfig, currentUser) && !hasDefaultUserToken());
|
|
1855
|
+
let storedTokens;
|
|
1856
|
+
let tokenLoadFailed = false;
|
|
1786
1857
|
try {
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1858
|
+
storedTokens = await loadStoredUserTokens(pfConfig, currentUser, platformConfig, lookupOptions);
|
|
1859
|
+
} catch (error) {
|
|
1860
|
+
tokenLoadFailed = true;
|
|
1861
|
+
logger.warn(`Failed to revoke token: ${error instanceof Error ? error.message : error}`);
|
|
1862
|
+
}
|
|
1863
|
+
if (!storedTokens && !tokenLoadFailed) {
|
|
1864
|
+
logger.info("You are not logged in.");
|
|
1865
|
+
if (shouldClearCurrentUser()) pfConfig.current_user = null;
|
|
1866
|
+
writePlatformConfig(pfConfig);
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
if (storedTokens) try {
|
|
1870
|
+
const client = initOAuth2Client(platformConfig);
|
|
1871
|
+
const tokenTypeHint = storedTokens.refreshToken ? "refresh_token" : "access_token";
|
|
1790
1872
|
await client.revoke({
|
|
1791
|
-
accessToken,
|
|
1792
|
-
refreshToken: refreshToken ?? null,
|
|
1793
|
-
expiresAt: Date.parse(userEntry.token_expires_at)
|
|
1873
|
+
accessToken: storedTokens.accessToken,
|
|
1874
|
+
refreshToken: storedTokens.refreshToken ?? null,
|
|
1875
|
+
expiresAt: Date.parse(storedTokens.userEntry.token_expires_at)
|
|
1794
1876
|
}, tokenTypeHint);
|
|
1795
1877
|
} catch (error) {
|
|
1796
1878
|
logger.warn(`Failed to revoke token: ${error instanceof Error ? error.message : error}`);
|
|
1797
1879
|
}
|
|
1798
|
-
await deleteUserTokens(pfConfig, currentUser);
|
|
1799
|
-
|
|
1800
|
-
pfConfig.current_user = null;
|
|
1880
|
+
await deleteUserTokens(pfConfig, currentUser, platformConfig, lookupOptions);
|
|
1881
|
+
if (shouldClearCurrentUser()) pfConfig.current_user = null;
|
|
1801
1882
|
writePlatformConfig(pfConfig);
|
|
1802
1883
|
logger.success("Successfully logged out from Tailor Platform.");
|
|
1803
1884
|
}
|
|
@@ -1809,11 +1890,11 @@ const machineuserCommand = defineCommand({
|
|
|
1809
1890
|
name: "machineuser",
|
|
1810
1891
|
description: "Manage machine users in your Tailor Platform application.",
|
|
1811
1892
|
subCommands: {
|
|
1812
|
-
list: listCommand$
|
|
1813
|
-
token: tokenCommand
|
|
1893
|
+
list: listCommand$9,
|
|
1894
|
+
token: tokenCommand$1
|
|
1814
1895
|
},
|
|
1815
1896
|
async run() {
|
|
1816
|
-
await runCommand(listCommand$
|
|
1897
|
+
await runCommand(listCommand$9, []);
|
|
1817
1898
|
}
|
|
1818
1899
|
});
|
|
1819
1900
|
|
|
@@ -1823,21 +1904,20 @@ const oauth2clientCommand = defineCommand({
|
|
|
1823
1904
|
name: "oauth2client",
|
|
1824
1905
|
description: "Manage OAuth2 clients in your Tailor Platform application.",
|
|
1825
1906
|
subCommands: {
|
|
1826
|
-
list: listCommand$
|
|
1907
|
+
list: listCommand$10,
|
|
1827
1908
|
get: getCommand$3
|
|
1828
1909
|
},
|
|
1829
1910
|
async run() {
|
|
1830
|
-
await runCommand(listCommand$
|
|
1911
|
+
await runCommand(listCommand$10, []);
|
|
1831
1912
|
}
|
|
1832
1913
|
});
|
|
1833
1914
|
|
|
1834
1915
|
//#endregion
|
|
1835
1916
|
//#region src/cli/commands/open.ts
|
|
1836
|
-
const consoleBaseUrl = "https://console.tailor.tech";
|
|
1837
1917
|
const openCommand = defineAppCommand({
|
|
1838
1918
|
name: "open",
|
|
1839
1919
|
description: "Open Tailor Platform Console.",
|
|
1840
|
-
args: z.
|
|
1920
|
+
args: z.strictObject({ ...deploymentArgs }),
|
|
1841
1921
|
run: async (args) => {
|
|
1842
1922
|
const workspaceId = await loadWorkspaceId({
|
|
1843
1923
|
workspaceId: args["workspace-id"],
|
|
@@ -1846,6 +1926,10 @@ const openCommand = defineAppCommand({
|
|
|
1846
1926
|
const { config } = await loadConfig(args.config);
|
|
1847
1927
|
const applicationName = config.name;
|
|
1848
1928
|
const consolePath = `/workspaces/${workspaceId}/applications/${encodeURIComponent(applicationName)}/overview`;
|
|
1929
|
+
const consoleBaseUrl = await loadConsoleBaseUrl({
|
|
1930
|
+
profile: args.profile,
|
|
1931
|
+
...args["workspace-id"] !== void 0 ? { allowMissingProfile: true } : {}
|
|
1932
|
+
});
|
|
1849
1933
|
const consoleUrl = new URL(consolePath, consoleBaseUrl).toString();
|
|
1850
1934
|
const jsonOutput = logger.jsonMode;
|
|
1851
1935
|
logger.info("Opening Tailor Platform Console...");
|
|
@@ -1879,9 +1963,9 @@ const folderCommand = defineCommand({
|
|
|
1879
1963
|
description: "Manage organization folders.",
|
|
1880
1964
|
subCommands: {
|
|
1881
1965
|
create: createCommand$3,
|
|
1882
|
-
delete: deleteCommand$
|
|
1966
|
+
delete: deleteCommand$4,
|
|
1883
1967
|
get: getCommand$4,
|
|
1884
|
-
list: listCommand$
|
|
1968
|
+
list: listCommand$11,
|
|
1885
1969
|
update: updateCommand$2
|
|
1886
1970
|
}
|
|
1887
1971
|
});
|
|
@@ -1894,12 +1978,399 @@ const organizationCommand = defineCommand({
|
|
|
1894
1978
|
subCommands: {
|
|
1895
1979
|
folder: folderCommand,
|
|
1896
1980
|
get: getCommand$5,
|
|
1897
|
-
list: listCommand$
|
|
1981
|
+
list: listCommand$12,
|
|
1898
1982
|
tree: treeCommand,
|
|
1899
1983
|
update: updateCommand$3
|
|
1900
1984
|
},
|
|
1901
1985
|
async run() {
|
|
1902
|
-
await runCommand(listCommand$
|
|
1986
|
+
await runCommand(listCommand$12, []);
|
|
1987
|
+
}
|
|
1988
|
+
});
|
|
1989
|
+
|
|
1990
|
+
//#endregion
|
|
1991
|
+
//#region src/cli/shared/builtin-commands.ts
|
|
1992
|
+
/**
|
|
1993
|
+
* Top-level builtin command names. A plugin named the same as one of these is
|
|
1994
|
+
* shadowed by the builtin and can never be dispatched.
|
|
1995
|
+
*
|
|
1996
|
+
* This list is the single source of truth used by `plugin list` to flag
|
|
1997
|
+
* shadowed plugins without importing the (cyclic) command tree. It is kept in
|
|
1998
|
+
* sync with `main-command.ts` by a drift test in `options.test.ts`.
|
|
1999
|
+
*/
|
|
2000
|
+
const BUILTIN_COMMAND_NAMES = [
|
|
2001
|
+
"api",
|
|
2002
|
+
"auth",
|
|
2003
|
+
"authconnection",
|
|
2004
|
+
"crashreport",
|
|
2005
|
+
"deploy",
|
|
2006
|
+
"executor",
|
|
2007
|
+
"function",
|
|
2008
|
+
"generate",
|
|
2009
|
+
"init",
|
|
2010
|
+
"login",
|
|
2011
|
+
"logout",
|
|
2012
|
+
"machineuser",
|
|
2013
|
+
"oauth2client",
|
|
2014
|
+
"open",
|
|
2015
|
+
"organization",
|
|
2016
|
+
"plugin",
|
|
2017
|
+
"profile",
|
|
2018
|
+
"query",
|
|
2019
|
+
"remove",
|
|
2020
|
+
"secret",
|
|
2021
|
+
"setup",
|
|
2022
|
+
"show",
|
|
2023
|
+
"skills",
|
|
2024
|
+
"staticwebsite",
|
|
2025
|
+
"tailordb",
|
|
2026
|
+
"upgrade",
|
|
2027
|
+
"user",
|
|
2028
|
+
"workflow",
|
|
2029
|
+
"workspace"
|
|
2030
|
+
];
|
|
2031
|
+
|
|
2032
|
+
//#endregion
|
|
2033
|
+
//#region src/cli/shared/plugin.ts
|
|
2034
|
+
/** PATH entry separator (`;` on Windows, `:` elsewhere). */
|
|
2035
|
+
const pathDelimiter = process.platform === "win32" ? ";" : ":";
|
|
2036
|
+
/**
|
|
2037
|
+
* Yield `start` and each ancestor directory up to the filesystem root.
|
|
2038
|
+
* @param start - Directory to start from
|
|
2039
|
+
* @yields Each ancestor directory, starting with `start`
|
|
2040
|
+
*/
|
|
2041
|
+
function* ancestorDirs(start) {
|
|
2042
|
+
let dir = path.resolve(start);
|
|
2043
|
+
let parent = path.dirname(dir);
|
|
2044
|
+
while (parent !== dir) {
|
|
2045
|
+
yield dir;
|
|
2046
|
+
dir = parent;
|
|
2047
|
+
parent = path.dirname(dir);
|
|
2048
|
+
}
|
|
2049
|
+
yield dir;
|
|
2050
|
+
}
|
|
2051
|
+
/** Fallback executable extensions when `PATHEXT` is unset (Windows). */
|
|
2052
|
+
const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.PS1";
|
|
2053
|
+
/**
|
|
2054
|
+
* Lowercased executable extensions from `PATHEXT` (Windows).
|
|
2055
|
+
* @returns Recognized executable extensions (e.g. `.exe`, `.cmd`)
|
|
2056
|
+
*/
|
|
2057
|
+
function windowsExecutableExts() {
|
|
2058
|
+
return (process.env.PATHEXT ?? DEFAULT_PATHEXT).split(";").map((e) => e.trim().toLowerCase()).filter(Boolean);
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Check whether a path exists and is runnable: X_OK on POSIX; on Windows, the
|
|
2062
|
+
* file must exist and either be extension-less (shebang script) or carry a
|
|
2063
|
+
* `PATHEXT` extension, so plain data files are not mistaken for plugins.
|
|
2064
|
+
* @param filePath - Path to check
|
|
2065
|
+
* @returns Whether the path is an executable file
|
|
2066
|
+
*/
|
|
2067
|
+
function isExecutable(filePath) {
|
|
2068
|
+
try {
|
|
2069
|
+
accessSync(filePath, process.platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
2070
|
+
} catch {
|
|
2071
|
+
return false;
|
|
2072
|
+
}
|
|
2073
|
+
if (process.platform === "win32") {
|
|
2074
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
2075
|
+
return ext === "" || windowsExecutableExts().includes(ext);
|
|
2076
|
+
}
|
|
2077
|
+
return true;
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Candidate file names for a plugin binary, accounting for Windows extensions.
|
|
2081
|
+
* @param binName - Base binary name (e.g. `tailor-foo`)
|
|
2082
|
+
* @returns Ordered candidate file names
|
|
2083
|
+
*/
|
|
2084
|
+
function binaryCandidates(binName) {
|
|
2085
|
+
if (process.platform !== "win32") return [binName];
|
|
2086
|
+
return [binName, ...windowsExecutableExts().map((ext) => `${binName}${ext}`)];
|
|
2087
|
+
}
|
|
2088
|
+
/**
|
|
2089
|
+
* Directories on the user's PATH.
|
|
2090
|
+
* @returns PATH entries
|
|
2091
|
+
*/
|
|
2092
|
+
function pathDirs() {
|
|
2093
|
+
return (process.env.PATH ?? "").split(pathDelimiter).filter(Boolean);
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Nearest `node_modules/.bin` directories, walking up from the current working
|
|
2097
|
+
* directory. Project-local bins take precedence over those higher in the tree.
|
|
2098
|
+
* @returns Ordered `node_modules/.bin` directories
|
|
2099
|
+
*/
|
|
2100
|
+
function nodeModulesBinDirs() {
|
|
2101
|
+
const dirs = [];
|
|
2102
|
+
for (const dir of ancestorDirs(process.cwd())) dirs.push(path.join(dir, "node_modules", ".bin"));
|
|
2103
|
+
return dirs;
|
|
2104
|
+
}
|
|
2105
|
+
/**
|
|
2106
|
+
* Find the first existing executable for the given base name across a set of
|
|
2107
|
+
* directories.
|
|
2108
|
+
* @param dirs - Directories to search
|
|
2109
|
+
* @param binName - Base binary name
|
|
2110
|
+
* @returns Absolute path, or undefined when not found
|
|
2111
|
+
*/
|
|
2112
|
+
function findExecutableIn(dirs, binName) {
|
|
2113
|
+
const candidates = binaryCandidates(binName);
|
|
2114
|
+
for (const dir of dirs) for (const candidate of candidates) {
|
|
2115
|
+
const full = path.join(dir, candidate);
|
|
2116
|
+
if (isExecutable(full)) return full;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Resolve a plugin executable by name.
|
|
2121
|
+
* Search order: project-local `node_modules/.bin` (nearest first), then PATH.
|
|
2122
|
+
* @param name - Plugin name (without the `<cli>-` prefix)
|
|
2123
|
+
* @param cliName - The host CLI name (e.g. `tailor`)
|
|
2124
|
+
* @returns The discovered plugin, or null when not found
|
|
2125
|
+
*/
|
|
2126
|
+
function resolvePlugin(name, cliName) {
|
|
2127
|
+
if (name.includes("/") || name.includes("\\") || name.includes("\0")) return null;
|
|
2128
|
+
const binName = `${cliName}-${name}`;
|
|
2129
|
+
const fromNodeModules = findExecutableIn(nodeModulesBinDirs(), binName);
|
|
2130
|
+
if (fromNodeModules) return {
|
|
2131
|
+
name,
|
|
2132
|
+
path: fromNodeModules,
|
|
2133
|
+
source: "node_modules"
|
|
2134
|
+
};
|
|
2135
|
+
const fromPath = findExecutableIn(pathDirs(), binName);
|
|
2136
|
+
if (fromPath) return {
|
|
2137
|
+
name,
|
|
2138
|
+
path: fromPath,
|
|
2139
|
+
source: "path"
|
|
2140
|
+
};
|
|
2141
|
+
return null;
|
|
2142
|
+
}
|
|
2143
|
+
/**
|
|
2144
|
+
* Strip a known binary extension from a file name (Windows).
|
|
2145
|
+
* @param fileName - File name
|
|
2146
|
+
* @returns File name without a recognized executable extension
|
|
2147
|
+
*/
|
|
2148
|
+
function stripBinExt(fileName) {
|
|
2149
|
+
if (process.platform !== "win32") return fileName;
|
|
2150
|
+
const ext = path.extname(fileName);
|
|
2151
|
+
return ext ? fileName.slice(0, -ext.length) : fileName;
|
|
2152
|
+
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Discover all plugins reachable from `node_modules/.bin` and PATH.
|
|
2155
|
+
* Earlier entries win on name collisions (project-local over PATH).
|
|
2156
|
+
* @param cliName - The host CLI name (e.g. `tailor`)
|
|
2157
|
+
* @returns Discovered plugins, deduped by name
|
|
2158
|
+
*/
|
|
2159
|
+
function listPlugins(cliName) {
|
|
2160
|
+
const prefix = `${cliName}-`;
|
|
2161
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2162
|
+
const scan = (dirs, source) => {
|
|
2163
|
+
for (const dir of dirs) {
|
|
2164
|
+
let entries;
|
|
2165
|
+
try {
|
|
2166
|
+
entries = readdirSync(dir);
|
|
2167
|
+
} catch {
|
|
2168
|
+
continue;
|
|
2169
|
+
}
|
|
2170
|
+
for (const entry of entries) {
|
|
2171
|
+
const base = stripBinExt(entry);
|
|
2172
|
+
if (!base.startsWith(prefix) || base.length === prefix.length) continue;
|
|
2173
|
+
const name = base.slice(prefix.length);
|
|
2174
|
+
if (seen.has(name)) continue;
|
|
2175
|
+
const full = path.join(dir, entry);
|
|
2176
|
+
if (!isExecutable(full)) continue;
|
|
2177
|
+
seen.set(name, {
|
|
2178
|
+
name,
|
|
2179
|
+
path: full,
|
|
2180
|
+
source
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
scan(nodeModulesBinDirs(), "node_modules");
|
|
2186
|
+
scan(pathDirs(), "path");
|
|
2187
|
+
return [...seen.values()];
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Resolve the active user identifier, preferring the stored email over the
|
|
2191
|
+
* internal user key.
|
|
2192
|
+
* @param profile - Active profile name, if any
|
|
2193
|
+
* @returns The resolved user (email when known), or undefined when not logged in
|
|
2194
|
+
*/
|
|
2195
|
+
async function resolveActiveUser(profile) {
|
|
2196
|
+
const config = await readPlatformConfig();
|
|
2197
|
+
const user = profile ? config.profiles[profile]?.user : config.current_user;
|
|
2198
|
+
if (!user) return void 0;
|
|
2199
|
+
return config.users[user]?.email ?? user;
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* Build the environment variables injected into a dispatched plugin.
|
|
2203
|
+
* Workspace, user, and token are resolved best-effort: whichever the current
|
|
2204
|
+
* context can provide is injected, and the rest are omitted so auth-free
|
|
2205
|
+
* plugins still run.
|
|
2206
|
+
* @param options - Plugin context options
|
|
2207
|
+
* @returns Environment variables to merge into the child process env
|
|
2208
|
+
*/
|
|
2209
|
+
async function buildPluginEnv(options = {}) {
|
|
2210
|
+
const { profile } = options;
|
|
2211
|
+
let platformConfig;
|
|
2212
|
+
try {
|
|
2213
|
+
platformConfig = await loadPlatformClientConfig({
|
|
2214
|
+
profile,
|
|
2215
|
+
allowMissingProfile: true
|
|
2216
|
+
});
|
|
2217
|
+
} catch {}
|
|
2218
|
+
const env = {
|
|
2219
|
+
TAILOR_PLATFORM_URL: getPlatformBaseUrl(platformConfig),
|
|
2220
|
+
TAILOR_PLATFORM_OAUTH2_CLIENT_ID: getOAuth2ClientId(platformConfig)
|
|
2221
|
+
};
|
|
2222
|
+
const binPath = process.argv[1];
|
|
2223
|
+
if (binPath) env.TAILOR_BIN = binPath;
|
|
2224
|
+
try {
|
|
2225
|
+
const { version } = await readPackageJson();
|
|
2226
|
+
if (version) env.TAILOR_VERSION = version;
|
|
2227
|
+
} catch {}
|
|
2228
|
+
const configPath = loadConfigPath();
|
|
2229
|
+
if (configPath) env.TAILOR_CONFIG_PATH = configPath;
|
|
2230
|
+
try {
|
|
2231
|
+
env.TAILOR_PLATFORM_WORKSPACE_ID = await loadWorkspaceId({ profile });
|
|
2232
|
+
} catch {}
|
|
2233
|
+
try {
|
|
2234
|
+
const user = await resolveActiveUser(profile);
|
|
2235
|
+
if (user) env.TAILOR_PLATFORM_USER = user;
|
|
2236
|
+
} catch {}
|
|
2237
|
+
try {
|
|
2238
|
+
const token = await loadAccessToken({ profile });
|
|
2239
|
+
if (token) env.TAILOR_PLATFORM_TOKEN = token;
|
|
2240
|
+
} catch {}
|
|
2241
|
+
return env;
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Map an exit signal to a conventional exit code (128 + signal number).
|
|
2245
|
+
* @param signal - Signal name
|
|
2246
|
+
* @returns Exit code
|
|
2247
|
+
*/
|
|
2248
|
+
function signalToExitCode(signal) {
|
|
2249
|
+
const num = os.constants.signals[signal];
|
|
2250
|
+
return typeof num === "number" ? 128 + num : 1;
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Build the command + args for spawning a plugin, handling Windows shebang
|
|
2254
|
+
* scripts via `sh` (following the `gh` extension dispatcher).
|
|
2255
|
+
* @param pluginPath - Resolved plugin executable path
|
|
2256
|
+
* @param args - Args to forward to the plugin
|
|
2257
|
+
* @returns Spawn command, args, and whether a shell is required
|
|
2258
|
+
*/
|
|
2259
|
+
function buildSpawnTarget(pluginPath, args) {
|
|
2260
|
+
if (process.platform !== "win32") return {
|
|
2261
|
+
command: pluginPath,
|
|
2262
|
+
args: [...args],
|
|
2263
|
+
shell: false
|
|
2264
|
+
};
|
|
2265
|
+
const ext = path.extname(pluginPath).toLowerCase();
|
|
2266
|
+
if (ext === ".exe" || ext === ".com") return {
|
|
2267
|
+
command: pluginPath,
|
|
2268
|
+
args: [...args],
|
|
2269
|
+
shell: false
|
|
2270
|
+
};
|
|
2271
|
+
if (ext === ".cmd" || ext === ".bat") return {
|
|
2272
|
+
command: pluginPath,
|
|
2273
|
+
args: [...args],
|
|
2274
|
+
shell: true
|
|
2275
|
+
};
|
|
2276
|
+
if (ext === ".ps1") return {
|
|
2277
|
+
command: findExecutableIn(pathDirs(), "pwsh") ?? findExecutableIn(pathDirs(), "powershell") ?? "powershell",
|
|
2278
|
+
args: [
|
|
2279
|
+
"-NoProfile",
|
|
2280
|
+
"-ExecutionPolicy",
|
|
2281
|
+
"Bypass",
|
|
2282
|
+
"-File",
|
|
2283
|
+
pluginPath,
|
|
2284
|
+
...args
|
|
2285
|
+
],
|
|
2286
|
+
shell: false
|
|
2287
|
+
};
|
|
2288
|
+
return {
|
|
2289
|
+
command: findExecutableIn(pathDirs(), "sh") ?? "sh",
|
|
2290
|
+
args: [
|
|
2291
|
+
"-c",
|
|
2292
|
+
"command \"$@\"",
|
|
2293
|
+
"--",
|
|
2294
|
+
pluginPath,
|
|
2295
|
+
...args
|
|
2296
|
+
],
|
|
2297
|
+
shell: false
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
/**
|
|
2301
|
+
* Resolve and execute a plugin, forwarding stdio and propagating its exit code.
|
|
2302
|
+
* @param params - Dispatch parameters
|
|
2303
|
+
* @param params.name - Plugin name (without the `<cli>-` prefix)
|
|
2304
|
+
* @param params.args - Args to forward to the plugin
|
|
2305
|
+
* @param params.cliName - The host CLI name
|
|
2306
|
+
* @param params.profile - Active profile name, if any
|
|
2307
|
+
* @returns The plugin's exit code, or undefined when no matching plugin exists
|
|
2308
|
+
*/
|
|
2309
|
+
async function dispatchPlugin(params) {
|
|
2310
|
+
const slug = [...params.commandPath ?? [], params.name].join("-");
|
|
2311
|
+
const plugin = resolvePlugin(slug, params.cliName);
|
|
2312
|
+
if (!plugin) return;
|
|
2313
|
+
const env = {
|
|
2314
|
+
...process.env,
|
|
2315
|
+
...await buildPluginEnv({ profile: params.profile })
|
|
2316
|
+
};
|
|
2317
|
+
const { command, args, shell } = buildSpawnTarget(plugin.path, params.args);
|
|
2318
|
+
return await new Promise((resolve) => {
|
|
2319
|
+
const child = spawn(command, args, {
|
|
2320
|
+
stdio: "inherit",
|
|
2321
|
+
env,
|
|
2322
|
+
shell
|
|
2323
|
+
});
|
|
2324
|
+
child.on("error", (error) => {
|
|
2325
|
+
logger.error(`Failed to run plugin "${params.cliName}-${slug}": ${error.message}`);
|
|
2326
|
+
resolve(1);
|
|
2327
|
+
});
|
|
2328
|
+
child.on("close", (code, signal) => {
|
|
2329
|
+
if (signal) resolve(signalToExitCode(signal));
|
|
2330
|
+
else resolve(code ?? 0);
|
|
2331
|
+
});
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
//#endregion
|
|
2336
|
+
//#region src/cli/commands/plugin/list.ts
|
|
2337
|
+
const listCommand$5 = defineAppCommand({
|
|
2338
|
+
name: "list",
|
|
2339
|
+
description: "List discovered plugins (executables named `<cli>-<name>` on PATH or node_modules/.bin).",
|
|
2340
|
+
args: z.strictObject({}),
|
|
2341
|
+
run: async () => {
|
|
2342
|
+
const pkg = await readPackageJson();
|
|
2343
|
+
const cliName = Object.keys(pkg.bin ?? {})[0] || "tailor";
|
|
2344
|
+
const builtins = new Set(BUILTIN_COMMAND_NAMES);
|
|
2345
|
+
const plugins = listPlugins(cliName);
|
|
2346
|
+
if (plugins.length === 0) {
|
|
2347
|
+
logger.info(multiline`
|
|
2348
|
+
No plugins found.
|
|
2349
|
+
Install an executable named "${cliName}-<name>" on your PATH or in node_modules/.bin,
|
|
2350
|
+
then run it with "${cliName} <name>".
|
|
2351
|
+
`);
|
|
2352
|
+
if (logger.jsonMode) logger.out([]);
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
const items = plugins.map((plugin) => ({
|
|
2356
|
+
name: plugin.name,
|
|
2357
|
+
source: plugin.source,
|
|
2358
|
+
path: plugin.path,
|
|
2359
|
+
shadowed: builtins.has(plugin.name)
|
|
2360
|
+
}));
|
|
2361
|
+
logger.out(items);
|
|
2362
|
+
for (const item of items) if (item.shadowed) logger.warn(`Plugin "${cliName}-${item.name}" is shadowed by the builtin "${cliName} ${item.name}" command and will not be dispatched.`);
|
|
2363
|
+
}
|
|
2364
|
+
});
|
|
2365
|
+
|
|
2366
|
+
//#endregion
|
|
2367
|
+
//#region src/cli/commands/plugin/index.ts
|
|
2368
|
+
const pluginCommand = defineCommand({
|
|
2369
|
+
name: "plugin",
|
|
2370
|
+
description: "Manage and inspect CLI plugins (beta).",
|
|
2371
|
+
subCommands: { list: listCommand$5 },
|
|
2372
|
+
async run() {
|
|
2373
|
+
await runCommand(listCommand$5, []);
|
|
1903
2374
|
}
|
|
1904
2375
|
});
|
|
1905
2376
|
|
|
@@ -1908,7 +2379,7 @@ const organizationCommand = defineCommand({
|
|
|
1908
2379
|
const createCommand$2 = defineAppCommand({
|
|
1909
2380
|
name: "create",
|
|
1910
2381
|
description: "Create a new profile.",
|
|
1911
|
-
args: z.
|
|
2382
|
+
args: z.strictObject({
|
|
1912
2383
|
name: arg(z.string(), {
|
|
1913
2384
|
positional: true,
|
|
1914
2385
|
description: "Profile name"
|
|
@@ -1926,14 +2397,32 @@ const createCommand$2 = defineAppCommand({
|
|
|
1926
2397
|
alias: "m",
|
|
1927
2398
|
description: "Default machine user name for application-data commands (query, workflow start, function test-run, machineuser token)."
|
|
1928
2399
|
}),
|
|
1929
|
-
"machine-user-override": arg(z.enum(["allow", "deny"]).optional(), { description: "Whether the command line or TAILOR_PLATFORM_MACHINE_USER_NAME may override the profile's machine user. 'deny' requires --machine-user." })
|
|
1930
|
-
|
|
2400
|
+
"machine-user-override": arg(z.enum(["allow", "deny"]).optional(), { description: "Whether the command line or TAILOR_PLATFORM_MACHINE_USER_NAME may override the profile's machine user. 'deny' requires --machine-user." }),
|
|
2401
|
+
"platform-url": arg(z.url().optional(), {
|
|
2402
|
+
description: "Platform API base URL for this profile.",
|
|
2403
|
+
env: "TAILOR_PLATFORM_URL"
|
|
2404
|
+
}),
|
|
2405
|
+
"oauth2-client-id": arg(z.string().optional(), {
|
|
2406
|
+
description: "OAuth2 client ID for logging in to this profile's platform.",
|
|
2407
|
+
env: "TAILOR_PLATFORM_OAUTH2_CLIENT_ID"
|
|
2408
|
+
}),
|
|
2409
|
+
"console-url": arg(z.url().optional(), {
|
|
2410
|
+
description: "Console base URL for this profile.",
|
|
2411
|
+
env: "TAILOR_PLATFORM_CONSOLE_URL"
|
|
2412
|
+
})
|
|
2413
|
+
}),
|
|
1931
2414
|
run: async (args) => {
|
|
1932
2415
|
if (args["machine-user-override"] === "deny" && !args["machine-user"]) throw new Error("--machine-user-override deny requires --machine-user.");
|
|
1933
2416
|
const config = await readPlatformConfig();
|
|
1934
2417
|
if (config.profiles[args.name]) throw new Error(`Profile "${args.name}" already exists.`);
|
|
1935
|
-
const
|
|
1936
|
-
|
|
2418
|
+
const platformConfigInput = {
|
|
2419
|
+
...args["platform-url"] ? { platformUrl: args["platform-url"] } : {},
|
|
2420
|
+
...args["oauth2-client-id"] ? { oauth2ClientId: args["oauth2-client-id"] } : {},
|
|
2421
|
+
...args["console-url"] ? { consoleUrl: args["console-url"] } : {}
|
|
2422
|
+
};
|
|
2423
|
+
const platformConfig = Object.keys(platformConfigInput).length > 0 ? platformConfigInput : void 0;
|
|
2424
|
+
const { accessToken: token, user: resolvedUser } = await fetchLatestToken(config, args.user, platformConfig);
|
|
2425
|
+
const client = await initOperatorClient(token, platformConfig);
|
|
1937
2426
|
if (!(await fetchAll(async (pageToken, maxPageSize) => {
|
|
1938
2427
|
const { workspaces, nextPageToken } = await client.listWorkspaces({
|
|
1939
2428
|
pageToken,
|
|
@@ -1946,7 +2435,10 @@ const createCommand$2 = defineAppCommand({
|
|
|
1946
2435
|
workspace_id: args["workspace-id"],
|
|
1947
2436
|
...args.permission === "read" ? { readonly: true } : {},
|
|
1948
2437
|
...args["machine-user"] ? { machine_user: args["machine-user"] } : {},
|
|
1949
|
-
...args["machine-user-override"] === "deny" ? { machine_user_override: "deny" } : {}
|
|
2438
|
+
...args["machine-user-override"] === "deny" ? { machine_user_override: "deny" } : {},
|
|
2439
|
+
...args["platform-url"] ? { platform_url: args["platform-url"] } : {},
|
|
2440
|
+
...args["oauth2-client-id"] ? { oauth2_client_id: args["oauth2-client-id"] } : {},
|
|
2441
|
+
...args["console-url"] ? { console_url: args["console-url"] } : {}
|
|
1950
2442
|
};
|
|
1951
2443
|
writePlatformConfig(config);
|
|
1952
2444
|
if (!args.json) logger.success(`Profile "${args.name}" created successfully.`);
|
|
@@ -1958,7 +2450,10 @@ const createCommand$2 = defineAppCommand({
|
|
|
1958
2450
|
...args["machine-user"] ? {
|
|
1959
2451
|
machineUser: args["machine-user"],
|
|
1960
2452
|
machineUserOverride: args["machine-user-override"] ?? "allow"
|
|
1961
|
-
} : {}
|
|
2453
|
+
} : {},
|
|
2454
|
+
...args["platform-url"] ? { platformUrl: args["platform-url"] } : {},
|
|
2455
|
+
...args["oauth2-client-id"] ? { oauth2ClientId: args["oauth2-client-id"] } : {},
|
|
2456
|
+
...args["console-url"] ? { consoleUrl: args["console-url"] } : {}
|
|
1962
2457
|
};
|
|
1963
2458
|
logger.out(profileInfo);
|
|
1964
2459
|
}
|
|
@@ -1966,13 +2461,13 @@ const createCommand$2 = defineAppCommand({
|
|
|
1966
2461
|
|
|
1967
2462
|
//#endregion
|
|
1968
2463
|
//#region src/cli/commands/profile/delete.ts
|
|
1969
|
-
const deleteCommand$
|
|
2464
|
+
const deleteCommand$3 = defineAppCommand({
|
|
1970
2465
|
name: "delete",
|
|
1971
2466
|
description: "Delete a profile.",
|
|
1972
|
-
args: z.
|
|
2467
|
+
args: z.strictObject({ name: arg(z.string(), {
|
|
1973
2468
|
positional: true,
|
|
1974
2469
|
description: "Profile name"
|
|
1975
|
-
}) })
|
|
2470
|
+
}) }),
|
|
1976
2471
|
run: async (args) => {
|
|
1977
2472
|
const config = await readPlatformConfig();
|
|
1978
2473
|
if (!config.profiles[args.name]) throw new Error(`Profile "${args.name}" not found.`);
|
|
@@ -1987,7 +2482,7 @@ const deleteCommand$2 = defineAppCommand({
|
|
|
1987
2482
|
const listCommand$4 = defineAppCommand({
|
|
1988
2483
|
name: "list",
|
|
1989
2484
|
description: "List all profiles.",
|
|
1990
|
-
args: z.
|
|
2485
|
+
args: z.strictObject({}),
|
|
1991
2486
|
run: async () => {
|
|
1992
2487
|
const config = await readPlatformConfig();
|
|
1993
2488
|
const jsonOutput = logger.jsonMode;
|
|
@@ -1995,7 +2490,7 @@ const listCommand$4 = defineAppCommand({
|
|
|
1995
2490
|
if (profiles.length === 0) {
|
|
1996
2491
|
logger.info(multiline`
|
|
1997
2492
|
No profiles found.
|
|
1998
|
-
Please create a profile first using 'tailor
|
|
2493
|
+
Please create a profile first using 'tailor profile create' command.
|
|
1999
2494
|
`);
|
|
2000
2495
|
if (jsonOutput) logger.out([]);
|
|
2001
2496
|
return;
|
|
@@ -2010,7 +2505,10 @@ const listCommand$4 = defineAppCommand({
|
|
|
2010
2505
|
...p.machine_user ? {
|
|
2011
2506
|
machineUser: p.machine_user,
|
|
2012
2507
|
machineUserOverride: p.machine_user_override ?? "allow"
|
|
2013
|
-
} : {}
|
|
2508
|
+
} : {},
|
|
2509
|
+
...p.platform_url ? { platformUrl: p.platform_url } : {},
|
|
2510
|
+
...p.oauth2_client_id ? { oauth2ClientId: p.oauth2_client_id } : {},
|
|
2511
|
+
...p.console_url ? { consoleUrl: p.console_url } : {}
|
|
2014
2512
|
};
|
|
2015
2513
|
});
|
|
2016
2514
|
logger.out(profileInfos);
|
|
@@ -2022,7 +2520,7 @@ const listCommand$4 = defineAppCommand({
|
|
|
2022
2520
|
const updateCommand$1 = defineAppCommand({
|
|
2023
2521
|
name: "update",
|
|
2024
2522
|
description: "Update profile properties.",
|
|
2025
|
-
args: z.
|
|
2523
|
+
args: z.strictObject({
|
|
2026
2524
|
name: arg(z.string(), {
|
|
2027
2525
|
positional: true,
|
|
2028
2526
|
description: "Profile name"
|
|
@@ -2040,13 +2538,16 @@ const updateCommand$1 = defineAppCommand({
|
|
|
2040
2538
|
alias: "m",
|
|
2041
2539
|
description: "Default machine user name for application-data commands (query, workflow start, function test-run, machineuser token). Pass an empty string to clear."
|
|
2042
2540
|
}),
|
|
2043
|
-
"machine-user-override": arg(z.enum(["allow", "deny"]).optional(), { description: "Whether the command line or TAILOR_PLATFORM_MACHINE_USER_NAME may override the profile's machine user. 'deny' requires --machine-user; 'allow' lifts the restriction." })
|
|
2044
|
-
|
|
2541
|
+
"machine-user-override": arg(z.enum(["allow", "deny"]).optional(), { description: "Whether the command line or TAILOR_PLATFORM_MACHINE_USER_NAME may override the profile's machine user. 'deny' requires --machine-user; 'allow' lifts the restriction." }),
|
|
2542
|
+
"platform-url": arg(z.union([z.url(), z.literal("")]).optional(), { description: "Platform API base URL for this profile. Pass an empty string to clear." }),
|
|
2543
|
+
"oauth2-client-id": arg(z.string().optional(), { description: "OAuth2 client ID for logging in to this profile's platform. Pass an empty string to clear." }),
|
|
2544
|
+
"console-url": arg(z.union([z.url(), z.literal("")]).optional(), { description: "Console base URL for this profile. Pass an empty string to clear." })
|
|
2545
|
+
}),
|
|
2045
2546
|
run: async (args) => {
|
|
2046
2547
|
const config = await readPlatformConfig();
|
|
2047
2548
|
const profile = config.profiles[args.name];
|
|
2048
2549
|
if (!profile) throw new Error(`Profile "${args.name}" not found.`);
|
|
2049
|
-
if (!args.user && !args["workspace-id"] && args.permission === void 0 && args["machine-user"] === void 0 && args["machine-user-override"] === void 0) throw new Error("Please provide at least one property to update.");
|
|
2550
|
+
if (!args.user && !args["workspace-id"] && args.permission === void 0 && args["machine-user"] === void 0 && args["machine-user-override"] === void 0 && args["platform-url"] === void 0 && args["oauth2-client-id"] === void 0 && args["console-url"] === void 0) throw new Error("Please provide at least one property to update.");
|
|
2050
2551
|
const oldUser = profile.user;
|
|
2051
2552
|
const newUser = args.user || oldUser;
|
|
2052
2553
|
const oldWorkspaceId = profile.workspace_id;
|
|
@@ -2054,14 +2555,24 @@ const updateCommand$1 = defineAppCommand({
|
|
|
2054
2555
|
let resolvedUser = newUser;
|
|
2055
2556
|
const finalMachineUser = args["machine-user"] === "" ? void 0 : args["machine-user"] ?? profile.machine_user;
|
|
2056
2557
|
const finalOverride = args["machine-user-override"] === "allow" ? void 0 : args["machine-user-override"] ?? profile.machine_user_override;
|
|
2558
|
+
const finalPlatformUrl = args["platform-url"] === "" ? void 0 : args["platform-url"] ?? profile.platform_url;
|
|
2559
|
+
const finalOAuth2ClientId = args["oauth2-client-id"] === "" ? void 0 : args["oauth2-client-id"] ?? profile.oauth2_client_id;
|
|
2560
|
+
const finalConsoleUrl = args["console-url"] === "" ? void 0 : args["console-url"] ?? profile.console_url;
|
|
2561
|
+
const finalPlatformConfigInput = {
|
|
2562
|
+
...finalPlatformUrl ? { platformUrl: finalPlatformUrl } : {},
|
|
2563
|
+
...finalOAuth2ClientId ? { oauth2ClientId: finalOAuth2ClientId } : {},
|
|
2564
|
+
...finalConsoleUrl ? { consoleUrl: finalConsoleUrl } : {}
|
|
2565
|
+
};
|
|
2566
|
+
const finalPlatformConfig = Object.keys(finalPlatformConfigInput).length > 0 ? finalPlatformConfigInput : void 0;
|
|
2567
|
+
const tokenLookupPlatformConfig = args["platform-url"] === "" ? platformConfigFromProfile(profile) : finalPlatformConfig;
|
|
2057
2568
|
if ((args["machine-user"] !== void 0 || args["machine-user-override"] !== void 0) && finalOverride === "deny" && !finalMachineUser) {
|
|
2058
2569
|
if (args["machine-user-override"] === "deny") throw new Error("--machine-user-override deny requires --machine-user.");
|
|
2059
2570
|
throw new Error(`Cannot clear the machine user while machine-user-override is "deny". Also pass --machine-user-override allow.`);
|
|
2060
2571
|
}
|
|
2061
|
-
if (args.user !== void 0 || args["workspace-id"] !== void 0) {
|
|
2062
|
-
const refreshed = await fetchLatestToken(config, newUser);
|
|
2572
|
+
if (args.user !== void 0 || args["workspace-id"] !== void 0 || args["platform-url"] !== void 0) {
|
|
2573
|
+
const refreshed = await fetchLatestToken(config, newUser, tokenLookupPlatformConfig);
|
|
2063
2574
|
resolvedUser = refreshed.user;
|
|
2064
|
-
const client = await initOperatorClient(refreshed.accessToken);
|
|
2575
|
+
const client = await initOperatorClient(refreshed.accessToken, finalPlatformConfig);
|
|
2065
2576
|
if (!(await fetchAll(async (pageToken, maxPageSize) => {
|
|
2066
2577
|
const { workspaces, nextPageToken } = await client.listWorkspaces({
|
|
2067
2578
|
pageToken,
|
|
@@ -2078,6 +2589,12 @@ const updateCommand$1 = defineAppCommand({
|
|
|
2078
2589
|
else profile.machine_user = args["machine-user"];
|
|
2079
2590
|
if (args["machine-user-override"] === "deny") profile.machine_user_override = "deny";
|
|
2080
2591
|
else if (args["machine-user-override"] === "allow") delete profile.machine_user_override;
|
|
2592
|
+
if (args["platform-url"] !== void 0) if (args["platform-url"] === "") delete profile.platform_url;
|
|
2593
|
+
else profile.platform_url = args["platform-url"];
|
|
2594
|
+
if (args["oauth2-client-id"] !== void 0) if (args["oauth2-client-id"] === "") delete profile.oauth2_client_id;
|
|
2595
|
+
else profile.oauth2_client_id = args["oauth2-client-id"];
|
|
2596
|
+
if (args["console-url"] !== void 0) if (args["console-url"] === "") delete profile.console_url;
|
|
2597
|
+
else profile.console_url = args["console-url"];
|
|
2081
2598
|
writePlatformConfig(config);
|
|
2082
2599
|
if (!args.json) logger.success(`Profile "${args.name}" updated successfully`);
|
|
2083
2600
|
const profileInfo = {
|
|
@@ -2088,7 +2605,10 @@ const updateCommand$1 = defineAppCommand({
|
|
|
2088
2605
|
...profile.machine_user ? {
|
|
2089
2606
|
machineUser: profile.machine_user,
|
|
2090
2607
|
machineUserOverride: profile.machine_user_override ?? "allow"
|
|
2091
|
-
} : {}
|
|
2608
|
+
} : {},
|
|
2609
|
+
...profile.platform_url ? { platformUrl: profile.platform_url } : {},
|
|
2610
|
+
...profile.oauth2_client_id ? { oauth2ClientId: profile.oauth2_client_id } : {},
|
|
2611
|
+
...profile.console_url ? { consoleUrl: profile.console_url } : {}
|
|
2092
2612
|
};
|
|
2093
2613
|
logger.out(profileInfo);
|
|
2094
2614
|
}
|
|
@@ -2101,7 +2621,7 @@ const profileCommand = defineCommand({
|
|
|
2101
2621
|
description: "Manage workspace profiles (user + workspace combinations).",
|
|
2102
2622
|
subCommands: {
|
|
2103
2623
|
create: createCommand$2,
|
|
2104
|
-
delete: deleteCommand$
|
|
2624
|
+
delete: deleteCommand$3,
|
|
2105
2625
|
list: listCommand$4,
|
|
2106
2626
|
update: updateCommand$1
|
|
2107
2627
|
},
|
|
@@ -2196,11 +2716,11 @@ async function releaseVaultOwnership(params) {
|
|
|
2196
2716
|
const createSecretCommand = defineAppCommand({
|
|
2197
2717
|
name: "create",
|
|
2198
2718
|
description: "Create a secret in a vault.",
|
|
2199
|
-
args: z.
|
|
2719
|
+
args: z.strictObject({
|
|
2200
2720
|
...workspaceArgs,
|
|
2201
2721
|
...secretValueArgs,
|
|
2202
2722
|
...confirmationArgs
|
|
2203
|
-
})
|
|
2723
|
+
}),
|
|
2204
2724
|
run: async (args) => {
|
|
2205
2725
|
await assertWritable({ profile: args.profile });
|
|
2206
2726
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
@@ -2246,11 +2766,11 @@ const createSecretCommand = defineAppCommand({
|
|
|
2246
2766
|
const deleteSecretCommand = defineAppCommand({
|
|
2247
2767
|
name: "delete",
|
|
2248
2768
|
description: "Delete a secret in a vault.",
|
|
2249
|
-
args: z.
|
|
2769
|
+
args: z.strictObject({
|
|
2250
2770
|
...workspaceArgs,
|
|
2251
2771
|
...secretIdentifyArgs,
|
|
2252
2772
|
...confirmationArgs
|
|
2253
|
-
})
|
|
2773
|
+
}),
|
|
2254
2774
|
run: async (args) => {
|
|
2255
2775
|
await assertWritable({ profile: args.profile });
|
|
2256
2776
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
@@ -2322,11 +2842,11 @@ async function secretList(options) {
|
|
|
2322
2842
|
const listSecretCommand = defineAppCommand({
|
|
2323
2843
|
name: "list",
|
|
2324
2844
|
description: "List all secrets in a vault.",
|
|
2325
|
-
args: z.
|
|
2845
|
+
args: z.strictObject({
|
|
2326
2846
|
...workspaceArgs,
|
|
2327
2847
|
...vaultArgs,
|
|
2328
2848
|
...paginationArgs()
|
|
2329
|
-
})
|
|
2849
|
+
}),
|
|
2330
2850
|
run: async (args) => {
|
|
2331
2851
|
try {
|
|
2332
2852
|
const secrets = await secretList({
|
|
@@ -2349,11 +2869,11 @@ const listSecretCommand = defineAppCommand({
|
|
|
2349
2869
|
const updateSecretCommand = defineAppCommand({
|
|
2350
2870
|
name: "update",
|
|
2351
2871
|
description: "Update a secret in a vault.",
|
|
2352
|
-
args: z.
|
|
2872
|
+
args: z.strictObject({
|
|
2353
2873
|
...workspaceArgs,
|
|
2354
2874
|
...secretValueArgs,
|
|
2355
2875
|
...confirmationArgs
|
|
2356
|
-
})
|
|
2876
|
+
}),
|
|
2357
2877
|
run: async (args) => {
|
|
2358
2878
|
await assertWritable({ profile: args.profile });
|
|
2359
2879
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
@@ -2403,10 +2923,10 @@ const nameArgs = { name: arg(z.string(), {
|
|
|
2403
2923
|
const createCommand$1 = defineAppCommand({
|
|
2404
2924
|
name: "create",
|
|
2405
2925
|
description: "Create a new Secret Manager vault.",
|
|
2406
|
-
args: z.
|
|
2926
|
+
args: z.strictObject({
|
|
2407
2927
|
...workspaceArgs,
|
|
2408
2928
|
...nameArgs
|
|
2409
|
-
})
|
|
2929
|
+
}),
|
|
2410
2930
|
run: async (args) => {
|
|
2411
2931
|
await assertWritable({ profile: args.profile });
|
|
2412
2932
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
@@ -2429,14 +2949,14 @@ const createCommand$1 = defineAppCommand({
|
|
|
2429
2949
|
|
|
2430
2950
|
//#endregion
|
|
2431
2951
|
//#region src/cli/commands/secret/vault/delete.ts
|
|
2432
|
-
const deleteCommand$
|
|
2952
|
+
const deleteCommand$2 = defineAppCommand({
|
|
2433
2953
|
name: "delete",
|
|
2434
2954
|
description: "Delete a Secret Manager vault.",
|
|
2435
|
-
args: z.
|
|
2955
|
+
args: z.strictObject({
|
|
2436
2956
|
...workspaceArgs,
|
|
2437
2957
|
...nameArgs,
|
|
2438
2958
|
...confirmationArgs
|
|
2439
|
-
})
|
|
2959
|
+
}),
|
|
2440
2960
|
run: async (args) => {
|
|
2441
2961
|
await assertWritable({ profile: args.profile });
|
|
2442
2962
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
@@ -2503,10 +3023,10 @@ async function vaultList(options) {
|
|
|
2503
3023
|
const listCommand$3 = defineAppCommand({
|
|
2504
3024
|
name: "list",
|
|
2505
3025
|
description: "List all Secret Manager vaults in the workspace.",
|
|
2506
|
-
args: z.
|
|
3026
|
+
args: z.strictObject({
|
|
2507
3027
|
...workspaceArgs,
|
|
2508
3028
|
...paginationArgs()
|
|
2509
|
-
})
|
|
3029
|
+
}),
|
|
2510
3030
|
run: async (args) => {
|
|
2511
3031
|
const vaults = await vaultList({
|
|
2512
3032
|
workspaceId: args["workspace-id"],
|
|
@@ -2525,7 +3045,7 @@ const vaultCommand = defineCommand({
|
|
|
2525
3045
|
description: "Manage Secret Manager vaults.",
|
|
2526
3046
|
subCommands: {
|
|
2527
3047
|
create: createCommand$1,
|
|
2528
|
-
delete: deleteCommand$
|
|
3048
|
+
delete: deleteCommand$2,
|
|
2529
3049
|
list: listCommand$3
|
|
2530
3050
|
},
|
|
2531
3051
|
async run() {
|
|
@@ -2551,7 +3071,7 @@ const secretCommand = defineCommand({
|
|
|
2551
3071
|
});
|
|
2552
3072
|
|
|
2553
3073
|
//#endregion
|
|
2554
|
-
//#region src/cli/commands/setup/
|
|
3074
|
+
//#region src/cli/commands/setup/git.ts
|
|
2555
3075
|
const defaultGitRunner = (args, cwd) => {
|
|
2556
3076
|
const result = spawnSync("git", args, {
|
|
2557
3077
|
cwd,
|
|
@@ -2582,11 +3102,11 @@ function detectDefaultBranch(cwd, run = defaultGitRunner) {
|
|
|
2582
3102
|
}
|
|
2583
3103
|
|
|
2584
3104
|
//#endregion
|
|
2585
|
-
//#region src/cli/commands/setup/
|
|
3105
|
+
//#region src/cli/commands/setup/lock.ts
|
|
2586
3106
|
/** Current lock schema version. Bumped only on breaking lock-format changes. */
|
|
2587
3107
|
const LOCK_VERSION = 1;
|
|
2588
3108
|
/** Lock file path, relative to the repository root. */
|
|
2589
|
-
const LOCK_FILENAME = ".github/tailor
|
|
3109
|
+
const LOCK_FILENAME = ".github/tailor.lock";
|
|
2590
3110
|
/**
|
|
2591
3111
|
* Compute the lock content hash for a rendered workflow file.
|
|
2592
3112
|
* @param content - File content to hash
|
|
@@ -2618,11 +3138,11 @@ function readLock(outputDir) {
|
|
|
2618
3138
|
try {
|
|
2619
3139
|
parsed = JSON.parse(fs$1.readFileSync(file, "utf-8"));
|
|
2620
3140
|
} catch (cause) {
|
|
2621
|
-
throw new Error(`${LOCK_FILENAME} is not valid JSON. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor
|
|
3141
|
+
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 });
|
|
2622
3142
|
}
|
|
2623
|
-
if (typeof parsed.version !== "number") throw new Error(`${LOCK_FILENAME} has no valid 'version' field. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor
|
|
3143
|
+
if (typeof parsed.version !== "number") throw new Error(`${LOCK_FILENAME} has no valid 'version' field. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor.lock) and re-run setup.`);
|
|
2624
3144
|
if (parsed.version > 1) throw new Error(`${LOCK_FILENAME} was written by a newer SDK (lock version ${String(parsed.version)}). Update @tailor-platform/sdk to continue (e.g. pnpm update @tailor-platform/sdk).`);
|
|
2625
|
-
if (!Array.isArray(parsed.targets)) throw new Error(`${LOCK_FILENAME} has no valid 'targets' array. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor
|
|
3145
|
+
if (!Array.isArray(parsed.targets)) throw new Error(`${LOCK_FILENAME} has no valid 'targets' array. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor.lock) and re-run setup.`);
|
|
2626
3146
|
return parsed;
|
|
2627
3147
|
}
|
|
2628
3148
|
/**
|
|
@@ -2648,66 +3168,36 @@ function findTarget(lock, kind, workspaceName) {
|
|
|
2648
3168
|
}
|
|
2649
3169
|
|
|
2650
3170
|
//#endregion
|
|
2651
|
-
//#region src/cli/commands/setup/
|
|
2652
|
-
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n with:\n fetch-depth: 0\n - id: tailor-merge-base\n if: github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork\n env:\n BASE_REF: ${{ github.base_ref }}\n run: |\n git config user.name \"github-actions[bot]\"\n git config user.email \"41898282+github-actions[bot]@users.noreply.github.com\"\n git fetch origin \"$BASE_REF:refs/remotes/origin/$BASE_REF\"\n git merge --no-edit \"origin/$BASE_REF\"\n # __SETUP_STEPS__\n - id: tailor-generate\n # __WORKING_DIRECTORY__\n run: __PM_EXEC__ tailor-sdk generate\n - id: tailor-generate-check\n run: |\n git add -A\n if ! git diff --cached --quiet; then\n git --no-pager diff --cached --stat\n echo \"::error::Generated files are out of date. Run 'tailor-sdk generate' locally and commit the result.\"\n exit 1\n fi\n - id: tailor-mask-credentials\n # Fork PRs cannot read secrets; the checks above still run for them.\n if: >-\n (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) &&\n vars.TAILOR_PLATFORM_WORKSPACE_ID != ''\n run: |\n echo \"::add-mask::$CLIENT_ID\"\n echo \"::add-mask::$CLIENT_SECRET\"\n env:\n CLIENT_ID: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n CLIENT_SECRET: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n - id: tailor-login\n if: >-\n (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) &&\n vars.TAILOR_PLATFORM_WORKSPACE_ID != ''\n # __WORKING_DIRECTORY__\n run: __PM_EXEC__ tailor-sdk login --machine-user\n env:\n TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n - id: tailor-plan\n if: >-\n (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) &&\n vars.TAILOR_PLATFORM_WORKSPACE_ID != ''\n # __WORKING_DIRECTORY__\n run: |\n if [ -z \"$TAILOR_PLATFORM_WORKSPACE_ID\" ]; then\n echo \"exit-code=\" >> \"$GITHUB_OUTPUT\"\n exit 0\n fi\n\n set +e\n OUTPUT=$(__PM_EXEC__ tailor-sdk deploy --dry-run --yes 2>&1)\n EXIT_CODE=$?\n set -e\n\n MAX_OUTPUT=60000\n if [ \"${#OUTPUT}\" -gt \"$MAX_OUTPUT\" ]; then\n LOG_STOP_TOKEN=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)\n echo \"::group::Full Tailor Platform plan output\"\n echo \"::stop-commands::$LOG_STOP_TOKEN\"\n printf '%s\\n' \"$OUTPUT\"\n echo \"::$LOG_STOP_TOKEN::\"\n echo \"::endgroup::\"\n OUTPUT_TAIL=\"${OUTPUT: -$MAX_OUTPUT}\"\n OUTPUT=$(printf '%s\\n\\n%s' \"[output truncated to the last $MAX_OUTPUT characters - see the workflow logs for the full plan]\" \"$OUTPUT_TAIL\")\n fi\n\n EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)\n {\n echo \"exit-code=$EXIT_CODE\"\n echo \"output<<$EOF\"\n echo \"$OUTPUT\"\n echo \"$EOF\"\n } >> \"$GITHUB_OUTPUT\"\n env:\n TAILOR_PLATFORM_WORKSPACE_ID: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n - id: tailor-plan-summary\n if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork\n run: |\n if [ -n \"$LABEL\" ]; then\n KEY=\"$LABEL\"\n elif [ -n \"$WORKSPACE_ID\" ]; then\n KEY=\"$WORKSPACE_ID\"\n else\n KEY=\"workspace\"\n fi\n\n if [ -z \"$WORKSPACE_ID\" ]; then\n {\n echo \"## Tailor Platform Plan ($KEY)\"\n echo \"\"\n echo \"Workspace is not provisioned yet. Set TAILOR_PLATFORM_WORKSPACE_ID after provisioning the workspace.\"\n } >> \"$GITHUB_STEP_SUMMARY\"\n exit 0\n fi\n\n if [ \"$DRY_RUN_EXIT_CODE\" = \"0\" ]; then\n STATUS=\"PASS\"\n else\n STATUS=\"FAIL\"\n fi\n\n {\n echo \"## $STATUS Tailor Platform Plan ($KEY)\"\n echo \"\"\n echo \"<details>\"\n echo \"<summary>Plan output (exit code: $DRY_RUN_EXIT_CODE)</summary>\"\n echo \"\"\n echo '```'\n echo \"$DRY_RUN_OUTPUT\"\n echo '```'\n echo \"\"\n echo \"</details>\"\n } >> \"$GITHUB_STEP_SUMMARY\"\n env:\n WORKSPACE_ID: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n LABEL: __WORKSPACE_NAME__\n DRY_RUN_OUTPUT: ${{ steps.tailor-plan.outputs.output }}\n DRY_RUN_EXIT_CODE: ${{ steps.tailor-plan.outputs.exit-code }}\n - id: tailor-plan-comment\n if: github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork\n uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9\n env:\n WORKSPACE_ID: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n LABEL: __WORKSPACE_NAME__\n DRY_RUN_OUTPUT: ${{ steps.tailor-plan.outputs.output }}\n DRY_RUN_EXIT_CODE: ${{ steps.tailor-plan.outputs.exit-code }}\n with:\n github-token: ${{ secrets.GITHUB_TOKEN }}\n script: |\n const workspaceId = process.env.WORKSPACE_ID;\n const label = process.env.LABEL;\n const key = label || workspaceId || 'workspace';\n const marker = `<!-- tailor-plan: ${key} -->`;\n\n let body;\n if (!workspaceId) {\n body = [\n marker,\n `## Tailor Platform Plan (${key})`,\n '',\n 'Workspace is not provisioned yet. Set TAILOR_PLATFORM_WORKSPACE_ID after provisioning the workspace.',\n '',\n ].join('\\n');\n } else {\n const exitCode = process.env.DRY_RUN_EXIT_CODE;\n let output = process.env.DRY_RUN_OUTPUT;\n const MAX_OUTPUT = 60000;\n if (output.length > MAX_OUTPUT) {\n const note = `[output truncated to the last ${MAX_OUTPUT} characters - see the job's step summary for the full plan]\\n\\n`;\n output = note + output.slice(output.length - MAX_OUTPUT);\n }\n const status = exitCode === '0' ? 'PASS' : 'FAIL';\n body = [\n marker,\n `## ${status} Tailor Platform Plan (${key})`,\n '',\n '<details>',\n `<summary>Plan output (exit code: ${exitCode})</summary>`,\n '',\n '```',\n output,\n '```',\n '',\n '</details>',\n '',\n ].join('\\n');\n }\n\n const comments = await github.paginate(github.rest.issues.listComments, {\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: context.issue.number,\n per_page: 100,\n });\n const existingComment = comments.find(comment =>\n comment.body?.includes(marker) &&\n comment.user?.type === 'Bot' &&\n comment.user?.login === 'github-actions[bot]'\n );\n if (existingComment) {\n await github.rest.issues.updateComment({\n owner: context.repo.owner,\n repo: context.repo.repo,\n comment_id: existingComment.id,\n body,\n });\n } else {\n await github.rest.issues.createComment({\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: context.issue.number,\n body,\n });\n }\n - id: tailor-plan-fail\n if: >-\n (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) &&\n vars.TAILOR_PLATFORM_WORKSPACE_ID != ''\n run: |\n if [ \"$DRY_RUN_EXIT_CODE\" != \"0\" ]; then\n echo \"::error::Plan dry-run failed with exit code $DRY_RUN_EXIT_CODE\"\n exit \"$DRY_RUN_EXIT_CODE\"\n fi\n env:\n DRY_RUN_EXIT_CODE: ${{ steps.tailor-plan.outputs.exit-code }}\n # __PLAN_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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n # __SETUP_STEPS__\n - id: tailor-validate-workspace\n env:\n WORKSPACE_ID: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n run: |\n if [ -z \"$WORKSPACE_ID\" ]; then\n echo \"::error::Workspace is not provisioned: TAILOR_PLATFORM_WORKSPACE_ID is empty. Provision the workspace and set the variable.\"\n exit 1\n fi\n - id: tailor-mask-credentials\n run: |\n echo \"::add-mask::$CLIENT_ID\"\n echo \"::add-mask::$CLIENT_SECRET\"\n env:\n CLIENT_ID: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n CLIENT_SECRET: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n - id: tailor-login\n # __WORKING_DIRECTORY__\n run: __PM_EXEC__ tailor-sdk login --machine-user\n env:\n TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n - id: tailor-generate\n # __WORKING_DIRECTORY__\n run: __PM_EXEC__ tailor-sdk generate\n env:\n TAILOR_PLATFORM_WORKSPACE_ID: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n - id: tailor-deploy\n # __WORKING_DIRECTORY__\n run: __PM_EXEC__ tailor-sdk deploy --yes\n env:\n TAILOR_PLATFORM_WORKSPACE_ID: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n";
|
|
3171
|
+
//#region src/cli/commands/setup/action.yml
|
|
3172
|
+
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 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 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 uses: tailor-platform/actions/deploy@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\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@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\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";
|
|
2653
3173
|
|
|
2654
3174
|
//#endregion
|
|
2655
|
-
//#region src/cli/commands/setup/
|
|
2656
|
-
var setup_bun_default = "- id: tailor-setup-bun\n uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0\n- id: tailor-install\n run: bun install --frozen-lockfile\n";
|
|
3175
|
+
//#region src/cli/commands/setup/branch.workflow.yml
|
|
3176
|
+
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";
|
|
2657
3177
|
|
|
2658
3178
|
//#endregion
|
|
2659
|
-
//#region src/cli/commands/setup/
|
|
2660
|
-
var
|
|
3179
|
+
//#region src/cli/commands/setup/coordinate.workflow.yml
|
|
3180
|
+
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@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\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";
|
|
2661
3181
|
|
|
2662
3182
|
//#endregion
|
|
2663
|
-
//#region src/cli/commands/setup/
|
|
2664
|
-
var
|
|
3183
|
+
//#region src/cli/commands/setup/preview.workflow.yml
|
|
3184
|
+
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@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 - id: tailor-preview-deploy\n uses: tailor-platform/actions/preview-deploy@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\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@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\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@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-preview-cleanup\n uses: tailor-platform/actions/preview-cleanup@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\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";
|
|
2665
3185
|
|
|
2666
3186
|
//#endregion
|
|
2667
|
-
//#region src/cli/commands/setup/
|
|
2668
|
-
var
|
|
3187
|
+
//#region src/cli/commands/setup/tag.workflow.yml
|
|
3188
|
+
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@e74173d4bdd931d036ad359cf5f7729fc6aa7067 # v1.5.1\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@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 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\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@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";
|
|
2669
3189
|
|
|
2670
3190
|
//#endregion
|
|
2671
|
-
//#region src/cli/commands/setup/
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
//#endregion
|
|
2675
|
-
//#region src/cli/commands/setup/github/templates.ts
|
|
2676
|
-
const setupStepIds = {
|
|
2677
|
-
pnpm: [
|
|
2678
|
-
"tailor-setup-pnpm",
|
|
2679
|
-
"tailor-setup-node",
|
|
2680
|
-
"tailor-install"
|
|
2681
|
-
],
|
|
2682
|
-
yarn: ["tailor-setup-node", "tailor-install"],
|
|
2683
|
-
npm: ["tailor-setup-node", "tailor-install"],
|
|
2684
|
-
bun: ["tailor-setup-bun", "tailor-install"]
|
|
2685
|
-
};
|
|
2686
|
-
const setupSteps = {
|
|
2687
|
-
pnpm: setup_pnpm_default,
|
|
2688
|
-
yarn: setup_yarn_default,
|
|
2689
|
-
npm: setup_npm_default,
|
|
2690
|
-
bun: setup_bun_default
|
|
2691
|
-
};
|
|
2692
|
-
const execPrefix = {
|
|
2693
|
-
npm: "npx",
|
|
2694
|
-
pnpm: "pnpm exec",
|
|
2695
|
-
yarn: "yarn",
|
|
2696
|
-
bun: "bunx"
|
|
2697
|
-
};
|
|
2698
|
-
const HEADER = `# Generated by \`tailor-sdk setup github\` — managed by the Tailor SDK.
|
|
3191
|
+
//#region src/cli/commands/setup/templates.ts
|
|
3192
|
+
const HEADER = `# Generated by \`tailor setup\` — managed by the Tailor SDK.
|
|
2699
3193
|
#
|
|
2700
3194
|
# - Jobs and steps whose id starts with \`tailor-\` are managed by the SDK.
|
|
2701
3195
|
# Do not edit or rename them.
|
|
2702
|
-
# - State is tracked in .github/tailor
|
|
2703
|
-
# - Re-running \`tailor
|
|
3196
|
+
# - State is tracked in .github/tailor.lock (machine-owned: commit it, never edit it).
|
|
3197
|
+
# - Re-running \`tailor setup\` regenerates this file. If you have
|
|
2704
3198
|
# edited it by hand, regeneration stops and asks for --force (which discards
|
|
2705
3199
|
# your edits), so prefer keeping customizations in your own jobs/steps and
|
|
2706
3200
|
# re-running setup after SDK updates.`;
|
|
2707
|
-
function indentSnippet(snippet, spaces) {
|
|
2708
|
-
const indent = " ".repeat(spaces);
|
|
2709
|
-
return snippet.trimEnd().split("\n").map((line) => line ? indent + line : line).join("\n");
|
|
2710
|
-
}
|
|
2711
3201
|
/**
|
|
2712
3202
|
* Detect the package manager used in a project directory by checking for lockfiles.
|
|
2713
3203
|
* @param dir - Project directory to inspect
|
|
@@ -2735,33 +3225,43 @@ function applyCommon(content, params) {
|
|
|
2735
3225
|
const { workingDirectory, environment, packageManager } = params;
|
|
2736
3226
|
let out = line(content, "HEADER", HEADER);
|
|
2737
3227
|
out = line(out, "WORKING_DIRECTORY", workingDirectory ? `working-directory: ${workingDirectory}` : void 0);
|
|
2738
|
-
out
|
|
2739
|
-
return out.replaceAll("__WORKSPACE_NAME__", () => params.workspaceName).replaceAll("__ENVIRONMENT__", () => environment).replaceAll("__PM_EXEC__", () => execPrefix[packageManager]);
|
|
2740
|
-
}
|
|
2741
|
-
function setupIds(job, packageManager) {
|
|
2742
|
-
return setupStepIds[packageManager].map((id) => `${job}/${id}`);
|
|
3228
|
+
return out.replaceAll("__WORKSPACE_NAME__", () => params.workspaceName).replaceAll("__ENVIRONMENT__", () => environment).replaceAll("__PACKAGE_MANAGER__", () => packageManager).replaceAll("__APP_DIR__", () => workingDirectory ?? ".");
|
|
2743
3229
|
}
|
|
2744
3230
|
/**
|
|
2745
3231
|
* Render the branch-target deploy workflow.
|
|
2746
|
-
*
|
|
2747
|
-
* When `plan` is false the plan job, the pull_request trigger, and the
|
|
2748
|
-
* workflow_dispatch dry-run input are all removed (a plan-less workflow has no
|
|
2749
|
-
* meaningful dry-run), and the deploy job runs unconditionally.
|
|
2750
3232
|
* @param params - Workspace and rendering configuration
|
|
2751
3233
|
* @returns Rendered YAML and the list of managed job/step ids
|
|
2752
3234
|
*/
|
|
2753
3235
|
function renderBranchWorkflow(params) {
|
|
2754
|
-
const { branch
|
|
3236
|
+
const { branch } = params;
|
|
3237
|
+
const seedValidate = params.seedValidate ?? false;
|
|
3238
|
+
const migrationDriftCheck = params.migrationDriftCheck ?? false;
|
|
3239
|
+
const erdPreview = params.erdPreview;
|
|
2755
3240
|
let out = branch_workflow_default;
|
|
2756
|
-
out = block(out, "PLAN_JOB",
|
|
2757
|
-
out = block(out, "
|
|
2758
|
-
out = block(out, "
|
|
2759
|
-
out =
|
|
3241
|
+
out = block(out, "PLAN_JOB", true);
|
|
3242
|
+
out = block(out, "ERD_PREVIEW_JOB", erdPreview !== null);
|
|
3243
|
+
out = block(out, "ERD_PREVIEW_COMMENT_JOB", erdPreview !== null);
|
|
3244
|
+
out = block(out, "PULL_REQUEST", true);
|
|
3245
|
+
out = block(out, "DISPATCH_INPUTS", true);
|
|
3246
|
+
out = block(out, "SEED_VALIDATE", seedValidate);
|
|
3247
|
+
out = block(out, "MIGRATION_DRIFT_CHECK", migrationDriftCheck);
|
|
3248
|
+
out = block(out, "SEED_DATA", false);
|
|
3249
|
+
out = line(out, "DEPLOY_IF", `if: >-\n github.event_name == 'push' ||\n (github.event_name == 'workflow_dispatch' && !inputs['dry-run'])`);
|
|
2760
3250
|
out = line(out, "PATHS", params.workingDirectory ? `paths: ["${params.workingDirectory}/**"]` : void 0);
|
|
2761
3251
|
out = applyCommon(out, params).replaceAll("__BRANCH__", () => branch);
|
|
2762
|
-
const generatedIds = [
|
|
2763
|
-
|
|
2764
|
-
|
|
3252
|
+
const generatedIds = [
|
|
3253
|
+
"tailor-plan",
|
|
3254
|
+
"tailor-plan/tailor-checkout",
|
|
3255
|
+
"tailor-plan/tailor-setup",
|
|
3256
|
+
"tailor-plan/tailor-install",
|
|
3257
|
+
"tailor-plan/tailor-generate-check"
|
|
3258
|
+
];
|
|
3259
|
+
if (seedValidate) generatedIds.push("tailor-plan/tailor-seed-validate");
|
|
3260
|
+
generatedIds.push("tailor-plan/tailor-drift-check");
|
|
3261
|
+
if (migrationDriftCheck) generatedIds.push("tailor-plan/tailor-migration-drift-check");
|
|
3262
|
+
generatedIds.push("tailor-plan/tailor-plan");
|
|
3263
|
+
if (erdPreview) generatedIds.push("tailor-erd-preview-matrix", "tailor-erd-preview-matrix/tailor-checkout", "tailor-erd-preview-matrix/tailor-checkout-base", "tailor-erd-preview-matrix/tailor-erd-preview-matrix", "tailor-erd-preview", "tailor-erd-preview/tailor-checkout", "tailor-erd-preview/tailor-setup", "tailor-erd-preview/tailor-checkout-base", "tailor-erd-preview/tailor-detect-base-package-manager", "tailor-erd-preview/tailor-setup-base-pnpm", "tailor-erd-preview/tailor-setup-base-node", "tailor-erd-preview/tailor-setup-base-yarn", "tailor-erd-preview/tailor-setup-base-bun", "tailor-erd-preview/tailor-install-base", "tailor-erd-preview/tailor-restore-head-setup", "tailor-erd-preview/tailor-build-erd-preview", "tailor-erd-preview/tailor-upload-erd-viewer", "tailor-erd-preview-comment", "tailor-erd-preview-comment/tailor-comment-erd-preview");
|
|
3264
|
+
generatedIds.push("tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup", "tailor-deploy/tailor-install", "tailor-deploy/tailor-apply", "tailor-deploy/tailor-notify");
|
|
2765
3265
|
return {
|
|
2766
3266
|
content: out,
|
|
2767
3267
|
generatedIds
|
|
@@ -2776,32 +3276,237 @@ function renderBranchWorkflow(params) {
|
|
|
2776
3276
|
* @returns Rendered YAML and the list of managed job/step ids
|
|
2777
3277
|
*/
|
|
2778
3278
|
function renderTagWorkflow(params) {
|
|
2779
|
-
const { tagPattern, branch
|
|
3279
|
+
const { tagPattern, branch } = params;
|
|
2780
3280
|
const hasGuard = branch !== void 0;
|
|
3281
|
+
const seedValidate = params.seedValidate ?? false;
|
|
3282
|
+
const migrationDriftCheck = params.migrationDriftCheck ?? false;
|
|
2781
3283
|
let out = tag_workflow_default;
|
|
2782
3284
|
out = block(out, "TAG_GUARD_JOB", hasGuard);
|
|
3285
|
+
out = block(out, "SEED_VALIDATE", seedValidate);
|
|
3286
|
+
out = block(out, "MIGRATION_DRIFT_CHECK", migrationDriftCheck);
|
|
3287
|
+
out = block(out, "SEED_DATA", false);
|
|
2783
3288
|
out = line(out, "PLAN_NEEDS", hasGuard ? "needs: tailor-tag-guard" : void 0);
|
|
2784
3289
|
out = line(out, "PLAN_IF", hasGuard ? `if: >-\n github.event_name == 'workflow_dispatch' ||\n needs.tailor-tag-guard.outputs.on-branch == 'true'` : void 0);
|
|
2785
3290
|
out = applyCommon(out, params).replaceAll("__TAG_PATTERN__", () => tagPattern);
|
|
2786
3291
|
if (hasGuard) out = out.replaceAll("__BRANCH__", () => branch);
|
|
2787
3292
|
const generatedIds = [];
|
|
2788
3293
|
if (hasGuard) generatedIds.push("tailor-tag-guard", "tailor-tag-guard/tailor-checkout", "tailor-tag-guard/tailor-tag-guard");
|
|
2789
|
-
generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout",
|
|
3294
|
+
generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout", "tailor-plan/tailor-setup", "tailor-plan/tailor-install", "tailor-plan/tailor-generate-check");
|
|
3295
|
+
if (seedValidate) generatedIds.push("tailor-plan/tailor-seed-validate");
|
|
3296
|
+
generatedIds.push("tailor-plan/tailor-drift-check");
|
|
3297
|
+
if (migrationDriftCheck) generatedIds.push("tailor-plan/tailor-migration-drift-check");
|
|
3298
|
+
generatedIds.push("tailor-plan/tailor-plan", "tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup", "tailor-deploy/tailor-install", "tailor-deploy/tailor-apply", "tailor-deploy/tailor-notify");
|
|
2790
3299
|
return {
|
|
2791
3300
|
content: out,
|
|
2792
3301
|
generatedIds
|
|
2793
3302
|
};
|
|
2794
3303
|
}
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
3304
|
+
/**
|
|
3305
|
+
* Render the preview workflow (PR open/sync/close triggers).
|
|
3306
|
+
*
|
|
3307
|
+
* The deploy job runs on opened/synchronize/reopened events; the cleanup job
|
|
3308
|
+
* runs on the closed event. Both jobs run in the same workflow so PR number
|
|
3309
|
+
* context is always available.
|
|
3310
|
+
* @param params - Workspace and rendering configuration
|
|
3311
|
+
* @returns Rendered YAML and the list of managed job/step ids
|
|
3312
|
+
*/
|
|
3313
|
+
function renderPreviewWorkflow(params) {
|
|
3314
|
+
const { branch } = params;
|
|
3315
|
+
const requirePreviewLabel = params.requirePreviewLabel ?? false;
|
|
3316
|
+
let out = preview_workflow_default;
|
|
3317
|
+
out = line(out, "PATHS", params.workingDirectory ? `paths: ["${params.workingDirectory}/**"]` : void 0);
|
|
3318
|
+
out = line(out, "PR_TYPES", requirePreviewLabel ? "types: [labeled, synchronize, reopened, closed]" : "types: [opened, synchronize, reopened, closed]");
|
|
3319
|
+
out = line(out, "DEPLOY_IF", requirePreviewLabel ? `if: >-\n contains(github.event.pull_request.labels.*.name, 'tailor:preview') &&\n github.event.action != 'closed' &&\n !github.event.pull_request.draft &&\n !github.event.pull_request.head.repo.fork` : `if: >-\n github.event.action != 'closed' &&\n !github.event.pull_request.draft &&\n !github.event.pull_request.head.repo.fork`);
|
|
3320
|
+
out = line(out, "CLEANUP_IF", `if: >-\n github.event.action == 'closed' &&\n !github.event.pull_request.head.repo.fork`);
|
|
3321
|
+
out = applyCommon(out, params).replaceAll("__BRANCH__", () => branch).replaceAll("__REGION__", () => params.region);
|
|
3322
|
+
return {
|
|
3323
|
+
content: out,
|
|
3324
|
+
generatedIds: [
|
|
3325
|
+
"tailor-preview-deploy",
|
|
3326
|
+
"tailor-preview-deploy/tailor-checkout",
|
|
3327
|
+
"tailor-preview-deploy/tailor-setup",
|
|
3328
|
+
"tailor-preview-deploy/tailor-install",
|
|
3329
|
+
"tailor-preview-deploy/tailor-generate-check",
|
|
3330
|
+
"tailor-preview-deploy/tailor-preview-deploy",
|
|
3331
|
+
"tailor-preview-deploy/tailor-preview-comment",
|
|
3332
|
+
"tailor-preview-cleanup",
|
|
3333
|
+
"tailor-preview-cleanup/tailor-checkout",
|
|
3334
|
+
"tailor-preview-cleanup/tailor-setup",
|
|
3335
|
+
"tailor-preview-cleanup/tailor-install",
|
|
3336
|
+
"tailor-preview-cleanup/tailor-preview-cleanup"
|
|
3337
|
+
]
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
/**
|
|
3341
|
+
* Render the per-app composite action.
|
|
3342
|
+
*
|
|
3343
|
+
* The action contains only the deploy step and a failure notification.
|
|
3344
|
+
* Checkout, setup, and install are the caller's responsibility (coordinator workflow).
|
|
3345
|
+
* @param params - Workspace and rendering configuration
|
|
3346
|
+
* @returns Rendered YAML and the list of managed step ids
|
|
3347
|
+
*/
|
|
3348
|
+
function renderActionWorkflow(params) {
|
|
3349
|
+
let out = action_default;
|
|
3350
|
+
out = line(out, "HEADER", HEADER);
|
|
3351
|
+
out = block(out, "STATIC_WEBSITE_BUILD", params.hasStaticWebsites ?? false);
|
|
3352
|
+
out = out.replaceAll("__WORKSPACE_NAME__", () => params.workspaceName);
|
|
3353
|
+
return {
|
|
3354
|
+
content: out,
|
|
3355
|
+
generatedIds: ["tailor-apply", "tailor-notify"]
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
const ACTIONS_SHA = "e74173d4bdd931d036ad359cf5f7729fc6aa7067";
|
|
3359
|
+
/**
|
|
3360
|
+
* Render the coordinator workflow that orchestrates per-app composite actions.
|
|
3361
|
+
*
|
|
3362
|
+
* Generates per-app plan steps (generate-check, drift-check, plan) and per-app
|
|
3363
|
+
* deploy steps (calling each app's composite action at .github/actions/tailor-<name>).
|
|
3364
|
+
* Checkout, setup, and install are done once per job using the local tailor-setup action.
|
|
3365
|
+
* @param params - Coordinator and app configuration
|
|
3366
|
+
* @returns Rendered YAML and the list of managed job/step ids
|
|
3367
|
+
*/
|
|
3368
|
+
function renderCoordinateWorkflow(params) {
|
|
3369
|
+
const { coordinatorName, kind, apps, environment, packageManager } = params;
|
|
3370
|
+
const isTag = kind === "tag";
|
|
3371
|
+
const branch = params.branch;
|
|
3372
|
+
const tagPattern = params.tagPattern ?? "v*";
|
|
3373
|
+
let out = coordinate_workflow_default;
|
|
3374
|
+
out = line(out, "HEADER", HEADER);
|
|
3375
|
+
out = block(out, "PULL_REQUEST", !isTag);
|
|
3376
|
+
out = block(out, "PUSH_BRANCHES", !isTag);
|
|
3377
|
+
out = block(out, "PUSH_TAGS", isTag);
|
|
3378
|
+
out = block(out, "TAG_GUARD_JOB", isTag && branch !== void 0);
|
|
3379
|
+
if (isTag && branch !== void 0) {
|
|
3380
|
+
out = line(out, "PLAN_NEEDS", "needs: tailor-tag-guard");
|
|
3381
|
+
out = line(out, "PLAN_IF", `if: >-\n github.event_name == 'workflow_dispatch' ||\n needs.tailor-tag-guard.outputs.on-branch == 'true'`);
|
|
3382
|
+
} else if (isTag) {
|
|
3383
|
+
out = line(out, "PLAN_NEEDS", void 0);
|
|
3384
|
+
out = line(out, "PLAN_IF", void 0);
|
|
3385
|
+
} else {
|
|
3386
|
+
out = line(out, "PLAN_NEEDS", void 0);
|
|
3387
|
+
out = line(out, "PLAN_IF", `if: >-\n github.event_name == 'pull_request' ||\n (github.event_name == 'workflow_dispatch' && inputs['dry-run'])`);
|
|
3388
|
+
}
|
|
3389
|
+
if (isTag) {
|
|
3390
|
+
out = line(out, "DEPLOY_NEEDS", "needs: tailor-plan");
|
|
3391
|
+
out = line(out, "DEPLOY_IF", `if: \${{ !(github.event_name == 'workflow_dispatch' && inputs['dry-run']) }}`);
|
|
3392
|
+
out = line(out, "DEPLOY_ENVIRONMENT", `environment: ${environment}`);
|
|
3393
|
+
} else {
|
|
3394
|
+
out = line(out, "DEPLOY_NEEDS", void 0);
|
|
3395
|
+
out = line(out, "DEPLOY_IF", `if: >-\n github.event_name == 'push' ||\n (github.event_name == 'workflow_dispatch' && !inputs['dry-run'])`);
|
|
3396
|
+
out = line(out, "DEPLOY_ENVIRONMENT", `environment: ${environment}`);
|
|
3397
|
+
}
|
|
3398
|
+
const driftCheckStep = [
|
|
3399
|
+
`- id: tailor-drift-check`,
|
|
3400
|
+
` uses: tailor-platform/actions/drift-check@${ACTIONS_SHA} # v1.5.1`,
|
|
3401
|
+
` with:`,
|
|
3402
|
+
` package-manager: ${packageManager}`
|
|
3403
|
+
].join("\n");
|
|
3404
|
+
const perAppPlanSteps = apps.flatMap((app) => {
|
|
3405
|
+
const wd = app.dir !== "." ? app.dir : void 0;
|
|
3406
|
+
const wdLine = wd ? `\n working-directory: ${wd}` : "";
|
|
3407
|
+
return [
|
|
3408
|
+
`- id: tailor-generate-check-${app.name}`,
|
|
3409
|
+
` uses: tailor-platform/actions/generate-check@${ACTIONS_SHA} # v1.5.1`,
|
|
3410
|
+
` with:`,
|
|
3411
|
+
` package-manager: ${packageManager}${wdLine}`,
|
|
3412
|
+
`- id: tailor-plan-${app.name}`,
|
|
3413
|
+
` if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork`,
|
|
3414
|
+
` uses: tailor-platform/actions/plan@${ACTIONS_SHA} # v1.5.1`,
|
|
3415
|
+
` with:`,
|
|
3416
|
+
` workspace-id: \${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}`,
|
|
3417
|
+
` package-manager: ${packageManager}${wdLine}`,
|
|
3418
|
+
` label: ${coordinatorName}/${app.name}`,
|
|
3419
|
+
` platform-client-id: \${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}`,
|
|
3420
|
+
` platform-client-secret: \${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}`,
|
|
3421
|
+
` github-token: \${{ secrets.GITHUB_TOKEN }}`
|
|
3422
|
+
];
|
|
3423
|
+
}).join("\n");
|
|
3424
|
+
out = line(out, "PER_APP_PLAN_STEPS", `${driftCheckStep}\n${perAppPlanSteps}`);
|
|
3425
|
+
const deploySteps = apps.flatMap((app) => [
|
|
3426
|
+
`- id: tailor-deploy-${app.name}`,
|
|
3427
|
+
` uses: ./.github/actions/tailor-${app.name}`,
|
|
3428
|
+
` with:`,
|
|
3429
|
+
` workspace-id: \${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}`,
|
|
3430
|
+
` name: ${app.name}`,
|
|
3431
|
+
` working-directory: ${app.dir}`,
|
|
3432
|
+
` package-manager: ${packageManager}`,
|
|
3433
|
+
` platform-client-id: \${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}`,
|
|
3434
|
+
` platform-client-secret: \${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}`,
|
|
3435
|
+
` slack-token: \${{ secrets.TAILOR_SLACK_BOT_TOKEN }}`,
|
|
3436
|
+
` slack-channel-id: \${{ vars.TAILOR_SLACK_CHANNEL_ID }}`,
|
|
3437
|
+
` # editable: api-url:`
|
|
3438
|
+
]).join("\n");
|
|
3439
|
+
out = line(out, "PER_APP_DEPLOY_STEPS", deploySteps);
|
|
3440
|
+
out = out.replaceAll("__WORKSPACE_NAME__", () => coordinatorName);
|
|
3441
|
+
out = out.replaceAll("__ENVIRONMENT__", () => environment);
|
|
3442
|
+
if (branch !== void 0) out = out.replaceAll("__BRANCH__", () => branch);
|
|
3443
|
+
if (isTag) out = out.replaceAll("__TAG_PATTERN__", () => tagPattern);
|
|
3444
|
+
const generatedIds = [];
|
|
3445
|
+
if (isTag && branch !== void 0) generatedIds.push("tailor-tag-guard", "tailor-tag-guard/tailor-checkout", "tailor-tag-guard/tailor-tag-guard");
|
|
3446
|
+
generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout", "tailor-plan/tailor-setup", "tailor-plan/tailor-drift-check");
|
|
3447
|
+
for (const app of apps) generatedIds.push(`tailor-plan/tailor-generate-check-${app.name}`, `tailor-plan/tailor-plan-${app.name}`);
|
|
3448
|
+
generatedIds.push("tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup");
|
|
3449
|
+
for (const app of apps) generatedIds.push(`tailor-deploy/tailor-deploy-${app.name}`);
|
|
3450
|
+
return {
|
|
3451
|
+
content: out,
|
|
3452
|
+
generatedIds
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
/**
|
|
3456
|
+
* Render the content of the user-owned .github/actions/tailor-setup/action.yml local action.
|
|
3457
|
+
*
|
|
3458
|
+
* This file is generated once by `setup coordinate` and never overwritten.
|
|
3459
|
+
* It wraps the setup and install actions with the package manager baked in.
|
|
3460
|
+
* @param params - Package manager configuration
|
|
3461
|
+
* @returns File content string
|
|
3462
|
+
*/
|
|
3463
|
+
function renderTailorSetupAction(params) {
|
|
3464
|
+
const { packageManager } = params;
|
|
3465
|
+
return [
|
|
3466
|
+
`# This file is user-owned. Generated once by \`tailor setup coordinate\` and never overwritten.`,
|
|
3467
|
+
`# Customize freely: add pre/post steps, change install flags, etc.`,
|
|
3468
|
+
`name: Tailor Setup`,
|
|
3469
|
+
`description: Install dependencies and run tailor setup for composite action callers.`,
|
|
3470
|
+
`runs:`,
|
|
3471
|
+
` using: composite`,
|
|
3472
|
+
` steps:`,
|
|
3473
|
+
` - uses: tailor-platform/actions/setup@${ACTIONS_SHA} # v1.5.1`,
|
|
3474
|
+
` with:`,
|
|
3475
|
+
` package-manager: ${packageManager}`,
|
|
3476
|
+
` # Add custom steps here (e.g. private registry authentication)`,
|
|
3477
|
+
` - uses: tailor-platform/actions/install@${ACTIONS_SHA} # v1.5.1`,
|
|
3478
|
+
` with:`,
|
|
3479
|
+
` package-manager: ${packageManager}`,
|
|
3480
|
+
` # editable: install-command:`,
|
|
3481
|
+
``
|
|
3482
|
+
].join("\n");
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
//#endregion
|
|
3486
|
+
//#region src/cli/commands/setup/generate.ts
|
|
3487
|
+
async function defaultLoadConfigName(configPath) {
|
|
3488
|
+
const { config } = await loadConfig(configPath);
|
|
3489
|
+
return config.name;
|
|
3490
|
+
}
|
|
3491
|
+
async function defaultLoadErdNamespaces$1(configPath) {
|
|
3492
|
+
const { config } = await loadConfig(configPath);
|
|
3493
|
+
return extractOwnedNamespaces(config);
|
|
3494
|
+
}
|
|
3495
|
+
async function defaultLoadHasMigrations$1(configPath) {
|
|
3496
|
+
const { config } = await loadConfig(configPath);
|
|
3497
|
+
return getNamespacesWithMigrations(config, path.dirname(configPath)).length > 0;
|
|
3498
|
+
}
|
|
3499
|
+
async function defaultLoadHasSeeds$1(configPath) {
|
|
3500
|
+
const { plugins } = await loadConfig(configPath);
|
|
3501
|
+
return plugins.some((p) => p.id === "@tailor-platform/seed");
|
|
3502
|
+
}
|
|
3503
|
+
async function defaultLoadHasStaticWebsites$1(configPath) {
|
|
3504
|
+
const { config } = await loadConfig(configPath);
|
|
3505
|
+
return (config.staticWebsites?.length ?? 0) > 0;
|
|
3506
|
+
}
|
|
3507
|
+
const WORKSPACE_NAME_RE = /^[a-z0-9-]+$/;
|
|
3508
|
+
function validateWorkspaceName(name) {
|
|
3509
|
+
if (name.length < 3 || name.length > 63 || !WORKSPACE_NAME_RE.test(name) || name.startsWith("-") || name.endsWith("-")) 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.`);
|
|
2805
3510
|
}
|
|
2806
3511
|
const BRANCH_RE = /^[A-Za-z0-9._/-]+$/;
|
|
2807
3512
|
const TAG_PATTERN_RE = /^[A-Za-z0-9._/*?![\]-]+$/;
|
|
@@ -2815,11 +3520,19 @@ const ENVIRONMENT_RE = /^[A-Za-z0-9._/-]+$/;
|
|
|
2815
3520
|
function validateEnvironment(environment) {
|
|
2816
3521
|
if (!ENVIRONMENT_RE.test(environment)) throw new Error(`Invalid environment name "${environment}". Only letters, numbers, ".", "_", "/", and "-" are supported.`);
|
|
2817
3522
|
}
|
|
3523
|
+
const REGION_RE = /^[A-Za-z0-9._-]+$/;
|
|
3524
|
+
function validateRegion(region) {
|
|
3525
|
+
if (!REGION_RE.test(region)) throw new Error(`Invalid region "${region}". Only letters, numbers, ".", "_", and "-" are supported.`);
|
|
3526
|
+
}
|
|
2818
3527
|
const DIR_RE = /^[A-Za-z0-9._/-]+$/;
|
|
2819
3528
|
function validateDir(dir) {
|
|
2820
3529
|
if (!DIR_RE.test(dir)) throw new Error(`Invalid --dir "${dir}". Only letters, numbers, ".", "_", "/", and "-" are supported.`);
|
|
2821
3530
|
}
|
|
2822
|
-
|
|
3531
|
+
const ERD_NAMESPACE_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
3532
|
+
function validateErdNamespaces(namespaces) {
|
|
3533
|
+
for (const namespace of namespaces) if (!ERD_NAMESPACE_RE.test(namespace)) throw new Error(`TailorDB namespace "${namespace}" cannot be used in --erd-preview. Only letters, numbers, '.', '_', and '-' are supported, and the name must start with a letter or number.`);
|
|
3534
|
+
}
|
|
3535
|
+
function escapesRoot$1(rel) {
|
|
2823
3536
|
return rel === ".." || rel.startsWith(`..${path.sep}`) || rel.startsWith("../") || path.isAbsolute(rel);
|
|
2824
3537
|
}
|
|
2825
3538
|
/**
|
|
@@ -2835,11 +3548,11 @@ function escapesRoot(rel) {
|
|
|
2835
3548
|
function resolveConfigPath(outputDir, dir) {
|
|
2836
3549
|
const appDir = path.resolve(outputDir, dir);
|
|
2837
3550
|
const rel = path.relative(outputDir, appDir);
|
|
2838
|
-
if (path.isAbsolute(dir) || escapesRoot(rel)) throw new Error(`--dir must be a relative path inside the repository (got "${dir}").`);
|
|
3551
|
+
if (path.isAbsolute(dir) || escapesRoot$1(rel)) throw new Error(`--dir must be a relative path inside the repository (got "${dir}").`);
|
|
2839
3552
|
if (fs$1.existsSync(appDir)) {
|
|
2840
3553
|
const realAppDir = path.normalize(fs$1.realpathSync(appDir));
|
|
2841
3554
|
const realOutputDir = path.normalize(fs$1.realpathSync(outputDir));
|
|
2842
|
-
if (escapesRoot(path.relative(realOutputDir, realAppDir))) throw new Error(`--dir must resolve to a directory inside the repository (got "${dir}", which links outside it).`);
|
|
3555
|
+
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).`);
|
|
2843
3556
|
}
|
|
2844
3557
|
const configPath = path.join(appDir, "tailor.config.ts");
|
|
2845
3558
|
if (!fs$1.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.`);
|
|
@@ -2851,53 +3564,102 @@ function resolveConfigPath(outputDir, dir) {
|
|
|
2851
3564
|
* @returns Resolved target metadata and rendered content
|
|
2852
3565
|
*/
|
|
2853
3566
|
async function resolve$1(options) {
|
|
2854
|
-
if (options.tag && !options.plan) throw new Error("--no-plan cannot be combined with --tag (tag targets always run plan before deploy). Drop --no-plan or use a branch target.");
|
|
2855
3567
|
const dir = options.dir.replaceAll("\\", "/").replace(/\/{2,}/g, "/").replace(/^\.\//, "").replace(/\/$/, "") || ".";
|
|
2856
3568
|
validateDir(dir);
|
|
2857
3569
|
const workingDirectory = dir !== "." ? dir : void 0;
|
|
2858
3570
|
const configPath = resolveConfigPath(options.outputDir, dir);
|
|
2859
3571
|
const loadName = options.loadConfigName ?? defaultLoadConfigName;
|
|
2860
3572
|
const workspaceName = options.workspaceName ?? await loadName(configPath);
|
|
2861
|
-
if (!workspaceName) throw new Error("Could not determine the workspace name. Pass --
|
|
3573
|
+
if (!workspaceName) throw new Error("Could not determine the workspace name. Pass --name, or set 'name' in tailor.config.ts.");
|
|
2862
3574
|
validateWorkspaceName(workspaceName);
|
|
2863
|
-
const kind = options
|
|
3575
|
+
const { kind } = options;
|
|
2864
3576
|
const packageManager = detectPackageManager(options.outputDir);
|
|
2865
3577
|
const environment = options.environment ?? workspaceName;
|
|
2866
3578
|
validateEnvironment(environment);
|
|
2867
3579
|
if (kind === "tag") validateTagPattern(options.tagPattern);
|
|
2868
3580
|
let branch = null;
|
|
3581
|
+
let branchAutoDetected = false;
|
|
2869
3582
|
let render;
|
|
3583
|
+
let erdNamespaces = [];
|
|
3584
|
+
let hasMigrations = false;
|
|
3585
|
+
let hasSeeds = false;
|
|
3586
|
+
let hasStaticWebsites = false;
|
|
3587
|
+
const loadHasMigrations = options.loadHasMigrations ?? defaultLoadHasMigrations$1;
|
|
3588
|
+
const loadHasSeeds = options.loadHasSeeds ?? defaultLoadHasSeeds$1;
|
|
3589
|
+
const loadHasStaticWebsites = options.loadHasStaticWebsites ?? defaultLoadHasStaticWebsites$1;
|
|
2870
3590
|
if (kind === "branch") {
|
|
3591
|
+
if (options.erdPreview) {
|
|
3592
|
+
erdNamespaces = await (options.loadErdNamespaces ?? defaultLoadErdNamespaces$1)(configPath);
|
|
3593
|
+
if (erdNamespaces.length === 0) throw new Error("No TailorDB namespaces found for --erd-preview. Define owned db namespaces in tailor.config.ts.");
|
|
3594
|
+
validateErdNamespaces(erdNamespaces);
|
|
3595
|
+
}
|
|
3596
|
+
branchAutoDetected = options.branch === void 0;
|
|
2871
3597
|
branch = options.branch ?? detectDefaultBranch(options.outputDir, options.gitRunner);
|
|
2872
3598
|
validateBranch(branch);
|
|
3599
|
+
hasMigrations = await loadHasMigrations(configPath);
|
|
3600
|
+
hasSeeds = await loadHasSeeds(configPath);
|
|
2873
3601
|
render = renderBranchWorkflow({
|
|
2874
3602
|
workspaceName,
|
|
2875
3603
|
branch,
|
|
2876
3604
|
workingDirectory,
|
|
2877
3605
|
environment,
|
|
2878
3606
|
packageManager,
|
|
2879
|
-
|
|
3607
|
+
erdPreview: options.erdPreview ? { namespaces: erdNamespaces } : null,
|
|
3608
|
+
migrationDriftCheck: hasMigrations,
|
|
3609
|
+
seedValidate: hasSeeds
|
|
2880
3610
|
});
|
|
2881
|
-
} else {
|
|
3611
|
+
} else if (kind === "tag") {
|
|
2882
3612
|
branch = options.branch ?? null;
|
|
2883
3613
|
if (branch !== null) validateBranch(branch);
|
|
3614
|
+
hasMigrations = await loadHasMigrations(configPath);
|
|
3615
|
+
hasSeeds = await loadHasSeeds(configPath);
|
|
2884
3616
|
render = renderTagWorkflow({
|
|
2885
3617
|
workspaceName,
|
|
2886
3618
|
tagPattern: options.tagPattern,
|
|
2887
3619
|
branch: options.branch,
|
|
2888
3620
|
workingDirectory,
|
|
2889
3621
|
environment,
|
|
2890
|
-
packageManager
|
|
3622
|
+
packageManager,
|
|
3623
|
+
migrationDriftCheck: hasMigrations,
|
|
3624
|
+
seedValidate: hasSeeds
|
|
3625
|
+
});
|
|
3626
|
+
} else if (kind === "preview") {
|
|
3627
|
+
branchAutoDetected = options.branch === void 0;
|
|
3628
|
+
branch = options.branch ?? detectDefaultBranch(options.outputDir, options.gitRunner);
|
|
3629
|
+
validateBranch(branch);
|
|
3630
|
+
validateRegion(options.region);
|
|
3631
|
+
render = renderPreviewWorkflow({
|
|
3632
|
+
workspaceName,
|
|
3633
|
+
branch,
|
|
3634
|
+
workingDirectory,
|
|
3635
|
+
environment,
|
|
3636
|
+
packageManager,
|
|
3637
|
+
region: options.region,
|
|
3638
|
+
requirePreviewLabel: options.requirePreviewLabel ?? false
|
|
3639
|
+
});
|
|
3640
|
+
} else {
|
|
3641
|
+
hasStaticWebsites = await loadHasStaticWebsites(configPath);
|
|
3642
|
+
render = renderActionWorkflow({
|
|
3643
|
+
workspaceName,
|
|
3644
|
+
workingDirectory,
|
|
3645
|
+
hasStaticWebsites
|
|
2891
3646
|
});
|
|
2892
3647
|
}
|
|
2893
|
-
const file = `.github/workflows/tailor-${workspaceName}.yml`;
|
|
3648
|
+
const file = kind === "action" ? `.github/actions/tailor-${workspaceName}/action.yml` : `.github/workflows/tailor-${workspaceName}${kind === "tag" ? "-tag" : kind === "preview" ? "-preview" : ""}.yml`;
|
|
2894
3649
|
const inputs = {
|
|
2895
|
-
branch,
|
|
3650
|
+
branch: kind === "action" ? null : branch,
|
|
3651
|
+
branchAutoDetected: kind === "branch" || kind === "preview" ? branchAutoDetected : void 0,
|
|
2896
3652
|
tagPattern: kind === "tag" ? options.tagPattern : null,
|
|
2897
3653
|
environment,
|
|
2898
3654
|
dir,
|
|
2899
3655
|
packageManager,
|
|
2900
|
-
|
|
3656
|
+
region: kind === "preview" ? options.region : void 0,
|
|
3657
|
+
requirePreviewLabel: kind === "preview" ? options.requirePreviewLabel ?? false : void 0,
|
|
3658
|
+
erdPreview: kind === "branch" ? options.erdPreview : false,
|
|
3659
|
+
erdNamespaces: kind === "branch" && options.erdPreview ? erdNamespaces : void 0,
|
|
3660
|
+
migrationDriftCheck: kind === "branch" || kind === "tag" ? hasMigrations : void 0,
|
|
3661
|
+
seedValidate: kind === "branch" || kind === "tag" ? hasSeeds : void 0,
|
|
3662
|
+
hasStaticWebsites: kind === "action" ? hasStaticWebsites : void 0
|
|
2901
3663
|
};
|
|
2902
3664
|
return {
|
|
2903
3665
|
kind,
|
|
@@ -2905,12 +3667,47 @@ async function resolve$1(options) {
|
|
|
2905
3667
|
branch,
|
|
2906
3668
|
environment,
|
|
2907
3669
|
packageManager,
|
|
3670
|
+
erdNamespaces,
|
|
2908
3671
|
render,
|
|
2909
3672
|
inputs,
|
|
2910
3673
|
file,
|
|
2911
3674
|
configPath
|
|
2912
3675
|
};
|
|
2913
3676
|
}
|
|
3677
|
+
const BUILD_SITE_RUN_RE = /([ \t]*- id: build-site\n[ \t]+shell: bash\n[ \t]+run: \|)([\s\S]*?)(\n[ \t]*- |\n*$)/;
|
|
3678
|
+
/**
|
|
3679
|
+
* Strip the mutable run: body of the build-site step before hashing.
|
|
3680
|
+
* When hasStaticWebsites is true the step is present; users are expected to
|
|
3681
|
+
* replace the placeholder command with their actual build command. We only
|
|
3682
|
+
* hash the structural parts (step id, shell declaration, run: key) so that
|
|
3683
|
+
* editing the build command does not look like unintended drift.
|
|
3684
|
+
* When hasStaticWebsites is false the step is absent, so the regex is a no-op.
|
|
3685
|
+
* @param content - Raw action workflow content
|
|
3686
|
+
* @returns Normalised content with the build-site run body replaced by a placeholder
|
|
3687
|
+
*/
|
|
3688
|
+
function normalizeActionContent(content) {
|
|
3689
|
+
return content.replace(BUILD_SITE_RUN_RE, (_, header, _body, tail) => `${header}\n true${tail}`);
|
|
3690
|
+
}
|
|
3691
|
+
/**
|
|
3692
|
+
* Extract the run: body of the build-site step from action content.
|
|
3693
|
+
* Returns null when the step is absent (hasStaticWebsites is false).
|
|
3694
|
+
* @param content - Action workflow content
|
|
3695
|
+
* @returns The captured run body string (includes leading newline), or null
|
|
3696
|
+
*/
|
|
3697
|
+
function extractBuildSiteRunBody(content) {
|
|
3698
|
+
const m = BUILD_SITE_RUN_RE.exec(content);
|
|
3699
|
+
return m ? m[2] ?? null : null;
|
|
3700
|
+
}
|
|
3701
|
+
/**
|
|
3702
|
+
* Replace the run: body of the build-site step in action content.
|
|
3703
|
+
* No-op when the step is absent.
|
|
3704
|
+
* @param content - Target action workflow content
|
|
3705
|
+
* @param body - Run body to inject (includes leading newline and indentation)
|
|
3706
|
+
* @returns Content with the run body replaced
|
|
3707
|
+
*/
|
|
3708
|
+
function injectBuildSiteRunBody(content, body) {
|
|
3709
|
+
return content.replace(BUILD_SITE_RUN_RE, (_, header, _placeholder, tail) => `${header}${body}${tail}`);
|
|
3710
|
+
}
|
|
2914
3711
|
/**
|
|
2915
3712
|
* Decide how to reconcile a target with the on-disk file and lock state.
|
|
2916
3713
|
* @param obj - Decision inputs
|
|
@@ -2918,10 +3715,11 @@ async function resolve$1(options) {
|
|
|
2918
3715
|
* @param obj.fileExists - Whether the workflow file is present on disk
|
|
2919
3716
|
* @param obj.currentContent - On-disk content when present
|
|
2920
3717
|
* @param obj.force - Whether --force was passed
|
|
3718
|
+
* @param obj.normalize - Optional content normaliser applied before hashing
|
|
2921
3719
|
* @returns The reconciliation action
|
|
2922
3720
|
*/
|
|
2923
3721
|
function decideAction(obj) {
|
|
2924
|
-
const { existing, fileExists, currentContent, force } = obj;
|
|
3722
|
+
const { existing, fileExists, currentContent, force, normalize } = obj;
|
|
2925
3723
|
if (!existing) {
|
|
2926
3724
|
if (!fileExists) return { action: "create" };
|
|
2927
3725
|
if (force) return { action: "regenerate" };
|
|
@@ -2931,7 +3729,9 @@ function decideAction(obj) {
|
|
|
2931
3729
|
};
|
|
2932
3730
|
}
|
|
2933
3731
|
if (!fileExists) return { action: "restore" };
|
|
2934
|
-
if (currentContent !== null
|
|
3732
|
+
if (currentContent !== null) {
|
|
3733
|
+
if (hashContent(normalize ? normalize(currentContent) : currentContent) === existing.contentHash) return { action: "regenerate" };
|
|
3734
|
+
}
|
|
2935
3735
|
if (force) return { action: "regenerate" };
|
|
2936
3736
|
return {
|
|
2937
3737
|
action: "conflict",
|
|
@@ -2949,7 +3749,7 @@ function decideAction(obj) {
|
|
|
2949
3749
|
function assertNoKindCollision(obj) {
|
|
2950
3750
|
const { lock, kind, workspaceName, file } = obj;
|
|
2951
3751
|
const collision = lock?.targets.find((t) => t.file === file && !(t.kind === kind && t.workspaceName === workspaceName));
|
|
2952
|
-
if (collision) throw new Error(`A ${collision.kind} target already owns ${file}, which conflicts with this ${kind} target. Pass a different name with --
|
|
3752
|
+
if (collision) throw new Error(`A ${collision.kind} target already owns ${file}, which conflicts with this ${kind} target. Pass a different name with --name to generate a separate workflow.`);
|
|
2953
3753
|
}
|
|
2954
3754
|
/**
|
|
2955
3755
|
* Print next-step guidance after generating workflow files.
|
|
@@ -2967,21 +3767,20 @@ function printNextSteps(obj) {
|
|
|
2967
3767
|
logger.log(` gh secret set TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET --env ${environment}`);
|
|
2968
3768
|
logger.newline();
|
|
2969
3769
|
logger.log(`2. Provision the workspace and set its id as the TAILOR_PLATFORM_WORKSPACE_ID variable on the "${environment}" environment:`);
|
|
2970
|
-
logger.log(" tailor
|
|
3770
|
+
logger.log(" tailor workspace create # if it does not exist yet; copy the id");
|
|
2971
3771
|
logger.log(` gh variable set TAILOR_PLATFORM_WORKSPACE_ID --env ${environment}`);
|
|
2972
3772
|
logger.newline();
|
|
2973
3773
|
logger.log("3. Commit the generated files:");
|
|
2974
3774
|
logger.log(" - .github/workflows/tailor-*.yml");
|
|
2975
|
-
logger.log(" - .github/tailor
|
|
3775
|
+
logger.log(" - .github/tailor.lock");
|
|
2976
3776
|
if (idInjected) logger.log(" - tailor.config.ts (app id was added)");
|
|
2977
3777
|
}
|
|
2978
3778
|
/**
|
|
2979
|
-
* Generate
|
|
2980
|
-
* with the lock file.
|
|
3779
|
+
* Generate a deploy target workflow and reconcile it with the lock file.
|
|
2981
3780
|
* @param options - Setup options
|
|
2982
3781
|
*/
|
|
2983
|
-
async function
|
|
2984
|
-
logBetaWarning("setup
|
|
3782
|
+
async function setupTarget(options) {
|
|
3783
|
+
logBetaWarning("setup");
|
|
2985
3784
|
const resolved = await resolve$1(options);
|
|
2986
3785
|
const lock = readLock(options.outputDir);
|
|
2987
3786
|
const absFile = path.join(options.outputDir, resolved.file);
|
|
@@ -2993,27 +3792,35 @@ async function setupGitHub(options) {
|
|
|
2993
3792
|
});
|
|
2994
3793
|
const existing = findTarget(lock, resolved.kind, resolved.workspaceName);
|
|
2995
3794
|
const fileExists = fs$1.existsSync(absFile);
|
|
3795
|
+
const currentContent = fileExists ? fs$1.readFileSync(absFile, "utf-8") : null;
|
|
3796
|
+
const normalize = resolved.kind === "action" ? normalizeActionContent : void 0;
|
|
2996
3797
|
const decision = decideAction({
|
|
2997
3798
|
existing,
|
|
2998
3799
|
fileExists,
|
|
2999
|
-
currentContent
|
|
3000
|
-
force: options.force
|
|
3800
|
+
currentContent,
|
|
3801
|
+
force: options.force,
|
|
3802
|
+
normalize
|
|
3001
3803
|
});
|
|
3002
3804
|
if (decision.action === "conflict") throw new Error(`${resolved.file}: ${decision.reason}`);
|
|
3003
3805
|
const idResult = await ensureConfigId(resolved.configPath);
|
|
3004
3806
|
if (idResult === null) logger.warn("Could not find a defineConfig() call to confirm an app id. The CI deploy will fail unless your config resolves to one with an 'id'.");
|
|
3005
3807
|
const idInjected = idResult?.injected ?? false;
|
|
3808
|
+
let contentToWrite = resolved.render.content;
|
|
3809
|
+
if (decision.action === "regenerate" && normalize && currentContent !== null) {
|
|
3810
|
+
const existingBody = extractBuildSiteRunBody(currentContent);
|
|
3811
|
+
if (existingBody !== null) contentToWrite = injectBuildSiteRunBody(contentToWrite, existingBody);
|
|
3812
|
+
}
|
|
3006
3813
|
fs$1.mkdirSync(path.dirname(absFile), { recursive: true });
|
|
3007
|
-
fs$1.writeFileSync(absFile,
|
|
3814
|
+
fs$1.writeFileSync(absFile, contentToWrite, "utf-8");
|
|
3008
3815
|
const newTarget = {
|
|
3009
3816
|
kind: resolved.kind,
|
|
3010
3817
|
workspaceName: resolved.workspaceName,
|
|
3011
3818
|
file: resolved.file,
|
|
3012
|
-
templateVersion:
|
|
3819
|
+
templateVersion: 6,
|
|
3013
3820
|
inputs: resolved.inputs,
|
|
3014
3821
|
generatedIds: resolved.render.generatedIds,
|
|
3015
3822
|
ejectedIds: existing?.ejectedIds ?? [],
|
|
3016
|
-
contentHash: hashContent(resolved.render.content)
|
|
3823
|
+
contentHash: hashContent(normalize ? normalize(resolved.render.content) : resolved.render.content)
|
|
3017
3824
|
};
|
|
3018
3825
|
const targets = [...lock?.targets ?? []];
|
|
3019
3826
|
const index = targets.findIndex((t) => t.kind === newTarget.kind && t.workspaceName === newTarget.workspaceName);
|
|
@@ -3026,145 +3833,559 @@ async function setupGitHub(options) {
|
|
|
3026
3833
|
if (decision.action === "restore") logger.success(`Regenerated ${styles.path(resolved.file)} (was missing on disk)`);
|
|
3027
3834
|
else if (decision.action === "regenerate") logger.success(`Regenerated ${styles.path(resolved.file)}`);
|
|
3028
3835
|
else logger.success(`Generated ${styles.path(resolved.file)}`);
|
|
3029
|
-
|
|
3836
|
+
if (resolved.kind === "action") {
|
|
3837
|
+
logger.newline();
|
|
3838
|
+
logger.info("Next steps:");
|
|
3839
|
+
logger.newline();
|
|
3840
|
+
logger.log(`The composite action has been generated at ${styles.path(resolved.file)}.`);
|
|
3841
|
+
logger.log("Use `tailor setup coordinate` to generate a coordinator workflow that orchestrates this action.");
|
|
3842
|
+
} else printNextSteps({
|
|
3030
3843
|
environment: resolved.environment,
|
|
3031
3844
|
idInjected
|
|
3032
3845
|
});
|
|
3033
3846
|
}
|
|
3847
|
+
/**
|
|
3848
|
+
* Generate the coordinator workflow that orchestrates per-app composite actions.
|
|
3849
|
+
*
|
|
3850
|
+
* Unlike `setupTarget`, this function does not read a Tailor config. The coordinator
|
|
3851
|
+
* name is required via `--name`. App working directories are resolved from
|
|
3852
|
+
* the lock file entries created by `setup action`.
|
|
3853
|
+
* @param options - Coordinate setup options
|
|
3854
|
+
*/
|
|
3855
|
+
async function setupCoordinate(options) {
|
|
3856
|
+
logBetaWarning("setup");
|
|
3857
|
+
const { coordinatorName, coordinateKind, actions, force, outputDir } = options;
|
|
3858
|
+
validateWorkspaceName(coordinatorName);
|
|
3859
|
+
if (actions.length === 0) throw new Error("At least one --action is required. Run `tailor setup action --dir <app-dir>` for each app first.");
|
|
3860
|
+
const environment = options.environment ?? coordinatorName;
|
|
3861
|
+
validateEnvironment(environment);
|
|
3862
|
+
let branch = null;
|
|
3863
|
+
let branchAutoDetected = false;
|
|
3864
|
+
if (coordinateKind === "branch") {
|
|
3865
|
+
branchAutoDetected = options.branch === void 0;
|
|
3866
|
+
branch = options.branch ?? detectDefaultBranch(outputDir, options.gitRunner);
|
|
3867
|
+
validateBranch(branch);
|
|
3868
|
+
} else if (options.branch !== void 0) {
|
|
3869
|
+
validateBranch(options.branch);
|
|
3870
|
+
branch = options.branch;
|
|
3871
|
+
}
|
|
3872
|
+
const tagPattern = options.tagPattern ?? "v*";
|
|
3873
|
+
if (coordinateKind === "tag") validateTagPattern(tagPattern);
|
|
3874
|
+
const packageManager = detectPackageManager(outputDir);
|
|
3875
|
+
const lock = readLock(outputDir);
|
|
3876
|
+
if (!lock) throw new Error(".github/tailor.lock not found. Run `tailor setup action --name <name>` for each app before running setup coordinate.");
|
|
3877
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
3878
|
+
const apps = actions.map((actionName) => {
|
|
3879
|
+
const name = actionName.startsWith("tailor-") ? actionName.slice(7) : actionName;
|
|
3880
|
+
if (seenNames.has(name)) throw new Error(`Duplicate --action "${name}". Each composite action can only appear once in a coordinator.`);
|
|
3881
|
+
seenNames.add(name);
|
|
3882
|
+
const entry = lock.targets.find((t) => t.kind === "action" && t.workspaceName === name);
|
|
3883
|
+
if (!entry) throw new Error(`Action target "${name}" not found in .github/tailor.lock. Run \`tailor setup action --name ${name}\` first.`);
|
|
3884
|
+
validateDir(entry.inputs.dir);
|
|
3885
|
+
return {
|
|
3886
|
+
name,
|
|
3887
|
+
dir: entry.inputs.dir
|
|
3888
|
+
};
|
|
3889
|
+
});
|
|
3890
|
+
const render = renderCoordinateWorkflow({
|
|
3891
|
+
coordinatorName,
|
|
3892
|
+
kind: coordinateKind,
|
|
3893
|
+
apps,
|
|
3894
|
+
branch: branch ?? void 0,
|
|
3895
|
+
tagPattern: coordinateKind === "tag" ? tagPattern : void 0,
|
|
3896
|
+
environment,
|
|
3897
|
+
packageManager
|
|
3898
|
+
});
|
|
3899
|
+
const file = `.github/workflows/tailor-coordinate-${coordinatorName}${coordinateKind === "tag" ? "-tag" : ""}.yml`;
|
|
3900
|
+
const absFile = path.join(outputDir, file);
|
|
3901
|
+
assertNoKindCollision({
|
|
3902
|
+
lock,
|
|
3903
|
+
kind: "coordinate",
|
|
3904
|
+
workspaceName: coordinatorName,
|
|
3905
|
+
file
|
|
3906
|
+
});
|
|
3907
|
+
const existing = findTarget(lock, "coordinate", coordinatorName);
|
|
3908
|
+
const fileExists = fs$1.existsSync(absFile);
|
|
3909
|
+
const decision = decideAction({
|
|
3910
|
+
existing,
|
|
3911
|
+
fileExists,
|
|
3912
|
+
currentContent: fileExists ? fs$1.readFileSync(absFile, "utf-8") : null,
|
|
3913
|
+
force
|
|
3914
|
+
});
|
|
3915
|
+
if (decision.action === "conflict") throw new Error(`${file}: ${decision.reason}`);
|
|
3916
|
+
fs$1.mkdirSync(path.dirname(absFile), { recursive: true });
|
|
3917
|
+
fs$1.writeFileSync(absFile, render.content, "utf-8");
|
|
3918
|
+
const tailorSetupFile = ".github/actions/tailor-setup/action.yml";
|
|
3919
|
+
const absTailorSetupFile = path.join(outputDir, tailorSetupFile);
|
|
3920
|
+
if (!fs$1.existsSync(absTailorSetupFile)) {
|
|
3921
|
+
fs$1.mkdirSync(path.dirname(absTailorSetupFile), { recursive: true });
|
|
3922
|
+
fs$1.writeFileSync(absTailorSetupFile, renderTailorSetupAction({ packageManager }), "utf-8");
|
|
3923
|
+
logger.success(`Generated ${styles.path(tailorSetupFile)}`);
|
|
3924
|
+
}
|
|
3925
|
+
const newTarget = {
|
|
3926
|
+
kind: "coordinate",
|
|
3927
|
+
workspaceName: coordinatorName,
|
|
3928
|
+
file,
|
|
3929
|
+
templateVersion: 6,
|
|
3930
|
+
inputs: {
|
|
3931
|
+
branch,
|
|
3932
|
+
branchAutoDetected: coordinateKind === "branch" ? branchAutoDetected : branch !== null ? false : void 0,
|
|
3933
|
+
tagPattern: coordinateKind === "tag" ? tagPattern : null,
|
|
3934
|
+
environment,
|
|
3935
|
+
dir: ".",
|
|
3936
|
+
packageManager,
|
|
3937
|
+
actionDirs: apps.map((a) => a.dir)
|
|
3938
|
+
},
|
|
3939
|
+
generatedIds: render.generatedIds,
|
|
3940
|
+
ejectedIds: existing?.ejectedIds ?? [],
|
|
3941
|
+
contentHash: hashContent(render.content)
|
|
3942
|
+
};
|
|
3943
|
+
const targets = [...lock.targets];
|
|
3944
|
+
const idx = targets.findIndex((t) => t.kind === newTarget.kind && t.workspaceName === newTarget.workspaceName);
|
|
3945
|
+
if (idx === -1) targets.push(newTarget);
|
|
3946
|
+
else targets[idx] = newTarget;
|
|
3947
|
+
writeLock(outputDir, {
|
|
3948
|
+
version: 1,
|
|
3949
|
+
targets
|
|
3950
|
+
});
|
|
3951
|
+
if (decision.action === "restore") logger.success(`Regenerated ${styles.path(file)} (was missing on disk)`);
|
|
3952
|
+
else if (decision.action === "regenerate") logger.success(`Regenerated ${styles.path(file)}`);
|
|
3953
|
+
else logger.success(`Generated ${styles.path(file)}`);
|
|
3954
|
+
logger.newline();
|
|
3955
|
+
logger.info("Next steps:");
|
|
3956
|
+
logger.newline();
|
|
3957
|
+
logger.log(`1. Set the machine-user credentials as secrets on the "${environment}" environment:`);
|
|
3958
|
+
logger.log(` gh secret set TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID --env ${environment}`);
|
|
3959
|
+
logger.log(` gh secret set TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET --env ${environment}`);
|
|
3960
|
+
logger.newline();
|
|
3961
|
+
logger.log(`2. Provision each workspace and set TAILOR_PLATFORM_WORKSPACE_ID on the "${environment}" environment:`);
|
|
3962
|
+
logger.log(" tailor workspace create # one per app; copy the id");
|
|
3963
|
+
logger.log(` gh variable set TAILOR_PLATFORM_WORKSPACE_ID --env ${environment}`);
|
|
3964
|
+
logger.newline();
|
|
3965
|
+
logger.log("3. Commit the generated files:");
|
|
3966
|
+
logger.log(` - ${file}`);
|
|
3967
|
+
logger.log(` - ${tailorSetupFile} (if newly created)`);
|
|
3968
|
+
logger.log(" - .github/tailor.lock");
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3971
|
+
//#endregion
|
|
3972
|
+
//#region src/cli/commands/setup/check.ts
|
|
3973
|
+
/**
|
|
3974
|
+
* Compute drift findings for one target by comparing its recorded lock state
|
|
3975
|
+
* against the current repository/config state.
|
|
3976
|
+
* @param target - The lock target being audited
|
|
3977
|
+
* @param state - Current repository/config state for this target
|
|
3978
|
+
* @returns Drift findings (empty when the target is in sync)
|
|
3979
|
+
*/
|
|
3980
|
+
function findTargetDrift(target, state) {
|
|
3981
|
+
const id = `${target.kind} ${target.workspaceName}`;
|
|
3982
|
+
const findings = [];
|
|
3983
|
+
if (!state.fileExists) findings.push({
|
|
3984
|
+
target: id,
|
|
3985
|
+
rule: "missing-file",
|
|
3986
|
+
message: `${target.file} is missing or unreadable. Re-run setup to restore it.`
|
|
3987
|
+
});
|
|
3988
|
+
else if (state.currentHash !== null && state.currentHash !== target.contentHash) findings.push({
|
|
3989
|
+
target: id,
|
|
3990
|
+
rule: "hand-edit",
|
|
3991
|
+
message: `${target.file} was edited by hand since it was generated. Re-run setup with --force to regenerate, or keep customizations in your own jobs/steps.`
|
|
3992
|
+
});
|
|
3993
|
+
if (target.templateVersion < state.templateVersion) findings.push({
|
|
3994
|
+
target: id,
|
|
3995
|
+
rule: "template-version",
|
|
3996
|
+
message: `A newer workflow template is available (generated with v${String(target.templateVersion)}, current v${String(state.templateVersion)}). Re-run setup to update.`
|
|
3997
|
+
});
|
|
3998
|
+
if (!state.configExists) findings.push({
|
|
3999
|
+
target: id,
|
|
4000
|
+
rule: "config-dir",
|
|
4001
|
+
message: `tailor.config.ts not found under "${target.inputs.dir}". The app directory may have moved; re-run setup with the correct --dir.`
|
|
4002
|
+
});
|
|
4003
|
+
if ((target.kind === "branch" || target.kind === "coordinate" || target.kind === "preview") && target.inputs.branchAutoDetected !== false && state.defaultBranch !== null && target.inputs.branch !== null && target.inputs.branch !== state.defaultBranch) findings.push({
|
|
4004
|
+
target: id,
|
|
4005
|
+
rule: "default-branch",
|
|
4006
|
+
message: `The workflow triggers on "${target.inputs.branch}" but the repository default branch is now "${state.defaultBranch}". If this is intentional, ignore this; otherwise re-run setup so the trigger matches the default branch.`
|
|
4007
|
+
});
|
|
4008
|
+
if (target.kind === "branch" && target.inputs.erdPreview && state.configExists) {
|
|
4009
|
+
const recorded = [...target.inputs.erdNamespaces ?? []].toSorted((a, b) => a.localeCompare(b));
|
|
4010
|
+
const current = state.erdNamespaces?.toSorted((a, b) => a.localeCompare(b)) ?? null;
|
|
4011
|
+
if (current === null || recorded.length !== current.length || recorded.some((namespace, index) => namespace !== current[index])) findings.push({
|
|
4012
|
+
target: id,
|
|
4013
|
+
rule: "erd-namespaces",
|
|
4014
|
+
message: "TailorDB namespaces for ERD preview changed. Re-run setup so the ERD preview matrix is regenerated."
|
|
4015
|
+
});
|
|
4016
|
+
}
|
|
4017
|
+
if ((target.kind === "branch" || target.kind === "tag") && target.inputs.migrationDriftCheck === false && state.hasMigrations === true) findings.push({
|
|
4018
|
+
target: id,
|
|
4019
|
+
rule: "migration-drift",
|
|
4020
|
+
message: "TailorDB namespaces with migrations were added to the config. Re-run setup so the tailor-migration-drift-check step is included in the plan job."
|
|
4021
|
+
});
|
|
4022
|
+
if ((target.kind === "branch" || target.kind === "tag") && target.inputs.seedValidate === false && state.hasSeeds === true) findings.push({
|
|
4023
|
+
target: id,
|
|
4024
|
+
rule: "seed-validate",
|
|
4025
|
+
message: "Seed plugin detected in the current config. Re-run setup so the tailor-seed-validate step is included in the plan job."
|
|
4026
|
+
});
|
|
4027
|
+
if (target.kind === "action" && target.inputs.hasStaticWebsites === false && state.hasStaticWebsites === true) findings.push({
|
|
4028
|
+
target: id,
|
|
4029
|
+
rule: "static-websites",
|
|
4030
|
+
message: "Static websites were added to the config. Re-run setup so the build-site slot is included in the composite action."
|
|
4031
|
+
});
|
|
4032
|
+
return findings;
|
|
4033
|
+
}
|
|
4034
|
+
function detectDefaultBranchSafe(cwd, run) {
|
|
4035
|
+
try {
|
|
4036
|
+
return detectDefaultBranch(cwd, run);
|
|
4037
|
+
} catch {
|
|
4038
|
+
return null;
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
function escapesRoot(rel) {
|
|
4042
|
+
return path.isAbsolute(rel) || rel === ".." || rel.startsWith(`..${path.sep}`) || rel.startsWith("../");
|
|
4043
|
+
}
|
|
4044
|
+
function resolveWithinRoot(outputDir, relPath) {
|
|
4045
|
+
if (path.isAbsolute(relPath)) return null;
|
|
4046
|
+
const abs = path.join(outputDir, relPath);
|
|
4047
|
+
if (escapesRoot(path.relative(outputDir, abs))) return null;
|
|
4048
|
+
try {
|
|
4049
|
+
if (escapesRoot(path.relative(fs$1.realpathSync(outputDir), fs$1.realpathSync(abs)))) return null;
|
|
4050
|
+
} catch {}
|
|
4051
|
+
return abs;
|
|
4052
|
+
}
|
|
4053
|
+
function readHash(absFile, normalize) {
|
|
4054
|
+
try {
|
|
4055
|
+
const content = fs$1.readFileSync(absFile, "utf-8");
|
|
4056
|
+
return hashContent(normalize ? normalize(content) : content);
|
|
4057
|
+
} catch {
|
|
4058
|
+
return null;
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
async function defaultLoadErdNamespaces(configPath) {
|
|
4062
|
+
const { config } = await loadConfig(configPath);
|
|
4063
|
+
return extractOwnedNamespaces(config);
|
|
4064
|
+
}
|
|
4065
|
+
async function defaultLoadHasMigrations(configPath) {
|
|
4066
|
+
const { config } = await loadConfig(configPath);
|
|
4067
|
+
return getNamespacesWithMigrations(config, path.dirname(configPath)).length > 0;
|
|
4068
|
+
}
|
|
4069
|
+
async function defaultLoadHasSeeds(configPath) {
|
|
4070
|
+
const { plugins } = await loadConfig(configPath);
|
|
4071
|
+
return plugins.some((p) => p.id === "@tailor-platform/seed");
|
|
4072
|
+
}
|
|
4073
|
+
async function defaultLoadHasStaticWebsites(configPath) {
|
|
4074
|
+
const { config } = await loadConfig(configPath);
|
|
4075
|
+
return (config.staticWebsites?.length ?? 0) > 0;
|
|
4076
|
+
}
|
|
4077
|
+
/**
|
|
4078
|
+
* Audit the generated workflows for drift against the current config/repo
|
|
4079
|
+
* state. Read-only: never writes files, the lock, or the config.
|
|
4080
|
+
*
|
|
4081
|
+
* Throws when drift is found (so it composes like the other `:check`
|
|
4082
|
+
* commands). The workflow drift-check step layers advisory behaviour on top
|
|
4083
|
+
* (per-rule ignore / continue-on-error); the CLI itself reports via exit code.
|
|
4084
|
+
* @param options - Check options
|
|
4085
|
+
*/
|
|
4086
|
+
async function checkGitHub(options) {
|
|
4087
|
+
logBetaWarning("setup");
|
|
4088
|
+
const { outputDir } = options;
|
|
4089
|
+
const lock = readLock(outputDir);
|
|
4090
|
+
if (!lock || lock.targets.length === 0) throw new Error("No managed workflows found (.github/tailor.lock is missing or empty). Run `tailor setup branch` (or another setup subcommand) first.");
|
|
4091
|
+
if (!options.ci) {
|
|
4092
|
+
if (lock.targets.some((t) => t.kind === "branch" || t.kind === "tag" || t.kind === "coordinate") && !process.env["TAILOR_PLATFORM_WORKSPACE_ID"]) throw new Error("TAILOR_PLATFORM_WORKSPACE_ID is not set. Provision the workspace and set the variable:\n tailor workspace create # if it does not exist yet; copy the id\n gh variable set TAILOR_PLATFORM_WORKSPACE_ID --env <environment>");
|
|
4093
|
+
}
|
|
4094
|
+
const slackToken = Boolean(process.env["TAILOR_SLACK_BOT_TOKEN"]);
|
|
4095
|
+
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.`);
|
|
4096
|
+
const exists = options.configExistsAt ?? ((p) => fs$1.existsSync(p));
|
|
4097
|
+
const defaultBranch = detectDefaultBranchSafe(outputDir, options.gitRunner);
|
|
4098
|
+
const loadErdNamespaces = options.loadErdNamespaces ?? defaultLoadErdNamespaces;
|
|
4099
|
+
const loadHasMigrations = options.loadHasMigrations ?? defaultLoadHasMigrations;
|
|
4100
|
+
const loadHasSeeds = options.loadHasSeeds ?? defaultLoadHasSeeds;
|
|
4101
|
+
const loadHasStaticWebsites = options.loadHasStaticWebsites ?? defaultLoadHasStaticWebsites;
|
|
4102
|
+
const findings = [];
|
|
4103
|
+
for (const target of lock.targets) {
|
|
4104
|
+
const absFile = resolveWithinRoot(outputDir, target.file);
|
|
4105
|
+
const currentHash = absFile === null ? null : readHash(absFile, target.kind === "action" ? normalizeActionContent : void 0);
|
|
4106
|
+
const configAbs = target.kind === "coordinate" ? null : resolveWithinRoot(outputDir, path.join(target.inputs.dir, "tailor.config.ts"));
|
|
4107
|
+
const configExists = target.kind === "coordinate" || configAbs !== null && exists(configAbs);
|
|
4108
|
+
const erdNamespaces = target.kind === "branch" && target.inputs.erdPreview && configAbs !== null && configExists ? await loadErdNamespaces(configAbs) : null;
|
|
4109
|
+
const hasMigrations = (target.kind === "branch" || target.kind === "tag") && target.inputs.migrationDriftCheck !== void 0 && configAbs !== null && configExists ? await loadHasMigrations(configAbs) : void 0;
|
|
4110
|
+
const hasSeeds = (target.kind === "branch" || target.kind === "tag") && target.inputs.seedValidate !== void 0 && configAbs !== null && configExists ? await loadHasSeeds(configAbs) : void 0;
|
|
4111
|
+
const hasStaticWebsites = target.kind === "action" && target.inputs.hasStaticWebsites !== void 0 && configAbs !== null && configExists ? await loadHasStaticWebsites(configAbs) : void 0;
|
|
4112
|
+
findings.push(...findTargetDrift(target, {
|
|
4113
|
+
fileExists: currentHash !== null,
|
|
4114
|
+
currentHash,
|
|
4115
|
+
configExists,
|
|
4116
|
+
defaultBranch,
|
|
4117
|
+
templateVersion: 6,
|
|
4118
|
+
erdNamespaces,
|
|
4119
|
+
hasMigrations,
|
|
4120
|
+
hasSeeds,
|
|
4121
|
+
hasStaticWebsites
|
|
4122
|
+
}));
|
|
4123
|
+
}
|
|
4124
|
+
const count = lock.targets.length;
|
|
4125
|
+
if (findings.length === 0) {
|
|
4126
|
+
logger.success(`No drift detected across ${String(count)} target(s).`);
|
|
4127
|
+
return;
|
|
4128
|
+
}
|
|
4129
|
+
for (const finding of findings) logger.warn(`[${finding.target}] ${finding.message} (ignore key: ${finding.rule})`);
|
|
4130
|
+
throw new Error(`Detected ${String(findings.length)} drift finding(s) across ${String(count)} target(s). Re-run \`tailor setup\` to regenerate, or address each finding above.`);
|
|
4131
|
+
}
|
|
3034
4132
|
|
|
3035
4133
|
//#endregion
|
|
3036
|
-
//#region src/cli/commands/setup/
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
4134
|
+
//#region src/cli/commands/setup/delete.ts
|
|
4135
|
+
function normalizeRelPath(input) {
|
|
4136
|
+
return input.replaceAll("\\", "/").replace(/\/{2,}/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
4137
|
+
}
|
|
4138
|
+
function targetKey(target) {
|
|
4139
|
+
return `${target.kind}:${target.workspaceName}`;
|
|
4140
|
+
}
|
|
4141
|
+
function resolveMatch(outputDir, targets, input) {
|
|
4142
|
+
const relPath = normalizeRelPath(input);
|
|
4143
|
+
const absPath = resolveWithinRoot(outputDir, relPath);
|
|
4144
|
+
if (absPath === null) throw new Error(`"${input}" must be a path inside the repository.`);
|
|
4145
|
+
const target = targets.find((t) => t.file === relPath);
|
|
4146
|
+
if (!target) throw new Error(`"${relPath}" is not a managed workflow/action file (not recorded in .github/tailor-sdk.lock). Only files generated by \`tailor-sdk setup\` can be deleted with this command.`);
|
|
4147
|
+
return {
|
|
4148
|
+
relPath,
|
|
4149
|
+
absPath,
|
|
4150
|
+
target
|
|
4151
|
+
};
|
|
4152
|
+
}
|
|
4153
|
+
function warnCoordinatorReferences(allTargets, deletedKeys, match) {
|
|
4154
|
+
if (match.target.kind !== "action") return;
|
|
4155
|
+
for (const coordinator of allTargets) if (coordinator.kind === "coordinate" && coordinator.inputs.actionDirs?.includes(match.target.inputs.dir) && !deletedKeys.has(targetKey(coordinator))) logger.warn(`Coordinator "${coordinator.workspaceName}" still references this action's directory (${match.target.inputs.dir}). Re-run \`tailor-sdk setup coordinate\` without --action ${match.target.workspaceName} to remove it there too.`);
|
|
4156
|
+
}
|
|
4157
|
+
/**
|
|
4158
|
+
* Delete managed workflow/action files and their `.github/tailor-sdk.lock` entries.
|
|
4159
|
+
*
|
|
4160
|
+
* Refuses to touch files that are not recorded in the lock, so it never
|
|
4161
|
+
* deletes hand-written workflows or the user-owned tailor-setup action.
|
|
4162
|
+
* @param options - Delete options
|
|
4163
|
+
*/
|
|
4164
|
+
async function setupDelete(options) {
|
|
4165
|
+
logBetaWarning("setup");
|
|
4166
|
+
const { outputDir, yes } = options;
|
|
4167
|
+
const lock = readLock(outputDir);
|
|
4168
|
+
if (!lock || lock.targets.length === 0) throw new Error("No managed workflows found (.github/tailor-sdk.lock is missing or empty). Run `tailor-sdk setup branch` (or another setup subcommand) first.");
|
|
4169
|
+
const matches = [...new Set(options.files.map(normalizeRelPath))].map((relPath) => resolveMatch(outputDir, lock.targets, relPath));
|
|
4170
|
+
const deletedKeys = new Set(matches.map((m) => targetKey(m.target)));
|
|
4171
|
+
for (const match of matches) warnCoordinatorReferences(lock.targets, deletedKeys, match);
|
|
4172
|
+
if (!yes) {
|
|
4173
|
+
const list = matches.map((m) => m.relPath).join("\n ");
|
|
4174
|
+
if (!await prompt.confirm({
|
|
4175
|
+
message: `This will delete the following file(s) and their .github/tailor-sdk.lock entries:\n ${list}\nContinue?`,
|
|
4176
|
+
default: false
|
|
4177
|
+
})) {
|
|
4178
|
+
logger.info("Delete cancelled.");
|
|
4179
|
+
return;
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
const remaining = [...lock.targets];
|
|
4183
|
+
try {
|
|
4184
|
+
for (const { relPath, absPath, target } of matches) {
|
|
4185
|
+
fs$1.rmSync(absPath, { force: true });
|
|
4186
|
+
if (target.kind === "action") {
|
|
4187
|
+
const dir = path.dirname(absPath);
|
|
4188
|
+
if (fs$1.existsSync(dir) && fs$1.readdirSync(dir).length === 0) fs$1.rmdirSync(dir);
|
|
4189
|
+
}
|
|
4190
|
+
const index = remaining.findIndex((t) => targetKey(t) === targetKey(target));
|
|
4191
|
+
if (index !== -1) remaining.splice(index, 1);
|
|
4192
|
+
logger.success(`Deleted ${styles.path(relPath)} (${target.kind} ${target.workspaceName})`);
|
|
4193
|
+
}
|
|
4194
|
+
} finally {
|
|
4195
|
+
if (remaining.length !== lock.targets.length) writeLock(outputDir, {
|
|
4196
|
+
version: 1,
|
|
4197
|
+
targets: remaining
|
|
4198
|
+
});
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
//#endregion
|
|
4203
|
+
//#region src/cli/commands/setup/index.ts
|
|
4204
|
+
const checkCommand = defineAppCommand({
|
|
4205
|
+
name: "check",
|
|
4206
|
+
description: "Audit generated workflows for drift against the current config/repo (read-only).",
|
|
4207
|
+
args: z.strictObject({ ci: arg(z.boolean().default(false), { description: "Run in CI mode: skip checks that are handled by the runtime (e.g. TAILOR_PLATFORM_WORKSPACE_ID)." }) }),
|
|
4208
|
+
run: async (args) => {
|
|
4209
|
+
await checkGitHub({
|
|
4210
|
+
outputDir: process.cwd(),
|
|
4211
|
+
ci: args.ci
|
|
4212
|
+
});
|
|
4213
|
+
}
|
|
4214
|
+
});
|
|
4215
|
+
const coordinateCommand = defineAppCommand({
|
|
4216
|
+
name: "coordinate",
|
|
4217
|
+
description: "Generate a coordinator workflow that orchestrates multiple --action-generated composite actions.",
|
|
4218
|
+
args: z.strictObject({
|
|
4219
|
+
name: arg(z.string().min(1), {
|
|
4220
|
+
alias: "n",
|
|
4221
|
+
description: "Coordinator name (used in the generated workflow file name and job names)"
|
|
4222
|
+
}),
|
|
4223
|
+
action: arg(z.array(z.string().min(1)).min(1), { description: "Composite action to include (can be specified multiple times). tailor- prefix optional." }),
|
|
4224
|
+
branch: arg(z.string().min(1).optional(), { description: "Branch target: deploy trigger branch (defaults to the detected default branch)" }),
|
|
4225
|
+
tag: arg(z.boolean().default(false), { description: "Generate a tag target coordinator" }),
|
|
4226
|
+
environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the plan/deploy jobs" }),
|
|
4227
|
+
force: arg(z.boolean().default(false), { description: "Discard hand edits and regenerate" })
|
|
4228
|
+
}),
|
|
4229
|
+
run: async (args) => {
|
|
4230
|
+
const coordinateKind = args.tag ? "tag" : "branch";
|
|
4231
|
+
await setupCoordinate({
|
|
4232
|
+
coordinatorName: args.name,
|
|
4233
|
+
coordinateKind,
|
|
4234
|
+
actions: args.action,
|
|
4235
|
+
branch: args.branch,
|
|
4236
|
+
environment: args.environment,
|
|
4237
|
+
force: args.force,
|
|
4238
|
+
outputDir: process.cwd()
|
|
4239
|
+
});
|
|
4240
|
+
}
|
|
4241
|
+
});
|
|
4242
|
+
const actionCommand = defineAppCommand({
|
|
4243
|
+
name: "action",
|
|
4244
|
+
description: "Generate a per-app composite action for use with setup coordinate (monorepo multi-app deploys).",
|
|
4245
|
+
args: z.strictObject({
|
|
4246
|
+
name: arg(z.string().min(1).optional(), {
|
|
3042
4247
|
alias: "n",
|
|
3043
|
-
description: "
|
|
4248
|
+
description: "Name (defaults to the config 'name')"
|
|
4249
|
+
}),
|
|
4250
|
+
dir: arg(z.string().min(1).default("."), {
|
|
4251
|
+
alias: "d",
|
|
4252
|
+
description: "App directory"
|
|
3044
4253
|
}),
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
4254
|
+
environment: arg(z.string().min(1).optional(), { description: "GitHub Environment (defaults to the workspace name)" }),
|
|
4255
|
+
force: arg(z.boolean().default(false), { description: "Discard hand edits and regenerate" })
|
|
4256
|
+
}),
|
|
4257
|
+
run: async (args) => {
|
|
4258
|
+
await setupTarget({
|
|
4259
|
+
kind: "action",
|
|
4260
|
+
workspaceName: args.name,
|
|
4261
|
+
dir: args.dir,
|
|
4262
|
+
environment: args.environment,
|
|
4263
|
+
force: args.force,
|
|
4264
|
+
outputDir: process.cwd()
|
|
4265
|
+
});
|
|
4266
|
+
}
|
|
4267
|
+
});
|
|
4268
|
+
const branchCommand = defineAppCommand({
|
|
4269
|
+
name: "branch",
|
|
4270
|
+
description: "Generate a branch-target deploy workflow (push to branch triggers deploy).",
|
|
4271
|
+
args: z.strictObject({
|
|
4272
|
+
name: arg(z.string().min(1).optional(), {
|
|
4273
|
+
alias: "n",
|
|
4274
|
+
description: "Name (defaults to the config 'name')"
|
|
4275
|
+
}),
|
|
4276
|
+
branch: arg(z.string().min(1).optional(), { description: "Deploy trigger branch (defaults to the detected default branch)" }),
|
|
3048
4277
|
environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the plan/deploy jobs (defaults to the workspace name)" }),
|
|
3049
|
-
"
|
|
4278
|
+
"erd-preview": arg(z.boolean().default(false), { description: "Add PR ERD viewer artifacts with current/diff previews for TailorDB namespaces" }),
|
|
3050
4279
|
dir: arg(z.string().min(1).default("."), {
|
|
3051
4280
|
alias: "d",
|
|
3052
4281
|
description: "App directory (for monorepo setups)"
|
|
3053
4282
|
}),
|
|
3054
4283
|
force: arg(z.boolean().default(false), { description: "Discard hand edits / take over unmanaged files and regenerate" })
|
|
3055
|
-
})
|
|
4284
|
+
}),
|
|
3056
4285
|
run: async (args) => {
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
workspaceName: args["workspace-name"],
|
|
4286
|
+
await setupTarget({
|
|
4287
|
+
kind: "branch",
|
|
4288
|
+
workspaceName: args.name,
|
|
3061
4289
|
branch: args.branch,
|
|
3062
|
-
tag: args.tag,
|
|
3063
|
-
tagPattern: args["tag-pattern"] ?? "v*",
|
|
3064
4290
|
environment: args.environment,
|
|
3065
|
-
|
|
4291
|
+
erdPreview: args["erd-preview"],
|
|
3066
4292
|
dir: args.dir,
|
|
3067
4293
|
force: args.force,
|
|
3068
4294
|
outputDir: process.cwd()
|
|
3069
4295
|
});
|
|
3070
4296
|
}
|
|
3071
4297
|
});
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
4298
|
+
const tagCommand = defineAppCommand({
|
|
4299
|
+
name: "tag",
|
|
4300
|
+
description: "Generate a tag-target deploy workflow (tag push triggers deploy).",
|
|
4301
|
+
args: z.strictObject({
|
|
4302
|
+
name: arg(z.string().min(1).optional(), {
|
|
4303
|
+
alias: "n",
|
|
4304
|
+
description: "Name (defaults to the config 'name')"
|
|
4305
|
+
}),
|
|
4306
|
+
"tag-pattern": arg(z.string().min(1).default("v*"), { description: "Tag glob to match (defaults to v*)" }),
|
|
4307
|
+
branch: arg(z.string().min(1).optional(), { description: "Tag-reachability guard branch (no guard when omitted)" }),
|
|
4308
|
+
environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the plan/deploy jobs (defaults to the workspace name)" }),
|
|
4309
|
+
dir: arg(z.string().min(1).default("."), {
|
|
4310
|
+
alias: "d",
|
|
4311
|
+
description: "App directory (for monorepo setups)"
|
|
4312
|
+
}),
|
|
4313
|
+
force: arg(z.boolean().default(false), { description: "Discard hand edits / take over unmanaged files and regenerate" })
|
|
4314
|
+
}),
|
|
4315
|
+
run: async (args) => {
|
|
4316
|
+
await setupTarget({
|
|
4317
|
+
kind: "tag",
|
|
4318
|
+
workspaceName: args.name,
|
|
4319
|
+
tagPattern: args["tag-pattern"],
|
|
4320
|
+
branch: args.branch,
|
|
4321
|
+
environment: args.environment,
|
|
4322
|
+
dir: args.dir,
|
|
4323
|
+
force: args.force,
|
|
4324
|
+
outputDir: process.cwd()
|
|
4325
|
+
});
|
|
4326
|
+
}
|
|
3079
4327
|
});
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
}
|
|
3088
|
-
function resolveSkillsSource(source) {
|
|
3089
|
-
return process.env[SKILLS_SOURCE_ENV_KEY] ?? source;
|
|
3090
|
-
}
|
|
3091
|
-
/**
|
|
3092
|
-
* Build CLI arguments for `skills add` with the fixed tailor-sdk skill target.
|
|
3093
|
-
* `--copy` is included so the installed skill survives `pnpm install` wiping `node_modules`.
|
|
3094
|
-
* @param options - Options controlling the generated `skills add` arguments
|
|
3095
|
-
* @param options.source
|
|
3096
|
-
* @param options.agent
|
|
3097
|
-
* @param options.yes
|
|
3098
|
-
* @returns CLI arguments for `npx skills add`
|
|
3099
|
-
*/
|
|
3100
|
-
function buildSkillsAddArgs(options) {
|
|
3101
|
-
const args = [
|
|
3102
|
-
"skills",
|
|
3103
|
-
"add",
|
|
3104
|
-
resolveSkillsSource(options.source),
|
|
3105
|
-
"--skill",
|
|
3106
|
-
SKILL_NAME,
|
|
3107
|
-
"--copy"
|
|
3108
|
-
];
|
|
3109
|
-
if (options.agent) args.push("--agent", options.agent);
|
|
3110
|
-
if (options.yes) args.push("--yes");
|
|
3111
|
-
return args;
|
|
3112
|
-
}
|
|
3113
|
-
/**
|
|
3114
|
-
* Run `npx skills add` to install the tailor-sdk skill.
|
|
3115
|
-
* @param options - Runtime options for skill installation
|
|
3116
|
-
* @returns Process exit code from the spawned `npx` command
|
|
3117
|
-
*/
|
|
3118
|
-
async function runSkillsInstaller(options) {
|
|
3119
|
-
const args = buildSkillsAddArgs({
|
|
3120
|
-
source: options.source,
|
|
3121
|
-
agent: options.agent,
|
|
3122
|
-
yes: options.yes
|
|
3123
|
-
});
|
|
3124
|
-
const childProcess = (options.spawnFn ?? ((command, spawnArgs, spawnOptions) => spawn(command, spawnArgs, spawnOptions)))(resolveNpxCommand(), args, { stdio: "inherit" });
|
|
3125
|
-
return await new Promise((resolve, reject) => {
|
|
3126
|
-
childProcess.on("error", (error) => reject(error));
|
|
3127
|
-
childProcess.on("close", (code) => resolve(code ?? 1));
|
|
3128
|
-
});
|
|
3129
|
-
}
|
|
3130
|
-
|
|
3131
|
-
//#endregion
|
|
3132
|
-
//#region src/cli/commands/skills/install.ts
|
|
3133
|
-
async function resolveBundledSkillsDir() {
|
|
3134
|
-
return resolve(dirname(await resolvePackageJSON(import.meta.url)), "agent-skills");
|
|
3135
|
-
}
|
|
3136
|
-
const DEFAULT_AGENT = "claude-code";
|
|
3137
|
-
const installCommand = defineAppCommand({
|
|
3138
|
-
name: "install",
|
|
3139
|
-
description: "Install the tailor-sdk agent skill from the installed SDK package.",
|
|
3140
|
-
args: z.object({
|
|
3141
|
-
agent: arg(z.string().default(DEFAULT_AGENT), {
|
|
3142
|
-
alias: "a",
|
|
3143
|
-
description: `vercel/skills agent name (e.g. ${DEFAULT_AGENT}, codex). Defaults to ${DEFAULT_AGENT}.`
|
|
4328
|
+
const previewCommand = defineAppCommand({
|
|
4329
|
+
name: "preview",
|
|
4330
|
+
description: "Generate a preview workflow (PR open/sync triggers deploy to a per-PR workspace).",
|
|
4331
|
+
args: z.strictObject({
|
|
4332
|
+
name: arg(z.string().min(1).optional(), {
|
|
4333
|
+
alias: "n",
|
|
4334
|
+
description: "Name (defaults to the config 'name')"
|
|
3144
4335
|
}),
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
4336
|
+
branch: arg(z.string().min(1).optional(), { description: "Branch to filter PRs by (defaults to the detected default branch)" }),
|
|
4337
|
+
region: arg(z.string().min(1), { description: "Workspace region for preview workspace creation (e.g. us-west). Required." }),
|
|
4338
|
+
"require-preview-label": arg(z.boolean().default(false), { description: "Deploy preview only for PRs labeled `tailor:preview` instead of all PRs." }),
|
|
4339
|
+
environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the preview jobs (defaults to the workspace name)" }),
|
|
4340
|
+
dir: arg(z.string().min(1).default("."), {
|
|
4341
|
+
alias: "d",
|
|
4342
|
+
description: "App directory (for monorepo setups)"
|
|
4343
|
+
}),
|
|
4344
|
+
force: arg(z.boolean().default(false), { description: "Discard hand edits / take over unmanaged files and regenerate" })
|
|
4345
|
+
}),
|
|
4346
|
+
run: async (args) => {
|
|
4347
|
+
await setupTarget({
|
|
4348
|
+
kind: "preview",
|
|
4349
|
+
workspaceName: args.name,
|
|
4350
|
+
branch: args.branch,
|
|
4351
|
+
region: args.region,
|
|
4352
|
+
requirePreviewLabel: args["require-preview-label"],
|
|
4353
|
+
environment: args.environment,
|
|
4354
|
+
dir: args.dir,
|
|
4355
|
+
force: args.force,
|
|
4356
|
+
outputDir: process.cwd()
|
|
4357
|
+
});
|
|
4358
|
+
}
|
|
4359
|
+
});
|
|
4360
|
+
const deleteCommand$1 = defineAppCommand({
|
|
4361
|
+
name: "delete",
|
|
4362
|
+
description: "Delete managed workflow/action file(s) and their .github/tailor.lock entries.",
|
|
4363
|
+
args: z.strictObject({
|
|
4364
|
+
...confirmationArgs,
|
|
4365
|
+
files: arg(z.string().array().min(1), {
|
|
4366
|
+
positional: true,
|
|
4367
|
+
description: "Workflow/action file(s) to delete, as generated under .github/workflows or .github/actions"
|
|
3148
4368
|
})
|
|
3149
|
-
})
|
|
4369
|
+
}),
|
|
3150
4370
|
run: async (args) => {
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
4371
|
+
await setupDelete({
|
|
4372
|
+
files: args.files,
|
|
4373
|
+
yes: args.yes,
|
|
4374
|
+
outputDir: process.cwd()
|
|
3155
4375
|
});
|
|
3156
|
-
if (exitCode !== 0) process.exit(exitCode);
|
|
3157
4376
|
}
|
|
3158
4377
|
});
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
4378
|
+
const setupCommand = defineCommand({
|
|
4379
|
+
name: "setup",
|
|
4380
|
+
description: "Generate CI deploy workflows for your project. (beta)",
|
|
4381
|
+
subCommands: {
|
|
4382
|
+
branch: branchCommand,
|
|
4383
|
+
tag: tagCommand,
|
|
4384
|
+
preview: previewCommand,
|
|
4385
|
+
action: actionCommand,
|
|
4386
|
+
coordinate: coordinateCommand,
|
|
4387
|
+
check: checkCommand,
|
|
4388
|
+
delete: deleteCommand$1
|
|
3168
4389
|
}
|
|
3169
4390
|
});
|
|
3170
4391
|
|
|
@@ -3208,7 +4429,7 @@ async function withTimeout(p, ms, message) {
|
|
|
3208
4429
|
//#endregion
|
|
3209
4430
|
//#region src/cli/commands/staticwebsite/deploy.ts
|
|
3210
4431
|
const CHUNK_SIZE = 64 * 1024;
|
|
3211
|
-
const IGNORED_FILES = new Set([
|
|
4432
|
+
const IGNORED_FILES = /* @__PURE__ */ new Set([
|
|
3212
4433
|
".DS_Store",
|
|
3213
4434
|
"thumbs.db",
|
|
3214
4435
|
"desktop.ini"
|
|
@@ -3328,7 +4549,7 @@ function logSkippedFiles(skippedFiles) {
|
|
|
3328
4549
|
const deployCommand = defineAppCommand({
|
|
3329
4550
|
name: "deploy",
|
|
3330
4551
|
description: "Deploy a static website from a local build directory.",
|
|
3331
|
-
args: z.
|
|
4552
|
+
args: z.strictObject({
|
|
3332
4553
|
...workspaceArgs,
|
|
3333
4554
|
name: arg(z.string(), {
|
|
3334
4555
|
alias: "n",
|
|
@@ -3339,7 +4560,7 @@ const deployCommand = defineAppCommand({
|
|
|
3339
4560
|
description: "Path to the static website files",
|
|
3340
4561
|
completion: { type: "directory" }
|
|
3341
4562
|
})
|
|
3342
|
-
})
|
|
4563
|
+
}),
|
|
3343
4564
|
run: async (args) => {
|
|
3344
4565
|
await assertWritable({ profile: args.profile });
|
|
3345
4566
|
logger.info(`Deploying static website "${args.name}" from directory: ${args.dir}`);
|
|
@@ -3381,13 +4602,13 @@ const statusLabels = {
|
|
|
3381
4602
|
const domainGetCommand = defineAppCommand({
|
|
3382
4603
|
name: "get",
|
|
3383
4604
|
description: "Get details of a custom domain.",
|
|
3384
|
-
args: z.
|
|
4605
|
+
args: z.strictObject({
|
|
3385
4606
|
...workspaceArgs,
|
|
3386
4607
|
domain: arg(z.string(), {
|
|
3387
4608
|
positional: true,
|
|
3388
4609
|
description: "Custom domain name"
|
|
3389
4610
|
})
|
|
3390
|
-
})
|
|
4611
|
+
}),
|
|
3391
4612
|
run: async (args) => {
|
|
3392
4613
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
3393
4614
|
const workspaceId = await loadWorkspaceId({
|
|
@@ -3421,13 +4642,13 @@ const domainGetCommand = defineAppCommand({
|
|
|
3421
4642
|
const domainListCommand = defineAppCommand({
|
|
3422
4643
|
name: "list",
|
|
3423
4644
|
description: "List custom domains for a static website.",
|
|
3424
|
-
args: z.
|
|
4645
|
+
args: z.strictObject({
|
|
3425
4646
|
...workspaceArgs,
|
|
3426
4647
|
name: arg(z.string(), {
|
|
3427
4648
|
positional: true,
|
|
3428
4649
|
description: "Static website name"
|
|
3429
4650
|
})
|
|
3430
|
-
})
|
|
4651
|
+
}),
|
|
3431
4652
|
run: async (args) => {
|
|
3432
4653
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
3433
4654
|
const workspaceId = await loadWorkspaceId({
|
|
@@ -3473,13 +4694,13 @@ const domainCommand = defineCommand({
|
|
|
3473
4694
|
const getCommand = defineAppCommand({
|
|
3474
4695
|
name: "get",
|
|
3475
4696
|
description: "Get details of a specific static website.",
|
|
3476
|
-
args: z.
|
|
4697
|
+
args: z.strictObject({
|
|
3477
4698
|
...workspaceArgs,
|
|
3478
4699
|
name: arg(z.string(), {
|
|
3479
4700
|
positional: true,
|
|
3480
4701
|
description: "Static website name"
|
|
3481
4702
|
})
|
|
3482
|
-
})
|
|
4703
|
+
}),
|
|
3483
4704
|
run: async (args) => {
|
|
3484
4705
|
const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
|
|
3485
4706
|
const workspaceId = await loadWorkspaceId({
|
|
@@ -3541,10 +4762,10 @@ async function listStaticWebsites(options) {
|
|
|
3541
4762
|
const listCommand$2 = defineAppCommand({
|
|
3542
4763
|
name: "list",
|
|
3543
4764
|
description: "List all static websites in a workspace.",
|
|
3544
|
-
args: z.
|
|
4765
|
+
args: z.strictObject({
|
|
3545
4766
|
...workspaceArgs,
|
|
3546
4767
|
...paginationArgs()
|
|
3547
|
-
})
|
|
4768
|
+
}),
|
|
3548
4769
|
run: async (args) => {
|
|
3549
4770
|
const jsonOutput = logger.jsonMode;
|
|
3550
4771
|
const websites = await listStaticWebsites({
|
|
@@ -3806,7 +5027,7 @@ function buildTailorDbErdSchema(options) {
|
|
|
3806
5027
|
namespace: namespaceData.namespace,
|
|
3807
5028
|
source: options.source ?? "local",
|
|
3808
5029
|
cleanRoom: {
|
|
3809
|
-
implementation: "tailor
|
|
5030
|
+
implementation: "tailor",
|
|
3810
5031
|
notes: CLEAN_ROOM_NOTES
|
|
3811
5032
|
},
|
|
3812
5033
|
tables,
|
|
@@ -3848,6 +5069,9 @@ async function initErdDeployContext(args) {
|
|
|
3848
5069
|
const VIEWER_ASSETS_DIR = "erd-viewer-assets";
|
|
3849
5070
|
const STYLES_LINK = "<link rel=\"stylesheet\" href=\"./styles.css\" />";
|
|
3850
5071
|
const APP_SCRIPT = "<script src=\"./app.js\" type=\"module\"><\/script>";
|
|
5072
|
+
function escapeHtml(value) {
|
|
5073
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
5074
|
+
}
|
|
3851
5075
|
function assetDirCandidates() {
|
|
3852
5076
|
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
3853
5077
|
return [
|
|
@@ -3870,8 +5094,8 @@ function resolveViewerAssetsDir() {
|
|
|
3870
5094
|
* are inlined as separately extractable blocks: a `<style>` element, a
|
|
3871
5095
|
* `<script type="module">`, and a `<script type="application/json"
|
|
3872
5096
|
* id="erd-schema">` data block. This renders without any sibling asset files
|
|
3873
|
-
* and lets external tooling (
|
|
3874
|
-
* `JSON.parse`.
|
|
5097
|
+
* and lets external tooling (including the ERD diff command) pull out the
|
|
5098
|
+
* schema via `JSON.parse`.
|
|
3875
5099
|
* @param options - Viewer build options.
|
|
3876
5100
|
* @returns The self-contained HTML document.
|
|
3877
5101
|
*/
|
|
@@ -3882,8 +5106,10 @@ function buildViewerHtml(options) {
|
|
|
3882
5106
|
const appJs = fs$1.readFileSync(path.join(assetsDir, "app.js"), "utf8");
|
|
3883
5107
|
if (!html.includes(STYLES_LINK) || !html.includes(APP_SCRIPT)) throw new Error("ERD viewer index.html is missing expected asset references for inlining.");
|
|
3884
5108
|
const embedScript = `<script type="application/json" id="erd-schema">${JSON.stringify(options.schema).replaceAll("<", "\\u003c")}<\/script>`;
|
|
5109
|
+
const currentSchemaScript = options.currentSchema === void 0 ? "" : `\n <script type="application/json" id="erd-current-schema">${JSON.stringify(options.currentSchema).replaceAll("<", "\\u003c")}<\/script>`;
|
|
5110
|
+
const diffScript = options.diff === void 0 ? "" : `\n <script type="application/json" id="erd-diff">${JSON.stringify(options.diff).replaceAll("<", "\\u003c")}<\/script>`;
|
|
3885
5111
|
const inlineScript = `<script type="module">\n${appJs.replace(/<\/script/gi, "<\\/script")}\n<\/script>`;
|
|
3886
|
-
return html.replace(STYLES_LINK, `<style>\n${css}\n</style>`).replace(APP_SCRIPT, `${embedScript}\n ${inlineScript}`);
|
|
5112
|
+
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}`);
|
|
3887
5113
|
}
|
|
3888
5114
|
/**
|
|
3889
5115
|
* Write the self-contained TailorDB ERD viewer to `<distDir>/index.html`.
|
|
@@ -3900,7 +5126,7 @@ function writeViewerDist(options) {
|
|
|
3900
5126
|
|
|
3901
5127
|
//#endregion
|
|
3902
5128
|
//#region src/cli/commands/tailordb/erd/export.ts
|
|
3903
|
-
const DEFAULT_ERD_BASE_DIR$1 = ".tailor
|
|
5129
|
+
const DEFAULT_ERD_BASE_DIR$1 = ".tailor/erd";
|
|
3904
5130
|
function getErdSite(context, namespace) {
|
|
3905
5131
|
const dbConfig = context.config.db?.[namespace];
|
|
3906
5132
|
if (!dbConfig || "external" in dbConfig) return;
|
|
@@ -3980,7 +5206,7 @@ function prepareErdBuildsFromContext(options) {
|
|
|
3980
5206
|
const erdExportCommand = defineAppCommand({
|
|
3981
5207
|
name: "export",
|
|
3982
5208
|
description: "Export TailorDB ERD static viewer from local TailorDB schema.",
|
|
3983
|
-
args: z.
|
|
5209
|
+
args: z.strictObject({
|
|
3984
5210
|
...configArg,
|
|
3985
5211
|
namespace: arg(z.string().optional(), {
|
|
3986
5212
|
alias: "n",
|
|
@@ -3991,7 +5217,7 @@ const erdExportCommand = defineAppCommand({
|
|
|
3991
5217
|
description: "Output directory path for TailorDB ERD viewer files (writes to `<outputDir>/<namespace>/dist`)",
|
|
3992
5218
|
completion: { type: "directory" }
|
|
3993
5219
|
})
|
|
3994
|
-
})
|
|
5220
|
+
}),
|
|
3995
5221
|
run: async (args) => {
|
|
3996
5222
|
initErdCommand();
|
|
3997
5223
|
const results = await prepareErdBuilds({
|
|
@@ -4016,13 +5242,13 @@ const erdExportCommand = defineAppCommand({
|
|
|
4016
5242
|
const erdDeployCommand = defineAppCommand({
|
|
4017
5243
|
name: "deploy",
|
|
4018
5244
|
description: "Deploy ERD static website for TailorDB namespace(s).",
|
|
4019
|
-
args: z.
|
|
5245
|
+
args: z.strictObject({
|
|
4020
5246
|
...deploymentArgs,
|
|
4021
5247
|
namespace: arg(z.string().optional(), {
|
|
4022
5248
|
alias: "n",
|
|
4023
5249
|
description: "TailorDB namespace name (optional - deploys all namespaces with erdSite if omitted)"
|
|
4024
5250
|
})
|
|
4025
|
-
})
|
|
5251
|
+
}),
|
|
4026
5252
|
run: async (args) => {
|
|
4027
5253
|
await assertWritable({ profile: args.profile });
|
|
4028
5254
|
const { client, workspaceId } = await initErdDeployContext(args);
|
|
@@ -4053,13 +5279,369 @@ const erdDeployCommand = defineAppCommand({
|
|
|
4053
5279
|
}
|
|
4054
5280
|
});
|
|
4055
5281
|
|
|
5282
|
+
//#endregion
|
|
5283
|
+
//#region src/cli/commands/tailordb/erd/diff.ts
|
|
5284
|
+
const SCHEMA_BLOCK_PATTERN = /<script type="application\/json" id="erd-schema">([\s\S]*?)<\/script>/;
|
|
5285
|
+
function stableValue(value) {
|
|
5286
|
+
if (Array.isArray(value)) return value.map((item) => stableValue(item));
|
|
5287
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, stableValue(item)]));
|
|
5288
|
+
return value;
|
|
5289
|
+
}
|
|
5290
|
+
function stableJson(value) {
|
|
5291
|
+
return JSON.stringify(stableValue(value));
|
|
5292
|
+
}
|
|
5293
|
+
function changedFields(base, head) {
|
|
5294
|
+
return [.../* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(head)])].toSorted((a, b) => a.localeCompare(b)).filter((key) => stableJson(base[key]) !== stableJson(head[key]));
|
|
5295
|
+
}
|
|
5296
|
+
function detailForChangedFields(fields) {
|
|
5297
|
+
return `Changed fields: ${fields.join(", ")}`;
|
|
5298
|
+
}
|
|
5299
|
+
function mapByName(items) {
|
|
5300
|
+
return new Map(items.map((item) => [item.name, item]));
|
|
5301
|
+
}
|
|
5302
|
+
function compareCommonNamed(baseItems, headItems, addChange) {
|
|
5303
|
+
const baseByName = mapByName(baseItems);
|
|
5304
|
+
const headByName = mapByName(headItems);
|
|
5305
|
+
const commonNames = [...baseByName.keys()].filter((name) => headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
|
|
5306
|
+
for (const name of commonNames) {
|
|
5307
|
+
const base = baseByName.get(name);
|
|
5308
|
+
const head = headByName.get(name);
|
|
5309
|
+
if (!base || !head) continue;
|
|
5310
|
+
const fields = changedFields(base, head);
|
|
5311
|
+
if (fields.length > 0) addChange(name, fields);
|
|
5312
|
+
}
|
|
5313
|
+
}
|
|
5314
|
+
function removedNames(baseItems, headItems) {
|
|
5315
|
+
const headByName = mapByName(headItems);
|
|
5316
|
+
return baseItems.map((item) => item.name).filter((name) => !headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
|
|
5317
|
+
}
|
|
5318
|
+
function addedNames(baseItems, headItems) {
|
|
5319
|
+
const baseByName = mapByName(baseItems);
|
|
5320
|
+
return headItems.map((item) => item.name).filter((name) => !baseByName.has(name)).toSorted((a, b) => a.localeCompare(b));
|
|
5321
|
+
}
|
|
5322
|
+
function tableMetadata(table) {
|
|
5323
|
+
return {
|
|
5324
|
+
backwardRelationships: table.backwardRelationships,
|
|
5325
|
+
description: table.description,
|
|
5326
|
+
forwardRelationships: table.forwardRelationships,
|
|
5327
|
+
pluralForm: table.pluralForm,
|
|
5328
|
+
source: table.source
|
|
5329
|
+
};
|
|
5330
|
+
}
|
|
5331
|
+
function pushChange(changes, change) {
|
|
5332
|
+
changes.push(change);
|
|
5333
|
+
}
|
|
5334
|
+
function diffColumns(changes, tableName, baseColumns, headColumns) {
|
|
5335
|
+
compareCommonNamed(baseColumns, headColumns, (name, fields) => {
|
|
5336
|
+
pushChange(changes, {
|
|
5337
|
+
action: "changed",
|
|
5338
|
+
entity: "column",
|
|
5339
|
+
path: `${tableName}.${name}`,
|
|
5340
|
+
detail: detailForChangedFields(fields)
|
|
5341
|
+
});
|
|
5342
|
+
});
|
|
5343
|
+
for (const name of removedNames(baseColumns, headColumns)) pushChange(changes, {
|
|
5344
|
+
action: "removed",
|
|
5345
|
+
entity: "column",
|
|
5346
|
+
path: `${tableName}.${name}`,
|
|
5347
|
+
detail: "Column removed"
|
|
5348
|
+
});
|
|
5349
|
+
for (const name of addedNames(baseColumns, headColumns)) pushChange(changes, {
|
|
5350
|
+
action: "added",
|
|
5351
|
+
entity: "column",
|
|
5352
|
+
path: `${tableName}.${name}`,
|
|
5353
|
+
detail: "Column added"
|
|
5354
|
+
});
|
|
5355
|
+
}
|
|
5356
|
+
function diffIndexes(changes, tableName, baseIndexes, headIndexes) {
|
|
5357
|
+
compareCommonNamed(baseIndexes, headIndexes, (name, fields) => {
|
|
5358
|
+
pushChange(changes, {
|
|
5359
|
+
action: "changed",
|
|
5360
|
+
entity: "index",
|
|
5361
|
+
path: `${tableName}.${name}`,
|
|
5362
|
+
detail: detailForChangedFields(fields)
|
|
5363
|
+
});
|
|
5364
|
+
});
|
|
5365
|
+
for (const name of removedNames(baseIndexes, headIndexes)) pushChange(changes, {
|
|
5366
|
+
action: "removed",
|
|
5367
|
+
entity: "index",
|
|
5368
|
+
path: `${tableName}.${name}`,
|
|
5369
|
+
detail: "Index removed"
|
|
5370
|
+
});
|
|
5371
|
+
for (const name of addedNames(baseIndexes, headIndexes)) pushChange(changes, {
|
|
5372
|
+
action: "added",
|
|
5373
|
+
entity: "index",
|
|
5374
|
+
path: `${tableName}.${name}`,
|
|
5375
|
+
detail: "Index added"
|
|
5376
|
+
});
|
|
5377
|
+
}
|
|
5378
|
+
function diffTables(changes, baseTables, headTables) {
|
|
5379
|
+
const baseByName = mapByName(baseTables);
|
|
5380
|
+
const headByName = mapByName(headTables);
|
|
5381
|
+
for (const name of removedNames(baseTables, headTables)) pushChange(changes, {
|
|
5382
|
+
action: "removed",
|
|
5383
|
+
entity: "table",
|
|
5384
|
+
path: name,
|
|
5385
|
+
detail: "Table removed"
|
|
5386
|
+
});
|
|
5387
|
+
for (const name of addedNames(baseTables, headTables)) pushChange(changes, {
|
|
5388
|
+
action: "added",
|
|
5389
|
+
entity: "table",
|
|
5390
|
+
path: name,
|
|
5391
|
+
detail: "Table added"
|
|
5392
|
+
});
|
|
5393
|
+
const commonNames = [...baseByName.keys()].filter((name) => headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
|
|
5394
|
+
for (const name of commonNames) {
|
|
5395
|
+
const base = baseByName.get(name);
|
|
5396
|
+
const head = headByName.get(name);
|
|
5397
|
+
if (!base || !head) continue;
|
|
5398
|
+
const tableFields = changedFields(tableMetadata(base), tableMetadata(head));
|
|
5399
|
+
if (tableFields.length > 0) pushChange(changes, {
|
|
5400
|
+
action: "changed",
|
|
5401
|
+
entity: "table",
|
|
5402
|
+
path: name,
|
|
5403
|
+
detail: detailForChangedFields(tableFields)
|
|
5404
|
+
});
|
|
5405
|
+
diffColumns(changes, name, base.columns, head.columns);
|
|
5406
|
+
diffIndexes(changes, name, base.indexes, head.indexes);
|
|
5407
|
+
}
|
|
5408
|
+
}
|
|
5409
|
+
function diffRelations(changes, baseRelations, headRelations) {
|
|
5410
|
+
compareCommonNamed(baseRelations, headRelations, (name, fields) => {
|
|
5411
|
+
pushChange(changes, {
|
|
5412
|
+
action: "changed",
|
|
5413
|
+
entity: "relation",
|
|
5414
|
+
path: name,
|
|
5415
|
+
detail: detailForChangedFields(fields)
|
|
5416
|
+
});
|
|
5417
|
+
});
|
|
5418
|
+
for (const name of removedNames(baseRelations, headRelations)) pushChange(changes, {
|
|
5419
|
+
action: "removed",
|
|
5420
|
+
entity: "relation",
|
|
5421
|
+
path: name,
|
|
5422
|
+
detail: "Relation removed"
|
|
5423
|
+
});
|
|
5424
|
+
for (const name of addedNames(baseRelations, headRelations)) pushChange(changes, {
|
|
5425
|
+
action: "added",
|
|
5426
|
+
entity: "relation",
|
|
5427
|
+
path: name,
|
|
5428
|
+
detail: "Relation added"
|
|
5429
|
+
});
|
|
5430
|
+
}
|
|
5431
|
+
function summarize(changes) {
|
|
5432
|
+
return {
|
|
5433
|
+
added: changes.filter((change) => change.action === "added").length,
|
|
5434
|
+
changed: changes.filter((change) => change.action === "changed").length,
|
|
5435
|
+
removed: changes.filter((change) => change.action === "removed").length
|
|
5436
|
+
};
|
|
5437
|
+
}
|
|
5438
|
+
function extractEmbeddedErdSchema(html) {
|
|
5439
|
+
const match = SCHEMA_BLOCK_PATTERN.exec(html);
|
|
5440
|
+
if (!match?.[1]) throw new Error("ERD schema block not found.");
|
|
5441
|
+
try {
|
|
5442
|
+
return JSON.parse(match[1]);
|
|
5443
|
+
} catch (error) {
|
|
5444
|
+
throw new Error(`Failed to parse ERD schema block: ${String(error)}`, { cause: error });
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
function createEmptyErdSchema(options) {
|
|
5448
|
+
return {
|
|
5449
|
+
version: 1,
|
|
5450
|
+
namespace: options.namespace,
|
|
5451
|
+
generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
5452
|
+
revision: options.revision,
|
|
5453
|
+
source: "local",
|
|
5454
|
+
cleanRoom: {
|
|
5455
|
+
implementation: "tailor",
|
|
5456
|
+
notes: ["Synthetic empty schema used for ERD diff generation."]
|
|
5457
|
+
},
|
|
5458
|
+
tables: [],
|
|
5459
|
+
relations: []
|
|
5460
|
+
};
|
|
5461
|
+
}
|
|
5462
|
+
function buildErdSchemaDiff(options) {
|
|
5463
|
+
const { base, head } = options;
|
|
5464
|
+
if (base.namespace !== head.namespace) throw new Error(`Cannot diff ERD schemas from different namespaces: ${base.namespace}, ${head.namespace}`);
|
|
5465
|
+
const changes = [];
|
|
5466
|
+
diffTables(changes, base.tables, head.tables);
|
|
5467
|
+
diffRelations(changes, base.relations, head.relations);
|
|
5468
|
+
const summary = summarize(changes);
|
|
5469
|
+
return {
|
|
5470
|
+
namespace: head.namespace,
|
|
5471
|
+
baseRevision: base.revision,
|
|
5472
|
+
headRevision: head.revision,
|
|
5473
|
+
changed: changes.length > 0,
|
|
5474
|
+
summary,
|
|
5475
|
+
changes
|
|
5476
|
+
};
|
|
5477
|
+
}
|
|
5478
|
+
function mergeNamedByHead(baseItems, headItems) {
|
|
5479
|
+
const headByName = mapByName(headItems);
|
|
5480
|
+
const removedItems = baseItems.filter((item) => !headByName.has(item.name)).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
5481
|
+
return [...headItems, ...removedItems];
|
|
5482
|
+
}
|
|
5483
|
+
function mergeDiffViewerTable(baseTable, headTable) {
|
|
5484
|
+
return {
|
|
5485
|
+
...headTable,
|
|
5486
|
+
columns: mergeNamedByHead(baseTable.columns, headTable.columns),
|
|
5487
|
+
indexes: mergeNamedByHead(baseTable.indexes, headTable.indexes)
|
|
5488
|
+
};
|
|
5489
|
+
}
|
|
5490
|
+
function mergeDiffViewerTables(baseTables, headTables) {
|
|
5491
|
+
const baseByName = mapByName(baseTables);
|
|
5492
|
+
const headByName = mapByName(headTables);
|
|
5493
|
+
const merged = headTables.map((headTable) => {
|
|
5494
|
+
const baseTable = baseByName.get(headTable.name);
|
|
5495
|
+
return baseTable ? mergeDiffViewerTable(baseTable, headTable) : headTable;
|
|
5496
|
+
});
|
|
5497
|
+
const removedTables = baseTables.filter((table) => !headByName.has(table.name)).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
5498
|
+
return [...merged, ...removedTables];
|
|
5499
|
+
}
|
|
5500
|
+
/**
|
|
5501
|
+
* Build the schema rendered by the diff viewer. The ordinary ERD viewer only
|
|
5502
|
+
* knows how to draw objects present in its schema, so the diff schema keeps
|
|
5503
|
+
* base-only tables, columns, indexes, and relations visible for removal
|
|
5504
|
+
* highlighting while preferring head-side metadata for unchanged objects.
|
|
5505
|
+
* @param options - Base and head schemas to merge for diff rendering.
|
|
5506
|
+
* @returns A TailorDB ERD schema suitable for the visual diff viewer.
|
|
5507
|
+
*/
|
|
5508
|
+
function buildErdDiffViewerSchema(options) {
|
|
5509
|
+
const { base, head } = options;
|
|
5510
|
+
if (base.namespace !== head.namespace) throw new Error(`Cannot diff ERD schemas from different namespaces: ${base.namespace}, ${head.namespace}`);
|
|
5511
|
+
return {
|
|
5512
|
+
...head,
|
|
5513
|
+
tables: mergeDiffViewerTables(base.tables, head.tables),
|
|
5514
|
+
relations: mergeNamedByHead(base.relations, head.relations)
|
|
5515
|
+
};
|
|
5516
|
+
}
|
|
5517
|
+
function renderErdDiffHtml(options) {
|
|
5518
|
+
return buildViewerHtml({
|
|
5519
|
+
schema: options.schema,
|
|
5520
|
+
currentSchema: options.currentSchema,
|
|
5521
|
+
diff: options.diff,
|
|
5522
|
+
title: `TailorDB ERD diff - ${options.diff.namespace}`
|
|
5523
|
+
});
|
|
5524
|
+
}
|
|
5525
|
+
|
|
5526
|
+
//#endregion
|
|
5527
|
+
//#region src/cli/commands/tailordb/erd/diff-command.ts
|
|
5528
|
+
function readSchema(filePath) {
|
|
5529
|
+
if (!filePath) return void 0;
|
|
5530
|
+
return extractEmbeddedErdSchema(fs$1.readFileSync(filePath, "utf8"));
|
|
5531
|
+
}
|
|
5532
|
+
function resolveNamespace(options) {
|
|
5533
|
+
const namespace = options.namespace ?? options.head?.namespace ?? options.base?.namespace;
|
|
5534
|
+
if (!namespace) throw new Error("Missing --namespace when one side of the ERD diff is omitted.");
|
|
5535
|
+
if (options.base && options.base.namespace !== namespace) throw new Error(`Base ERD namespace "${options.base.namespace}" does not match "${namespace}".`);
|
|
5536
|
+
if (options.head && options.head.namespace !== namespace) throw new Error(`Head ERD namespace "${options.head.namespace}" does not match "${namespace}".`);
|
|
5537
|
+
return namespace;
|
|
5538
|
+
}
|
|
5539
|
+
function writeFile(filePath, content) {
|
|
5540
|
+
fs$1.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
5541
|
+
fs$1.writeFileSync(filePath, content, "utf8");
|
|
5542
|
+
}
|
|
5543
|
+
function writeErdDiff(options) {
|
|
5544
|
+
const base = readSchema(options.baseHtml);
|
|
5545
|
+
const head = readSchema(options.headHtml);
|
|
5546
|
+
if (!base && !head) throw new Error("At least one of --base-html or --head-html is required.");
|
|
5547
|
+
const namespace = resolveNamespace({
|
|
5548
|
+
namespace: options.namespace,
|
|
5549
|
+
base,
|
|
5550
|
+
head
|
|
5551
|
+
});
|
|
5552
|
+
const baseSchema = base ?? createEmptyErdSchema({
|
|
5553
|
+
namespace,
|
|
5554
|
+
revision: "missing-base"
|
|
5555
|
+
});
|
|
5556
|
+
const headSchema = head ?? createEmptyErdSchema({
|
|
5557
|
+
namespace,
|
|
5558
|
+
revision: "missing-head"
|
|
5559
|
+
});
|
|
5560
|
+
const diff = buildErdSchemaDiff({
|
|
5561
|
+
base: baseSchema,
|
|
5562
|
+
head: headSchema
|
|
5563
|
+
});
|
|
5564
|
+
const viewerSchema = buildErdDiffViewerSchema({
|
|
5565
|
+
base: baseSchema,
|
|
5566
|
+
head: headSchema
|
|
5567
|
+
});
|
|
5568
|
+
writeFile(options.outputHtml, renderErdDiffHtml({
|
|
5569
|
+
schema: viewerSchema,
|
|
5570
|
+
currentSchema: headSchema,
|
|
5571
|
+
diff
|
|
5572
|
+
}));
|
|
5573
|
+
if (options.outputJson) writeFile(options.outputJson, `${JSON.stringify(diff, null, 2)}\n`);
|
|
5574
|
+
return {
|
|
5575
|
+
namespace,
|
|
5576
|
+
outputHtml: options.outputHtml,
|
|
5577
|
+
outputJson: options.outputJson,
|
|
5578
|
+
diff
|
|
5579
|
+
};
|
|
5580
|
+
}
|
|
5581
|
+
const erdDiffCommand = defineAppCommand({
|
|
5582
|
+
name: "diff",
|
|
5583
|
+
description: "Render TailorDB ERD schema diff HTML from exported ERD viewers.",
|
|
5584
|
+
args: z.strictObject({
|
|
5585
|
+
"base-html": arg(z.string().optional(), {
|
|
5586
|
+
description: "Base ERD viewer HTML file",
|
|
5587
|
+
completion: {
|
|
5588
|
+
type: "file",
|
|
5589
|
+
matcher: [".html"]
|
|
5590
|
+
}
|
|
5591
|
+
}),
|
|
5592
|
+
"head-html": arg(z.string().optional(), {
|
|
5593
|
+
description: "Head ERD viewer HTML file",
|
|
5594
|
+
completion: {
|
|
5595
|
+
type: "file",
|
|
5596
|
+
matcher: [".html"]
|
|
5597
|
+
}
|
|
5598
|
+
}),
|
|
5599
|
+
namespace: arg(z.string().optional(), {
|
|
5600
|
+
alias: "n",
|
|
5601
|
+
description: "TailorDB namespace name (defaults to the provided ERD schema namespace)"
|
|
5602
|
+
}),
|
|
5603
|
+
output: arg(z.string().min(1), {
|
|
5604
|
+
alias: "o",
|
|
5605
|
+
description: "Output ERD diff HTML file",
|
|
5606
|
+
completion: {
|
|
5607
|
+
type: "file",
|
|
5608
|
+
matcher: [".html"]
|
|
5609
|
+
}
|
|
5610
|
+
}),
|
|
5611
|
+
"output-json": arg(z.string().optional(), {
|
|
5612
|
+
description: "Optional output JSON file for the computed diff",
|
|
5613
|
+
completion: {
|
|
5614
|
+
type: "file",
|
|
5615
|
+
matcher: [".json"]
|
|
5616
|
+
}
|
|
5617
|
+
})
|
|
5618
|
+
}),
|
|
5619
|
+
run: (args) => {
|
|
5620
|
+
initErdCommand();
|
|
5621
|
+
const result = writeErdDiff({
|
|
5622
|
+
baseHtml: args["base-html"],
|
|
5623
|
+
headHtml: args["head-html"],
|
|
5624
|
+
namespace: args.namespace,
|
|
5625
|
+
outputHtml: args.output,
|
|
5626
|
+
outputJson: args["output-json"]
|
|
5627
|
+
});
|
|
5628
|
+
if (args.json) logger.out({
|
|
5629
|
+
namespace: result.namespace,
|
|
5630
|
+
outputHtml: result.outputHtml,
|
|
5631
|
+
outputJson: result.outputJson,
|
|
5632
|
+
summary: result.diff.summary
|
|
5633
|
+
});
|
|
5634
|
+
else logger.success(`Wrote ERD diff to ${path.relative(process.cwd(), result.outputHtml)}`);
|
|
5635
|
+
}
|
|
5636
|
+
});
|
|
5637
|
+
|
|
4056
5638
|
//#endregion
|
|
4057
5639
|
//#region src/cli/commands/tailordb/erd/serve.ts
|
|
4058
|
-
const DEFAULT_ERD_BASE_DIR = ".tailor
|
|
5640
|
+
const DEFAULT_ERD_BASE_DIR = ".tailor/erd";
|
|
4059
5641
|
const LOCAL_HOST = "127.0.0.1";
|
|
4060
5642
|
const GLOB_CHARS = /[*?[\]{}()!+@]/;
|
|
4061
5643
|
function formatServeCommand(namespace) {
|
|
4062
|
-
return `tailor
|
|
5644
|
+
return `tailor tailordb erd serve --namespace ${namespace}`;
|
|
4063
5645
|
}
|
|
4064
5646
|
function getCacheControl(filePath) {
|
|
4065
5647
|
return filePath.endsWith(".html") || filePath.endsWith(".json") ? "no-cache" : "public, max-age=3600";
|
|
@@ -4353,7 +5935,7 @@ async function waitForShutdown(server, watcher) {
|
|
|
4353
5935
|
const erdServeCommand = defineAppCommand({
|
|
4354
5936
|
name: "serve",
|
|
4355
5937
|
description: "Generate and serve TailorDB ERD locally with watch reload. (beta)",
|
|
4356
|
-
args: z.
|
|
5938
|
+
args: z.strictObject({
|
|
4357
5939
|
...configArg,
|
|
4358
5940
|
namespace: arg(z.string().optional(), {
|
|
4359
5941
|
alias: "n",
|
|
@@ -4361,7 +5943,7 @@ const erdServeCommand = defineAppCommand({
|
|
|
4361
5943
|
}),
|
|
4362
5944
|
port: arg(z.coerce.number().int().min(0).max(65535).default(0), { description: "Local server port (0 selects a free port)" }),
|
|
4363
5945
|
open: arg(z.boolean().default(false), { description: "Open the ERD viewer in the default browser" })
|
|
4364
|
-
})
|
|
5946
|
+
}),
|
|
4365
5947
|
run: async (args) => {
|
|
4366
5948
|
initErdCommand();
|
|
4367
5949
|
const outputDir = path.resolve(process.cwd(), DEFAULT_ERD_BASE_DIR);
|
|
@@ -4413,6 +5995,7 @@ const erdCommand = defineCommand({
|
|
|
4413
5995
|
description: "Generate TailorDB ERD viewer artifacts from local TailorDB schema. (beta)",
|
|
4414
5996
|
subCommands: {
|
|
4415
5997
|
export: erdExportCommand,
|
|
5998
|
+
diff: erdDiffCommand,
|
|
4416
5999
|
serve: erdServeCommand,
|
|
4417
6000
|
deploy: erdDeployCommand
|
|
4418
6001
|
}
|
|
@@ -4456,7 +6039,7 @@ async function script(options) {
|
|
|
4456
6039
|
logger.info(` DB types: ${getMigrationFilePath(migrationsDir, migrationNumber, "db")}`);
|
|
4457
6040
|
logger.newline();
|
|
4458
6041
|
logger.log("Edit the script to implement your data migration logic.");
|
|
4459
|
-
logger.log("It will be executed by 'tailor
|
|
6042
|
+
logger.log("It will be executed by 'tailor deploy' between Pre and Post phases.");
|
|
4460
6043
|
const editor = getConfiguredEditorCommand();
|
|
4461
6044
|
if (!editor) return;
|
|
4462
6045
|
logger.newline();
|
|
@@ -4481,7 +6064,7 @@ function resolveTargetNamespace(namespacesWithMigrations, requested) {
|
|
|
4481
6064
|
const scriptCommand = defineAppCommand({
|
|
4482
6065
|
name: "script",
|
|
4483
6066
|
description: "Add a migration script (migrate.ts) template to an existing migration directory.",
|
|
4484
|
-
args: z.
|
|
6067
|
+
args: z.strictObject({
|
|
4485
6068
|
...configArg,
|
|
4486
6069
|
number: arg(z.string(), {
|
|
4487
6070
|
positional: true,
|
|
@@ -4491,7 +6074,7 @@ const scriptCommand = defineAppCommand({
|
|
|
4491
6074
|
alias: "n",
|
|
4492
6075
|
description: "Target TailorDB namespace (required if multiple namespaces exist)"
|
|
4493
6076
|
})
|
|
4494
|
-
})
|
|
6077
|
+
}),
|
|
4495
6078
|
run: async (args) => {
|
|
4496
6079
|
await script({
|
|
4497
6080
|
configPath: args.config,
|
|
@@ -4577,7 +6160,7 @@ async function set(options) {
|
|
|
4577
6160
|
const setCommand = defineAppCommand({
|
|
4578
6161
|
name: "set",
|
|
4579
6162
|
description: "Set migration checkpoint to a specific number.",
|
|
4580
|
-
args: z.
|
|
6163
|
+
args: z.strictObject({
|
|
4581
6164
|
...deploymentArgs,
|
|
4582
6165
|
...confirmationArgs,
|
|
4583
6166
|
number: arg(z.string(), {
|
|
@@ -4588,7 +6171,7 @@ const setCommand = defineAppCommand({
|
|
|
4588
6171
|
alias: "n",
|
|
4589
6172
|
description: "Target TailorDB namespace (required if multiple namespaces exist)"
|
|
4590
6173
|
})
|
|
4591
|
-
})
|
|
6174
|
+
}),
|
|
4592
6175
|
run: async (args) => {
|
|
4593
6176
|
await assertWritable({ profile: args.profile });
|
|
4594
6177
|
await set({
|
|
@@ -4676,13 +6259,13 @@ async function status(options) {
|
|
|
4676
6259
|
const statusCommand = defineAppCommand({
|
|
4677
6260
|
name: "status",
|
|
4678
6261
|
description: "Show the current migration status for TailorDB namespaces, including applied and pending migrations.",
|
|
4679
|
-
args: z.
|
|
6262
|
+
args: z.strictObject({
|
|
4680
6263
|
...deploymentArgs,
|
|
4681
6264
|
namespace: arg(z.string().optional(), {
|
|
4682
6265
|
alias: "n",
|
|
4683
6266
|
description: "Target TailorDB namespace (shows all namespaces if not specified)"
|
|
4684
6267
|
})
|
|
4685
|
-
})
|
|
6268
|
+
}),
|
|
4686
6269
|
run: async (args) => {
|
|
4687
6270
|
await status({
|
|
4688
6271
|
configPath: args.config,
|
|
@@ -4697,19 +6280,14 @@ const statusCommand = defineAppCommand({
|
|
|
4697
6280
|
//#endregion
|
|
4698
6281
|
//#region src/cli/commands/tailordb/migrate/sync.ts
|
|
4699
6282
|
async function fetchRemoteGqlPermissions(client, workspaceId, namespace) {
|
|
4700
|
-
return
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
return [permissions, nextPageToken];
|
|
4709
|
-
} catch (error) {
|
|
4710
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
4711
|
-
throw error;
|
|
4712
|
-
}
|
|
6283
|
+
return fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
6284
|
+
const { permissions, nextPageToken } = await client.listTailorDBGQLPermissions({
|
|
6285
|
+
workspaceId,
|
|
6286
|
+
namespaceName: namespace,
|
|
6287
|
+
pageToken,
|
|
6288
|
+
pageSize: maxPageSize
|
|
6289
|
+
});
|
|
6290
|
+
return [permissions, nextPageToken];
|
|
4713
6291
|
});
|
|
4714
6292
|
}
|
|
4715
6293
|
async function fetchRemoteTypes(client, workspaceId, namespace) {
|
|
@@ -4749,7 +6327,7 @@ async function fetchRemoteTypes(client, workspaceId, namespace) {
|
|
|
4749
6327
|
async function assertMigrationsReproduceLocalTypes(loaded, target) {
|
|
4750
6328
|
const { config, plugins } = loaded;
|
|
4751
6329
|
const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
|
|
4752
|
-
const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-
|
|
6330
|
+
const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-D2hSK1ZL.mjs");
|
|
4753
6331
|
const application = defineApplication({
|
|
4754
6332
|
config,
|
|
4755
6333
|
pluginManager
|
|
@@ -4761,7 +6339,7 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
|
|
|
4761
6339
|
await service.processNamespacePlugins();
|
|
4762
6340
|
}
|
|
4763
6341
|
const pluginExecutorFiles = generatePluginFilesIfNeeded(pluginManager, application.tailorDBServices, config.path);
|
|
4764
|
-
const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-
|
|
6342
|
+
const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-CkIwEctX.mjs")).createExecutorService({ config: { files: [] } }) : void 0);
|
|
4765
6343
|
await executorService?.loadExecutors();
|
|
4766
6344
|
if (pluginExecutorFiles.length > 0) await executorService?.loadPluginExecutorFiles([...pluginExecutorFiles]);
|
|
4767
6345
|
const executorUsedTypes = /* @__PURE__ */ new Set();
|
|
@@ -4779,7 +6357,7 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
|
|
|
4779
6357
|
logger.newline();
|
|
4780
6358
|
logger.info("This usually means one of the following:");
|
|
4781
6359
|
logger.info(" - Migration files were edited and replaying them no longer matches the type definitions — fix the migration files.", { mode: "plain" });
|
|
4782
|
-
logger.info(" - Type definitions changed without a new migration — run 'tailor
|
|
6360
|
+
logger.info(" - Type definitions changed without a new migration — run 'tailor tailordb migration generate' first.", { mode: "plain" });
|
|
4783
6361
|
logger.newline();
|
|
4784
6362
|
throw new Error("Refusing to sync: the migration history must reproduce the current local schema before it can be applied to the remote.");
|
|
4785
6363
|
}
|
|
@@ -4796,21 +6374,15 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
|
|
|
4796
6374
|
* @returns Existing labels and the parsed current migration number
|
|
4797
6375
|
*/
|
|
4798
6376
|
async function fetchRemoteMigrationState(client, trn) {
|
|
4799
|
-
|
|
6377
|
+
const labels = (await getOrNull(async () => {
|
|
4800
6378
|
const { metadata } = await client.getMetadata({ trn });
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
}
|
|
4808
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return {
|
|
4809
|
-
labels: {},
|
|
4810
|
-
current: null
|
|
4811
|
-
};
|
|
4812
|
-
throw error;
|
|
4813
|
-
}
|
|
6379
|
+
return metadata;
|
|
6380
|
+
}))?.labels ?? {};
|
|
6381
|
+
const label = labels[MIGRATION_LABEL_KEY];
|
|
6382
|
+
return {
|
|
6383
|
+
labels,
|
|
6384
|
+
current: label ? parseMigrationLabelNumber(label) : null
|
|
6385
|
+
};
|
|
4814
6386
|
}
|
|
4815
6387
|
function selectTargetNamespace(namespacesWithMigrations, requested) {
|
|
4816
6388
|
if (namespacesWithMigrations.length === 0) throw new Error("No TailorDB services with migrations configuration found");
|
|
@@ -4921,7 +6493,7 @@ async function sync(options) {
|
|
|
4921
6493
|
tailordbType
|
|
4922
6494
|
}))]);
|
|
4923
6495
|
} catch (error) {
|
|
4924
|
-
handleOptionalToRequiredError(error, ["The target snapshot marks a field as required, but existing remote records have no value for it.", "Populate those records first (e.g. with a migration script applied via 'tailor
|
|
6496
|
+
handleOptionalToRequiredError(error, ["The target snapshot marks a field as required, but existing remote records have no value for it.", "Populate those records first (e.g. with a migration script applied via 'tailor deploy'), then re-run the sync."]);
|
|
4925
6497
|
}
|
|
4926
6498
|
await Promise.all(desiredGqlPermissions.map(({ typeName, permission }) => {
|
|
4927
6499
|
const request = {
|
|
@@ -4952,13 +6524,13 @@ async function sync(options) {
|
|
|
4952
6524
|
logger.success(`Synced namespace ${styles.bold(target.namespace)} to migration ${styles.bold(formatMigrationNumber(targetVersion))}.`);
|
|
4953
6525
|
if (targetVersion < latest) {
|
|
4954
6526
|
logger.newline();
|
|
4955
|
-
logger.info(`Run 'tailor
|
|
6527
|
+
logger.info(`Run 'tailor deploy' to apply migrations ${formatMigrationNumber(targetVersion + 1)}–${formatMigrationNumber(latest)} from the working tree.`);
|
|
4956
6528
|
}
|
|
4957
6529
|
}
|
|
4958
6530
|
const syncCommand = defineAppCommand({
|
|
4959
6531
|
name: "sync",
|
|
4960
6532
|
description: "Sync remote TailorDB schema to a specific migration snapshot (recovery from --no-schema-check drift).",
|
|
4961
|
-
args: z.
|
|
6533
|
+
args: z.strictObject({
|
|
4962
6534
|
...deploymentArgs,
|
|
4963
6535
|
...confirmationArgs,
|
|
4964
6536
|
number: arg(z.string(), {
|
|
@@ -4969,7 +6541,7 @@ const syncCommand = defineAppCommand({
|
|
|
4969
6541
|
alias: "n",
|
|
4970
6542
|
description: "Target TailorDB namespace (required if multiple namespaces exist)"
|
|
4971
6543
|
})
|
|
4972
|
-
})
|
|
6544
|
+
}),
|
|
4973
6545
|
run: async (args) => {
|
|
4974
6546
|
await assertWritable({ profile: args.profile });
|
|
4975
6547
|
await sync({
|
|
@@ -5024,7 +6596,7 @@ const tailordbCommand = defineCommand({
|
|
|
5024
6596
|
const upgradeCommand = defineAppCommand({
|
|
5025
6597
|
name: "upgrade",
|
|
5026
6598
|
description: "Run codemods to upgrade your project to a newer SDK version.",
|
|
5027
|
-
args: z.
|
|
6599
|
+
args: z.strictObject({
|
|
5028
6600
|
from: arg(z.string(), { description: "SDK version before the upgrade (e.g., 1.33.0)" }),
|
|
5029
6601
|
"dry-run": arg(z.boolean().default(false), {
|
|
5030
6602
|
alias: "d",
|
|
@@ -5034,11 +6606,11 @@ const upgradeCommand = defineAppCommand({
|
|
|
5034
6606
|
description: "Project directory to upgrade",
|
|
5035
6607
|
completion: { type: "directory" }
|
|
5036
6608
|
})
|
|
5037
|
-
})
|
|
6609
|
+
}),
|
|
5038
6610
|
run: async (args) => {
|
|
5039
|
-
const { initTelemetry } = await import("../telemetry-
|
|
6611
|
+
const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
|
|
5040
6612
|
await initTelemetry();
|
|
5041
|
-
const { upgrade } = await import("../service-
|
|
6613
|
+
const { upgrade } = await import("../service-DeZeYa06.mjs");
|
|
5042
6614
|
await upgrade({
|
|
5043
6615
|
from: args.from,
|
|
5044
6616
|
dryRun: args["dry-run"],
|
|
@@ -5052,32 +6624,59 @@ const upgradeCommand = defineAppCommand({
|
|
|
5052
6624
|
const currentCommand = defineAppCommand({
|
|
5053
6625
|
name: "current",
|
|
5054
6626
|
description: "Show current user.",
|
|
5055
|
-
args: z.
|
|
6627
|
+
args: z.strictObject({}),
|
|
5056
6628
|
run: async () => {
|
|
5057
6629
|
const config = await readPlatformConfig();
|
|
6630
|
+
const profile = process.env.TAILOR_PLATFORM_PROFILE;
|
|
6631
|
+
const profileEntry = profile ? config.profiles[profile] : void 0;
|
|
6632
|
+
if (profile && !profileEntry) throw new Error(`Profile "${profile}" not found`);
|
|
6633
|
+
const platformConfig = profileEntry ? platformConfigFromProfile(profileEntry) : void 0;
|
|
6634
|
+
const currentUser = profile ? profileEntry?.user ?? null : config.current_user;
|
|
5058
6635
|
const jsonOutput = logger.jsonMode;
|
|
5059
|
-
if (!
|
|
6636
|
+
if (!currentUser) throw new Error(multiline`
|
|
5060
6637
|
Current user not set.
|
|
5061
|
-
Please login first using 'tailor
|
|
6638
|
+
Please login first using 'tailor login' command to register a user.
|
|
5062
6639
|
`);
|
|
5063
|
-
if (!config
|
|
5064
|
-
Current user '${
|
|
5065
|
-
Please login again using 'tailor
|
|
6640
|
+
if (!hasUserTokenEntry(config, currentUser, platformConfig)) throw new Error(multiline`
|
|
6641
|
+
Current user '${currentUser}' not found in registered users.
|
|
6642
|
+
Please login again using 'tailor login' command to register the user.
|
|
5066
6643
|
`);
|
|
5067
6644
|
if (jsonOutput) {
|
|
5068
|
-
logger.out({ user:
|
|
6645
|
+
logger.out({ user: currentUser });
|
|
5069
6646
|
return;
|
|
5070
6647
|
}
|
|
5071
|
-
logger.out(
|
|
6648
|
+
logger.out(currentUser);
|
|
5072
6649
|
}
|
|
5073
6650
|
});
|
|
5074
6651
|
|
|
5075
6652
|
//#endregion
|
|
5076
6653
|
//#region src/cli/commands/user/list.ts
|
|
6654
|
+
function activeCurrentUserKey(config) {
|
|
6655
|
+
const activeProfile = process.env.TAILOR_PLATFORM_PROFILE;
|
|
6656
|
+
if (!activeProfile) {
|
|
6657
|
+
if (!config.current_user) return null;
|
|
6658
|
+
return resolveUserTokenKey(config, config.current_user);
|
|
6659
|
+
}
|
|
6660
|
+
const profile = config.profiles[activeProfile];
|
|
6661
|
+
if (!profile) return null;
|
|
6662
|
+
return resolveUserTokenKey(config, profile.user, platformConfigFromProfile(profile));
|
|
6663
|
+
}
|
|
6664
|
+
function toUserListInfo(userKey, currentUserKey) {
|
|
6665
|
+
const separatorIndex = userKey.indexOf("|");
|
|
6666
|
+
const platformUrl = separatorIndex === -1 ? null : userKey.slice(0, separatorIndex);
|
|
6667
|
+
return {
|
|
6668
|
+
user: separatorIndex === -1 ? userKey : userKey.slice(separatorIndex + 1),
|
|
6669
|
+
platformUrl,
|
|
6670
|
+
current: currentUserKey === userKey
|
|
6671
|
+
};
|
|
6672
|
+
}
|
|
6673
|
+
function formatUserListInfo(info) {
|
|
6674
|
+
return `${info.user}${info.platformUrl ? ` [${info.platformUrl}]` : ""}${info.current ? " (current)" : ""}`;
|
|
6675
|
+
}
|
|
5077
6676
|
const listCommand$1 = defineAppCommand({
|
|
5078
6677
|
name: "list",
|
|
5079
6678
|
description: "List all users.",
|
|
5080
|
-
args: z.
|
|
6679
|
+
args: z.strictObject({}),
|
|
5081
6680
|
run: async () => {
|
|
5082
6681
|
const config = await readPlatformConfig();
|
|
5083
6682
|
const jsonOutput = logger.jsonMode;
|
|
@@ -5085,18 +6684,20 @@ const listCommand$1 = defineAppCommand({
|
|
|
5085
6684
|
if (users.length === 0) {
|
|
5086
6685
|
logger.info(multiline`
|
|
5087
6686
|
No users found.
|
|
5088
|
-
Please login first using 'tailor
|
|
6687
|
+
Please login first using 'tailor login' command to register a user.
|
|
5089
6688
|
`);
|
|
5090
6689
|
if (jsonOutput) logger.out([]);
|
|
5091
6690
|
return;
|
|
5092
6691
|
}
|
|
6692
|
+
const currentUserKey = activeCurrentUserKey(config);
|
|
6693
|
+
const userInfos = users.map((user) => toUserListInfo(user, currentUserKey));
|
|
5093
6694
|
if (jsonOutput) {
|
|
5094
|
-
logger.out(
|
|
6695
|
+
logger.out([...new Set(userInfos.map((userInfo) => userInfo.user))]);
|
|
5095
6696
|
return;
|
|
5096
6697
|
}
|
|
5097
|
-
|
|
5098
|
-
if (
|
|
5099
|
-
else logger.log(
|
|
6698
|
+
userInfos.forEach((userInfo) => {
|
|
6699
|
+
if (userInfo.current) logger.success(formatUserListInfo(userInfo), { mode: "plain" });
|
|
6700
|
+
else logger.log(formatUserListInfo(userInfo));
|
|
5100
6701
|
});
|
|
5101
6702
|
}
|
|
5102
6703
|
});
|
|
@@ -5157,12 +6758,31 @@ function printCreatedToken(name, token, write, action) {
|
|
|
5157
6758
|
`);
|
|
5158
6759
|
}
|
|
5159
6760
|
|
|
6761
|
+
//#endregion
|
|
6762
|
+
//#region src/cli/commands/user/pat/user.ts
|
|
6763
|
+
function resolvePatUser(config) {
|
|
6764
|
+
const activeProfile = process.env.TAILOR_PLATFORM_PROFILE;
|
|
6765
|
+
if (activeProfile) return config.profiles[activeProfile]?.user ?? null;
|
|
6766
|
+
return config.current_user;
|
|
6767
|
+
}
|
|
6768
|
+
async function createPatOperatorClient() {
|
|
6769
|
+
const config = await readPlatformConfig();
|
|
6770
|
+
const activeProfile = process.env.TAILOR_PLATFORM_PROFILE;
|
|
6771
|
+
const profileEntry = activeProfile ? config.profiles[activeProfile] : void 0;
|
|
6772
|
+
if (activeProfile && !profileEntry) throw new Error(`Profile "${activeProfile}" not found`);
|
|
6773
|
+
const platformConfig = profileEntry ? platformConfigFromProfile(profileEntry) : void 0;
|
|
6774
|
+
const user = resolvePatUser(config);
|
|
6775
|
+
if (!user) throw new Error("No user logged in.\nPlease login first using 'tailor login' command.");
|
|
6776
|
+
const { accessToken } = await fetchLatestToken(config, user, platformConfig);
|
|
6777
|
+
return await initOperatorClient(accessToken, platformConfig);
|
|
6778
|
+
}
|
|
6779
|
+
|
|
5160
6780
|
//#endregion
|
|
5161
6781
|
//#region src/cli/commands/user/pat/create.ts
|
|
5162
6782
|
const createCommand = defineAppCommand({
|
|
5163
6783
|
name: "create",
|
|
5164
6784
|
description: "Create a new personal access token.",
|
|
5165
|
-
args: z.
|
|
6785
|
+
args: z.strictObject({
|
|
5166
6786
|
name: arg(z.string(), {
|
|
5167
6787
|
positional: true,
|
|
5168
6788
|
description: "Token name"
|
|
@@ -5171,16 +6791,10 @@ const createCommand = defineAppCommand({
|
|
|
5171
6791
|
alias: "W",
|
|
5172
6792
|
description: "Grant write permission (default: read-only)"
|
|
5173
6793
|
})
|
|
5174
|
-
})
|
|
6794
|
+
}),
|
|
5175
6795
|
run: async (args) => {
|
|
5176
6796
|
await assertWritable();
|
|
5177
|
-
const
|
|
5178
|
-
if (!config.current_user) throw new Error(multiline`
|
|
5179
|
-
No user logged in.
|
|
5180
|
-
Please login first using 'tailor-sdk login' command.
|
|
5181
|
-
`);
|
|
5182
|
-
const { accessToken: token } = await fetchLatestToken(config, config.current_user);
|
|
5183
|
-
const client = await initOperatorClient(token);
|
|
6797
|
+
const client = await createPatOperatorClient();
|
|
5184
6798
|
const scopes = getScopesFromWriteFlag(args.write);
|
|
5185
6799
|
const result = await client.createPersonalAccessToken({
|
|
5186
6800
|
name: args.name,
|
|
@@ -5196,19 +6810,13 @@ const createCommand = defineAppCommand({
|
|
|
5196
6810
|
const deleteCommand = defineAppCommand({
|
|
5197
6811
|
name: "delete",
|
|
5198
6812
|
description: "Delete a personal access token.",
|
|
5199
|
-
args: z.
|
|
6813
|
+
args: z.strictObject({ name: arg(z.string(), {
|
|
5200
6814
|
positional: true,
|
|
5201
6815
|
description: "Token name"
|
|
5202
|
-
}) })
|
|
6816
|
+
}) }),
|
|
5203
6817
|
run: async (args) => {
|
|
5204
6818
|
await assertWritable();
|
|
5205
|
-
|
|
5206
|
-
if (!config.current_user) throw new Error(multiline`
|
|
5207
|
-
No user logged in.
|
|
5208
|
-
Please login first using 'tailor-sdk login' command.
|
|
5209
|
-
`);
|
|
5210
|
-
const { accessToken: token } = await fetchLatestToken(config, config.current_user);
|
|
5211
|
-
await (await initOperatorClient(token)).deletePersonalAccessToken({ name: args.name });
|
|
6819
|
+
await (await createPatOperatorClient()).deletePersonalAccessToken({ name: args.name });
|
|
5212
6820
|
logger.success(`Personal access token "${args.name}" deleted successfully.`);
|
|
5213
6821
|
}
|
|
5214
6822
|
});
|
|
@@ -5218,16 +6826,10 @@ const deleteCommand = defineAppCommand({
|
|
|
5218
6826
|
const listCommand = defineAppCommand({
|
|
5219
6827
|
name: "list",
|
|
5220
6828
|
description: "List all personal access tokens.",
|
|
5221
|
-
args: z.
|
|
6829
|
+
args: z.strictObject({ ...paginationArgs() }),
|
|
5222
6830
|
run: async (args) => {
|
|
5223
6831
|
const jsonOutput = logger.jsonMode;
|
|
5224
|
-
const
|
|
5225
|
-
if (!config.current_user) throw new Error(multiline`
|
|
5226
|
-
No user logged in.
|
|
5227
|
-
Please login first using 'tailor-sdk login' command.
|
|
5228
|
-
`);
|
|
5229
|
-
const { accessToken: token } = await fetchLatestToken(config, config.current_user);
|
|
5230
|
-
const client = await initOperatorClient(token);
|
|
6832
|
+
const client = await createPatOperatorClient();
|
|
5231
6833
|
const pageDirection = toPageDirection(args.order);
|
|
5232
6834
|
const pats = await fetchPaged(async (pageToken, pageSize) => {
|
|
5233
6835
|
const { personalAccessTokens, nextPageToken } = await client.listPersonalAccessTokens({
|
|
@@ -5240,7 +6842,7 @@ const listCommand = defineAppCommand({
|
|
|
5240
6842
|
if (pats.length === 0) {
|
|
5241
6843
|
logger.info(multiline`
|
|
5242
6844
|
No personal access tokens found.
|
|
5243
|
-
Please create a token using 'tailor
|
|
6845
|
+
Please create a token using 'tailor user pat create' command.
|
|
5244
6846
|
`);
|
|
5245
6847
|
if (!jsonOutput) return;
|
|
5246
6848
|
}
|
|
@@ -5263,7 +6865,7 @@ const listCommand = defineAppCommand({
|
|
|
5263
6865
|
const updateCommand = defineAppCommand({
|
|
5264
6866
|
name: "update",
|
|
5265
6867
|
description: "Update a personal access token (delete and recreate).",
|
|
5266
|
-
args: z.
|
|
6868
|
+
args: z.strictObject({
|
|
5267
6869
|
name: arg(z.string(), {
|
|
5268
6870
|
positional: true,
|
|
5269
6871
|
description: "Token name"
|
|
@@ -5272,16 +6874,10 @@ const updateCommand = defineAppCommand({
|
|
|
5272
6874
|
alias: "W",
|
|
5273
6875
|
description: "Grant write permission (if not specified, keeps read-only)"
|
|
5274
6876
|
})
|
|
5275
|
-
})
|
|
6877
|
+
}),
|
|
5276
6878
|
run: async (args) => {
|
|
5277
6879
|
await assertWritable();
|
|
5278
|
-
const
|
|
5279
|
-
if (!config.current_user) throw new Error(multiline`
|
|
5280
|
-
No user logged in.
|
|
5281
|
-
Please login first using 'tailor-sdk login' command.
|
|
5282
|
-
`);
|
|
5283
|
-
const { accessToken: token } = await fetchLatestToken(config, config.current_user);
|
|
5284
|
-
const client = await initOperatorClient(token);
|
|
6880
|
+
const client = await createPatOperatorClient();
|
|
5285
6881
|
await client.deletePersonalAccessToken({ name: args.name });
|
|
5286
6882
|
const scopes = getScopesFromWriteFlag(args.write);
|
|
5287
6883
|
const result = await client.createPersonalAccessToken({
|
|
@@ -5314,20 +6910,26 @@ const patCommand = defineCommand({
|
|
|
5314
6910
|
const switchCommand = defineAppCommand({
|
|
5315
6911
|
name: "switch",
|
|
5316
6912
|
description: "Set current user.",
|
|
5317
|
-
args: z.
|
|
6913
|
+
args: z.strictObject({ user: arg(z.string(), {
|
|
5318
6914
|
positional: true,
|
|
5319
6915
|
description: "User email address or machine user client ID"
|
|
5320
|
-
}) })
|
|
6916
|
+
}) }),
|
|
5321
6917
|
run: async (args) => {
|
|
5322
6918
|
const config = await readPlatformConfig();
|
|
5323
|
-
const
|
|
6919
|
+
const activeProfileName = process.env.TAILOR_PLATFORM_PROFILE;
|
|
6920
|
+
const activeProfileEntry = activeProfileName ? config.profiles[activeProfileName] : void 0;
|
|
6921
|
+
if (activeProfileName && !activeProfileEntry) throw new Error(`Profile "${activeProfileName}" not found`);
|
|
6922
|
+
const platformConfig = activeProfileEntry ? platformConfigFromProfile(activeProfileEntry) : void 0;
|
|
6923
|
+
if (args.user.includes("|")) throw new Error(`User "${args.user}" looks like a platform-scoped token key. Pass the user name without the platform URL and select the platform with TAILOR_PLATFORM_URL or a profile.`);
|
|
6924
|
+
const user = resolveConfigUser(config, args.user, platformConfig);
|
|
5324
6925
|
if (!user) throw new Error(multiline`
|
|
5325
6926
|
User "${args.user}" not found.
|
|
5326
|
-
Please login first using 'tailor
|
|
6927
|
+
Please login first using 'tailor login' command to register this user.
|
|
5327
6928
|
`);
|
|
5328
|
-
|
|
6929
|
+
if (activeProfileEntry) activeProfileEntry.user = user;
|
|
6930
|
+
else config.current_user = user;
|
|
5329
6931
|
writePlatformConfig(config);
|
|
5330
|
-
logger.success(`Current user set to "${
|
|
6932
|
+
logger.success(`Current user set to "${user}" successfully.`);
|
|
5331
6933
|
}
|
|
5332
6934
|
});
|
|
5333
6935
|
|
|
@@ -5353,14 +6955,15 @@ const workflowCommand = defineCommand({
|
|
|
5353
6955
|
name: "workflow",
|
|
5354
6956
|
description: "Manage workflows and workflow executions.",
|
|
5355
6957
|
subCommands: {
|
|
5356
|
-
list: listCommand$
|
|
6958
|
+
list: listCommand$13,
|
|
5357
6959
|
get: getCommand$6,
|
|
5358
6960
|
start: startCommand,
|
|
6961
|
+
wait: waitCommand,
|
|
5359
6962
|
executions: executionsCommand,
|
|
5360
6963
|
resume: resumeCommand
|
|
5361
6964
|
},
|
|
5362
6965
|
async run() {
|
|
5363
|
-
await runCommand(listCommand$
|
|
6966
|
+
await runCommand(listCommand$13, []);
|
|
5364
6967
|
}
|
|
5365
6968
|
});
|
|
5366
6969
|
|
|
@@ -5371,10 +6974,10 @@ const appCommand = defineCommand({
|
|
|
5371
6974
|
description: "Manage workspace applications",
|
|
5372
6975
|
subCommands: {
|
|
5373
6976
|
health: healthCommand,
|
|
5374
|
-
list: listCommand$
|
|
6977
|
+
list: listCommand$14
|
|
5375
6978
|
},
|
|
5376
6979
|
async run() {
|
|
5377
|
-
await runCommand(listCommand$
|
|
6980
|
+
await runCommand(listCommand$14, []);
|
|
5378
6981
|
}
|
|
5379
6982
|
});
|
|
5380
6983
|
|
|
@@ -5385,12 +6988,12 @@ const userCommand = defineCommand({
|
|
|
5385
6988
|
description: "Manage workspace users",
|
|
5386
6989
|
subCommands: {
|
|
5387
6990
|
invite: inviteCommand,
|
|
5388
|
-
list: listCommand$
|
|
6991
|
+
list: listCommand$15,
|
|
5389
6992
|
remove: removeCommand,
|
|
5390
6993
|
update: updateCommand$4
|
|
5391
6994
|
},
|
|
5392
6995
|
async run() {
|
|
5393
|
-
await runCommand(listCommand$
|
|
6996
|
+
await runCommand(listCommand$15, []);
|
|
5394
6997
|
}
|
|
5395
6998
|
});
|
|
5396
6999
|
|
|
@@ -5402,31 +7005,82 @@ const workspaceCommand = defineCommand({
|
|
|
5402
7005
|
subCommands: {
|
|
5403
7006
|
app: appCommand,
|
|
5404
7007
|
create: createCommand$4,
|
|
5405
|
-
delete: deleteCommand$
|
|
7008
|
+
delete: deleteCommand$5,
|
|
5406
7009
|
get: getCommand$7,
|
|
5407
|
-
list: listCommand$
|
|
7010
|
+
list: listCommand$16,
|
|
5408
7011
|
restore: restoreCommand,
|
|
5409
7012
|
user: userCommand
|
|
5410
7013
|
},
|
|
5411
7014
|
async run() {
|
|
5412
|
-
await runCommand(listCommand$
|
|
7015
|
+
await runCommand(listCommand$16, []);
|
|
5413
7016
|
}
|
|
5414
7017
|
});
|
|
5415
7018
|
|
|
5416
7019
|
//#endregion
|
|
5417
7020
|
//#region src/cli/index.ts
|
|
5418
|
-
|
|
5419
|
-
const { register } = await import("tsx/esm/api");
|
|
5420
|
-
register();
|
|
5421
|
-
}
|
|
7021
|
+
await registerTsHook(new URL("./ts-hook.mjs", import.meta.url));
|
|
5422
7022
|
initCrashReporting();
|
|
5423
7023
|
const packageJson = await readPackageJson();
|
|
5424
|
-
const cliName = Object.keys(packageJson.bin ?? {})[0] || "tailor
|
|
5425
|
-
const
|
|
7024
|
+
const cliName = Object.keys(packageJson.bin ?? {})[0] || "tailor";
|
|
7025
|
+
const packageName = packageJson.name ?? "@tailor-platform/sdk";
|
|
7026
|
+
const bundledSkillsDir = resolve(dirname(await resolvePackageJSON(import.meta.url)), "agent-skills");
|
|
7027
|
+
function replaceCommandArgs(command, removals = []) {
|
|
7028
|
+
const args = command.args;
|
|
7029
|
+
if (!args?.shape) return command;
|
|
7030
|
+
const shape = { ...args.shape };
|
|
7031
|
+
for (const name of removals) delete shape[name];
|
|
7032
|
+
return {
|
|
7033
|
+
...command,
|
|
7034
|
+
args: z.strictObject({ ...shape })
|
|
7035
|
+
};
|
|
7036
|
+
}
|
|
7037
|
+
function removeCommandAliases(command) {
|
|
7038
|
+
const next = { ...command };
|
|
7039
|
+
delete next.aliases;
|
|
7040
|
+
return next;
|
|
7041
|
+
}
|
|
7042
|
+
function alignSkillCommand(command) {
|
|
7043
|
+
const subCommands = command.subCommands ?? {};
|
|
7044
|
+
const add = {
|
|
7045
|
+
...removeCommandAliases(replaceCommandArgs(subCommands.add, ["verbose"])),
|
|
7046
|
+
description: "Install Tailor SDK agent skills."
|
|
7047
|
+
};
|
|
7048
|
+
const list = {
|
|
7049
|
+
...replaceCommandArgs(subCommands.list, ["json"]),
|
|
7050
|
+
description: "List Tailor SDK agent skills."
|
|
7051
|
+
};
|
|
7052
|
+
const remove = {
|
|
7053
|
+
...removeCommandAliases(replaceCommandArgs(subCommands.remove)),
|
|
7054
|
+
description: "Remove installed Tailor SDK agent skills."
|
|
7055
|
+
};
|
|
7056
|
+
const sync = {
|
|
7057
|
+
...replaceCommandArgs(subCommands.sync, ["verbose"]),
|
|
7058
|
+
description: "Remove and reinstall Tailor SDK agent skills."
|
|
7059
|
+
};
|
|
7060
|
+
return {
|
|
7061
|
+
...command,
|
|
7062
|
+
description: "Manage Tailor SDK agent skills.",
|
|
7063
|
+
async run() {
|
|
7064
|
+
const result = await runCommand(add, []);
|
|
7065
|
+
if (!result.success) throw result.error;
|
|
7066
|
+
},
|
|
7067
|
+
subCommands: {
|
|
7068
|
+
...subCommands,
|
|
7069
|
+
add,
|
|
7070
|
+
list,
|
|
7071
|
+
remove,
|
|
7072
|
+
sync
|
|
7073
|
+
}
|
|
7074
|
+
};
|
|
7075
|
+
}
|
|
7076
|
+
const commandWithSkills = withSkillCommand(defineCommand({
|
|
5426
7077
|
name: cliName,
|
|
5427
7078
|
description: packageJson.description || "Tailor CLI for managing Tailor Platform SDK applications",
|
|
7079
|
+
notes: `CLI plugins (beta): an unknown subcommand is dispatched to an external plugin executable named \`${cliName}-<name>\` (found on your PATH or in node_modules/.bin), similar to \`gh\` extensions.
|
|
7080
|
+
Run \`${cliName} plugin list\` to see which plugins are installed and where they resolve from.`,
|
|
5428
7081
|
subCommands: {
|
|
5429
7082
|
api: apiCommand,
|
|
7083
|
+
auth: authCommand,
|
|
5430
7084
|
authconnection: authconnectionCommand,
|
|
5431
7085
|
crashreport: crashReportCommand,
|
|
5432
7086
|
deploy: deployCommand$1,
|
|
@@ -5440,13 +7094,13 @@ const mainCommand = withCompletionCommand(defineCommand({
|
|
|
5440
7094
|
oauth2client: oauth2clientCommand,
|
|
5441
7095
|
open: openCommand,
|
|
5442
7096
|
organization: organizationCommand,
|
|
7097
|
+
plugin: pluginCommand,
|
|
5443
7098
|
profile: profileCommand,
|
|
5444
7099
|
query: queryCommand,
|
|
5445
7100
|
remove: removeCommand$1,
|
|
5446
7101
|
secret: secretCommand,
|
|
5447
7102
|
setup: setupCommand,
|
|
5448
7103
|
show: showCommand,
|
|
5449
|
-
skills: skillsCommand,
|
|
5450
7104
|
staticwebsite: staticwebsiteCommand,
|
|
5451
7105
|
tailordb: tailordbCommand,
|
|
5452
7106
|
upgrade: upgradeCommand,
|
|
@@ -5454,11 +7108,31 @@ const mainCommand = withCompletionCommand(defineCommand({
|
|
|
5454
7108
|
workflow: workflowCommand,
|
|
5455
7109
|
workspace: workspaceCommand
|
|
5456
7110
|
}
|
|
5457
|
-
})
|
|
7111
|
+
}), {
|
|
7112
|
+
sourceDir: bundledSkillsDir,
|
|
7113
|
+
package: packageName,
|
|
7114
|
+
mode: "copy",
|
|
7115
|
+
descriptionAppend: false
|
|
7116
|
+
});
|
|
7117
|
+
const commandWithSkillsSubCommands = commandWithSkills.subCommands ?? {};
|
|
7118
|
+
const mainCommand = withCompletionCommand({
|
|
7119
|
+
...commandWithSkills,
|
|
7120
|
+
subCommands: {
|
|
7121
|
+
...commandWithSkillsSubCommands,
|
|
7122
|
+
skills: alignSkillCommand(commandWithSkillsSubCommands.skills)
|
|
7123
|
+
}
|
|
7124
|
+
});
|
|
5458
7125
|
runMain(mainCommand, {
|
|
5459
7126
|
version: packageJson.version,
|
|
5460
7127
|
globalArgs: z.object(commonArgs),
|
|
5461
7128
|
displayErrors: false,
|
|
7129
|
+
onUnknownSubcommand: ({ commandPath, name, args }) => dispatchPlugin({
|
|
7130
|
+
commandPath,
|
|
7131
|
+
name,
|
|
7132
|
+
args,
|
|
7133
|
+
cliName,
|
|
7134
|
+
profile: process.env.TAILOR_PLATFORM_PROFILE
|
|
7135
|
+
}),
|
|
5462
7136
|
cleanup: async ({ error }) => {
|
|
5463
7137
|
if (error) {
|
|
5464
7138
|
if (isCLIError(error)) {
|
|
@@ -5469,11 +7143,11 @@ runMain(mainCommand, {
|
|
|
5469
7143
|
if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
|
|
5470
7144
|
} else logger.error(`Unknown error: ${error}`);
|
|
5471
7145
|
if (!isCLIError(error) && (!(error instanceof Error) || error instanceof TypeError || error instanceof RangeError)) {
|
|
5472
|
-
const { reportCrash } = await import("../crashreport-
|
|
7146
|
+
const { reportCrash } = await import("../crashreport-D3RjuSYb.mjs");
|
|
5473
7147
|
await reportCrash(error, "handledError");
|
|
5474
7148
|
}
|
|
5475
7149
|
}
|
|
5476
|
-
const { shutdownTelemetry } = await import("../telemetry-
|
|
7150
|
+
const { shutdownTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
|
|
5477
7151
|
await shutdownTelemetry();
|
|
5478
7152
|
}
|
|
5479
7153
|
});
|