@gurulu/cli 0.1.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 +66 -0
- package/bin/gurulu.js +2 -0
- package/dist/api-client.d.ts +27 -0
- package/dist/api-client.js +150 -0
- package/dist/commands/add-server.d.ts +9 -0
- package/dist/commands/add-server.js +155 -0
- package/dist/commands/alerts.d.ts +22 -0
- package/dist/commands/alerts.js +281 -0
- package/dist/commands/api-keys.d.ts +20 -0
- package/dist/commands/api-keys.js +130 -0
- package/dist/commands/audiences.d.ts +16 -0
- package/dist/commands/audiences.js +180 -0
- package/dist/commands/audit.d.ts +20 -0
- package/dist/commands/audit.js +130 -0
- package/dist/commands/auth.d.ts +20 -0
- package/dist/commands/auth.js +214 -0
- package/dist/commands/chat.d.ts +18 -0
- package/dist/commands/chat.js +117 -0
- package/dist/commands/config.d.ts +10 -0
- package/dist/commands/config.js +92 -0
- package/dist/commands/db.d.ts +25 -0
- package/dist/commands/db.js +322 -0
- package/dist/commands/destinations.d.ts +20 -0
- package/dist/commands/destinations.js +191 -0
- package/dist/commands/doctor.d.ts +7 -0
- package/dist/commands/doctor.js +318 -0
- package/dist/commands/events.d.ts +27 -0
- package/dist/commands/events.js +147 -0
- package/dist/commands/experiments.d.ts +18 -0
- package/dist/commands/experiments.js +233 -0
- package/dist/commands/identity.d.ts +13 -0
- package/dist/commands/identity.js +107 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.js +215 -0
- package/dist/commands/insights.d.ts +10 -0
- package/dist/commands/insights.js +65 -0
- package/dist/commands/install.d.ts +233 -0
- package/dist/commands/install.js +920 -0
- package/dist/commands/login.d.ts +20 -0
- package/dist/commands/login.js +170 -0
- package/dist/commands/logout.d.ts +10 -0
- package/dist/commands/logout.js +41 -0
- package/dist/commands/playground.d.ts +11 -0
- package/dist/commands/playground.js +47 -0
- package/dist/commands/sites.d.ts +18 -0
- package/dist/commands/sites.js +139 -0
- package/dist/commands/sourcemap.d.ts +21 -0
- package/dist/commands/sourcemap.js +137 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +136 -0
- package/dist/commands/warehouse.d.ts +20 -0
- package/dist/commands/warehouse.js +65 -0
- package/dist/commands/warehouses.d.ts +17 -0
- package/dist/commands/warehouses.js +182 -0
- package/dist/commands/whoami.d.ts +9 -0
- package/dist/commands/whoami.js +47 -0
- package/dist/config.d.ts +75 -0
- package/dist/config.js +329 -0
- package/dist/frameworks/detect.d.ts +8 -0
- package/dist/frameworks/detect.js +362 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +429 -0
- package/dist/install-intent-proposal.d.ts +99 -0
- package/dist/install-intent-proposal.js +202 -0
- package/dist/utils/api.d.ts +20 -0
- package/dist/utils/api.js +47 -0
- package/dist/utils/config.d.ts +13 -0
- package/dist/utils/config.js +30 -0
- package/dist/utils/confirm.d.ts +17 -0
- package/dist/utils/confirm.js +40 -0
- package/dist/utils/dry-run.d.ts +20 -0
- package/dist/utils/dry-run.js +67 -0
- package/dist/utils/from-file.d.ts +9 -0
- package/dist/utils/from-file.js +72 -0
- package/dist/utils/ui.d.ts +14 -0
- package/dist/utils/ui.js +59 -0
- package/package.json +26 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 18.5 W2 — `gurulu login`.
|
|
3
|
+
*
|
|
4
|
+
* Rewritten from the Phase 8 legacy command. Authenticates against
|
|
5
|
+
* `POST /api/cli/verify-key`, persists the credentials into a profile,
|
|
6
|
+
* and prints a short summary fetched from `GET /api/cli/me`.
|
|
7
|
+
*
|
|
8
|
+
* The old login command lived in this same file, but nothing outside this
|
|
9
|
+
* package imports it directly — `src/index.ts` is the only caller.
|
|
10
|
+
*/
|
|
11
|
+
export interface LoginArgs {
|
|
12
|
+
profile?: string;
|
|
13
|
+
email?: string;
|
|
14
|
+
secretKey?: string;
|
|
15
|
+
noInteractive?: boolean;
|
|
16
|
+
useKeychain?: boolean;
|
|
17
|
+
apiBase?: string;
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function loginCommand(args: LoginArgs): Promise<void>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 18.5 W2 — `gurulu login`.
|
|
4
|
+
*
|
|
5
|
+
* Rewritten from the Phase 8 legacy command. Authenticates against
|
|
6
|
+
* `POST /api/cli/verify-key`, persists the credentials into a profile,
|
|
7
|
+
* and prints a short summary fetched from `GET /api/cli/me`.
|
|
8
|
+
*
|
|
9
|
+
* The old login command lived in this same file, but nothing outside this
|
|
10
|
+
* package imports it directly — `src/index.ts` is the only caller.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.loginCommand = loginCommand;
|
|
14
|
+
const config_1 = require("../config");
|
|
15
|
+
const api_client_1 = require("../api-client");
|
|
16
|
+
const ui_1 = require("../utils/ui");
|
|
17
|
+
function parseKeyFormat(key) {
|
|
18
|
+
if (!key)
|
|
19
|
+
return { valid: false, type: null };
|
|
20
|
+
const m = key.match(/^(gsk|gpk)_(live|test)_([A-Za-z0-9]+)$/);
|
|
21
|
+
if (!m)
|
|
22
|
+
return { valid: false, type: null };
|
|
23
|
+
if (m[3].length < 24)
|
|
24
|
+
return { valid: false, type: null };
|
|
25
|
+
return { valid: true, type: m[1] === 'gsk' ? 'sk' : 'pk' };
|
|
26
|
+
}
|
|
27
|
+
async function promptHidden(question) {
|
|
28
|
+
// Best-effort secret input. Does not echo while typing when stdin is a TTY.
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const stdin = process.stdin;
|
|
31
|
+
const stdout = process.stdout;
|
|
32
|
+
stdout.write(question);
|
|
33
|
+
const isTTY = !!stdin.isTTY;
|
|
34
|
+
if (!isTTY) {
|
|
35
|
+
// Non-TTY: fall back to visible readline.
|
|
36
|
+
const readline = require('readline');
|
|
37
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
38
|
+
rl.question('', (answer) => {
|
|
39
|
+
rl.close();
|
|
40
|
+
resolve(answer.trim());
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
stdin.setRawMode(true);
|
|
45
|
+
stdin.resume();
|
|
46
|
+
stdin.setEncoding('utf8');
|
|
47
|
+
let buf = '';
|
|
48
|
+
const onData = (ch) => {
|
|
49
|
+
for (const c of ch) {
|
|
50
|
+
if (c === '\n' || c === '\r' || c === '\u0004') {
|
|
51
|
+
stdin.setRawMode(false);
|
|
52
|
+
stdin.pause();
|
|
53
|
+
stdin.removeListener('data', onData);
|
|
54
|
+
stdout.write('\n');
|
|
55
|
+
resolve(buf.trim());
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (c === '\u0003') {
|
|
59
|
+
// Ctrl+C
|
|
60
|
+
stdin.setRawMode(false);
|
|
61
|
+
stdout.write('\n');
|
|
62
|
+
process.exit(130);
|
|
63
|
+
}
|
|
64
|
+
if (c === '\u007f' || c === '\b') {
|
|
65
|
+
if (buf.length > 0) {
|
|
66
|
+
buf = buf.slice(0, -1);
|
|
67
|
+
stdout.write('\b \b');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
buf += c;
|
|
72
|
+
stdout.write('*');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
stdin.on('data', onData);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async function loginCommand(args) {
|
|
80
|
+
const profileName = args.profile || 'personal';
|
|
81
|
+
let email = args.email || process.env.GURULU_EMAIL;
|
|
82
|
+
let secretKey = args.secretKey || args.apiKey || process.env.GURULU_SECRET_KEY;
|
|
83
|
+
if (!email) {
|
|
84
|
+
if (args.noInteractive) {
|
|
85
|
+
(0, ui_1.error)('Email required. Use --email or set GURULU_EMAIL.');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
email = await (0, ui_1.prompt)('? Email: ');
|
|
89
|
+
}
|
|
90
|
+
if (!secretKey) {
|
|
91
|
+
if (args.noInteractive) {
|
|
92
|
+
(0, ui_1.error)('Secret key required. Use --secret-key or set GURULU_SECRET_KEY.');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
secretKey = await promptHidden('? Secret key (gsk_...): ');
|
|
96
|
+
}
|
|
97
|
+
const parsed = parseKeyFormat(secretKey || '');
|
|
98
|
+
if (!parsed.valid || parsed.type !== 'sk') {
|
|
99
|
+
(0, ui_1.error)('Invalid key format. Expected gsk_live_... or gsk_test_...');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
const apiBase = args.apiBase || (0, config_1.defaultApiBase)();
|
|
103
|
+
(0, ui_1.info)('[•] Verifying credentials...');
|
|
104
|
+
let verify;
|
|
105
|
+
try {
|
|
106
|
+
const res = await (0, api_client_1.cliApi)('/api/cli/verify-key', {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
skipAuth: true,
|
|
109
|
+
body: JSON.stringify({ email, secret_key: secretKey }),
|
|
110
|
+
preloadedProfile: {
|
|
111
|
+
name: profileName,
|
|
112
|
+
email: email,
|
|
113
|
+
secret_key: secretKey,
|
|
114
|
+
api_base: apiBase,
|
|
115
|
+
source: 'env',
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
if (res.status === 401) {
|
|
119
|
+
(0, ui_1.error)('Invalid email or key.');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
if (res.status === 429) {
|
|
123
|
+
(0, ui_1.error)('Too many attempts. Try again in 5 minutes.');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
verify = await res.json();
|
|
127
|
+
if (!res.ok || !verify?.ok) {
|
|
128
|
+
(0, ui_1.error)(`Login failed: ${verify?.message || 'unknown error'}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
(0, ui_1.error)(`Login failed: ${err.message}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
// Persist to profile.
|
|
137
|
+
const { storedInKeychain } = await (0, config_1.saveProfile)(profileName, { email: email, secret_key: secretKey, api_base: apiBase }, { useKeychain: args.useKeychain ?? (0, config_1.isKeychainAvailable)() });
|
|
138
|
+
// Summary from /me (non-fatal if it fails).
|
|
139
|
+
let me = null;
|
|
140
|
+
try {
|
|
141
|
+
me = await (0, api_client_1.cliApiJson)('/api/cli/me', {
|
|
142
|
+
preloadedProfile: {
|
|
143
|
+
name: profileName,
|
|
144
|
+
email: email,
|
|
145
|
+
secret_key: secretKey,
|
|
146
|
+
api_base: apiBase,
|
|
147
|
+
source: 'file',
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
/* ignore */
|
|
153
|
+
}
|
|
154
|
+
const tenantName = verify.tenant?.name || '—';
|
|
155
|
+
const plan = verify.tenant?.plan || 'free';
|
|
156
|
+
const tenantId = verify.tenant?.id || '';
|
|
157
|
+
(0, ui_1.success)(`Authenticated as ${email} · ${tenantName} (${plan} plan)`);
|
|
158
|
+
if (tenantId)
|
|
159
|
+
(0, ui_1.info)(` Tenant: ${tenantId}`);
|
|
160
|
+
if (me) {
|
|
161
|
+
const sites = Array.isArray(me.sites) ? me.sites.length : 0;
|
|
162
|
+
const keys = Array.isArray(me.apiKeys) ? me.apiKeys.length : 0;
|
|
163
|
+
const live = (me.apiKeys || []).filter((k) => k.mode === 'live').length;
|
|
164
|
+
const test = (me.apiKeys || []).filter((k) => k.mode === 'test').length;
|
|
165
|
+
(0, ui_1.info)(` Sites: ${sites} active`);
|
|
166
|
+
(0, ui_1.info)(` API keys: ${keys} (${live} live, ${test} test)`);
|
|
167
|
+
}
|
|
168
|
+
(0, ui_1.info)('');
|
|
169
|
+
(0, ui_1.info)(`Config saved to ${(0, ui_1.dim)(storedInKeychain ? `~/.gurulu/config.json + macOS Keychain` : `~/.gurulu/config.json`)}`);
|
|
170
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 18.5 W2 — `gurulu logout`. Removes a stored profile (or all with
|
|
3
|
+
* --all). Non-interactive unless --yes is passed.
|
|
4
|
+
*/
|
|
5
|
+
export interface LogoutArgs {
|
|
6
|
+
profile?: string;
|
|
7
|
+
all?: boolean;
|
|
8
|
+
yes?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function logoutCommand(args: LogoutArgs): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 18.5 W2 — `gurulu logout`. Removes a stored profile (or all with
|
|
4
|
+
* --all). Non-interactive unless --yes is passed.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.logoutCommand = logoutCommand;
|
|
8
|
+
const config_1 = require("../config");
|
|
9
|
+
const ui_1 = require("../utils/ui");
|
|
10
|
+
async function logoutCommand(args) {
|
|
11
|
+
if (args.all) {
|
|
12
|
+
if (!args.yes) {
|
|
13
|
+
const answer = await (0, ui_1.prompt)('? Remove ALL stored profiles? [y/N] ');
|
|
14
|
+
if (!answer.toLowerCase().startsWith('y')) {
|
|
15
|
+
(0, ui_1.info)('Aborted.');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const removed = await (0, config_1.deleteAllProfiles)();
|
|
20
|
+
(0, ui_1.success)(`Removed ${removed.length} profile(s).`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const name = args.profile || 'personal';
|
|
24
|
+
const stored = (0, config_1.getStoredProfile)(name);
|
|
25
|
+
if (!stored) {
|
|
26
|
+
(0, ui_1.error)(`Profile '${name}' not found.`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
if (!args.yes) {
|
|
30
|
+
const answer = await (0, ui_1.prompt)(`? Remove profile '${name}' (${stored.email})? [y/N] `);
|
|
31
|
+
if (!answer.toLowerCase().startsWith('y')) {
|
|
32
|
+
(0, ui_1.info)('Aborted.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const ok = await (0, config_1.deleteProfile)(name);
|
|
37
|
+
if (ok)
|
|
38
|
+
(0, ui_1.success)(`Profile '${name}' removed.`);
|
|
39
|
+
else
|
|
40
|
+
(0, ui_1.error)(`Failed to remove profile '${name}'.`);
|
|
41
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 19.5 W2 B9 — `gurulu playground list`.
|
|
3
|
+
*/
|
|
4
|
+
export interface PlaygroundArgs {
|
|
5
|
+
action?: string;
|
|
6
|
+
status?: string;
|
|
7
|
+
limit?: number;
|
|
8
|
+
json?: boolean;
|
|
9
|
+
profile?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function playgroundCommand(args: PlaygroundArgs): Promise<void>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 19.5 W2 B9 — `gurulu playground list`.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.playgroundCommand = playgroundCommand;
|
|
7
|
+
const api_client_1 = require("../api-client");
|
|
8
|
+
const ui_1 = require("../utils/ui");
|
|
9
|
+
async function playgroundCommand(args) {
|
|
10
|
+
const action = args.action || 'list';
|
|
11
|
+
switch (action) {
|
|
12
|
+
case 'list':
|
|
13
|
+
return listCmd(args);
|
|
14
|
+
default:
|
|
15
|
+
(0, ui_1.error)(`Unknown playground action: ${action}`);
|
|
16
|
+
(0, ui_1.info)('Usage: gurulu playground [list]');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function listCmd(args) {
|
|
21
|
+
const qs = new URLSearchParams();
|
|
22
|
+
if (args.status)
|
|
23
|
+
qs.set('status', args.status);
|
|
24
|
+
if (args.limit)
|
|
25
|
+
qs.set('limit', String(args.limit));
|
|
26
|
+
const path = `/api/cli/playground/sessions${qs.toString() ? `?${qs.toString()}` : ''}`;
|
|
27
|
+
const body = await (0, api_client_1.cliApiJson)(path, { profile: args.profile });
|
|
28
|
+
if (args.json) {
|
|
29
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const rows = body.sessions || [];
|
|
33
|
+
if (rows.length === 0) {
|
|
34
|
+
(0, ui_1.info)('No playground sessions.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
process.stdout.write(['ID', 'SITE', 'STATUS', 'STARTED', 'VERIFICATIONS'].join('\t') + '\n');
|
|
38
|
+
for (const s of rows) {
|
|
39
|
+
process.stdout.write([
|
|
40
|
+
s.id,
|
|
41
|
+
s.siteName || s.siteId,
|
|
42
|
+
s.status,
|
|
43
|
+
String(s.startedAt),
|
|
44
|
+
String(s.verificationsCount ?? 0),
|
|
45
|
+
].join('\t') + '\n');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 18.5 W2 — `gurulu sites ...` subcommands.
|
|
3
|
+
*
|
|
4
|
+
* Resolves name-or-id arguments against `GET /api/cli/sites` first so users
|
|
5
|
+
* can pass either form. All commands tolerate --json and support --yes for
|
|
6
|
+
* non-interactive use.
|
|
7
|
+
*/
|
|
8
|
+
export interface SitesArgs {
|
|
9
|
+
action?: string;
|
|
10
|
+
target?: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
domain?: string;
|
|
13
|
+
json?: boolean;
|
|
14
|
+
yes?: boolean;
|
|
15
|
+
archived?: boolean;
|
|
16
|
+
profile?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function sitesCommand(args: SitesArgs): Promise<void>;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 18.5 W2 — `gurulu sites ...` subcommands.
|
|
4
|
+
*
|
|
5
|
+
* Resolves name-or-id arguments against `GET /api/cli/sites` first so users
|
|
6
|
+
* can pass either form. All commands tolerate --json and support --yes for
|
|
7
|
+
* non-interactive use.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.sitesCommand = sitesCommand;
|
|
11
|
+
const api_client_1 = require("../api-client");
|
|
12
|
+
const ui_1 = require("../utils/ui");
|
|
13
|
+
async function listSites(profile) {
|
|
14
|
+
const body = await (0, api_client_1.cliApiJson)('/api/cli/sites', { profile });
|
|
15
|
+
return body.sites || [];
|
|
16
|
+
}
|
|
17
|
+
async function resolveSiteId(target, profile) {
|
|
18
|
+
if (!target)
|
|
19
|
+
throw new Error('site name or id required');
|
|
20
|
+
const sites = await listSites(profile);
|
|
21
|
+
const byId = sites.find((s) => s.id === target);
|
|
22
|
+
if (byId)
|
|
23
|
+
return byId.id;
|
|
24
|
+
const byName = sites.find((s) => s.name === target);
|
|
25
|
+
if (byName)
|
|
26
|
+
return byName.id;
|
|
27
|
+
const prefix = sites.find((s) => s.id.startsWith(target));
|
|
28
|
+
if (prefix)
|
|
29
|
+
return prefix.id;
|
|
30
|
+
throw new Error(`Site '${target}' not found.`);
|
|
31
|
+
}
|
|
32
|
+
async function sitesCommand(args) {
|
|
33
|
+
const action = args.action || 'list';
|
|
34
|
+
switch (action) {
|
|
35
|
+
case 'list':
|
|
36
|
+
return listCmd(args);
|
|
37
|
+
case 'create':
|
|
38
|
+
return createCmd(args);
|
|
39
|
+
case 'show':
|
|
40
|
+
return showCmd(args);
|
|
41
|
+
case 'delete':
|
|
42
|
+
return deleteCmd(args);
|
|
43
|
+
case 'rotate-token':
|
|
44
|
+
return rotateCmd(args);
|
|
45
|
+
default:
|
|
46
|
+
(0, ui_1.error)(`Unknown sites action: ${action}`);
|
|
47
|
+
(0, ui_1.info)('Usage: gurulu sites [list|create|show|delete|rotate-token]');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function listCmd(args) {
|
|
52
|
+
const sites = await listSites(args.profile);
|
|
53
|
+
if (args.json) {
|
|
54
|
+
process.stdout.write(JSON.stringify(sites, null, 2) + '\n');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (sites.length === 0) {
|
|
58
|
+
(0, ui_1.info)('No sites yet. Run `gurulu sites create --name ... --domain ...`');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
process.stdout.write(['NAME', 'ID', 'DOMAIN', 'CREATED'].join('\t') + '\n');
|
|
62
|
+
for (const s of sites) {
|
|
63
|
+
process.stdout.write([s.name || '-', s.id, s.domain, new Date(s.createdAt).toISOString().slice(0, 10)].join('\t') + '\n');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function createCmd(args) {
|
|
67
|
+
if (!args.domain) {
|
|
68
|
+
(0, ui_1.error)('--domain is required.');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
const body = await (0, api_client_1.cliApiJson)('/api/cli/sites', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
profile: args.profile,
|
|
74
|
+
json: { name: args.name, domain: args.domain },
|
|
75
|
+
});
|
|
76
|
+
const site = body.site;
|
|
77
|
+
if (args.json) {
|
|
78
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
(0, ui_1.success)('Site created');
|
|
82
|
+
process.stdout.write(` ID: ${site.id}\n`);
|
|
83
|
+
process.stdout.write(` Name: ${site.name || '-'}\n`);
|
|
84
|
+
process.stdout.write(` Domain: ${site.domain}\n`);
|
|
85
|
+
process.stdout.write(` Publishable key: ${site.publishableKey}\n\n`);
|
|
86
|
+
(0, ui_1.info)('Add to your site:');
|
|
87
|
+
process.stdout.write(`\n <script src="https://gurulu.io/t.js" data-gurulu-site-id="${site.id}" data-gurulu-publishable-key="${site.publishableKey}" async></script>\n\n`);
|
|
88
|
+
(0, ui_1.info)(`Or run: ${(0, ui_1.dim)(`gurulu install --site ${site.name || site.id}`)}`);
|
|
89
|
+
}
|
|
90
|
+
async function showCmd(args) {
|
|
91
|
+
if (!args.target) {
|
|
92
|
+
(0, ui_1.error)('Usage: gurulu sites show <name-or-id>');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
const id = await resolveSiteId(args.target, args.profile);
|
|
96
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/sites/${encodeURIComponent(id)}`, {
|
|
97
|
+
profile: args.profile,
|
|
98
|
+
});
|
|
99
|
+
const site = body.site;
|
|
100
|
+
if (args.json) {
|
|
101
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
process.stdout.write(`Site: ${site.name || '-'}\n`);
|
|
105
|
+
process.stdout.write(`ID: ${site.id}\n`);
|
|
106
|
+
process.stdout.write(`Domain: ${site.domain}\n`);
|
|
107
|
+
process.stdout.write(`Publishable key: ${site.publishableKey}\n`);
|
|
108
|
+
process.stdout.write(`Created: ${site.createdAt}\n`);
|
|
109
|
+
}
|
|
110
|
+
async function deleteCmd(args) {
|
|
111
|
+
if (!args.target) {
|
|
112
|
+
(0, ui_1.error)('Usage: gurulu sites delete <name-or-id>');
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
const id = await resolveSiteId(args.target, args.profile);
|
|
116
|
+
if (!args.yes) {
|
|
117
|
+
const ans = await (0, ui_1.prompt)(`? Delete site '${args.target}'? [y/N] `);
|
|
118
|
+
if (!ans.toLowerCase().startsWith('y')) {
|
|
119
|
+
(0, ui_1.info)('Aborted.');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
await (0, api_client_1.cliApiJson)(`/api/cli/sites/${encodeURIComponent(id)}`, {
|
|
124
|
+
method: 'DELETE',
|
|
125
|
+
profile: args.profile,
|
|
126
|
+
});
|
|
127
|
+
(0, ui_1.success)(`Site '${args.target}' deleted.`);
|
|
128
|
+
}
|
|
129
|
+
async function rotateCmd(args) {
|
|
130
|
+
if (!args.target) {
|
|
131
|
+
(0, ui_1.error)('Usage: gurulu sites rotate-token <name-or-id>');
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
const id = await resolveSiteId(args.target, args.profile);
|
|
135
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/sites/${encodeURIComponent(id)}/rotate-token`, { method: 'POST', profile: args.profile, json: {} });
|
|
136
|
+
(0, ui_1.warn)(`Rotating publishable key for '${args.target}'`);
|
|
137
|
+
process.stdout.write(` New: ${body.publishableKey}\n`);
|
|
138
|
+
(0, ui_1.warn)('Update your <script> tag before the 24h grace window expires.');
|
|
139
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gurulu sourcemap upload` — Upload source maps for error deobfuscation.
|
|
3
|
+
*
|
|
4
|
+
* Reads all `.map` files from the specified directory and POSTs them to
|
|
5
|
+
* `/api/errors/sourcemaps` with Bearer auth. The server stores them on
|
|
6
|
+
* disk for later stack trace deobfuscation (follow-up task).
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* gurulu sourcemap upload --release v1.0.0 --dir .next/static/
|
|
10
|
+
*/
|
|
11
|
+
import { loadActiveProfile } from '../config';
|
|
12
|
+
export interface SourcemapArgs {
|
|
13
|
+
action?: string;
|
|
14
|
+
release?: string;
|
|
15
|
+
dir?: string;
|
|
16
|
+
site?: string;
|
|
17
|
+
profile?: string;
|
|
18
|
+
json?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function sourcemapCommand(args: SourcemapArgs): Promise<void>;
|
|
21
|
+
export declare const _loadActiveProfile: typeof loadActiveProfile;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `gurulu sourcemap upload` — Upload source maps for error deobfuscation.
|
|
4
|
+
*
|
|
5
|
+
* Reads all `.map` files from the specified directory and POSTs them to
|
|
6
|
+
* `/api/errors/sourcemaps` with Bearer auth. The server stores them on
|
|
7
|
+
* disk for later stack trace deobfuscation (follow-up task).
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* gurulu sourcemap upload --release v1.0.0 --dir .next/static/
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports._loadActiveProfile = void 0;
|
|
47
|
+
exports.sourcemapCommand = sourcemapCommand;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const api_client_1 = require("../api-client");
|
|
51
|
+
const config_1 = require("../config");
|
|
52
|
+
const ui_1 = require("../utils/ui");
|
|
53
|
+
async function sourcemapCommand(args) {
|
|
54
|
+
const action = args.action || 'upload';
|
|
55
|
+
if (action !== 'upload') {
|
|
56
|
+
(0, ui_1.error)(`Unknown sourcemap action: ${action}`);
|
|
57
|
+
(0, ui_1.info)('Usage: gurulu sourcemap upload --release <version> --dir <path>');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
return uploadCmd(args);
|
|
61
|
+
}
|
|
62
|
+
function findMapFiles(dir) {
|
|
63
|
+
const results = [];
|
|
64
|
+
function walk(d) {
|
|
65
|
+
const entries = fs.readdirSync(d, { withFileTypes: true });
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
const full = path.join(d, entry.name);
|
|
68
|
+
if (entry.isDirectory()) {
|
|
69
|
+
walk(full);
|
|
70
|
+
}
|
|
71
|
+
else if (entry.name.endsWith('.map')) {
|
|
72
|
+
results.push(full);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
walk(dir);
|
|
77
|
+
return results;
|
|
78
|
+
}
|
|
79
|
+
async function uploadCmd(args) {
|
|
80
|
+
if (!args.release) {
|
|
81
|
+
(0, ui_1.error)('Missing --release flag. Example: gurulu sourcemap upload --release v1.0.0 --dir .next/static/');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
if (!args.dir) {
|
|
85
|
+
(0, ui_1.error)('Missing --dir flag. Example: gurulu sourcemap upload --release v1.0.0 --dir .next/static/');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const dir = path.resolve(args.dir);
|
|
89
|
+
if (!fs.existsSync(dir)) {
|
|
90
|
+
(0, ui_1.error)(`Directory not found: ${dir}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
const mapFiles = findMapFiles(dir);
|
|
94
|
+
if (mapFiles.length === 0) {
|
|
95
|
+
(0, ui_1.error)(`No .map files found in ${dir}`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
(0, ui_1.info)(`Uploading ${mapFiles.length} source maps for ${args.release}...`);
|
|
99
|
+
// Resolve siteId from profile config
|
|
100
|
+
const profile = await (0, config_1.loadActiveProfile)({ profile: args.profile });
|
|
101
|
+
const siteId = args.site || profile.site_id;
|
|
102
|
+
if (!siteId) {
|
|
103
|
+
(0, ui_1.error)('Missing --site flag or default site in profile. Use: gurulu sourcemap upload --site <site-id> ...');
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
// Build FormData
|
|
107
|
+
const formData = new FormData();
|
|
108
|
+
formData.append('release', args.release);
|
|
109
|
+
formData.append('siteId', siteId);
|
|
110
|
+
for (const filePath of mapFiles) {
|
|
111
|
+
const content = fs.readFileSync(filePath);
|
|
112
|
+
const fileName = path.basename(filePath);
|
|
113
|
+
const blob = new Blob([content], { type: 'application/json' });
|
|
114
|
+
formData.append(fileName, blob, fileName);
|
|
115
|
+
}
|
|
116
|
+
const res = await (0, api_client_1.cliApi)('/api/errors/sourcemaps', {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
body: formData,
|
|
119
|
+
profile: args.profile,
|
|
120
|
+
// Do not set content-type — fetch will add multipart boundary automatically
|
|
121
|
+
headers: {},
|
|
122
|
+
});
|
|
123
|
+
if (!res.ok) {
|
|
124
|
+
const body = await res.json().catch(() => ({ error: 'Unknown error' }));
|
|
125
|
+
(0, ui_1.error)(`Upload failed (${res.status}): ${body.error || res.statusText}`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
const result = await res.json();
|
|
129
|
+
if (args.json) {
|
|
130
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
(0, ui_1.success)(`${result.uploaded} source maps uploaded for release ${args.release}`);
|
|
134
|
+
process.stdout.write((0, ui_1.dim)(` Site: ${result.siteId}\n`));
|
|
135
|
+
process.stdout.write((0, ui_1.dim)(` Path: ${result.path}\n`));
|
|
136
|
+
}
|
|
137
|
+
exports._loadActiveProfile = config_1.loadActiveProfile;
|