@farming-labs/docs 0.2.1 → 0.2.3
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/cli/index.mjs
CHANGED
|
@@ -98,36 +98,36 @@ async function main() {
|
|
|
98
98
|
]
|
|
99
99
|
};
|
|
100
100
|
if (!parsedCommand.command || parsedCommand.command === "init") {
|
|
101
|
-
const { init } = await import("../init-
|
|
101
|
+
const { init } = await import("../init-DeeHyP-N.mjs");
|
|
102
102
|
await init(initOptions);
|
|
103
103
|
} else if (parsedCommand.command === "dev") {
|
|
104
104
|
const { dev } = await import("../dev-D58nBPBv.mjs");
|
|
105
105
|
await dev(devOptions);
|
|
106
106
|
} else if (parsedCommand.command === "deploy") {
|
|
107
|
-
const { runCloudDeploy } = await import("../cloud-
|
|
107
|
+
const { runCloudDeploy } = await import("../cloud-DLc03Z41.mjs");
|
|
108
108
|
await runCloudDeploy(cloudOptions);
|
|
109
109
|
} else if (parsedCommand.command === "preview") {
|
|
110
|
-
const { runCloudPreview } = await import("../cloud-
|
|
110
|
+
const { runCloudPreview } = await import("../cloud-DLc03Z41.mjs");
|
|
111
111
|
await runCloudPreview(cloudOptions);
|
|
112
112
|
} else if (parsedCommand.command === "cloud" && subcommand === "deploy") {
|
|
113
|
-
const { runCloudDeploy } = await import("../cloud-
|
|
113
|
+
const { runCloudDeploy } = await import("../cloud-DLc03Z41.mjs");
|
|
114
114
|
await runCloudDeploy(cloudOptions);
|
|
115
115
|
} else if (parsedCommand.command === "cloud" && subcommand === "preview") {
|
|
116
|
-
const { runCloudPreview } = await import("../cloud-
|
|
116
|
+
const { runCloudPreview } = await import("../cloud-DLc03Z41.mjs");
|
|
117
117
|
await runCloudPreview(cloudOptions);
|
|
118
118
|
} else if (parsedCommand.command === "cloud" && subcommand === "init") {
|
|
119
|
-
const { runCloudInit } = await import("../cloud-
|
|
119
|
+
const { runCloudInit } = await import("../cloud-DLc03Z41.mjs");
|
|
120
120
|
await runCloudInit(cloudOptions);
|
|
121
121
|
} else if (parsedCommand.command === "cloud" && subcommand === "sync") {
|
|
122
|
-
const { syncCloudConfig } = await import("../cloud-
|
|
122
|
+
const { syncCloudConfig } = await import("../cloud-DLc03Z41.mjs");
|
|
123
123
|
await syncCloudConfig(cloudOptions);
|
|
124
124
|
} else if (parsedCommand.command === "cloud" && subcommand === "check") {
|
|
125
|
-
const { runCloudCheck } = await import("../cloud-
|
|
125
|
+
const { runCloudCheck } = await import("../cloud-DLc03Z41.mjs");
|
|
126
126
|
await runCloudCheck(cloudOptions);
|
|
127
127
|
} else if (parsedCommand.command === "cloud") {
|
|
128
128
|
console.error(pc.red(`Unknown cloud subcommand: ${subcommand ?? "(missing)"}`));
|
|
129
129
|
console.error();
|
|
130
|
-
const { printCloudHelp } = await import("../cloud-
|
|
130
|
+
const { printCloudHelp } = await import("../cloud-DLc03Z41.mjs");
|
|
131
131
|
printCloudHelp();
|
|
132
132
|
process.exit(1);
|
|
133
133
|
} else if (parsedCommand.command === "mcp") {
|
|
@@ -12,7 +12,7 @@ const DOCS_CLOUD_SCHEMA_URL = "https://docs.farming-labs.dev/schema/docs.json";
|
|
|
12
12
|
const DOCS_CLOUD_DEFAULT_API_KEY_ENV = "DOCS_CLOUD_API_KEY";
|
|
13
13
|
const DOCS_CLOUD_DEFAULT_ANALYTICS_PROJECT_ID_ENV = "NEXT_PUBLIC_DOCS_CLOUD_PROJECT_ID";
|
|
14
14
|
const DOCS_CLOUD_MISSING_API_KEY_DOCS_URL = "https://docs.farming-labs.dev/docs/cloud/deploy#missing-api-key";
|
|
15
|
-
const DEFAULT_DOCS_CLOUD_API_BASE_URL = "https://
|
|
15
|
+
const DEFAULT_DOCS_CLOUD_API_BASE_URL = "https://api.farming-labs.dev";
|
|
16
16
|
const DEFAULT_PREVIEW_TIMEOUT_MS = 300 * 1e3;
|
|
17
17
|
const DEFAULT_PREVIEW_POLL_INTERVAL_MS = 2e3;
|
|
18
18
|
const REQUIRED_PREVIEW_API_KEY_SCOPES = [
|
|
@@ -652,6 +652,9 @@ function createCheck(name, status, message, details) {
|
|
|
652
652
|
...details ? { details } : {}
|
|
653
653
|
};
|
|
654
654
|
}
|
|
655
|
+
function isBrowserSafeEnvName(name) {
|
|
656
|
+
return name.startsWith("NEXT_PUBLIC_");
|
|
657
|
+
}
|
|
655
658
|
function summarizeIdentity(identity) {
|
|
656
659
|
if (!isRecord(identity)) return void 0;
|
|
657
660
|
const workspace = isRecord(identity.workspace) ? identity.workspace : void 0;
|
|
@@ -684,8 +687,24 @@ function resolveCloudCheckTargets(options) {
|
|
|
684
687
|
function formatCloudCheckTargets(targets) {
|
|
685
688
|
return targets.join(", ");
|
|
686
689
|
}
|
|
687
|
-
function resolveApiBaseUrl(options) {
|
|
688
|
-
|
|
690
|
+
function resolveApiBaseUrl(options, rootDir = process.cwd()) {
|
|
691
|
+
if (options.apiBaseUrl?.trim()) return {
|
|
692
|
+
url: options.apiBaseUrl.trim().replace(/\/+$/, ""),
|
|
693
|
+
source: "flag"
|
|
694
|
+
};
|
|
695
|
+
const projectEnv = loadProjectEnv(rootDir);
|
|
696
|
+
for (const envName of ["DOCS_CLOUD_API_URL", "NEXT_PUBLIC_DOCS_CLOUD_URL"]) {
|
|
697
|
+
const value = process.env[envName]?.trim() ?? projectEnv[envName]?.trim();
|
|
698
|
+
if (value) return {
|
|
699
|
+
url: value.replace(/\/+$/, ""),
|
|
700
|
+
source: "env",
|
|
701
|
+
env: envName
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
url: DEFAULT_DOCS_CLOUD_API_BASE_URL,
|
|
706
|
+
source: "default"
|
|
707
|
+
};
|
|
689
708
|
}
|
|
690
709
|
function resolveApiKey(options, rootDir, envName) {
|
|
691
710
|
if (options.apiKey?.trim()) return options.apiKey.trim();
|
|
@@ -696,6 +715,112 @@ function resolveApiKey(options, rootDir, envName) {
|
|
|
696
715
|
if (token) return token;
|
|
697
716
|
throw new Error(`Missing Docs Cloud API key. Set ${envName} in your shell or .env.local, or configure cloud.apiKey.env in docs.config.ts. See ${DOCS_CLOUD_MISSING_API_KEY_DOCS_URL}.`);
|
|
698
717
|
}
|
|
718
|
+
function isLocalhostUrl(value) {
|
|
719
|
+
try {
|
|
720
|
+
const url = new URL(value);
|
|
721
|
+
return [
|
|
722
|
+
"localhost",
|
|
723
|
+
"127.0.0.1",
|
|
724
|
+
"::1"
|
|
725
|
+
].includes(url.hostname);
|
|
726
|
+
} catch {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function readNestedString(value, pathSegments) {
|
|
731
|
+
let current = value;
|
|
732
|
+
for (const segment of pathSegments) {
|
|
733
|
+
if (!isRecord(current)) return void 0;
|
|
734
|
+
current = current[segment];
|
|
735
|
+
}
|
|
736
|
+
return typeof current === "string" && current.trim() ? current.trim() : void 0;
|
|
737
|
+
}
|
|
738
|
+
function readDocsSiteOrigin(snapshot, env) {
|
|
739
|
+
const envSite = readFirstEnv(env, [
|
|
740
|
+
"NEXT_PUBLIC_BASE_URL",
|
|
741
|
+
"NEXT_PUBLIC_SITE_URL",
|
|
742
|
+
"SITE_URL"
|
|
743
|
+
]);
|
|
744
|
+
const sitemapBlock = extractNestedObjectLiteral(snapshot.content ?? "", ["sitemap"]);
|
|
745
|
+
const llmsTxtBlock = extractNestedObjectLiteral(snapshot.content ?? "", ["llmsTxt"]);
|
|
746
|
+
const robotsBlock = extractNestedObjectLiteral(snapshot.content ?? "", ["robots"]);
|
|
747
|
+
const candidates = [
|
|
748
|
+
{
|
|
749
|
+
value: envSite?.value,
|
|
750
|
+
source: envSite ? envSite.name : "env"
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
value: readNestedString(snapshot.config, ["site", "url"]),
|
|
754
|
+
source: "site.url"
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
value: readNestedString(snapshot.config, ["sitemap", "baseUrl"]),
|
|
758
|
+
source: "sitemap.baseUrl"
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
value: readNestedString(snapshot.config, ["llmsTxt", "baseUrl"]),
|
|
762
|
+
source: "llmsTxt.baseUrl"
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
value: readNestedString(snapshot.config, ["robots", "baseUrl"]),
|
|
766
|
+
source: "robots.baseUrl"
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
value: sitemapBlock ? readStringProperty(sitemapBlock, "baseUrl") : void 0,
|
|
770
|
+
source: "sitemap.baseUrl"
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
value: llmsTxtBlock ? readStringProperty(llmsTxtBlock, "baseUrl") : void 0,
|
|
774
|
+
source: "llmsTxt.baseUrl"
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
value: robotsBlock ? readStringProperty(robotsBlock, "baseUrl") : void 0,
|
|
778
|
+
source: "robots.baseUrl"
|
|
779
|
+
}
|
|
780
|
+
];
|
|
781
|
+
for (const candidate of candidates) {
|
|
782
|
+
if (!candidate.value) continue;
|
|
783
|
+
try {
|
|
784
|
+
return {
|
|
785
|
+
origin: new URL(candidate.value).origin,
|
|
786
|
+
source: candidate.source
|
|
787
|
+
};
|
|
788
|
+
} catch {}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
async function checkCorsPreflight(params) {
|
|
792
|
+
const response = await fetch(params.url, {
|
|
793
|
+
method: "OPTIONS",
|
|
794
|
+
headers: {
|
|
795
|
+
Origin: params.origin,
|
|
796
|
+
"Access-Control-Request-Method": "POST",
|
|
797
|
+
"Access-Control-Request-Headers": params.requestHeaders
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
const allowOrigin = response.headers.get("access-control-allow-origin");
|
|
801
|
+
const allowMethods = response.headers.get("access-control-allow-methods");
|
|
802
|
+
const allowHeaders = response.headers.get("access-control-allow-headers");
|
|
803
|
+
const normalizedAllowOrigin = allowOrigin?.toLowerCase();
|
|
804
|
+
const normalizedOrigin = params.origin.toLowerCase();
|
|
805
|
+
return {
|
|
806
|
+
ok: response.ok && (normalizedAllowOrigin === "*" || normalizedAllowOrigin === normalizedOrigin) && Boolean(allowMethods?.toUpperCase().includes("POST")) && areCorsRequestHeadersAllowed(params.requestHeaders, allowHeaders),
|
|
807
|
+
status: response.status,
|
|
808
|
+
allowOrigin,
|
|
809
|
+
allowMethods,
|
|
810
|
+
allowHeaders
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function areCorsRequestHeadersAllowed(requestHeaders, allowHeaders) {
|
|
814
|
+
const requested = parseCorsHeaderList(requestHeaders);
|
|
815
|
+
if (requested.length === 0) return true;
|
|
816
|
+
if (!allowHeaders) return false;
|
|
817
|
+
const allowed = parseCorsHeaderList(allowHeaders);
|
|
818
|
+
if (allowed.includes("*")) return true;
|
|
819
|
+
return requested.every((header) => allowed.includes(header));
|
|
820
|
+
}
|
|
821
|
+
function parseCorsHeaderList(value) {
|
|
822
|
+
return value.split(",").map((header) => header.trim().toLowerCase()).filter(Boolean);
|
|
823
|
+
}
|
|
699
824
|
async function readJsonResponse(response) {
|
|
700
825
|
const text = await response.text();
|
|
701
826
|
if (!text.trim()) return {};
|
|
@@ -811,7 +936,7 @@ async function fetchCloudJson(params) {
|
|
|
811
936
|
const body = await readJsonResponse(response);
|
|
812
937
|
if (!response.ok) {
|
|
813
938
|
const requestPath = new URL(params.url).pathname;
|
|
814
|
-
if (response.status === 404 && requestPath === "/
|
|
939
|
+
if (response.status === 404 && requestPath === "/v1/cloud/preview") throw new Error("Docs Cloud preview API is not available on this cloud host yet. The API key was validated, but the host did not expose /v1/cloud/preview.");
|
|
815
940
|
const message = readResponseMessage(body, `Docs Cloud request failed with HTTP ${response.status}.`);
|
|
816
941
|
throw new Error(message);
|
|
817
942
|
}
|
|
@@ -861,7 +986,7 @@ function readStatusLabel(body) {
|
|
|
861
986
|
}
|
|
862
987
|
async function requestPreview(params) {
|
|
863
988
|
const initial = await fetchCloudJson({
|
|
864
|
-
url: `${params.apiBaseUrl}/
|
|
989
|
+
url: `${params.apiBaseUrl}/v1/cloud/preview`,
|
|
865
990
|
apiKey: params.apiKey,
|
|
866
991
|
init: {
|
|
867
992
|
method: "POST",
|
|
@@ -952,9 +1077,11 @@ async function checkCloudConfig(options = {}) {
|
|
|
952
1077
|
});
|
|
953
1078
|
const serialized = serializeMaterializedDocsJson(config);
|
|
954
1079
|
const previous = existing ? fs.readFileSync(docsJsonPath, "utf-8") : void 0;
|
|
955
|
-
const
|
|
1080
|
+
const apiBaseUrlResolution = resolveApiBaseUrl(options, rootDir);
|
|
1081
|
+
const apiBaseUrl = apiBaseUrlResolution.url;
|
|
956
1082
|
const apiKeyEnv = config.cloud?.apiKey?.env ?? DOCS_CLOUD_DEFAULT_API_KEY_ENV;
|
|
957
1083
|
const env = readCombinedEnv(rootDir);
|
|
1084
|
+
const siteOrigin = readDocsSiteOrigin(snapshot, env);
|
|
958
1085
|
const checks = [];
|
|
959
1086
|
const configPath = snapshot.path ?? docsJsonPath;
|
|
960
1087
|
const network = options.network !== false;
|
|
@@ -968,6 +1095,14 @@ async function checkCloudConfig(options = {}) {
|
|
|
968
1095
|
let identity;
|
|
969
1096
|
checks.push(createCheck("config", snapshot.path ? "pass" : "warn", snapshot.path ? `Loaded ${path.relative(rootDir, snapshot.path) || "docs.config.ts"}` : `No docs.config.* found; checking ${DOCS_JSON_FILE} defaults instead.`));
|
|
970
1097
|
checks.push(createCheck("docs.json", !existing ? "warn" : previous === serialized ? "pass" : "warn", !existing ? `${DOCS_JSON_FILE} is missing. Run docs cloud sync to materialize cloud config.` : previous === serialized ? `${DOCS_JSON_FILE} is in sync with docs.config.` : `${DOCS_JSON_FILE} is stale. Run docs cloud sync before deploying.`));
|
|
1098
|
+
checks.push(createCheck("cloud.apiBaseUrl", isLocalhostUrl(apiBaseUrl) ? "warn" : "pass", apiBaseUrlResolution.source === "default" ? `Using the hosted Docs Cloud API at ${apiBaseUrl}.` : isLocalhostUrl(apiBaseUrl) ? `Docs Cloud API base URL is ${apiBaseUrl}; production docs should use the hosted API base URL.` : `Docs Cloud API base URL is ${apiBaseUrl}.`, {
|
|
1099
|
+
source: apiBaseUrlResolution.source,
|
|
1100
|
+
...apiBaseUrlResolution.env ? { env: apiBaseUrlResolution.env } : {}
|
|
1101
|
+
}));
|
|
1102
|
+
if (checkAnalytics || checkAskAi) checks.push(createCheck("docs.siteOrigin", siteOrigin ? "pass" : "warn", siteOrigin ? `Public docs origin is ${siteOrigin.origin}.` : "Could not infer the public docs origin for CORS checks. Set NEXT_PUBLIC_BASE_URL, NEXT_PUBLIC_SITE_URL, SITE_URL, or a docs config baseUrl.", siteOrigin ? {
|
|
1103
|
+
origin: siteOrigin.origin,
|
|
1104
|
+
source: siteOrigin.source
|
|
1105
|
+
} : void 0));
|
|
971
1106
|
const apiKey = explicitApiKey || readEnvValue(env, apiKeyEnv);
|
|
972
1107
|
if (checkDeploy) {
|
|
973
1108
|
try {
|
|
@@ -998,19 +1133,75 @@ async function checkCloudConfig(options = {}) {
|
|
|
998
1133
|
const analyticsNeedsProjectId = cloudAnalyticsEnabled && !runtimeAnalyticsDisabled;
|
|
999
1134
|
if (checkProjectEnv) checks.push(createCheck("project.env", projectEnv ? "pass" : analyticsNeedsProjectId || checkAskAi ? "fail" : "warn", projectEnv ? `Docs Cloud project id is present in ${projectEnv.name}.` : `Missing Docs Cloud project id. Set ${DOCS_CLOUD_PROJECT_ID_ENVS.join(" or ")} for analytics and docs-cloud Ask AI.`, projectEnv ? { env: projectEnv.name } : void 0));
|
|
1000
1135
|
const aiProvider = readAiProvider(snapshot);
|
|
1136
|
+
let askAiCorsMode = "none";
|
|
1001
1137
|
if (checkAskAi) if (aiProvider === "docs-cloud") {
|
|
1002
1138
|
checks.push(createCheck("askAi.provider", "pass", "Ask AI is configured with provider: \"docs-cloud\"."));
|
|
1003
1139
|
const configuredApiKeyEnv = readConfiguredCloudApiKeyEnv(snapshot);
|
|
1004
|
-
const
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1140
|
+
const publicApiKeyEnv = configuredApiKeyEnv && isBrowserSafeEnvName(configuredApiKeyEnv) ? configuredApiKeyEnv : readFirstEnv(env, [DEFAULT_PUBLIC_DOCS_CLOUD_API_KEY_ENV])?.name ?? DEFAULT_PUBLIC_DOCS_CLOUD_API_KEY_ENV;
|
|
1141
|
+
const publicApiKey = readEnvValue(env, publicApiKeyEnv);
|
|
1142
|
+
const publicProjectEnv = readFirstEnv(env, [DOCS_CLOUD_DEFAULT_ANALYTICS_PROJECT_ID_ENV]);
|
|
1143
|
+
const serverApiKeyEnv = configuredApiKeyEnv ?? DOCS_CLOUD_DEFAULT_API_KEY_ENV;
|
|
1144
|
+
const serverApiKey = readEnvValue(env, serverApiKeyEnv);
|
|
1145
|
+
if (publicApiKey && publicProjectEnv) {
|
|
1146
|
+
checks.push(createCheck("askAi.direct", "pass", `Ask AI can call the Docs Cloud knowledge endpoint directly with ${publicApiKeyEnv}.`, {
|
|
1147
|
+
apiKeyEnv: publicApiKeyEnv,
|
|
1148
|
+
projectIdEnv: publicProjectEnv.name
|
|
1149
|
+
}));
|
|
1150
|
+
askAiCorsMode = "direct";
|
|
1151
|
+
} else if (serverApiKey && projectEnv) {
|
|
1152
|
+
checks.push(createCheck("askAi.direct", "warn", `Direct browser Ask AI needs ${DEFAULT_PUBLIC_DOCS_CLOUD_API_KEY_ENV} and ${DOCS_CLOUD_DEFAULT_ANALYTICS_PROJECT_ID_ENV}; this app can use the local docs API route with ${serverApiKeyEnv}.`, {
|
|
1153
|
+
apiKeyEnv: serverApiKeyEnv,
|
|
1154
|
+
projectIdEnv: projectEnv.name,
|
|
1155
|
+
proxy: true
|
|
1156
|
+
}));
|
|
1157
|
+
askAiCorsMode = "proxy";
|
|
1158
|
+
} else checks.push(createCheck("askAi.direct", "fail", `Ask AI docs-cloud direct mode needs ${DEFAULT_PUBLIC_DOCS_CLOUD_API_KEY_ENV} and ${DOCS_CLOUD_DEFAULT_ANALYTICS_PROJECT_ID_ENV}.`, { apiKeyEnv: publicApiKeyEnv }));
|
|
1007
1159
|
} else if (aiProvider) checks.push(createCheck("askAi.provider", "pass", `Ask AI provider is ${aiProvider}.`));
|
|
1008
1160
|
else checks.push(createCheck("askAi.provider", "warn", "Ask AI is not configured with provider: \"docs-cloud\"."));
|
|
1161
|
+
if (checkAnalytics || checkAskAi) if (!network) checks.push(createCheck("cloud.cors", "warn", "Skipped Docs Cloud CORS validation because --no-network was passed."));
|
|
1162
|
+
else if (!siteOrigin) checks.push(createCheck("cloud.cors", "warn", "Could not infer the docs site origin for CORS checks. Set NEXT_PUBLIC_BASE_URL or a docs config baseUrl."));
|
|
1163
|
+
else {
|
|
1164
|
+
if (checkAnalytics) try {
|
|
1165
|
+
const cors = await checkCorsPreflight({
|
|
1166
|
+
url: `${apiBaseUrl}/v1/analytics/events`,
|
|
1167
|
+
origin: siteOrigin.origin,
|
|
1168
|
+
requestHeaders: "content-type"
|
|
1169
|
+
});
|
|
1170
|
+
checks.push(createCheck("cors.analytics", cors.ok ? "pass" : "fail", cors.ok ? `Analytics CORS allows ${siteOrigin.origin}.` : `Analytics CORS blocked ${siteOrigin.origin}.`, {
|
|
1171
|
+
origin: siteOrigin.origin,
|
|
1172
|
+
originSource: siteOrigin.source,
|
|
1173
|
+
status: cors.status,
|
|
1174
|
+
allowOrigin: cors.allowOrigin,
|
|
1175
|
+
allowMethods: cors.allowMethods,
|
|
1176
|
+
allowHeaders: cors.allowHeaders
|
|
1177
|
+
}));
|
|
1178
|
+
} catch (error) {
|
|
1179
|
+
checks.push(createCheck("cors.analytics", "fail", error instanceof Error ? `Analytics CORS check failed: ${error.message}` : "Analytics CORS check failed."));
|
|
1180
|
+
}
|
|
1181
|
+
if (checkAskAi && projectEnv && askAiCorsMode === "direct") try {
|
|
1182
|
+
const cors = await checkCorsPreflight({
|
|
1183
|
+
url: `${apiBaseUrl}/v1/projects/${encodeURIComponent(projectEnv.value)}/knowledge/ask`,
|
|
1184
|
+
origin: siteOrigin.origin,
|
|
1185
|
+
requestHeaders: "authorization, content-type"
|
|
1186
|
+
});
|
|
1187
|
+
checks.push(createCheck("cors.askAi", cors.ok ? "pass" : "fail", cors.ok ? `Ask AI CORS allows ${siteOrigin.origin}.` : `Ask AI CORS blocked ${siteOrigin.origin}.`, {
|
|
1188
|
+
origin: siteOrigin.origin,
|
|
1189
|
+
originSource: siteOrigin.source,
|
|
1190
|
+
status: cors.status,
|
|
1191
|
+
allowOrigin: cors.allowOrigin,
|
|
1192
|
+
allowMethods: cors.allowMethods,
|
|
1193
|
+
allowHeaders: cors.allowHeaders
|
|
1194
|
+
}));
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
checks.push(createCheck("cors.askAi", "fail", error instanceof Error ? `Ask AI CORS check failed: ${error.message}` : "Ask AI CORS check failed."));
|
|
1197
|
+
}
|
|
1198
|
+
else if (checkAskAi && askAiCorsMode === "proxy") checks.push(createCheck("cors.askAi", "pass", "Ask AI uses the local docs API route, so Docs Cloud browser CORS is not required.", { proxy: true }));
|
|
1199
|
+
}
|
|
1009
1200
|
if (checkDeploy) if (!network) checks.push(createCheck("apiKey.network", "warn", "Skipped Docs Cloud API validation because --no-network was passed."));
|
|
1010
1201
|
else if (!apiKey) checks.push(createCheck("apiKey.network", "warn", "Skipped Docs Cloud API validation because no API key value was available."));
|
|
1011
1202
|
else try {
|
|
1012
1203
|
const response = await fetchCloudJson({
|
|
1013
|
-
url: `${apiBaseUrl}/
|
|
1204
|
+
url: `${apiBaseUrl}/v1/cloud/me`,
|
|
1014
1205
|
apiKey
|
|
1015
1206
|
});
|
|
1016
1207
|
identity = summarizeIdentity(response);
|
|
@@ -1132,10 +1323,10 @@ async function runCloudDeployment(options = {}) {
|
|
|
1132
1323
|
if (materialized.config.cloud?.preview?.enabled === false) throw new Error("Docs Cloud preview deployments are disabled in cloud.preview.enabled. Remove that legacy override before deploying hosted preview docs.");
|
|
1133
1324
|
if (materialized.config.cloud?.deploy?.enabled === false) throw new Error("Docs Cloud deployment is disabled in cloud.deploy.enabled. Set it to true before deploying hosted preview docs.");
|
|
1134
1325
|
const apiKey = resolveApiKey(options, rootDir, materialized.apiKeyEnv);
|
|
1135
|
-
const apiBaseUrl = resolveApiBaseUrl(options);
|
|
1326
|
+
const apiBaseUrl = resolveApiBaseUrl(options, rootDir).url;
|
|
1136
1327
|
spinner.update("Validating Docs Cloud API key");
|
|
1137
1328
|
const identity = await fetchCloudJson({
|
|
1138
|
-
url: `${apiBaseUrl}/
|
|
1329
|
+
url: `${apiBaseUrl}/v1/cloud/me`,
|
|
1139
1330
|
apiKey
|
|
1140
1331
|
});
|
|
1141
1332
|
assertPreviewApiKeyScopes(identity);
|
|
@@ -188,7 +188,7 @@ async function configureDocsCloudOnboarding(options) {
|
|
|
188
188
|
enabled = cloudAnswer;
|
|
189
189
|
}
|
|
190
190
|
if (!enabled) return;
|
|
191
|
-
const { initCloudConfig } = await import("./cloud-
|
|
191
|
+
const { initCloudConfig } = await import("./cloud-DLc03Z41.mjs");
|
|
192
192
|
printDocsCloudOnboardingInstructions(await initCloudConfig({
|
|
193
193
|
rootDir: options.rootDir,
|
|
194
194
|
configPath: getDocsCloudConfigPathForFramework(options.framework)
|