@tailor-platform/sdk 2.0.0-next.10 → 2.0.0-next.11

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 (129) hide show
  1. package/CHANGELOG.md +165 -0
  2. package/dist/{application-miwiXtB4.mjs → application-CM6hKnSK.mjs} +312 -89
  3. package/dist/application-CM6hKnSK.mjs.map +1 -0
  4. package/dist/application-CXNaUNhv.mjs +3 -0
  5. package/dist/cli/commands/deploy/aigateway.d.mts +1 -1
  6. package/dist/cli/commands/deploy/application.d.mts +1 -1
  7. package/dist/cli/commands/deploy/auth.d.mts +1 -1
  8. package/dist/cli/commands/deploy/deploy.d.mts +1 -0
  9. package/dist/cli/commands/deploy/executor.d.mts +1 -1
  10. package/dist/cli/commands/deploy/function-registry.d.mts +1 -2
  11. package/dist/cli/commands/deploy/idp.d.mts +1 -1
  12. package/dist/cli/commands/deploy/label.d.mts +2 -0
  13. package/dist/cli/commands/deploy/resolver.d.mts +1 -1
  14. package/dist/cli/commands/deploy/staticwebsite.d.mts +1 -1
  15. package/dist/cli/commands/deploy/tailordb/index.d.mts +2 -1
  16. package/dist/cli/commands/deploy/types.d.mts +2 -1
  17. package/dist/cli/commands/deploy/workflow-execution-policy.d.mts +1 -2
  18. package/dist/cli/commands/deploy/workflow.d.mts +1 -1
  19. package/dist/cli/commands/executor/get.d.mts +1 -10
  20. package/dist/cli/commands/executor/jobs.d.mts +1 -38
  21. package/dist/cli/commands/executor/trigger.d.mts +1 -11
  22. package/dist/cli/commands/generate/seed/bundler.d.mts +2 -1
  23. package/dist/cli/commands/tailordb/migrate/schema-checks.d.mts +5 -0
  24. package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +1 -1
  25. package/dist/cli/commands/workflow/executions.d.mts +1 -13
  26. package/dist/cli/commands/workflow/get.d.mts +1 -10
  27. package/dist/cli/commands/workflow/start.d.mts +2 -14
  28. package/dist/cli/lib.d.mts +7 -11
  29. package/dist/cli/lib.mjs +99 -42
  30. package/dist/cli/lib.mjs.map +1 -1
  31. package/dist/cli/main.mjs +318 -124
  32. package/dist/cli/main.mjs.map +1 -1
  33. package/dist/cli/services/http-adapter/bundler.d.mts +1 -0
  34. package/dist/cli/services/workflow/bundler.d.mts +2 -1
  35. package/dist/cli/shared/client.d.mts +28 -3
  36. package/dist/cli/shared/config-loader.d.mts +11 -4
  37. package/dist/cli/shared/tsconfig-paths-plugin.d.mts +2 -0
  38. package/dist/cli/shared/type-generator.d.mts +4 -3
  39. package/dist/completion/zsh-worker.zsh +82 -22
  40. package/dist/configure/config/types.d.mts +26 -3
  41. package/dist/configure/index.d.mts +2 -2
  42. package/dist/configure/index.mjs +171 -3
  43. package/dist/configure/index.mjs.map +1 -1
  44. package/dist/configure/services/executor/index.d.mts +2 -2
  45. package/dist/configure/services/executor/trigger/event.d.mts +245 -2
  46. package/dist/configure/services/executor/trigger/index.d.mts +3 -3
  47. package/dist/configure/services/idp/index.d.mts +1 -1
  48. package/dist/configure/services/index.d.mts +2 -2
  49. package/dist/configure/services/tailordb/index.d.mts +0 -1
  50. package/dist/configure/services/workflow/execution-policy.d.mts +1 -1
  51. package/dist/configure/services/workflow/execution-policy.types.d.mts +1 -1
  52. package/dist/configure/services/workflow/job.d.mts +11 -2
  53. package/dist/configure/services/workflow/workflow.d.mts +10 -0
  54. package/dist/{globals-DQotNuwK.mjs → globals-TfAVItuK.mjs} +1 -2
  55. package/dist/globals-TfAVItuK.mjs.map +1 -0
  56. package/dist/node-builtins-CmaL2Cbq.mjs +36 -0
  57. package/dist/node-builtins-CmaL2Cbq.mjs.map +1 -0
  58. package/dist/plugin/builtin/enum-constants/index.mjs +115 -2
  59. package/dist/plugin/builtin/enum-constants/index.mjs.map +1 -0
  60. package/dist/plugin/builtin/file-utils/index.mjs +149 -2
  61. package/dist/plugin/builtin/file-utils/index.mjs.map +1 -0
  62. package/dist/plugin/builtin/kysely-type/index.mjs +274 -2
  63. package/dist/plugin/builtin/kysely-type/index.mjs.map +1 -0
  64. package/dist/plugin/builtin/seed/index.mjs +1 -1
  65. package/dist/plugin/builtin/seed/seed-type-processor.d.mts +2 -0
  66. package/dist/{register-ts-hook-BAHMwxfG.mjs → register-ts-hook-LYV7zH-e.mjs} +2252 -797
  67. package/dist/register-ts-hook-LYV7zH-e.mjs.map +1 -0
  68. package/dist/registry-BIGVUrMB.mjs.map +1 -1
  69. package/dist/runtime/index.mjs +1 -1
  70. package/dist/runtime/workflow.d.mts +2 -20
  71. package/dist/runtime/workflow.mjs +1 -1
  72. package/dist/{seed-LUVr7jHL.mjs → seed-B-yckgQY.mjs} +42 -3
  73. package/dist/seed-B-yckgQY.mjs.map +1 -0
  74. package/dist/service-M7OYziQP.mjs +3 -0
  75. package/dist/{service-BNtPsK9Q.mjs → service-k1xy_A5G.mjs} +29 -2
  76. package/dist/{service-BNtPsK9Q.mjs.map → service-k1xy_A5G.mjs.map} +1 -1
  77. package/dist/{service_pb-BwZRiNug.mjs → service_pb-D5--My_M.mjs} +5 -5
  78. package/dist/service_pb-D5--My_M.mjs.map +1 -0
  79. package/dist/{service_pb-wRkxUta6.mjs → service_pb-i8CmnQP4.mjs} +1 -1
  80. package/dist/tailor-proto/src/tailor/v1/application_pb.d.mts +106 -1
  81. package/dist/tailor-proto/src/tailor/v1/executor_resource_pb.d.mts +24 -1
  82. package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +49 -1
  83. package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +89 -6
  84. package/dist/tailor-proto/src/tailor/v1/telemetryrouter_resource_pb.d.mts +26 -1
  85. package/dist/tailor-proto/src/tailor/v1/workflow_pb.d.mts +101 -1
  86. package/dist/tailor-proto/src/tailor/v1/workflow_resource_pb.d.mts +22 -1
  87. package/dist/types/executor.generated.d.mts +23 -3
  88. package/dist/types/idp.generated.d.mts +1 -1
  89. package/dist/utils/test/index.d.mts +1 -2
  90. package/dist/utils/test/index.mjs +1 -173
  91. package/dist/utils/test/index.mjs.map +1 -1
  92. package/dist/vitest/environment.mjs +1 -1
  93. package/dist/vitest/index.d.mts +2 -2
  94. package/dist/vitest/index.mjs +10 -52
  95. package/dist/vitest/index.mjs.map +1 -1
  96. package/dist/vitest/mocks/workflow.d.mts +0 -9
  97. package/dist/vitest/setup.mjs +1 -1
  98. package/dist/{workflow-CsaQ9qK-.mjs → workflow-Bamae_Yc.mjs} +1 -9
  99. package/dist/workflow-Bamae_Yc.mjs.map +1 -0
  100. package/dist/{workspace_resource_pb-UGK1SSn_.mjs → workspace_resource_pb-DXnO2AT-.mjs} +7 -7
  101. package/dist/workspace_resource_pb-DXnO2AT-.mjs.map +1 -0
  102. package/docs/cli/tailordb.md +39 -7
  103. package/docs/cli-reference.md +11 -10
  104. package/docs/configuration.md +50 -0
  105. package/docs/migration/v2.md +306 -9
  106. package/docs/runtime.md +1 -1
  107. package/docs/services/executor.md +83 -3
  108. package/docs/services/idp.md +8 -6
  109. package/docs/services/resolver.md +6 -4
  110. package/docs/services/tailordb-migration.md +17 -6
  111. package/docs/services/tailordb.md +6 -4
  112. package/docs/services/workflow.md +33 -3
  113. package/package.json +19 -17
  114. package/dist/application-DArFTTJ9.mjs +0 -3
  115. package/dist/application-miwiXtB4.mjs.map +0 -1
  116. package/dist/enum-constants-j9QBF0cB.mjs +0 -116
  117. package/dist/enum-constants-j9QBF0cB.mjs.map +0 -1
  118. package/dist/file-utils-yEiIS4nJ.mjs +0 -150
  119. package/dist/file-utils-yEiIS4nJ.mjs.map +0 -1
  120. package/dist/globals-DQotNuwK.mjs.map +0 -1
  121. package/dist/kysely-type-DqIRgDYR.mjs +0 -275
  122. package/dist/kysely-type-DqIRgDYR.mjs.map +0 -1
  123. package/dist/register-ts-hook-BAHMwxfG.mjs.map +0 -1
  124. package/dist/seed-LUVr7jHL.mjs.map +0 -1
  125. package/dist/service-D5jn05ZI.mjs +0 -3
  126. package/dist/service_pb-BwZRiNug.mjs.map +0 -1
  127. package/dist/utils/test/mock.d.mts +0 -86
  128. package/dist/workflow-CsaQ9qK-.mjs.map +0 -1
  129. package/dist/workspace_resource_pb-UGK1SSn_.mjs.map +0 -1
package/dist/cli/main.mjs CHANGED
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { r as parseInputFields } from "../schema--xYWRGfe.mjs";
3
- import { At as PATScope, Ct as AuthInvokerSchema, E as CustomDomainStatus, W as FunctionExecution_Type } from "../workspace_resource_pb-UGK1SSn_.mjs";
3
+ import { At as PATScope, Ct as AuthInvokerSchema, E as CustomDomainStatus, W as FunctionExecution_Type } from "../workspace_resource_pb-DXnO2AT-.mjs";
4
4
  import { t as assertDefined } from "../assert-DBxo8jPo.mjs";
5
5
  import { n as logger, r as styles } from "../logger-CXQq9YIp.mjs";
6
- import { $n as removeLegacyUserAlias, $t as ensureConfigId, An as hasChanges, Bn as deleteUserTokens, C as healthCommand, Cn as isValidMigrationNumber, Dn as parseMigrationNumberArg, E as resumeCommand, En as formatMigrationNumber, Et as triggerCommand, F as writeDbTypesFile, Ft as startCommand, G as removeCommand$1, Gn as loadConfigPath, H as extractOwnedNamespaces, Hn as hasAnyUserTokenEntry, Ht as functionExecutionStatusToString, I as getConfiguredEditorCommand, In as prompt, Jn as loadPlatformClientConfig, K as updateCommand$3, Kn as loadConsoleBaseUrl, Kt as deployFromCLI, L as openInConfiguredEditor, Ln as apiCall, Lt as getCommand$6, Mn as resourceTrn, Mt as jobsCommand, N as generateCommand$1, Nn as sdkNameLabelKey, O as listCommand$14, Ot as listCommand$8, P as generateMigrationScript, Pn as PluginManager, Q as getCommand$5, Qn as readPlatformConfig, Qt as workspaceNameSchema, R as deployCommand$1, Rn as assertWritable, St as getCommand$2, Tn as reconstructSnapshotFromMigrations, Tt as webhookCommand, U as logBetaWarning, Un as hasUserTokenEntry, Ut as getCommand$1, V as showCommand, Vn as fetchLatestToken, Wn as loadAccessToken, X as listCommand$13, Xn as loadWorkspaceId, Y as treeCommand, Yn as loadStoredUserTokens, Yt as createCommand$4, Zn as platformConfigFromProfile, Zt as formatKeyValueTable, _n as createSnapshotFromLocalTypes, a as queryCommand, an as parseMigrationLabelNumber, ar as commonArgs, bn as getMigrationFilePath, bt as listCommand$9, c as removeCommand, cn as protoGqlPermission, cr as deploymentArgs, ct as createCommand$3, dn as INITIAL_SCHEMA_NUMBER, dr as pagedLogArgs, en as executeScript, er as resolveConfigUser, et as updateCommand$2, f as inviteCommand, fr as paginationArgs, ft as getCommand$3, g as getCommand$7, gt as listCommand$10, hn as compareLocalTypesWithSnapshot, hr as workspaceArgs, ht as tokenCommand$1, in as handleOptionalToRequiredError, ir as defineAppCommand, it as getCommand$4, j as truncateCommand, jn as getNamespacesWithMigrations, kn as formatMigrationDiff, lr as isVerbose, m as restoreCommand, mn as assertValidMigrationFiles, mr as toPageDirection, nr as saveUserTokens, nt as listCommand$12, o as updateCommand$4, on as compareSnapshotWithRemote, or as configArg, ot as deleteCommand$4, pr as resolveMachineUserInputSource, qn as loadMachineUserName, qt as listCommand$17, r as serializeError, rn as MIGRATION_LABEL_KEY, rr as writePlatformConfig, sn as generateAllTypeManifestsFromSnapshot, sr as confirmationArgs, t as registerTsHook, tr as resolveUserTokenKey, u as listCommand$16, ur as multiConfigArg, ut as listCommand$11, v as deleteCommand$5, vn as getLatestMigrationNumber, vt as generate, w as waitCommand, wn as loadDiff, x as listCommand$15, xn as getMigrationFiles, zn as loadConfig, zt as executionsCommand } from "../register-ts-hook-BAHMwxfG.mjs";
7
- import { A as defaultPlatformBaseUrl, B as getPlatformBaseUrl, E as getDistDir, F as fetchPlatformMachineUserToken, H as initOperatorClient, I as fetchUserInfo, M as fetchAllTolerant, P as fetchPaged, R as getOAuth2ClientId, S as resolveBundleLogLevel, U as isDefaultPlatform, V as initOAuth2Client, a as resolveInlineSourcemap, b as composeFunctionTreeshakeOptions, c as WorkflowJobSchema, d as INVOKER_EXPR, j as fetchAll, k as closeConnectionPool, l as ResolverSchema, m as buildResolverPermissionAndInputCheckExpr, v as resolveTSConfigWithFallback, x as createLogLevelTreeshakeOptions, y as platformBundleDefinePlugin, z as getOrNull } from "../application-miwiXtB4.mjs";
8
- import { i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-BNtPsK9Q.mjs";
6
+ import { $n as hasUserTokenEntry, $t as ensureConfigId, An as loadDiff, Bn as sdkAppIdLabelKey, C as healthCommand, Cn as createSnapshotFromLocalTypes, Cr as resolveMachineUserInputSource, Dn as getMigrationFilePath, E as resumeCommand, Et as triggerCommand, F as writeDbTypesFile, Fn as formatMigrationDiff, Ft as startCommand, G as removeCommand$1, H as extractOwnedNamespaces, Hn as sdkVersionLabelKey, Ht as functionExecutionStatusToString, I as getConfiguredEditorCommand, In as hasChanges, Jn as assertWritable, K as updateCommand$3, Kn as prompt, Kt as deployFromCLI, L as openInConfiguredEditor, Ln as getNamespacesWithMigrations, Lt as getCommand$6, Mn as formatMigrationNumber, Mt as jobsCommand, N as generateCommand$1, Nn as parseMigrationNumberArg, O as listCommand$14, On as getMigrationFiles, Ot as listCommand$8, P as generateMigrationScript, Pn as formatDiffSummary, Q as getCommand$5, Qn as hasAnyUserTokenEntry, Qt as workspaceNameSchema, R as deployCommand$1, Rn as selectTargetNamespace, Sr as paginationArgs, St as getCommand$2, Tr as workspaceArgs, Tt as webhookCommand, U as logBetaWarning, Un as writeMetadataLabels, Ut as getCommand$1, V as showCommand, Vn as sdkNameLabelKey, Wn as PluginManager, X as listCommand$13, Xn as deleteUserTokens, Y as treeCommand, Yn as loadConfig, Yt as createCommand$4, Zn as fetchLatestToken, Zt as formatKeyValueTable, _r as confirmationArgs, a as queryCommand, an as toTailorDBDeployInput, ar as loadStoredUserTokens, bn as assertValidMigrationFiles, br as multiConfigArg, bt as listCommand$9, c as removeCommand, cn as handleOptionalToRequiredError, cr as readPlatformConfig, ct as createCommand$3, dn as compareSnapshotWithRemote, dr as resolveUserTokenKey, en as executeScript, er as loadAccessToken, et as updateCommand$2, f as inviteCommand, fn as generateAllTypeManifestsFromSnapshot, fr as saveUserTokens, ft as getCommand$3, g as getCommand$7, gn as INITIAL_SCHEMA_NUMBER, gr as configArg, gt as listCommand$10, hr as commonArgs, ht as tokenCommand$1, in as logRemoteDriftGuidance, ir as loadPlatformClientConfig, it as getCommand$4, j as truncateCommand, jn as reconstructSnapshotFromMigrations, ln as parseMigrationLabelNumber, lr as removeLegacyUserAlias, m as restoreCommand, mr as defineAppCommand, nr as loadConsoleBaseUrl, nt as listCommand$12, o as updateCommand$4, on as verifyRemoteSchema, or as loadWorkspaceId, ot as deleteCommand$4, pn as protoGqlPermission, pr as writePlatformConfig, qn as apiCall, qt as listCommand$17, r as serializeError, rn as checkMigrationDiffs, rr as loadMachineUserName, sn as MIGRATION_LABEL_KEY, sr as platformConfigFromProfile, t as registerTsHook, tr as loadConfigPath, u as listCommand$16, un as sanitizeMigrationLabel, ur as resolveConfigUser, ut as listCommand$11, v as deleteCommand$5, vr as deploymentArgs, vt as generate, w as waitCommand, wn as formatSchemaDrifts, wr as toPageDirection, x as listCommand$15, xn as compareLocalTypesWithSnapshot, xr as pagedLogArgs, yn as assertMigrationNumberExists, yr as isVerbose, zn as resourceTrn, zt as executionsCommand } from "../register-ts-hook-LYV7zH-e.mjs";
7
+ import { C as createLogLevelTreeshakeOptions, F as fetchAllTolerant, G as initOperatorClient, H as getOrNull, K as isDefaultPlatform, L as fetchPaged, M as closeConnectionPool, N as defaultPlatformBaseUrl, P as fetchAll, R as fetchPlatformMachineUserToken, S as composeFunctionTreeshakeOptions, T as createBundleLog, U as getPlatformBaseUrl, V as getOAuth2ClientId, W as initOAuth2Client, a as resolveInlineSourcemap, b as resolveTSConfigWithFallback, c as WorkflowJobSchema, d as INVOKER_EXPR, h as assertUniqueLocalTailorDBTypeNames, k as getDistDir, l as ResolverSchema, m as buildResolverPermissionAndInputCheckExpr, v as createGeneratedEntryResolverPlugin, w as resolveBundleLogLevel, x as platformBundleDefinePlugin, y as createTsconfigPathsPlugin, z as fetchUserInfo } from "../application-CM6hKnSK.mjs";
8
+ import { i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-k1xy_A5G.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
12
  import { n as isCLIError, t as createCLIError } from "../errors-D7583Zz7.mjs";
13
- import { t as OperatorService } from "../service_pb-BwZRiNug.mjs";
13
+ import { t as OperatorService } from "../service_pb-D5--My_M.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-B5LqCTfF.mjs";
15
15
  import * as path from "pathe";
16
16
  import { dirname, resolve } from "pathe";
@@ -1697,22 +1697,29 @@ async function bundleForTestRun(options) {
1697
1697
  const entryContent = generateEntry(detected, sourceFile, env, machineUser, workspaceId);
1698
1698
  fs.writeFileSync(entryPath, entryContent);
1699
1699
  const tsconfig = await resolveTSConfigWithFallback(baseDir);
1700
+ const bundleLog = createBundleLog({ tsconfig });
1701
+ const buildResult = await rolldown.build({
1702
+ plugins: [
1703
+ createGeneratedEntryResolverPlugin(entryPath, baseDir),
1704
+ createTsconfigPathsPlugin(),
1705
+ platformBundleDefinePlugin
1706
+ ],
1707
+ input: entryPath,
1708
+ write: false,
1709
+ output: {
1710
+ format: "esm",
1711
+ sourcemap: inlineSourcemap ? "inline" : true,
1712
+ minify: inlineSourcemap ? { mangle: { keepNames: true } } : true,
1713
+ codeSplitting: false,
1714
+ dir: process.cwd()
1715
+ },
1716
+ tsconfig,
1717
+ treeshake: composeFunctionTreeshakeOptions([createLogLevelTreeshakeOptions(bundleLogLevel)]),
1718
+ ...bundleLog.options
1719
+ });
1720
+ bundleLog.assertAllResolved();
1700
1721
  return {
1701
- bundledCode: (await rolldown.build({
1702
- plugins: [platformBundleDefinePlugin],
1703
- input: entryPath,
1704
- write: false,
1705
- output: {
1706
- format: "esm",
1707
- sourcemap: inlineSourcemap ? "inline" : true,
1708
- minify: inlineSourcemap ? { mangle: { keepNames: true } } : true,
1709
- codeSplitting: false,
1710
- dir: process.cwd()
1711
- },
1712
- tsconfig,
1713
- treeshake: composeFunctionTreeshakeOptions([createLogLevelTreeshakeOptions(bundleLogLevel)]),
1714
- logLevel: "silent"
1715
- })).output[0].code,
1722
+ bundledCode: buildResult.output[0].code,
1716
1723
  scriptName
1717
1724
  };
1718
1725
  }
@@ -3378,17 +3385,26 @@ async function checkVaultManaged(params) {
3378
3385
  /**
3379
3386
  * Release ownership of a managed vault by removing SDK labels from metadata.
3380
3387
  * Call this after the user has confirmed they want to proceed with a CLI operation on a managed vault.
3381
- * @param params - Client, TRN, and existing labels from checkVaultManaged result
3388
+ * The labels are read again at write time, so the caller does not pass the ones
3389
+ * `checkVaultManaged` saw: anything written since then is kept, and only the
3390
+ * ownership labels are removed.
3391
+ *
3392
+ * That includes the app id, which decides ownership on its own: leaving it
3393
+ * behind would keep the vault SDK-owned, and the next deploy of a config that
3394
+ * no longer declares it would delete the vault and every secret in it.
3395
+ * @param params - Client and TRN from the checkVaultManaged result
3382
3396
  * @param params.client - Operator client used to update vault metadata
3383
3397
  * @param params.trn - TRN of the vault resource
3384
- * @param params.existingLabels - Existing metadata labels on the vault before release
3385
3398
  */
3386
3399
  async function releaseVaultOwnership(params) {
3387
- const { client, trn, existingLabels } = params;
3388
- const { [sdkNameLabelKey]: _, "sdk-version": __, ...remainingLabels } = existingLabels;
3389
- await client.setMetadata({
3400
+ const { client, trn } = params;
3401
+ await writeMetadataLabels(client, {
3390
3402
  trn,
3391
- labels: remainingLabels
3403
+ remove: [
3404
+ sdkNameLabelKey,
3405
+ sdkVersionLabelKey,
3406
+ sdkAppIdLabelKey
3407
+ ]
3392
3408
  });
3393
3409
  logger.info("Config ownership has been removed from this vault. Remove it from defineSecretManager() in your config to prevent the next apply from re-claiming it.");
3394
3410
  }
@@ -5533,6 +5549,27 @@ const scriptCommand = defineAppCommand({
5533
5549
  }
5534
5550
  });
5535
5551
 
5552
+ //#endregion
5553
+ //#region src/cli/commands/tailordb/migrate/remote-state.ts
5554
+ /**
5555
+ * Fetch the namespace's current migration number from its metadata labels.
5556
+ *
5557
+ * Only a namespace that has not been deployed yet reads as "no current
5558
+ * migration". Every other lookup failure must propagate: treating it as
5559
+ * unset would misreport a transient error as "no checkpoint", showing
5560
+ * every migration as pending to callers.
5561
+ * @param client - Operator client
5562
+ * @param trn - Namespace TRN
5563
+ * @returns Parsed current migration number, or null when unset or unparseable
5564
+ */
5565
+ async function fetchRemoteMigrationNumber(client, trn) {
5566
+ const label = (await getOrNull(async () => {
5567
+ const { metadata } = await client.getMetadata({ trn });
5568
+ return metadata;
5569
+ }))?.labels[MIGRATION_LABEL_KEY];
5570
+ return label ? parseMigrationLabelNumber(label) : null;
5571
+ }
5572
+
5536
5573
  //#endregion
5537
5574
  //#region src/cli/commands/tailordb/migrate/set.ts
5538
5575
  /**
@@ -5541,41 +5578,23 @@ const scriptCommand = defineAppCommand({
5541
5578
  */
5542
5579
  async function set(options) {
5543
5580
  logBetaWarning("tailordb migration");
5544
- const numberStr = options.number;
5545
- let migrationNumber;
5546
- if (isValidMigrationNumber(numberStr)) migrationNumber = parseInt(numberStr, 10);
5547
- else {
5548
- migrationNumber = parseInt(numberStr, 10);
5549
- if (isNaN(migrationNumber) || migrationNumber < 0) throw new Error(`Invalid migration number format: ${numberStr}. Expected 4-digit format (e.g., 0001) or integer (e.g., 1).`);
5550
- }
5581
+ const migrationNumber = parseMigrationNumberArg(options.number);
5551
5582
  const { config } = await loadConfig(options.configPath);
5552
- const namespacesWithMigrations = getNamespacesWithMigrations(config, path.dirname(config.path));
5553
- if (namespacesWithMigrations.length === 0) throw new Error("No TailorDB services with migrations configuration found");
5554
- let targetNamespace;
5555
- if (options.namespace) {
5556
- if (!namespacesWithMigrations.some((ns) => ns.namespace === options.namespace)) throw new Error(`Namespace "${options.namespace}" not found or does not have migrations configured`);
5557
- targetNamespace = options.namespace;
5558
- } else if (namespacesWithMigrations.length === 1) {
5559
- const [ns] = namespacesWithMigrations;
5560
- targetNamespace = assertDefined(ns, "namespace with migrations missing").namespace;
5561
- } else throw new Error(`Multiple TailorDB services found. Please specify namespace with --namespace flag: ${namespacesWithMigrations.map((ns) => ns.namespace).join(", ")}`);
5583
+ const target = selectTargetNamespace(getNamespacesWithMigrations(config, path.dirname(config.path)), options.namespace);
5584
+ const targetNamespace = target.namespace;
5585
+ assertValidMigrationFiles(target.migrationsDir, targetNamespace);
5586
+ assertMigrationNumberExists(target.migrationsDir, migrationNumber);
5562
5587
  const client = await initOperatorClient(await loadAccessToken({ profile: options.profile }));
5563
5588
  const trn = resourceTrn(await loadWorkspaceId({
5564
5589
  workspaceId: options.workspaceId,
5565
5590
  profile: options.profile
5566
5591
  }), "tailordb", targetNamespace);
5567
- let currentMigration;
5568
- try {
5569
- const { metadata } = await client.getMetadata({ trn });
5570
- const label = metadata?.labels["sdk-migration"];
5571
- currentMigration = label ? parseMigrationLabelNumber(label) ?? 0 : 0;
5572
- } catch {
5573
- currentMigration = 0;
5574
- }
5592
+ const current = await fetchRemoteMigrationNumber(client, trn);
5593
+ const currentMigration = current ?? 0;
5575
5594
  logger.newline();
5576
5595
  logger.warn("This operation will change the migration checkpoint.");
5577
5596
  logger.log(`Namespace: ${styles.bold(targetNamespace)}`);
5578
- logger.log(`Current migration: ${styles.bold(formatMigrationNumber(currentMigration))}`);
5597
+ logger.log(`Current migration: ${current === null ? "<unset>" : styles.bold(formatMigrationNumber(current))}`);
5579
5598
  logger.log(`New migration: ${styles.bold(formatMigrationNumber(migrationNumber))}`);
5580
5599
  logger.newline();
5581
5600
  if (migrationNumber < currentMigration) {
@@ -5595,20 +5614,18 @@ async function set(options) {
5595
5614
  }
5596
5615
  logger.newline();
5597
5616
  }
5598
- const { metadata } = await client.getMetadata({ trn });
5599
- const existingLabels = metadata?.labels ?? {};
5600
- await client.setMetadata({
5617
+ await writeMetadataLabels(client, {
5601
5618
  trn,
5602
- labels: {
5603
- ...existingLabels,
5604
- "sdk-migration": `m${formatMigrationNumber(migrationNumber)}`
5605
- }
5619
+ labels: { [MIGRATION_LABEL_KEY]: sanitizeMigrationLabel(migrationNumber) }
5606
5620
  });
5607
5621
  logger.success(`Migration checkpoint set to ${styles.bold(formatMigrationNumber(migrationNumber))} for namespace ${styles.bold(targetNamespace)}`);
5608
5622
  }
5609
5623
  const setCommand = defineAppCommand({
5610
5624
  name: "set",
5611
5625
  description: "Set migration checkpoint to a specific number.",
5626
+ notes: `The migration number must be a 4-digit value (e.g. \`0001\`) or a bare integer (e.g. \`1\`) within 0–9999, and must exist in the local migration history; \`0\` is always accepted as the baseline, provided the local history passes validation. A gapped history is rejected.
5627
+
5628
+ Metadata lookup failures (authentication, permission, or network errors) are reported as errors; only a not-yet-deployed namespace is treated as having no checkpoint.`,
5612
5629
  args: z.strictObject({
5613
5630
  ...deploymentArgs,
5614
5631
  ...confirmationArgs,
@@ -5636,6 +5653,9 @@ const setCommand = defineAppCommand({
5636
5653
 
5637
5654
  //#endregion
5638
5655
  //#region src/cli/commands/tailordb/migrate/status.ts
5656
+ function isStatusFailure(row) {
5657
+ return row.status === "error";
5658
+ }
5639
5659
  async function collectMigrationStatuses(options) {
5640
5660
  const { config } = await loadConfig(options.configPath);
5641
5661
  const namespacesWithMigrations = getNamespacesWithMigrations(config, path.dirname(config.path));
@@ -5647,17 +5667,21 @@ async function collectMigrationStatuses(options) {
5647
5667
  workspaceId: options.workspaceId,
5648
5668
  profile: options.profile
5649
5669
  });
5650
- const statuses = [];
5670
+ const rows = [];
5651
5671
  for (const { namespace, migrationsDir } of targetNamespaces) {
5652
5672
  const trn = resourceTrn(workspaceId, "tailordb", namespace);
5653
- let currentMigration;
5673
+ let current;
5654
5674
  try {
5655
- const { metadata } = await client.getMetadata({ trn });
5656
- const label = metadata?.labels["sdk-migration"];
5657
- currentMigration = label ? parseMigrationLabelNumber(label) ?? 0 : 0;
5658
- } catch {
5659
- currentMigration = 0;
5675
+ current = await fetchRemoteMigrationNumber(client, trn);
5676
+ } catch (error) {
5677
+ rows.push({
5678
+ status: "error",
5679
+ namespace,
5680
+ error: error instanceof Error ? error.message : String(error)
5681
+ });
5682
+ continue;
5660
5683
  }
5684
+ const currentMigration = current ?? 0;
5661
5685
  const pendingMigrations = getMigrationFiles(migrationsDir).map((f) => f.number).filter((n, i, arr) => arr.indexOf(n) === i).toSorted((a, b) => a - b).filter((n) => n > currentMigration).map((num) => {
5662
5686
  const diffPath = getMigrationFilePath(migrationsDir, num, "diff");
5663
5687
  let description;
@@ -5670,23 +5694,28 @@ async function collectMigrationStatuses(options) {
5670
5694
  ...description ? { description } : {}
5671
5695
  };
5672
5696
  });
5673
- statuses.push({
5697
+ rows.push({
5698
+ status: "ok",
5674
5699
  namespace,
5675
5700
  currentMigration,
5676
5701
  currentMigrationLabel: formatMigrationNumber(currentMigration),
5677
5702
  pendingMigrations
5678
5703
  });
5679
5704
  }
5680
- return statuses;
5705
+ return rows;
5681
5706
  }
5682
- function printMigrationStatuses(statuses) {
5683
- for (const statusInfo of statuses) {
5707
+ function printMigrationStatuses(rows) {
5708
+ for (const row of rows) {
5684
5709
  logger.newline();
5685
- logger.info(`Namespace: ${styles.bold(statusInfo.namespace)}`);
5686
- logger.log(` Current migration: ${styles.bold(statusInfo.currentMigrationLabel)}`);
5687
- if (statusInfo.pendingMigrations.length > 0) {
5710
+ logger.info(`Namespace: ${styles.bold(row.namespace)}`);
5711
+ if (isStatusFailure(row)) {
5712
+ logger.error(` Failed to read migration state: ${row.error}`);
5713
+ continue;
5714
+ }
5715
+ logger.log(` Current migration: ${styles.bold(row.currentMigrationLabel)}`);
5716
+ if (row.pendingMigrations.length > 0) {
5688
5717
  logger.log(" Pending migrations:");
5689
- for (const pending of statusInfo.pendingMigrations) if (pending.description) logger.log(` - ${pending.label}: ${pending.description}`);
5718
+ for (const pending of row.pendingMigrations) if (pending.description) logger.log(` - ${pending.label}: ${pending.description}`);
5690
5719
  else logger.log(` - ${pending.label}`);
5691
5720
  } else logger.log(" Pending migrations: (none)");
5692
5721
  }
@@ -5698,16 +5727,19 @@ function printMigrationStatuses(statuses) {
5698
5727
  */
5699
5728
  async function status(options) {
5700
5729
  logBetaWarning("tailordb migration");
5701
- const statuses = await collectMigrationStatuses(options);
5702
- if (options.json) {
5703
- logger.out(statuses);
5704
- return;
5730
+ const rows = await collectMigrationStatuses(options);
5731
+ if (options.json) logger.out(rows);
5732
+ else printMigrationStatuses(rows);
5733
+ const failures = rows.filter(isStatusFailure);
5734
+ if (failures.length > 0) {
5735
+ const namespaces = failures.map((f) => f.namespace).join(", ");
5736
+ throw new Error(`Failed to read migration state for ${failures.length} namespace${failures.length === 1 ? "" : "s"}: ${namespaces}`);
5705
5737
  }
5706
- printMigrationStatuses(statuses);
5707
5738
  }
5708
5739
  const statusCommand = defineAppCommand({
5709
5740
  name: "status",
5710
5741
  description: "Show the current migration status for TailorDB namespaces, including applied and pending migrations.",
5742
+ notes: "Metadata lookup failures (authentication, permission, or network errors) are reported per namespace and make the command exit non-zero; only a not-yet-deployed namespace is treated as having no applied migrations.",
5711
5743
  args: z.strictObject({
5712
5744
  ...deploymentArgs,
5713
5745
  namespace: arg(z.string().optional(), {
@@ -5776,7 +5808,7 @@ async function fetchRemoteTypes(client, workspaceId, namespace) {
5776
5808
  async function assertMigrationsReproduceLocalTypes(loaded, target) {
5777
5809
  const { config, plugins } = loaded;
5778
5810
  const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
5779
- const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-DArFTTJ9.mjs");
5811
+ const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-CXNaUNhv.mjs");
5780
5812
  const application = defineApplication({
5781
5813
  config,
5782
5814
  pluginManager
@@ -5788,7 +5820,7 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
5788
5820
  await service.processNamespacePlugins();
5789
5821
  }
5790
5822
  const pluginExecutorFiles = generatePluginFilesIfNeeded(pluginManager, application.tailorDBServices, config.path);
5791
- const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-D5jn05ZI.mjs")).createExecutorService({
5823
+ const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-M7OYziQP.mjs")).createExecutorService({
5792
5824
  config: { files: [] },
5793
5825
  baseDir: path.dirname(config.path)
5794
5826
  }) : void 0);
@@ -5814,39 +5846,6 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
5814
5846
  throw new Error("Refusing to sync: the migration history must reproduce the current local schema before it can be applied to the remote.");
5815
5847
  }
5816
5848
  /**
5817
- * Fetch the namespace's metadata labels and current migration number.
5818
- *
5819
- * Only GetMetadata NotFound is treated as "metadata does not exist yet".
5820
- * Any other failure aborts the sync (which has not mutated anything at this
5821
- * point): the fetched labels are written back verbatim at the end, so
5822
- * proceeding with empty labels after a transient error would wipe the
5823
- * namespace's existing metadata.
5824
- * @param client - Operator client
5825
- * @param trn - Namespace TRN
5826
- * @returns Existing labels and the parsed current migration number
5827
- */
5828
- async function fetchRemoteMigrationState(client, trn) {
5829
- const labels = (await getOrNull(async () => {
5830
- const { metadata } = await client.getMetadata({ trn });
5831
- return metadata;
5832
- }))?.labels ?? {};
5833
- const label = labels[MIGRATION_LABEL_KEY];
5834
- return {
5835
- labels,
5836
- current: label ? parseMigrationLabelNumber(label) : null
5837
- };
5838
- }
5839
- function selectTargetNamespace(namespacesWithMigrations, requested) {
5840
- if (namespacesWithMigrations.length === 0) throw new Error("No TailorDB services with migrations configuration found");
5841
- if (requested) {
5842
- const found = namespacesWithMigrations.find((ns) => ns.namespace === requested);
5843
- if (!found) throw new Error(`Namespace "${requested}" not found or does not have migrations configured`);
5844
- return found;
5845
- }
5846
- if (namespacesWithMigrations.length > 1) throw new Error(`Multiple TailorDB services found. Please specify namespace with --namespace flag: ${namespacesWithMigrations.map((ns) => ns.namespace).join(", ")}`);
5847
- return assertDefined(namespacesWithMigrations[0], "namespace with migrations missing");
5848
- }
5849
- /**
5850
5849
  * Sync remote TailorDB schema to a specific migration snapshot.
5851
5850
  *
5852
5851
  * Reconstructs the schema state at `<number>` from `0000/schema.json` + diffs,
@@ -5868,8 +5867,7 @@ async function sync(options) {
5868
5867
  const { config } = loaded;
5869
5868
  const target = selectTargetNamespace(getNamespacesWithMigrations(config, path.dirname(config.path)), options.namespace);
5870
5869
  assertValidMigrationFiles(target.migrationsDir, target.namespace);
5871
- const latest = getLatestMigrationNumber(target.migrationsDir);
5872
- if (targetVersion > latest) throw new Error(`Migration ${formatMigrationNumber(targetVersion)} does not exist in working tree (latest is ${formatMigrationNumber(latest)}).`);
5870
+ const latest = assertMigrationNumberExists(target.migrationsDir, targetVersion);
5873
5871
  const snapshot = reconstructSnapshotFromMigrations(target.migrationsDir, targetVersion);
5874
5872
  if (!snapshot) throw new Error(`No initial schema snapshot found in ${target.migrationsDir}. Expected 0000/schema.json.`);
5875
5873
  const manifestOptions = await assertMigrationsReproduceLocalTypes(loaded, target);
@@ -5879,7 +5877,7 @@ async function sync(options) {
5879
5877
  profile: options.profile
5880
5878
  });
5881
5879
  const trn = resourceTrn(workspaceId, "tailordb", target.namespace);
5882
- const remoteState = await fetchRemoteMigrationState(client, trn);
5880
+ const current = await fetchRemoteMigrationNumber(client, trn);
5883
5881
  const remoteTypes = await fetchRemoteTypes(client, workspaceId, target.namespace);
5884
5882
  const { creates, updates, deletes } = compareSnapshotWithRemote(snapshot, new Set(remoteTypes.map((t) => t.name)));
5885
5883
  const remoteGqlPermissions = await fetchRemoteGqlPermissions(client, workspaceId, target.namespace);
@@ -5890,7 +5888,6 @@ async function sync(options) {
5890
5888
  }] : []);
5891
5889
  const desiredGqlPermissionTypes = new Set(desiredGqlPermissions.map((p) => p.typeName));
5892
5890
  const gqlPermissionDeletes = remoteGqlPermissions.filter((p) => !desiredGqlPermissionTypes.has(p.typeName));
5893
- const current = remoteState.current;
5894
5891
  logger.newline();
5895
5892
  logger.info(`Namespace: ${styles.bold(target.namespace)}`);
5896
5893
  logger.log(` Current migration: ${current === null ? "<unset>" : styles.bold(formatMigrationNumber(current))}`);
@@ -5966,12 +5963,9 @@ async function sync(options) {
5966
5963
  namespaceName: target.namespace,
5967
5964
  tailordbTypeName: typeName
5968
5965
  })));
5969
- await client.setMetadata({
5966
+ await writeMetadataLabels(client, {
5970
5967
  trn,
5971
- labels: {
5972
- ...remoteState.labels,
5973
- [MIGRATION_LABEL_KEY]: `${"m"}${formatMigrationNumber(targetVersion)}`
5974
- }
5968
+ labels: { [MIGRATION_LABEL_KEY]: sanitizeMigrationLabel(targetVersion) }
5975
5969
  });
5976
5970
  logger.success(`Synced namespace ${styles.bold(target.namespace)} to migration ${styles.bold(formatMigrationNumber(targetVersion))}.`);
5977
5971
  if (targetVersion < latest) {
@@ -6007,6 +6001,204 @@ const syncCommand = defineAppCommand({
6007
6001
  }
6008
6002
  });
6009
6003
 
6004
+ //#endregion
6005
+ //#region src/cli/commands/tailordb/migrate/validate.ts
6006
+ /**
6007
+ * Assert that every migration in the local history whose diff requires a data
6008
+ * migration script has a migrate.ts on disk or an explicit skip acknowledgment
6009
+ * @param {string} migrationsDir - Migrations directory path
6010
+ * @param {string} namespace - TailorDB namespace (for error messages)
6011
+ */
6012
+ function assertRequiredMigrationScripts(migrationsDir, namespace) {
6013
+ const missing = [];
6014
+ for (const file of getMigrationFiles(migrationsDir)) {
6015
+ if (file.type !== "diff") continue;
6016
+ const diff = loadDiff(file.path);
6017
+ if (!diff.requiresMigrationScript || diff.scriptSkipped) continue;
6018
+ if (!fs.existsSync(getMigrationFilePath(migrationsDir, file.number, "migrate"))) missing.push(file.number);
6019
+ }
6020
+ 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 "..."'.`);
6021
+ }
6022
+ /**
6023
+ * Build per-namespace reports from the checks that completed
6024
+ * @param {BuildValidationReportsOptions} options - Completed validation results
6025
+ * @returns {NamespaceValidationReport[]} Validation reports per namespace
6026
+ */
6027
+ function buildValidationReports(options) {
6028
+ const { targetNamespaces, migrationFileErrors, localResults, remoteResults } = options;
6029
+ return targetNamespaces.map((target) => {
6030
+ const fileError = migrationFileErrors.get(target.namespace);
6031
+ if (fileError !== void 0) return {
6032
+ namespace: target.namespace,
6033
+ valid: false,
6034
+ migrationFiles: {
6035
+ valid: false,
6036
+ error: fileError
6037
+ }
6038
+ };
6039
+ const local = assertDefined(localResults.find((result) => result.namespace === target.namespace), `local schema check result missing for namespace "${target.namespace}"`);
6040
+ const localSchema = {
6041
+ hasDiff: local.hasDiff,
6042
+ ...local.diff ? { diff: local.diff } : {}
6043
+ };
6044
+ if (remoteResults === void 0) return {
6045
+ namespace: target.namespace,
6046
+ valid: false,
6047
+ migrationFiles: { valid: true },
6048
+ localSchema,
6049
+ remoteSchema: { skipped: "check_failed" }
6050
+ };
6051
+ const remote = assertDefined(remoteResults.find((result) => result.namespace === target.namespace), `remote schema check result missing for namespace "${target.namespace}"`);
6052
+ const checkpointMissingLocal = remote.checkpointMissingLocal === true;
6053
+ const remoteSchema = {
6054
+ remoteMigrationNumber: remote.remoteMigrationNumber,
6055
+ hasDrift: remote.hasDrift,
6056
+ drifts: remote.drifts,
6057
+ ...remote.skipped ? { skipped: remote.skipped } : {},
6058
+ ...checkpointMissingLocal ? { checkpointMissingLocal: true } : {}
6059
+ };
6060
+ return {
6061
+ namespace: target.namespace,
6062
+ valid: !localSchema.hasDiff && !remoteSchema.hasDrift && !checkpointMissingLocal,
6063
+ migrationFiles: { valid: true },
6064
+ localSchema,
6065
+ remoteSchema
6066
+ };
6067
+ });
6068
+ }
6069
+ /**
6070
+ * Run all migration validation checks and collect per-namespace reports
6071
+ * @param {ValidateOptions} options - Command options
6072
+ * @returns {Promise<CollectedValidationReports>} Validation reports and any remote error
6073
+ */
6074
+ async function collectValidationReports(options) {
6075
+ const { config, plugins } = await loadConfig(options.configPath);
6076
+ const namespacesWithMigrations = getNamespacesWithMigrations(config, path.dirname(config.path));
6077
+ if (namespacesWithMigrations.length === 0) throw new Error("No TailorDB services with migrations configuration found");
6078
+ const targetNamespaces = options.namespace ? namespacesWithMigrations.filter((ns) => ns.namespace === options.namespace) : namespacesWithMigrations;
6079
+ if (targetNamespaces.length === 0) throw new Error(`Namespace "${options.namespace}" not found or does not have migrations configured`);
6080
+ const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
6081
+ const { defineApplication } = await import("../application-CXNaUNhv.mjs");
6082
+ const application = defineApplication({
6083
+ config,
6084
+ pluginManager
6085
+ });
6086
+ for (const service of application.tailorDBServices) {
6087
+ await service.loadTypes();
6088
+ await service.processNamespacePlugins();
6089
+ }
6090
+ assertUniqueLocalTailorDBTypeNames({ tailorDBServices: application.tailorDBServices });
6091
+ for (const { namespace } of targetNamespaces) if (!application.tailorDBServices.some((s) => s.namespace === namespace)) throw new Error(`No TailorDB service found for namespace "${namespace}"`);
6092
+ const tailorDBInputs = application.tailorDBServices.map(toTailorDBDeployInput);
6093
+ const typesByNamespace = new Map(tailorDBInputs.map((input) => [input.namespace, input.types]));
6094
+ const migrationFileErrors = /* @__PURE__ */ new Map();
6095
+ const checkableNamespaces = [];
6096
+ for (const target of targetNamespaces) try {
6097
+ assertValidMigrationFiles(target.migrationsDir, target.namespace);
6098
+ reconstructSnapshotFromMigrations(target.migrationsDir);
6099
+ assertRequiredMigrationScripts(target.migrationsDir, target.namespace);
6100
+ checkableNamespaces.push(target);
6101
+ } catch (error) {
6102
+ migrationFileErrors.set(target.namespace, error instanceof Error ? error.message : String(error));
6103
+ }
6104
+ const localReportOptions = {
6105
+ targetNamespaces,
6106
+ migrationFileErrors,
6107
+ localResults: await checkMigrationDiffs(typesByNamespace, checkableNamespaces)
6108
+ };
6109
+ if (checkableNamespaces.length === 0) return { reports: buildValidationReports(localReportOptions) };
6110
+ let remoteResults;
6111
+ try {
6112
+ remoteResults = await verifyRemoteSchema(await initOperatorClient(await loadAccessToken({ profile: options.profile })), await loadWorkspaceId({
6113
+ workspaceId: options.workspaceId,
6114
+ profile: options.profile
6115
+ }), checkableNamespaces, config, tailorDBInputs);
6116
+ } catch (remoteError) {
6117
+ return {
6118
+ reports: buildValidationReports(localReportOptions),
6119
+ remoteError
6120
+ };
6121
+ }
6122
+ return { reports: buildValidationReports({
6123
+ ...localReportOptions,
6124
+ remoteResults
6125
+ }) };
6126
+ }
6127
+ function printValidationReports(reports) {
6128
+ for (const report of reports) {
6129
+ logger.newline();
6130
+ logger.info(`Namespace: ${styles.bold(report.namespace)}`);
6131
+ if (!report.migrationFiles.valid) {
6132
+ logger.log(` Migration files: ${styles.error("invalid")}`);
6133
+ logger.log(` ${report.migrationFiles.error}`);
6134
+ continue;
6135
+ }
6136
+ logger.log(` Migration files: ${styles.success("OK")}`);
6137
+ const local = report.localSchema;
6138
+ if (local?.hasDiff) if (local.diff) {
6139
+ logger.log(` Local schema: ${styles.error("changes not in migration files")}`);
6140
+ logger.log(` ${formatDiffSummary(local.diff)}`);
6141
+ logger.log(formatMigrationDiff(local.diff));
6142
+ } else logger.log(` Local schema: ${styles.error("no migration snapshot found")}`);
6143
+ else logger.log(` Local schema: ${styles.success("OK")}`);
6144
+ const remote = report.remoteSchema;
6145
+ if (remote?.skipped === "check_failed") logger.log(` Remote schema: ${styles.error("not checked")}`);
6146
+ else if (remote?.checkpointMissingLocal) logger.log(` Remote schema: ${styles.error(`remote migration ${formatMigrationNumber(remote.remoteMigrationNumber)} is not in the local migration history`)}`);
6147
+ else if (remote?.hasDrift) {
6148
+ logger.log(` Remote schema: ${styles.error("drift detected")} (remote migration: ${formatMigrationNumber(remote.remoteMigrationNumber)})`);
6149
+ logger.log(formatSchemaDrifts(remote.drifts));
6150
+ } else if (remote?.skipped === "not_deployed") logger.log(` Remote schema: ${styles.dim("skipped (namespace not deployed)")}`);
6151
+ else if (remote?.skipped === "no_migration_label") logger.log(` Remote schema: ${styles.dim("skipped (deployed namespace has no migration state)")}`);
6152
+ else if (remote?.skipped === "no_snapshot") logger.log(` Remote schema: ${styles.dim(`skipped (no local snapshot for remote migration ${formatMigrationNumber(remote.remoteMigrationNumber)})`)}`);
6153
+ else if (remote) logger.log(` Remote schema: ${styles.success("OK")} (remote migration: ${formatMigrationNumber(remote.remoteMigrationNumber)})`);
6154
+ }
6155
+ logger.newline();
6156
+ }
6157
+ function printResolutionHints(reports) {
6158
+ if (reports.some((r) => r.localSchema?.hasDiff && !r.localSchema.diff)) logger.info("Run 'tailor tailordb migration generate' to create the initial snapshot.");
6159
+ if (reports.some((r) => r.localSchema?.diff)) logger.info("Run 'tailor tailordb migration generate' to create migration files.");
6160
+ if (reports.some((r) => r.remoteSchema?.checkpointMissingLocal)) logger.info("The remote migration checkpoint is ahead of the local history. Pull the latest migration files, or run 'tailor tailordb migration status' to compare.");
6161
+ if (reports.some((r) => r.remoteSchema?.hasDrift)) logRemoteDriftGuidance();
6162
+ }
6163
+ /**
6164
+ * Validate migration files and schema state without deploying
6165
+ * @param {ValidateOptions} options - Command options
6166
+ */
6167
+ async function validate(options) {
6168
+ logBetaWarning("tailordb migration");
6169
+ const collected = await collectValidationReports(options);
6170
+ const { reports } = collected;
6171
+ const invalidCount = reports.filter((r) => !r.valid).length;
6172
+ if (options.json) logger.out(reports);
6173
+ else {
6174
+ printValidationReports(reports);
6175
+ if (invalidCount === 0 && !("remoteError" in collected)) logger.success("All migration validation checks passed.");
6176
+ else printResolutionHints(reports);
6177
+ }
6178
+ if ("remoteError" in collected) throw collected.remoteError;
6179
+ if (invalidCount > 0) throw new Error(`Migration validation failed for ${invalidCount} namespace(s)`);
6180
+ }
6181
+ const validateCommand = defineAppCommand({
6182
+ name: "validate",
6183
+ description: "Validate the full migration history and detect schema drift (local types vs. migration snapshot, remote schema vs. migration checkpoint) without deploying. This includes the migration and schema-drift checks used by 'deploy' and exits with a non-zero code when issues are found.",
6184
+ args: z.strictObject({
6185
+ ...deploymentArgs,
6186
+ namespace: arg(z.string().optional(), {
6187
+ alias: "n",
6188
+ description: "Target TailorDB namespace (validates all namespaces if not specified)"
6189
+ })
6190
+ }),
6191
+ run: async (args) => {
6192
+ await validate({
6193
+ configPath: args.config,
6194
+ namespace: args.namespace,
6195
+ workspaceId: args["workspace-id"],
6196
+ profile: args.profile,
6197
+ json: logger.jsonMode
6198
+ });
6199
+ }
6200
+ });
6201
+
6010
6202
  //#endregion
6011
6203
  //#region src/cli/commands/tailordb/migrate/index.ts
6012
6204
  /**
@@ -6018,6 +6210,7 @@ const syncCommand = defineAppCommand({
6018
6210
  * - set: Set migration checkpoint to a specific number
6019
6211
  * - status: Show migration status for TailorDB namespaces
6020
6212
  * - sync: Sync remote TailorDB schema to a specific migration snapshot
6213
+ * - validate: Validate migration files and detect schema drift without deploying
6021
6214
  */
6022
6215
  const migrationCommand = defineCommand({
6023
6216
  name: "migration",
@@ -6027,7 +6220,8 @@ const migrationCommand = defineCommand({
6027
6220
  script: scriptCommand,
6028
6221
  set: setCommand,
6029
6222
  status: statusCommand,
6030
- sync: syncCommand
6223
+ sync: syncCommand,
6224
+ validate: validateCommand
6031
6225
  }
6032
6226
  });
6033
6227