@gscdump/cli 4.8.0 → 5.0.0
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/auth.mjs +1 -7
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -2
- package/dist/commands/bing.mjs +4 -4
- package/dist/commands/doctor.mjs +10 -67
- package/dist/commands/init.mjs +3 -88
- package/dist/commands/query.mjs +11 -4
- package/dist/commands/sitemaps.mjs +4 -4
- package/dist/commands/sites.mjs +7 -7
- package/dist/coverage.mjs +1 -3
- package/dist/environment.mjs +1 -5
- package/dist/error-handler.mjs +12 -0
- package/dist/hosted-query.mjs +23 -1
- package/dist/mcp/types.mjs +1 -0
- package/dist/package.mjs +1 -1
- package/dist/render/query.mjs +1 -1
- package/dist/sync-plan.mjs +1 -1
- package/dist/utils.mjs +4 -1
- package/package.json +7 -7
- package/dist/env-file.mjs +0 -53
package/dist/auth.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { resolveAuthentication } from "./auth-state.mjs";
|
|
2
2
|
import { getConfigDir, loadConfig, saveConfig } from "./config.mjs";
|
|
3
3
|
import { pickCliEnvironmentValue, resolveCliEnvironment } from "./environment.mjs";
|
|
4
|
-
import { getAppliedEnvKeys, getLoadedEnvPath } from "./env-file.mjs";
|
|
5
4
|
import { displayPath, logger } from "./utils.mjs";
|
|
6
5
|
import process from "node:process";
|
|
7
6
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
@@ -361,7 +360,7 @@ async function resolveAuth(opts = {}) {
|
|
|
361
360
|
return getAuth(opts);
|
|
362
361
|
}
|
|
363
362
|
function envSourceLabel(envVar) {
|
|
364
|
-
return
|
|
363
|
+
return `environment (${envVar})`;
|
|
365
364
|
}
|
|
366
365
|
function pickEnvSource(...envVars) {
|
|
367
366
|
return pickCliEnvironmentValue(envVars);
|
|
@@ -451,11 +450,6 @@ async function describeAuthProvenance() {
|
|
|
451
450
|
else if (tokens) effective = "saved-tokens";
|
|
452
451
|
else effective = "none";
|
|
453
452
|
if ((effective === "byok-refresh-token" || effective === "byok-access-token") && tokens) warnings.push("Environment credentials take priority over saved tokens. If you just ran `auth login --force`, unset or update the environment tokens.");
|
|
454
|
-
if (effective === "byok-refresh-token" && clientId && refreshTok) {
|
|
455
|
-
if (getAppliedEnvKeys().has(clientId.envVar) !== getAppliedEnvKeys().has(refreshTok.envVar)) warnings.push(`client_id and refresh_token come from different sources (${envSourceLabel(clientId.envVar)} vs ${envSourceLabel(refreshTok.envVar)}); they may not match.`);
|
|
456
|
-
}
|
|
457
|
-
const envFile = getLoadedEnvPath();
|
|
458
|
-
if (envFile && getAppliedEnvKeys().size > 0) warnings.push(`Loaded ${getAppliedEnvKeys().size} var(s) from ${displayPath(envFile)}.`);
|
|
459
453
|
return {
|
|
460
454
|
rows,
|
|
461
455
|
effective,
|
package/dist/cli.d.mts
CHANGED
|
@@ -2,7 +2,6 @@ import { CliRuntime, CreateCliRuntimeOptions, createCliRuntime } from "./runtime
|
|
|
2
2
|
export declare const main: import("citty").CommandDef<import("citty").ArgsDef>;
|
|
3
3
|
export interface RunCliOptions {
|
|
4
4
|
rawArgs?: string[];
|
|
5
|
-
loadEnv?: boolean;
|
|
6
5
|
environment?: Record<string, string | undefined>;
|
|
7
6
|
runtime?: CliRuntime;
|
|
8
7
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -5,7 +5,6 @@ import { parseAuthMode } from "./auth-state.mjs";
|
|
|
5
5
|
import { checkCliArgs } from "./cli-args.mjs";
|
|
6
6
|
import { resolveCliEnvironment } from "./environment.mjs";
|
|
7
7
|
import { applyProfileFromCli } from "./commands/profile-selection.mjs";
|
|
8
|
-
import { loadEnvFromCwd } from "./env-file.mjs";
|
|
9
8
|
import { resolveOutputOptions, terminalOutputOptions } from "./render/terminal.mjs";
|
|
10
9
|
import { VERSION, setNoColor, showSplash, withConfiguredOutput } from "./utils.mjs";
|
|
11
10
|
import process from "node:process";
|
|
@@ -98,7 +97,6 @@ async function runCli(opts = {}) {
|
|
|
98
97
|
return runWithCliRuntime(runtime, async () => {
|
|
99
98
|
let rawArgs = [...input];
|
|
100
99
|
try {
|
|
101
|
-
if (opts.loadEnv !== false) loadEnvFromCwd();
|
|
102
100
|
rawArgs = prepareCliArgs(input);
|
|
103
101
|
runtime.rawArgs = [...rawArgs];
|
|
104
102
|
const argumentError = await checkCliArgs(main, rawArgs);
|
package/dist/commands/bing.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { bingCommandMeta } from "../command-meta.mjs";
|
|
2
2
|
import { useCliRuntime } from "../runtime.mjs";
|
|
3
3
|
import { resolveAuthentication, saveAuthentication } from "../auth-state.mjs";
|
|
4
|
-
import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
|
|
4
|
+
import { OUTPUT_ARGS, applyOutputMode, logger, plural } from "../utils.mjs";
|
|
5
5
|
import { BING_REDIRECT_URI, clearBingCredentials, getBingClient, loginBingOAuth, saveBingCredentials } from "../bing-auth.mjs";
|
|
6
6
|
import { BING_DATASETS, dumpBingSite, parseBingDumpOptions, resolveBingSites, unwrapBing } from "../bing-data.mjs";
|
|
7
7
|
import { dumpHostedBingSite, hostedBingClient, inspectHostedBingUrl, listHostedBingSites, resolveHostedBingSites } from "../bing-hosted.mjs";
|
|
@@ -171,7 +171,7 @@ const bingCommand = defineCommand({
|
|
|
171
171
|
authenticated: true,
|
|
172
172
|
verifiedSites: sites.filter((site) => site.isVerified).length
|
|
173
173
|
}));
|
|
174
|
-
else logger.success(`Bing authenticated. ${sites.filter((site) => site.isVerified).length
|
|
174
|
+
else logger.success(`Bing authenticated. ${plural(sites.filter((site) => site.isVerified).length, "verified site", "verified sites")}.`);
|
|
175
175
|
}
|
|
176
176
|
}),
|
|
177
177
|
sites: defineCommand({
|
|
@@ -250,7 +250,7 @@ const bingCommand = defineCommand({
|
|
|
250
250
|
for (const site of sites) {
|
|
251
251
|
const summary = await dumpHostedBingSite(authentication, site, args.out, options);
|
|
252
252
|
summaries.push(summary);
|
|
253
|
-
if (!json) logger.success(`Bing ${site.siteUrl}: ${summary.files.length
|
|
253
|
+
if (!json) logger.success(`Bing ${site.siteUrl}: ${plural(summary.files.length, "file", "files")} exported to ${args.out}.`);
|
|
254
254
|
}
|
|
255
255
|
if (json) console.log(JSON.stringify(args["all-sites"] ? {
|
|
256
256
|
searchEngine: "bing",
|
|
@@ -267,7 +267,7 @@ const bingCommand = defineCommand({
|
|
|
267
267
|
for (const site of sites) {
|
|
268
268
|
const summary = await dumpBingSite(client, site, args.out, options);
|
|
269
269
|
summaries.push(summary);
|
|
270
|
-
if (!json) logger.success(`Bing ${site}: ${summary.files.length
|
|
270
|
+
if (!json) logger.success(`Bing ${site}: ${plural(summary.files.length, "file", "files")} exported to ${args.out}.`);
|
|
271
271
|
}
|
|
272
272
|
if (json) console.log(JSON.stringify(args["all-sites"] ? {
|
|
273
273
|
searchEngine: "bing",
|
package/dist/commands/doctor.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { doctorCommandMeta } from "../command-meta.mjs";
|
|
2
2
|
import { getHostedAccount, resolveAuthentication } from "../auth-state.mjs";
|
|
3
3
|
import { loadConfig } from "../config.mjs";
|
|
4
|
-
import { resolveCliEnvironment } from "../environment.mjs";
|
|
5
|
-
import { parseEnvFile } from "../env-file.mjs";
|
|
4
|
+
import { pickCliEnvironmentValue, resolveCliEnvironment } from "../environment.mjs";
|
|
6
5
|
import { OUTPUT_ARGS, applyOutputMode, displayPath, logger } from "../utils.mjs";
|
|
7
6
|
import { getAuth, isStaleServiceAccountPointer, loadTokens, resolveAuth, resolveBYOK, resolveServiceAccount } from "../auth.mjs";
|
|
8
7
|
import { createLocalStore } from "../local-store.mjs";
|
|
@@ -12,73 +11,19 @@ import { currentAccessToken, fetchTokenInfo, redactTokens } from "../token-info.
|
|
|
12
11
|
import process from "node:process";
|
|
13
12
|
import { defineCommand } from "citty";
|
|
14
13
|
import fs from "node:fs/promises";
|
|
15
|
-
import path from "node:path";
|
|
16
14
|
import { googleSearchConsole } from "gscdump/client";
|
|
17
15
|
import { resolveSiteInput } from "gscdump";
|
|
18
16
|
import { ofetch } from "ofetch";
|
|
19
17
|
const FETCH_TIMEOUT_MS = 5e3;
|
|
20
18
|
const TIME_SKEW_WARN_MS = 3e5;
|
|
21
19
|
const WATERMARK_STALE_DAYS_WARN = 7;
|
|
22
|
-
|
|
23
|
-
"GSC_ACCESS_TOKEN",
|
|
24
|
-
"GSC_CLIENT_ID",
|
|
25
|
-
"GSC_CLIENT_SECRET",
|
|
26
|
-
"GSC_REFRESH_TOKEN",
|
|
27
|
-
"GOOGLE_ACCESS_TOKEN",
|
|
28
|
-
"GOOGLE_CLIENT_ID",
|
|
29
|
-
"GOOGLE_CLIENT_SECRET",
|
|
30
|
-
"GOOGLE_REFRESH_TOKEN",
|
|
31
|
-
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
32
|
-
"GSC_SERVICE_ACCOUNT_JSON"
|
|
33
|
-
];
|
|
34
|
-
function redact(v) {
|
|
35
|
-
if (!v) return "<missing>";
|
|
36
|
-
if (v.length <= 6) return "***";
|
|
37
|
-
return `***${v.slice(-6)}`;
|
|
38
|
-
}
|
|
39
|
-
async function checkEnv() {
|
|
40
|
-
const envPath = path.join(process.cwd(), ".env");
|
|
41
|
-
const parsed = parseEnvFile(envPath);
|
|
42
|
-
if (!parsed) return {
|
|
43
|
-
checks: [{
|
|
44
|
-
name: "env",
|
|
45
|
-
status: "info",
|
|
46
|
-
detail: "no .env (using shell env / saved tokens)"
|
|
47
|
-
}],
|
|
48
|
-
envKeys: /* @__PURE__ */ new Set()
|
|
49
|
-
};
|
|
50
|
-
const relevant = RELEVANT_ENV_KEYS.filter((k) => parsed[k] !== void 0);
|
|
51
|
-
const envKeys = new Set(relevant);
|
|
52
|
-
if (relevant.length === 0) return {
|
|
53
|
-
checks: [{
|
|
54
|
-
name: "env",
|
|
55
|
-
status: "info",
|
|
56
|
-
detail: `${displayPath(envPath)} found, no auth vars`
|
|
57
|
-
}],
|
|
58
|
-
envKeys
|
|
59
|
-
};
|
|
60
|
-
const inventory = relevant.map((k) => {
|
|
61
|
-
const v = parsed[k];
|
|
62
|
-
if (k.endsWith("CLIENT_ID")) return `${k}=${v}`;
|
|
63
|
-
return `${k}=${redact(v)}`;
|
|
64
|
-
});
|
|
65
|
-
return {
|
|
66
|
-
checks: [{
|
|
67
|
-
name: "env",
|
|
68
|
-
status: "info",
|
|
69
|
-
detail: `${displayPath(envPath)} → ${inventory.join(", ")} (not validated — see auth)`
|
|
70
|
-
}],
|
|
71
|
-
envKeys
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function describeAuthSource(envKeys, byok) {
|
|
20
|
+
function describeAuthSource(byok) {
|
|
75
21
|
if (!byok) return "saved tokens";
|
|
76
22
|
const isAccessToken = typeof byok === "string";
|
|
77
|
-
const driver = isAccessToken ? "GSC_ACCESS_TOKEN" : "GSC_REFRESH_TOKEN";
|
|
78
|
-
|
|
79
|
-
return `environment credentials ${isAccessToken ? "(access-token)" : "(refresh-token)"} from ${source} via ${driver}`;
|
|
23
|
+
const driver = pickCliEnvironmentValue(isAccessToken ? ["GSC_ACCESS_TOKEN", "GOOGLE_ACCESS_TOKEN"] : ["GSC_REFRESH_TOKEN", "GOOGLE_REFRESH_TOKEN"])?.envVar ?? (isAccessToken ? "GSC_ACCESS_TOKEN" : "GSC_REFRESH_TOKEN");
|
|
24
|
+
return `environment credentials ${isAccessToken ? "(access-token)" : "(refresh-token)"} via ${driver}`;
|
|
80
25
|
}
|
|
81
|
-
async function checkAuth(
|
|
26
|
+
async function checkAuth() {
|
|
82
27
|
const checks = [];
|
|
83
28
|
const serviceAccount = await resolveServiceAccount().catch((error) => {
|
|
84
29
|
if (isStaleServiceAccountPointer(error)) {
|
|
@@ -153,7 +98,7 @@ async function checkAuth(envKeys) {
|
|
|
153
98
|
checks.push({
|
|
154
99
|
name: "auth",
|
|
155
100
|
status: "fail",
|
|
156
|
-
detail: `${describeAuthSource(
|
|
101
|
+
detail: `${describeAuthSource(byok)}: refresh failed: ${current.detail}. Run \`gscdump auth login --force\` and update the source above.`
|
|
157
102
|
});
|
|
158
103
|
return {
|
|
159
104
|
checks,
|
|
@@ -177,7 +122,7 @@ async function checkAuth(envKeys) {
|
|
|
177
122
|
checks.push({
|
|
178
123
|
name: "auth",
|
|
179
124
|
status: "pass",
|
|
180
|
-
detail: describeAuthSource(
|
|
125
|
+
detail: describeAuthSource(byok)
|
|
181
126
|
});
|
|
182
127
|
if (info.email) checks.push({
|
|
183
128
|
name: "auth.account",
|
|
@@ -410,9 +355,9 @@ async function checkHostedConnection(authentication) {
|
|
|
410
355
|
...sitesChecks
|
|
411
356
|
];
|
|
412
357
|
}
|
|
413
|
-
async function checkLocalConnection(
|
|
358
|
+
async function checkLocalConnection() {
|
|
414
359
|
const [authResult, timeChecks, gscApi, indexingApi, siteVerificationApi] = await Promise.all([
|
|
415
|
-
checkAuth(
|
|
360
|
+
checkAuth(),
|
|
416
361
|
checkTimeSkew(),
|
|
417
362
|
checkApiReachable("gsc.api", "https://searchconsole.googleapis.com/$discovery/rest?version=v1"),
|
|
418
363
|
checkApiReachable("indexing.api", "https://indexing.googleapis.com/$discovery/rest?version=v3"),
|
|
@@ -439,14 +384,12 @@ const doctorCommand = defineCommand({
|
|
|
439
384
|
const { json } = applyOutputMode(args);
|
|
440
385
|
const { dataDir } = await createCommandContext();
|
|
441
386
|
const authentication = await resolveAuthentication();
|
|
442
|
-
const envResult = await checkEnv();
|
|
443
387
|
const [connectionChecks, dataDirChecks, watermarkChecks] = await Promise.all([
|
|
444
|
-
authentication._tag === "Hosted" ? checkHostedConnection(authentication) : checkLocalConnection(
|
|
388
|
+
authentication._tag === "Hosted" ? checkHostedConnection(authentication) : checkLocalConnection(),
|
|
445
389
|
checkDataDir(dataDir),
|
|
446
390
|
checkStoreWatermarks(dataDir)
|
|
447
391
|
]);
|
|
448
392
|
const all = [
|
|
449
|
-
...envResult.checks,
|
|
450
393
|
...connectionChecks,
|
|
451
394
|
...dataDirChecks,
|
|
452
395
|
...watermarkChecks
|
package/dist/commands/init.mjs
CHANGED
|
@@ -2,17 +2,13 @@ import { initCommandMeta } from "../command-meta.mjs";
|
|
|
2
2
|
import { useCliRuntime } from "../runtime.mjs";
|
|
3
3
|
import { saveAuthentication } from "../auth-state.mjs";
|
|
4
4
|
import { defaultDataDir, loadConfig, saveConfig } from "../config.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { ACCESS_NOT_SET_UP, authenticate, getAuth, loadTokens, resolveBYOK, resolveServiceAccount, saveTokens } from "../auth.mjs";
|
|
5
|
+
import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
|
|
6
|
+
import { ACCESS_NOT_SET_UP, getAuth, loadTokens, resolveBYOK, resolveServiceAccount } from "../auth.mjs";
|
|
8
7
|
import { loginHosted } from "./auth.mjs";
|
|
9
8
|
import process from "node:process";
|
|
10
9
|
import { defineCommand } from "citty";
|
|
11
|
-
import
|
|
12
|
-
import path from "node:path";
|
|
13
|
-
import { confirm, isCancel, text } from "@clack/prompts";
|
|
10
|
+
import { isCancel, text } from "@clack/prompts";
|
|
14
11
|
import { googleSearchConsole } from "gscdump/client";
|
|
15
|
-
const ENV_LINE_RE = /^([^=]+)=(.*)$/;
|
|
16
12
|
function canPrompt() {
|
|
17
13
|
return process.stdin.isTTY === true;
|
|
18
14
|
}
|
|
@@ -27,24 +23,6 @@ async function promptDataDir(existing) {
|
|
|
27
23
|
if (isCancel(answer)) process.exit(1);
|
|
28
24
|
return String(answer) || fallback;
|
|
29
25
|
}
|
|
30
|
-
async function loadEnvFile() {
|
|
31
|
-
const envPath = path.join(process.cwd(), ".env");
|
|
32
|
-
const content = await fs.readFile(envPath, "utf-8").catch(() => null);
|
|
33
|
-
if (!content) return null;
|
|
34
|
-
const env = {};
|
|
35
|
-
for (const line of content.split("\n")) {
|
|
36
|
-
const trimmed = line.trim();
|
|
37
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
38
|
-
const match = trimmed.match(ENV_LINE_RE);
|
|
39
|
-
if (match) {
|
|
40
|
-
const key = match[1].trim();
|
|
41
|
-
let value = match[2].trim();
|
|
42
|
-
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
43
|
-
env[key] = value;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return env;
|
|
47
|
-
}
|
|
48
26
|
const initCommand = defineCommand({
|
|
49
27
|
meta: initCommandMeta,
|
|
50
28
|
args: {
|
|
@@ -121,40 +99,6 @@ const initCommand = defineCommand({
|
|
|
121
99
|
printNextSteps();
|
|
122
100
|
return;
|
|
123
101
|
}
|
|
124
|
-
const envFile = await loadEnvFile();
|
|
125
|
-
const envCid = envFile?.GSC_CLIENT_ID ?? envFile?.GOOGLE_CLIENT_ID;
|
|
126
|
-
const envSec = envFile?.GSC_CLIENT_SECRET ?? envFile?.GOOGLE_CLIENT_SECRET;
|
|
127
|
-
const envRef = envFile?.GSC_REFRESH_TOKEN ?? envFile?.GOOGLE_REFRESH_TOKEN;
|
|
128
|
-
const envAcc = envFile?.GSC_ACCESS_TOKEN ?? envFile?.GOOGLE_ACCESS_TOKEN;
|
|
129
|
-
if (envCid && envSec && envRef) {
|
|
130
|
-
logger.info("Found .env file with credentials");
|
|
131
|
-
applyCliEnvironment({
|
|
132
|
-
GOOGLE_ACCESS_TOKEN: envAcc,
|
|
133
|
-
GOOGLE_CLIENT_ID: envCid,
|
|
134
|
-
GOOGLE_CLIENT_SECRET: envSec,
|
|
135
|
-
GOOGLE_REFRESH_TOKEN: envRef
|
|
136
|
-
});
|
|
137
|
-
await saveConfig({
|
|
138
|
-
...config,
|
|
139
|
-
clientId: envCid,
|
|
140
|
-
clientSecret: envSec,
|
|
141
|
-
dataDir: config.dataDir ?? defaultDataDir()
|
|
142
|
-
});
|
|
143
|
-
const creds = (await authenticate({
|
|
144
|
-
clientId: envCid,
|
|
145
|
-
clientSecret: envSec
|
|
146
|
-
}, false)).credentials;
|
|
147
|
-
if (creds.access_token) await saveTokens({
|
|
148
|
-
access_token: creds.access_token,
|
|
149
|
-
refresh_token: creds.refresh_token || envRef,
|
|
150
|
-
expiry_date: creds.expiry_date
|
|
151
|
-
});
|
|
152
|
-
await saveAuthentication({ _tag: "Local" });
|
|
153
|
-
console.log();
|
|
154
|
-
logger.success("Setup complete using .env credentials.");
|
|
155
|
-
printNextSteps();
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
102
|
if (!canPrompt()) {
|
|
159
103
|
if (!await loadTokens()) {
|
|
160
104
|
logger.error(`Init cannot prompt without a terminal.\n${ACCESS_NOT_SET_UP}`);
|
|
@@ -180,7 +124,6 @@ const initCommand = defineCommand({
|
|
|
180
124
|
});
|
|
181
125
|
const sites = await runSmokeTest(await getAuth({ interactive: true }));
|
|
182
126
|
await saveAuthentication({ _tag: "Local" });
|
|
183
|
-
if (config.clientId && config.clientSecret) await maybeWriteEnvFile(config.clientId, config.clientSecret);
|
|
184
127
|
console.log();
|
|
185
128
|
logger.success("Setup complete.");
|
|
186
129
|
printNextSteps(sites);
|
|
@@ -229,32 +172,4 @@ async function runSmokeTest(oauth) {
|
|
|
229
172
|
logger.success(`Verified: ${sites.length} GSC site(s) accessible`);
|
|
230
173
|
return sites.flatMap((site) => site.siteUrl ? [site.siteUrl] : []);
|
|
231
174
|
}
|
|
232
|
-
async function maybeWriteEnvFile(clientId, clientSecret) {
|
|
233
|
-
const tokens = await loadTokens();
|
|
234
|
-
if (!tokens?.refresh_token) return;
|
|
235
|
-
const wants = await confirm({
|
|
236
|
-
message: "Write a `.env` file with these credentials? (handy for CI / other machines)",
|
|
237
|
-
initialValue: false
|
|
238
|
-
});
|
|
239
|
-
if (isCancel(wants) || !wants) return;
|
|
240
|
-
const envPath = path.join(process.cwd(), ".env");
|
|
241
|
-
if (await fs.stat(envPath).then(() => true).catch(() => false)) {
|
|
242
|
-
const overwrite = await confirm({
|
|
243
|
-
message: `${displayPath(envPath)} exists. Overwrite?`,
|
|
244
|
-
initialValue: false
|
|
245
|
-
});
|
|
246
|
-
if (isCancel(overwrite) || !overwrite) {
|
|
247
|
-
logger.info(`Skipped — keep credentials at ${displayPath(envPath)} manually if needed`);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
const content = [
|
|
252
|
-
`GSC_CLIENT_ID=${clientId}`,
|
|
253
|
-
`GSC_CLIENT_SECRET=${clientSecret}`,
|
|
254
|
-
`GSC_REFRESH_TOKEN=${tokens.refresh_token}`,
|
|
255
|
-
""
|
|
256
|
-
].join("\n");
|
|
257
|
-
await fs.writeFile(envPath, content, { mode: 384 });
|
|
258
|
-
logger.success(`Wrote ${displayPath(envPath)}`);
|
|
259
|
-
}
|
|
260
175
|
export { initCommand, printHostedNextSteps, printNextSteps, runSmokeTest };
|
package/dist/commands/query.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { queryCommandMeta } from "../command-meta.mjs";
|
|
2
|
+
import { queryHostedRows } from "../hosted-query.mjs";
|
|
2
3
|
import { useCliRuntime } from "../runtime.mjs";
|
|
3
4
|
import { resolveAuthentication } from "../auth-state.mjs";
|
|
4
5
|
import { loadConfig } from "../config.mjs";
|
|
@@ -12,14 +13,13 @@ import { renderTable } from "../render/layout.mjs";
|
|
|
12
13
|
import { asRecord, columnsFor } from "../render/analysis.mjs";
|
|
13
14
|
import { resolveHostedSite } from "../hosted-site.mjs";
|
|
14
15
|
import { FILTER_DIMS, filterDimensions, parseFilterArgs, toLiveFilter, toLocalFilter } from "../filters.mjs";
|
|
15
|
-
import { queryHostedRows } from "../hosted-query.mjs";
|
|
16
16
|
import { renderQuery } from "../render/query.mjs";
|
|
17
17
|
import { openSqlViews, referencedTables } from "../sql-views.mjs";
|
|
18
18
|
import process from "node:process";
|
|
19
19
|
import { defineCommand } from "citty";
|
|
20
20
|
import fs from "node:fs/promises";
|
|
21
21
|
import { and, between, country, date, device, gsc, hour, page, query, searchAppearance } from "gscdump/query";
|
|
22
|
-
import { getLatestGscDate } from "gscdump/dates";
|
|
22
|
+
import { getLatestGscDate, toIsoDate } from "gscdump/dates";
|
|
23
23
|
import { cancel, isCancel, multiselect, text } from "@clack/prompts";
|
|
24
24
|
import { collectSpans } from "@gscdump/engine/profile";
|
|
25
25
|
import { inferDataset, isDatasetResolvable } from "gscdump/query/plan";
|
|
@@ -431,7 +431,7 @@ const queryCommand = defineCommand({
|
|
|
431
431
|
end: endDate
|
|
432
432
|
},
|
|
433
433
|
total: result.rows.length,
|
|
434
|
-
data: result.rows,
|
|
434
|
+
data: result.rows.map(withIsoDate),
|
|
435
435
|
meta: { source: "local" }
|
|
436
436
|
},
|
|
437
437
|
format,
|
|
@@ -547,7 +547,14 @@ function buildLocalState(dimNames, startDate, endDate, rowLimit, dimensionFilter
|
|
|
547
547
|
const dims = dimNames.map((d) => DIM_COLUMNS[d]).filter((c) => Boolean(c));
|
|
548
548
|
const dateFilter = between(date, startDate, endDate);
|
|
549
549
|
const filter = dimensionFilter ? and(dateFilter, dimensionFilter) : dateFilter;
|
|
550
|
-
|
|
550
|
+
const builder = gsc.select(...dims).where(filter);
|
|
551
|
+
return (dimNames.includes("date") ? builder.orderBy(date, "asc") : builder).limit(rowLimit).getState();
|
|
552
|
+
}
|
|
553
|
+
function withIsoDate(row) {
|
|
554
|
+
return typeof row.date === "number" ? {
|
|
555
|
+
...row,
|
|
556
|
+
date: toIsoDate(new Date(row.date))
|
|
557
|
+
} : row;
|
|
551
558
|
}
|
|
552
559
|
async function runSqlMode(opts) {
|
|
553
560
|
const ctx = await createCommandContext({ needsStore: true });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { sitemapsCommandMeta } from "../command-meta.mjs";
|
|
2
|
-
import { OUTPUT_ARGS, applyOutputMode, logger, parseIntegerOption } from "../utils.mjs";
|
|
2
|
+
import { OUTPUT_ARGS, applyOutputMode, logger, parseIntegerOption, plural } from "../utils.mjs";
|
|
3
3
|
import { createCommandContext } from "../context.mjs";
|
|
4
4
|
import { discoverLiveSitemap, loadSitemapUrls } from "../sitemap.mjs";
|
|
5
5
|
import { HOSTED_ARGS, resolveHostedSite } from "../hosted-site.mjs";
|
|
@@ -59,12 +59,12 @@ const listCommand = defineCommand({
|
|
|
59
59
|
logger.warn("No sitemaps found");
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
|
-
logger.success(`Found ${sitemaps.length
|
|
62
|
+
logger.success(`Found ${plural(sitemaps.length, "sitemap", "sitemaps")}:`);
|
|
63
63
|
console.log();
|
|
64
64
|
for (const sm of sitemaps) {
|
|
65
65
|
const pending = sm.isPending ? " \x1B[33m(pending)\x1B[0m" : "";
|
|
66
|
-
const errors = sm.errors ? ` \x1B[31m${sm.errors
|
|
67
|
-
const warnings = sm.warnings ? ` \x1B[33m${sm.warnings
|
|
66
|
+
const errors = sm.errors ? ` \x1B[31m${plural(sm.errors, "error", "errors")}\x1B[0m` : "";
|
|
67
|
+
const warnings = sm.warnings ? ` \x1B[33m${plural(sm.warnings, "warning", "warnings")}\x1B[0m` : "";
|
|
68
68
|
const submitted = sm.lastSubmitted ? ` \x1B[90msubmitted ${sm.lastSubmitted}\x1B[0m` : "";
|
|
69
69
|
console.log(` ${sm.path}${pending}${errors}${warnings}${submitted}`);
|
|
70
70
|
}
|
package/dist/commands/sites.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sitesCommandMeta } from "../command-meta.mjs";
|
|
2
2
|
import { LOCAL_MODE_REQUIRED, formatHostedSync, getHostedAccount, resolveAuthentication } from "../auth-state.mjs";
|
|
3
|
-
import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
|
|
3
|
+
import { OUTPUT_ARGS, applyOutputMode, logger, plural } from "../utils.mjs";
|
|
4
4
|
import { createCommandContext, formatSiteResolution } from "../context.mjs";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { defineCommand } from "citty";
|
|
@@ -362,7 +362,7 @@ const verifyListCommand = defineCommand({
|
|
|
362
362
|
logger.warn("No verified WebResources found");
|
|
363
363
|
return;
|
|
364
364
|
}
|
|
365
|
-
logger.success(`${resources.length
|
|
365
|
+
logger.success(`${plural(resources.length, "verified WebResource", "verified WebResources")}:`);
|
|
366
366
|
console.log();
|
|
367
367
|
for (const r of resources) {
|
|
368
368
|
const id = r.id ?? "?";
|
|
@@ -441,7 +441,7 @@ async function runListHostedSites(args, authentication) {
|
|
|
441
441
|
logger.warn("Your hosted record has no Sites. Connect a Site at https://gscdump.com/app/onboarding?step=connect-sites.");
|
|
442
442
|
return;
|
|
443
443
|
}
|
|
444
|
-
logger.success(`Found ${sites.length
|
|
444
|
+
logger.success(`Found ${plural(sites.length, "hosted Site", "hosted Sites")}:`);
|
|
445
445
|
console.log();
|
|
446
446
|
for (const site of sites) console.log(` ${site.siteUrl} \x1B[90m(${site.siteId})\x1B[0m hosted sync ${formatHostedSync(site)}`);
|
|
447
447
|
}
|
|
@@ -470,10 +470,10 @@ async function runListSites(args) {
|
|
|
470
470
|
return;
|
|
471
471
|
}
|
|
472
472
|
if (sites.length === 0) {
|
|
473
|
-
logger.warn(ownerOnly ? "No owned
|
|
473
|
+
logger.warn(ownerOnly ? "No owned Sites found" : "No verified Sites found");
|
|
474
474
|
return;
|
|
475
475
|
}
|
|
476
|
-
logger.success(`Found ${sites.length
|
|
476
|
+
logger.success(`Found ${ownerOnly ? plural(sites.length, "owned Site", "owned Sites") : plural(sites.length, "verified Site", "verified Sites")}:`);
|
|
477
477
|
console.log();
|
|
478
478
|
for (const site of sites) {
|
|
479
479
|
const perm = site.permissionLevel === "siteOwner" ? "\x1B[32m" : "\x1B[90m";
|
|
@@ -492,10 +492,10 @@ async function runListSites(args) {
|
|
|
492
492
|
return;
|
|
493
493
|
}
|
|
494
494
|
if (sites.length === 0) {
|
|
495
|
-
logger.warn(ownerOnly ? "No owned
|
|
495
|
+
logger.warn(ownerOnly ? "No owned Sites found" : "No verified Sites found");
|
|
496
496
|
return;
|
|
497
497
|
}
|
|
498
|
-
logger.success(`Found ${sites.length
|
|
498
|
+
logger.success(`Found ${ownerOnly ? plural(sites.length, "owned Site", "owned Sites") : plural(sites.length, "Site", "Sites")}:`);
|
|
499
499
|
console.log();
|
|
500
500
|
for (const site of sites) {
|
|
501
501
|
const perm = site.permissionLevel === "siteOwner" ? "\x1B[32m" : "\x1B[90m";
|
package/dist/coverage.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { quotaLedgerPath, quotaStatus, readLedgerState } from "./quota-ledger.mjs";
|
|
2
|
+
import { plural } from "./utils.mjs";
|
|
2
3
|
import { INSPECTION_QPD_PER_PROPERTY } from "./inspection-record.mjs";
|
|
3
4
|
import { inspectionCandidates, loadInspectionState } from "./local-entities.mjs";
|
|
4
5
|
import { datesForJob, jobWindowStart } from "./sync-plan.mjs";
|
|
@@ -117,9 +118,6 @@ function hasSyncedDays(states, table, searchType) {
|
|
|
117
118
|
function n(value) {
|
|
118
119
|
return value.toLocaleString("en-US");
|
|
119
120
|
}
|
|
120
|
-
function plural(count, one, many) {
|
|
121
|
-
return `${n(count)} ${count === 1 ? one : many}`;
|
|
122
|
-
}
|
|
123
121
|
function clock(at) {
|
|
124
122
|
return new Date(at).toLocaleString("en-US", {
|
|
125
123
|
hour: "numeric",
|
package/dist/environment.mjs
CHANGED
|
@@ -23,8 +23,4 @@ function pickCliEnvironmentValue(names, values = useCliRuntime().environment) {
|
|
|
23
23
|
}
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
for (const [key, value] of Object.entries(updates)) if (value === void 0) delete values[key];
|
|
28
|
-
else values[key] = value;
|
|
29
|
-
}
|
|
30
|
-
export { applyCliEnvironment, pickCliEnvironmentValue, resolveCliEnvironment };
|
|
26
|
+
export { pickCliEnvironmentValue, resolveCliEnvironment };
|
package/dist/error-handler.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { isUsageError } from "./command-registry.mjs";
|
|
2
|
+
import { describeRecordReadRefusal } from "./hosted-query.mjs";
|
|
2
3
|
import { quotaStopOf } from "./quota-ledger.mjs";
|
|
3
4
|
import { isAnalysisError } from "@gscdump/analysis/errors";
|
|
5
|
+
import { parseRecordReadRefusal } from "@gscdump/contracts";
|
|
4
6
|
import { isEngineError } from "@gscdump/engine/errors";
|
|
5
7
|
import { classifyError } from "gscdump/errors";
|
|
6
8
|
import { isQueryError } from "gscdump/query";
|
|
@@ -103,6 +105,10 @@ const DEFECT_ERRORS = /* @__PURE__ */ new Set([
|
|
|
103
105
|
"EvalError",
|
|
104
106
|
"URIError"
|
|
105
107
|
]);
|
|
108
|
+
function recordReadRefusalOf(error) {
|
|
109
|
+
if (!(error instanceof Error) || error.name !== "GscdumpV1Error") return null;
|
|
110
|
+
return parseRecordReadRefusal(error.details);
|
|
111
|
+
}
|
|
106
112
|
function describeCliError(error) {
|
|
107
113
|
if (isUsageError(error)) return {
|
|
108
114
|
kind: "usage",
|
|
@@ -136,6 +142,12 @@ function describeCliError(error) {
|
|
|
136
142
|
hint: error.mode === "local" ? "Pass --live to run against the GSC API." : "",
|
|
137
143
|
showAuthSources: false
|
|
138
144
|
};
|
|
145
|
+
const refusal = recordReadRefusalOf(error);
|
|
146
|
+
if (refusal) return {
|
|
147
|
+
kind: "expected",
|
|
148
|
+
...describeRecordReadRefusal(refusal),
|
|
149
|
+
showAuthSources: false
|
|
150
|
+
};
|
|
139
151
|
const classified = classifyError(error);
|
|
140
152
|
if (classified.message === HOSTED_KEY_REJECTED) return {
|
|
141
153
|
kind: "expected",
|
package/dist/hosted-query.mjs
CHANGED
|
@@ -28,4 +28,26 @@ async function queryHostedRows(client, siteId, state) {
|
|
|
28
28
|
}
|
|
29
29
|
return rows;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
function describeRecordReadRefusal(refusal) {
|
|
32
|
+
const running = refusal.syncStatus === "syncing" || refusal.syncStatus === "pending";
|
|
33
|
+
if (refusal.reason === "record_not_ready") return {
|
|
34
|
+
message: "The Site's record is not readable yet.",
|
|
35
|
+
hint: refusal.syncStatus === "synced" && refusal.lastSyncAt !== void 0 ? `Sync finished at ${utcMinute(refusal.lastSyncAt)}. gscdump prepares the record for reads after Sync. Try again in a few minutes.` : running ? "Sync is still running. Run `gscdump sites` to see its progress, then try again when it finishes." : "Try again in a few minutes. Run `gscdump sites` to see the Sync state."
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
message: `The Site's record does not hold ${refusal.missingStart === refusal.missingEnd ? refusal.missingStart : `${refusal.missingStart} to ${refusal.missingEnd}`}.`,
|
|
39
|
+
hint: rangeHint(refusal, running)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function rangeHint(refusal, running) {
|
|
43
|
+
const { oldestDateSynced: oldest, newestDateSynced: newest } = refusal;
|
|
44
|
+
const synced = oldest !== void 0 && newest !== void 0;
|
|
45
|
+
if (synced && refusal.missingStart >= oldest && refusal.missingEnd <= newest) return "Sync covers these days, but the record does not hold them yet. Pick an earlier end date with --end, or try again later.";
|
|
46
|
+
const covers = synced ? `Sync covers ${oldest} to ${newest}. ` : "";
|
|
47
|
+
if (running) return `${covers}Sync is still running. Try again when it finishes.`;
|
|
48
|
+
return synced ? `${covers}Pick dates in that range with --start and --end.` : "Run `gscdump sites` to see the Sync state.";
|
|
49
|
+
}
|
|
50
|
+
function utcMinute(unixSeconds) {
|
|
51
|
+
return `${(/* @__PURE__ */ new Date(unixSeconds * 1e3)).toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
52
|
+
}
|
|
53
|
+
export { describeRecordReadRefusal, queryHostedRows };
|
package/dist/mcp/types.mjs
CHANGED
package/dist/package.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var version = "
|
|
1
|
+
var version = "5.0.0";
|
|
2
2
|
export { version };
|
package/dist/render/query.mjs
CHANGED
|
@@ -21,7 +21,7 @@ function renderQuery(input, options) {
|
|
|
21
21
|
if (clicks !== -1) columns[clicks] = barColumn(input.rows, "clicks", options);
|
|
22
22
|
}
|
|
23
23
|
return [
|
|
24
|
-
...textLines(`${parseGscSiteUrl(input.site).hostname} /
|
|
24
|
+
...textLines(`${parseGscSiteUrl(input.site).hostname} / ${input.dimensions.join(", ")}`, options, "accent"),
|
|
25
25
|
...textLines(`${input.start} to ${input.end}`, options, "muted"),
|
|
26
26
|
"",
|
|
27
27
|
...renderTable(input.rows, columns, options)
|
package/dist/sync-plan.mjs
CHANGED
package/dist/utils.mjs
CHANGED
|
@@ -156,6 +156,9 @@ function displayPath(absPath) {
|
|
|
156
156
|
if (absPath.startsWith(`${home}/`)) return `~/${absPath.slice(home.length + 1)}`;
|
|
157
157
|
return absPath;
|
|
158
158
|
}
|
|
159
|
+
function plural(count, one, many) {
|
|
160
|
+
return `${count.toLocaleString("en-US")} ${count === 1 ? one : many}`;
|
|
161
|
+
}
|
|
159
162
|
function formatAge(ms) {
|
|
160
163
|
const delta = Date.now() - ms;
|
|
161
164
|
if (delta < 6e4) return "just now";
|
|
@@ -193,4 +196,4 @@ async function readUrlList(input) {
|
|
|
193
196
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
194
197
|
return parseUrlLines(Buffer.concat(chunks).toString("utf-8"));
|
|
195
198
|
}
|
|
196
|
-
export { ALL_SEARCH_TYPES, OUTPUT_ARGS, VERSION, applyOutputMode, clearLine, color, cyan, dim, displayPath, formatAge, isColorEnabled, logger, parseFetchBudget, parseIntegerOption, parseNameList, parseSearchType, progressBar, readUrlList, red, runWithConcurrency, setNoColor, setQuiet, showSplash, toCSV, withConfiguredOutput };
|
|
199
|
+
export { ALL_SEARCH_TYPES, OUTPUT_ARGS, VERSION, applyOutputMode, clearLine, color, cyan, dim, displayPath, formatAge, isColorEnabled, logger, parseFetchBudget, parseIntegerOption, parseNameList, parseSearchType, plural, progressBar, readUrlList, red, runWithConcurrency, setNoColor, setQuiet, showSplash, toCSV, withConfiguredOutput };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "5.0.0",
|
|
5
5
|
"description": "CLI for Google Search Console and Bing with hosted or local authentication, data exports, and an MCP server",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -44,16 +44,16 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@clack/prompts": "^1.8.1",
|
|
46
46
|
"@duckdb/node-api": "1.5.5-r.5",
|
|
47
|
-
"@gscdump/analysis": "^
|
|
48
|
-
"@gscdump/contracts": "^
|
|
49
|
-
"@gscdump/engine": "^
|
|
50
|
-
"@gscdump/engine-gsc-api": "^
|
|
51
|
-
"@gscdump/sdk": "^
|
|
47
|
+
"@gscdump/analysis": "^5.0.0",
|
|
48
|
+
"@gscdump/contracts": "^5.0.0",
|
|
49
|
+
"@gscdump/engine": "^5.0.0",
|
|
50
|
+
"@gscdump/engine-gsc-api": "^5.0.0",
|
|
51
|
+
"@gscdump/sdk": "^5.0.0",
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
53
53
|
"citty": "^0.2.2",
|
|
54
54
|
"consola": "^3.4.2",
|
|
55
55
|
"google-auth-library": "^11.1.0",
|
|
56
|
-
"gscdump": "^
|
|
56
|
+
"gscdump": "^5.0.0",
|
|
57
57
|
"ofetch": "^1.5.1",
|
|
58
58
|
"open": "^11.0.4",
|
|
59
59
|
"proper-lockfile": "^4.1.2",
|
package/dist/env-file.mjs
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { useCliRuntime } from "./runtime.mjs";
|
|
2
|
-
import process from "node:process";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
const ENV_LINE_RE = /^([^=]+)=(.*)$/;
|
|
6
|
-
function parseEnvFile(envPath) {
|
|
7
|
-
let content;
|
|
8
|
-
try {
|
|
9
|
-
content = fs.readFileSync(envPath, "utf-8");
|
|
10
|
-
} catch {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
const env = {};
|
|
14
|
-
for (const line of content.split("\n")) {
|
|
15
|
-
const trimmed = line.trim();
|
|
16
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
17
|
-
const match = trimmed.match(ENV_LINE_RE);
|
|
18
|
-
if (!match) continue;
|
|
19
|
-
const key = match[1].trim();
|
|
20
|
-
let value = match[2].trim();
|
|
21
|
-
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
22
|
-
env[key] = value;
|
|
23
|
-
}
|
|
24
|
-
return env;
|
|
25
|
-
}
|
|
26
|
-
const appliedEnvKeys = /* @__PURE__ */ new Set();
|
|
27
|
-
let loadedEnvPath = null;
|
|
28
|
-
function getAppliedEnvKeys() {
|
|
29
|
-
return appliedEnvKeys;
|
|
30
|
-
}
|
|
31
|
-
function getLoadedEnvPath() {
|
|
32
|
-
return loadedEnvPath;
|
|
33
|
-
}
|
|
34
|
-
function loadEnvFromCwd() {
|
|
35
|
-
return loadEnvFile({
|
|
36
|
-
cwd: process.cwd(),
|
|
37
|
-
env: useCliRuntime().environment
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
function loadEnvFile(opts) {
|
|
41
|
-
const envPath = path.join(opts.cwd, ".env");
|
|
42
|
-
const parsed = parseEnvFile(envPath);
|
|
43
|
-
if (!parsed) return [];
|
|
44
|
-
loadedEnvPath = envPath;
|
|
45
|
-
const applied = [];
|
|
46
|
-
for (const [key, value] of Object.entries(parsed)) if (opts.env[key] === void 0) {
|
|
47
|
-
opts.env[key] = value;
|
|
48
|
-
applied.push(key);
|
|
49
|
-
appliedEnvKeys.add(key);
|
|
50
|
-
}
|
|
51
|
-
return applied;
|
|
52
|
-
}
|
|
53
|
-
export { getAppliedEnvKeys, getLoadedEnvPath, loadEnvFile, loadEnvFromCwd, parseEnvFile };
|