@treeseed/sdk 0.6.2 → 0.6.4
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/dist/operations/services/config-runtime.js +19 -8
- package/dist/operations/services/github-api.d.ts +2 -1
- package/dist/operations/services/github-api.js +71 -2
- package/dist/operations/services/project-platform.js +38 -6
- package/dist/platform/env.yaml +3 -0
- package/dist/platform/environment.d.ts +4 -0
- package/dist/platform/environment.js +3 -0
- package/package.json +1 -1
|
@@ -1444,9 +1444,17 @@ function resolveTreeseedLaunchEnvironment({
|
|
|
1444
1444
|
overrides = {}
|
|
1445
1445
|
}) {
|
|
1446
1446
|
warnDeprecatedTreeseedLocalEnvFiles(tenantRoot);
|
|
1447
|
+
let machineValues = {};
|
|
1448
|
+
try {
|
|
1449
|
+
machineValues = resolveTreeseedMachineEnvironmentValues(tenantRoot, scope);
|
|
1450
|
+
} catch (error) {
|
|
1451
|
+
if (!(error instanceof TreeseedKeyAgentError)) {
|
|
1452
|
+
throw error;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
const scopedValues = scope === "local" ? { ...baseEnv, ...machineValues } : { ...machineValues, ...baseEnv };
|
|
1447
1456
|
return {
|
|
1448
|
-
...
|
|
1449
|
-
...resolveTreeseedMachineEnvironmentValues(tenantRoot, scope),
|
|
1457
|
+
...scopedValues,
|
|
1450
1458
|
...overrides
|
|
1451
1459
|
};
|
|
1452
1460
|
}
|
|
@@ -1467,7 +1475,7 @@ function formatTreeseedConfigEnvironmentReport({ tenantRoot, scope, env = proces
|
|
|
1467
1475
|
function applyTreeseedEnvironmentToProcess({ tenantRoot, scope, override = false }) {
|
|
1468
1476
|
let resolvedValues = {};
|
|
1469
1477
|
try {
|
|
1470
|
-
resolvedValues = resolveTreeseedLaunchEnvironment({ tenantRoot, scope
|
|
1478
|
+
resolvedValues = resolveTreeseedLaunchEnvironment({ tenantRoot, scope });
|
|
1471
1479
|
} catch (error) {
|
|
1472
1480
|
if (!(error instanceof TreeseedKeyAgentError)) {
|
|
1473
1481
|
throw error;
|
|
@@ -1893,10 +1901,14 @@ async function syncTreeseedGitHubEnvironment({
|
|
|
1893
1901
|
} : {};
|
|
1894
1902
|
const githubClient = createGitHubApiClient({ env: ghEnv });
|
|
1895
1903
|
const environment = scope === "prod" ? "production" : scope;
|
|
1904
|
+
const deploymentBranch = scope === "prod" ? PRODUCTION_BRANCH : scope === "staging" ? STAGING_BRANCH : null;
|
|
1896
1905
|
const progress = (message, stream = "stdout") => onProgress?.(message, stream);
|
|
1897
1906
|
if (!dryRun) {
|
|
1898
1907
|
progress(`[${scope}][github][environment] Ensuring GitHub environment ${environment} exists...`);
|
|
1899
|
-
await ensureGitHubActionsEnvironment(repository, environment, {
|
|
1908
|
+
await ensureGitHubActionsEnvironment(repository, environment, {
|
|
1909
|
+
client: githubClient,
|
|
1910
|
+
branchName: deploymentBranch
|
|
1911
|
+
});
|
|
1900
1912
|
}
|
|
1901
1913
|
progress(`[${scope}][github][sync] Loading existing GitHub secrets and variables...`);
|
|
1902
1914
|
const [secretNames, variableNames] = dryRun ? [/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()] : await Promise.all([
|
|
@@ -1913,10 +1925,9 @@ async function syncTreeseedGitHubEnvironment({
|
|
|
1913
1925
|
if (!value) {
|
|
1914
1926
|
continue;
|
|
1915
1927
|
}
|
|
1916
|
-
if (entry.
|
|
1928
|
+
if (entry.sensitivity === "secret") {
|
|
1917
1929
|
items.push({ kind: "secret", name: entry.id, value, existed: secretNames.has(entry.id) });
|
|
1918
|
-
}
|
|
1919
|
-
if (entry.targets.includes("github-variable")) {
|
|
1930
|
+
} else {
|
|
1920
1931
|
items.push({ kind: "variable", name: entry.id, value, existed: variableNames.has(entry.id) });
|
|
1921
1932
|
}
|
|
1922
1933
|
}
|
|
@@ -2245,7 +2256,7 @@ function configGroupRank(group) {
|
|
|
2245
2256
|
}
|
|
2246
2257
|
function listRelevantTreeseedConfigEntries(registry, scope) {
|
|
2247
2258
|
return registry.entries.filter(
|
|
2248
|
-
(entry) => entry.scopes.includes(scope) && (!entry.isRelevant || entry.isRelevant(registry.context, scope, "config") || Boolean(entry.onboardingFeature))
|
|
2259
|
+
(entry) => entry.visibility !== "system" && entry.scopes.includes(scope) && (!entry.isRelevant || entry.isRelevant(registry.context, scope, "config") || Boolean(entry.onboardingFeature))
|
|
2249
2260
|
).sort((left, right) => {
|
|
2250
2261
|
const leftRequired = isTreeseedEnvironmentEntryRequired(left, registry.context, scope, "config");
|
|
2251
2262
|
const rightRequired = isTreeseedEnvironmentEntryRequired(right, registry.context, scope, "config");
|
|
@@ -66,8 +66,9 @@ export declare function listGitHubRepositoryVariableNames(repository: string | {
|
|
|
66
66
|
export declare function ensureGitHubActionsEnvironment(repository: string | {
|
|
67
67
|
owner: string;
|
|
68
68
|
name: string;
|
|
69
|
-
}, environmentName: string, { client }?: {
|
|
69
|
+
}, environmentName: string, { client, branchName, }?: {
|
|
70
70
|
client?: GitHubApiClient;
|
|
71
|
+
branchName?: string | null;
|
|
71
72
|
}): Promise<{
|
|
72
73
|
repository: string;
|
|
73
74
|
environment: string;
|
|
@@ -220,19 +220,88 @@ async function listGitHubRepositoryVariableNames(repository, { client = createGi
|
|
|
220
220
|
throw normalizeGitHubApiError(error, `Unable to list GitHub variables for ${owner}/${name}`);
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
|
-
async function ensureGitHubActionsEnvironment(repository, environmentName, {
|
|
223
|
+
async function ensureGitHubActionsEnvironment(repository, environmentName, {
|
|
224
|
+
client = createGitHubApiClient(),
|
|
225
|
+
branchName
|
|
226
|
+
} = {}) {
|
|
224
227
|
const { owner, name: repo } = typeof repository === "string" ? parseGitHubRepositorySlug(repository) : repository;
|
|
225
228
|
try {
|
|
226
229
|
await withGitHubApiRetries(() => client.request("PUT /repos/{owner}/{repo}/environments/{environment_name}", {
|
|
227
230
|
owner,
|
|
228
231
|
repo,
|
|
229
|
-
environment_name: environmentName
|
|
232
|
+
environment_name: environmentName,
|
|
233
|
+
...branchName ? {
|
|
234
|
+
deployment_branch_policy: {
|
|
235
|
+
protected_branches: false,
|
|
236
|
+
custom_branch_policies: true
|
|
237
|
+
}
|
|
238
|
+
} : {}
|
|
230
239
|
}));
|
|
240
|
+
if (branchName) {
|
|
241
|
+
await ensureGitHubEnvironmentBranchPolicy(client, {
|
|
242
|
+
owner,
|
|
243
|
+
repo,
|
|
244
|
+
environmentName,
|
|
245
|
+
branchName
|
|
246
|
+
});
|
|
247
|
+
}
|
|
231
248
|
return { repository: `${owner}/${repo}`, environment: environmentName };
|
|
232
249
|
} catch (error) {
|
|
233
250
|
throw normalizeGitHubApiError(error, `Unable to ensure GitHub environment ${environmentName} for ${owner}/${repo}`);
|
|
234
251
|
}
|
|
235
252
|
}
|
|
253
|
+
async function ensureGitHubEnvironmentBranchPolicy(client, {
|
|
254
|
+
owner,
|
|
255
|
+
repo,
|
|
256
|
+
environmentName,
|
|
257
|
+
branchName
|
|
258
|
+
}) {
|
|
259
|
+
const response = await withGitHubApiRetries(() => client.request(
|
|
260
|
+
"GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies",
|
|
261
|
+
{
|
|
262
|
+
owner,
|
|
263
|
+
repo,
|
|
264
|
+
environment_name: environmentName,
|
|
265
|
+
per_page: 100
|
|
266
|
+
}
|
|
267
|
+
));
|
|
268
|
+
const policies = Array.isArray(response.data?.branch_policies) ? response.data.branch_policies : [];
|
|
269
|
+
const desired = policies.find((policy) => policy.name === branchName && (policy.type ?? "branch") === "branch");
|
|
270
|
+
for (const policy of policies) {
|
|
271
|
+
if (!policy.id || policy.name === branchName && (policy.type ?? "branch") === "branch") {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
await withGitHubApiRetries(() => client.request(
|
|
275
|
+
"DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}",
|
|
276
|
+
{
|
|
277
|
+
owner,
|
|
278
|
+
repo,
|
|
279
|
+
environment_name: environmentName,
|
|
280
|
+
branch_policy_id: policy.id
|
|
281
|
+
}
|
|
282
|
+
));
|
|
283
|
+
}
|
|
284
|
+
if (desired) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
await withGitHubApiRetries(() => client.request(
|
|
289
|
+
"POST /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies",
|
|
290
|
+
{
|
|
291
|
+
owner,
|
|
292
|
+
repo,
|
|
293
|
+
environment_name: environmentName,
|
|
294
|
+
name: branchName,
|
|
295
|
+
type: "branch"
|
|
296
|
+
}
|
|
297
|
+
));
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (error && typeof error === "object" && error.status === 303) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
236
305
|
async function paginateGitHubEnvironmentNames(client, route, params) {
|
|
237
306
|
const paginate = client.paginate;
|
|
238
307
|
return await paginateNames(() => paginate(route, {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
signEditorialPreviewToken
|
|
13
13
|
} from "../../platform/published-content.js";
|
|
14
14
|
import { createPublishedContentPipeline } from "../../platform/published-content-pipeline.js";
|
|
15
|
-
import { collectTreeseedReconcileStatus, reconcileTreeseedTarget } from "../../reconcile/index.js";
|
|
15
|
+
import { collectTreeseedReconcileStatus, reconcileTreeseedTarget, resolveTreeseedBootstrapSelection } from "../../reconcile/index.js";
|
|
16
16
|
import { loadTreeseedManifest } from "../../platform/tenant-config.js";
|
|
17
17
|
import { applyTreeseedEnvironmentToProcess } from "./config-runtime.js";
|
|
18
18
|
import {
|
|
@@ -40,6 +40,7 @@ import { loadCliDeployConfig, packageScriptPath, resolveWranglerBin } from "./ru
|
|
|
40
40
|
import { CloudflareQueuePullClient, CloudflareQueuePushClient } from "../../remote.js";
|
|
41
41
|
import { runPrefixedCommand, runTreeseedBootstrapDag, sleep, writeTreeseedBootstrapLine } from "./bootstrap-runner.js";
|
|
42
42
|
import { runTenantDeployPreflight } from "./save-deploy-preflight.js";
|
|
43
|
+
const PROJECT_PLATFORM_BOOTSTRAP_SYSTEMS = ["data", "web", "api", "agents"];
|
|
43
44
|
function stableHash(value) {
|
|
44
45
|
return createHash("sha256").update(value).digest("hex");
|
|
45
46
|
}
|
|
@@ -90,6 +91,30 @@ function runNodeScript(tenantRoot, scriptName, scriptArgs = []) {
|
|
|
90
91
|
throw new Error(`${scriptName} failed.`);
|
|
91
92
|
}
|
|
92
93
|
}
|
|
94
|
+
function resolveProjectPlatformBootstrapSystems(options, siteConfig = loadCliDeployConfig(options.tenantRoot)) {
|
|
95
|
+
if (options.bootstrapSystems && options.bootstrapSystems.length > 0) {
|
|
96
|
+
return [...options.bootstrapSystems];
|
|
97
|
+
}
|
|
98
|
+
const selection = resolveTreeseedBootstrapSelection({
|
|
99
|
+
deployConfig: siteConfig,
|
|
100
|
+
env: { ...process.env, ...options.env ?? {} },
|
|
101
|
+
systems: PROJECT_PLATFORM_BOOTSTRAP_SYSTEMS,
|
|
102
|
+
skipUnavailable: true
|
|
103
|
+
});
|
|
104
|
+
for (const skipped of selection.skipped) {
|
|
105
|
+
writeTreeseedBootstrapLine(
|
|
106
|
+
options.write,
|
|
107
|
+
{
|
|
108
|
+
scope: options.scope,
|
|
109
|
+
system: skipped.system,
|
|
110
|
+
task: "availability",
|
|
111
|
+
stage: "skip"
|
|
112
|
+
},
|
|
113
|
+
skipped.reason
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return selection.runnable.filter((system) => system !== "github");
|
|
117
|
+
}
|
|
93
118
|
function runWrangler(tenantRoot, args, extraEnv = {}, options = {}) {
|
|
94
119
|
const result = spawnSync(process.execPath, [resolveWranglerBin(), ...args], {
|
|
95
120
|
cwd: tenantRoot,
|
|
@@ -818,18 +843,24 @@ async function provisionProjectPlatform(options) {
|
|
|
818
843
|
const reporter = resolveReporter(options.tenantRoot, options.reporter);
|
|
819
844
|
const target = createPersistentDeployTarget(options.scope === "local" ? "staging" : options.scope);
|
|
820
845
|
const siteConfig = loadCliDeployConfig(options.tenantRoot);
|
|
846
|
+
const bootstrapSystems = resolveProjectPlatformBootstrapSystems(options, siteConfig);
|
|
847
|
+
const selectedSystems = new Set(bootstrapSystems);
|
|
848
|
+
const env = { ...process.env, ...options.env ?? {} };
|
|
821
849
|
const summary = await reconcileTreeseedTarget({
|
|
822
850
|
tenantRoot: options.tenantRoot,
|
|
823
851
|
target,
|
|
824
|
-
env
|
|
852
|
+
env,
|
|
853
|
+
systems: bootstrapSystems
|
|
825
854
|
});
|
|
826
855
|
const verification = await collectTreeseedReconcileStatus({
|
|
827
856
|
tenantRoot: options.tenantRoot,
|
|
828
857
|
target,
|
|
829
|
-
env
|
|
858
|
+
env,
|
|
859
|
+
systems: bootstrapSystems
|
|
830
860
|
});
|
|
831
861
|
ensureGeneratedWranglerConfig(options.tenantRoot, { target });
|
|
832
|
-
const
|
|
862
|
+
const shouldValidateRailway = selectedSystems.has("api") || selectedSystems.has("agents");
|
|
863
|
+
const railwayValidation = shouldValidateRailway ? options.scope === "local" ? validateRailwayServiceConfiguration(options.tenantRoot, options.scope) : validateRailwayDeployPrerequisites(options.tenantRoot, options.scope, { env }) : { services: [] };
|
|
833
864
|
const railwaySchedules = [];
|
|
834
865
|
const railwayScheduleVerification = {
|
|
835
866
|
ok: true,
|
|
@@ -969,7 +1000,8 @@ async function deployProjectPlatform(options) {
|
|
|
969
1000
|
const reporter = resolveReporter(options.tenantRoot, options.reporter);
|
|
970
1001
|
const commitSha = currentCommit(options.tenantRoot);
|
|
971
1002
|
const branchName = currentRef(options.tenantRoot);
|
|
972
|
-
const
|
|
1003
|
+
const bootstrapSystems = resolveProjectPlatformBootstrapSystems(options);
|
|
1004
|
+
const selectedSystems = new Set(bootstrapSystems);
|
|
973
1005
|
const execution = options.bootstrapExecution ?? "parallel";
|
|
974
1006
|
const write = options.write;
|
|
975
1007
|
const env = { ...process.env, ...options.env ?? {} };
|
|
@@ -983,7 +1015,7 @@ async function deployProjectPlatform(options) {
|
|
|
983
1015
|
metadata: { scope: options.scope }
|
|
984
1016
|
});
|
|
985
1017
|
if (!options.skipProvision) {
|
|
986
|
-
await provisionProjectPlatform({ ...options, reporter });
|
|
1018
|
+
await provisionProjectPlatform({ ...options, reporter, bootstrapSystems });
|
|
987
1019
|
}
|
|
988
1020
|
const nodes = [];
|
|
989
1021
|
let cloudflareContext = null;
|
package/dist/platform/env.yaml
CHANGED
|
@@ -748,6 +748,7 @@ entries:
|
|
|
748
748
|
TREESEED_RAILWAY_PROJECT_ID:
|
|
749
749
|
label: Railway project ID
|
|
750
750
|
group: hosting
|
|
751
|
+
visibility: system
|
|
751
752
|
description: Railway project identifier used by runtime scaling and reconciliation helpers for the active environment.
|
|
752
753
|
howToGet: Copy the project ID from Railway when automatic worker scaling is enabled.
|
|
753
754
|
sensitivity: plain
|
|
@@ -770,6 +771,7 @@ entries:
|
|
|
770
771
|
TREESEED_RAILWAY_ENVIRONMENT_ID:
|
|
771
772
|
label: Railway environment ID
|
|
772
773
|
group: hosting
|
|
774
|
+
visibility: system
|
|
773
775
|
description: Railway environment identifier used by the runtime scaler when adjusting worker replicas.
|
|
774
776
|
howToGet: Copy the environment ID from Railway for the matching staging or production environment.
|
|
775
777
|
sensitivity: plain
|
|
@@ -792,6 +794,7 @@ entries:
|
|
|
792
794
|
TREESEED_RAILWAY_WORKER_SERVICE_ID:
|
|
793
795
|
label: Railway worker service ID
|
|
794
796
|
group: hosting
|
|
797
|
+
visibility: system
|
|
795
798
|
description: Railway service identifier for the scalable worker pool that the manager adjusts at runtime.
|
|
796
799
|
howToGet: Copy the worker service ID from Railway after the hosted project environment is provisioned.
|
|
797
800
|
sensitivity: plain
|
|
@@ -7,6 +7,7 @@ export declare const TREESEED_ENVIRONMENT_PURPOSES: readonly ["dev", "save", "de
|
|
|
7
7
|
export declare const TREESEED_ENVIRONMENT_SENSITIVITY: readonly ["secret", "plain", "derived"];
|
|
8
8
|
export declare const TREESEED_ENVIRONMENT_STORAGE: readonly ["scoped", "shared"];
|
|
9
9
|
export declare const TREESEED_CONFIG_STARTUP_PROFILES: readonly ["core", "optional", "advanced"];
|
|
10
|
+
export declare const TREESEED_ENVIRONMENT_VISIBILITY: readonly ["user", "system"];
|
|
10
11
|
export type TreeseedEnvironmentScope = (typeof TREESEED_ENVIRONMENT_SCOPES)[number];
|
|
11
12
|
export type TreeseedEnvironmentRequirement = (typeof TREESEED_ENVIRONMENT_REQUIREMENTS)[number];
|
|
12
13
|
export type TreeseedEnvironmentTarget = (typeof TREESEED_ENVIRONMENT_TARGETS)[number];
|
|
@@ -14,6 +15,7 @@ export type TreeseedEnvironmentPurpose = (typeof TREESEED_ENVIRONMENT_PURPOSES)[
|
|
|
14
15
|
export type TreeseedEnvironmentSensitivity = (typeof TREESEED_ENVIRONMENT_SENSITIVITY)[number];
|
|
15
16
|
export type TreeseedEnvironmentStorage = (typeof TREESEED_ENVIRONMENT_STORAGE)[number];
|
|
16
17
|
export type TreeseedConfigStartupProfile = (typeof TREESEED_CONFIG_STARTUP_PROFILES)[number];
|
|
18
|
+
export type TreeseedEnvironmentVisibility = (typeof TREESEED_ENVIRONMENT_VISIBILITY)[number];
|
|
17
19
|
export type TreeseedEnvironmentValidation = {
|
|
18
20
|
kind: 'string' | 'nonempty' | 'url' | 'email';
|
|
19
21
|
minLength?: number;
|
|
@@ -68,6 +70,7 @@ export type TreeseedEnvironmentEntry = {
|
|
|
68
70
|
cluster?: string;
|
|
69
71
|
onboardingFeature?: string;
|
|
70
72
|
startupProfile?: TreeseedConfigStartupProfile;
|
|
73
|
+
visibility?: TreeseedEnvironmentVisibility;
|
|
71
74
|
description: string;
|
|
72
75
|
howToGet: string;
|
|
73
76
|
sensitivity: TreeseedEnvironmentSensitivity;
|
|
@@ -87,6 +90,7 @@ export type TreeseedEnvironmentEntryYaml = Omit<TreeseedEnvironmentEntry, 'id' |
|
|
|
87
90
|
cluster?: string;
|
|
88
91
|
onboardingFeature?: string;
|
|
89
92
|
startupProfile?: TreeseedConfigStartupProfile;
|
|
93
|
+
visibility?: TreeseedEnvironmentVisibility;
|
|
90
94
|
defaultValueRef?: string;
|
|
91
95
|
localDefaultValueRef?: string;
|
|
92
96
|
relevanceRef?: string;
|
|
@@ -24,6 +24,7 @@ const TREESEED_ENVIRONMENT_PURPOSES = ["dev", "save", "deploy", "destroy", "conf
|
|
|
24
24
|
const TREESEED_ENVIRONMENT_SENSITIVITY = ["secret", "plain", "derived"];
|
|
25
25
|
const TREESEED_ENVIRONMENT_STORAGE = ["scoped", "shared"];
|
|
26
26
|
const TREESEED_CONFIG_STARTUP_PROFILES = ["core", "optional", "advanced"];
|
|
27
|
+
const TREESEED_ENVIRONMENT_VISIBILITY = ["user", "system"];
|
|
27
28
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
28
29
|
function resolveCoreEnvironmentPath() {
|
|
29
30
|
const candidates = [
|
|
@@ -367,6 +368,7 @@ function materializeEntry(id, entry) {
|
|
|
367
368
|
id,
|
|
368
369
|
cluster: entry.cluster ?? `${entry.group}:${id}`,
|
|
369
370
|
onboardingFeature: entry.onboardingFeature,
|
|
371
|
+
visibility: entry.visibility ?? "user",
|
|
370
372
|
startupProfile: entry.startupProfile ?? (entry.onboardingFeature ? "optional" : entry.group === "auth" || entry.id === "TREESEED_FORM_TOKEN_SECRET" || entry.group === "local-development" ? "core" : "advanced"),
|
|
371
373
|
storage: entry.storage ?? "scoped",
|
|
372
374
|
defaultValue: resolveNamedValueResolver(entry.defaultValueRef),
|
|
@@ -578,6 +580,7 @@ export {
|
|
|
578
580
|
TREESEED_ENVIRONMENT_SENSITIVITY,
|
|
579
581
|
TREESEED_ENVIRONMENT_STORAGE,
|
|
580
582
|
TREESEED_ENVIRONMENT_TARGETS,
|
|
583
|
+
TREESEED_ENVIRONMENT_VISIBILITY,
|
|
581
584
|
getTreeseedEnvironmentSuggestedValues,
|
|
582
585
|
isTreeseedEnvironmentEntryRelevant,
|
|
583
586
|
isTreeseedEnvironmentEntryRequired,
|