@hubspot/cli 8.0.3-experimental.1 → 8.0.4-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.
Files changed (36) hide show
  1. package/commands/project/migrate.js +2 -2
  2. package/lang/en.d.ts +5 -8
  3. package/lang/en.js +6 -9
  4. package/lib/getStartedV2Actions.d.ts +13 -0
  5. package/lib/getStartedV2Actions.js +53 -0
  6. package/lib/projects/__tests__/upload.test.js +0 -10
  7. package/lib/projects/platformVersion.d.ts +1 -1
  8. package/lib/projects/platformVersion.js +2 -1
  9. package/lib/projects/upload.js +0 -9
  10. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +20 -3
  11. package/mcp-server/tools/project/AddFeatureToProjectTool.js +6 -10
  12. package/mcp-server/tools/project/CreateProjectTool.d.ts +24 -4
  13. package/mcp-server/tools/project/CreateProjectTool.js +5 -10
  14. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +5 -8
  15. package/mcp-server/tools/project/GetBuildLogsTool.d.ts +2 -2
  16. package/mcp-server/tools/project/GetBuildLogsTool.js +6 -7
  17. package/mcp-server/tools/project/GetBuildStatusTool.d.ts +1 -1
  18. package/mcp-server/tools/project/GetBuildStatusTool.js +3 -4
  19. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +6 -1
  20. package/mcp-server/tools/project/GuidedWalkthroughTool.js +1 -6
  21. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +0 -32
  22. package/mcp-server/tools/project/constants.d.ts +12 -1
  23. package/mcp-server/tools/project/constants.js +12 -16
  24. package/package.json +3 -3
  25. package/ui/components/getStarted/GetStartedFlow.js +79 -2
  26. package/ui/components/getStarted/reducer.d.ts +20 -0
  27. package/ui/components/getStarted/reducer.js +36 -0
  28. package/ui/components/getStarted/screens/InstallationScreen.d.ts +7 -0
  29. package/ui/components/getStarted/screens/InstallationScreen.js +16 -0
  30. package/ui/components/getStarted/screens/ProjectSetupScreen.js +2 -1
  31. package/ui/lib/constants.d.ts +1 -0
  32. package/ui/lib/constants.js +1 -0
  33. package/lib/projects/__tests__/workspaceArchive.test.d.ts +0 -1
  34. package/lib/projects/__tests__/workspaceArchive.test.js +0 -207
  35. package/lib/projects/workspaces.d.ts +0 -36
  36. package/lib/projects/workspaces.js +0 -224
@@ -1,224 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import crypto from 'crypto';
4
- import { shouldIgnoreFile } from '@hubspot/local-dev-lib/ignoreRules';
5
- import { getPackableFiles, } from '@hubspot/project-parsing-lib/workspaces';
6
- import { uiLogger } from '../ui/logger.js';
7
- import { lib } from '../../lang/en.js';
8
- /**
9
- * Generates a short hash of the input string for use in workspace paths.
10
- * Uses SHA256 truncated to 8 hex characters (4 billion possibilities).
11
- */
12
- export function shortHash(input) {
13
- return crypto.createHash('sha256').update(input).digest('hex').slice(0, 8);
14
- }
15
- /**
16
- * Determines the archive path for an external workspace or file: dependency.
17
- * Produces `_workspaces/<basename>-<hash>` with no subdirectory.
18
- * The hash prevents collisions between different directories with the same basename.
19
- */
20
- export function computeExternalArchivePath(absolutePath) {
21
- const resolved = path.resolve(absolutePath);
22
- const name = path.basename(resolved);
23
- return path.join('_workspaces', `${name}-${shortHash(resolved)}`);
24
- }
25
- /**
26
- * Returns true if dir is inside srcDir (i.e. it will already be included
27
- * in the archive from the srcDir walk and must not be copied again).
28
- */
29
- function isInsideSrcDir(dir, srcDir) {
30
- const rel = path.relative(path.resolve(srcDir), path.resolve(dir));
31
- return !rel.startsWith('..') && !path.isAbsolute(rel);
32
- }
33
- /**
34
- * Creates a file filter function for workspace archiving.
35
- * Filters files based on packable files list and ignore rules.
36
- */
37
- function createWorkspaceFileFilter(packableFiles) {
38
- return (file) => {
39
- if (packableFiles.size > 0 && !packableFiles.has(file.name)) {
40
- uiLogger.debug(lib.projectUpload.handleProjectUpload.fileFiltered(file.name));
41
- return false;
42
- }
43
- const ignored = shouldIgnoreFile(file.name, true);
44
- if (ignored) {
45
- uiLogger.debug(lib.projectUpload.handleProjectUpload.fileFiltered(file.name));
46
- return false;
47
- }
48
- return file;
49
- };
50
- }
51
- /**
52
- * Archives workspace directories and returns mapping information.
53
- *
54
- * Internal workspaces (inside srcDir) are not archived — they are already
55
- * included via the srcDir walk. Their relative paths (from the package.json
56
- * directory to the workspace directory) are stored directly in the entries.
57
- *
58
- * External workspaces (outside srcDir) are copied to `_workspaces/<name>-<hash>`
59
- * and their absolute archive paths (`/_workspaces/<name>-<hash>`) are stored.
60
- */
61
- async function archiveWorkspaceDirectories(archive, srcDir, workspaceMappings) {
62
- const externalArchivePaths = new Map(); // resolvedDir -> archivePath
63
- const archivePathToDir = new Map(); // archivePath -> resolvedDir (collision detection)
64
- const packageWorkspaceEntries = new Map();
65
- const externalsToArchive = [];
66
- for (const mapping of workspaceMappings) {
67
- const { workspaceDir, sourcePackageJsonPath } = mapping;
68
- if (!packageWorkspaceEntries.has(sourcePackageJsonPath)) {
69
- packageWorkspaceEntries.set(sourcePackageJsonPath, []);
70
- }
71
- if (isInsideSrcDir(workspaceDir, srcDir)) {
72
- // Internal: already in archive from srcDir walk.
73
- // Store the relative path from the package.json directory so npm can resolve it.
74
- const relPath = path.relative(path.dirname(sourcePackageJsonPath), path.resolve(workspaceDir));
75
- packageWorkspaceEntries.get(sourcePackageJsonPath).push(relPath);
76
- }
77
- else {
78
- // External: archive to _workspaces/<name>-<hash>.
79
- const archivePath = computeExternalArchivePath(workspaceDir);
80
- const resolvedDir = path.resolve(workspaceDir);
81
- // Detect hash collisions (different dirs mapping to the same archive path)
82
- const existing = archivePathToDir.get(archivePath);
83
- if (existing && existing !== resolvedDir) {
84
- throw new Error(lib.projectUpload.handleProjectUpload.workspaceCollision(archivePath, workspaceDir, existing));
85
- }
86
- if (!externalArchivePaths.has(resolvedDir)) {
87
- externalArchivePaths.set(resolvedDir, archivePath);
88
- archivePathToDir.set(archivePath, resolvedDir);
89
- externalsToArchive.push({ dir: workspaceDir, archivePath });
90
- }
91
- // Store absolute archive path so DFS can locate it in the project tree
92
- packageWorkspaceEntries
93
- .get(sourcePackageJsonPath)
94
- .push(`/${archivePath}`);
95
- }
96
- }
97
- // Fetch packable files in parallel (I/O optimization)
98
- const withPackableFiles = await Promise.all(externalsToArchive.map(async (item) => ({
99
- ...item,
100
- packableFiles: await getPackableFiles(item.dir),
101
- })));
102
- // Archive directories sequentially (archiver requires sequential operations)
103
- for (const { dir, archivePath, packableFiles } of withPackableFiles) {
104
- uiLogger.log(lib.projectUpload.handleProjectUpload.workspaceIncluded(dir, archivePath));
105
- archive.directory(dir, archivePath, createWorkspaceFileFilter(packableFiles));
106
- }
107
- return { externalArchivePaths, packageWorkspaceEntries };
108
- }
109
- /**
110
- * Archives file: dependencies and returns mapping information.
111
- *
112
- * Internal file: dependencies (inside srcDir) are skipped — their original
113
- * `file:` references in package.json remain valid after upload.
114
- *
115
- * External file: dependencies are archived to `_workspaces/<name>-<hash>`
116
- * and tracked in the returned map so package.json can be rewritten.
117
- */
118
- async function archiveFileDependencies(archive, srcDir, fileDependencyMappings, externalArchivePaths) {
119
- const packageFileDeps = new Map();
120
- const toArchive = [];
121
- for (const mapping of fileDependencyMappings) {
122
- const { packageName, localPath, sourcePackageJsonPath } = mapping;
123
- if (isInsideSrcDir(localPath, srcDir)) {
124
- // Internal: original file: reference stays unchanged, nothing to do
125
- continue;
126
- }
127
- // External: archive to _workspaces/<name>-<hash>
128
- const archivePath = computeExternalArchivePath(localPath);
129
- const resolvedPath = path.resolve(localPath);
130
- if (!packageFileDeps.has(sourcePackageJsonPath)) {
131
- packageFileDeps.set(sourcePackageJsonPath, new Map());
132
- }
133
- packageFileDeps.get(sourcePackageJsonPath).set(packageName, archivePath);
134
- // Only archive each unique path once
135
- if (!externalArchivePaths.has(resolvedPath)) {
136
- externalArchivePaths.set(resolvedPath, archivePath);
137
- toArchive.push({ localPath, archivePath, packageName });
138
- }
139
- }
140
- // Fetch packable files in parallel (I/O optimization)
141
- const withPackableFiles = await Promise.all(toArchive.map(async (item) => ({
142
- ...item,
143
- packableFiles: await getPackableFiles(item.localPath),
144
- })));
145
- // Archive directories sequentially (archiver requires sequential operations)
146
- for (const { localPath, archivePath, packageName, packableFiles, } of withPackableFiles) {
147
- uiLogger.log(lib.projectUpload.handleProjectUpload.fileDependencyIncluded(packageName, localPath, archivePath));
148
- archive.directory(localPath, archivePath, createWorkspaceFileFilter(packableFiles));
149
- }
150
- return packageFileDeps;
151
- }
152
- /**
153
- * Updates package.json files in the archive to reflect new workspace and file: dependency paths.
154
- *
155
- * Workspace entries in packageWorkspaces are already in final form:
156
- * - Internal workspaces: relative paths (e.g. "../packages/utils")
157
- * - External workspaces: absolute paths (e.g. "/_workspaces/logger-abc")
158
- *
159
- * Only external file: dependencies appear in packageFileDeps; internal ones
160
- * keep their original file: references and are left untouched.
161
- */
162
- export async function updatePackageJsonInArchive(archive, srcDir, packageWorkspaces, packageFileDeps) {
163
- // Collect all package.json paths that need updating
164
- const allPackageJsonPaths = new Set([
165
- ...packageWorkspaces.keys(),
166
- ...packageFileDeps.keys(),
167
- ]);
168
- for (const packageJsonPath of allPackageJsonPaths) {
169
- if (!fs.existsSync(packageJsonPath)) {
170
- continue;
171
- }
172
- let packageJson;
173
- try {
174
- packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
175
- }
176
- catch (e) {
177
- uiLogger.warn(lib.projectUpload.handleProjectUpload.malformedPackageJson(packageJsonPath, e instanceof Error ? e.message : String(e)));
178
- continue;
179
- }
180
- const relativePackageJsonPath = path.relative(srcDir, packageJsonPath);
181
- let modified = false;
182
- // Update workspaces field — entries are already in their final form
183
- const workspaceEntries = packageWorkspaces.get(packageJsonPath);
184
- if (workspaceEntries && packageJson.workspaces) {
185
- packageJson.workspaces = workspaceEntries;
186
- modified = true;
187
- uiLogger.debug(lib.projectUpload.handleProjectUpload.updatingPackageJsonWorkspaces(relativePackageJsonPath));
188
- uiLogger.debug(lib.projectUpload.handleProjectUpload.updatedWorkspaces(workspaceEntries.join(', ')));
189
- }
190
- // Update external file: dependencies; internal ones are left untouched
191
- const fileDeps = packageFileDeps.get(packageJsonPath);
192
- if (fileDeps && fileDeps.size > 0 && packageJson.dependencies) {
193
- for (const [packageName, archivePath] of fileDeps.entries()) {
194
- if (packageJson.dependencies[packageName]?.startsWith('file:')) {
195
- packageJson.dependencies[packageName] = `file:/${archivePath}`;
196
- modified = true;
197
- uiLogger.debug(lib.projectUpload.handleProjectUpload.updatedFileDependency(packageName, `/${archivePath}`));
198
- }
199
- }
200
- }
201
- if (modified) {
202
- // Add modified package.json to archive (will replace the original)
203
- archive.append(JSON.stringify(packageJson, null, 2), {
204
- name: relativePackageJsonPath,
205
- });
206
- }
207
- }
208
- // Ensure all append operations are queued before finalize is called
209
- // Use setImmediate to yield control and let archiver process the queue
210
- await new Promise(resolve => setImmediate(resolve));
211
- }
212
- /**
213
- * Main orchestration function that handles archiving of workspaces and file dependencies.
214
- * This is the clean integration point for upload.ts.
215
- */
216
- export async function archiveWorkspacesAndDependencies(archive, srcDir, projectDir, workspaceMappings, fileDependencyMappings) {
217
- // Archive workspace directories (internal ones are skipped, externals are copied)
218
- const { externalArchivePaths, packageWorkspaceEntries } = await archiveWorkspaceDirectories(archive, srcDir, workspaceMappings);
219
- // Archive external file: dependencies (internals are skipped)
220
- const packageFileDeps = await archiveFileDependencies(archive, srcDir, fileDependencyMappings, externalArchivePaths);
221
- // Update package.json files with new paths
222
- await updatePackageJsonInArchive(archive, srcDir, packageWorkspaceEntries, packageFileDeps);
223
- return { packageWorkspaces: packageWorkspaceEntries, packageFileDeps };
224
- }