mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-26 00:01:37 +00:00
fix: improve code structure to reduce unecessary dependency of apppreview on heavy packages
This commit is contained in:
@@ -7,10 +7,6 @@
|
||||
import { ClipboardCopy, Download, Expand } from 'lucide-svelte'
|
||||
import Portal from 'svelte-portal'
|
||||
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
|
||||
import ScriptFix from './codeGen/ScriptFix.svelte'
|
||||
import type { Preview } from '$lib/gen'
|
||||
import type Editor from './Editor.svelte'
|
||||
import type DiffEditor from './DiffEditor.svelte'
|
||||
|
||||
export let result: any
|
||||
export let requireHtmlApproval = false
|
||||
@@ -18,9 +14,6 @@
|
||||
export let disableExpand = false
|
||||
export let jobId: string | undefined = undefined
|
||||
export let workspaceId: string | undefined = undefined
|
||||
export let editor: Editor | undefined = undefined
|
||||
export let diffEditor: DiffEditor | undefined = undefined
|
||||
export let lang: Preview.language | undefined = undefined
|
||||
|
||||
let resultKind:
|
||||
| 'json'
|
||||
@@ -230,9 +223,7 @@
|
||||
.message}{:else}{JSON.stringify(result.error, null, 4)}{/if}</span
|
||||
>
|
||||
<pre class="text-sm whitespace-pre-wrap text-primary">{result.error.stack ?? ''}</pre>
|
||||
{#if lang && editor && diffEditor}
|
||||
<ScriptFix error={JSON.stringify(result.error)} {lang} {editor} {diffEditor} />
|
||||
{/if}
|
||||
<slot />
|
||||
</div>
|
||||
{:else if !forceJson && resultKind == 'approval'}<div class="flex flex-col gap-3 mt-8 mx-4">
|
||||
<Button
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
|
||||
import typescript from 'svelte-highlight/languages/typescript'
|
||||
import { cleanExpr } from './flows/utils'
|
||||
import FlowPathViewer from './flows/content/FlowPathViewer.svelte'
|
||||
import SchemaViewer from './SchemaViewer.svelte'
|
||||
import { scriptPathToHref } from '$lib/scripts'
|
||||
import { cleanExpr } from '$lib/utils'
|
||||
export let flow: {
|
||||
summary: string
|
||||
description?: string
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import DynamicInputHelpBox from './flows/content/DynamicInputHelpBox.svelte'
|
||||
import type { PropPickerWrapperContext } from './flows/propPicker/PropPickerWrapper.svelte'
|
||||
import { codeToStaticTemplate, getDefaultExpr, isCodeInjection } from './flows/utils'
|
||||
import { codeToStaticTemplate, getDefaultExpr } from './flows/utils'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import { Button } from './common'
|
||||
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
@@ -17,7 +17,7 @@
|
||||
import type ItemPicker from './ItemPicker.svelte'
|
||||
import { ResourceService, type InputTransform } from '$lib/gen'
|
||||
import TemplateEditor from './TemplateEditor.svelte'
|
||||
import { setInputCat as computeInputCat } from '$lib/utils'
|
||||
import { setInputCat as computeInputCat, isCodeInjection } from '$lib/utils'
|
||||
import { Code, Plug } from 'lucide-svelte'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { InputTransform } from '$lib/gen'
|
||||
import { cleanExpr } from '$lib/utils'
|
||||
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
|
||||
import { cleanExpr } from './flows/utils'
|
||||
|
||||
export let inputTransforms: Record<string, InputTransform>
|
||||
$: entries = Object.entries(inputTransforms)
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import ObjectResourceInput from './ObjectResourceInput.svelte'
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
import type { SchemaProperty } from '$lib/common'
|
||||
import autosize from 'svelte-autosize'
|
||||
import Toggle from './Toggle.svelte'
|
||||
@@ -17,6 +15,8 @@
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { X } from 'lucide-svelte'
|
||||
import LightweightResourcePicker from './LightweightResourcePicker.svelte'
|
||||
import LightweightObjectResourceInput from './LightweightObjectResourceInput.svelte'
|
||||
|
||||
export let css: ComponentCustomCSS<'schemaformcomponent'> | undefined = undefined
|
||||
export let label: string = ''
|
||||
@@ -274,7 +274,7 @@
|
||||
</span>
|
||||
</div>
|
||||
{:else if inputCat == 'resource-object'}
|
||||
<ObjectResourceInput {format} bind:value />
|
||||
<LightweightObjectResourceInput {format} bind:value />
|
||||
{:else if inputCat == 'object'}
|
||||
{#if properties && Object.keys(properties).length > 0}
|
||||
<div class="p-4 pl-8 border rounded w-full">
|
||||
@@ -320,12 +320,14 @@
|
||||
multiple={false}
|
||||
/>
|
||||
{:else if inputCat == 'resource-string'}
|
||||
<ResourcePicker
|
||||
bind:value
|
||||
resourceType={format.split('-').length > 1
|
||||
? format.substring('resource-'.length)
|
||||
: undefined}
|
||||
/>
|
||||
<div class="flex flex-row gap-x-1 w-full">
|
||||
<LightweightResourcePicker
|
||||
bind:value
|
||||
resourceType={format.split('-').length > 1
|
||||
? format.substring('resource-'.length)
|
||||
: undefined}
|
||||
/>
|
||||
</div>
|
||||
{:else if inputCat == 'string'}
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex flex-row w-full items-center justify-between">
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import LightweightResourcePicker from './LightweightResourcePicker.svelte'
|
||||
|
||||
export let format: string
|
||||
export let value: any
|
||||
export let disablePortal = false
|
||||
|
||||
function isString(value: any) {
|
||||
return typeof value === 'string' || value instanceof String
|
||||
}
|
||||
|
||||
let path: string = ''
|
||||
|
||||
function resourceToValue() {
|
||||
if (path) {
|
||||
value = `$res:${path}`
|
||||
} else {
|
||||
value = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function isResource() {
|
||||
return isString(value) && value.length >= '$res:'.length
|
||||
}
|
||||
|
||||
function valueToPath() {
|
||||
if (isResource()) {
|
||||
path = value.substr('$res:'.length)
|
||||
}
|
||||
}
|
||||
|
||||
$: value && valueToPath()
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row w-full flex-wrap gap-x-2 gap-y-0.5">
|
||||
<LightweightResourcePicker
|
||||
{disablePortal}
|
||||
on:change={(e) => {
|
||||
path = e.detail
|
||||
resourceToValue()
|
||||
}}
|
||||
bind:value={path}
|
||||
resourceType={format.split('-').length > 1 ? format.substring('resource-'.length) : undefined}
|
||||
/>
|
||||
</div>
|
||||
@@ -0,0 +1,93 @@
|
||||
<script lang="ts">
|
||||
import { ResourceService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import Select from './apps/svelte-select/lib/index'
|
||||
import { SELECT_INPUT_DEFAULT_STYLE } from '../defaults'
|
||||
|
||||
import DarkModeObserver from './DarkModeObserver.svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let initialValue: string | undefined = undefined
|
||||
export let value: string | undefined = initialValue
|
||||
export let resourceType: string | undefined = undefined
|
||||
export let disablePortal = false
|
||||
|
||||
let valueSelect =
|
||||
initialValue || value
|
||||
? {
|
||||
value: value ?? initialValue,
|
||||
label: value ?? initialValue
|
||||
}
|
||||
: undefined
|
||||
|
||||
let collection = [valueSelect]
|
||||
|
||||
async function loadResources(resourceType: string | undefined) {
|
||||
const nc = (
|
||||
await ResourceService.listResource({
|
||||
workspace: $workspaceStore!,
|
||||
resourceType
|
||||
})
|
||||
).map((x) => ({
|
||||
value: x.path,
|
||||
label: x.path
|
||||
}))
|
||||
|
||||
// TODO check if this is needed
|
||||
if (!nc.find((x) => x.value == value) && (initialValue || value)) {
|
||||
nc.push({ value: value ?? initialValue!, label: value ?? initialValue! })
|
||||
}
|
||||
collection = nc
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
loadResources(resourceType)
|
||||
}
|
||||
}
|
||||
$: dispatch('change', value)
|
||||
|
||||
let darkMode: boolean = false
|
||||
|
||||
function onThemeChange() {
|
||||
if (document.documentElement.classList.contains('dark')) {
|
||||
darkMode = true
|
||||
} else {
|
||||
darkMode = false
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
onThemeChange()
|
||||
})
|
||||
</script>
|
||||
|
||||
<DarkModeObserver on:change={onThemeChange} />
|
||||
|
||||
<Select
|
||||
portal={!disablePortal}
|
||||
value={valueSelect}
|
||||
on:change={(e) => {
|
||||
value = e.detail.value
|
||||
valueSelect = e.detail
|
||||
}}
|
||||
on:clear={() => {
|
||||
value = undefined
|
||||
valueSelect = undefined
|
||||
}}
|
||||
items={collection}
|
||||
class="text-clip grow min-w-0"
|
||||
placeholder="{resourceType ?? 'any'} resource"
|
||||
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
|
||||
containerStyles={darkMode
|
||||
? SELECT_INPUT_DEFAULT_STYLE.containerStylesDark
|
||||
: SELECT_INPUT_DEFAULT_STYLE.containerStyles}
|
||||
/>
|
||||
|
||||
<style>
|
||||
:global(.svelte-select-list) {
|
||||
font-size: small !important;
|
||||
}
|
||||
</style>
|
||||
@@ -19,6 +19,7 @@
|
||||
import type { PickableProperties } from './flows/previousResults'
|
||||
import type DiffEditor from './DiffEditor.svelte'
|
||||
import type Editor from './Editor.svelte'
|
||||
import ScriptFix from './codeGen/ScriptFix.svelte'
|
||||
|
||||
export let mod: FlowModule
|
||||
export let schema: Schema
|
||||
@@ -129,14 +130,16 @@
|
||||
<Pane size={50} minSize={10} class="text-sm text-tertiary">
|
||||
{#if testJob != undefined && 'result' in testJob && testJob.result != undefined}
|
||||
<pre class="overflow-x-auto break-words relative h-full px-2">
|
||||
<DisplayResult
|
||||
workspaceId={testJob?.workspace_id}
|
||||
jobId={testJob?.id}
|
||||
result={testJob.result}
|
||||
{editor}
|
||||
{diffEditor}
|
||||
{lang}
|
||||
/>
|
||||
<DisplayResult workspaceId={testJob?.workspace_id} jobId={testJob?.id} result={testJob.result}>
|
||||
{#if lang && editor && diffEditor && testJob?.result?.error}
|
||||
<ScriptFix
|
||||
error={JSON.stringify(testJob.result.error)}
|
||||
{lang}
|
||||
{editor}
|
||||
{diffEditor}
|
||||
/>
|
||||
{/if}
|
||||
</DisplayResult>
|
||||
</pre>
|
||||
{:else}
|
||||
<div class="p-2">
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { isCodeInjection } from '$lib/components/flows/utils'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { Clipboard } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { copyToClipboard } from '../../../../utils'
|
||||
import { copyToClipboard, isCodeInjection } from '../../../../utils'
|
||||
import Button from '../../../common/button/Button.svelte'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import { components } from '../../editor/component'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { isCodeInjection } from '$lib/components/flows/utils'
|
||||
import { createEventDispatcher, getContext, onDestroy, tick } from 'svelte'
|
||||
import type { AppInput, EvalAppInput, UploadAppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ListContext, RichConfiguration } from '../../types'
|
||||
@@ -7,6 +6,7 @@
|
||||
import { computeGlobalContext, eval_like } from './eval'
|
||||
import deepEqualWithOrderedArray from './deepEqualWithOrderedArray'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { isCodeInjection } from '$lib/utils'
|
||||
|
||||
type T = string | number | boolean | Record<string | number, any> | undefined
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { getContext } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type { AppViewerContext, RichConfigurations } from '../../types'
|
||||
import '../../../../../../node_modules/quill/dist/quill.snow.css'
|
||||
import 'quill/dist/quill.snow.css'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
type InputTransform,
|
||||
type Job
|
||||
} from '$lib/gen'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { loadSchema, loadSchemaFlow } from '$lib/scripts'
|
||||
import { inferArgs, loadSchemaFromPath } from '$lib/infer'
|
||||
import { loadSchemaFlow } from '$lib/scripts'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
import { cleanExpr, emptySchema } from '$lib/utils'
|
||||
import { get } from 'svelte/store'
|
||||
import type { FlowModuleState } from './flowState'
|
||||
import type { PickableProperties } from './previousResults'
|
||||
@@ -91,13 +91,6 @@ export function cleanInputs(flow: Flow | any): Flow {
|
||||
return newFlow
|
||||
}
|
||||
|
||||
export function cleanExpr(expr: string): string {
|
||||
return expr
|
||||
.split('\n')
|
||||
.filter((x) => x != '' && !x.startsWith(`import `))
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
export async function loadSchemaFromModule(module: FlowModule): Promise<{
|
||||
input_transforms: Record<string, InputTransform>
|
||||
schema: Schema
|
||||
@@ -110,7 +103,7 @@ export async function loadSchemaFromModule(module: FlowModule): Promise<{
|
||||
schema = emptySchema()
|
||||
await inferArgs(mod.language!, mod.content ?? '', schema)
|
||||
} else if (mod.type == 'script' && mod.path && mod.path != '') {
|
||||
schema = await loadSchema(mod.path!, mod.hash)
|
||||
schema = await loadSchemaFromPath(mod.path!, mod.hash)
|
||||
} else if (mod.type == 'flow' && mod.path && mod.path != '') {
|
||||
schema = await loadSchemaFlow(mod.path!)
|
||||
} else {
|
||||
@@ -151,16 +144,6 @@ export async function loadSchemaFromModule(module: FlowModule): Promise<{
|
||||
}
|
||||
}
|
||||
|
||||
const dynamicTemplateRegex = new RegExp(/\$\{(.*)\}/)
|
||||
|
||||
export function isCodeInjection(expr: string | undefined): boolean {
|
||||
if (!expr) {
|
||||
return false
|
||||
}
|
||||
|
||||
return dynamicTemplateRegex.test(expr)
|
||||
}
|
||||
|
||||
export function getDefaultExpr(
|
||||
key: string = 'myfield',
|
||||
previousModuleId: string | undefined,
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import type Editor from '../Editor.svelte'
|
||||
import type DiffEditor from '../DiffEditor.svelte'
|
||||
import ScriptFix from '../codeGen/ScriptFix.svelte'
|
||||
|
||||
export let lang: Preview.language | undefined
|
||||
export let previewIsLoading = false
|
||||
@@ -96,10 +97,16 @@
|
||||
workspaceId={previewJob?.workspace_id}
|
||||
jobId={previewJob?.id}
|
||||
result={previewJob.result}
|
||||
{editor}
|
||||
{diffEditor}
|
||||
{lang}
|
||||
/>
|
||||
>
|
||||
{#if lang && editor && diffEditor && previewJob?.result?.error}
|
||||
<ScriptFix
|
||||
error={JSON.stringify(previewJob.result.error)}
|
||||
{lang}
|
||||
{editor}
|
||||
{diffEditor}
|
||||
/>
|
||||
{/if}
|
||||
</DisplayResult>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-tertiary p-2">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ScriptService, type MainArgSignature, FlowService } from '$lib/gen'
|
||||
import { ScriptService, type MainArgSignature, FlowService, Script } from '$lib/gen'
|
||||
import { get, writable } from 'svelte/store'
|
||||
import type { Schema, SchemaProperty, SupportedLanguage } from './common.js'
|
||||
import { emptySchema, sortObject } from './utils.js'
|
||||
@@ -13,6 +13,7 @@ import init, {
|
||||
parse_bigquery
|
||||
} from 'windmill-parser-wasm'
|
||||
import wasmUrl from 'windmill-parser-wasm/windmill_parser_wasm_bg.wasm?url'
|
||||
import { workspaceStore } from './stores.js'
|
||||
|
||||
init(wasmUrl)
|
||||
|
||||
@@ -184,6 +185,41 @@ function argSigToJsonSchemaType(
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadSchemaFromPath(path: string, hash?: string): Promise<Schema> {
|
||||
if (path.startsWith('hub/')) {
|
||||
const { content, language, schema } = await ScriptService.getHubScriptByPath({ path })
|
||||
if (language == 'deno') {
|
||||
const newSchema = emptySchema()
|
||||
await inferArgs('deno' as SupportedLanguage, content ?? '', newSchema)
|
||||
return newSchema
|
||||
} else {
|
||||
return schema ?? emptySchema()
|
||||
}
|
||||
} else if (hash) {
|
||||
const script = await ScriptService.getScriptByHash({
|
||||
workspace: get(workspaceStore)!,
|
||||
hash
|
||||
})
|
||||
return inferSchemaIfNecessary(script)
|
||||
} else {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: get(workspaceStore)!,
|
||||
path: path ?? ''
|
||||
})
|
||||
return inferSchemaIfNecessary(script)
|
||||
}
|
||||
}
|
||||
|
||||
async function inferSchemaIfNecessary(script: Script) {
|
||||
if (script.schema) {
|
||||
return script.schema as any
|
||||
} else {
|
||||
const newSchema = emptySchema()
|
||||
await inferArgs(script.language, script.content ?? '', newSchema)
|
||||
return newSchema
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadSchema(
|
||||
workspace: string,
|
||||
path: string,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { get } from 'svelte/store'
|
||||
import type { Schema, SupportedLanguage } from './common'
|
||||
import { FlowService, Script, ScriptService } from './gen'
|
||||
import { inferArgs } from './infer'
|
||||
import { workspaceStore, hubScripts } from './stores'
|
||||
import { emptySchema } from './utils'
|
||||
|
||||
export function scriptLangToEditorLang(lang: Script.language) {
|
||||
if (lang == 'deno') {
|
||||
@@ -31,41 +29,6 @@ export function scriptLangToEditorLang(lang: Script.language) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadSchema(path: string, hash?: string): Promise<Schema> {
|
||||
if (path.startsWith('hub/')) {
|
||||
const { content, language, schema } = await ScriptService.getHubScriptByPath({ path })
|
||||
if (language == 'deno') {
|
||||
const newSchema = emptySchema()
|
||||
await inferArgs('deno' as SupportedLanguage, content ?? '', newSchema)
|
||||
return newSchema
|
||||
} else {
|
||||
return schema ?? emptySchema()
|
||||
}
|
||||
} else if (hash) {
|
||||
const script = await ScriptService.getScriptByHash({
|
||||
workspace: get(workspaceStore)!,
|
||||
hash
|
||||
})
|
||||
return inferSchemaIfNecessary(script)
|
||||
} else {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: get(workspaceStore)!,
|
||||
path: path ?? ''
|
||||
})
|
||||
return inferSchemaIfNecessary(script)
|
||||
}
|
||||
}
|
||||
|
||||
async function inferSchemaIfNecessary(script: Script) {
|
||||
if (script.schema) {
|
||||
return script.schema as any
|
||||
} else {
|
||||
const newSchema = emptySchema()
|
||||
await inferArgs(script.language, script.content ?? '', newSchema)
|
||||
return newSchema
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadSchemaFlow(path: string): Promise<Schema> {
|
||||
const flow = await FlowService.getFlowByPath({
|
||||
workspace: get(workspaceStore)!,
|
||||
@@ -101,7 +64,7 @@ export async function getScriptByPath(path: string): Promise<{
|
||||
description: '',
|
||||
tag: undefined,
|
||||
concurrent_limit: undefined,
|
||||
concurrency_time_window_s: undefined,
|
||||
concurrency_time_window_s: undefined
|
||||
}
|
||||
} else {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
@@ -115,7 +78,7 @@ export async function getScriptByPath(path: string): Promise<{
|
||||
description: script.description,
|
||||
tag: script.tag,
|
||||
concurrent_limit: script.concurrent_limit,
|
||||
concurrency_time_window_s: script.concurrency_time_window_s,
|
||||
concurrency_time_window_s: script.concurrency_time_window_s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,3 +587,20 @@ export function toCamel(s: string) {
|
||||
return $1.toUpperCase().replace('-', '').replace('_', '')
|
||||
})
|
||||
}
|
||||
|
||||
export function cleanExpr(expr: string): string {
|
||||
return expr
|
||||
.split('\n')
|
||||
.filter((x) => x != '' && !x.startsWith(`import `))
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
const dynamicTemplateRegex = new RegExp(/\$\{(.*)\}/)
|
||||
|
||||
export function isCodeInjection(expr: string | undefined): boolean {
|
||||
if (!expr) {
|
||||
return false
|
||||
}
|
||||
|
||||
return dynamicTemplateRegex.test(expr)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user