-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
addBlock.ts
384 lines (346 loc) · 10.3 KB
/
addBlock.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
import assert from 'assert';
import chalk from 'chalk';
import { IApi } from 'umi-types';
import { existsSync, readFileSync } from 'fs';
import { dirname, join } from 'path';
import ora from 'ora';
import { merge } from 'lodash';
import getNpmRegistry from 'getnpmregistry';
import clipboardy from 'clipboardy';
import { winPath } from 'umi-utils';
import { getParsedData, makeSureMaterialsTempPathExist } from './download';
import writeNewRoute from '../../../utils/writeNewRoute';
import { getNameFromPkg } from './getBlockGenerator';
import appendBlockToContainer from './appendBlockToContainer';
import { gitClone, gitUpdate } from './util';
import installDependencies from './installDependencies';
export interface CtxTypes {
repo?: any;
branch?: any;
path?: string;
id?: string;
routePath?: string;
isLocal?: boolean;
sourcePath?: string;
repoExists?: boolean;
filePath?: string;
templateTmpDirPath?: string;
pkg?: { blockConfig: { [key: string]: any } };
}
export interface AddBlockOption {
// 从命令行传入会有这个
_?: string[];
// 区块的名称和安装的地址
url?: string;
// 安装区块需要的分支
branch?: string;
// 安装的文件地址
path?: string;
// 区块安装的名称
// --page false 是有效
name?: string;
// 安装的路由地址
routePath?: string;
// 包管理器
npmClient?: string;
// 测试运行
dryRun?: boolean;
// 跳过安装依赖
skipDependencies?: boolean;
// 跳过修改路由
skipModifyRoutes?: boolean;
// 是不是区块
page?: boolean;
// 如果是 layout 会在路由中生成一个 children
layout?: boolean;
// npm 源
registry?: string;
// 把 ts 转化为 js
js?: boolean;
// 删除区块的 i18n 代码
uni18n?: boolean;
// 执行环境,默认是 shell ,如果是 auto,发生冲突直接报错
// 在 ci 与 function 中执行可以设置为 auto
execution?: 'shell' | 'auto';
index?: number;
// 传输 log 用
remoteLog?: (log: string) => void;
}
// fix demo => /demo
export const addPrefix = path => {
if (!/^\//.test(path)) {
return `/${path}`;
}
return path;
};
export async function getCtx(url, args: AddBlockOption = {}, api: IApi): Promise<CtxTypes> {
const { debug, config } = api;
debug(`get url ${url}`);
const ctx: CtxTypes = await getParsedData(url, { ...(config.block || {}), ...args });
if (!ctx.isLocal) {
const blocksTempPath = makeSureMaterialsTempPathExist(args.dryRun);
const templateTmpDirPath = join(blocksTempPath, ctx.id);
merge(ctx, {
routePath: args.routePath,
sourcePath: join(templateTmpDirPath, ctx.path),
branch: args.branch || ctx.branch,
templateTmpDirPath,
blocksTempPath,
repoExists: existsSync(templateTmpDirPath),
});
} else {
merge(ctx, {
routePath: args.routePath,
templateTmpDirPath: dirname(url),
});
}
return ctx;
}
async function add(
args: AddBlockOption = {},
opts: AddBlockOption = {},
api: IApi & {
sendLog: (info: string) => void;
},
) {
const { log, paths, debug, config, applyPlugins, sendLog } = api;
const blockConfig: {
npmClient?: string;
} = config.block || {};
const addLogs = [];
const getSpinner = () => {
const spinner = ora();
return {
...spinner,
succeed: (info?: string) => spinner.succeed(info),
start: info => {
if (sendLog) {
sendLog(info);
}
spinner.start(info);
addLogs.push(info);
},
fail: (info?: string) => spinner.fail(info),
stopAndPersist: (option?: any) => spinner.stopAndPersist(option),
};
};
const spinner = getSpinner();
if (!opts.remoteLog) {
opts.remoteLog = () => {};
}
// 1. parse url and args
spinner.start('😁 Parse url and args');
const { url } = args;
assert(url, `run ${chalk.cyan.underline('umi help block')} to checkout the usage`);
const useYarn = existsSync(join(paths.cwd, 'yarn.lock'));
const defaultNpmClient = blockConfig.npmClient || (useYarn ? 'yarn' : 'npm');
debug(`defaultNpmClient: ${defaultNpmClient}`);
debug(`args: ${JSON.stringify(args)}`);
// get faster registry url
const registryUrl = await getNpmRegistry();
const {
path,
name,
routePath,
index,
npmClient = defaultNpmClient,
dryRun,
skipDependencies,
skipModifyRoutes,
page: isPage,
layout: isLayout,
registry = registryUrl,
js,
execution = 'shell',
uni18n,
} = args;
const ctx = await getCtx(url, args, api);
spinner.succeed();
// 2. clone git repo
if (!ctx.isLocal && !ctx.repoExists) {
opts.remoteLog('Clone the git repo');
await gitClone(ctx, spinner);
}
// 3. update git repo
if (!ctx.isLocal && ctx.repoExists) {
try {
opts.remoteLog('Update the git repo');
await gitUpdate(ctx, spinner);
} catch (error) {
log.info('发生错误,请尝试 `umi block clear`');
}
}
// make sure sourcePath exists
assert(existsSync(ctx.sourcePath), `${ctx.sourcePath} don't exists`);
// get block's package.json
const pkgPath = join(ctx.sourcePath, 'package.json');
if (!existsSync(pkgPath)) {
throw new Error(`not find package.json in ${this.sourcePath}`);
} else {
// eslint-disable-next-line
ctx.pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
}
// setup route path
if (!path) {
const blockName = getNameFromPkg(ctx.pkg);
if (!blockName) {
log.error("not find name in block's package.json");
return;
}
ctx.filePath = `/${blockName}`;
log.info(`Not find --path, use block name '${ctx.filePath}' as the target path.`);
} else {
ctx.filePath = winPath(path);
}
ctx.filePath = addPrefix(ctx.filePath);
// 如果 ctx.routePath 不存在,使用 filePath
if (!routePath) {
ctx.routePath = ctx.filePath;
}
ctx.routePath = addPrefix(ctx.routePath);
// 4. install additional dependencies
// check dependencies conflict and install dependencies
// install
opts.remoteLog('📦 Install extra dependencies');
spinner.start('📦 install dependencies package');
await installDependencies(
{ npmClient, registry, applyPlugins, paths, debug, dryRun, spinner, skipDependencies },
ctx,
);
spinner.succeed();
// 5. run generator
opts.remoteLog('🔥 Generate files');
spinner.start('🔥 Generate files');
spinner.stopAndPersist();
const BlockGenerator = require('./getBlockGenerator').default(api);
let isPageBlock = ctx.pkg.blockConfig && ctx.pkg.blockConfig.specVersion === '0.1';
if (isPage !== undefined) {
// when user use `umi block add --page`
isPageBlock = isPage;
}
debug(`isPageBlock: ${isPageBlock}`);
const generator = new BlockGenerator(args._ ? args._.slice(2) : [], {
sourcePath: ctx.sourcePath,
path: ctx.filePath,
routePath: ctx.routePath,
blockName: name || getNameFromPkg(ctx.pkg),
isPageBlock,
dryRun,
execution,
env: {
cwd: api.cwd,
},
resolved: winPath(__dirname),
});
try {
await generator.run();
} catch (e) {
spinner.fail();
throw new Error(e);
}
// write dependencies
if (ctx.pkg.blockConfig && ctx.pkg.blockConfig.dependencies) {
const subBlocks = ctx.pkg.blockConfig.dependencies;
try {
await Promise.all(
subBlocks.map((block: string) => {
const subBlockPath = join(ctx.templateTmpDirPath, block);
debug(`subBlockPath: ${subBlockPath}`);
return new BlockGenerator(args._.slice(2), {
sourcePath: subBlockPath,
path: isPageBlock ? generator.path : join(generator.path, generator.blockFolderName),
// eslint-disable-next-line
blockName: getNameFromPkg(require(join(subBlockPath, 'package.json'))),
isPageBlock: false,
dryRun,
env: {
cwd: api.cwd,
},
routes: api.config.routes,
resolved: winPath(__dirname),
}).run();
}),
);
} catch (e) {
spinner.fail();
throw new Error(e);
}
}
spinner.succeed();
// 调用 sylvanas 转化 ts
if (js) {
opts.remoteLog('🤔 TypeScript to JavaScript');
spinner.start('🤔 TypeScript to JavaScript');
require('./tsTojs').default(generator.blockFolderPath);
spinner.succeed();
}
if (uni18n) {
opts.remoteLog('🌎 remove i18n code');
spinner.start('🌎 remove i18n code');
require('./remove-locale').default(generator.blockFolderPath, uni18n);
spinner.succeed();
}
// 6. write routes
if (generator.needCreateNewRoute && api.config.routes && !skipModifyRoutes) {
opts.remoteLog('⛱ Write route');
spinner.start(`⛱ Write route ${generator.routePath} to ${api.service.userConfig.file}`);
// 当前 _modifyBlockNewRouteConfig 只支持配置式路由
// 未来可以做下自动写入注释配置,支持约定式路由
const newRouteConfig = applyPlugins('_modifyBlockNewRouteConfig', {
initialValue: {
path: generator.routePath.toLowerCase(),
component: `.${generator.path}`,
...(isLayout ? { routes: [] } : {}),
},
});
try {
if (!dryRun) {
writeNewRoute(newRouteConfig, api.service.userConfig.file, paths.absSrcPath);
}
} catch (e) {
spinner.fail();
throw new Error(e);
}
spinner.succeed();
}
// 6. import block to container
if (!generator.isPageBlock) {
spinner.start(
`Write block component ${generator.blockFolderName} import to ${generator.entryPath}`,
);
try {
appendBlockToContainer({
entryPath: generator.entryPath,
blockFolderName: generator.blockFolderName,
dryRun,
index,
});
} catch (e) {
spinner.fail();
throw new Error(e);
}
spinner.succeed();
}
// Final: show success message
const { PORT, BASE_PORT } = process.env;
// Final: show success message
const viewUrl = `http://localhost:${BASE_PORT || PORT || '8000'}${generator.path.toLowerCase()}`;
try {
clipboardy.writeSync(viewUrl);
log.success(
`✨ Probable url ${chalk.cyan(viewUrl)} ${chalk.dim(
'(copied to clipboard)',
)} for view the block.`,
);
} catch (e) {
log.success(`✨ Probable url ${chalk.cyan(viewUrl)} for view the block.`);
log.error('copy to clipboard failed');
}
// return ctx and generator for test
return {
generator,
ctx,
logs: addLogs,
};
}
export default add;