@tailor-platform/sdk 2.0.0-next.5 → 2.0.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/dist/{application-Bf8wxOi6.mjs → application-BJXRpQj5.mjs} +190 -1258
- package/dist/application-BJXRpQj5.mjs.map +1 -0
- package/dist/application-BV-AXawv.mjs +3 -0
- package/dist/cli/commands/deploy/deploy.d.mts +8 -3
- package/dist/cli/commands/generate/options.d.mts +0 -1
- package/dist/cli/commands/generate/service.d.mts +1 -1
- package/dist/cli/commands/staticwebsite/deploy.d.mts +19 -0
- package/dist/cli/commands/workspace/create.d.mts +3 -0
- package/dist/cli/commands/workspace/list.d.mts +1 -0
- package/dist/cli/commands/workspace/transform.d.mts +2 -0
- package/dist/cli/index.mjs +776 -1551
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +7 -3
- package/dist/cli/lib.mjs +49 -3
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/shared/config-loader.d.mts +1 -1
- package/dist/cli/shared/readonly-guard.d.mts +25 -0
- package/dist/cli/shared/tailordb-namespaces.d.mts +37 -0
- package/dist/cli/shared/workspace-name.d.mts +1 -0
- package/dist/completion/zsh-worker.zsh +15 -303
- package/dist/configure/index.mjs +7 -6
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/executor/executor.d.mts +2 -13
- package/dist/configure/services/executor/operation.d.mts +7 -2
- package/dist/configure/services/tailordb/schema.d.mts +1 -7
- package/dist/configure/services/tailordb/types.d.mts +1 -1
- package/dist/configure/services/workflow/execution-policy.d.mts +2 -2
- package/dist/configure/types/type.d.mts +1 -9
- package/dist/{crashreport-D3RjuSYb.mjs → crashreport-0ioNRZ4K.mjs} +1 -1
- package/dist/{crashreport-Cg-pqRJV.mjs → crashreport-CxHne11E.mjs} +4 -3
- package/dist/{crashreport-Cg-pqRJV.mjs.map → crashreport-CxHne11E.mjs.map} +1 -1
- package/dist/errors-DfnF4ibq.mjs +100 -0
- package/dist/errors-DfnF4ibq.mjs.map +1 -0
- package/dist/{globals-BjhJJ2dN.mjs → globals-D-YbJKW-.mjs} +16 -10
- package/dist/globals-D-YbJKW-.mjs.map +1 -0
- package/dist/{logger-BEiZZ3qT.mjs → logger-BwS4ppwO.mjs} +3 -3
- package/dist/logger-BwS4ppwO.mjs.map +1 -0
- package/dist/parser/service/tailordb/type-source.d.mts +10 -0
- package/dist/{register-ts-hook-BhzVkej0.mjs → register-ts-hook-DL31O2W9.mjs} +2623 -1645
- package/dist/register-ts-hook-DL31O2W9.mjs.map +1 -0
- package/dist/{registry-BSbq6SPo.mjs → registry-CC3CbQiF.mjs} +3 -3
- package/dist/registry-CC3CbQiF.mjs.map +1 -0
- package/dist/runtime/field-parse.d.mts +10 -0
- package/dist/runtime/globals.d.mts +5 -2
- package/dist/runtime/index.mjs +1 -1
- package/dist/runtime/workflow.d.mts +70 -10
- package/dist/runtime/workflow.mjs +1 -1
- package/dist/{schema-iCd1ugGp.mjs → schema--xYWRGfe.mjs} +42 -24
- package/dist/schema--xYWRGfe.mjs.map +1 -0
- package/dist/{secret-file-aoNXJmKa.mjs → secret-file-C3J4Gy8r.mjs} +2 -27
- package/dist/secret-file-C3J4Gy8r.mjs.map +1 -0
- package/dist/{secretmanager-Bd45j7an.mjs → secretmanager-IY4UvinW.mjs} +42 -9
- package/dist/secretmanager-IY4UvinW.mjs.map +1 -0
- package/dist/{service-CzZEVrp9.mjs → service-CnHz9rwz.mjs} +34 -12
- package/dist/service-CnHz9rwz.mjs.map +1 -0
- package/dist/{service-DeZeYa06.mjs → service-DaeJlBoH.mjs} +3 -3
- package/dist/{service-DeZeYa06.mjs.map → service-DaeJlBoH.mjs.map} +1 -1
- package/dist/service-nU6ITOHL.mjs +3 -0
- package/dist/service_pb-BoLM0X4K.mjs +167 -0
- package/dist/service_pb-BoLM0X4K.mjs.map +1 -0
- package/dist/{service_pb-DZkfjB_6.mjs → service_pb-r1T7kpJc.mjs} +1 -1
- package/dist/types/executor.generated.d.mts +60 -42
- package/dist/user-agent-Bgsszb5I.mjs +28 -0
- package/dist/user-agent-Bgsszb5I.mjs.map +1 -0
- package/dist/vitest/environment.mjs +1 -1
- package/dist/vitest/index.d.mts +8 -8
- package/dist/vitest/index.mjs +591 -173
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mock.d.mts +8 -8
- package/dist/vitest/mocks/aigateway.d.mts +14 -4
- package/dist/vitest/mocks/authconnection.d.mts +14 -4
- package/dist/vitest/mocks/file.d.mts +23 -6
- package/dist/vitest/mocks/iconv.d.mts +28 -4
- package/dist/vitest/mocks/idp.d.mts +25 -9
- package/dist/vitest/mocks/secretmanager.d.mts +13 -4
- package/dist/vitest/mocks/tailordb.d.mts +48 -7
- package/dist/vitest/mocks/workflow.d.mts +68 -22
- package/dist/vitest/setup.mjs +2 -2
- package/dist/workflow-9kHGKxF2.mjs +64 -0
- package/dist/workflow-9kHGKxF2.mjs.map +1 -0
- package/dist/{service_pb-Bk7dL-yT.mjs → workspace_resource_pb-Db3fv68L.mjs} +47 -165
- package/dist/workspace_resource_pb-Db3fv68L.mjs.map +1 -0
- package/docs/cli/application.md +61 -15
- package/docs/cli/tailordb.md +8 -140
- package/docs/cli/workspace.md +6 -4
- package/docs/cli-reference.md +11 -8
- package/docs/github-actions.md +8 -0
- package/docs/migration/v2.md +41 -0
- package/docs/runtime.md +5 -6
- package/docs/services/executor.md +5 -0
- package/docs/services/tailordb.md +44 -0
- package/docs/services/workflow.md +2 -2
- package/docs/testing.md +105 -89
- package/package.json +2 -5
- package/dist/application-Bf8wxOi6.mjs.map +0 -1
- package/dist/application-CUkjzfij.mjs +0 -3
- package/dist/cli/erd-viewer-assets/app.js +0 -1424
- package/dist/cli/erd-viewer-assets/index.html +0 -77
- package/dist/cli/erd-viewer-assets/serve.json +0 -13
- package/dist/cli/erd-viewer-assets/styles.css +0 -1036
- package/dist/configure/types/field-runtime.d.mts +0 -16
- package/dist/errors-118wUDRr.mjs +0 -42
- package/dist/errors-118wUDRr.mjs.map +0 -1
- package/dist/globals-BjhJJ2dN.mjs.map +0 -1
- package/dist/logger-BEiZZ3qT.mjs.map +0 -1
- package/dist/register-ts-hook-BhzVkej0.mjs.map +0 -1
- package/dist/registry-BSbq6SPo.mjs.map +0 -1
- package/dist/resource_pb-BRv7AMXK.mjs +0 -49
- package/dist/resource_pb-BRv7AMXK.mjs.map +0 -1
- package/dist/schema-iCd1ugGp.mjs.map +0 -1
- package/dist/secret-file-aoNXJmKa.mjs.map +0 -1
- package/dist/secretmanager-Bd45j7an.mjs.map +0 -1
- package/dist/service-BT0GPd01.mjs +0 -3
- package/dist/service-CzZEVrp9.mjs.map +0 -1
- package/dist/service_pb-Bk7dL-yT.mjs.map +0 -1
- package/dist/workflow-GNuSnsTx.mjs +0 -22
- package/dist/workflow-GNuSnsTx.mjs.map +0 -1
|
@@ -1,291 +1,31 @@
|
|
|
1
1
|
import { n as isSdkBranded } from "./brand-Eo4pLXPJ.mjs";
|
|
2
|
-
import { i as PageDirection } from "./resource_pb-BRv7AMXK.mjs";
|
|
3
2
|
import { t as assertDefined } from "./assert-DBxo8jPo.mjs";
|
|
4
|
-
import { a as parseBoolean, n as logger, r as styles } from "./logger-
|
|
5
|
-
import { a as AuthConfigSchema, c as stripTailorDBTypeBuilderHelpers, d as applyRelationMetadataToFieldConfig, f as buildRelationInfo, g as functionSchema, h as loadFilesWithIgnores, l as TailorDBServiceConfigSchema, m as validateRelationConfig, n as loadExecutor, o as AuthInvokerSchema, p as processRelationMetadata, s as TailorFieldSchema, t as createExecutorService, u as TailorDBTypeSchema } from "./service-
|
|
3
|
+
import { a as parseBoolean, n as logger, r as styles } from "./logger-BwS4ppwO.mjs";
|
|
4
|
+
import { a as AuthConfigSchema, c as stripTailorDBTypeBuilderHelpers, d as applyRelationMetadataToFieldConfig, f as buildRelationInfo, g as functionSchema, h as loadFilesWithIgnores, l as TailorDBServiceConfigSchema, m as validateRelationConfig, n as loadExecutor, o as AuthInvokerSchema, p as processRelationMetadata, s as TailorFieldSchema, t as createExecutorService, u as TailorDBTypeSchema } from "./service-CnHz9rwz.mjs";
|
|
6
5
|
import { t as multiline } from "./multiline-sfHpTZZK.mjs";
|
|
7
|
-
import { t as
|
|
8
|
-
import { i as userAgent, n as tightenSecretFilePermissions, r as writeSecretFile } from "./secret-file-aoNXJmKa.mjs";
|
|
9
|
-
import { t as createCLIError } from "./errors-118wUDRr.mjs";
|
|
6
|
+
import { t as userAgent } from "./user-agent-Bgsszb5I.mjs";
|
|
10
7
|
import { t as isPluginGeneratedType } from "./type-source-DH_LH20p.mjs";
|
|
11
8
|
import { builtinModules, createRequire } from "node:module";
|
|
12
9
|
import * as path from "pathe";
|
|
13
|
-
import {
|
|
10
|
+
import { resolve } from "pathe";
|
|
14
11
|
import { resolveTSConfig } from "pkg-types";
|
|
15
|
-
import { arg } from "politty";
|
|
16
12
|
import { z } from "zod";
|
|
17
13
|
import { create } from "@bufbuild/protobuf";
|
|
18
|
-
import * as fs
|
|
19
|
-
import {
|
|
20
|
-
import { parseEnv } from "node:util";
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
21
16
|
import { MethodOptions_IdempotencyLevel } from "@bufbuild/protobuf/wkt";
|
|
22
17
|
import { pathToFileURL } from "node:url";
|
|
23
|
-
import { parseYAML, stringifyYAML } from "confbox";
|
|
24
|
-
import { findUpSync } from "find-up-simple";
|
|
25
|
-
import { lt } from "semver";
|
|
26
|
-
import { xdgConfig } from "xdg-basedir";
|
|
27
18
|
import { OAuth2Client } from "@badgateway/oauth2-client";
|
|
28
19
|
import { Code, ConnectError, createClient } from "@connectrpc/connect";
|
|
29
20
|
import { getGlobalDispatcher } from "undici";
|
|
30
21
|
import pLimit from "p-limit";
|
|
31
22
|
import * as crypto from "node:crypto";
|
|
32
23
|
import * as rolldown from "rolldown";
|
|
33
|
-
import * as fs from "node:fs/promises";
|
|
34
24
|
import { parseSync } from "oxc-parser";
|
|
35
25
|
import * as inflection from "inflection";
|
|
36
26
|
import * as globals from "globals";
|
|
37
27
|
import * as os from "node:os";
|
|
38
28
|
|
|
39
|
-
//#region src/cli/shared/args.ts
|
|
40
|
-
const unitToMs = {
|
|
41
|
-
ms: 1,
|
|
42
|
-
s: 1e3,
|
|
43
|
-
m: 60 * 1e3
|
|
44
|
-
};
|
|
45
|
-
const durationPattern = /^(\d+)(ms|s|m)$/;
|
|
46
|
-
/**
|
|
47
|
-
* Schema for duration string validation (e.g., "3s", "500ms", "1m")
|
|
48
|
-
* Only validates format; use parseDuration() to convert to milliseconds
|
|
49
|
-
*/
|
|
50
|
-
const durationArg = z.string().refine((val) => durationPattern.test(val), { message: "Invalid duration format. Expected format: '3s', '500ms', '1m'" }).refine((val) => {
|
|
51
|
-
const match = val.match(durationPattern);
|
|
52
|
-
if (!match) return false;
|
|
53
|
-
const digits = match[1];
|
|
54
|
-
return digits !== void 0 && parseInt(digits, 10) > 0;
|
|
55
|
-
}, { message: "Duration must be greater than 0" });
|
|
56
|
-
/**
|
|
57
|
-
* Parse a validated duration string into milliseconds
|
|
58
|
-
* @param duration - Duration string (e.g., "3s", "500ms", "1m")
|
|
59
|
-
* @returns Duration in milliseconds
|
|
60
|
-
*/
|
|
61
|
-
function parseDuration(duration) {
|
|
62
|
-
const match = assertDefined(duration.match(durationPattern), `invalid duration format: ${duration}`);
|
|
63
|
-
const value = parseInt(assertDefined(match[1], "duration digits group missing"), 10);
|
|
64
|
-
const unit = assertDefined(match[2], "duration unit group missing");
|
|
65
|
-
return value * unitToMs[unit];
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Schema for positive integer validation (from string input)
|
|
69
|
-
* Transforms the string to a number
|
|
70
|
-
*/
|
|
71
|
-
const positiveIntArg = z.coerce.number().int().positive();
|
|
72
|
-
/**
|
|
73
|
-
* Schema for non-negative integer validation (from string input).
|
|
74
|
-
* Accepts 0 (used for `--limit 0` to disable the limit).
|
|
75
|
-
*/
|
|
76
|
-
const nonNegativeIntArg = z.coerce.number().int().nonnegative();
|
|
77
|
-
/**
|
|
78
|
-
* Schema for sort order (`asc` or `desc`).
|
|
79
|
-
*/
|
|
80
|
-
const orderArg = z.enum(["asc", "desc"]);
|
|
81
|
-
/**
|
|
82
|
-
* Translate a CLI `--order` value into the proto `PageDirection` enum.
|
|
83
|
-
* Returns `undefined` when the user did not specify an order so that
|
|
84
|
-
* callers can omit the field and fall back to the server default.
|
|
85
|
-
* @param order - Order string from CLI args (`"asc"` | `"desc"` | undefined)
|
|
86
|
-
* @returns PageDirection, or undefined when `order` is undefined
|
|
87
|
-
*/
|
|
88
|
-
function toPageDirection(order) {
|
|
89
|
-
if (order === void 0) return void 0;
|
|
90
|
-
return order === "asc" ? PageDirection.ASC : PageDirection.DESC;
|
|
91
|
-
}
|
|
92
|
-
function hasMachineUserFlag(argv) {
|
|
93
|
-
return argv.slice(0, argv.indexOf("--") === -1 ? argv.length : argv.indexOf("--")).some((token) => token === "-m" || token.startsWith("-m=") || token === "--machine-user" || token.startsWith("--machine-user=") || token === "--machineUser" || token.startsWith("--machineUser=") || token === "--machineuser" || token.startsWith("--machineuser="));
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Resolve whether a parsed machine user value came from an explicit CLI option or env fallback.
|
|
97
|
-
* @param machineUser - Parsed machine user value
|
|
98
|
-
* @param argv - Raw CLI argv, excluding the executable and script path
|
|
99
|
-
* @param options - Source resolution options
|
|
100
|
-
* @returns Machine user input source, or undefined when no value was parsed
|
|
101
|
-
*/
|
|
102
|
-
function resolveMachineUserInputSource(machineUser, argv = process.argv.slice(2), options = {}) {
|
|
103
|
-
if (machineUser === void 0) return void 0;
|
|
104
|
-
if (options.valueIsExplicit) return "option";
|
|
105
|
-
if (hasMachineUserFlag(argv)) return "option";
|
|
106
|
-
return process.env.TAILOR_PLATFORM_MACHINE_USER_NAME === machineUser ? "env" : "option";
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Load env files from parsed arguments.
|
|
110
|
-
* Processes --env-file first, then --env-file-if-exists.
|
|
111
|
-
*
|
|
112
|
-
* Follows Node.js --env-file behavior:
|
|
113
|
-
* - Variables already set in the environment are NOT overwritten
|
|
114
|
-
* - Variables from later files override those from earlier files
|
|
115
|
-
* @param envFiles - Required env file path(s) that must exist
|
|
116
|
-
* @param envFilesIfExists - Optional env file path(s) that are loaded if they exist
|
|
117
|
-
*/
|
|
118
|
-
function loadEnvFiles(envFiles, envFilesIfExists) {
|
|
119
|
-
const originalEnvKeys = new Set(Object.keys(process.env));
|
|
120
|
-
const load = (files, required) => {
|
|
121
|
-
for (const file of [files ?? []].flat()) {
|
|
122
|
-
const envPath = path.resolve(process.cwd(), file);
|
|
123
|
-
if (!fs$1.existsSync(envPath)) {
|
|
124
|
-
if (required) throw new Error(`Environment file not found: ${envPath}`);
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
const parsed = parseEnv(fs$1.readFileSync(envPath, "utf-8"));
|
|
128
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
129
|
-
if (originalEnvKeys.has(key)) continue;
|
|
130
|
-
process.env[key] = value;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
load(envFiles, true);
|
|
135
|
-
load(envFilesIfExists, false);
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Common arguments for all CLI commands
|
|
139
|
-
*
|
|
140
|
-
* NOTE: --env-file and --env-file-if-exists collide with Node.js flags due to a bug
|
|
141
|
-
* (https://github.com/nodejs/node/issues/54232). Node.js parses these even after the
|
|
142
|
-
* script path, causing warnings (twice due to tsx loader).
|
|
143
|
-
*/
|
|
144
|
-
const commonArgs = {
|
|
145
|
-
"env-file": arg(z.string().optional(), {
|
|
146
|
-
alias: "e",
|
|
147
|
-
description: "Path to the environment file (error if not found)",
|
|
148
|
-
completion: {
|
|
149
|
-
type: "file",
|
|
150
|
-
matcher: [".env.*", ".env"]
|
|
151
|
-
}
|
|
152
|
-
}),
|
|
153
|
-
"env-file-if-exists": arg(z.string().optional(), {
|
|
154
|
-
description: "Path to the environment file (ignored if not found)",
|
|
155
|
-
completion: {
|
|
156
|
-
type: "file",
|
|
157
|
-
matcher: [".env.*", ".env"]
|
|
158
|
-
},
|
|
159
|
-
effect: (_value, { args }) => {
|
|
160
|
-
loadEnvFiles(args["env-file"], args["env-file-if-exists"]);
|
|
161
|
-
}
|
|
162
|
-
}),
|
|
163
|
-
verbose: arg(z.boolean().default(false), {
|
|
164
|
-
description: "Enable verbose logging",
|
|
165
|
-
effect: (value) => {
|
|
166
|
-
verboseMode = value;
|
|
167
|
-
}
|
|
168
|
-
}),
|
|
169
|
-
json: arg(z.boolean().default(false), {
|
|
170
|
-
alias: "j",
|
|
171
|
-
description: "Output as JSON",
|
|
172
|
-
effect: (value) => {
|
|
173
|
-
logger.jsonMode = value;
|
|
174
|
-
}
|
|
175
|
-
})
|
|
176
|
-
};
|
|
177
|
-
/**
|
|
178
|
-
* Arguments for commands that require workspace context
|
|
179
|
-
*/
|
|
180
|
-
const workspaceArgs = {
|
|
181
|
-
"workspace-id": arg(z.string().optional(), {
|
|
182
|
-
alias: "w",
|
|
183
|
-
description: "Workspace ID",
|
|
184
|
-
env: "TAILOR_PLATFORM_WORKSPACE_ID",
|
|
185
|
-
completion: { type: "none" }
|
|
186
|
-
}),
|
|
187
|
-
profile: arg(z.string().optional(), {
|
|
188
|
-
alias: "p",
|
|
189
|
-
description: "Workspace profile",
|
|
190
|
-
env: "TAILOR_PLATFORM_PROFILE",
|
|
191
|
-
completion: { type: "none" }
|
|
192
|
-
})
|
|
193
|
-
};
|
|
194
|
-
/**
|
|
195
|
-
* Shared config arg for commands that accept a config file path
|
|
196
|
-
*/
|
|
197
|
-
const configArg = { config: arg(z.string().default("tailor.config.ts"), {
|
|
198
|
-
alias: "c",
|
|
199
|
-
description: "Path to Tailor config file",
|
|
200
|
-
env: "TAILOR_CONFIG_PATH",
|
|
201
|
-
completion: {
|
|
202
|
-
type: "file",
|
|
203
|
-
extensions: ["ts"]
|
|
204
|
-
}
|
|
205
|
-
}) };
|
|
206
|
-
/**
|
|
207
|
-
* Shared config arg for commands that accept one or more comma-separated config file paths
|
|
208
|
-
*/
|
|
209
|
-
const multiConfigArg = { config: arg(z.string().default("tailor.config.ts"), {
|
|
210
|
-
alias: "c",
|
|
211
|
-
description: "Path to SDK config file. Use comma-separated paths to deploy multiple apps together.",
|
|
212
|
-
env: "TAILOR_PLATFORM_SDK_CONFIG_PATH",
|
|
213
|
-
completion: {
|
|
214
|
-
type: "file",
|
|
215
|
-
extensions: ["ts"]
|
|
216
|
-
}
|
|
217
|
-
}) };
|
|
218
|
-
/**
|
|
219
|
-
* Arguments for commands that interact with deployed resources (includes config)
|
|
220
|
-
*/
|
|
221
|
-
const deploymentArgs = {
|
|
222
|
-
...workspaceArgs,
|
|
223
|
-
...configArg
|
|
224
|
-
};
|
|
225
|
-
/**
|
|
226
|
-
* Arguments for commands that require confirmation
|
|
227
|
-
*/
|
|
228
|
-
const confirmationArgs = { yes: arg(z.boolean().default(false), {
|
|
229
|
-
alias: "y",
|
|
230
|
-
description: "Skip confirmation prompts"
|
|
231
|
-
}) };
|
|
232
|
-
/**
|
|
233
|
-
* Arguments for commands that require organization context
|
|
234
|
-
*/
|
|
235
|
-
const organizationArgs = { "organization-id": arg(z.string(), {
|
|
236
|
-
alias: "o",
|
|
237
|
-
description: "Organization ID",
|
|
238
|
-
env: "TAILOR_PLATFORM_ORGANIZATION_ID",
|
|
239
|
-
completion: { type: "none" }
|
|
240
|
-
}) };
|
|
241
|
-
/**
|
|
242
|
-
* Arguments for list commands that accept `--order` / `--limit`. Sort
|
|
243
|
-
* order defaults to `desc` (newest first) because most callers want the
|
|
244
|
-
* latest items; pass `--order asc` to opt in to ascending order. The
|
|
245
|
-
* limit is unbounded by default so existing invocations keep returning
|
|
246
|
-
* every item; pass `--limit N` to cap the result size.
|
|
247
|
-
* @param defaultOrder - Default value for `--order` (defaults to `"desc"`)
|
|
248
|
-
* @returns Argument shape suitable for spreading into a command schema
|
|
249
|
-
*/
|
|
250
|
-
const paginationArgs = (defaultOrder = "desc") => ({
|
|
251
|
-
order: arg(orderArg.default(defaultOrder), { description: "Sort order (asc or desc)" }),
|
|
252
|
-
limit: arg(nonNegativeIntArg.optional(), {
|
|
253
|
-
alias: "l",
|
|
254
|
-
description: "Maximum number of items to return (0 or omit: unlimited)"
|
|
255
|
-
})
|
|
256
|
-
});
|
|
257
|
-
/**
|
|
258
|
-
* Arguments for time-series log list commands. Defaults to newest-first
|
|
259
|
-
* (`desc`) and a 50-item cap so that listing stays responsive on busy
|
|
260
|
-
* workspaces. Pass `--limit 0` to disable the cap and fetch all entries.
|
|
261
|
-
*/
|
|
262
|
-
const pagedLogArgs = {
|
|
263
|
-
order: arg(orderArg.default("desc"), { description: "Sort order (asc or desc)" }),
|
|
264
|
-
limit: arg(nonNegativeIntArg.default(50), {
|
|
265
|
-
alias: "l",
|
|
266
|
-
description: "Maximum number of items to return (0: unlimited)"
|
|
267
|
-
})
|
|
268
|
-
};
|
|
269
|
-
/**
|
|
270
|
-
* Arguments for commands that require folder context
|
|
271
|
-
*/
|
|
272
|
-
const folderArgs = { "folder-id": arg(z.string(), {
|
|
273
|
-
alias: "f",
|
|
274
|
-
description: "Folder ID",
|
|
275
|
-
env: "TAILOR_PLATFORM_FOLDER_ID",
|
|
276
|
-
completion: { type: "none" }
|
|
277
|
-
}) };
|
|
278
|
-
let verboseMode = false;
|
|
279
|
-
/**
|
|
280
|
-
* Returns whether verbose mode is enabled.
|
|
281
|
-
* Used by the global cleanup handler which doesn't have access to parsed args.
|
|
282
|
-
* @returns Whether verbose mode is enabled
|
|
283
|
-
*/
|
|
284
|
-
function isVerbose() {
|
|
285
|
-
return verboseMode;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
//#endregion
|
|
289
29
|
//#region src/parser/app-config/log-level.ts
|
|
290
30
|
const LOG_LEVELS = [
|
|
291
31
|
"DEBUG",
|
|
@@ -298,64 +38,6 @@ function isLogLevel(value) {
|
|
|
298
38
|
return LOG_LEVELS.includes(value);
|
|
299
39
|
}
|
|
300
40
|
|
|
301
|
-
//#endregion
|
|
302
|
-
//#region src/parser/app-config/schema.ts
|
|
303
|
-
const envValueSchema = z.union([
|
|
304
|
-
z.string(),
|
|
305
|
-
z.number(),
|
|
306
|
-
z.boolean()
|
|
307
|
-
]);
|
|
308
|
-
const LogLevelSchema = z.enum(LOG_LEVELS);
|
|
309
|
-
const logLevelSchema = z.string().refine((value) => LogLevelSchema.safeParse(value.trim().toUpperCase()).success, { message: `'logLevel' must be one of: ${LOG_LEVELS.join(", ")}.` });
|
|
310
|
-
/**
|
|
311
|
-
* Structural validation schema for `defineConfig({...})`. Validates only
|
|
312
|
-
* top-level fields with platform-side constraints (notably `id`); fields
|
|
313
|
-
* that carry SDK builder objects (`auth`, `idp`, `db`, ...) are accepted
|
|
314
|
-
* as opaque values, since their internal shapes are validated by their
|
|
315
|
-
* own factory functions and parser-level schemas.
|
|
316
|
-
*
|
|
317
|
-
* The `id` is auto-managed by `deploy` and stored as a plain UUID. A
|
|
318
|
-
* label-compatible prefix is added at the metadata boundary, so user-facing
|
|
319
|
-
* configs only need to carry a UUID.
|
|
320
|
-
*/
|
|
321
|
-
const AppConfigSchema = z.strictObject({
|
|
322
|
-
id: z.uuid({ message: "'id' must be a UUID." }).optional(),
|
|
323
|
-
name: z.string().min(1, { message: "'name' must be a non-empty string." }),
|
|
324
|
-
env: z.record(z.string(), envValueSchema).optional(),
|
|
325
|
-
cors: z.array(z.string()).optional(),
|
|
326
|
-
allowedIpAddresses: z.array(z.string()).optional(),
|
|
327
|
-
disableIntrospection: z.boolean().optional(),
|
|
328
|
-
inlineSourcemap: z.boolean().optional(),
|
|
329
|
-
logLevel: logLevelSchema.optional(),
|
|
330
|
-
db: z.unknown().optional(),
|
|
331
|
-
resolver: z.unknown().optional(),
|
|
332
|
-
idp: z.unknown().optional(),
|
|
333
|
-
auth: z.unknown().optional(),
|
|
334
|
-
executor: z.unknown().optional(),
|
|
335
|
-
workflow: z.unknown().optional(),
|
|
336
|
-
httpAdapter: z.unknown().optional(),
|
|
337
|
-
staticWebsites: z.unknown().optional(),
|
|
338
|
-
aiGateways: z.unknown().optional(),
|
|
339
|
-
secrets: z.unknown().optional()
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
//#endregion
|
|
343
|
-
//#region src/parser/plugin-config/schema.ts
|
|
344
|
-
const PluginConfigSchema = z.looseObject({
|
|
345
|
-
id: z.string(),
|
|
346
|
-
description: z.string(),
|
|
347
|
-
importPath: z.string().optional(),
|
|
348
|
-
pluginConfig: z.unknown().optional(),
|
|
349
|
-
typeConfigRequired: z.union([z.boolean(), functionSchema]).optional(),
|
|
350
|
-
onTypeLoaded: functionSchema.optional(),
|
|
351
|
-
onNamespaceLoaded: functionSchema.optional(),
|
|
352
|
-
onTailorDBReady: functionSchema.optional(),
|
|
353
|
-
onResolverReady: functionSchema.optional(),
|
|
354
|
-
onExecutorReady: functionSchema.optional()
|
|
355
|
-
}).refine((p) => {
|
|
356
|
-
return !(p.onTypeLoaded || p.onNamespaceLoaded) || !!p.importPath;
|
|
357
|
-
}, { message: "importPath is required when plugin has definition-time hooks (onTypeLoaded/onNamespaceLoaded)" }).transform((plugin) => plugin);
|
|
358
|
-
|
|
359
41
|
//#endregion
|
|
360
42
|
//#region src/cli/shared/apply-concurrency.ts
|
|
361
43
|
/**
|
|
@@ -498,7 +180,7 @@ function initOAuth2Client(config) {
|
|
|
498
180
|
*/
|
|
499
181
|
async function initOperatorClient(accessToken, config) {
|
|
500
182
|
const platformConfig = config ?? getPlatformConfigForToken(accessToken);
|
|
501
|
-
const [{ createTracingInterceptor }, { OperatorService }] = await Promise.all([import("./interceptor-e33JtsC-.mjs"), import("./service_pb-
|
|
183
|
+
const [{ createTracingInterceptor }, { OperatorService }] = await Promise.all([import("./interceptor-e33JtsC-.mjs"), import("./service_pb-r1T7kpJc.mjs")]);
|
|
502
184
|
const interceptors = [
|
|
503
185
|
await userAgentInterceptor(),
|
|
504
186
|
await bearerTokenInterceptor(accessToken),
|
|
@@ -551,9 +233,9 @@ async function bearerTokenInterceptor(accessToken) {
|
|
|
551
233
|
/**
|
|
552
234
|
* Create an interceptor that retries failed unary requests with backoff.
|
|
553
235
|
*
|
|
554
|
-
* Retries
|
|
555
|
-
* only for methods declared idempotent, up to 3 attempts
|
|
556
|
-
*
|
|
236
|
+
* Retries unary methods on `Unavailable`/`ResourceExhausted`, and `Internal`
|
|
237
|
+
* only for methods declared idempotent, up to 3 attempts. Workspace creation is
|
|
238
|
+
* excluded because it has no idempotency key and a lost response is ambiguous.
|
|
557
239
|
* As a targeted exception for the deploy/apply flow, a post-retry `AlreadyExists`
|
|
558
240
|
* from an allowlisted Create (see `RETRY_SAFE_CREATE_METHODS`) is treated as
|
|
559
241
|
* success, since it means a prior attempt already committed the resource
|
|
@@ -578,10 +260,10 @@ function retryInterceptor() {
|
|
|
578
260
|
logger.debug(`retry: ${req.method.name} returned AlreadyExists on attempt ${i + 1}; treating as success (prior attempt likely committed)`);
|
|
579
261
|
return synthesizeEmptyUnaryResponse(req);
|
|
580
262
|
}
|
|
581
|
-
const { reportCrash } = await import("./crashreport-
|
|
263
|
+
const { reportCrash } = await import("./crashreport-0ioNRZ4K.mjs");
|
|
582
264
|
await reportCrash(error, "handledError");
|
|
583
265
|
}
|
|
584
|
-
if (isRetirable(error, req.method.idempotency)) {
|
|
266
|
+
if (req.method.name !== "CreateWorkspace" && isRetirable(error, req.method.idempotency)) {
|
|
585
267
|
lastError = error;
|
|
586
268
|
logger.debug(`retry: ${req.method.name} attempt ${i + 1} failed with ${connectCodeName(error)}; retrying`);
|
|
587
269
|
continue;
|
|
@@ -986,806 +668,6 @@ async function closeConnectionPool() {
|
|
|
986
668
|
await getGlobalDispatcher().close();
|
|
987
669
|
}
|
|
988
670
|
|
|
989
|
-
//#endregion
|
|
990
|
-
//#region src/cli/shared/token-store.ts
|
|
991
|
-
const SERVICE_NAME = "tailor-platform-cli";
|
|
992
|
-
let entryClass;
|
|
993
|
-
async function getEntryClass() {
|
|
994
|
-
if (entryClass !== void 0) return entryClass;
|
|
995
|
-
try {
|
|
996
|
-
const mod = await import("@napi-rs/keyring");
|
|
997
|
-
const probe = new mod.Entry(SERVICE_NAME, "__probe__");
|
|
998
|
-
probe.setPassword("probe");
|
|
999
|
-
probe.deletePassword();
|
|
1000
|
-
entryClass = mod.Entry;
|
|
1001
|
-
} catch {
|
|
1002
|
-
logger.warn("System keyring is not available. Tokens will be stored in the config file. Set TAILOR_PLATFORM_TOKEN environment variable for CI environments.");
|
|
1003
|
-
entryClass = false;
|
|
1004
|
-
}
|
|
1005
|
-
return entryClass;
|
|
1006
|
-
}
|
|
1007
|
-
/**
|
|
1008
|
-
* Check whether the OS keyring is available and functional.
|
|
1009
|
-
* @returns true if keyring is available
|
|
1010
|
-
*/
|
|
1011
|
-
async function isKeyringAvailable() {
|
|
1012
|
-
return await getEntryClass() !== false;
|
|
1013
|
-
}
|
|
1014
|
-
/**
|
|
1015
|
-
* Load tokens from the OS keyring for a given account.
|
|
1016
|
-
* @param account - User identifier (e.g. email or client ID)
|
|
1017
|
-
* @returns Token data or undefined if not found or keyring unavailable
|
|
1018
|
-
*/
|
|
1019
|
-
async function loadKeyringTokens(account) {
|
|
1020
|
-
const Entry = await getEntryClass();
|
|
1021
|
-
if (!Entry) return void 0;
|
|
1022
|
-
try {
|
|
1023
|
-
const raw = new Entry(SERVICE_NAME, account).getPassword();
|
|
1024
|
-
if (raw === null) return void 0;
|
|
1025
|
-
return JSON.parse(raw);
|
|
1026
|
-
} catch {
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
/**
|
|
1031
|
-
* Save tokens to the OS keyring for a given account.
|
|
1032
|
-
* @param account - User identifier (e.g. email or client ID)
|
|
1033
|
-
* @param tokens - Token data to store
|
|
1034
|
-
*/
|
|
1035
|
-
async function saveKeyringTokens(account, tokens) {
|
|
1036
|
-
const Entry = await getEntryClass();
|
|
1037
|
-
if (!Entry) return;
|
|
1038
|
-
new Entry(SERVICE_NAME, account).setPassword(JSON.stringify(tokens));
|
|
1039
|
-
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Delete tokens from the OS keyring for a given account.
|
|
1042
|
-
* @param account - User identifier (e.g. email or client ID)
|
|
1043
|
-
*/
|
|
1044
|
-
async function deleteKeyringTokens(account) {
|
|
1045
|
-
const Entry = await getEntryClass();
|
|
1046
|
-
if (!Entry) return;
|
|
1047
|
-
try {
|
|
1048
|
-
new Entry(SERVICE_NAME, account).deletePassword();
|
|
1049
|
-
} catch {}
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
//#endregion
|
|
1053
|
-
//#region src/cli/shared/context.ts
|
|
1054
|
-
const pfProfileSchema = z.object({
|
|
1055
|
-
user: z.string(),
|
|
1056
|
-
workspace_id: z.string(),
|
|
1057
|
-
readonly: z.boolean().optional(),
|
|
1058
|
-
machine_user: z.string().optional(),
|
|
1059
|
-
machine_user_override: z.enum(["allow", "deny"]).optional(),
|
|
1060
|
-
platform_url: z.url().optional(),
|
|
1061
|
-
oauth2_client_id: z.string().optional(),
|
|
1062
|
-
console_url: z.url().optional()
|
|
1063
|
-
});
|
|
1064
|
-
const pfUserSchemaV1 = z.object({
|
|
1065
|
-
access_token: z.string(),
|
|
1066
|
-
refresh_token: z.string().optional(),
|
|
1067
|
-
token_expires_at: z.string()
|
|
1068
|
-
});
|
|
1069
|
-
const pfUserKeyringSchema = z.object({
|
|
1070
|
-
storage: z.literal("keyring"),
|
|
1071
|
-
token_expires_at: z.string()
|
|
1072
|
-
});
|
|
1073
|
-
const pfUserFileSchema = z.object({
|
|
1074
|
-
storage: z.literal("file"),
|
|
1075
|
-
token_expires_at: z.string(),
|
|
1076
|
-
access_token: z.string(),
|
|
1077
|
-
refresh_token: z.string().optional()
|
|
1078
|
-
});
|
|
1079
|
-
const pfUserSchemaV2 = z.discriminatedUnion("storage", [pfUserKeyringSchema, pfUserFileSchema]);
|
|
1080
|
-
const pfUserKeyringSchemaV3 = pfUserKeyringSchema.extend({ email: z.string().optional() });
|
|
1081
|
-
const pfUserFileSchemaV3 = pfUserFileSchema.extend({ email: z.string().optional() });
|
|
1082
|
-
const pfUserSchemaV3 = z.discriminatedUnion("storage", [pfUserKeyringSchemaV3, pfUserFileSchemaV3]);
|
|
1083
|
-
const pfConfigSchemaV1 = z.object({
|
|
1084
|
-
version: z.literal(1),
|
|
1085
|
-
users: z.partialRecord(z.string(), pfUserSchemaV1),
|
|
1086
|
-
profiles: z.partialRecord(z.string(), pfProfileSchema),
|
|
1087
|
-
current_user: z.string().nullable()
|
|
1088
|
-
});
|
|
1089
|
-
const V2_CONFIG_VERSION = 2;
|
|
1090
|
-
const LATEST_CONFIG_VERSION = 3;
|
|
1091
|
-
const V2_MIN_SDK_VERSION = "1.29.0";
|
|
1092
|
-
const V3_MIN_SDK_VERSION = "2.0.0";
|
|
1093
|
-
const semverSchema = z.templateLiteral([
|
|
1094
|
-
z.number().int(),
|
|
1095
|
-
".",
|
|
1096
|
-
z.number().int(),
|
|
1097
|
-
".",
|
|
1098
|
-
z.number().int()
|
|
1099
|
-
]);
|
|
1100
|
-
const pfConfigSchemaV2 = z.object({
|
|
1101
|
-
version: z.literal(V2_CONFIG_VERSION),
|
|
1102
|
-
min_sdk_version: semverSchema,
|
|
1103
|
-
latest_version: z.number().int().optional(),
|
|
1104
|
-
latest_min_sdk_version: semverSchema.optional(),
|
|
1105
|
-
users: z.partialRecord(z.string(), pfUserSchemaV2),
|
|
1106
|
-
profiles: z.partialRecord(z.string(), pfProfileSchema),
|
|
1107
|
-
current_user: z.string().nullable()
|
|
1108
|
-
});
|
|
1109
|
-
const pfConfigSchemaV3 = z.object({
|
|
1110
|
-
version: z.literal(LATEST_CONFIG_VERSION),
|
|
1111
|
-
min_sdk_version: semverSchema,
|
|
1112
|
-
latest_version: z.number().int().optional(),
|
|
1113
|
-
latest_min_sdk_version: semverSchema.optional(),
|
|
1114
|
-
users: z.partialRecord(z.string(), pfUserSchemaV3),
|
|
1115
|
-
profiles: z.partialRecord(z.string(), pfProfileSchema),
|
|
1116
|
-
current_user: z.string().nullable()
|
|
1117
|
-
});
|
|
1118
|
-
function platformConfigPath() {
|
|
1119
|
-
if (!xdgConfig) throw new Error("User home directory not found");
|
|
1120
|
-
return path.join(xdgConfig, "tailor-platform", "config.yaml");
|
|
1121
|
-
}
|
|
1122
|
-
/**
|
|
1123
|
-
* Convert stored profile platform fields to platform client settings.
|
|
1124
|
-
* @param profile - Profile platform settings
|
|
1125
|
-
* @returns Platform client settings
|
|
1126
|
-
*/
|
|
1127
|
-
function platformConfigFromProfile(profile) {
|
|
1128
|
-
const config = {
|
|
1129
|
-
...profile?.platform_url ? { platformUrl: profile.platform_url } : {},
|
|
1130
|
-
...profile?.oauth2_client_id ? { oauth2ClientId: profile.oauth2_client_id } : {},
|
|
1131
|
-
...profile?.console_url ? { consoleUrl: profile.console_url } : {}
|
|
1132
|
-
};
|
|
1133
|
-
return Object.keys(config).length > 0 ? config : void 0;
|
|
1134
|
-
}
|
|
1135
|
-
function platformUserKey(user, config) {
|
|
1136
|
-
const platformUrl = getPlatformBaseUrl(config);
|
|
1137
|
-
if (platformUrl === normalizeBaseUrl("https://api.tailor.tech")) return user;
|
|
1138
|
-
return `${platformUrl}|${user}`;
|
|
1139
|
-
}
|
|
1140
|
-
function userFromPlatformUserKey(userKey, config) {
|
|
1141
|
-
const platformPrefix = `${getPlatformBaseUrl(config)}|`;
|
|
1142
|
-
return userKey.startsWith(platformPrefix) ? userKey.slice(platformPrefix.length) : userKey;
|
|
1143
|
-
}
|
|
1144
|
-
function canUseLegacyUserKey(platformUrl) {
|
|
1145
|
-
try {
|
|
1146
|
-
return getPlatformBaseUrl() === platformUrl;
|
|
1147
|
-
} catch {
|
|
1148
|
-
return false;
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
function findUserByEmail(users, email, platformConfig) {
|
|
1152
|
-
const platformUrl = getPlatformBaseUrl(platformConfig);
|
|
1153
|
-
const platformPrefix = `${platformUrl}|`;
|
|
1154
|
-
const defaultPlatform = platformUrl === normalizeBaseUrl(defaultPlatformBaseUrl);
|
|
1155
|
-
return Object.entries(users).find(([key, entry]) => {
|
|
1156
|
-
if (entry?.email !== email) return false;
|
|
1157
|
-
return defaultPlatform ? !key.includes("|") : key.startsWith(platformPrefix);
|
|
1158
|
-
});
|
|
1159
|
-
}
|
|
1160
|
-
function findUserEntry(config, user, platformConfig, opts = {}) {
|
|
1161
|
-
const userKey = platformUserKey(user, platformConfig);
|
|
1162
|
-
const userEntry = config.users[userKey];
|
|
1163
|
-
if (userEntry) return {
|
|
1164
|
-
userKey,
|
|
1165
|
-
userEntry
|
|
1166
|
-
};
|
|
1167
|
-
const emailMatch = findUserByEmail(config.users, user, platformConfig);
|
|
1168
|
-
if (emailMatch?.[1]) return {
|
|
1169
|
-
userKey: emailMatch[0],
|
|
1170
|
-
userEntry: emailMatch[1]
|
|
1171
|
-
};
|
|
1172
|
-
const platformUrl = getPlatformBaseUrl(platformConfig);
|
|
1173
|
-
if (userKey !== user && (opts.allowLegacyUserKey === false || !canUseLegacyUserKey(platformUrl))) return {
|
|
1174
|
-
userKey,
|
|
1175
|
-
userEntry
|
|
1176
|
-
};
|
|
1177
|
-
const legacyEntry = config.users[user];
|
|
1178
|
-
return legacyEntry ? {
|
|
1179
|
-
userKey: user,
|
|
1180
|
-
userEntry: legacyEntry
|
|
1181
|
-
} : {
|
|
1182
|
-
userKey,
|
|
1183
|
-
userEntry
|
|
1184
|
-
};
|
|
1185
|
-
}
|
|
1186
|
-
/**
|
|
1187
|
-
* Resolve the config user key that would be used for a user on the selected platform.
|
|
1188
|
-
* @param config - Platform config
|
|
1189
|
-
* @param user - User name
|
|
1190
|
-
* @param platformConfig - Optional platform connection settings
|
|
1191
|
-
* @param opts - Token lookup options
|
|
1192
|
-
* @returns Resolved config user key
|
|
1193
|
-
*/
|
|
1194
|
-
function resolveUserTokenKey(config, user, platformConfig, opts) {
|
|
1195
|
-
return findUserEntry(config, user, platformConfig, opts).userKey;
|
|
1196
|
-
}
|
|
1197
|
-
function resolveConfigUser(config, user, platformConfig, opts) {
|
|
1198
|
-
const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
|
|
1199
|
-
return userEntry ? userFromPlatformUserKey(userKey, platformConfig) : void 0;
|
|
1200
|
-
}
|
|
1201
|
-
/**
|
|
1202
|
-
* Check whether tokens are registered for a user on the selected platform.
|
|
1203
|
-
* @param config - Platform config
|
|
1204
|
-
* @param user - User name
|
|
1205
|
-
* @param platformConfig - Optional platform connection settings
|
|
1206
|
-
* @param opts - Token lookup options
|
|
1207
|
-
* @returns True when the user has a registered token entry
|
|
1208
|
-
*/
|
|
1209
|
-
function hasUserTokenEntry(config, user, platformConfig, opts) {
|
|
1210
|
-
return findUserEntry(config, user, platformConfig, opts).userEntry !== void 0;
|
|
1211
|
-
}
|
|
1212
|
-
function hasUserKeyForName(users, user) {
|
|
1213
|
-
return users[user] !== void 0 || Object.keys(users).some((key) => key.endsWith(`|${user}`));
|
|
1214
|
-
}
|
|
1215
|
-
function hasUserEmailEntry(users, user) {
|
|
1216
|
-
return Object.values(users).some((entry) => entry?.email === user);
|
|
1217
|
-
}
|
|
1218
|
-
/**
|
|
1219
|
-
* Check whether any platform has tokens registered for a user.
|
|
1220
|
-
* @param config - Platform config
|
|
1221
|
-
* @param user - User name
|
|
1222
|
-
* @returns True when the user has a token entry for any platform
|
|
1223
|
-
*/
|
|
1224
|
-
function hasAnyUserTokenEntry(config, user) {
|
|
1225
|
-
return hasUserKeyForName(config.users, user) || hasUserEmailEntry(config.users, user);
|
|
1226
|
-
}
|
|
1227
|
-
function hasCurrentUserEntry(users, currentUser) {
|
|
1228
|
-
return hasUserKeyForName(users, currentUser);
|
|
1229
|
-
}
|
|
1230
|
-
/**
|
|
1231
|
-
* Migrate a v1 config to v2.
|
|
1232
|
-
* Tokens are kept in the config file (storage: "file") during migration.
|
|
1233
|
-
* They will be moved to the OS keyring on next login or token refresh.
|
|
1234
|
-
* @param v1Config - v1 configuration to migrate
|
|
1235
|
-
* @returns Migrated v2 configuration
|
|
1236
|
-
*/
|
|
1237
|
-
function migrateV1ToV2(v1Config) {
|
|
1238
|
-
const users = {};
|
|
1239
|
-
for (const [name, v1User] of Object.entries(v1Config.users)) {
|
|
1240
|
-
if (!v1User) continue;
|
|
1241
|
-
users[name] = {
|
|
1242
|
-
access_token: v1User.access_token,
|
|
1243
|
-
refresh_token: v1User.refresh_token,
|
|
1244
|
-
token_expires_at: v1User.token_expires_at,
|
|
1245
|
-
storage: "file"
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
return {
|
|
1249
|
-
version: V2_CONFIG_VERSION,
|
|
1250
|
-
min_sdk_version: V2_MIN_SDK_VERSION,
|
|
1251
|
-
users,
|
|
1252
|
-
profiles: v1Config.profiles,
|
|
1253
|
-
current_user: v1Config.current_user
|
|
1254
|
-
};
|
|
1255
|
-
}
|
|
1256
|
-
function inferEmailFromUserId(user) {
|
|
1257
|
-
return z.email().safeParse(user).success ? user : void 0;
|
|
1258
|
-
}
|
|
1259
|
-
function migrateV2ToV3(v2Config) {
|
|
1260
|
-
const users = {};
|
|
1261
|
-
for (const [user, entry] of Object.entries(v2Config.users)) {
|
|
1262
|
-
if (!entry) continue;
|
|
1263
|
-
const email = inferEmailFromUserId(user);
|
|
1264
|
-
users[user] = {
|
|
1265
|
-
...entry,
|
|
1266
|
-
...email ? { email } : {}
|
|
1267
|
-
};
|
|
1268
|
-
}
|
|
1269
|
-
return {
|
|
1270
|
-
version: LATEST_CONFIG_VERSION,
|
|
1271
|
-
min_sdk_version: V3_MIN_SDK_VERSION,
|
|
1272
|
-
users,
|
|
1273
|
-
profiles: v2Config.profiles,
|
|
1274
|
-
current_user: v2Config.current_user
|
|
1275
|
-
};
|
|
1276
|
-
}
|
|
1277
|
-
function migrateV1ToV3(v1Config) {
|
|
1278
|
-
return migrateV2ToV3(migrateV1ToV2(v1Config));
|
|
1279
|
-
}
|
|
1280
|
-
function formatUnknownError(error) {
|
|
1281
|
-
return error instanceof Error ? error.message : String(error);
|
|
1282
|
-
}
|
|
1283
|
-
async function trySaveTokensInKeyring(user, tokens) {
|
|
1284
|
-
if (!await isKeyringAvailable()) return false;
|
|
1285
|
-
try {
|
|
1286
|
-
await saveKeyringTokens(user, tokens);
|
|
1287
|
-
return true;
|
|
1288
|
-
} catch (error) {
|
|
1289
|
-
logger.warn(`System keyring failed to store credentials. Tokens will be stored in the config file. ${formatUnknownError(error)}`);
|
|
1290
|
-
return false;
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
async function warnIfNewerConfigAvailable(config) {
|
|
1294
|
-
if (!config.latest_min_sdk_version) return;
|
|
1295
|
-
if (lt((await readPackageJson()).version ?? "0.0.0", config.latest_min_sdk_version)) logger.warn(multiline`
|
|
1296
|
-
A newer config version (${String(config.latest_version)}) is available.
|
|
1297
|
-
Please update your SDK to >= ${config.latest_min_sdk_version}: pnpm update @tailor-platform/sdk
|
|
1298
|
-
`);
|
|
1299
|
-
}
|
|
1300
|
-
/**
|
|
1301
|
-
* Read Tailor Platform CLI configuration, migrating from v1 if necessary.
|
|
1302
|
-
* @returns Parsed platform configuration
|
|
1303
|
-
*/
|
|
1304
|
-
async function readPlatformConfig() {
|
|
1305
|
-
const configPath = platformConfigPath();
|
|
1306
|
-
if (!fs$1.existsSync(configPath)) {
|
|
1307
|
-
const config = {
|
|
1308
|
-
version: LATEST_CONFIG_VERSION,
|
|
1309
|
-
min_sdk_version: V3_MIN_SDK_VERSION,
|
|
1310
|
-
users: {},
|
|
1311
|
-
profiles: {},
|
|
1312
|
-
current_user: null
|
|
1313
|
-
};
|
|
1314
|
-
writePlatformConfig(config);
|
|
1315
|
-
return config;
|
|
1316
|
-
}
|
|
1317
|
-
const rawConfig = parseYAML(fs$1.readFileSync(configPath, "utf-8"));
|
|
1318
|
-
tightenSecretFilePermissions(configPath);
|
|
1319
|
-
const version = rawConfig != null && typeof rawConfig === "object" && "version" in rawConfig ? rawConfig.version : void 0;
|
|
1320
|
-
if (typeof version === "number" && version > LATEST_CONFIG_VERSION) {
|
|
1321
|
-
const minSdk = "min_sdk_version" in rawConfig ? String(rawConfig.min_sdk_version) : void 0;
|
|
1322
|
-
const updateHint = minSdk ? `Please update your SDK to >= ${minSdk}: pnpm update @tailor-platform/sdk` : "Please update your SDK: pnpm update @tailor-platform/sdk";
|
|
1323
|
-
throw new Error(multiline`
|
|
1324
|
-
Config file uses version ${String(version)}, but this SDK only supports up to version ${String(LATEST_CONFIG_VERSION)}.
|
|
1325
|
-
${updateHint}
|
|
1326
|
-
`);
|
|
1327
|
-
}
|
|
1328
|
-
const v3Result = pfConfigSchemaV3.safeParse(rawConfig);
|
|
1329
|
-
if (v3Result.success) {
|
|
1330
|
-
await warnIfNewerConfigAvailable(v3Result.data);
|
|
1331
|
-
return v3Result.data;
|
|
1332
|
-
}
|
|
1333
|
-
const v2Result = pfConfigSchemaV2.safeParse(rawConfig);
|
|
1334
|
-
if (v2Result.success) {
|
|
1335
|
-
await warnIfNewerConfigAvailable(v2Result.data);
|
|
1336
|
-
return migrateV2ToV3(v2Result.data);
|
|
1337
|
-
}
|
|
1338
|
-
const v1Result = pfConfigSchemaV1.safeParse(rawConfig);
|
|
1339
|
-
if (v1Result.success) return migrateV1ToV3(v1Result.data);
|
|
1340
|
-
throw new Error(multiline`
|
|
1341
|
-
Failed to parse config file at ${configPath}.
|
|
1342
|
-
The file may be corrupted or created by an incompatible SDK version.
|
|
1343
|
-
`);
|
|
1344
|
-
}
|
|
1345
|
-
function toV1ForDisk(config) {
|
|
1346
|
-
const users = {};
|
|
1347
|
-
for (const [name, entry] of Object.entries(config.users)) {
|
|
1348
|
-
if (!entry || entry.storage === "keyring") continue;
|
|
1349
|
-
users[name] = {
|
|
1350
|
-
access_token: entry.access_token,
|
|
1351
|
-
refresh_token: entry.refresh_token,
|
|
1352
|
-
token_expires_at: entry.token_expires_at
|
|
1353
|
-
};
|
|
1354
|
-
}
|
|
1355
|
-
const currentUser = config.current_user && hasCurrentUserEntry(users, config.current_user) ? config.current_user : null;
|
|
1356
|
-
return {
|
|
1357
|
-
version: 1,
|
|
1358
|
-
users,
|
|
1359
|
-
profiles: config.profiles,
|
|
1360
|
-
current_user: currentUser
|
|
1361
|
-
};
|
|
1362
|
-
}
|
|
1363
|
-
function hasProfilePlatformSettings(config) {
|
|
1364
|
-
return Object.values(config.profiles).some((profile) => profile?.platform_url !== void 0 || profile?.oauth2_client_id !== void 0 || profile?.console_url !== void 0);
|
|
1365
|
-
}
|
|
1366
|
-
function hasScopedUserKeys(config) {
|
|
1367
|
-
return Object.keys(config.users).some((userKey) => userKey.includes("|"));
|
|
1368
|
-
}
|
|
1369
|
-
function hasUserEmailMetadata(config) {
|
|
1370
|
-
return Object.values(config.users).some((user) => user != null && "email" in user && user.email !== void 0);
|
|
1371
|
-
}
|
|
1372
|
-
function toLatestForDisk(config) {
|
|
1373
|
-
return {
|
|
1374
|
-
...config.version === 1 ? migrateV1ToV2(config) : config,
|
|
1375
|
-
version: LATEST_CONFIG_VERSION,
|
|
1376
|
-
min_sdk_version: V3_MIN_SDK_VERSION
|
|
1377
|
-
};
|
|
1378
|
-
}
|
|
1379
|
-
/**
|
|
1380
|
-
* Write Tailor Platform CLI configuration to disk.
|
|
1381
|
-
* By default, file-backed configs without newer fields are converted to V1 for
|
|
1382
|
-
* backward compatibility, so an older SDK can still read the file. Configs
|
|
1383
|
-
* containing a keyring user are kept in V2 or later because the keyring storage
|
|
1384
|
-
* variant is not representable in V1. Configs containing profile-level Platform
|
|
1385
|
-
* settings, platform-scoped user tokens, canonical user IDs, or email metadata
|
|
1386
|
-
* are written in the latest min-SDK-gated format because older SDKs would
|
|
1387
|
-
* silently drop or misread those settings.
|
|
1388
|
-
*
|
|
1389
|
-
* The config file may contain access/refresh tokens when the OS keyring is
|
|
1390
|
-
* unavailable, so it is written via {@link writeSecretFile} so other users
|
|
1391
|
-
* on the host cannot read it.
|
|
1392
|
-
* @param config - Platform configuration to write
|
|
1393
|
-
*/
|
|
1394
|
-
function writePlatformConfig(config) {
|
|
1395
|
-
const configPath = platformConfigPath();
|
|
1396
|
-
const hasKeyringUser = config.version !== 1 && Object.values(config.users).some((u) => u?.storage === "keyring");
|
|
1397
|
-
writeSecretFile(configPath, stringifyYAML(config.version === LATEST_CONFIG_VERSION || hasProfilePlatformSettings(config) || hasScopedUserKeys(config) || hasUserEmailMetadata(config) ? toLatestForDisk(config) : config.version === V2_CONFIG_VERSION && !hasKeyringUser ? toV1ForDisk(config) : config));
|
|
1398
|
-
}
|
|
1399
|
-
function validateUUID(value, source) {
|
|
1400
|
-
const result = z.uuid().safeParse(value);
|
|
1401
|
-
if (!result.success) throw new Error(`Invalid value from ${source}: must be a valid UUID`);
|
|
1402
|
-
return result.data;
|
|
1403
|
-
}
|
|
1404
|
-
/**
|
|
1405
|
-
* Load workspace ID from command options, environment variables, or platform config.
|
|
1406
|
-
* In CLI context, env fallback is also handled by politty's arg env option.
|
|
1407
|
-
* Priority: opts/workspaceId > env/workspaceId > opts/profile > error
|
|
1408
|
-
* @param opts - Workspace and profile options
|
|
1409
|
-
* @returns Resolved workspace ID
|
|
1410
|
-
*/
|
|
1411
|
-
async function loadWorkspaceId(opts) {
|
|
1412
|
-
const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
1413
|
-
if (opts?.workspaceId) return validateUUID(opts.workspaceId, "--workspace-id option");
|
|
1414
|
-
if (process.env.TAILOR_PLATFORM_WORKSPACE_ID) return validateUUID(process.env.TAILOR_PLATFORM_WORKSPACE_ID, "TAILOR_PLATFORM_WORKSPACE_ID environment variable");
|
|
1415
|
-
if (profile) {
|
|
1416
|
-
const wsId = (await readPlatformConfig()).profiles[profile]?.workspace_id;
|
|
1417
|
-
if (!wsId) throw new Error(`Profile "${profile}" not found`);
|
|
1418
|
-
return validateUUID(wsId, `profile "${profile}"`);
|
|
1419
|
-
}
|
|
1420
|
-
throw new Error(multiline`
|
|
1421
|
-
Workspace ID not found.
|
|
1422
|
-
Please specify workspace ID via --workspace-id option or TAILOR_PLATFORM_WORKSPACE_ID environment variable.
|
|
1423
|
-
`);
|
|
1424
|
-
}
|
|
1425
|
-
/**
|
|
1426
|
-
* Load machine user name from command options, environment variables, or platform config.
|
|
1427
|
-
* In CLI context, env fallback is also handled by politty's arg env option.
|
|
1428
|
-
* Priority: opts/machineUser > env/TAILOR_PLATFORM_MACHINE_USER_NAME > opts/profile (profile default) > undefined.
|
|
1429
|
-
* An explicitly empty `opts.machineUser` is rejected with a CLIError (`MACHINE_USER_NAME_EMPTY`) rather than falling back to the env var or profile default.
|
|
1430
|
-
* When the active profile has `machine_user_override: "deny"`, an explicit value that differs from the profile's machine user throws a CLIError with code `PROFILE_MACHINE_USER_OVERRIDE_DENIED`.
|
|
1431
|
-
* @param opts - Machine user and profile options
|
|
1432
|
-
* @returns Resolved machine user name, or undefined if not set
|
|
1433
|
-
*/
|
|
1434
|
-
async function loadMachineUserName(opts) {
|
|
1435
|
-
if (opts?.machineUser === "") throw createCLIError({
|
|
1436
|
-
code: "MACHINE_USER_NAME_EMPTY",
|
|
1437
|
-
message: "Machine user name cannot be empty.",
|
|
1438
|
-
suggestion: "Pass a non-empty machine user name, or omit the option to use the environment variable or profile default."
|
|
1439
|
-
});
|
|
1440
|
-
const envMachineUser = process.env.TAILOR_PLATFORM_MACHINE_USER_NAME || void 0;
|
|
1441
|
-
const explicitMachineUser = opts?.machineUser ? {
|
|
1442
|
-
source: opts.machineUserSource ?? "option",
|
|
1443
|
-
value: opts.machineUser
|
|
1444
|
-
} : envMachineUser ? {
|
|
1445
|
-
source: "env",
|
|
1446
|
-
value: envMachineUser
|
|
1447
|
-
} : void 0;
|
|
1448
|
-
const explicit = explicitMachineUser?.value;
|
|
1449
|
-
const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
1450
|
-
if (!profile) return explicit;
|
|
1451
|
-
const entry = (await readPlatformConfig()).profiles[profile];
|
|
1452
|
-
if (!entry) {
|
|
1453
|
-
if (explicit) return explicit;
|
|
1454
|
-
throw new Error(`Profile "${profile}" not found`);
|
|
1455
|
-
}
|
|
1456
|
-
if (entry.machine_user && entry.machine_user_override === "deny") {
|
|
1457
|
-
if (explicit && explicit !== entry.machine_user) {
|
|
1458
|
-
const details = explicitMachineUser.source === "env" ? `The machine user is being set to "${explicit}" via the TAILOR_PLATFORM_MACHINE_USER_NAME environment variable, which conflicts with this profile's pinned machine user "${entry.machine_user}".` : `This profile fixes the machine user to "${entry.machine_user}" for application-data commands.`;
|
|
1459
|
-
throw createCLIError({
|
|
1460
|
-
code: "PROFILE_MACHINE_USER_OVERRIDE_DENIED",
|
|
1461
|
-
message: `Profile "${profile}" denies overriding the machine user.`,
|
|
1462
|
-
details,
|
|
1463
|
-
suggestion: `Omit the machine user option, unset TAILOR_PLATFORM_MACHINE_USER_NAME, or run 'tailor profile update ${profile} --machine-user-override allow'.`
|
|
1464
|
-
});
|
|
1465
|
-
}
|
|
1466
|
-
return entry.machine_user;
|
|
1467
|
-
}
|
|
1468
|
-
return explicit || entry.machine_user;
|
|
1469
|
-
}
|
|
1470
|
-
/**
|
|
1471
|
-
* Load access token from environment variables, command options, or platform config.
|
|
1472
|
-
* In CLI context, profile env fallback is also handled by politty's arg env option.
|
|
1473
|
-
* Priority: env/TAILOR_PLATFORM_TOKEN > env/TAILOR_TOKEN (deprecated) > opts/profile > env/profile > config/currentUser > error
|
|
1474
|
-
* @param opts - Profile options
|
|
1475
|
-
* @returns Resolved access token
|
|
1476
|
-
*/
|
|
1477
|
-
async function loadAccessToken(opts) {
|
|
1478
|
-
const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
1479
|
-
const envToken = process.env.TAILOR_PLATFORM_TOKEN ?? process.env.TAILOR_TOKEN;
|
|
1480
|
-
if (envToken && !process.env.TAILOR_PLATFORM_TOKEN) logger.warn("TAILOR_TOKEN is deprecated. Please use TAILOR_PLATFORM_TOKEN instead.");
|
|
1481
|
-
if (envToken) {
|
|
1482
|
-
rememberPlatformConfigForToken(envToken, await loadPlatformClientConfig({
|
|
1483
|
-
profile,
|
|
1484
|
-
allowMissingProfile: true
|
|
1485
|
-
}));
|
|
1486
|
-
return envToken;
|
|
1487
|
-
}
|
|
1488
|
-
const pfConfig = await readPlatformConfig();
|
|
1489
|
-
const profileEntry = profile ? pfConfig.profiles[profile] : void 0;
|
|
1490
|
-
if (profile && !profileEntry) throw new Error(`Profile "${profile}" not found`);
|
|
1491
|
-
const user = profileEntry?.user ?? pfConfig.current_user;
|
|
1492
|
-
if (!user) throw new Error(multiline`
|
|
1493
|
-
Tailor Platform token not found.
|
|
1494
|
-
Please specify token via TAILOR_PLATFORM_TOKEN environment variable or login using 'tailor login' command.
|
|
1495
|
-
`);
|
|
1496
|
-
return (await fetchLatestToken(pfConfig, user, profileEntry ? platformConfigFromProfile(profileEntry) : void 0)).accessToken;
|
|
1497
|
-
}
|
|
1498
|
-
/**
|
|
1499
|
-
* Load platform connection settings from the active profile.
|
|
1500
|
-
* @param opts - Profile options
|
|
1501
|
-
* @returns Resolved platform connection settings, or undefined for the default environment
|
|
1502
|
-
*/
|
|
1503
|
-
async function loadPlatformClientConfig(opts) {
|
|
1504
|
-
const profile = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
|
|
1505
|
-
if (!profile) return;
|
|
1506
|
-
let pfConfig;
|
|
1507
|
-
try {
|
|
1508
|
-
pfConfig = await readPlatformConfig();
|
|
1509
|
-
} catch (error) {
|
|
1510
|
-
if (opts?.allowMissingProfile) return;
|
|
1511
|
-
throw error;
|
|
1512
|
-
}
|
|
1513
|
-
const profileEntry = pfConfig.profiles[profile];
|
|
1514
|
-
if (!profileEntry) {
|
|
1515
|
-
if (opts?.allowMissingProfile) return;
|
|
1516
|
-
throw new Error(`Profile "${profile}" not found`);
|
|
1517
|
-
}
|
|
1518
|
-
return platformConfigFromProfile(profileEntry);
|
|
1519
|
-
}
|
|
1520
|
-
/**
|
|
1521
|
-
* Load the Tailor Platform Console base URL from environment variables or the active profile.
|
|
1522
|
-
* @param opts - Profile options
|
|
1523
|
-
* @returns Resolved Console base URL
|
|
1524
|
-
*/
|
|
1525
|
-
async function loadConsoleBaseUrl(opts) {
|
|
1526
|
-
return getConsoleBaseUrl(await loadPlatformClientConfig(opts));
|
|
1527
|
-
}
|
|
1528
|
-
/**
|
|
1529
|
-
* Resolve the actual token values for a user, reading from keyring or config as appropriate.
|
|
1530
|
-
* @param userEntry - User entry from the config
|
|
1531
|
-
* @param user - User identifier
|
|
1532
|
-
* @param label - User-facing identifier used in error messages
|
|
1533
|
-
* @returns Access token and optional refresh token
|
|
1534
|
-
*/
|
|
1535
|
-
async function resolveTokens(userEntry, user, label = user) {
|
|
1536
|
-
if (userEntry.storage === "keyring") {
|
|
1537
|
-
const tokens = await loadKeyringTokens(user);
|
|
1538
|
-
if (!tokens) throw new Error(multiline`
|
|
1539
|
-
Credentials not found in OS keyring for "${label}".
|
|
1540
|
-
Please run 'tailor login' and try again.
|
|
1541
|
-
`);
|
|
1542
|
-
return tokens;
|
|
1543
|
-
}
|
|
1544
|
-
return {
|
|
1545
|
-
accessToken: userEntry.access_token,
|
|
1546
|
-
refreshToken: userEntry.refresh_token
|
|
1547
|
-
};
|
|
1548
|
-
}
|
|
1549
|
-
/**
|
|
1550
|
-
* Save tokens for a user, writing to keyring by default when available.
|
|
1551
|
-
* @param config - Platform config
|
|
1552
|
-
* @param user - User identifier
|
|
1553
|
-
* @param tokens - Token data to save
|
|
1554
|
-
* @param tokens.accessToken - Access token to save
|
|
1555
|
-
* @param tokens.refreshToken - Optional refresh token to save
|
|
1556
|
-
* @param expiresAt - Token expiration date
|
|
1557
|
-
* @param opts - Optional platform and user metadata
|
|
1558
|
-
*/
|
|
1559
|
-
async function saveUserTokens(config, user, tokens, expiresAt, opts = {}) {
|
|
1560
|
-
const userKey = platformUserKey(user, opts.platformConfig);
|
|
1561
|
-
const email = opts.email ?? config.users[userKey]?.email;
|
|
1562
|
-
if (await trySaveTokensInKeyring(userKey, tokens)) config.users[userKey] = {
|
|
1563
|
-
token_expires_at: expiresAt,
|
|
1564
|
-
storage: "keyring",
|
|
1565
|
-
...email ? { email } : {}
|
|
1566
|
-
};
|
|
1567
|
-
else {
|
|
1568
|
-
if (config.users[userKey]?.storage === "keyring") await deleteKeyringTokens(userKey);
|
|
1569
|
-
config.users[userKey] = {
|
|
1570
|
-
access_token: tokens.accessToken,
|
|
1571
|
-
refresh_token: tokens.refreshToken,
|
|
1572
|
-
token_expires_at: expiresAt,
|
|
1573
|
-
storage: "file",
|
|
1574
|
-
...email ? { email } : {}
|
|
1575
|
-
};
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
/**
|
|
1579
|
-
* Delete tokens for a user from keyring if applicable.
|
|
1580
|
-
* @param config - Platform config
|
|
1581
|
-
* @param user - User identifier
|
|
1582
|
-
* @param platformConfig - Optional platform connection settings
|
|
1583
|
-
* @param opts - Token lookup options
|
|
1584
|
-
*/
|
|
1585
|
-
async function deleteUserTokens(config, user, platformConfig, opts) {
|
|
1586
|
-
const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
|
|
1587
|
-
if (userEntry?.storage === "keyring") await deleteKeyringTokens(userKey);
|
|
1588
|
-
delete config.users[userKey];
|
|
1589
|
-
}
|
|
1590
|
-
/**
|
|
1591
|
-
* Resolve stored tokens for a user on the selected platform.
|
|
1592
|
-
* @param config - Platform config
|
|
1593
|
-
* @param user - User name
|
|
1594
|
-
* @param platformConfig - Optional platform connection settings
|
|
1595
|
-
* @param opts - Token lookup options
|
|
1596
|
-
* @returns Stored user entry and token values, or undefined when the user is not logged in
|
|
1597
|
-
*/
|
|
1598
|
-
async function loadStoredUserTokens(config, user, platformConfig, opts) {
|
|
1599
|
-
const { userKey, userEntry } = findUserEntry(config, user, platformConfig, opts);
|
|
1600
|
-
if (!userEntry) return void 0;
|
|
1601
|
-
return {
|
|
1602
|
-
userEntry,
|
|
1603
|
-
...await resolveTokens(userEntry, userKey, user)
|
|
1604
|
-
};
|
|
1605
|
-
}
|
|
1606
|
-
function updateUserReferences(config, fromUser, toUser) {
|
|
1607
|
-
if (fromUser === toUser) return;
|
|
1608
|
-
if (config.current_user === fromUser) config.current_user = toUser;
|
|
1609
|
-
for (const profile of Object.values(config.profiles)) if (profile?.user === fromUser) profile.user = toUser;
|
|
1610
|
-
}
|
|
1611
|
-
/**
|
|
1612
|
-
* Remove a legacy alias after a canonical user ID has been written.
|
|
1613
|
-
* @param config - Platform config
|
|
1614
|
-
* @param legacyUser - Previous user key
|
|
1615
|
-
* @param canonicalUser - Canonical user key
|
|
1616
|
-
* @param platformConfig - Platform settings for scoped token storage
|
|
1617
|
-
*/
|
|
1618
|
-
async function removeLegacyUserAlias(config, legacyUser, canonicalUser, platformConfig) {
|
|
1619
|
-
if (legacyUser === canonicalUser) return;
|
|
1620
|
-
updateUserReferences(config, legacyUser, canonicalUser);
|
|
1621
|
-
const canonicalKey = platformUserKey(canonicalUser, platformConfig);
|
|
1622
|
-
const legacyKeys = /* @__PURE__ */ new Set([legacyUser, platformUserKey(legacyUser, platformConfig)]);
|
|
1623
|
-
for (const legacyKey of legacyKeys) {
|
|
1624
|
-
if (legacyKey === canonicalKey) continue;
|
|
1625
|
-
if (config.users[legacyKey]?.storage === "keyring") await deleteKeyringTokens(legacyKey);
|
|
1626
|
-
delete config.users[legacyKey];
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
function shouldResolveSubjectOnRefresh(user, userEntry) {
|
|
1630
|
-
return Boolean(userEntry.email || inferEmailFromUserId(user));
|
|
1631
|
-
}
|
|
1632
|
-
/**
|
|
1633
|
-
* Fetch the latest access token, refreshing if necessary.
|
|
1634
|
-
* @param config - Platform config
|
|
1635
|
-
* @param user - User identifier
|
|
1636
|
-
* @param platformConfig - Optional platform connection settings
|
|
1637
|
-
* @returns Latest access token and the canonical user ID it is stored under
|
|
1638
|
-
* (the resolved subject when a legacy email key was migrated during refresh,
|
|
1639
|
-
* otherwise the matching config user)
|
|
1640
|
-
*/
|
|
1641
|
-
async function fetchLatestToken(config, user, platformConfig) {
|
|
1642
|
-
const { userKey: storedUser, userEntry } = findUserEntry(config, user, platformConfig);
|
|
1643
|
-
if (!userEntry) throw new Error(multiline`
|
|
1644
|
-
User "${user}" not found.
|
|
1645
|
-
Please verify your user name and login using 'tailor login' command.
|
|
1646
|
-
`);
|
|
1647
|
-
const storedConfigUser = userFromPlatformUserKey(storedUser, platformConfig);
|
|
1648
|
-
const tokens = await resolveTokens(userEntry, storedUser, user);
|
|
1649
|
-
if (new Date(userEntry.token_expires_at) > /* @__PURE__ */ new Date()) {
|
|
1650
|
-
rememberPlatformConfigForToken(tokens.accessToken, platformConfig);
|
|
1651
|
-
return {
|
|
1652
|
-
accessToken: tokens.accessToken,
|
|
1653
|
-
user: storedConfigUser
|
|
1654
|
-
};
|
|
1655
|
-
}
|
|
1656
|
-
if (!tokens.refreshToken) throw new Error(multiline`
|
|
1657
|
-
Token expired.
|
|
1658
|
-
Please run 'tailor login' and try again.
|
|
1659
|
-
`);
|
|
1660
|
-
const client = initOAuth2Client(platformConfig);
|
|
1661
|
-
let resp;
|
|
1662
|
-
try {
|
|
1663
|
-
resp = await client.refreshToken({
|
|
1664
|
-
accessToken: tokens.accessToken,
|
|
1665
|
-
refreshToken: tokens.refreshToken,
|
|
1666
|
-
expiresAt: Date.parse(userEntry.token_expires_at)
|
|
1667
|
-
});
|
|
1668
|
-
} catch {
|
|
1669
|
-
throw new Error(multiline`
|
|
1670
|
-
Failed to refresh token. Your session may have expired.
|
|
1671
|
-
Please run 'tailor login' and try again.
|
|
1672
|
-
`);
|
|
1673
|
-
}
|
|
1674
|
-
const newExpiresAt = new Date(assertDefined(resp.expiresAt, "token refresh response missing expiresAt")).toISOString();
|
|
1675
|
-
let resolvedUser = storedConfigUser;
|
|
1676
|
-
const previousEmail = userEntry.email ?? inferEmailFromUserId(user) ?? inferEmailFromUserId(storedConfigUser);
|
|
1677
|
-
let email = previousEmail;
|
|
1678
|
-
if (shouldResolveSubjectOnRefresh(user, userEntry) || shouldResolveSubjectOnRefresh(storedConfigUser, userEntry)) try {
|
|
1679
|
-
const userInfo = await fetchUserInfo(resp.accessToken, platformConfig);
|
|
1680
|
-
resolvedUser = userInfo.sub;
|
|
1681
|
-
email = userInfo.email;
|
|
1682
|
-
} catch (error) {
|
|
1683
|
-
logger.debug(`Failed to resolve refreshed token user info: ${String(error)}`);
|
|
1684
|
-
}
|
|
1685
|
-
await saveUserTokens(config, resolvedUser, {
|
|
1686
|
-
accessToken: resp.accessToken,
|
|
1687
|
-
refreshToken: resp.refreshToken ?? void 0
|
|
1688
|
-
}, newExpiresAt, {
|
|
1689
|
-
platformConfig,
|
|
1690
|
-
email
|
|
1691
|
-
});
|
|
1692
|
-
await removeLegacyUserAlias(config, user, resolvedUser, platformConfig);
|
|
1693
|
-
if (storedUser !== platformUserKey(resolvedUser, platformConfig)) {
|
|
1694
|
-
if (config.users[storedUser]?.storage === "keyring") await deleteKeyringTokens(storedUser);
|
|
1695
|
-
delete config.users[storedUser];
|
|
1696
|
-
}
|
|
1697
|
-
if (previousEmail && email && previousEmail !== email) logger.info(`Updated local user email from "${previousEmail}" to "${email}".`);
|
|
1698
|
-
writePlatformConfig(config);
|
|
1699
|
-
rememberPlatformConfigForToken(resp.accessToken, platformConfig);
|
|
1700
|
-
return {
|
|
1701
|
-
accessToken: resp.accessToken,
|
|
1702
|
-
user: resolvedUser
|
|
1703
|
-
};
|
|
1704
|
-
}
|
|
1705
|
-
const DEFAULT_CONFIG_FILENAME = "tailor.config.ts";
|
|
1706
|
-
/**
|
|
1707
|
-
* Load config path from command options, environment variables, or search parent directories.
|
|
1708
|
-
* In CLI context, env fallback is also handled by politty's arg env option.
|
|
1709
|
-
* Priority: opts/config > env/config > search parent directories
|
|
1710
|
-
* @param configPath - Optional explicit config path
|
|
1711
|
-
* @returns Resolved config path or undefined
|
|
1712
|
-
*/
|
|
1713
|
-
function loadConfigPath(configPath) {
|
|
1714
|
-
if (configPath) return configPath;
|
|
1715
|
-
if (process.env.TAILOR_CONFIG_PATH) return process.env.TAILOR_CONFIG_PATH;
|
|
1716
|
-
return findUpSync(DEFAULT_CONFIG_FILENAME);
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
//#endregion
|
|
1720
|
-
//#region src/cli/shared/mock.ts
|
|
1721
|
-
/**
|
|
1722
|
-
* Install a stub `globalThis.tailordb` so that user code loaded by the CLI
|
|
1723
|
-
* (e.g. via `createGetDB` in `@tailor-platform/sdk/kysely`) can reference
|
|
1724
|
-
* `tailordb.Client` without hitting a `ReferenceError`. The CLI never
|
|
1725
|
-
* actually executes the user code paths that issue queries, so a no-op
|
|
1726
|
-
* client suffices.
|
|
1727
|
-
*
|
|
1728
|
-
* Exposed as a function (rather than a top-level statement) so that
|
|
1729
|
-
* `package.json#sideEffects` can keep the file marked side-effect-free
|
|
1730
|
-
* without bundlers eliminating the install step.
|
|
1731
|
-
*/
|
|
1732
|
-
function installCliTailordbStub() {
|
|
1733
|
-
globalThis.tailordb = { Client: class {
|
|
1734
|
-
constructor(_config) {}
|
|
1735
|
-
async connect() {}
|
|
1736
|
-
async end() {}
|
|
1737
|
-
async queryObject() {
|
|
1738
|
-
return {};
|
|
1739
|
-
}
|
|
1740
|
-
} };
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
//#endregion
|
|
1744
|
-
//#region src/cli/shared/config-loader.ts
|
|
1745
|
-
/**
|
|
1746
|
-
* Load Tailor configuration file and associated plugins.
|
|
1747
|
-
* @param configPath - Optional explicit config path
|
|
1748
|
-
* @param options - Optional module import behavior.
|
|
1749
|
-
* @returns Loaded config, plugins, and config path
|
|
1750
|
-
*/
|
|
1751
|
-
async function loadConfig(configPath, options = {}) {
|
|
1752
|
-
installCliTailordbStub();
|
|
1753
|
-
const foundPath = loadConfigPath(configPath);
|
|
1754
|
-
if (!foundPath) throw new Error("Configuration file not found: tailor.config.ts not found in current or parent directories");
|
|
1755
|
-
const resolvedPath = path.resolve(process.cwd(), foundPath);
|
|
1756
|
-
if (!fs$1.existsSync(resolvedPath)) throw new Error(`Configuration file not found: ${configPath}`);
|
|
1757
|
-
const configUrl = pathToFileURL(resolvedPath);
|
|
1758
|
-
if (options.importNonce) configUrl.searchParams.set("tailorImportNonce", options.importNonce);
|
|
1759
|
-
const configModule = await import(configUrl.href);
|
|
1760
|
-
if (!configModule || !configModule.default) throw new Error("Invalid Tailor config module: default export not found");
|
|
1761
|
-
const validated = AppConfigSchema.safeParse(configModule.default);
|
|
1762
|
-
if (!validated.success) {
|
|
1763
|
-
const issues = validated.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
1764
|
-
throw new Error(`Invalid Tailor config in ${resolvedPath}:\n${issues}`);
|
|
1765
|
-
}
|
|
1766
|
-
const allPlugins = [];
|
|
1767
|
-
for (const value of Object.values(configModule)) if (Array.isArray(value)) {
|
|
1768
|
-
const pluginParsed = value.reduce((acc, item) => {
|
|
1769
|
-
if (!acc.success) return acc;
|
|
1770
|
-
const result = PluginConfigSchema.safeParse(item);
|
|
1771
|
-
if (result.success) acc.items.push(result.data);
|
|
1772
|
-
else acc.success = false;
|
|
1773
|
-
return acc;
|
|
1774
|
-
}, {
|
|
1775
|
-
success: true,
|
|
1776
|
-
items: []
|
|
1777
|
-
});
|
|
1778
|
-
if (pluginParsed.success && pluginParsed.items.length > 0) allPlugins.push(...pluginParsed.items);
|
|
1779
|
-
}
|
|
1780
|
-
return {
|
|
1781
|
-
config: {
|
|
1782
|
-
...configModule.default,
|
|
1783
|
-
path: resolvedPath
|
|
1784
|
-
},
|
|
1785
|
-
plugins: allPlugins
|
|
1786
|
-
};
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
671
|
//#endregion
|
|
1790
672
|
//#region src/cli/cache/hasher.ts
|
|
1791
673
|
/**
|
|
@@ -1802,7 +684,7 @@ function hashContent(content) {
|
|
|
1802
684
|
* @returns Hex-encoded SHA-256 hash of the file content
|
|
1803
685
|
*/
|
|
1804
686
|
function hashFile(filePath) {
|
|
1805
|
-
const content = fs
|
|
687
|
+
const content = fs.readFileSync(filePath);
|
|
1806
688
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
1807
689
|
}
|
|
1808
690
|
/**
|
|
@@ -1931,7 +813,10 @@ function createBundleCache(store) {
|
|
|
1931
813
|
return;
|
|
1932
814
|
}
|
|
1933
815
|
if (currentHash !== entry.inputHash) return;
|
|
1934
|
-
|
|
816
|
+
const content = store.restoreBundleContent(cacheKey);
|
|
817
|
+
const output = entry.outputFiles.find((file) => file.outputPath === cacheKey);
|
|
818
|
+
if (content === void 0 || !output || hashContent(content) !== output.contentHash) return;
|
|
819
|
+
return content;
|
|
1935
820
|
}
|
|
1936
821
|
function save(params) {
|
|
1937
822
|
const { kind, namespace, name, sourceFile, content, dependencyPaths, contextHash } = params;
|
|
@@ -1978,7 +863,7 @@ function resolveRelativePluginImportPath(pluginImportPath, baseDirs) {
|
|
|
1978
863
|
if (!pluginImportPath.startsWith(".")) return null;
|
|
1979
864
|
for (const baseDir of baseDirs) {
|
|
1980
865
|
const absolutePath = path.resolve(baseDir, pluginImportPath);
|
|
1981
|
-
if (fs
|
|
866
|
+
if (fs.existsSync(absolutePath)) return absolutePath;
|
|
1982
867
|
}
|
|
1983
868
|
return null;
|
|
1984
869
|
}
|
|
@@ -2062,13 +947,13 @@ function generatePluginExecutorFiles(executors, outputDir, typeGenerationResult,
|
|
|
2062
947
|
function generateSingleExecutorFile(info, outputDir, typeGenerationResult, sourceTypeInfoMap, baseDirs = []) {
|
|
2063
948
|
const pluginDir = sanitizePluginId$1(info.pluginId);
|
|
2064
949
|
const executorOutputDir = path.join(outputDir, pluginDir, "executors");
|
|
2065
|
-
fs
|
|
950
|
+
fs.mkdirSync(executorOutputDir, { recursive: true });
|
|
2066
951
|
const fileName = sanitizeExecutorFileName(info.executor.name);
|
|
2067
952
|
const filePath = path.join(executorOutputDir, `${fileName}.ts`);
|
|
2068
953
|
let content;
|
|
2069
954
|
if (isPluginExecutorWithFile(info.executor)) content = generateExecutorFileContentNew(info, info.executor, outputDir, typeGenerationResult, sourceTypeInfoMap, baseDirs);
|
|
2070
955
|
else content = generateExecutorFileContentLegacy(info.executor);
|
|
2071
|
-
fs
|
|
956
|
+
fs.writeFileSync(filePath, content);
|
|
2072
957
|
return filePath;
|
|
2073
958
|
}
|
|
2074
959
|
/**
|
|
@@ -2300,7 +1185,7 @@ function extractDynamicImportSpecifier(resolve) {
|
|
|
2300
1185
|
function resolvePluginBaseDir(pluginImportPath, baseDirs) {
|
|
2301
1186
|
if (pluginImportPath.startsWith(".")) {
|
|
2302
1187
|
const resolvedPath = resolveRelativePluginImportPath(pluginImportPath, baseDirs) ?? path.resolve(baseDirs[0] ?? process.cwd(), pluginImportPath);
|
|
2303
|
-
if (fs
|
|
1188
|
+
if (fs.existsSync(resolvedPath)) return fs.statSync(resolvedPath).isDirectory() ? resolvedPath : path.dirname(resolvedPath);
|
|
2304
1189
|
return path.extname(resolvedPath) ? path.dirname(resolvedPath) : resolvedPath;
|
|
2305
1190
|
}
|
|
2306
1191
|
for (const baseDir of baseDirs) try {
|
|
@@ -2387,11 +1272,11 @@ function generatePluginTypeFiles(types, outputDir) {
|
|
|
2387
1272
|
seenTypeNames.set(info.type.name, info);
|
|
2388
1273
|
const pluginDir = sanitizePluginId(info.pluginId);
|
|
2389
1274
|
const typeOutputDir = path.join(outputDir, pluginDir, "types");
|
|
2390
|
-
fs
|
|
1275
|
+
fs.mkdirSync(typeOutputDir, { recursive: true });
|
|
2391
1276
|
const fileName = `${toKebabCase(info.type.name)}.ts`;
|
|
2392
1277
|
const filePath = path.join(typeOutputDir, fileName);
|
|
2393
1278
|
const content = generateTypeFileContent(info);
|
|
2394
|
-
fs
|
|
1279
|
+
fs.writeFileSync(filePath, content);
|
|
2395
1280
|
generatedFiles.push(filePath);
|
|
2396
1281
|
const relativePath = path.relative(outputDir, filePath);
|
|
2397
1282
|
typeFilePaths.set(info.type.name, relativePath);
|
|
@@ -2512,20 +1397,6 @@ function toCamelCase(str) {
|
|
|
2512
1397
|
return result.charAt(0).toLowerCase() + result.slice(1);
|
|
2513
1398
|
}
|
|
2514
1399
|
|
|
2515
|
-
//#endregion
|
|
2516
|
-
//#region src/cli/services/stale-cleanup.ts
|
|
2517
|
-
/**
|
|
2518
|
-
* Remove stale `.entry.js` files from the output directory.
|
|
2519
|
-
*
|
|
2520
|
-
* Must be called before parallel bundling; concurrent builds
|
|
2521
|
-
* sharing the same output directory would otherwise conflict.
|
|
2522
|
-
* @param outputDir - Directory to clean
|
|
2523
|
-
*/
|
|
2524
|
-
async function removeStaleEntryFiles(outputDir) {
|
|
2525
|
-
const files = await fs.readdir(outputDir);
|
|
2526
|
-
await Promise.all(files.filter((file) => file.endsWith(".entry.js")).map((file) => fs.rm(path.join(outputDir, file), { force: true })));
|
|
2527
|
-
}
|
|
2528
|
-
|
|
2529
1400
|
//#endregion
|
|
2530
1401
|
//#region src/cli/services/workflow/ast-utils.ts
|
|
2531
1402
|
/**
|
|
@@ -2934,7 +1805,7 @@ async function buildTriggerContext(workflowConfig, authNamespace) {
|
|
|
2934
1805
|
authNamespace
|
|
2935
1806
|
};
|
|
2936
1807
|
for (const file of loadFilesWithIgnores(workflowConfig)) try {
|
|
2937
|
-
const source = await fs
|
|
1808
|
+
const source = await fs.promises.readFile(file, "utf-8");
|
|
2938
1809
|
const { program } = parseSync("input.ts", source);
|
|
2939
1810
|
modules.set(normalizeFilePath(file), createModuleBindings(program, source));
|
|
2940
1811
|
} catch (error) {
|
|
@@ -3264,13 +2135,39 @@ const platformBundleDefinePlugin = {
|
|
|
3264
2135
|
}
|
|
3265
2136
|
};
|
|
3266
2137
|
|
|
2138
|
+
//#endregion
|
|
2139
|
+
//#region src/cli/shared/virtual-entry.ts
|
|
2140
|
+
/**
|
|
2141
|
+
* Create an in-memory rolldown entry module with a deterministic ID.
|
|
2142
|
+
* @param name - Logical entry name
|
|
2143
|
+
* @param code - Entry module source
|
|
2144
|
+
* @param sourceType - Parser type for the generated module
|
|
2145
|
+
* @returns Rolldown input and plugin for loading the entry
|
|
2146
|
+
*/
|
|
2147
|
+
function createVirtualEntry(name, code, sourceType = "js") {
|
|
2148
|
+
const input = `tailor-sdk-entry:${name}.${sourceType}`;
|
|
2149
|
+
const resolvedId = `\0${input}`;
|
|
2150
|
+
return {
|
|
2151
|
+
input,
|
|
2152
|
+
plugin: {
|
|
2153
|
+
name: "tailor-sdk-virtual-entry",
|
|
2154
|
+
resolveId(source, importer) {
|
|
2155
|
+
return source === input && importer === void 0 ? resolvedId : null;
|
|
2156
|
+
},
|
|
2157
|
+
load(id) {
|
|
2158
|
+
return id === resolvedId ? code : null;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
|
|
3267
2164
|
//#endregion
|
|
3268
2165
|
//#region src/cli/services/auth/bundler.ts
|
|
3269
2166
|
/**
|
|
3270
2167
|
* Bundle a single auth hook handler.
|
|
3271
2168
|
*
|
|
3272
2169
|
* Follows the same pattern as the executor bundler:
|
|
3273
|
-
* 1. Generate an entry
|
|
2170
|
+
* 1. Generate an in-memory entry module that re-exports the handler as `main`
|
|
3274
2171
|
* 2. Bundle with rolldown + tree-shaking
|
|
3275
2172
|
* @param options - Bundle options
|
|
3276
2173
|
* @returns Map of function name to bundled code
|
|
@@ -3279,9 +2176,6 @@ async function bundleAuthHooks(options) {
|
|
|
3279
2176
|
const { configPath, authName, handlerAccessPath, env = {}, triggerContext, cache, inlineSourcemap, bundleLogLevel = "DEBUG" } = options;
|
|
3280
2177
|
logger.newline();
|
|
3281
2178
|
logger.log(`Bundling auth hook for ${styles.info(`"${authName}"`)}`);
|
|
3282
|
-
const outputDir = path.resolve(getDistDir(), "auth-hooks");
|
|
3283
|
-
fs$1.mkdirSync(outputDir, { recursive: true });
|
|
3284
|
-
await removeStaleEntryFiles(outputDir);
|
|
3285
2179
|
let tsconfig;
|
|
3286
2180
|
try {
|
|
3287
2181
|
tsconfig = await resolveTSConfig();
|
|
@@ -3306,7 +2200,6 @@ async function bundleAuthHooks(options) {
|
|
|
3306
2200
|
prefix: sortedEnvPrefix
|
|
3307
2201
|
}),
|
|
3308
2202
|
async build(cachePlugins) {
|
|
3309
|
-
const entryPath = path.join(outputDir, `${functionName}.entry.js`);
|
|
3310
2203
|
const entryContent = multiline`
|
|
3311
2204
|
import _config from "${absoluteConfigPath}";
|
|
3312
2205
|
const __auth_hook_function = _config.${handlerAccessPath};
|
|
@@ -3315,12 +2208,13 @@ async function bundleAuthHooks(options) {
|
|
|
3315
2208
|
return await __auth_hook_function({ ...args, env });
|
|
3316
2209
|
}
|
|
3317
2210
|
`;
|
|
3318
|
-
|
|
2211
|
+
const entry = createVirtualEntry(`auth-hook:${functionName}`, entryContent);
|
|
3319
2212
|
const triggerPlugin = createTriggerTransformPlugin(triggerContext);
|
|
3320
|
-
const plugins =
|
|
2213
|
+
const plugins = [entry.plugin];
|
|
2214
|
+
if (triggerPlugin) plugins.push(triggerPlugin);
|
|
3321
2215
|
plugins.push(platformBundleDefinePlugin, ...cachePlugins);
|
|
3322
2216
|
return (await rolldown.build({
|
|
3323
|
-
input:
|
|
2217
|
+
input: entry.input,
|
|
3324
2218
|
write: false,
|
|
3325
2219
|
output: {
|
|
3326
2220
|
format: "esm",
|
|
@@ -3685,6 +2579,36 @@ function findOmittedPermitRules(rawPermissions) {
|
|
|
3685
2579
|
});
|
|
3686
2580
|
return locations;
|
|
3687
2581
|
}
|
|
2582
|
+
/**
|
|
2583
|
+
* Check whether GraphQL exposure is fully disabled for a type, given its
|
|
2584
|
+
* effective gqlOperations (the type's own setting, falling back to the
|
|
2585
|
+
* TailorDB namespace default). `undefined` means the default of all
|
|
2586
|
+
* operations enabled, so it is never considered fully disabled.
|
|
2587
|
+
* @param gqlOperations - Effective, normalized gqlOperations configuration
|
|
2588
|
+
* @returns Whether create, update, delete, and read are all explicitly disabled
|
|
2589
|
+
*/
|
|
2590
|
+
function isGqlOperationsFullyDisabled(gqlOperations) {
|
|
2591
|
+
if (!gqlOperations) return false;
|
|
2592
|
+
return gqlOperations.create === false && gqlOperations.update === false && gqlOperations.delete === false && gqlOperations.read === false;
|
|
2593
|
+
}
|
|
2594
|
+
/**
|
|
2595
|
+
* Find missing permission configuration for a TailorDB type.
|
|
2596
|
+
*
|
|
2597
|
+
* Record-level permission is always required: TailorDB denies all record
|
|
2598
|
+
* operations for a type without it, regardless of whether the type is
|
|
2599
|
+
* exposed via GraphQL. GraphQL permission is required whenever GraphQL
|
|
2600
|
+
* exposure is enabled for the type (the default, unless every operation is
|
|
2601
|
+
* explicitly disabled via `gqlOperations`).
|
|
2602
|
+
* @param rawPermissions - Raw permissions definition for the type
|
|
2603
|
+
* @param effectiveGqlOperations - The type's own gqlOperations, falling back to the namespace default
|
|
2604
|
+
* @returns Which permission configuration, if any, is missing
|
|
2605
|
+
*/
|
|
2606
|
+
function findMissingPermissionConfig(rawPermissions, effectiveGqlOperations) {
|
|
2607
|
+
return {
|
|
2608
|
+
missingPermission: !rawPermissions.record,
|
|
2609
|
+
missingGqlPermission: !rawPermissions.gql && !isGqlOperationsFullyDisabled(effectiveGqlOperations)
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
3688
2612
|
|
|
3689
2613
|
//#endregion
|
|
3690
2614
|
//#region src/parser/service/tailordb/type-parser.ts
|
|
@@ -4221,7 +3145,7 @@ function buildMinimalEntryFromResolved(imports, declarations, fnSource, sourceFi
|
|
|
4221
3145
|
].join("\n");
|
|
4222
3146
|
}
|
|
4223
3147
|
async function bundleScriptTarget(args) {
|
|
4224
|
-
const { fn, kind, sourceFilePath, sourceBindings,
|
|
3148
|
+
const { fn, kind, sourceFilePath, sourceBindings, typeName, targetIndex, tsconfig } = args;
|
|
4225
3149
|
const context = `${kind} in ${sourceFilePath}`;
|
|
4226
3150
|
const fnSource = stringifyFunction(fn);
|
|
4227
3151
|
if ((kind === "typeValidate" || kind === "validate") && fn.constructor.name === "AsyncFunction") throw new Error(`${context} must be synchronous — the generated validator runs synchronously, so issues reported after an await are silently lost. Remove the async keyword.`);
|
|
@@ -4233,11 +3157,10 @@ async function bundleScriptTarget(args) {
|
|
|
4233
3157
|
if (unresolved.length > 0) throw new Error(`${context} captures unresolvable variables (${unresolved.join(", ")}). Hooks and validators must not reference variables that cannot be resolved from the source file.
|
|
4234
3158
|
${kind}: ${fnSource}`);
|
|
4235
3159
|
const entryContent = buildMinimalEntryFromResolved(imports, declarations, fnSource, sourceFilePath, kind === "typeValidate");
|
|
4236
|
-
const
|
|
4237
|
-
writeFileSync(entryPath, entryContent);
|
|
3160
|
+
const entry = createVirtualEntry(`tailordb-script:${typeName}:${targetIndex}`, entryContent, "ts");
|
|
4238
3161
|
const bundledCode = (await rolldown.build({
|
|
4239
|
-
plugins: [platformBundleDefinePlugin],
|
|
4240
|
-
input:
|
|
3162
|
+
plugins: [entry.plugin, platformBundleDefinePlugin],
|
|
3163
|
+
input: entry.input,
|
|
4241
3164
|
write: false,
|
|
4242
3165
|
output: {
|
|
4243
3166
|
format: "cjs",
|
|
@@ -4267,27 +3190,18 @@ async function precompileTailorDBTypeScripts(type, sourceFilePath, tsconfig) {
|
|
|
4267
3190
|
const targets = collectScriptTargets(type);
|
|
4268
3191
|
if (targets.length === 0) return;
|
|
4269
3192
|
const sourceBindings = collectSourceBindings(sourceFilePath);
|
|
4270
|
-
const
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
const firstError = results.find((r) => r.status === "rejected");
|
|
4283
|
-
if (firstError) throw firstError.reason;
|
|
4284
|
-
for (const [index, result] of results.entries()) if (result.status === "fulfilled") setPrecompiledScriptExpr(assertDefined(targets[index], `bundle target at index ${index} missing`).fn, result.value);
|
|
4285
|
-
} finally {
|
|
4286
|
-
rmSync(tempDir, {
|
|
4287
|
-
recursive: true,
|
|
4288
|
-
force: true
|
|
4289
|
-
});
|
|
4290
|
-
}
|
|
3193
|
+
const results = await Promise.allSettled(targets.map((target, index) => bundleScriptTarget({
|
|
3194
|
+
fn: target.fn,
|
|
3195
|
+
kind: target.kind,
|
|
3196
|
+
sourceFilePath,
|
|
3197
|
+
sourceBindings,
|
|
3198
|
+
typeName: type.name,
|
|
3199
|
+
targetIndex: index,
|
|
3200
|
+
tsconfig
|
|
3201
|
+
})));
|
|
3202
|
+
const firstError = results.find((r) => r.status === "rejected");
|
|
3203
|
+
if (firstError) throw firstError.reason;
|
|
3204
|
+
for (const [index, result] of results.entries()) if (result.status === "fulfilled") setPrecompiledScriptExpr(assertDefined(targets[index], `bundle target at index ${index} missing`).fn, result.value);
|
|
4291
3205
|
}
|
|
4292
3206
|
|
|
4293
3207
|
//#endregion
|
|
@@ -4445,6 +3359,19 @@ function createTailorDBService(params) {
|
|
|
4445
3359
|
if (locations.length > 0) logger.warn(`TailorDB type "${typeName}" has permission rule(s) ${locations.join(", ")} in object form without an explicit "permit"; they default to "deny". Set permit: true (allow) or permit: false (deny) to silence this warning.`);
|
|
4446
3360
|
}
|
|
4447
3361
|
};
|
|
3362
|
+
const validateRequiredPermissions = () => {
|
|
3363
|
+
const errors = [];
|
|
3364
|
+
for (const fileTypes of Object.values(rawTypes)) for (const [typeName, type] of Object.entries(fileTypes)) {
|
|
3365
|
+
const effectiveGqlOperations = type.metadata.settings?.gqlOperations ?? config.gqlOperations;
|
|
3366
|
+
const { missingPermission, missingGqlPermission } = findMissingPermissionConfig(type.metadata.permissions, effectiveGqlOperations);
|
|
3367
|
+
if (!missingPermission && !missingGqlPermission) continue;
|
|
3368
|
+
const source = formatTailorDBTypeSourceInfo(typeSourceInfo[typeName]);
|
|
3369
|
+
const location = source ? ` (${source})` : "";
|
|
3370
|
+
if (missingPermission) errors.push(`TailorDB type "${typeName}"${location} has no .permission() configured. TailorDB denies all record operations for types without permission; call .permission(...) to grant access explicitly.`);
|
|
3371
|
+
if (missingGqlPermission) errors.push(`TailorDB type "${typeName}"${location} has no .gqlPermission() configured, but GraphQL operations are enabled for it. Call .gqlPermission(...) to grant GraphQL access explicitly, or disable GraphQL exposure with .features({ gqlOperations: { create: false, update: false, delete: false, read: false } }).`);
|
|
3372
|
+
}
|
|
3373
|
+
if (errors.length > 0) throw new Error(`TailorDB permission configuration errors in service "${namespace}":\n${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
3374
|
+
};
|
|
4448
3375
|
/**
|
|
4449
3376
|
* Process plugins for a type and add generated types to rawTypes
|
|
4450
3377
|
* @param rawType - The raw TailorDB type being processed
|
|
@@ -4537,6 +3464,7 @@ function createTailorDBService(params) {
|
|
|
4537
3464
|
else await Promise.all(typeFiles.map((typeFile) => loadTypeFile(typeFile, tsconfig)));
|
|
4538
3465
|
doParseTypes();
|
|
4539
3466
|
warnOmittedPermit();
|
|
3467
|
+
validateRequiredPermissions();
|
|
4540
3468
|
return types;
|
|
4541
3469
|
})();
|
|
4542
3470
|
return loadPromise;
|
|
@@ -4578,7 +3506,10 @@ function createTailorDBService(params) {
|
|
|
4578
3506
|
hasGeneratedTypes = true;
|
|
4579
3507
|
logger.log(` Generated: ${styles.success(generatedType.name)} by namespace plugin ${styles.info(pluginId)}`);
|
|
4580
3508
|
}
|
|
4581
|
-
if (hasGeneratedTypes || hadPreviousGeneratedTypes)
|
|
3509
|
+
if (hasGeneratedTypes || hadPreviousGeneratedTypes) {
|
|
3510
|
+
doParseTypes();
|
|
3511
|
+
validateRequiredPermissions();
|
|
3512
|
+
}
|
|
4582
3513
|
}
|
|
4583
3514
|
};
|
|
4584
3515
|
}
|
|
@@ -4665,9 +3596,31 @@ function resolveBundleConcurrency() {
|
|
|
4665
3596
|
* @param worker - Async worker function
|
|
4666
3597
|
* @returns Worker results in input order
|
|
4667
3598
|
*/
|
|
4668
|
-
function withBundleConcurrency(items, worker) {
|
|
4669
|
-
const
|
|
4670
|
-
|
|
3599
|
+
async function withBundleConcurrency(items, worker) {
|
|
3600
|
+
const resultCount = items.length;
|
|
3601
|
+
const workItems = items.flatMap((item, index) => [{
|
|
3602
|
+
index,
|
|
3603
|
+
item
|
|
3604
|
+
}]);
|
|
3605
|
+
const results = [];
|
|
3606
|
+
results.length = resultCount;
|
|
3607
|
+
let nextWorkIndex = 0;
|
|
3608
|
+
let rejection;
|
|
3609
|
+
const runWorker = async () => {
|
|
3610
|
+
while (!rejection) {
|
|
3611
|
+
const workItem = workItems[nextWorkIndex++];
|
|
3612
|
+
if (workItem === void 0) return;
|
|
3613
|
+
try {
|
|
3614
|
+
results[workItem.index] = await worker(workItem.item);
|
|
3615
|
+
} catch (reason) {
|
|
3616
|
+
rejection ??= { reason };
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
};
|
|
3620
|
+
const workerCount = Math.min(resolveBundleConcurrency(), workItems.length);
|
|
3621
|
+
await Promise.all(Array.from({ length: workerCount }, runWorker));
|
|
3622
|
+
if (rejection) throw rejection.reason;
|
|
3623
|
+
return results;
|
|
4671
3624
|
}
|
|
4672
3625
|
|
|
4673
3626
|
//#endregion
|
|
@@ -4678,7 +3631,7 @@ function withBundleConcurrency(items, worker) {
|
|
|
4678
3631
|
* Two delivery paths:
|
|
4679
3632
|
* - Apply config: shipped with apply and evaluated by the platform before
|
|
4680
3633
|
* invoking user code.
|
|
4681
|
-
* - Bundle inline: interpolated into the generated
|
|
3634
|
+
* - Bundle inline: interpolated into the generated entry module and
|
|
4682
3635
|
* evaluated inside the bundled script at function entry.
|
|
4683
3636
|
*
|
|
4684
3637
|
* The principal field mapping (server → SDK) shared across services is built by
|
|
@@ -4768,7 +3721,7 @@ function buildResolverOperationHookExpr(env) {
|
|
|
4768
3721
|
* Bundle executors from the specified configuration
|
|
4769
3722
|
*
|
|
4770
3723
|
* This function:
|
|
4771
|
-
* 1. Creates entry
|
|
3724
|
+
* 1. Creates an in-memory entry module that extracts operation.body
|
|
4772
3725
|
* 2. Bundles in a single step with tree-shaking
|
|
4773
3726
|
* @param options - Bundle executor options
|
|
4774
3727
|
* @returns Map of executor name to bundled code
|
|
@@ -4803,21 +3756,18 @@ async function bundleExecutors(options) {
|
|
|
4803
3756
|
logger.debug(" No function executors to bundle");
|
|
4804
3757
|
return bundledCode;
|
|
4805
3758
|
}
|
|
4806
|
-
const outputDir = path.resolve(getDistDir(), "executors");
|
|
4807
|
-
fs$1.mkdirSync(outputDir, { recursive: true });
|
|
4808
|
-
await removeStaleEntryFiles(outputDir);
|
|
4809
3759
|
let tsconfig;
|
|
4810
3760
|
try {
|
|
4811
3761
|
tsconfig = await resolveTSConfig();
|
|
4812
3762
|
} catch {
|
|
4813
3763
|
tsconfig = void 0;
|
|
4814
3764
|
}
|
|
4815
|
-
const results = await withBundleConcurrency(executors, (executor) => bundleSingleExecutor(executor,
|
|
3765
|
+
const results = await withBundleConcurrency(executors, (executor) => bundleSingleExecutor(executor, tsconfig, triggerContext, cache, inlineSourcemap, bundleLogLevel));
|
|
4816
3766
|
for (const [name, code] of results) bundledCode.set(name, code);
|
|
4817
3767
|
logger.log(`${styles.success("Bundled")} ${styles.info("\"executor\"")}`);
|
|
4818
3768
|
return bundledCode;
|
|
4819
3769
|
}
|
|
4820
|
-
async function bundleSingleExecutor(executor,
|
|
3770
|
+
async function bundleSingleExecutor(executor, tsconfig, triggerContext, cache, inlineSourcemap, bundleLogLevel = "DEBUG") {
|
|
4821
3771
|
const serializedTriggerContext = serializeTriggerContext(triggerContext);
|
|
4822
3772
|
const contextHash = computeBundlerContextHash({
|
|
4823
3773
|
sourceFile: executor.sourceFile,
|
|
@@ -4833,7 +3783,6 @@ async function bundleSingleExecutor(executor, outputDir, tsconfig, triggerContex
|
|
|
4833
3783
|
sourceFile: executor.sourceFile,
|
|
4834
3784
|
contextHash,
|
|
4835
3785
|
async build(cachePlugins) {
|
|
4836
|
-
const entryPath = path.join(outputDir, `${executor.name}.entry.js`);
|
|
4837
3786
|
const entryContent = multiline`
|
|
4838
3787
|
import _internalExecutor from "${path.resolve(executor.sourceFile)}";
|
|
4839
3788
|
|
|
@@ -4844,12 +3793,13 @@ async function bundleSingleExecutor(executor, outputDir, tsconfig, triggerContex
|
|
|
4844
3793
|
|
|
4845
3794
|
export { __executor_function as main };
|
|
4846
3795
|
`;
|
|
4847
|
-
|
|
3796
|
+
const entry = createVirtualEntry(`executor:${executor.name}`, entryContent);
|
|
4848
3797
|
const triggerPlugin = createTriggerTransformPlugin(triggerContext);
|
|
4849
|
-
const plugins =
|
|
3798
|
+
const plugins = [entry.plugin];
|
|
3799
|
+
if (triggerPlugin) plugins.push(triggerPlugin);
|
|
4850
3800
|
plugins.push(platformBundleDefinePlugin, ...cachePlugins);
|
|
4851
3801
|
return (await rolldown.build({
|
|
4852
|
-
input:
|
|
3802
|
+
input: entry.input,
|
|
4853
3803
|
write: false,
|
|
4854
3804
|
output: {
|
|
4855
3805
|
format: "esm",
|
|
@@ -4932,8 +3882,6 @@ async function bundleHttpAdapters(adapters, cache, bundleLogLevel = "DEBUG") {
|
|
|
4932
3882
|
};
|
|
4933
3883
|
logger.newline();
|
|
4934
3884
|
logger.log(`Bundling ${styles.highlight(adapters.length.toString())} files for ${styles.info("\"http-adapter\"")}`);
|
|
4935
|
-
const outputDir = path.resolve(getDistDir(), "http-adapters");
|
|
4936
|
-
fs$1.mkdirSync(outputDir, { recursive: true });
|
|
4937
3885
|
let tsconfig;
|
|
4938
3886
|
try {
|
|
4939
3887
|
tsconfig = await resolveTSConfig();
|
|
@@ -4945,7 +3893,7 @@ async function bundleHttpAdapters(adapters, cache, bundleLogLevel = "DEBUG") {
|
|
|
4945
3893
|
adapter,
|
|
4946
3894
|
kind
|
|
4947
3895
|
}));
|
|
4948
|
-
}), ({ adapter, kind }) => bundleAdapterScript(adapter, kind,
|
|
3896
|
+
}), ({ adapter, kind }) => bundleAdapterScript(adapter, kind, tsconfig, cache, bundleLogLevel));
|
|
4949
3897
|
const bundledInputs = /* @__PURE__ */ new Map();
|
|
4950
3898
|
const bundledOutputs = /* @__PURE__ */ new Map();
|
|
4951
3899
|
for (const [name, kind, code] of results) if (kind === "input") bundledInputs.set(name, code);
|
|
@@ -4956,7 +3904,7 @@ async function bundleHttpAdapters(adapters, cache, bundleLogLevel = "DEBUG") {
|
|
|
4956
3904
|
bundledOutputs
|
|
4957
3905
|
};
|
|
4958
3906
|
}
|
|
4959
|
-
async function bundleAdapterScript(adapter, kind,
|
|
3907
|
+
async function bundleAdapterScript(adapter, kind, tsconfig, cache, bundleLogLevel = "DEBUG") {
|
|
4960
3908
|
const contextHash = computeBundlerContextHash({
|
|
4961
3909
|
sourceFile: adapter.sourceFile,
|
|
4962
3910
|
serializedTriggerContext: kind === "input" ? adapter.methods.join(",") : "",
|
|
@@ -4972,9 +3920,11 @@ async function bundleAdapterScript(adapter, kind, outputDir, tsconfig, cache, bu
|
|
|
4972
3920
|
sourceFile: adapter.sourceFile,
|
|
4973
3921
|
contextHash,
|
|
4974
3922
|
async build(cachePlugins) {
|
|
4975
|
-
const entryPath = path.join(outputDir, `${adapter.name}.${kind}.entry.js`);
|
|
4976
3923
|
const absoluteSourcePath = path.resolve(adapter.sourceFile);
|
|
3924
|
+
const entryContent = kind === "input" ? buildInputEntry(absoluteSourcePath, adapter.methods, GRAPHQL_WEB_MODULE) : buildOutputEntry(absoluteSourcePath);
|
|
3925
|
+
const entry = createVirtualEntry(`http-adapter:${adapter.name}:${kind}`, entryContent);
|
|
4977
3926
|
const plugins = [
|
|
3927
|
+
entry.plugin,
|
|
4978
3928
|
{
|
|
4979
3929
|
name: "http-adapter-reject-node-imports",
|
|
4980
3930
|
resolveId(source) {
|
|
@@ -4998,30 +3948,21 @@ async function bundleAdapterScript(adapter, kind, outputDir, tsconfig, cache, bu
|
|
|
4998
3948
|
},
|
|
4999
3949
|
...cachePlugins
|
|
5000
3950
|
];
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
transform: { target: "es2017" },
|
|
5017
|
-
treeshake: composeFunctionTreeshakeOptions([createLogLevelTreeshakeOptions(bundleLogLevel)]),
|
|
5018
|
-
logLevel: "silent"
|
|
5019
|
-
})).output[0].code;
|
|
5020
|
-
} finally {
|
|
5021
|
-
try {
|
|
5022
|
-
fs$1.rmSync(entryPath, { force: true });
|
|
5023
|
-
} catch {}
|
|
5024
|
-
}
|
|
3951
|
+
const bundled = (await rolldown.build({
|
|
3952
|
+
input: entry.input,
|
|
3953
|
+
write: false,
|
|
3954
|
+
output: {
|
|
3955
|
+
format: "iife",
|
|
3956
|
+
sourcemap: false,
|
|
3957
|
+
minify: true,
|
|
3958
|
+
codeSplitting: false
|
|
3959
|
+
},
|
|
3960
|
+
tsconfig,
|
|
3961
|
+
plugins,
|
|
3962
|
+
transform: { target: "es2017" },
|
|
3963
|
+
treeshake: composeFunctionTreeshakeOptions([createLogLevelTreeshakeOptions(bundleLogLevel)]),
|
|
3964
|
+
logLevel: "silent"
|
|
3965
|
+
})).output[0].code;
|
|
5025
3966
|
const byteLength = Buffer.byteLength(bundled, "utf8");
|
|
5026
3967
|
if (byteLength > ADAPTER_BUNDLE_ERROR_BYTES) throw new Error(`HTTP adapter "${adapter.name}" ${kind} script is ${byteLength} bytes, exceeding the ${ADAPTER_BUNDLE_ERROR_BYTES} byte limit`);
|
|
5027
3968
|
if (byteLength > ADAPTER_BUNDLE_WARN_BYTES) logger.warn(`HTTP adapter "${adapter.name}" ${kind} script is ${byteLength} bytes, larger than the recommended ${ADAPTER_BUNDLE_WARN_BYTES} byte limit`);
|
|
@@ -5220,7 +4161,7 @@ async function loadResolver(resolverFilePath) {
|
|
|
5220
4161
|
*
|
|
5221
4162
|
* This function:
|
|
5222
4163
|
* 1. Uses a transform plugin to add validation wrapper during bundling
|
|
5223
|
-
* 2. Creates entry
|
|
4164
|
+
* 2. Creates an in-memory entry module
|
|
5224
4165
|
* 3. Bundles in a single step with tree-shaking
|
|
5225
4166
|
* @param namespace - Resolver namespace name
|
|
5226
4167
|
* @param config - Resolver file loading configuration
|
|
@@ -5251,21 +4192,18 @@ async function bundleResolvers(namespace, config, triggerContext, cache, inlineS
|
|
|
5251
4192
|
sourceFile: file
|
|
5252
4193
|
});
|
|
5253
4194
|
}
|
|
5254
|
-
const outputDir = path.resolve(getDistDir(), "resolvers");
|
|
5255
|
-
fs$1.mkdirSync(outputDir, { recursive: true });
|
|
5256
|
-
await removeStaleEntryFiles(outputDir);
|
|
5257
4195
|
let tsconfig;
|
|
5258
4196
|
try {
|
|
5259
4197
|
tsconfig = await resolveTSConfig();
|
|
5260
4198
|
} catch {
|
|
5261
4199
|
tsconfig = void 0;
|
|
5262
4200
|
}
|
|
5263
|
-
const results = await withBundleConcurrency(resolvers, (resolver) => bundleSingleResolver(namespace, resolver,
|
|
4201
|
+
const results = await withBundleConcurrency(resolvers, (resolver) => bundleSingleResolver(namespace, resolver, tsconfig, triggerContext, cache, inlineSourcemap, bundleLogLevel));
|
|
5264
4202
|
for (const [name, code] of results) bundledCode.set(name, code);
|
|
5265
4203
|
logger.log(`${styles.success("Bundled")} ${styles.info(`"${namespace}"`)}`);
|
|
5266
4204
|
return bundledCode;
|
|
5267
4205
|
}
|
|
5268
|
-
async function bundleSingleResolver(namespace, resolver,
|
|
4206
|
+
async function bundleSingleResolver(namespace, resolver, tsconfig, triggerContext, cache, inlineSourcemap, bundleLogLevel = "DEBUG") {
|
|
5269
4207
|
const serializedTriggerContext = serializeTriggerContext(triggerContext);
|
|
5270
4208
|
const contextHash = computeBundlerContextHash({
|
|
5271
4209
|
sourceFile: resolver.sourceFile,
|
|
@@ -5282,7 +4220,6 @@ async function bundleSingleResolver(namespace, resolver, outputDir, tsconfig, tr
|
|
|
5282
4220
|
sourceFile: resolver.sourceFile,
|
|
5283
4221
|
contextHash,
|
|
5284
4222
|
async build(cachePlugins) {
|
|
5285
|
-
const entryPath = path.join(outputDir, `${resolver.name}.entry.js`);
|
|
5286
4223
|
const entryContent = multiline`
|
|
5287
4224
|
import _internalResolver from "${path.resolve(resolver.sourceFile)}";
|
|
5288
4225
|
import { t } from "@tailor-platform/sdk";
|
|
@@ -5309,12 +4246,13 @@ async function bundleSingleResolver(namespace, resolver, outputDir, tsconfig, tr
|
|
|
5309
4246
|
|
|
5310
4247
|
export { $tailor_resolver_body as main };
|
|
5311
4248
|
`;
|
|
5312
|
-
|
|
4249
|
+
const entry = createVirtualEntry(`resolver:${resolver.name}`, entryContent);
|
|
5313
4250
|
const triggerPlugin = createTriggerTransformPlugin(triggerContext);
|
|
5314
|
-
const plugins =
|
|
4251
|
+
const plugins = [entry.plugin];
|
|
4252
|
+
if (triggerPlugin) plugins.push(triggerPlugin);
|
|
5315
4253
|
plugins.push(platformBundleDefinePlugin, ...cachePlugins);
|
|
5316
4254
|
return (await rolldown.build({
|
|
5317
|
-
input:
|
|
4255
|
+
input: entry.input,
|
|
5318
4256
|
write: false,
|
|
5319
4257
|
output: {
|
|
5320
4258
|
format: "esm",
|
|
@@ -5502,7 +4440,7 @@ function transformWorkflowSource(source, targetJobName, targetJobExportName, oth
|
|
|
5502
4440
|
function safeRealpath(p) {
|
|
5503
4441
|
const resolved = path.resolve(p);
|
|
5504
4442
|
try {
|
|
5505
|
-
return fs
|
|
4443
|
+
return fs.realpathSync(resolved);
|
|
5506
4444
|
} catch (e) {
|
|
5507
4445
|
logger.debug(`realpathSync failed for ${resolved}: ${e instanceof Error ? e.message : e}`);
|
|
5508
4446
|
return resolved;
|
|
@@ -5514,7 +4452,7 @@ function safeRealpath(p) {
|
|
|
5514
4452
|
* This function:
|
|
5515
4453
|
* 1. Detects which jobs are actually used (mainJobs + their dependencies)
|
|
5516
4454
|
* 2. Uses a transform plugin to transform trigger calls during bundling
|
|
5517
|
-
* 3. Creates entry
|
|
4455
|
+
* 3. Creates an in-memory entry module and bundles with tree-shaking
|
|
5518
4456
|
*
|
|
5519
4457
|
* Returns metadata about which jobs each workflow uses.
|
|
5520
4458
|
* @param allJobs - All available job infos
|
|
@@ -5538,19 +4476,13 @@ async function bundleWorkflowJobs(allJobs, mainJobNames, env = {}, triggerContex
|
|
|
5538
4476
|
const { usedJobs, mainJobDeps } = await filterUsedJobs(allJobs, mainJobNames, triggerContext);
|
|
5539
4477
|
logger.newline();
|
|
5540
4478
|
logger.log(`Bundling ${styles.highlight(usedJobs.length.toString())} files for ${styles.info("\"workflow-job\"")}`);
|
|
5541
|
-
const outputDir = path.resolve(getDistDir(), "workflow-jobs");
|
|
5542
|
-
fs$1.mkdirSync(outputDir, { recursive: true });
|
|
5543
|
-
const currentJobNames = new Set(usedJobs.map((j) => j.name));
|
|
5544
|
-
const existingFiles = fs$1.readdirSync(outputDir);
|
|
5545
|
-
for (const file of existingFiles) if (file.endsWith(".js") && !currentJobNames.has(path.basename(file, ".js"))) fs$1.rmSync(path.join(outputDir, file), { force: true });
|
|
5546
|
-
else if (file.endsWith(".js.map") && !currentJobNames.has(path.basename(file, ".js.map"))) fs$1.rmSync(path.join(outputDir, file), { force: true });
|
|
5547
4479
|
let tsconfig;
|
|
5548
4480
|
try {
|
|
5549
4481
|
tsconfig = await resolveTSConfig();
|
|
5550
4482
|
} catch {
|
|
5551
4483
|
tsconfig = void 0;
|
|
5552
4484
|
}
|
|
5553
|
-
const results = await withBundleConcurrency(usedJobs, (job) => bundleSingleJob(job, usedJobs,
|
|
4485
|
+
const results = await withBundleConcurrency(usedJobs, (job) => bundleSingleJob(job, usedJobs, tsconfig, env, triggerContext, cache, inlineSourcemap, bundleLogLevel));
|
|
5554
4486
|
const bundledCode = /* @__PURE__ */ new Map();
|
|
5555
4487
|
for (const [name, code] of results) bundledCode.set(name, code);
|
|
5556
4488
|
logger.log(`${styles.success("Bundled")} ${styles.info("\"workflow-job\"")}`);
|
|
@@ -5586,7 +4518,7 @@ async function filterUsedJobs(allJobs, mainJobNames, triggerContext) {
|
|
|
5586
4518
|
const dependencies = /* @__PURE__ */ new Map();
|
|
5587
4519
|
const fileResults = await Promise.all(Array.from(jobsBySourceFile.entries()).map(async ([sourceFile, jobs]) => {
|
|
5588
4520
|
try {
|
|
5589
|
-
const source = await fs
|
|
4521
|
+
const source = await fs.promises.readFile(sourceFile, "utf-8");
|
|
5590
4522
|
const { program } = parseSync("input.ts", source);
|
|
5591
4523
|
const detectedJobs = findAllJobs(program, source);
|
|
5592
4524
|
const triggerCalls = detectResolvedTriggerCalls(program, source, triggerContext, sourceFile);
|
|
@@ -5626,7 +4558,7 @@ async function filterUsedJobs(allJobs, mainJobNames, triggerContext) {
|
|
|
5626
4558
|
mainJobDeps
|
|
5627
4559
|
};
|
|
5628
4560
|
}
|
|
5629
|
-
async function bundleSingleJob(job, allJobs,
|
|
4561
|
+
async function bundleSingleJob(job, allJobs, tsconfig, env, triggerContext, cache, inlineSourcemap, bundleLogLevel = "DEBUG") {
|
|
5630
4562
|
const serializedTriggerContext = serializeTriggerContext(triggerContext);
|
|
5631
4563
|
const sortedEnvPrefix = JSON.stringify(Object.fromEntries(Object.entries(env).toSorted(([a], [b]) => a.localeCompare(b))));
|
|
5632
4564
|
const contextHash = computeBundlerContextHash({
|
|
@@ -5644,7 +4576,6 @@ async function bundleSingleJob(job, allJobs, outputDir, tsconfig, env, triggerCo
|
|
|
5644
4576
|
sourceFile: job.sourceFile,
|
|
5645
4577
|
contextHash,
|
|
5646
4578
|
async build(cachePlugins) {
|
|
5647
|
-
const entryPath = path.join(outputDir, `${job.name}.entry.js`);
|
|
5648
4579
|
const absoluteSourcePath = path.resolve(job.sourceFile);
|
|
5649
4580
|
const entryContent = multiline`
|
|
5650
4581
|
import { ${job.exportName} } from "${absoluteSourcePath}";
|
|
@@ -5655,10 +4586,11 @@ async function bundleSingleJob(job, allJobs, outputDir, tsconfig, env, triggerCo
|
|
|
5655
4586
|
return await ${job.exportName}.body(input, { env, invoker });
|
|
5656
4587
|
}
|
|
5657
4588
|
`;
|
|
5658
|
-
|
|
4589
|
+
const entry = createVirtualEntry(`workflow-job:${job.name}`, entryContent);
|
|
5659
4590
|
const resolvedSourceFile = safeRealpath(job.sourceFile);
|
|
5660
4591
|
const otherJobExportNames = allJobs.filter((candidate) => candidate.name !== job.name && safeRealpath(candidate.sourceFile) === resolvedSourceFile).map((j) => j.exportName);
|
|
5661
4592
|
const plugins = [
|
|
4593
|
+
entry.plugin,
|
|
5662
4594
|
{
|
|
5663
4595
|
name: "workflow-transform",
|
|
5664
4596
|
transform: {
|
|
@@ -5677,7 +4609,7 @@ async function bundleSingleJob(job, allJobs, outputDir, tsconfig, env, triggerCo
|
|
|
5677
4609
|
...cachePlugins
|
|
5678
4610
|
];
|
|
5679
4611
|
return (await rolldown.build({
|
|
5680
|
-
input:
|
|
4612
|
+
input: entry.input,
|
|
5681
4613
|
write: false,
|
|
5682
4614
|
output: {
|
|
5683
4615
|
format: "esm",
|
|
@@ -5736,7 +4668,7 @@ const RetryPolicySchema = z.strictObject({
|
|
|
5736
4668
|
});
|
|
5737
4669
|
const ConcurrencyPolicySchema = z.strictObject({ maxConcurrentExecutions: z.number().int().min(1).max(1e3).describe("Maximum number of concurrent executions (1-1000)") });
|
|
5738
4670
|
const ExecutionPolicyNameSchema = z.string().regex(/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/, "Invalid execution policy name: must match [a-z0-9-] (3-63 chars; must start and end with [a-z0-9])").describe("Workspace-unique execution policy name embedded in the resource TRN");
|
|
5739
|
-
const ExecutionPolicyKeySchema = z.string().regex(/^[a-z0-9][a-z0-9_:.-]{0,62}[a-z0-9*]$/, "Invalid execution policy key: must match [a-z0-9_:.-] (2-64 chars; must start with [a-z0-9] and end with [a-z0-9] or a trailing '*')").describe("Execution policy key passed to triggerJobFunction's executionPolicyKey option");
|
|
4671
|
+
const ExecutionPolicyKeySchema = z.string().regex(/^[a-z0-9][a-z0-9_:.-]{0,62}[a-z0-9*]$/, "Invalid execution policy key: must match [a-z0-9_:.-] (2-64 chars; must start with [a-z0-9] and end with [a-z0-9] or a trailing '*')").describe("Execution policy key passed to startJobFunction's (or its frozen alias triggerJobFunction's) executionPolicyKey option");
|
|
5740
4672
|
const WorkflowJobFunctionExecutionPolicySchema = z.strictObject({
|
|
5741
4673
|
name: ExecutionPolicyNameSchema,
|
|
5742
4674
|
key: ExecutionPolicyKeySchema,
|
|
@@ -6493,5 +5425,5 @@ async function loadApplication(params) {
|
|
|
6493
5425
|
}
|
|
6494
5426
|
|
|
6495
5427
|
//#endregion
|
|
6496
|
-
export {
|
|
6497
|
-
//# sourceMappingURL=application-
|
|
5428
|
+
export { fetchAllTolerant as A, initOperatorClient as B, createBundleCache as C, closeConnectionPool as D, hashFile as E, getConsoleBaseUrl as F, byName as G, normalizeBaseUrl as H, getOAuth2ClientId as I, LOG_LEVELS as K, getOrNull as L, fetchPaged as M, fetchPlatformMachineUserToken as N, defaultPlatformBaseUrl as O, fetchUserInfo as P, getPlatformBaseUrl as R, hasGenerationHooks as S, hashContent as T, rememberPlatformConfigForToken as U, isDefaultPlatform as V, resolveStaticWebsiteUrls as W, platformBundleDefinePlugin as _, resolveInlineSourcemap as a, resolveBundleLogLevel as b, WorkflowJobSchema as c, INVOKER_EXPR as d, buildExecutorArgsExpr as f, stringifyFunction as g, assertUniqueTailorDBTypeNamesWithExternal as h, resolverBundleKey as i, fetchMachineUserToken as j, fetchAll as k, ResolverSchema as l, assertUniqueLocalTailorDBTypeNames as m, generatePluginFilesIfNeeded as n, getApplicationAuthNamespace as o, buildResolverOperationHookExpr as p, loadApplication as r, WorkflowJobFunctionExecutionPolicySchema as s, defineApplication as t, HTTP_METHODS as u, composeFunctionTreeshakeOptions as v, getDistDir as w, getPluginGenerationDependencies as x, createLogLevelTreeshakeOptions as y, initOAuth2Client as z };
|
|
5429
|
+
//# sourceMappingURL=application-BJXRpQj5.mjs.map
|