@treeseed/sdk 0.10.24 → 0.10.25
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/index.d.ts +12 -2
- package/dist/index.js +42 -1
- package/dist/market-client.d.ts +23 -0
- package/dist/market-client.js +30 -0
- package/dist/operations/providers/default.js +103 -10
- package/dist/operations/repository-operations.d.ts +6 -1
- package/dist/operations/repository-operations.js +44 -0
- package/dist/operations/services/config-runtime.d.ts +24 -9
- package/dist/operations/services/config-runtime.js +60 -12
- package/dist/operations/services/deploy.js +6 -1
- package/dist/operations/services/hub-launch.js +1 -0
- package/dist/operations/services/hub-provider-launch.d.ts +11 -1
- package/dist/operations/services/hub-provider-launch.js +81 -8
- package/dist/operations/services/project-host-operations.d.ts +153 -0
- package/dist/operations/services/project-host-operations.js +365 -0
- package/dist/operations/services/project-platform.d.ts +198 -193
- package/dist/operations/services/project-platform.js +29 -14
- package/dist/operations/services/railway-deploy.d.ts +3 -0
- package/dist/operations/services/railway-deploy.js +74 -35
- package/dist/operations/services/release-candidate.js +8 -2
- package/dist/operations/services/template-host-bindings.d.ts +68 -0
- package/dist/operations/services/template-host-bindings.js +400 -0
- package/dist/operations/services/template-registry.d.ts +22 -2
- package/dist/operations/services/template-registry.js +60 -3
- package/dist/operations/services/template-secret-sync.d.ts +97 -0
- package/dist/operations/services/template-secret-sync.js +292 -0
- package/dist/platform/environment.d.ts +3 -0
- package/dist/project-workflow.d.ts +7 -1
- package/dist/scripts/scaffold-site.js +3 -2
- package/dist/scripts/test-scaffold.js +2 -1
- package/dist/sdk-types.d.ts +87 -0
- package/dist/sdk-types.js +29 -0
- package/dist/template-catalog.js +3 -1
- package/dist/template-launch-requirements.d.ts +118 -0
- package/dist/template-launch-requirements.js +759 -0
- package/dist/template-launch-ui.d.ts +85 -0
- package/dist/template-launch-ui.js +189 -0
- package/dist/treeseed/template-catalog/catalog.fixture.json +330 -3
- package/package.json +13 -1
|
@@ -2123,6 +2123,7 @@ async function syncTreeseedGitHubEnvironment({
|
|
|
2123
2123
|
dryRun = false,
|
|
2124
2124
|
repository: repositoryInput,
|
|
2125
2125
|
valuesOverlay = {},
|
|
2126
|
+
entryIds,
|
|
2126
2127
|
managedHostMode = "auto",
|
|
2127
2128
|
execution = "parallel",
|
|
2128
2129
|
concurrency = 4,
|
|
@@ -2145,8 +2146,10 @@ async function syncTreeseedGitHubEnvironment({
|
|
|
2145
2146
|
}) : null;
|
|
2146
2147
|
const allowedSecrets = allowed ? new Set(allowed.secrets) : null;
|
|
2147
2148
|
const allowedVariables = allowed ? new Set(allowed.variables) : null;
|
|
2149
|
+
const entryFilter = Array.isArray(entryIds) && entryIds.length > 0 ? new Set(entryIds) : null;
|
|
2148
2150
|
const relevant = registry.entries.filter((entry) => {
|
|
2149
2151
|
if (!entry.scopes.includes(scope)) return false;
|
|
2152
|
+
if (entryFilter && !entryFilter.has(entry.id)) return false;
|
|
2150
2153
|
if (!managedBoundary) return true;
|
|
2151
2154
|
if (entry.sensitivity === "secret") {
|
|
2152
2155
|
return Boolean(entry.targets.includes("github-secret") && allowedSecrets?.has(entry.id));
|
|
@@ -2218,25 +2221,48 @@ async function syncTreeseedGitHubEnvironment({
|
|
|
2218
2221
|
repository,
|
|
2219
2222
|
scope,
|
|
2220
2223
|
environment,
|
|
2224
|
+
entryIds: entryFilter ? [...entryFilter] : void 0,
|
|
2221
2225
|
...synced
|
|
2222
2226
|
};
|
|
2223
2227
|
}
|
|
2224
|
-
function syncTreeseedCloudflareEnvironment({
|
|
2225
|
-
|
|
2228
|
+
function syncTreeseedCloudflareEnvironment({
|
|
2229
|
+
tenantRoot,
|
|
2230
|
+
scope = "prod",
|
|
2231
|
+
dryRun = false,
|
|
2232
|
+
valuesOverlay = {},
|
|
2233
|
+
entryIds,
|
|
2234
|
+
onProgress
|
|
2235
|
+
}) {
|
|
2236
|
+
const values = {
|
|
2237
|
+
...resolveTreeseedMachineEnvironmentValues(tenantRoot, scope),
|
|
2238
|
+
...nonEmptyEnvironmentValues(valuesOverlay)
|
|
2239
|
+
};
|
|
2226
2240
|
const target = createPersistentDeployTarget(scope);
|
|
2241
|
+
const progress = (message, stream = "stdout") => onProgress?.(message, stream);
|
|
2227
2242
|
for (const [key, value] of Object.entries(values)) {
|
|
2228
2243
|
if (typeof value === "string" && value.length > 0) {
|
|
2229
2244
|
process.env[key] = value;
|
|
2230
2245
|
}
|
|
2231
2246
|
}
|
|
2247
|
+
progress(`[${scope}][cloudflare][config] Generating Wrangler config...`);
|
|
2232
2248
|
const { wranglerPath } = ensureGeneratedWranglerConfig(tenantRoot, { target });
|
|
2233
|
-
const syncedSecrets = syncCloudflareSecrets(tenantRoot, { dryRun, target });
|
|
2234
2249
|
const registry = collectTreeseedEnvironmentContext(tenantRoot);
|
|
2235
|
-
const
|
|
2250
|
+
const entryFilter = Array.isArray(entryIds) && entryIds.length > 0 ? new Set(entryIds) : null;
|
|
2251
|
+
const cloudflareSecrets = Object.fromEntries(registry.entries.filter((entry) => entry.scopes.includes(scope) && entry.targets.includes("cloudflare-secret") && (!entryFilter || entryFilter.has(entry.id))).map((entry) => [entry.id, values[entry.id]]).filter(([, value]) => typeof value === "string" && value.length > 0));
|
|
2252
|
+
progress(`[${scope}][cloudflare][sync] Syncing Cloudflare secrets...`);
|
|
2253
|
+
const syncedSecrets = syncCloudflareSecrets(tenantRoot, {
|
|
2254
|
+
dryRun,
|
|
2255
|
+
target,
|
|
2256
|
+
extraSecrets: cloudflareSecrets,
|
|
2257
|
+
entryIds
|
|
2258
|
+
});
|
|
2259
|
+
const cloudflareVars = registry.entries.filter((entry) => entry.scopes.includes(scope) && entry.targets.includes("cloudflare-var") && (!entryFilter || entryFilter.has(entry.id))).map((entry) => entry.id).filter((key) => typeof values[key] === "string" && values[key].length > 0);
|
|
2260
|
+
progress(`[${scope}][cloudflare][sync] Complete: ${syncedSecrets.length} secrets, ${cloudflareVars.length} vars.`);
|
|
2236
2261
|
return {
|
|
2237
2262
|
scope,
|
|
2238
2263
|
target,
|
|
2239
2264
|
wranglerPath,
|
|
2265
|
+
entryIds: entryFilter ? [...entryFilter] : void 0,
|
|
2240
2266
|
secrets: syncedSecrets,
|
|
2241
2267
|
varsManagedByWranglerConfig: cloudflareVars
|
|
2242
2268
|
};
|
|
@@ -2245,18 +2271,30 @@ function environmentEntryTargetsService(entry, serviceKey) {
|
|
|
2245
2271
|
const targets = Array.isArray(entry.serviceTargets) ? entry.serviceTargets.map((value) => String(value).trim()).filter(Boolean) : [];
|
|
2246
2272
|
return targets.length === 0 || targets.includes(serviceKey);
|
|
2247
2273
|
}
|
|
2248
|
-
function railwayEnvironmentEntryIdsForService(registry, values, scope, target, serviceKey) {
|
|
2249
|
-
return registry.entries.filter((entry) => entry.scopes.includes(scope) && entry.targets.includes(target) && environmentEntryTargetsService(entry, serviceKey)).map((entry) => entry.id).filter((key) => typeof values[key] === "string" && values[key].length > 0);
|
|
2274
|
+
function railwayEnvironmentEntryIdsForService(registry, values, scope, target, serviceKey, entryFilter = null) {
|
|
2275
|
+
return registry.entries.filter((entry) => entry.scopes.includes(scope) && entry.targets.includes(target) && (!entryFilter || entryFilter.has(entry.id)) && environmentEntryTargetsService(entry, serviceKey)).map((entry) => entry.id).filter((key) => typeof values[key] === "string" && values[key].length > 0);
|
|
2250
2276
|
}
|
|
2251
|
-
function syncTreeseedRailwayEnvironment({
|
|
2277
|
+
function syncTreeseedRailwayEnvironment({
|
|
2278
|
+
tenantRoot,
|
|
2279
|
+
scope = "prod",
|
|
2280
|
+
dryRun = false,
|
|
2281
|
+
valuesOverlay = {},
|
|
2282
|
+
entryIds,
|
|
2283
|
+
onProgress
|
|
2284
|
+
}) {
|
|
2252
2285
|
const config = syncManagedServiceSettingsFromDeployConfig(tenantRoot);
|
|
2253
|
-
const values =
|
|
2286
|
+
const values = {
|
|
2287
|
+
...resolveTreeseedMachineEnvironmentValues(tenantRoot, scope),
|
|
2288
|
+
...nonEmptyEnvironmentValues(valuesOverlay)
|
|
2289
|
+
};
|
|
2254
2290
|
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
2255
2291
|
const marketDatabaseService = deployConfig.services?.marketDatabase;
|
|
2256
2292
|
const marketDatabaseBaseName = typeof marketDatabaseService?.railway?.serviceName === "string" && marketDatabaseService.railway.serviceName.trim() ? marketDatabaseService.railway.serviceName.trim() : `${deployConfig.slug ?? "treeseed-market"}-postgres`;
|
|
2257
2293
|
const marketDatabaseServiceName = `${marketDatabaseBaseName.replace(/-(staging|prod|production)$/u, "")}-${scope === "prod" ? "prod" : scope}`;
|
|
2258
2294
|
const marketDatabaseUrl = typeof values.TREESEED_MARKET_DATABASE_URL === "string" && values.TREESEED_MARKET_DATABASE_URL.length > 0 ? values.TREESEED_MARKET_DATABASE_URL : marketDatabaseService?.enabled !== false && marketDatabaseService?.provider === "railway" ? `\${{${marketDatabaseServiceName}.DATABASE_URL}}` : "";
|
|
2259
2295
|
const registry = collectTreeseedEnvironmentContext(tenantRoot);
|
|
2296
|
+
const entryFilter = Array.isArray(entryIds) && entryIds.length > 0 ? new Set(entryIds) : null;
|
|
2297
|
+
const progress = (message, stream = "stdout") => onProgress?.(message, stream);
|
|
2260
2298
|
const serviceValuesByName = /* @__PURE__ */ new Map();
|
|
2261
2299
|
const services = configuredRailwayServices(tenantRoot, scope).map((service) => {
|
|
2262
2300
|
const fallbackServiceName = service.key === "api" ? config.settings.services.railway.apiServiceName : service.serviceName;
|
|
@@ -2281,13 +2319,14 @@ function syncTreeseedRailwayEnvironment({ tenantRoot, scope = "prod", dryRun = f
|
|
|
2281
2319
|
rootDir: service.rootDir,
|
|
2282
2320
|
baseUrl: service.publicBaseUrl ?? "(unset)",
|
|
2283
2321
|
environmentName,
|
|
2284
|
-
secrets: railwayEnvironmentEntryIdsForService(registry, serviceValues, scope, "railway-secret", service.key),
|
|
2285
|
-
variables: railwayEnvironmentEntryIdsForService(registry, serviceValues, scope, "railway-var", service.key),
|
|
2322
|
+
secrets: railwayEnvironmentEntryIdsForService(registry, serviceValues, scope, "railway-secret", service.key, entryFilter),
|
|
2323
|
+
variables: railwayEnvironmentEntryIdsForService(registry, serviceValues, scope, "railway-var", service.key, entryFilter),
|
|
2286
2324
|
dryRun
|
|
2287
2325
|
};
|
|
2288
2326
|
}).filter(Boolean);
|
|
2289
2327
|
for (const service of services) {
|
|
2290
2328
|
const serviceValues = serviceValuesByName.get(service.serviceName || service.serviceId || service.instanceKey || service.service) ?? values;
|
|
2329
|
+
progress(`[${scope}][railway][${service.service}] Syncing ${service.secrets.length} secrets and ${service.variables.length} variables...`);
|
|
2291
2330
|
for (const key of service.secrets) {
|
|
2292
2331
|
runRailway(
|
|
2293
2332
|
["variable", "set", "--service", service.serviceName || service.serviceId, "--environment", service.environmentName, "--stdin", "--skip-deploys", key],
|
|
@@ -2300,9 +2339,11 @@ function syncTreeseedRailwayEnvironment({ tenantRoot, scope = "prod", dryRun = f
|
|
|
2300
2339
|
{ cwd: service.rootDir, dryRun, input: serviceValues[key] }
|
|
2301
2340
|
);
|
|
2302
2341
|
}
|
|
2342
|
+
progress(`[${scope}][railway][${service.service}] Complete.`);
|
|
2303
2343
|
}
|
|
2304
2344
|
return {
|
|
2305
2345
|
scope,
|
|
2346
|
+
entryIds: entryFilter ? [...entryFilter] : void 0,
|
|
2306
2347
|
services
|
|
2307
2348
|
};
|
|
2308
2349
|
}
|
|
@@ -2477,7 +2518,8 @@ function formatTreeseedConfigValidationFailure(validations, scopes) {
|
|
|
2477
2518
|
lines.push(`${scope}:`);
|
|
2478
2519
|
for (const problem of [...validation.missing, ...validation.invalid]) {
|
|
2479
2520
|
const targets = problem.entry.targets.length > 0 ? ` Targets: ${problem.entry.targets.join(", ")}.` : "";
|
|
2480
|
-
|
|
2521
|
+
const source = problem.entry.sourceRequirement ? ` Source: ${problem.entry.sourceRequirement}${problem.entry.sourceProvider ? ` (${problem.entry.sourceProvider})` : ""}.` : "";
|
|
2522
|
+
lines.push(`- ${problem.id}: ${problem.message}${targets}${source}`);
|
|
2481
2523
|
}
|
|
2482
2524
|
}
|
|
2483
2525
|
return lines.join("\n");
|
|
@@ -2596,6 +2638,9 @@ function buildConfigEntrySnapshot(scope, entry, currentValue, suggestedValue) {
|
|
|
2596
2638
|
purposes: [...entry.purposes],
|
|
2597
2639
|
storage: entry.storage ?? "scoped",
|
|
2598
2640
|
validation: entry.validation,
|
|
2641
|
+
sourceRequirement: entry.sourceRequirement,
|
|
2642
|
+
sourceHostType: entry.sourceHostType ?? null,
|
|
2643
|
+
sourceProvider: entry.sourceProvider ?? null,
|
|
2599
2644
|
scope,
|
|
2600
2645
|
sharedScopes: entry.storage === "shared" ? [...entry.scopes] : [scope],
|
|
2601
2646
|
required: false,
|
|
@@ -3046,7 +3091,10 @@ function collectTreeseedPrintEnvReport({
|
|
|
3046
3091
|
sensitivity: entry.sensitivity,
|
|
3047
3092
|
value: rawValue,
|
|
3048
3093
|
displayValue: rawValue ? entry.sensitivity === "secret" && !revealSecrets ? maskValue(rawValue) : rawValue : "(unset)",
|
|
3049
|
-
source: sources[entry.id] ?? "unset"
|
|
3094
|
+
source: sources[entry.id] ?? "unset",
|
|
3095
|
+
sourceRequirement: entry.sourceRequirement,
|
|
3096
|
+
sourceHostType: entry.sourceHostType ?? null,
|
|
3097
|
+
sourceProvider: entry.sourceProvider ?? null
|
|
3050
3098
|
};
|
|
3051
3099
|
})
|
|
3052
3100
|
};
|
|
@@ -2916,7 +2916,12 @@ function syncCloudflareSecrets(tenantRoot, options = {}) {
|
|
|
2916
2916
|
const env = {
|
|
2917
2917
|
CLOUDFLARE_ACCOUNT_ID: resolveConfiguredCloudflareAccountId(deployConfig)
|
|
2918
2918
|
};
|
|
2919
|
-
const
|
|
2919
|
+
const entryFilter = Array.isArray(options.entryIds) && options.entryIds.length > 0 ? new Set(options.entryIds) : null;
|
|
2920
|
+
const extraSecrets = options.extraSecrets && typeof options.extraSecrets === "object" ? Object.fromEntries(Object.entries(options.extraSecrets).filter(([key, value]) => (!entryFilter || entryFilter.has(key)) && typeof value === "string" && value.length > 0)) : {};
|
|
2921
|
+
const secrets = {
|
|
2922
|
+
...buildSecretMap(deployConfig, state),
|
|
2923
|
+
...extraSecrets
|
|
2924
|
+
};
|
|
2920
2925
|
const synced = [];
|
|
2921
2926
|
const dryRun = options.dryRun ?? false;
|
|
2922
2927
|
for (const [key, value] of Object.entries(secrets)) {
|
|
@@ -223,6 +223,7 @@ function phaseFromProviderLaunch(entry) {
|
|
|
223
223
|
repo_provision: "repository_create",
|
|
224
224
|
content_repository: "content_repository_create",
|
|
225
225
|
content_bootstrap: "starting_shape_apply",
|
|
226
|
+
host_binding_config: "host_binding_config",
|
|
226
227
|
workflow_bootstrap: "config_sync",
|
|
227
228
|
hosting_registration: "cloudflare_reconcile",
|
|
228
229
|
runtime_connection: "backend_processing_connect"
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { checkTreeseedProviderConnections, syncTreeseedGitHubEnvironment } from './config-runtime.ts';
|
|
2
2
|
import { configuredRailwayServices, deployRailwayService, ensureRailwayScheduledJobs, verifyRailwayScheduledJobs } from './railway-deploy.ts';
|
|
3
|
+
import { type ProjectLaunchSecretSyncResult } from './template-secret-sync.ts';
|
|
3
4
|
import { buildKnowledgePackMarketPackage, buildTemplateMarketPackage } from './market-packaging.ts';
|
|
4
|
-
|
|
5
|
+
import type { ProjectLaunchConfigWritePlanItem, ProjectLaunchResolvedHostBinding, ProjectLaunchSecretDeploymentPlanItem } from '../../template-launch-requirements.ts';
|
|
6
|
+
export type KnowledgeHubProviderLaunchFailurePhase = 'repo_provision_failed' | 'content_bootstrap_failed' | 'workflow_bootstrap_failed' | 'hosting_registration_failed' | 'host_binding_secret_sync_failed' | 'runtime_connection_failed';
|
|
5
7
|
export interface KnowledgeHubProviderLaunchInput {
|
|
6
8
|
projectId: string;
|
|
7
9
|
teamId: string;
|
|
@@ -47,6 +49,13 @@ export interface KnowledgeHubProviderLaunchInput {
|
|
|
47
49
|
manageDns?: boolean;
|
|
48
50
|
provider?: string | null;
|
|
49
51
|
} | null;
|
|
52
|
+
hostBindings?: Record<string, ProjectLaunchResolvedHostBinding>;
|
|
53
|
+
hostBindingPlans?: {
|
|
54
|
+
configWrites?: ProjectLaunchConfigWritePlanItem[];
|
|
55
|
+
secretDeployment?: {
|
|
56
|
+
items?: ProjectLaunchSecretDeploymentPlanItem[];
|
|
57
|
+
};
|
|
58
|
+
};
|
|
50
59
|
}
|
|
51
60
|
export interface KnowledgeHubCloudflareHostConfig {
|
|
52
61
|
CLOUDFLARE_API_TOKEN?: string;
|
|
@@ -110,6 +119,7 @@ export interface KnowledgeHubProviderLaunchResult {
|
|
|
110
119
|
created: string[];
|
|
111
120
|
};
|
|
112
121
|
environmentSync?: Array<Awaited<ReturnType<typeof syncTreeseedGitHubEnvironment>>>;
|
|
122
|
+
hostBindingSecretSync?: ProjectLaunchSecretSyncResult | null;
|
|
113
123
|
};
|
|
114
124
|
cloudflare: {
|
|
115
125
|
staging: ReturnType<typeof provisionCloudflareResources>;
|
|
@@ -17,8 +17,14 @@ import { configuredRailwayServices, deployRailwayService, ensureRailwayScheduled
|
|
|
17
17
|
import { loadCliDeployConfig } from "./runtime-tools.js";
|
|
18
18
|
import { templateCatalogRoot } from "./runtime-paths.js";
|
|
19
19
|
import { scaffoldTemplateProject } from "./template-registry.js";
|
|
20
|
+
import { applyProjectLaunchHostBindingConfig } from "./template-host-bindings.js";
|
|
21
|
+
import {
|
|
22
|
+
ProjectLaunchSecretSyncError,
|
|
23
|
+
syncProjectLaunchHostBindingSecrets
|
|
24
|
+
} from "./template-secret-sync.js";
|
|
20
25
|
import { buildKnowledgePackMarketPackage, buildTemplateMarketPackage, importKnowledgePack } from "./market-packaging.js";
|
|
21
26
|
import { resolveTreeseedToolBinary } from "../../managed-dependencies.js";
|
|
27
|
+
import { TREESEED_DEFAULT_STARTER_TEMPLATE_ID } from "../../sdk-types.js";
|
|
22
28
|
class KnowledgeHubProviderLaunchError extends Error {
|
|
23
29
|
phase;
|
|
24
30
|
phases;
|
|
@@ -609,10 +615,10 @@ function buildCloudflareHostEnvironmentOverlay(input, scope) {
|
|
|
609
615
|
}
|
|
610
616
|
function scaffoldLaunchSource(projectRoot, input) {
|
|
611
617
|
const repositoryName = slugify(input.repoName ?? input.projectSlug, "project");
|
|
612
|
-
const templateId = input.sourceKind === "template" ? slugify(input.sourceRef ??
|
|
618
|
+
const templateId = input.sourceKind === "template" ? slugify(input.sourceRef ?? TREESEED_DEFAULT_STARTER_TEMPLATE_ID, TREESEED_DEFAULT_STARTER_TEMPLATE_ID) : TREESEED_DEFAULT_STARTER_TEMPLATE_ID;
|
|
613
619
|
const templateCatalogEnv = { TREESEED_TEMPLATE_CATALOG_URL: currentTemplateCatalogUrl() };
|
|
614
620
|
if (input.sourceKind === "knowledge_pack") {
|
|
615
|
-
return scaffoldTemplateProject(
|
|
621
|
+
return scaffoldTemplateProject(TREESEED_DEFAULT_STARTER_TEMPLATE_ID, projectRoot, {
|
|
616
622
|
target: input.projectSlug,
|
|
617
623
|
name: input.projectName,
|
|
618
624
|
slug: input.projectSlug,
|
|
@@ -769,6 +775,26 @@ async function executeKnowledgeHubProviderLaunch(input, options = {}) {
|
|
|
769
775
|
await scaffoldLaunchSource(workingRoot, input);
|
|
770
776
|
ensureHostedProjectFiles(workingRoot);
|
|
771
777
|
const managedDefaults = applyManagedProjectDefaults(workingRoot, input);
|
|
778
|
+
const hostBindingConfig = applyProjectLaunchHostBindingConfig({
|
|
779
|
+
projectRoot: workingRoot,
|
|
780
|
+
hostBindings: input.hostBindings,
|
|
781
|
+
hostBindingPlans: input.hostBindingPlans,
|
|
782
|
+
launchInput: input,
|
|
783
|
+
derived: {
|
|
784
|
+
projectSlug: slugify(input.projectSlug, "project"),
|
|
785
|
+
projectName: input.projectName,
|
|
786
|
+
repositoryName: repoName
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
if (hostBindingConfig.configWrites.length > 0 || hostBindingConfig.environmentWrites.length > 0) {
|
|
790
|
+
await appendPhase(
|
|
791
|
+
phases,
|
|
792
|
+
"host_binding_config",
|
|
793
|
+
"completed",
|
|
794
|
+
`Applied ${hostBindingConfig.configWrites.length} host config write${hostBindingConfig.configWrites.length === 1 ? "" : "s"} and ${hostBindingConfig.environmentWrites.length} environment overlay entr${hostBindingConfig.environmentWrites.length === 1 ? "y" : "ies"}.`,
|
|
795
|
+
reportPhase
|
|
796
|
+
);
|
|
797
|
+
}
|
|
772
798
|
const seed = seedLaunchContent(workingRoot, input);
|
|
773
799
|
packageSourceRoot = mkdtempSync(join(tmpdir(), `market-package-${slugify(input.projectSlug, "project")}-`));
|
|
774
800
|
cpSync(workingRoot, packageSourceRoot, { recursive: true });
|
|
@@ -823,7 +849,11 @@ async function executeKnowledgeHubProviderLaunch(input, options = {}) {
|
|
|
823
849
|
const workflows = await ensureGitHubDeployAutomation(workingRoot, { valuesOverlay: prodEnvOverlay });
|
|
824
850
|
commitAndPushLaunchRepository(workingRoot, `Configure ${input.projectName} deployment`, { forcePush: !input.existingRepository?.url });
|
|
825
851
|
pushDefaultWorkstreamBranch(workingRoot);
|
|
826
|
-
let workflowSummary = {
|
|
852
|
+
let workflowSummary = {
|
|
853
|
+
...workflows,
|
|
854
|
+
environmentSync: [],
|
|
855
|
+
hostBindingSecretSync: null
|
|
856
|
+
};
|
|
827
857
|
await appendPhase(phases, "workflow_bootstrap", "completed", "Configured GitHub workflows.", reportPhase);
|
|
828
858
|
await appendPhase(phases, "hosting_registration", "running", "Provisioning Cloudflare resources and deploy state.", reportPhase);
|
|
829
859
|
const staging = await reconcileTreeseedTarget({
|
|
@@ -844,11 +874,51 @@ async function executeKnowledgeHubProviderLaunch(input, options = {}) {
|
|
|
844
874
|
...typeof widget.secret === "string" && widget.secret.length > 0 ? { TREESEED_TURNSTILE_SECRET_KEY: widget.secret } : {}
|
|
845
875
|
};
|
|
846
876
|
};
|
|
847
|
-
const
|
|
848
|
-
for (const [scope, valuesOverlay] of [
|
|
877
|
+
const scopedEnvironmentOverlays = [
|
|
849
878
|
["staging", turnstileOverlay(staging.state, stagingEnvOverlay)],
|
|
850
879
|
["prod", turnstileOverlay(prod.state, prodEnvOverlay)]
|
|
851
|
-
]
|
|
880
|
+
];
|
|
881
|
+
const hostBindingSecretPlanItems = input.hostBindingPlans?.secretDeployment?.items ?? [];
|
|
882
|
+
if (hostBindingSecretPlanItems.length > 0) {
|
|
883
|
+
await appendPhase(
|
|
884
|
+
phases,
|
|
885
|
+
"host_binding_secret_sync",
|
|
886
|
+
"running",
|
|
887
|
+
`Syncing ${hostBindingSecretPlanItems.length} host-bound environment entr${hostBindingSecretPlanItems.length === 1 ? "y" : "ies"}.`,
|
|
888
|
+
reportPhase
|
|
889
|
+
);
|
|
890
|
+
try {
|
|
891
|
+
const hostBindingSecretSync = await syncProjectLaunchHostBindingSecrets({
|
|
892
|
+
projectRoot: workingRoot,
|
|
893
|
+
repository: repository.slug,
|
|
894
|
+
hostBindings: input.hostBindings,
|
|
895
|
+
secretDeploymentPlan: input.hostBindingPlans?.secretDeployment,
|
|
896
|
+
valuesByScope: Object.fromEntries(scopedEnvironmentOverlays),
|
|
897
|
+
onProgress: async (event) => {
|
|
898
|
+
await appendPhase(
|
|
899
|
+
phases,
|
|
900
|
+
`host_binding_secret_sync_${event.provider}_${event.scope}`,
|
|
901
|
+
event.status === "running" ? "running" : event.status,
|
|
902
|
+
event.message,
|
|
903
|
+
reportPhase
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
workflowSummary = { ...workflowSummary, hostBindingSecretSync };
|
|
908
|
+
await appendPhase(phases, "host_binding_secret_sync", "completed", "Synced host-bound environment entries.", reportPhase);
|
|
909
|
+
} catch (error) {
|
|
910
|
+
const result = error instanceof ProjectLaunchSecretSyncError ? error.result : null;
|
|
911
|
+
workflowSummary = { ...workflowSummary, hostBindingSecretSync: result };
|
|
912
|
+
await appendPhase(phases, "host_binding_secret_sync", "failed", error instanceof Error ? error.message : String(error), reportPhase);
|
|
913
|
+
throw new KnowledgeHubProviderLaunchError(
|
|
914
|
+
"host_binding_secret_sync_failed",
|
|
915
|
+
error instanceof Error ? error.message : String(error),
|
|
916
|
+
phases
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
const githubEnvironmentSync = [];
|
|
921
|
+
for (const [scope, valuesOverlay] of scopedEnvironmentOverlays) {
|
|
852
922
|
githubEnvironmentSync.push(await syncTreeseedGitHubEnvironment({
|
|
853
923
|
tenantRoot: workingRoot,
|
|
854
924
|
scope,
|
|
@@ -960,8 +1030,11 @@ async function executeKnowledgeHubProviderLaunch(input, options = {}) {
|
|
|
960
1030
|
};
|
|
961
1031
|
} catch (error) {
|
|
962
1032
|
const message = error instanceof Error ? error.message : String(error);
|
|
963
|
-
const phase = error instanceof KnowledgeHubProviderLaunchError ? error.phase : phases.some((entry) => entry.phase === "runtime_connection" && entry.status === "running") ? "runtime_connection_failed" : phases.some((entry) => entry.phase === "hosting_registration" && entry.status === "running") ? "hosting_registration_failed" : phases.some((entry) => entry.phase === "workflow_bootstrap" && entry.status === "running") ? "workflow_bootstrap_failed" : phases.some((entry) => entry.phase === "content_bootstrap" && entry.status === "running") ? "content_bootstrap_failed" : "repo_provision_failed";
|
|
964
|
-
|
|
1033
|
+
const phase = error instanceof KnowledgeHubProviderLaunchError ? error.phase : phases.some((entry) => entry.phase === "runtime_connection" && entry.status === "running") ? "runtime_connection_failed" : phases.some((entry) => entry.phase === "host_binding_secret_sync" && entry.status === "running") ? "host_binding_secret_sync_failed" : phases.some((entry) => entry.phase === "hosting_registration" && entry.status === "running") ? "hosting_registration_failed" : phases.some((entry) => entry.phase === "workflow_bootstrap" && entry.status === "running") ? "workflow_bootstrap_failed" : phases.some((entry) => entry.phase === "content_bootstrap" && entry.status === "running") ? "content_bootstrap_failed" : "repo_provision_failed";
|
|
1034
|
+
const failedPhase = phase.replace(/_failed$/u, "");
|
|
1035
|
+
if (!phases.some((entry) => entry.phase === failedPhase && entry.status === "failed")) {
|
|
1036
|
+
await appendPhase(phases, failedPhase, "failed", message, reportPhase);
|
|
1037
|
+
}
|
|
965
1038
|
throw new KnowledgeHubProviderLaunchError(phase, message, phases);
|
|
966
1039
|
} finally {
|
|
967
1040
|
if (input.preserveWorkingTree === false) {
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { ProjectEnvironmentName, ProjectLaunchHostBindingInput, TemplateLaunchRequirements } from '../../sdk-types.ts';
|
|
2
|
+
import { type PlatformRepositoryDescriptor } from '../repository-operations.ts';
|
|
3
|
+
import { type ProjectLaunchSecretSyncProgressEvent, type ProjectLaunchSecretSyncResult } from './template-secret-sync.ts';
|
|
4
|
+
import { type ProjectLaunchHostInventoryRecord, type ProjectLaunchResolvedHostBinding, type ResolveProjectLaunchHostBindingsResult } from '../../template-launch-requirements.ts';
|
|
5
|
+
export type ProjectHostOperationKind = 'inspect' | 'audit' | 'resync' | 'replace' | 'rotate';
|
|
6
|
+
export type ProjectHostOperationStatus = 'ok' | 'warning' | 'blocked';
|
|
7
|
+
export interface ProjectHostOperationDiagnostic {
|
|
8
|
+
code: string;
|
|
9
|
+
status: ProjectHostOperationStatus;
|
|
10
|
+
message: string;
|
|
11
|
+
requirementKey?: string;
|
|
12
|
+
provider?: string | null;
|
|
13
|
+
hostId?: string | null;
|
|
14
|
+
path?: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface ProjectHostRequirementBindingView {
|
|
17
|
+
requirementKey: string;
|
|
18
|
+
displayName: string;
|
|
19
|
+
type: string;
|
|
20
|
+
required: boolean;
|
|
21
|
+
purpose: string;
|
|
22
|
+
compatibleProviders: string[];
|
|
23
|
+
binding: {
|
|
24
|
+
provider: string | null;
|
|
25
|
+
hostId: string | null;
|
|
26
|
+
managedHostKey: string | null;
|
|
27
|
+
mode: string | null;
|
|
28
|
+
displayName: string | null;
|
|
29
|
+
ownership: string | null;
|
|
30
|
+
status: string | null;
|
|
31
|
+
environmentScopes: ProjectEnvironmentName[];
|
|
32
|
+
selectedBy: string | null;
|
|
33
|
+
selectedAt: string | null;
|
|
34
|
+
} | null;
|
|
35
|
+
configWrites: Array<{
|
|
36
|
+
target: string;
|
|
37
|
+
path: string;
|
|
38
|
+
valueFrom: string;
|
|
39
|
+
provider: string | null;
|
|
40
|
+
}>;
|
|
41
|
+
secretTargets: Array<{
|
|
42
|
+
env: string;
|
|
43
|
+
targets: string[];
|
|
44
|
+
scopes: ProjectEnvironmentName[];
|
|
45
|
+
sensitivity: string;
|
|
46
|
+
provider: string | null;
|
|
47
|
+
}>;
|
|
48
|
+
audit: {
|
|
49
|
+
status: ProjectHostOperationStatus;
|
|
50
|
+
diagnostics: ProjectHostOperationDiagnostic[];
|
|
51
|
+
marketHostId: string | null;
|
|
52
|
+
repositoryConfig: 'planned' | 'not_declared';
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export interface ProjectHostBindingsView {
|
|
56
|
+
requirements: ProjectHostRequirementBindingView[];
|
|
57
|
+
summary: {
|
|
58
|
+
status: ProjectHostOperationStatus;
|
|
59
|
+
total: number;
|
|
60
|
+
blocked: number;
|
|
61
|
+
warnings: number;
|
|
62
|
+
};
|
|
63
|
+
diagnostics: ProjectHostOperationDiagnostic[];
|
|
64
|
+
}
|
|
65
|
+
export interface PlanProjectHostBindingOperationOptions {
|
|
66
|
+
kind: ProjectHostOperationKind;
|
|
67
|
+
requirementKey?: string | null;
|
|
68
|
+
currentHostBindings?: Record<string, ProjectLaunchResolvedHostBinding> | null;
|
|
69
|
+
replacementHostBindings?: Record<string, ProjectLaunchHostBindingInput> | null;
|
|
70
|
+
launchRequirements?: TemplateLaunchRequirements | null;
|
|
71
|
+
repositoryHosts?: ProjectLaunchHostInventoryRecord[];
|
|
72
|
+
teamHosts?: ProjectLaunchHostInventoryRecord[];
|
|
73
|
+
managedHosts?: ProjectLaunchHostInventoryRecord[];
|
|
74
|
+
defaultHosts?: Record<string, unknown> | null;
|
|
75
|
+
projectSlug?: string | null;
|
|
76
|
+
projectName?: string | null;
|
|
77
|
+
selectedAt?: string;
|
|
78
|
+
}
|
|
79
|
+
export interface PlanProjectHostBindingOperationResult {
|
|
80
|
+
kind: ProjectHostOperationKind;
|
|
81
|
+
requirementKey: string | null;
|
|
82
|
+
previousHostBindings: Record<string, ProjectLaunchResolvedHostBinding>;
|
|
83
|
+
nextHostBindings: Record<string, ProjectLaunchResolvedHostBinding>;
|
|
84
|
+
compatibility: ResolveProjectLaunchHostBindingsResult['compatibility'];
|
|
85
|
+
hostBindingPlans: {
|
|
86
|
+
configWrites: ResolveProjectLaunchHostBindingsResult['configWritePlan'];
|
|
87
|
+
secretDeployment: ResolveProjectLaunchHostBindingsResult['secretDeploymentPlan'];
|
|
88
|
+
};
|
|
89
|
+
audit: ProjectHostBindingsView;
|
|
90
|
+
operationSummary: {
|
|
91
|
+
requiresRepositoryConfigWrite: boolean;
|
|
92
|
+
requiresSecretSync: boolean;
|
|
93
|
+
changedRequirementKeys: string[];
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export interface ExecuteProjectHostBindingOperationInput {
|
|
97
|
+
projectId?: string | null;
|
|
98
|
+
teamId?: string | null;
|
|
99
|
+
kind: ProjectHostOperationKind;
|
|
100
|
+
requirementKey?: string | null;
|
|
101
|
+
repository: PlatformRepositoryDescriptor;
|
|
102
|
+
hostBindings: Record<string, ProjectLaunchResolvedHostBinding>;
|
|
103
|
+
previousHostBindings?: Record<string, ProjectLaunchResolvedHostBinding> | null;
|
|
104
|
+
hostBindingPlans: PlanProjectHostBindingOperationResult['hostBindingPlans'];
|
|
105
|
+
operationSummary?: PlanProjectHostBindingOperationResult['operationSummary'] | null;
|
|
106
|
+
projectSlug?: string | null;
|
|
107
|
+
projectName?: string | null;
|
|
108
|
+
repositoryName?: string | null;
|
|
109
|
+
commitMessage?: string | null;
|
|
110
|
+
approvalRequired?: boolean;
|
|
111
|
+
approvalId?: string | null;
|
|
112
|
+
dryRun?: boolean;
|
|
113
|
+
}
|
|
114
|
+
export interface ExecuteProjectHostBindingOperationContext {
|
|
115
|
+
workspaceRoot: string;
|
|
116
|
+
environment?: string;
|
|
117
|
+
valuesOverlay?: Record<string, string | undefined> | null;
|
|
118
|
+
valuesByScope?: Record<string, Record<string, string | undefined> | null> | null;
|
|
119
|
+
processEnv?: Record<string, string | undefined>;
|
|
120
|
+
onProgress?: (event: ProjectLaunchSecretSyncProgressEvent) => void | Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
export interface ExecuteProjectHostBindingOperationResult {
|
|
123
|
+
ok: boolean;
|
|
124
|
+
kind: ProjectHostOperationKind;
|
|
125
|
+
requirementKey: string | null;
|
|
126
|
+
hostBindings: Record<string, ProjectLaunchResolvedHostBinding>;
|
|
127
|
+
previousHostBindings: Record<string, ProjectLaunchResolvedHostBinding>;
|
|
128
|
+
hostBindingPlans: PlanProjectHostBindingOperationResult['hostBindingPlans'];
|
|
129
|
+
repository: {
|
|
130
|
+
operation: string;
|
|
131
|
+
branch: string | null;
|
|
132
|
+
commitSha: string | null;
|
|
133
|
+
changedPaths: string[];
|
|
134
|
+
audit: unknown;
|
|
135
|
+
config: unknown;
|
|
136
|
+
};
|
|
137
|
+
secretSync: ProjectLaunchSecretSyncResult | null;
|
|
138
|
+
summary: {
|
|
139
|
+
requiresRepositoryConfigWrite: boolean;
|
|
140
|
+
requiresSecretSync: boolean;
|
|
141
|
+
changedRequirementKeys: string[];
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export declare function deriveProjectHostBindingsView(options: {
|
|
145
|
+
launchRequirements?: TemplateLaunchRequirements | null;
|
|
146
|
+
hostBindings?: Record<string, ProjectLaunchResolvedHostBinding> | null;
|
|
147
|
+
hostBindingPlans?: {
|
|
148
|
+
configWrites?: ResolveProjectLaunchHostBindingsResult['configWritePlan'] | null;
|
|
149
|
+
secretDeployment?: ResolveProjectLaunchHostBindingsResult['secretDeploymentPlan'] | null;
|
|
150
|
+
} | null;
|
|
151
|
+
}): ProjectHostBindingsView;
|
|
152
|
+
export declare function planProjectHostBindingOperation(options: PlanProjectHostBindingOperationOptions): PlanProjectHostBindingOperationResult;
|
|
153
|
+
export declare function executeProjectHostBindingOperation(input: ExecuteProjectHostBindingOperationInput, context: ExecuteProjectHostBindingOperationContext): Promise<ExecuteProjectHostBindingOperationResult>;
|