@hubspot/project-parsing-lib 0.2.2-experimental.0 → 0.2.2-experimental.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/project-parsing-lib",
3
- "version": "0.2.2-experimental.0",
3
+ "version": "0.2.2-experimental.1",
4
4
  "description": "Parsing library for converting projects directory structures to their intermediate representation",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -111,7 +111,7 @@
111
111
  "@inquirer/prompts": "^7.1.0",
112
112
  "@types/node": "^24.9.0",
113
113
  "@types/npm-packlist": "7.0.3",
114
- "@types/npmcli__map-workspaces": "^3.0.4",
114
+ "@types/npmcli__map-workspaces": "3.0.4",
115
115
  "@types/semver": "^7.5.8",
116
116
  "@typescript-eslint/eslint-plugin": "8.54.0",
117
117
  "@typescript-eslint/parser": "8.54.0",
@@ -127,7 +127,7 @@
127
127
  },
128
128
  "dependencies": {
129
129
  "@hubspot/local-dev-lib": "4.0.4",
130
- "@npmcli/map-workspaces": "^4.0.2",
130
+ "@npmcli/map-workspaces": "4.0.2",
131
131
  "ajv": "8.18.0",
132
132
  "ajv-formats": "3.0.1",
133
133
  "glob": "13.0.0",
@@ -1,4 +1,4 @@
1
1
  export { getProjectMetadata } from '../lib/project.js';
2
2
  export { projectContainsHsMetaFiles } from '../lib/files.js';
3
3
  export type { ProjectMetadata } from '../lib/project.js';
4
- export { LATEST_SUPPORTED_PLATFORM_VERSION, isSupportedPlatformVersion, isLegacyProject, } from '../lib/platformVersion.js';
4
+ export { LATEST_SUPPORTED_PLATFORM_VERSION, isSupportedPlatformVersion, isLegacyProject, supportsComponentPermissions, } from '../lib/platformVersion.js';
@@ -1,3 +1,3 @@
1
1
  export { getProjectMetadata } from '../lib/project.js';
2
2
  export { projectContainsHsMetaFiles } from '../lib/files.js';
3
- export { LATEST_SUPPORTED_PLATFORM_VERSION, isSupportedPlatformVersion, isLegacyProject, } from '../lib/platformVersion.js';
3
+ export { LATEST_SUPPORTED_PLATFORM_VERSION, isSupportedPlatformVersion, isLegacyProject, supportsComponentPermissions, } from '../lib/platformVersion.js';
@@ -1,3 +1,3 @@
1
1
  export { translate, translateForLocalDev } from '../lib/translate.js';
2
2
  export { isTranslationError, type TranslationError } from '../lib/errors.js';
3
- export type { IntermediateRepresentation, IntermediateRepresentationLocalDev, IntermediateRepresentationNodeLocalDev, IntermediateRepresentationNode, TranslationContext, TranslationOptions, TranslationOptionsLocalDev, ComponentMeta, } from '../lib/types.js';
3
+ export type { IntermediateRepresentation, IntermediateRepresentationLocalDev, IntermediateRepresentationNodeLocalDev, IntermediateRepresentationNode, TranslationContext, TranslationOptions, TranslationOptionsLocalDev, ComponentMeta, ComponentPermissions, } from '../lib/types.js';
@@ -33,6 +33,7 @@ export declare const errorMessages: {
33
33
  unsupportedType: (type: string) => string;
34
34
  errorWithField: (field: string, error: string | undefined) => string;
35
35
  invalidJson: string;
36
+ permissionsNotSupported: (platformVersion: string) => string;
36
37
  unexpectedToplevelFields: (unexpectedFields: string) => string;
37
38
  wrongDirectoryForComponent: (directory: string, componentType: string, componentMetadata: ComponentMetadata, correctDir: string) => string;
38
39
  };
@@ -51,4 +52,7 @@ export declare const logMessages: {
51
52
  files: {
52
53
  skippingPath: (path: string) => string;
53
54
  };
55
+ workspaces: {
56
+ cannotAccessDirectory: (dir: string, e: unknown) => string;
57
+ };
54
58
  };
package/src/lang/copy.js CHANGED
@@ -34,6 +34,7 @@ export const errorMessages = {
34
34
  unsupportedType: (type) => `Unsupported type: ${mapToUserFacingType(type)}`,
35
35
  errorWithField: (field, error) => `${field}: ${error || 'Unknown error'}`,
36
36
  invalidJson: 'Invalid JSON',
37
+ permissionsNotSupported: (platformVersion) => `The "permissions" field is not supported on platform version "${platformVersion}". Upgrade to "2026.09" or later to use component-level permissions.`,
37
38
  unexpectedToplevelFields: (unexpectedFields) => `Unexpected top-level properties found: ${unexpectedFields}`,
38
39
  wrongDirectoryForComponent: (directory, componentType, componentMetadata, correctDir) => `The directory '${directory}' is incorrect for type '${componentType}'. ${componentMetadata.userFriendlyName} ${componentMetadata.userFriendlyTypePlural} should only be placed in the '${correctDir}' directory`,
39
40
  },
@@ -66,4 +67,7 @@ export const logMessages = {
66
67
  files: {
67
68
  skippingPath: (path) => `Skipping ${path} as it is not in a valid directory`,
68
69
  },
70
+ workspaces: {
71
+ cannotAccessDirectory: (dir, e) => `Cannot access directory ${dir}: ${e}`,
72
+ },
69
73
  };
@@ -30,6 +30,7 @@ export declare const WEBHOOKS_KEY = "webhooks";
30
30
  export declare const WEBHOOKS_JOURNAL_KEY = "webhooks-journal";
31
31
  export declare const WORKFLOW_ACTIONS_KEY = "workflow-action";
32
32
  export declare const ACTIONS_KEY = "action";
33
+ export declare const BREEZE_TOOL_KEY = "breeze-tool";
33
34
  export declare const APP_FUNCTIONS_PACKAGE_KEY = "serverless-package";
34
35
  export declare const AUTO_GENERATED_COMPONENT_TYPES: string[];
35
36
  export interface ComponentMetadata {
@@ -34,6 +34,7 @@ export const WEBHOOKS_KEY = 'webhooks';
34
34
  export const WEBHOOKS_JOURNAL_KEY = 'webhooks-journal';
35
35
  export const WORKFLOW_ACTIONS_KEY = 'workflow-action';
36
36
  export const ACTIONS_KEY = 'action';
37
+ export const BREEZE_TOOL_KEY = 'breeze-tool';
37
38
  // Auto-generated component types
38
39
  export const APP_FUNCTIONS_PACKAGE_KEY = 'serverless-package';
39
40
  export const AUTO_GENERATED_COMPONENT_TYPES = [APP_FUNCTIONS_PACKAGE_KEY];
@@ -54,6 +55,9 @@ const PAGE_COMPONENT = {
54
55
  userFriendlyName: 'Page',
55
56
  singularComponent: true,
56
57
  };
58
+ // Breeze components share a `breeze/` group directory so future breeze features
59
+ // (e.g. breeze/skills, breeze/subagents) can sit alongside breeze/tools.
60
+ const BREEZE_COMPONENT_DIR = 'breeze';
57
61
  export const Components = {
58
62
  [APP_KEY]: {
59
63
  dir: APP_KEY,
@@ -182,6 +186,12 @@ export const Components = {
182
186
  singularComponent: true,
183
187
  ...SUB_COMPONENT_FIELDS,
184
188
  },
189
+ [BREEZE_TOOL_KEY]: {
190
+ dir: `${BREEZE_COMPONENT_DIR}/tools`,
191
+ parentComponent: APP_KEY,
192
+ userFriendlyName: 'Breeze Tool',
193
+ ...SUB_COMPONENT_FIELDS,
194
+ },
185
195
  };
186
196
  export const USER_FACING_TO_INTERNAL_TYPE = {
187
197
  [APP_KEY]: 'APPLICATION',
@@ -231,4 +241,5 @@ export const ALLOWED_TOP_LEVEL_FIELDS = new Set([
231
241
  'type',
232
242
  'config',
233
243
  'dependencies',
244
+ 'permissions',
234
245
  ]);
@@ -1,3 +1,4 @@
1
1
  export declare const LATEST_SUPPORTED_PLATFORM_VERSION: string;
2
2
  export declare function isSupportedPlatformVersion(platformVersion?: string | null): boolean;
3
3
  export declare function isLegacyProject(platformVersion?: string | null): boolean;
4
+ export declare function supportsComponentPermissions(platformVersion: string): boolean;
@@ -6,11 +6,25 @@ export function isSupportedPlatformVersion(platformVersion) {
6
6
  if (!platformVersion || typeof platformVersion !== 'string') {
7
7
  return false;
8
8
  }
9
+ if (process.env.HUBSPOT_PROJECT_INTERNAL_TEST === 'true')
10
+ return true;
9
11
  const supportedPlatformVersions = Object.values(PLATFORM_VERSIONS);
10
12
  return supportedPlatformVersions.includes(platformVersion);
11
13
  }
12
14
  export function isLegacyProject(platformVersion) {
13
15
  if (!platformVersion)
14
16
  return false;
17
+ if (process.env.HUBSPOT_PROJECT_INTERNAL_TEST === 'true')
18
+ return false;
15
19
  return [PLATFORM_VERSIONS.v2023_2, PLATFORM_VERSIONS.v2025_1].includes(platformVersion);
16
20
  }
21
+ const PRE_PERMISSIONS_VERSIONS = new Set([
22
+ PLATFORM_VERSIONS.v2023_2,
23
+ PLATFORM_VERSIONS.v2025_1,
24
+ PLATFORM_VERSIONS.v2025_2,
25
+ PLATFORM_VERSIONS.v2026_03_BETA,
26
+ PLATFORM_VERSIONS.v2026_03,
27
+ ]);
28
+ export function supportsComponentPermissions(platformVersion) {
29
+ return !PRE_PERMISSIONS_VERSIONS.has(platformVersion);
30
+ }
@@ -7,11 +7,13 @@ import { errorMessages } from '../lang/copy.js';
7
7
  import { getJavaNumberType } from './utils.js';
8
8
  import { convertPathToPosixPath } from './files.js';
9
9
  function calculateComponentDeps(fileValidationResult, parentComponents, appObjects, appFunctionsPackageUid) {
10
- let dependencies = {};
11
- // If there are dependencies in the config file, pass them through
12
- if (!fileValidationResult.content?.dependencies) {
13
- dependencies = { ...fileValidationResult.content?.dependencies };
14
- }
10
+ // Pass through any developer-authored dependencies declared in the hsmeta
11
+ // file. Framework-injected structural dependencies (app, allAppObjects,
12
+ // serverlessPackage) are layered on top below and take precedence over any
13
+ // authored value for those reserved keys.
14
+ const dependencies = {
15
+ ...fileValidationResult.content?.dependencies,
16
+ };
15
17
  const { type } = fileValidationResult.content;
16
18
  // If the component is a child of the App component, add the app and appObjects as dependencies
17
19
  if (Components[type]?.parentComponent === APP_KEY) {
@@ -95,7 +97,7 @@ export function transform(fileParseResults, translationContext, hsProfileContent
95
97
  fileParseResult: currentFile,
96
98
  };
97
99
  }
98
- const { config, uid, type } = currentFile.content;
100
+ const { config, uid, type, permissions } = currentFile.content;
99
101
  const { dependencies, errors } = calculateComponentDeps(currentFile, parentComponents, allAppObjects, serverlessPackageUid);
100
102
  if (errors) {
101
103
  currentFile.errors?.push(...errors);
@@ -108,6 +110,7 @@ export function transform(fileParseResults, translationContext, hsProfileContent
108
110
  componentDeps: dependencies,
109
111
  metaFilePath: convertPathToPosixPath(currentFile.file),
110
112
  files: {},
113
+ ...(permissions ? { permissions } : {}),
111
114
  },
112
115
  fileParseResult: currentFile,
113
116
  };
@@ -1,10 +1,16 @@
1
1
  import type { ErrorObject } from 'ajv';
2
2
  export type Dependencies = Record<string, string | string[]>;
3
+ export interface ComponentPermissions {
4
+ requiredScopes?: string[];
5
+ optionalScopes?: string[];
6
+ conditionallyRequiredScopes?: string[];
7
+ }
3
8
  export interface ComponentMeta {
4
9
  uid: string;
5
10
  type: string;
6
11
  config: unknown;
7
12
  dependencies?: Dependencies;
13
+ permissions?: ComponentPermissions;
8
14
  }
9
15
  export interface FileActionResult {
10
16
  file: string;
@@ -24,6 +30,7 @@ export interface IntermediateRepresentationNode {
24
30
  uid: string;
25
31
  config: unknown;
26
32
  files: unknown;
33
+ permissions?: ComponentPermissions;
27
34
  }
28
35
  export type BEProfileVariableType = 'PROFILE_INT' | 'PROFILE_LONG' | 'PROFILE_STRING' | 'PROFILE_BOOLEAN';
29
36
  export interface BEProfileVariables {
@@ -5,6 +5,7 @@ import { errorMessages } from '../lang/copy.js';
5
5
  import { AUTO_GENERATED_COMPONENT_TYPES, Components } from './constants.js';
6
6
  import path from 'path';
7
7
  import { findTransformationByUid, mapToUserFacingType } from './transform.js';
8
+ import { supportsComponentPermissions } from './platformVersion.js';
8
9
  import { validateUid } from './uid.js';
9
10
  // ajv-formats does not play nicely with ESM and TS
10
11
  import addFormatsModule from 'ajv-formats';
@@ -47,6 +48,10 @@ function validateIntermediateRepresentationNode(schema, transformation, irNode,
47
48
  // If there is no config block, there is nothing to validation
48
49
  shouldSkipValidation = true;
49
50
  }
51
+ if (irNode.permissions &&
52
+ !supportsComponentPermissions(translationContext.platformVersion)) {
53
+ transformation.fileParseResult.errors.push(errorMessages.validation.permissionsNotSupported(translationContext.platformVersion));
54
+ }
50
55
  if (!schema[irNode.componentType]) {
51
56
  if (!irNode.componentType) {
52
57
  transformation.fileParseResult.errors.push(errorMessages.validation.missingType);
@@ -8,6 +8,7 @@ import packlist from 'npm-packlist';
8
8
  import { logger } from '@hubspot/local-dev-lib/logger';
9
9
  import { walk } from '@hubspot/local-dev-lib/fs';
10
10
  import { createMinimalTree } from './minimalArboristTree.js';
11
+ import { logMessages } from '../lang/copy.js';
11
12
  /**
12
13
  * Error thrown when a workspace directory cannot be resolved
13
14
  */
@@ -60,10 +61,9 @@ export class OrphanWorkspaceProtocolError extends Error {
60
61
  this.name = 'OrphanWorkspaceProtocolError';
61
62
  }
62
63
  }
63
- const TARBALL_EXTENSIONS = ['.tgz', '.tar.gz', '.tar'];
64
64
  function hasTarballExtension(p) {
65
- const lower = p.toLowerCase();
66
- return TARBALL_EXTENSIONS.some(ext => lower.endsWith(ext));
65
+ const { ext, name } = path.parse(p.toLowerCase());
66
+ return (ext === '.tgz' || ext === '.tar' || (ext === '.gz' && name.endsWith('.tar')));
67
67
  }
68
68
  /**
69
69
  * Expands tilde (~) in paths to the user's home directory
@@ -181,7 +181,7 @@ export async function resolveWorkspaceDirectories(baseDir, workspaceGlobs) {
181
181
  }
182
182
  }
183
183
  catch (e) {
184
- logger.warn(`Cannot access directory ${dir}: ${e}`);
184
+ logger.warn(logMessages.workspaces.cannotAccessDirectory(dir, e));
185
185
  }
186
186
  }
187
187
  return Array.from(resolved);