@tailor-platform/sdk 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +1 -1
  3. package/dist/{application-BpLeawBg.mjs → application-C8a0UJ-z.mjs} +312 -166
  4. package/dist/application-C8a0UJ-z.mjs.map +1 -0
  5. package/dist/application-D4zOiLLa.mjs +3 -0
  6. package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +47 -24
  7. package/dist/cli/commands/tailordb/migrate/generate.d.mts +6 -0
  8. package/dist/cli/commands/tailordb/migrate/rename-detection.d.mts +22 -0
  9. package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +1 -1
  10. package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +30 -11
  11. package/dist/cli/commands/tailordb/truncate.d.mts +2 -2
  12. package/dist/cli/lib.mjs +2 -2
  13. package/dist/cli/lib.mjs.map +1 -1
  14. package/dist/cli/main.mjs +366 -137
  15. package/dist/cli/main.mjs.map +1 -1
  16. package/dist/cli/ts-hook.mjs +55 -8
  17. package/dist/completion/zsh-worker.zsh +38 -10
  18. package/dist/configure/config/index.d.mts +1 -1
  19. package/dist/configure/index.d.mts +3 -2
  20. package/dist/configure/index.mjs +92 -25
  21. package/dist/configure/index.mjs.map +1 -1
  22. package/dist/configure/services/aigateway/index.d.mts +5 -4
  23. package/dist/configure/services/aigateway/types.d.mts +6 -1
  24. package/dist/configure/services/auth/types.d.mts +2 -2
  25. package/dist/configure/services/index.d.mts +2 -2
  26. package/dist/configure/services/tailordb/permission.d.mts +1 -1
  27. package/dist/configure/services/tailordb/schema.d.mts +1 -1
  28. package/dist/configure/services/tailordb/types.d.mts +7 -4
  29. package/dist/configure/services/workflow/index.d.mts +2 -2
  30. package/dist/configure/services/workflow/wait-point.d.mts +72 -13
  31. package/dist/configure/types/auth-namespace-name.d.mts +18 -0
  32. package/dist/{crashreport-CINZ5dVN.mjs → crashreport-Cd4TqfZq.mjs} +1 -1
  33. package/dist/{crashreport-CIIfLnoO.mjs → crashreport-Q0pDAlrj.mjs} +8 -4
  34. package/dist/{crashreport-CIIfLnoO.mjs.map → crashreport-Q0pDAlrj.mjs.map} +1 -1
  35. package/dist/{errors-CWj21238.mjs → errors-Bxut5_Sy.mjs} +17 -3
  36. package/dist/{errors-CWj21238.mjs.map → errors-Bxut5_Sy.mjs.map} +1 -1
  37. package/dist/file-BUcmVIH2.mjs.map +1 -1
  38. package/dist/kysely/index.d.mts +2 -1
  39. package/dist/kysely/index.mjs +2 -1
  40. package/dist/kysely/index.mjs.map +1 -1
  41. package/dist/{logger-DSdExfor.mjs → logger-D0bEznPv.mjs} +9 -5
  42. package/dist/{logger-DSdExfor.mjs.map → logger-D0bEznPv.mjs.map} +1 -1
  43. package/dist/package-json-8b0O9TlX.mjs.map +1 -1
  44. package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
  45. package/dist/plugin/builtin/enum-constants/index.mjs +5 -5
  46. package/dist/plugin/builtin/enum-constants/index.mjs.map +1 -1
  47. package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
  48. package/dist/plugin/builtin/file-utils/index.mjs +14 -11
  49. package/dist/plugin/builtin/file-utils/index.mjs.map +1 -1
  50. package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
  51. package/dist/plugin/builtin/kysely-type/index.mjs +9 -9
  52. package/dist/plugin/builtin/kysely-type/index.mjs.map +1 -1
  53. package/dist/plugin/builtin/seed/index.mjs +1 -1
  54. package/dist/plugin/builtin/seed/seed-type-processor.d.mts +4 -4
  55. package/dist/plugin/get-generated-type.d.mts +4 -4
  56. package/dist/plugin/index.mjs +13 -13
  57. package/dist/plugin/index.mjs.map +1 -1
  58. package/dist/plugin/types.d.mts +2 -2
  59. package/dist/plugin/with-context.d.mts +1 -1
  60. package/dist/{register-ts-hook-CTth1eqj.mjs → register-ts-hook-Vqjza10T.mjs} +2516 -857
  61. package/dist/register-ts-hook-Vqjza10T.mjs.map +1 -0
  62. package/dist/{repl-editor-BCozyiNq.mjs → repl-editor-F9LotMNx.mjs} +3 -2
  63. package/dist/{repl-editor-BCozyiNq.mjs.map → repl-editor-F9LotMNx.mjs.map} +1 -1
  64. package/dist/runtime/file.d.mts +14 -14
  65. package/dist/{schema--xYWRGfe.mjs → schema-D4TC4BC3.mjs} +4 -3
  66. package/dist/schema-D4TC4BC3.mjs.map +1 -0
  67. package/dist/seed/index.d.mts +4 -4
  68. package/dist/seed/index.mjs +3 -3
  69. package/dist/seed/index.mjs.map +1 -1
  70. package/dist/{seed-xqM9WIHP.mjs → seed-Cibjmtry.mjs} +46 -40
  71. package/dist/seed-Cibjmtry.mjs.map +1 -0
  72. package/dist/{service-goqlJJgA.mjs → service-2grcAOCJ.mjs} +61 -27
  73. package/dist/service-2grcAOCJ.mjs.map +1 -0
  74. package/dist/service-CaBPNDIZ.mjs +3 -0
  75. package/dist/{service-CCwl3Avt.mjs → service-LTfp03ZZ.mjs} +7 -5
  76. package/dist/{service-CCwl3Avt.mjs.map → service-LTfp03ZZ.mjs.map} +1 -1
  77. package/dist/types/aigateway.generated.d.mts +2 -2
  78. package/dist/types/auth.generated.d.mts +3 -3
  79. package/dist/types/executor.generated.d.mts +1 -1
  80. package/dist/types/tailordb.generated.d.mts +12 -12
  81. package/dist/utils/test/index.d.mts +4 -4
  82. package/dist/utils/test/index.mjs +4 -4
  83. package/dist/utils/test/index.mjs.map +1 -1
  84. package/dist/vitest/index.mjs +95 -7
  85. package/dist/vitest/index.mjs.map +1 -1
  86. package/dist/vitest/mocks/workflow.d.mts +13 -1
  87. package/dist/wait-point-invoker-__oE88_P.mjs +148 -0
  88. package/dist/wait-point-invoker-__oE88_P.mjs.map +1 -0
  89. package/dist/wait-point-registry-TL99zotw.mjs +47 -0
  90. package/dist/wait-point-registry-TL99zotw.mjs.map +1 -0
  91. package/docs/cli/application.md +3 -1
  92. package/docs/cli/function.md +17 -13
  93. package/docs/cli/setup.md +14 -1
  94. package/docs/cli/tailordb.md +30 -27
  95. package/docs/cli/workspace.md +2 -2
  96. package/docs/cli-reference.md +23 -22
  97. package/docs/configuration.md +2 -2
  98. package/docs/github-actions.md +73 -28
  99. package/docs/migration/v2.md +58 -4
  100. package/docs/migration/v3.md +41 -0
  101. package/docs/quickstart.md +1 -1
  102. package/docs/services/aigateway.md +33 -9
  103. package/docs/services/executor.md +5 -1
  104. package/docs/services/tailordb-migration.md +146 -44
  105. package/docs/services/tailordb.md +7 -7
  106. package/docs/services/workflow.md +52 -2
  107. package/docs/testing.md +13 -1
  108. package/package.json +8 -8
  109. package/dist/application-BpLeawBg.mjs.map +0 -1
  110. package/dist/application-BxLLiLsr.mjs +0 -3
  111. package/dist/register-ts-hook-CTth1eqj.mjs.map +0 -1
  112. package/dist/schema--xYWRGfe.mjs.map +0 -1
  113. package/dist/seed-xqM9WIHP.mjs.map +0 -1
  114. package/dist/service-_XmjSEGr.mjs +0 -3
  115. package/dist/service-goqlJJgA.mjs.map +0 -1
  116. package/dist/test-env-key-D7UkZp99.mjs +0 -75
  117. package/dist/test-env-key-D7UkZp99.mjs.map +0 -1
package/dist/cli/main.mjs CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import { r as parseInputFields } from "../schema--xYWRGfe.mjs";
2
+ import { r as parseInputFields } from "../schema-D4TC4BC3.mjs";
3
3
  import { At as PATScope, Ct as AuthInvokerSchema, E as CustomDomainStatus, Ft as CloneOperationStatus, W as FunctionExecution_Type } from "../workspace_resource_pb-HNIL_Vfj.mjs";
4
4
  import { t as assertDefined } from "../assert-DBxo8jPo.mjs";
5
- import { n as logger, r as styles } from "../logger-DSdExfor.mjs";
6
- import { $ as treeCommand, $n as formatDiffSummary, An as MIGRATION_NUMBER_PATTERN, Ar as removeLegacyUserAlias, B as MIGRATION_REVIEW_REQUIRED_MARKER, Br as isVerbose, Bt as getCommand$6, C as healthCommand, Cn as captureFileState, Cr as loadConsoleBaseUrl, Ct as listCommand$9, Dr as loadWorkspaceId, E as resumeCommand, En as DIFF_FILE_NAME, Er as loadStoredUserTokens, F as chunkSeedData, Fn as compareLocalTypesWithSnapshot, Fr as defineAppCommand, Ft as jobsCommand, G as showCommand, Gn as loadSnapshot, Gr as toPageDirection, Gt as functionExecutionStatusToString, H as deployCommand$1, Hn as getMigrationFiles, Hr as pagedLogArgs, Ht as executionsCommand, I as bundleSeedScript, In as compareSnapshots, Ir as commonArgs, Jn as MIGRATION_HISTORY_LABEL_KEY, K as extractOwnedNamespaces, Kn as normalizeSchemaSnapshot, Kr as workspaceArgs, Kt as getCommand$1, Ln as createSnapshotFromLocalTypes, Lr as configArg, M as loadSeedContext, Mn as UnsupportedMigrationFileVersionError, Mr as resolveUserTokenKey, N as loadApplicationNamespaces, Nn as assertMigrationNumberExists, Nr as saveUserTokens, O as listCommand$14, On as MIGRATE_FILE_NAME, Or as platformConfigFromProfile, Ot as webhookCommand, Pn as assertValidMigrationFiles, Pr as writePlatformConfig, Qn as sanitizeMigrationLabel, Qt as listCommand$17, R as generateCommand$1, Rn as formatSchemaDrifts, Rr as confirmationArgs, Rt as startCommand, Sn as protoGqlPermission, Sr as loadConfigPath, St as generate, Tn as DB_TYPES_FILE_NAME, Tr as loadPlatformClientConfig, Tt as getCommand$2, Ur as paginationArgs, V as generateSchemaFile, Vn as getMigrationFilePath, Vr as multiConfigArg, Wn as loadDiff, Wr as resolveMachineUserInputSource, X as updateCommand$3, Xn as createMigrationHistoryId, Xt as deployMigrationTestBaseline, Y as removeCommand$1, Yn as MIGRATION_LABEL_KEY, Yt as deployFromCLI, Zn as handleOptionalToRequiredError, Zt as deployMigrationTestTarget, _n as verifyRemoteSchema, _r as deleteUserTokens, a as queryCommand, an as ensureConfigId, ar as sdkAppIdLabelKey, bn as compareSnapshotWithRemote, br as hasUserTokenEntry, bt as listCommand$10, c as removeCommand, cn as executeScript, cr as writeMetadataLabels, ct as getCommand$4, dn as bundleMigrationScript, dr as formatMigrationNumber, en as createCommand$4, er as formatMigrationDiff, et as listCommand$13, f as inviteCommand, fn as checkMigrationDiffs, fr as parseMigrationNumberArg, ft as createCommand$3, g as getCommand$7, gn as toTailorDBDeployInput, gr as loadConfig, gt as getCommand$3, hn as logRemoteDriftGuidance, hr as assertWritable, in as workspaceNameSchema, ir as resourceTrn, it as updateCommand$2, j as truncateCommand, jn as SCHEMA_FILE_NAME, jr as resolveConfigUser, jt as listCommand$8, kr as readPlatformConfig, kt as triggerCommand, lr as PluginManager, m as restoreCommand, mn as formatRemoteVerificationResults, mr as apiCall, mt as listCommand$11, nr as getNamespacesWithMigrations, nt as getCommand$5, o as updateCommand$4, on as getMigrationMachineUser, or as sdkNameLabelKey, ot as listCommand$12, pn as fetchRemoteSchemaSnapshot, pr as prompt, q as logBetaWarning, qn as reconstructSnapshotFromMigrations, r as serializeError, rn as formatKeyValueTable, rr as selectTargetNamespace, sn as updateMigrationLabel, sr as sdkVersionLabelKey, t as registerTsHook, tn as createValidatedWorkspaceWithClient, tr as hasChanges, u as listCommand$16, un as formatMigrationScriptCommand, ut as deleteCommand$4, v as deleteCommand$5, vn as fetchRemoteMigrationNumber, vr as fetchLatestToken, w as waitCommand, wn as captureMigrationFileState, wr as loadMachineUserName, x as listCommand$15, xn as generateAllTypeManifestsFromSnapshot, xr as loadAccessToken, yn as fetchRemoteMigrationState, yr as hasAnyUserTokenEntry, yt as tokenCommand$1, z as scriptCommand, zn as getLatestMigrationNumber, zr as deploymentArgs } from "../register-ts-hook-CTth1eqj.mjs";
7
- import { A as getDistDir, B as fetchUserInfo, C as composeFunctionTreeshakeOptions, E as createBundleLog, F as fetchAll, G as initOAuth2Client, H as getOAuth2ClientId, I as fetchAllTolerant, K as initOperatorClient, N as closeConnectionPool, P as defaultPlatformBaseUrl, R as fetchPaged, S as platformBundleDefinePlugin, T as resolveBundleLogLevel, U as getOrNull, W as getPlatformBaseUrl, a as resolveInlineSourcemap, b as createTsconfigPathsPlugin, c as WorkflowJobSchema, f as INVOKER_EXPR, g as assertUniqueLocalTailorDBTypeNames, h as buildResolverPermissionAndInputCheckExpr, l as resolveResolverDefaultPermissionForFile, q as isDefaultPlatform, u as ResolverSchema, w as createLogLevelTreeshakeOptions, x as resolveTSConfigWithFallback, y as createGeneratedEntryResolverPlugin, z as fetchPlatformMachineUserToken } from "../application-BpLeawBg.mjs";
8
- import { h as loadFilesWithIgnores, i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-goqlJJgA.mjs";
5
+ import { n as logger, r as styles } from "../logger-D0bEznPv.mjs";
6
+ import { $ as treeCommand, $n as sanitizeMigrationLabel, An as MIGRATION_FILE_NAMES, Ar as readPlatformConfig, B as MIGRATION_REVIEW_REQUIRED_MARKER, Bn as getLatestMigrationNumber, Br as deploymentArgs, Bt as getCommand$6, C as healthCommand, Cn as captureFileState, Cr as loadConfigPath, Ct as listCommand$9, Dr as loadStoredUserTokens, E as resumeCommand, Er as loadPlatformClientConfig, F as chunkSeedData, Fn as assertValidMigrationFiles, Fr as writePlatformConfig, Ft as jobsCommand, G as showCommand, Gn as loadDiff, Gr as resolveMachineUserInputSource, Gt as functionExecutionStatusToString, H as deployCommand$1, Hn as getMigrationFilePath, Hr as multiConfigArg, Ht as executionsCommand, I as bundleSeedScript, In as compareLocalTypesWithSnapshot, Ir as defineAppCommand, Jn as reconstructSnapshotFromMigrations, K as extractOwnedNamespaces, Kn as loadSnapshot, Kr as toPageDirection, Kt as getCommand$1, Ln as compareSnapshots, Lr as commonArgs, M as loadSeedContext, Mr as resolveConfigUser, N as loadApplicationNamespaces, Nn as UnsupportedMigrationFileVersionError, Nr as resolveUserTokenKey, O as listCommand$14, Or as loadWorkspaceId, Ot as webhookCommand, Pn as assertMigrationNumberExists, Pr as saveUserTokens, Qn as handleOptionalToRequiredError, Qt as listCommand$17, R as generateCommand$1, Rn as createSnapshotFromLocalTypes, Rr as configArg, Rt as startCommand, Sn as protoGqlPermission, Sr as loadAccessToken, St as generate, Tr as loadMachineUserName, Tt as getCommand$2, Un as getMigrationFiles, Ur as pagedLogArgs, V as generateSchemaFile, Vr as isVerbose, Wr as paginationArgs, X as updateCommand$3, Xn as MIGRATION_LABEL_KEY, Xt as deployMigrationTestBaseline, Y as removeCommand$1, Yn as MIGRATION_HISTORY_LABEL_KEY, Yt as deployFromCLI, Zn as createMigrationHistoryId, Zt as deployMigrationTestTarget, _n as verifyRemoteSchema, _r as loadConfig, a as queryCommand, an as ensureConfigId, ar as resourceTrn, bn as compareSnapshotWithRemote, br as hasAnyUserTokenEntry, bt as listCommand$10, c as removeCommand, cn as executeScript, cr as sdkVersionLabelKey, ct as getCommand$4, dn as bundleMigrationScript, en as createCommand$4, er as formatDiffSummary, et as listCommand$13, f as inviteCommand, fn as checkMigrationDiffs, fr as formatMigrationNumber, ft as createCommand$3, g as getCommand$7, gn as toTailorDBDeployInput, gr as assertWritable, gt as getCommand$3, hn as logRemoteDriftGuidance, hr as apiCall, in as workspaceNameSchema, ir as selectTargetNamespace, it as updateCommand$2, j as truncateCommand, jn as MIGRATION_NUMBER_PATTERN, jr as removeLegacyUserAlias, jt as listCommand$8, kr as platformConfigFromProfile, kt as triggerCommand, lr as writeMetadataLabels, m as restoreCommand, mn as formatRemoteVerificationResults, mr as prompt, mt as listCommand$11, nr as hasChanges, nt as getCommand$5, o as updateCommand$4, on as getMigrationMachineUser, or as sdkAppIdLabelKey, ot as listCommand$12, pn as fetchRemoteSchemaSnapshot, pr as parseMigrationNumberArg, q as logBetaWarning, qn as normalizeSchemaSnapshot, qr as workspaceArgs, r as serializeError, rn as formatKeyValueTable, rr as getNamespacesWithMigrations, sn as updateMigrationLabel, sr as sdkNameLabelKey, t as registerTsHook, tn as createValidatedWorkspaceWithClient, tr as formatMigrationDiff, u as listCommand$16, un as formatMigrationScriptCommand, ur as PluginManager, ut as deleteCommand$4, v as deleteCommand$5, vn as fetchRemoteMigrationNumber, vr as deleteUserTokens, w as waitCommand, wn as captureMigrationFileState, wr as loadConsoleBaseUrl, x as listCommand$15, xn as generateAllTypeManifestsFromSnapshot, xr as hasUserTokenEntry, yn as fetchRemoteMigrationState, yr as fetchLatestToken, yt as tokenCommand$1, z as scriptCommand, zn as formatSchemaDrifts, zr as confirmationArgs } from "../register-ts-hook-Vqjza10T.mjs";
7
+ import { C as createTsconfigPathsPlugin, D as createLogLevelTreeshakeOptions, E as composeFunctionTreeshakeOptions, G as getOAuth2ClientId, H as fetchPlatformMachineUserToken, I as closeConnectionPool, J as initOAuth2Client, K as getOrNull, L as defaultPlatformBaseUrl, N as getDistDir, O as resolveBundleLogLevel, R as fetchAll, S as createGeneratedEntryResolverPlugin, T as platformBundleDefinePlugin, U as fetchUserInfo, V as fetchPaged, X as isDefaultPlatform, Y as initOperatorClient, _ as assertUniqueLocalTailorDBTypeNames, a as resolveInlineSourcemap, d as ResolverSchema, g as buildResolverPermissionAndInputCheckExpr, k as createBundleLog, l as WorkflowJobSchema, p as INVOKER_EXPR, q as getPlatformBaseUrl, u as resolveResolverDefaultPermissionForFile, w as resolveTSConfigWithFallback, z as fetchAllTolerant } from "../application-C8a0UJ-z.mjs";
8
+ import { h as loadFilesWithIgnores, i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-2grcAOCJ.mjs";
9
9
  import { t as multiline } from "../multiline-sfHpTZZK.mjs";
10
10
  import { t as readPackageJson } from "../package-json-8b0O9TlX.mjs";
11
11
  import { t as userAgent } from "../user-agent-Bgsszb5I.mjs";
12
- import { i as isCLIError, r as formatNextAction, t as createCLIError } from "../errors-CWj21238.mjs";
12
+ import { a as typeOnlyImportHint, i as isCLIError, r as formatNextAction, t as createCLIError } from "../errors-Bxut5_Sy.mjs";
13
13
  import { t as OperatorService } from "../service_pb-BEWj1pOV.mjs";
14
- import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-CIIfLnoO.mjs";
14
+ import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-Q0pDAlrj.mjs";
15
15
  import * as path from "pathe";
16
16
  import { dirname, resolve } from "pathe";
17
17
  import { resolvePackageJSON } from "pkg-types";
@@ -650,7 +650,8 @@ const authorizeAuthConnectionCommand = defineAppCommand({
650
650
  }),
651
651
  run: async (args) => {
652
652
  await assertWritable({ profile: args.profile });
653
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
653
+ const accessToken = await loadAccessToken({ profile: args.profile });
654
+ const client = await initOperatorClient(accessToken);
654
655
  const workspaceId = await loadWorkspaceId({
655
656
  workspaceId: args["workspace-id"],
656
657
  profile: args.profile
@@ -713,7 +714,7 @@ const authorizeAuthConnectionCommand = defineAppCommand({
713
714
  const timeout = setTimeout(() => {
714
715
  server.close();
715
716
  reject(/* @__PURE__ */ new Error("Authorization timeout exceeded (5 minutes)."));
716
- }, 300 * 1e3);
717
+ }, 3e5);
717
718
  server.on("close", () => {
718
719
  clearTimeout(timeout);
719
720
  });
@@ -751,7 +752,8 @@ const deleteAuthConnectionCommand = defineAppCommand({
751
752
  }),
752
753
  run: async (args) => {
753
754
  await assertWritable({ profile: args.profile });
754
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
755
+ const accessToken = await loadAccessToken({ profile: args.profile });
756
+ const client = await initOperatorClient(accessToken);
755
757
  const workspaceId = await loadWorkspaceId({
756
758
  workspaceId: args["workspace-id"],
757
759
  profile: args.profile
@@ -798,7 +800,8 @@ const listAuthConnectionCommand = defineAppCommand({
798
800
  ...paginationArgs()
799
801
  }),
800
802
  run: async (args) => {
801
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
803
+ const accessToken = await loadAccessToken({ profile: args.profile });
804
+ const client = await initOperatorClient(accessToken);
802
805
  const workspaceId = await loadWorkspaceId({
803
806
  workspaceId: args["workspace-id"],
804
807
  profile: args.profile
@@ -878,7 +881,8 @@ const revokeAuthConnectionCommand = defineAppCommand({
878
881
  }),
879
882
  run: async (args) => {
880
883
  await assertWritable({ profile: args.profile });
881
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
884
+ const accessToken = await loadAccessToken({ profile: args.profile });
885
+ const client = await initOperatorClient(accessToken);
882
886
  const workspaceId = await loadWorkspaceId({
883
887
  workspaceId: args["workspace-id"],
884
888
  profile: args.profile
@@ -1196,7 +1200,7 @@ async function downloadFunctionScript(options) {
1196
1200
  //#region src/cli/shared/stack-trace.ts
1197
1201
  /**
1198
1202
  * Stack trace parsing, sourcemap-based source identification, and
1199
- * formatted error display for the test-run command.
1203
+ * formatted error display for the function run command.
1200
1204
  *
1201
1205
  * The platform runtime automatically applies inline sourcemaps to V8
1202
1206
  * stack traces, so frame positions are already original source positions.
@@ -1246,7 +1250,8 @@ function extractInlineSourcemap(bundledCode) {
1246
1250
  if (!match) return null;
1247
1251
  try {
1248
1252
  const decoded = Buffer.from(assertDefined(match[1], "sourcemap base64 data missing"), "base64").toString("utf-8");
1249
- return new TraceMap(JSON.parse(decoded));
1253
+ const rawSourceMap = JSON.parse(decoded);
1254
+ return new TraceMap(rawSourceMap);
1250
1255
  } catch {
1251
1256
  return null;
1252
1257
  }
@@ -1396,7 +1401,7 @@ function formatMappedError(errorMessage, frames, traceMap, bundleDir) {
1396
1401
  }
1397
1402
  /**
1398
1403
  * Format an error string with sourcemap-based source locations.
1399
- * This is the main entry point for test-run error display.
1404
+ * This is the main entry point for function run error display.
1400
1405
  *
1401
1406
  * The platform runtime applies inline sourcemaps automatically, so V8
1402
1407
  * stack frames already contain original source positions. This function
@@ -1510,7 +1515,8 @@ function formatExecutionErrorFallback(error) {
1510
1515
  */
1511
1516
  function formatExecutionError(error, bundledCode) {
1512
1517
  if (bundledCode && error.stackTrace) {
1513
- const formatted = formatErrorWithSourcemap(composeExecutionErrorString(error), bundledCode, process.cwd());
1518
+ const errorString = composeExecutionErrorString(error);
1519
+ const formatted = formatErrorWithSourcemap(errorString, bundledCode, process.cwd());
1514
1520
  if (formatted) return formatted;
1515
1521
  }
1516
1522
  return formatExecutionErrorFallback(error);
@@ -1576,7 +1582,7 @@ async function downloadScriptForMapping(options) {
1576
1582
  const { client, workspaceId, scriptName, executionType, executionContentHash } = options;
1577
1583
  const registryName = scriptNameToRegistryName(scriptName, executionType);
1578
1584
  if (registryName == null) {
1579
- logger.debug(`Script "${scriptName}" is not a deployed registry script (e.g. test-run or seed); skipping sourcemap mapping.`);
1585
+ logger.debug(`Script "${scriptName}" is not a deployed registry script (e.g. function run or seed); skipping sourcemap mapping.`);
1580
1586
  return null;
1581
1587
  }
1582
1588
  if (executionContentHash === "") {
@@ -1598,7 +1604,7 @@ async function downloadScriptForMapping(options) {
1598
1604
  const logsCommand = defineAppCommand({
1599
1605
  name: "logs",
1600
1606
  description: "List or get function execution logs.",
1601
- notes: `When viewing a specific execution that failed, the command displays error details with the stack trace mapped back to your original source files (clickable file links and code snippets, matching \`function test-run\` output).
1607
+ notes: `When viewing a specific execution that failed, the command displays error details with the stack trace mapped back to your original source files (clickable file links and code snippets, matching \`function run\` output).
1602
1608
 
1603
1609
  Stack traces are mapped only when the execution includes a content hash for the exact build that ran. If the content hash is missing or the build is no longer available, the command falls back to a plain-text error display.`,
1604
1610
  examples: [
@@ -1628,7 +1634,8 @@ Stack traces are mapped only when the execution includes a content hash for the
1628
1634
  })
1629
1635
  }),
1630
1636
  run: async (args) => {
1631
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
1637
+ const accessToken = await loadAccessToken({ profile: args.profile });
1638
+ const client = await initOperatorClient(accessToken);
1632
1639
  const workspaceId = await loadWorkspaceId({
1633
1640
  workspaceId: args["workspace-id"],
1634
1641
  profile: args.profile
@@ -1674,18 +1681,18 @@ Stack traces are mapped only when the execution includes a content hash for the
1674
1681
  //#endregion
1675
1682
  //#region src/cli/commands/function/bundle.ts
1676
1683
  /**
1677
- * Bundler for function test-run command
1684
+ * Bundler for the function run command
1678
1685
  *
1679
1686
  * Bundles a single function file for execution via the TestExecScript API.
1680
1687
  * Generates an entry file based on the detected function type and bundles
1681
1688
  * with rolldown, following the same patterns as the existing bundlers.
1682
1689
  */
1683
1690
  /**
1684
- * Bundle a function file for test-run execution via TestExecScript API.
1691
+ * Bundle a function file for `function run` execution via the TestExecScript API.
1685
1692
  * @param options - Bundle options
1686
1693
  * @returns Bundled code and script name
1687
1694
  */
1688
- async function bundleForTestRun(options) {
1695
+ async function bundleForRun(options) {
1689
1696
  const { detected, sourceFile, baseDir, env = {}, machineUser, workspaceId } = options;
1690
1697
  const inlineSourcemap = resolveInlineSourcemap(options.inlineSourcemap);
1691
1698
  const bundleLogLevel = resolveBundleLogLevel(options.logLevel);
@@ -1820,7 +1827,7 @@ function buildMachinePrincipalExpr(machineUser, workspaceId) {
1820
1827
  //#endregion
1821
1828
  //#region src/cli/commands/function/detect.ts
1822
1829
  /**
1823
- * Function type detection for test-run command
1830
+ * Function type detection for the function run command
1824
1831
  *
1825
1832
  * Detects the function type (resolver, executor, workflow job, or plain function)
1826
1833
  * by dynamically importing the module and checking against known schemas.
@@ -1932,15 +1939,16 @@ function deriveNameFromPath(filePath) {
1932
1939
  }
1933
1940
 
1934
1941
  //#endregion
1935
- //#region src/cli/commands/function/test-run.ts
1942
+ //#region src/cli/commands/function/run.ts
1936
1943
  /**
1937
- * `tailor function test-run` command
1944
+ * `tailor function run` command
1938
1945
  *
1939
1946
  * Bundles and executes a function on the Tailor Platform server
1940
1947
  * without deploying (applying) the application.
1941
1948
  */
1942
- const testRunCommand = defineAppCommand({
1943
- name: "test-run",
1949
+ const runFunctionCommand = defineAppCommand({
1950
+ name: "run",
1951
+ aliases: ["test-run"],
1944
1952
  description: "Run a function on the Tailor Platform server without deploying.",
1945
1953
  args: z.object({
1946
1954
  ...workspaceArgs,
@@ -1969,8 +1977,10 @@ const testRunCommand = defineAppCommand({
1969
1977
  notes: `You can pass either a source file (\`.ts\`) or a pre-bundled file (\`.js\`).
1970
1978
  When a \`.js\` file is provided, detection and bundling are skipped and the file is executed as-is.
1971
1979
 
1980
+ \`test-run\` is a deprecated alias of this command and will be removed in v3.
1981
+
1972
1982
  > [!WARNING]
1973
- > Workflow job \`.start()\` calls do not work in test-run mode.
1983
+ > Workflow job \`.start()\` calls do not work in this mode.
1974
1984
  > Started jobs are not executed; only the target job's \`body\` function runs in isolation.`,
1975
1985
  examples: [
1976
1986
  {
@@ -1987,6 +1997,7 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
1987
1997
  }
1988
1998
  ],
1989
1999
  run: async (args) => {
2000
+ if (invokedViaTestRunAlias(process.argv)) logger.warn("`tailor function test-run` is deprecated and will be removed in v3. Use `tailor function run` instead.");
1990
2001
  const jsonOutput = logger.jsonMode;
1991
2002
  const filePath = path.resolve(args.file);
1992
2003
  if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
@@ -1998,7 +2009,8 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
1998
2009
  profile: args.profile,
1999
2010
  authConfig: config.auth
2000
2011
  });
2001
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
2012
+ const accessToken = await loadAccessToken({ profile: args.profile });
2013
+ const client = await initOperatorClient(accessToken);
2002
2014
  const workspaceId = await loadWorkspaceId({
2003
2015
  workspaceId: args["workspace-id"],
2004
2016
  profile: args.profile
@@ -2035,7 +2047,7 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
2035
2047
  }
2036
2048
  logger.info("Bundling...");
2037
2049
  const baseDir = path.dirname(config.path);
2038
- ({bundledCode, scriptName} = await bundleForTestRun({
2050
+ ({bundledCode, scriptName} = await bundleForRun({
2039
2051
  detected,
2040
2052
  sourceFile: filePath,
2041
2053
  baseDir,
@@ -2107,6 +2119,16 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
2107
2119
  }
2108
2120
  });
2109
2121
  /**
2122
+ * Detect whether the command was invoked through the deprecated `test-run`
2123
+ * alias. politty resolves aliases before dispatch, so the invoked name is only
2124
+ * observable from the raw argv.
2125
+ * @param argv - Process argv tokens
2126
+ * @returns true when the `test-run` alias follows the `function` subcommand
2127
+ */
2128
+ function invokedViaTestRunAlias(argv) {
2129
+ return argv.some((token, i) => token === "function" && argv[i + 1] === "test-run");
2130
+ }
2131
+ /**
2110
2132
  * Resolve auth namespace from config.
2111
2133
  * @param authConfig - Auth configuration from tailor.config.ts
2112
2134
  * @returns Resolved auth namespace
@@ -2181,7 +2203,7 @@ const functionCommand = defineCommand({
2181
2203
  get: getCommand$2,
2182
2204
  list: listCommand$9,
2183
2205
  logs: logsCommand,
2184
- "test-run": testRunCommand
2206
+ run: runFunctionCommand
2185
2207
  },
2186
2208
  async run() {
2187
2209
  await runCommand(listCommand$9, []);
@@ -2352,7 +2374,7 @@ const startAuthServer = async (args = {}) => {
2352
2374
  const timeout = setTimeout(() => {
2353
2375
  server.close();
2354
2376
  reject(/* @__PURE__ */ new Error("Login timeout exceeded"));
2355
- }, 300 * 1e3);
2377
+ }, 3e5);
2356
2378
  server.on("close", () => {
2357
2379
  clearTimeout(timeout);
2358
2380
  });
@@ -3097,7 +3119,7 @@ const createCommand$2 = defineAppCommand({
3097
3119
  permission: arg(z.enum(["write", "read"]).default("write"), { description: "Profile permission. 'read' blocks all write commands while the profile is active." }),
3098
3120
  "machine-user": arg(z.string().optional(), {
3099
3121
  alias: "m",
3100
- description: "Default machine user name for application-data commands (query, workflow start, function test-run, machineuser token)."
3122
+ description: "Default machine user name for application-data commands (query, workflow start, function run, machineuser token)."
3101
3123
  }),
3102
3124
  "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." }),
3103
3125
  "platform-url": arg(z.url().optional(), {
@@ -3238,7 +3260,7 @@ const updateCommand$1 = defineAppCommand({
3238
3260
  permission: arg(z.enum(["write", "read"]).optional(), { description: "Profile permission. 'read' blocks all write commands; 'write' lifts the restriction." }),
3239
3261
  "machine-user": arg(z.string().optional(), {
3240
3262
  alias: "m",
3241
- description: "Default machine user name for application-data commands (query, workflow start, function test-run, machineuser token). Pass an empty string to clear."
3263
+ description: "Default machine user name for application-data commands (query, workflow start, function run, machineuser token). Pass an empty string to clear."
3242
3264
  }),
3243
3265
  "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." }),
3244
3266
  "platform-url": arg(z.union([z.url(), z.literal("")]).optional(), { description: "Platform API base URL for this profile. Pass an empty string to clear." }),
@@ -3287,16 +3309,24 @@ const updateCommand$1 = defineAppCommand({
3287
3309
  profile.workspace_id = newWorkspaceId;
3288
3310
  if (args.permission === "read") profile.readonly = true;
3289
3311
  else if (args.permission === "write") delete profile.readonly;
3290
- if (args["machine-user"] !== void 0) if (args["machine-user"] === "") delete profile.machine_user;
3291
- else profile.machine_user = args["machine-user"];
3312
+ if (args["machine-user"] !== void 0) {
3313
+ if (args["machine-user"] === "") delete profile.machine_user;
3314
+ else profile.machine_user = args["machine-user"];
3315
+ }
3292
3316
  if (args["machine-user-override"] === "deny") profile.machine_user_override = "deny";
3293
3317
  else if (args["machine-user-override"] === "allow") delete profile.machine_user_override;
3294
- if (args["platform-url"] !== void 0) if (args["platform-url"] === "") delete profile.platform_url;
3295
- else profile.platform_url = args["platform-url"];
3296
- if (args["oauth2-client-id"] !== void 0) if (args["oauth2-client-id"] === "") delete profile.oauth2_client_id;
3297
- else profile.oauth2_client_id = args["oauth2-client-id"];
3298
- if (args["console-url"] !== void 0) if (args["console-url"] === "") delete profile.console_url;
3299
- else profile.console_url = args["console-url"];
3318
+ if (args["platform-url"] !== void 0) {
3319
+ if (args["platform-url"] === "") delete profile.platform_url;
3320
+ else profile.platform_url = args["platform-url"];
3321
+ }
3322
+ if (args["oauth2-client-id"] !== void 0) {
3323
+ if (args["oauth2-client-id"] === "") delete profile.oauth2_client_id;
3324
+ else profile.oauth2_client_id = args["oauth2-client-id"];
3325
+ }
3326
+ if (args["console-url"] !== void 0) {
3327
+ if (args["console-url"] === "") delete profile.console_url;
3328
+ else profile.console_url = args["console-url"];
3329
+ }
3300
3330
  writePlatformConfig(config);
3301
3331
  if (!args.json) logger.success(`Profile "${args.name}" updated successfully`);
3302
3332
  const profileInfo = {
@@ -3434,7 +3464,8 @@ const createSecretCommand = defineAppCommand({
3434
3464
  }),
3435
3465
  run: async (args) => {
3436
3466
  await assertWritable({ profile: args.profile });
3437
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
3467
+ const accessToken = await loadAccessToken({ profile: args.profile });
3468
+ const client = await initOperatorClient(accessToken);
3438
3469
  const workspaceId = await loadWorkspaceId({
3439
3470
  workspaceId: args["workspace-id"],
3440
3471
  profile: args.profile
@@ -3484,7 +3515,8 @@ const deleteSecretCommand = defineAppCommand({
3484
3515
  }),
3485
3516
  run: async (args) => {
3486
3517
  await assertWritable({ profile: args.profile });
3487
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
3518
+ const accessToken = await loadAccessToken({ profile: args.profile });
3519
+ const client = await initOperatorClient(accessToken);
3488
3520
  const workspaceId = await loadWorkspaceId({
3489
3521
  workspaceId: args["workspace-id"],
3490
3522
  profile: args.profile
@@ -3533,7 +3565,8 @@ function secretInfo(secret) {
3533
3565
  * @returns List of secrets
3534
3566
  */
3535
3567
  async function secretList(options) {
3536
- const client = await initOperatorClient(await loadAccessToken({ profile: options.profile }));
3568
+ const accessToken = await loadAccessToken({ profile: options.profile });
3569
+ const client = await initOperatorClient(accessToken);
3537
3570
  const workspaceId = await loadWorkspaceId({
3538
3571
  workspaceId: options.workspaceId,
3539
3572
  profile: options.profile
@@ -3587,7 +3620,8 @@ const updateSecretCommand = defineAppCommand({
3587
3620
  }),
3588
3621
  run: async (args) => {
3589
3622
  await assertWritable({ profile: args.profile });
3590
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
3623
+ const accessToken = await loadAccessToken({ profile: args.profile });
3624
+ const client = await initOperatorClient(accessToken);
3591
3625
  const workspaceId = await loadWorkspaceId({
3592
3626
  workspaceId: args["workspace-id"],
3593
3627
  profile: args.profile
@@ -3640,7 +3674,8 @@ const createCommand$1 = defineAppCommand({
3640
3674
  }),
3641
3675
  run: async (args) => {
3642
3676
  await assertWritable({ profile: args.profile });
3643
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
3677
+ const accessToken = await loadAccessToken({ profile: args.profile });
3678
+ const client = await initOperatorClient(accessToken);
3644
3679
  const workspaceId = await loadWorkspaceId({
3645
3680
  workspaceId: args["workspace-id"],
3646
3681
  profile: args.profile
@@ -3670,7 +3705,8 @@ const deleteCommand$2 = defineAppCommand({
3670
3705
  }),
3671
3706
  run: async (args) => {
3672
3707
  await assertWritable({ profile: args.profile });
3673
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
3708
+ const accessToken = await loadAccessToken({ profile: args.profile });
3709
+ const client = await initOperatorClient(accessToken);
3674
3710
  const workspaceId = await loadWorkspaceId({
3675
3711
  workspaceId: args["workspace-id"],
3676
3712
  profile: args.profile
@@ -3715,7 +3751,8 @@ function vaultInfo(vault) {
3715
3751
  * @returns List of vaults
3716
3752
  */
3717
3753
  async function vaultList(options) {
3718
- const client = await initOperatorClient(await loadAccessToken({ profile: options?.profile }));
3754
+ const accessToken = await loadAccessToken({ profile: options?.profile });
3755
+ const client = await initOperatorClient(accessToken);
3719
3756
  const workspaceId = await loadWorkspaceId({
3720
3757
  workspaceId: options?.workspaceId,
3721
3758
  profile: options?.profile
@@ -3818,6 +3855,14 @@ function detectDefaultBranch(cwd, run = defaultGitRunner) {
3818
3855
  const LOCK_VERSION = 1;
3819
3856
  /** Lock file path, relative to the repository root. */
3820
3857
  const LOCK_FILENAME = ".github/tailor.lock";
3858
+ function assertSafeLockPath(outputDir) {
3859
+ for (const relativePath of [".github", LOCK_FILENAME]) try {
3860
+ if (fs.lstatSync(path.join(outputDir, relativePath)).isSymbolicLink()) throw new Error(`Refusing to use ${LOCK_FILENAME}: "${relativePath}" is a symbolic link.`);
3861
+ } catch (error) {
3862
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") continue;
3863
+ throw error;
3864
+ }
3865
+ }
3821
3866
  /**
3822
3867
  * Compute the lock content hash for a rendered workflow file.
3823
3868
  * @param content - File content to hash
@@ -3843,6 +3888,7 @@ function lockPath(outputDir) {
3843
3888
  * @returns Parsed lock file, or null when absent
3844
3889
  */
3845
3890
  function readLock(outputDir) {
3891
+ assertSafeLockPath(outputDir);
3846
3892
  const file = lockPath(outputDir);
3847
3893
  if (!fs.existsSync(file)) return null;
3848
3894
  let parsed;
@@ -3862,6 +3908,7 @@ function readLock(outputDir) {
3862
3908
  * @param lock - Lock file contents to serialize
3863
3909
  */
3864
3910
  function writeLock(outputDir, lock) {
3911
+ assertSafeLockPath(outputDir);
3865
3912
  const file = lockPath(outputDir);
3866
3913
  fs.mkdirSync(path.dirname(file), { recursive: true });
3867
3914
  fs.writeFileSync(file, `${JSON.stringify(lock, null, 2)}\n`, "utf-8");
@@ -3880,23 +3927,23 @@ function findTarget(lock, kind, workspaceName) {
3880
3927
 
3881
3928
  //#endregion
3882
3929
  //#region src/cli/commands/setup/action.yml
3883
- var action_default = "# __HEADER__\nname: __WORKSPACE_NAME__\ndescription: >-\n Deploy __WORKSPACE_NAME__ to Tailor Platform.\n Checkout and dependency installation are expected to be handled by the caller.\n\ninputs:\n workspace-id:\n description: Tailor Platform workspace id\n required: true\n name:\n description: Workspace name (used in Slack notifications)\n required: true\n working-directory:\n description: App directory relative to the repository root\n required: false\n default: \".\"\n config:\n description: SDK config path, or comma-separated config paths for multi-config deploys\n required: false\n default: \"tailor.config.ts\"\n deploy:\n description: Whether to run the Tailor deploy step after build steps\n required: false\n default: \"true\"\n build-site:\n description: Whether to run the static website build slot when present\n required: false\n default: \"true\"\n platform-client-id:\n description: Machine-user client id\n required: true\n platform-client-secret:\n description: Machine-user client secret\n required: true\n slack-token:\n description: Slack bot token\n required: false\n slack-channel-id:\n description: Slack channel id\n required: false\n user-mapping:\n description: JSON string mapping GitHub usernames to Slack user IDs\n required: false\n package-manager:\n description: Package manager used for dependency installation (pnpm, npm, yarn, bun)\n required: false\n default: pnpm\n\noutputs:\n app-url:\n description: Deployed application URL\n value: ${{ steps.tailor-apply.outputs.app-url }}\n\nruns:\n using: composite\n steps:\n # __STATIC_WEBSITE_BUILD_START__\n - id: build-site\n if: inputs.build-site == 'true'\n shell: bash\n run: |\n # Replace with your static website build command.\n # Example: pnpm --filter <package-name> run build:static\n # Note: this run: command is user-editable. Custom build commands are preserved across\n # reruns and are not treated as drift by setup check. Other edits to this file are flagged\n # as drift and will be overwritten if you re-run setup action with --force.\n true\n # __STATIC_WEBSITE_BUILD_END__\n - id: tailor-apply\n if: inputs.deploy == 'true'\n uses: tailor-platform/actions/deploy@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n env:\n TAILOR_PLATFORM_SDK_CONFIG_PATH: ${{ inputs.config }}\n with:\n workspace-id: ${{ inputs.workspace-id }}\n working-directory: ${{ inputs.working-directory }}\n package-manager: ${{ inputs.package-manager }}\n platform-client-id: ${{ inputs.platform-client-id }}\n platform-client-secret: ${{ inputs.platform-client-secret }}\n - id: tailor-notify\n if: failure() && inputs.slack-token != '' && inputs.slack-channel-id != ''\n uses: tailor-platform/actions/notify@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n provider: slack\n status: failure\n workspace-name: ${{ inputs.name }}\n slack-token: ${{ inputs.slack-token }}\n slack-channel-id: ${{ inputs.slack-channel-id }}\n user-mapping: ${{ inputs.user-mapping }}\n";
3930
+ var action_default = "# __HEADER__\nname: __WORKSPACE_NAME__\ndescription: >-\n Deploy __WORKSPACE_NAME__ to Tailor Platform.\n Checkout and dependency installation are expected to be handled by the caller.\n\ninputs:\n workspace-id:\n description: Tailor Platform workspace id\n required: true\n name:\n description: Workspace name (used in Slack notifications)\n required: true\n working-directory:\n description: App directory relative to the repository root\n required: false\n default: \".\"\n config:\n description: SDK config path, or comma-separated config paths for multi-config deploys\n required: false\n default: \"tailor.config.ts\"\n deploy:\n description: Whether to run the Tailor deploy step after build steps\n required: false\n default: \"true\"\n build-site:\n description: Whether to run the static website build slot when present\n required: false\n default: \"true\"\n platform-client-id:\n description: Machine-user client id\n required: true\n platform-client-secret:\n description: Machine-user client secret\n required: true\n slack-token:\n description: Slack bot token\n required: false\n slack-channel-id:\n description: Slack channel id\n required: false\n user-mapping:\n description: JSON string mapping GitHub usernames to Slack user IDs\n required: false\n package-manager:\n description: Package manager used for dependency installation (pnpm, npm, yarn, bun)\n required: false\n default: pnpm\n\noutputs:\n app-url:\n description: Deployed application URL\n value: ${{ steps.tailor-apply.outputs.app-url }}\n\nruns:\n using: composite\n steps:\n # __STATIC_WEBSITE_BUILD_START__\n - id: build-site\n if: inputs.build-site == 'true'\n shell: bash\n run: |\n # Replace with your static website build command.\n # Example: pnpm --filter <package-name> run build:static\n # Note: this run: command is user-editable. Custom build commands are preserved across\n # reruns and are not treated as drift by setup check. Other edits to this file are flagged\n # as drift and will be overwritten if you re-run setup action with --force.\n true\n # __STATIC_WEBSITE_BUILD_END__\n - id: tailor-apply\n if: inputs.deploy == 'true'\n uses: tailor-platform/actions/deploy@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n env:\n TAILOR_PLATFORM_SDK_CONFIG_PATH: ${{ inputs.config }}\n with:\n workspace-id: ${{ inputs.workspace-id }}\n working-directory: ${{ inputs.working-directory }}\n package-manager: ${{ inputs.package-manager }}\n platform-client-id: ${{ inputs.platform-client-id }}\n platform-client-secret: ${{ inputs.platform-client-secret }}\n - id: tailor-notify\n if: failure() && inputs.slack-token != '' && inputs.slack-channel-id != ''\n uses: tailor-platform/actions/notify@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n provider: slack\n status: failure\n workspace-name: ${{ inputs.name }}\n slack-token: ${{ inputs.slack-token }}\n slack-channel-id: ${{ inputs.slack-channel-id }}\n user-mapping: ${{ inputs.user-mapping }}\n";
3884
3931
 
3885
3932
  //#endregion
3886
3933
  //#region src/cli/commands/setup/branch.workflow.yml
3887
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: tailor-platform/actions/setup@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_START__\n - id: tailor-seed-validate\n run: __TAILOR_CLI__ seed validate\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_END__\n - id: tailor-drift-check\n uses: tailor-platform/actions/drift-check@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __MIGRATION_DRIFT_CHECK_START__\n - id: tailor-migration-drift-check\n uses: tailor-platform/actions/migration-drift-check@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __MIGRATION_DRIFT_CHECK_END__\n - id: tailor-plan\n # Fork PRs cannot read secrets; the checks above still run for them.\n if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork\n uses: tailor-platform/actions/plan@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n # __PLAN_JOB_END__\n # __ERD_PREVIEW_JOB_START__\n tailor-erd-preview-matrix:\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n contents: read\n outputs:\n namespaces: ${{ steps.tailor-erd-preview-matrix.outputs.namespaces }}\n base-app-dir: ${{ steps.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n with:\n persist-credentials: false\n - id: tailor-checkout-base\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n with:\n persist-credentials: false\n - id: tailor-setup\n uses: tailor-platform/actions/setup@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-checkout-base\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\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@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10\n with:\n version: ${{ steps.tailor-detect-base-package-manager.outputs['pnpm-version'] }}\n package_json_file: .tailor-erd-base/package.json\n run_install: false\n - id: tailor-setup-base-node\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] != 'bun'\n uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7\n with:\n node-version-file: .tailor-erd-base/package.json\n - id: tailor-setup-base-yarn\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'yarn'\n shell: bash\n working-directory: .tailor-erd-base\n run: |\n set -euo pipefail\n corepack enable\n - id: tailor-setup-base-bun\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'bun'\n uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0\n - id: tailor-install-base\n shell: bash\n working-directory: .tailor-erd-base\n env:\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n run: |\n set -euo pipefail\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) pnpm install --frozen-lockfile ;;\n npm) npm ci ;;\n yarn)\n if grep -q '^__metadata:' yarn.lock; then\n yarn install --immutable\n else\n yarn install --frozen-lockfile\n fi\n ;;\n bun) bun install --frozen-lockfile ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n - id: tailor-restore-head-setup\n uses: tailor-platform/actions/setup@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.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-platform/sdk \"$@\" ;;\n yarn) yarn tailor \"$@\" ;;\n bun) bun run 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: tailor-platform/actions/setup@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n # __SEED_DATA_START__\n - id: seed-data\n if: false\n shell: bash\n # __WORKING_DIRECTORY__\n run: |\n # Uncomment and implement to seed data on deploy.\n # Run after apply so the schema is ready.\n # Example: node scripts/seed.mjs\n true\n # __SEED_DATA_END__\n - id: slack-prereq\n if: always()\n shell: bash\n env:\n SLACK_TOKEN: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n SLACK_CHANNEL_ID: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n run: |\n if [[ -n \"$SLACK_TOKEN\" && -n \"$SLACK_CHANNEL_ID\" ]]; then\n echo \"ready=true\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-notify\n if: always() && steps.slack-prereq.outputs.ready == 'true'\n uses: tailor-platform/actions/notify@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n provider: slack\n status: ${{ job.status }}\n workspace-name: __WORKSPACE_NAME__\n slack-token: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n slack-channel-id: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n # user-mapping: ${{ vars.TAILOR_SLACK_USER_MAPPING }}\n";
3934
+ 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: tailor-platform/actions/setup@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_START__\n - id: tailor-seed-validate\n run: __TAILOR_CLI__ seed validate\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_END__\n - id: tailor-drift-check\n uses: tailor-platform/actions/drift-check@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n fail-on-drift: ${{ vars.TAILOR_PLATFORM_FAIL_ON_DRIFT == 'true' }}\n # __MIGRATION_DRIFT_CHECK_START__\n - id: tailor-migration-drift-check\n uses: tailor-platform/actions/migration-drift-check@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __MIGRATION_DRIFT_CHECK_END__\n - id: tailor-plan\n # Fork PRs cannot read secrets; the checks above still run for them.\n if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork\n uses: tailor-platform/actions/plan@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n # __PLAN_JOB_END__\n # __ERD_PREVIEW_JOB_START__\n tailor-erd-preview-matrix:\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n contents: read\n outputs:\n namespaces: ${{ steps.tailor-erd-preview-matrix.outputs.namespaces }}\n base-app-dir: ${{ steps.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n with:\n persist-credentials: false\n - id: tailor-checkout-base\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n with:\n persist-credentials: false\n - id: tailor-setup\n uses: tailor-platform/actions/setup@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-checkout-base\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\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@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10\n with:\n version: ${{ steps.tailor-detect-base-package-manager.outputs['pnpm-version'] }}\n package_json_file: .tailor-erd-base/package.json\n run_install: false\n - id: tailor-setup-base-node\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] != 'bun'\n uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7\n with:\n node-version-file: .tailor-erd-base/package.json\n - id: tailor-setup-base-yarn\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'yarn'\n shell: bash\n working-directory: .tailor-erd-base\n run: |\n set -euo pipefail\n corepack enable\n - id: tailor-setup-base-bun\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'bun'\n uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0\n - id: tailor-install-base\n shell: bash\n working-directory: .tailor-erd-base\n env:\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n run: |\n set -euo pipefail\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) pnpm install --frozen-lockfile ;;\n npm) npm ci ;;\n yarn)\n if grep -q '^__metadata:' yarn.lock; then\n yarn install --immutable\n else\n yarn install --frozen-lockfile\n fi\n ;;\n bun) bun install --frozen-lockfile ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n - id: tailor-restore-head-setup\n uses: tailor-platform/actions/setup@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.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-platform/sdk \"$@\" ;;\n yarn) yarn tailor \"$@\" ;;\n bun) bun run 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: tailor-platform/actions/setup@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n # __SEED_DATA_START__\n - id: seed-data\n if: false\n shell: bash\n # __WORKING_DIRECTORY__\n run: |\n # Uncomment and implement to seed data on deploy.\n # Run after apply so the schema is ready.\n # Example: node scripts/seed.mjs\n true\n # __SEED_DATA_END__\n - id: slack-prereq\n if: always()\n shell: bash\n env:\n SLACK_TOKEN: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n SLACK_CHANNEL_ID: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n run: |\n if [[ -n \"$SLACK_TOKEN\" && -n \"$SLACK_CHANNEL_ID\" ]]; then\n echo \"ready=true\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-notify\n if: always() && steps.slack-prereq.outputs.ready == 'true'\n uses: tailor-platform/actions/notify@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n provider: slack\n status: ${{ job.status }}\n workspace-name: __WORKSPACE_NAME__\n slack-token: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n slack-channel-id: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n # user-mapping: ${{ vars.TAILOR_SLACK_USER_MAPPING }}\n";
3888
3935
 
3889
3936
  //#endregion
3890
3937
  //#region src/cli/commands/setup/coordinate.workflow.yml
3891
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n target-branch: __BRANCH__\n # __TAG_GUARD_JOB_END__\n\n tailor-plan:\n # __PLAN_NEEDS__\n # __PLAN_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 60\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-plan-__WORKSPACE_NAME__-${{ github.event.pull_request.number || github.run_id }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: ./.github/actions/tailor-setup\n # __PER_APP_DEPLOY_STEPS__\n";
3938
+ 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n target-branch: __BRANCH__\n # __TAG_GUARD_JOB_END__\n\n tailor-plan:\n # __PLAN_NEEDS__\n # __PLAN_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 60\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-plan-__WORKSPACE_NAME__-${{ github.event.pull_request.number || github.run_id }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: ./.github/actions/tailor-setup\n # __PER_APP_DEPLOY_STEPS__\n";
3892
3939
 
3893
3940
  //#endregion
3894
3941
  //#region src/cli/commands/setup/preview.workflow.yml
3895
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: tailor-platform/actions/setup@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n - id: tailor-preview-deploy\n uses: tailor-platform/actions/preview-deploy@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n workspace-name-prefix: __WORKSPACE_NAME__\n region: \"__REGION__\"\n organization-id: ${{ vars.TAILOR_PLATFORM_ORGANIZATION_ID }}\n folder-id: ${{ vars.TAILOR_PLATFORM_FOLDER_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n - id: tailor-preview-comment\n if: always()\n uses: tailor-platform/actions/preview-comment@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n workspace-id: ${{ steps.tailor-preview-deploy.outputs.workspace-id }}\n workspace-name: ${{ steps.tailor-preview-deploy.outputs.workspace-name }}\n status: ${{ job.status == 'success' && 'success' || 'failure' }}\n app-url: ${{ steps.tailor-preview-deploy.outputs.app-url }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n\n tailor-preview-cleanup:\n # __CLEANUP_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 15\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n steps:\n - id: tailor-checkout\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: tailor-platform/actions/setup@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-preview-cleanup\n uses: tailor-platform/actions/preview-cleanup@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n workspace-name-prefix: __WORKSPACE_NAME__\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n";
3942
+ 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: tailor-platform/actions/setup@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n - id: tailor-drift-check\n uses: tailor-platform/actions/drift-check@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n fail-on-drift: ${{ vars.TAILOR_PLATFORM_FAIL_ON_DRIFT == 'true' }}\n - id: tailor-preview-deploy\n uses: tailor-platform/actions/preview-deploy@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n workspace-name-prefix: __WORKSPACE_NAME__\n region: \"__REGION__\"\n organization-id: ${{ vars.TAILOR_PLATFORM_ORGANIZATION_ID }}\n folder-id: ${{ vars.TAILOR_PLATFORM_FOLDER_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n - id: tailor-preview-comment\n if: always()\n uses: tailor-platform/actions/preview-comment@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n workspace-id: ${{ steps.tailor-preview-deploy.outputs.workspace-id }}\n workspace-name: ${{ steps.tailor-preview-deploy.outputs.workspace-name }}\n status: ${{ job.status == 'success' && 'success' || 'failure' }}\n app-url: ${{ steps.tailor-preview-deploy.outputs.app-url }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n\n tailor-preview-cleanup:\n # __CLEANUP_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 15\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n steps:\n - id: tailor-checkout\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: tailor-platform/actions/setup@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-preview-cleanup\n uses: tailor-platform/actions/preview-cleanup@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n workspace-name-prefix: __WORKSPACE_NAME__\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n";
3896
3943
 
3897
3944
  //#endregion
3898
3945
  //#region src/cli/commands/setup/tag.workflow.yml
3899
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n target-branch: \"__BRANCH__\"\n # __TAG_GUARD_JOB_END__\n tailor-plan:\n # __PLAN_NEEDS__\n # __PLAN_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n steps:\n - id: tailor-checkout\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: tailor-platform/actions/setup@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_START__\n - id: tailor-seed-validate\n run: __TAILOR_CLI__ seed validate\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_END__\n - id: tailor-drift-check\n uses: tailor-platform/actions/drift-check@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __MIGRATION_DRIFT_CHECK_START__\n - id: tailor-migration-drift-check\n uses: tailor-platform/actions/migration-drift-check@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __MIGRATION_DRIFT_CHECK_END__\n - id: tailor-plan\n uses: tailor-platform/actions/plan@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n\n tailor-deploy:\n needs: tailor-plan\n if: ${{ !(github.event_name == 'workflow_dispatch' && inputs['dry-run']) }}\n environment: __ENVIRONMENT__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: tailor-platform/actions/setup@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n # __SEED_DATA_START__\n - id: seed-data\n if: false\n shell: bash\n # __WORKING_DIRECTORY__\n run: |\n # Uncomment and implement to seed data on deploy.\n # Run after apply so the schema is ready.\n # Example: node scripts/seed.mjs\n true\n # __SEED_DATA_END__\n - id: slack-prereq\n if: always()\n shell: bash\n env:\n SLACK_TOKEN: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n SLACK_CHANNEL_ID: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n run: |\n if [[ -n \"$SLACK_TOKEN\" && -n \"$SLACK_CHANNEL_ID\" ]]; then\n echo \"ready=true\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-notify\n if: always() && steps.slack-prereq.outputs.ready == 'true'\n uses: tailor-platform/actions/notify@1f2d0cc26b84b9fdae8a3a0427526df31e6c966b # v2.0.0\n with:\n provider: slack\n status: ${{ job.status }}\n workspace-name: __WORKSPACE_NAME__\n slack-token: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n slack-channel-id: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n # user-mapping: ${{ vars.TAILOR_SLACK_USER_MAPPING }}\n";
3946
+ 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n target-branch: \"__BRANCH__\"\n # __TAG_GUARD_JOB_END__\n tailor-plan:\n # __PLAN_NEEDS__\n # __PLAN_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n steps:\n - id: tailor-checkout\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: tailor-platform/actions/setup@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_START__\n - id: tailor-seed-validate\n run: __TAILOR_CLI__ seed validate\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_END__\n - id: tailor-drift-check\n uses: tailor-platform/actions/drift-check@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n fail-on-drift: ${{ vars.TAILOR_PLATFORM_FAIL_ON_DRIFT == 'true' }}\n # __MIGRATION_DRIFT_CHECK_START__\n - id: tailor-migration-drift-check\n uses: tailor-platform/actions/migration-drift-check@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __MIGRATION_DRIFT_CHECK_END__\n - id: tailor-plan\n uses: tailor-platform/actions/plan@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n\n tailor-deploy:\n needs: tailor-plan\n if: ${{ !(github.event_name == 'workflow_dispatch' && inputs['dry-run']) }}\n environment: __ENVIRONMENT__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n - id: tailor-setup\n uses: tailor-platform/actions/setup@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n # __SEED_DATA_START__\n - id: seed-data\n if: false\n shell: bash\n # __WORKING_DIRECTORY__\n run: |\n # Uncomment and implement to seed data on deploy.\n # Run after apply so the schema is ready.\n # Example: node scripts/seed.mjs\n true\n # __SEED_DATA_END__\n - id: slack-prereq\n if: always()\n shell: bash\n env:\n SLACK_TOKEN: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n SLACK_CHANNEL_ID: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n run: |\n if [[ -n \"$SLACK_TOKEN\" && -n \"$SLACK_CHANNEL_ID\" ]]; then\n echo \"ready=true\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-notify\n if: always() && steps.slack-prereq.outputs.ready == 'true'\n uses: tailor-platform/actions/notify@85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c # v2.2.0\n with:\n provider: slack\n status: ${{ job.status }}\n workspace-name: __WORKSPACE_NAME__\n slack-token: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n slack-channel-id: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n # user-mapping: ${{ vars.TAILOR_SLACK_USER_MAPPING }}\n";
3900
3947
 
3901
3948
  //#endregion
3902
3949
  //#region src/cli/commands/setup/templates.ts
@@ -4044,6 +4091,7 @@ function renderPreviewWorkflow(params) {
4044
4091
  "tailor-preview-deploy/tailor-setup",
4045
4092
  "tailor-preview-deploy/tailor-install",
4046
4093
  "tailor-preview-deploy/tailor-generate-check",
4094
+ "tailor-preview-deploy/tailor-drift-check",
4047
4095
  "tailor-preview-deploy/tailor-preview-deploy",
4048
4096
  "tailor-preview-deploy/tailor-preview-comment",
4049
4097
  "tailor-preview-cleanup",
@@ -4072,8 +4120,8 @@ function renderActionWorkflow(params) {
4072
4120
  generatedIds: ["tailor-apply", "tailor-notify"]
4073
4121
  };
4074
4122
  }
4075
- const ACTIONS_SHA = "1f2d0cc26b84b9fdae8a3a0427526df31e6c966b";
4076
- const ACTIONS_VERSION = "v2.0.0";
4123
+ const ACTIONS_SHA = "85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c";
4124
+ const ACTIONS_VERSION = "v2.2.0";
4077
4125
  /**
4078
4126
  * Render the coordinator workflow that orchestrates per-app composite actions.
4079
4127
  *
@@ -4117,7 +4165,8 @@ function renderCoordinateWorkflow(params) {
4117
4165
  `- id: tailor-drift-check`,
4118
4166
  ` uses: tailor-platform/actions/drift-check@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
4119
4167
  ` with:`,
4120
- ` package-manager: ${packageManager}`
4168
+ ` package-manager: ${packageManager}`,
4169
+ ` fail-on-drift: \${{ vars.TAILOR_PLATFORM_FAIL_ON_DRIFT == 'true' }}`
4121
4170
  ].join("\n");
4122
4171
  const configPath = (app) => app.dir === "." ? "tailor.config.ts" : `${app.dir}/tailor.config.ts`;
4123
4172
  const groupConfigPath = (group) => group.apps.map(configPath).join(",");
@@ -4535,7 +4584,8 @@ function decideAction(obj) {
4535
4584
  }
4536
4585
  if (!fileExists) return { action: "restore" };
4537
4586
  if (currentContent !== null) {
4538
- if (hashContent(normalize ? normalize(currentContent) : currentContent) === existing.contentHash) return { action: "regenerate" };
4587
+ const normalizedContent = normalize ? normalize(currentContent) : currentContent;
4588
+ if (hashContent(normalizedContent) === existing.contentHash) return { action: "regenerate" };
4539
4589
  }
4540
4590
  if (force) return { action: "regenerate" };
4541
4591
  return {
@@ -4621,7 +4671,7 @@ async function setupTarget(options) {
4621
4671
  kind: resolved.kind,
4622
4672
  workspaceName: resolved.workspaceName,
4623
4673
  file: resolved.file,
4624
- templateVersion: 9,
4674
+ templateVersion: 11,
4625
4675
  inputs: resolved.inputs,
4626
4676
  generatedIds: resolved.render.generatedIds,
4627
4677
  ejectedIds: existing?.ejectedIds ?? [],
@@ -4689,7 +4739,7 @@ async function setupCoordinate(options) {
4689
4739
  seenNames.add(name);
4690
4740
  const entry = actionTargets.get(name);
4691
4741
  if (!entry) throw new Error(`Action target "${name}" not found in .github/tailor.lock. Run \`tailor setup action --name ${name}\` first.`);
4692
- if (names.length > 1 && entry.templateVersion < 9) throw new Error(`Action target "${name}" was generated with an older setup template. Run \`tailor setup action --name ${name} --force\` before grouping it in setup coordinate.`);
4742
+ if (names.length > 1 && entry.templateVersion < 11) throw new Error(`Action target "${name}" was generated with an older setup template. Run \`tailor setup action --name ${name} --force\` before grouping it in setup coordinate.`);
4693
4743
  validateDir(entry.inputs.dir);
4694
4744
  return {
4695
4745
  name,
@@ -4741,7 +4791,7 @@ async function setupCoordinate(options) {
4741
4791
  kind: "coordinate",
4742
4792
  workspaceName: coordinatorName,
4743
4793
  file,
4744
- templateVersion: 9,
4794
+ templateVersion: 11,
4745
4795
  inputs: {
4746
4796
  branch,
4747
4797
  branchAutoDetected: coordinateKind === "branch" ? branchAutoDetected : branch !== null ? false : void 0,
@@ -4785,6 +4835,7 @@ async function setupCoordinate(options) {
4785
4835
 
4786
4836
  //#endregion
4787
4837
  //#region src/cli/commands/setup/check.ts
4838
+ const DRIFT_COUNT_MARKER = "TAILOR_SETUP_CHECK_DRIFT_COUNT";
4788
4839
  /**
4789
4840
  * Compute drift findings for one target by comparing its recorded lock state
4790
4841
  * against the current repository/config state.
@@ -4929,7 +4980,7 @@ async function checkGitHub(options) {
4929
4980
  currentHash,
4930
4981
  configExists,
4931
4982
  defaultBranch,
4932
- templateVersion: 9,
4983
+ templateVersion: 11,
4933
4984
  erdNamespaces,
4934
4985
  hasMigrations,
4935
4986
  hasSeeds,
@@ -4942,6 +4993,7 @@ async function checkGitHub(options) {
4942
4993
  return;
4943
4994
  }
4944
4995
  for (const finding of findings) logger.warn(`[${finding.target}] ${finding.message} (ignore key: ${finding.rule})`);
4996
+ if (options.ci) logger.log(`${DRIFT_COUNT_MARKER}=${String(findings.length)}`);
4945
4997
  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.`);
4946
4998
  }
4947
4999
 
@@ -5014,6 +5066,134 @@ async function setupDelete(options) {
5014
5066
  }
5015
5067
  }
5016
5068
 
5069
+ //#endregion
5070
+ //#region src/cli/commands/setup/renovate.ts
5071
+ const RENOVATE_CONFIG_FILE = "renovate.json";
5072
+ const RENOVATE_PRESET = "github>tailor-inc/renovate-config";
5073
+ const RENOVATE_CONFIG_FILES = [
5074
+ RENOVATE_CONFIG_FILE,
5075
+ "renovate.jsonc",
5076
+ "renovate.json5",
5077
+ ".github/renovate.json",
5078
+ ".github/renovate.jsonc",
5079
+ ".github/renovate.json5",
5080
+ ".gitlab/renovate.json",
5081
+ ".gitlab/renovate.jsonc",
5082
+ ".gitlab/renovate.json5",
5083
+ ".renovaterc",
5084
+ ".renovaterc.json",
5085
+ ".renovaterc.jsonc",
5086
+ ".renovaterc.json5"
5087
+ ];
5088
+ function getPathEntry(filePath) {
5089
+ try {
5090
+ return fs.lstatSync(filePath);
5091
+ } catch (error) {
5092
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
5093
+ throw error;
5094
+ }
5095
+ }
5096
+ function pathEntryExists(filePath) {
5097
+ return getPathEntry(filePath) !== null;
5098
+ }
5099
+ function extendsPreset(config) {
5100
+ if (!("extends" in config)) return false;
5101
+ const value = config.extends;
5102
+ return Array.isArray(value) && value.includes("github>tailor-inc/renovate-config");
5103
+ }
5104
+ function readJsonConfig(filePath) {
5105
+ const entry = getPathEntry(filePath);
5106
+ if (entry === null || !entry.isFile()) return null;
5107
+ let parsed;
5108
+ try {
5109
+ parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
5110
+ } catch {
5111
+ return null;
5112
+ }
5113
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
5114
+ }
5115
+ function classifyConfig(location, filePath, config) {
5116
+ if (config === null) return {
5117
+ kind: "unreadable",
5118
+ location
5119
+ };
5120
+ if (extendsPreset(config)) return {
5121
+ kind: "extends-preset",
5122
+ location
5123
+ };
5124
+ return {
5125
+ kind: "appendable",
5126
+ location,
5127
+ filePath,
5128
+ config
5129
+ };
5130
+ }
5131
+ function findExistingConfig(outputDir) {
5132
+ for (const file of RENOVATE_CONFIG_FILES) {
5133
+ const filePath = path.join(outputDir, file);
5134
+ if (!pathEntryExists(filePath)) continue;
5135
+ return classifyConfig(file, filePath, readJsonConfig(filePath));
5136
+ }
5137
+ const packageJsonPath = path.join(outputDir, "package.json");
5138
+ if (!fs.existsSync(packageJsonPath)) return null;
5139
+ let packageJson;
5140
+ try {
5141
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
5142
+ } catch (cause) {
5143
+ throw new Error("package.json is not valid JSON; could not inspect its Renovate config.", { cause });
5144
+ }
5145
+ if (typeof packageJson === "object" && packageJson !== null && "renovate" in packageJson) {
5146
+ const renovate = packageJson.renovate;
5147
+ return classifyConfig("package.json#renovate", packageJsonPath, typeof renovate === "object" && renovate !== null && !Array.isArray(renovate) ? renovate : null);
5148
+ }
5149
+ return null;
5150
+ }
5151
+ function detectIndent(source) {
5152
+ return /^(?<indent>[ ]+)"/m.exec(source)?.groups?.indent?.length ?? 2;
5153
+ }
5154
+ function appendPreset(existing) {
5155
+ const source = fs.readFileSync(existing.filePath, "utf-8");
5156
+ const current = "extends" in existing.config ? existing.config.extends : void 0;
5157
+ if (current !== void 0 && !Array.isArray(current)) throw new Error(`Renovate config at "${existing.location}" has a non-array "extends". No files were changed. Add "${RENOVATE_PRESET}" to it manually.`);
5158
+ const config = {
5159
+ ...existing.config,
5160
+ extends: [...current ?? [], RENOVATE_PRESET]
5161
+ };
5162
+ const root = existing.location === "package.json#renovate" ? {
5163
+ ...JSON.parse(source),
5164
+ renovate: config
5165
+ } : config;
5166
+ const trailingNewline = source.endsWith("\n") ? "\n" : "";
5167
+ fs.writeFileSync(existing.filePath, `${JSON.stringify(root, null, detectIndent(source))}${trailingNewline}`, "utf-8");
5168
+ }
5169
+ function renderRenovateConfig() {
5170
+ return `${JSON.stringify({
5171
+ $schema: "https://docs.renovatebot.com/renovate-schema.json",
5172
+ extends: [RENOVATE_PRESET]
5173
+ }, null, 2)}\n`;
5174
+ }
5175
+ /**
5176
+ * Generate a repository-level Renovate config that extends Tailor's shared preset.
5177
+ * @param options - Renovate setup options
5178
+ */
5179
+ async function setupRenovate(options) {
5180
+ logBetaWarning("setup");
5181
+ const existingConfig = findExistingConfig(options.outputDir);
5182
+ if (existingConfig !== null) {
5183
+ if (existingConfig.kind === "extends-preset") {
5184
+ logger.info(`Renovate is already set up at ${styles.path(existingConfig.location)}.`);
5185
+ return;
5186
+ }
5187
+ if (existingConfig.kind === "unreadable") throw new Error(`Found a Renovate config at "${existingConfig.location}" but could not parse it as JSON. Comment-containing JSON5/JSONC configs are not supported yet. No files were changed. Check its extends array for "${RENOVATE_PRESET}" manually.`);
5188
+ appendPreset(existingConfig);
5189
+ logger.success(`Added the Tailor preset to ${styles.path(existingConfig.location)}`);
5190
+ return;
5191
+ }
5192
+ const outputPath = path.join(options.outputDir, RENOVATE_CONFIG_FILE);
5193
+ fs.writeFileSync(outputPath, renderRenovateConfig(), "utf-8");
5194
+ logger.success(`Generated ${styles.path(RENOVATE_CONFIG_FILE)}`);
5195
+ }
5196
+
5017
5197
  //#endregion
5018
5198
  //#region src/cli/commands/setup/index.ts
5019
5199
  const checkCommand = defineAppCommand({
@@ -5172,6 +5352,14 @@ const previewCommand = defineAppCommand({
5172
5352
  });
5173
5353
  }
5174
5354
  });
5355
+ const renovateCommand = defineAppCommand({
5356
+ name: "renovate",
5357
+ description: "Generate a Renovate config for Tailor dependency and workflow updates.",
5358
+ args: z.strictObject({}),
5359
+ run: async () => {
5360
+ await setupRenovate({ outputDir: process.cwd() });
5361
+ }
5362
+ });
5175
5363
  const deleteCommand$1 = defineAppCommand({
5176
5364
  name: "delete",
5177
5365
  description: "Delete managed workflow/action file(s) and their .github/tailor.lock entries.",
@@ -5192,13 +5380,14 @@ const deleteCommand$1 = defineAppCommand({
5192
5380
  });
5193
5381
  const setupCommand = defineCommand({
5194
5382
  name: "setup",
5195
- description: "Generate CI deploy workflows for your project. (beta)",
5383
+ description: "Set up repository automation for your project. (beta)",
5196
5384
  subCommands: {
5197
5385
  branch: branchCommand,
5198
5386
  tag: tagCommand,
5199
5387
  preview: previewCommand,
5200
5388
  action: actionCommand,
5201
5389
  coordinate: coordinateCommand,
5390
+ renovate: renovateCommand,
5202
5391
  check: checkCommand,
5203
5392
  delete: deleteCommand$1
5204
5393
  }
@@ -5228,7 +5417,8 @@ const domainGetCommand = defineAppCommand({
5228
5417
  })
5229
5418
  }),
5230
5419
  run: async (args) => {
5231
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
5420
+ const accessToken = await loadAccessToken({ profile: args.profile });
5421
+ const client = await initOperatorClient(accessToken);
5232
5422
  const workspaceId = await loadWorkspaceId({
5233
5423
  workspaceId: args["workspace-id"],
5234
5424
  profile: args.profile
@@ -5268,7 +5458,8 @@ const domainListCommand = defineAppCommand({
5268
5458
  })
5269
5459
  }),
5270
5460
  run: async (args) => {
5271
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
5461
+ const accessToken = await loadAccessToken({ profile: args.profile });
5462
+ const client = await initOperatorClient(accessToken);
5272
5463
  const workspaceId = await loadWorkspaceId({
5273
5464
  workspaceId: args["workspace-id"],
5274
5465
  profile: args.profile
@@ -5320,7 +5511,8 @@ const getCommand = defineAppCommand({
5320
5511
  })
5321
5512
  }),
5322
5513
  run: async (args) => {
5323
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
5514
+ const accessToken = await loadAccessToken({ profile: args.profile });
5515
+ const client = await initOperatorClient(accessToken);
5324
5516
  const workspaceId = await loadWorkspaceId({
5325
5517
  workspaceId: args["workspace-id"],
5326
5518
  profile: args.profile
@@ -5355,7 +5547,8 @@ const getCommand = defineAppCommand({
5355
5547
  * @returns List of static websites
5356
5548
  */
5357
5549
  async function listStaticWebsites(options) {
5358
- const client = await initOperatorClient(await loadAccessToken({ profile: options?.profile }));
5550
+ const accessToken = await loadAccessToken({ profile: options?.profile });
5551
+ const client = await initOperatorClient(accessToken);
5359
5552
  const workspaceId = await loadWorkspaceId({
5360
5553
  workspaceId: options?.workspaceId,
5361
5554
  profile: options?.profile
@@ -5426,12 +5619,7 @@ const staticwebsiteCommand = defineCommand({
5426
5619
 
5427
5620
  //#endregion
5428
5621
  //#region src/cli/commands/tailordb/migrate/rebaseline.ts
5429
- const MIGRATION_ARTIFACT_NAMES = /* @__PURE__ */ new Set([
5430
- SCHEMA_FILE_NAME,
5431
- DIFF_FILE_NAME,
5432
- MIGRATE_FILE_NAME,
5433
- DB_TYPES_FILE_NAME
5434
- ]);
5622
+ const MIGRATION_ARTIFACT_NAMES = new Set(Object.values(MIGRATION_FILE_NAMES));
5435
5623
  async function activateBaseline(migrationsDir, snapshot, namespace, rebaseline) {
5436
5624
  const resolvedMigrationsDir = await fsPromises.realpath(migrationsDir);
5437
5625
  const parentDir = path.dirname(resolvedMigrationsDir);
@@ -5451,13 +5639,15 @@ async function activateBaseline(migrationsDir, snapshot, namespace, rebaseline)
5451
5639
  force: true
5452
5640
  });
5453
5641
  }
5454
- loadSnapshot((await generateSchemaFile({
5642
+ const stagedSnapshot = {
5455
5643
  ...snapshot,
5456
5644
  namespace,
5457
- version: 2,
5645
+ version: 5,
5458
5646
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5459
5647
  rebaseline
5460
- }, stagingDir, 0)).filePath);
5648
+ };
5649
+ const result = await generateSchemaFile(stagedSnapshot, stagingDir, 0);
5650
+ loadSnapshot(result.filePath);
5461
5651
  await fsPromises.rename(resolvedMigrationsDir, backupDir);
5462
5652
  movedLiveDirectory = true;
5463
5653
  try {
@@ -5507,7 +5697,7 @@ async function rebaseline(options) {
5507
5697
  const currentHistoryId = reconstructSnapshotFromMigrations(target.migrationsDir, 0)?.rebaseline?.historyId ?? null;
5508
5698
  const initialFileState = captureMigrationFileState([target])[target.namespace];
5509
5699
  const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
5510
- const { defineApplication } = await import("../application-BxLLiLsr.mjs");
5700
+ const { defineApplication } = await import("../application-D4zOiLLa.mjs");
5511
5701
  const application = defineApplication({
5512
5702
  config,
5513
5703
  pluginManager
@@ -5520,7 +5710,7 @@ async function rebaseline(options) {
5520
5710
  if (!targetService) throw new Error(`No TailorDB service found for namespace "${target.namespace}"`);
5521
5711
  const assertLocalTypesReady = () => {
5522
5712
  const currentSnapshot = createSnapshotFromLocalTypes(targetService.types, target.namespace);
5523
- const localDiff = compareLocalTypesWithSnapshot(latestSnapshot, currentSnapshot.types, target.namespace);
5713
+ const localDiff = compareLocalTypesWithSnapshot(latestSnapshot, currentSnapshot.tables, target.namespace);
5524
5714
  if (hasChanges(localDiff)) {
5525
5715
  logger.error(`Migration history does not reproduce the current local schema for namespace ${styles.bold(target.namespace)}:`);
5526
5716
  logger.log(formatMigrationDiff(localDiff));
@@ -5530,7 +5720,8 @@ async function rebaseline(options) {
5530
5720
  const localSourceFiles = () => [config.path, ...loadFilesWithIgnores(targetService.config, configDir)];
5531
5721
  assertLocalTypesReady();
5532
5722
  const initialLocalFileState = captureFileState(localSourceFiles());
5533
- const client = await initOperatorClient(await loadAccessToken({ profile: options.profile }));
5723
+ const accessToken = await loadAccessToken({ profile: options.profile });
5724
+ const client = await initOperatorClient(accessToken);
5534
5725
  const workspaceId = await loadWorkspaceId({
5535
5726
  workspaceId: options.workspaceId,
5536
5727
  profile: options.profile
@@ -5598,7 +5789,7 @@ async function rebaseline(options) {
5598
5789
  }
5599
5790
  assertValidMigrationFiles(target.migrationsDir, target.namespace);
5600
5791
  if (captureMigrationFileState([target])[target.namespace] !== initialFileState) throw new Error("Migration files changed while waiting for confirmation. Run the command again.");
5601
- if (captureFileState(localSourceFiles()) !== initialLocalFileState) throw new Error("Local TailorDB type or config files changed. Run the command again.");
5792
+ if (captureFileState(localSourceFiles()) !== initialLocalFileState) throw new Error("Local TailorDB table or config files changed. Run the command again.");
5602
5793
  assertLocalTypesReady();
5603
5794
  await assertConnectedWorkspaceReady(currentHistoryId, "confirmation");
5604
5795
  const rebaselineMarker = {
@@ -5648,16 +5839,20 @@ async function set(options) {
5648
5839
  logBetaWarning("tailordb migration");
5649
5840
  const migrationNumber = parseMigrationNumberArg(options.number);
5650
5841
  const { config } = await loadConfig(options.configPath);
5651
- const target = selectTargetNamespace(getNamespacesWithMigrations(config, path.dirname(config.path)), options.namespace);
5842
+ const configDir = path.dirname(config.path);
5843
+ const namespacesWithMigrations = getNamespacesWithMigrations(config, configDir);
5844
+ const target = selectTargetNamespace(namespacesWithMigrations, options.namespace);
5652
5845
  const targetNamespace = target.namespace;
5653
5846
  assertValidMigrationFiles(target.migrationsDir, targetNamespace);
5654
5847
  assertMigrationNumberExists(target.migrationsDir, migrationNumber);
5655
5848
  const historyId = reconstructSnapshotFromMigrations(target.migrationsDir, 0)?.rebaseline?.historyId;
5656
- const client = await initOperatorClient(await loadAccessToken({ profile: options.profile }));
5657
- const trn = resourceTrn(await loadWorkspaceId({
5849
+ const accessToken = await loadAccessToken({ profile: options.profile });
5850
+ const client = await initOperatorClient(accessToken);
5851
+ const workspaceId = await loadWorkspaceId({
5658
5852
  workspaceId: options.workspaceId,
5659
5853
  profile: options.profile
5660
- }), "tailordb", targetNamespace);
5854
+ });
5855
+ const trn = resourceTrn(workspaceId, "tailordb", targetNamespace);
5661
5856
  const currentState = await fetchRemoteMigrationState(client, trn);
5662
5857
  const current = currentState.number;
5663
5858
  const currentMigration = current ?? 0;
@@ -5736,7 +5931,8 @@ function isStatusFailure(row) {
5736
5931
  }
5737
5932
  async function collectMigrationStatuses(options) {
5738
5933
  const { config } = await loadConfig(options.configPath);
5739
- const namespacesWithMigrations = getNamespacesWithMigrations(config, path.dirname(config.path));
5934
+ const configDir = path.dirname(config.path);
5935
+ const namespacesWithMigrations = getNamespacesWithMigrations(config, configDir);
5740
5936
  if (namespacesWithMigrations.length === 0) throw new Error("No TailorDB services with migrations configuration found");
5741
5937
  const targetNamespaces = options.namespace ? namespacesWithMigrations.filter((ns) => ns.namespace === options.namespace) : namespacesWithMigrations;
5742
5938
  if (targetNamespaces.length === 0) throw new Error(`Namespace "${options.namespace}" not found or does not have migrations configured`);
@@ -5772,7 +5968,8 @@ async function collectMigrationStatuses(options) {
5772
5968
  namespace,
5773
5969
  error: localFailures.get(namespace) ?? "Failed to read local migration history."
5774
5970
  }));
5775
- const client = await initOperatorClient(await loadAccessToken({ profile: options.profile }));
5971
+ const accessToken = await loadAccessToken({ profile: options.profile });
5972
+ const client = await initOperatorClient(accessToken);
5776
5973
  const workspaceId = await loadWorkspaceId({
5777
5974
  workspaceId: options.workspaceId,
5778
5975
  profile: options.profile
@@ -5945,7 +6142,7 @@ async function fetchRemoteTypes(client, workspaceId, namespace) {
5945
6142
  async function assertMigrationsReproduceLocalTypes(loaded, target) {
5946
6143
  const { config, plugins } = loaded;
5947
6144
  const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
5948
- const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-BxLLiLsr.mjs");
6145
+ const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-D4zOiLLa.mjs");
5949
6146
  const application = defineApplication({
5950
6147
  config,
5951
6148
  pluginManager
@@ -5957,7 +6154,7 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
5957
6154
  await service.processNamespacePlugins();
5958
6155
  }
5959
6156
  const pluginExecutorFiles = generatePluginFilesIfNeeded(pluginManager, application.tailorDBServices, config.path);
5960
- const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-_XmjSEGr.mjs")).createExecutorService({
6157
+ const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-CaBPNDIZ.mjs")).createExecutorService({
5961
6158
  config: { files: [] },
5962
6159
  baseDir: path.dirname(config.path)
5963
6160
  }) : void 0);
@@ -5971,14 +6168,15 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
5971
6168
  };
5972
6169
  const latestSnapshot = reconstructSnapshotFromMigrations(target.migrationsDir);
5973
6170
  if (!latestSnapshot) return manifestOptions;
5974
- const diff = compareLocalTypesWithSnapshot(latestSnapshot, createSnapshotFromLocalTypes(tailordbService.types, target.namespace).types, target.namespace);
6171
+ const currentSnapshot = createSnapshotFromLocalTypes(tailordbService.types, target.namespace);
6172
+ const diff = compareLocalTypesWithSnapshot(latestSnapshot, currentSnapshot.tables, target.namespace);
5975
6173
  if (!hasChanges(diff)) return manifestOptions;
5976
6174
  logger.error(`Migration history does not reproduce the current local schema for namespace ${styles.bold(target.namespace)}:`);
5977
6175
  logger.log(formatMigrationDiff(diff));
5978
6176
  logger.newline();
5979
6177
  logger.info("This usually means one of the following:");
5980
- logger.info(" - Migration files were edited and replaying them no longer matches the type definitions — fix the migration files.", { mode: "plain" });
5981
- logger.info(" - Type definitions changed without a new migration — run 'tailor tailordb migration generate' first.", { mode: "plain" });
6178
+ logger.info(" - Migration files were edited and replaying them no longer matches the table definitions — fix the migration files.", { mode: "plain" });
6179
+ logger.info(" - Table definitions changed without a new migration — run 'tailor tailordb migration generate' first.", { mode: "plain" });
5982
6180
  logger.newline();
5983
6181
  throw new Error("Refusing to sync: the migration history must reproduce the current local schema before it can be applied to the remote.");
5984
6182
  }
@@ -6002,13 +6200,16 @@ async function sync(options) {
6002
6200
  const targetVersion = parseMigrationNumberArg(options.number);
6003
6201
  const loaded = await loadConfig(options.configPath);
6004
6202
  const { config } = loaded;
6005
- const target = selectTargetNamespace(getNamespacesWithMigrations(config, path.dirname(config.path)), options.namespace);
6203
+ const configDir = path.dirname(config.path);
6204
+ const namespacesWithMigrations = getNamespacesWithMigrations(config, configDir);
6205
+ const target = selectTargetNamespace(namespacesWithMigrations, options.namespace);
6006
6206
  assertValidMigrationFiles(target.migrationsDir, target.namespace);
6007
6207
  const latest = assertMigrationNumberExists(target.migrationsDir, targetVersion);
6008
6208
  const snapshot = reconstructSnapshotFromMigrations(target.migrationsDir, targetVersion);
6009
6209
  if (!snapshot) throw new Error(`No initial schema snapshot found in ${target.migrationsDir}. Expected 0000/schema.json.`);
6010
6210
  const manifestOptions = await assertMigrationsReproduceLocalTypes(loaded, target);
6011
- const client = await initOperatorClient(await loadAccessToken({ profile: options.profile }));
6211
+ const accessToken = await loadAccessToken({ profile: options.profile });
6212
+ const client = await initOperatorClient(accessToken);
6012
6213
  const workspaceId = await loadWorkspaceId({
6013
6214
  workspaceId: options.workspaceId,
6014
6215
  profile: options.profile
@@ -6016,10 +6217,11 @@ async function sync(options) {
6016
6217
  const trn = resourceTrn(workspaceId, "tailordb", target.namespace);
6017
6218
  const current = await fetchRemoteMigrationNumber(client, trn);
6018
6219
  const remoteTypes = await fetchRemoteTypes(client, workspaceId, target.namespace);
6019
- const { creates, updates, deletes } = compareSnapshotWithRemote(snapshot, new Set(remoteTypes.map((t) => t.name)));
6220
+ const existingTypeNames = new Set(remoteTypes.map((t) => t.name));
6221
+ const { creates, updates, deletes } = compareSnapshotWithRemote(snapshot, existingTypeNames);
6020
6222
  const remoteGqlPermissions = await fetchRemoteGqlPermissions(client, workspaceId, target.namespace);
6021
6223
  const remoteGqlPermissionTypes = new Set(remoteGqlPermissions.map((p) => p.typeName));
6022
- const desiredGqlPermissions = Object.entries(snapshot.types).flatMap(([typeName, snapshotType]) => snapshotType.permissions?.gql ? [{
6224
+ const desiredGqlPermissions = Object.entries(snapshot.tables).flatMap(([typeName, snapshotType]) => snapshotType.permissions?.gql ? [{
6023
6225
  typeName,
6024
6226
  permission: protoGqlPermission(snapshotType.permissions.gql)
6025
6227
  }] : []);
@@ -6029,16 +6231,16 @@ async function sync(options) {
6029
6231
  logger.info(`Namespace: ${styles.bold(target.namespace)}`);
6030
6232
  logger.log(` Current migration: ${current === null ? "<unset>" : styles.bold(formatMigrationNumber(current))}`);
6031
6233
  logger.log(` Target migration: ${styles.bold(formatMigrationNumber(targetVersion))}`);
6032
- logger.log(` Types to create: ${styles.bold(String(creates.length))}`);
6033
- logger.log(` Types to update: ${styles.bold(String(updates.length))}`);
6034
- logger.log(` Types to delete: ${styles.bold(String(deletes.length))}`);
6234
+ logger.log(` Tables to create: ${styles.bold(String(creates.length))}`);
6235
+ logger.log(` Tables to update: ${styles.bold(String(updates.length))}`);
6236
+ logger.log(` Tables to delete: ${styles.bold(String(deletes.length))}`);
6035
6237
  logger.log(` GQL permissions to set: ${styles.bold(String(desiredGqlPermissions.length))}`);
6036
6238
  logger.log(` GQL permissions to delete: ${styles.bold(String(gqlPermissionDeletes.length))}`);
6037
6239
  logger.newline();
6038
- if (creates.length + updates.length + deletes.length + desiredGqlPermissions.length + gqlPermissionDeletes.length === 0) logger.info("No types to apply; only the migration label will be updated.");
6240
+ if (creates.length + updates.length + deletes.length + desiredGqlPermissions.length + gqlPermissionDeletes.length === 0) logger.info("No tables to apply; only the migration label will be updated.");
6039
6241
  else {
6040
- logger.warn("This operation will overwrite remote TailorDB types to match the selected snapshot.");
6041
- if (deletes.length > 0) logger.warn("Existing data in deleted types will be lost.");
6242
+ logger.warn("This operation will overwrite remote TailorDB tables to match the selected snapshot.");
6243
+ if (deletes.length > 0) logger.warn("Existing data in deleted tables will be lost.");
6042
6244
  logger.newline();
6043
6245
  }
6044
6246
  logger.warn("Sync never runs migrate.ts scripts; it only applies the schema snapshot and moves the migration label.");
@@ -6063,7 +6265,7 @@ async function sync(options) {
6063
6265
  const manifests = generateAllTypeManifestsFromSnapshot(snapshot, manifestOptions);
6064
6266
  const manifestFor = (typeName) => {
6065
6267
  const manifest = manifests.get(typeName);
6066
- if (!manifest) throw new Error(`Internal error: no manifest generated for type "${typeName}". No changes were applied.`);
6268
+ if (!manifest) throw new Error(`Internal error: no manifest generated for table "${typeName}". No changes were applied.`);
6067
6269
  return manifest;
6068
6270
  };
6069
6271
  const createManifests = creates.map((typeName) => manifestFor(typeName));
@@ -6145,7 +6347,7 @@ const syncCommand = defineAppCommand({
6145
6347
  //#endregion
6146
6348
  //#region src/cli/commands/tailordb/migrate/test-runtime.ts
6147
6349
  const CLONE_POLL_INTERVAL = 1e3;
6148
- const CLONE_TIMEOUT = 300 * 1e3;
6350
+ const CLONE_TIMEOUT = 3e5;
6149
6351
  function delay(milliseconds) {
6150
6352
  return milliseconds > 0 ? new Promise((resolve) => setTimeout(resolve, milliseconds)) : Promise.resolve();
6151
6353
  }
@@ -6245,7 +6447,7 @@ async function waitForCloneApplicationData(client, options) {
6245
6447
  function loadSnapshotSeedData(dataDir, typeNames, snapshot) {
6246
6448
  const data = {};
6247
6449
  for (const typeName of typeNames) {
6248
- const snapshotType = snapshot?.types[typeName];
6450
+ const snapshotType = snapshot?.tables[typeName];
6249
6451
  const jsonlPath = path.join(dataDir, `${typeName}.jsonl`);
6250
6452
  let content;
6251
6453
  try {
@@ -6296,11 +6498,11 @@ function isJsonObject(value) {
6296
6498
  * @returns Dependency-ordered type names and self-referencing types
6297
6499
  */
6298
6500
  function sortSeedTypesForSnapshot(snapshot) {
6299
- const typeNames = Object.keys(snapshot.types);
6501
+ const typeNames = Object.keys(snapshot.tables);
6300
6502
  const available = new Set(typeNames);
6301
6503
  const dependencies = /* @__PURE__ */ new Map();
6302
6504
  const selfRefTypes = [];
6303
- for (const [typeName, type] of Object.entries(snapshot.types)) {
6505
+ for (const [typeName, type] of Object.entries(snapshot.tables)) {
6304
6506
  const referenced = /* @__PURE__ */ new Set();
6305
6507
  for (const field of Object.values(type.fields)) {
6306
6508
  const target = field.foreignKeyType;
@@ -6356,7 +6558,8 @@ async function assertSourceBaselineFresh(state, prepared, sourceWorkspaceId) {
6356
6558
  for (const input of unmigratedInputs) {
6357
6559
  const snapshot = prepared.baselineSnapshots.get(input.namespace);
6358
6560
  if (!snapshot) continue;
6359
- if (compareSnapshots(snapshot, await fetchRemoteSchemaSnapshot(state.client, sourceWorkspaceId, input.namespace)).changes.length > 0) throw new Error(`Source namespace "${input.namespace}" schema changed after migration test preparation. Run the migration test again.`);
6561
+ const current = await fetchRemoteSchemaSnapshot(state.client, sourceWorkspaceId, input.namespace);
6562
+ if (compareSnapshots(snapshot, current).changes.length > 0) throw new Error(`Source namespace "${input.namespace}" schema changed after migration test preparation. Run the migration test again.`);
6360
6563
  }
6361
6564
  }
6362
6565
  /**
@@ -6407,7 +6610,9 @@ async function prepareMigrationTest(options) {
6407
6610
  }
6408
6611
  if (options.assertionPath) assertAssertionScript(options.assertionPath);
6409
6612
  const inputs = loaded.application.tailorDBServices.map(toTailorDBDeployInput);
6410
- const client = await initOperatorClient(await loadAccessToken({ profile: options.profile }), await loadPlatformClientConfig({ profile: options.profile }));
6613
+ const accessToken = await loadAccessToken({ profile: options.profile });
6614
+ const platformConfig = await loadPlatformClientConfig({ profile: options.profile });
6615
+ const client = await initOperatorClient(accessToken, platformConfig);
6411
6616
  const sourceWorkspaceId = await loadWorkspaceId({
6412
6617
  workspaceId: options.workspaceId,
6413
6618
  profile: options.profile
@@ -6498,7 +6703,8 @@ function createMigrationTestDependencies() {
6498
6703
  return prepared;
6499
6704
  },
6500
6705
  createWorkspace: async (prepared) => {
6501
- const workspace = await createValidatedWorkspaceWithClient(stateOrThrow(runtimeState).client, prepared.temporaryWorkspace);
6706
+ const state = stateOrThrow(runtimeState);
6707
+ const workspace = await createValidatedWorkspaceWithClient(state.client, prepared.temporaryWorkspace);
6502
6708
  logger.info(`Created temporary workspace ${workspace.name} (${workspace.id}).`);
6503
6709
  return {
6504
6710
  id: workspace.id,
@@ -6528,7 +6734,7 @@ function createMigrationTestDependencies() {
6528
6734
  version: 1,
6529
6735
  namespace: service.namespace,
6530
6736
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6531
- types: input.types
6737
+ tables: input.types
6532
6738
  })];
6533
6739
  }));
6534
6740
  for (const [namespace, snapshot] of seedSnapshots) {
@@ -6744,15 +6950,18 @@ const testCommand = defineAppCommand({
6744
6950
  //#region src/cli/commands/tailordb/migrate/validate.ts
6745
6951
  /**
6746
6952
  * Walk the local migration history once to assert that every required
6747
- * migration script exists and generated normalization logic has been
6748
- * reviewed, and to collect migrations with data-loss warnings that have
6749
- * neither a migrate.ts nor a recorded --no-script acknowledgment
6953
+ * migration script exists, no migration carries both a --no-script
6954
+ * acknowledgment and a migrate.ts, and generated normalization logic has
6955
+ * been reviewed, and to collect migrations with data-loss warnings that
6956
+ * have neither a migrate.ts nor a recorded --no-script acknowledgment
6750
6957
  * @param {string} migrationsDir - Migrations directory path
6751
6958
  * @param {string} namespace - TailorDB namespace (for error messages)
6959
+ * @param {string} [configPath] - Config path the current run used (for remediation hints)
6752
6960
  * @returns {UnacknowledgedWarningMigration[]} Unacknowledged warning migrations in the local history
6753
6961
  */
6754
- function assertMigrationScriptsReady(migrationsDir, namespace) {
6962
+ function assertMigrationScriptsReady(migrationsDir, namespace, configPath) {
6755
6963
  const missing = [];
6964
+ const conflicting = [];
6756
6965
  const unreviewed = [];
6757
6966
  const unacknowledgedWarnings = [];
6758
6967
  for (const file of getMigrationFiles(migrationsDir)) {
@@ -6761,6 +6970,7 @@ function assertMigrationScriptsReady(migrationsDir, namespace) {
6761
6970
  const migrateFilePath = getMigrationFilePath(migrationsDir, file.number, "migrate");
6762
6971
  const hasScript = fs.existsSync(migrateFilePath);
6763
6972
  if (diff.requiresMigrationScript && !diff.scriptSkipped && !hasScript) missing.push(file.number);
6973
+ if (diff.scriptSkipped && hasScript) conflicting.push(file.number);
6764
6974
  if (hasScript && fs.readFileSync(migrateFilePath, "utf8").includes("TODO(tailor-migration-review)")) unreviewed.push(file.number);
6765
6975
  if (diff.hasWarnings && !diff.scriptSkipped && !hasScript) unacknowledgedWarnings.push({
6766
6976
  migrationNumber: file.number,
@@ -6768,6 +6978,15 @@ function assertMigrationScriptsReady(migrationsDir, namespace) {
6768
6978
  });
6769
6979
  }
6770
6980
  if (missing.length > 0) throw new Error(`Migration(s) ${missing.map(formatMigrationNumber).join(", ")} in namespace "${namespace}" require a migration script but have no migrate.ts. Add one with 'tailor tailordb migration script <number>', or record that no script is needed with 'tailor tailordb migration script <number> --no-script --reason "..."'.`);
6981
+ if (conflicting.length > 0) {
6982
+ const clearCommands = conflicting.map((migrationNumber) => ` ${formatMigrationScriptCommand({
6983
+ migrationNumber,
6984
+ namespace,
6985
+ configPath
6986
+ })}`).join("\n");
6987
+ throw new Error(`Migration(s) ${conflicting.map(formatMigrationNumber).join(", ")} in namespace "${namespace}" have both a --no-script skip acknowledgment and migrate.ts. Clear the stale acknowledgment(s):
6988
+ ${clearCommands}\nOr delete migrate.ts to keep the skip.`);
6989
+ }
6771
6990
  if (unreviewed.length > 0) throw new Error(`Migration(s) ${unreviewed.map(formatMigrationNumber).join(", ")} in namespace "${namespace}" contain generated normalization logic that still requires review in migrate.ts. Review each ${MIGRATION_REVIEW_REQUIRED_MARKER} marker, then remove the marker and its associated \`never\` annotation.`);
6772
6991
  return unacknowledgedWarnings;
6773
6992
  }
@@ -6836,12 +7055,13 @@ function buildValidationReports(options) {
6836
7055
  */
6837
7056
  async function collectValidationReports(options) {
6838
7057
  const { config, plugins } = await loadConfig(options.configPath);
6839
- const namespacesWithMigrations = getNamespacesWithMigrations(config, path.dirname(config.path));
7058
+ const configDir = path.dirname(config.path);
7059
+ const namespacesWithMigrations = getNamespacesWithMigrations(config, configDir);
6840
7060
  if (namespacesWithMigrations.length === 0) throw new Error("No TailorDB services with migrations configuration found");
6841
7061
  const targetNamespaces = options.namespace ? namespacesWithMigrations.filter((ns) => ns.namespace === options.namespace) : namespacesWithMigrations;
6842
7062
  if (targetNamespaces.length === 0) throw new Error(`Namespace "${options.namespace}" not found or does not have migrations configured`);
6843
7063
  const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
6844
- const { defineApplication } = await import("../application-BxLLiLsr.mjs");
7064
+ const { defineApplication } = await import("../application-D4zOiLLa.mjs");
6845
7065
  const application = defineApplication({
6846
7066
  config,
6847
7067
  pluginManager
@@ -6860,7 +7080,7 @@ async function collectValidationReports(options) {
6860
7080
  for (const target of targetNamespaces) try {
6861
7081
  assertValidMigrationFiles(target.migrationsDir, target.namespace);
6862
7082
  reconstructSnapshotFromMigrations(target.migrationsDir);
6863
- const namespaceWarnings = assertMigrationScriptsReady(target.migrationsDir, target.namespace);
7083
+ const namespaceWarnings = assertMigrationScriptsReady(target.migrationsDir, target.namespace, options.configPath);
6864
7084
  unacknowledgedWarnings?.set(target.namespace, namespaceWarnings);
6865
7085
  checkableNamespaces.push(target);
6866
7086
  } catch (error) {
@@ -6875,10 +7095,13 @@ async function collectValidationReports(options) {
6875
7095
  if (checkableNamespaces.length === 0) return { reports: buildValidationReports(localReportOptions) };
6876
7096
  let remoteResults;
6877
7097
  try {
6878
- remoteResults = await verifyRemoteSchema(await initOperatorClient(await loadAccessToken({ profile: options.profile })), await loadWorkspaceId({
7098
+ const accessToken = await loadAccessToken({ profile: options.profile });
7099
+ const client = await initOperatorClient(accessToken);
7100
+ const workspaceId = await loadWorkspaceId({
6879
7101
  workspaceId: options.workspaceId,
6880
7102
  profile: options.profile
6881
- }), checkableNamespaces, config, tailorDBInputs);
7103
+ });
7104
+ remoteResults = await verifyRemoteSchema(client, workspaceId, checkableNamespaces, config, tailorDBInputs);
6882
7105
  } catch (remoteError) {
6883
7106
  return {
6884
7107
  reports: buildValidationReports(localReportOptions),
@@ -6901,12 +7124,13 @@ function printValidationReports(reports) {
6901
7124
  }
6902
7125
  logger.log(` Migration files: ${styles.success("OK")}`);
6903
7126
  const local = report.localSchema;
6904
- if (local?.hasDiff) if (local.diff) {
6905
- logger.log(` Local schema: ${styles.error("changes not in migration files")}`);
6906
- logger.log(` ${formatDiffSummary(local.diff)}`);
6907
- logger.log(formatMigrationDiff(local.diff));
6908
- } else logger.log(` Local schema: ${styles.error("no migration snapshot found")}`);
6909
- else logger.log(` Local schema: ${styles.success("OK")}`);
7127
+ if (local?.hasDiff) {
7128
+ if (local.diff) {
7129
+ logger.log(` Local schema: ${styles.error("changes not in migration files")}`);
7130
+ logger.log(` ${formatDiffSummary(local.diff)}`);
7131
+ logger.log(formatMigrationDiff(local.diff));
7132
+ } else logger.log(` Local schema: ${styles.error("no migration snapshot found")}`);
7133
+ } else logger.log(` Local schema: ${styles.success("OK")}`);
6910
7134
  const remote = report.remoteSchema;
6911
7135
  if (remote?.skipped === "check_failed") logger.log(` Remote schema: ${styles.error("not checked")}`);
6912
7136
  else if (remote?.checkpointRepair) logger.log(` Remote schema: ${styles.success("OK")} (next deploy will reset the checkpoint to 0000 from ${formatMigrationNumber(remote.checkpointRepair.from)})`);
@@ -6919,14 +7143,16 @@ function printValidationReports(reports) {
6919
7143
  else if (remote?.skipped === "no_snapshot") logger.log(` Remote schema: ${styles.dim(`skipped (no local snapshot for remote migration ${formatMigrationNumber(remote.remoteMigrationNumber)})`)}`);
6920
7144
  else if (remote) logger.log(` Remote schema: ${styles.success("OK")} (remote migration: ${formatMigrationNumber(remote.remoteMigrationNumber)})`);
6921
7145
  const acknowledgments = report.warningAcknowledgments;
6922
- if (acknowledgments) if (acknowledgments.valid) logger.log(` Warning acknowledgments: ${styles.success("OK")}`);
6923
- else {
6924
- logger.log(` Warning acknowledgments: ${styles.error("missing for pending migration(s) with possible data loss")}`);
6925
- for (const migration of acknowledgments.missing) {
6926
- const label = formatMigrationNumber(migration.migrationNumber);
6927
- for (const warning of migration.warnings) {
6928
- const field = warning.fieldName ? `.${warning.fieldName}` : "";
6929
- logger.log(` ${label}: ${warning.typeName}${field}: ${warning.reason}`);
7146
+ if (acknowledgments) {
7147
+ if (acknowledgments.valid) logger.log(` Warning acknowledgments: ${styles.success("OK")}`);
7148
+ else {
7149
+ logger.log(` Warning acknowledgments: ${styles.error("missing for pending migration(s) with possible data loss")}`);
7150
+ for (const migration of acknowledgments.missing) {
7151
+ const label = formatMigrationNumber(migration.migrationNumber);
7152
+ for (const warning of migration.warnings) {
7153
+ const field = warning.fieldName ? `.${warning.fieldName}` : "";
7154
+ logger.log(` ${label}: ${warning.tableName}${field}: ${warning.reason}`);
7155
+ }
6930
7156
  }
6931
7157
  }
6932
7158
  }
@@ -6969,7 +7195,7 @@ async function validate(options) {
6969
7195
  }
6970
7196
  const validateCommand = defineAppCommand({
6971
7197
  name: "validate",
6972
- description: "Validate the full migration history, unreviewed generated migration scripts, and schema drift (local types vs. migration snapshot, remote schema vs. migration checkpoint) without deploying. This includes the migration and schema-drift checks used by 'deploy' and exits with a non-zero code when issues are found.",
7198
+ description: "Validate the full migration history, unreviewed generated migration scripts, and schema drift (local tables vs. migration snapshot, remote schema vs. migration checkpoint) without deploying. This includes the migration and schema-drift checks used by 'deploy' and exits with a non-zero code when issues are found.",
6973
7199
  args: z.strictObject({
6974
7200
  ...deploymentArgs,
6975
7201
  namespace: arg(z.string().optional(), {
@@ -7051,7 +7277,7 @@ const upgradeCommand = defineAppCommand({
7051
7277
  run: async (args) => {
7052
7278
  const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
7053
7279
  await initTelemetry();
7054
- const { upgrade } = await import("../service-CCwl3Avt.mjs");
7280
+ const { upgrade } = await import("../service-LTfp03ZZ.mjs");
7055
7281
  await upgrade({
7056
7282
  from: args.from,
7057
7283
  dryRun: args["dry-run"],
@@ -7464,7 +7690,8 @@ initCrashReporting();
7464
7690
  const packageJson = await readPackageJson();
7465
7691
  const cliName = Object.keys(packageJson.bin ?? {})[0] || "tailor";
7466
7692
  const packageName = packageJson.name ?? "@tailor-platform/sdk";
7467
- const bundledSkillsDir = resolve(dirname(await resolvePackageJSON(import.meta.url)), "agent-skills");
7693
+ const packageJsonPath = await resolvePackageJSON(import.meta.url);
7694
+ const bundledSkillsDir = resolve(dirname(packageJsonPath), "agent-skills");
7468
7695
  function defaultSkillsRunToAdd(command) {
7469
7696
  const add = (command.subCommands ?? {}).add;
7470
7697
  return {
@@ -7555,10 +7782,12 @@ runMain(mainCommand, {
7555
7782
  if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
7556
7783
  } else if (error instanceof Error) {
7557
7784
  logger.error(error.message);
7785
+ const hint = typeOnlyImportHint(error);
7786
+ if (hint) logger.log(` ${styles.info("Suggestion:")} ${hint}`);
7558
7787
  if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
7559
7788
  } else logger.error(`Unknown error: ${error}`);
7560
7789
  if (!isCLIError(error) && (!(error instanceof Error) || error instanceof TypeError || error instanceof RangeError)) {
7561
- const { reportCrash } = await import("../crashreport-CINZ5dVN.mjs");
7790
+ const { reportCrash } = await import("../crashreport-Cd4TqfZq.mjs");
7562
7791
  await reportCrash(error, "handledError");
7563
7792
  }
7564
7793
  }