扣子智能体
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

193 lines
5.5 KiB

/*
* 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();