netlify-cli 27.5.1 → 27.6.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/commands/base-command.js +7 -4
- package/dist/commands/login/login-request.js +2 -2
- package/dist/recipes/ai-context/context.d.ts +1 -1
- package/dist/recipes/ai-context/context.js +8 -2
- package/dist/recipes/ai-context/index.js +6 -1
- package/dist/utils/agent-detection.d.ts +11 -0
- package/dist/utils/agent-detection.js +151 -0
- package/dist/utils/command-helpers.d.ts +1 -0
- package/dist/utils/command-helpers.js +5 -0
- package/dist/utils/login-url.d.ts +2 -0
- package/dist/utils/login-url.js +25 -0
- package/dist/utils/telemetry/telemetry.js +14 -1
- package/package.json +4 -4
|
@@ -15,10 +15,11 @@ import inquirer from 'inquirer';
|
|
|
15
15
|
import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt';
|
|
16
16
|
import { deepMerge, pick } from '../utils/object-utilities.js';
|
|
17
17
|
import { getAgent } from '../lib/http-agent.js';
|
|
18
|
-
import { NETLIFY_CYAN, USER_AGENT, chalk, logAndThrowError, logJson, exit, getToken, log, version, normalizeConfig, padLeft, pollForToken, sortOptions, warn, logError, } from '../utils/command-helpers.js';
|
|
18
|
+
import { NETLIFY_CYAN, USER_AGENT, chalk, logAndThrowError, logJson, exit, getRequestUserAgent, getToken, log, version, normalizeConfig, padLeft, pollForToken, sortOptions, warn, logError, } from '../utils/command-helpers.js';
|
|
19
19
|
import { handleOptionError, isOptionError } from '../utils/command-error-handler.js';
|
|
20
20
|
import { getFrameworksAPIPaths } from '../utils/frameworks-api.js';
|
|
21
21
|
import { getSiteByName } from '../utils/get-site.js';
|
|
22
|
+
import { buildAuthorizeUrl } from '../utils/login-url.js';
|
|
22
23
|
import openBrowser from '../utils/open-browser.js';
|
|
23
24
|
import { isInteractive } from '../utils/scripted-commands.js';
|
|
24
25
|
import { identify, reportError, setCommandForErrorReporting, track } from '../utils/telemetry/index.js';
|
|
@@ -374,14 +375,16 @@ export default class BaseCommand extends Command {
|
|
|
374
375
|
}
|
|
375
376
|
}
|
|
376
377
|
async expensivelyAuthenticate() {
|
|
377
|
-
const webUI = process.env.NETLIFY_WEB_UI || 'https://app.netlify.com';
|
|
378
378
|
log(`Logging into your Netlify account...`);
|
|
379
379
|
// Create ticket for auth
|
|
380
380
|
const ticket = await this.netlify.api.createTicket({
|
|
381
381
|
clientId: CLIENT_ID,
|
|
382
382
|
});
|
|
383
|
+
if (!ticket.id) {
|
|
384
|
+
return logAndThrowError('Failed to create login ticket');
|
|
385
|
+
}
|
|
383
386
|
// Open browser for authentication
|
|
384
|
-
const authLink =
|
|
387
|
+
const authLink = buildAuthorizeUrl(ticket.id);
|
|
385
388
|
log(`Opening ${authLink}`);
|
|
386
389
|
const browserOpened = await openBrowser({ url: authLink });
|
|
387
390
|
if (!browserOpened && !isInteractive() && ticket.id) {
|
|
@@ -502,7 +505,7 @@ export default class BaseCommand extends Command {
|
|
|
502
505
|
const state = new LocalState(this.workingDir);
|
|
503
506
|
const [token] = await getToken(flags.auth);
|
|
504
507
|
const apiUrlOpts = {
|
|
505
|
-
userAgent:
|
|
508
|
+
userAgent: getRequestUserAgent(),
|
|
506
509
|
};
|
|
507
510
|
if (process.env.NETLIFY_API_URL) {
|
|
508
511
|
const apiUrl = new URL(process.env.NETLIFY_API_URL);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { NetlifyAPI } from '@netlify/api';
|
|
2
2
|
import { log, logAndThrowError, logJson } from '../../utils/command-helpers.js';
|
|
3
|
+
import { buildAuthorizeUrl } from '../../utils/login-url.js';
|
|
3
4
|
import { CLIENT_ID } from '../base-command.js';
|
|
4
5
|
export const loginRequest = async (message, apiOpts) => {
|
|
5
|
-
const webUI = process.env.NETLIFY_WEB_UI || 'https://app.netlify.com';
|
|
6
6
|
const api = new NetlifyAPI('', apiOpts);
|
|
7
7
|
const ticket = await api.createTicket({ clientId: CLIENT_ID, body: { message } });
|
|
8
8
|
if (!ticket.id) {
|
|
9
9
|
return logAndThrowError('Failed to create login ticket');
|
|
10
10
|
}
|
|
11
11
|
const ticketId = ticket.id;
|
|
12
|
-
const url =
|
|
12
|
+
const url = buildAuthorizeUrl(ticketId);
|
|
13
13
|
logJson({
|
|
14
14
|
ticket_id: ticketId,
|
|
15
15
|
url,
|
|
@@ -54,6 +54,6 @@ export declare const applyOverrides: (template: string, overrides?: string) => s
|
|
|
54
54
|
export declare const getExistingContext: (path: string) => Promise<ParsedContextFile | null>;
|
|
55
55
|
export declare const writeFile: (path: string, contents: string) => Promise<void>;
|
|
56
56
|
export declare const deleteFile: (path: string) => Promise<void>;
|
|
57
|
-
export declare const downloadAndWriteContextFiles: (consumer: ConsumerConfig, { command }: RunRecipeOptions) => Promise<
|
|
57
|
+
export declare const downloadAndWriteContextFiles: (consumer: ConsumerConfig, { command }: RunRecipeOptions) => Promise<boolean>;
|
|
58
58
|
export {};
|
|
59
59
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -148,7 +148,7 @@ export const deleteFile = async (path) => {
|
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
150
|
export const downloadAndWriteContextFiles = async (consumer, { command }) => {
|
|
151
|
-
await Promise.allSettled(Object.keys(consumer.contextScopes).map(async (contextKey) => {
|
|
151
|
+
const results = await Promise.allSettled(Object.keys(consumer.contextScopes).map(async (contextKey) => {
|
|
152
152
|
const contextConfig = consumer.contextScopes[contextKey];
|
|
153
153
|
const { contents: downloadedFile, minimumCLIVersion } = (await downloadFile(version, contextConfig, consumer).catch(() => null)) ?? {};
|
|
154
154
|
if (!downloadedFile) {
|
|
@@ -168,7 +168,7 @@ export const downloadAndWriteContextFiles = async (consumer, { command }) => {
|
|
|
168
168
|
if (existing.provider?.toLowerCase() === NETLIFY_PROVIDER) {
|
|
169
169
|
if (remote.version === existing.version) {
|
|
170
170
|
log(`You're all up to date! ${chalk.underline(absoluteFilePath)} contains the latest version of the context files.`);
|
|
171
|
-
return;
|
|
171
|
+
return false;
|
|
172
172
|
}
|
|
173
173
|
// We must preserve any overrides found in the existing file.
|
|
174
174
|
contents = applyOverrides(remote.contents, existing.overrides?.innerContents);
|
|
@@ -190,6 +190,12 @@ export const downloadAndWriteContextFiles = async (consumer, { command }) => {
|
|
|
190
190
|
}
|
|
191
191
|
await writeFile(absoluteFilePath, contents);
|
|
192
192
|
log(`${existing ? 'Updated' : 'Created'} context files at ${chalk.underline(absoluteFilePath)}`);
|
|
193
|
+
return true;
|
|
193
194
|
}));
|
|
195
|
+
const failure = results.find((result) => result.status === 'rejected');
|
|
196
|
+
if (failure) {
|
|
197
|
+
throw failure.reason;
|
|
198
|
+
}
|
|
199
|
+
return results.some((result) => result.status === 'fulfilled' && result.value);
|
|
194
200
|
};
|
|
195
201
|
//# sourceMappingURL=context.js.map
|
|
@@ -2,6 +2,7 @@ import { resolve } from 'node:path';
|
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import execa from 'execa';
|
|
4
4
|
import { logAndThrowError, log, version } from '../../utils/command-helpers.js';
|
|
5
|
+
import { track } from '../../utils/telemetry/index.js';
|
|
5
6
|
import { getExistingContext, NTL_DEV_MCP_FILE_NAME, getContextConsumers, deleteFile, downloadAndWriteContextFiles, } from './context.js';
|
|
6
7
|
export const description = 'Manage context files for AI tools';
|
|
7
8
|
// context consumers endpoints returns all supported IDE and other consumers
|
|
@@ -114,8 +115,9 @@ export const run = async (runOptions) => {
|
|
|
114
115
|
log('No context files found for this consumer. Try again or let us know if this happens again via our support channels.');
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
118
|
+
let wroteFiles = false;
|
|
117
119
|
try {
|
|
118
|
-
await downloadAndWriteContextFiles(consumer, runOptions);
|
|
120
|
+
wroteFiles = await downloadAndWriteContextFiles(consumer, runOptions);
|
|
119
121
|
// the deprecated MCP file path
|
|
120
122
|
// let's remove that file if it exists.
|
|
121
123
|
const priorContextFilePath = resolve(command?.workingDir ?? '', consumer.path, NTL_DEV_MCP_FILE_NAME);
|
|
@@ -128,5 +130,8 @@ export const run = async (runOptions) => {
|
|
|
128
130
|
catch (error) {
|
|
129
131
|
logAndThrowError(error);
|
|
130
132
|
}
|
|
133
|
+
if (wroteFiles) {
|
|
134
|
+
await track('sites_aiContextInstalled', { consumer: consumer.key });
|
|
135
|
+
}
|
|
131
136
|
};
|
|
132
137
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const CANONICAL_AGENT_NAMES: readonly ['claude', 'codex', 'copilot', 'gemini', 'cursor', 'opencode', 'kiro', 'cline', 'amp', 'warp', 'claudeai', 'chatgpt', 'other'];
|
|
2
|
+
export type CanonicalAgentName = (typeof CANONICAL_AGENT_NAMES)[number];
|
|
3
|
+
export type DrivingAgent = {
|
|
4
|
+
name: CanonicalAgentName;
|
|
5
|
+
source: string;
|
|
6
|
+
version?: string;
|
|
7
|
+
markers?: CanonicalAgentName[];
|
|
8
|
+
otherValue?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const getDrivingAgent: (env?: NodeJS.ProcessEnv) => DrivingAgent | undefined;
|
|
11
|
+
//# sourceMappingURL=agent-detection.d.ts.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Only markers an agent product sets on its own count as a signal; never infer from process names, terminals, or the process tree.
|
|
2
|
+
// The result is untrusted attribution from the environment, for telemetry and labeling only, never for authorization.
|
|
3
|
+
export const CANONICAL_AGENT_NAMES = [
|
|
4
|
+
'claude',
|
|
5
|
+
'codex',
|
|
6
|
+
'copilot',
|
|
7
|
+
'gemini',
|
|
8
|
+
'cursor',
|
|
9
|
+
'opencode',
|
|
10
|
+
'kiro',
|
|
11
|
+
'cline',
|
|
12
|
+
'amp',
|
|
13
|
+
'warp',
|
|
14
|
+
'claudeai',
|
|
15
|
+
'chatgpt',
|
|
16
|
+
'other',
|
|
17
|
+
];
|
|
18
|
+
const ANNOUNCED_NAME_TABLE = new Map([
|
|
19
|
+
...CANONICAL_AGENT_NAMES.filter((name) => name !== 'other').map((name) => [name, name]),
|
|
20
|
+
['claude-code', 'claude'],
|
|
21
|
+
['claude-ai', 'claudeai'],
|
|
22
|
+
['github-copilot', 'copilot'],
|
|
23
|
+
['github-copilot-cli', 'copilot'],
|
|
24
|
+
['github-copilot-vscode-agent', 'copilot'],
|
|
25
|
+
['cursor-cli', 'cursor'],
|
|
26
|
+
['gemini-cli', 'gemini'],
|
|
27
|
+
['kiro-cli', 'kiro'],
|
|
28
|
+
['warp-oz', 'warp'],
|
|
29
|
+
]);
|
|
30
|
+
const lookupAnnouncedName = (key) => ANNOUNCED_NAME_TABLE.get(key.replace(/_/g, '-'));
|
|
31
|
+
const sanitizeAnnouncedValue = (raw) => raw.replace(/[^A-Za-z0-9_.-]/g, '').slice(0, 64);
|
|
32
|
+
const nonEmpty = (value) => (value ? value : undefined);
|
|
33
|
+
const parseAnnouncedName = (raw) => {
|
|
34
|
+
const atIndex = raw.indexOf('@');
|
|
35
|
+
const sanitized = sanitizeAnnouncedValue(atIndex === -1 ? raw : raw.slice(0, atIndex));
|
|
36
|
+
if (sanitized === '') {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const announcedVersion = atIndex === -1 ? undefined : nonEmpty(sanitizeAnnouncedValue(raw.slice(atIndex + 1)));
|
|
40
|
+
const key = sanitized.toLowerCase();
|
|
41
|
+
const exact = lookupAnnouncedName(key);
|
|
42
|
+
if (exact) {
|
|
43
|
+
return { name: exact, version: announcedVersion };
|
|
44
|
+
}
|
|
45
|
+
const withoutAgentSuffix = key.replace(/_agent$/, '');
|
|
46
|
+
const suffixMatch = lookupAnnouncedName(withoutAgentSuffix);
|
|
47
|
+
if (suffixMatch) {
|
|
48
|
+
return { name: suffixMatch, version: announcedVersion };
|
|
49
|
+
}
|
|
50
|
+
const lastUnderscore = withoutAgentSuffix.lastIndexOf('_');
|
|
51
|
+
if (lastUnderscore !== -1) {
|
|
52
|
+
const head = withoutAgentSuffix.slice(0, lastUnderscore);
|
|
53
|
+
const headMatch = lookupAnnouncedName(head);
|
|
54
|
+
if (headMatch) {
|
|
55
|
+
const tail = withoutAgentSuffix.slice(lastUnderscore + 1);
|
|
56
|
+
return { name: headMatch, version: announcedVersion ?? tail.replace(/-/g, '.') };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { name: 'other', otherValue: sanitized, version: announcedVersion };
|
|
60
|
+
};
|
|
61
|
+
// Precedence, first match wins: NETLIFY_AGENT (explicit, even when unknown); markers only the process
|
|
62
|
+
// running the command sets; AI_AGENT (explicit, even when unknown); markers inherited from an agent
|
|
63
|
+
// session; runner/task markers such as Warp's last, since the agent inside the run is the more specific answer.
|
|
64
|
+
const SIGNALS = [
|
|
65
|
+
{
|
|
66
|
+
source: 'NETLIFY_AGENT',
|
|
67
|
+
detect: (env) => (env.NETLIFY_AGENT === undefined ? undefined : parseAnnouncedName(env.NETLIFY_AGENT)),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
source: 'CODEX_CI',
|
|
71
|
+
detect: (env) => (env.CODEX_CI === '1' ? { name: 'codex' } : undefined),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
source: 'GEMINI_CLI',
|
|
75
|
+
detect: (env) => (env.GEMINI_CLI === '1' ? { name: 'gemini' } : undefined),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
source: 'COPILOT_CLI',
|
|
79
|
+
detect: (env) => (env.COPILOT_CLI === '1' ? { name: 'copilot' } : undefined),
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
source: 'COPILOT_AGENT_SESSION_ID',
|
|
83
|
+
detect: (env) => (nonEmpty(env.COPILOT_AGENT_SESSION_ID) === undefined ? undefined : { name: 'copilot' }),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
source: 'OPENCODE',
|
|
87
|
+
detect: (env) => env.OPENCODE === '1' && nonEmpty(env.OPENCODE_TERMINAL) === undefined ? { name: 'opencode' } : undefined,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
source: 'AGENT_DISPLAY_OUT',
|
|
91
|
+
detect: (env) => nonEmpty(env.AGENT_DISPLAY_OUT) !== undefined && nonEmpty(env.AGENT_CONTEXT_OUT) !== undefined
|
|
92
|
+
? { name: 'kiro' }
|
|
93
|
+
: undefined,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
source: 'AI_AGENT',
|
|
97
|
+
detect: (env) => (env.AI_AGENT === undefined ? undefined : parseAnnouncedName(env.AI_AGENT)),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
source: 'COPILOT_AGENT',
|
|
101
|
+
detect: (env) => (env.COPILOT_AGENT === '1' ? { name: 'copilot' } : undefined),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
source: 'CURSOR_AGENT',
|
|
105
|
+
detect: (env) => (env.CURSOR_AGENT === '1' ? { name: 'cursor' } : undefined),
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
source: 'CLINE_ACTIVE',
|
|
109
|
+
detect: (env) => (env.CLINE_ACTIVE === 'true' ? { name: 'cline' } : undefined),
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
source: 'AGENT',
|
|
113
|
+
detect: (env) => (env.AGENT === 'amp' ? { name: 'amp' } : undefined),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
source: 'CLAUDE_CODE_CHILD_SESSION',
|
|
117
|
+
detect: (env) => (env.CLAUDE_CODE_CHILD_SESSION === '1' ? { name: 'claude' } : undefined),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
source: 'OZ_RUN_ID',
|
|
121
|
+
detect: (env) => (nonEmpty(env.OZ_RUN_ID) === undefined ? undefined : { name: 'warp' }),
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
source: 'WARP_RUN_ID',
|
|
125
|
+
detect: (env) => (nonEmpty(env.WARP_RUN_ID) === undefined ? undefined : { name: 'warp' }),
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
export const getDrivingAgent = (env = process.env) => {
|
|
129
|
+
const matches = [];
|
|
130
|
+
for (const signal of SIGNALS) {
|
|
131
|
+
const result = signal.detect(env);
|
|
132
|
+
if (result) {
|
|
133
|
+
matches.push({ source: signal.source, ...result });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (matches.length === 0) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
const [winner] = matches;
|
|
140
|
+
const codexVersion = winner.source === 'CODEX_CI' ? nonEmpty(env.CODEX_VERSION) : undefined;
|
|
141
|
+
const version = winner.version ?? (codexVersion === undefined ? undefined : sanitizeAnnouncedValue(codexVersion));
|
|
142
|
+
const distinctNames = [...new Set(matches.map((match) => match.name))];
|
|
143
|
+
return {
|
|
144
|
+
name: winner.name,
|
|
145
|
+
source: winner.source,
|
|
146
|
+
...(version ? { version } : {}),
|
|
147
|
+
...(distinctNames.length >= 2 ? { markers: distinctNames } : {}),
|
|
148
|
+
...(winner.name === 'other' ? { otherValue: winner.otherValue ?? '' } : {}),
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=agent-detection.js.map
|
|
@@ -15,6 +15,7 @@ export type ChalkInstance = ChalkInstancePrimitiveType;
|
|
|
15
15
|
export declare const padLeft: (str: string, count: number, filler?: string) => string;
|
|
16
16
|
export declare const version: string;
|
|
17
17
|
export declare const USER_AGENT: string;
|
|
18
|
+
export declare const getRequestUserAgent: (env?: NodeJS.ProcessEnv) => string;
|
|
18
19
|
export declare const NETLIFY_CYAN: ChalkInstancePrimitiveType;
|
|
19
20
|
export declare const NETLIFY_CYAN_HEX = "#28b5ac";
|
|
20
21
|
export declare const NETLIFYDEVLOG: string;
|
|
@@ -7,6 +7,7 @@ import { Chalk } from 'chalk';
|
|
|
7
7
|
import WSL from 'is-wsl';
|
|
8
8
|
import terminalLink from 'terminal-link';
|
|
9
9
|
import { startSpinner } from '../lib/spinner.js';
|
|
10
|
+
import { getDrivingAgent } from './agent-detection.js';
|
|
10
11
|
import getCLIPackageJson from './get-cli-package-json.js';
|
|
11
12
|
import { reportError } from './telemetry/report-error.js';
|
|
12
13
|
/** The parsed process argv without the binary only arguments and flags */
|
|
@@ -38,6 +39,10 @@ const arch = os.arch() === 'ia32' ? 'x86' : os.arch();
|
|
|
38
39
|
const { name, version: packageVersion } = await getCLIPackageJson();
|
|
39
40
|
export const version = packageVersion;
|
|
40
41
|
export const USER_AGENT = `${name}/${version} ${platform}-${arch} node-${process.version}`;
|
|
42
|
+
export const getRequestUserAgent = (env = process.env) => {
|
|
43
|
+
const agent = getDrivingAgent(env);
|
|
44
|
+
return agent ? `${USER_AGENT} agent/${agent.name}` : USER_AGENT;
|
|
45
|
+
};
|
|
41
46
|
/** A list of base command flags that needs to be sorted down on documentation and on help pages */
|
|
42
47
|
const BASE_FLAGS = new Set(['--debug', '--http-proxy', '--http-proxy-certificate-filename']);
|
|
43
48
|
export const NETLIFY_CYAN = chalk.rgb(40, 180, 170);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getDrivingAgent } from './agent-detection.js';
|
|
2
|
+
// By contract these two hold only a non-sensitive agent name[@version]. Every other marker's value is a flag,
|
|
3
|
+
// a session or run id, or a path, none of which may reach a URL.
|
|
4
|
+
const SOURCES_WITH_ANNOUNCED_VALUE = new Set(['NETLIFY_AGENT', 'AI_AGENT']);
|
|
5
|
+
const sanitizeUtmTerm = (raw) => raw.replace(/[^A-Za-z0-9_.:@-]/g, '').slice(0, 64);
|
|
6
|
+
const getUtmTerm = (source, env) => {
|
|
7
|
+
const value = SOURCES_WITH_ANNOUNCED_VALUE.has(source) ? env[source] : undefined;
|
|
8
|
+
return sanitizeUtmTerm(value ? `${source}:${value}` : source);
|
|
9
|
+
};
|
|
10
|
+
export const buildAuthorizeUrl = (ticketId, env = process.env) => {
|
|
11
|
+
const webUI = env.NETLIFY_WEB_UI || 'https://app.netlify.com';
|
|
12
|
+
const params = new URLSearchParams({
|
|
13
|
+
response_type: 'ticket',
|
|
14
|
+
ticket: ticketId,
|
|
15
|
+
utm_source: 'cli',
|
|
16
|
+
utm_campaign: 'integrations',
|
|
17
|
+
});
|
|
18
|
+
const agent = getDrivingAgent(env);
|
|
19
|
+
if (agent) {
|
|
20
|
+
params.set('utm_content', agent.name);
|
|
21
|
+
params.set('utm_term', getUtmTerm(agent.source, env));
|
|
22
|
+
}
|
|
23
|
+
return `${webUI}/authorize?${params.toString()}`;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=login-url.js.map
|
|
@@ -3,6 +3,7 @@ import process, { version as nodejsVersion } from 'process';
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { getGlobalConfigStore } from '@netlify/dev-utils';
|
|
5
5
|
import { isCI } from 'ci-info';
|
|
6
|
+
import { getDrivingAgent } from '../agent-detection.js';
|
|
6
7
|
import execa from '../execa.js';
|
|
7
8
|
import { isTelemetryDisabled, cliVersion } from './utils.js';
|
|
8
9
|
import isValidEventName from './validation.js';
|
|
@@ -36,6 +37,18 @@ const eventConfig = {
|
|
|
36
37
|
'user',
|
|
37
38
|
],
|
|
38
39
|
};
|
|
40
|
+
// Every key is always present so a caller's payload can never supply its own agent attribution;
|
|
41
|
+
// undefined values are dropped when the event is serialized.
|
|
42
|
+
const getAgentProperties = () => {
|
|
43
|
+
const agent = getDrivingAgent();
|
|
44
|
+
return {
|
|
45
|
+
agent: agent?.name,
|
|
46
|
+
agent_source: agent?.source,
|
|
47
|
+
agent_version: agent?.version,
|
|
48
|
+
agent_markers: agent?.markers,
|
|
49
|
+
agent_other_value: agent?.otherValue,
|
|
50
|
+
};
|
|
51
|
+
};
|
|
39
52
|
/**
|
|
40
53
|
* Tracks a custom event with the provided payload
|
|
41
54
|
*/
|
|
@@ -65,7 +78,7 @@ export async function track(eventName, payload = {}) {
|
|
|
65
78
|
anonymousId: cliId,
|
|
66
79
|
duration,
|
|
67
80
|
status,
|
|
68
|
-
properties: { ...properties, nodejsVersion, cliVersion },
|
|
81
|
+
properties: { ...properties, nodejsVersion, cliVersion, ...getAgentProperties() },
|
|
69
82
|
};
|
|
70
83
|
return send('track', defaultData);
|
|
71
84
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netlify-cli",
|
|
3
|
-
"version": "27.
|
|
3
|
+
"version": "27.6.0",
|
|
4
4
|
"description": "Netlify command line tool",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"@netlify/ai": "^1.0.1",
|
|
57
57
|
"@netlify/api": "^15.1.2",
|
|
58
58
|
"@netlify/blobs": "^11.0.3",
|
|
59
|
-
"@netlify/build": "^36.4.
|
|
59
|
+
"@netlify/build": "^36.4.8",
|
|
60
60
|
"@netlify/build-info": "^11.3.0",
|
|
61
|
-
"@netlify/config": "^25.2.
|
|
61
|
+
"@netlify/config": "^25.2.5",
|
|
62
62
|
"@netlify/dev": "^5.0.5",
|
|
63
63
|
"@netlify/dev-utils": "^6.0.1",
|
|
64
64
|
"@netlify/edge-bundler": "^16.0.4",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"@netlify/images": "^2.0.1",
|
|
69
69
|
"@netlify/local-functions-proxy": "^2.0.3",
|
|
70
70
|
"@netlify/redirect-parser": "^16.1.1",
|
|
71
|
-
"@netlify/zip-it-and-ship-it": "^15.5.
|
|
71
|
+
"@netlify/zip-it-and-ship-it": "^15.5.1",
|
|
72
72
|
"@octokit/rest": "^22.0.0",
|
|
73
73
|
"@opentelemetry/api": "^1.8.0",
|
|
74
74
|
"@pnpm/tabtab": "^0.5.4",
|