@hubspot/cli 8.1.0-experimental.0 → 8.1.1-experimental.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/commands/project/list.d.ts +2 -2
- package/commands/project/list.js +1 -0
- package/commands/project/upload.d.ts +1 -0
- package/commands/project/upload.js +7 -1
- package/lang/en.d.ts +10 -0
- package/lang/en.js +10 -0
- package/lib/api/usageTracking.d.ts +29 -0
- package/lib/api/usageTracking.js +28 -0
- package/lib/projects/npmAuditOnUpload.d.ts +10 -0
- package/lib/projects/npmAuditOnUpload.js +73 -0
- package/lib/projects/upload.d.ts +2 -1
- package/lib/projects/upload.js +13 -3
- package/lib/projects/workspaces.d.ts +1 -11
- package/lib/projects/workspaces.js +12 -27
- package/lib/usageTracking.d.ts +7 -17
- package/lib/usageTracking.js +43 -29
- package/lib/yargs/makeYargsHandlerWithUsageTracking.js +28 -2
- package/mcp-server/utils/toolUsageTracking.js +10 -6
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AccountArgs, CommonArgs, ConfigArgs, YargsCommandModule } from '../../types/Yargs.js';
|
|
2
|
-
type ProjectListArgs = CommonArgs & ConfigArgs & AccountArgs;
|
|
1
|
+
import { AccountArgs, CommonArgs, ConfigArgs, EnvironmentArgs, YargsCommandModule } from '../../types/Yargs.js';
|
|
2
|
+
type ProjectListArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs;
|
|
3
3
|
declare const projectListCommand: YargsCommandModule<unknown, ProjectListArgs>;
|
|
4
4
|
export default projectListCommand;
|
package/commands/project/list.js
CHANGED
|
@@ -18,7 +18,7 @@ import { projectProfilePrompt } from '../../lib/prompts/projectProfilePrompt.js'
|
|
|
18
18
|
const command = 'upload';
|
|
19
19
|
const describe = commands.project.upload.describe;
|
|
20
20
|
async function handler(args) {
|
|
21
|
-
const { forceCreate, message, derivedAccountId, skipValidation, formatOutputAsJson, profile: profileOption, useEnv: useEnvOption, exit, addUsageMetadata, } = args;
|
|
21
|
+
const { forceCreate, message, derivedAccountId, skipValidation, skipNpmAudit, formatOutputAsJson, profile: profileOption, useEnv: useEnvOption, exit, addUsageMetadata, } = args;
|
|
22
22
|
const jsonOutput = {};
|
|
23
23
|
const { projectConfig, projectDir } = await getProjectConfig();
|
|
24
24
|
try {
|
|
@@ -66,6 +66,7 @@ async function handler(args) {
|
|
|
66
66
|
isUploadCommand: true,
|
|
67
67
|
sendIR: !isLegacyProject(projectConfig.platformVersion),
|
|
68
68
|
skipValidation,
|
|
69
|
+
skipNpmAudit,
|
|
69
70
|
profile: profileName,
|
|
70
71
|
});
|
|
71
72
|
if (uploadError) {
|
|
@@ -127,6 +128,11 @@ function projectUploadBuilder(yargs) {
|
|
|
127
128
|
hidden: true,
|
|
128
129
|
default: false,
|
|
129
130
|
},
|
|
131
|
+
'skip-npm-audit': {
|
|
132
|
+
describe: commands.project.upload.options.skipNpmAudit.describe,
|
|
133
|
+
type: 'boolean',
|
|
134
|
+
default: false,
|
|
135
|
+
},
|
|
130
136
|
profile: {
|
|
131
137
|
type: 'string',
|
|
132
138
|
alias: 'p',
|
package/lang/en.d.ts
CHANGED
|
@@ -1826,6 +1826,9 @@ export declare const commands: {
|
|
|
1826
1826
|
profile: {
|
|
1827
1827
|
describe: string;
|
|
1828
1828
|
};
|
|
1829
|
+
skipNpmAudit: {
|
|
1830
|
+
describe: string;
|
|
1831
|
+
};
|
|
1829
1832
|
};
|
|
1830
1833
|
};
|
|
1831
1834
|
watch: {
|
|
@@ -3089,6 +3092,9 @@ export declare const lib: {
|
|
|
3089
3092
|
process: {
|
|
3090
3093
|
exitDebug: (signal: string) => string;
|
|
3091
3094
|
};
|
|
3095
|
+
handlerLogFile: {
|
|
3096
|
+
saved: (filePath: string) => string;
|
|
3097
|
+
};
|
|
3092
3098
|
DevServerManager: {
|
|
3093
3099
|
portConflict: (port: string) => string;
|
|
3094
3100
|
notInitialized: string;
|
|
@@ -3391,6 +3397,10 @@ export declare const lib: {
|
|
|
3391
3397
|
updatingPackageJsonWorkspaces: (packageJsonPath: string) => string;
|
|
3392
3398
|
updatedWorkspaces: (workspaces: string) => string;
|
|
3393
3399
|
updatedFileDependency: (packageName: string, relativePath: string) => string;
|
|
3400
|
+
npmAuditClean: (packageRoot: string) => string;
|
|
3401
|
+
npmAuditIssues: (packageRoot: string, details: string) => string;
|
|
3402
|
+
npmAuditNpmUnavailable: (packageRoot: string) => string;
|
|
3403
|
+
npmAuditNonZeroExit: (packageRoot: string, exitCode: number) => string;
|
|
3394
3404
|
};
|
|
3395
3405
|
};
|
|
3396
3406
|
importData: {
|
package/lang/en.js
CHANGED
|
@@ -1843,6 +1843,9 @@ export const commands = {
|
|
|
1843
1843
|
profile: {
|
|
1844
1844
|
describe: 'Profile to target for this upload',
|
|
1845
1845
|
},
|
|
1846
|
+
skipNpmAudit: {
|
|
1847
|
+
describe: 'Skip the npm audit security check before uploading',
|
|
1848
|
+
},
|
|
1846
1849
|
},
|
|
1847
1850
|
},
|
|
1848
1851
|
watch: {
|
|
@@ -3115,6 +3118,9 @@ export const lib = {
|
|
|
3115
3118
|
process: {
|
|
3116
3119
|
exitDebug: (signal) => `Attempting to gracefully exit. Triggered by ${signal}`,
|
|
3117
3120
|
},
|
|
3121
|
+
handlerLogFile: {
|
|
3122
|
+
saved: (filePath) => `Debug logs can be viewed at ${filePath}`,
|
|
3123
|
+
},
|
|
3118
3124
|
DevServerManager: {
|
|
3119
3125
|
portConflict: (port) => `The port ${port} is already in use.`,
|
|
3120
3126
|
notInitialized: 'The Dev Server Manager must be initialized before it is started.',
|
|
@@ -3417,6 +3423,10 @@ export const lib = {
|
|
|
3417
3423
|
updatingPackageJsonWorkspaces: (packageJsonPath) => `Updating package.json workspaces in archive: ${packageJsonPath}`,
|
|
3418
3424
|
updatedWorkspaces: (workspaces) => ` Updated workspaces: ${workspaces}`,
|
|
3419
3425
|
updatedFileDependency: (packageName, relativePath) => ` Updated dependencies.${packageName}: file:${relativePath}`,
|
|
3426
|
+
npmAuditClean: (packageRoot) => `npm audit: No npm dependency issues found for ${chalk.bold(packageRoot)}`,
|
|
3427
|
+
npmAuditIssues: (packageRoot, details) => `npm audit: security issues found for ${chalk.bold(packageRoot)}: ${details}`,
|
|
3428
|
+
npmAuditNpmUnavailable: (packageRoot) => `npm audit: skipped for ${chalk.bold(packageRoot)} (npm not available in PATH)`,
|
|
3429
|
+
npmAuditNonZeroExit: (packageRoot, exitCode) => `npm audit: ${chalk.bold(packageRoot)} exited with code ${exitCode}`,
|
|
3420
3430
|
},
|
|
3421
3431
|
},
|
|
3422
3432
|
importData: {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type ConfigType = 'local' | 'global';
|
|
2
|
+
export type ExecutionSource = 'ci' | 'mcp' | 'user';
|
|
3
|
+
export type UsageTrackingMeta = {
|
|
4
|
+
action?: string;
|
|
5
|
+
os?: string;
|
|
6
|
+
nodeVersion?: string;
|
|
7
|
+
nodeMajorVersion?: string;
|
|
8
|
+
version?: string;
|
|
9
|
+
command?: string;
|
|
10
|
+
authType?: string;
|
|
11
|
+
step?: string;
|
|
12
|
+
assetType?: string;
|
|
13
|
+
mode?: string;
|
|
14
|
+
type?: string | number;
|
|
15
|
+
file?: boolean;
|
|
16
|
+
successful?: boolean;
|
|
17
|
+
configType?: ConfigType;
|
|
18
|
+
executionSource?: ExecutionSource;
|
|
19
|
+
platformVersion?: string;
|
|
20
|
+
executionTime?: number;
|
|
21
|
+
};
|
|
22
|
+
export type UsageTrackingRequest = {
|
|
23
|
+
portalId?: number;
|
|
24
|
+
accountId?: number;
|
|
25
|
+
eventName: string;
|
|
26
|
+
eventClass: string;
|
|
27
|
+
meta: UsageTrackingMeta;
|
|
28
|
+
};
|
|
29
|
+
export declare function sendUsageEvent(request: UsageTrackingRequest): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { http } from '@hubspot/local-dev-lib/http';
|
|
2
|
+
import { http as unauthedHttp } from '@hubspot/local-dev-lib/http/unauthed';
|
|
3
|
+
import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
|
|
4
|
+
const USAGE_PATH = 'local/dev/tools/proxy/v1/usage';
|
|
5
|
+
const USAGE_AUTHENTICATED_PATH = `${USAGE_PATH}/authenticated`;
|
|
6
|
+
export async function sendUsageEvent(request) {
|
|
7
|
+
const { accountId } = request;
|
|
8
|
+
if (accountId) {
|
|
9
|
+
try {
|
|
10
|
+
const account = getConfigAccountById(accountId);
|
|
11
|
+
if (account?.authType === 'personalaccesskey') {
|
|
12
|
+
await http.post(accountId, {
|
|
13
|
+
url: USAGE_AUTHENTICATED_PATH,
|
|
14
|
+
data: request,
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch (_e) { }
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
await unauthedHttp.post({
|
|
23
|
+
url: USAGE_PATH,
|
|
24
|
+
data: request,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch (_e) { }
|
|
28
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ParsedPackageJson } from '@hubspot/project-parsing-lib/workspaces';
|
|
2
|
+
export declare function summarizeNpmAuditJson(source: string): string | null;
|
|
3
|
+
type RunNpmAuditsBeforeProjectUploadArgs = {
|
|
4
|
+
srcDir: string;
|
|
5
|
+
projectDir: string;
|
|
6
|
+
parsedPackageJsons: ParsedPackageJson[];
|
|
7
|
+
isLegacyPlatform: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare function runNpmAuditsBeforeProjectUpload({ srcDir, projectDir, parsedPackageJsons, isLegacyPlatform, }: RunNpmAuditsBeforeProjectUploadArgs): Promise<void>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { runNpmAuditJson } from '@hubspot/ui-extensions-dev-server';
|
|
3
|
+
import { lib } from '../../lang/en.js';
|
|
4
|
+
import { uiLogger } from '../ui/logger.js';
|
|
5
|
+
export function summarizeNpmAuditJson(source) {
|
|
6
|
+
try {
|
|
7
|
+
const data = JSON.parse(source);
|
|
8
|
+
const errorMessage = data.error?.message ?? data.error?.summary;
|
|
9
|
+
if (errorMessage) {
|
|
10
|
+
return errorMessage;
|
|
11
|
+
}
|
|
12
|
+
const v = data.metadata?.vulnerabilities;
|
|
13
|
+
if (!v) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const total = v.total ?? 0;
|
|
17
|
+
if (total === 0) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const severityOrder = [
|
|
21
|
+
'critical',
|
|
22
|
+
'high',
|
|
23
|
+
'moderate',
|
|
24
|
+
'low',
|
|
25
|
+
'info',
|
|
26
|
+
];
|
|
27
|
+
const parts = severityOrder
|
|
28
|
+
.filter(severity => (v[severity] ?? 0) > 0)
|
|
29
|
+
.map(severity => `${v[severity]} ${severity}`);
|
|
30
|
+
return `${total} total (${parts.join(', ')})`;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function runNpmAuditsBeforeProjectUpload({ srcDir, projectDir, parsedPackageJsons, isLegacyPlatform, }) {
|
|
37
|
+
const auditRoots = new Set();
|
|
38
|
+
if (isLegacyPlatform) {
|
|
39
|
+
auditRoots.add(srcDir);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
for (const { dir } of parsedPackageJsons) {
|
|
43
|
+
auditRoots.add(dir);
|
|
44
|
+
}
|
|
45
|
+
if (auditRoots.size === 0) {
|
|
46
|
+
auditRoots.add(srcDir);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const auditRootArray = [...auditRoots];
|
|
50
|
+
const results = await Promise.all(auditRootArray.map(auditRoot => runNpmAuditJson(auditRoot)));
|
|
51
|
+
for (let i = 0; i < auditRootArray.length; i++) {
|
|
52
|
+
const auditRoot = auditRootArray[i];
|
|
53
|
+
const result = results[i];
|
|
54
|
+
if (result.skipped) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const relativeRoot = path.relative(projectDir, auditRoot) || '.';
|
|
58
|
+
const summary = summarizeNpmAuditJson(result.source);
|
|
59
|
+
if (result.exitCode === 127) {
|
|
60
|
+
uiLogger.warn(lib.projectUpload.handleProjectUpload.npmAuditNpmUnavailable(relativeRoot));
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (summary) {
|
|
64
|
+
uiLogger.warn(lib.projectUpload.handleProjectUpload.npmAuditIssues(relativeRoot, summary));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (result.exitCode !== 0) {
|
|
68
|
+
uiLogger.warn(lib.projectUpload.handleProjectUpload.npmAuditNonZeroExit(relativeRoot, result.exitCode));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
uiLogger.success(lib.projectUpload.handleProjectUpload.npmAuditClean(relativeRoot));
|
|
72
|
+
}
|
|
73
|
+
}
|
package/lib/projects/upload.d.ts
CHANGED
|
@@ -16,9 +16,10 @@ type HandleProjectUploadArg<T> = {
|
|
|
16
16
|
isUploadCommand?: boolean;
|
|
17
17
|
sendIR?: boolean;
|
|
18
18
|
skipValidation?: boolean;
|
|
19
|
+
skipNpmAudit?: boolean;
|
|
19
20
|
profile?: string;
|
|
20
21
|
};
|
|
21
|
-
export declare function handleProjectUpload<T>({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage, forceCreate, isUploadCommand, sendIR, skipValidation, }: HandleProjectUploadArg<T>): Promise<ProjectUploadResult<T>>;
|
|
22
|
+
export declare function handleProjectUpload<T>({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage, forceCreate, isUploadCommand, sendIR, skipValidation, skipNpmAudit, }: HandleProjectUploadArg<T>): Promise<ProjectUploadResult<T>>;
|
|
22
23
|
export declare function validateSourceDirectory(srcDir: string, projectConfig: ProjectConfig, projectDir: string): Promise<void>;
|
|
23
24
|
export declare function validateNoHSMetaMismatch(srcDir: string, projectConfig: ProjectConfig): Promise<void>;
|
|
24
25
|
type HandleTranslateArg = {
|
package/lib/projects/upload.js
CHANGED
|
@@ -18,6 +18,7 @@ import { walk } from '@hubspot/local-dev-lib/fs';
|
|
|
18
18
|
import { LEGACY_CONFIG_FILES } from '../constants.js';
|
|
19
19
|
import { archiveWorkspacesAndDependencies, getPackageJsonPathsToUpdate, getLockfilePathsToUpdate, } from './workspaces.js';
|
|
20
20
|
import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
|
|
21
|
+
import { runNpmAuditsBeforeProjectUpload } from './npmAuditOnUpload.js';
|
|
21
22
|
async function uploadProjectFiles(accountId, projectName, filePath, uploadMessage, platformVersion, intermediateRepresentation) {
|
|
22
23
|
const accountIdentifier = uiAccountDescription(accountId) || `${accountId}`;
|
|
23
24
|
SpinniesManager.add('upload', {
|
|
@@ -44,7 +45,7 @@ async function uploadProjectFiles(accountId, projectName, filePath, uploadMessag
|
|
|
44
45
|
}
|
|
45
46
|
return { buildId, error };
|
|
46
47
|
}
|
|
47
|
-
export async function handleProjectUpload({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage = '', forceCreate = false, isUploadCommand = false, sendIR = false, skipValidation = false, }) {
|
|
48
|
+
export async function handleProjectUpload({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage = '', forceCreate = false, isUploadCommand = false, sendIR = false, skipValidation = false, skipNpmAudit = false, }) {
|
|
48
49
|
const srcDir = path.resolve(projectDir, projectConfig.srcDir);
|
|
49
50
|
await validateSourceDirectory(srcDir, projectConfig, projectDir);
|
|
50
51
|
await validateNoHSMetaMismatch(srcDir, projectConfig);
|
|
@@ -54,11 +55,20 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
|
|
|
54
55
|
// Versions <= 2025.1 do not support the new npm workspaces bundling behavior.
|
|
55
56
|
let workspaceMappings = [];
|
|
56
57
|
let fileDependencyMappings = [];
|
|
58
|
+
let parsedPackageJsons = [];
|
|
57
59
|
if (!isLegacyProject(projectConfig.platformVersion)) {
|
|
58
|
-
|
|
60
|
+
parsedPackageJsons = await findAndParsePackageJsonFiles(srcDir);
|
|
59
61
|
workspaceMappings = await collectWorkspaceDirectories(parsedPackageJsons);
|
|
60
62
|
fileDependencyMappings = await collectFileDependencies(parsedPackageJsons);
|
|
61
63
|
}
|
|
64
|
+
if (isUploadCommand && !skipNpmAudit) {
|
|
65
|
+
await runNpmAuditsBeforeProjectUpload({
|
|
66
|
+
srcDir,
|
|
67
|
+
projectDir,
|
|
68
|
+
parsedPackageJsons,
|
|
69
|
+
isLegacyPlatform: isLegacyProject(projectConfig.platformVersion),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
62
72
|
const output = fs.createWriteStream(tempFile.name);
|
|
63
73
|
const archive = archiver('zip');
|
|
64
74
|
const result = new Promise((resolve, reject) => output.on('close', async function () {
|
|
@@ -123,7 +133,7 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
|
|
|
123
133
|
return ignored ? false : file;
|
|
124
134
|
});
|
|
125
135
|
// Archive workspaces and file: dependencies
|
|
126
|
-
await archiveWorkspacesAndDependencies(archive, srcDir, workspaceMappings, fileDependencyMappings);
|
|
136
|
+
await archiveWorkspacesAndDependencies(archive, srcDir, projectDir, workspaceMappings, fileDependencyMappings);
|
|
127
137
|
archive.finalize();
|
|
128
138
|
return result;
|
|
129
139
|
}
|
|
@@ -12,16 +12,6 @@ export type WorkspaceArchiveResult = {
|
|
|
12
12
|
* Uses SHA256 truncated to 8 hex characters (4 billion possibilities).
|
|
13
13
|
*/
|
|
14
14
|
export declare function shortHash(input: string): string;
|
|
15
|
-
/**
|
|
16
|
-
* Converts native path separators to POSIX forward slashes.
|
|
17
|
-
*
|
|
18
|
-
* Zip entry names and npm workspace globs are POSIX-only. On Windows,
|
|
19
|
-
* `path.relative` returns backslash-separated paths; archiver normalizes
|
|
20
|
-
* its appended entry names to forward slashes but its filter callback
|
|
21
|
-
* receives forward-slashed names too. Without this normalization, lookups
|
|
22
|
-
* in our exclusion Sets miss on Windows and a file gets archived twice.
|
|
23
|
-
*/
|
|
24
|
-
export declare function toPosixPath(p: string): string;
|
|
25
15
|
/**
|
|
26
16
|
* Determines the archive path for an external workspace or file: dependency.
|
|
27
17
|
* Produces `_workspaces/<basename>-<hash>` with no subdirectory.
|
|
@@ -49,4 +39,4 @@ export declare function getLockfilePathsToUpdate(srcDir: string, workspaceMappin
|
|
|
49
39
|
* Main orchestration function that handles archiving of workspaces and file dependencies.
|
|
50
40
|
* This is the clean integration point for upload.ts.
|
|
51
41
|
*/
|
|
52
|
-
export declare function archiveWorkspacesAndDependencies(archive: archiver.Archiver, srcDir: string, workspaceMappings: WorkspaceMapping[], fileDependencyMappings: FileDependencyMapping[]): Promise<WorkspaceArchiveResult>;
|
|
42
|
+
export declare function archiveWorkspacesAndDependencies(archive: archiver.Archiver, srcDir: string, projectDir: string, workspaceMappings: WorkspaceMapping[], fileDependencyMappings: FileDependencyMapping[]): Promise<WorkspaceArchiveResult>;
|
|
@@ -12,21 +12,6 @@ import { lib } from '../../lang/en.js';
|
|
|
12
12
|
export function shortHash(input) {
|
|
13
13
|
return crypto.createHash('sha256').update(input).digest('hex').slice(0, 8);
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Converts native path separators to POSIX forward slashes.
|
|
17
|
-
*
|
|
18
|
-
* Zip entry names and npm workspace globs are POSIX-only. On Windows,
|
|
19
|
-
* `path.relative` returns backslash-separated paths; archiver normalizes
|
|
20
|
-
* its appended entry names to forward slashes but its filter callback
|
|
21
|
-
* receives forward-slashed names too. Without this normalization, lookups
|
|
22
|
-
* in our exclusion Sets miss on Windows and a file gets archived twice.
|
|
23
|
-
*/
|
|
24
|
-
export function toPosixPath(p) {
|
|
25
|
-
if (path.sep === path.posix.sep) {
|
|
26
|
-
return p;
|
|
27
|
-
}
|
|
28
|
-
return p.replaceAll(path.sep, path.posix.sep);
|
|
29
|
-
}
|
|
30
15
|
/**
|
|
31
16
|
* Determines the archive path for an external workspace or file: dependency.
|
|
32
17
|
* Produces `_workspaces/<basename>-<hash>` with no subdirectory.
|
|
@@ -35,7 +20,7 @@ export function toPosixPath(p) {
|
|
|
35
20
|
export function computeExternalArchivePath(absolutePath) {
|
|
36
21
|
const resolved = path.resolve(absolutePath);
|
|
37
22
|
const name = path.basename(resolved);
|
|
38
|
-
return path.
|
|
23
|
+
return path.join('_workspaces', `${name}-${shortHash(resolved)}`);
|
|
39
24
|
}
|
|
40
25
|
/**
|
|
41
26
|
* Returns true if dir is inside srcDir (i.e. it will already be included
|
|
@@ -86,7 +71,7 @@ async function archiveWorkspaceDirectories(archive, srcDir, workspaceMappings) {
|
|
|
86
71
|
if (isInsideSrcDir(workspaceDir, srcDir)) {
|
|
87
72
|
// Internal: already in archive from srcDir walk.
|
|
88
73
|
// Store the relative path from the package.json directory so npm can resolve it.
|
|
89
|
-
const relPath =
|
|
74
|
+
const relPath = path.relative(path.dirname(sourcePackageJsonPath), path.resolve(workspaceDir));
|
|
90
75
|
packageWorkspaceEntries.get(sourcePackageJsonPath).push(relPath);
|
|
91
76
|
}
|
|
92
77
|
else {
|
|
@@ -104,7 +89,7 @@ async function archiveWorkspaceDirectories(archive, srcDir, workspaceMappings) {
|
|
|
104
89
|
externalsToArchive.push({ dir: workspaceDir, archivePath });
|
|
105
90
|
}
|
|
106
91
|
const relPkgJsonDir = path.relative(srcDir, path.dirname(sourcePackageJsonPath));
|
|
107
|
-
const relativeEntry =
|
|
92
|
+
const relativeEntry = path.relative(relPkgJsonDir, archivePath);
|
|
108
93
|
packageWorkspaceEntries.get(sourcePackageJsonPath).push(relativeEntry);
|
|
109
94
|
}
|
|
110
95
|
}
|
|
@@ -145,7 +130,7 @@ async function archiveFileDependencies(archive, srcDir, fileDependencyMappings,
|
|
|
145
130
|
packageFileDeps.set(sourcePackageJsonPath, new Map());
|
|
146
131
|
}
|
|
147
132
|
const relPkgJsonDir = path.relative(srcDir, path.dirname(sourcePackageJsonPath));
|
|
148
|
-
const relativeArchivePath =
|
|
133
|
+
const relativeArchivePath = path.relative(relPkgJsonDir, archivePath);
|
|
149
134
|
packageFileDeps
|
|
150
135
|
.get(sourcePackageJsonPath)
|
|
151
136
|
.set(packageName, relativeArchivePath);
|
|
@@ -187,7 +172,7 @@ export async function updatePackageJsonInArchive(archive, srcDir, packageWorkspa
|
|
|
187
172
|
if (!fs.existsSync(packageJsonPath)) {
|
|
188
173
|
continue;
|
|
189
174
|
}
|
|
190
|
-
const relativePackageJsonPath =
|
|
175
|
+
const relativePackageJsonPath = path.relative(srcDir, packageJsonPath);
|
|
191
176
|
let rawContent;
|
|
192
177
|
try {
|
|
193
178
|
rawContent = fs.readFileSync(packageJsonPath, 'utf8');
|
|
@@ -276,11 +261,11 @@ export function rewriteLockfileForExternalDeps(lockfileContent, pathMappings) {
|
|
|
276
261
|
export function getPackageJsonPathsToUpdate(srcDir, workspaceMappings, fileDependencyMappings) {
|
|
277
262
|
const paths = new Set();
|
|
278
263
|
for (const { sourcePackageJsonPath } of workspaceMappings) {
|
|
279
|
-
paths.add(
|
|
264
|
+
paths.add(path.relative(srcDir, sourcePackageJsonPath));
|
|
280
265
|
}
|
|
281
266
|
for (const { localPath, sourcePackageJsonPath } of fileDependencyMappings) {
|
|
282
267
|
if (!isInsideSrcDir(localPath, srcDir)) {
|
|
283
|
-
paths.add(
|
|
268
|
+
paths.add(path.relative(srcDir, sourcePackageJsonPath));
|
|
284
269
|
}
|
|
285
270
|
}
|
|
286
271
|
return paths;
|
|
@@ -305,7 +290,7 @@ export function getLockfilePathsToUpdate(srcDir, workspaceMappings, fileDependen
|
|
|
305
290
|
for (const dir of dirsWithExternalDeps) {
|
|
306
291
|
const lockfilePath = path.join(dir, 'package-lock.json');
|
|
307
292
|
if (fs.existsSync(lockfilePath)) {
|
|
308
|
-
paths.add(
|
|
293
|
+
paths.add(path.relative(srcDir, lockfilePath));
|
|
309
294
|
}
|
|
310
295
|
}
|
|
311
296
|
return paths;
|
|
@@ -334,12 +319,12 @@ async function rewriteLockfilesInArchive(archive, srcDir, externalArchivePaths,
|
|
|
334
319
|
const pathMappings = [];
|
|
335
320
|
for (const [absoluteExternalPath, archivePath] of externalArchivePaths) {
|
|
336
321
|
pathMappings.push({
|
|
337
|
-
oldPath:
|
|
338
|
-
newPath:
|
|
322
|
+
oldPath: path.relative(dir, absoluteExternalPath),
|
|
323
|
+
newPath: path.relative(dir, path.join(srcDir, archivePath)),
|
|
339
324
|
});
|
|
340
325
|
}
|
|
341
326
|
const rewritten = rewriteLockfileForExternalDeps(lockfileContent, pathMappings);
|
|
342
|
-
const relativeLockfilePath =
|
|
327
|
+
const relativeLockfilePath = path.relative(srcDir, lockfilePath);
|
|
343
328
|
uiLogger.debug(lib.projectUpload.handleProjectUpload.updatingLockfile(relativeLockfilePath));
|
|
344
329
|
archive.append(JSON.stringify(rewritten, null, 2), {
|
|
345
330
|
name: relativeLockfilePath,
|
|
@@ -351,7 +336,7 @@ async function rewriteLockfilesInArchive(archive, srcDir, externalArchivePaths,
|
|
|
351
336
|
* Main orchestration function that handles archiving of workspaces and file dependencies.
|
|
352
337
|
* This is the clean integration point for upload.ts.
|
|
353
338
|
*/
|
|
354
|
-
export async function archiveWorkspacesAndDependencies(archive, srcDir, workspaceMappings, fileDependencyMappings) {
|
|
339
|
+
export async function archiveWorkspacesAndDependencies(archive, srcDir, projectDir, workspaceMappings, fileDependencyMappings) {
|
|
355
340
|
// Archive workspace directories (internal ones are skipped, externals are copied)
|
|
356
341
|
const { externalArchivePaths, packageWorkspaceEntries } = await archiveWorkspaceDirectories(archive, srcDir, workspaceMappings);
|
|
357
342
|
// Archive external file: dependencies (internals are skipped)
|
package/lib/usageTracking.d.ts
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
os?: string;
|
|
4
|
-
nodeVersion?: string;
|
|
5
|
-
nodeMajorVersion?: string;
|
|
6
|
-
version?: string;
|
|
7
|
-
command?: string;
|
|
8
|
-
authType?: string;
|
|
9
|
-
step?: string;
|
|
10
|
-
assetType?: string;
|
|
11
|
-
mode?: string;
|
|
12
|
-
type?: string | number;
|
|
13
|
-
file?: boolean;
|
|
14
|
-
successful?: boolean;
|
|
15
|
-
};
|
|
1
|
+
import { type UsageTrackingMeta, type ConfigType, type ExecutionSource } from './api/usageTracking.js';
|
|
2
|
+
export type { UsageTrackingMeta, ConfigType, ExecutionSource, } from './api/usageTracking.js';
|
|
16
3
|
export declare const EventClass: {
|
|
17
4
|
USAGE: string;
|
|
18
5
|
INTERACTION: string;
|
|
19
6
|
VIEW: string;
|
|
20
7
|
ACTIVATION: string;
|
|
21
8
|
};
|
|
22
|
-
export declare function
|
|
9
|
+
export declare function getExecutionEnvironmentMeta(): {
|
|
10
|
+
os: string;
|
|
23
11
|
nodeVersion: string;
|
|
24
12
|
nodeMajorVersion: string;
|
|
13
|
+
version: string;
|
|
14
|
+
configType?: ConfigType;
|
|
15
|
+
executionSource: ExecutionSource;
|
|
25
16
|
};
|
|
26
|
-
export declare function getPlatform(): string;
|
|
27
17
|
export declare function trackCommandUsage(command: string, meta?: UsageTrackingMeta, accountId?: number): Promise<void>;
|
|
28
18
|
export declare function trackHelpUsage(command: string): Promise<void>;
|
|
29
19
|
export declare function trackConvertFieldsUsage(command: string): Promise<void>;
|
package/lib/usageTracking.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getConfigAccountById, getConfig } from '@hubspot/local-dev-lib/config';
|
|
1
|
+
import { getConfig, getConfigAccountById, getConfigFilePath, getGlobalConfigFilePath, } from '@hubspot/local-dev-lib/config';
|
|
3
2
|
import { API_KEY_AUTH_METHOD } from '@hubspot/local-dev-lib/constants/auth';
|
|
4
3
|
import { uiLogger } from './ui/logger.js';
|
|
5
4
|
import { pkg } from './jsonLoader.js';
|
|
6
5
|
import { debugError } from './errorHandlers/index.js';
|
|
7
6
|
import { isUsageTrackingDisableFlagSet } from './middleware/usageTrackingMiddleware.js';
|
|
7
|
+
import { sendUsageEvent, } from './api/usageTracking.js';
|
|
8
8
|
const version = pkg.version;
|
|
9
9
|
const usageTrackingDiabled = 'Usage tracking is disabled via the --disable-usage-tracking flag, not sending usage events';
|
|
10
10
|
export const EventClass = {
|
|
@@ -13,13 +13,13 @@ export const EventClass = {
|
|
|
13
13
|
VIEW: 'VIEW',
|
|
14
14
|
ACTIVATION: 'ACTIVATION',
|
|
15
15
|
};
|
|
16
|
-
|
|
16
|
+
function getNodeVersionData() {
|
|
17
17
|
return {
|
|
18
18
|
nodeVersion: process.version,
|
|
19
19
|
nodeMajorVersion: (process.version || '').split('.')[0],
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
function getPlatform() {
|
|
23
23
|
switch (process.platform) {
|
|
24
24
|
case 'darwin':
|
|
25
25
|
return 'macos';
|
|
@@ -29,6 +29,34 @@ export function getPlatform() {
|
|
|
29
29
|
return process.platform;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
function getConfigType() {
|
|
33
|
+
try {
|
|
34
|
+
return getConfigFilePath() === getGlobalConfigFilePath()
|
|
35
|
+
? 'global'
|
|
36
|
+
: 'local';
|
|
37
|
+
}
|
|
38
|
+
catch (_e) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function getExecutionSource() {
|
|
43
|
+
if (process.env.HUBSPOT_MCP_AI_AGENT) {
|
|
44
|
+
return 'mcp';
|
|
45
|
+
}
|
|
46
|
+
if (process.env.CI) {
|
|
47
|
+
return 'ci';
|
|
48
|
+
}
|
|
49
|
+
return 'user';
|
|
50
|
+
}
|
|
51
|
+
export function getExecutionEnvironmentMeta() {
|
|
52
|
+
return {
|
|
53
|
+
os: getPlatform(),
|
|
54
|
+
...getNodeVersionData(),
|
|
55
|
+
version,
|
|
56
|
+
configType: getConfigType(),
|
|
57
|
+
executionSource: getExecutionSource(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
32
60
|
export async function trackCommandUsage(command, meta = {}, accountId) {
|
|
33
61
|
if (isUsageTrackingDisableFlagSet()) {
|
|
34
62
|
uiLogger.debug(usageTrackingDiabled);
|
|
@@ -130,39 +158,25 @@ async function trackCliInteraction({ action, accountId, command, authType, meta
|
|
|
130
158
|
if (config?.allowUsageTracking === false) {
|
|
131
159
|
return;
|
|
132
160
|
}
|
|
161
|
+
if (isUsageTrackingDisableFlagSet()) {
|
|
162
|
+
uiLogger.debug(usageTrackingDiabled);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
133
165
|
const usageTrackingEvent = {
|
|
134
166
|
action,
|
|
135
|
-
os: getPlatform(),
|
|
136
|
-
...getNodeVersionData(),
|
|
137
|
-
version,
|
|
138
167
|
command,
|
|
139
168
|
authType,
|
|
169
|
+
...getExecutionEnvironmentMeta(),
|
|
140
170
|
...meta,
|
|
141
171
|
};
|
|
142
|
-
if (process.env.HUBSPOT_MCP_AI_AGENT) {
|
|
143
|
-
try {
|
|
144
|
-
await trackUsage('cli-interaction', EventClass.INTERACTION, {
|
|
145
|
-
...usageTrackingEvent,
|
|
146
|
-
action: 'cli-mcp-server',
|
|
147
|
-
type: process.env.HUBSPOT_MCP_AI_AGENT,
|
|
148
|
-
}, accountId);
|
|
149
|
-
uiLogger.debug('Sent AI usage tracking command event:', {
|
|
150
|
-
...usageTrackingEvent,
|
|
151
|
-
action: 'cli-mcp-server',
|
|
152
|
-
type: process.env.HUBSPOT_MCP_AI_AGENT,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
debugError(error);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (isUsageTrackingDisableFlagSet()) {
|
|
160
|
-
uiLogger.debug(usageTrackingDiabled);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
172
|
try {
|
|
164
173
|
uiLogger.debug('Sent usage tracking command event:', usageTrackingEvent);
|
|
165
|
-
await
|
|
174
|
+
await sendUsageEvent({
|
|
175
|
+
eventName: 'cli-interaction',
|
|
176
|
+
eventClass: EventClass.INTERACTION,
|
|
177
|
+
meta: usageTrackingEvent,
|
|
178
|
+
accountId,
|
|
179
|
+
});
|
|
166
180
|
}
|
|
167
181
|
catch (error) {
|
|
168
182
|
debugError(error);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
1
3
|
import { getConfig } from '@hubspot/local-dev-lib/config';
|
|
2
4
|
import { getStateValue, setStateValue, } from '@hubspot/local-dev-lib/config/state';
|
|
3
5
|
import { STATE_FLAGS } from '@hubspot/local-dev-lib/constants/config';
|
|
6
|
+
import { logger as ldlLogger } from '@hubspot/local-dev-lib/logger';
|
|
4
7
|
import { trackCommandUsage as _trackCommandUsage } from '../usageTracking.js';
|
|
5
8
|
import { pkg } from '../jsonLoader.js';
|
|
6
9
|
import { uiLogger } from '../ui/logger.js';
|
|
@@ -8,6 +11,7 @@ import { lib } from '../../lang/en.js';
|
|
|
8
11
|
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
9
12
|
import { isPromptExitError } from '../errors/PromptExitError.js';
|
|
10
13
|
import { debugError } from '../errorHandlers/index.js';
|
|
14
|
+
const HANDLER_LOG_DIR = path.join(os.homedir(), '.hscli', 'logs');
|
|
11
15
|
function logUsageTrackingMessage(isJsonOutput) {
|
|
12
16
|
if (isJsonOutput) {
|
|
13
17
|
return;
|
|
@@ -30,6 +34,7 @@ function logUsageTrackingMessage(isJsonOutput) {
|
|
|
30
34
|
}
|
|
31
35
|
export function makeYargsHandlerWithUsageTracking(trackingName, handler) {
|
|
32
36
|
return async (args) => {
|
|
37
|
+
const startTime = Date.now();
|
|
33
38
|
const meta = {};
|
|
34
39
|
let trackingFired = false;
|
|
35
40
|
const trackingArgs = args;
|
|
@@ -45,6 +50,7 @@ export function makeYargsHandlerWithUsageTracking(trackingName, handler) {
|
|
|
45
50
|
try {
|
|
46
51
|
const { accountId: overrideAccountId, ...trackingMeta } = meta;
|
|
47
52
|
trackingMeta.successful = successful;
|
|
53
|
+
trackingMeta.executionTime = Date.now() - startTime;
|
|
48
54
|
await _trackCommandUsage(trackingName, trackingMeta, overrideAccountId ?? args.derivedAccountId);
|
|
49
55
|
}
|
|
50
56
|
catch (_e) { }
|
|
@@ -68,12 +74,31 @@ export function makeYargsHandlerWithUsageTracking(trackingName, handler) {
|
|
|
68
74
|
process.removeListener('SIGINT', onSigint);
|
|
69
75
|
process.removeListener('SIGINT', onForcedExit);
|
|
70
76
|
};
|
|
77
|
+
const jsonArgs = args;
|
|
78
|
+
const isJsonOutput = Boolean(jsonArgs.json || jsonArgs.formatOutputAsJson);
|
|
79
|
+
const writeFailureLogFile = () => {
|
|
80
|
+
// Skip in JSON output modes so the side effect + stderr message don't
|
|
81
|
+
// interfere with structured output consumers.
|
|
82
|
+
if (isJsonOutput) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const savedPath = ldlLogger.writeBufferedLogsToFile({
|
|
86
|
+
dir: HANDLER_LOG_DIR,
|
|
87
|
+
filenamePrefix: trackingName,
|
|
88
|
+
});
|
|
89
|
+
if (savedPath) {
|
|
90
|
+
uiLogger.log('');
|
|
91
|
+
uiLogger.error(lib.handlerLogFile.saved(savedPath));
|
|
92
|
+
}
|
|
93
|
+
};
|
|
71
94
|
trackingArgs.exit = async (code) => {
|
|
72
95
|
await trackCommandUsageAndRemoveListeners(code !== EXIT_CODES.ERROR);
|
|
96
|
+
if (code === EXIT_CODES.ERROR) {
|
|
97
|
+
writeFailureLogFile();
|
|
98
|
+
}
|
|
73
99
|
return process.exit(code);
|
|
74
100
|
};
|
|
75
|
-
|
|
76
|
-
logUsageTrackingMessage(Boolean(jsonArgs.json || jsonArgs.formatOutputAsJson));
|
|
101
|
+
logUsageTrackingMessage(isJsonOutput);
|
|
77
102
|
try {
|
|
78
103
|
await handler(trackingArgs);
|
|
79
104
|
}
|
|
@@ -87,6 +112,7 @@ export function makeYargsHandlerWithUsageTracking(trackingName, handler) {
|
|
|
87
112
|
}
|
|
88
113
|
else {
|
|
89
114
|
debugError(e);
|
|
115
|
+
writeFailureLogFile();
|
|
90
116
|
return process.exit(EXIT_CODES.ERROR);
|
|
91
117
|
}
|
|
92
118
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { EventClass, getNodeVersionData, getPlatform, } from '../../lib/usageTracking.js';
|
|
1
|
+
import { EventClass, getExecutionEnvironmentMeta, } from '../../lib/usageTracking.js';
|
|
3
2
|
import { getConfig, getConfigDefaultAccountIfExists, } from '@hubspot/local-dev-lib/config';
|
|
3
|
+
import { sendUsageEvent } from '../../lib/api/usageTracking.js';
|
|
4
4
|
export async function trackToolUsage(toolName, meta) {
|
|
5
5
|
const config = getConfig();
|
|
6
6
|
if (config?.allowUsageTracking === false) {
|
|
@@ -8,15 +8,19 @@ export async function trackToolUsage(toolName, meta) {
|
|
|
8
8
|
}
|
|
9
9
|
const usageTrackingEvent = {
|
|
10
10
|
action: 'cli-mcp-tool-invocation',
|
|
11
|
-
os: getPlatform(),
|
|
12
|
-
...getNodeVersionData(),
|
|
13
11
|
command: toolName,
|
|
14
12
|
type: process.env.HUBSPOT_MCP_AI_AGENT,
|
|
13
|
+
...getExecutionEnvironmentMeta(),
|
|
15
14
|
...meta,
|
|
16
15
|
};
|
|
17
16
|
const accountId = getConfigDefaultAccountIfExists()?.accountId || undefined;
|
|
18
17
|
try {
|
|
19
|
-
await
|
|
18
|
+
await sendUsageEvent({
|
|
19
|
+
eventName: 'cli-interaction',
|
|
20
|
+
eventClass: EventClass.INTERACTION,
|
|
21
|
+
meta: usageTrackingEvent,
|
|
22
|
+
accountId,
|
|
23
|
+
});
|
|
20
24
|
}
|
|
21
|
-
catch (
|
|
25
|
+
catch (_error) { }
|
|
22
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/cli",
|
|
3
|
-
"version": "8.1.
|
|
3
|
+
"version": "8.1.1-experimental.0",
|
|
4
4
|
"description": "The official CLI for developing on HubSpot",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": "https://github.com/HubSpot/hubspot-cli",
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
"!**/__tests__/**"
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@hubspot/local-dev-lib": "5.
|
|
13
|
+
"@hubspot/local-dev-lib": "5.7.0",
|
|
14
14
|
"@hubspot/project-parsing-lib": "0.16.0",
|
|
15
15
|
"@hubspot/serverless-dev-runtime": "7.0.7",
|
|
16
|
-
"@hubspot/ui-extensions-dev-server": "2.0.
|
|
16
|
+
"@hubspot/ui-extensions-dev-server": "2.0.7",
|
|
17
17
|
"@inquirer/prompts": "7.1.0",
|
|
18
18
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
19
19
|
"archiver": "7.0.1",
|