@tailor-platform/sdk 2.2.0 → 2.3.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 (55) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/dist/application-BZfk4HKm.mjs +3 -0
  3. package/dist/{application-BpLeawBg.mjs → application-F-nl107y.mjs} +118 -24
  4. package/dist/application-F-nl107y.mjs.map +1 -0
  5. package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +43 -20
  6. package/dist/cli/commands/tailordb/migrate/generate.d.mts +6 -0
  7. package/dist/cli/commands/tailordb/migrate/rename-detection.d.mts +22 -0
  8. package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +1 -1
  9. package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +26 -7
  10. package/dist/cli/lib.mjs +2 -2
  11. package/dist/cli/main.mjs +203 -42
  12. package/dist/cli/main.mjs.map +1 -1
  13. package/dist/cli/ts-hook.mjs +52 -7
  14. package/dist/completion/zsh-worker.zsh +23 -3
  15. package/dist/configure/index.d.mts +2 -2
  16. package/dist/configure/index.mjs +90 -23
  17. package/dist/configure/index.mjs.map +1 -1
  18. package/dist/configure/services/index.d.mts +2 -2
  19. package/dist/configure/services/workflow/index.d.mts +2 -2
  20. package/dist/configure/services/workflow/wait-point.d.mts +72 -13
  21. package/dist/{errors-CWj21238.mjs → errors-BVb6vYGy.mjs} +16 -2
  22. package/dist/{errors-CWj21238.mjs.map → errors-BVb6vYGy.mjs.map} +1 -1
  23. package/dist/kysely/index.d.mts +2 -1
  24. package/dist/kysely/index.mjs.map +1 -1
  25. package/dist/{register-ts-hook-CTth1eqj.mjs → register-ts-hook-ClI226n2.mjs} +2034 -511
  26. package/dist/register-ts-hook-ClI226n2.mjs.map +1 -0
  27. package/dist/{service-goqlJJgA.mjs → service-C_WpbKHu.mjs} +38 -4
  28. package/dist/service-C_WpbKHu.mjs.map +1 -0
  29. package/dist/service-D1RCdzIL.mjs +3 -0
  30. package/dist/{service-CCwl3Avt.mjs → service-hZskxZmg.mjs} +2 -2
  31. package/dist/{service-CCwl3Avt.mjs.map → service-hZskxZmg.mjs.map} +1 -1
  32. package/dist/vitest/index.mjs +83 -1
  33. package/dist/vitest/index.mjs.map +1 -1
  34. package/dist/vitest/mocks/file.d.mts +1 -1
  35. package/dist/vitest/mocks/workflow.d.mts +13 -1
  36. package/dist/wait-point-invoker-__oE88_P.mjs +148 -0
  37. package/dist/wait-point-invoker-__oE88_P.mjs.map +1 -0
  38. package/dist/wait-point-registry-TL99zotw.mjs +47 -0
  39. package/dist/wait-point-registry-TL99zotw.mjs.map +1 -0
  40. package/docs/cli/setup.md +14 -1
  41. package/docs/cli/tailordb.md +9 -6
  42. package/docs/cli-reference.md +2 -1
  43. package/docs/github-actions.md +73 -28
  44. package/docs/migration/v2.md +54 -0
  45. package/docs/services/tailordb-migration.md +137 -35
  46. package/docs/services/workflow.md +52 -2
  47. package/docs/testing.md +12 -0
  48. package/package.json +4 -4
  49. package/dist/application-BpLeawBg.mjs.map +0 -1
  50. package/dist/application-BxLLiLsr.mjs +0 -3
  51. package/dist/register-ts-hook-CTth1eqj.mjs.map +0 -1
  52. package/dist/service-_XmjSEGr.mjs +0 -3
  53. package/dist/service-goqlJJgA.mjs.map +0 -1
  54. package/dist/test-env-key-D7UkZp99.mjs +0 -75
  55. package/dist/test-env-key-D7UkZp99.mjs.map +0 -1
package/dist/cli/main.mjs CHANGED
@@ -3,13 +3,13 @@ import { r as parseInputFields } from "../schema--xYWRGfe.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
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";
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-ClI226n2.mjs";
7
+ import { B as fetchPaged, C as resolveTSConfigWithFallback, D as resolveBundleLogLevel, E as createLogLevelTreeshakeOptions, F as closeConnectionPool, G as getOrNull, H as fetchUserInfo, I as defaultPlatformBaseUrl, J as initOperatorClient, K as getPlatformBaseUrl, L as fetchAll, M as getDistDir, O as createBundleLog, R as fetchAllTolerant, S as createTsconfigPathsPlugin, T as composeFunctionTreeshakeOptions, V as fetchPlatformMachineUserToken, W as getOAuth2ClientId, Y as isDefaultPlatform, a as resolveInlineSourcemap, c as WorkflowJobSchema, f as INVOKER_EXPR, g as assertUniqueLocalTailorDBTypeNames, h as buildResolverPermissionAndInputCheckExpr, l as resolveResolverDefaultPermissionForFile, q as initOAuth2Client, u as ResolverSchema, w as platformBundleDefinePlugin, x as createGeneratedEntryResolverPlugin } from "../application-F-nl107y.mjs";
8
+ import { h as loadFilesWithIgnores, i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-C_WpbKHu.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-BVb6vYGy.mjs";
13
13
  import { t as OperatorService } from "../service_pb-BEWj1pOV.mjs";
14
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";
15
15
  import * as path from "pathe";
@@ -3818,6 +3818,14 @@ function detectDefaultBranch(cwd, run = defaultGitRunner) {
3818
3818
  const LOCK_VERSION = 1;
3819
3819
  /** Lock file path, relative to the repository root. */
3820
3820
  const LOCK_FILENAME = ".github/tailor.lock";
3821
+ function assertSafeLockPath(outputDir) {
3822
+ for (const relativePath of [".github", LOCK_FILENAME]) try {
3823
+ if (fs.lstatSync(path.join(outputDir, relativePath)).isSymbolicLink()) throw new Error(`Refusing to use ${LOCK_FILENAME}: "${relativePath}" is a symbolic link.`);
3824
+ } catch (error) {
3825
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") continue;
3826
+ throw error;
3827
+ }
3828
+ }
3821
3829
  /**
3822
3830
  * Compute the lock content hash for a rendered workflow file.
3823
3831
  * @param content - File content to hash
@@ -3843,6 +3851,7 @@ function lockPath(outputDir) {
3843
3851
  * @returns Parsed lock file, or null when absent
3844
3852
  */
3845
3853
  function readLock(outputDir) {
3854
+ assertSafeLockPath(outputDir);
3846
3855
  const file = lockPath(outputDir);
3847
3856
  if (!fs.existsSync(file)) return null;
3848
3857
  let parsed;
@@ -3862,6 +3871,7 @@ function readLock(outputDir) {
3862
3871
  * @param lock - Lock file contents to serialize
3863
3872
  */
3864
3873
  function writeLock(outputDir, lock) {
3874
+ assertSafeLockPath(outputDir);
3865
3875
  const file = lockPath(outputDir);
3866
3876
  fs.mkdirSync(path.dirname(file), { recursive: true });
3867
3877
  fs.writeFileSync(file, `${JSON.stringify(lock, null, 2)}\n`, "utf-8");
@@ -3880,23 +3890,23 @@ function findTarget(lock, kind, workspaceName) {
3880
3890
 
3881
3891
  //#endregion
3882
3892
  //#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";
3893
+ 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
3894
 
3885
3895
  //#endregion
3886
3896
  //#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";
3897
+ 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
3898
 
3889
3899
  //#endregion
3890
3900
  //#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";
3901
+ 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
3902
 
3893
3903
  //#endregion
3894
3904
  //#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";
3905
+ 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
3906
 
3897
3907
  //#endregion
3898
3908
  //#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";
3909
+ 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
3910
 
3901
3911
  //#endregion
3902
3912
  //#region src/cli/commands/setup/templates.ts
@@ -4044,6 +4054,7 @@ function renderPreviewWorkflow(params) {
4044
4054
  "tailor-preview-deploy/tailor-setup",
4045
4055
  "tailor-preview-deploy/tailor-install",
4046
4056
  "tailor-preview-deploy/tailor-generate-check",
4057
+ "tailor-preview-deploy/tailor-drift-check",
4047
4058
  "tailor-preview-deploy/tailor-preview-deploy",
4048
4059
  "tailor-preview-deploy/tailor-preview-comment",
4049
4060
  "tailor-preview-cleanup",
@@ -4072,8 +4083,8 @@ function renderActionWorkflow(params) {
4072
4083
  generatedIds: ["tailor-apply", "tailor-notify"]
4073
4084
  };
4074
4085
  }
4075
- const ACTIONS_SHA = "1f2d0cc26b84b9fdae8a3a0427526df31e6c966b";
4076
- const ACTIONS_VERSION = "v2.0.0";
4086
+ const ACTIONS_SHA = "85f0ec85d3bcbdec915daa315b6ddf46ce9b6d2c";
4087
+ const ACTIONS_VERSION = "v2.2.0";
4077
4088
  /**
4078
4089
  * Render the coordinator workflow that orchestrates per-app composite actions.
4079
4090
  *
@@ -4117,7 +4128,8 @@ function renderCoordinateWorkflow(params) {
4117
4128
  `- id: tailor-drift-check`,
4118
4129
  ` uses: tailor-platform/actions/drift-check@${ACTIONS_SHA} # ${ACTIONS_VERSION}`,
4119
4130
  ` with:`,
4120
- ` package-manager: ${packageManager}`
4131
+ ` package-manager: ${packageManager}`,
4132
+ ` fail-on-drift: \${{ vars.TAILOR_PLATFORM_FAIL_ON_DRIFT == 'true' }}`
4121
4133
  ].join("\n");
4122
4134
  const configPath = (app) => app.dir === "." ? "tailor.config.ts" : `${app.dir}/tailor.config.ts`;
4123
4135
  const groupConfigPath = (group) => group.apps.map(configPath).join(",");
@@ -4621,7 +4633,7 @@ async function setupTarget(options) {
4621
4633
  kind: resolved.kind,
4622
4634
  workspaceName: resolved.workspaceName,
4623
4635
  file: resolved.file,
4624
- templateVersion: 9,
4636
+ templateVersion: 11,
4625
4637
  inputs: resolved.inputs,
4626
4638
  generatedIds: resolved.render.generatedIds,
4627
4639
  ejectedIds: existing?.ejectedIds ?? [],
@@ -4689,7 +4701,7 @@ async function setupCoordinate(options) {
4689
4701
  seenNames.add(name);
4690
4702
  const entry = actionTargets.get(name);
4691
4703
  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.`);
4704
+ 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
4705
  validateDir(entry.inputs.dir);
4694
4706
  return {
4695
4707
  name,
@@ -4741,7 +4753,7 @@ async function setupCoordinate(options) {
4741
4753
  kind: "coordinate",
4742
4754
  workspaceName: coordinatorName,
4743
4755
  file,
4744
- templateVersion: 9,
4756
+ templateVersion: 11,
4745
4757
  inputs: {
4746
4758
  branch,
4747
4759
  branchAutoDetected: coordinateKind === "branch" ? branchAutoDetected : branch !== null ? false : void 0,
@@ -4785,6 +4797,7 @@ async function setupCoordinate(options) {
4785
4797
 
4786
4798
  //#endregion
4787
4799
  //#region src/cli/commands/setup/check.ts
4800
+ const DRIFT_COUNT_MARKER = "TAILOR_SETUP_CHECK_DRIFT_COUNT";
4788
4801
  /**
4789
4802
  * Compute drift findings for one target by comparing its recorded lock state
4790
4803
  * against the current repository/config state.
@@ -4929,7 +4942,7 @@ async function checkGitHub(options) {
4929
4942
  currentHash,
4930
4943
  configExists,
4931
4944
  defaultBranch,
4932
- templateVersion: 9,
4945
+ templateVersion: 11,
4933
4946
  erdNamespaces,
4934
4947
  hasMigrations,
4935
4948
  hasSeeds,
@@ -4942,6 +4955,7 @@ async function checkGitHub(options) {
4942
4955
  return;
4943
4956
  }
4944
4957
  for (const finding of findings) logger.warn(`[${finding.target}] ${finding.message} (ignore key: ${finding.rule})`);
4958
+ if (options.ci) logger.log(`${DRIFT_COUNT_MARKER}=${String(findings.length)}`);
4945
4959
  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
4960
  }
4947
4961
 
@@ -5014,6 +5028,134 @@ async function setupDelete(options) {
5014
5028
  }
5015
5029
  }
5016
5030
 
5031
+ //#endregion
5032
+ //#region src/cli/commands/setup/renovate.ts
5033
+ const RENOVATE_CONFIG_FILE = "renovate.json";
5034
+ const RENOVATE_PRESET = "github>tailor-inc/renovate-config";
5035
+ const RENOVATE_CONFIG_FILES = [
5036
+ RENOVATE_CONFIG_FILE,
5037
+ "renovate.jsonc",
5038
+ "renovate.json5",
5039
+ ".github/renovate.json",
5040
+ ".github/renovate.jsonc",
5041
+ ".github/renovate.json5",
5042
+ ".gitlab/renovate.json",
5043
+ ".gitlab/renovate.jsonc",
5044
+ ".gitlab/renovate.json5",
5045
+ ".renovaterc",
5046
+ ".renovaterc.json",
5047
+ ".renovaterc.jsonc",
5048
+ ".renovaterc.json5"
5049
+ ];
5050
+ function getPathEntry(filePath) {
5051
+ try {
5052
+ return fs.lstatSync(filePath);
5053
+ } catch (error) {
5054
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
5055
+ throw error;
5056
+ }
5057
+ }
5058
+ function pathEntryExists(filePath) {
5059
+ return getPathEntry(filePath) !== null;
5060
+ }
5061
+ function extendsPreset(config) {
5062
+ if (!("extends" in config)) return false;
5063
+ const value = config.extends;
5064
+ return Array.isArray(value) && value.includes("github>tailor-inc/renovate-config");
5065
+ }
5066
+ function readJsonConfig(filePath) {
5067
+ const entry = getPathEntry(filePath);
5068
+ if (entry === null || !entry.isFile()) return null;
5069
+ let parsed;
5070
+ try {
5071
+ parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
5072
+ } catch {
5073
+ return null;
5074
+ }
5075
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
5076
+ }
5077
+ function classifyConfig(location, filePath, config) {
5078
+ if (config === null) return {
5079
+ kind: "unreadable",
5080
+ location
5081
+ };
5082
+ if (extendsPreset(config)) return {
5083
+ kind: "extends-preset",
5084
+ location
5085
+ };
5086
+ return {
5087
+ kind: "appendable",
5088
+ location,
5089
+ filePath,
5090
+ config
5091
+ };
5092
+ }
5093
+ function findExistingConfig(outputDir) {
5094
+ for (const file of RENOVATE_CONFIG_FILES) {
5095
+ const filePath = path.join(outputDir, file);
5096
+ if (!pathEntryExists(filePath)) continue;
5097
+ return classifyConfig(file, filePath, readJsonConfig(filePath));
5098
+ }
5099
+ const packageJsonPath = path.join(outputDir, "package.json");
5100
+ if (!fs.existsSync(packageJsonPath)) return null;
5101
+ let packageJson;
5102
+ try {
5103
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
5104
+ } catch (cause) {
5105
+ throw new Error("package.json is not valid JSON; could not inspect its Renovate config.", { cause });
5106
+ }
5107
+ if (typeof packageJson === "object" && packageJson !== null && "renovate" in packageJson) {
5108
+ const renovate = packageJson.renovate;
5109
+ return classifyConfig("package.json#renovate", packageJsonPath, typeof renovate === "object" && renovate !== null && !Array.isArray(renovate) ? renovate : null);
5110
+ }
5111
+ return null;
5112
+ }
5113
+ function detectIndent(source) {
5114
+ return /^(?<indent>[ ]+)"/m.exec(source)?.groups?.indent?.length ?? 2;
5115
+ }
5116
+ function appendPreset(existing) {
5117
+ const source = fs.readFileSync(existing.filePath, "utf-8");
5118
+ const current = "extends" in existing.config ? existing.config.extends : void 0;
5119
+ 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.`);
5120
+ const config = {
5121
+ ...existing.config,
5122
+ extends: [...current ?? [], RENOVATE_PRESET]
5123
+ };
5124
+ const root = existing.location === "package.json#renovate" ? {
5125
+ ...JSON.parse(source),
5126
+ renovate: config
5127
+ } : config;
5128
+ const trailingNewline = source.endsWith("\n") ? "\n" : "";
5129
+ fs.writeFileSync(existing.filePath, `${JSON.stringify(root, null, detectIndent(source))}${trailingNewline}`, "utf-8");
5130
+ }
5131
+ function renderRenovateConfig() {
5132
+ return `${JSON.stringify({
5133
+ $schema: "https://docs.renovatebot.com/renovate-schema.json",
5134
+ extends: [RENOVATE_PRESET]
5135
+ }, null, 2)}\n`;
5136
+ }
5137
+ /**
5138
+ * Generate a repository-level Renovate config that extends Tailor's shared preset.
5139
+ * @param options - Renovate setup options
5140
+ */
5141
+ async function setupRenovate(options) {
5142
+ logBetaWarning("setup");
5143
+ const existingConfig = findExistingConfig(options.outputDir);
5144
+ if (existingConfig !== null) {
5145
+ if (existingConfig.kind === "extends-preset") {
5146
+ logger.info(`Renovate is already set up at ${styles.path(existingConfig.location)}.`);
5147
+ return;
5148
+ }
5149
+ 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.`);
5150
+ appendPreset(existingConfig);
5151
+ logger.success(`Added the Tailor preset to ${styles.path(existingConfig.location)}`);
5152
+ return;
5153
+ }
5154
+ const outputPath = path.join(options.outputDir, RENOVATE_CONFIG_FILE);
5155
+ fs.writeFileSync(outputPath, renderRenovateConfig(), "utf-8");
5156
+ logger.success(`Generated ${styles.path(RENOVATE_CONFIG_FILE)}`);
5157
+ }
5158
+
5017
5159
  //#endregion
5018
5160
  //#region src/cli/commands/setup/index.ts
5019
5161
  const checkCommand = defineAppCommand({
@@ -5172,6 +5314,14 @@ const previewCommand = defineAppCommand({
5172
5314
  });
5173
5315
  }
5174
5316
  });
5317
+ const renovateCommand = defineAppCommand({
5318
+ name: "renovate",
5319
+ description: "Generate a Renovate config for Tailor dependency and workflow updates.",
5320
+ args: z.strictObject({}),
5321
+ run: async () => {
5322
+ await setupRenovate({ outputDir: process.cwd() });
5323
+ }
5324
+ });
5175
5325
  const deleteCommand$1 = defineAppCommand({
5176
5326
  name: "delete",
5177
5327
  description: "Delete managed workflow/action file(s) and their .github/tailor.lock entries.",
@@ -5192,13 +5342,14 @@ const deleteCommand$1 = defineAppCommand({
5192
5342
  });
5193
5343
  const setupCommand = defineCommand({
5194
5344
  name: "setup",
5195
- description: "Generate CI deploy workflows for your project. (beta)",
5345
+ description: "Set up repository automation for your project. (beta)",
5196
5346
  subCommands: {
5197
5347
  branch: branchCommand,
5198
5348
  tag: tagCommand,
5199
5349
  preview: previewCommand,
5200
5350
  action: actionCommand,
5201
5351
  coordinate: coordinateCommand,
5352
+ renovate: renovateCommand,
5202
5353
  check: checkCommand,
5203
5354
  delete: deleteCommand$1
5204
5355
  }
@@ -5426,12 +5577,7 @@ const staticwebsiteCommand = defineCommand({
5426
5577
 
5427
5578
  //#endregion
5428
5579
  //#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
- ]);
5580
+ const MIGRATION_ARTIFACT_NAMES = new Set(Object.values(MIGRATION_FILE_NAMES));
5435
5581
  async function activateBaseline(migrationsDir, snapshot, namespace, rebaseline) {
5436
5582
  const resolvedMigrationsDir = await fsPromises.realpath(migrationsDir);
5437
5583
  const parentDir = path.dirname(resolvedMigrationsDir);
@@ -5454,7 +5600,7 @@ async function activateBaseline(migrationsDir, snapshot, namespace, rebaseline)
5454
5600
  loadSnapshot((await generateSchemaFile({
5455
5601
  ...snapshot,
5456
5602
  namespace,
5457
- version: 2,
5603
+ version: 5,
5458
5604
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5459
5605
  rebaseline
5460
5606
  }, stagingDir, 0)).filePath);
@@ -5507,7 +5653,7 @@ async function rebaseline(options) {
5507
5653
  const currentHistoryId = reconstructSnapshotFromMigrations(target.migrationsDir, 0)?.rebaseline?.historyId ?? null;
5508
5654
  const initialFileState = captureMigrationFileState([target])[target.namespace];
5509
5655
  const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
5510
- const { defineApplication } = await import("../application-BxLLiLsr.mjs");
5656
+ const { defineApplication } = await import("../application-BZfk4HKm.mjs");
5511
5657
  const application = defineApplication({
5512
5658
  config,
5513
5659
  pluginManager
@@ -5520,7 +5666,7 @@ async function rebaseline(options) {
5520
5666
  if (!targetService) throw new Error(`No TailorDB service found for namespace "${target.namespace}"`);
5521
5667
  const assertLocalTypesReady = () => {
5522
5668
  const currentSnapshot = createSnapshotFromLocalTypes(targetService.types, target.namespace);
5523
- const localDiff = compareLocalTypesWithSnapshot(latestSnapshot, currentSnapshot.types, target.namespace);
5669
+ const localDiff = compareLocalTypesWithSnapshot(latestSnapshot, currentSnapshot.tables, target.namespace);
5524
5670
  if (hasChanges(localDiff)) {
5525
5671
  logger.error(`Migration history does not reproduce the current local schema for namespace ${styles.bold(target.namespace)}:`);
5526
5672
  logger.log(formatMigrationDiff(localDiff));
@@ -5945,7 +6091,7 @@ async function fetchRemoteTypes(client, workspaceId, namespace) {
5945
6091
  async function assertMigrationsReproduceLocalTypes(loaded, target) {
5946
6092
  const { config, plugins } = loaded;
5947
6093
  const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
5948
- const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-BxLLiLsr.mjs");
6094
+ const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-BZfk4HKm.mjs");
5949
6095
  const application = defineApplication({
5950
6096
  config,
5951
6097
  pluginManager
@@ -5957,7 +6103,7 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
5957
6103
  await service.processNamespacePlugins();
5958
6104
  }
5959
6105
  const pluginExecutorFiles = generatePluginFilesIfNeeded(pluginManager, application.tailorDBServices, config.path);
5960
- const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-_XmjSEGr.mjs")).createExecutorService({
6106
+ const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-D1RCdzIL.mjs")).createExecutorService({
5961
6107
  config: { files: [] },
5962
6108
  baseDir: path.dirname(config.path)
5963
6109
  }) : void 0);
@@ -5971,7 +6117,7 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
5971
6117
  };
5972
6118
  const latestSnapshot = reconstructSnapshotFromMigrations(target.migrationsDir);
5973
6119
  if (!latestSnapshot) return manifestOptions;
5974
- const diff = compareLocalTypesWithSnapshot(latestSnapshot, createSnapshotFromLocalTypes(tailordbService.types, target.namespace).types, target.namespace);
6120
+ const diff = compareLocalTypesWithSnapshot(latestSnapshot, createSnapshotFromLocalTypes(tailordbService.types, target.namespace).tables, target.namespace);
5975
6121
  if (!hasChanges(diff)) return manifestOptions;
5976
6122
  logger.error(`Migration history does not reproduce the current local schema for namespace ${styles.bold(target.namespace)}:`);
5977
6123
  logger.log(formatMigrationDiff(diff));
@@ -6019,7 +6165,7 @@ async function sync(options) {
6019
6165
  const { creates, updates, deletes } = compareSnapshotWithRemote(snapshot, new Set(remoteTypes.map((t) => t.name)));
6020
6166
  const remoteGqlPermissions = await fetchRemoteGqlPermissions(client, workspaceId, target.namespace);
6021
6167
  const remoteGqlPermissionTypes = new Set(remoteGqlPermissions.map((p) => p.typeName));
6022
- const desiredGqlPermissions = Object.entries(snapshot.types).flatMap(([typeName, snapshotType]) => snapshotType.permissions?.gql ? [{
6168
+ const desiredGqlPermissions = Object.entries(snapshot.tables).flatMap(([typeName, snapshotType]) => snapshotType.permissions?.gql ? [{
6023
6169
  typeName,
6024
6170
  permission: protoGqlPermission(snapshotType.permissions.gql)
6025
6171
  }] : []);
@@ -6245,7 +6391,7 @@ async function waitForCloneApplicationData(client, options) {
6245
6391
  function loadSnapshotSeedData(dataDir, typeNames, snapshot) {
6246
6392
  const data = {};
6247
6393
  for (const typeName of typeNames) {
6248
- const snapshotType = snapshot?.types[typeName];
6394
+ const snapshotType = snapshot?.tables[typeName];
6249
6395
  const jsonlPath = path.join(dataDir, `${typeName}.jsonl`);
6250
6396
  let content;
6251
6397
  try {
@@ -6296,11 +6442,11 @@ function isJsonObject(value) {
6296
6442
  * @returns Dependency-ordered type names and self-referencing types
6297
6443
  */
6298
6444
  function sortSeedTypesForSnapshot(snapshot) {
6299
- const typeNames = Object.keys(snapshot.types);
6445
+ const typeNames = Object.keys(snapshot.tables);
6300
6446
  const available = new Set(typeNames);
6301
6447
  const dependencies = /* @__PURE__ */ new Map();
6302
6448
  const selfRefTypes = [];
6303
- for (const [typeName, type] of Object.entries(snapshot.types)) {
6449
+ for (const [typeName, type] of Object.entries(snapshot.tables)) {
6304
6450
  const referenced = /* @__PURE__ */ new Set();
6305
6451
  for (const field of Object.values(type.fields)) {
6306
6452
  const target = field.foreignKeyType;
@@ -6528,7 +6674,7 @@ function createMigrationTestDependencies() {
6528
6674
  version: 1,
6529
6675
  namespace: service.namespace,
6530
6676
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6531
- types: input.types
6677
+ tables: input.types
6532
6678
  })];
6533
6679
  }));
6534
6680
  for (const [namespace, snapshot] of seedSnapshots) {
@@ -6744,15 +6890,18 @@ const testCommand = defineAppCommand({
6744
6890
  //#region src/cli/commands/tailordb/migrate/validate.ts
6745
6891
  /**
6746
6892
  * 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
6893
+ * migration script exists, no migration carries both a --no-script
6894
+ * acknowledgment and a migrate.ts, and generated normalization logic has
6895
+ * been reviewed, and to collect migrations with data-loss warnings that
6896
+ * have neither a migrate.ts nor a recorded --no-script acknowledgment
6750
6897
  * @param {string} migrationsDir - Migrations directory path
6751
6898
  * @param {string} namespace - TailorDB namespace (for error messages)
6899
+ * @param {string} [configPath] - Config path the current run used (for remediation hints)
6752
6900
  * @returns {UnacknowledgedWarningMigration[]} Unacknowledged warning migrations in the local history
6753
6901
  */
6754
- function assertMigrationScriptsReady(migrationsDir, namespace) {
6902
+ function assertMigrationScriptsReady(migrationsDir, namespace, configPath) {
6755
6903
  const missing = [];
6904
+ const conflicting = [];
6756
6905
  const unreviewed = [];
6757
6906
  const unacknowledgedWarnings = [];
6758
6907
  for (const file of getMigrationFiles(migrationsDir)) {
@@ -6761,6 +6910,7 @@ function assertMigrationScriptsReady(migrationsDir, namespace) {
6761
6910
  const migrateFilePath = getMigrationFilePath(migrationsDir, file.number, "migrate");
6762
6911
  const hasScript = fs.existsSync(migrateFilePath);
6763
6912
  if (diff.requiresMigrationScript && !diff.scriptSkipped && !hasScript) missing.push(file.number);
6913
+ if (diff.scriptSkipped && hasScript) conflicting.push(file.number);
6764
6914
  if (hasScript && fs.readFileSync(migrateFilePath, "utf8").includes("TODO(tailor-migration-review)")) unreviewed.push(file.number);
6765
6915
  if (diff.hasWarnings && !diff.scriptSkipped && !hasScript) unacknowledgedWarnings.push({
6766
6916
  migrationNumber: file.number,
@@ -6768,6 +6918,15 @@ function assertMigrationScriptsReady(migrationsDir, namespace) {
6768
6918
  });
6769
6919
  }
6770
6920
  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 "..."'.`);
6921
+ if (conflicting.length > 0) {
6922
+ const clearCommands = conflicting.map((migrationNumber) => ` ${formatMigrationScriptCommand({
6923
+ migrationNumber,
6924
+ namespace,
6925
+ configPath
6926
+ })}`).join("\n");
6927
+ 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):
6928
+ ${clearCommands}\nOr delete migrate.ts to keep the skip.`);
6929
+ }
6771
6930
  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
6931
  return unacknowledgedWarnings;
6773
6932
  }
@@ -6841,7 +7000,7 @@ async function collectValidationReports(options) {
6841
7000
  const targetNamespaces = options.namespace ? namespacesWithMigrations.filter((ns) => ns.namespace === options.namespace) : namespacesWithMigrations;
6842
7001
  if (targetNamespaces.length === 0) throw new Error(`Namespace "${options.namespace}" not found or does not have migrations configured`);
6843
7002
  const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
6844
- const { defineApplication } = await import("../application-BxLLiLsr.mjs");
7003
+ const { defineApplication } = await import("../application-BZfk4HKm.mjs");
6845
7004
  const application = defineApplication({
6846
7005
  config,
6847
7006
  pluginManager
@@ -6860,7 +7019,7 @@ async function collectValidationReports(options) {
6860
7019
  for (const target of targetNamespaces) try {
6861
7020
  assertValidMigrationFiles(target.migrationsDir, target.namespace);
6862
7021
  reconstructSnapshotFromMigrations(target.migrationsDir);
6863
- const namespaceWarnings = assertMigrationScriptsReady(target.migrationsDir, target.namespace);
7022
+ const namespaceWarnings = assertMigrationScriptsReady(target.migrationsDir, target.namespace, options.configPath);
6864
7023
  unacknowledgedWarnings?.set(target.namespace, namespaceWarnings);
6865
7024
  checkableNamespaces.push(target);
6866
7025
  } catch (error) {
@@ -6926,7 +7085,7 @@ function printValidationReports(reports) {
6926
7085
  const label = formatMigrationNumber(migration.migrationNumber);
6927
7086
  for (const warning of migration.warnings) {
6928
7087
  const field = warning.fieldName ? `.${warning.fieldName}` : "";
6929
- logger.log(` ${label}: ${warning.typeName}${field}: ${warning.reason}`);
7088
+ logger.log(` ${label}: ${warning.tableName}${field}: ${warning.reason}`);
6930
7089
  }
6931
7090
  }
6932
7091
  }
@@ -7051,7 +7210,7 @@ const upgradeCommand = defineAppCommand({
7051
7210
  run: async (args) => {
7052
7211
  const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
7053
7212
  await initTelemetry();
7054
- const { upgrade } = await import("../service-CCwl3Avt.mjs");
7213
+ const { upgrade } = await import("../service-hZskxZmg.mjs");
7055
7214
  await upgrade({
7056
7215
  from: args.from,
7057
7216
  dryRun: args["dry-run"],
@@ -7555,6 +7714,8 @@ runMain(mainCommand, {
7555
7714
  if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
7556
7715
  } else if (error instanceof Error) {
7557
7716
  logger.error(error.message);
7717
+ const hint = typeOnlyImportHint(error);
7718
+ if (hint) logger.log(` ${styles.info("Suggestion:")} ${hint}`);
7558
7719
  if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
7559
7720
  } else logger.error(`Unknown error: ${error}`);
7560
7721
  if (!isCLIError(error) && (!(error instanceof Error) || error instanceof TypeError || error instanceof RangeError)) {