@next-core/build-next-bricks 1.5.3 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/build-next-bricks.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import build from "../src/build.js";
|
|
5
5
|
import scanBricks from "../src/scanBricks.js";
|
|
6
|
-
import
|
|
6
|
+
import generatePkgBuild from "../src/generatePkgBuild.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @typedef {T | Array<T>} MaybeArray<T>
|
|
@@ -68,7 +68,7 @@ try {
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
if (configList.some((config) => !config.type || config.type === "bricks")) {
|
|
71
|
-
await
|
|
71
|
+
await generatePkgBuild(packageDir);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
console.log(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@next-core/build-next-bricks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Build next bricks",
|
|
5
5
|
"homepage": "https://github.com/easyops-cn/next-core/tree/v3/packages/build-next-bricks",
|
|
6
6
|
"license": "GPL-3.0",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"css-loader": "^6.7.4",
|
|
39
39
|
"cssnano": "^6.0.1",
|
|
40
40
|
"cssnano-preset-lite": "^3.0.0",
|
|
41
|
+
"doctrine": "^3.0.0",
|
|
41
42
|
"lodash": "^4.17.21",
|
|
42
43
|
"mini-css-extract-plugin": "^2.7.6",
|
|
43
44
|
"postcss": "^8.4.23",
|
|
@@ -47,5 +48,8 @@
|
|
|
47
48
|
"typescript": "^5.0.4",
|
|
48
49
|
"webpack": "^5.84.1"
|
|
49
50
|
},
|
|
50
|
-
"
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@next-core/brick-manifest": "^0.1.0"
|
|
53
|
+
},
|
|
54
|
+
"gitHead": "1f4fb165de7f1f1a9dd2de5586748b5d08d21d7c"
|
|
51
55
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import webpack from "webpack";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import("@next-core/brick-manifest").PackageManifest} PackageManifest
|
|
5
|
+
*/
|
|
6
|
+
|
|
3
7
|
const pluginName = "EmitBricksJsonPlugin";
|
|
4
8
|
|
|
5
9
|
export default class EmitBricksJsonPlugin {
|
|
6
10
|
/**
|
|
7
|
-
* @param {{ packageName: string; bricks: string[]; processors: string[]; dependencies: Record<string, string[]>; }} options
|
|
11
|
+
* @param {{ packageName: string; bricks: string[]; processors: string[]; dependencies: Record<string, string[]>; manifest: PackageManifest; }} options
|
|
8
12
|
*/
|
|
9
13
|
constructor(options) {
|
|
10
14
|
this.packageName = options.packageName;
|
|
@@ -12,6 +16,7 @@ export default class EmitBricksJsonPlugin {
|
|
|
12
16
|
this.elements = options.elements;
|
|
13
17
|
this.processors = options.processors;
|
|
14
18
|
this.dependencies = options.dependencies;
|
|
19
|
+
this.manifest = options.manifest;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
/**
|
|
@@ -60,6 +65,12 @@ export default class EmitBricksJsonPlugin {
|
|
|
60
65
|
new webpack.sources.RawSource(bricksJson, false)
|
|
61
66
|
);
|
|
62
67
|
|
|
68
|
+
const manifestJson = JSON.stringify(this.manifest, null, 2);
|
|
69
|
+
compilation.emitAsset(
|
|
70
|
+
"manifest.json",
|
|
71
|
+
new webpack.sources.RawSource(manifestJson, false)
|
|
72
|
+
);
|
|
73
|
+
|
|
63
74
|
console.log("Defined bricks:", this.bricks);
|
|
64
75
|
console.log("Defined elements:", this.elements);
|
|
65
76
|
console.log("Defined processors:", this.processors);
|
package/src/build.js
CHANGED
|
@@ -9,7 +9,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
9
9
|
/**
|
|
10
10
|
* @param {string} packageDir
|
|
11
11
|
*/
|
|
12
|
-
export default async function
|
|
12
|
+
export default async function generatePkgBuild(packageDir) {
|
|
13
13
|
const targetPkgBuildDir = path.join(packageDir, ".pkgbuild");
|
|
14
14
|
if (!existsSync(targetPkgBuildDir)) {
|
|
15
15
|
mkdirSync(targetPkgBuildDir);
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { parse } from "doctrine";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import("@next-core/brick-manifest").BrickManifest} BrickManifest
|
|
5
|
+
* @typedef {import("@next-core/brick-manifest").PropertyManifest} PropertyManifest
|
|
6
|
+
* @typedef {import("@next-core/brick-manifest").EventManifest} EventManifest
|
|
7
|
+
* @typedef {import("@next-core/brick-manifest").MethodManifest} MethodManifest
|
|
8
|
+
* @typedef {import("@babel/types").Node} Node
|
|
9
|
+
* @typedef {import("@babel/traverse").NodePath} NodePath
|
|
10
|
+
* @typedef {import("@babel/types").ClassDeclaration} ClassDeclaration
|
|
11
|
+
* @typedef {import("doctrine").Tag} Tag
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} name
|
|
16
|
+
* @param {NodePath} nodePath
|
|
17
|
+
* @param {string} source
|
|
18
|
+
* @returns {BrickManifest}
|
|
19
|
+
*/
|
|
20
|
+
export default function makeBrickManifest(name, nodePath, source) {
|
|
21
|
+
/** @type {import("@babel/traverse").NodePath<ClassDeclaration>} */
|
|
22
|
+
const classPath = nodePath.parentPath;
|
|
23
|
+
/** @type {BrickManifest} */
|
|
24
|
+
const manifest = {
|
|
25
|
+
name,
|
|
26
|
+
properties: [],
|
|
27
|
+
events: [],
|
|
28
|
+
slots: [],
|
|
29
|
+
methods: [],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const docComment = findDocComment(nodePath, source);
|
|
33
|
+
if (docComment) {
|
|
34
|
+
manifest.description = docComment.description;
|
|
35
|
+
for (const tag of docComment.tags) {
|
|
36
|
+
if (tag.title === "slot") {
|
|
37
|
+
const match = tag.description.match(/^(?:([-\w]+)\s+-\s+)?(.*)$/);
|
|
38
|
+
if (!match) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Doc comment for slot is invalid: '${tag.description}'`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
manifest.slots.push({
|
|
44
|
+
name: match[1] ?? null,
|
|
45
|
+
description: match[2],
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
scanFields(manifest, classPath.node.body.body, source);
|
|
52
|
+
|
|
53
|
+
return manifest;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {NodePath} nodePath
|
|
58
|
+
* @param {string} source
|
|
59
|
+
*/
|
|
60
|
+
function findDocComment({ node, parentPath }, source) {
|
|
61
|
+
if (node.type !== "Program") {
|
|
62
|
+
const docComment = parseDocComment(node, source);
|
|
63
|
+
if (docComment) {
|
|
64
|
+
return docComment;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (parentPath) {
|
|
68
|
+
return findDocComment(parentPath, source);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {BrickManifest} manifest
|
|
74
|
+
* @param {Node[]} nodes
|
|
75
|
+
* @param {string} source
|
|
76
|
+
*/
|
|
77
|
+
function scanFields(manifest, nodes, source) {
|
|
78
|
+
for (const node of nodes) {
|
|
79
|
+
if (node.type === "ClassAccessorProperty" && node.decorators?.length) {
|
|
80
|
+
for (const { expression } of node.decorators) {
|
|
81
|
+
if (
|
|
82
|
+
expression.type === "CallExpression" &&
|
|
83
|
+
expression.callee.type === "Identifier"
|
|
84
|
+
) {
|
|
85
|
+
switch (expression.callee.name) {
|
|
86
|
+
case "property": {
|
|
87
|
+
/** @type {PropertyManifest} */
|
|
88
|
+
const prop = {
|
|
89
|
+
name: node.key.name,
|
|
90
|
+
};
|
|
91
|
+
const docComment = parseDocComment(node, source);
|
|
92
|
+
if (docComment) {
|
|
93
|
+
prop.description = docComment.description;
|
|
94
|
+
prop.required = getBooleanTag(docComment.tags, "required");
|
|
95
|
+
prop.deprecated = getDeprecatedInfo(docComment.tags);
|
|
96
|
+
prop.default = findTag(docComment.tags, "default")?.description;
|
|
97
|
+
}
|
|
98
|
+
// Find out the `attribute` option for the property.
|
|
99
|
+
if (expression.arguments.length > 0) {
|
|
100
|
+
const options = expression.arguments[0];
|
|
101
|
+
if (options.type === "ObjectExpression") {
|
|
102
|
+
for (const opt of options.properties) {
|
|
103
|
+
if (
|
|
104
|
+
opt.type === "ObjectProperty" &&
|
|
105
|
+
!opt.computed &&
|
|
106
|
+
opt.key.type === "Identifier" &&
|
|
107
|
+
opt.key.name === "attribute"
|
|
108
|
+
) {
|
|
109
|
+
if (
|
|
110
|
+
opt.value.type === "BooleanLiteral" ||
|
|
111
|
+
opt.value.type === "StringLiteral"
|
|
112
|
+
) {
|
|
113
|
+
prop.attribute = opt.value.value;
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Find out the type annotation for the property.
|
|
121
|
+
if (
|
|
122
|
+
node.typeAnnotation &&
|
|
123
|
+
node.typeAnnotation.type === "TSTypeAnnotation"
|
|
124
|
+
) {
|
|
125
|
+
const { typeAnnotation } = node.typeAnnotation;
|
|
126
|
+
prop.type = getTypeWithoutUndefined(typeAnnotation, source);
|
|
127
|
+
}
|
|
128
|
+
if (node.value && !prop.default) {
|
|
129
|
+
prop.default = source.substring(
|
|
130
|
+
node.value.start,
|
|
131
|
+
node.value.end
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
manifest.properties.push(prop);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
case "event": {
|
|
139
|
+
/** @type {EventManifest} */
|
|
140
|
+
const event = {};
|
|
141
|
+
|
|
142
|
+
// Find out the `type` option for the event.
|
|
143
|
+
if (expression.arguments.length > 0) {
|
|
144
|
+
const options = expression.arguments[0];
|
|
145
|
+
if (options.type === "ObjectExpression") {
|
|
146
|
+
for (const opt of options.properties) {
|
|
147
|
+
if (
|
|
148
|
+
opt.type === "ObjectProperty" &&
|
|
149
|
+
!opt.computed &&
|
|
150
|
+
opt.key.type === "Identifier" &&
|
|
151
|
+
opt.key.name === "type"
|
|
152
|
+
) {
|
|
153
|
+
if (opt.value.type === "StringLiteral") {
|
|
154
|
+
event.name = opt.value.value;
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (event.name === undefined) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`Invalid @event() call: no literal type option in event '${node.key.name}'`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const docComment = parseDocComment(node, source);
|
|
167
|
+
if (docComment) {
|
|
168
|
+
event.description = docComment.description;
|
|
169
|
+
event.deprecated = getDeprecatedInfo(docComment.tags);
|
|
170
|
+
const detailTag = findTag(docComment.tags, "detail");
|
|
171
|
+
if (detailTag) {
|
|
172
|
+
event.detail ??= {};
|
|
173
|
+
event.detail.description = detailTag.description;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Find out the type annotation for the event detail.
|
|
177
|
+
if (
|
|
178
|
+
node.typeAnnotation &&
|
|
179
|
+
node.typeAnnotation.type === "TSTypeAnnotation"
|
|
180
|
+
) {
|
|
181
|
+
const { typeAnnotation } = node.typeAnnotation;
|
|
182
|
+
if (
|
|
183
|
+
typeAnnotation.type === "TSTypeReference" &&
|
|
184
|
+
typeAnnotation.typeName.type === "Identifier" &&
|
|
185
|
+
typeAnnotation.typeName.name === "EventEmitter"
|
|
186
|
+
) {
|
|
187
|
+
// Extract the parameters from `EventEmitter`
|
|
188
|
+
const param = typeAnnotation.typeParameters.params[0];
|
|
189
|
+
event.detail ??= {};
|
|
190
|
+
event.detail.type = source.substring(param.start, param.end);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
manifest.events.push(event);
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} else if (node.type === "ClassMethod" && node.decorators?.length) {
|
|
200
|
+
for (const { expression } of node.decorators) {
|
|
201
|
+
if (
|
|
202
|
+
expression.type === "CallExpression" &&
|
|
203
|
+
expression.callee.type === "Identifier" &&
|
|
204
|
+
expression.callee.name === "method"
|
|
205
|
+
) {
|
|
206
|
+
/** @type {MethodManifest} */
|
|
207
|
+
const method = {
|
|
208
|
+
name: node.key.name,
|
|
209
|
+
params: [],
|
|
210
|
+
};
|
|
211
|
+
const docComment = parseDocComment(node, source);
|
|
212
|
+
if (docComment) {
|
|
213
|
+
method.description = docComment.description;
|
|
214
|
+
method.deprecated = getDeprecatedInfo(docComment.tags);
|
|
215
|
+
}
|
|
216
|
+
for (const param of node.params) {
|
|
217
|
+
method.params.push(source.substring(param.start, param.end));
|
|
218
|
+
}
|
|
219
|
+
if (node.returnType && node.returnType.type === "TSTypeAnnotation") {
|
|
220
|
+
const { typeAnnotation } = node.returnType;
|
|
221
|
+
method.return ??= {};
|
|
222
|
+
method.return.type = source.substring(
|
|
223
|
+
typeAnnotation.start,
|
|
224
|
+
typeAnnotation.end
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
manifest.methods.push(method);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @param {Node[]} nodes
|
|
236
|
+
* @param {string} source
|
|
237
|
+
*/
|
|
238
|
+
function parseDocComment(node, source) {
|
|
239
|
+
if (node.leadingComments) {
|
|
240
|
+
const docComment = node.leadingComments.find(
|
|
241
|
+
(comment) => comment.type === "CommentBlock"
|
|
242
|
+
);
|
|
243
|
+
if (docComment) {
|
|
244
|
+
const docSource = source.substring(docComment.start, docComment.end);
|
|
245
|
+
const parsed = parse(docSource, { unwrap: true });
|
|
246
|
+
return parsed;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param {Node} node
|
|
253
|
+
* @param {string} source
|
|
254
|
+
*/
|
|
255
|
+
function getTypeWithoutUndefined(node, source) {
|
|
256
|
+
if (node.type === "TSUnionType") {
|
|
257
|
+
const filteredTypes = node.types.filter(
|
|
258
|
+
(type) => type.type !== "TSUndefinedKeyword"
|
|
259
|
+
);
|
|
260
|
+
if (filteredTypes.length < node.types.length) {
|
|
261
|
+
return filteredTypes
|
|
262
|
+
.map((type) => source.substring(type.start, type.end))
|
|
263
|
+
.join(" | ");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return source.substring(node.start, node.end);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* @param {Tag[]} tags
|
|
271
|
+
* @param {string} title
|
|
272
|
+
* @returns {Tag | undefined}
|
|
273
|
+
*/
|
|
274
|
+
function findTag(tags, title) {
|
|
275
|
+
for (const tag of tags) {
|
|
276
|
+
if (tag.title === title) {
|
|
277
|
+
return tag;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* @param {Tag[]} tags
|
|
284
|
+
* @param {string} title
|
|
285
|
+
* @returns {boolean | undefined}
|
|
286
|
+
*/
|
|
287
|
+
function getBooleanTag(tags, title) {
|
|
288
|
+
const tag = findTag(tags, title);
|
|
289
|
+
return tag ? true : undefined;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @param {Tag[]} tags
|
|
294
|
+
* @returns {boolean | string | undefined}
|
|
295
|
+
*/
|
|
296
|
+
function getDeprecatedInfo(tags) {
|
|
297
|
+
const tag = findTag(tags, "deprecated");
|
|
298
|
+
return tag ? (tag.description === null ? true : tag.description) : undefined;
|
|
299
|
+
}
|
package/src/scanBricks.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs, { existsSync, statSync } from "node:fs";
|
|
3
|
-
import { readdir, readFile
|
|
3
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
4
4
|
import { parse } from "@babel/parser";
|
|
5
5
|
import babelTraverse from "@babel/traverse";
|
|
6
6
|
import _ from "lodash";
|
|
7
7
|
import getCamelPackageName from "./getCamelPackageName.js";
|
|
8
|
+
import makeBrickManifest from "./makeBrickManifest.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import("@next-core/brick-manifest").PackageManifest} PackageManifest
|
|
12
|
+
* @typedef {{import: string; name: string; noNamespace?: boolean;}} Expose
|
|
13
|
+
* @typedef {Record<string, Expose>} Exposes
|
|
14
|
+
*/
|
|
8
15
|
|
|
9
16
|
const { default: traverse } = babelTraverse;
|
|
10
17
|
const { escapeRegExp } = _;
|
|
@@ -19,10 +26,10 @@ const validExposeName = /^[-\w]+$/;
|
|
|
19
26
|
* Scan defined bricks by AST.
|
|
20
27
|
*
|
|
21
28
|
* @param {string} packageDir
|
|
22
|
-
* @returns {Promise<{exposes:
|
|
29
|
+
* @returns {Promise<{exposes: Exposes; dependencies: Record<string, string[]>; manifest: PackageManifest}>}
|
|
23
30
|
*/
|
|
24
31
|
export default async function scanBricks(packageDir) {
|
|
25
|
-
/** @type {Map<string,
|
|
32
|
+
/** @type {Map<string, Expose>} */
|
|
26
33
|
const exposes = new Map();
|
|
27
34
|
/** @type {Record<string, string[]>} */
|
|
28
35
|
const specifiedDeps = {};
|
|
@@ -38,6 +45,14 @@ export default async function scanBricks(packageDir) {
|
|
|
38
45
|
const packageName = packageJson.name.split("/").pop();
|
|
39
46
|
const camelPackageName = getCamelPackageName(packageName);
|
|
40
47
|
|
|
48
|
+
/** @type {PackageManifest} */
|
|
49
|
+
const manifest = {
|
|
50
|
+
manifest_version: 1,
|
|
51
|
+
package: packageJson.name,
|
|
52
|
+
name: packageName,
|
|
53
|
+
bricks: [],
|
|
54
|
+
};
|
|
55
|
+
|
|
41
56
|
/** @type {Map<string, Set<string>} */
|
|
42
57
|
const usingWrappedBricks = new Map();
|
|
43
58
|
|
|
@@ -302,7 +317,10 @@ export default async function scanBricks(packageDir) {
|
|
|
302
317
|
}
|
|
303
318
|
}
|
|
304
319
|
},
|
|
305
|
-
Decorator(
|
|
320
|
+
Decorator(nodePath) {
|
|
321
|
+
const {
|
|
322
|
+
node: { expression },
|
|
323
|
+
} = nodePath;
|
|
306
324
|
// Match `@defineElement(...)`
|
|
307
325
|
if (
|
|
308
326
|
expression.type === "CallExpression" &&
|
|
@@ -372,6 +390,8 @@ export default async function scanBricks(packageDir) {
|
|
|
372
390
|
|
|
373
391
|
brickSourceFiles.set(fullName, filePath);
|
|
374
392
|
|
|
393
|
+
manifest.bricks.push(makeBrickManifest(fullName, nodePath, content));
|
|
394
|
+
|
|
375
395
|
exposes.set(`./${brickName}`, {
|
|
376
396
|
import: `./${path
|
|
377
397
|
.relative(packageDir, overrideImport || filePath)
|
|
@@ -503,5 +523,6 @@ export default async function scanBricks(packageDir) {
|
|
|
503
523
|
...analyzedDeps,
|
|
504
524
|
...specifiedDeps,
|
|
505
525
|
},
|
|
526
|
+
manifest,
|
|
506
527
|
};
|
|
507
528
|
}
|