Add standard command menu items (#18527)
## Add standard command menu items
### Summary
This PR introduces standard command menu items, migrating hardcoded
command menu actions to the backend command menu item architecture
powered by front components. It adds a new `twenty-standard-application`
package that defines, builds, and registers front components as standard
command menu items, gated behind the `IS_COMMAND_MENU_ITEM_ENABLED`
feature flag.
### Description
- **New `twenty-standard-application` package**: Contains front
component definitions with an esbuild-based build pipeline that
generates minified `.mjs` bundles and a manifest with checksums.
- **Server-side registration**: New constants register all items with
metadata (labels, icons, positions, availability types, conditional
expressions). A `StandardFrontComponentUploadService` uploads built
components to file storage.
- **`FALLBACK` availability type**: New enum value for command menu
items that appear as fallback options (e.g., "Search Records" fallback).
- **`CommandMenuContextApi` refactor**
- **Conditional availability enhancements**: New array-based helper
functions for evaluating multi-record conditions.
- **Frontend wiring** (twenty-front):
`useCommandMenuItemFrontComponentCommands`
## Next steps
Only simple commands have been implemented for now:
- **Navigation (9)** -- `CommandLink`: go-to-companies,
go-to-dashboards, go-to-notes, go-to-opportunities, go-to-people,
go-to-runs, go-to-settings, go-to-tasks, go-to-workflows
- **Side panel (4)** -- `CommandOpenSidePanelPage`: ask-ai,
search-records, search-records-fallback, view-previous-ai-chats
We still have to implement front components for all the following
commands:
All have placeholder `execute` logic (`async () => {}`) with a `// TODO:
implement execute logic` comment:
**Record (22)**
- `add-to-favorites`, `remove-from-favorites`
- `create-new-record`, `create-new-view`
- `delete-single-record`, `delete-multiple-records`
- `destroy-single-record`, `destroy-multiple-records`
- `restore-single-record`, `restore-multiple-records`
- `export-from-record-index`, `export-from-record-show`,
`export-multiple-records`, `export-note-to-pdf`, `export-view`
- `hide-deleted-records`, `see-deleted-records`
- `import-records`, `merge-multiple-records`, `update-multiple-records`
- `navigate-to-next-record`, `navigate-to-previous-record`
**Page layout (3)** -- `cancel-record-page-layout`,
`edit-record-page-layout`, `save-record-page-layout`
**Dashboard (4)** -- `cancel-dashboard-layout`, `duplicate-dashboard`,
`edit-dashboard-layout`, `save-dashboard-layout`
**Workflow (10)** -- `activate-workflow`, `add-node-workflow`,
`deactivate-workflow`, `discard-draft-workflow`, `duplicate-workflow`,
`see-active-version-workflow`, `see-runs-workflow`,
`see-versions-workflow`, `test-workflow`, `tidy-up-workflow`
**Workflow version (4)** -- `see-runs-workflow-version`,
`see-versions-workflow-version`, `see-workflow-workflow-version`,
`use-as-draft-workflow-version`
**Workflow run (3)** -- `see-version-workflow-run`,
`see-workflow-workflow-run`, `stop-workflow-run`
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
LOGIC_FUNCTION_EXTERNAL_MODULES,
|
||||
createSdkClientsResolverPlugin,
|
||||
} from '@/cli/utilities/build/common/esbuild-watcher';
|
||||
import { FRONT_COMPONENT_EXTERNAL_MODULES } from '@/cli/utilities/build/common/front-component-build/constants/front-component-external-modules';
|
||||
import { getBaseFrontComponentBuildOptions } from '@/cli/utilities/build/common/front-component-build/utils/get-base-front-component-build-options';
|
||||
import { getFrontComponentBuildPlugins } from '@/cli/utilities/build/common/front-component-build/utils/get-front-component-build-plugins';
|
||||
import { type OnFileBuiltCallback } from '@/cli/utilities/build/common/restartable-watcher-interface';
|
||||
import { type EntityFilePaths } from '@/cli/utilities/build/manifest/manifest-extract-config';
|
||||
@@ -90,17 +90,9 @@ export const buildApplication = async (
|
||||
sourcePaths: frontComponents,
|
||||
fileFolder: FileFolder.BuiltFrontComponent,
|
||||
buildOptions: {
|
||||
bundle: true,
|
||||
splitting: false,
|
||||
format: 'esm',
|
||||
...getBaseFrontComponentBuildOptions(),
|
||||
outdir: join(options.appPath, OUTPUT_DIR),
|
||||
outExtension: { '.js': '.mjs' },
|
||||
external: FRONT_COMPONENT_EXTERNAL_MODULES,
|
||||
tsconfig: join(options.appPath, 'tsconfig.json'),
|
||||
jsx: 'automatic',
|
||||
sourcemap: true,
|
||||
metafile: true,
|
||||
logLevel: 'silent',
|
||||
plugins: [
|
||||
createSdkClientsResolverPlugin(options.appPath),
|
||||
...getFrontComponentBuildPlugins(),
|
||||
|
||||
+3
-2
@@ -1,8 +1,9 @@
|
||||
import {
|
||||
defineFrontComponent,
|
||||
none,
|
||||
numberOfSelectedRecords,
|
||||
objectPermissions,
|
||||
selectedRecord,
|
||||
selectedRecords,
|
||||
} from '@/sdk';
|
||||
|
||||
const MyComponent = () => null;
|
||||
@@ -15,7 +16,7 @@ export default defineFrontComponent({
|
||||
label: 'Complex Soft Delete',
|
||||
conditionalAvailabilityExpression:
|
||||
objectPermissions.canSoftDeleteObjectRecords &&
|
||||
!selectedRecord.isRemote &&
|
||||
none(selectedRecords, 'isRemote') &&
|
||||
numberOfSelectedRecords > 0,
|
||||
},
|
||||
});
|
||||
|
||||
+5
-2
@@ -1,4 +1,4 @@
|
||||
import { defineFrontComponent, isDefined, selectedRecord } from '@/sdk';
|
||||
import { defineFrontComponent, someDefined, selectedRecords } from '@/sdk';
|
||||
|
||||
const MyComponent = () => null;
|
||||
|
||||
@@ -8,6 +8,9 @@ export default defineFrontComponent({
|
||||
command: {
|
||||
universalIdentifier: 'custom-function-cmd',
|
||||
label: 'Custom Function',
|
||||
conditionalAvailabilityExpression: isDefined(selectedRecord.deletedAt),
|
||||
conditionalAvailabilityExpression: someDefined(
|
||||
selectedRecords,
|
||||
'deletedAt',
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
+9
-2
@@ -1,4 +1,9 @@
|
||||
import { defineFrontComponent, isFavorite, isRemote, isShowPage } from '@/sdk';
|
||||
import {
|
||||
defineFrontComponent,
|
||||
favoriteRecordIds,
|
||||
objectMetadataItem,
|
||||
pageType,
|
||||
} from '@/sdk';
|
||||
|
||||
const MyComponent = () => null;
|
||||
|
||||
@@ -8,6 +13,8 @@ export default defineFrontComponent({
|
||||
command: {
|
||||
universalIdentifier: 'parenthesized-expression-cmd',
|
||||
label: 'Parenthesized Expression',
|
||||
conditionalAvailabilityExpression: (isShowPage || isFavorite) && !isRemote,
|
||||
conditionalAvailabilityExpression:
|
||||
(pageType === 'RECORD_PAGE' || favoriteRecordIds.length > 0) &&
|
||||
!objectMetadataItem.isRemote,
|
||||
},
|
||||
});
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { defineFrontComponent, isShowPage } from '@/sdk';
|
||||
import { defineFrontComponent, pageType } from '@/sdk';
|
||||
|
||||
const MyComponent = () => null;
|
||||
|
||||
@@ -8,6 +8,6 @@ export default defineFrontComponent({
|
||||
command: {
|
||||
universalIdentifier: 'simple-boolean-cmd',
|
||||
label: 'Simple Boolean',
|
||||
conditionalAvailabilityExpression: isShowPage,
|
||||
conditionalAvailabilityExpression: pageType === 'RECORD_PAGE',
|
||||
},
|
||||
});
|
||||
|
||||
+8
-2
@@ -1,4 +1,9 @@
|
||||
import { defineFrontComponent, isShowPage, selectedRecord } from '@/sdk';
|
||||
import {
|
||||
defineFrontComponent,
|
||||
everyEquals,
|
||||
pageType,
|
||||
selectedRecords,
|
||||
} from '@/sdk';
|
||||
|
||||
const MyComponent = () => null;
|
||||
|
||||
@@ -9,6 +14,7 @@ export default defineFrontComponent({
|
||||
universalIdentifier: 'string-comparison-cmd',
|
||||
label: 'String Comparison',
|
||||
conditionalAvailabilityExpression:
|
||||
isShowPage && selectedRecord.company.name === 'apple',
|
||||
pageType === 'RECORD_PAGE' &&
|
||||
everyEquals(selectedRecords, 'company.name', 'apple'),
|
||||
},
|
||||
});
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
defineFrontComponent,
|
||||
isShowPage,
|
||||
pageType,
|
||||
targetObjectWritePermissions,
|
||||
} from '@/sdk';
|
||||
|
||||
@@ -13,6 +13,6 @@ export default defineFrontComponent({
|
||||
universalIdentifier: 'target-permissions-cmd',
|
||||
label: 'Target Permissions',
|
||||
conditionalAvailabilityExpression:
|
||||
isShowPage && targetObjectWritePermissions.person,
|
||||
pageType === 'RECORD_PAGE' && targetObjectWritePermissions.person,
|
||||
},
|
||||
});
|
||||
|
||||
+14
@@ -77,6 +77,20 @@ describe('toExprEval', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('array .length to arrayLength()', () => {
|
||||
it('should convert favoriteRecordIds.length to arrayLength(favoriteRecordIds)', () => {
|
||||
expect(toExprEval('favoriteRecordIds.length > 0')).toBe(
|
||||
'arrayLength(favoriteRecordIds) > 0',
|
||||
);
|
||||
});
|
||||
|
||||
it('should convert selectedRecords.length to arrayLength(selectedRecords)', () => {
|
||||
expect(toExprEval('selectedRecords.length === 1')).toBe(
|
||||
'arrayLength(selectedRecords) == 1',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should return an empty string unchanged', () => {
|
||||
expect(toExprEval('')).toBe('');
|
||||
|
||||
+87
-70
@@ -2,7 +2,10 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { transformConditionalAvailabilityExpressionsForEsBuildPlugin } from '@/cli/utilities/build/common/conditional-availability/utils/transform-conditional-availability-expressions';
|
||||
import { type CommandMenuContextApi } from 'twenty-shared/types';
|
||||
import {
|
||||
CommandMenuContextApiPageType,
|
||||
type CommandMenuContextApi,
|
||||
} from 'twenty-shared/types';
|
||||
import { evaluateConditionalAvailabilityExpression } from 'twenty-shared/utils';
|
||||
|
||||
const MOCKS_DIR = path.join(__dirname, '__mocks__');
|
||||
@@ -13,11 +16,10 @@ const readMock = (filename: string): string =>
|
||||
const buildMockCommandMenuContextApi = (
|
||||
overrides: Partial<CommandMenuContextApi> = {},
|
||||
): CommandMenuContextApi => ({
|
||||
isShowPage: false,
|
||||
pageType: CommandMenuContextApiPageType.INDEX_PAGE,
|
||||
isInSidePanel: false,
|
||||
isFavorite: false,
|
||||
isRemote: false,
|
||||
isNoteOrTask: false,
|
||||
isPageInEditMode: false,
|
||||
favoriteRecordIds: [],
|
||||
isSelectAll: false,
|
||||
hasAnySoftDeleteFilterOnView: false,
|
||||
numberOfSelectedRecords: 0,
|
||||
@@ -31,10 +33,11 @@ const buildMockCommandMenuContextApi = (
|
||||
rowLevelPermissionPredicates: [],
|
||||
rowLevelPermissionPredicateGroups: [],
|
||||
},
|
||||
selectedRecord: undefined,
|
||||
selectedRecords: [],
|
||||
featureFlags: {},
|
||||
targetObjectReadPermissions: {},
|
||||
targetObjectWritePermissions: {},
|
||||
objectMetadataItem: {},
|
||||
...overrides,
|
||||
});
|
||||
|
||||
@@ -156,35 +159,37 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => {
|
||||
|
||||
it('should handle multiple expressions in one source', () => {
|
||||
const source = [
|
||||
'const a = { conditionalAvailabilityExpression: isShowPage };',
|
||||
'const b = { conditionalAvailabilityExpression: isFavorite && !isRemote };',
|
||||
'const a = { conditionalAvailabilityExpression: pageType === "RECORD_PAGE" };',
|
||||
'const b = { conditionalAvailabilityExpression: favoriteRecordIds.length > 0 && !objectMetadataItem.isRemote };',
|
||||
].join('\n');
|
||||
const transformed =
|
||||
transformConditionalAvailabilityExpressionsForEsBuildPlugin(source);
|
||||
|
||||
expect(transformed).toContain(
|
||||
'conditionalAvailabilityExpression: "isShowPage"',
|
||||
'conditionalAvailabilityExpression: "pageType == \\"RECORD_PAGE\\""',
|
||||
);
|
||||
expect(transformed).toContain(
|
||||
'conditionalAvailabilityExpression: "isFavorite and not isRemote"',
|
||||
'conditionalAvailabilityExpression: "arrayLength(favoriteRecordIds) > 0 and not objectMetadataItem.isRemote"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle extra spaces around colon', () => {
|
||||
const source =
|
||||
'{ conditionalAvailabilityExpression : isShowPage && isFavorite }';
|
||||
'{ conditionalAvailabilityExpression : pageType === "RECORD_PAGE" && favoriteRecordIds.length > 0 }';
|
||||
const transformed =
|
||||
transformConditionalAvailabilityExpressionsForEsBuildPlugin(source);
|
||||
|
||||
expect(transformed).toContain('"isShowPage and isFavorite"');
|
||||
expect(transformed).toContain(
|
||||
'"pageType == \\"RECORD_PAGE\\" and arrayLength(favoriteRecordIds) > 0"',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('e2e: mock file -> transform -> evaluate', () => {
|
||||
describe('simple-boolean-front-component', () => {
|
||||
it('should evaluate isShowPage when true', () => {
|
||||
it('should evaluate pageType when RECORD_PAGE', () => {
|
||||
const context = buildMockCommandMenuContextApi({
|
||||
isShowPage: true,
|
||||
pageType: CommandMenuContextApiPageType.RECORD_PAGE,
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -195,9 +200,9 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should evaluate isShowPage when false', () => {
|
||||
it('should evaluate pageType when INDEX_PAGE', () => {
|
||||
const context = buildMockCommandMenuContextApi({
|
||||
isShowPage: false,
|
||||
pageType: CommandMenuContextApiPageType.INDEX_PAGE,
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -297,13 +302,15 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => {
|
||||
it('should allow soft delete for local record with selection', () => {
|
||||
const context = buildMockCommandMenuContextApi({
|
||||
numberOfSelectedRecords: 2,
|
||||
selectedRecord: {
|
||||
id: 'rec-1',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
deletedAt: null,
|
||||
isRemote: false,
|
||||
},
|
||||
selectedRecords: [
|
||||
{
|
||||
id: 'rec-1',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
deletedAt: null,
|
||||
isRemote: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -317,13 +324,15 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => {
|
||||
it('should deny soft delete when record is remote', () => {
|
||||
const context = buildMockCommandMenuContextApi({
|
||||
numberOfSelectedRecords: 1,
|
||||
selectedRecord: {
|
||||
id: 'rec-1',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
deletedAt: null,
|
||||
isRemote: true,
|
||||
},
|
||||
selectedRecords: [
|
||||
{
|
||||
id: 'rec-1',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
deletedAt: null,
|
||||
isRemote: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -338,9 +347,9 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => {
|
||||
describe('parenthesized-expression-front-component', () => {
|
||||
it('should allow when favorite and not remote', () => {
|
||||
const context = buildMockCommandMenuContextApi({
|
||||
isShowPage: false,
|
||||
isFavorite: true,
|
||||
isRemote: false,
|
||||
pageType: CommandMenuContextApiPageType.INDEX_PAGE,
|
||||
favoriteRecordIds: ['rec-1'],
|
||||
objectMetadataItem: { isRemote: false },
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -351,11 +360,11 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny when remote even if show page', () => {
|
||||
it('should deny when remote even if record page', () => {
|
||||
const context = buildMockCommandMenuContextApi({
|
||||
isShowPage: true,
|
||||
isFavorite: false,
|
||||
isRemote: true,
|
||||
pageType: CommandMenuContextApiPageType.RECORD_PAGE,
|
||||
favoriteRecordIds: [],
|
||||
objectMetadataItem: { isRemote: true },
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -370,12 +379,14 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => {
|
||||
describe('custom-function-front-component', () => {
|
||||
it('should return false when deletedAt is null', () => {
|
||||
const context = buildMockCommandMenuContextApi({
|
||||
selectedRecord: {
|
||||
id: 'rec-1',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
deletedAt: null,
|
||||
},
|
||||
selectedRecords: [
|
||||
{
|
||||
id: 'rec-1',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
deletedAt: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -388,12 +399,14 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => {
|
||||
|
||||
it('should return true when deletedAt is set', () => {
|
||||
const context = buildMockCommandMenuContextApi({
|
||||
selectedRecord: {
|
||||
id: 'rec-1',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
deletedAt: '2024-06-01',
|
||||
},
|
||||
selectedRecords: [
|
||||
{
|
||||
id: 'rec-1',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
deletedAt: '2024-06-01',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -406,16 +419,18 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => {
|
||||
});
|
||||
|
||||
describe('string-comparison-front-component', () => {
|
||||
it('should match when on show page and company name matches', () => {
|
||||
it('should match when on record page and company name matches', () => {
|
||||
const context = buildMockCommandMenuContextApi({
|
||||
isShowPage: true,
|
||||
selectedRecord: {
|
||||
id: 'rec-1',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
deletedAt: null,
|
||||
company: { name: 'apple' },
|
||||
},
|
||||
pageType: CommandMenuContextApiPageType.RECORD_PAGE,
|
||||
selectedRecords: [
|
||||
{
|
||||
id: 'rec-1',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
deletedAt: null,
|
||||
company: { name: 'apple' },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -428,14 +443,16 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => {
|
||||
|
||||
it('should not match when company name differs', () => {
|
||||
const context = buildMockCommandMenuContextApi({
|
||||
isShowPage: true,
|
||||
selectedRecord: {
|
||||
id: 'rec-1',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
deletedAt: null,
|
||||
company: { name: 'google' },
|
||||
},
|
||||
pageType: CommandMenuContextApiPageType.RECORD_PAGE,
|
||||
selectedRecords: [
|
||||
{
|
||||
id: 'rec-1',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01',
|
||||
deletedAt: null,
|
||||
company: { name: 'google' },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -448,9 +465,9 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => {
|
||||
});
|
||||
|
||||
describe('target-permissions-front-component', () => {
|
||||
it('should allow when on show page with write permission', () => {
|
||||
it('should allow when on record page with write permission', () => {
|
||||
const context = buildMockCommandMenuContextApi({
|
||||
isShowPage: true,
|
||||
pageType: CommandMenuContextApiPageType.RECORD_PAGE,
|
||||
targetObjectWritePermissions: { person: true },
|
||||
});
|
||||
|
||||
@@ -462,9 +479,9 @@ describe('transformConditionalAvailabilityExpressionsForEsBuildPlugin', () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny when not on show page', () => {
|
||||
it('should deny when not on record page', () => {
|
||||
const context = buildMockCommandMenuContextApi({
|
||||
isShowPage: false,
|
||||
pageType: CommandMenuContextApiPageType.INDEX_PAGE,
|
||||
targetObjectWritePermissions: { person: true },
|
||||
});
|
||||
|
||||
|
||||
+15
-6
@@ -1,7 +1,16 @@
|
||||
const ARRAY_LENGTH_REPLACEMENTS: [RegExp, string][] = [
|
||||
[/\bfavoriteRecordIds\.length\b/g, 'arrayLength(favoriteRecordIds)'],
|
||||
[/\bselectedRecords\.length\b/g, 'arrayLength(selectedRecords)'],
|
||||
];
|
||||
|
||||
export const toExprEval = (raw: string): string =>
|
||||
raw
|
||||
.replace(/!==/g, '!=')
|
||||
.replace(/===/g, '==')
|
||||
.replace(/&&/g, 'and')
|
||||
.replace(/\|\|/g, 'or')
|
||||
.replace(/!(?!=)/g, 'not ');
|
||||
ARRAY_LENGTH_REPLACEMENTS.reduce(
|
||||
(acc, [pattern, replacement]) => acc.replace(pattern, replacement),
|
||||
raw
|
||||
.replace(/!==/g, '!=')
|
||||
.replace(/===/g, '==')
|
||||
.replace(/&&/g, 'and')
|
||||
.replace(/\|\|/g, 'or')
|
||||
.replace(/!(?!=)/g, 'not ')
|
||||
.replace(/,(\s*\))/g, '$1'),
|
||||
);
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { toExprEval } from './to-expr-eval';
|
||||
|
||||
const CONDITIONAL_AVAILABILITY_EXPRESSION_PATTERN =
|
||||
/(conditionalAvailabilityExpression\s*:\s*)(?!['"`])([^,}]+)/g;
|
||||
/(conditionalAvailabilityExpression\s*:\s*)(?!['"`])((?:[^,}()]|\([^()]*\))+)/g;
|
||||
|
||||
export const transformConditionalAvailabilityExpressionsForEsBuildPlugin = (
|
||||
source: string,
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import type * as esbuild from 'esbuild';
|
||||
|
||||
import { FRONT_COMPONENT_EXTERNAL_MODULES } from '../constants/front-component-external-modules';
|
||||
import { getFrontComponentBuildPlugins } from './get-front-component-build-plugins';
|
||||
|
||||
export const getBaseFrontComponentBuildOptions = (): esbuild.BuildOptions => ({
|
||||
bundle: true,
|
||||
splitting: false,
|
||||
format: 'esm',
|
||||
outExtension: { '.js': '.mjs' },
|
||||
external: FRONT_COMPONENT_EXTERNAL_MODULES,
|
||||
jsx: 'automatic',
|
||||
sourcemap: true,
|
||||
metafile: true,
|
||||
logLevel: 'silent',
|
||||
plugins: [...getFrontComponentBuildPlugins()],
|
||||
});
|
||||
Reference in New Issue
Block a user