@tailor-platform/sdk 2.0.0-next.5 → 2.0.0-next.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -0
- package/dist/{application-Bf8wxOi6.mjs → application-GzW98_Xr.mjs} +377 -1418
- package/dist/application-GzW98_Xr.mjs.map +1 -0
- package/dist/application-OM0taSPn.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 +781 -1553
- 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/config/types.d.mts +5 -1
- package/dist/configure/index.mjs +20 -19
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/auth/types.d.mts +18 -23
- package/dist/configure/services/executor/executor.d.mts +2 -13
- package/dist/configure/services/executor/operation.d.mts +8 -3
- 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/services/workflow/job.d.mts +10 -10
- package/dist/configure/services/workflow/workflow.d.mts +6 -4
- 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-CBZ0egXT.mjs} +13 -10
- package/dist/globals-CBZ0egXT.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-DvEs6YsL.mjs} +2571 -1636
- package/dist/register-ts-hook-DvEs6YsL.mjs.map +1 -0
- package/dist/{registry-BSbq6SPo.mjs → registry-i7EdJ-D5.mjs} +10 -10
- package/dist/registry-i7EdJ-D5.mjs.map +1 -0
- package/dist/runtime/field-parse.d.mts +10 -0
- package/dist/runtime/globals.d.mts +3 -3
- package/dist/runtime/index.mjs +1 -1
- package/dist/runtime/workflow.d.mts +18 -33
- 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-BJAQ70e5.mjs +3 -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-CzZEVrp9.mjs → service-Dmxa2I4i.mjs} +64 -28
- package/dist/service-Dmxa2I4i.mjs.map +1 -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/utils/test/index.mjs +11 -11
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/utils/test/mock.d.mts +6 -6
- package/dist/vitest/environment.mjs +1 -1
- package/dist/vitest/index.d.mts +8 -8
- package/dist/vitest/index.mjs +613 -227
- 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 +59 -33
- package/dist/vitest/setup.mjs +2 -2
- package/dist/vitest/workflow-local.d.mts +2 -2
- package/dist/workflow-CsBvRE3g.mjs +34 -0
- package/dist/workflow-CsBvRE3g.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 +63 -15
- package/docs/cli/function.md +2 -2
- package/docs/cli/tailordb.md +8 -140
- package/docs/cli/workspace.md +6 -4
- package/docs/cli-reference.md +11 -8
- package/docs/configuration.md +2 -0
- package/docs/github-actions.md +8 -0
- package/docs/migration/v2.md +169 -11
- package/docs/runtime.md +5 -6
- package/docs/services/auth.md +7 -7
- package/docs/services/executor.md +5 -0
- package/docs/services/tailordb.md +44 -0
- package/docs/services/workflow.md +43 -43
- package/docs/testing.md +112 -96
- 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-Dmxa2I4i.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
|
/**
|
|
@@ -1871,15 +753,16 @@ function combineHash(fileHash, contextHash) {
|
|
|
1871
753
|
/**
|
|
1872
754
|
* Compute a context hash for cache invalidation across bundlers.
|
|
1873
755
|
*
|
|
1874
|
-
* Combines the source file path,
|
|
1875
|
-
*
|
|
1876
|
-
*
|
|
756
|
+
* Combines the source file path, a caller-supplied extra context string
|
|
757
|
+
* (e.g. serialized workflow start-call bindings, or an HTTP adapter's method
|
|
758
|
+
* list), tsconfig hash, sourcemap mode, bundle log level, and an optional
|
|
759
|
+
* prefix (e.g., serialized env variables) into a single SHA-256 hash.
|
|
1877
760
|
* @param params - Context hash computation parameters
|
|
1878
761
|
* @returns SHA-256 hex digest of the combined context
|
|
1879
762
|
*/
|
|
1880
763
|
function computeBundlerContextHash(params) {
|
|
1881
|
-
const { sourceFile,
|
|
1882
|
-
return hashContent((prefix ?? "") + path.resolve(sourceFile) +
|
|
764
|
+
const { sourceFile, extraContext, tsconfig, inlineSourcemap, bundleLogLevel, prefix } = params;
|
|
765
|
+
return hashContent((prefix ?? "") + path.resolve(sourceFile) + extraContext + (tsconfig ? hashFile(tsconfig) : "") + String(inlineSourcemap ?? false) + (bundleLogLevel ?? ""));
|
|
1883
766
|
}
|
|
1884
767
|
/**
|
|
1885
768
|
* Run a build with optional cache restore/save around it.
|
|
@@ -1931,7 +814,10 @@ function createBundleCache(store) {
|
|
|
1931
814
|
return;
|
|
1932
815
|
}
|
|
1933
816
|
if (currentHash !== entry.inputHash) return;
|
|
1934
|
-
|
|
817
|
+
const content = store.restoreBundleContent(cacheKey);
|
|
818
|
+
const output = entry.outputFiles.find((file) => file.outputPath === cacheKey);
|
|
819
|
+
if (content === void 0 || !output || hashContent(content) !== output.contentHash) return;
|
|
820
|
+
return content;
|
|
1935
821
|
}
|
|
1936
822
|
function save(params) {
|
|
1937
823
|
const { kind, namespace, name, sourceFile, content, dependencyPaths, contextHash } = params;
|
|
@@ -1978,7 +864,7 @@ function resolveRelativePluginImportPath(pluginImportPath, baseDirs) {
|
|
|
1978
864
|
if (!pluginImportPath.startsWith(".")) return null;
|
|
1979
865
|
for (const baseDir of baseDirs) {
|
|
1980
866
|
const absolutePath = path.resolve(baseDir, pluginImportPath);
|
|
1981
|
-
if (fs
|
|
867
|
+
if (fs.existsSync(absolutePath)) return absolutePath;
|
|
1982
868
|
}
|
|
1983
869
|
return null;
|
|
1984
870
|
}
|
|
@@ -2062,13 +948,13 @@ function generatePluginExecutorFiles(executors, outputDir, typeGenerationResult,
|
|
|
2062
948
|
function generateSingleExecutorFile(info, outputDir, typeGenerationResult, sourceTypeInfoMap, baseDirs = []) {
|
|
2063
949
|
const pluginDir = sanitizePluginId$1(info.pluginId);
|
|
2064
950
|
const executorOutputDir = path.join(outputDir, pluginDir, "executors");
|
|
2065
|
-
fs
|
|
951
|
+
fs.mkdirSync(executorOutputDir, { recursive: true });
|
|
2066
952
|
const fileName = sanitizeExecutorFileName(info.executor.name);
|
|
2067
953
|
const filePath = path.join(executorOutputDir, `${fileName}.ts`);
|
|
2068
954
|
let content;
|
|
2069
955
|
if (isPluginExecutorWithFile(info.executor)) content = generateExecutorFileContentNew(info, info.executor, outputDir, typeGenerationResult, sourceTypeInfoMap, baseDirs);
|
|
2070
956
|
else content = generateExecutorFileContentLegacy(info.executor);
|
|
2071
|
-
fs
|
|
957
|
+
fs.writeFileSync(filePath, content);
|
|
2072
958
|
return filePath;
|
|
2073
959
|
}
|
|
2074
960
|
/**
|
|
@@ -2300,7 +1186,7 @@ function extractDynamicImportSpecifier(resolve) {
|
|
|
2300
1186
|
function resolvePluginBaseDir(pluginImportPath, baseDirs) {
|
|
2301
1187
|
if (pluginImportPath.startsWith(".")) {
|
|
2302
1188
|
const resolvedPath = resolveRelativePluginImportPath(pluginImportPath, baseDirs) ?? path.resolve(baseDirs[0] ?? process.cwd(), pluginImportPath);
|
|
2303
|
-
if (fs
|
|
1189
|
+
if (fs.existsSync(resolvedPath)) return fs.statSync(resolvedPath).isDirectory() ? resolvedPath : path.dirname(resolvedPath);
|
|
2304
1190
|
return path.extname(resolvedPath) ? path.dirname(resolvedPath) : resolvedPath;
|
|
2305
1191
|
}
|
|
2306
1192
|
for (const baseDir of baseDirs) try {
|
|
@@ -2387,11 +1273,11 @@ function generatePluginTypeFiles(types, outputDir) {
|
|
|
2387
1273
|
seenTypeNames.set(info.type.name, info);
|
|
2388
1274
|
const pluginDir = sanitizePluginId(info.pluginId);
|
|
2389
1275
|
const typeOutputDir = path.join(outputDir, pluginDir, "types");
|
|
2390
|
-
fs
|
|
1276
|
+
fs.mkdirSync(typeOutputDir, { recursive: true });
|
|
2391
1277
|
const fileName = `${toKebabCase(info.type.name)}.ts`;
|
|
2392
1278
|
const filePath = path.join(typeOutputDir, fileName);
|
|
2393
1279
|
const content = generateTypeFileContent(info);
|
|
2394
|
-
fs
|
|
1280
|
+
fs.writeFileSync(filePath, content);
|
|
2395
1281
|
generatedFiles.push(filePath);
|
|
2396
1282
|
const relativePath = path.relative(outputDir, filePath);
|
|
2397
1283
|
typeFilePaths.set(info.type.name, relativePath);
|
|
@@ -2512,20 +1398,6 @@ function toCamelCase(str) {
|
|
|
2512
1398
|
return result.charAt(0).toLowerCase() + result.slice(1);
|
|
2513
1399
|
}
|
|
2514
1400
|
|
|
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
1401
|
//#endregion
|
|
2530
1402
|
//#region src/cli/services/workflow/ast-utils.ts
|
|
2531
1403
|
/**
|
|
@@ -2571,18 +1443,18 @@ function argumentSourceText(arg, sourceText) {
|
|
|
2571
1443
|
if (arg && typeof arg === "object" && "start" in arg && "end" in arg) return sourceText.slice(arg.start, arg.end);
|
|
2572
1444
|
}
|
|
2573
1445
|
/**
|
|
2574
|
-
* Get metadata for a static `identifier.
|
|
1446
|
+
* Get metadata for a static `identifier.start(...)` call.
|
|
2575
1447
|
* @param node - AST node to inspect
|
|
2576
1448
|
* @param sourceText - Source code text
|
|
2577
|
-
* @returns
|
|
1449
|
+
* @returns Start call metadata, or null when the node is not a start call
|
|
2578
1450
|
*/
|
|
2579
|
-
function
|
|
1451
|
+
function getStartCallInfo(node, sourceText) {
|
|
2580
1452
|
if (!node || typeof node !== "object" || node.type !== "CallExpression") return null;
|
|
2581
1453
|
const callExpr = node;
|
|
2582
1454
|
const callee = callExpr.callee;
|
|
2583
1455
|
if (callee.type !== "MemberExpression") return null;
|
|
2584
1456
|
const memberExpr = callee;
|
|
2585
|
-
if (memberExpr.computed || memberExpr.property.name !== "
|
|
1457
|
+
if (memberExpr.computed || memberExpr.property.name !== "start" || memberExpr.object.type !== "Identifier") return null;
|
|
2586
1458
|
return {
|
|
2587
1459
|
identifierName: memberExpr.object.name,
|
|
2588
1460
|
callRange: {
|
|
@@ -2865,9 +1737,9 @@ function findAllWorkflows(program, _sourceText) {
|
|
|
2865
1737
|
}
|
|
2866
1738
|
|
|
2867
1739
|
//#endregion
|
|
2868
|
-
//#region src/cli/shared/
|
|
1740
|
+
//#region src/cli/shared/start-context.ts
|
|
2869
1741
|
/**
|
|
2870
|
-
* Normalize a source module path for
|
|
1742
|
+
* Normalize a source module path for start-call binding lookup.
|
|
2871
1743
|
* @param filePath - Source file path or extensionless relative import path
|
|
2872
1744
|
* @returns Absolute path without a JavaScript or TypeScript extension
|
|
2873
1745
|
*/
|
|
@@ -2922,19 +1794,20 @@ function createModuleBindings(program, source) {
|
|
|
2922
1794
|
};
|
|
2923
1795
|
}
|
|
2924
1796
|
/**
|
|
2925
|
-
* Build
|
|
1797
|
+
* Build start-call context from configured workflow source files.
|
|
2926
1798
|
* @param workflowConfig - Workflow file loading configuration
|
|
2927
1799
|
* @param authNamespace - Auth service namespace (optional, used for string-literal invoker expansion)
|
|
1800
|
+
* @param baseDir - Directory the workflow config's file patterns are resolved against (defaults to process.cwd())
|
|
2928
1801
|
* @returns Module-local workflow and job binding metadata
|
|
2929
1802
|
*/
|
|
2930
|
-
async function
|
|
1803
|
+
async function buildStartContext(workflowConfig, authNamespace, baseDir = process.cwd()) {
|
|
2931
1804
|
const modules = /* @__PURE__ */ new Map();
|
|
2932
1805
|
if (!workflowConfig) return {
|
|
2933
1806
|
modules,
|
|
2934
1807
|
authNamespace
|
|
2935
1808
|
};
|
|
2936
|
-
for (const file of loadFilesWithIgnores(workflowConfig)) try {
|
|
2937
|
-
const source = await fs
|
|
1809
|
+
for (const file of loadFilesWithIgnores(workflowConfig, baseDir)) try {
|
|
1810
|
+
const source = await fs.promises.readFile(file, "utf-8");
|
|
2938
1811
|
const { program } = parseSync("input.ts", source);
|
|
2939
1812
|
modules.set(normalizeFilePath(file), createModuleBindings(program, source));
|
|
2940
1813
|
} catch (error) {
|
|
@@ -2954,11 +1827,11 @@ function sortedTargets(bindings) {
|
|
|
2954
1827
|
]);
|
|
2955
1828
|
}
|
|
2956
1829
|
/**
|
|
2957
|
-
* Serialize
|
|
2958
|
-
* @param context -
|
|
1830
|
+
* Serialize start-call context to a deterministic cache input.
|
|
1831
|
+
* @param context - Start-call context to serialize
|
|
2959
1832
|
* @returns Deterministic string, or an empty string when context is absent
|
|
2960
1833
|
*/
|
|
2961
|
-
function
|
|
1834
|
+
function serializeStartContext(context) {
|
|
2962
1835
|
if (!context) return "";
|
|
2963
1836
|
const modules = [...context.modules].toSorted(([a], [b]) => a.localeCompare(b)).map(([file, bindings]) => [
|
|
2964
1837
|
file,
|
|
@@ -2969,12 +1842,23 @@ function serializeTriggerContext(context) {
|
|
|
2969
1842
|
}
|
|
2970
1843
|
|
|
2971
1844
|
//#endregion
|
|
2972
|
-
//#region src/cli/services/workflow/
|
|
2973
|
-
const
|
|
1845
|
+
//#region src/cli/services/workflow/start-transformer.ts
|
|
1846
|
+
const START_CALL_RE = /\.start(?:\s|\/\*[\s\S]*?\*\/|\/\/[^\n]*\n)*\(/;
|
|
1847
|
+
/**
|
|
1848
|
+
* Fast pre-check for whether `code` contains a `.start(` call, tolerating
|
|
1849
|
+
* whitespace, newlines, and comments between `.start` and `(` that a plain
|
|
1850
|
+
* substring check would miss.
|
|
1851
|
+
* @param code - Source text to scan
|
|
1852
|
+
* @returns Whether `code` contains a `.start(` call
|
|
1853
|
+
*/
|
|
1854
|
+
function hasStartCall(code) {
|
|
1855
|
+
return START_CALL_RE.test(code);
|
|
1856
|
+
}
|
|
1857
|
+
const NORMALIZER_IDENTIFIER = "__tailor_normalizeStartOptions";
|
|
2974
1858
|
/**
|
|
2975
1859
|
* Build the source text of the injected normalizer helper.
|
|
2976
1860
|
*
|
|
2977
|
-
* Renames an `invoker` in the
|
|
1861
|
+
* Renames an `invoker` in the start options to the `authInvoker` form the
|
|
2978
1862
|
* platform RPC expects: a plain string (machine user name) becomes
|
|
2979
1863
|
* `{ namespace, machineUserName }`, while an object form passes through
|
|
2980
1864
|
* as-is. Any other options value is unchanged. The auth namespace is baked
|
|
@@ -3102,42 +1986,42 @@ function collectLocalTargets(program, context, currentFilePath) {
|
|
|
3102
1986
|
}
|
|
3103
1987
|
return targets;
|
|
3104
1988
|
}
|
|
3105
|
-
function
|
|
1989
|
+
function detectStartCallsWithTargets(program, sourceText, targets) {
|
|
3106
1990
|
const calls = [];
|
|
3107
1991
|
walkBindingAware(program, new Set(targets.keys()), (node, shadowedNames) => {
|
|
3108
|
-
const
|
|
3109
|
-
if (!
|
|
3110
|
-
const target = targets.get(
|
|
1992
|
+
const startCall = getStartCallInfo(node, sourceText);
|
|
1993
|
+
if (!startCall || shadowedNames.has(startCall.identifierName)) return;
|
|
1994
|
+
const target = targets.get(startCall.identifierName);
|
|
3111
1995
|
if (target) calls.push({
|
|
3112
|
-
...
|
|
1996
|
+
...startCall,
|
|
3113
1997
|
kind: target.kind,
|
|
3114
1998
|
targetName: target.name
|
|
3115
1999
|
});
|
|
3116
2000
|
});
|
|
3117
2001
|
return calls;
|
|
3118
2002
|
}
|
|
3119
|
-
function
|
|
3120
|
-
return
|
|
2003
|
+
function detectResolvedStartCalls(program, sourceText, context, currentFilePath) {
|
|
2004
|
+
return detectStartCallsWithTargets(program, sourceText, collectLocalTargets(program, context, currentFilePath));
|
|
3121
2005
|
}
|
|
3122
|
-
function
|
|
2006
|
+
function transformStartCalls(source, startContext, currentFilePath) {
|
|
3123
2007
|
const { program } = parseSync("input.ts", source);
|
|
3124
|
-
const localTargets = collectLocalTargets(program,
|
|
3125
|
-
const { authNamespace } =
|
|
3126
|
-
const
|
|
3127
|
-
const
|
|
3128
|
-
const
|
|
3129
|
-
const parent =
|
|
2008
|
+
const localTargets = collectLocalTargets(program, startContext, currentFilePath);
|
|
2009
|
+
const { authNamespace } = startContext;
|
|
2010
|
+
const allStartCalls = detectStartCallsWithTargets(program, source, localTargets);
|
|
2011
|
+
const nestedStartCalls = [];
|
|
2012
|
+
const startCalls = allStartCalls.filter((call) => {
|
|
2013
|
+
const parent = allStartCalls.find((other) => other !== call && other.callRange.start <= call.callRange.start && call.callRange.end <= other.callRange.end);
|
|
3130
2014
|
if (!parent) return true;
|
|
3131
|
-
|
|
2015
|
+
nestedStartCalls.push({
|
|
3132
2016
|
call,
|
|
3133
2017
|
parent
|
|
3134
2018
|
});
|
|
3135
2019
|
return false;
|
|
3136
2020
|
});
|
|
3137
|
-
for (const { call, parent } of
|
|
2021
|
+
for (const { call, parent } of nestedStartCalls) logger.warn(`Nested start call "${call.identifierName}.start(...)" inside "${parent.identifierName}.start(...)" cannot be converted. Move it to a separate statement and pass the result instead.`);
|
|
3138
2022
|
const replacements = [];
|
|
3139
2023
|
let needsNormalizerHelper = false;
|
|
3140
|
-
for (const call of
|
|
2024
|
+
for (const call of startCalls) {
|
|
3141
2025
|
let transformedCall;
|
|
3142
2026
|
if (call.kind === "workflow") {
|
|
3143
2027
|
let optionsPart = "";
|
|
@@ -3145,10 +2029,10 @@ function transformFunctionTriggers(source, triggerContext, currentFilePath) {
|
|
|
3145
2029
|
optionsPart = `, ${NORMALIZER_IDENTIFIER}(${call.optionsText})`;
|
|
3146
2030
|
needsNormalizerHelper = true;
|
|
3147
2031
|
} else optionsPart = `, ${call.optionsText}`;
|
|
3148
|
-
transformedCall = `tailor.workflow.
|
|
2032
|
+
transformedCall = `tailor.workflow.startWorkflow(${JSON.stringify(call.targetName)}, ${call.argsText || "undefined"}${optionsPart})`;
|
|
3149
2033
|
} else {
|
|
3150
2034
|
const optionsPart = call.optionsText !== void 0 ? `, ${call.optionsText}` : "";
|
|
3151
|
-
transformedCall = `tailor.workflow.
|
|
2035
|
+
transformedCall = `tailor.workflow.startJobFunction(${JSON.stringify(call.targetName)}, ${call.argsText || "undefined"}${optionsPart})`;
|
|
3152
2036
|
}
|
|
3153
2037
|
replacements.push({
|
|
3154
2038
|
start: call.callRange.start,
|
|
@@ -3159,15 +2043,15 @@ function transformFunctionTriggers(source, triggerContext, currentFilePath) {
|
|
|
3159
2043
|
const transformed = applyReplacements(source, replacements);
|
|
3160
2044
|
return needsNormalizerHelper && authNamespace ? buildNormalizerHelperSource(authNamespace) + transformed : transformed;
|
|
3161
2045
|
}
|
|
3162
|
-
function
|
|
3163
|
-
if (!
|
|
2046
|
+
function createStartTransformPlugin(startContext) {
|
|
2047
|
+
if (!startContext) return void 0;
|
|
3164
2048
|
return {
|
|
3165
|
-
name: "
|
|
2049
|
+
name: "start-transform",
|
|
3166
2050
|
transform: {
|
|
3167
2051
|
filter: { id: { include: [/\.(ts|mts|cts|js|mjs|cjs)$/] } },
|
|
3168
2052
|
handler(code, id) {
|
|
3169
|
-
if (!code
|
|
3170
|
-
return { code:
|
|
2053
|
+
if (!hasStartCall(code)) return null;
|
|
2054
|
+
return { code: transformStartCalls(code, startContext, id) };
|
|
3171
2055
|
}
|
|
3172
2056
|
}
|
|
3173
2057
|
};
|
|
@@ -3264,34 +2148,77 @@ const platformBundleDefinePlugin = {
|
|
|
3264
2148
|
}
|
|
3265
2149
|
};
|
|
3266
2150
|
|
|
2151
|
+
//#endregion
|
|
2152
|
+
//#region src/cli/shared/resolve-tsconfig.ts
|
|
2153
|
+
const warnedBaseDirs = /* @__PURE__ */ new Set();
|
|
2154
|
+
/**
|
|
2155
|
+
* Resolve the nearest tsconfig.json for baseDir, falling back to the tsconfig
|
|
2156
|
+
* resolved from the invocation cwd when baseDir's own ancestry has none.
|
|
2157
|
+
* @param baseDir - Directory to resolve the tsconfig against
|
|
2158
|
+
* @returns Absolute path to the resolved tsconfig.json, or undefined if none was found
|
|
2159
|
+
*/
|
|
2160
|
+
async function resolveTSConfigWithFallback(baseDir) {
|
|
2161
|
+
const tsconfig = await tryResolve(baseDir);
|
|
2162
|
+
if (tsconfig || baseDir === process.cwd()) return tsconfig;
|
|
2163
|
+
const fallback = await tryResolve(process.cwd());
|
|
2164
|
+
if (fallback && !warnedBaseDirs.has(baseDir)) {
|
|
2165
|
+
warnedBaseDirs.add(baseDir);
|
|
2166
|
+
logger.warn(`No tsconfig found from "${baseDir}"; falling back to the tsconfig resolved from process.cwd(). Move (or extend) a tsconfig into this config's own directory before v2, when this fallback will be removed.`);
|
|
2167
|
+
}
|
|
2168
|
+
return fallback;
|
|
2169
|
+
}
|
|
2170
|
+
async function tryResolve(dir) {
|
|
2171
|
+
try {
|
|
2172
|
+
return await resolveTSConfig(dir);
|
|
2173
|
+
} catch {
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
//#endregion
|
|
2179
|
+
//#region src/cli/shared/virtual-entry.ts
|
|
2180
|
+
/**
|
|
2181
|
+
* Create an in-memory rolldown entry module with a deterministic ID.
|
|
2182
|
+
* @param name - Logical entry name
|
|
2183
|
+
* @param code - Entry module source
|
|
2184
|
+
* @param sourceType - Parser type for the generated module
|
|
2185
|
+
* @returns Rolldown input and plugin for loading the entry
|
|
2186
|
+
*/
|
|
2187
|
+
function createVirtualEntry(name, code, sourceType = "js") {
|
|
2188
|
+
const input = `tailor-sdk-entry:${name}.${sourceType}`;
|
|
2189
|
+
const resolvedId = `\0${input}`;
|
|
2190
|
+
return {
|
|
2191
|
+
input,
|
|
2192
|
+
plugin: {
|
|
2193
|
+
name: "tailor-sdk-virtual-entry",
|
|
2194
|
+
resolveId(source, importer) {
|
|
2195
|
+
return source === input && importer === void 0 ? resolvedId : null;
|
|
2196
|
+
},
|
|
2197
|
+
load(id) {
|
|
2198
|
+
return id === resolvedId ? code : null;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
|
|
3267
2204
|
//#endregion
|
|
3268
2205
|
//#region src/cli/services/auth/bundler.ts
|
|
3269
2206
|
/**
|
|
3270
2207
|
* Bundle a single auth hook handler.
|
|
3271
2208
|
*
|
|
3272
2209
|
* Follows the same pattern as the executor bundler:
|
|
3273
|
-
* 1. Generate an entry
|
|
2210
|
+
* 1. Generate an in-memory entry module that re-exports the handler as `main`
|
|
3274
2211
|
* 2. Bundle with rolldown + tree-shaking
|
|
3275
2212
|
* @param options - Bundle options
|
|
3276
2213
|
* @returns Map of function name to bundled code
|
|
3277
2214
|
*/
|
|
3278
2215
|
async function bundleAuthHooks(options) {
|
|
3279
|
-
const { configPath, authName, handlerAccessPath, env = {},
|
|
2216
|
+
const { configPath, authName, handlerAccessPath, env = {}, startContext, cache, inlineSourcemap, bundleLogLevel = "DEBUG", baseDir } = options;
|
|
3280
2217
|
logger.newline();
|
|
3281
2218
|
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
|
-
let tsconfig;
|
|
3286
|
-
try {
|
|
3287
|
-
tsconfig = await resolveTSConfig();
|
|
3288
|
-
} catch {
|
|
3289
|
-
tsconfig = void 0;
|
|
3290
|
-
}
|
|
3291
|
-
const functionName = `auth-hook--${authName}--before-login`;
|
|
3292
2219
|
const absoluteConfigPath = path.resolve(configPath);
|
|
3293
|
-
const
|
|
3294
|
-
const
|
|
2220
|
+
const tsconfig = await resolveTSConfigWithFallback(baseDir);
|
|
2221
|
+
const functionName = `auth-hook--${authName}--before-login`;
|
|
3295
2222
|
const code = await withCache({
|
|
3296
2223
|
cache,
|
|
3297
2224
|
kind: "auth-hook",
|
|
@@ -3299,14 +2226,13 @@ async function bundleAuthHooks(options) {
|
|
|
3299
2226
|
sourceFile: absoluteConfigPath,
|
|
3300
2227
|
contextHash: computeBundlerContextHash({
|
|
3301
2228
|
sourceFile: absoluteConfigPath,
|
|
3302
|
-
|
|
2229
|
+
extraContext: serializeStartContext(startContext),
|
|
3303
2230
|
tsconfig,
|
|
3304
2231
|
inlineSourcemap,
|
|
3305
2232
|
bundleLogLevel,
|
|
3306
|
-
prefix:
|
|
2233
|
+
prefix: JSON.stringify(Object.fromEntries(Object.entries(env).toSorted(([a], [b]) => a.localeCompare(b))))
|
|
3307
2234
|
}),
|
|
3308
2235
|
async build(cachePlugins) {
|
|
3309
|
-
const entryPath = path.join(outputDir, `${functionName}.entry.js`);
|
|
3310
2236
|
const entryContent = multiline`
|
|
3311
2237
|
import _config from "${absoluteConfigPath}";
|
|
3312
2238
|
const __auth_hook_function = _config.${handlerAccessPath};
|
|
@@ -3315,12 +2241,13 @@ async function bundleAuthHooks(options) {
|
|
|
3315
2241
|
return await __auth_hook_function({ ...args, env });
|
|
3316
2242
|
}
|
|
3317
2243
|
`;
|
|
3318
|
-
|
|
3319
|
-
const
|
|
3320
|
-
const plugins =
|
|
2244
|
+
const entry = createVirtualEntry(`auth-hook:${functionName}`, entryContent);
|
|
2245
|
+
const startPlugin = createStartTransformPlugin(startContext);
|
|
2246
|
+
const plugins = [entry.plugin];
|
|
2247
|
+
if (startPlugin) plugins.push(startPlugin);
|
|
3321
2248
|
plugins.push(platformBundleDefinePlugin, ...cachePlugins);
|
|
3322
2249
|
return (await rolldown.build({
|
|
3323
|
-
input:
|
|
2250
|
+
input: entry.input,
|
|
3324
2251
|
write: false,
|
|
3325
2252
|
output: {
|
|
3326
2253
|
format: "esm",
|
|
@@ -3685,6 +2612,36 @@ function findOmittedPermitRules(rawPermissions) {
|
|
|
3685
2612
|
});
|
|
3686
2613
|
return locations;
|
|
3687
2614
|
}
|
|
2615
|
+
/**
|
|
2616
|
+
* Check whether GraphQL exposure is fully disabled for a type, given its
|
|
2617
|
+
* effective gqlOperations (the type's own setting, falling back to the
|
|
2618
|
+
* TailorDB namespace default). `undefined` means the default of all
|
|
2619
|
+
* operations enabled, so it is never considered fully disabled.
|
|
2620
|
+
* @param gqlOperations - Effective, normalized gqlOperations configuration
|
|
2621
|
+
* @returns Whether create, update, delete, and read are all explicitly disabled
|
|
2622
|
+
*/
|
|
2623
|
+
function isGqlOperationsFullyDisabled(gqlOperations) {
|
|
2624
|
+
if (!gqlOperations) return false;
|
|
2625
|
+
return gqlOperations.create === false && gqlOperations.update === false && gqlOperations.delete === false && gqlOperations.read === false;
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* Find missing permission configuration for a TailorDB type.
|
|
2629
|
+
*
|
|
2630
|
+
* Record-level permission is always required: TailorDB denies all record
|
|
2631
|
+
* operations for a type without it, regardless of whether the type is
|
|
2632
|
+
* exposed via GraphQL. GraphQL permission is required whenever GraphQL
|
|
2633
|
+
* exposure is enabled for the type (the default, unless every operation is
|
|
2634
|
+
* explicitly disabled via `gqlOperations`).
|
|
2635
|
+
* @param rawPermissions - Raw permissions definition for the type
|
|
2636
|
+
* @param effectiveGqlOperations - The type's own gqlOperations, falling back to the namespace default
|
|
2637
|
+
* @returns Which permission configuration, if any, is missing
|
|
2638
|
+
*/
|
|
2639
|
+
function findMissingPermissionConfig(rawPermissions, effectiveGqlOperations) {
|
|
2640
|
+
return {
|
|
2641
|
+
missingPermission: !rawPermissions.record,
|
|
2642
|
+
missingGqlPermission: !rawPermissions.gql && !isGqlOperationsFullyDisabled(effectiveGqlOperations)
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
3688
2645
|
|
|
3689
2646
|
//#endregion
|
|
3690
2647
|
//#region src/parser/service/tailordb/type-parser.ts
|
|
@@ -4221,7 +3178,7 @@ function buildMinimalEntryFromResolved(imports, declarations, fnSource, sourceFi
|
|
|
4221
3178
|
].join("\n");
|
|
4222
3179
|
}
|
|
4223
3180
|
async function bundleScriptTarget(args) {
|
|
4224
|
-
const { fn, kind, sourceFilePath, sourceBindings,
|
|
3181
|
+
const { fn, kind, sourceFilePath, sourceBindings, typeName, targetIndex, tsconfig } = args;
|
|
4225
3182
|
const context = `${kind} in ${sourceFilePath}`;
|
|
4226
3183
|
const fnSource = stringifyFunction(fn);
|
|
4227
3184
|
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 +3190,10 @@ async function bundleScriptTarget(args) {
|
|
|
4233
3190
|
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
3191
|
${kind}: ${fnSource}`);
|
|
4235
3192
|
const entryContent = buildMinimalEntryFromResolved(imports, declarations, fnSource, sourceFilePath, kind === "typeValidate");
|
|
4236
|
-
const
|
|
4237
|
-
writeFileSync(entryPath, entryContent);
|
|
3193
|
+
const entry = createVirtualEntry(`tailordb-script:${typeName}:${targetIndex}`, entryContent, "ts");
|
|
4238
3194
|
const bundledCode = (await rolldown.build({
|
|
4239
|
-
plugins: [platformBundleDefinePlugin],
|
|
4240
|
-
input:
|
|
3195
|
+
plugins: [entry.plugin, platformBundleDefinePlugin],
|
|
3196
|
+
input: entry.input,
|
|
4241
3197
|
write: false,
|
|
4242
3198
|
output: {
|
|
4243
3199
|
format: "cjs",
|
|
@@ -4267,27 +3223,18 @@ async function precompileTailorDBTypeScripts(type, sourceFilePath, tsconfig) {
|
|
|
4267
3223
|
const targets = collectScriptTargets(type);
|
|
4268
3224
|
if (targets.length === 0) return;
|
|
4269
3225
|
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
|
-
}
|
|
3226
|
+
const results = await Promise.allSettled(targets.map((target, index) => bundleScriptTarget({
|
|
3227
|
+
fn: target.fn,
|
|
3228
|
+
kind: target.kind,
|
|
3229
|
+
sourceFilePath,
|
|
3230
|
+
sourceBindings,
|
|
3231
|
+
typeName: type.name,
|
|
3232
|
+
targetIndex: index,
|
|
3233
|
+
tsconfig
|
|
3234
|
+
})));
|
|
3235
|
+
const firstError = results.find((r) => r.status === "rejected");
|
|
3236
|
+
if (firstError) throw firstError.reason;
|
|
3237
|
+
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
3238
|
}
|
|
4292
3239
|
|
|
4293
3240
|
//#endregion
|
|
@@ -4417,7 +3364,7 @@ function formatTailorDBTypeNameSource(source) {
|
|
|
4417
3364
|
* @returns A new TailorDBService instance
|
|
4418
3365
|
*/
|
|
4419
3366
|
function createTailorDBService(params) {
|
|
4420
|
-
const { namespace, config, pluginManager } = params;
|
|
3367
|
+
const { namespace, config, pluginManager, baseDir } = params;
|
|
4421
3368
|
const createRawTypesByName = () => Object.create(null);
|
|
4422
3369
|
const rawTypes = Object.create(null);
|
|
4423
3370
|
let types = {};
|
|
@@ -4445,6 +3392,19 @@ function createTailorDBService(params) {
|
|
|
4445
3392
|
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
3393
|
}
|
|
4447
3394
|
};
|
|
3395
|
+
const validateRequiredPermissions = () => {
|
|
3396
|
+
const errors = [];
|
|
3397
|
+
for (const fileTypes of Object.values(rawTypes)) for (const [typeName, type] of Object.entries(fileTypes)) {
|
|
3398
|
+
const effectiveGqlOperations = type.metadata.settings?.gqlOperations ?? config.gqlOperations;
|
|
3399
|
+
const { missingPermission, missingGqlPermission } = findMissingPermissionConfig(type.metadata.permissions, effectiveGqlOperations);
|
|
3400
|
+
if (!missingPermission && !missingGqlPermission) continue;
|
|
3401
|
+
const source = formatTailorDBTypeSourceInfo(typeSourceInfo[typeName]);
|
|
3402
|
+
const location = source ? ` (${source})` : "";
|
|
3403
|
+
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.`);
|
|
3404
|
+
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 } }).`);
|
|
3405
|
+
}
|
|
3406
|
+
if (errors.length > 0) throw new Error(`TailorDB permission configuration errors in service "${namespace}":\n${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
3407
|
+
};
|
|
4448
3408
|
/**
|
|
4449
3409
|
* Process plugins for a type and add generated types to rawTypes
|
|
4450
3410
|
* @param rawType - The raw TailorDB type being processed
|
|
@@ -4524,19 +3484,15 @@ function createTailorDBService(params) {
|
|
|
4524
3484
|
loadTypes: async () => {
|
|
4525
3485
|
if (!loadPromise) loadPromise = (async () => {
|
|
4526
3486
|
if (config.files.length === 0) return;
|
|
4527
|
-
const typeFiles = [...new Set(loadFilesWithIgnores(config))];
|
|
4528
|
-
|
|
4529
|
-
try {
|
|
4530
|
-
tsconfig = await resolveTSConfig();
|
|
4531
|
-
} catch {
|
|
4532
|
-
tsconfig = void 0;
|
|
4533
|
-
}
|
|
3487
|
+
const typeFiles = [...new Set(loadFilesWithIgnores(config, baseDir))];
|
|
3488
|
+
const tsconfig = await resolveTSConfigWithFallback(baseDir);
|
|
4534
3489
|
logger.newline();
|
|
4535
3490
|
logger.log(`Found ${styles.highlight(typeFiles.length.toString())} type files for TailorDB service ${styles.highlight(`"${namespace}"`)}`);
|
|
4536
3491
|
if (pluginManager) for (const typeFile of typeFiles) await loadTypeFile(typeFile, tsconfig);
|
|
4537
3492
|
else await Promise.all(typeFiles.map((typeFile) => loadTypeFile(typeFile, tsconfig)));
|
|
4538
3493
|
doParseTypes();
|
|
4539
3494
|
warnOmittedPermit();
|
|
3495
|
+
validateRequiredPermissions();
|
|
4540
3496
|
return types;
|
|
4541
3497
|
})();
|
|
4542
3498
|
return loadPromise;
|
|
@@ -4578,7 +3534,10 @@ function createTailorDBService(params) {
|
|
|
4578
3534
|
hasGeneratedTypes = true;
|
|
4579
3535
|
logger.log(` Generated: ${styles.success(generatedType.name)} by namespace plugin ${styles.info(pluginId)}`);
|
|
4580
3536
|
}
|
|
4581
|
-
if (hasGeneratedTypes || hadPreviousGeneratedTypes)
|
|
3537
|
+
if (hasGeneratedTypes || hadPreviousGeneratedTypes) {
|
|
3538
|
+
doParseTypes();
|
|
3539
|
+
validateRequiredPermissions();
|
|
3540
|
+
}
|
|
4582
3541
|
}
|
|
4583
3542
|
};
|
|
4584
3543
|
}
|
|
@@ -4665,9 +3624,31 @@ function resolveBundleConcurrency() {
|
|
|
4665
3624
|
* @param worker - Async worker function
|
|
4666
3625
|
* @returns Worker results in input order
|
|
4667
3626
|
*/
|
|
4668
|
-
function withBundleConcurrency(items, worker) {
|
|
4669
|
-
const
|
|
4670
|
-
|
|
3627
|
+
async function withBundleConcurrency(items, worker) {
|
|
3628
|
+
const resultCount = items.length;
|
|
3629
|
+
const workItems = items.flatMap((item, index) => [{
|
|
3630
|
+
index,
|
|
3631
|
+
item
|
|
3632
|
+
}]);
|
|
3633
|
+
const results = [];
|
|
3634
|
+
results.length = resultCount;
|
|
3635
|
+
let nextWorkIndex = 0;
|
|
3636
|
+
let rejection;
|
|
3637
|
+
const runWorker = async () => {
|
|
3638
|
+
while (!rejection) {
|
|
3639
|
+
const workItem = workItems[nextWorkIndex++];
|
|
3640
|
+
if (workItem === void 0) return;
|
|
3641
|
+
try {
|
|
3642
|
+
results[workItem.index] = await worker(workItem.item);
|
|
3643
|
+
} catch (reason) {
|
|
3644
|
+
rejection ??= { reason };
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
};
|
|
3648
|
+
const workerCount = Math.min(resolveBundleConcurrency(), workItems.length);
|
|
3649
|
+
await Promise.all(Array.from({ length: workerCount }, runWorker));
|
|
3650
|
+
if (rejection) throw rejection.reason;
|
|
3651
|
+
return results;
|
|
4671
3652
|
}
|
|
4672
3653
|
|
|
4673
3654
|
//#endregion
|
|
@@ -4678,7 +3659,7 @@ function withBundleConcurrency(items, worker) {
|
|
|
4678
3659
|
* Two delivery paths:
|
|
4679
3660
|
* - Apply config: shipped with apply and evaluated by the platform before
|
|
4680
3661
|
* invoking user code.
|
|
4681
|
-
* - Bundle inline: interpolated into the generated
|
|
3662
|
+
* - Bundle inline: interpolated into the generated entry module and
|
|
4682
3663
|
* evaluated inside the bundled script at function entry.
|
|
4683
3664
|
*
|
|
4684
3665
|
* The principal field mapping (server → SDK) shared across services is built by
|
|
@@ -4768,15 +3749,15 @@ function buildResolverOperationHookExpr(env) {
|
|
|
4768
3749
|
* Bundle executors from the specified configuration
|
|
4769
3750
|
*
|
|
4770
3751
|
* This function:
|
|
4771
|
-
* 1. Creates entry
|
|
3752
|
+
* 1. Creates an in-memory entry module that extracts operation.body
|
|
4772
3753
|
* 2. Bundles in a single step with tree-shaking
|
|
4773
3754
|
* @param options - Bundle executor options
|
|
4774
3755
|
* @returns Map of executor name to bundled code
|
|
4775
3756
|
*/
|
|
4776
3757
|
async function bundleExecutors(options) {
|
|
4777
3758
|
const bundledCode = /* @__PURE__ */ new Map();
|
|
4778
|
-
const { config,
|
|
4779
|
-
const files = [...loadFilesWithIgnores(config), ...additionalFiles];
|
|
3759
|
+
const { config, startContext, additionalFiles = [], cache, inlineSourcemap, bundleLogLevel = "DEBUG", baseDir } = options;
|
|
3760
|
+
const files = [...loadFilesWithIgnores(config, baseDir), ...additionalFiles];
|
|
4780
3761
|
if (files.length === 0) {
|
|
4781
3762
|
logger.warn(`No executor files found for patterns: ${config.files.join(", ")}`);
|
|
4782
3763
|
return bundledCode;
|
|
@@ -4803,25 +3784,17 @@ async function bundleExecutors(options) {
|
|
|
4803
3784
|
logger.debug(" No function executors to bundle");
|
|
4804
3785
|
return bundledCode;
|
|
4805
3786
|
}
|
|
4806
|
-
const
|
|
4807
|
-
|
|
4808
|
-
await removeStaleEntryFiles(outputDir);
|
|
4809
|
-
let tsconfig;
|
|
4810
|
-
try {
|
|
4811
|
-
tsconfig = await resolveTSConfig();
|
|
4812
|
-
} catch {
|
|
4813
|
-
tsconfig = void 0;
|
|
4814
|
-
}
|
|
4815
|
-
const results = await withBundleConcurrency(executors, (executor) => bundleSingleExecutor(executor, outputDir, tsconfig, triggerContext, cache, inlineSourcemap, bundleLogLevel));
|
|
3787
|
+
const tsconfig = await resolveTSConfigWithFallback(baseDir);
|
|
3788
|
+
const results = await withBundleConcurrency(executors, (executor) => bundleSingleExecutor(executor, tsconfig, startContext, cache, inlineSourcemap, bundleLogLevel));
|
|
4816
3789
|
for (const [name, code] of results) bundledCode.set(name, code);
|
|
4817
3790
|
logger.log(`${styles.success("Bundled")} ${styles.info("\"executor\"")}`);
|
|
4818
3791
|
return bundledCode;
|
|
4819
3792
|
}
|
|
4820
|
-
async function bundleSingleExecutor(executor,
|
|
4821
|
-
const
|
|
3793
|
+
async function bundleSingleExecutor(executor, tsconfig, startContext, cache, inlineSourcemap, bundleLogLevel = "DEBUG") {
|
|
3794
|
+
const serializedStartContext = serializeStartContext(startContext);
|
|
4822
3795
|
const contextHash = computeBundlerContextHash({
|
|
4823
3796
|
sourceFile: executor.sourceFile,
|
|
4824
|
-
|
|
3797
|
+
extraContext: serializedStartContext,
|
|
4825
3798
|
tsconfig,
|
|
4826
3799
|
inlineSourcemap,
|
|
4827
3800
|
bundleLogLevel
|
|
@@ -4833,7 +3806,6 @@ async function bundleSingleExecutor(executor, outputDir, tsconfig, triggerContex
|
|
|
4833
3806
|
sourceFile: executor.sourceFile,
|
|
4834
3807
|
contextHash,
|
|
4835
3808
|
async build(cachePlugins) {
|
|
4836
|
-
const entryPath = path.join(outputDir, `${executor.name}.entry.js`);
|
|
4837
3809
|
const entryContent = multiline`
|
|
4838
3810
|
import _internalExecutor from "${path.resolve(executor.sourceFile)}";
|
|
4839
3811
|
|
|
@@ -4844,12 +3816,13 @@ async function bundleSingleExecutor(executor, outputDir, tsconfig, triggerContex
|
|
|
4844
3816
|
|
|
4845
3817
|
export { __executor_function as main };
|
|
4846
3818
|
`;
|
|
4847
|
-
|
|
4848
|
-
const
|
|
4849
|
-
const plugins =
|
|
3819
|
+
const entry = createVirtualEntry(`executor:${executor.name}`, entryContent);
|
|
3820
|
+
const startPlugin = createStartTransformPlugin(startContext);
|
|
3821
|
+
const plugins = [entry.plugin];
|
|
3822
|
+
if (startPlugin) plugins.push(startPlugin);
|
|
4850
3823
|
plugins.push(platformBundleDefinePlugin, ...cachePlugins);
|
|
4851
3824
|
return (await rolldown.build({
|
|
4852
|
-
input:
|
|
3825
|
+
input: entry.input,
|
|
4853
3826
|
write: false,
|
|
4854
3827
|
output: {
|
|
4855
3828
|
format: "esm",
|
|
@@ -4921,31 +3894,25 @@ const GRAPHQL_WEB_MODULE = createRequire(import.meta.url).resolve("@0no-co/graph
|
|
|
4921
3894
|
* IIFE defining a global `transform(input)` entry point. `input` gets a
|
|
4922
3895
|
* generated dispatcher that routes by `req.method`; `output` is used as is.
|
|
4923
3896
|
* @param adapters - Detected adapters to bundle
|
|
3897
|
+
* @param baseDir - Directory the owning config's tsconfig is resolved against
|
|
4924
3898
|
* @param cache - Optional bundle cache for skipping unchanged builds
|
|
4925
3899
|
* @param bundleLogLevel - Controls which console calls are kept in bundled code
|
|
4926
3900
|
* @returns Bundled scripts keyed by adapter name
|
|
4927
3901
|
*/
|
|
4928
|
-
async function bundleHttpAdapters(adapters, cache, bundleLogLevel = "DEBUG") {
|
|
3902
|
+
async function bundleHttpAdapters(adapters, baseDir, cache, bundleLogLevel = "DEBUG") {
|
|
4929
3903
|
if (adapters.length === 0) return {
|
|
4930
3904
|
bundledInputs: /* @__PURE__ */ new Map(),
|
|
4931
3905
|
bundledOutputs: /* @__PURE__ */ new Map()
|
|
4932
3906
|
};
|
|
4933
3907
|
logger.newline();
|
|
4934
3908
|
logger.log(`Bundling ${styles.highlight(adapters.length.toString())} files for ${styles.info("\"http-adapter\"")}`);
|
|
4935
|
-
const
|
|
4936
|
-
fs$1.mkdirSync(outputDir, { recursive: true });
|
|
4937
|
-
let tsconfig;
|
|
4938
|
-
try {
|
|
4939
|
-
tsconfig = await resolveTSConfig();
|
|
4940
|
-
} catch {
|
|
4941
|
-
tsconfig = void 0;
|
|
4942
|
-
}
|
|
3909
|
+
const tsconfig = await resolveTSConfigWithFallback(baseDir);
|
|
4943
3910
|
const results = await withBundleConcurrency(adapters.flatMap((adapter) => {
|
|
4944
3911
|
return (adapter.hasOutput ? ["input", "output"] : ["input"]).map((kind) => ({
|
|
4945
3912
|
adapter,
|
|
4946
3913
|
kind
|
|
4947
3914
|
}));
|
|
4948
|
-
}), ({ adapter, kind }) => bundleAdapterScript(adapter, kind,
|
|
3915
|
+
}), ({ adapter, kind }) => bundleAdapterScript(adapter, kind, tsconfig, cache, bundleLogLevel));
|
|
4949
3916
|
const bundledInputs = /* @__PURE__ */ new Map();
|
|
4950
3917
|
const bundledOutputs = /* @__PURE__ */ new Map();
|
|
4951
3918
|
for (const [name, kind, code] of results) if (kind === "input") bundledInputs.set(name, code);
|
|
@@ -4956,10 +3923,10 @@ async function bundleHttpAdapters(adapters, cache, bundleLogLevel = "DEBUG") {
|
|
|
4956
3923
|
bundledOutputs
|
|
4957
3924
|
};
|
|
4958
3925
|
}
|
|
4959
|
-
async function bundleAdapterScript(adapter, kind,
|
|
3926
|
+
async function bundleAdapterScript(adapter, kind, tsconfig, cache, bundleLogLevel = "DEBUG") {
|
|
4960
3927
|
const contextHash = computeBundlerContextHash({
|
|
4961
3928
|
sourceFile: adapter.sourceFile,
|
|
4962
|
-
|
|
3929
|
+
extraContext: kind === "input" ? adapter.methods.join(",") : "",
|
|
4963
3930
|
tsconfig,
|
|
4964
3931
|
inlineSourcemap: false,
|
|
4965
3932
|
bundleLogLevel,
|
|
@@ -4972,9 +3939,11 @@ async function bundleAdapterScript(adapter, kind, outputDir, tsconfig, cache, bu
|
|
|
4972
3939
|
sourceFile: adapter.sourceFile,
|
|
4973
3940
|
contextHash,
|
|
4974
3941
|
async build(cachePlugins) {
|
|
4975
|
-
const entryPath = path.join(outputDir, `${adapter.name}.${kind}.entry.js`);
|
|
4976
3942
|
const absoluteSourcePath = path.resolve(adapter.sourceFile);
|
|
3943
|
+
const entryContent = kind === "input" ? buildInputEntry(absoluteSourcePath, adapter.methods, GRAPHQL_WEB_MODULE) : buildOutputEntry(absoluteSourcePath);
|
|
3944
|
+
const entry = createVirtualEntry(`http-adapter:${adapter.name}:${kind}`, entryContent);
|
|
4977
3945
|
const plugins = [
|
|
3946
|
+
entry.plugin,
|
|
4978
3947
|
{
|
|
4979
3948
|
name: "http-adapter-reject-node-imports",
|
|
4980
3949
|
resolveId(source) {
|
|
@@ -4998,30 +3967,21 @@ async function bundleAdapterScript(adapter, kind, outputDir, tsconfig, cache, bu
|
|
|
4998
3967
|
},
|
|
4999
3968
|
...cachePlugins
|
|
5000
3969
|
];
|
|
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
|
-
}
|
|
3970
|
+
const bundled = (await rolldown.build({
|
|
3971
|
+
input: entry.input,
|
|
3972
|
+
write: false,
|
|
3973
|
+
output: {
|
|
3974
|
+
format: "iife",
|
|
3975
|
+
sourcemap: false,
|
|
3976
|
+
minify: true,
|
|
3977
|
+
codeSplitting: false
|
|
3978
|
+
},
|
|
3979
|
+
tsconfig,
|
|
3980
|
+
plugins,
|
|
3981
|
+
transform: { target: "es2017" },
|
|
3982
|
+
treeshake: composeFunctionTreeshakeOptions([createLogLevelTreeshakeOptions(bundleLogLevel)]),
|
|
3983
|
+
logLevel: "silent"
|
|
3984
|
+
})).output[0].code;
|
|
5025
3985
|
const byteLength = Buffer.byteLength(bundled, "utf8");
|
|
5026
3986
|
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
3987
|
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`);
|
|
@@ -5095,7 +4055,7 @@ function rejectAsyncInBundle(code, adapterName, kind) {
|
|
|
5095
4055
|
//#endregion
|
|
5096
4056
|
//#region src/cli/services/http-adapter/service.ts
|
|
5097
4057
|
function createHttpAdapterService(params) {
|
|
5098
|
-
const { config } = params;
|
|
4058
|
+
const { config, baseDir } = params;
|
|
5099
4059
|
let adapters = [];
|
|
5100
4060
|
let fileCount = 0;
|
|
5101
4061
|
let loaded = false;
|
|
@@ -5109,7 +4069,7 @@ function createHttpAdapterService(params) {
|
|
|
5109
4069
|
},
|
|
5110
4070
|
loadAdapters: async () => {
|
|
5111
4071
|
if (loaded) return;
|
|
5112
|
-
const result = await loadAdapterFiles(config);
|
|
4072
|
+
const result = await loadAdapterFiles(config, baseDir);
|
|
5113
4073
|
adapters = result.adapters;
|
|
5114
4074
|
fileCount = result.fileCount;
|
|
5115
4075
|
loaded = true;
|
|
@@ -5125,12 +4085,12 @@ function createHttpAdapterService(params) {
|
|
|
5125
4085
|
}
|
|
5126
4086
|
};
|
|
5127
4087
|
}
|
|
5128
|
-
async function loadAdapterFiles(config) {
|
|
4088
|
+
async function loadAdapterFiles(config, baseDir) {
|
|
5129
4089
|
if (config.files.length === 0) return {
|
|
5130
4090
|
adapters: [],
|
|
5131
4091
|
fileCount: 0
|
|
5132
4092
|
};
|
|
5133
|
-
const files = loadFilesWithIgnores(config);
|
|
4093
|
+
const files = loadFilesWithIgnores(config, baseDir);
|
|
5134
4094
|
const loadResults = await Promise.all(files.map(loadAdapterFromFile));
|
|
5135
4095
|
const adapters = [];
|
|
5136
4096
|
const seenNames = /* @__PURE__ */ new Map();
|
|
@@ -5220,19 +4180,20 @@ async function loadResolver(resolverFilePath) {
|
|
|
5220
4180
|
*
|
|
5221
4181
|
* This function:
|
|
5222
4182
|
* 1. Uses a transform plugin to add validation wrapper during bundling
|
|
5223
|
-
* 2. Creates entry
|
|
4183
|
+
* 2. Creates an in-memory entry module
|
|
5224
4184
|
* 3. Bundles in a single step with tree-shaking
|
|
5225
4185
|
* @param namespace - Resolver namespace name
|
|
5226
4186
|
* @param config - Resolver file loading configuration
|
|
5227
|
-
* @param
|
|
4187
|
+
* @param baseDir - Directory the config's file patterns are resolved against
|
|
4188
|
+
* @param startContext - Start context for workflow/job transformations
|
|
5228
4189
|
* @param cache - Optional bundle cache for skipping unchanged builds
|
|
5229
4190
|
* @param inlineSourcemap - Whether to enable inline sourcemaps
|
|
5230
4191
|
* @param bundleLogLevel - Controls which console calls are kept in bundled code
|
|
5231
4192
|
* @returns Map of resolver name to bundled code
|
|
5232
4193
|
*/
|
|
5233
|
-
async function bundleResolvers(namespace, config,
|
|
4194
|
+
async function bundleResolvers(namespace, config, baseDir, startContext, cache, inlineSourcemap, bundleLogLevel = "DEBUG") {
|
|
5234
4195
|
const bundledCode = /* @__PURE__ */ new Map();
|
|
5235
|
-
const files = loadFilesWithIgnores(config);
|
|
4196
|
+
const files = loadFilesWithIgnores(config, baseDir);
|
|
5236
4197
|
if (files.length === 0) {
|
|
5237
4198
|
logger.warn(`No resolver files found for patterns: ${config.files.join(", ")}`);
|
|
5238
4199
|
return bundledCode;
|
|
@@ -5251,25 +4212,17 @@ async function bundleResolvers(namespace, config, triggerContext, cache, inlineS
|
|
|
5251
4212
|
sourceFile: file
|
|
5252
4213
|
});
|
|
5253
4214
|
}
|
|
5254
|
-
const
|
|
5255
|
-
|
|
5256
|
-
await removeStaleEntryFiles(outputDir);
|
|
5257
|
-
let tsconfig;
|
|
5258
|
-
try {
|
|
5259
|
-
tsconfig = await resolveTSConfig();
|
|
5260
|
-
} catch {
|
|
5261
|
-
tsconfig = void 0;
|
|
5262
|
-
}
|
|
5263
|
-
const results = await withBundleConcurrency(resolvers, (resolver) => bundleSingleResolver(namespace, resolver, outputDir, tsconfig, triggerContext, cache, inlineSourcemap, bundleLogLevel));
|
|
4215
|
+
const tsconfig = await resolveTSConfigWithFallback(baseDir);
|
|
4216
|
+
const results = await withBundleConcurrency(resolvers, (resolver) => bundleSingleResolver(namespace, resolver, tsconfig, startContext, cache, inlineSourcemap, bundleLogLevel));
|
|
5264
4217
|
for (const [name, code] of results) bundledCode.set(name, code);
|
|
5265
4218
|
logger.log(`${styles.success("Bundled")} ${styles.info(`"${namespace}"`)}`);
|
|
5266
4219
|
return bundledCode;
|
|
5267
4220
|
}
|
|
5268
|
-
async function bundleSingleResolver(namespace, resolver,
|
|
5269
|
-
const
|
|
4221
|
+
async function bundleSingleResolver(namespace, resolver, tsconfig, startContext, cache, inlineSourcemap, bundleLogLevel = "DEBUG") {
|
|
4222
|
+
const serializedStartContext = serializeStartContext(startContext);
|
|
5270
4223
|
const contextHash = computeBundlerContextHash({
|
|
5271
4224
|
sourceFile: resolver.sourceFile,
|
|
5272
|
-
|
|
4225
|
+
extraContext: serializedStartContext,
|
|
5273
4226
|
tsconfig,
|
|
5274
4227
|
inlineSourcemap,
|
|
5275
4228
|
bundleLogLevel
|
|
@@ -5282,7 +4235,6 @@ async function bundleSingleResolver(namespace, resolver, outputDir, tsconfig, tr
|
|
|
5282
4235
|
sourceFile: resolver.sourceFile,
|
|
5283
4236
|
contextHash,
|
|
5284
4237
|
async build(cachePlugins) {
|
|
5285
|
-
const entryPath = path.join(outputDir, `${resolver.name}.entry.js`);
|
|
5286
4238
|
const entryContent = multiline`
|
|
5287
4239
|
import _internalResolver from "${path.resolve(resolver.sourceFile)}";
|
|
5288
4240
|
import { t } from "@tailor-platform/sdk";
|
|
@@ -5309,12 +4261,13 @@ async function bundleSingleResolver(namespace, resolver, outputDir, tsconfig, tr
|
|
|
5309
4261
|
|
|
5310
4262
|
export { $tailor_resolver_body as main };
|
|
5311
4263
|
`;
|
|
5312
|
-
|
|
5313
|
-
const
|
|
5314
|
-
const plugins =
|
|
4264
|
+
const entry = createVirtualEntry(`resolver:${resolver.name}`, entryContent);
|
|
4265
|
+
const startPlugin = createStartTransformPlugin(startContext);
|
|
4266
|
+
const plugins = [entry.plugin];
|
|
4267
|
+
if (startPlugin) plugins.push(startPlugin);
|
|
5315
4268
|
plugins.push(platformBundleDefinePlugin, ...cachePlugins);
|
|
5316
4269
|
return (await rolldown.build({
|
|
5317
|
-
input:
|
|
4270
|
+
input: entry.input,
|
|
5318
4271
|
write: false,
|
|
5319
4272
|
output: {
|
|
5320
4273
|
format: "esm",
|
|
@@ -5338,9 +4291,10 @@ async function bundleSingleResolver(namespace, resolver, outputDir, tsconfig, tr
|
|
|
5338
4291
|
* Creates a new ResolverService instance.
|
|
5339
4292
|
* @param namespace - The namespace for this resolver service
|
|
5340
4293
|
* @param config - The resolver service configuration
|
|
4294
|
+
* @param baseDir - Directory the config's file patterns are resolved against
|
|
5341
4295
|
* @returns A new ResolverService instance
|
|
5342
4296
|
*/
|
|
5343
|
-
function createResolverService(namespace, config) {
|
|
4297
|
+
function createResolverService(namespace, config, baseDir) {
|
|
5344
4298
|
const resolvers = {};
|
|
5345
4299
|
const loadResolverForFile = async (resolverFile) => {
|
|
5346
4300
|
try {
|
|
@@ -5369,7 +4323,7 @@ function createResolverService(namespace, config) {
|
|
|
5369
4323
|
loadResolvers: async () => {
|
|
5370
4324
|
if (Object.keys(resolvers).length > 0) return;
|
|
5371
4325
|
if (config.files.length === 0) return;
|
|
5372
|
-
const resolverFiles = loadFilesWithIgnores(config);
|
|
4326
|
+
const resolverFiles = loadFilesWithIgnores(config, baseDir);
|
|
5373
4327
|
logger.log(`Found ${styles.highlight(resolverFiles.length.toString())} resolver files for service ${styles.highlight(`"${namespace}"`)}`);
|
|
5374
4328
|
await Promise.all(resolverFiles.map((resolverFile) => loadResolverForFile(resolverFile)));
|
|
5375
4329
|
assertUniqueResolverNames(resolvers, namespace);
|
|
@@ -5502,7 +4456,7 @@ function transformWorkflowSource(source, targetJobName, targetJobExportName, oth
|
|
|
5502
4456
|
function safeRealpath(p) {
|
|
5503
4457
|
const resolved = path.resolve(p);
|
|
5504
4458
|
try {
|
|
5505
|
-
return fs
|
|
4459
|
+
return fs.realpathSync(resolved);
|
|
5506
4460
|
} catch (e) {
|
|
5507
4461
|
logger.debug(`realpathSync failed for ${resolved}: ${e instanceof Error ? e.message : e}`);
|
|
5508
4462
|
return resolved;
|
|
@@ -5513,20 +4467,21 @@ function safeRealpath(p) {
|
|
|
5513
4467
|
*
|
|
5514
4468
|
* This function:
|
|
5515
4469
|
* 1. Detects which jobs are actually used (mainJobs + their dependencies)
|
|
5516
|
-
* 2. Uses a transform plugin to transform
|
|
5517
|
-
* 3. Creates entry
|
|
4470
|
+
* 2. Uses a transform plugin to transform start calls during bundling
|
|
4471
|
+
* 3. Creates an in-memory entry module and bundles with tree-shaking
|
|
5518
4472
|
*
|
|
5519
4473
|
* Returns metadata about which jobs each workflow uses.
|
|
5520
4474
|
* @param allJobs - All available job infos
|
|
5521
4475
|
* @param mainJobNames - Names of main jobs
|
|
5522
4476
|
* @param env - Environment variables to inject
|
|
5523
|
-
* @param
|
|
4477
|
+
* @param startContext - Start context for transformations
|
|
4478
|
+
* @param baseDir - Directory the owning config's tsconfig is resolved against
|
|
5524
4479
|
* @param cache - Optional bundle cache for skipping unchanged builds
|
|
5525
4480
|
* @param inlineSourcemap - Whether to enable inline sourcemaps
|
|
5526
4481
|
* @param bundleLogLevel - Controls which console calls are kept in bundled code
|
|
5527
4482
|
* @returns Workflow job bundling result
|
|
5528
4483
|
*/
|
|
5529
|
-
async function bundleWorkflowJobs(allJobs, mainJobNames, env = {},
|
|
4484
|
+
async function bundleWorkflowJobs(allJobs, mainJobNames, env = {}, startContext, baseDir, cache, inlineSourcemap, bundleLogLevel = "DEBUG") {
|
|
5530
4485
|
if (allJobs.length === 0) {
|
|
5531
4486
|
logger.warn("No workflow jobs to bundle");
|
|
5532
4487
|
return {
|
|
@@ -5535,22 +4490,11 @@ async function bundleWorkflowJobs(allJobs, mainJobNames, env = {}, triggerContex
|
|
|
5535
4490
|
bundledCode: /* @__PURE__ */ new Map()
|
|
5536
4491
|
};
|
|
5537
4492
|
}
|
|
5538
|
-
const { usedJobs, mainJobDeps } = await filterUsedJobs(allJobs, mainJobNames,
|
|
4493
|
+
const { usedJobs, mainJobDeps } = await filterUsedJobs(allJobs, mainJobNames, startContext);
|
|
5539
4494
|
logger.newline();
|
|
5540
4495
|
logger.log(`Bundling ${styles.highlight(usedJobs.length.toString())} files for ${styles.info("\"workflow-job\"")}`);
|
|
5541
|
-
const
|
|
5542
|
-
|
|
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
|
-
let tsconfig;
|
|
5548
|
-
try {
|
|
5549
|
-
tsconfig = await resolveTSConfig();
|
|
5550
|
-
} catch {
|
|
5551
|
-
tsconfig = void 0;
|
|
5552
|
-
}
|
|
5553
|
-
const results = await withBundleConcurrency(usedJobs, (job) => bundleSingleJob(job, usedJobs, outputDir, tsconfig, env, triggerContext, cache, inlineSourcemap, bundleLogLevel));
|
|
4496
|
+
const tsconfig = await resolveTSConfigWithFallback(baseDir);
|
|
4497
|
+
const results = await withBundleConcurrency(usedJobs, (job) => bundleSingleJob(job, usedJobs, tsconfig, env, startContext, cache, inlineSourcemap, bundleLogLevel));
|
|
5554
4498
|
const bundledCode = /* @__PURE__ */ new Map();
|
|
5555
4499
|
for (const [name, code] of results) bundledCode.set(name, code);
|
|
5556
4500
|
logger.log(`${styles.success("Bundled")} ${styles.info("\"workflow-job\"")}`);
|
|
@@ -5564,15 +4508,15 @@ async function bundleWorkflowJobs(allJobs, mainJobNames, env = {}, triggerContex
|
|
|
5564
4508
|
* Filter jobs to only include those that are actually used.
|
|
5565
4509
|
* A job is "used" if:
|
|
5566
4510
|
* - It's a mainJob of a workflow
|
|
5567
|
-
* - It's called via .
|
|
4511
|
+
* - It's called via .start() from another used job (transitively)
|
|
5568
4512
|
*
|
|
5569
4513
|
* Also returns a map of mainJob -> all jobs it depends on (for metadata).
|
|
5570
4514
|
* @param allJobs - All available job infos
|
|
5571
4515
|
* @param mainJobNames - Names of main jobs
|
|
5572
|
-
* @param
|
|
4516
|
+
* @param startContext - Module binding metadata for resolving start targets
|
|
5573
4517
|
* @returns Used jobs and main job dependency map
|
|
5574
4518
|
*/
|
|
5575
|
-
async function filterUsedJobs(allJobs, mainJobNames,
|
|
4519
|
+
async function filterUsedJobs(allJobs, mainJobNames, startContext) {
|
|
5576
4520
|
if (allJobs.length === 0 || mainJobNames.length === 0) return {
|
|
5577
4521
|
usedJobs: [],
|
|
5578
4522
|
mainJobDeps: {}
|
|
@@ -5586,16 +4530,16 @@ async function filterUsedJobs(allJobs, mainJobNames, triggerContext) {
|
|
|
5586
4530
|
const dependencies = /* @__PURE__ */ new Map();
|
|
5587
4531
|
const fileResults = await Promise.all(Array.from(jobsBySourceFile.entries()).map(async ([sourceFile, jobs]) => {
|
|
5588
4532
|
try {
|
|
5589
|
-
const source = await fs
|
|
4533
|
+
const source = await fs.promises.readFile(sourceFile, "utf-8");
|
|
5590
4534
|
const { program } = parseSync("input.ts", source);
|
|
5591
4535
|
const detectedJobs = findAllJobs(program, source);
|
|
5592
|
-
const
|
|
4536
|
+
const startCalls = detectResolvedStartCalls(program, source, startContext, sourceFile);
|
|
5593
4537
|
const jobDependencies = [];
|
|
5594
4538
|
for (const job of jobs) {
|
|
5595
4539
|
const detectedJob = detectedJobs.find((d) => d.name === job.name);
|
|
5596
4540
|
if (!detectedJob) continue;
|
|
5597
4541
|
const jobDeps = /* @__PURE__ */ new Set();
|
|
5598
|
-
for (const call of
|
|
4542
|
+
for (const call of startCalls) if (call.kind === "job" && call.callRange.start >= detectedJob.bodyValueRange.start && call.callRange.end <= detectedJob.bodyValueRange.end) jobDeps.add(call.targetName);
|
|
5599
4543
|
if (jobDeps.size > 0) jobDependencies.push({
|
|
5600
4544
|
jobName: job.name,
|
|
5601
4545
|
deps: jobDeps
|
|
@@ -5626,12 +4570,12 @@ async function filterUsedJobs(allJobs, mainJobNames, triggerContext) {
|
|
|
5626
4570
|
mainJobDeps
|
|
5627
4571
|
};
|
|
5628
4572
|
}
|
|
5629
|
-
async function bundleSingleJob(job, allJobs,
|
|
5630
|
-
const
|
|
4573
|
+
async function bundleSingleJob(job, allJobs, tsconfig, env, startContext, cache, inlineSourcemap, bundleLogLevel = "DEBUG") {
|
|
4574
|
+
const serializedStartContext = serializeStartContext(startContext);
|
|
5631
4575
|
const sortedEnvPrefix = JSON.stringify(Object.fromEntries(Object.entries(env).toSorted(([a], [b]) => a.localeCompare(b))));
|
|
5632
4576
|
const contextHash = computeBundlerContextHash({
|
|
5633
4577
|
sourceFile: job.sourceFile,
|
|
5634
|
-
|
|
4578
|
+
extraContext: serializedStartContext,
|
|
5635
4579
|
tsconfig,
|
|
5636
4580
|
inlineSourcemap,
|
|
5637
4581
|
bundleLogLevel,
|
|
@@ -5644,7 +4588,6 @@ async function bundleSingleJob(job, allJobs, outputDir, tsconfig, env, triggerCo
|
|
|
5644
4588
|
sourceFile: job.sourceFile,
|
|
5645
4589
|
contextHash,
|
|
5646
4590
|
async build(cachePlugins) {
|
|
5647
|
-
const entryPath = path.join(outputDir, `${job.name}.entry.js`);
|
|
5648
4591
|
const absoluteSourcePath = path.resolve(job.sourceFile);
|
|
5649
4592
|
const entryContent = multiline`
|
|
5650
4593
|
import { ${job.exportName} } from "${absoluteSourcePath}";
|
|
@@ -5655,19 +4598,20 @@ async function bundleSingleJob(job, allJobs, outputDir, tsconfig, env, triggerCo
|
|
|
5655
4598
|
return await ${job.exportName}.body(input, { env, invoker });
|
|
5656
4599
|
}
|
|
5657
4600
|
`;
|
|
5658
|
-
|
|
4601
|
+
const entry = createVirtualEntry(`workflow-job:${job.name}`, entryContent);
|
|
5659
4602
|
const resolvedSourceFile = safeRealpath(job.sourceFile);
|
|
5660
4603
|
const otherJobExportNames = allJobs.filter((candidate) => candidate.name !== job.name && safeRealpath(candidate.sourceFile) === resolvedSourceFile).map((j) => j.exportName);
|
|
5661
4604
|
const plugins = [
|
|
4605
|
+
entry.plugin,
|
|
5662
4606
|
{
|
|
5663
4607
|
name: "workflow-transform",
|
|
5664
4608
|
transform: {
|
|
5665
4609
|
filter: { id: { include: [/\.(ts|mts|cts|js|mjs|cjs)$/] } },
|
|
5666
4610
|
handler(code, id) {
|
|
5667
|
-
if (!code.includes("createWorkflowJob") && !code.includes("createWorkflow") && !code
|
|
4611
|
+
if (!code.includes("createWorkflowJob") && !code.includes("createWorkflow") && !hasStartCall(code)) return null;
|
|
5668
4612
|
let transformed = code;
|
|
5669
4613
|
if (safeRealpath(id) === resolvedSourceFile) transformed = transformWorkflowSource(code, job.name, job.exportName, otherJobExportNames);
|
|
5670
|
-
if (transformed
|
|
4614
|
+
if (hasStartCall(transformed)) transformed = transformStartCalls(transformed, startContext, id);
|
|
5671
4615
|
if (transformed === code) return null;
|
|
5672
4616
|
return { code: transformed };
|
|
5673
4617
|
}
|
|
@@ -5677,7 +4621,7 @@ async function bundleSingleJob(job, allJobs, outputDir, tsconfig, env, triggerCo
|
|
|
5677
4621
|
...cachePlugins
|
|
5678
4622
|
];
|
|
5679
4623
|
return (await rolldown.build({
|
|
5680
|
-
input:
|
|
4624
|
+
input: entry.input,
|
|
5681
4625
|
write: false,
|
|
5682
4626
|
output: {
|
|
5683
4627
|
format: "esm",
|
|
@@ -5699,7 +4643,7 @@ async function bundleSingleJob(job, allJobs, outputDir, tsconfig, env, triggerCo
|
|
|
5699
4643
|
//#region src/parser/service/workflow/schema.ts
|
|
5700
4644
|
const WorkflowJobSchema = z.strictObject({
|
|
5701
4645
|
name: z.string().describe("Job name (must be unique across the project)"),
|
|
5702
|
-
|
|
4646
|
+
start: functionSchema.describe("Start function that initiates the job"),
|
|
5703
4647
|
body: functionSchema.describe("Job implementation function")
|
|
5704
4648
|
});
|
|
5705
4649
|
const durationUnits = [
|
|
@@ -5736,7 +4680,7 @@ const RetryPolicySchema = z.strictObject({
|
|
|
5736
4680
|
});
|
|
5737
4681
|
const ConcurrencyPolicySchema = z.strictObject({ maxConcurrentExecutions: z.number().int().min(1).max(1e3).describe("Maximum number of concurrent executions (1-1000)") });
|
|
5738
4682
|
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
|
|
4683
|
+
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 executionPolicyKey option");
|
|
5740
4684
|
const WorkflowJobFunctionExecutionPolicySchema = z.strictObject({
|
|
5741
4685
|
name: ExecutionPolicyNameSchema,
|
|
5742
4686
|
key: ExecutionPolicyKeySchema,
|
|
@@ -5751,9 +4695,9 @@ const WorkflowSchema = z.strictObject({
|
|
|
5751
4695
|
|
|
5752
4696
|
//#endregion
|
|
5753
4697
|
//#region src/cli/services/workflow/service.ts
|
|
5754
|
-
function
|
|
5755
|
-
if (!isSdkBranded(workflow, "workflow") || workflow === null || typeof workflow !== "object" || !("
|
|
5756
|
-
const {
|
|
4698
|
+
function stripRuntimeStart(workflow) {
|
|
4699
|
+
if (!isSdkBranded(workflow, "workflow") || workflow === null || typeof workflow !== "object" || !("start" in workflow)) return workflow;
|
|
4700
|
+
const { start: _start, ...rest } = workflow;
|
|
5757
4701
|
return rest;
|
|
5758
4702
|
}
|
|
5759
4703
|
/**
|
|
@@ -5762,7 +4706,7 @@ function stripRuntimeTrigger(workflow) {
|
|
|
5762
4706
|
* @returns A new WorkflowService instance
|
|
5763
4707
|
*/
|
|
5764
4708
|
function createWorkflowService(params) {
|
|
5765
|
-
const { config } = params;
|
|
4709
|
+
const { config, baseDir } = params;
|
|
5766
4710
|
let workflows = {};
|
|
5767
4711
|
let workflowSources = [];
|
|
5768
4712
|
let jobs = [];
|
|
@@ -5784,7 +4728,7 @@ function createWorkflowService(params) {
|
|
|
5784
4728
|
},
|
|
5785
4729
|
loadWorkflows: async () => {
|
|
5786
4730
|
if (loaded) return;
|
|
5787
|
-
const result = await loadAndCollectJobs(config);
|
|
4731
|
+
const result = await loadAndCollectJobs(config, baseDir);
|
|
5788
4732
|
workflows = result.workflows;
|
|
5789
4733
|
workflowSources = result.workflowSources;
|
|
5790
4734
|
jobs = result.jobs;
|
|
@@ -5806,9 +4750,10 @@ function createWorkflowService(params) {
|
|
|
5806
4750
|
* Load workflow files and collect all jobs in a single pass.
|
|
5807
4751
|
* Dependencies are detected at bundle time via AST analysis.
|
|
5808
4752
|
* @param config - Workflow service configuration
|
|
4753
|
+
* @param baseDir - Directory the config's file patterns are resolved against
|
|
5809
4754
|
* @returns Loaded workflows and collected jobs
|
|
5810
4755
|
*/
|
|
5811
|
-
async function loadAndCollectJobs(config) {
|
|
4756
|
+
async function loadAndCollectJobs(config, baseDir) {
|
|
5812
4757
|
const workflows = {};
|
|
5813
4758
|
const workflowSources = [];
|
|
5814
4759
|
const collectedJobs = [];
|
|
@@ -5818,7 +4763,7 @@ async function loadAndCollectJobs(config) {
|
|
|
5818
4763
|
jobs: collectedJobs,
|
|
5819
4764
|
fileCount: 0
|
|
5820
4765
|
};
|
|
5821
|
-
const workflowFiles = loadFilesWithIgnores(config);
|
|
4766
|
+
const workflowFiles = loadFilesWithIgnores(config, baseDir);
|
|
5822
4767
|
const fileCount = workflowFiles.length;
|
|
5823
4768
|
const allJobsMap = /* @__PURE__ */ new Map();
|
|
5824
4769
|
const loadResults = await Promise.all(workflowFiles.map(async (workflowFile) => {
|
|
@@ -5863,7 +4808,7 @@ async function loadFileContent(filePath) {
|
|
|
5863
4808
|
const module = await import(pathToFileURL(filePath).href);
|
|
5864
4809
|
for (const [exportName, exportValue] of Object.entries(module)) {
|
|
5865
4810
|
if (exportName === "default") {
|
|
5866
|
-
const workflowResult = WorkflowSchema.safeParse(
|
|
4811
|
+
const workflowResult = WorkflowSchema.safeParse(stripRuntimeStart(exportValue));
|
|
5867
4812
|
if (workflowResult.success) workflow = workflowResult.data;
|
|
5868
4813
|
else if (isSdkBranded(exportValue, ["workflow", "workflow-job"])) throw workflowResult.error;
|
|
5869
4814
|
continue;
|
|
@@ -6131,7 +5076,7 @@ const StaticWebsiteSchema = z.strictObject({
|
|
|
6131
5076
|
|
|
6132
5077
|
//#endregion
|
|
6133
5078
|
//#region src/cli/services/application.ts
|
|
6134
|
-
function defineTailorDB(config, pluginManager) {
|
|
5079
|
+
function defineTailorDB(config, baseDir, pluginManager) {
|
|
6135
5080
|
const tailorDBServices = [];
|
|
6136
5081
|
const externalTailorDBNamespaces = [];
|
|
6137
5082
|
const subgraphs = [];
|
|
@@ -6146,7 +5091,8 @@ function defineTailorDB(config, pluginManager) {
|
|
|
6146
5091
|
const tailorDB = createTailorDBService({
|
|
6147
5092
|
namespace,
|
|
6148
5093
|
config: TailorDBServiceConfigSchema.parse(serviceConfig),
|
|
6149
|
-
pluginManager
|
|
5094
|
+
pluginManager,
|
|
5095
|
+
baseDir
|
|
6150
5096
|
});
|
|
6151
5097
|
tailorDBServices.push(tailorDB);
|
|
6152
5098
|
}
|
|
@@ -6161,7 +5107,7 @@ function defineTailorDB(config, pluginManager) {
|
|
|
6161
5107
|
subgraphs
|
|
6162
5108
|
};
|
|
6163
5109
|
}
|
|
6164
|
-
function defineResolver(config) {
|
|
5110
|
+
function defineResolver(config, baseDir) {
|
|
6165
5111
|
const resolverServices = [];
|
|
6166
5112
|
const subgraphs = [];
|
|
6167
5113
|
if (!config) return {
|
|
@@ -6170,7 +5116,7 @@ function defineResolver(config) {
|
|
|
6170
5116
|
};
|
|
6171
5117
|
for (const [namespace, serviceConfig] of Object.entries(config)) {
|
|
6172
5118
|
if (!("external" in serviceConfig)) {
|
|
6173
|
-
const resolverService = createResolverService(namespace, serviceConfig);
|
|
5119
|
+
const resolverService = createResolverService(namespace, serviceConfig, baseDir);
|
|
6174
5120
|
resolverServices.push(resolverService);
|
|
6175
5121
|
}
|
|
6176
5122
|
subgraphs.push({
|
|
@@ -6232,17 +5178,26 @@ function defineAuth(config, tailorDBServices, externalTailorDBNamespaces) {
|
|
|
6232
5178
|
subgraphs
|
|
6233
5179
|
};
|
|
6234
5180
|
}
|
|
6235
|
-
function defineExecutor(config, hasPluginExecutors) {
|
|
5181
|
+
function defineExecutor(config, baseDir, hasPluginExecutors) {
|
|
6236
5182
|
if (!config && !hasPluginExecutors) return;
|
|
6237
|
-
return createExecutorService({
|
|
5183
|
+
return createExecutorService({
|
|
5184
|
+
config: config ?? { files: [] },
|
|
5185
|
+
baseDir
|
|
5186
|
+
});
|
|
6238
5187
|
}
|
|
6239
|
-
function defineWorkflow(config) {
|
|
5188
|
+
function defineWorkflow(config, baseDir) {
|
|
6240
5189
|
if (!config) return;
|
|
6241
|
-
return createWorkflowService({
|
|
5190
|
+
return createWorkflowService({
|
|
5191
|
+
config,
|
|
5192
|
+
baseDir
|
|
5193
|
+
});
|
|
6242
5194
|
}
|
|
6243
|
-
function defineHttpAdapterService(config) {
|
|
5195
|
+
function defineHttpAdapterService(config, baseDir) {
|
|
6244
5196
|
if (!config) return;
|
|
6245
|
-
return createHttpAdapterService({
|
|
5197
|
+
return createHttpAdapterService({
|
|
5198
|
+
config,
|
|
5199
|
+
baseDir
|
|
5200
|
+
});
|
|
6246
5201
|
}
|
|
6247
5202
|
function stripStaticWebsiteUrlHelper(config) {
|
|
6248
5203
|
const configWithUrl = config;
|
|
@@ -6294,9 +5249,9 @@ function parseSecretManager(config) {
|
|
|
6294
5249
|
ignoreNullishValues
|
|
6295
5250
|
};
|
|
6296
5251
|
}
|
|
6297
|
-
function defineServices(config, pluginManager) {
|
|
6298
|
-
const tailordbResult = defineTailorDB(config.db, pluginManager);
|
|
6299
|
-
const resolverResult = defineResolver(config.resolver);
|
|
5252
|
+
function defineServices(config, baseDir, pluginManager) {
|
|
5253
|
+
const tailordbResult = defineTailorDB(config.db, baseDir, pluginManager);
|
|
5254
|
+
const resolverResult = defineResolver(config.resolver, baseDir);
|
|
6300
5255
|
const idpResult = defineIdp(config.idp);
|
|
6301
5256
|
const authResult = defineAuth(config.auth, tailordbResult.tailorDBServices, tailordbResult.externalTailorDBNamespaces);
|
|
6302
5257
|
const staticWebsiteServices = defineStaticWebsites(config.staticWebsites);
|
|
@@ -6352,10 +5307,11 @@ function buildApplication(params) {
|
|
|
6352
5307
|
*/
|
|
6353
5308
|
function defineApplication(params) {
|
|
6354
5309
|
const { config, pluginManager } = params;
|
|
6355
|
-
const
|
|
6356
|
-
const
|
|
6357
|
-
const
|
|
6358
|
-
const
|
|
5310
|
+
const baseDir = path.dirname(config.path);
|
|
5311
|
+
const services = defineServices(config, baseDir, pluginManager);
|
|
5312
|
+
const executorService = defineExecutor(config.executor, baseDir, false);
|
|
5313
|
+
const workflowService = defineWorkflow(config.workflow, baseDir);
|
|
5314
|
+
const httpAdapterService = defineHttpAdapterService(config.httpAdapter, baseDir);
|
|
6359
5315
|
return buildApplication({
|
|
6360
5316
|
config,
|
|
6361
5317
|
...services,
|
|
@@ -6400,22 +5356,23 @@ function generatePluginFilesIfNeeded(pluginManager, tailorDBServices, configPath
|
|
|
6400
5356
|
*/
|
|
6401
5357
|
async function loadApplication(params) {
|
|
6402
5358
|
const { config, pluginManager, bundleCache } = params;
|
|
6403
|
-
const
|
|
5359
|
+
const baseDir = path.dirname(config.path);
|
|
5360
|
+
const { tailordbResult, resolverResult, idpResult, authResult, staticWebsiteServices, aiGatewayServices, secrets, ignoreNullishValues } = defineServices(config, baseDir, pluginManager);
|
|
6404
5361
|
for (const tailordb of tailordbResult.tailorDBServices) {
|
|
6405
5362
|
await tailordb.loadTypes();
|
|
6406
5363
|
await tailordb.processNamespacePlugins();
|
|
6407
5364
|
}
|
|
6408
5365
|
assertUniqueLocalTailorDBTypeNames({ tailorDBServices: tailordbResult.tailorDBServices });
|
|
6409
5366
|
const pluginExecutorFiles = generatePluginFilesIfNeeded(pluginManager, tailordbResult.tailorDBServices, config.path);
|
|
6410
|
-
const executorService = defineExecutor(config.executor, pluginExecutorFiles.length > 0);
|
|
6411
|
-
const workflowService = defineWorkflow(config.workflow);
|
|
5367
|
+
const executorService = defineExecutor(config.executor, baseDir, pluginExecutorFiles.length > 0);
|
|
5368
|
+
const workflowService = defineWorkflow(config.workflow, baseDir);
|
|
6412
5369
|
if (workflowService) await workflowService.loadWorkflows();
|
|
6413
|
-
const httpAdapterService = defineHttpAdapterService(config.httpAdapter);
|
|
5370
|
+
const httpAdapterService = defineHttpAdapterService(config.httpAdapter, baseDir);
|
|
6414
5371
|
if (httpAdapterService) await httpAdapterService.loadAdapters();
|
|
6415
|
-
const
|
|
5372
|
+
const startContext = await buildStartContext(config.workflow, getApplicationAuthNamespace({
|
|
6416
5373
|
authService: authResult.authService,
|
|
6417
5374
|
config
|
|
6418
|
-
}));
|
|
5375
|
+
}), baseDir);
|
|
6419
5376
|
const inlineSourcemap = resolveInlineSourcemap(config.inlineSourcemap);
|
|
6420
5377
|
const bundleLogLevel = resolveBundleLogLevel(config.logLevel);
|
|
6421
5378
|
const bundledScripts = {
|
|
@@ -6425,21 +5382,22 @@ async function loadApplication(params) {
|
|
|
6425
5382
|
authHooks: /* @__PURE__ */ new Map()
|
|
6426
5383
|
};
|
|
6427
5384
|
for (const pipeline of resolverResult.resolverServices) {
|
|
6428
|
-
const resolverBundles = await bundleResolvers(pipeline.namespace, pipeline.config,
|
|
5385
|
+
const resolverBundles = await bundleResolvers(pipeline.namespace, pipeline.config, baseDir, startContext, bundleCache, inlineSourcemap, bundleLogLevel);
|
|
6429
5386
|
for (const [name, code] of resolverBundles) bundledScripts.resolvers.set(resolverBundleKey(pipeline.namespace, name), code);
|
|
6430
5387
|
}
|
|
6431
5388
|
if (executorService) bundledScripts.executors = await bundleExecutors({
|
|
6432
5389
|
config: executorService.config,
|
|
6433
|
-
|
|
5390
|
+
startContext,
|
|
6434
5391
|
additionalFiles: [...pluginExecutorFiles],
|
|
6435
5392
|
cache: bundleCache,
|
|
6436
5393
|
inlineSourcemap,
|
|
6437
|
-
bundleLogLevel
|
|
5394
|
+
bundleLogLevel,
|
|
5395
|
+
baseDir
|
|
6438
5396
|
});
|
|
6439
5397
|
let workflowBuildResult;
|
|
6440
5398
|
if (workflowService && workflowService.jobs.length > 0) {
|
|
6441
5399
|
const mainJobNames = workflowService.workflowSources.map((ws) => ws.workflow.mainJob.name);
|
|
6442
|
-
workflowBuildResult = await bundleWorkflowJobs(workflowService.jobs, mainJobNames, config.env ?? {},
|
|
5400
|
+
workflowBuildResult = await bundleWorkflowJobs(workflowService.jobs, mainJobNames, config.env ?? {}, startContext, baseDir, bundleCache, inlineSourcemap, bundleLogLevel);
|
|
6443
5401
|
bundledScripts.workflowJobs = workflowBuildResult.bundledCode;
|
|
6444
5402
|
}
|
|
6445
5403
|
let httpAdapterBuildResult;
|
|
@@ -6448,7 +5406,7 @@ async function loadApplication(params) {
|
|
|
6448
5406
|
sourceFile: a.sourceFile,
|
|
6449
5407
|
methods: a.methods,
|
|
6450
5408
|
hasOutput: a.hasOutput
|
|
6451
|
-
})), bundleCache, bundleLogLevel);
|
|
5409
|
+
})), baseDir, bundleCache, bundleLogLevel);
|
|
6452
5410
|
if (authResult.authService?.config.hooks?.beforeLogin) {
|
|
6453
5411
|
const authName = authResult.authService.config.name;
|
|
6454
5412
|
bundledScripts.authHooks = await bundleAuthHooks({
|
|
@@ -6456,10 +5414,11 @@ async function loadApplication(params) {
|
|
|
6456
5414
|
authName,
|
|
6457
5415
|
handlerAccessPath: `auth.hooks.beforeLogin.handler`,
|
|
6458
5416
|
env: config.env ?? {},
|
|
6459
|
-
|
|
5417
|
+
startContext,
|
|
6460
5418
|
cache: bundleCache,
|
|
6461
5419
|
inlineSourcemap,
|
|
6462
|
-
bundleLogLevel
|
|
5420
|
+
bundleLogLevel,
|
|
5421
|
+
baseDir
|
|
6463
5422
|
});
|
|
6464
5423
|
}
|
|
6465
5424
|
for (const pipeline of resolverResult.resolverServices) await pipeline.loadResolvers();
|
|
@@ -6493,5 +5452,5 @@ async function loadApplication(params) {
|
|
|
6493
5452
|
}
|
|
6494
5453
|
|
|
6495
5454
|
//#endregion
|
|
6496
|
-
export {
|
|
6497
|
-
//# sourceMappingURL=application-
|
|
5455
|
+
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 };
|
|
5456
|
+
//# sourceMappingURL=application-GzW98_Xr.mjs.map
|