@hubspot/cli 8.1.3-experimental.0 → 8.1.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.
@@ -1,15 +1,11 @@
1
1
  import archiver from 'archiver';
2
- import { WorkspaceMapping, FileDependencyMapping, FileDependencyKind, LocalDependencyProtocol } from '@hubspot/project-parsing-lib/workspaces';
3
- export type FileDepArchiveEntry = {
4
- archivePath: string;
5
- protocol: LocalDependencyProtocol;
6
- };
2
+ import { WorkspaceMapping, FileDependencyMapping } from '@hubspot/project-parsing-lib/workspaces';
7
3
  /**
8
4
  * Result of archiving workspaces and file dependencies
9
5
  */
10
6
  export type WorkspaceArchiveResult = {
11
7
  packageWorkspaces: Map<string, string[]>;
12
- packageFileDeps: Map<string, Map<string, FileDepArchiveEntry>>;
8
+ packageFileDeps: Map<string, Map<string, string>>;
13
9
  };
14
10
  /**
15
11
  * Generates a short hash of the input string for use in workspace paths.
@@ -28,14 +24,10 @@ export declare function shortHash(input: string): string;
28
24
  export declare function toPosixPath(p: string): string;
29
25
  /**
30
26
  * Determines the archive path for an external workspace or file: dependency.
31
- *
32
- * For directories, produces `_workspaces/<basename>-<hash>`.
33
- * For tarballs, produces `_workspaces/<basename-no-ext>-<hash>/<original-basename>`,
34
- * so the rewritten package.json reference still ends in the original filename.
35
- *
36
- * The hash prevents collisions between different paths with the same basename.
27
+ * Produces `_workspaces/<basename>-<hash>` with no subdirectory.
28
+ * The hash prevents collisions between different directories with the same basename.
37
29
  */
38
- export declare function computeExternalArchivePath(absolutePath: string, kind?: FileDependencyKind): string;
30
+ export declare function computeExternalArchivePath(absolutePath: string): string;
39
31
  /**
40
32
  * Updates package.json files in the archive to reflect new workspace and file: dependency paths.
41
33
  *
@@ -46,7 +38,7 @@ export declare function computeExternalArchivePath(absolutePath: string, kind?:
46
38
  * Only external file: dependencies appear in packageFileDeps; internal ones
47
39
  * keep their original file: references and are left untouched.
48
40
  */
49
- export declare function updatePackageJsonInArchive(archive: archiver.Archiver, srcDir: string, packageWorkspaces: Map<string, string[]>, packageFileDeps: Map<string, Map<string, FileDepArchiveEntry>>): Promise<void>;
41
+ export declare function updatePackageJsonInArchive(archive: archiver.Archiver, srcDir: string, packageWorkspaces: Map<string, string[]>, packageFileDeps: Map<string, Map<string, string>>): Promise<void>;
50
42
  export declare function rewriteLockfileForExternalDeps(lockfileContent: Record<string, unknown>, pathMappings: Array<{
51
43
  oldPath: string;
52
44
  newPath: string;
@@ -27,35 +27,14 @@ export function toPosixPath(p) {
27
27
  }
28
28
  return p.replaceAll(path.sep, path.posix.sep);
29
29
  }
30
- /**
31
- * Strips the longest matching tarball extension (.tar.gz, .tgz, .tar)
32
- * from a file basename. Returns the input unchanged if no extension matches.
33
- */
34
- function stripTarballExtension(basename) {
35
- const lower = basename.toLowerCase();
36
- for (const ext of ['.tar.gz', '.tgz', '.tar']) {
37
- if (lower.endsWith(ext)) {
38
- return basename.slice(0, basename.length - ext.length);
39
- }
40
- }
41
- return basename;
42
- }
43
30
  /**
44
31
  * Determines the archive path for an external workspace or file: dependency.
45
- *
46
- * For directories, produces `_workspaces/<basename>-<hash>`.
47
- * For tarballs, produces `_workspaces/<basename-no-ext>-<hash>/<original-basename>`,
48
- * so the rewritten package.json reference still ends in the original filename.
49
- *
50
- * The hash prevents collisions between different paths with the same basename.
32
+ * Produces `_workspaces/<basename>-<hash>` with no subdirectory.
33
+ * The hash prevents collisions between different directories with the same basename.
51
34
  */
52
- export function computeExternalArchivePath(absolutePath, kind = 'directory') {
35
+ export function computeExternalArchivePath(absolutePath) {
53
36
  const resolved = path.resolve(absolutePath);
54
37
  const name = path.basename(resolved);
55
- if (kind === 'tarball') {
56
- const nameNoExt = stripTarballExtension(name);
57
- return path.posix.join('_workspaces', `${nameNoExt}-${shortHash(resolved)}`, name);
58
- }
59
38
  return path.posix.join('_workspaces', `${name}-${shortHash(resolved)}`);
60
39
  }
61
40
  /**
@@ -142,25 +121,25 @@ async function archiveWorkspaceDirectories(archive, srcDir, workspaceMappings) {
142
121
  return { externalArchivePaths, packageWorkspaceEntries };
143
122
  }
144
123
  /**
145
- * Archives file: and link: dependencies and returns mapping information.
124
+ * Archives file: dependencies and returns mapping information.
146
125
  *
147
- * Internal dependencies (inside srcDir) are skipped — their original
148
- * references in package.json remain valid after upload.
126
+ * Internal file: dependencies (inside srcDir) are skipped — their original
127
+ * `file:` references in package.json remain valid after upload.
149
128
  *
150
- * External directory dependencies are archived to `_workspaces/<name>-<hash>`.
151
- * External tarball dependencies are archived to
152
- * `_workspaces/<name-no-ext>-<hash>/<original-basename>` so the rewritten
153
- * reference still ends in the original filename.
129
+ * External file: dependencies are archived to `_workspaces/<name>-<hash>`
130
+ * and tracked in the returned map so package.json can be rewritten.
154
131
  */
155
132
  async function archiveFileDependencies(archive, srcDir, fileDependencyMappings, externalArchivePaths) {
156
133
  const packageFileDeps = new Map();
157
134
  const toArchive = [];
158
135
  for (const mapping of fileDependencyMappings) {
159
- const { packageName, localPath, sourcePackageJsonPath, kind, protocol } = mapping;
136
+ const { packageName, localPath, sourcePackageJsonPath } = mapping;
160
137
  if (isInsideSrcDir(localPath, srcDir)) {
138
+ // Internal: original file: reference stays unchanged, nothing to do
161
139
  continue;
162
140
  }
163
- const archivePath = computeExternalArchivePath(localPath, kind);
141
+ // External: archive to _workspaces/<name>-<hash>
142
+ const archivePath = computeExternalArchivePath(localPath);
164
143
  const resolvedPath = path.resolve(localPath);
165
144
  if (!packageFileDeps.has(sourcePackageJsonPath)) {
166
145
  packageFileDeps.set(sourcePackageJsonPath, new Map());
@@ -169,27 +148,23 @@ async function archiveFileDependencies(archive, srcDir, fileDependencyMappings,
169
148
  const relativeArchivePath = toPosixPath(path.relative(relPkgJsonDir, archivePath));
170
149
  packageFileDeps
171
150
  .get(sourcePackageJsonPath)
172
- .set(packageName, { archivePath: relativeArchivePath, protocol });
151
+ .set(packageName, relativeArchivePath);
152
+ // Only archive each unique path once
173
153
  if (!externalArchivePaths.has(resolvedPath)) {
174
154
  externalArchivePaths.set(resolvedPath, archivePath);
175
- toArchive.push({ localPath, archivePath, packageName, kind });
155
+ toArchive.push({ localPath, archivePath, packageName });
176
156
  }
177
157
  }
178
- const directoryItems = toArchive.filter(item => item.kind === 'directory');
179
- const tarballItems = toArchive.filter(item => item.kind === 'tarball');
180
- // getPackableFiles only applies to directory deps; tarballs are a single file.
181
- const directoriesWithPackableFiles = await Promise.all(directoryItems.map(async (item) => ({
158
+ // Fetch packable files in parallel (I/O optimization)
159
+ const withPackableFiles = await Promise.all(toArchive.map(async (item) => ({
182
160
  ...item,
183
161
  packableFiles: await getPackableFiles(item.localPath),
184
162
  })));
185
- for (const { localPath, archivePath, packageName, packableFiles, } of directoriesWithPackableFiles) {
163
+ // Archive directories sequentially (archiver requires sequential operations)
164
+ for (const { localPath, archivePath, packageName, packableFiles, } of withPackableFiles) {
186
165
  uiLogger.log(lib.projectUpload.handleProjectUpload.fileDependencyIncluded(packageName, localPath, archivePath));
187
166
  archive.directory(localPath, archivePath, createWorkspaceFileFilter(packableFiles));
188
167
  }
189
- for (const { localPath, archivePath, packageName } of tarballItems) {
190
- uiLogger.log(lib.projectUpload.handleProjectUpload.fileDependencyIncluded(packageName, localPath, archivePath));
191
- archive.file(localPath, { name: archivePath });
192
- }
193
168
  return packageFileDeps;
194
169
  }
195
170
  /**
@@ -238,18 +213,14 @@ export async function updatePackageJsonInArchive(archive, srcDir, packageWorkspa
238
213
  uiLogger.debug(lib.projectUpload.handleProjectUpload.updatingPackageJsonWorkspaces(relativePackageJsonPath));
239
214
  uiLogger.debug(lib.projectUpload.handleProjectUpload.updatedWorkspaces(workspaceEntries.join(', ')));
240
215
  }
241
- // Update external file: and link: dependencies; internal ones are left untouched.
242
- // The protocol prefix (file: vs link:) is preserved from the original spec.
216
+ // Update external file: dependencies; internal ones are left untouched
243
217
  const fileDeps = packageFileDeps.get(packageJsonPath);
244
218
  if (fileDeps && fileDeps.size > 0 && packageJson.dependencies) {
245
- for (const [packageName, { archivePath, protocol },] of fileDeps.entries()) {
246
- const current = packageJson.dependencies[packageName];
247
- if (typeof current === 'string' &&
248
- (current.startsWith('file:') || current.startsWith('link:'))) {
249
- const newValue = `${protocol}:${archivePath}`;
250
- packageJson.dependencies[packageName] = newValue;
219
+ for (const [packageName, archivePath] of fileDeps.entries()) {
220
+ if (packageJson.dependencies[packageName]?.startsWith('file:')) {
221
+ packageJson.dependencies[packageName] = `file:${archivePath}`;
251
222
  modified = true;
252
- uiLogger.debug(lib.projectUpload.handleProjectUpload.updatedFileDependency(packageName, newValue));
223
+ uiLogger.debug(lib.projectUpload.handleProjectUpload.updatedFileDependency(packageName, archivePath));
253
224
  }
254
225
  }
255
226
  }
@@ -277,24 +248,11 @@ export function rewriteLockfileForExternalDeps(lockfileContent, pathMappings) {
277
248
  typeof value === 'object' &&
278
249
  value !== null) {
279
250
  const entry = value;
280
- if (typeof entry.resolved !== 'string')
281
- continue;
282
- // Symlink entries (directory deps with link:true) store resolved as a
283
- // bare relative path. Tarball entries store resolved as a "file:" URL.
284
- const resolved = entry.resolved;
285
- const filePrefix = 'file:';
286
- const isFileUrl = resolved.startsWith(filePrefix);
287
- const resolvedPath = isFileUrl
288
- ? resolved.slice(filePrefix.length)
289
- : resolved;
290
- const mapping = pathMappings.find(m => m.oldPath === resolvedPath);
291
- if (mapping) {
292
- newPackages[key] = {
293
- ...entry,
294
- resolved: isFileUrl
295
- ? `${filePrefix}${mapping.newPath}`
296
- : mapping.newPath,
297
- };
251
+ if (entry.link === true && typeof entry.resolved === 'string') {
252
+ const mapping = pathMappings.find(m => m.oldPath === entry.resolved);
253
+ if (mapping) {
254
+ newPackages[key] = { ...entry, resolved: mapping.newPath };
255
+ }
298
256
  }
299
257
  }
300
258
  }
@@ -24,10 +24,12 @@ export async function runCommandInDir(directory, command) {
24
24
  }
25
25
  finalCommand = addFlag(finalCommand, 'disable-usage-tracking', true);
26
26
  }
27
+ const resolvedDir = path.resolve(directory);
27
28
  return execAsync(finalCommand, {
28
- cwd: path.resolve(directory),
29
+ cwd: resolvedDir,
29
30
  env: {
30
31
  ...process.env,
32
+ INIT_CWD: resolvedDir,
31
33
  },
32
34
  });
33
35
  }
@@ -3,6 +3,7 @@ export function setupHubSpotConfig(absoluteCurrentWorkingDirectory) {
3
3
  if (!absoluteCurrentWorkingDirectory) {
4
4
  return;
5
5
  }
6
+ process.env.INIT_CWD = absoluteCurrentWorkingDirectory;
6
7
  const configPath = getLocalConfigFilePathIfExists(absoluteCurrentWorkingDirectory);
7
8
  if (configPath) {
8
9
  process.env.HUBSPOT_CONFIG_PATH = configPath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "8.1.3-experimental.0",
3
+ "version": "8.1.4-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",
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "dependencies": {
13
13
  "@hubspot/local-dev-lib": "5.7.1",
14
- "@hubspot/project-parsing-lib": "0.2.2-experimental.0",
14
+ "@hubspot/project-parsing-lib": "0.16.0",
15
15
  "@hubspot/serverless-dev-runtime": "7.0.7",
16
16
  "@hubspot/ui-extensions-dev-server": "2.0.7",
17
17
  "@inquirer/prompts": "7.1.0",