@farming-labs/docs 0.2.0 → 0.2.2
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
|
@@ -27,7 +27,11 @@ function parseFlags(argv) {
|
|
|
27
27
|
"algolia",
|
|
28
28
|
"verbose",
|
|
29
29
|
"host",
|
|
30
|
-
"json"
|
|
30
|
+
"json",
|
|
31
|
+
"network",
|
|
32
|
+
"analytics",
|
|
33
|
+
"ask-ai",
|
|
34
|
+
"deploy"
|
|
31
35
|
]);
|
|
32
36
|
for (let i = 0; i < argv.length; i++) {
|
|
33
37
|
const arg = argv[i];
|
|
@@ -85,36 +89,45 @@ async function main() {
|
|
|
85
89
|
apiBaseUrl: typeof flags["api-base-url"] === "string" ? flags["api-base-url"] : typeof flags.url === "string" ? flags.url : void 0,
|
|
86
90
|
apiKey: typeof flags["api-key"] === "string" ? flags["api-key"] : void 0,
|
|
87
91
|
apiKeyEnv: typeof flags["api-key-env"] === "string" ? flags["api-key-env"] : void 0,
|
|
88
|
-
json: typeof flags.json === "boolean" ? flags.json : void 0
|
|
92
|
+
json: typeof flags.json === "boolean" ? flags.json : void 0,
|
|
93
|
+
network: typeof flags.network === "boolean" ? flags.network : void 0,
|
|
94
|
+
checkTargets: [
|
|
95
|
+
...flags.deploy === true ? ["deploy"] : [],
|
|
96
|
+
...flags.analytics === true ? ["analytics"] : [],
|
|
97
|
+
...flags["ask-ai"] === true ? ["ask-ai"] : []
|
|
98
|
+
]
|
|
89
99
|
};
|
|
90
100
|
if (!parsedCommand.command || parsedCommand.command === "init") {
|
|
91
|
-
const { init } = await import("../init-
|
|
101
|
+
const { init } = await import("../init-DeeHyP-N.mjs");
|
|
92
102
|
await init(initOptions);
|
|
93
103
|
} else if (parsedCommand.command === "dev") {
|
|
94
104
|
const { dev } = await import("../dev-D58nBPBv.mjs");
|
|
95
105
|
await dev(devOptions);
|
|
96
106
|
} else if (parsedCommand.command === "deploy") {
|
|
97
|
-
const { runCloudDeploy } = await import("../cloud-
|
|
107
|
+
const { runCloudDeploy } = await import("../cloud-DLc03Z41.mjs");
|
|
98
108
|
await runCloudDeploy(cloudOptions);
|
|
99
109
|
} else if (parsedCommand.command === "preview") {
|
|
100
|
-
const { runCloudPreview } = await import("../cloud-
|
|
110
|
+
const { runCloudPreview } = await import("../cloud-DLc03Z41.mjs");
|
|
101
111
|
await runCloudPreview(cloudOptions);
|
|
102
112
|
} else if (parsedCommand.command === "cloud" && subcommand === "deploy") {
|
|
103
|
-
const { runCloudDeploy } = await import("../cloud-
|
|
113
|
+
const { runCloudDeploy } = await import("../cloud-DLc03Z41.mjs");
|
|
104
114
|
await runCloudDeploy(cloudOptions);
|
|
105
115
|
} else if (parsedCommand.command === "cloud" && subcommand === "preview") {
|
|
106
|
-
const { runCloudPreview } = await import("../cloud-
|
|
116
|
+
const { runCloudPreview } = await import("../cloud-DLc03Z41.mjs");
|
|
107
117
|
await runCloudPreview(cloudOptions);
|
|
108
118
|
} else if (parsedCommand.command === "cloud" && subcommand === "init") {
|
|
109
|
-
const { runCloudInit } = await import("../cloud-
|
|
119
|
+
const { runCloudInit } = await import("../cloud-DLc03Z41.mjs");
|
|
110
120
|
await runCloudInit(cloudOptions);
|
|
111
121
|
} else if (parsedCommand.command === "cloud" && subcommand === "sync") {
|
|
112
|
-
const { syncCloudConfig } = await import("../cloud-
|
|
122
|
+
const { syncCloudConfig } = await import("../cloud-DLc03Z41.mjs");
|
|
113
123
|
await syncCloudConfig(cloudOptions);
|
|
124
|
+
} else if (parsedCommand.command === "cloud" && subcommand === "check") {
|
|
125
|
+
const { runCloudCheck } = await import("../cloud-DLc03Z41.mjs");
|
|
126
|
+
await runCloudCheck(cloudOptions);
|
|
114
127
|
} else if (parsedCommand.command === "cloud") {
|
|
115
128
|
console.error(pc.red(`Unknown cloud subcommand: ${subcommand ?? "(missing)"}`));
|
|
116
129
|
console.error();
|
|
117
|
-
const { printCloudHelp } = await import("../cloud-
|
|
130
|
+
const { printCloudHelp } = await import("../cloud-DLc03Z41.mjs");
|
|
118
131
|
printCloudHelp();
|
|
119
132
|
process.exit(1);
|
|
120
133
|
} else if (parsedCommand.command === "mcp") {
|
|
@@ -253,7 +266,7 @@ ${pc.dim("Commands:")}
|
|
|
253
266
|
${pc.cyan("dev")} Run frameworkless docs locally from ${pc.dim("docs.json")}
|
|
254
267
|
${pc.cyan("deploy")} Sync cloud config and deploy hosted preview docs
|
|
255
268
|
${pc.cyan("preview")} Alias for ${pc.cyan("deploy")}
|
|
256
|
-
${pc.cyan("cloud")} Docs Cloud utilities (${pc.dim("init")}, ${pc.dim("deploy")}, ${pc.dim("preview")}, ${pc.dim("sync")})
|
|
269
|
+
${pc.cyan("cloud")} Docs Cloud utilities (${pc.dim("init")}, ${pc.dim("check")}, ${pc.dim("deploy")}, ${pc.dim("preview")}, ${pc.dim("sync")})
|
|
257
270
|
${pc.cyan("agent")} Agent utilities (${pc.dim("compact")} to generate sibling agent.md files)
|
|
258
271
|
${pc.cyan("agents")} AGENTS.md utilities (${pc.dim("generate")} for static agent instructions)
|
|
259
272
|
${pc.cyan("doctor")} Inspect and score agent or reader-facing docs quality
|
|
@@ -289,17 +302,22 @@ ${pc.dim("Options for dev:")}
|
|
|
289
302
|
${pc.cyan("--host [host]")} Expose the preview on your network; optionally pass a host value
|
|
290
303
|
${pc.cyan("--verbose")} Show raw runtime logs in addition to branded CLI output
|
|
291
304
|
|
|
292
|
-
${pc.dim("Options for cloud
|
|
305
|
+
${pc.dim("Options for cloud:")}
|
|
293
306
|
${pc.cyan("cloud init")} Add Docs Cloud config to ${pc.dim("docs.config.ts")} and ${pc.dim("docs.json")}
|
|
294
307
|
${pc.cyan("deploy")} Sync ${pc.dim("docs.config.ts")} into ${pc.dim("docs.json")} and deploy hosted preview docs
|
|
295
308
|
${pc.cyan("cloud deploy")} Same as ${pc.cyan("deploy")}
|
|
296
309
|
${pc.cyan("preview")} Alias for ${pc.cyan("deploy")}
|
|
297
310
|
${pc.cyan("cloud preview")} Compatibility alias for ${pc.cyan("cloud deploy")}
|
|
298
311
|
${pc.cyan("cloud sync")} Only materialize cloud settings into ${pc.dim("docs.json")}
|
|
312
|
+
${pc.cyan("cloud check")} Validate Docs Cloud config, analytics envs, API key, and Ask AI wiring
|
|
299
313
|
${pc.cyan("--config <path>")} Use a custom docs config path
|
|
300
314
|
${pc.cyan("--api-key-env <name>")} Env var that stores the Docs Cloud API key
|
|
301
315
|
${pc.cyan("--api-base-url <url>")} Override the Docs Cloud API base URL
|
|
302
316
|
${pc.cyan("--api-key <key>")} Use an API key directly; prefer ${pc.dim("cloud.apiKey.env")}
|
|
317
|
+
${pc.cyan("--analytics")} Only check Docs Cloud analytics integration
|
|
318
|
+
${pc.cyan("--ask-ai")} Only check Docs Cloud Ask AI integration
|
|
319
|
+
${pc.cyan("--deploy")} Only check Docs Cloud deploy integration
|
|
320
|
+
${pc.cyan("--no-network")} Skip live Docs Cloud API validation for ${pc.cyan("cloud check")}
|
|
303
321
|
${pc.cyan("--json")} Print machine-readable output
|
|
304
322
|
${pc.dim("required scopes")} project:read, preview:write, jobs:read
|
|
305
323
|
|
|
@@ -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 = [
|
|
@@ -20,6 +20,13 @@ const REQUIRED_PREVIEW_API_KEY_SCOPES = [
|
|
|
20
20
|
"preview:write",
|
|
21
21
|
"jobs:read"
|
|
22
22
|
];
|
|
23
|
+
const DOCS_CLOUD_PROJECT_ID_ENVS = ["NEXT_PUBLIC_DOCS_CLOUD_PROJECT_ID", "DOCS_CLOUD_PROJECT_ID"];
|
|
24
|
+
const DEFAULT_PUBLIC_DOCS_CLOUD_API_KEY_ENV = "NEXT_PUBLIC_DOCS_CLOUD_API_KEY";
|
|
25
|
+
const CLOUD_CHECK_TARGETS = [
|
|
26
|
+
"deploy",
|
|
27
|
+
"analytics",
|
|
28
|
+
"ask-ai"
|
|
29
|
+
];
|
|
23
30
|
function isRecord(value) {
|
|
24
31
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
25
32
|
}
|
|
@@ -593,8 +600,111 @@ async function materializeCloudConfig(options = {}) {
|
|
|
593
600
|
updated
|
|
594
601
|
};
|
|
595
602
|
}
|
|
596
|
-
function
|
|
597
|
-
|
|
603
|
+
function readCombinedEnv(rootDir) {
|
|
604
|
+
const env = { ...loadProjectEnv(rootDir) };
|
|
605
|
+
for (const [key, value] of Object.entries(process.env)) if (typeof value === "string") env[key] = value;
|
|
606
|
+
return env;
|
|
607
|
+
}
|
|
608
|
+
function readEnvValue(env, name) {
|
|
609
|
+
if (!name) return void 0;
|
|
610
|
+
const value = env[name]?.trim();
|
|
611
|
+
return value ? value : void 0;
|
|
612
|
+
}
|
|
613
|
+
function readFirstEnv(env, names) {
|
|
614
|
+
for (const name of names) {
|
|
615
|
+
const value = readEnvValue(env, name);
|
|
616
|
+
if (value) return {
|
|
617
|
+
name,
|
|
618
|
+
value
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function readConfiguredCloudApiKeyEnv(snapshot) {
|
|
623
|
+
const moduleEnv = snapshot.config?.cloud?.apiKey?.env?.trim();
|
|
624
|
+
if (moduleEnv) return moduleEnv;
|
|
625
|
+
const apiKeyBlock = extractNestedObjectLiteral(snapshot.content ?? "", ["cloud", "apiKey"]);
|
|
626
|
+
return (apiKeyBlock ? readStringProperty(apiKeyBlock, "env") : void 0)?.trim() || void 0;
|
|
627
|
+
}
|
|
628
|
+
function readAiProvider(snapshot) {
|
|
629
|
+
const moduleProvider = (snapshot.config?.ai)?.provider;
|
|
630
|
+
if (typeof moduleProvider === "string" && moduleProvider.trim()) return moduleProvider.trim();
|
|
631
|
+
const aiBlock = extractNestedObjectLiteral(snapshot.content ?? "", ["ai"]);
|
|
632
|
+
return (aiBlock ? readStringProperty(aiBlock, "provider") : void 0)?.trim() || void 0;
|
|
633
|
+
}
|
|
634
|
+
function readRuntimeAnalyticsDisabled(snapshot) {
|
|
635
|
+
const moduleAnalytics = snapshot.config?.analytics;
|
|
636
|
+
if (moduleAnalytics === false) return true;
|
|
637
|
+
if (isRecord(moduleAnalytics) && moduleAnalytics.enabled === false) return true;
|
|
638
|
+
if (readTopLevelBooleanProperty(snapshot.content ?? "", "analytics") === false) return true;
|
|
639
|
+
const analyticsBlock = extractNestedObjectLiteral(snapshot.content ?? "", ["analytics"]);
|
|
640
|
+
return (analyticsBlock ? readTopLevelBooleanProperty(analyticsBlock, "enabled") : void 0) === false;
|
|
641
|
+
}
|
|
642
|
+
function isCloudAnalyticsEnabled(analytics) {
|
|
643
|
+
if (analytics === false) return false;
|
|
644
|
+
if (isRecord(analytics) && analytics.enabled === false) return false;
|
|
645
|
+
return typeof analytics !== "undefined";
|
|
646
|
+
}
|
|
647
|
+
function createCheck(name, status, message, details) {
|
|
648
|
+
return {
|
|
649
|
+
name,
|
|
650
|
+
status,
|
|
651
|
+
message,
|
|
652
|
+
...details ? { details } : {}
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
function isBrowserSafeEnvName(name) {
|
|
656
|
+
return name.startsWith("NEXT_PUBLIC_");
|
|
657
|
+
}
|
|
658
|
+
function summarizeIdentity(identity) {
|
|
659
|
+
if (!isRecord(identity)) return void 0;
|
|
660
|
+
const workspace = isRecord(identity.workspace) ? identity.workspace : void 0;
|
|
661
|
+
const apiKey = isRecord(identity.apiKey) ? identity.apiKey : void 0;
|
|
662
|
+
const scopes = readApiKeyScopes(identity);
|
|
663
|
+
return {
|
|
664
|
+
...workspace ? { workspace: {
|
|
665
|
+
...typeof workspace.id === "string" ? { id: workspace.id } : {},
|
|
666
|
+
...typeof workspace.name === "string" ? { name: workspace.name } : {}
|
|
667
|
+
} } : {},
|
|
668
|
+
...apiKey ? { apiKey: {
|
|
669
|
+
...typeof apiKey.id === "string" ? { id: apiKey.id } : {},
|
|
670
|
+
...scopes.length > 0 ? { scopes } : {}
|
|
671
|
+
} } : {}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
function formatCheckStatus(status) {
|
|
675
|
+
if (status === "pass") return pc.green("ok");
|
|
676
|
+
if (status === "warn") return pc.yellow("warn");
|
|
677
|
+
return pc.red("fail");
|
|
678
|
+
}
|
|
679
|
+
function countChecks(checks, status) {
|
|
680
|
+
return checks.filter((check) => check.status === status).length;
|
|
681
|
+
}
|
|
682
|
+
function resolveCloudCheckTargets(options) {
|
|
683
|
+
const targets = new Set(options.checkTargets);
|
|
684
|
+
if (targets.size > 0) return targets;
|
|
685
|
+
return new Set(CLOUD_CHECK_TARGETS);
|
|
686
|
+
}
|
|
687
|
+
function formatCloudCheckTargets(targets) {
|
|
688
|
+
return targets.join(", ");
|
|
689
|
+
}
|
|
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
|
+
};
|
|
598
708
|
}
|
|
599
709
|
function resolveApiKey(options, rootDir, envName) {
|
|
600
710
|
if (options.apiKey?.trim()) return options.apiKey.trim();
|
|
@@ -605,6 +715,112 @@ function resolveApiKey(options, rootDir, envName) {
|
|
|
605
715
|
if (token) return token;
|
|
606
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}.`);
|
|
607
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
|
+
}
|
|
608
824
|
async function readJsonResponse(response) {
|
|
609
825
|
const text = await response.text();
|
|
610
826
|
if (!text.trim()) return {};
|
|
@@ -720,7 +936,7 @@ async function fetchCloudJson(params) {
|
|
|
720
936
|
const body = await readJsonResponse(response);
|
|
721
937
|
if (!response.ok) {
|
|
722
938
|
const requestPath = new URL(params.url).pathname;
|
|
723
|
-
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.");
|
|
724
940
|
const message = readResponseMessage(body, `Docs Cloud request failed with HTTP ${response.status}.`);
|
|
725
941
|
throw new Error(message);
|
|
726
942
|
}
|
|
@@ -770,7 +986,7 @@ function readStatusLabel(body) {
|
|
|
770
986
|
}
|
|
771
987
|
async function requestPreview(params) {
|
|
772
988
|
const initial = await fetchCloudJson({
|
|
773
|
-
url: `${params.apiBaseUrl}/
|
|
989
|
+
url: `${params.apiBaseUrl}/v1/cloud/preview`,
|
|
774
990
|
apiKey: params.apiKey,
|
|
775
991
|
init: {
|
|
776
992
|
method: "POST",
|
|
@@ -849,6 +1065,195 @@ function createSpinner(initialMessage, options = {}) {
|
|
|
849
1065
|
}
|
|
850
1066
|
};
|
|
851
1067
|
}
|
|
1068
|
+
async function checkCloudConfig(options = {}) {
|
|
1069
|
+
const rootDir = options.rootDir ?? process.cwd();
|
|
1070
|
+
const docsJsonPath = path.join(rootDir, DOCS_JSON_FILE);
|
|
1071
|
+
const existing = readExistingDocsJson(docsJsonPath);
|
|
1072
|
+
const snapshot = await loadDocsConfigSnapshot(rootDir, options.configPath);
|
|
1073
|
+
const config = materializeDocsJsonObject({
|
|
1074
|
+
rootDir,
|
|
1075
|
+
snapshot,
|
|
1076
|
+
existing
|
|
1077
|
+
});
|
|
1078
|
+
const serialized = serializeMaterializedDocsJson(config);
|
|
1079
|
+
const previous = existing ? fs.readFileSync(docsJsonPath, "utf-8") : void 0;
|
|
1080
|
+
const apiBaseUrlResolution = resolveApiBaseUrl(options, rootDir);
|
|
1081
|
+
const apiBaseUrl = apiBaseUrlResolution.url;
|
|
1082
|
+
const apiKeyEnv = config.cloud?.apiKey?.env ?? DOCS_CLOUD_DEFAULT_API_KEY_ENV;
|
|
1083
|
+
const env = readCombinedEnv(rootDir);
|
|
1084
|
+
const siteOrigin = readDocsSiteOrigin(snapshot, env);
|
|
1085
|
+
const checks = [];
|
|
1086
|
+
const configPath = snapshot.path ?? docsJsonPath;
|
|
1087
|
+
const network = options.network !== false;
|
|
1088
|
+
const explicitApiKey = options.apiKey?.trim();
|
|
1089
|
+
const targetSet = resolveCloudCheckTargets(options);
|
|
1090
|
+
const targets = CLOUD_CHECK_TARGETS.filter((target) => targetSet.has(target));
|
|
1091
|
+
const checkDeploy = targetSet.has("deploy");
|
|
1092
|
+
const checkAnalytics = targetSet.has("analytics");
|
|
1093
|
+
const checkAskAi = targetSet.has("ask-ai");
|
|
1094
|
+
const checkProjectEnv = checkAnalytics || checkAskAi;
|
|
1095
|
+
let identity;
|
|
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.`));
|
|
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));
|
|
1106
|
+
const apiKey = explicitApiKey || readEnvValue(env, apiKeyEnv);
|
|
1107
|
+
if (checkDeploy) {
|
|
1108
|
+
try {
|
|
1109
|
+
normalizeEnvName(apiKeyEnv, DOCS_CLOUD_DEFAULT_API_KEY_ENV);
|
|
1110
|
+
checks.push(createCheck("apiKey.config", "pass", `Using cloud.apiKey.env ${apiKeyEnv}.`));
|
|
1111
|
+
} catch (error) {
|
|
1112
|
+
checks.push(createCheck("apiKey.config", "fail", error instanceof Error ? error.message : `Invalid API key env ${apiKeyEnv}.`));
|
|
1113
|
+
}
|
|
1114
|
+
checks.push(createCheck("apiKey.value", apiKey ? "pass" : "fail", apiKey ? explicitApiKey ? "Docs Cloud API key was provided with --api-key." : `Docs Cloud API key is present in ${apiKeyEnv}.` : `Missing Docs Cloud API key. Set ${apiKeyEnv} or pass --api-key.`, {
|
|
1115
|
+
env: apiKeyEnv,
|
|
1116
|
+
source: explicitApiKey ? "flag" : apiKey ? "env" : "missing"
|
|
1117
|
+
}));
|
|
1118
|
+
}
|
|
1119
|
+
checks.push(createCheck("cloud.enabled", config.cloud?.enabled === false ? "fail" : "pass", config.cloud?.enabled === false ? "Docs Cloud is disabled by cloud.enabled: false." : "Docs Cloud is enabled."));
|
|
1120
|
+
if (checkDeploy) {
|
|
1121
|
+
if (config.cloud?.deploy?.enabled === false) checks.push(createCheck("deploy.enabled", "fail", "Docs Cloud deployment is disabled by cloud.deploy.enabled: false."));
|
|
1122
|
+
else checks.push(createCheck("deploy.enabled", "pass", "Docs Cloud deployment is enabled."));
|
|
1123
|
+
if (config.cloud?.preview?.enabled === false) checks.push(createCheck("preview.enabled", "fail", "Docs Cloud preview deployment is disabled by cloud.preview.enabled: false."));
|
|
1124
|
+
}
|
|
1125
|
+
const runtimeAnalyticsDisabled = readRuntimeAnalyticsDisabled(snapshot);
|
|
1126
|
+
const cloudAnalyticsEnabled = isCloudAnalyticsEnabled(config.cloud?.analytics);
|
|
1127
|
+
if (checkAnalytics) {
|
|
1128
|
+
if (runtimeAnalyticsDisabled) checks.push(createCheck("analytics.runtime", "fail", "Runtime analytics is disabled by analytics: false or analytics.enabled: false."));
|
|
1129
|
+
else checks.push(createCheck("analytics.runtime", "pass", "Runtime analytics is not disabled."));
|
|
1130
|
+
checks.push(createCheck("analytics.cloud", cloudAnalyticsEnabled ? "pass" : "warn", cloudAnalyticsEnabled ? "Docs Cloud analytics is enabled in cloud.analytics." : "cloud.analytics is not enabled; run docs cloud init to add the recommended analytics config."));
|
|
1131
|
+
}
|
|
1132
|
+
const projectEnv = readFirstEnv(env, DOCS_CLOUD_PROJECT_ID_ENVS);
|
|
1133
|
+
const analyticsNeedsProjectId = cloudAnalyticsEnabled && !runtimeAnalyticsDisabled;
|
|
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));
|
|
1135
|
+
const aiProvider = readAiProvider(snapshot);
|
|
1136
|
+
let askAiCorsMode = "none";
|
|
1137
|
+
if (checkAskAi) if (aiProvider === "docs-cloud") {
|
|
1138
|
+
checks.push(createCheck("askAi.provider", "pass", "Ask AI is configured with provider: \"docs-cloud\"."));
|
|
1139
|
+
const configuredApiKeyEnv = readConfiguredCloudApiKeyEnv(snapshot);
|
|
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 }));
|
|
1159
|
+
} else if (aiProvider) checks.push(createCheck("askAi.provider", "pass", `Ask AI provider is ${aiProvider}.`));
|
|
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
|
+
}
|
|
1200
|
+
if (checkDeploy) if (!network) checks.push(createCheck("apiKey.network", "warn", "Skipped Docs Cloud API validation because --no-network was passed."));
|
|
1201
|
+
else if (!apiKey) checks.push(createCheck("apiKey.network", "warn", "Skipped Docs Cloud API validation because no API key value was available."));
|
|
1202
|
+
else try {
|
|
1203
|
+
const response = await fetchCloudJson({
|
|
1204
|
+
url: `${apiBaseUrl}/v1/cloud/me`,
|
|
1205
|
+
apiKey
|
|
1206
|
+
});
|
|
1207
|
+
identity = summarizeIdentity(response);
|
|
1208
|
+
checks.push(createCheck("apiKey.network", "pass", `Validated API key with ${apiBaseUrl}.`, identity));
|
|
1209
|
+
const scopes = readApiKeyScopes(response);
|
|
1210
|
+
if (scopes.length === 0) checks.push(createCheck("apiKey.scopes", "warn", "Docs Cloud validated the API key but did not return scope metadata."));
|
|
1211
|
+
else {
|
|
1212
|
+
const missing = REQUIRED_PREVIEW_API_KEY_SCOPES.filter((scope) => !scopes.includes(scope));
|
|
1213
|
+
checks.push(createCheck("apiKey.scopes", missing.length === 0 ? "pass" : "fail", missing.length === 0 ? `API key has required deploy scopes: ${REQUIRED_PREVIEW_API_KEY_SCOPES.join(", ")}.` : `API key is missing required deploy scope${missing.length === 1 ? "" : "s"}: ${missing.join(", ")}.`, { scopes }));
|
|
1214
|
+
}
|
|
1215
|
+
} catch (error) {
|
|
1216
|
+
checks.push(createCheck("apiKey.network", "fail", error instanceof Error ? error.message : "Could not validate Docs Cloud API key."));
|
|
1217
|
+
}
|
|
1218
|
+
return {
|
|
1219
|
+
ok: countChecks(checks, "fail") === 0,
|
|
1220
|
+
apiBaseUrl,
|
|
1221
|
+
configPath,
|
|
1222
|
+
docsJsonPath,
|
|
1223
|
+
apiKeyEnv,
|
|
1224
|
+
analyticsProjectIdEnv: projectEnv?.name,
|
|
1225
|
+
network,
|
|
1226
|
+
targets,
|
|
1227
|
+
checks,
|
|
1228
|
+
...identity ? { identity } : {}
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
async function runCloudCheck(options = {}) {
|
|
1232
|
+
const result = await checkCloudConfig(options);
|
|
1233
|
+
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
1234
|
+
else {
|
|
1235
|
+
console.log(pc.bold("Docs Cloud check"));
|
|
1236
|
+
console.log(`${pc.dim("api")} ${result.apiBaseUrl}`);
|
|
1237
|
+
console.log(`${pc.dim("scope")} ${formatCloudCheckTargets(result.targets)}`);
|
|
1238
|
+
console.log();
|
|
1239
|
+
for (const check of result.checks) console.log(`${formatCheckStatus(check.status)} ${pc.bold(check.name)} ${check.message}`);
|
|
1240
|
+
console.log();
|
|
1241
|
+
if (result.ok) {
|
|
1242
|
+
const warnings = countChecks(result.checks, "warn");
|
|
1243
|
+
const suffix = warnings > 0 ? ` with ${warnings} warning${warnings === 1 ? "" : "s"}` : "";
|
|
1244
|
+
console.log(`${pc.green("ok")} Docs Cloud check passed${suffix}.`);
|
|
1245
|
+
} else {
|
|
1246
|
+
const failures = countChecks(result.checks, "fail");
|
|
1247
|
+
console.log(`${pc.red("fail")} Docs Cloud check failed with ${failures} failed check${failures === 1 ? "" : "s"}.`);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
if (!result.ok) {
|
|
1251
|
+
const error = /* @__PURE__ */ new Error("Docs Cloud check failed.");
|
|
1252
|
+
markCliErrorReported(error);
|
|
1253
|
+
throw error;
|
|
1254
|
+
}
|
|
1255
|
+
return result;
|
|
1256
|
+
}
|
|
852
1257
|
async function syncCloudConfig(options = {}) {
|
|
853
1258
|
const result = await materializeCloudConfig(options);
|
|
854
1259
|
if (options.json) {
|
|
@@ -918,10 +1323,10 @@ async function runCloudDeployment(options = {}) {
|
|
|
918
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.");
|
|
919
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.");
|
|
920
1325
|
const apiKey = resolveApiKey(options, rootDir, materialized.apiKeyEnv);
|
|
921
|
-
const apiBaseUrl = resolveApiBaseUrl(options);
|
|
1326
|
+
const apiBaseUrl = resolveApiBaseUrl(options, rootDir).url;
|
|
922
1327
|
spinner.update("Validating Docs Cloud API key");
|
|
923
1328
|
const identity = await fetchCloudJson({
|
|
924
|
-
url: `${apiBaseUrl}/
|
|
1329
|
+
url: `${apiBaseUrl}/v1/cloud/me`,
|
|
925
1330
|
apiKey
|
|
926
1331
|
});
|
|
927
1332
|
assertPreviewApiKeyScopes(identity);
|
|
@@ -970,6 +1375,7 @@ ${pc.bold("@farming-labs/docs cloud")}
|
|
|
970
1375
|
|
|
971
1376
|
${pc.dim("Usage:")}
|
|
972
1377
|
${pc.cyan("docs cloud init")} Add Docs Cloud config to ${pc.dim("docs.config.ts")} and ${pc.dim("docs.json")}
|
|
1378
|
+
${pc.cyan("docs cloud check")} Validate Docs Cloud config, analytics envs, API key, and Ask AI wiring
|
|
973
1379
|
${pc.cyan("docs deploy")} Sync ${pc.dim("docs.config.ts")} to ${pc.dim("docs.json")} and deploy hosted preview docs
|
|
974
1380
|
${pc.cyan("docs cloud deploy")} Same as ${pc.cyan("docs deploy")}
|
|
975
1381
|
${pc.cyan("docs preview")} Compatibility alias for ${pc.cyan("docs deploy")}
|
|
@@ -981,6 +1387,10 @@ ${pc.dim("Options:")}
|
|
|
981
1387
|
${pc.cyan("--api-key-env <name>")} Env var that stores the Docs Cloud API key
|
|
982
1388
|
${pc.cyan("--api-base-url <url>")} Override Docs Cloud API base URL
|
|
983
1389
|
${pc.cyan("--api-key <key>")} Use an API key directly; prefer ${pc.dim("cloud.apiKey.env")}
|
|
1390
|
+
${pc.cyan("--analytics")} Only check Docs Cloud analytics integration
|
|
1391
|
+
${pc.cyan("--ask-ai")} Only check Docs Cloud Ask AI integration
|
|
1392
|
+
${pc.cyan("--deploy")} Only check Docs Cloud deploy integration
|
|
1393
|
+
${pc.cyan("--no-network")} Skip live Docs Cloud API validation for ${pc.cyan("cloud check")}
|
|
984
1394
|
${pc.cyan("--json")} Print machine-readable output
|
|
985
1395
|
|
|
986
1396
|
${pc.dim("API key scopes:")}
|
|
@@ -997,4 +1407,4 @@ ${pc.dim("Config example:")}
|
|
|
997
1407
|
}
|
|
998
1408
|
|
|
999
1409
|
//#endregion
|
|
1000
|
-
export { initCloudConfig, printCloudHelp, runCloudDeploy, runCloudInit, runCloudPreview, syncCloudConfig };
|
|
1410
|
+
export { initCloudConfig, printCloudHelp, runCloudCheck, runCloudDeploy, runCloudInit, runCloudPreview, syncCloudConfig };
|
|
@@ -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)
|