@tailor-platform/sdk 2.0.0-next.5 → 2.0.0-next.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/dist/{application-Bf8wxOi6.mjs → application-GzW98_Xr.mjs} +377 -1418
  3. package/dist/application-GzW98_Xr.mjs.map +1 -0
  4. package/dist/application-OM0taSPn.mjs +3 -0
  5. package/dist/cli/commands/deploy/deploy.d.mts +8 -3
  6. package/dist/cli/commands/generate/options.d.mts +0 -1
  7. package/dist/cli/commands/generate/service.d.mts +1 -1
  8. package/dist/cli/commands/staticwebsite/deploy.d.mts +19 -0
  9. package/dist/cli/commands/workspace/create.d.mts +3 -0
  10. package/dist/cli/commands/workspace/list.d.mts +1 -0
  11. package/dist/cli/commands/workspace/transform.d.mts +2 -0
  12. package/dist/cli/index.mjs +781 -1553
  13. package/dist/cli/index.mjs.map +1 -1
  14. package/dist/cli/lib.d.mts +7 -3
  15. package/dist/cli/lib.mjs +49 -3
  16. package/dist/cli/lib.mjs.map +1 -1
  17. package/dist/cli/shared/config-loader.d.mts +1 -1
  18. package/dist/cli/shared/readonly-guard.d.mts +25 -0
  19. package/dist/cli/shared/tailordb-namespaces.d.mts +37 -0
  20. package/dist/cli/shared/workspace-name.d.mts +1 -0
  21. package/dist/completion/zsh-worker.zsh +15 -303
  22. package/dist/configure/config/types.d.mts +5 -1
  23. package/dist/configure/index.mjs +20 -19
  24. package/dist/configure/index.mjs.map +1 -1
  25. package/dist/configure/services/auth/types.d.mts +18 -23
  26. package/dist/configure/services/executor/executor.d.mts +2 -13
  27. package/dist/configure/services/executor/operation.d.mts +8 -3
  28. package/dist/configure/services/tailordb/schema.d.mts +1 -7
  29. package/dist/configure/services/tailordb/types.d.mts +1 -1
  30. package/dist/configure/services/workflow/execution-policy.d.mts +2 -2
  31. package/dist/configure/services/workflow/job.d.mts +10 -10
  32. package/dist/configure/services/workflow/workflow.d.mts +6 -4
  33. package/dist/configure/types/type.d.mts +1 -9
  34. package/dist/{crashreport-D3RjuSYb.mjs → crashreport-0ioNRZ4K.mjs} +1 -1
  35. package/dist/{crashreport-Cg-pqRJV.mjs → crashreport-CxHne11E.mjs} +4 -3
  36. package/dist/{crashreport-Cg-pqRJV.mjs.map → crashreport-CxHne11E.mjs.map} +1 -1
  37. package/dist/errors-DfnF4ibq.mjs +100 -0
  38. package/dist/errors-DfnF4ibq.mjs.map +1 -0
  39. package/dist/{globals-BjhJJ2dN.mjs → globals-CBZ0egXT.mjs} +13 -10
  40. package/dist/globals-CBZ0egXT.mjs.map +1 -0
  41. package/dist/{logger-BEiZZ3qT.mjs → logger-BwS4ppwO.mjs} +3 -3
  42. package/dist/logger-BwS4ppwO.mjs.map +1 -0
  43. package/dist/parser/service/tailordb/type-source.d.mts +10 -0
  44. package/dist/{register-ts-hook-BhzVkej0.mjs → register-ts-hook-DvEs6YsL.mjs} +2571 -1636
  45. package/dist/register-ts-hook-DvEs6YsL.mjs.map +1 -0
  46. package/dist/{registry-BSbq6SPo.mjs → registry-i7EdJ-D5.mjs} +10 -10
  47. package/dist/registry-i7EdJ-D5.mjs.map +1 -0
  48. package/dist/runtime/field-parse.d.mts +10 -0
  49. package/dist/runtime/globals.d.mts +3 -3
  50. package/dist/runtime/index.mjs +1 -1
  51. package/dist/runtime/workflow.d.mts +18 -33
  52. package/dist/runtime/workflow.mjs +1 -1
  53. package/dist/{schema-iCd1ugGp.mjs → schema--xYWRGfe.mjs} +42 -24
  54. package/dist/schema--xYWRGfe.mjs.map +1 -0
  55. package/dist/{secret-file-aoNXJmKa.mjs → secret-file-C3J4Gy8r.mjs} +2 -27
  56. package/dist/secret-file-C3J4Gy8r.mjs.map +1 -0
  57. package/dist/{secretmanager-Bd45j7an.mjs → secretmanager-IY4UvinW.mjs} +42 -9
  58. package/dist/secretmanager-IY4UvinW.mjs.map +1 -0
  59. package/dist/service-BJAQ70e5.mjs +3 -0
  60. package/dist/{service-DeZeYa06.mjs → service-DaeJlBoH.mjs} +3 -3
  61. package/dist/{service-DeZeYa06.mjs.map → service-DaeJlBoH.mjs.map} +1 -1
  62. package/dist/{service-CzZEVrp9.mjs → service-Dmxa2I4i.mjs} +64 -28
  63. package/dist/service-Dmxa2I4i.mjs.map +1 -0
  64. package/dist/service_pb-BoLM0X4K.mjs +167 -0
  65. package/dist/service_pb-BoLM0X4K.mjs.map +1 -0
  66. package/dist/{service_pb-DZkfjB_6.mjs → service_pb-r1T7kpJc.mjs} +1 -1
  67. package/dist/types/executor.generated.d.mts +60 -42
  68. package/dist/user-agent-Bgsszb5I.mjs +28 -0
  69. package/dist/user-agent-Bgsszb5I.mjs.map +1 -0
  70. package/dist/utils/test/index.mjs +11 -11
  71. package/dist/utils/test/index.mjs.map +1 -1
  72. package/dist/utils/test/mock.d.mts +6 -6
  73. package/dist/vitest/environment.mjs +1 -1
  74. package/dist/vitest/index.d.mts +8 -8
  75. package/dist/vitest/index.mjs +613 -227
  76. package/dist/vitest/index.mjs.map +1 -1
  77. package/dist/vitest/mock.d.mts +8 -8
  78. package/dist/vitest/mocks/aigateway.d.mts +14 -4
  79. package/dist/vitest/mocks/authconnection.d.mts +14 -4
  80. package/dist/vitest/mocks/file.d.mts +23 -6
  81. package/dist/vitest/mocks/iconv.d.mts +28 -4
  82. package/dist/vitest/mocks/idp.d.mts +25 -9
  83. package/dist/vitest/mocks/secretmanager.d.mts +13 -4
  84. package/dist/vitest/mocks/tailordb.d.mts +48 -7
  85. package/dist/vitest/mocks/workflow.d.mts +59 -33
  86. package/dist/vitest/setup.mjs +2 -2
  87. package/dist/vitest/workflow-local.d.mts +2 -2
  88. package/dist/workflow-CsBvRE3g.mjs +34 -0
  89. package/dist/workflow-CsBvRE3g.mjs.map +1 -0
  90. package/dist/{service_pb-Bk7dL-yT.mjs → workspace_resource_pb-Db3fv68L.mjs} +47 -165
  91. package/dist/workspace_resource_pb-Db3fv68L.mjs.map +1 -0
  92. package/docs/cli/application.md +63 -15
  93. package/docs/cli/function.md +2 -2
  94. package/docs/cli/tailordb.md +8 -140
  95. package/docs/cli/workspace.md +6 -4
  96. package/docs/cli-reference.md +11 -8
  97. package/docs/configuration.md +2 -0
  98. package/docs/github-actions.md +8 -0
  99. package/docs/migration/v2.md +169 -11
  100. package/docs/runtime.md +5 -6
  101. package/docs/services/auth.md +7 -7
  102. package/docs/services/executor.md +5 -0
  103. package/docs/services/tailordb.md +44 -0
  104. package/docs/services/workflow.md +43 -43
  105. package/docs/testing.md +112 -96
  106. package/package.json +2 -5
  107. package/dist/application-Bf8wxOi6.mjs.map +0 -1
  108. package/dist/application-CUkjzfij.mjs +0 -3
  109. package/dist/cli/erd-viewer-assets/app.js +0 -1424
  110. package/dist/cli/erd-viewer-assets/index.html +0 -77
  111. package/dist/cli/erd-viewer-assets/serve.json +0 -13
  112. package/dist/cli/erd-viewer-assets/styles.css +0 -1036
  113. package/dist/configure/types/field-runtime.d.mts +0 -16
  114. package/dist/errors-118wUDRr.mjs +0 -42
  115. package/dist/errors-118wUDRr.mjs.map +0 -1
  116. package/dist/globals-BjhJJ2dN.mjs.map +0 -1
  117. package/dist/logger-BEiZZ3qT.mjs.map +0 -1
  118. package/dist/register-ts-hook-BhzVkej0.mjs.map +0 -1
  119. package/dist/registry-BSbq6SPo.mjs.map +0 -1
  120. package/dist/resource_pb-BRv7AMXK.mjs +0 -49
  121. package/dist/resource_pb-BRv7AMXK.mjs.map +0 -1
  122. package/dist/schema-iCd1ugGp.mjs.map +0 -1
  123. package/dist/secret-file-aoNXJmKa.mjs.map +0 -1
  124. package/dist/secretmanager-Bd45j7an.mjs.map +0 -1
  125. package/dist/service-BT0GPd01.mjs +0 -3
  126. package/dist/service-CzZEVrp9.mjs.map +0 -1
  127. package/dist/service_pb-Bk7dL-yT.mjs.map +0 -1
  128. package/dist/workflow-GNuSnsTx.mjs +0 -22
  129. package/dist/workflow-GNuSnsTx.mjs.map +0 -1
@@ -1,16 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import { r as parseInputFields } from "../schema--xYWRGfe.mjs";
3
+ import { St as AuthInvokerSchema, T as CustomDomainStatus, U as FunctionExecution_Type, kt as PATScope } from "../workspace_resource_pb-Db3fv68L.mjs";
2
4
  import { t as assertDefined } from "../assert-DBxo8jPo.mjs";
3
- import { n as logger, r as styles } from "../logger-BEiZZ3qT.mjs";
4
- import { $ as fetchUserInfo, A as hasAnyUserTokenEntry, B as readPlatformConfig, D as loadConfig, Et as workspaceArgs, F as loadMachineUserName, G as writePlatformConfig, H as resolveConfigUser, I as loadPlatformClientConfig, J as fetchAll, K as closeConnectionPool, L as loadStoredUserTokens, M as loadAccessToken, N as loadConfigPath, O as deleteUserTokens, P as loadConsoleBaseUrl, Q as fetchPlatformMachineUserToken, R as loadWorkspaceId, T as hashContent$1, Tt as toPageDirection, U as resolveUserTokenKey, V as removeLegacyUserAlias, W as saveUserTokens, Y as fetchAllTolerant, Z as fetchPaged, _ as platformBundleDefinePlugin, a as resolveInlineSourcemap, at as initOperatorClient, b as resolveBundleLogLevel, bt as pagedLogArgs, c as WorkflowJobSchema, d as INVOKER_EXPR, dt as confirmationArgs, ft as deploymentArgs, gt as multiConfigArg, ht as isVerbose, it as initOAuth2Client, j as hasUserTokenEntry, k as fetchLatestToken, l as ResolverSchema, lt as commonArgs, nt as getOrNull, ot as isDefaultPlatform, q as defaultPlatformBaseUrl, rt as getPlatformBaseUrl, t as defineApplication, tt as getOAuth2ClientId, ut as configArg, v as composeFunctionTreeshakeOptions, w as getDistDir, wt as resolveMachineUserInputSource, xt as paginationArgs, y as createLogLevelTreeshakeOptions, z as platformConfigFromProfile } from "../application-Bf8wxOi6.mjs";
5
- import { $t as compareSnapshotWithRemote, A as listCommand$13, An as apiCommand, C as listCommand$14, Cn as hasChanges, Dn as PluginManager, Dt as listCommand$7, E as waitCommand, En as sdkNameLabelKey, F as generateCommand$1, G as removeCommand$1, Gt as deploy, H as extractOwnedNamespaces, Ht as formatKeyValueTable, I as generateMigrationScript, It as getCommand$6, K as updateCommand$3, Kt as ensureConfigId, L as writeDbTypesFile, Mn as assertWritable, N as truncateCommand, Nn as defineAppCommand, O as resumeCommand, On as generateUserTypes, Pt as startCommand, Q as getCommand$5, Qt as parseMigrationLabelNumber, R as getConfiguredEditorCommand, Rt as executionsCommand, Sn as formatMigrationDiff, T as healthCommand, Tn as resourceTrn, Tt as triggerCommand, U as logBetaWarning, Ut as getCommand$1, V as showCommand, Vt as functionExecutionStatusToString, X as listCommand$12, Xt as MIGRATION_LABEL_KEY, Y as treeCommand, Zt as handleOptionalToRequiredError, _n as loadDiff, b as createCommand$4, bn as parseMigrationNumberArg, c as listCommand$15, cn as compareLocalTypesWithSnapshot, ct as createCommand$3, dn as getLatestMigrationNumber, en as generateAllTypeManifestsFromSnapshot, et as updateCommand$2, f as restoreCommand, ft as getCommand$3, g as getCommand$7, gn as isValidMigrationNumber, gt as listCommand$9, ht as tokenCommand$1, i as updateCommand$4, in as INITIAL_SCHEMA_NUMBER, it as getCommand$4, jt as jobsCommand, kn as prompt, m as listCommand$16, mn as getMigrationFiles, nt as listCommand$11, o as removeCommand, ot as deleteCommand$4, pn as getMigrationFilePath, qt as executeScript, r as queryCommand, sn as assertValidMigrationFiles, t as registerTsHook, tn as protoGqlPermission, u as inviteCommand, un as createSnapshotFromLocalTypes, ut as listCommand$10, v as deleteCommand$5, vn as reconstructSnapshotFromMigrations, vt as generate, wn as getNamespacesWithMigrations, wt as webhookCommand, xt as getCommand$2, yn as formatMigrationNumber, yt as listCommand$8, z as openInConfiguredEditor } from "../register-ts-hook-BhzVkej0.mjs";
6
- import { i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-CzZEVrp9.mjs";
5
+ import { n as logger, r as styles } from "../logger-BwS4ppwO.mjs";
6
+ import { $n as saveUserTokens, An as sdkNameLabelKey, B as extractOwnedNamespaces, Bn as hasUserTokenEntry, Bt as getCommand$1, Cn as formatMigrationNumber, Ct as triggerCommand, Dn as hasChanges, E as listCommand$14, En as formatMigrationDiff, F as openInConfiguredEditor, Fn as assertWritable, Gn as loadPlatformClientConfig, Hn as loadConfigPath, I as deployCommand$1, In as loadConfig, It as executionsCommand, J as listCommand$13, Jn as platformConfigFromProfile, Jt as formatKeyValueTable, Kn as loadStoredUserTokens, Kt as createCommand$4, Ln as deleteUserTokens, M as generateMigrationScript, Mt as startCommand, N as writeDbTypesFile, Nn as prompt, On as getNamespacesWithMigrations, P as getConfiguredEditorCommand, Pn as apiCall, Pt as getCommand$6, Q as updateCommand$2, Qn as resolveUserTokenKey, Rn as fetchLatestToken, S as waitCommand, Sn as reconstructSnapshotFromMigrations, St as webhookCommand, Tt as listCommand$8, U as removeCommand$1, Un as loadConsoleBaseUrl, Ut as deployFromCLI, V as logBetaWarning, Vn as loadAccessToken, W as updateCommand$3, Wn as loadMachineUserName, Wt as listCommand$17, X as getCommand$5, Xn as removeLegacyUserAlias, Xt as ensureConfigId, Yn as readPlatformConfig, Yt as workspaceNameSchema, Zn as resolveConfigUser, Zt as executeScript, _n as getMigrationFilePath, _t as listCommand$9, an as protoGqlPermission, ar as deploymentArgs, bn as isValidMigrationNumber, c as listCommand$16, cn as INITIAL_SCHEMA_NUMBER, cr as pagedLogArgs, ct as listCommand$11, dn as assertValidMigrationFiles, dr as toPageDirection, en as MIGRATION_LABEL_KEY, er as writePlatformConfig, et as listCommand$12, f as restoreCommand, fn as compareLocalTypesWithSnapshot, fr as workspaceArgs, g as deleteCommand$5, gt as generate, hn as getLatestMigrationNumber, i as updateCommand$4, in as generateAllTypeManifestsFromSnapshot, ir as confirmationArgs, it as deleteCommand$4, j as generateCommand$1, jn as PluginManager, k as truncateCommand, kn as resourceTrn, kt as jobsCommand, lr as paginationArgs, m as getCommand$7, mn as createSnapshotFromLocalTypes, mt as listCommand$10, nn as parseMigrationLabelNumber, nr as commonArgs, nt as getCommand$4, o as removeCommand, or as isVerbose, ot as createCommand$3, pt as tokenCommand$1, q as treeCommand, qn as loadWorkspaceId, r as queryCommand, rn as compareSnapshotWithRemote, rr as configArg, sr as multiConfigArg, t as registerTsHook, tn as handleOptionalToRequiredError, tr as defineAppCommand, u as inviteCommand, ur as resolveMachineUserInputSource, ut as getCommand$3, vn as getMigrationFiles, w as resumeCommand, wn as parseMigrationNumberArg, x as healthCommand, xn as loadDiff, y as listCommand$15, yt as getCommand$2, z as showCommand, zn as hasAnyUserTokenEntry, zt as functionExecutionStatusToString } from "../register-ts-hook-DvEs6YsL.mjs";
7
+ import { A as fetchAllTolerant, B as initOperatorClient, D as closeConnectionPool, I as getOAuth2ClientId, L as getOrNull, M as fetchPaged, N as fetchPlatformMachineUserToken, O as defaultPlatformBaseUrl, P as fetchUserInfo, R as getPlatformBaseUrl, V as isDefaultPlatform, _ as platformBundleDefinePlugin, a as resolveInlineSourcemap, b as resolveBundleLogLevel, c as WorkflowJobSchema, d as INVOKER_EXPR, k as fetchAll, l as ResolverSchema, v as composeFunctionTreeshakeOptions, w as getDistDir, y as createLogLevelTreeshakeOptions, z as initOAuth2Client } from "../application-GzW98_Xr.mjs";
8
+ import { i as ExecutorSchema, r as stripExecutorTriggerArgs } from "../service-Dmxa2I4i.mjs";
7
9
  import { t as multiline } from "../multiline-sfHpTZZK.mjs";
8
10
  import { t as readPackageJson } from "../package-json-8b0O9TlX.mjs";
9
- import { i as userAgent } from "../secret-file-aoNXJmKa.mjs";
10
- import { n as isCLIError } from "../errors-118wUDRr.mjs";
11
- import { B as FunctionExecution_Type, Ct as PATScope, T as CustomDomainStatus, gt as AuthInvokerSchema } from "../service_pb-Bk7dL-yT.mjs";
12
- import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-Cg-pqRJV.mjs";
13
- import { t as isPluginGeneratedType } from "../type-source-DH_LH20p.mjs";
11
+ import { t as userAgent } from "../user-agent-Bgsszb5I.mjs";
12
+ import { n as errorToJson, r as isCLIError, t as createCLIError } from "../errors-DfnF4ibq.mjs";
13
+ import { t as OperatorService } from "../service_pb-BoLM0X4K.mjs";
14
+ import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-CxHne11E.mjs";
14
15
  import * as path from "pathe";
15
16
  import { dirname, resolve } from "pathe";
16
17
  import { resolvePackageJSON, resolveTSConfig } from "pkg-types";
@@ -18,29 +19,578 @@ import { arg, defineCommand, runCommand, runMain } from "politty";
18
19
  import { withCompletionCommand } from "politty/completion";
19
20
  import { withSkillCommand } from "politty/skill";
20
21
  import { z } from "zod";
21
- import { create } from "@bufbuild/protobuf";
22
- import * as fs$1 from "node:fs";
22
+ import { ScalarType, create } from "@bufbuild/protobuf";
23
+ import * as fs from "node:fs";
23
24
  import { accessSync, constants, readdirSync } from "node:fs";
24
25
  import { timestampDate } from "@bufbuild/protobuf/wkt";
25
- import { fileURLToPath, pathToFileURL } from "node:url";
26
+ import { pathToFileURL } from "node:url";
26
27
  import { generateCodeVerifier } from "@badgateway/oauth2-client";
27
28
  import { Code, ConnectError } from "@connectrpc/connect";
28
- import pLimit from "p-limit";
29
29
  import * as crypto from "node:crypto";
30
30
  import { createHash } from "node:crypto";
31
31
  import * as http from "node:http";
32
32
  import open from "open";
33
33
  import * as rolldown from "rolldown";
34
- import * as fsPromises from "node:fs/promises";
35
- import { glob } from "node:fs/promises";
36
34
  import * as os from "node:os";
37
- import { setTimeout as setTimeout$1 } from "node:timers/promises";
35
+ import * as fsPromises from "node:fs/promises";
38
36
  import { TraceMap, generatedPositionFor, originalPositionFor } from "@jridgewell/trace-mapping";
39
37
  import { spawn, spawnSync } from "node:child_process";
40
- import { watch } from "chokidar";
41
- import * as fs from "fs";
42
- import { lookup } from "mime-types";
43
38
 
39
+ //#region src/cli/commands/api/proto-reflect.ts
40
+ const UNARY_METHODS = OperatorService.methods.filter((m) => m.methodKind === "unary");
41
+ function listMethodNames() {
42
+ return UNARY_METHODS.map((m) => m.name).toSorted();
43
+ }
44
+ /**
45
+ * Returns every accepted form of the `endpoint` positional — bare method name
46
+ * and fully-qualified `service/Method`. politty's expand completion keys its
47
+ * static table by the literal endpoint string, so both forms must appear in
48
+ * `choices` or the user typing the fully-qualified name gets no `--field`
49
+ * completion candidates back at TAB time.
50
+ * @returns Sorted list of accepted endpoint values
51
+ */
52
+ function listMethodChoices() {
53
+ return UNARY_METHODS.flatMap((m) => [m.name, `${OperatorService.typeName}/${m.name}`]).toSorted();
54
+ }
55
+ function getMethodDescriptor(methodName) {
56
+ return UNARY_METHODS.find((m) => m.name === methodName);
57
+ }
58
+ function extractMethodName(endpoint) {
59
+ if (!endpoint.includes("/")) return endpoint;
60
+ return endpoint.split("/").pop() ?? endpoint;
61
+ }
62
+ /**
63
+ * Returns the nested message descriptor when the field is a message, a list of
64
+ * messages, or a map with message values. Otherwise returns undefined.
65
+ * @param field - The proto field descriptor to inspect
66
+ * @returns The nested message descriptor, or undefined when the field isn't message-shaped
67
+ */
68
+ function nestedMessage(field) {
69
+ if (field.fieldKind === "message") return field.message;
70
+ if (field.fieldKind === "list" && field.listKind === "message") return field.message;
71
+ if (field.fieldKind === "map" && field.mapKind === "message") return field.message;
72
+ }
73
+ /**
74
+ * Most `google.protobuf.*` well-known types serialize as scalars in proto JSON
75
+ * (Timestamp/Duration → RFC3339/duration string, FieldMask → comma-joined
76
+ * string, *Value wrappers → underlying primitive, etc.), so we must not let
77
+ * `--field` drill into their internal fields. Treating them as leaves keeps
78
+ * the completion and the body shape consistent with what the server expects.
79
+ * @param message - The proto message descriptor to inspect
80
+ * @returns True when the message is a well-known type
81
+ */
82
+ function isWellKnownType(message) {
83
+ return message.typeName.startsWith("google.protobuf.");
84
+ }
85
+ const UNREPRESENTABLE_WELL_KNOWN_TYPES = /* @__PURE__ */ new Set([
86
+ "google.protobuf.Struct",
87
+ "google.protobuf.Value",
88
+ "google.protobuf.ListValue",
89
+ "google.protobuf.NullValue",
90
+ "google.protobuf.Any",
91
+ "google.protobuf.Empty"
92
+ ]);
93
+ /**
94
+ * @param message - The proto message descriptor to inspect
95
+ * @returns True when the message is a well-known type that `--field` cannot represent
96
+ */
97
+ function isUnrepresentableWellKnownType(message) {
98
+ return UNREPRESENTABLE_WELL_KNOWN_TYPES.has(message.typeName);
99
+ }
100
+ /**
101
+ * Pre-enumerates every `--field` completion candidate for `methodName`,
102
+ * walking the input message tree to produce a flat list that the shell can
103
+ * prefix-filter at TAB time. Used by the `expand` completion variant so no
104
+ * Node process is spawned per keystroke.
105
+ *
106
+ * For each leaf field, emits `key=` (key completion). Enum leaves additionally
107
+ * emit `key=ENUM_VALUE` per value, bool leaves emit `key=true` and
108
+ * `key=false`. For nested messages, emits `key.` (drill-down) and recurses.
109
+ *
110
+ * Returns an empty array when `methodName` is unknown so the `expand`
111
+ * generator stays exception-free at script-generation time.
112
+ * @param methodName - Name of the unary RPC whose input message is being walked
113
+ * @returns Flat list of `{ value, description }` candidates
114
+ */
115
+ function enumerateAllFieldCompletions(methodName) {
116
+ const method = getMethodDescriptor(methodName);
117
+ if (!method) return [];
118
+ const candidates = [];
119
+ const visited = /* @__PURE__ */ new Set();
120
+ function walk(message, prefix) {
121
+ visited.add(message);
122
+ for (const field of message.fields) {
123
+ if (field.fieldKind === "list" || field.fieldKind === "map") continue;
124
+ const fullKey = prefix + field.localName;
125
+ if (field.fieldKind === "message") {
126
+ if (isUnrepresentableWellKnownType(field.message)) continue;
127
+ if (!isWellKnownType(field.message)) {
128
+ const nested = field.message;
129
+ candidates.push({
130
+ value: `${fullKey}.`,
131
+ description: `${fullKey} (message)`
132
+ });
133
+ if (!visited.has(nested)) walk(nested, `${fullKey}.`);
134
+ continue;
135
+ }
136
+ }
137
+ candidates.push({
138
+ value: `${fullKey}=`,
139
+ description: `Set ${fullKey}`
140
+ });
141
+ if (field.fieldKind === "enum") for (const v of field.enum.values) candidates.push({
142
+ value: `${fullKey}=${v.name}`,
143
+ description: v.name
144
+ });
145
+ else if (field.fieldKind === "scalar" && field.scalar === ScalarType.BOOL) {
146
+ candidates.push({
147
+ value: `${fullKey}=true`,
148
+ description: "true"
149
+ });
150
+ candidates.push({
151
+ value: `${fullKey}=false`,
152
+ description: "false"
153
+ });
154
+ }
155
+ }
156
+ visited.delete(message);
157
+ }
158
+ walk(method.input, "");
159
+ return candidates;
160
+ }
161
+ /**
162
+ * Resolves the leaf `DescField` at a dotted `--field` path within an input
163
+ * message tree. Returns undefined when any segment is missing or when a
164
+ * non-leaf segment isn't a singular message — callers fall back to treating
165
+ * the value as a string in that case.
166
+ * @param input - The RPC input message descriptor
167
+ * @param path - Dot-split path segments to follow (e.g. ["application", "name"])
168
+ * @returns The leaf field descriptor, or undefined when the path doesn't resolve
169
+ */
170
+ function resolveLeafField(input, path) {
171
+ let message = input;
172
+ for (let i = 0; i < path.length; i++) {
173
+ const field = message.fields.find((f) => f.localName === path[i]);
174
+ if (!field) return void 0;
175
+ if (i === path.length - 1) {
176
+ if (field.fieldKind === "message" && isUnrepresentableWellKnownType(field.message)) return;
177
+ return field;
178
+ }
179
+ if (field.fieldKind !== "message") return void 0;
180
+ if (isWellKnownType(field.message)) return void 0;
181
+ message = field.message;
182
+ }
183
+ }
184
+
185
+ //#endregion
186
+ //#region src/cli/commands/api/render.ts
187
+ const SCALAR_LABEL = {
188
+ [ScalarType.DOUBLE]: "double",
189
+ [ScalarType.FLOAT]: "float",
190
+ [ScalarType.INT64]: "int64",
191
+ [ScalarType.UINT64]: "uint64",
192
+ [ScalarType.INT32]: "int32",
193
+ [ScalarType.FIXED64]: "fixed64",
194
+ [ScalarType.FIXED32]: "fixed32",
195
+ [ScalarType.BOOL]: "bool",
196
+ [ScalarType.STRING]: "string",
197
+ [ScalarType.BYTES]: "bytes",
198
+ [ScalarType.UINT32]: "uint32",
199
+ [ScalarType.SFIXED32]: "sfixed32",
200
+ [ScalarType.SFIXED64]: "sfixed64",
201
+ [ScalarType.SINT32]: "sint32",
202
+ [ScalarType.SINT64]: "sint64"
203
+ };
204
+ function shortName(typeName) {
205
+ const dot = typeName.lastIndexOf(".");
206
+ return dot < 0 ? typeName : typeName.slice(dot + 1);
207
+ }
208
+ function scalarLabel(scalar) {
209
+ return SCALAR_LABEL[scalar] ?? `scalar(${scalar})`;
210
+ }
211
+ function enumLabel(enumDesc) {
212
+ return `enum ${shortName(enumDesc.typeName)}`;
213
+ }
214
+ function enumOf(field) {
215
+ if (field.fieldKind === "enum") return field.enum;
216
+ if (field.fieldKind === "list" && field.listKind === "enum") return field.enum;
217
+ }
218
+ function describeFieldType(field) {
219
+ switch (field.fieldKind) {
220
+ case "scalar": return scalarLabel(field.scalar);
221
+ case "enum": return enumLabel(field.enum);
222
+ case "message": return shortName(field.message.typeName);
223
+ case "list": {
224
+ let inner;
225
+ if (field.listKind === "scalar") inner = scalarLabel(field.scalar);
226
+ else if (field.listKind === "enum") inner = enumLabel(field.enum);
227
+ else inner = shortName(field.message.typeName);
228
+ return `repeated ${inner}`;
229
+ }
230
+ case "map": {
231
+ const keyType = scalarLabel(field.mapKey);
232
+ let valType;
233
+ if (field.mapKind === "scalar") valType = scalarLabel(field.scalar);
234
+ else if (field.mapKind === "enum") valType = enumLabel(field.enum);
235
+ else valType = shortName(field.message.typeName);
236
+ return `map<${keyType}, ${valType}>`;
237
+ }
238
+ default: return "<unknown>";
239
+ }
240
+ }
241
+ function fieldToJson(field, visited) {
242
+ const json = {
243
+ name: field.localName,
244
+ protoName: field.name,
245
+ type: describeFieldType(field),
246
+ fieldKind: field.fieldKind,
247
+ repeated: field.fieldKind === "list"
248
+ };
249
+ if (field.oneof) json.oneof = field.oneof.name;
250
+ const fieldEnum = enumOf(field);
251
+ if (fieldEnum) json.enumValues = fieldEnum.values.map((v) => v.name);
252
+ const nested = nestedMessage(field);
253
+ if (nested && !visited.has(nested)) {
254
+ visited.add(nested);
255
+ json.message = {
256
+ typeName: nested.typeName,
257
+ fields: nested.fields.map((f) => fieldToJson(f, visited))
258
+ };
259
+ visited.delete(nested);
260
+ } else if (nested) json.message = {
261
+ typeName: nested.typeName,
262
+ fields: [],
263
+ recursive: true
264
+ };
265
+ return json;
266
+ }
267
+ function renderInspectJson(method) {
268
+ const visited = /* @__PURE__ */ new Set([method.input]);
269
+ return {
270
+ method: method.name,
271
+ input: {
272
+ typeName: method.input.typeName,
273
+ fields: method.input.fields.map((f) => fieldToJson(f, visited))
274
+ },
275
+ output: { typeName: method.output.typeName }
276
+ };
277
+ }
278
+ function renderFieldText(field, indent, visited) {
279
+ const lines = [];
280
+ const oneofTag = field.oneof ? ` (oneof ${field.oneof.name})` : "";
281
+ lines.push(`${indent}${field.localName}: ${describeFieldType(field)}${oneofTag}`);
282
+ const fieldEnum = enumOf(field);
283
+ if (fieldEnum) {
284
+ const values = fieldEnum.values.map((v) => v.name).join(", ");
285
+ lines.push(`${indent} values: ${values}`);
286
+ }
287
+ const nested = nestedMessage(field);
288
+ if (nested && !visited.has(nested)) {
289
+ visited.add(nested);
290
+ for (const sub of nested.fields) lines.push(...renderFieldText(sub, `${indent} `, visited));
291
+ visited.delete(nested);
292
+ } else if (nested) lines.push(`${indent} …(recursive ${nested.typeName})`);
293
+ return lines;
294
+ }
295
+ function renderInspectText(method) {
296
+ const lines = [];
297
+ lines.push(`${method.name}`);
298
+ lines.push(` request: ${method.input.typeName}`);
299
+ const visited = /* @__PURE__ */ new Set([method.input]);
300
+ for (const f of method.input.fields) lines.push(...renderFieldText(f, " ", visited));
301
+ lines.push(` response: ${method.output.typeName}`);
302
+ return lines.join("\n");
303
+ }
304
+
305
+ //#endregion
306
+ //#region src/cli/commands/api/inspect.ts
307
+ const inspectCommand = defineAppCommand({
308
+ name: "inspect",
309
+ description: "Print the input message tree of an OperatorService endpoint.",
310
+ notes: "Combine with the global `--json` flag for a machine-readable descriptor. Recursive type references and `oneof` membership are annotated. Use `tailor api list` to discover endpoint names.",
311
+ examples: [{
312
+ cmd: "GetApplication",
313
+ desc: "Show fields of GetApplicationRequest."
314
+ }, {
315
+ cmd: "CreateExecutorExecutor",
316
+ desc: "Inspect a deeply nested input with `(oneof config)` annotations."
317
+ }],
318
+ args: z.strictObject({ endpoint: arg(z.string(), {
319
+ positional: true,
320
+ description: "API endpoint to inspect (e.g., 'GetApplication' or 'tailor.v1.OperatorService/GetApplication').",
321
+ completion: { custom: { choices: listMethodNames() } }
322
+ }) }),
323
+ run: (args) => {
324
+ const methodName = extractMethodName(args.endpoint);
325
+ const method = getMethodDescriptor(methodName);
326
+ if (!method) throw createCLIError({
327
+ message: `unknown method: ${methodName}`,
328
+ suggestion: "Run `tailor api list` to see available methods.",
329
+ command: "api inspect"
330
+ });
331
+ if (logger.jsonMode) logger.out(renderInspectJson(method));
332
+ else logger.out(renderInspectText(method));
333
+ }
334
+ });
335
+
336
+ //#endregion
337
+ //#region src/cli/commands/api/list.ts
338
+ const listCommand$7 = defineAppCommand({
339
+ name: "list",
340
+ description: "List all invocable OperatorService methods.",
341
+ notes: "Only single-request (non-streaming) methods are listed, because the CLI issues a single JSON request and reads one JSON response.",
342
+ args: z.strictObject({}),
343
+ run: () => {
344
+ const names = listMethodNames();
345
+ if (logger.jsonMode) logger.out(names);
346
+ else for (const name of names) logger.out(name);
347
+ }
348
+ });
349
+
350
+ //#endregion
351
+ //#region src/cli/commands/api/index.ts
352
+ function resolveNamespaceName(methodName, config) {
353
+ if (/Auth|Tenant|UserProfile/.test(methodName)) return config.auth?.name;
354
+ if (/IdP/.test(methodName)) {
355
+ if (config.idp?.length === 1) return assertDefined(config.idp[0], "idp config missing").name;
356
+ return;
357
+ }
358
+ if (/TailorDB/.test(methodName)) {
359
+ const keys = Object.keys(config.db ?? {});
360
+ if (keys.length === 1) return keys[0];
361
+ return;
362
+ }
363
+ if (/Pipeline/.test(methodName)) {
364
+ const keys = Object.keys(config.resolver ?? {});
365
+ if (keys.length === 1) return keys[0];
366
+ return;
367
+ }
368
+ }
369
+ /**
370
+ * Parse a JSON body string as a plain object for field injection.
371
+ * Returns undefined for invalid JSON or non-object values (null, arrays, primitives),
372
+ * so callers can fall back to sending the raw string unchanged.
373
+ * @param body - Raw body string
374
+ * @returns Parsed object or undefined
375
+ */
376
+ function parseBodyAsObject(body) {
377
+ let parsed;
378
+ try {
379
+ parsed = JSON.parse(body);
380
+ } catch {
381
+ return;
382
+ }
383
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
384
+ return parsed;
385
+ }
386
+ /**
387
+ * Set a dotted path on a body object, replacing non-object intermediates as
388
+ * needed. `--field` takes precedence over `--body`, so collisions overwrite.
389
+ * @param obj - The body object to mutate
390
+ * @param path - Dot-split path segments (e.g. ["application", "name"])
391
+ * @param value - Value to assign at the leaf
392
+ */
393
+ function setNestedPath(obj, path, value) {
394
+ let cursor = obj;
395
+ for (let i = 0; i < path.length - 1; i++) {
396
+ const key = assertDefined(path[i], "path segment missing");
397
+ const next = cursor[key];
398
+ if (typeof next !== "object" || next === null || Array.isArray(next)) cursor[key] = {};
399
+ cursor = cursor[key];
400
+ }
401
+ cursor[assertDefined(path[path.length - 1], "path last segment missing")] = value;
402
+ }
403
+ /**
404
+ * Coerces a raw `--field` string value to match the leaf proto type so the
405
+ * JSON body sends a properly typed value. Today we only coerce bool — the
406
+ * completion candidates explicitly suggest `=true`/`=false`, so sending the
407
+ * literal string "true" would be a visible mismatch. Other scalars are left
408
+ * as strings: proto JSON accepts string forms for ints/floats/int64/etc.,
409
+ * and we'd rather pass through what the user typed than silently coerce.
410
+ * @param field - The resolved leaf field descriptor, or undefined when the path didn't resolve
411
+ * @param raw - The raw string value after `=`
412
+ * @returns The value to write into the body object
413
+ */
414
+ function coerceFieldValue(field, raw) {
415
+ if (field && field.fieldKind === "scalar" && field.scalar === ScalarType.BOOL) {
416
+ if (raw === "true") return true;
417
+ if (raw === "false") return false;
418
+ throw new Error(`Invalid value for bool field: '${raw}'. Expected 'true' or 'false'.`);
419
+ }
420
+ return raw;
421
+ }
422
+ /**
423
+ * Collapse top-level body keys that address a known input field by its
424
+ * protobuf name (snake_case) or JSON name to the field's `localName`. protojson
425
+ * accepts every alias, so converging them here lets the presence checks below
426
+ * reason in a single namespace — otherwise a `--body` written as
427
+ * `{"workspace_id": ...}` slips past the camelCase check and we inject a second
428
+ * `workspaceId`, which the server rejects as a duplicate field. When both forms
429
+ * are present the canonical key wins and the alias is dropped.
430
+ * @param body - The parsed body object to mutate
431
+ * @param fields - The target endpoint's input fields
432
+ * @returns Whether any key was rewritten
433
+ */
434
+ function normalizeBodyFieldKeys(body, fields) {
435
+ const aliasToLocal = /* @__PURE__ */ new Map();
436
+ for (const f of fields) {
437
+ aliasToLocal.set(f.name, f.localName);
438
+ aliasToLocal.set(f.jsonName, f.localName);
439
+ }
440
+ let changed = false;
441
+ for (const key of Object.keys(body)) {
442
+ const local = aliasToLocal.get(key);
443
+ if (!local || local === key) continue;
444
+ if (!Object.hasOwn(body, local)) body[local] = body[key];
445
+ delete body[key];
446
+ changed = true;
447
+ }
448
+ return changed;
449
+ }
450
+ const FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set([
451
+ "__proto__",
452
+ "constructor",
453
+ "prototype"
454
+ ]);
455
+ const fieldArg = z.string().transform((val, ctx) => {
456
+ const eq = val.indexOf("=");
457
+ if (eq < 0) {
458
+ ctx.addIssue({
459
+ code: z.ZodIssueCode.custom,
460
+ message: `Invalid field format: '${val}'. Expected format: 'key=value' or 'a.b.c=value'`
461
+ });
462
+ return z.NEVER;
463
+ }
464
+ const key = val.slice(0, eq);
465
+ if (key.length === 0) {
466
+ ctx.addIssue({
467
+ code: z.ZodIssueCode.custom,
468
+ message: "Field key cannot be empty"
469
+ });
470
+ return z.NEVER;
471
+ }
472
+ const segments = key.split(".");
473
+ if (segments.some((seg) => seg.length === 0)) {
474
+ ctx.addIssue({
475
+ code: z.ZodIssueCode.custom,
476
+ message: `Invalid field key: '${key}'. Dotted segments cannot be empty`
477
+ });
478
+ return z.NEVER;
479
+ }
480
+ const forbidden = segments.find((seg) => FORBIDDEN_SEGMENTS.has(seg));
481
+ if (forbidden) {
482
+ ctx.addIssue({
483
+ code: z.ZodIssueCode.custom,
484
+ message: `Invalid field key: '${key}'. Segment '${forbidden}' is not allowed.`
485
+ });
486
+ return z.NEVER;
487
+ }
488
+ return {
489
+ path: segments,
490
+ value: val.slice(eq + 1)
491
+ };
492
+ });
493
+ const apiCommand = defineAppCommand({
494
+ name: "api",
495
+ description: "Call Tailor Platform API endpoints directly.",
496
+ notes: `Use \`tailor api list\` to enumerate invocable methods and \`tailor api inspect <endpoint>\` to print an endpoint's input message tree (combine with \`--json\` for machine-readable output).
497
+
498
+ The request body is inferred from the target endpoint's request schema, and commonly required fields are auto-injected so they can be omitted from \`--body\`:
499
+
500
+ - \`workspaceId\` — resolved from \`-w\` / \`TAILOR_PLATFORM_WORKSPACE_ID\` / the selected profile.
501
+ - \`namespaceName\` — resolved from \`tailor.config.ts\` based on the endpoint's service:
502
+ - Auth / Tenant / UserProfile endpoints use \`auth.name\`.
503
+ - IdP / TailorDB / Pipeline endpoints use the sole configured namespace when exactly one is defined.
504
+
505
+ Values already present in \`--body\` are never overridden. If a value cannot be resolved (e.g. no config found), injection is silently skipped and the server-side validation error takes precedence.
506
+
507
+ Use \`--field key=value\` (repeatable) to set request body fields without writing JSON. Dotted keys (e.g. \`application.name=foo\`) build nested objects. \`--field\` overrides matching fields in \`--body\` and tab-completes from the endpoint's request schema.`,
508
+ examples: [
509
+ {
510
+ cmd: "GetApplication -b '{\"applicationName\":\"app-1\"}'",
511
+ desc: "Call an endpoint; workspaceId is auto-injected."
512
+ },
513
+ {
514
+ cmd: "GetApplication -f applicationName=app-1",
515
+ desc: "Same as above, using --field instead of --body."
516
+ },
517
+ {
518
+ cmd: "list",
519
+ desc: "List all invocable OperatorService methods."
520
+ },
521
+ {
522
+ cmd: "inspect GetApplication",
523
+ desc: "Show the input message tree for an endpoint."
524
+ }
525
+ ],
526
+ subCommands: {
527
+ list: listCommand$7,
528
+ inspect: inspectCommand
529
+ },
530
+ args: z.strictObject({
531
+ ...workspaceArgs,
532
+ ...configArg,
533
+ body: arg(z.string().default("{}"), {
534
+ alias: "b",
535
+ description: "Request body as JSON."
536
+ }),
537
+ field: arg(fieldArg.array().optional(), {
538
+ alias: "f",
539
+ description: "Set a body field as `key=value` (repeatable; dotted keys nest). Overrides --body.",
540
+ completion: { custom: { expand: {
541
+ dependsOn: ["endpoint"],
542
+ enumerate: ({ endpoint }) => enumerateAllFieldCompletions(extractMethodName(endpoint ?? ""))
543
+ } } }
544
+ }),
545
+ endpoint: arg(z.string(), {
546
+ positional: true,
547
+ description: "API endpoint to call (e.g., 'GetApplication' or 'tailor.v1.OperatorService/GetApplication').",
548
+ completion: { custom: { choices: listMethodChoices() } }
549
+ })
550
+ }),
551
+ run: async (args) => {
552
+ await assertWritable({ profile: args.profile });
553
+ const methodName = extractMethodName(args.endpoint);
554
+ const method = getMethodDescriptor(methodName);
555
+ const parsedBody = parseBodyAsObject(args.body);
556
+ let mutated = false;
557
+ if (args.field && args.field.length > 0) {
558
+ if (!parsedBody) throw new Error("--field requires --body to be a JSON object (or omitted).");
559
+ for (const f of args.field) {
560
+ const leaf = method ? resolveLeafField(method.input, f.path) : void 0;
561
+ setNestedPath(parsedBody, f.path, coerceFieldValue(leaf, f.value));
562
+ }
563
+ mutated = true;
564
+ }
565
+ if (parsedBody && method) {
566
+ if (normalizeBodyFieldKeys(parsedBody, method.input.fields)) mutated = true;
567
+ const fieldNames = method.input.fields.map((f) => f.localName);
568
+ if (fieldNames.includes("workspaceId") && !Object.hasOwn(parsedBody, "workspaceId")) try {
569
+ parsedBody.workspaceId = await loadWorkspaceId({
570
+ workspaceId: args["workspace-id"],
571
+ profile: args.profile
572
+ });
573
+ mutated = true;
574
+ } catch {}
575
+ if (fieldNames.includes("namespaceName") && !Object.hasOwn(parsedBody, "namespaceName")) try {
576
+ const { config } = await loadConfig(args.config);
577
+ const ns = resolveNamespaceName(methodName, config);
578
+ if (ns) {
579
+ parsedBody.namespaceName = ns;
580
+ mutated = true;
581
+ }
582
+ } catch {}
583
+ }
584
+ const result = await apiCall({
585
+ profile: args.profile,
586
+ endpoint: args.endpoint,
587
+ body: mutated && parsedBody ? JSON.stringify(parsedBody) : args.body
588
+ });
589
+ logger.out(JSON.stringify(result.data, null, 2));
590
+ }
591
+ });
592
+
593
+ //#endregion
44
594
  //#region src/cli/commands/auth/token.ts
45
595
  const tokenCommand = defineAppCommand({
46
596
  name: "token",
@@ -396,7 +946,7 @@ const listCommand$6 = defineAppCommand({
396
946
  }
397
947
  let entries;
398
948
  try {
399
- entries = fs$1.readdirSync(config.localDir);
949
+ entries = fs.readdirSync(config.localDir);
400
950
  } catch {
401
951
  logger.info("No crash reports found.");
402
952
  if (jsonOutput) logger.out([]);
@@ -436,7 +986,7 @@ const sendCommand = defineAppCommand({
436
986
  run: async (args) => {
437
987
  let content;
438
988
  try {
439
- content = fs$1.readFileSync(args.file, "utf-8");
989
+ content = fs.readFileSync(args.file, "utf-8");
440
990
  } catch {
441
991
  logger.error(`Crash report file not found: ${args.file}`);
442
992
  process.exit(1);
@@ -489,44 +1039,26 @@ const crashReportCommand = defineCommand({
489
1039
  }
490
1040
  });
491
1041
 
492
- //#endregion
493
- //#region src/parser/service/resolver/local-input-parser.ts
494
- /**
495
- * Build a local, in-process validator for a resolver's declared input fields.
496
- * Each field already carries its own bound `_parseInternal` (attached when the
497
- * user's resolver module was loaded via dynamic import), so this only needs to
498
- * orchestrate calling every field and collecting issues.
499
- * @param fields - Resolver's raw input field definitions (already-built TailorField instances)
500
- * @returns An object exposing a `.parse()` method for local format detection
501
- */
502
- function buildLocalInputParser(fields) {
503
- return { parse(args) {
504
- const { value, data, invoker } = args;
505
- if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date) return { issues: [{ message: `Expected an object: received ${String(value)}` }] };
506
- const record = value;
507
- const issues = [];
508
- for (const [name, field] of Object.entries(fields)) {
509
- const result = field._parseInternal({
510
- value: record[name],
511
- data,
512
- invoker,
513
- pathArray: [name]
514
- });
515
- if (result.issues) issues.push(...result.issues);
516
- }
517
- return issues.length > 0 ? { issues } : {};
518
- } };
519
- }
520
-
521
1042
  //#endregion
522
1043
  //#region src/cli/commands/deploy/index.ts
523
- const deployCommand$1 = defineAppCommand({
1044
+ const deployCommand = defineAppCommand({
524
1045
  name: "deploy",
525
1046
  description: "Deploy your application by applying the Tailor configuration.",
526
1047
  args: z.strictObject({
527
1048
  ...workspaceArgs,
528
1049
  ...multiConfigArg,
529
1050
  ...confirmationArgs,
1051
+ "create-workspace": arg(z.boolean().optional(), { description: "Create a workspace when the account has none" }),
1052
+ "workspace-name": arg(z.string().optional(), { description: "Name for a workspace created during deploy" }),
1053
+ "workspace-region": arg(z.string().optional(), { description: "Region for a workspace created during deploy" }),
1054
+ "organization-id": arg(z.string().optional(), {
1055
+ description: "Organization ID for a workspace created during deploy",
1056
+ env: "TAILOR_PLATFORM_ORGANIZATION_ID"
1057
+ }),
1058
+ "folder-id": arg(z.string().optional(), {
1059
+ description: "Folder ID for a workspace created during deploy",
1060
+ env: "TAILOR_PLATFORM_FOLDER_ID"
1061
+ }),
530
1062
  "dry-run": arg(z.boolean().optional(), {
531
1063
  alias: "d",
532
1064
  description: "Run the command without making any changes"
@@ -540,16 +1072,26 @@ const deployCommand$1 = defineAppCommand({
540
1072
  await assertWritable({ profile: args.profile });
541
1073
  const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
542
1074
  await initTelemetry();
543
- await deploy({
1075
+ await deployFromCLI({
544
1076
  workspaceId: args["workspace-id"],
545
1077
  profile: args.profile,
546
1078
  configPath: args.config,
547
1079
  dryRun: args["dry-run"],
548
1080
  yes: args.yes,
1081
+ createWorkspace: args["create-workspace"],
1082
+ workspaceName: args["workspace-name"],
1083
+ workspaceRegion: args["workspace-region"],
1084
+ organizationId: args["organization-id"],
1085
+ folderId: args["folder-id"],
549
1086
  noSchemaCheck: args["no-schema-check"],
550
1087
  noValidate: args["no-validate"],
551
1088
  noCache: args["no-cache"],
552
1089
  cleanCache: args["clean-cache"]
1090
+ }, {
1091
+ envFile: args["env-file"],
1092
+ envFileIfExists: args["env-file-if-exists"],
1093
+ verbose: args.verbose,
1094
+ json: args.json
553
1095
  });
554
1096
  }
555
1097
  });
@@ -562,12 +1104,12 @@ const executorCommand = defineCommand({
562
1104
  subCommands: {
563
1105
  trigger: triggerCommand,
564
1106
  jobs: jobsCommand,
565
- list: listCommand$7,
1107
+ list: listCommand$8,
566
1108
  get: getCommand$1,
567
1109
  webhook: webhookCommand
568
1110
  },
569
1111
  async run() {
570
- await runCommand(listCommand$7, []);
1112
+ await runCommand(listCommand$8, []);
571
1113
  }
572
1114
  });
573
1115
 
@@ -1148,12 +1690,12 @@ async function bundleForTestRun(options) {
1148
1690
  const inlineSourcemap = resolveInlineSourcemap(options.inlineSourcemap);
1149
1691
  const bundleLogLevel = resolveBundleLogLevel(options.logLevel);
1150
1692
  const outputDir = path.resolve(getDistDir(), "test-run");
1151
- fs$1.mkdirSync(outputDir, { recursive: true });
1693
+ fs.mkdirSync(outputDir, { recursive: true });
1152
1694
  const baseName = `test-run--${detected.name}`;
1153
1695
  const scriptName = `${baseName}.js`;
1154
1696
  const entryPath = path.join(outputDir, `${baseName}.entry.js`);
1155
1697
  const entryContent = generateEntry(detected, sourceFile, env, machineUser, workspaceId);
1156
- fs$1.writeFileSync(entryPath, entryContent);
1698
+ fs.writeFileSync(entryPath, entryContent);
1157
1699
  let tsconfig;
1158
1700
  try {
1159
1701
  tsconfig = await resolveTSConfig();
@@ -1301,7 +1843,13 @@ async function detectFunctionType(options) {
1301
1843
  if (resolverResult.success) {
1302
1844
  const rawInput = module.default.input;
1303
1845
  let inputSchema;
1304
- if (rawInput) inputSchema = buildLocalInputParser(rawInput);
1846
+ if (rawInput) {
1847
+ const fields = rawInput;
1848
+ inputSchema = { parse: (args) => parseInputFields({
1849
+ ...args,
1850
+ fields
1851
+ }) };
1852
+ }
1305
1853
  return {
1306
1854
  type: "resolver",
1307
1855
  name: resolverResult.data.name,
@@ -1427,8 +1975,8 @@ const testRunCommand = defineAppCommand({
1427
1975
  When a \`.js\` file is provided, detection and bundling are skipped and the file is executed as-is.
1428
1976
 
1429
1977
  > [!WARNING]
1430
- > Workflow job \`.trigger()\` calls do not work in test-run mode.
1431
- > Triggered jobs are not executed; only the target job's \`body\` function runs in isolation.`,
1978
+ > Workflow job \`.start()\` calls do not work in test-run mode.
1979
+ > Started jobs are not executed; only the target job's \`body\` function runs in isolation.`,
1432
1980
  examples: [
1433
1981
  {
1434
1982
  cmd: "resolvers/add.ts --arg '{\"a\":1,\"b\":2}'",
@@ -1446,7 +1994,7 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
1446
1994
  run: async (args) => {
1447
1995
  const jsonOutput = logger.jsonMode;
1448
1996
  const filePath = path.resolve(args.file);
1449
- if (!fs$1.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
1997
+ if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
1450
1998
  const { config } = await loadConfig(args.config);
1451
1999
  const authNamespace = resolveAuthNamespace(config.auth);
1452
2000
  const machineUserName = await resolveMachineUserName({
@@ -1475,7 +2023,7 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
1475
2023
  let functionName;
1476
2024
  if (isPreBundled) {
1477
2025
  scriptName = path.basename(filePath);
1478
- bundledCode = fs$1.readFileSync(filePath, "utf-8");
2026
+ bundledCode = fs.readFileSync(filePath, "utf-8");
1479
2027
  logger.info(`Using pre-bundled script ${styles.bold(scriptName)}`);
1480
2028
  } else {
1481
2029
  logger.info(`Detecting function type from ${styles.path(relativePath)}`);
@@ -1629,12 +2177,12 @@ const functionCommand = defineCommand({
1629
2177
  description: "Manage functions",
1630
2178
  subCommands: {
1631
2179
  get: getCommand$2,
1632
- list: listCommand$8,
2180
+ list: listCommand$9,
1633
2181
  logs: logsCommand,
1634
2182
  "test-run": testRunCommand
1635
2183
  },
1636
2184
  async run() {
1637
- await runCommand(listCommand$8, []);
2185
+ await runCommand(listCommand$9, []);
1638
2186
  }
1639
2187
  });
1640
2188
 
@@ -1643,23 +2191,14 @@ const functionCommand = defineCommand({
1643
2191
  const generateCommand = defineAppCommand({
1644
2192
  name: "generate",
1645
2193
  description: "Generate files using Tailor configuration.",
1646
- args: z.strictObject({
1647
- config: arg(z.string().default("tailor.config.ts"), {
1648
- alias: "c",
1649
- description: "Path to SDK config file"
1650
- }),
1651
- watch: arg(z.boolean().default(false), {
1652
- alias: "W",
1653
- description: "Watch for type/resolver changes and regenerate"
1654
- })
1655
- }),
2194
+ args: z.strictObject({ config: arg(z.string().default("tailor.config.ts"), {
2195
+ alias: "c",
2196
+ description: "Path to SDK config file"
2197
+ }) }),
1656
2198
  run: async (args) => {
1657
2199
  const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
1658
2200
  await initTelemetry();
1659
- await generate({
1660
- configPath: args.config,
1661
- watch: args.watch
1662
- });
2201
+ await generate({ configPath: args.config });
1663
2202
  }
1664
2203
  });
1665
2204
 
@@ -1970,11 +2509,11 @@ const machineuserCommand = defineCommand({
1970
2509
  name: "machineuser",
1971
2510
  description: "Manage machine users in your Tailor Platform application.",
1972
2511
  subCommands: {
1973
- list: listCommand$9,
2512
+ list: listCommand$10,
1974
2513
  token: tokenCommand$1
1975
2514
  },
1976
2515
  async run() {
1977
- await runCommand(listCommand$9, []);
2516
+ await runCommand(listCommand$10, []);
1978
2517
  }
1979
2518
  });
1980
2519
 
@@ -1984,11 +2523,11 @@ const oauth2clientCommand = defineCommand({
1984
2523
  name: "oauth2client",
1985
2524
  description: "Manage OAuth2 clients in your Tailor Platform application.",
1986
2525
  subCommands: {
1987
- list: listCommand$10,
2526
+ list: listCommand$11,
1988
2527
  get: getCommand$3
1989
2528
  },
1990
2529
  async run() {
1991
- await runCommand(listCommand$10, []);
2530
+ await runCommand(listCommand$11, []);
1992
2531
  }
1993
2532
  });
1994
2533
 
@@ -2045,7 +2584,7 @@ const folderCommand = defineCommand({
2045
2584
  create: createCommand$3,
2046
2585
  delete: deleteCommand$4,
2047
2586
  get: getCommand$4,
2048
- list: listCommand$11,
2587
+ list: listCommand$12,
2049
2588
  update: updateCommand$2
2050
2589
  }
2051
2590
  });
@@ -2058,12 +2597,12 @@ const organizationCommand = defineCommand({
2058
2597
  subCommands: {
2059
2598
  folder: folderCommand,
2060
2599
  get: getCommand$5,
2061
- list: listCommand$12,
2600
+ list: listCommand$13,
2062
2601
  tree: treeCommand,
2063
2602
  update: updateCommand$3
2064
2603
  },
2065
2604
  async run() {
2066
- await runCommand(listCommand$12, []);
2605
+ await runCommand(listCommand$13, []);
2067
2606
  }
2068
2607
  });
2069
2608
 
@@ -2111,6 +2650,12 @@ const BUILTIN_COMMAND_NAMES = [
2111
2650
 
2112
2651
  //#endregion
2113
2652
  //#region src/cli/shared/plugin.ts
2653
+ /**
2654
+ * npm packages known to provide a plugin slug, keyed by the slug the
2655
+ * dispatcher resolves (command path joined with `-`). Used to suggest an
2656
+ * install command when the plugin executable is not found.
2657
+ */
2658
+ const KNOWN_PLUGIN_PACKAGES = { "tailordb-erd": "@tailor-platform/sdk-plugin-tailordb-erd" };
2114
2659
  /** PATH entry separator (`;` on Windows, `:` elsewhere). */
2115
2660
  const pathDelimiter = process.platform === "win32" ? ";" : ":";
2116
2661
  /**
@@ -2288,17 +2833,7 @@ async function resolveActiveUser(profile) {
2288
2833
  */
2289
2834
  async function buildPluginEnv(options = {}) {
2290
2835
  const { profile } = options;
2291
- let platformConfig;
2292
- try {
2293
- platformConfig = await loadPlatformClientConfig({
2294
- profile,
2295
- allowMissingProfile: true
2296
- });
2297
- } catch {}
2298
- const env = {
2299
- TAILOR_PLATFORM_URL: getPlatformBaseUrl(platformConfig),
2300
- TAILOR_PLATFORM_OAUTH2_CLIENT_ID: getOAuth2ClientId(platformConfig)
2301
- };
2836
+ const env = {};
2302
2837
  const binPath = process.argv[1];
2303
2838
  if (binPath) env.TAILOR_BIN = binPath;
2304
2839
  try {
@@ -2307,6 +2842,16 @@ async function buildPluginEnv(options = {}) {
2307
2842
  } catch {}
2308
2843
  const configPath = loadConfigPath();
2309
2844
  if (configPath) env.TAILOR_CONFIG_PATH = configPath;
2845
+ if (options.skipPlatformContext) return env;
2846
+ let platformConfig;
2847
+ try {
2848
+ platformConfig = await loadPlatformClientConfig({
2849
+ profile,
2850
+ allowMissingProfile: true
2851
+ });
2852
+ } catch {}
2853
+ env.TAILOR_PLATFORM_URL = getPlatformBaseUrl(platformConfig);
2854
+ env.TAILOR_PLATFORM_OAUTH2_CLIENT_ID = getOAuth2ClientId(platformConfig);
2310
2855
  try {
2311
2856
  env.TAILOR_PLATFORM_WORKSPACE_ID = await loadWorkspaceId({ profile });
2312
2857
  } catch {}
@@ -2378,21 +2923,72 @@ function buildSpawnTarget(pluginPath, args) {
2378
2923
  };
2379
2924
  }
2380
2925
  /**
2926
+ * Check whether args forwarded to a plugin carry an explicit env-file flag.
2927
+ * Scanning stops at a `--` terminator. The check is presence-only:
2928
+ * `--env-file-if-exists` counts even when the named file does not exist, so
2929
+ * no platform context is injected in that case either.
2930
+ * @param args - Args forwarded to the plugin
2931
+ * @returns Whether an `--env-file`/`-e`/`--env-file-if-exists` flag is present
2932
+ */
2933
+ function hasEnvFileFlag(args) {
2934
+ for (const token of args) {
2935
+ if (token === "--") return false;
2936
+ if (token === "--env-file" || token === "-e" || token === "--env-file-if-exists") return true;
2937
+ if (token.startsWith("--env-file=") || token.startsWith("-e=") || token.startsWith("--env-file-if-exists=")) return true;
2938
+ }
2939
+ return false;
2940
+ }
2941
+ /**
2942
+ * Extract an explicit `--profile`/`-p` value from args forwarded to a plugin,
2943
+ * so the injected platform context matches the profile the plugin will use.
2944
+ * Scanning stops at a `--` terminator; the last occurrence wins.
2945
+ * @param args - Args forwarded to the plugin
2946
+ * @returns The explicit profile value, or undefined when not present
2947
+ */
2948
+ function explicitProfileFromArgs(args) {
2949
+ let profile;
2950
+ for (let i = 0; i < args.length; i++) {
2951
+ const token = args[i];
2952
+ if (token === void 0 || token === "--") break;
2953
+ if (token === "--profile" || token === "-p") {
2954
+ const next = args[i + 1];
2955
+ if (next !== void 0 && !next.startsWith("-")) {
2956
+ profile = next;
2957
+ i++;
2958
+ }
2959
+ continue;
2960
+ }
2961
+ if (token.startsWith("--profile=")) profile = token.slice(10);
2962
+ else if (token.startsWith("-p=")) profile = token.slice(3);
2963
+ }
2964
+ return profile || void 0;
2965
+ }
2966
+ /**
2967
+ * Build the plugin slug from the known command path plus the unknown name,
2968
+ * so `tailor tailordb erd` resolves `tailor-tailordb-erd`.
2969
+ * @param commandPath - Parent command path preceding the unknown subcommand
2970
+ * @param name - Unknown subcommand name
2971
+ * @returns The plugin slug
2972
+ */
2973
+ function pluginSlug(commandPath, name) {
2974
+ return [...commandPath ?? [], name].join("-");
2975
+ }
2976
+ /**
2381
2977
  * Resolve and execute a plugin, forwarding stdio and propagating its exit code.
2382
2978
  * @param params - Dispatch parameters
2383
- * @param params.name - Plugin name (without the `<cli>-` prefix)
2384
- * @param params.args - Args to forward to the plugin
2385
- * @param params.cliName - The host CLI name
2386
- * @param params.profile - Active profile name, if any
2387
2979
  * @returns The plugin's exit code, or undefined when no matching plugin exists
2388
2980
  */
2389
2981
  async function dispatchPlugin(params) {
2390
- const slug = [...params.commandPath ?? [], params.name].join("-");
2982
+ const slug = pluginSlug(params.commandPath, params.name);
2391
2983
  const plugin = resolvePlugin(slug, params.cliName);
2392
2984
  if (!plugin) return;
2985
+ const profile = explicitProfileFromArgs(params.args) ?? params.profile;
2393
2986
  const env = {
2394
2987
  ...process.env,
2395
- ...await buildPluginEnv({ profile: params.profile })
2988
+ ...await buildPluginEnv({
2989
+ profile,
2990
+ skipPlatformContext: hasEnvFileFlag(params.args)
2991
+ })
2396
2992
  };
2397
2993
  const { command, args, shell } = buildSpawnTarget(plugin.path, params.args);
2398
2994
  return await new Promise((resolve) => {
@@ -2411,6 +3007,27 @@ async function dispatchPlugin(params) {
2411
3007
  });
2412
3008
  });
2413
3009
  }
3010
+ /**
3011
+ * Dispatch a plugin, printing an install hint when the subcommand maps to a
3012
+ * known plugin package that is not installed.
3013
+ * @param params - Dispatch parameters
3014
+ * @returns The plugin's exit code (1 after an install hint), or undefined when
3015
+ * the subcommand matches no plugin and no known package
3016
+ */
3017
+ async function dispatchPluginWithInstallHint(params) {
3018
+ const exitCode = await dispatchPlugin(params);
3019
+ if (exitCode !== void 0) return exitCode;
3020
+ const knownPackage = KNOWN_PLUGIN_PACKAGES[pluginSlug(params.commandPath, params.name)];
3021
+ if (!knownPackage) return;
3022
+ const fullCommand = [
3023
+ params.cliName,
3024
+ ...params.commandPath ?? [],
3025
+ params.name
3026
+ ].join(" ");
3027
+ logger.error(`"${fullCommand}" is provided by the ${knownPackage} CLI plugin, which is not installed.`);
3028
+ logger.info(`Install it with: npm install -D ${knownPackage}`);
3029
+ return 1;
3030
+ }
2414
3031
 
2415
3032
  //#endregion
2416
3033
  //#region src/cli/commands/plugin/list.ts
@@ -3213,10 +3830,10 @@ function lockPath(outputDir) {
3213
3830
  */
3214
3831
  function readLock(outputDir) {
3215
3832
  const file = lockPath(outputDir);
3216
- if (!fs$1.existsSync(file)) return null;
3833
+ if (!fs.existsSync(file)) return null;
3217
3834
  let parsed;
3218
3835
  try {
3219
- parsed = JSON.parse(fs$1.readFileSync(file, "utf-8"));
3836
+ parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
3220
3837
  } catch (cause) {
3221
3838
  throw new Error(`${LOCK_FILENAME} is not valid JSON. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor.lock) and re-run setup.`, { cause });
3222
3839
  }
@@ -3232,8 +3849,8 @@ function readLock(outputDir) {
3232
3849
  */
3233
3850
  function writeLock(outputDir, lock) {
3234
3851
  const file = lockPath(outputDir);
3235
- fs$1.mkdirSync(path.dirname(file), { recursive: true });
3236
- fs$1.writeFileSync(file, `${JSON.stringify(lock, null, 2)}\n`, "utf-8");
3852
+ fs.mkdirSync(path.dirname(file), { recursive: true });
3853
+ fs.writeFileSync(file, `${JSON.stringify(lock, null, 2)}\n`, "utf-8");
3237
3854
  }
3238
3855
  /**
3239
3856
  * Find a lock target by identity. Targets are identified by (kind,
@@ -3284,10 +3901,10 @@ const HEADER = `# Generated by \`tailor setup\` — managed by the Tailor SDK.
3284
3901
  * @returns Detected package manager, defaults to npm
3285
3902
  */
3286
3903
  function detectPackageManager(dir) {
3287
- if (fs$1.existsSync(path.join(dir, "pnpm-lock.yaml"))) return "pnpm";
3288
- if (fs$1.existsSync(path.join(dir, "yarn.lock"))) return "yarn";
3289
- if (fs$1.existsSync(path.join(dir, "bun.lockb")) || fs$1.existsSync(path.join(dir, "bun.lock"))) return "bun";
3290
- if (fs$1.existsSync(path.join(dir, "package-lock.json"))) return "npm";
3904
+ if (fs.existsSync(path.join(dir, "pnpm-lock.yaml"))) return "pnpm";
3905
+ if (fs.existsSync(path.join(dir, "yarn.lock"))) return "yarn";
3906
+ if (fs.existsSync(path.join(dir, "bun.lockb")) || fs.existsSync(path.join(dir, "bun.lock"))) return "bun";
3907
+ if (fs.existsSync(path.join(dir, "package-lock.json"))) return "npm";
3291
3908
  return "npm";
3292
3909
  }
3293
3910
  function block(content, name, keep) {
@@ -3673,9 +4290,8 @@ async function defaultLoadHasStaticWebsites$1(configPath) {
3673
4290
  const { config } = await loadConfig(configPath);
3674
4291
  return (config.staticWebsites?.length ?? 0) > 0;
3675
4292
  }
3676
- const WORKSPACE_NAME_RE = /^[a-z0-9-]+$/;
3677
4293
  function validateWorkspaceName(name) {
3678
- if (name.length < 3 || name.length > 63 || !WORKSPACE_NAME_RE.test(name) || name.startsWith("-") || name.endsWith("-")) throw new Error(`Invalid workspace name "${name}". Names must be 3-63 characters of lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen. Pass a valid name with --name.`);
4294
+ if (!workspaceNameSchema.safeParse(name).success) throw new Error(`Invalid workspace name "${name}". Names must be 3-63 characters of lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen. Pass a valid name with --name.`);
3679
4295
  }
3680
4296
  const BRANCH_RE = /^[A-Za-z0-9._/-]+$/;
3681
4297
  const TAG_PATTERN_RE = /^[A-Za-z0-9._/*?![\]-]+$/;
@@ -3718,13 +4334,13 @@ function resolveConfigPath(outputDir, dir) {
3718
4334
  const appDir = path.resolve(outputDir, dir);
3719
4335
  const rel = path.relative(outputDir, appDir);
3720
4336
  if (path.isAbsolute(dir) || escapesRoot$1(rel)) throw new Error(`--dir must be a relative path inside the repository (got "${dir}").`);
3721
- if (fs$1.existsSync(appDir)) {
3722
- const realAppDir = path.normalize(fs$1.realpathSync(appDir));
3723
- const realOutputDir = path.normalize(fs$1.realpathSync(outputDir));
4337
+ if (fs.existsSync(appDir)) {
4338
+ const realAppDir = path.normalize(fs.realpathSync(appDir));
4339
+ const realOutputDir = path.normalize(fs.realpathSync(outputDir));
3724
4340
  if (escapesRoot$1(path.relative(realOutputDir, realAppDir))) throw new Error(`--dir must resolve to a directory inside the repository (got "${dir}", which links outside it).`);
3725
4341
  }
3726
4342
  const configPath = path.join(appDir, "tailor.config.ts");
3727
- if (!fs$1.existsSync(configPath)) throw new Error(`tailor.config.ts not found at ${configPath}. Run this from your SDK project root, or pass the app directory with --dir.`);
4343
+ if (!fs.existsSync(configPath)) throw new Error(`tailor.config.ts not found at ${configPath}. Run this from your SDK project root, or pass the app directory with --dir.`);
3728
4344
  return configPath;
3729
4345
  }
3730
4346
  /**
@@ -3960,8 +4576,8 @@ async function setupTarget(options) {
3960
4576
  file: resolved.file
3961
4577
  });
3962
4578
  const existing = findTarget(lock, resolved.kind, resolved.workspaceName);
3963
- const fileExists = fs$1.existsSync(absFile);
3964
- const currentContent = fileExists ? fs$1.readFileSync(absFile, "utf-8") : null;
4579
+ const fileExists = fs.existsSync(absFile);
4580
+ const currentContent = fileExists ? fs.readFileSync(absFile, "utf-8") : null;
3965
4581
  const normalize = resolved.kind === "action" ? normalizeActionContent : void 0;
3966
4582
  const decision = decideAction({
3967
4583
  existing,
@@ -3979,8 +4595,8 @@ async function setupTarget(options) {
3979
4595
  const existingBody = extractBuildSiteRunBody(currentContent);
3980
4596
  if (existingBody !== null) contentToWrite = injectBuildSiteRunBody(contentToWrite, existingBody);
3981
4597
  }
3982
- fs$1.mkdirSync(path.dirname(absFile), { recursive: true });
3983
- fs$1.writeFileSync(absFile, contentToWrite, "utf-8");
4598
+ fs.mkdirSync(path.dirname(absFile), { recursive: true });
4599
+ fs.writeFileSync(absFile, contentToWrite, "utf-8");
3984
4600
  const newTarget = {
3985
4601
  kind: resolved.kind,
3986
4602
  workspaceName: resolved.workspaceName,
@@ -4084,21 +4700,21 @@ async function setupCoordinate(options) {
4084
4700
  file
4085
4701
  });
4086
4702
  const existing = findTarget(lock, "coordinate", coordinatorName);
4087
- const fileExists = fs$1.existsSync(absFile);
4703
+ const fileExists = fs.existsSync(absFile);
4088
4704
  const decision = decideAction({
4089
4705
  existing,
4090
4706
  fileExists,
4091
- currentContent: fileExists ? fs$1.readFileSync(absFile, "utf-8") : null,
4707
+ currentContent: fileExists ? fs.readFileSync(absFile, "utf-8") : null,
4092
4708
  force
4093
4709
  });
4094
4710
  if (decision.action === "conflict") throw new Error(`${file}: ${decision.reason}`);
4095
- fs$1.mkdirSync(path.dirname(absFile), { recursive: true });
4096
- fs$1.writeFileSync(absFile, render.content, "utf-8");
4711
+ fs.mkdirSync(path.dirname(absFile), { recursive: true });
4712
+ fs.writeFileSync(absFile, render.content, "utf-8");
4097
4713
  const tailorSetupFile = ".github/actions/tailor-setup/action.yml";
4098
4714
  const absTailorSetupFile = path.join(outputDir, tailorSetupFile);
4099
- if (!fs$1.existsSync(absTailorSetupFile)) {
4100
- fs$1.mkdirSync(path.dirname(absTailorSetupFile), { recursive: true });
4101
- fs$1.writeFileSync(absTailorSetupFile, renderTailorSetupAction({ packageManager }), "utf-8");
4715
+ if (!fs.existsSync(absTailorSetupFile)) {
4716
+ fs.mkdirSync(path.dirname(absTailorSetupFile), { recursive: true });
4717
+ fs.writeFileSync(absTailorSetupFile, renderTailorSetupAction({ packageManager }), "utf-8");
4102
4718
  logger.success(`Generated ${styles.path(tailorSetupFile)}`);
4103
4719
  }
4104
4720
  const newTarget = {
@@ -4225,13 +4841,13 @@ function resolveWithinRoot(outputDir, relPath) {
4225
4841
  const abs = path.join(outputDir, relPath);
4226
4842
  if (escapesRoot(path.relative(outputDir, abs))) return null;
4227
4843
  try {
4228
- if (escapesRoot(path.relative(fs$1.realpathSync(outputDir), fs$1.realpathSync(abs)))) return null;
4844
+ if (escapesRoot(path.relative(fs.realpathSync(outputDir), fs.realpathSync(abs)))) return null;
4229
4845
  } catch {}
4230
4846
  return abs;
4231
4847
  }
4232
4848
  function readHash(absFile, normalize) {
4233
4849
  try {
4234
- const content = fs$1.readFileSync(absFile, "utf-8");
4850
+ const content = fs.readFileSync(absFile, "utf-8");
4235
4851
  return hashContent(normalize ? normalize(content) : content);
4236
4852
  } catch {
4237
4853
  return null;
@@ -4272,7 +4888,7 @@ async function checkGitHub(options) {
4272
4888
  }
4273
4889
  const slackToken = Boolean(process.env["TAILOR_SLACK_BOT_TOKEN"]);
4274
4890
  if (slackToken !== Boolean(process.env["TAILOR_SLACK_CHANNEL_ID"])) throw new Error(`Slack is partially configured: ${slackToken ? "TAILOR_SLACK_BOT_TOKEN is set" : "TAILOR_SLACK_CHANNEL_ID is set"} but not both. TAILOR_SLACK_BOT_TOKEN (secret) and TAILOR_SLACK_CHANNEL_ID (variable) must be set together, or neither should be set.`);
4275
- const exists = options.configExistsAt ?? ((p) => fs$1.existsSync(p));
4891
+ const exists = options.configExistsAt ?? ((p) => fs.existsSync(p));
4276
4892
  const defaultBranch = detectDefaultBranchSafe(outputDir, options.gitRunner);
4277
4893
  const loadErdNamespaces = options.loadErdNamespaces ?? defaultLoadErdNamespaces;
4278
4894
  const loadHasMigrations = options.loadHasMigrations ?? defaultLoadHasMigrations;
@@ -4361,10 +4977,10 @@ async function setupDelete(options) {
4361
4977
  const remaining = [...lock.targets];
4362
4978
  try {
4363
4979
  for (const { relPath, absPath, target } of matches) {
4364
- fs$1.rmSync(absPath, { force: true });
4980
+ fs.rmSync(absPath, { force: true });
4365
4981
  if (target.kind === "action") {
4366
4982
  const dir = path.dirname(absPath);
4367
- if (fs$1.existsSync(dir) && fs$1.readdirSync(dir).length === 0) fs$1.rmdirSync(dir);
4983
+ if (fs.existsSync(dir) && fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);
4368
4984
  }
4369
4985
  const index = remaining.findIndex((t) => targetKey(t) === targetKey(target));
4370
4986
  if (index !== -1) remaining.splice(index, 1);
@@ -4569,212 +5185,15 @@ const setupCommand = defineCommand({
4569
5185
  });
4570
5186
 
4571
5187
  //#endregion
4572
- //#region src/cli/shared/progress.ts
4573
- /**
4574
- * Create a simple progress reporter that writes updates to stderr.
4575
- * @param label - Label to prefix progress output
4576
- * @param total - Total number of steps
4577
- * @returns Progress helpers
4578
- */
4579
- function createProgress(label, total) {
4580
- let current = 0;
4581
- const update = () => {
4582
- current += 1;
4583
- const percent = Math.round(current / total * 100);
4584
- process.stderr.write(`\r${label} ${current}/${total} (${percent}%)`);
4585
- };
4586
- const finish = () => {
4587
- process.stderr.write("\n");
4588
- };
4589
- return {
4590
- update,
4591
- finish
4592
- };
4593
- }
4594
- /**
4595
- * Wrap a promise with a timeout, rejecting if the timeout elapses first.
4596
- * @template T
4597
- * @param p - Promise to await
4598
- * @param ms - Timeout in milliseconds
4599
- * @param message - Error message on timeout
4600
- * @returns Result of the original promise if it completes in time
4601
- */
4602
- async function withTimeout(p, ms, message) {
4603
- return await Promise.race([p, setTimeout$1(ms).then(() => {
4604
- throw new Error(message);
4605
- })]);
4606
- }
4607
-
4608
- //#endregion
4609
- //#region src/cli/commands/staticwebsite/deploy.ts
4610
- const CHUNK_SIZE = 64 * 1024;
4611
- const IGNORED_FILES = /* @__PURE__ */ new Set([
4612
- ".DS_Store",
4613
- "thumbs.db",
4614
- "desktop.ini"
4615
- ]);
4616
- function shouldIgnoreFile(filePath) {
4617
- const fileName = path.basename(filePath).toLowerCase();
4618
- return IGNORED_FILES.has(fileName);
4619
- }
4620
- /**
4621
- * Deploy a static website by creating a deployment, uploading files, and publishing it.
4622
- * @param client - Operator client instance
4623
- * @param workspaceId - Workspace ID
4624
- * @param name - Static website name
4625
- * @param distDir - Directory containing static site files
4626
- * @param showProgress - Whether to show upload progress
4627
- * @returns Deployment result with URL and skipped files
4628
- */
4629
- async function deployStaticWebsite(client, workspaceId, name, distDir, showProgress = true) {
4630
- const { deploymentId } = await client.createDeployment({
4631
- workspaceId,
4632
- name
4633
- });
4634
- if (!deploymentId) throw new Error("createDeployment returned empty deploymentId");
4635
- const skippedFiles = await uploadDirectory(client, workspaceId, deploymentId, distDir, showProgress);
4636
- const { url } = await client.publishDeployment({
4637
- workspaceId,
4638
- deploymentId
4639
- });
4640
- if (!url) throw new Error("publishDeployment returned empty url");
4641
- return {
4642
- url,
4643
- skippedFiles
4644
- };
4645
- }
4646
- async function uploadDirectory(client, workspaceId, deploymentId, rootDir, showProgress) {
4647
- const files = await collectFiles(rootDir);
4648
- if (files.length === 0) {
4649
- logger.warn(`No files found under ${rootDir}`);
4650
- return [];
4651
- }
4652
- const limit = pLimit(5);
4653
- const total = files.length;
4654
- const progress = showProgress ? createProgress("Uploading files", total) : void 0;
4655
- const skippedFiles = [];
4656
- await Promise.all(files.map((relativePath) => limit(async () => {
4657
- await uploadSingleFile(client, workspaceId, deploymentId, rootDir, relativePath, skippedFiles);
4658
- if (progress) progress.update();
4659
- })));
4660
- if (progress) progress.finish();
4661
- return skippedFiles;
4662
- }
4663
- /**
4664
- * Recursively collect all deployable files under the given directory.
4665
- * @param rootDir - Root directory to scan
4666
- * @param currentDir - Current relative directory (for recursion)
4667
- * @returns List of file paths relative to rootDir
4668
- */
4669
- async function collectFiles(rootDir, currentDir = "") {
4670
- const dirPath = path.join(rootDir, currentDir);
4671
- const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
4672
- const files = [];
4673
- for (const entry of entries) {
4674
- const rel = path.join(currentDir, entry.name);
4675
- if (entry.isDirectory()) {
4676
- const sub = await collectFiles(rootDir, rel);
4677
- files.push(...sub);
4678
- } else if (entry.isFile() && !entry.isSymbolicLink() && !shouldIgnoreFile(rel)) files.push(rel);
4679
- }
4680
- return files;
4681
- }
4682
- async function uploadSingleFile(client, workspaceId, deploymentId, rootDir, filePath, skippedFiles) {
4683
- const absPath = path.join(rootDir, filePath);
4684
- const mime = lookup(filePath);
4685
- if (!mime) {
4686
- skippedFiles.push(`${filePath} (unsupported content type; no MIME mapping found)`);
4687
- return;
4688
- }
4689
- const contentType = mime;
4690
- const readStream = fs.createReadStream(absPath, { highWaterMark: CHUNK_SIZE });
4691
- async function* requestStream() {
4692
- yield { payload: {
4693
- case: "initialMetadata",
4694
- value: {
4695
- workspaceId,
4696
- deploymentId,
4697
- filePath,
4698
- contentType
4699
- }
4700
- } };
4701
- for await (const chunk of readStream) yield { payload: {
4702
- case: "chunkData",
4703
- value: chunk
4704
- } };
4705
- }
4706
- async function uploadWithLogging() {
4707
- try {
4708
- await client.uploadFile(requestStream());
4709
- } catch (error) {
4710
- if (error instanceof ConnectError && error.code === Code.InvalidArgument) {
4711
- skippedFiles.push(`${filePath} (server rejected file as invalid: ${error.message})`);
4712
- return;
4713
- }
4714
- throw error;
4715
- }
4716
- }
4717
- await withTimeout(uploadWithLogging(), 2 * 6e4, `Upload timed out for "${filePath}"`);
4718
- }
4719
- /**
4720
- * Log skipped files after a deployment, including reasons for skipping.
4721
- * @param skippedFiles - List of skipped file descriptions
4722
- */
4723
- function logSkippedFiles(skippedFiles) {
4724
- if (skippedFiles.length === 0) return;
4725
- logger.warn("Deployment completed, but some files failed to upload. These files may have unsupported content types or other validation issues. Please review the list below:");
4726
- for (const file of skippedFiles) logger.log(` - ${file}`);
4727
- }
4728
- const deployCommand = defineAppCommand({
4729
- name: "deploy",
4730
- description: "Deploy a static website from a local build directory.",
4731
- args: z.strictObject({
4732
- ...workspaceArgs,
4733
- name: arg(z.string(), {
4734
- alias: "n",
4735
- description: "Static website name"
4736
- }),
4737
- dir: arg(z.string(), {
4738
- alias: "d",
4739
- description: "Path to the static website files",
4740
- completion: { type: "directory" }
4741
- })
4742
- }),
4743
- run: async (args) => {
4744
- await assertWritable({ profile: args.profile });
4745
- logger.info(`Deploying static website "${args.name}" from directory: ${args.dir}`);
4746
- const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
4747
- const name = args.name;
4748
- const dir = path.resolve(process.cwd(), args.dir);
4749
- const workspaceId = await loadWorkspaceId({
4750
- workspaceId: args["workspace-id"],
4751
- profile: args.profile
4752
- });
4753
- if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) throw new Error(`Directory not found or not a directory: ${dir}`);
4754
- const { url, skippedFiles } = await withTimeout(deployStaticWebsite(client, workspaceId, name, dir, !args.json), 10 * 6e4, "Deployment timed out after 10 minutes.");
4755
- if (args.json) logger.out({
4756
- name,
4757
- workspaceId,
4758
- url,
4759
- skippedFiles
4760
- });
4761
- else {
4762
- logger.success(`Static website "${name}" deployed successfully. URL: ${url}`);
4763
- logSkippedFiles(skippedFiles);
4764
- }
4765
- }
4766
- });
4767
-
4768
- //#endregion
4769
- //#region src/cli/commands/staticwebsite/domain/status.ts
4770
- const statusLabels = {
4771
- [CustomDomainStatus.UNSPECIFIED]: "unspecified",
4772
- [CustomDomainStatus.PENDING]: "pending",
4773
- [CustomDomainStatus.VERIFYING]: "verifying",
4774
- [CustomDomainStatus.CERT_ISSUED]: "cert_issued",
4775
- [CustomDomainStatus.ACTIVE]: "active",
4776
- [CustomDomainStatus.FAILED]: "failed"
4777
- };
5188
+ //#region src/cli/commands/staticwebsite/domain/status.ts
5189
+ const statusLabels = {
5190
+ [CustomDomainStatus.UNSPECIFIED]: "unspecified",
5191
+ [CustomDomainStatus.PENDING]: "pending",
5192
+ [CustomDomainStatus.VERIFYING]: "verifying",
5193
+ [CustomDomainStatus.CERT_ISSUED]: "cert_issued",
5194
+ [CustomDomainStatus.ACTIVE]: "active",
5195
+ [CustomDomainStatus.FAILED]: "failed"
5196
+ };
4778
5197
 
4779
5198
  //#endregion
4780
5199
  //#region src/cli/commands/staticwebsite/domain/get.ts
@@ -4975,7 +5394,7 @@ const staticwebsiteCommand = defineCommand({
4975
5394
  name: "staticwebsite",
4976
5395
  description: "Manage static websites in your workspace.",
4977
5396
  subCommands: {
4978
- deploy: deployCommand,
5397
+ deploy: deployCommand$1,
4979
5398
  domain: domainCommand,
4980
5399
  list: listCommand$2,
4981
5400
  get: getCommand
@@ -4985,1201 +5404,6 @@ const staticwebsiteCommand = defineCommand({
4985
5404
  }
4986
5405
  });
4987
5406
 
4988
- //#endregion
4989
- //#region src/cli/commands/tailordb/erd/local-schema.ts
4990
- /**
4991
- * Resolve TailorDB namespaces that need local type loading for ERD generation.
4992
- * @param config - Loaded Tailor config.
4993
- * @param options - Namespace selection options.
4994
- * @returns Namespace names to load, or undefined to load all owned namespaces.
4995
- */
4996
- function resolveLocalErdSchemaNamespaces(config, options) {
4997
- if (options.namespaces) return options.namespaces;
4998
- if (!options.requireErdSite) return;
4999
- return Object.entries(config.db ?? {}).flatMap(([namespace, dbConfig]) => "external" in dbConfig || !dbConfig.erdSite ? [] : [namespace]);
5000
- }
5001
- /**
5002
- * Load local TailorDB namespaces exactly as SDK generation/deploy sees them.
5003
- * @param options - Local schema loading options.
5004
- * @returns Loaded TailorDB namespace data.
5005
- */
5006
- async function loadLocalErdSchema(options) {
5007
- const { config, plugins } = await loadConfig(options.configPath);
5008
- await generateUserTypes({
5009
- config,
5010
- configPath: config.path
5011
- });
5012
- const application = defineApplication({
5013
- config,
5014
- pluginManager: plugins.length > 0 ? new PluginManager(plugins) : void 0
5015
- });
5016
- const namespaceNames = resolveLocalErdSchemaNamespaces(config, {
5017
- namespaces: options.namespaces,
5018
- requireErdSite: options.requireErdSite
5019
- });
5020
- const namespaceFilter = namespaceNames ? new Set(namespaceNames) : void 0;
5021
- const services = namespaceFilter ? application.tailorDBServices.filter((db) => namespaceFilter.has(db.namespace)) : application.tailorDBServices;
5022
- if (namespaceFilter && services.length !== namespaceFilter.size) {
5023
- const available = application.tailorDBServices.map((db) => db.namespace).join(", ");
5024
- const requested = [...namespaceFilter].join(", ");
5025
- throw new Error(`TailorDB namespace "${requested}" not found in local config.db.` + (available ? ` Available owned namespaces: ${available}` : ""));
5026
- }
5027
- const namespaces = [];
5028
- for (const db of services) {
5029
- await db.loadTypes();
5030
- await db.processNamespacePlugins();
5031
- namespaces.push({
5032
- namespace: db.namespace,
5033
- types: { ...db.types },
5034
- sourceInfo: new Map(Object.entries(db.typeSourceInfo)),
5035
- pluginAttachments: db.pluginAttachments
5036
- });
5037
- }
5038
- return {
5039
- config,
5040
- namespaces
5041
- };
5042
- }
5043
-
5044
- //#endregion
5045
- //#region src/cli/commands/tailordb/erd/schema.ts
5046
- const CLEAN_ROOM_NOTES = [
5047
- "Generated by a TailorDB-specific viewer implementation.",
5048
- "The implementation is based on TailorDB schema contracts, public Liam documentation, Liam CLI help, and black-box generated-output observation.",
5049
- "It does not copy Liam source code, generated JavaScript/CSS, parser internals, or layout internals."
5050
- ];
5051
- function buildRevision(schema) {
5052
- return hashContent$1(JSON.stringify(schema)).slice(0, 16);
5053
- }
5054
- function toTypeSource(source) {
5055
- if (!source) return void 0;
5056
- if (isPluginGeneratedType(source)) return {
5057
- kind: "plugin",
5058
- exportName: source.exportName,
5059
- pluginId: source.pluginId,
5060
- pluginImportPath: source.pluginImportPath,
5061
- originalExportName: source.originalExportName,
5062
- generatedTypeKind: source.generatedTypeKind,
5063
- namespace: source.namespace
5064
- };
5065
- return {
5066
- kind: "user",
5067
- exportName: source.exportName
5068
- };
5069
- }
5070
- function toRelationships(relationships) {
5071
- return Object.entries(relationships).toSorted(([a], [b]) => a.localeCompare(b)).map(([name, relationship]) => ({
5072
- name,
5073
- targetType: relationship.targetType,
5074
- targetField: relationship.targetField,
5075
- sourceField: relationship.sourceField,
5076
- isArray: relationship.isArray,
5077
- ...relationship.description && { description: relationship.description }
5078
- }));
5079
- }
5080
- function toIndexes(type) {
5081
- return Object.entries(type.indexes ?? {}).toSorted(([a], [b]) => a.localeCompare(b)).map(([name, index]) => ({
5082
- name,
5083
- fields: [...index.fields],
5084
- unique: index.unique === true
5085
- }));
5086
- }
5087
- function toColumnRelation(options) {
5088
- const { fieldConfig, parsedField } = options;
5089
- if (!fieldConfig.foreignKey || !fieldConfig.foreignKeyType) return;
5090
- const required = fieldConfig.required !== false;
5091
- const kind = parsedField?.relation || fieldConfig.rawRelation ? "relation" : "foreignKey";
5092
- return {
5093
- targetTable: fieldConfig.foreignKeyType,
5094
- targetColumn: fieldConfig.foreignKeyField || "id",
5095
- kind,
5096
- required,
5097
- ...fieldConfig.rawRelation?.type && { relationType: fieldConfig.rawRelation.type },
5098
- ...parsedField?.relation?.forwardName && { forwardName: parsedField.relation.forwardName },
5099
- ...parsedField?.relation?.backwardName && { backwardName: parsedField.relation.backwardName }
5100
- };
5101
- }
5102
- function toColumn(options) {
5103
- const { fieldName, fieldConfig } = options;
5104
- const indexNames = options.indexEntries.filter(([, index]) => index.fields.includes(fieldName)).map(([name]) => name);
5105
- const uniqueIndexNames = options.indexEntries.filter(([, index]) => index.unique && index.fields.includes(fieldName)).map(([name]) => name);
5106
- const enumValues = fieldConfig.allowedValues?.map((value) => value.value) ?? [];
5107
- const enumValueDescriptions = Object.fromEntries((fieldConfig.allowedValues ?? []).flatMap((value) => value.description ? [[value.value, value.description]] : []));
5108
- const nestedFields = Object.entries(fieldConfig.fields ?? {}).map(([nestedName, nestedConfig]) => toColumn({
5109
- fieldName: nestedName,
5110
- fieldConfig: nestedConfig,
5111
- indexEntries: []
5112
- }));
5113
- const relation = toColumnRelation(options);
5114
- return {
5115
- name: fieldName,
5116
- type: fieldConfig.type || "string",
5117
- required: fieldConfig.required !== false,
5118
- array: fieldConfig.array === true,
5119
- ...fieldConfig.description && { description: fieldConfig.description },
5120
- ...fieldConfig.unique && { unique: true },
5121
- ...(fieldConfig.index || indexNames.length > 0) && { index: true },
5122
- ...indexNames.length > 0 && { indexNames },
5123
- ...uniqueIndexNames.length > 0 && { uniqueIndexNames },
5124
- ...enumValues.length > 0 && { enumValues },
5125
- ...Object.keys(enumValueDescriptions).length > 0 && { enumValueDescriptions },
5126
- ...fieldConfig.vector && { vector: true },
5127
- ...fieldConfig.serial && { serial: { ...fieldConfig.serial } },
5128
- ...fieldConfig.scale !== void 0 && { scale: fieldConfig.scale },
5129
- ...fieldConfig.validate?.length && { validations: fieldConfig.validate.length },
5130
- ...fieldConfig.hooks && { hooks: {
5131
- ...fieldConfig.hooks.create && { create: true },
5132
- ...fieldConfig.hooks.update && { update: true }
5133
- } },
5134
- ...nestedFields.length > 0 && { fields: nestedFields },
5135
- ...relation && { relation }
5136
- };
5137
- }
5138
- function toTable(type, source) {
5139
- const indexes = toIndexes(type);
5140
- const indexEntries = indexes.map((index) => [index.name, index]);
5141
- const fieldColumns = Object.entries(type.fields).filter(([fieldName]) => fieldName !== "id").map(([fieldName, field]) => toColumn({
5142
- fieldName,
5143
- fieldConfig: field.config,
5144
- parsedField: field,
5145
- indexEntries
5146
- }));
5147
- const typeSource = toTypeSource(source);
5148
- return {
5149
- name: type.name,
5150
- pluralForm: type.pluralForm,
5151
- ...type.description && { description: type.description },
5152
- ...typeSource && { source: typeSource },
5153
- columns: [{
5154
- name: "id",
5155
- type: "uuid",
5156
- required: true,
5157
- array: false,
5158
- primaryKey: true,
5159
- unique: true
5160
- }, ...fieldColumns],
5161
- indexes,
5162
- forwardRelationships: toRelationships(type.forwardRelationships),
5163
- backwardRelationships: toRelationships(type.backwardRelationships)
5164
- };
5165
- }
5166
- function toRelation(sourceTable, field) {
5167
- const relation = toColumnRelation({
5168
- fieldName: field.name,
5169
- fieldConfig: field.config,
5170
- parsedField: field,
5171
- indexEntries: []
5172
- });
5173
- if (!relation) return void 0;
5174
- return {
5175
- name: `${sourceTable}.${field.name}->${relation.targetTable}.${relation.targetColumn}`,
5176
- sourceTable,
5177
- sourceColumns: [field.name],
5178
- targetTable: relation.targetTable,
5179
- targetColumns: [relation.targetColumn],
5180
- required: relation.required,
5181
- unique: field.config.unique === true || field.relation?.unique === true,
5182
- kind: relation.kind,
5183
- ...relation.relationType && { relationType: relation.relationType },
5184
- ...relation.forwardName && { forwardName: relation.forwardName },
5185
- ...relation.backwardName && { backwardName: relation.backwardName }
5186
- };
5187
- }
5188
- function buildRelations(types) {
5189
- const relations = [];
5190
- for (const type of Object.values(types)) for (const field of Object.values(type.fields)) {
5191
- const relation = toRelation(type.name, field);
5192
- if (relation) relations.push(relation);
5193
- }
5194
- return relations.toSorted((a, b) => a.name.localeCompare(b.name));
5195
- }
5196
- /**
5197
- * Build the TailorDB ERD viewer schema for one namespace.
5198
- * @param options - Schema build options.
5199
- * @returns TailorDB ERD viewer schema.
5200
- */
5201
- function buildTailorDbErdSchema(options) {
5202
- const { namespaceData } = options;
5203
- const tables = Object.values(namespaceData.types).toSorted((a, b) => a.name.localeCompare(b.name)).map((type) => toTable(type, namespaceData.sourceInfo.get(type.name)));
5204
- const schemaWithoutRevision = {
5205
- version: 1,
5206
- namespace: namespaceData.namespace,
5207
- source: options.source ?? "local",
5208
- cleanRoom: {
5209
- implementation: "tailor",
5210
- notes: CLEAN_ROOM_NOTES
5211
- },
5212
- tables,
5213
- relations: buildRelations(namespaceData.types)
5214
- };
5215
- return {
5216
- ...schemaWithoutRevision,
5217
- generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
5218
- revision: buildRevision(schemaWithoutRevision)
5219
- };
5220
- }
5221
-
5222
- //#endregion
5223
- //#region src/cli/commands/tailordb/erd/utils.ts
5224
- /**
5225
- * Initialize shared ERD command behavior.
5226
- */
5227
- function initErdCommand() {
5228
- logBetaWarning("tailordb erd");
5229
- }
5230
- /**
5231
- * Initialize platform context for ERD deployment.
5232
- * @param args - CLI arguments.
5233
- * @returns Initialized deploy context.
5234
- */
5235
- async function initErdDeployContext(args) {
5236
- initErdCommand();
5237
- return {
5238
- client: await initOperatorClient(await loadAccessToken({ profile: args.profile })),
5239
- workspaceId: await loadWorkspaceId({
5240
- workspaceId: args.workspaceId,
5241
- profile: args.profile
5242
- })
5243
- };
5244
- }
5245
-
5246
- //#endregion
5247
- //#region src/cli/commands/tailordb/erd/viewer.ts
5248
- const VIEWER_ASSETS_DIR = "erd-viewer-assets";
5249
- const STYLES_LINK = "<link rel=\"stylesheet\" href=\"./styles.css\" />";
5250
- const APP_SCRIPT = "<script src=\"./app.js\" type=\"module\"><\/script>";
5251
- function escapeHtml(value) {
5252
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
5253
- }
5254
- function assetDirCandidates() {
5255
- const currentDir = path.dirname(fileURLToPath(import.meta.url));
5256
- return [
5257
- path.join(currentDir, "viewer-assets"),
5258
- path.join(currentDir, VIEWER_ASSETS_DIR),
5259
- path.join(currentDir, "commands", "tailordb", "erd", VIEWER_ASSETS_DIR),
5260
- path.resolve(process.cwd(), "packages/sdk/src/cli/commands/tailordb/erd/viewer-assets")
5261
- ];
5262
- }
5263
- /**
5264
- * Resolve the packaged ERD viewer asset directory.
5265
- * @returns Absolute path to the viewer asset directory.
5266
- */
5267
- function resolveViewerAssetsDir() {
5268
- for (const candidate of assetDirCandidates()) if (fs$1.existsSync(path.join(candidate, "index.html"))) return candidate;
5269
- throw new Error(`ERD viewer assets not found. Checked: ${assetDirCandidates().join(", ")}`);
5270
- }
5271
- /**
5272
- * Build the self-contained ERD viewer HTML document. CSS, JS, and the schema
5273
- * are inlined as separately extractable blocks: a `<style>` element, a
5274
- * `<script type="module">`, and a `<script type="application/json"
5275
- * id="erd-schema">` data block. This renders without any sibling asset files
5276
- * and lets external tooling (including the ERD diff command) pull out the
5277
- * schema via `JSON.parse`.
5278
- * @param options - Viewer build options.
5279
- * @returns The self-contained HTML document.
5280
- */
5281
- function buildViewerHtml(options) {
5282
- const assetsDir = resolveViewerAssetsDir();
5283
- const html = fs$1.readFileSync(path.join(assetsDir, "index.html"), "utf8");
5284
- const css = fs$1.readFileSync(path.join(assetsDir, "styles.css"), "utf8");
5285
- const appJs = fs$1.readFileSync(path.join(assetsDir, "app.js"), "utf8");
5286
- if (!html.includes(STYLES_LINK) || !html.includes(APP_SCRIPT)) throw new Error("ERD viewer index.html is missing expected asset references for inlining.");
5287
- const embedScript = `<script type="application/json" id="erd-schema">${JSON.stringify(options.schema).replaceAll("<", "\\u003c")}<\/script>`;
5288
- const currentSchemaScript = options.currentSchema === void 0 ? "" : `\n <script type="application/json" id="erd-current-schema">${JSON.stringify(options.currentSchema).replaceAll("<", "\\u003c")}<\/script>`;
5289
- const diffScript = options.diff === void 0 ? "" : `\n <script type="application/json" id="erd-diff">${JSON.stringify(options.diff).replaceAll("<", "\\u003c")}<\/script>`;
5290
- const inlineScript = `<script type="module">\n${appJs.replace(/<\/script/gi, "<\\/script")}\n<\/script>`;
5291
- return html.replace("<title>TailorDB ERD</title>", () => `<title>${escapeHtml(options.title ?? "TailorDB ERD")}</title>`).replace(STYLES_LINK, () => `<style>\n${css}\n</style>`).replace(APP_SCRIPT, () => `${embedScript}${currentSchemaScript}${diffScript}\n ${inlineScript}`);
5292
- }
5293
- /**
5294
- * Write the self-contained TailorDB ERD viewer to `<distDir>/index.html`.
5295
- * @param options - Viewer dist write options.
5296
- */
5297
- function writeViewerDist(options) {
5298
- fs$1.rmSync(options.distDir, {
5299
- recursive: true,
5300
- force: true
5301
- });
5302
- fs$1.mkdirSync(options.distDir, { recursive: true });
5303
- fs$1.writeFileSync(path.join(options.distDir, "index.html"), buildViewerHtml({ schema: options.schema }), "utf8");
5304
- }
5305
-
5306
- //#endregion
5307
- //#region src/cli/commands/tailordb/erd/export.ts
5308
- const DEFAULT_ERD_BASE_DIR$1 = ".tailor/erd";
5309
- function getErdSite(context, namespace) {
5310
- const dbConfig = context.config.db?.[namespace];
5311
- if (!dbConfig || "external" in dbConfig) return;
5312
- return dbConfig.erdSite;
5313
- }
5314
- function resolveExplicitTarget(options) {
5315
- const namespaceData = options.context.namespaces.find((candidate) => candidate.namespace === options.namespace);
5316
- if (!namespaceData) {
5317
- const available = options.context.namespaces.map((candidate) => candidate.namespace).join(", ");
5318
- throw new Error(`TailorDB namespace "${options.namespace}" not found in local config.db.` + (available ? ` Available owned namespaces: ${available}` : ""));
5319
- }
5320
- const erdSite = getErdSite(options.context, namespaceData.namespace);
5321
- if (options.requireErdSite && !erdSite) throw new Error(`No erdSite configured for namespace "${namespaceData.namespace}". Add erdSite: "<static-website-name>" to db.${namespaceData.namespace} in tailor.config.ts.`);
5322
- return toTarget(options.outputDir, namespaceData, erdSite);
5323
- }
5324
- function resolveAllTargets(options) {
5325
- const namespaces = options.context.namespaces.filter((namespaceData) => !options.requireErdSite || getErdSite(options.context, namespaceData.namespace));
5326
- if (namespaces.length === 0) throw new Error(options.requireErdSite ? "No namespaces with erdSite configured found. Add erdSite: \"<static-website-name>\" to db.<namespace> in tailor.config.ts." : "No TailorDB namespaces found in config. Please define db services in tailor.config.ts.");
5327
- logger.info(`Found ${namespaces.length} namespace(s)${options.requireErdSite ? " with erdSite configured" : ""}.`);
5328
- return namespaces.map((namespaceData) => toTarget(options.outputDir, namespaceData, getErdSite(options.context, namespaceData.namespace)));
5329
- }
5330
- function toTarget(outputDir, namespaceData, erdSite) {
5331
- return {
5332
- namespaceData,
5333
- erdSite,
5334
- distDir: path.join(outputDir, namespaceData.namespace, "dist")
5335
- };
5336
- }
5337
- function resolveTargets(options) {
5338
- if (options.namespace) return [resolveExplicitTarget(options)];
5339
- return resolveAllTargets(options);
5340
- }
5341
- function prepareErdBuild(target) {
5342
- writeViewerDist({
5343
- schema: buildTailorDbErdSchema({ namespaceData: target.namespaceData }),
5344
- distDir: target.distDir
5345
- });
5346
- const relativePath = path.relative(process.cwd(), target.distDir);
5347
- logger.success(`Built ERD to ${relativePath}`);
5348
- return {
5349
- namespace: target.namespaceData.namespace,
5350
- erdSite: target.erdSite,
5351
- distDir: target.distDir
5352
- };
5353
- }
5354
- /**
5355
- * Prepare TailorDB ERD static viewer builds for one or more namespaces.
5356
- * @param options - Build options.
5357
- * @returns Build results by namespace.
5358
- */
5359
- async function prepareErdBuilds(options) {
5360
- return prepareErdBuildsFromContext({
5361
- context: await loadLocalErdSchema({
5362
- configPath: options.configPath,
5363
- namespaces: options.namespace ? [options.namespace] : void 0,
5364
- requireErdSite: options.requireErdSite
5365
- }),
5366
- namespace: options.namespace,
5367
- outputDir: options.outputDir,
5368
- requireErdSite: options.requireErdSite
5369
- });
5370
- }
5371
- /**
5372
- * Prepare TailorDB ERD static viewer builds from an already loaded schema context.
5373
- * @param options - Build options.
5374
- * @returns Build results by namespace.
5375
- */
5376
- function prepareErdBuildsFromContext(options) {
5377
- const outputDir = path.resolve(process.cwd(), options.outputDir ?? DEFAULT_ERD_BASE_DIR$1);
5378
- return resolveTargets({
5379
- context: options.context,
5380
- namespace: options.namespace,
5381
- outputDir,
5382
- requireErdSite: options.requireErdSite
5383
- }).map((target) => prepareErdBuild(target));
5384
- }
5385
- const erdExportCommand = defineAppCommand({
5386
- name: "export",
5387
- description: "Export TailorDB ERD static viewer from local TailorDB schema.",
5388
- args: z.strictObject({
5389
- ...configArg,
5390
- namespace: arg(z.string().optional(), {
5391
- alias: "n",
5392
- description: "TailorDB namespace name (optional if only one namespace is defined in config)"
5393
- }),
5394
- output: arg(z.string().default(DEFAULT_ERD_BASE_DIR$1), {
5395
- alias: "o",
5396
- description: "Output directory path for TailorDB ERD viewer files (writes to `<outputDir>/<namespace>/dist`)",
5397
- completion: { type: "directory" }
5398
- })
5399
- }),
5400
- run: async (args) => {
5401
- initErdCommand();
5402
- const results = await prepareErdBuilds({
5403
- configPath: args.config,
5404
- namespace: args.namespace,
5405
- outputDir: args.output
5406
- });
5407
- logger.newline();
5408
- if (args.json) logger.out(results.map((result) => ({
5409
- namespace: result.namespace,
5410
- distDir: result.distDir
5411
- })));
5412
- else for (const result of results) {
5413
- logger.out(`Exported ERD for namespace "${result.namespace}"`);
5414
- logger.out(` - ERD viewer: ${path.join(result.distDir, "index.html")}`);
5415
- }
5416
- }
5417
- });
5418
-
5419
- //#endregion
5420
- //#region src/cli/commands/tailordb/erd/deploy.ts
5421
- const erdDeployCommand = defineAppCommand({
5422
- name: "deploy",
5423
- description: "Deploy ERD static website for TailorDB namespace(s).",
5424
- args: z.strictObject({
5425
- ...deploymentArgs,
5426
- namespace: arg(z.string().optional(), {
5427
- alias: "n",
5428
- description: "TailorDB namespace name (optional - deploys all namespaces with erdSite if omitted)"
5429
- })
5430
- }),
5431
- run: async (args) => {
5432
- await assertWritable({ profile: args.profile });
5433
- const { client, workspaceId } = await initErdDeployContext(args);
5434
- const buildResults = await prepareErdBuilds({
5435
- configPath: args.config,
5436
- namespace: args.namespace,
5437
- requireErdSite: true
5438
- });
5439
- const deployResults = await Promise.all(buildResults.map(async (result) => {
5440
- if (!result.erdSite) throw new Error(`No erdSite configured for namespace "${result.namespace}". Add erdSite: "<static-website-name>" to db.${result.namespace} in tailor.config.ts.`);
5441
- if (!args.json) logger.info(`Deploying ERD for namespace "${result.namespace}" to site "${result.erdSite}"...`);
5442
- const { url, skippedFiles } = await deployStaticWebsite(client, workspaceId, result.erdSite, result.distDir, !args.json);
5443
- return {
5444
- namespace: result.namespace,
5445
- erdSite: result.erdSite,
5446
- url,
5447
- skippedFiles
5448
- };
5449
- }));
5450
- logger.newline();
5451
- if (args.json) logger.out(deployResults);
5452
- else for (const result of deployResults) {
5453
- logSkippedFiles(result.skippedFiles);
5454
- logger.newline();
5455
- logger.success(`ERD site "${result.erdSite}" deployed successfully.`);
5456
- logger.out(result.url);
5457
- }
5458
- }
5459
- });
5460
-
5461
- //#endregion
5462
- //#region src/cli/commands/tailordb/erd/diff.ts
5463
- const SCHEMA_BLOCK_PATTERN = /<script type="application\/json" id="erd-schema">([\s\S]*?)<\/script>/;
5464
- function stableValue(value) {
5465
- if (Array.isArray(value)) return value.map((item) => stableValue(item));
5466
- if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, stableValue(item)]));
5467
- return value;
5468
- }
5469
- function stableJson(value) {
5470
- return JSON.stringify(stableValue(value));
5471
- }
5472
- function changedFields(base, head) {
5473
- return [.../* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(head)])].toSorted((a, b) => a.localeCompare(b)).filter((key) => stableJson(base[key]) !== stableJson(head[key]));
5474
- }
5475
- function detailForChangedFields(fields) {
5476
- return `Changed fields: ${fields.join(", ")}`;
5477
- }
5478
- function mapByName(items) {
5479
- return new Map(items.map((item) => [item.name, item]));
5480
- }
5481
- function compareCommonNamed(baseItems, headItems, addChange) {
5482
- const baseByName = mapByName(baseItems);
5483
- const headByName = mapByName(headItems);
5484
- const commonNames = [...baseByName.keys()].filter((name) => headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
5485
- for (const name of commonNames) {
5486
- const base = baseByName.get(name);
5487
- const head = headByName.get(name);
5488
- if (!base || !head) continue;
5489
- const fields = changedFields(base, head);
5490
- if (fields.length > 0) addChange(name, fields);
5491
- }
5492
- }
5493
- function removedNames(baseItems, headItems) {
5494
- const headByName = mapByName(headItems);
5495
- return baseItems.map((item) => item.name).filter((name) => !headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
5496
- }
5497
- function addedNames(baseItems, headItems) {
5498
- const baseByName = mapByName(baseItems);
5499
- return headItems.map((item) => item.name).filter((name) => !baseByName.has(name)).toSorted((a, b) => a.localeCompare(b));
5500
- }
5501
- function tableMetadata(table) {
5502
- return {
5503
- backwardRelationships: table.backwardRelationships,
5504
- description: table.description,
5505
- forwardRelationships: table.forwardRelationships,
5506
- pluralForm: table.pluralForm,
5507
- source: table.source
5508
- };
5509
- }
5510
- function pushChange(changes, change) {
5511
- changes.push(change);
5512
- }
5513
- function diffColumns(changes, tableName, baseColumns, headColumns) {
5514
- compareCommonNamed(baseColumns, headColumns, (name, fields) => {
5515
- pushChange(changes, {
5516
- action: "changed",
5517
- entity: "column",
5518
- path: `${tableName}.${name}`,
5519
- detail: detailForChangedFields(fields)
5520
- });
5521
- });
5522
- for (const name of removedNames(baseColumns, headColumns)) pushChange(changes, {
5523
- action: "removed",
5524
- entity: "column",
5525
- path: `${tableName}.${name}`,
5526
- detail: "Column removed"
5527
- });
5528
- for (const name of addedNames(baseColumns, headColumns)) pushChange(changes, {
5529
- action: "added",
5530
- entity: "column",
5531
- path: `${tableName}.${name}`,
5532
- detail: "Column added"
5533
- });
5534
- }
5535
- function diffIndexes(changes, tableName, baseIndexes, headIndexes) {
5536
- compareCommonNamed(baseIndexes, headIndexes, (name, fields) => {
5537
- pushChange(changes, {
5538
- action: "changed",
5539
- entity: "index",
5540
- path: `${tableName}.${name}`,
5541
- detail: detailForChangedFields(fields)
5542
- });
5543
- });
5544
- for (const name of removedNames(baseIndexes, headIndexes)) pushChange(changes, {
5545
- action: "removed",
5546
- entity: "index",
5547
- path: `${tableName}.${name}`,
5548
- detail: "Index removed"
5549
- });
5550
- for (const name of addedNames(baseIndexes, headIndexes)) pushChange(changes, {
5551
- action: "added",
5552
- entity: "index",
5553
- path: `${tableName}.${name}`,
5554
- detail: "Index added"
5555
- });
5556
- }
5557
- function diffTables(changes, baseTables, headTables) {
5558
- const baseByName = mapByName(baseTables);
5559
- const headByName = mapByName(headTables);
5560
- for (const name of removedNames(baseTables, headTables)) pushChange(changes, {
5561
- action: "removed",
5562
- entity: "table",
5563
- path: name,
5564
- detail: "Table removed"
5565
- });
5566
- for (const name of addedNames(baseTables, headTables)) pushChange(changes, {
5567
- action: "added",
5568
- entity: "table",
5569
- path: name,
5570
- detail: "Table added"
5571
- });
5572
- const commonNames = [...baseByName.keys()].filter((name) => headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
5573
- for (const name of commonNames) {
5574
- const base = baseByName.get(name);
5575
- const head = headByName.get(name);
5576
- if (!base || !head) continue;
5577
- const tableFields = changedFields(tableMetadata(base), tableMetadata(head));
5578
- if (tableFields.length > 0) pushChange(changes, {
5579
- action: "changed",
5580
- entity: "table",
5581
- path: name,
5582
- detail: detailForChangedFields(tableFields)
5583
- });
5584
- diffColumns(changes, name, base.columns, head.columns);
5585
- diffIndexes(changes, name, base.indexes, head.indexes);
5586
- }
5587
- }
5588
- function diffRelations(changes, baseRelations, headRelations) {
5589
- compareCommonNamed(baseRelations, headRelations, (name, fields) => {
5590
- pushChange(changes, {
5591
- action: "changed",
5592
- entity: "relation",
5593
- path: name,
5594
- detail: detailForChangedFields(fields)
5595
- });
5596
- });
5597
- for (const name of removedNames(baseRelations, headRelations)) pushChange(changes, {
5598
- action: "removed",
5599
- entity: "relation",
5600
- path: name,
5601
- detail: "Relation removed"
5602
- });
5603
- for (const name of addedNames(baseRelations, headRelations)) pushChange(changes, {
5604
- action: "added",
5605
- entity: "relation",
5606
- path: name,
5607
- detail: "Relation added"
5608
- });
5609
- }
5610
- function summarize(changes) {
5611
- return {
5612
- added: changes.filter((change) => change.action === "added").length,
5613
- changed: changes.filter((change) => change.action === "changed").length,
5614
- removed: changes.filter((change) => change.action === "removed").length
5615
- };
5616
- }
5617
- function extractEmbeddedErdSchema(html) {
5618
- const match = SCHEMA_BLOCK_PATTERN.exec(html);
5619
- if (!match?.[1]) throw new Error("ERD schema block not found.");
5620
- try {
5621
- return JSON.parse(match[1]);
5622
- } catch (error) {
5623
- throw new Error(`Failed to parse ERD schema block: ${String(error)}`, { cause: error });
5624
- }
5625
- }
5626
- function createEmptyErdSchema(options) {
5627
- return {
5628
- version: 1,
5629
- namespace: options.namespace,
5630
- generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
5631
- revision: options.revision,
5632
- source: "local",
5633
- cleanRoom: {
5634
- implementation: "tailor",
5635
- notes: ["Synthetic empty schema used for ERD diff generation."]
5636
- },
5637
- tables: [],
5638
- relations: []
5639
- };
5640
- }
5641
- function buildErdSchemaDiff(options) {
5642
- const { base, head } = options;
5643
- if (base.namespace !== head.namespace) throw new Error(`Cannot diff ERD schemas from different namespaces: ${base.namespace}, ${head.namespace}`);
5644
- const changes = [];
5645
- diffTables(changes, base.tables, head.tables);
5646
- diffRelations(changes, base.relations, head.relations);
5647
- const summary = summarize(changes);
5648
- return {
5649
- namespace: head.namespace,
5650
- baseRevision: base.revision,
5651
- headRevision: head.revision,
5652
- changed: changes.length > 0,
5653
- summary,
5654
- changes
5655
- };
5656
- }
5657
- function mergeNamedByHead(baseItems, headItems) {
5658
- const headByName = mapByName(headItems);
5659
- const removedItems = baseItems.filter((item) => !headByName.has(item.name)).toSorted((a, b) => a.name.localeCompare(b.name));
5660
- return [...headItems, ...removedItems];
5661
- }
5662
- function mergeDiffViewerTable(baseTable, headTable) {
5663
- return {
5664
- ...headTable,
5665
- columns: mergeNamedByHead(baseTable.columns, headTable.columns),
5666
- indexes: mergeNamedByHead(baseTable.indexes, headTable.indexes)
5667
- };
5668
- }
5669
- function mergeDiffViewerTables(baseTables, headTables) {
5670
- const baseByName = mapByName(baseTables);
5671
- const headByName = mapByName(headTables);
5672
- const merged = headTables.map((headTable) => {
5673
- const baseTable = baseByName.get(headTable.name);
5674
- return baseTable ? mergeDiffViewerTable(baseTable, headTable) : headTable;
5675
- });
5676
- const removedTables = baseTables.filter((table) => !headByName.has(table.name)).toSorted((a, b) => a.name.localeCompare(b.name));
5677
- return [...merged, ...removedTables];
5678
- }
5679
- /**
5680
- * Build the schema rendered by the diff viewer. The ordinary ERD viewer only
5681
- * knows how to draw objects present in its schema, so the diff schema keeps
5682
- * base-only tables, columns, indexes, and relations visible for removal
5683
- * highlighting while preferring head-side metadata for unchanged objects.
5684
- * @param options - Base and head schemas to merge for diff rendering.
5685
- * @returns A TailorDB ERD schema suitable for the visual diff viewer.
5686
- */
5687
- function buildErdDiffViewerSchema(options) {
5688
- const { base, head } = options;
5689
- if (base.namespace !== head.namespace) throw new Error(`Cannot diff ERD schemas from different namespaces: ${base.namespace}, ${head.namespace}`);
5690
- return {
5691
- ...head,
5692
- tables: mergeDiffViewerTables(base.tables, head.tables),
5693
- relations: mergeNamedByHead(base.relations, head.relations)
5694
- };
5695
- }
5696
- function renderErdDiffHtml(options) {
5697
- return buildViewerHtml({
5698
- schema: options.schema,
5699
- currentSchema: options.currentSchema,
5700
- diff: options.diff,
5701
- title: `TailorDB ERD diff - ${options.diff.namespace}`
5702
- });
5703
- }
5704
-
5705
- //#endregion
5706
- //#region src/cli/commands/tailordb/erd/diff-command.ts
5707
- function readSchema(filePath) {
5708
- if (!filePath) return void 0;
5709
- return extractEmbeddedErdSchema(fs$1.readFileSync(filePath, "utf8"));
5710
- }
5711
- function resolveNamespace(options) {
5712
- const namespace = options.namespace ?? options.head?.namespace ?? options.base?.namespace;
5713
- if (!namespace) throw new Error("Missing --namespace when one side of the ERD diff is omitted.");
5714
- if (options.base && options.base.namespace !== namespace) throw new Error(`Base ERD namespace "${options.base.namespace}" does not match "${namespace}".`);
5715
- if (options.head && options.head.namespace !== namespace) throw new Error(`Head ERD namespace "${options.head.namespace}" does not match "${namespace}".`);
5716
- return namespace;
5717
- }
5718
- function writeFile(filePath, content) {
5719
- fs$1.mkdirSync(path.dirname(filePath), { recursive: true });
5720
- fs$1.writeFileSync(filePath, content, "utf8");
5721
- }
5722
- function writeErdDiff(options) {
5723
- const base = readSchema(options.baseHtml);
5724
- const head = readSchema(options.headHtml);
5725
- if (!base && !head) throw new Error("At least one of --base-html or --head-html is required.");
5726
- const namespace = resolveNamespace({
5727
- namespace: options.namespace,
5728
- base,
5729
- head
5730
- });
5731
- const baseSchema = base ?? createEmptyErdSchema({
5732
- namespace,
5733
- revision: "missing-base"
5734
- });
5735
- const headSchema = head ?? createEmptyErdSchema({
5736
- namespace,
5737
- revision: "missing-head"
5738
- });
5739
- const diff = buildErdSchemaDiff({
5740
- base: baseSchema,
5741
- head: headSchema
5742
- });
5743
- const viewerSchema = buildErdDiffViewerSchema({
5744
- base: baseSchema,
5745
- head: headSchema
5746
- });
5747
- writeFile(options.outputHtml, renderErdDiffHtml({
5748
- schema: viewerSchema,
5749
- currentSchema: headSchema,
5750
- diff
5751
- }));
5752
- if (options.outputJson) writeFile(options.outputJson, `${JSON.stringify(diff, null, 2)}\n`);
5753
- return {
5754
- namespace,
5755
- outputHtml: options.outputHtml,
5756
- outputJson: options.outputJson,
5757
- diff
5758
- };
5759
- }
5760
- const erdDiffCommand = defineAppCommand({
5761
- name: "diff",
5762
- description: "Render TailorDB ERD schema diff HTML from exported ERD viewers.",
5763
- args: z.strictObject({
5764
- "base-html": arg(z.string().optional(), {
5765
- description: "Base ERD viewer HTML file",
5766
- completion: {
5767
- type: "file",
5768
- matcher: [".html"]
5769
- }
5770
- }),
5771
- "head-html": arg(z.string().optional(), {
5772
- description: "Head ERD viewer HTML file",
5773
- completion: {
5774
- type: "file",
5775
- matcher: [".html"]
5776
- }
5777
- }),
5778
- namespace: arg(z.string().optional(), {
5779
- alias: "n",
5780
- description: "TailorDB namespace name (defaults to the provided ERD schema namespace)"
5781
- }),
5782
- output: arg(z.string().min(1), {
5783
- alias: "o",
5784
- description: "Output ERD diff HTML file",
5785
- completion: {
5786
- type: "file",
5787
- matcher: [".html"]
5788
- }
5789
- }),
5790
- "output-json": arg(z.string().optional(), {
5791
- description: "Optional output JSON file for the computed diff",
5792
- completion: {
5793
- type: "file",
5794
- matcher: [".json"]
5795
- }
5796
- })
5797
- }),
5798
- run: (args) => {
5799
- initErdCommand();
5800
- const result = writeErdDiff({
5801
- baseHtml: args["base-html"],
5802
- headHtml: args["head-html"],
5803
- namespace: args.namespace,
5804
- outputHtml: args.output,
5805
- outputJson: args["output-json"]
5806
- });
5807
- if (args.json) logger.out({
5808
- namespace: result.namespace,
5809
- outputHtml: result.outputHtml,
5810
- outputJson: result.outputJson,
5811
- summary: result.diff.summary
5812
- });
5813
- else logger.success(`Wrote ERD diff to ${path.relative(process.cwd(), result.outputHtml)}`);
5814
- }
5815
- });
5816
-
5817
- //#endregion
5818
- //#region src/cli/commands/tailordb/erd/serve.ts
5819
- const DEFAULT_ERD_BASE_DIR = ".tailor/erd";
5820
- const LOCAL_HOST = "127.0.0.1";
5821
- const GLOB_CHARS = /[*?[\]{}()!+@]/;
5822
- function formatServeCommand(namespace) {
5823
- return `tailor tailordb erd serve --namespace ${namespace}`;
5824
- }
5825
- function getCacheControl(filePath) {
5826
- return filePath.endsWith(".html") || filePath.endsWith(".json") ? "no-cache" : "public, max-age=3600";
5827
- }
5828
- function resolveRequestPath(distDir, requestUrl) {
5829
- const url = new URL(requestUrl ?? "/", "http://localhost");
5830
- let pathname;
5831
- try {
5832
- pathname = decodeURIComponent(url.pathname);
5833
- } catch {
5834
- return;
5835
- }
5836
- if (pathname === "/" || pathname.endsWith("/")) pathname = path.join(pathname, "index.html");
5837
- const root = path.resolve(distDir);
5838
- const filePath = path.resolve(root, `.${pathname}`);
5839
- if (filePath !== root && !filePath.startsWith(`${root}${path.sep}`)) return;
5840
- return filePath;
5841
- }
5842
- function openStaticFile(filePath) {
5843
- let fd;
5844
- try {
5845
- fd = fs$1.openSync(filePath, "r");
5846
- if (!fs$1.fstatSync(fd).isFile()) {
5847
- fs$1.closeSync(fd);
5848
- return;
5849
- }
5850
- return {
5851
- filePath,
5852
- fd
5853
- };
5854
- } catch {
5855
- if (fd !== void 0) fs$1.closeSync(fd);
5856
- return;
5857
- }
5858
- }
5859
- function serveFile(distDir, req, res) {
5860
- const filePath = resolveRequestPath(distDir, req.url);
5861
- if (!filePath) {
5862
- res.writeHead(403);
5863
- res.end("Forbidden");
5864
- return;
5865
- }
5866
- const fallbackPath = path.join(distDir, "index.html");
5867
- const target = openStaticFile(filePath) ?? openStaticFile(fallbackPath);
5868
- if (!target) {
5869
- res.writeHead(503, {
5870
- "Content-Type": "text/plain; charset=utf-8",
5871
- "Cache-Control": "no-cache",
5872
- "Retry-After": "1"
5873
- });
5874
- res.end("ERD build is refreshing. Please retry.");
5875
- return;
5876
- }
5877
- const mimeType = lookup(target.filePath) || "application/octet-stream";
5878
- const stream = fs$1.createReadStream(target.filePath, {
5879
- fd: target.fd,
5880
- autoClose: true
5881
- });
5882
- stream.on("error", () => {
5883
- if (!res.headersSent) {
5884
- res.writeHead(503, {
5885
- "Content-Type": "text/plain; charset=utf-8",
5886
- "Cache-Control": "no-cache",
5887
- "Retry-After": "1"
5888
- });
5889
- res.end("ERD build is refreshing. Please retry.");
5890
- return;
5891
- }
5892
- res.destroy();
5893
- });
5894
- res.writeHead(200, {
5895
- "Content-Type": mimeType,
5896
- "Cache-Control": getCacheControl(target.filePath)
5897
- });
5898
- stream.pipe(res);
5899
- }
5900
- async function startStaticServer(options) {
5901
- const server = http.createServer((req, res) => {
5902
- serveFile(options.distDir, req, res);
5903
- });
5904
- return await new Promise((resolve, reject) => {
5905
- server.once("error", reject);
5906
- server.listen(options.port, LOCAL_HOST, () => {
5907
- server.off("error", reject);
5908
- const address = server.address();
5909
- if (!address || typeof address === "string") {
5910
- reject(/* @__PURE__ */ new Error("Failed to determine ERD server address."));
5911
- return;
5912
- }
5913
- resolve({
5914
- server,
5915
- url: `http://${LOCAL_HOST}:${address.port}`
5916
- });
5917
- });
5918
- });
5919
- }
5920
- function getWatchPatterns(config, results) {
5921
- const namespaces = new Set(results.map((result) => result.namespace));
5922
- const patterns = [config.path];
5923
- for (const namespace of namespaces) {
5924
- const dbConfig = config.db?.[namespace];
5925
- if (dbConfig && !("external" in dbConfig)) patterns.push(...dbConfig.files);
5926
- }
5927
- return [...new Set(patterns)];
5928
- }
5929
- function hasGlobPattern(pattern) {
5930
- return GLOB_CHARS.test(pattern);
5931
- }
5932
- function globBaseDir(pattern) {
5933
- const absolutePattern = path.resolve(pattern);
5934
- const parsed = path.parse(absolutePattern);
5935
- const relativePattern = absolutePattern.slice(parsed.root.length);
5936
- const literalParts = [];
5937
- for (const part of relativePattern.split(path.sep)) {
5938
- if (!part || GLOB_CHARS.test(part)) break;
5939
- literalParts.push(part);
5940
- }
5941
- const literalPath = literalParts.length > 0 ? path.join(parsed.root, ...literalParts) : parsed.root;
5942
- if (!literalPath || literalPath === parsed.root) return parsed.root || process.cwd();
5943
- if (!fs$1.existsSync(literalPath)) return path.dirname(literalPath);
5944
- return fs$1.statSync(literalPath).isDirectory() ? literalPath : path.dirname(literalPath);
5945
- }
5946
- async function expandWatchPattern(pattern) {
5947
- if (!hasGlobPattern(pattern)) return [path.resolve(pattern)];
5948
- const paths = /* @__PURE__ */ new Set();
5949
- for await (const file of glob(pattern)) paths.add(path.resolve(file));
5950
- const baseDir = globBaseDir(pattern);
5951
- if (fs$1.existsSync(baseDir) && fs$1.statSync(baseDir).isDirectory()) paths.add(baseDir);
5952
- return [...paths];
5953
- }
5954
- async function resolveWatchPathsFromConfig(config, results) {
5955
- const paths = /* @__PURE__ */ new Set();
5956
- for (const pattern of getWatchPatterns(config, results)) for (const watchPath of await expandWatchPattern(pattern)) paths.add(watchPath);
5957
- return [...paths];
5958
- }
5959
- async function resolveWatchPaths(context, results) {
5960
- return await resolveWatchPathsFromConfig(context.config, results);
5961
- }
5962
- function parseFreshErdExportResults(stdout) {
5963
- const lines = stdout.trim().split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
5964
- for (const line of lines.toReversed()) try {
5965
- const parsed = JSON.parse(line);
5966
- if (!Array.isArray(parsed)) continue;
5967
- return parsed.map((entry) => {
5968
- const result = entry;
5969
- if (typeof result.namespace !== "string" || typeof result.distDir !== "string") throw new Error("Invalid ERD export JSON output.");
5970
- return {
5971
- namespace: result.namespace,
5972
- distDir: result.distDir
5973
- };
5974
- });
5975
- } catch {
5976
- continue;
5977
- }
5978
- throw new Error("Failed to parse ERD export JSON output.");
5979
- }
5980
- function freshErdExportArgs(options) {
5981
- const cliEntry = process.argv[1];
5982
- if (!cliEntry) throw new Error("Cannot rebuild ERD schema in a fresh process: CLI entrypoint is unavailable.");
5983
- const args = [
5984
- cliEntry,
5985
- "tailordb",
5986
- "erd",
5987
- "export",
5988
- "--output",
5989
- options.outputDir,
5990
- "--json"
5991
- ];
5992
- if (options.configPath) args.push("--config", options.configPath);
5993
- if (options.namespace) args.push("--namespace", options.namespace);
5994
- return args;
5995
- }
5996
- async function runFreshErdExport(options) {
5997
- return await new Promise((resolve, reject) => {
5998
- const child = spawn(process.execPath, freshErdExportArgs(options), {
5999
- cwd: process.cwd(),
6000
- env: process.env,
6001
- stdio: [
6002
- "ignore",
6003
- "pipe",
6004
- "pipe"
6005
- ]
6006
- });
6007
- let stdout = "";
6008
- let stderr = "";
6009
- child.stdout.setEncoding("utf8");
6010
- child.stderr.setEncoding("utf8");
6011
- child.stdout.on("data", (chunk) => {
6012
- stdout += chunk;
6013
- });
6014
- child.stderr.on("data", (chunk) => {
6015
- stderr += chunk;
6016
- });
6017
- child.on("error", reject);
6018
- child.on("close", (code, signal) => {
6019
- if (code !== 0) {
6020
- const detail = stderr.trim() || signal || `exit code ${code}`;
6021
- reject(/* @__PURE__ */ new Error(`Fresh ERD export failed: ${detail}`));
6022
- return;
6023
- }
6024
- try {
6025
- resolve(parseFreshErdExportResults(stdout));
6026
- } catch (error) {
6027
- reject(error);
6028
- }
6029
- });
6030
- });
6031
- }
6032
- function selectPrimaryResult(results) {
6033
- const [primary, ...rest] = results;
6034
- if (!primary) throw new Error("No ERD build results found.");
6035
- logger.info(`Serving ERD for namespace "${primary.namespace}".`);
6036
- if (rest.length > 0) {
6037
- const commands = rest.map((result) => ` - ${formatServeCommand(result.namespace)}`).join("\n");
6038
- logger.warn(`Multiple namespaces found. To serve another namespace, run:\n${commands}`);
6039
- }
6040
- return primary;
6041
- }
6042
- async function createErdWatcher(options) {
6043
- let rebuilding = false;
6044
- let pending = false;
6045
- let watchPaths = await resolveWatchPaths(options.initialContext, options.initialResults);
6046
- let importNonce = 0;
6047
- const watcher = watch(watchPaths, {
6048
- ignored: /node_modules/,
6049
- ignoreInitial: true,
6050
- awaitWriteFinish: {
6051
- stabilityThreshold: 100,
6052
- pollInterval: 100
6053
- }
6054
- });
6055
- async function rebuild() {
6056
- if (rebuilding) {
6057
- pending = true;
6058
- return;
6059
- }
6060
- rebuilding = true;
6061
- try {
6062
- const results = await runFreshErdExport({
6063
- configPath: options.configPath,
6064
- namespace: options.namespace,
6065
- outputDir: options.outputDir
6066
- });
6067
- const { config } = await loadConfig(options.configPath, { importNonce: String(importNonce += 1) });
6068
- const nextWatchPaths = await resolveWatchPathsFromConfig(config, results);
6069
- watcher.unwatch(watchPaths);
6070
- watcher.add(nextWatchPaths);
6071
- watchPaths = nextWatchPaths;
6072
- logger.success(`Rebuilt ERD schema (${results.map((result) => result.namespace).join(", ")})`, { mode: "stream" });
6073
- } catch (error) {
6074
- logger.error("Failed to rebuild ERD schema. Serving the last successful build.", { mode: "stream" });
6075
- logger.error(String(error));
6076
- } finally {
6077
- rebuilding = false;
6078
- if (pending) {
6079
- pending = false;
6080
- await rebuild();
6081
- }
6082
- }
6083
- }
6084
- let debounceTimer;
6085
- const scheduleRebuild = (changedPath) => {
6086
- logger.info(`Schema source changed: ${path.relative(process.cwd(), changedPath)}`, { mode: "stream" });
6087
- if (debounceTimer) clearTimeout(debounceTimer);
6088
- debounceTimer = setTimeout(() => {
6089
- rebuild();
6090
- }, 150);
6091
- };
6092
- watcher.on("add", scheduleRebuild);
6093
- watcher.on("change", scheduleRebuild);
6094
- watcher.on("unlink", scheduleRebuild);
6095
- watcher.on("error", (error) => {
6096
- logger.error(`ERD watcher error: ${String(error)}`, { mode: "stream" });
6097
- });
6098
- return watcher;
6099
- }
6100
- async function waitForShutdown(server, watcher) {
6101
- return await new Promise((resolve) => {
6102
- const shutdown = () => {
6103
- watcher.close().finally(() => {
6104
- server.close(() => {
6105
- logger.info("ERD server stopped.");
6106
- resolve();
6107
- });
6108
- });
6109
- };
6110
- process.once("SIGINT", shutdown);
6111
- process.once("SIGTERM", shutdown);
6112
- });
6113
- }
6114
- const erdServeCommand = defineAppCommand({
6115
- name: "serve",
6116
- description: "Generate and serve TailorDB ERD locally with watch reload. (beta)",
6117
- args: z.strictObject({
6118
- ...configArg,
6119
- namespace: arg(z.string().optional(), {
6120
- alias: "n",
6121
- description: "TailorDB namespace name (uses first namespace in config if not specified)"
6122
- }),
6123
- port: arg(z.coerce.number().int().min(0).max(65535).default(0), { description: "Local server port (0 selects a free port)" }),
6124
- open: arg(z.boolean().default(false), { description: "Open the ERD viewer in the default browser" })
6125
- }),
6126
- run: async (args) => {
6127
- initErdCommand();
6128
- const outputDir = path.resolve(process.cwd(), DEFAULT_ERD_BASE_DIR);
6129
- const context = await loadLocalErdSchema({
6130
- configPath: args.config,
6131
- namespaces: args.namespace ? [args.namespace] : void 0
6132
- });
6133
- const results = prepareErdBuildsFromContext({
6134
- context,
6135
- namespace: args.namespace,
6136
- outputDir
6137
- });
6138
- const primary = selectPrimaryResult(results);
6139
- const { server, url } = await startStaticServer({
6140
- distDir: primary.distDir,
6141
- port: args.port
6142
- });
6143
- const watchUrl = `${url}/?watch=1`;
6144
- const watcher = await createErdWatcher({
6145
- configPath: args.config,
6146
- namespace: args.namespace,
6147
- outputDir,
6148
- initialContext: context,
6149
- initialResults: results
6150
- });
6151
- logger.newline();
6152
- if (args.json) logger.out({
6153
- namespace: primary.namespace,
6154
- url: watchUrl,
6155
- distDir: primary.distDir
6156
- });
6157
- else {
6158
- logger.success("ERD server started.");
6159
- logger.out(watchUrl);
6160
- }
6161
- if (args.open) try {
6162
- await open(watchUrl);
6163
- } catch {
6164
- logger.warn("Failed to open browser automatically. Please open the URL above manually.");
6165
- }
6166
- await waitForShutdown(server, watcher);
6167
- }
6168
- });
6169
-
6170
- //#endregion
6171
- //#region src/cli/commands/tailordb/erd/index.ts
6172
- const erdCommand = defineCommand({
6173
- name: "erd",
6174
- description: "Generate TailorDB ERD viewer artifacts from local TailorDB schema. (beta)",
6175
- subCommands: {
6176
- export: erdExportCommand,
6177
- diff: erdDiffCommand,
6178
- serve: erdServeCommand,
6179
- deploy: erdDeployCommand
6180
- }
6181
- });
6182
-
6183
5407
  //#endregion
6184
5408
  //#region src/cli/commands/tailordb/migrate/script.ts
6185
5409
  /**
@@ -6204,9 +5428,9 @@ async function script(options) {
6204
5428
  const targetNamespace = resolveTargetNamespace(namespacesWithMigrations, options.namespace);
6205
5429
  const { migrationsDir } = assertDefined(namespacesWithMigrations.find((ns) => ns.namespace === targetNamespace), "namespace with migrations not found");
6206
5430
  const diffPath = getMigrationFilePath(migrationsDir, migrationNumber, "diff");
6207
- if (!fs$1.existsSync(diffPath)) throw new Error(`Migration ${options.number} not found in ${migrationsDir}. Expected ${diffPath}.`);
5431
+ if (!fs.existsSync(diffPath)) throw new Error(`Migration ${options.number} not found in ${migrationsDir}. Expected ${diffPath}.`);
6208
5432
  const migratePath = getMigrationFilePath(migrationsDir, migrationNumber, "migrate");
6209
- if (fs$1.existsSync(migratePath)) throw new Error(`Migration script already exists at ${migratePath}.`);
5433
+ if (fs.existsSync(migratePath)) throw new Error(`Migration script already exists at ${migratePath}.`);
6210
5434
  const diff = loadDiff(diffPath);
6211
5435
  const previousSnapshot = reconstructSnapshotFromMigrations(migrationsDir, migrationNumber - 1);
6212
5436
  if (!previousSnapshot) throw new Error(`Could not reconstruct previous schema for migration ${options.number}. Make sure migration ${0} exists.`);
@@ -6391,7 +5615,7 @@ async function collectMigrationStatuses(options) {
6391
5615
  const pendingMigrations = getMigrationFiles(migrationsDir).map((f) => f.number).filter((n, i, arr) => arr.indexOf(n) === i).toSorted((a, b) => a - b).filter((n) => n > currentMigration).map((num) => {
6392
5616
  const diffPath = getMigrationFilePath(migrationsDir, num, "diff");
6393
5617
  let description;
6394
- if (fs$1.existsSync(diffPath)) try {
5618
+ if (fs.existsSync(diffPath)) try {
6395
5619
  description = loadDiff(diffPath).description;
6396
5620
  } catch {}
6397
5621
  return {
@@ -6506,7 +5730,7 @@ async function fetchRemoteTypes(client, workspaceId, namespace) {
6506
5730
  async function assertMigrationsReproduceLocalTypes(loaded, target) {
6507
5731
  const { config, plugins } = loaded;
6508
5732
  const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
6509
- const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-CUkjzfij.mjs");
5733
+ const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-OM0taSPn.mjs");
6510
5734
  const application = defineApplication({
6511
5735
  config,
6512
5736
  pluginManager
@@ -6518,7 +5742,10 @@ async function assertMigrationsReproduceLocalTypes(loaded, target) {
6518
5742
  await service.processNamespacePlugins();
6519
5743
  }
6520
5744
  const pluginExecutorFiles = generatePluginFilesIfNeeded(pluginManager, application.tailorDBServices, config.path);
6521
- const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-BT0GPd01.mjs")).createExecutorService({ config: { files: [] } }) : void 0);
5745
+ const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-BJAQ70e5.mjs")).createExecutorService({
5746
+ config: { files: [] },
5747
+ baseDir: path.dirname(config.path)
5748
+ }) : void 0);
6522
5749
  await executorService?.loadExecutors();
6523
5750
  if (pluginExecutorFiles.length > 0) await executorService?.loadPluginExecutorFiles([...pluginExecutorFiles]);
6524
5751
  const executorUsedTypes = /* @__PURE__ */ new Set();
@@ -6763,10 +5990,10 @@ const migrationCommand = defineCommand({
6763
5990
  const tailordbCommand = defineCommand({
6764
5991
  name: "tailordb",
6765
5992
  description: "Manage TailorDB tables and data.",
5993
+ notes: "The `tailordb erd` commands are provided by the @tailor-platform/sdk-plugin-tailordb-erd CLI plugin.",
6766
5994
  subCommands: {
6767
5995
  truncate: truncateCommand,
6768
- migration: migrationCommand,
6769
- erd: erdCommand
5996
+ migration: migrationCommand
6770
5997
  }
6771
5998
  });
6772
5999
 
@@ -6789,7 +6016,7 @@ const upgradeCommand = defineAppCommand({
6789
6016
  run: async (args) => {
6790
6017
  const { initTelemetry } = await import("../telemetry-CkbkeJxl.mjs");
6791
6018
  await initTelemetry();
6792
- const { upgrade } = await import("../service-DeZeYa06.mjs");
6019
+ const { upgrade } = await import("../service-DaeJlBoH.mjs");
6793
6020
  await upgrade({
6794
6021
  from: args.from,
6795
6022
  dryRun: args["dry-run"],
@@ -7134,7 +6361,7 @@ const workflowCommand = defineCommand({
7134
6361
  name: "workflow",
7135
6362
  description: "Manage workflows and workflow executions.",
7136
6363
  subCommands: {
7137
- list: listCommand$13,
6364
+ list: listCommand$14,
7138
6365
  get: getCommand$6,
7139
6366
  start: startCommand,
7140
6367
  wait: waitCommand,
@@ -7142,7 +6369,7 @@ const workflowCommand = defineCommand({
7142
6369
  resume: resumeCommand
7143
6370
  },
7144
6371
  async run() {
7145
- await runCommand(listCommand$13, []);
6372
+ await runCommand(listCommand$14, []);
7146
6373
  }
7147
6374
  });
7148
6375
 
@@ -7153,10 +6380,10 @@ const appCommand = defineCommand({
7153
6380
  description: "Manage workspace applications",
7154
6381
  subCommands: {
7155
6382
  health: healthCommand,
7156
- list: listCommand$14
6383
+ list: listCommand$15
7157
6384
  },
7158
6385
  async run() {
7159
- await runCommand(listCommand$14, []);
6386
+ await runCommand(listCommand$15, []);
7160
6387
  }
7161
6388
  });
7162
6389
 
@@ -7167,12 +6394,12 @@ const userCommand = defineCommand({
7167
6394
  description: "Manage workspace users",
7168
6395
  subCommands: {
7169
6396
  invite: inviteCommand,
7170
- list: listCommand$15,
6397
+ list: listCommand$16,
7171
6398
  remove: removeCommand,
7172
6399
  update: updateCommand$4
7173
6400
  },
7174
6401
  async run() {
7175
- await runCommand(listCommand$15, []);
6402
+ await runCommand(listCommand$16, []);
7176
6403
  }
7177
6404
  });
7178
6405
 
@@ -7186,12 +6413,12 @@ const workspaceCommand = defineCommand({
7186
6413
  create: createCommand$4,
7187
6414
  delete: deleteCommand$5,
7188
6415
  get: getCommand$7,
7189
- list: listCommand$16,
6416
+ list: listCommand$17,
7190
6417
  restore: restoreCommand,
7191
6418
  user: userCommand
7192
6419
  },
7193
6420
  async run() {
7194
- await runCommand(listCommand$16, []);
6421
+ await runCommand(listCommand$17, []);
7195
6422
  }
7196
6423
  });
7197
6424
 
@@ -7247,7 +6474,7 @@ Run \`${cliName} plugin list\` to see which plugins are installed and where they
7247
6474
  auth: authCommand,
7248
6475
  authconnection: authconnectionCommand,
7249
6476
  crashreport: crashReportCommand,
7250
- deploy: deployCommand$1,
6477
+ deploy: deployCommand,
7251
6478
  executor: executorCommand,
7252
6479
  function: functionCommand,
7253
6480
  generate: generateCommand,
@@ -7295,7 +6522,7 @@ runMain(mainCommand, {
7295
6522
  version: packageJson.version,
7296
6523
  globalArgs: z.object(commonArgs),
7297
6524
  displayErrors: false,
7298
- onUnknownSubcommand: ({ commandPath, name, args }) => dispatchPlugin({
6525
+ onUnknownSubcommand: ({ commandPath, name, args }) => dispatchPluginWithInstallHint({
7299
6526
  commandPath,
7300
6527
  name,
7301
6528
  args,
@@ -7304,7 +6531,8 @@ runMain(mainCommand, {
7304
6531
  }),
7305
6532
  cleanup: async ({ error }) => {
7306
6533
  if (error) {
7307
- if (isCLIError(error)) {
6534
+ if (logger.jsonMode) logger.log(JSON.stringify(errorToJson(error, { includeStack: isVerbose() })));
6535
+ else if (isCLIError(error)) {
7308
6536
  logger.log(error.format());
7309
6537
  if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
7310
6538
  } else if (error instanceof Error) {
@@ -7312,7 +6540,7 @@ runMain(mainCommand, {
7312
6540
  if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
7313
6541
  } else logger.error(`Unknown error: ${error}`);
7314
6542
  if (!isCLIError(error) && (!(error instanceof Error) || error instanceof TypeError || error instanceof RangeError)) {
7315
- const { reportCrash } = await import("../crashreport-D3RjuSYb.mjs");
6543
+ const { reportCrash } = await import("../crashreport-0ioNRZ4K.mjs");
7316
6544
  await reportCrash(error, "handledError");
7317
6545
  }
7318
6546
  }