@hubspot/eslint-config-ui-extensions 1.1.1 → 1.2.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/README.md +1 -0
  2. package/dist/__tests__/index.spec.js +22 -1
  3. package/dist/__tests__/rules/no-browser-dialogs.spec.js +2 -2
  4. package/dist/__tests__/rules/no-browser-storage.spec.js +2 -2
  5. package/dist/__tests__/rules/no-console.spec.js +2 -2
  6. package/dist/__tests__/rules/no-dom-access.spec.js +2 -2
  7. package/dist/__tests__/rules/no-experimental-imports.spec.js +2 -2
  8. package/dist/__tests__/rules/no-html-elements.spec.js +2 -2
  9. package/dist/__tests__/rules/no-import-meta-env.spec.d.ts +1 -0
  10. package/dist/__tests__/rules/no-import-meta-env.spec.js +100 -0
  11. package/dist/__tests__/rules/no-invalid-extension-point-imports.spec.js +2 -2
  12. package/dist/__tests__/rules/no-invalid-image-src.spec.js +2 -2
  13. package/dist/__tests__/rules/no-native-http.spec.js +2 -2
  14. package/dist/__tests__/rules/no-parent-imports.spec.js +2 -2
  15. package/dist/__tests__/rules/no-restricted-globals.spec.js +2 -2
  16. package/dist/__tests__/utils/ast-utils.spec.js +1 -1
  17. package/dist/__tests__/utils/extension-point-utils.spec.js +1 -1
  18. package/dist/__tests__/utils/import-utils.spec.js +1 -1
  19. package/dist/__tests__/utils/jsx-utils.spec.js +1 -1
  20. package/dist/__tests__/utils/scope-utils.spec.js +1 -1
  21. package/dist/index.js +15 -12
  22. package/dist/rules/no-browser-dialogs.js +2 -2
  23. package/dist/rules/no-browser-storage.js +2 -2
  24. package/dist/rules/no-console.js +2 -2
  25. package/dist/rules/no-dom-access.js +2 -2
  26. package/dist/rules/no-experimental-imports.js +1 -1
  27. package/dist/rules/no-html-elements.js +1 -1
  28. package/dist/rules/no-import-meta-env.d.ts +2 -0
  29. package/dist/rules/no-import-meta-env.js +67 -0
  30. package/dist/rules/no-invalid-extension-point-imports.js +2 -2
  31. package/dist/rules/no-invalid-image-src.js +2 -2
  32. package/dist/rules/no-native-http.js +2 -2
  33. package/dist/rules/no-parent-imports.js +2 -2
  34. package/dist/rules/no-restricted-globals.js +2 -2
  35. package/dist/utils/extension-point-utils.js +1 -1
  36. package/package.json +7 -6
package/README.md CHANGED
@@ -182,6 +182,7 @@ export default defineConfig([
182
182
  | [no-dom-access](docs/rules/no-dom-access.md) | Prevent access to the DOM (document object) in UI Extensions, which run in a sandboxed web worker without DOM access. | |
183
183
  | [no-experimental-imports](docs/rules/no-experimental-imports.md) | Warn when importing from the experimental path of @hubspot/ui-extensions. | |
184
184
  | [no-html-elements](docs/rules/no-html-elements.md) | Disallow usage of unsupported HTML elements in UI Extensions. | |
185
+ | [no-import-meta-env](docs/rules/no-import-meta-env.md) | Prevent reading unsupported import.meta.env keys in UI Extensions builds. | |
185
186
  | [no-invalid-extension-point-imports](docs/rules/no-invalid-extension-point-imports.md) | Prevent importing components from unsupported extension points. | |
186
187
  | [no-invalid-image-src](docs/rules/no-invalid-image-src.md) | Only allow valid image URLs in the src attribute of Image components from @hubspot/ui-extensions. | 🔧 |
187
188
  | [no-native-http](docs/rules/no-native-http.md) | Prevent usage of native browser HTTP APIs (fetch and XMLHttpRequest) in UI Extensions. | |
@@ -1,5 +1,5 @@
1
1
  import { ESLint } from 'eslint';
2
- import { config } from "../index.js";
2
+ import { config } from '../index.js';
3
3
  /**
4
4
  * A helper function to lint code with the imported ESLint config.
5
5
  * Returns the linting messages.
@@ -300,4 +300,25 @@ describe('config', () => {
300
300
  expect(customRuleError).toBeUndefined();
301
301
  });
302
302
  });
303
+ describe('custom no-import-meta-env rule', () => {
304
+ it('should error when reading an unsupported import.meta.env key', async () => {
305
+ const code = `
306
+ const apiUrl = import.meta.env.VITE_API_URL;
307
+ `;
308
+ const messages = await lintCode(code, 'test.js');
309
+ const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-import-meta-env');
310
+ expect(customRuleError).toBeDefined();
311
+ expect(customRuleError?.severity).toBe(2);
312
+ expect(customRuleError?.message).toContain('VITE_API_URL');
313
+ });
314
+ it('should allow reading always-defined import.meta.env keys', async () => {
315
+ const code = `
316
+ const isDev = import.meta.env.DEV;
317
+ const mode = import.meta.env.MODE;
318
+ `;
319
+ const messages = await lintCode(code, 'test.js');
320
+ const customRuleError = messages.find((m) => m.ruleId === '@hubspot/ui-extensions/no-import-meta-env');
321
+ expect(customRuleError).toBeUndefined();
322
+ });
323
+ });
303
324
  });
@@ -1,7 +1,7 @@
1
1
  import { RuleTester } from 'eslint';
2
2
  import tseslint from 'typescript-eslint';
3
- import { noBrowserDialogs } from "../../rules/no-browser-dialogs.js";
4
- import { languageOptions } from "../../runtime.js";
3
+ import { noBrowserDialogs } from '../../rules/no-browser-dialogs.js';
4
+ import { languageOptions } from '../../runtime.js';
5
5
  const ruleTester = new RuleTester({
6
6
  languageOptions: {
7
7
  ...languageOptions,
@@ -1,7 +1,7 @@
1
1
  import { RuleTester } from 'eslint';
2
2
  import tseslint from 'typescript-eslint';
3
- import { noBrowserStorage } from "../../rules/no-browser-storage.js";
4
- import { languageOptions } from "../../runtime.js";
3
+ import { noBrowserStorage } from '../../rules/no-browser-storage.js';
4
+ import { languageOptions } from '../../runtime.js';
5
5
  const ruleTester = new RuleTester({
6
6
  languageOptions: {
7
7
  ...languageOptions,
@@ -1,7 +1,7 @@
1
1
  import { RuleTester } from 'eslint';
2
2
  import tseslint from 'typescript-eslint';
3
- import { noConsole } from "../../rules/no-console.js";
4
- import { languageOptions } from "../../runtime.js";
3
+ import { noConsole } from '../../rules/no-console.js';
4
+ import { languageOptions } from '../../runtime.js';
5
5
  const ruleTester = new RuleTester({
6
6
  languageOptions: {
7
7
  ...languageOptions,
@@ -1,7 +1,7 @@
1
1
  import { RuleTester } from 'eslint';
2
2
  import tseslint from 'typescript-eslint';
3
- import { noDomAccess } from "../../rules/no-dom-access.js";
4
- import { languageOptions } from "../../runtime.js";
3
+ import { noDomAccess } from '../../rules/no-dom-access.js';
4
+ import { languageOptions } from '../../runtime.js';
5
5
  const ruleTester = new RuleTester({
6
6
  languageOptions: {
7
7
  ...languageOptions,
@@ -1,6 +1,6 @@
1
1
  import { RuleTester } from 'eslint';
2
- import { noExperimentalImports } from "../../rules/no-experimental-imports.js";
3
- import { languageOptions } from "../../runtime.js";
2
+ import { noExperimentalImports } from '../../rules/no-experimental-imports.js';
3
+ import { languageOptions } from '../../runtime.js';
4
4
  const ruleTesterOptions = {
5
5
  languageOptions: {
6
6
  ...languageOptions,
@@ -1,6 +1,6 @@
1
1
  import { RuleTester } from 'eslint';
2
- import { unsupportedHtmlElements } from "../../rules/no-html-elements.js";
3
- import { languageOptions } from "../../runtime.js";
2
+ import { unsupportedHtmlElements } from '../../rules/no-html-elements.js';
3
+ import { languageOptions } from '../../runtime.js';
4
4
  const ruleTester = new RuleTester({
5
5
  languageOptions: {
6
6
  ...languageOptions,
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,100 @@
1
+ import { RuleTester } from 'eslint';
2
+ import { noImportMetaEnv } from '../../rules/no-import-meta-env.js';
3
+ import { languageOptions } from '../../runtime.js';
4
+ const ruleTester = new RuleTester({
5
+ languageOptions: {
6
+ ...languageOptions,
7
+ parserOptions: {
8
+ ecmaVersion: 'latest',
9
+ sourceType: 'module',
10
+ ecmaFeatures: {
11
+ jsx: true,
12
+ },
13
+ },
14
+ },
15
+ });
16
+ describe('no-import-meta-env', () => {
17
+ ruleTester.run('no-import-meta-env', noImportMetaEnv, {
18
+ valid: [
19
+ {
20
+ name: 'allows import.meta.env.MODE',
21
+ code: `const mode = import.meta.env.MODE;`,
22
+ },
23
+ {
24
+ name: 'allows import.meta.env.DEV',
25
+ code: `const isDev = import.meta.env.DEV;`,
26
+ },
27
+ {
28
+ name: 'allows import.meta.env.PROD',
29
+ code: `const isProd = import.meta.env.PROD;`,
30
+ },
31
+ {
32
+ name: 'allows import.meta.env.SSR',
33
+ code: `const isSsr = import.meta.env.SSR;`,
34
+ },
35
+ {
36
+ name: 'allows import.meta.env.BASE_URL',
37
+ code: `const base = import.meta.env.BASE_URL;`,
38
+ },
39
+ {
40
+ name: 'allows bracket access for allowed keys',
41
+ code: `const mode = import.meta.env['MODE'];`,
42
+ },
43
+ {
44
+ name: 'allows import.meta.env itself (no property access)',
45
+ code: `const env = import.meta.env;`,
46
+ },
47
+ {
48
+ name: 'allows import.meta without env',
49
+ code: `const url = import.meta.url;`,
50
+ },
51
+ ],
52
+ invalid: [
53
+ {
54
+ name: 'flags dot access for custom VITE_ key',
55
+ code: `const apiKey = import.meta.env.VITE_API_KEY;`,
56
+ errors: [
57
+ { messageId: 'unsupportedKey', data: { key: 'VITE_API_KEY' } },
58
+ ],
59
+ },
60
+ {
61
+ name: 'flags bracket access for custom VITE_ key',
62
+ code: `const apiKey = import.meta.env['VITE_API_KEY'];`,
63
+ errors: [
64
+ { messageId: 'unsupportedKey', data: { key: 'VITE_API_KEY' } },
65
+ ],
66
+ },
67
+ {
68
+ name: 'flags dynamic computed access',
69
+ code: `const key = 'VITE_FOO'; const val = import.meta.env[key];`,
70
+ errors: [{ messageId: 'dynamicAccess' }],
71
+ },
72
+ {
73
+ name: 'flags multiple unsupported keys in one file',
74
+ code: `
75
+ const a = import.meta.env.VITE_API_URL;
76
+ const b = import.meta.env.VITE_SECRET;
77
+ `,
78
+ errors: [
79
+ { messageId: 'unsupportedKey', data: { key: 'VITE_API_URL' } },
80
+ { messageId: 'unsupportedKey', data: { key: 'VITE_SECRET' } },
81
+ ],
82
+ },
83
+ {
84
+ name: 'flags unsupported key in JSX expression',
85
+ code: `
86
+ import { Text } from '@hubspot/ui-extensions';
87
+ function Extension() {
88
+ return <Text>{import.meta.env.VITE_LABEL}</Text>;
89
+ }
90
+ `,
91
+ errors: [{ messageId: 'unsupportedKey', data: { key: 'VITE_LABEL' } }],
92
+ },
93
+ {
94
+ name: 'flags bracket access for a non-string expression',
95
+ code: `const val = import.meta.env[getKey()];`,
96
+ errors: [{ messageId: 'dynamicAccess' }],
97
+ },
98
+ ],
99
+ });
100
+ });
@@ -1,6 +1,6 @@
1
1
  import { RuleTester } from 'eslint';
2
- import { noInvalidExtensionPointImports } from "../../rules/no-invalid-extension-point-imports.js";
3
- import { languageOptions } from "../../runtime.js";
2
+ import { noInvalidExtensionPointImports } from '../../rules/no-invalid-extension-point-imports.js';
3
+ import { languageOptions } from '../../runtime.js';
4
4
  const ruleTesterOptions = {
5
5
  languageOptions: {
6
6
  ...languageOptions,
@@ -1,6 +1,6 @@
1
1
  import { RuleTester } from 'eslint';
2
- import { noInvalidImageSrc } from "../../rules/no-invalid-image-src.js";
3
- import { languageOptions } from "../../runtime.js";
2
+ import { noInvalidImageSrc } from '../../rules/no-invalid-image-src.js';
3
+ import { languageOptions } from '../../runtime.js';
4
4
  const ruleTester = new RuleTester({
5
5
  languageOptions: {
6
6
  ...languageOptions,
@@ -1,7 +1,7 @@
1
1
  import { RuleTester } from 'eslint';
2
2
  import tseslint from 'typescript-eslint';
3
- import { noNativeHttp } from "../../rules/no-native-http.js";
4
- import { languageOptions } from "../../runtime.js";
3
+ import { noNativeHttp } from '../../rules/no-native-http.js';
4
+ import { languageOptions } from '../../runtime.js';
5
5
  const ruleTester = new RuleTester({
6
6
  languageOptions: {
7
7
  ...languageOptions,
@@ -1,6 +1,6 @@
1
1
  import { RuleTester } from 'eslint';
2
- import { noParentImports } from "../../rules/no-parent-imports.js";
3
- import { languageOptions } from "../../runtime.js";
2
+ import { noParentImports } from '../../rules/no-parent-imports.js';
3
+ import { languageOptions } from '../../runtime.js';
4
4
  const ruleTesterOptions = {
5
5
  languageOptions: {
6
6
  ...languageOptions,
@@ -1,7 +1,7 @@
1
1
  import { RuleTester } from 'eslint';
2
2
  import tseslint from 'typescript-eslint';
3
- import { noRestrictedGlobals } from "../../rules/no-restricted-globals.js";
4
- import { languageOptions } from "../../runtime.js";
3
+ import { noRestrictedGlobals } from '../../rules/no-restricted-globals.js';
4
+ import { languageOptions } from '../../runtime.js';
5
5
  const ruleTester = new RuleTester({
6
6
  languageOptions: {
7
7
  ...languageOptions,
@@ -1,4 +1,4 @@
1
- import { getMemberExpressionPropertyName, isGlobalIdentifierCall, isWindowSelfOrGlobalThisPropertyAccess, } from "../../utils/ast-utils.js";
1
+ import { getMemberExpressionPropertyName, isGlobalIdentifierCall, isWindowSelfOrGlobalThisPropertyAccess, } from '../../utils/ast-utils.js';
2
2
  describe('ast-utils', () => {
3
3
  describe('isGlobalIdentifierCall', () => {
4
4
  it('should return true for global identifier calls', () => {
@@ -1,4 +1,4 @@
1
- import { getExtensionPointFromFilePath, getHubspotImportExtensionPoint, isImportOutsideExtensionPoint, mapExtensionPointToImportDirectory, } from "../../utils/extension-point-utils.js";
1
+ import { getExtensionPointFromFilePath, getHubspotImportExtensionPoint, isImportOutsideExtensionPoint, mapExtensionPointToImportDirectory, } from '../../utils/extension-point-utils.js';
2
2
  describe('extension-point-utils', () => {
3
3
  describe('getExtensionPointFromFilePath', () => {
4
4
  it('should return the extension point for valid paths', () => {
@@ -1,4 +1,4 @@
1
- import { getImportedComponentName, isExperimentalImport, isHubspotExtensibilityImport, isRelativeImport, } from "../../utils/import-utils.js";
1
+ import { getImportedComponentName, isExperimentalImport, isHubspotExtensibilityImport, isRelativeImport, } from '../../utils/import-utils.js';
2
2
  describe('import-utils', () => {
3
3
  describe('isHubspotExtensibilityImport', () => {
4
4
  it('should return true for @hubspot/ui-extensions imports', () => {
@@ -1,4 +1,4 @@
1
- import { findJSXAttribute, isJSXComponent } from "../../utils/jsx-utils.js";
1
+ import { findJSXAttribute, isJSXComponent } from '../../utils/jsx-utils.js';
2
2
  describe('jsx-utils', () => {
3
3
  describe('findJSXAttribute', () => {
4
4
  it('should find an attribute by name', () => {
@@ -1,4 +1,4 @@
1
- import { isInTypeContext } from "../../utils/scope-utils.js";
1
+ import { isInTypeContext } from '../../utils/scope-utils.js';
2
2
  describe('scope-utils', () => {
3
3
  describe('isInTypeContext', () => {
4
4
  it('should return true when node has a TypeScript type parent', () => {
package/dist/index.js CHANGED
@@ -1,16 +1,17 @@
1
1
  import globals from 'globals';
2
- import { noBrowserDialogs } from "./rules/no-browser-dialogs.js";
3
- import { noBrowserStorage } from "./rules/no-browser-storage.js";
4
- import { noConsole } from "./rules/no-console.js";
5
- import { noDomAccess } from "./rules/no-dom-access.js";
6
- import { noExperimentalImports } from "./rules/no-experimental-imports.js";
7
- import { unsupportedHtmlElements } from "./rules/no-html-elements.js";
8
- import { noInvalidExtensionPointImports } from "./rules/no-invalid-extension-point-imports.js";
9
- import { noInvalidImageSrc } from "./rules/no-invalid-image-src.js";
10
- import { noNativeHttp } from "./rules/no-native-http.js";
11
- import { noParentImports } from "./rules/no-parent-imports.js";
12
- import { noRestrictedGlobals } from "./rules/no-restricted-globals.js";
13
- import { languageOptions } from "./runtime.js";
2
+ import { noBrowserDialogs } from './rules/no-browser-dialogs.js';
3
+ import { noBrowserStorage } from './rules/no-browser-storage.js';
4
+ import { noConsole } from './rules/no-console.js';
5
+ import { noDomAccess } from './rules/no-dom-access.js';
6
+ import { noExperimentalImports } from './rules/no-experimental-imports.js';
7
+ import { unsupportedHtmlElements } from './rules/no-html-elements.js';
8
+ import { noImportMetaEnv } from './rules/no-import-meta-env.js';
9
+ import { noInvalidExtensionPointImports } from './rules/no-invalid-extension-point-imports.js';
10
+ import { noInvalidImageSrc } from './rules/no-invalid-image-src.js';
11
+ import { noNativeHttp } from './rules/no-native-http.js';
12
+ import { noParentImports } from './rules/no-parent-imports.js';
13
+ import { noRestrictedGlobals } from './rules/no-restricted-globals.js';
14
+ import { languageOptions } from './runtime.js';
14
15
  const rules = {
15
16
  'no-html-elements': unsupportedHtmlElements,
16
17
  'no-invalid-extension-point-imports': noInvalidExtensionPointImports,
@@ -23,6 +24,7 @@ const rules = {
23
24
  'no-browser-storage': noBrowserStorage,
24
25
  'no-parent-imports': noParentImports,
25
26
  'no-restricted-globals': noRestrictedGlobals,
27
+ 'no-import-meta-env': noImportMetaEnv,
26
28
  };
27
29
  // We export a config array for our users to extend
28
30
  export const config = [
@@ -62,6 +64,7 @@ export const config = [
62
64
  '@hubspot/ui-extensions/no-browser-storage': 'error',
63
65
  '@hubspot/ui-extensions/no-parent-imports': 'error',
64
66
  '@hubspot/ui-extensions/no-restricted-globals': 'error',
67
+ '@hubspot/ui-extensions/no-import-meta-env': 'error',
65
68
  },
66
69
  },
67
70
  ];
@@ -1,5 +1,5 @@
1
- import { getMemberExpressionPropertyName, isGlobalIdentifierCall, isWindowSelfOrGlobalThisPropertyAccess, } from "../utils/ast-utils.js";
2
- import { isInTypeContext, isLocalVariable } from "../utils/scope-utils.js";
1
+ import { getMemberExpressionPropertyName, isGlobalIdentifierCall, isWindowSelfOrGlobalThisPropertyAccess, } from '../utils/ast-utils.js';
2
+ import { isInTypeContext, isLocalVariable } from '../utils/scope-utils.js';
3
3
  const MESSAGE_IDS = {
4
4
  alert: 'nativeAlert',
5
5
  confirm: 'nativeConfirm',
@@ -1,5 +1,5 @@
1
- import { isWindowSelfOrGlobalThisPropertyAccess } from "../utils/ast-utils.js";
2
- import { isInTypeContext, isLocalVariable } from "../utils/scope-utils.js";
1
+ import { isWindowSelfOrGlobalThisPropertyAccess } from '../utils/ast-utils.js';
2
+ import { isInTypeContext, isLocalVariable } from '../utils/scope-utils.js';
3
3
  const STORAGE_IDENTIFIERS = new Set([
4
4
  'localStorage',
5
5
  'sessionStorage',
@@ -1,5 +1,5 @@
1
- import { getMemberExpressionPropertyName } from "../utils/ast-utils.js";
2
- import { isInTypeContext, isLocalVariable } from "../utils/scope-utils.js";
1
+ import { getMemberExpressionPropertyName } from '../utils/ast-utils.js';
2
+ import { isInTypeContext, isLocalVariable } from '../utils/scope-utils.js';
3
3
  const CONSOLE_METHODS = ['log', 'info', 'warn', 'debug', 'error'];
4
4
  const LOGGER_ALTERNATIVES = {
5
5
  log: 'logger.debug()',
@@ -1,5 +1,5 @@
1
- import { isWindowSelfOrGlobalThisPropertyAccess } from "../utils/ast-utils.js";
2
- import { isInTypeContext, isLocalVariable } from "../utils/scope-utils.js";
1
+ import { isWindowSelfOrGlobalThisPropertyAccess } from '../utils/ast-utils.js';
2
+ import { isInTypeContext, isLocalVariable } from '../utils/scope-utils.js';
3
3
  export const noDomAccess = {
4
4
  meta: {
5
5
  type: 'problem',
@@ -1,4 +1,4 @@
1
- import { isExperimentalImport } from "../utils/import-utils.js";
1
+ import { isExperimentalImport } from '../utils/import-utils.js';
2
2
  export const noExperimentalImports = {
3
3
  meta: {
4
4
  type: 'suggestion',
@@ -1,4 +1,4 @@
1
- import { htmlTags } from "../constants.js";
1
+ import { htmlTags } from '../constants.js';
2
2
  export const unsupportedHtmlElements = {
3
3
  meta: {
4
4
  type: 'problem',
@@ -0,0 +1,2 @@
1
+ import type { Rule } from 'eslint';
2
+ export declare const noImportMetaEnv: Rule.RuleModule;
@@ -0,0 +1,67 @@
1
+ // Vite's always-defined import.meta.env members
2
+ const ALLOWED_KEYS = new Set(['MODE', 'DEV', 'PROD', 'SSR', 'BASE_URL']);
3
+ function isImportMetaEnv(node) {
4
+ const obj = node.object;
5
+ if (obj.type !== 'MemberExpression')
6
+ return false;
7
+ const meta = obj.object;
8
+ const envProp = obj.property;
9
+ return (meta.type === 'MetaProperty' &&
10
+ meta.meta.name === 'import' &&
11
+ meta.property.name === 'meta' &&
12
+ envProp.type === 'Identifier' &&
13
+ envProp.name === 'env' &&
14
+ !obj.computed);
15
+ }
16
+ export const noImportMetaEnv = {
17
+ meta: {
18
+ type: 'problem',
19
+ docs: {
20
+ description: 'Prevent reading unsupported import.meta.env keys in UI Extensions builds.',
21
+ url: 'https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/linting/rules/no-import-meta-env',
22
+ },
23
+ messages: {
24
+ unsupportedKey: '"import.meta.env.{{key}}" is not available in UI Extensions builds. Only MODE, DEV, PROD, SSR, and BASE_URL are always defined.',
25
+ dynamicAccess: 'Dynamic access of import.meta.env is not supported in UI Extensions builds. Only MODE, DEV, PROD, SSR, and BASE_URL are always defined.',
26
+ },
27
+ schema: [],
28
+ },
29
+ create(context) {
30
+ return {
31
+ MemberExpression(node) {
32
+ if (!isImportMetaEnv(node))
33
+ return;
34
+ const prop = node.property;
35
+ if (!node.computed) {
36
+ // import.meta.env.KEY
37
+ const key = prop.name;
38
+ if (!ALLOWED_KEYS.has(key)) {
39
+ context.report({
40
+ node: node,
41
+ messageId: 'unsupportedKey',
42
+ data: { key },
43
+ });
44
+ }
45
+ }
46
+ else if (prop.type === 'Literal' && typeof prop.value === 'string') {
47
+ // import.meta.env['KEY']
48
+ const key = prop.value;
49
+ if (!ALLOWED_KEYS.has(key)) {
50
+ context.report({
51
+ node: node,
52
+ messageId: 'unsupportedKey',
53
+ data: { key },
54
+ });
55
+ }
56
+ }
57
+ else {
58
+ // import.meta.env[dynamicKey]
59
+ context.report({
60
+ node: node,
61
+ messageId: 'dynamicAccess',
62
+ });
63
+ }
64
+ },
65
+ };
66
+ },
67
+ };
@@ -1,6 +1,6 @@
1
1
  import path from 'path';
2
- import { getExtensionPointFromFilePath, getHubspotImportExtensionPoint, mapExtensionPointToImportDirectory, } from "../utils/extension-point-utils.js";
3
- import { isHubspotExtensibilityImport } from "../utils/import-utils.js";
2
+ import { getExtensionPointFromFilePath, getHubspotImportExtensionPoint, mapExtensionPointToImportDirectory, } from '../utils/extension-point-utils.js';
3
+ import { isHubspotExtensibilityImport } from '../utils/import-utils.js';
4
4
  export const noInvalidExtensionPointImports = {
5
5
  meta: {
6
6
  type: 'problem',
@@ -1,5 +1,5 @@
1
- import { getImportedComponentName, isHubspotExtensibilityImport, } from "../utils/import-utils.js";
2
- import { findJSXAttribute, isJSXComponent } from "../utils/jsx-utils.js";
1
+ import { getImportedComponentName, isHubspotExtensibilityImport, } from '../utils/import-utils.js';
2
+ import { findJSXAttribute, isJSXComponent } from '../utils/jsx-utils.js';
3
3
  const ALLOWED_PROTOCOLS = {
4
4
  HTTP: 'http:',
5
5
  HTTPS: 'https:',
@@ -1,5 +1,5 @@
1
- import { isGlobalIdentifierCall, isWindowSelfOrGlobalThisPropertyAccess, } from "../utils/ast-utils.js";
2
- import { isInTypeContext, isLocalVariable } from "../utils/scope-utils.js";
1
+ import { isGlobalIdentifierCall, isWindowSelfOrGlobalThisPropertyAccess, } from '../utils/ast-utils.js';
2
+ import { isInTypeContext, isLocalVariable } from '../utils/scope-utils.js';
3
3
  export const noNativeHttp = {
4
4
  meta: {
5
5
  type: 'problem',
@@ -1,5 +1,5 @@
1
- import { getExtensionPointFromFilePath, isImportOutsideExtensionPoint, } from "../utils/extension-point-utils.js";
2
- import { isRelativeImport } from "../utils/import-utils.js";
1
+ import { getExtensionPointFromFilePath, isImportOutsideExtensionPoint, } from '../utils/extension-point-utils.js';
2
+ import { isRelativeImport } from '../utils/import-utils.js';
3
3
  export const noParentImports = {
4
4
  meta: {
5
5
  type: 'problem',
@@ -1,5 +1,5 @@
1
- import { isGlobalIdentifierCall, isWindowSelfOrGlobalThisPropertyAccess, } from "../utils/ast-utils.js";
2
- import { isInTypeContext, isLocalVariable } from "../utils/scope-utils.js";
1
+ import { isGlobalIdentifierCall, isWindowSelfOrGlobalThisPropertyAccess, } from '../utils/ast-utils.js';
2
+ import { isInTypeContext, isLocalVariable } from '../utils/scope-utils.js';
3
3
  // Function-like globals that should only be checked as calls
4
4
  const RESTRICTED_FUNCTION_GLOBALS = new Set([
5
5
  'importScripts',
@@ -1,5 +1,5 @@
1
1
  import path from 'path';
2
- import { EXTENSION_IMPORT_DIRECTORIES, EXTENSION_POINT_DIRECTORIES, } from "../constants.js";
2
+ import { EXTENSION_IMPORT_DIRECTORIES, EXTENSION_POINT_DIRECTORIES, } from '../constants.js';
3
3
  /**
4
4
  * This function takes a file path as input and returns the extension point.
5
5
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/eslint-config-ui-extensions",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "eslint plugins for HubSpot React-based UI Extensions",
6
6
  "keywords": [
@@ -20,13 +20,14 @@
20
20
  "globals": "13.24.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@eslint/js": "9.39.2",
23
+ "@eslint/js": "9.39.3",
24
24
  "@types/eslint": "9.6.1",
25
25
  "@types/estree-jsx": "1.0.5",
26
- "@types/node": "22.19.7",
26
+ "@types/node": "24.13.3",
27
+ "@typescript/native-preview": "7.0.0-dev.20260707.2",
27
28
  "@vitest/coverage-v8": "4.0.18",
28
29
  "@vitest/eslint-plugin": "1.6.6",
29
- "eslint": "9.39.2",
30
+ "eslint": "9.39.3",
30
31
  "eslint-doc-generator": "2.3.0",
31
32
  "eslint-plugin-eslint-plugin": "5.5.1",
32
33
  "eslint-plugin-node": "11.1.0",
@@ -52,7 +53,7 @@
52
53
  "license": "MIT",
53
54
  "gitHead": "36d3a36ffaa5bde1a7b73dc139d75027aae65b7f",
54
55
  "scripts": {
55
- "build": "pnpm run clean && tsc",
56
+ "build": "pnpm run clean && tsgo",
56
57
  "clean": "rm -rf dist/",
57
58
  "format": "prettier --write . --ignore-path ../../.prettierignore",
58
59
  "format:check": "prettier --check . --ignore-path ../../.prettierignore",
@@ -64,6 +65,6 @@
64
65
  "test:coverage": "vitest run --coverage",
65
66
  "test:watch": "vitest",
66
67
  "update:eslint-docs": "eslint-doc-generator",
67
- "watch": "tsc --watch"
68
+ "watch": "tsgo --watch"
68
69
  }
69
70
  }