@gscdump/cli 0.37.3 → 0.37.5
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/bin/gscdump.mjs +3 -0
- package/dist/_chunks/analysis-local.mjs +88 -0
- package/dist/_chunks/analyze.mjs +332 -0
- package/dist/_chunks/auth.mjs +476 -0
- package/dist/_chunks/auth2.mjs +293 -0
- package/dist/_chunks/config.mjs +93 -0
- package/dist/_chunks/config2.mjs +232 -0
- package/dist/_chunks/context.mjs +69 -0
- package/dist/_chunks/doctor.mjs +400 -0
- package/dist/_chunks/dump.mjs +193 -0
- package/dist/_chunks/entities.mjs +414 -0
- package/dist/_chunks/env-file.mjs +53 -0
- package/dist/_chunks/environment.mjs +30 -0
- package/dist/_chunks/error-handler.mjs +86 -0
- package/dist/_chunks/indexing.mjs +314 -0
- package/dist/_chunks/init.mjs +190 -0
- package/dist/_chunks/inspect.mjs +203 -0
- package/dist/_chunks/mcp.mjs +867 -0
- package/dist/_chunks/native-duckdb.mjs +46 -0
- package/dist/_chunks/profile.mjs +290 -0
- package/dist/_chunks/query.mjs +537 -0
- package/dist/_chunks/report.mjs +243 -0
- package/dist/_chunks/sitemaps.mjs +274 -0
- package/dist/_chunks/sites.mjs +500 -0
- package/dist/_chunks/store.mjs +652 -0
- package/dist/_chunks/sync.mjs +489 -0
- package/dist/_chunks/utils.mjs +191 -0
- package/dist/cli.d.mts +28 -0
- package/dist/cli.mjs +104 -0
- package/package.json +15 -7
- package/dist/index.d.mts +0 -1
- package/dist/index.mjs +0 -7330
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
|
+
import { dateColumnsFor } from "@gscdump/engine/schema";
|
|
3
|
+
import { sqlEscape } from "@gscdump/engine/sql";
|
|
4
|
+
import { dateReplaceClause } from "@gscdump/engine/sql-fragments";
|
|
5
|
+
function parquetFileListSql(filePaths) {
|
|
6
|
+
return filePaths.map((filePath) => `'${sqlEscape(filePath)}'`).join(", ");
|
|
7
|
+
}
|
|
8
|
+
async function loadDuckDB() {
|
|
9
|
+
return import("@duckdb/node-api");
|
|
10
|
+
}
|
|
11
|
+
async function readParquetRows(filePaths, table) {
|
|
12
|
+
const { DuckDBInstance } = await loadDuckDB();
|
|
13
|
+
const instance = await DuckDBInstance.create(":memory:");
|
|
14
|
+
const conn = await instance.connect();
|
|
15
|
+
try {
|
|
16
|
+
const replace = dateReplaceClause(dateColumnsFor(table), "string");
|
|
17
|
+
return (await conn.runAndReadAll(`SELECT * ${replace} FROM read_parquet([${parquetFileListSql(filePaths)}], union_by_name=true)`)).getRowObjects();
|
|
18
|
+
} finally {
|
|
19
|
+
conn.closeSync();
|
|
20
|
+
instance.closeSync();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function materializeParquetTables(outPath, tables, force = false) {
|
|
24
|
+
if (force) await rm(outPath, { force: true });
|
|
25
|
+
const { DuckDBInstance } = await loadDuckDB();
|
|
26
|
+
const instance = await DuckDBInstance.create(outPath);
|
|
27
|
+
const conn = await instance.connect();
|
|
28
|
+
const results = [];
|
|
29
|
+
try {
|
|
30
|
+
for (const input of tables) {
|
|
31
|
+
const replace = dateReplaceClause(dateColumnsFor(input.table), "date");
|
|
32
|
+
await conn.run(`CREATE OR REPLACE TABLE ${input.table} AS SELECT * ${replace} FROM read_parquet([${parquetFileListSql(input.filePaths)}], union_by_name=true)`);
|
|
33
|
+
const rows = (await conn.runAndReadAll(`SELECT count(*)::BIGINT AS n FROM ${input.table}`)).getRowObjects();
|
|
34
|
+
results.push({
|
|
35
|
+
table: input.table,
|
|
36
|
+
files: input.filePaths.length,
|
|
37
|
+
rows: Number(rows[0]?.n ?? 0)
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
} finally {
|
|
41
|
+
conn.closeSync();
|
|
42
|
+
instance.closeSync();
|
|
43
|
+
}
|
|
44
|
+
return results;
|
|
45
|
+
}
|
|
46
|
+
export { materializeParquetTables, readParquetRows };
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { __exportAll, getConfigDir, setConfigDir, useCliRuntime } from "./config.mjs";
|
|
2
|
+
import { resolveCliEnvironment } from "./environment.mjs";
|
|
3
|
+
import { OUTPUT_ARGS, applyOutputMode, displayPath, logger, noSubcommandSelected } from "./utils.mjs";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { defineCommand } from "citty";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import fs$1 from "node:fs/promises";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { confirm, isCancel } from "@clack/prompts";
|
|
11
|
+
var profile_exports = /* @__PURE__ */ __exportAll({
|
|
12
|
+
adoptCurrentConfigAsProfile: () => adoptCurrentConfigAsProfile,
|
|
13
|
+
applyProfileFromCli: () => applyProfileFromCli,
|
|
14
|
+
createProfile: () => createProfile,
|
|
15
|
+
getProfileDir: () => getProfileDir,
|
|
16
|
+
listProfiles: () => listProfiles,
|
|
17
|
+
profileCommand: () => profileCommand,
|
|
18
|
+
profileNameFromEmail: () => profileNameFromEmail,
|
|
19
|
+
resolveActiveProfile: () => resolveActiveProfile,
|
|
20
|
+
setActiveProfile: () => setActiveProfile
|
|
21
|
+
});
|
|
22
|
+
const ROOT_DIR = path.join(os.homedir(), ".config", "gscdump");
|
|
23
|
+
const PROFILES_DIR = path.join(ROOT_DIR, "profiles");
|
|
24
|
+
const ACTIVE_MARKER = path.join(ROOT_DIR, "active-profile");
|
|
25
|
+
function getProfileDir(name) {
|
|
26
|
+
return path.join(PROFILES_DIR, name);
|
|
27
|
+
}
|
|
28
|
+
function readActiveMarkerSync() {
|
|
29
|
+
if (!fs.existsSync(ACTIVE_MARKER)) return null;
|
|
30
|
+
return fs.readFileSync(ACTIVE_MARKER, "utf-8").trim() || null;
|
|
31
|
+
}
|
|
32
|
+
function resolveActiveProfile() {
|
|
33
|
+
return useCliRuntime().activeProfileOverride ?? resolveCliEnvironment().profile ?? readActiveMarkerSync();
|
|
34
|
+
}
|
|
35
|
+
function applyProfileFromCli(opts) {
|
|
36
|
+
if (opts.configDir) {
|
|
37
|
+
setConfigDir(opts.configDir);
|
|
38
|
+
useCliRuntime().configDirOverridden = true;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (opts.profile) {
|
|
42
|
+
useCliRuntime().activeProfileOverride = opts.profile;
|
|
43
|
+
setConfigDir(getProfileDir(opts.profile));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const envProfile = opts.envProfile ?? resolveCliEnvironment().profile;
|
|
47
|
+
if (envProfile) {
|
|
48
|
+
setConfigDir(getProfileDir(envProfile));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const marker = readActiveMarkerSync();
|
|
52
|
+
if (marker) setConfigDir(getProfileDir(marker));
|
|
53
|
+
}
|
|
54
|
+
async function setActiveProfile(name) {
|
|
55
|
+
await fs$1.mkdir(ROOT_DIR, {
|
|
56
|
+
recursive: true,
|
|
57
|
+
mode: 448
|
|
58
|
+
});
|
|
59
|
+
if (name == null) {
|
|
60
|
+
await fs$1.rm(ACTIVE_MARKER, { force: true });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
await fs$1.writeFile(ACTIVE_MARKER, name, { mode: 384 });
|
|
64
|
+
}
|
|
65
|
+
async function listProfiles() {
|
|
66
|
+
return fs$1.readdir(PROFILES_DIR).then((entries) => entries.filter((e) => !e.startsWith(".")).sort()).catch(() => []);
|
|
67
|
+
}
|
|
68
|
+
async function createProfile(name) {
|
|
69
|
+
const dir = getProfileDir(name);
|
|
70
|
+
await fs$1.mkdir(dir, {
|
|
71
|
+
recursive: true,
|
|
72
|
+
mode: 448
|
|
73
|
+
});
|
|
74
|
+
return dir;
|
|
75
|
+
}
|
|
76
|
+
function profileNameFromEmail(email) {
|
|
77
|
+
return email.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
78
|
+
}
|
|
79
|
+
async function adoptCurrentConfigAsProfile(name) {
|
|
80
|
+
const runtime = useCliRuntime();
|
|
81
|
+
if (runtime.configDirOverridden) return null;
|
|
82
|
+
if (resolveActiveProfile()) return null;
|
|
83
|
+
const currentDir = getConfigDir();
|
|
84
|
+
const targetDir = getProfileDir(name);
|
|
85
|
+
if (currentDir === targetDir) return targetDir;
|
|
86
|
+
await fs$1.mkdir(targetDir, {
|
|
87
|
+
recursive: true,
|
|
88
|
+
mode: 448
|
|
89
|
+
});
|
|
90
|
+
for (const f of ["tokens.json", "config.json"]) {
|
|
91
|
+
const src = path.join(currentDir, f);
|
|
92
|
+
const dst = path.join(targetDir, f);
|
|
93
|
+
if (await fs$1.stat(src).then(() => true).catch(() => false)) await fs$1.rename(src, dst);
|
|
94
|
+
}
|
|
95
|
+
await setActiveProfile(name);
|
|
96
|
+
runtime.activeProfileOverride = name;
|
|
97
|
+
setConfigDir(targetDir);
|
|
98
|
+
return targetDir;
|
|
99
|
+
}
|
|
100
|
+
const listCmd = defineCommand({
|
|
101
|
+
meta: {
|
|
102
|
+
name: "list",
|
|
103
|
+
description: "List configured profiles"
|
|
104
|
+
},
|
|
105
|
+
args: { ...OUTPUT_ARGS },
|
|
106
|
+
async run({ args }) {
|
|
107
|
+
const { json } = applyOutputMode(args);
|
|
108
|
+
const names = await listProfiles();
|
|
109
|
+
const active = resolveActiveProfile();
|
|
110
|
+
if (json) {
|
|
111
|
+
console.log(JSON.stringify({
|
|
112
|
+
active,
|
|
113
|
+
profiles: names
|
|
114
|
+
}, null, 2));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (names.length === 0) {
|
|
118
|
+
logger.warn(`No profiles in ${PROFILES_DIR}`);
|
|
119
|
+
logger.info("Run `gscdump auth login` to create one automatically, or `gscdump profile create <name>`");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
for (const n of names) console.log(`${n === active ? "*" : " "} ${n}`);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
const profileCommand = defineCommand({
|
|
126
|
+
meta: {
|
|
127
|
+
name: "profile",
|
|
128
|
+
description: "Manage gscdump profiles (per-account token + config dirs)"
|
|
129
|
+
},
|
|
130
|
+
subCommands: {
|
|
131
|
+
list: listCmd,
|
|
132
|
+
path: defineCommand({
|
|
133
|
+
meta: {
|
|
134
|
+
name: "path",
|
|
135
|
+
description: "Print the config directory for a profile"
|
|
136
|
+
},
|
|
137
|
+
args: { name: {
|
|
138
|
+
type: "positional",
|
|
139
|
+
required: false,
|
|
140
|
+
description: "Profile name (default: active)"
|
|
141
|
+
} },
|
|
142
|
+
async run({ args }) {
|
|
143
|
+
const name = args.name ? String(args.name) : resolveActiveProfile();
|
|
144
|
+
if (!name) {
|
|
145
|
+
logger.error("No profile specified and none active (set --profile, GSCDUMP_PROFILE, or run `gscdump profile use <name>`)");
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
console.log(getProfileDir(name));
|
|
149
|
+
}
|
|
150
|
+
}),
|
|
151
|
+
current: defineCommand({
|
|
152
|
+
meta: {
|
|
153
|
+
name: "current",
|
|
154
|
+
description: "Print the active profile name"
|
|
155
|
+
},
|
|
156
|
+
async run() {
|
|
157
|
+
const active = resolveActiveProfile();
|
|
158
|
+
if (!active) process.exit(1);
|
|
159
|
+
console.log(active);
|
|
160
|
+
}
|
|
161
|
+
}),
|
|
162
|
+
use: defineCommand({
|
|
163
|
+
meta: {
|
|
164
|
+
name: "use",
|
|
165
|
+
description: "Set the persisted active profile (subsequent commands no longer need --profile)"
|
|
166
|
+
},
|
|
167
|
+
args: {
|
|
168
|
+
...OUTPUT_ARGS,
|
|
169
|
+
name: {
|
|
170
|
+
type: "positional",
|
|
171
|
+
required: true,
|
|
172
|
+
description: "Profile name"
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
async run({ args }) {
|
|
176
|
+
applyOutputMode(args);
|
|
177
|
+
const name = String(args.name);
|
|
178
|
+
const dir = getProfileDir(name);
|
|
179
|
+
if (!await fs$1.stat(dir).then(() => true).catch(() => false)) {
|
|
180
|
+
logger.error(`Profile not found: ${name}`);
|
|
181
|
+
logger.info(`Create it with: gscdump profile create ${name}`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
await setActiveProfile(name);
|
|
185
|
+
logger.success(`Active profile: ${name}`);
|
|
186
|
+
}
|
|
187
|
+
}),
|
|
188
|
+
create: defineCommand({
|
|
189
|
+
meta: {
|
|
190
|
+
name: "create",
|
|
191
|
+
description: "Create an empty profile directory"
|
|
192
|
+
},
|
|
193
|
+
args: {
|
|
194
|
+
...OUTPUT_ARGS,
|
|
195
|
+
"name": {
|
|
196
|
+
type: "positional",
|
|
197
|
+
required: true,
|
|
198
|
+
description: "Profile name"
|
|
199
|
+
},
|
|
200
|
+
"no-use": {
|
|
201
|
+
type: "boolean",
|
|
202
|
+
default: false,
|
|
203
|
+
description: "Do not mark the new profile as active"
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
async run({ args }) {
|
|
207
|
+
applyOutputMode(args);
|
|
208
|
+
const name = String(args.name);
|
|
209
|
+
const dir = await createProfile(name);
|
|
210
|
+
if (!args["no-use"]) await setActiveProfile(name);
|
|
211
|
+
logger.success(`Created profile: ${name}${args["no-use"] ? "" : " (active)"}`);
|
|
212
|
+
logger.info(displayPath(dir));
|
|
213
|
+
}
|
|
214
|
+
}),
|
|
215
|
+
clear: defineCommand({
|
|
216
|
+
meta: {
|
|
217
|
+
name: "clear",
|
|
218
|
+
description: "Clear the persisted active profile (commands fall back to root config dir)"
|
|
219
|
+
},
|
|
220
|
+
args: { ...OUTPUT_ARGS },
|
|
221
|
+
async run({ args }) {
|
|
222
|
+
applyOutputMode(args);
|
|
223
|
+
await setActiveProfile(null);
|
|
224
|
+
logger.success("Cleared active profile");
|
|
225
|
+
}
|
|
226
|
+
}),
|
|
227
|
+
delete: defineCommand({
|
|
228
|
+
meta: {
|
|
229
|
+
name: "delete",
|
|
230
|
+
description: "Remove a profile directory (tokens + config)"
|
|
231
|
+
},
|
|
232
|
+
args: {
|
|
233
|
+
...OUTPUT_ARGS,
|
|
234
|
+
name: {
|
|
235
|
+
type: "positional",
|
|
236
|
+
required: true,
|
|
237
|
+
description: "Profile name"
|
|
238
|
+
},
|
|
239
|
+
yes: {
|
|
240
|
+
type: "boolean",
|
|
241
|
+
alias: "y",
|
|
242
|
+
default: false,
|
|
243
|
+
description: "Skip confirmation"
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
async run({ args }) {
|
|
247
|
+
applyOutputMode(args);
|
|
248
|
+
const name = String(args.name);
|
|
249
|
+
const dir = getProfileDir(name);
|
|
250
|
+
if (!await fs$1.stat(dir).then(() => true).catch(() => false)) {
|
|
251
|
+
logger.error(`Profile not found: ${name}`);
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
if (!args.yes) {
|
|
255
|
+
const ok = await confirm({
|
|
256
|
+
message: `Delete profile "${name}" at ${dir}? Tokens and config will be lost.`,
|
|
257
|
+
initialValue: false
|
|
258
|
+
});
|
|
259
|
+
if (isCancel(ok) || !ok) {
|
|
260
|
+
logger.info("Cancelled");
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
await fs$1.rm(dir, {
|
|
265
|
+
recursive: true,
|
|
266
|
+
force: true
|
|
267
|
+
});
|
|
268
|
+
if (readActiveMarkerSync() === name) await setActiveProfile(null);
|
|
269
|
+
logger.success(`Removed profile: ${name}`);
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
},
|
|
273
|
+
async run({ args }) {
|
|
274
|
+
if (!noSubcommandSelected("profile", [
|
|
275
|
+
"list",
|
|
276
|
+
"path",
|
|
277
|
+
"current",
|
|
278
|
+
"use",
|
|
279
|
+
"create",
|
|
280
|
+
"clear",
|
|
281
|
+
"delete"
|
|
282
|
+
])) return;
|
|
283
|
+
await listCmd.run?.({
|
|
284
|
+
args,
|
|
285
|
+
cmd: listCmd,
|
|
286
|
+
rawArgs: []
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
export { adoptCurrentConfigAsProfile, applyProfileFromCli, profileNameFromEmail, profile_exports, resolveActiveProfile };
|