google-apis-cli 0.1.0 → 0.2.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/README.md +39 -26
- package/dist/bin/gapi.js +6 -137
- package/dist/src/apis/ads.js +51 -49
- package/dist/src/apis/bid-manager.js +4 -0
- package/dist/src/apis/bigquery-dts.js +4 -0
- package/dist/src/apis/business-account-management.js +5 -0
- package/dist/src/apis/business-information.js +5 -0
- package/dist/src/apis/business-lodging.js +5 -0
- package/dist/src/apis/business-notifications.js +5 -0
- package/dist/src/apis/business-performance.js +5 -0
- package/dist/src/apis/business-place-actions.js +5 -0
- package/dist/src/apis/business-v4.js +52 -0
- package/dist/src/apis/business-verifications.js +5 -0
- package/dist/src/apis/cm360.js +4 -0
- package/dist/src/apis/crux.js +4 -0
- package/dist/src/apis/data-manager.js +4 -0
- package/dist/src/apis/dv360.js +4 -0
- package/dist/src/apis/ga4-admin-alpha.js +4 -0
- package/dist/src/apis/ga4-admin.js +1 -288
- package/dist/src/apis/ga4-data-alpha.js +4 -0
- package/dist/src/apis/ga4-data.js +1 -69
- package/dist/src/apis/gsc.js +5 -58
- package/dist/src/apis/gtm.js +1 -189
- package/dist/src/apis/indexing.js +37 -15
- package/dist/src/apis/merchant-accounts.js +4 -0
- package/dist/src/apis/merchant-conversions.js +4 -0
- package/dist/src/apis/merchant-datasources.js +4 -0
- package/dist/src/apis/merchant-inventories.js +4 -0
- package/dist/src/apis/merchant-issueresolution.js +4 -0
- package/dist/src/apis/merchant-lfp.js +4 -0
- package/dist/src/apis/merchant-loyaltycustomers.js +4 -0
- package/dist/src/apis/merchant-notifications.js +4 -0
- package/dist/src/apis/merchant-ordertracking.js +4 -0
- package/dist/src/apis/merchant-products.js +4 -0
- package/dist/src/apis/merchant-promotions.js +4 -0
- package/dist/src/apis/merchant-quota.js +4 -0
- package/dist/src/apis/merchant-reports.js +4 -0
- package/dist/src/apis/merchant-reviews.js +4 -0
- package/dist/src/apis/pagespeed.js +4 -0
- package/dist/src/apis/places.js +15 -0
- package/dist/src/apis/postmaster.js +4 -0
- package/dist/src/apis/sa360-legacy.js +4 -0
- package/dist/src/apis/sa360.js +12 -0
- package/dist/src/apis/sheets.js +4 -0
- package/dist/src/apis/site-verification.js +4 -0
- package/dist/src/apis/web-risk.js +14 -0
- package/dist/src/apis/youtube-analytics.js +4 -0
- package/dist/src/apis/youtube-reporting.js +4 -0
- package/dist/src/apis/youtube.js +4 -0
- package/dist/src/catalog.js +203 -0
- package/dist/src/credentials.js +316 -0
- package/dist/src/errors.js +16 -0
- package/dist/src/json.js +11 -0
- package/dist/src/plan.js +315 -0
- package/dist/src/render.js +183 -0
- package/dist/src/run.js +263 -0
- package/dist/src/transport/batch.js +75 -0
- package/dist/src/transport/download.js +15 -0
- package/dist/src/transport/upload.js +81 -0
- package/dist/src/transport.js +99 -0
- package/dist/src/types.js +1 -13
- package/package.json +22 -7
- package/dist/src/apis/ga4-admin-v1alpha.js +0 -280
- package/dist/src/auth.js +0 -172
- package/dist/src/config.js +0 -89
- package/dist/src/manifest.js +0 -69
- package/dist/src/rest.js +0 -90
package/dist/src/config.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
// Static config: OAuth client, the 13 scopes, and file paths.
|
|
2
|
-
// Client id/secret follow the public-CLI pattern (gcloud/gh/firebase-tools):
|
|
3
|
-
// embedded, overridable by env for the operator's own Desktop OAuth client.
|
|
4
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
-
import { homedir } from 'node:os';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
// Embedded Desktop-client credentials (gcloud/gh/firebase-tools pattern),
|
|
8
|
-
// overridable. Precedence: env → ~/.config/gapi/config.json (via `gapi auth
|
|
9
|
-
// setup`) → embedded. Filled once the operator registers the OAuth Desktop
|
|
10
|
-
// client (open item: credential not yet provisioned).
|
|
11
|
-
const EMBEDDED_CLIENT_ID = '';
|
|
12
|
-
const EMBEDDED_CLIENT_SECRET = '';
|
|
13
|
-
// Resolved lazily (not at import) so it reads the config file after CONFIG_PATH
|
|
14
|
-
// is initialized, and picks up a fresh `gapi auth setup` in the same process.
|
|
15
|
-
export function getOAuthClient() {
|
|
16
|
-
const cfg = readConfigFile();
|
|
17
|
-
return {
|
|
18
|
-
clientId: process.env.GAPI_OAUTH_CLIENT_ID || String(cfg.oauth_client_id ?? '') || EMBEDDED_CLIENT_ID,
|
|
19
|
-
clientSecret: process.env.GAPI_OAUTH_CLIENT_SECRET ||
|
|
20
|
-
String(cfg.oauth_client_secret ?? '') ||
|
|
21
|
-
EMBEDDED_CLIENT_SECRET,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
// Short name -> full scope URL. Manifest rows reference the short names;
|
|
25
|
-
// the schema self-check asserts every row scope is a key here.
|
|
26
|
-
const A = 'https://www.googleapis.com/auth/';
|
|
27
|
-
export const SCOPES = {
|
|
28
|
-
adwords: `${A}adwords`,
|
|
29
|
-
'tagmanager.readonly': `${A}tagmanager.readonly`,
|
|
30
|
-
'tagmanager.edit.containers': `${A}tagmanager.edit.containers`,
|
|
31
|
-
'tagmanager.edit.containerversions': `${A}tagmanager.edit.containerversions`,
|
|
32
|
-
'tagmanager.publish': `${A}tagmanager.publish`,
|
|
33
|
-
'tagmanager.delete.containers': `${A}tagmanager.delete.containers`,
|
|
34
|
-
'tagmanager.manage.users': `${A}tagmanager.manage.users`,
|
|
35
|
-
'tagmanager.manage.accounts': `${A}tagmanager.manage.accounts`,
|
|
36
|
-
'analytics.readonly': `${A}analytics.readonly`,
|
|
37
|
-
'analytics.edit': `${A}analytics.edit`,
|
|
38
|
-
'analytics.manage.users': `${A}analytics.manage.users`,
|
|
39
|
-
webmasters: `${A}webmasters`,
|
|
40
|
-
indexing: `${A}indexing`,
|
|
41
|
-
};
|
|
42
|
-
export const ALL_SCOPE_URLS = Object.values(SCOPES);
|
|
43
|
-
// Identity scopes (non-sensitive) so `auth status` can show the authenticated
|
|
44
|
-
// account: the id_token then carries the email. Separate from the 13 API
|
|
45
|
-
// scopes, which stay the coverage/self-check set.
|
|
46
|
-
export const IDENTITY_SCOPES = ['openid', 'email'];
|
|
47
|
-
export const CONSENT_SCOPES = [...ALL_SCOPE_URLS, ...IDENTITY_SCOPES];
|
|
48
|
-
export const CONFIG_DIR = join(homedir(), '.config', 'gapi');
|
|
49
|
-
export const CREDENTIALS_PATH = join(CONFIG_DIR, 'credentials.json');
|
|
50
|
-
export const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
|
|
51
|
-
// Read ~/.config/gapi/config.json (OAuth client id/secret, Ads login-customer-id,
|
|
52
|
-
// quota project).
|
|
53
|
-
export function readConfigFile() {
|
|
54
|
-
try {
|
|
55
|
-
return JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return {};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// Write JSON under CONFIG_DIR readable only by the owner (dir 700, file 600).
|
|
62
|
-
// Shared by the two things we persist: the config file and the credentials.
|
|
63
|
-
export function writeSecureJson(path, data) {
|
|
64
|
-
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
65
|
-
writeFileSync(path, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
66
|
-
}
|
|
67
|
-
// Precedence flag -> env -> config file. `flags` is the parsed argv values.
|
|
68
|
-
// `cfg` defaults to the config file; injectable so precedence is unit-testable
|
|
69
|
-
// without writing to ~/.config.
|
|
70
|
-
export function resolveSetting(flags, { flag, env, key }, cfg = readConfigFile()) {
|
|
71
|
-
return flags?.[flag] ?? (env && process.env[env]) ?? cfg?.[key];
|
|
72
|
-
}
|
|
73
|
-
export const QUOTA_PROJECT_ENV = 'GOOGLE_CLOUD_PROJECT';
|
|
74
|
-
// Google Ads sends login-customer-id (optional, MCC callers only) as an HTTP
|
|
75
|
-
// header, not an OAuth scope, resolved flag -> env -> config file (header name
|
|
76
|
-
// == flag == config key). developer-token was sunset 2026-09-09 (ignored by the
|
|
77
|
-
// API; access is governed by the Cloud project), so it is not sent.
|
|
78
|
-
const ADS_HEADERS = [
|
|
79
|
-
{ name: 'login-customer-id', env: 'GOOGLE_ADS_LOGIN_CUSTOMER_ID' },
|
|
80
|
-
];
|
|
81
|
-
export function resolveAdsHeaders(flags, cfg = readConfigFile()) {
|
|
82
|
-
const out = {};
|
|
83
|
-
for (const { name, env } of ADS_HEADERS) {
|
|
84
|
-
const v = resolveSetting(flags, { flag: name, env, key: name }, cfg);
|
|
85
|
-
if (v != null)
|
|
86
|
-
out[name] = String(v);
|
|
87
|
-
}
|
|
88
|
-
return out;
|
|
89
|
-
}
|
package/dist/src/manifest.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
-
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
return path;
|
|
8
|
-
};
|
|
9
|
-
// Auto-discovery of API manifests. No central registry: a glob of
|
|
10
|
-
// src/apis/*.js finds every surface, so a new API slice never edits a
|
|
11
|
-
// shared file. Each manifest file default-exports:
|
|
12
|
-
// { api, baseUrl, resources: { <resource>: { <verb>: row } } }
|
|
13
|
-
// row = { httpMethod, pathTemplate, scopes, requiredHeaders?, decoder?, listKey?, query? }
|
|
14
|
-
import { readdirSync } from 'node:fs';
|
|
15
|
-
import { dirname, join } from 'node:path';
|
|
16
|
-
import { fileURLToPath } from 'node:url';
|
|
17
|
-
import { SCOPES } from './config.js';
|
|
18
|
-
const APIS_DIR = join(dirname(fileURLToPath(import.meta.url)), 'apis');
|
|
19
|
-
export async function loadManifests(dir = APIS_DIR) {
|
|
20
|
-
// Match dev (.ts) and published (.js); skip declaration files.
|
|
21
|
-
const files = readdirSync(dir).filter((f) => (f.endsWith('.ts') || f.endsWith('.js')) && !f.endsWith('.d.ts'));
|
|
22
|
-
const manifests = {};
|
|
23
|
-
for (const f of files) {
|
|
24
|
-
const mod = await import(__rewriteRelativeImportExtension(join(dir, f)));
|
|
25
|
-
const m = mod.default;
|
|
26
|
-
manifests[m.api] = m;
|
|
27
|
-
}
|
|
28
|
-
return manifests;
|
|
29
|
-
}
|
|
30
|
-
// Flatten one manifest row and inject baseUrl so the executor is self-contained.
|
|
31
|
-
export function findRow(manifests, api, resource, verb) {
|
|
32
|
-
const m = manifests[api];
|
|
33
|
-
const row = m?.resources?.[resource]?.[verb];
|
|
34
|
-
if (!row)
|
|
35
|
-
return null;
|
|
36
|
-
return { ...row, baseUrl: m.baseUrl };
|
|
37
|
-
}
|
|
38
|
-
const REQUIRED = ['httpMethod', 'pathTemplate', 'scopes'];
|
|
39
|
-
// Schema self-check. Throws with all problems found. Substitutes N handler tests.
|
|
40
|
-
export function checkSchema(manifests) {
|
|
41
|
-
const errors = [];
|
|
42
|
-
const known = new Set(Object.keys(SCOPES));
|
|
43
|
-
for (const [api, m] of Object.entries(manifests)) {
|
|
44
|
-
if (!m.baseUrl)
|
|
45
|
-
errors.push(`${api}: missing baseUrl`);
|
|
46
|
-
for (const [resource, verbs] of Object.entries(m.resources || {})) {
|
|
47
|
-
for (const [verb, row] of Object.entries(verbs)) {
|
|
48
|
-
const at = `${api}.${resource}.${verb}`;
|
|
49
|
-
for (const field of REQUIRED) {
|
|
50
|
-
if (row[field] == null)
|
|
51
|
-
errors.push(`${at}: missing ${field}`);
|
|
52
|
-
}
|
|
53
|
-
// pathTemplate placeholders must be simple {name} tokens (params supply them).
|
|
54
|
-
if (typeof row.pathTemplate === 'string') {
|
|
55
|
-
const bad = row.pathTemplate.match(/\{[^}]*[^\w}][^}]*\}/g);
|
|
56
|
-
if (bad)
|
|
57
|
-
errors.push(`${at}: bad path placeholder ${bad.join(',')}`);
|
|
58
|
-
}
|
|
59
|
-
for (const s of row.scopes || []) {
|
|
60
|
-
if (!known.has(s))
|
|
61
|
-
errors.push(`${at}: unknown scope "${s}"`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (errors.length)
|
|
67
|
-
throw new Error(`manifest schema errors:\n${errors.join('\n')}`);
|
|
68
|
-
return true;
|
|
69
|
-
}
|
package/dist/src/rest.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { RestError } from './types.js';
|
|
2
|
-
const RESERVED = new Set(['json', 'raw', 'limit', 'body']);
|
|
3
|
-
// The {name} placeholders a pathTemplate declares. Shared with the dispatcher
|
|
4
|
-
// so path params are extracted in exactly one place.
|
|
5
|
-
export function pathParams(pathTemplate) {
|
|
6
|
-
return [...pathTemplate.matchAll(/\{(\w+)\}/g)].map((m) => m[1]);
|
|
7
|
-
}
|
|
8
|
-
// Fill {placeholders} in the path from params; return the used keys too.
|
|
9
|
-
export function resolvePath(pathTemplate, params) {
|
|
10
|
-
const used = new Set();
|
|
11
|
-
const path = pathTemplate.replace(/\{(\w+)\}/g, (_, name) => {
|
|
12
|
-
if (params[name] == null)
|
|
13
|
-
throw new Error(`missing path param: ${name}`);
|
|
14
|
-
used.add(name);
|
|
15
|
-
return encodeURIComponent(String(params[name]));
|
|
16
|
-
});
|
|
17
|
-
return { path, used };
|
|
18
|
-
}
|
|
19
|
-
function buildUrl(baseUrl, pathTemplate, params) {
|
|
20
|
-
const { path, used } = resolvePath(pathTemplate, params);
|
|
21
|
-
const url = new URL(baseUrl + path);
|
|
22
|
-
// Leftover non-reserved params become query string, for every method: writes
|
|
23
|
-
// carry query params too (e.g. PATCH `updateMask`, list `pageSize`). The body
|
|
24
|
-
// is separate — RESERVED excludes it.
|
|
25
|
-
for (const [k, v] of Object.entries(params)) {
|
|
26
|
-
if (used.has(k) || RESERVED.has(k) || v == null)
|
|
27
|
-
continue;
|
|
28
|
-
url.searchParams.set(k, String(v));
|
|
29
|
-
}
|
|
30
|
-
return url;
|
|
31
|
-
}
|
|
32
|
-
// Merge a page's list array into acc under listKey. Returns the merged list.
|
|
33
|
-
function mergeList(acc, page, listKey) {
|
|
34
|
-
const items = page?.[listKey];
|
|
35
|
-
if (Array.isArray(items))
|
|
36
|
-
return acc.concat(items);
|
|
37
|
-
return acc;
|
|
38
|
-
}
|
|
39
|
-
export async function execute(row, params, opts) {
|
|
40
|
-
const { tokenProvider, fetch: fetchImpl, quotaProject, extraHeaders = {} } = opts;
|
|
41
|
-
const method = row.httpMethod;
|
|
42
|
-
const headers = { Authorization: `Bearer ${await tokenProvider()}` };
|
|
43
|
-
if (quotaProject)
|
|
44
|
-
headers['x-goog-user-project'] = quotaProject;
|
|
45
|
-
for (const name of row.requiredHeaders || []) {
|
|
46
|
-
if (extraHeaders[name] == null)
|
|
47
|
-
throw new Error(`missing required header: ${name}`);
|
|
48
|
-
}
|
|
49
|
-
Object.assign(headers, extraHeaders);
|
|
50
|
-
const hasBody = method !== 'GET' && params.body != null;
|
|
51
|
-
if (hasBody)
|
|
52
|
-
headers['Content-Type'] = 'application/json';
|
|
53
|
-
const limit = params.limit != null ? Number(params.limit) : undefined;
|
|
54
|
-
let url = buildUrl(row.baseUrl, row.pathTemplate, params);
|
|
55
|
-
let last;
|
|
56
|
-
let merged = [];
|
|
57
|
-
let paged = false;
|
|
58
|
-
while (true) {
|
|
59
|
-
const res = await fetchImpl(url.toString(), {
|
|
60
|
-
method,
|
|
61
|
-
headers,
|
|
62
|
-
body: hasBody ? JSON.stringify(params.body) : undefined,
|
|
63
|
-
});
|
|
64
|
-
const text = await res.text();
|
|
65
|
-
const data = text ? JSON.parse(text) : {};
|
|
66
|
-
if (!res.ok) {
|
|
67
|
-
throw new RestError(data?.error?.message || `HTTP ${res.status}`, res.status, data);
|
|
68
|
-
}
|
|
69
|
-
last = data;
|
|
70
|
-
if (row.listKey) {
|
|
71
|
-
merged = mergeList(merged, data, row.listKey);
|
|
72
|
-
paged = true;
|
|
73
|
-
if (limit != null && merged.length >= limit) {
|
|
74
|
-
merged = merged.slice(0, limit);
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
const next = data.nextPageToken;
|
|
78
|
-
if (!next)
|
|
79
|
-
break;
|
|
80
|
-
url = buildUrl(row.baseUrl, row.pathTemplate, { ...params, pageToken: next });
|
|
81
|
-
continue; // yagni: sequential paging; parallel prefetch if a surface ever needs it
|
|
82
|
-
}
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
const raw = last;
|
|
86
|
-
let payload = paged ? { [row.listKey]: merged } : last;
|
|
87
|
-
if (row.decoder)
|
|
88
|
-
payload = row.decoder(paged ? merged : last, raw);
|
|
89
|
-
return { json: payload, raw };
|
|
90
|
-
}
|