|
|
|
@ -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(); |