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