fix(workflow): create node script (#1856)
This commit is contained in:
parent
042f2299f0
commit
2ffd7a8221
File diff suppressed because it is too large
Load Diff
|
|
@ -27,7 +27,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"create:node": "plop --plopfile ./scripts/create-node/plopfile.js",
|
||||
"create:node": "node ./scripts/create-node/index.js",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
|
|
@ -168,6 +168,7 @@
|
|||
"@coze-arch/tea": "workspace:*",
|
||||
"@coze-arch/ts-config": "workspace:*",
|
||||
"@coze-arch/vitest-config": "workspace:*",
|
||||
"@inquirer/prompts": "^7.8.4",
|
||||
"@lezer/common": "^1.2.2",
|
||||
"@monaco-editor/react": "^4.5.2",
|
||||
"@rspack/core": "0.6.0",
|
||||
|
|
@ -184,11 +185,12 @@
|
|||
"@types/semver": "^7.3.4",
|
||||
"@vitest/coverage-v8": "~3.0.5",
|
||||
"debug": "^4.3.4",
|
||||
"esbuild-register": "^3.6.0",
|
||||
"eta": "^3.5.0",
|
||||
"fp-ts": "^2.5.0",
|
||||
"i18next": ">= 19.0.0",
|
||||
"less": "^3.13.1",
|
||||
"monaco-editor": "^0.45.0",
|
||||
"plop": "~4.0.1",
|
||||
"prop-types": "^15.5.7",
|
||||
"react": "~18.2.0",
|
||||
"react-dom": "~18.2.0",
|
||||
|
|
@ -199,6 +201,7 @@
|
|||
"styled-components": ">=4",
|
||||
"stylelint": "^15.11.0",
|
||||
"tailwindcss": "~3.3.3",
|
||||
"ts-morph": "^20.0.0",
|
||||
"typescript": "~5.8.2",
|
||||
"utility-types": "^3.10.0",
|
||||
"vite": "^4.3.9",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
require('esbuild-register');
|
||||
require('./index.ts');
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import { Project, SyntaxKind, type SourceFile } from 'ts-morph';
|
||||
import { camelCase, upperFirst, snakeCase, toUpper } from 'lodash-es';
|
||||
import { Eta } from 'eta';
|
||||
import { input, confirm } from '@inquirer/prompts';
|
||||
|
||||
const tsProject = new Project({});
|
||||
|
||||
class InsertSourceCode {
|
||||
source: SourceFile;
|
||||
constructor(private sourcePath: string) {
|
||||
this.source = tsProject.addSourceFileAtPath(this.sourcePath);
|
||||
}
|
||||
addNamedExport(name: string, specifier: string) {
|
||||
const allExports = this.source.getExportDeclarations();
|
||||
const exist = allExports.some(
|
||||
e =>
|
||||
e.getModuleSpecifierValue() === specifier &&
|
||||
e.getNamedExports().some(i => i.getName() === name),
|
||||
);
|
||||
if (exist) {
|
||||
console.warn(
|
||||
`⚠️ export ${name} in file ${this.sourcePath} already exists.`,
|
||||
);
|
||||
}
|
||||
this.source.addExportDeclaration({
|
||||
namedExports: [name],
|
||||
moduleSpecifier: specifier,
|
||||
});
|
||||
}
|
||||
addNamedImport(name: string, specifier: string) {
|
||||
const allImports = this.source.getImportDeclarations();
|
||||
const exist = allImports.some(
|
||||
e =>
|
||||
e.getModuleSpecifierValue() === specifier &&
|
||||
e.getNamedImports().some(i => i.getName() === name),
|
||||
);
|
||||
if (exist) {
|
||||
console.warn(
|
||||
`⚠️ import ${name} in file ${this.sourcePath} already exists.`,
|
||||
);
|
||||
}
|
||||
this.source.addImportDeclaration({
|
||||
namedImports: [name],
|
||||
moduleSpecifier: specifier,
|
||||
});
|
||||
}
|
||||
getVariableValue<T extends SyntaxKind>(name: string, kind: T) {
|
||||
return this.source
|
||||
.getVariableDeclaration(name)
|
||||
?.getInitializer()
|
||||
?.asKindOrThrow<T>(kind);
|
||||
}
|
||||
save() {
|
||||
return this.source.save();
|
||||
}
|
||||
}
|
||||
|
||||
interface Options {
|
||||
name: string;
|
||||
camelCaseName: string;
|
||||
pascalCaseName: string;
|
||||
constantName: string;
|
||||
registryName: string;
|
||||
isSupportTest: boolean;
|
||||
}
|
||||
|
||||
const ROOT_DIR = process.cwd();
|
||||
|
||||
function copyTemplateFiles(options: Options) {
|
||||
const { name, camelCaseName, constantName, pascalCaseName, isSupportTest } =
|
||||
options;
|
||||
const templateDir = path.join(__dirname, 'templates');
|
||||
const sourceDir = path.join(ROOT_DIR, `./src/node-registries/${name}`);
|
||||
const eta = new Eta({ views: templateDir });
|
||||
|
||||
if (!fs.existsSync(sourceDir)) {
|
||||
fs.mkdirSync(sourceDir, { recursive: true });
|
||||
}
|
||||
|
||||
const templates = fs.readdirSync(templateDir);
|
||||
templates.forEach(temp => {
|
||||
const str = eta.render(temp, {
|
||||
PASCAL_NAME_PLACE_HOLDER: pascalCaseName,
|
||||
CAMEL_NAME_PLACE_HOLDER: camelCaseName,
|
||||
CONSTANT_NAME_PLACE_HOLDER: constantName,
|
||||
IS_SUPPORT_TEST: isSupportTest,
|
||||
});
|
||||
fs.writeFileSync(
|
||||
path.join(sourceDir, temp.replace(/\.eta$/, '')),
|
||||
str,
|
||||
'utf-8',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function insertSourceCode(options: Options) {
|
||||
const { pascalCaseName, registryName, name } = options;
|
||||
// node-registries/index.ts
|
||||
const nodeRegistriesIndex = new InsertSourceCode(
|
||||
path.join(ROOT_DIR, './src/node-registries/index.ts'),
|
||||
);
|
||||
nodeRegistriesIndex.addNamedExport(registryName, `./${name}`);
|
||||
await nodeRegistriesIndex.save();
|
||||
|
||||
// src/nodes-v2/constants.ts;
|
||||
const nodeV2Constants = new InsertSourceCode(
|
||||
path.join(ROOT_DIR, './src/nodes-v2/constants.ts'),
|
||||
);
|
||||
nodeV2Constants.addNamedImport(registryName, '@/node-registries');
|
||||
nodeV2Constants
|
||||
.getVariableValue('NODES_V2', SyntaxKind.ArrayLiteralExpression)
|
||||
?.addElement(registryName, { useNewLines: true });
|
||||
await nodeV2Constants.save();
|
||||
|
||||
// components/node-render/node-render-new/content/index.tsx
|
||||
const nodeRenderContentIndex = new InsertSourceCode(
|
||||
path.join(
|
||||
ROOT_DIR,
|
||||
'./src/components/node-render/node-render-new/content/index.tsx',
|
||||
),
|
||||
);
|
||||
nodeRenderContentIndex.addNamedImport(
|
||||
`${pascalCaseName}Content`,
|
||||
`@/node-registries/${name}`,
|
||||
);
|
||||
nodeRenderContentIndex
|
||||
.getVariableValue('ContentMap', SyntaxKind.ObjectLiteralExpression)
|
||||
?.addPropertyAssignment({
|
||||
name: `[StandardNodeType.${pascalCaseName}]`,
|
||||
initializer: `${pascalCaseName}Content`,
|
||||
});
|
||||
await nodeRenderContentIndex.save();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const name = await input({
|
||||
message:
|
||||
'Enter component name (use "-" as separator), e.g."database-create":',
|
||||
required: true,
|
||||
});
|
||||
const camelCaseName = await input({
|
||||
message: 'Use camelCase (lower camel) for variable prefixes:',
|
||||
default: camelCase(name),
|
||||
required: true,
|
||||
});
|
||||
const pascalCaseName = await input({
|
||||
message: 'Use PascalCase (Upper Camel) for class names:',
|
||||
default: upperFirst(camelCaseName),
|
||||
required: true,
|
||||
});
|
||||
const isSupportTest = await confirm({
|
||||
message: 'Is single-node testing supported?',
|
||||
default: false,
|
||||
});
|
||||
|
||||
const constantName = toUpper(snakeCase(name));
|
||||
const registryName = `${constantName}_NODE_REGISTRY`;
|
||||
const options = {
|
||||
name,
|
||||
camelCaseName,
|
||||
pascalCaseName,
|
||||
constantName,
|
||||
registryName,
|
||||
isSupportTest,
|
||||
};
|
||||
|
||||
copyTemplateFiles(options);
|
||||
|
||||
await insertSourceCode(options);
|
||||
|
||||
console.log('done.');
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const ROOT_DIR = process.cwd();
|
||||
|
||||
// Tool function aa-bb-cc - > AaBbCc
|
||||
const getPascalName = name =>
|
||||
name
|
||||
.split('-')
|
||||
.map(s => s.slice(0, 1).toUpperCase() + s.slice(1))
|
||||
.join('');
|
||||
|
||||
// Tool function aa-bb-cc - > aaBbCc
|
||||
const getCamelName = name =>
|
||||
name
|
||||
.split('-')
|
||||
.map((s, i) => (i === 0 ? s : s.slice(0, 1).toUpperCase() + s.slice(1)))
|
||||
.join('');
|
||||
|
||||
// Tool function aa-bb-cc - > AA_BB_CC
|
||||
const getConstantName = name =>
|
||||
name
|
||||
.split('-')
|
||||
.map(s => s.toUpperCase())
|
||||
.join('_');
|
||||
|
||||
module.exports = plop => {
|
||||
// Register a new action to add new node registration information in the export and registration files
|
||||
plop.setActionType('registryNode', async answers => {
|
||||
const { name, pascalName, supportTest } = answers;
|
||||
const constantName = getConstantName(name);
|
||||
const registryName = `${constantName}_NODE_REGISTRY`;
|
||||
|
||||
// Modify the export file
|
||||
const nodeExportFilePath = './src/node-registries/index.ts';
|
||||
const nodeContent = fs.readFileSync(nodeExportFilePath, 'utf8');
|
||||
const nodeContentNew = nodeContent.replace(
|
||||
'// cli 脚本插入标识(registry),请勿修改/删除此行注释',
|
||||
`export { ${registryName} } from './${name}';
|
||||
// The cli script inserts the identifier (registry), please do not modify/delete this line comment `,
|
||||
);
|
||||
fs.writeFileSync(nodeExportFilePath, nodeContentNew, 'utf8');
|
||||
|
||||
// Modify registration documents
|
||||
const nodeRegistryFilePath = './src/nodes-v2/constants.ts';
|
||||
const nodeRegistryContent = fs.readFileSync(nodeRegistryFilePath, 'utf8');
|
||||
const nodeRegistryContentNew = nodeRegistryContent
|
||||
.replace(
|
||||
'// cli 脚本插入标识(import),请勿修改/删除此行注释',
|
||||
`${registryName},
|
||||
// The cli script inserts the identity (import), please do not modify/delete this line comment `,
|
||||
)
|
||||
.replace(
|
||||
'// cli 脚本插入标识(registry),请勿修改/删除此行注释',
|
||||
`// cli 脚本插入标识(registry),请勿修改/删除此行注释
|
||||
${registryName},`,
|
||||
);
|
||||
fs.writeFileSync(nodeRegistryFilePath, nodeRegistryContentNew, 'utf8');
|
||||
|
||||
// Modify the node-content registration file
|
||||
const nodeContentRegistryFilePath =
|
||||
'./src/components/node-render/node-render-new/content/index.tsx';
|
||||
const nodeContentRegistryContent = fs.readFileSync(
|
||||
nodeContentRegistryFilePath,
|
||||
'utf8',
|
||||
);
|
||||
|
||||
const nodeContentRegistryContentNew = nodeContentRegistryContent
|
||||
.replace(
|
||||
'// cli 脚本插入标识(import),请勿修改/删除此行注释',
|
||||
`import { ${pascalName}Content } from '@/node-registries/${name}';
|
||||
// The cli script inserts the identity (import), please do not modify/delete this line comment `,
|
||||
)
|
||||
.replace(
|
||||
'// cli 脚本插入标识(registry),请勿修改/删除此行注释',
|
||||
`[StandardNodeType.${pascalName}]: ${pascalName}Content,
|
||||
// The cli script inserts the identifier (registry), please do not modify/delete this line comment `,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
nodeContentRegistryFilePath,
|
||||
nodeContentRegistryContentNew,
|
||||
'utf8',
|
||||
);
|
||||
|
||||
// If the node does not need to support single-node testing, delete the node-test file
|
||||
const testFilePath = path.resolve(
|
||||
ROOT_DIR,
|
||||
`./src/node-registries/${name}/node-test.ts`,
|
||||
);
|
||||
if (!supportTest && fs.existsSync(testFilePath)) {
|
||||
fs.unlinkSync(testFilePath);
|
||||
}
|
||||
|
||||
return `节点 ${name} 已注册`;
|
||||
});
|
||||
|
||||
// Register a new generator for creating new node directories and files
|
||||
plop.setGenerator('create node', {
|
||||
description: 'generate template',
|
||||
prompts: [
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message:
|
||||
'请输入组件名称,以"-"(空格)分隔,用于生成目录名称, eg: "database-create"',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'pascalName',
|
||||
message:
|
||||
'请确认大写驼峰命名,用于类名,注意特殊命名: http -> HTTP ,而不是 http -> Http: ',
|
||||
default: answers => getPascalName(answers.name),
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'camelName',
|
||||
message:
|
||||
'请确认小写驼峰命名,用于变量前缀,注意特殊命名: my-ai -> myAI,而不是 my-ai -> myAi: ',
|
||||
default: answers => getCamelName(answers.name),
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'supportTest',
|
||||
message: '是否支持单节点测试?',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
actions: data => {
|
||||
const { name, pascalName, camelName, supportTest } = data;
|
||||
const constantName = getConstantName(data.name);
|
||||
const actions = [
|
||||
{
|
||||
type: 'addMany',
|
||||
destination: path.resolve(ROOT_DIR, `./src/node-registries/${name}`),
|
||||
templateFiles: 'templates',
|
||||
data: {
|
||||
PASCAL_NAME_PLACE_HOLDER: pascalName,
|
||||
CAMEL_NAME_PLACE_HOLDER: camelName,
|
||||
CONSTANT_NAME_PLACE_HOLDER: constantName,
|
||||
SUPPORT_TEST: supportTest,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'registryNode',
|
||||
},
|
||||
];
|
||||
return actions;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -13,7 +13,7 @@ import { type FormData } from './types';
|
|||
import { FormRender } from './form';
|
||||
import { transformOnInit, transformOnSubmit } from './data-transformer';
|
||||
|
||||
export const {{CONSTANT_NAME_PLACE_HOLDER}}_FORM_META: FormMetaV2<FormData> = {
|
||||
export const <%= it.CONSTANT_NAME_PLACE_HOLDER %>_FORM_META: FormMetaV2<FormData> = {
|
||||
// 节点表单渲染
|
||||
render: () => <FormRender />,
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ export const FormRender = () => (
|
|||
<OutputsField
|
||||
title={I18n.t('workflow_detail_node_output')}
|
||||
tooltip={I18n.t('node_http_response_data')}
|
||||
id="{{CAMEL_NAME_PLACE_HOLDER}}-node-outputs"
|
||||
id="<%= it.CAMEL_NAME_PLACE_HOLDER %>-node-outputs"
|
||||
name="outputs"
|
||||
topLevelReadonly={true}
|
||||
customReadonly
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { <%= it.CONSTANT_NAME_PLACE_HOLDER %>_NODE_REGISTRY } from './node-registry';
|
||||
export { <%= it.PASCAL_NAME_PLACE_HOLDER %>Content } from './node-content';
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export { {{CONSTANT_NAME_PLACE_HOLDER}}_NODE_REGISTRY } from './node-registry';
|
||||
export { {{PASCAL_NAME_PLACE_HOLDER}}Content } from './node-content';
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { InputParameters, Outputs } from '../common/components';
|
||||
|
||||
export function {{PASCAL_NAME_PLACE_HOLDER}}Content() {
|
||||
export function <%= it.PASCAL_NAME_PLACE_HOLDER %>Content() {
|
||||
return (
|
||||
<>
|
||||
<InputParameters />
|
||||
|
|
@ -7,21 +7,19 @@ import {
|
|||
type WorkflowNodeRegistry,
|
||||
} from '@coze-workflow/base';
|
||||
|
||||
import { {{CONSTANT_NAME_PLACE_HOLDER}}_FORM_META } from './form-meta';
|
||||
import { <%= it.CONSTANT_NAME_PLACE_HOLDER %>_FORM_META } from './form-meta';
|
||||
import { INPUT_PATH } from './constants';
|
||||
{{#if SUPPORT_TEST}}
|
||||
import { test, type NodeTestMeta } from './node-test';
|
||||
{{/if}}
|
||||
|
||||
export const {{CONSTANT_NAME_PLACE_HOLDER}}_NODE_REGISTRY: WorkflowNodeRegistry{{#if SUPPORT_TEST}}<NodeTestMeta>{{/if}} = {
|
||||
type: StandardNodeType.{{PASCAL_NAME_PLACE_HOLDER}},
|
||||
export const <%= it.CONSTANT_NAME_PLACE_HOLDER %>_NODE_REGISTRY: WorkflowNodeRegistry<NodeTestMeta> = {
|
||||
type: StandardNodeType.<%= it.PASCAL_NAME_PLACE_HOLDER %>,
|
||||
meta: {
|
||||
nodeDTOType: StandardNodeType.{{PASCAL_NAME_PLACE_HOLDER}},
|
||||
nodeDTOType: StandardNodeType.<%= it.PASCAL_NAME_PLACE_HOLDER %>,
|
||||
size: { width: 360, height: 130.7 },
|
||||
nodeMetaPath: DEFAULT_NODE_META_PATH,
|
||||
outputsPath: DEFAULT_OUTPUTS_PATH,
|
||||
inputParametersPath: INPUT_PATH,
|
||||
test{{#unless SUPPORT_TEST}}: false{{/unless}},
|
||||
test,
|
||||
},
|
||||
formMeta: {{CONSTANT_NAME_PLACE_HOLDER}}_FORM_META,
|
||||
formMeta: <%= it.CONSTANT_NAME_PLACE_HOLDER %>_FORM_META,
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import type { NodeTestMeta } from '@/test-run-kit';
|
||||
|
||||
const test: NodeTestMeta = <%= it.IS_SUPPORT_TEST %>;
|
||||
|
||||
export { test, type NodeTestMeta };
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import type { NodeTestMeta } from '@/test-run-kit';
|
||||
|
||||
const test: NodeTestMeta = true;
|
||||
|
||||
export { test, type NodeTestMeta };
|
||||
|
|
@ -52,7 +52,6 @@ import { DatabaseDeleteContent } from './database-delete-content';
|
|||
import { DatabaseCreateContent } from './database-create-content';
|
||||
import { DatabaseContent } from './database-content';
|
||||
import { CommonContent } from './common-content';
|
||||
// CLI script insert ID (import), do not modify/delete this line comment
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
|
|
@ -91,7 +90,6 @@ const ContentMap = {
|
|||
[StandardNodeType.Api]: PluginContent,
|
||||
[StandardNodeType.Variable]: VariableContent,
|
||||
[StandardNodeType.JsonStringify]: JsonStringifyContent,
|
||||
// The cli script inserts the identifier (registry), do not modify/delete this line comment
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { CODE_NODE_REGISTRY } from './code';
|
||||
export { COMMENT_NODE_REGISTRY } from './comment';
|
||||
export { DATABASE_NODE_REGISTRY } from './database/database-base';
|
||||
|
|
@ -48,4 +47,3 @@ export { PLUGIN_NODE_REGISTRY } from './plugin';
|
|||
export { SUB_WORKFLOW_NODE_REGISTRY } from './sub-workflow';
|
||||
export { VARIABLE_NODE_REGISTRY } from './variable';
|
||||
export { JSON_STRINGIFY_NODE_REGISTRY } from './json-stringify';
|
||||
// The cli script inserts the identifier (registry), do not modify/delete this line comment
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ import {
|
|||
SUB_WORKFLOW_NODE_REGISTRY,
|
||||
VARIABLE_NODE_REGISTRY,
|
||||
JSON_STRINGIFY_NODE_REGISTRY,
|
||||
// CLI script insert ID (import), do not modify/delete this line comment
|
||||
} from '@/node-registries';
|
||||
|
||||
import {
|
||||
|
|
@ -69,7 +68,6 @@ import {
|
|||
} from './chat';
|
||||
|
||||
export const NODES_V2 = [
|
||||
// The cli script inserts the identifier (registry), do not modify/delete this line comment
|
||||
JSON_STRINGIFY_NODE_REGISTRY,
|
||||
IF_NODE_REGISTRY,
|
||||
INTENT_NODE_REGISTRY,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
"stories",
|
||||
"vitest.config.ts",
|
||||
"tailwind.config.ts",
|
||||
"src/**/*.test.ts"
|
||||
"src/**/*.test.ts",
|
||||
"scripts/**/*.ts"
|
||||
],
|
||||
"exclude": ["dist"],
|
||||
"references": [
|
||||
|
|
|
|||
Loading…
Reference in New Issue