light-generator
Advanced tools
Comparing version 0.0.1 to 0.0.2
{ | ||
"name": "light-generator", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "light generator for generate a boilerplate", | ||
@@ -12,3 +12,3 @@ "main": "dist/index", | ||
"json-cycle": "^1.3.0", | ||
"lodash": "^4.17.15", | ||
"tar": "^4.4.10", | ||
"untildify": "^4.0.0" | ||
@@ -31,3 +31,4 @@ }, | ||
"test": "npm run lint && NODE_ENV=test midway-bin test --ts --full-trace", | ||
"cov": "NODE_ENV=unittest midway-bin cov --ts" | ||
"cov": "NODE_ENV=unittest midway-bin cov --ts", | ||
"prepublishOnly": "npm run build" | ||
}, | ||
@@ -34,0 +35,0 @@ "files": [ |
154
README.md
# light generator | ||
一个非常轻量的模板生成器,用于各种脚手架的生成。 | ||
一个非常轻量的脚手架生成器,用于各种脚手架的生成。 | ||
## 使用 | ||
核心功能: | ||
- 获取模板到本地(支持本地模板,github 地址,或者npm 地址) | ||
- 拷贝到指定的用户路径 | ||
- 执行自定义规则(比如替换文件名等) | ||
## Quick Guide | ||
```ts | ||
import {LightGenerator} from 'light-generator'; | ||
const generator = new LightGenerator({ | ||
templatePath: 'npm://xxxx' | ||
const generator = new LightGenerator().defineNpmPackage({ | ||
npmPackage: 'egg-boilerplate-simple', | ||
targetPath | ||
}); | ||
await generator.run({ | ||
@@ -18,4 +26,140 @@ name: 'demo' | ||
默认会将模板中的下划线前缀的文件替换为正常文件名,并替换模板中的 `{{xxx}}` 变量为传入的参数。 | ||
## LightGenerator | ||
### Constructor | ||
- options {object} | ||
- disableDefaultRule {boolean} 可选,默认 false,用于禁止默认规则; | ||
- copyRule { CopyRule[] } 可选,可以通过这个配置,传递自定义拷贝规则 | ||
### API | ||
`LightGenerator` 通过定义一系列格式,返回一个特定格式的生成器,执行该生成器即可拿到相应的结果。 | ||
```ts | ||
interface LightGenerator { | ||
defineLocalPath(options): CommonGenerator; | ||
defineNpmPackage(options): CommonGenerator; | ||
} | ||
interface CommonGenerator { | ||
async getParameterList(): object; | ||
async run(replaceParameter?: object); | ||
} | ||
``` | ||
- getParameterList() 获取该生成器(模板)定义的用户可覆盖的配置 | ||
- run() 执行该生成器,在这个时候传入用户覆盖的配置 | ||
#### defineLocalPath | ||
生成本地路径的模板 | ||
- options {object} | ||
- templatePath { string } 本地模板路径 | ||
- targetPath { string } 需要拷贝到的目录 | ||
**示例** | ||
```ts | ||
const localGenerator = new LightGenerator().defineLocalPath({ | ||
templatePath: join(__dirname, './fixtures/boilerplate-2'), | ||
targetPath | ||
}); | ||
``` | ||
#### defineNpmPackage | ||
生成一个 npm 包资源类型的模板 | ||
- options {object} | ||
- npmPackage { string } npm 包名 | ||
- targetPath { string } 需要拷贝到的目录 | ||
- npmClient { string } 可选,默认为 npm | ||
**示例** | ||
```ts | ||
const npmGenerator = new LightGenerator().defineNpmPackage({ | ||
npmPackage: 'egg-boilerplate-simple', | ||
targetPath | ||
}); | ||
``` | ||
## Add custom copy rule | ||
生成器可以在生成完模板文件后,做一些自定义的操作,比如替换内容,修改文件名等。 | ||
简单的来说,规则是一个函数,参数为当前文件路径,用于对当前文件进行操作。 | ||
**示例** | ||
```ts | ||
import {LightGenerator} from 'light-generator'; | ||
const customRule = async (currentFilePath) => { | ||
// TODO | ||
} | ||
const generator = new LightGenerator({ | ||
copyRule: [customRule] | ||
}); | ||
``` | ||
内置了一些默认规则,比如 | ||
- ignoreRule: 用于一些可能会被忽略的文件,在模板文件前缀加入下划线(_),执行此规则会移除该下划线 | ||
- replaceRule: 用于替换文本内容 | ||
这些规则默认已经内置,并且生效。 | ||
**示例** | ||
```ts | ||
import {LightGenerator, ignoreRule} from 'light-generator'; | ||
const generator = new LightGenerator({ | ||
copyRule: [ignoreRule] | ||
}); | ||
``` | ||
如果不希望这些规则生效,请在初始化时关闭,并自行传递规则。 | ||
**示例** | ||
```ts | ||
import {LightGenerator, ignoreRule} from 'light-generator'; | ||
const generator = new LightGenerator({ | ||
disableDefaultRule: true, | ||
copyRule: [ignoreRule] | ||
}); | ||
``` | ||
## 模板规则 | ||
默认情况下,会拷贝整个模板目录的内容到目标目录,但是在 `package.json` 中加入 `boilerplateConfig` 段落可以额外配置这个行为。 | ||
```json | ||
{ | ||
"boilerplateConfig": { | ||
"root": "template", | ||
"replaceParameter": "index.js", | ||
"replaceFile": [ | ||
"src/index.ts" | ||
] | ||
} | ||
} | ||
``` | ||
参数 | ||
- root { string } 设置模板根路径,相对于包根路径,如果配置了 `boilerplateConfig` 字段,默认为 `boilerplate` 目录 | ||
- replaceParameter { string } 用户可替换参数文件路径,相对于包根路径,默认为 `index.js` | ||
- replaceFile { string | string []} 需要替换的文件列表,默认为 `README.md`,相对于 root | ||
## 其他 | ||
此模块核心代码从 serverless 模块中抽取。 | ||
此模块部分核心代码从 serverless 模块中抽取。 |
@@ -1,8 +0,7 @@ | ||
import untildify from 'untildify'; | ||
import { join } from 'path'; | ||
import { downloadTemplateFromRepo } from './util/download'; | ||
import { dirExistsSync } from './util/dirExistsSync'; | ||
import { DirectoryCopyWarker } from './util/copyDirContents'; | ||
import { renameService } from './util/renameService'; | ||
import { DirectoryCopyWalker } from './util/copyDirContents'; | ||
import { CopyRule, CopyWalker } from './interface'; | ||
import { NpmPatternGenerator } from './generator/NpmPatternGenerator'; | ||
import { UrlPatternGenerator } from './generator/UrlPatternGenerator'; | ||
import { LocalPatternGenerator } from './generator/LocalPatternGenerator'; | ||
import { ignoreRule, replaceRule } from './rule'; | ||
@@ -15,44 +14,44 @@ export class LightGenerator { | ||
constructor(options: { | ||
templatePath: string; | ||
templateUrl: string; | ||
templateName: string; | ||
targetPath: string; | ||
copyRule: CopyRule[]; | ||
}) { | ||
disableDefaultRule: boolean; | ||
copyRule?: CopyRule[]; | ||
} = { disableDefaultRule: false }) { | ||
this.options = options; | ||
this.copyWalker = new DirectoryCopyWarker(this.options.copyRule); | ||
this.copyWalker = new DirectoryCopyWalker(this.options); | ||
if (!this.options.disableDefaultRule) { | ||
this.addDefaultCopyRule(); | ||
} | ||
} | ||
async run(replaceArgs) { | ||
if ('templateUrl' in this.options) { | ||
const serviceName = await downloadTemplateFromRepo( | ||
this.copyWalker, | ||
this.options[ 'templateUrl' ], | ||
this.options.templateName, | ||
this.options.targetPath | ||
); | ||
const message = [ | ||
`Successfully installed "${serviceName}" `, | ||
`${this.options.templateName && | ||
this.options.templateName !== serviceName ? `as "${this.options.templateName}"` : ''}`, | ||
].join(''); | ||
addDefaultCopyRule() { | ||
this.copyWalker.addCopyRule(replaceRule); | ||
this.copyWalker.addCopyRule(ignoreRule); | ||
} | ||
console.log(message); | ||
} else if ('templatePath' in this.options) { | ||
// Copying template from a local directory | ||
const servicePath = this.options.targetPath | ||
? untildify(this.options.targetPath) | ||
: join(process.cwd(), this.options.templateName); | ||
if (dirExistsSync(servicePath)) { | ||
const errorMessage = `A folder named "${servicePath}" already exists.`; | ||
throw new Error(errorMessage); | ||
} | ||
await this.copyWalker.copy(untildify(this.options[ 'templatePath' ]), servicePath, { | ||
noLinks: true, | ||
}); | ||
if (this.options.templateName) { | ||
renameService(this.options.templateName, servicePath); | ||
} | ||
} | ||
defineLocalPath(options: { templateName?: string; templatePath: string; targetPath: string; }) { | ||
return new LocalPatternGenerator({ | ||
templateUri: options.templatePath, | ||
targetPath: options.targetPath, | ||
templateName: options.templateName, | ||
copyWalker: this.copyWalker, | ||
}); | ||
} | ||
defineRemoteUrl(options: { templateUrl: string; targetPath: string; templateName: string; }) { | ||
return new UrlPatternGenerator({ | ||
templateUri: options.templateUrl, | ||
targetPath: options.targetPath, | ||
templateName: options.templateName, | ||
copyWalker: this.copyWalker, | ||
}); | ||
} | ||
defineNpmPackage(options: { npmPackage: string; targetPath: string; npmClient?: string; }) { | ||
return new NpmPatternGenerator({ | ||
templateUri: options.npmPackage, | ||
targetPath: options.targetPath, | ||
copyWalker: this.copyWalker, | ||
npmClient: options.npmClient || 'npm' | ||
}); | ||
} | ||
} |
@@ -1,5 +0,31 @@ | ||
export type CopyRule = (currentFile: string) => void; | ||
export type CopyRule = (currentFile: string, copyRuleOptions: CopyRuleOptions) => void; | ||
export interface CopyWalker { | ||
addCopyRule(copyRule: CopyRule); | ||
copy(srcDir, destDir, options?); | ||
} | ||
export interface CommonGeneratorOptions { | ||
templateUri: string; | ||
targetPath: string; | ||
templateName?: string; | ||
copyWalker: CopyWalker; | ||
} | ||
export interface NpmGeneratorOptions extends CommonGeneratorOptions { | ||
npmClient: string; | ||
} | ||
export interface TemplatePackageConfig { | ||
root: string; | ||
replaceFile: string | string[]; | ||
replaceParameter: string | object; | ||
} | ||
export interface CopyRuleOptions { | ||
templateDir: string; | ||
targetDir: string; | ||
targetRelativeFile: string; | ||
replaceParameter: object; | ||
templateConfig: TemplatePackageConfig; | ||
} |
import * as fse from 'fs-extra'; | ||
import { CopyRuleOptions } from './interface'; | ||
import { join } from 'path'; | ||
export const ignoreRule = async (currentFilePath) => { | ||
if (/^_/.test(currentFilePath)) { | ||
await fse.rename(currentFilePath, currentFilePath.replace('_', '')); | ||
/** | ||
* 移除文件下划线 | ||
* @param currentFilePath | ||
*/ | ||
export const ignoreRule = async (currentFilePath, copyRuleOptions: CopyRuleOptions) => { | ||
if (/^_/.test(copyRuleOptions.targetRelativeFile)) { | ||
await fse.rename(currentFilePath, join(copyRuleOptions.targetDir, copyRuleOptions.targetRelativeFile.replace('_', ''))); | ||
} | ||
}; | ||
export const replaceRule = async (currentFilePath) => { | ||
if (/^_/.test(currentFilePath)) { | ||
await fse.rename(currentFilePath, currentFilePath.replace('_', '')); | ||
const pattern = /\{\{(\w*[:]*[=]*\w+)\}\}(?!})/g; | ||
/** | ||
* 替换文本内容包含 {{}} 的字符串模板 | ||
* @param currentFilePath | ||
* @param copyRuleOptions | ||
*/ | ||
export const replaceRule = async (currentFilePath, copyRuleOptions: CopyRuleOptions) => { | ||
const replaceArgs = copyRuleOptions.replaceParameter || {}; | ||
if (copyRuleOptions.templateConfig.replaceFile.includes(copyRuleOptions.targetRelativeFile)) { | ||
const contents = fse.readFileSync(currentFilePath, 'utf-8') | ||
.replace(pattern, (match, key, value) => { | ||
return replaceArgs[ key ]; | ||
}); | ||
return fse.writeFileSync(currentFilePath, contents); | ||
} | ||
}; |
@@ -6,19 +6,38 @@ import * as path from 'path'; | ||
export class DirectoryCopyWarker implements CopyWalker { | ||
export class DirectoryCopyWalker implements CopyWalker { | ||
rules; | ||
constructor(rules: CopyRule[] = []) { | ||
this.rules = rules; | ||
constructor(options: { | ||
rules?: CopyRule[] | ||
} = {}) { | ||
this.rules = options.rules || []; | ||
} | ||
async copy(srcDir, destDir, options = {}) { | ||
addCopyRule(rule: CopyRule) { | ||
this.rules.push(rule); | ||
} | ||
async copy(srcDir, destDir, options = { | ||
replaceParameter: {}, | ||
templateConfig: {} | ||
}) { | ||
const fullFilesPaths = walkDirSync(srcDir, options); | ||
for (const fullFilePath of fullFilesPaths) { | ||
const relativeFilePath = fullFilePath.replace(srcDir, ''); | ||
const relativeFilePath = path.relative(srcDir, fullFilePath); | ||
const targetFilePath = path.join(destDir, relativeFilePath); | ||
await fse.copy(fullFilePath, path.join(destDir, relativeFilePath)); | ||
for (const rule of this.rules) { | ||
await rule(targetFilePath); | ||
await rule(targetFilePath, { | ||
templateDir: srcDir, | ||
targetDir: destDir, | ||
replaceParameter: options.replaceParameter, | ||
targetRelativeFile: relativeFilePath, | ||
templateConfig: options.templateConfig || { | ||
root: destDir, | ||
replaceFile: [], | ||
replaceParameter: {}, | ||
} | ||
}); | ||
} | ||
@@ -25,0 +44,0 @@ } |
@@ -5,3 +5,3 @@ import * as URL from 'url'; | ||
import * as os from 'os'; | ||
import { dirExistsSync } from './dirExistsSync'; | ||
import { dirExistsSync } from './fs'; | ||
import download from 'download'; | ||
@@ -169,8 +169,2 @@ import * as qs from 'querystring'; | ||
/** | ||
* @param {string} inputUrl | ||
* @param {string} [templateName] | ||
* @param {string} [path] | ||
* @returns {Promise} | ||
*/ | ||
export async function downloadTemplateFromRepo(copyWalker: CopyWalker, inputUrl: string, templateName: string, downloadPath: string) { | ||
@@ -177,0 +171,0 @@ const repoInformation = parseRepoURL(inputUrl); |
@@ -16,2 +16,11 @@ import * as fse from 'fs-extra'; | ||
export function dirExistsSync(dirPath) { | ||
try { | ||
const stats = fse.statSync(dirPath); | ||
return stats.isDirectory(); | ||
} catch (e) { | ||
return false; | ||
} | ||
} | ||
export function readFileSync(filePath) { | ||
@@ -18,0 +27,0 @@ const contents = fse.readFileSync(filePath); |
import * as jc from 'json-cycle'; | ||
import * as YAML from 'js-yaml'; | ||
// import * as _ from 'lodash'; | ||
// const cloudFormationSchema = require('../../plugins/aws/lib/cloudformationSchema'); | ||
const loadYaml = (contents, options) => { | ||
@@ -26,7 +23,3 @@ let data; | ||
}; | ||
let result = loadYaml(contents.toString(), options); | ||
if (result.error && result.error.name === 'YAMLException') { | ||
// _.merge(options, { schema: cloudFormationSchema.schema }); | ||
result = loadYaml(contents.toString(), options); | ||
} | ||
const result = loadYaml(contents.toString(), options); | ||
if (result.error) { | ||
@@ -33,0 +26,0 @@ throw result.error; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
81903
45
1444
165
10
2
+ Addedtar@^4.4.10
+ Addedchownr@1.1.4(transitive)
+ Addedfs-minipass@1.2.7(transitive)
+ Addedminipass@2.9.0(transitive)
+ Addedminizlib@1.3.3(transitive)
+ Addedtar@4.4.19(transitive)
+ Addedyallist@3.1.1(transitive)
- Removedlodash@^4.17.15
- Removedlodash@4.17.21(transitive)