From 971891ad277175458a01e130c219520d00f88602 Mon Sep 17 00:00:00 2001 From: Tom Ross Date: Thu, 9 Mar 2023 13:26:00 +0000 Subject: [PATCH] Code Intel: Add inference script preview (#48898) --- client/web/src/components/SaveToolbar.tsx | 20 +- .../components/InferenceScriptEditor.tsx | 114 -------- .../inference-form/CommandInput.module.scss | 3 + .../inference-form/CommandInput.tsx | 94 +++++++ .../inference-form/IndexJobLabel.module.scss | 14 + .../inference-form/IndexJobLabel.tsx | 19 ++ .../inference-form/IndexJobNode.module.scss | 92 +++++++ .../inference-form/IndexJobNode.tsx | 248 ++++++++++++++++++ .../inference-form/InferenceForm.tsx | 103 ++++++++ .../auto-index-to-schema.test.ts.snap | 136 ++++++++++ .../inference-form/auto-index-to-form-job.ts | 51 ++++ .../auto-index-to-schema.test.ts | 76 ++++++ .../inference-form/form-data-to-schema.ts | 25 ++ .../components/inference-form/types.ts | 51 ++++ .../InferenceScriptEditor.tsx | 91 +++++++ .../InferenceScriptPreview.module.scss | 15 ++ .../InferenceScriptPreview.tsx | 96 +++++++ .../components/inference-script/backend.ts | 53 ++++ .../configuration/hooks/useInferJobs.tsx | 81 ------ .../CodeIntelInferenceConfigurationPage.tsx | 68 ++++- package.json | 1 + pnpm-lock.yaml | 87 ++---- 22 files changed, 1261 insertions(+), 277 deletions(-) delete mode 100644 client/web/src/enterprise/codeintel/configuration/components/InferenceScriptEditor.tsx create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-form/CommandInput.module.scss create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-form/CommandInput.tsx create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobLabel.module.scss create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobLabel.tsx create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobNode.module.scss create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobNode.tsx create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-form/InferenceForm.tsx create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-form/__snapshots__/auto-index-to-schema.test.ts.snap create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-form/auto-index-to-form-job.ts create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-form/auto-index-to-schema.test.ts create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-form/form-data-to-schema.ts create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-form/types.ts create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-script/InferenceScriptEditor.tsx create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-script/InferenceScriptPreview.module.scss create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-script/InferenceScriptPreview.tsx create mode 100644 client/web/src/enterprise/codeintel/configuration/components/inference-script/backend.ts delete mode 100644 client/web/src/enterprise/codeintel/configuration/hooks/useInferJobs.tsx diff --git a/client/web/src/components/SaveToolbar.tsx b/client/web/src/components/SaveToolbar.tsx index 76d1369f284..e3000874265 100644 --- a/client/web/src/components/SaveToolbar.tsx +++ b/client/web/src/components/SaveToolbar.tsx @@ -12,6 +12,11 @@ export interface SaveToolbarProps { saving?: boolean error?: Error + /** + * Determine if consumer children is added before or after the toolbar actions. + */ + childrenPosition?: 'start' | 'end' + onSave: () => void onDiscard: () => void /** @@ -32,7 +37,17 @@ export type SaveToolbarPropsGenerator = ( export const SaveToolbar: React.FunctionComponent< React.PropsWithChildren> -> = ({ dirty, saving, error, onSave, onDiscard, children, willShowError, saveDiscardDisabled }) => { +> = ({ + dirty, + saving, + error, + onSave, + onDiscard, + children, + childrenPosition = 'end', + willShowError, + saveDiscardDisabled, +}) => { const disabled = saveDiscardDisabled ? saveDiscardDisabled() : saving || !dirty let saveDiscardTitle: string | undefined if (saving) { @@ -54,6 +69,7 @@ export const SaveToolbar: React.FunctionComponent< )}
+ {childrenPosition === 'start' && children} - {children} + {childrenPosition === 'end' && children} {saving && ( Saving... diff --git a/client/web/src/enterprise/codeintel/configuration/components/InferenceScriptEditor.tsx b/client/web/src/enterprise/codeintel/configuration/components/InferenceScriptEditor.tsx deleted file mode 100644 index 5da073dfaed..00000000000 --- a/client/web/src/enterprise/codeintel/configuration/components/InferenceScriptEditor.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { FunctionComponent, useCallback, useMemo, useState } from 'react' - -import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' -import { useIsLightTheme } from '@sourcegraph/shared/src/theme' -import { LoadingSpinner, PageHeader, screenReaderAnnounce, ErrorAlert } from '@sourcegraph/wildcard' - -import { AuthenticatedUser } from '../../../../auth' -import { PageTitle } from '../../../../components/PageTitle' -import { SaveToolbar, SaveToolbarProps, SaveToolbarPropsGenerator } from '../../../../components/SaveToolbar' -import { DynamicallyImportedMonacoSettingsEditor } from '../../../../settings/DynamicallyImportedMonacoSettingsEditor' -import { INFERENCE_SCRIPT, useInferenceScript } from '../hooks/useInferenceScript' -import { useUpdateInferenceScript } from '../hooks/useUpdateInferenceScript' - -import styles from './CodeIntelConfigurationPageHeader.module.scss' - -export interface InferenceScriptEditorProps extends TelemetryProps { - authenticatedUser: AuthenticatedUser | null -} - -export const InferenceScriptEditor: FunctionComponent = ({ - authenticatedUser, - telemetryService, -}) => { - const { inferenceScript, loadingScript, fetchError } = useInferenceScript() - const { updateInferenceScript, isUpdating, updatingError } = useUpdateInferenceScript() - - const save = useCallback( - async (script: string) => - updateInferenceScript({ - variables: { script }, - refetchQueries: [INFERENCE_SCRIPT], - }).then(() => { - screenReaderAnnounce('Saved successfully') - setDirty(false) - }), - [updateInferenceScript] - ) - - const [dirty, setDirty] = useState() - const isLightTheme = useIsLightTheme() - - const customToolbar = useMemo<{ - saveToolbar: FunctionComponent - propsGenerator: SaveToolbarPropsGenerator<{}> - }>( - () => ({ - saveToolbar: SaveToolbar, - propsGenerator: props => { - const mergedProps = { - ...props, - loading: isUpdating, - } - mergedProps.willShowError = () => !mergedProps.saving - mergedProps.saveDiscardDisabled = () => mergedProps.saving || !dirty - - return mergedProps - }, - }), - [dirty, isUpdating] - ) - - const title = ( - <> - -
- Code graph inference script, - }, - ]} - description={`Lua script that emits complete and/or partial auto-indexing - job specifications. `} - className="mb-3" - /> -
- - ) - - if (fetchError) { - return ( - <> - {title} - - - ) - } - - return ( - <> - {title} - {updatingError && } - - {loadingScript ? ( - - ) : ( - - )} - - ) -} diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-form/CommandInput.module.scss b/client/web/src/enterprise/codeintel/configuration/components/inference-form/CommandInput.module.scss new file mode 100644 index 00000000000..7f9b584f58d --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-form/CommandInput.module.scss @@ -0,0 +1,3 @@ +.command-input { + max-height: 20rem; +} diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-form/CommandInput.tsx b/client/web/src/enterprise/codeintel/configuration/components/inference-form/CommandInput.tsx new file mode 100644 index 00000000000..ef2aac298a9 --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-form/CommandInput.tsx @@ -0,0 +1,94 @@ +import React, { useMemo, useRef } from 'react' + +import { defaultKeymap, history } from '@codemirror/commands' +import { StreamLanguage, syntaxHighlighting, HighlightStyle } from '@codemirror/language' +import { shell } from '@codemirror/legacy-modes/mode/shell' +import { EditorState, Extension } from '@codemirror/state' +import { EditorView, keymap } from '@codemirror/view' +import { tags } from '@lezer/highlight' +import classNames from 'classnames' + +import { useCodeMirror, defaultSyntaxHighlighting } from '@sourcegraph/shared/src/components/CodeMirrorEditor' + +import styles from './CommandInput.module.scss' + +const shellHighlighting: Extension = [ + syntaxHighlighting(HighlightStyle.define([{ tag: [tags.keyword], class: 'hljs-keyword' }])), + defaultSyntaxHighlighting, +] + +const staticExtensions: Extension = [ + keymap.of(defaultKeymap), + history(), + EditorView.theme({ + '&': { + flex: 1, + backgroundColor: 'var(--input-bg)', + borderRadius: 'var(--border-radius)', + borderColor: 'var(--border-color)', + marginRight: '0.5rem', + }, + '&.cm-editor.cm-focused': { + outline: 'none', + }, + '.cm-scroller': { + overflowX: 'hidden', + }, + '.cm-content': { + caretColor: 'var(--search-query-text-color)', + fontFamily: 'var(--code-font-family)', + fontSize: 'var(--code-font-size)', + }, + '.cm-content.focus-visible': { + boxShadow: 'none', + }, + '.cm-line': { + padding: '0', + }, + }), + StreamLanguage.define(shell), + shellHighlighting, +] + +interface CommandInputProps { + value: string + onChange?: (value: string) => void + readOnly: boolean + className?: string +} + +export const CommandInput: React.FunctionComponent = React.memo(function CodeMirrorComandInput({ + value, + className, + readOnly, + onChange = () => {}, +}) { + const containerRef = useRef(null) + const editorRef = useRef(null) + + useCodeMirror( + editorRef, + containerRef, + value, + useMemo( + () => [ + staticExtensions, + EditorState.readOnly.of(readOnly), + EditorView.updateListener.of(update => { + if (update.docChanged) { + onChange(update.state.sliceDoc()) + } + }), + ], + [onChange, readOnly] + ) + ) + + return ( +
+ ) +}) diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobLabel.module.scss b/client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobLabel.module.scss new file mode 100644 index 00000000000..74ccbfa65e8 --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobLabel.module.scss @@ -0,0 +1,14 @@ +.job-field { + display: grid; + grid-template-columns: [label] 15% [value] 85%; + + &:not(:last-of-type) { + margin-bottom: 0.5rem; + } +} + +.job-label { + margin-top: 0.25rem; + grid-area: label; + margin-bottom: 0; +} diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobLabel.tsx b/client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobLabel.tsx new file mode 100644 index 00000000000..fd372d313ea --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobLabel.tsx @@ -0,0 +1,19 @@ +import { Label } from '@sourcegraph/wildcard' + +import styles from './IndexJobLabel.module.scss' + +interface IndexJobLabelProps { + label: string +} + +export const IndexJobLabel: React.FunctionComponent> = ({ + label, + children, +}) => ( + <> +
  • + + {children} +
  • + +) diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobNode.module.scss b/client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobNode.module.scss new file mode 100644 index 00000000000..d82d4429174 --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobNode.module.scss @@ -0,0 +1,92 @@ +.job-anchor { + display: block; + position: relative; + top: -4rem; + visibility: hidden; +} + +// IndexJob +.job { + padding: 1rem; + margin-bottom: 1.5rem; +} +.job-content { + padding: 0.25rem; + margin-bottom: 0; + list-style-type: none; +} + +// IndexJobStep +.job-step-container { + padding: 0 1rem; + margin-top: 1rem; + border: 3px solid var(--subtle-bg); +} +.job-step { + padding: 1rem 0; + border-bottom: 3px solid var(--subtle-bg); + + &:last-of-type { + border-bottom: none; + } +} +.job-step-content { + padding: 0; + list-style-type: none; +} + +// Misc +.job-header, +.job-step-header { + font-weight: 600; + display: flex; + justify-content: space-between; + + margin-top: -0.25rem; + margin-left: -0.25rem; + margin-bottom: 1rem; +} + +.job-input { + grid-area: value; + // Ensure consistent height even when no value + min-height: 2rem; + height: auto; + margin-bottom: 0.5rem; + overflow: auto; + + input[readonly] { + // Override disabled bg-color + background-color: var(--input-bg); + } + + &:last-of-type { + margin-bottom: 0; + } +} + +.job-input-action { + grid-area: value; + min-height: 2rem; + margin-bottom: 0.5rem; + max-width: 7rem; + + &:last-of-type { + margin-bottom: 0; + } +} + +.job-command-container { + grid-area: value; + padding: 1rem; + border: 3px solid var(--subtle-bg); +} + +.job-command { + display: flex; + margin-bottom: 0.5rem; + + &:last-child { + margin-bottom: 0; + } +} diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobNode.tsx b/client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobNode.tsx new file mode 100644 index 00000000000..b1b3e53c9a7 --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-form/IndexJobNode.tsx @@ -0,0 +1,248 @@ +import React from 'react' + +import { mdiClose, mdiPlus } from '@mdi/js' +import { uniqueId } from 'lodash' + +import { Button, Container, H3, H4, Icon, Input } from '@sourcegraph/wildcard' + +import { CommandInput } from './CommandInput' +import { IndexJobLabel } from './IndexJobLabel' +import { InferenceArrayValue, InferenceFormJob, InferenceFormJobStep } from './types' + +import styles from './IndexJobNode.module.scss' + +interface IndexJobNodeProps { + job: InferenceFormJob + jobNumber: number + readOnly: boolean + onChange: (name: keyof InferenceFormJob, value: unknown) => void + onRemove: () => void +} + +export const IndexJobNode: React.FunctionComponent = ({ + job, + jobNumber, + readOnly, + onChange, + onRemove, +}) => { + const comparisonKey = job.meta.id + + return ( + +
    +

    Job #{jobNumber}

    + {!readOnly && ( + + )} +
    +
      + + onChange('root', event.target.value)} + readOnly={readOnly} + className={styles.jobInput} + /> + + + onChange('indexer', value)} + readOnly={readOnly} + className={styles.jobInput} + /> + + + + + + + + + + + + onChange('outfile', event.target.value)} + readOnly={readOnly} + className={styles.jobInput} + /> + + {job.steps.length > 0 && ( + + {job.steps.map((step, index) => ( +
      +
      +

      Step #{index + 1}

      + {!readOnly && ( + + )} +
      + { + const steps = [...job.steps] + steps[index] = { ...steps[index], [name]: value } + onChange('steps', steps) + }} + /> +
      + ))} +
      + )} + {!readOnly && ( + + )} +
    +
    + ) +} + +interface IndexStepNodeProps { + step: InferenceFormJobStep + readOnly: boolean + onChange: (name: keyof InferenceFormJobStep, value: unknown) => void +} + +const IndexStepNode: React.FunctionComponent = ({ step, readOnly, onChange }) => ( +
      + + onChange('root', event.target.value)} + readOnly={readOnly} + className={styles.jobInput} + /> + + + onChange('image', value)} + readOnly={readOnly} + className={styles.jobInput} + /> + + + + commands={step.commands} + name="commands" + addLabel="command" + readOnly={readOnly} + onChange={onChange} + /> + +
    +) + +interface IndexCommandNodeProps { + name: formKey + addLabel: string + commands: InferenceArrayValue[] + onChange: (name: formKey, value: unknown) => void + readOnly: boolean +} + +const IndexCommandNode = ({ + name, + addLabel, + commands, + onChange, + readOnly, +}: IndexCommandNodeProps): JSX.Element | null => ( +
    + {commands.map((command, index) => ( +
    + { + const prevCommands = [...commands] + prevCommands[index].value = value + onChange(name, prevCommands) + }} + readOnly={readOnly} + className={styles.jobInput} + /> + {!readOnly && ( + + )} +
    + ))} + {!readOnly && ( + + )} +
    +) diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-form/InferenceForm.tsx b/client/web/src/enterprise/codeintel/configuration/components/inference-form/InferenceForm.tsx new file mode 100644 index 00000000000..a69a3a2324b --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-form/InferenceForm.tsx @@ -0,0 +1,103 @@ +import React, { useCallback, useState } from 'react' + +import AJV from 'ajv' +import addFormats from 'ajv-formats' + +import { Button, Form } from '@sourcegraph/wildcard' + +import { AutoIndexJobDescriptionFields } from '../../../../../graphql-operations' +import schema from '../../schema.json' + +import { autoIndexJobsToFormData } from './auto-index-to-form-job' +import { formDataToSchema } from './form-data-to-schema' +import { IndexJobNode } from './IndexJobNode' +import { InferenceFormData, InferenceFormJob, SchemaCompatibleInferenceFormData } from './types' + +const ajv = new AJV({ strict: false }) +addFormats(ajv) + +interface InferenceFormProps { + readOnly: boolean + jobs: AutoIndexJobDescriptionFields[] + onSubmit?: (data: SchemaCompatibleInferenceFormData) => void +} + +export const InferenceForm: React.FunctionComponent = ({ jobs, readOnly, onSubmit }) => { + const [formData, setFormData] = useState(autoIndexJobsToFormData(jobs)) + + const handleSubmit = useCallback( + (event: React.FormEvent) => { + event.preventDefault() + + if (!onSubmit) { + return + } + + const schemaCompatibleFormData = formDataToSchema(formData) + + // Validate form data against JSONSchema + const isValid = ajv.validate(schema, schemaCompatibleFormData) + + if (isValid) { + onSubmit(schemaCompatibleFormData) + } + }, + [formData, onSubmit] + ) + + const getChangeHandler = useCallback( + (id: string) => (name: keyof InferenceFormJob, value: unknown) => { + setFormData(previous => { + const index = previous.index_jobs.findIndex(job => job.meta.id === id) + const job = previous.index_jobs[index] + + return { + index_jobs: [ + ...previous.index_jobs.slice(0, index), + { + ...job, + [name]: value, + }, + ...previous.index_jobs.slice(index + 1), + ], + } + }) + }, + [] + ) + + const getRemoveHandler = useCallback( + (id: string) => () => { + if (!window.confirm('Are you sure you want to remove this entire job?')) { + return + } + + setFormData(previous => ({ + index_jobs: previous.index_jobs.filter(job => job.meta.id !== id), + })) + }, + [] + ) + + return ( +
    + <> + {formData.index_jobs.map((job, index) => ( + + ))} + + {!readOnly && ( + + )} + + ) +} diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-form/__snapshots__/auto-index-to-schema.test.ts.snap b/client/web/src/enterprise/codeintel/configuration/components/inference-form/__snapshots__/auto-index-to-schema.test.ts.snap new file mode 100644 index 00000000000..905892c819e --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-form/__snapshots__/auto-index-to-schema.test.ts.snap @@ -0,0 +1,136 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`autoIndexToAutoIndexSchema should build form data as expected 1`] = ` +Object { + "index_jobs": Array [ + Object { + "indexer": "indexer-1@123", + "indexer_args": Array [ + Object { + "meta": Object { + "id": "test-id", + }, + "value": "arg-1", + }, + Object { + "meta": Object { + "id": "test-id", + }, + "value": "arg-2", + }, + ], + "local_steps": Array [ + Object { + "meta": Object { + "id": "test-id", + }, + "value": "command-1", + }, + Object { + "meta": Object { + "id": "test-id", + }, + "value": "command-2", + }, + ], + "meta": Object { + "id": "key-1", + }, + "outfile": "outfile-1", + "requestedEnvVars": Array [ + Object { + "meta": Object { + "id": "test-id", + }, + "value": "ENV_VAR", + }, + Object { + "meta": Object { + "id": "test-id", + }, + "value": "ENV_VAR_2", + }, + ], + "root": "root-1", + "steps": Array [ + Object { + "commands": Array [ + Object { + "meta": Object { + "id": "test-id", + }, + "value": "pre-command-1", + }, + Object { + "meta": Object { + "id": "test-id", + }, + "value": "pre-command-2", + }, + ], + "image": "indexer-1@123", + "meta": Object { + "id": "test-id", + }, + "root": "pre-root-1", + }, + ], + }, + Object { + "indexer": "", + "indexer_args": Array [], + "local_steps": Array [], + "meta": Object { + "id": "key-2", + }, + "outfile": "", + "requestedEnvVars": Array [], + "root": "", + "steps": Array [], + }, + ], +} +`; + +exports[`autoIndexToAutoIndexSchema should build schema data as expected 1`] = ` +Object { + "index_jobs": Array [ + Object { + "indexer": "indexer-1@123", + "indexer_args": Array [ + "arg-1", + "arg-2", + ], + "local_steps": Array [ + "command-1", + "command-2", + ], + "outfile": "outfile-1", + "requestedEnvVars": Array [ + "ENV_VAR", + "ENV_VAR_2", + ], + "root": "root-1", + "steps": Array [ + Object { + "commands": Array [ + "pre-command-1", + "pre-command-2", + ], + "image": "indexer-1@123", + "root": "pre-root-1", + }, + ], + }, + Object { + "indexer": "", + "indexer_args": Array [], + "local_steps": Array [], + "outfile": "", + "requestedEnvVars": Array [], + "root": "", + "steps": Array [], + }, + ], +} +`; diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-form/auto-index-to-form-job.ts b/client/web/src/enterprise/codeintel/configuration/components/inference-form/auto-index-to-form-job.ts new file mode 100644 index 00000000000..28fe79161c6 --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-form/auto-index-to-form-job.ts @@ -0,0 +1,51 @@ +import { uniqueId } from 'lodash' + +import { AutoIndexJobDescriptionFields, AutoIndexLsifPreIndexFields } from '../../../../../graphql-operations' + +import { InferenceFormData, InferenceFormJobStep, InferenceFormJob } from './types' + +const autoIndexStepToFormStep = (step: AutoIndexLsifPreIndexFields): InferenceFormJobStep => ({ + root: step.root, + image: step.image ?? '', + commands: step.commands.map(arg => ({ + value: arg, + meta: { + id: uniqueId(), + }, + })), + meta: { + id: uniqueId(), + }, +}) + +const autoIndexJobToFormJob = (job: AutoIndexJobDescriptionFields): InferenceFormJob => ({ + root: job.root, + indexer: job.indexer?.imageName ?? '', + indexer_args: job.steps.index.indexerArgs.map(arg => ({ + value: arg, + meta: { + id: uniqueId(), + }, + })), + requestedEnvVars: (job.steps.index.requestedEnvVars ?? []).map(envVar => ({ + value: envVar, + meta: { + id: uniqueId(), + }, + })), + local_steps: job.steps.index.commands.map(command => ({ + value: command, + meta: { + id: uniqueId(), + }, + })), + outfile: job.steps.index.outfile ?? '', + steps: job.steps.preIndex.map(autoIndexStepToFormStep), + meta: { + id: job.comparisonKey, + }, +}) + +export const autoIndexJobsToFormData = (jobs: AutoIndexJobDescriptionFields[]): InferenceFormData => ({ + index_jobs: jobs.map(autoIndexJobToFormJob), +}) diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-form/auto-index-to-schema.test.ts b/client/web/src/enterprise/codeintel/configuration/components/inference-form/auto-index-to-schema.test.ts new file mode 100644 index 00000000000..1acdde5e19e --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-form/auto-index-to-schema.test.ts @@ -0,0 +1,76 @@ +import AJV from 'ajv' +import addFormats from 'ajv-formats' + +import { AutoIndexJobDescriptionFields } from '../../../../../graphql-operations' +import schema from '../../schema.json' + +import { autoIndexJobsToFormData } from './auto-index-to-form-job' +import { formDataToSchema } from './form-data-to-schema' + +const ajv = new AJV({ strict: false }) +addFormats(ajv) + +const mockAutoIndexJobs: AutoIndexJobDescriptionFields[] = [ + { + comparisonKey: 'key-1', + indexer: { + imageName: 'indexer-1@123', + name: 'indexer-1', + url: 'https://example.com', + key: 'key-indexer-1', + }, + root: 'root-1', + steps: { + index: { + commands: ['command-1', 'command-2'], + indexerArgs: ['arg-1', 'arg-2'], + requestedEnvVars: ['ENV_VAR', 'ENV_VAR_2'], + outfile: 'outfile-1', + }, + preIndex: [ + { + commands: ['pre-command-1', 'pre-command-2'], + root: 'pre-root-1', + image: 'indexer-1@123', + }, + ], + }, + }, + { + comparisonKey: 'key-2', + indexer: null, + root: '', + steps: { + index: { + commands: [], + indexerArgs: [], + requestedEnvVars: null, + outfile: null, + }, + preIndex: [], + }, + }, +] + +describe('autoIndexToAutoIndexSchema', () => { + it('should build form data as expected', () => { + const formData = autoIndexJobsToFormData(mockAutoIndexJobs) + expect(formData).toMatchSnapshot() + }) + + it('should build schema data as expected', () => { + const formData = autoIndexJobsToFormData(mockAutoIndexJobs) + const schemaData = formDataToSchema(formData) + expect(schemaData).toMatchSnapshot() + }) + + it('should build valid schema data', () => { + const formData = autoIndexJobsToFormData(mockAutoIndexJobs) + const schemaData = formDataToSchema(formData) + + // Validate form data against JSONSchema + const isValid = ajv.validate(schema, schemaData) + + expect(isValid).toBe(true) + }) +}) diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-form/form-data-to-schema.ts b/client/web/src/enterprise/codeintel/configuration/components/inference-form/form-data-to-schema.ts new file mode 100644 index 00000000000..49c34d6a4eb --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-form/form-data-to-schema.ts @@ -0,0 +1,25 @@ +import { InferenceFormData, SchemaCompatibleInferenceFormData } from './types' + +export const formDataToSchema = (formData: InferenceFormData): SchemaCompatibleInferenceFormData => { + // Remove all meta information from the form data + const cleanJobs = formData.index_jobs.map(job => { + const { meta, ...cleanJob } = job + return { + ...cleanJob, + steps: job.steps.map(step => { + const { meta, ...cleanStep } = step + return { + ...cleanStep, + commands: step.commands.map(command => command.value), + } + }), + indexer_args: job.indexer_args.map(arg => arg.value), + requestedEnvVars: job.requestedEnvVars.map(envVar => envVar.value), + local_steps: job.local_steps.map(step => step.value), + } + }) + + return { + index_jobs: cleanJobs, + } +} diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-form/types.ts b/client/web/src/enterprise/codeintel/configuration/components/inference-form/types.ts new file mode 100644 index 00000000000..5d5af4daa48 --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-form/types.ts @@ -0,0 +1,51 @@ +interface SchemaCompatibleInferenceFormStep { + root: string + image: string + commands: string[] +} + +interface SchemaCompatibleInferenceFormJob { + root: string + indexer: string + indexer_args: string[] + requestedEnvVars: string[] + local_steps: string[] + outfile: string + steps: SchemaCompatibleInferenceFormStep[] +} + +export interface SchemaCompatibleInferenceFormData { + index_jobs: SchemaCompatibleInferenceFormJob[] +} + +/** + * Form data with additional metadata that is unrelated to submission + */ +interface MetaIdentifier { + meta: { + id: string + } +} + +// InferenceJobs only return a unique ID for the actual job, not values within the job. +// As we want to build a dynamic form, we need each array of values to have a unique id. +export interface InferenceArrayValue extends MetaIdentifier { + value: string +} + +export interface InferenceFormJobStep extends Omit, MetaIdentifier { + commands: InferenceArrayValue[] +} + +export interface InferenceFormJob + extends Omit, + MetaIdentifier { + indexer_args: InferenceArrayValue[] + requestedEnvVars: InferenceArrayValue[] + local_steps: InferenceArrayValue[] + steps: InferenceFormJobStep[] +} + +export interface InferenceFormData { + index_jobs: InferenceFormJob[] +} diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-script/InferenceScriptEditor.tsx b/client/web/src/enterprise/codeintel/configuration/components/inference-script/InferenceScriptEditor.tsx new file mode 100644 index 00000000000..6c3b2505a68 --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-script/InferenceScriptEditor.tsx @@ -0,0 +1,91 @@ +import { FunctionComponent, useCallback, useMemo, useState } from 'react' + +import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' +import { useIsLightTheme } from '@sourcegraph/shared/src/theme' +import { screenReaderAnnounce, ErrorAlert, Button } from '@sourcegraph/wildcard' + +import { AuthenticatedUser } from '../../../../../auth' +import { SaveToolbar, SaveToolbarProps, SaveToolbarPropsGenerator } from '../../../../../components/SaveToolbar' +import { DynamicallyImportedMonacoSettingsEditor } from '../../../../../settings/DynamicallyImportedMonacoSettingsEditor' +import { INFERENCE_SCRIPT } from '../../hooks/useInferenceScript' +import { useUpdateInferenceScript } from '../../hooks/useUpdateInferenceScript' + +export interface InferenceScriptEditorProps extends TelemetryProps { + script: string + authenticatedUser: AuthenticatedUser | null + setPreviewScript: (script: string) => void + previewDisabled: boolean + setTab: (index: number) => void +} + +export const InferenceScriptEditor: FunctionComponent = ({ + script: inferenceScript, + setPreviewScript, + previewDisabled, + setTab, + authenticatedUser, + telemetryService, +}) => { + const { updateInferenceScript, isUpdating, updatingError } = useUpdateInferenceScript() + + const save = useCallback( + async (script: string) => + updateInferenceScript({ + variables: { script }, + refetchQueries: [INFERENCE_SCRIPT], + }).then(() => { + screenReaderAnnounce('Saved successfully') + setDirty(false) + }), + [updateInferenceScript] + ) + + const [dirty, setDirty] = useState() + const isLightTheme = useIsLightTheme() + + const customToolbar = useMemo<{ + saveToolbar: FunctionComponent + propsGenerator: SaveToolbarPropsGenerator<{}> + }>( + () => ({ + saveToolbar: props => ( + + + + ), + propsGenerator: props => { + const mergedProps = { + ...props, + loading: isUpdating, + } + mergedProps.willShowError = () => !mergedProps.saving + mergedProps.saveDiscardDisabled = () => mergedProps.saving || !dirty + + return mergedProps + }, + }), + [dirty, isUpdating, previewDisabled, setTab] + ) + + return ( + <> + {updatingError && } + + + ) +} diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-script/InferenceScriptPreview.module.scss b/client/web/src/enterprise/codeintel/configuration/components/inference-script/InferenceScriptPreview.module.scss new file mode 100644 index 00000000000..f6bc52b7823 --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-script/InferenceScriptPreview.module.scss @@ -0,0 +1,15 @@ +.container { + padding: 1rem; +} + +.action-container { + height: 100%; + width: 100%; + margin-bottom: 1rem; +} + +.action-input { + margin-right: 0.5rem; + width: 25rem; + flex: 1; +} diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-script/InferenceScriptPreview.tsx b/client/web/src/enterprise/codeintel/configuration/components/inference-script/InferenceScriptPreview.tsx new file mode 100644 index 00000000000..3fe8dfa899f --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-script/InferenceScriptPreview.tsx @@ -0,0 +1,96 @@ +import React, { useEffect } from 'react' + +import { useLazyQuery } from '@sourcegraph/http-client' +import { + LoadingSpinner, + ErrorAlert, + Input, + Button, + getDefaultInputProps, + useField, + useForm, + Form, + Label, +} from '@sourcegraph/wildcard' + +import { + GetRepoIdResult, + GetRepoIdVariables, + InferAutoIndexJobsForRepoResult, + InferAutoIndexJobsForRepoVariables, +} from '../../../../../graphql-operations' +import { RepositoryField } from '../../../../insights/components' +import { InferenceForm } from '../inference-form/InferenceForm' + +import { GET_REPO_ID, INFER_JOBS_SCRIPT } from './backend' + +import styles from './InferenceScriptPreview.module.scss' + +interface InferenceScriptPreviewFormValues { + repository: string +} + +interface InferenceScriptPreviewProps { + active: boolean + script: string + setTab: (index: number) => void +} + +export const InferenceScriptPreview: React.FunctionComponent = ({ active, script }) => { + const [getRepoId, repoData] = useLazyQuery(GET_REPO_ID, {}) + const [inferJobs, { data, loading, error }] = useLazyQuery< + InferAutoIndexJobsForRepoResult, + InferAutoIndexJobsForRepoVariables + >(INFER_JOBS_SCRIPT, {}) + + const form = useForm({ + initialValues: { repository: '' }, + onSubmit: async ({ repository }) => getRepoId({ variables: { name: repository } }), + }) + + useEffect(() => { + const id = repoData?.data?.repository?.id + + if (active && id) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + inferJobs({ variables: { repository: id, script, rev: null }, fetchPolicy: 'cache-first' }) + } + }, [active, inferJobs, repoData, script]) + + const repository = useField({ + name: 'repository', + formApi: form.formAPI, + }) + + return ( +
    +
    + +
    + + + +
    +
    + {loading ? ( + + ) : error ? ( + + ) : data ? ( + + ) : ( + <> + )} +
    + ) +} diff --git a/client/web/src/enterprise/codeintel/configuration/components/inference-script/backend.ts b/client/web/src/enterprise/codeintel/configuration/components/inference-script/backend.ts new file mode 100644 index 00000000000..1a8d02afd6a --- /dev/null +++ b/client/web/src/enterprise/codeintel/configuration/components/inference-script/backend.ts @@ -0,0 +1,53 @@ +import { gql } from '@sourcegraph/http-client' + +export const GET_REPO_ID = gql` + query GetRepoId($name: String!) { + repository(name: $name) { + id + } + } +` + +export const INFER_JOBS_SCRIPT = gql` + query InferAutoIndexJobsForRepo($repository: ID!, $rev: String, $script: String) { + inferAutoIndexJobsForRepo(repository: $repository, rev: $rev, script: $script) { + ...AutoIndexJobDescriptionFields + } + } + + fragment AutoIndexJobDescriptionFields on AutoIndexJobDescription { + comparisonKey + root + indexer { + key + imageName + name + url + } + steps { + ...AutoIndexLsifIndexStepsFields + } + } + + fragment AutoIndexLsifIndexStepsFields on IndexSteps { + preIndex { + ...AutoIndexLsifPreIndexFields + } + index { + ...AutoIndexLsifIndexFields + } + } + + fragment AutoIndexLsifPreIndexFields on PreIndexStep { + root + image + commands + } + + fragment AutoIndexLsifIndexFields on IndexStep { + indexerArgs + outfile + commands + requestedEnvVars + } +` diff --git a/client/web/src/enterprise/codeintel/configuration/hooks/useInferJobs.tsx b/client/web/src/enterprise/codeintel/configuration/hooks/useInferJobs.tsx deleted file mode 100644 index b4cce28105e..00000000000 --- a/client/web/src/enterprise/codeintel/configuration/hooks/useInferJobs.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { ApolloError, gql, useQuery } from '@apollo/client' - -import { InferAutoIndexJobsForRepoVariables, InferAutoIndexJobsForRepoResult } from '../../../../graphql-operations' - -interface InferJobsScriptResult { - data?: InferAutoIndexJobsForRepoResult - loading: boolean - error: ApolloError | undefined -} - -export const INFER_JOBS_SCRIPT = gql` - query InferAutoIndexJobsForRepo($repository: ID!, $rev: String, $script: String) { - inferAutoIndexJobsForRepo(repository: $repository, rev: $rev, script: $script) { - ...AutoIndexJobDescriptionFields - } - } - - fragment AutoIndexJobDescriptionFields on AutoIndexJobDescription { - root - indexer { - name - url - } - steps { - ...LsifIndexStepsFields - } - } - - fragment LsifIndexStepsFields on IndexSteps { - setup { - ...ExecutionLogEntryFields - } - preIndex { - root - image - commands - logEntry { - ...ExecutionLogEntryFields - } - } - index { - indexerArgs - outfile - logEntry { - ...ExecutionLogEntryFields - } - } - upload { - ...ExecutionLogEntryFields - } - teardown { - ...ExecutionLogEntryFields - } - } - - fragment ExecutionLogEntryFields on ExecutionLogEntry { - key - command - startTime - exitCode - out - durationMilliseconds - } -` - -export const useInferJobs = ({ - variables, -}: { - variables: InferAutoIndexJobsForRepoVariables -}): InferJobsScriptResult => { - const { data, loading, error } = useQuery(INFER_JOBS_SCRIPT, { - variables, - nextFetchPolicy: 'cache-first', - }) - - return { - data, - loading, - error, - } -} diff --git a/client/web/src/enterprise/codeintel/configuration/pages/CodeIntelInferenceConfigurationPage.tsx b/client/web/src/enterprise/codeintel/configuration/pages/CodeIntelInferenceConfigurationPage.tsx index 997eebf6ab5..560716ef661 100644 --- a/client/web/src/enterprise/codeintel/configuration/pages/CodeIntelInferenceConfigurationPage.tsx +++ b/client/web/src/enterprise/codeintel/configuration/pages/CodeIntelInferenceConfigurationPage.tsx @@ -1,10 +1,13 @@ -import { FunctionComponent } from 'react' +import { FunctionComponent, useState, useCallback } from 'react' import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' +import { ErrorAlert, LoadingSpinner, PageHeader, Tab, TabList, TabPanel, TabPanels, Tabs } from '@sourcegraph/wildcard' import { AuthenticatedUser } from '../../../../auth' import { PageTitle } from '../../../../components/PageTitle' -import { InferenceScriptEditor } from '../components/InferenceScriptEditor' +import { InferenceScriptEditor } from '../components/inference-script/InferenceScriptEditor' +import { InferenceScriptPreview } from '../components/inference-script/InferenceScriptPreview' +import { useInferenceScript } from '../hooks/useInferenceScript' export interface CodeIntelInferenceConfigurationPageProps extends TelemetryProps { authenticatedUser: AuthenticatedUser | null @@ -13,10 +16,59 @@ export interface CodeIntelInferenceConfigurationPageProps extends TelemetryProps export const CodeIntelInferenceConfigurationPage: FunctionComponent = ({ authenticatedUser, ...props -}) => ( - <> - +}) => { + const [activeTabIndex, setActiveTabIndex] = useState(0) + const { inferenceScript, loadingScript, fetchError } = useInferenceScript() + const [previewScript, setPreviewScript] = useState(null) + const setTab = useCallback((index: number) => { + setActiveTabIndex(index) + }, []) - - -) + const inferencePreview = previewScript !== null ? previewScript : inferenceScript + const previewDisabled = inferencePreview === '' + + return ( + <> + + Code graph inference script, + }, + ]} + description="Lua script that emits complete and/or partial auto-indexing job specifications." + className="mb-3" + /> + {fetchError && } + {loadingScript && } + + + Script + + Preview + + + + + + + + + + + + + ) +} diff --git a/package.json b/package.json index 3dc87f293db..a86cddd9a98 100644 --- a/package.json +++ b/package.json @@ -338,6 +338,7 @@ "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-markdown": "^6.0.0", "@codemirror/language": "^6.2.0", + "@codemirror/legacy-modes": "^6.3.1", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.1", "@codemirror/state": "^6.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2eaad238ba2..402eaa09d51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,7 @@ importers: '@codemirror/lang-json': ^6.0.0 '@codemirror/lang-markdown': ^6.0.0 '@codemirror/language': ^6.2.0 + '@codemirror/legacy-modes': ^6.3.1 '@codemirror/lint': ^6.0.0 '@codemirror/search': ^6.0.1 '@codemirror/state': ^6.2.0 @@ -402,6 +403,7 @@ importers: '@codemirror/lang-json': 6.0.0 '@codemirror/lang-markdown': 6.0.0 '@codemirror/language': 6.2.0 + '@codemirror/legacy-modes': 6.3.1 '@codemirror/lint': 6.0.0 '@codemirror/search': 6.0.1 '@codemirror/state': 6.2.0 @@ -3916,6 +3918,12 @@ packages: style-mod: 4.0.0 dev: false + /@codemirror/legacy-modes/6.3.1: + resolution: {integrity: sha512-icXmCs4Mhst2F8mE0TNpmG6l7YTj1uxam3AbZaFaabINH5oWAdg2CfR/PVi+d/rqxJ+TuTnvkKK5GILHrNThtw==} + dependencies: + '@codemirror/language': 6.2.0 + dev: false + /@codemirror/lint/6.0.0: resolution: {integrity: sha512-nUUXcJW1Xp54kNs+a1ToPLK8MadO0rMTnJB8Zk4Z8gBdrN0kqV7uvUraU/T2yqg+grDNR38Vmy/MrhQN/RgwiA==} dependencies: @@ -13040,7 +13048,7 @@ packages: '@babel/core': 7.20.5 find-cache-dir: 3.3.2 schema-utils: 4.0.0 - webpack: 5.75.0_4wpfvhs5obqvjbkpkxtqpnn5oe + webpack: 5.75.0_cf7cgeqdkm72g3fdehkr7aaod4 dev: true /babel-plugin-add-react-displayname/0.0.5: @@ -15295,7 +15303,7 @@ packages: postcss-modules-values: 4.0.0_postcss@8.4.21 postcss-value-parser: 4.2.0 semver: 7.3.8 - webpack: 5.75.0_esbuild@0.17.8 + webpack: 5.75.0_cf7cgeqdkm72g3fdehkr7aaod4 /css-minimizer-webpack-plugin/4.2.2_6hyl5w2uqyeivowpusuiulbmoy: resolution: {integrity: sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==} @@ -20113,7 +20121,7 @@ packages: lodash: 4.17.21 pretty-error: 4.0.0 tapable: 2.2.1 - webpack: 5.75.0_yrajokeiryagdtuqucziuwdxti + webpack: 5.75.0_cf7cgeqdkm72g3fdehkr7aaod4 dev: true /htmlparser2/6.1.0: @@ -26920,7 +26928,7 @@ packages: klona: 2.0.5 postcss: 8.4.21 semver: 7.3.8 - webpack: 5.75.0_esbuild@0.17.8 + webpack: 5.75.0_cf7cgeqdkm72g3fdehkr7aaod4 /postcss-media-query-parser/0.2.3: resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} @@ -29778,7 +29786,7 @@ packages: klona: 2.0.5 neo-async: 2.6.2 sass: 1.32.4 - webpack: 5.75.0_esbuild@0.17.8 + webpack: 5.75.0_cf7cgeqdkm72g3fdehkr7aaod4 /sass/1.32.4: resolution: {integrity: sha512-N0BT0PI/t3+gD8jKa83zJJUb7ssfQnRRfqN+GIErokW6U4guBpfYl8qYB+OFLEho+QvnV5ZH1R9qhUC/Z2Ch9w==} @@ -31677,7 +31685,7 @@ packages: schema-utils: 3.1.1 serialize-javascript: 6.0.0 terser: 5.16.1 - webpack: 5.75.0_esbuild@0.17.8 + webpack: 5.75.0_cf7cgeqdkm72g3fdehkr7aaod4 /terser-webpack-plugin/5.3.6_oa2ac2s5skpozptxi7rtd3zsrm: resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} @@ -31705,32 +31713,6 @@ packages: webpack: 5.75.0_yrajokeiryagdtuqucziuwdxti dev: true - /terser-webpack-plugin/5.3.6_tqhwwsvj4cnhuarwr7flkmuvna: - resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@swc/core': '*' - esbuild: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@swc/core': - optional: true - esbuild: - optional: true - uglify-js: - optional: true - dependencies: - '@jridgewell/trace-mapping': 0.3.17 - '@swc/core': 1.3.28 - esbuild: 0.17.8 - jest-worker: 27.5.1 - schema-utils: 3.1.1 - serialize-javascript: 6.0.0 - terser: 5.16.1 - webpack: 5.75.0_4wpfvhs5obqvjbkpkxtqpnn5oe - dev: true - /terser/4.8.1: resolution: {integrity: sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==} engines: {node: '>=6.0.0'} @@ -33759,46 +33741,6 @@ packages: resolution: {integrity: sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==} dev: true - /webpack/5.75.0_4wpfvhs5obqvjbkpkxtqpnn5oe: - resolution: {integrity: sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - dependencies: - '@types/eslint-scope': 3.7.3 - '@types/estree': 0.0.51 - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/wasm-edit': 1.11.1 - '@webassemblyjs/wasm-parser': 1.11.1 - acorn: 8.8.1 - acorn-import-assertions: 1.8.0_acorn@8.8.1 - browserslist: 4.21.4 - chrome-trace-event: 1.0.2 - enhanced-resolve: 5.10.0 - es-module-lexer: 0.9.3 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.10 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.2.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.1.1 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.6_tqhwwsvj4cnhuarwr7flkmuvna - watchpack: 2.4.0 - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - dev: true - /webpack/5.75.0_cf7cgeqdkm72g3fdehkr7aaod4: resolution: {integrity: sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==} engines: {node: '>=10.13.0'} @@ -33877,6 +33819,7 @@ packages: - '@swc/core' - esbuild - uglify-js + dev: false /webpack/5.75.0_yrajokeiryagdtuqucziuwdxti: resolution: {integrity: sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==}