@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.
- package/CHANGELOG.md +165 -0
- package/dist/{application-miwiXtB4.mjs → application-CM6hKnSK.mjs} +312 -89
- package/dist/application-CM6hKnSK.mjs.map +1 -0
- package/dist/application-CXNaUNhv.mjs +3 -0
- package/dist/cli/commands/deploy/aigateway.d.mts +1 -1
- package/dist/cli/commands/deploy/application.d.mts +1 -1
- package/dist/cli/commands/deploy/auth.d.mts +1 -1
- package/dist/cli/commands/deploy/deploy.d.mts +1 -0
- package/dist/cli/commands/deploy/executor.d.mts +1 -1
- package/dist/cli/commands/deploy/function-registry.d.mts +1 -2
- package/dist/cli/commands/deploy/idp.d.mts +1 -1
- package/dist/cli/commands/deploy/label.d.mts +2 -0
- package/dist/cli/commands/deploy/resolver.d.mts +1 -1
- package/dist/cli/commands/deploy/staticwebsite.d.mts +1 -1
- package/dist/cli/commands/deploy/tailordb/index.d.mts +2 -1
- package/dist/cli/commands/deploy/types.d.mts +2 -1
- package/dist/cli/commands/deploy/workflow-execution-policy.d.mts +1 -2
- package/dist/cli/commands/deploy/workflow.d.mts +1 -1
- package/dist/cli/commands/executor/get.d.mts +1 -10
- package/dist/cli/commands/executor/jobs.d.mts +1 -38
- package/dist/cli/commands/executor/trigger.d.mts +1 -11
- package/dist/cli/commands/generate/seed/bundler.d.mts +2 -1
- package/dist/cli/commands/tailordb/migrate/schema-checks.d.mts +5 -0
- package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +1 -1
- package/dist/cli/commands/workflow/executions.d.mts +1 -13
- package/dist/cli/commands/workflow/get.d.mts +1 -10
- package/dist/cli/commands/workflow/start.d.mts +2 -14
- package/dist/cli/lib.d.mts +7 -11
- package/dist/cli/lib.mjs +99 -42
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/main.mjs +318 -124
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/services/http-adapter/bundler.d.mts +1 -0
- package/dist/cli/services/workflow/bundler.d.mts +2 -1
- package/dist/cli/shared/client.d.mts +28 -3
- package/dist/cli/shared/config-loader.d.mts +11 -4
- package/dist/cli/shared/tsconfig-paths-plugin.d.mts +2 -0
- package/dist/cli/shared/type-generator.d.mts +4 -3
- package/dist/completion/zsh-worker.zsh +82 -22
- package/dist/configure/config/types.d.mts +26 -3
- package/dist/configure/index.d.mts +2 -2
- package/dist/configure/index.mjs +171 -3
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/executor/index.d.mts +2 -2
- package/dist/configure/services/executor/trigger/event.d.mts +245 -2
- package/dist/configure/services/executor/trigger/index.d.mts +3 -3
- package/dist/configure/services/idp/index.d.mts +1 -1
- package/dist/configure/services/index.d.mts +2 -2
- package/dist/configure/services/tailordb/index.d.mts +0 -1
- package/dist/configure/services/workflow/execution-policy.d.mts +1 -1
- package/dist/configure/services/workflow/execution-policy.types.d.mts +1 -1
- package/dist/configure/services/workflow/job.d.mts +11 -2
- package/dist/configure/services/workflow/workflow.d.mts +10 -0
- package/dist/{globals-DQotNuwK.mjs → globals-TfAVItuK.mjs} +1 -2
- package/dist/globals-TfAVItuK.mjs.map +1 -0
- package/dist/node-builtins-CmaL2Cbq.mjs +36 -0
- package/dist/node-builtins-CmaL2Cbq.mjs.map +1 -0
- package/dist/plugin/builtin/enum-constants/index.mjs +115 -2
- package/dist/plugin/builtin/enum-constants/index.mjs.map +1 -0
- package/dist/plugin/builtin/file-utils/index.mjs +149 -2
- package/dist/plugin/builtin/file-utils/index.mjs.map +1 -0
- package/dist/plugin/builtin/kysely-type/index.mjs +274 -2
- package/dist/plugin/builtin/kysely-type/index.mjs.map +1 -0
- package/dist/plugin/builtin/seed/index.mjs +1 -1
- package/dist/plugin/builtin/seed/seed-type-processor.d.mts +2 -0
- package/dist/{register-ts-hook-BAHMwxfG.mjs → register-ts-hook-LYV7zH-e.mjs} +2252 -797
- package/dist/register-ts-hook-LYV7zH-e.mjs.map +1 -0
- package/dist/registry-BIGVUrMB.mjs.map +1 -1
- package/dist/runtime/index.mjs +1 -1
- package/dist/runtime/workflow.d.mts +2 -20
- package/dist/runtime/workflow.mjs +1 -1
- package/dist/{seed-LUVr7jHL.mjs → seed-B-yckgQY.mjs} +42 -3
- package/dist/seed-B-yckgQY.mjs.map +1 -0
- package/dist/service-M7OYziQP.mjs +3 -0
- package/dist/{service-BNtPsK9Q.mjs → service-k1xy_A5G.mjs} +29 -2
- package/dist/{service-BNtPsK9Q.mjs.map → service-k1xy_A5G.mjs.map} +1 -1
- package/dist/{service_pb-BwZRiNug.mjs → service_pb-D5--My_M.mjs} +5 -5
- package/dist/service_pb-D5--My_M.mjs.map +1 -0
- package/dist/{service_pb-wRkxUta6.mjs → service_pb-i8CmnQP4.mjs} +1 -1
- package/dist/tailor-proto/src/tailor/v1/application_pb.d.mts +106 -1
- package/dist/tailor-proto/src/tailor/v1/executor_resource_pb.d.mts +24 -1
- package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +49 -1
- package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +89 -6
- package/dist/tailor-proto/src/tailor/v1/telemetryrouter_resource_pb.d.mts +26 -1
- package/dist/tailor-proto/src/tailor/v1/workflow_pb.d.mts +101 -1
- package/dist/tailor-proto/src/tailor/v1/workflow_resource_pb.d.mts +22 -1
- package/dist/types/executor.generated.d.mts +23 -3
- package/dist/types/idp.generated.d.mts +1 -1
- package/dist/utils/test/index.d.mts +1 -2
- package/dist/utils/test/index.mjs +1 -173
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/vitest/environment.mjs +1 -1
- package/dist/vitest/index.d.mts +2 -2
- package/dist/vitest/index.mjs +10 -52
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mocks/workflow.d.mts +0 -9
- package/dist/vitest/setup.mjs +1 -1
- package/dist/{workflow-CsaQ9qK-.mjs → workflow-Bamae_Yc.mjs} +1 -9
- package/dist/workflow-Bamae_Yc.mjs.map +1 -0
- package/dist/{workspace_resource_pb-UGK1SSn_.mjs → workspace_resource_pb-DXnO2AT-.mjs} +7 -7
- package/dist/workspace_resource_pb-DXnO2AT-.mjs.map +1 -0
- package/docs/cli/tailordb.md +39 -7
- package/docs/cli-reference.md +11 -10
- package/docs/configuration.md +50 -0
- package/docs/migration/v2.md +306 -9
- package/docs/runtime.md +1 -1
- package/docs/services/executor.md +83 -3
- package/docs/services/idp.md +8 -6
- package/docs/services/resolver.md +6 -4
- package/docs/services/tailordb-migration.md +17 -6
- package/docs/services/tailordb.md +6 -4
- package/docs/services/workflow.md +33 -3
- package/package.json +19 -17
- package/dist/application-DArFTTJ9.mjs +0 -3
- package/dist/application-miwiXtB4.mjs.map +0 -1
- package/dist/enum-constants-j9QBF0cB.mjs +0 -116
- package/dist/enum-constants-j9QBF0cB.mjs.map +0 -1
- package/dist/file-utils-yEiIS4nJ.mjs +0 -150
- package/dist/file-utils-yEiIS4nJ.mjs.map +0 -1
- package/dist/globals-DQotNuwK.mjs.map +0 -1
- package/dist/kysely-type-DqIRgDYR.mjs +0 -275
- package/dist/kysely-type-DqIRgDYR.mjs.map +0 -1
- package/dist/register-ts-hook-BAHMwxfG.mjs.map +0 -1
- package/dist/seed-LUVr7jHL.mjs.map +0 -1
- package/dist/service-D5jn05ZI.mjs +0 -3
- package/dist/service_pb-BwZRiNug.mjs.map +0 -1
- package/dist/utils/test/mock.d.mts +0 -86
- package/dist/workflow-CsaQ9qK-.mjs.map +0 -1
- 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-
|
|
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
|
|
7
|
-
import {
|
|
8
|
-
import { i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-
|
|
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-
|
|
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:
|
|
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
|
-
*
|
|
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
|
|
3388
|
-
|
|
3389
|
-
await client.setMetadata({
|
|
3400
|
+
const { client, trn } = params;
|
|
3401
|
+
await writeMetadataLabels(client, {
|
|
3390
3402
|
trn,
|
|
3391
|
-
|
|
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
|
|
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
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
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
|
-
|
|
5568
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
5670
|
+
const rows = [];
|
|
5651
5671
|
for (const { namespace, migrationsDir } of targetNamespaces) {
|
|
5652
5672
|
const trn = resourceTrn(workspaceId, "tailordb", namespace);
|
|
5653
|
-
let
|
|
5673
|
+
let current;
|
|
5654
5674
|
try {
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
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
|
-
|
|
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
|
|
5705
|
+
return rows;
|
|
5681
5706
|
}
|
|
5682
|
-
function printMigrationStatuses(
|
|
5683
|
-
for (const
|
|
5707
|
+
function printMigrationStatuses(rows) {
|
|
5708
|
+
for (const row of rows) {
|
|
5684
5709
|
logger.newline();
|
|
5685
|
-
logger.info(`Namespace: ${styles.bold(
|
|
5686
|
-
|
|
5687
|
-
|
|
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
|
|
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
|
|
5702
|
-
if (options.json)
|
|
5703
|
-
|
|
5704
|
-
|
|
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-
|
|
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-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|