@galacean/cli 0.0.1 → 0.0.2

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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ## Usage
4
4
 
5
5
  ```bash
6
- $ npm i -g @galacean/@galacean/cli
6
+ $ npm i -g @galacean/cli
7
7
 
8
8
  galacean diff <project1-url> <project2-url>
9
9
  ```
@@ -0,0 +1,18 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ export class Markdown {
4
+ _txt = "";
5
+ get txt() {
6
+ return this._txt;
7
+ }
8
+ addLine(line, tab = 0) {
9
+ for (let i = 0; i < tab; i++) {
10
+ line = "\t" + line;
11
+ }
12
+ this._txt += line + "\n";
13
+ }
14
+ generate() {
15
+ fs.writeFileSync(path.join(process.cwd(), "项目 Diff.md"), this._txt);
16
+ }
17
+ }
18
+ export const markdown = new Markdown();
package/lib/cli.js ADDED
@@ -0,0 +1,28 @@
1
+ import { cac } from "cac";
2
+ import path from "path";
3
+ import fs from "fs-extra";
4
+ import * as url from "url";
5
+ import { diffProjects } from "./diff.js";
6
+ import { getFileBufferFromUrl } from "./utils.js";
7
+ import { markdown } from "./Markdown.js";
8
+ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
9
+ const cli = cac();
10
+ cli.command("diff [...files]", "diff files").action((files) => {
11
+ markdown.addLine("# 资产 Diff");
12
+ markdown.addLine(``);
13
+ markdown.addLine(`- ${files[0]}`);
14
+ markdown.addLine(`- ${files[1]}`);
15
+ markdown.addLine(``);
16
+ Promise.all([
17
+ getFileBufferFromUrl(files[0]),
18
+ getFileBufferFromUrl(files[1]),
19
+ ]).then(async (res) => {
20
+ const result = res.map((item) => JSON.parse(item.toString()));
21
+ await diffProjects(result[0], result[1]);
22
+ markdown.generate();
23
+ });
24
+ });
25
+ const pkg = fs.readJSONSync(path.join(__dirname, "../package.json"));
26
+ cli.help();
27
+ cli.version(pkg.version);
28
+ cli.parse();
package/lib/diff.js ADDED
@@ -0,0 +1,155 @@
1
+ import { Operation, diff, flattenChangeset } from "json-diff-ts";
2
+ import { groupBy, cloneDeep } from "lodash-es";
3
+ import { getFileBufferFromUrl } from "./utils.js";
4
+ import { markdown } from "./Markdown.js";
5
+ export async function diffProjects(project1, project2) {
6
+ const result = diff(project1, project2, { files: "id" });
7
+ const changes = flattenChangeset(result);
8
+ const assets1 = {};
9
+ const assets2 = {};
10
+ project1.files.forEach((item) => {
11
+ assets1[item.id] = item;
12
+ });
13
+ project2.files.forEach((item) => {
14
+ assets2[item.id] = item;
15
+ });
16
+ const diffs = groupBy(changes, "type");
17
+ const sceneChanges = [];
18
+ markdown.addLine("## 资产");
19
+ markdown.addLine(``);
20
+ markdown.addLine(`- 资产新增`);
21
+ diffs[Operation.ADD]?.forEach((item) => {
22
+ markdown.addLine(`- ${item.value.virtualPath}`, 1);
23
+ });
24
+ markdown.addLine(``);
25
+ markdown.addLine(`- 资产移除`);
26
+ diffs[Operation.REMOVE]?.forEach((item) => {
27
+ markdown.addLine(`- ${item.value.virtualPath}`, 1);
28
+ });
29
+ // markdown.addLine(``);
30
+ // markdown.addLine(`- 资产修改`);
31
+ // markdown.addLine(``);
32
+ diffs[Operation.UPDATE]?.forEach((item) => {
33
+ const id = getIdFromPath(item.path);
34
+ const asset = assets1[id];
35
+ if (asset.type === "Scene") {
36
+ sceneChanges.push(asset);
37
+ }
38
+ // markdown.addLine(`- ${asset.virtualPath}`, 1);
39
+ });
40
+ markdown.addLine(``);
41
+ markdown.addLine("## 场景");
42
+ markdown.addLine(``);
43
+ await Promise.all(sceneChanges.map((asset) => {
44
+ markdown.addLine(`### ${assets2[asset.id].virtualPath}`);
45
+ markdown.addLine(``);
46
+ return diffScene(asset.path, assets2[asset.id].path);
47
+ }));
48
+ }
49
+ async function diffScene(url1, url2) {
50
+ const [scene1, scene2] = await Promise.all([
51
+ getFileBufferFromUrl(url1),
52
+ getFileBufferFromUrl(url2),
53
+ ]).then((res) => res.map((item) => JSON.parse(item.toString())));
54
+ const { diffObj: diffObj1, entities } = getEntities(scene1);
55
+ const { diffObj: diffObj2, entities: newEntities } = getEntities(scene2);
56
+ const changes = flattenChangeset(diff(diffObj1, diffObj2));
57
+ // const changesGroup = groupBy(changes, "type");
58
+ // markdown.addLine(``);
59
+ // markdown.addLine(`- Entity 移除`);
60
+ // changesGroup[Operation.REMOVE]?.forEach((item) => {
61
+ // markdown.addLine(`- ${entities[item.key].name}`, 1);
62
+ // });
63
+ const groups = groupBy(changes, (item) => {
64
+ if (item.path.includes(".components")) {
65
+ return "components";
66
+ }
67
+ return "entities";
68
+ });
69
+ if (groups["entities"]) {
70
+ markdown.addLine(`#### Entities`);
71
+ markdown.addLine("");
72
+ const changesGroup = groupBy(groups["entities"], "type");
73
+ markdown.addLine(`- 新增`);
74
+ changesGroup[Operation.ADD]?.forEach((item) => {
75
+ const entityPath = getEntityPath(item.key, newEntities);
76
+ markdown.addLine(`- ${entityPath}`, 1);
77
+ });
78
+ markdown.addLine(`- 移除`);
79
+ changesGroup[Operation.REMOVE]?.forEach((item) => {
80
+ const entityPath = getEntityPath(item.key, entities);
81
+ markdown.addLine(`- ${entityPath}`, 1);
82
+ });
83
+ }
84
+ if (groups["components"]) {
85
+ markdown.addLine(``);
86
+ markdown.addLine(`#### 组件`);
87
+ markdown.addLine("");
88
+ const changesGroup = groupBy(groups["components"], "type");
89
+ markdown.addLine(`- 新增`);
90
+ changesGroup[Operation.ADD]?.forEach((item) => {
91
+ const entityId = getComponentEntityId(item.path);
92
+ const entityPath = getEntityPath(entityId, entities);
93
+ const comp = item.value;
94
+ markdown.addLine(`- ${entityPath} [${comp["class"]}]`, 1);
95
+ });
96
+ markdown.addLine(`- 移除`);
97
+ changesGroup[Operation.REMOVE]?.forEach((item) => {
98
+ const entityId = getComponentEntityId(item.path);
99
+ const entityPath = getEntityPath(entityId, entities);
100
+ const comp = item.value;
101
+ markdown.addLine(`- ${entityPath} [${comp["class"]}]`, 1);
102
+ });
103
+ }
104
+ }
105
+ function getComponentEntityId(str) {
106
+ let regex = /\.([^.]*?)\.components/;
107
+ let matches = str.match(regex);
108
+ if (matches) {
109
+ return matches[1];
110
+ }
111
+ }
112
+ function getEntityPath(id, entities) {
113
+ const routes = [];
114
+ let entity = entities[id];
115
+ while (entity) {
116
+ routes.unshift(entity.name);
117
+ entity = entities[entity.parent];
118
+ }
119
+ return routes.join("/");
120
+ }
121
+ function getEntities(scene) {
122
+ const obj = {};
123
+ scene.entities.forEach((item) => {
124
+ const comps = {};
125
+ item.components.forEach((comp) => {
126
+ comps[comp.id] = comp;
127
+ });
128
+ item.components = comps;
129
+ item.children = {};
130
+ obj[item.id] = item;
131
+ });
132
+ const entities = cloneDeep(obj);
133
+ const hasParentItems = [];
134
+ scene.entities.forEach((item) => {
135
+ if (item.parent) {
136
+ obj[item.parent].children[item.id] = item;
137
+ hasParentItems.push(item.id);
138
+ }
139
+ });
140
+ hasParentItems.forEach((id) => {
141
+ delete obj[id];
142
+ });
143
+ return { diffObj: obj, entities };
144
+ }
145
+ function getIdFromPath(str) {
146
+ const regex = /id=='([^']+)'/;
147
+ const match = str.match(regex);
148
+ if (match) {
149
+ const id = match[1]; // 提取的 id 值
150
+ return id;
151
+ }
152
+ else {
153
+ console.log("No match found");
154
+ }
155
+ }
package/lib/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from "./cli.js";
package/lib/type.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/lib/utils.js ADDED
@@ -0,0 +1,128 @@
1
+ import https from "https";
2
+ import http from "http";
3
+ import fs from "fs-extra";
4
+ import path from "path";
5
+ import fetch from "node-fetch";
6
+ import { URL } from "url";
7
+ export function streamToBuffer(stream) {
8
+ return new Promise((resolve, reject) => {
9
+ const buffers = [];
10
+ stream.on("error", reject);
11
+ // @ts-ignore
12
+ stream.on("data", (data) => buffers.push(data));
13
+ stream.on("end", () => resolve(Buffer.concat(buffers)));
14
+ });
15
+ }
16
+ export function streamToString(stream) {
17
+ const chunks = [];
18
+ return new Promise((resolve, reject) => {
19
+ stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
20
+ stream.on("error", (err) => reject(err));
21
+ stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
22
+ });
23
+ }
24
+ export function toBuffer(view) {
25
+ const buf = Buffer.alloc(view.byteLength);
26
+ for (let i = 0; i < buf.length; ++i) {
27
+ buf[i] = view[i];
28
+ }
29
+ return buf;
30
+ }
31
+ export function urlToBuffer(url) {
32
+ return new Promise((resolve) => {
33
+ let req = url.startsWith("https://")
34
+ ? https.get(url, callback)
35
+ : http.get(url, callback);
36
+ let body = "";
37
+ function callback(res) {
38
+ res.on("data", (chunk) => {
39
+ body += chunk;
40
+ });
41
+ res.on("end", () => {
42
+ resolve(Buffer.from(body));
43
+ });
44
+ }
45
+ });
46
+ }
47
+ export function getUrlSize(url, attempts = 0) {
48
+ return new Promise((res, rej) => {
49
+ let req = url.startsWith("https://") ? https.get(url) : http.get(url);
50
+ req.once("response", (r) => {
51
+ req.destroy();
52
+ if (r.statusCode >= 300 && r.statusCode < 400 && r.headers.location) {
53
+ if (attempts > 5) {
54
+ return rej(new Error("Too many redirects"));
55
+ }
56
+ return res(getUrlSize(r.headers.location, attempts + 1));
57
+ }
58
+ let c = parseInt(r.headers["content-length"]);
59
+ if (!isNaN(c))
60
+ res(c);
61
+ else
62
+ rej("Couldn't get file size");
63
+ });
64
+ req.once("error", (e) => rej(e));
65
+ });
66
+ }
67
+ export function sum(array) {
68
+ let sum = 0;
69
+ for (let i = 0; i < array.length; i++) {
70
+ sum += array[i];
71
+ }
72
+ return sum;
73
+ }
74
+ export async function getBufferAndNameFromUri(uri) {
75
+ let buffer;
76
+ let name;
77
+ const isUrl = /^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(uri);
78
+ if (isUrl) {
79
+ buffer = await urlToBuffer(uri);
80
+ const urlRegex = /\/([^/]+\.[^/.]+)$/;
81
+ const matches = uri.match(urlRegex);
82
+ if (matches) {
83
+ name = matches[1];
84
+ }
85
+ else {
86
+ name = "noname";
87
+ }
88
+ }
89
+ else {
90
+ uri = uri ?? ".";
91
+ const filepath = path.isAbsolute(uri) ? uri : path.join(process.cwd(), uri);
92
+ const stream = await fs.createReadStream(filepath);
93
+ buffer = await streamToBuffer(stream);
94
+ name = path.basename(filepath);
95
+ }
96
+ return { buffer, name };
97
+ }
98
+ export function stringIsAValidUrl(s) {
99
+ try {
100
+ new URL(s);
101
+ return true;
102
+ }
103
+ catch (err) {
104
+ return false;
105
+ }
106
+ }
107
+ export function getFileBufferFromUrl(url) {
108
+ if (stringIsAValidUrl(url)) {
109
+ return fetch(url)
110
+ .then((response) => response.arrayBuffer())
111
+ .then((ab) => Buffer.from(ab));
112
+ }
113
+ else {
114
+ const filepath = path.isAbsolute(url) ? url : path.join(process.cwd(), url);
115
+ return new Promise((resolve, reject) => {
116
+ fs.readFile(filepath, (err, data) => {
117
+ if (err)
118
+ reject(err);
119
+ resolve(data);
120
+ });
121
+ });
122
+ }
123
+ }
124
+ export function getNameFromUrl(uri) {
125
+ const urlRegex = /\/([^/]+\.[^/.]+)$/;
126
+ const matches = uri.match(urlRegex);
127
+ return matches ? matches[1] : "noname";
128
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@galacean/cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "author": {
@@ -16,7 +16,9 @@
16
16
  },
17
17
  "files": [
18
18
  "dist",
19
- "bin"
19
+ "bin",
20
+ "lib",
21
+ "types"
20
22
  ],
21
23
  "types": "types",
22
24
  "license": "MIT",
@@ -0,0 +1,7 @@
1
+ export declare class Markdown {
2
+ private _txt;
3
+ get txt(): string;
4
+ addLine(line: string, tab?: number): void;
5
+ generate(): void;
6
+ }
7
+ export declare const markdown: Markdown;
package/types/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare function diffProjects(project1: any, project2: any): Promise<void>;
@@ -0,0 +1 @@
1
+ export * from "./cli.js";
@@ -0,0 +1,15 @@
1
+ export type IVector3 = {
2
+ x: number;
3
+ y: number;
4
+ z: number;
5
+ };
6
+ export type IEntity = {
7
+ id: string;
8
+ name: string;
9
+ isActive: boolean;
10
+ position: IVector3;
11
+ rotation: IVector3;
12
+ scale: IVector3;
13
+ parent: string;
14
+ isClone: boolean;
15
+ };
@@ -0,0 +1,14 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ export declare function streamToBuffer(stream: any): Promise<Buffer>;
3
+ export declare function streamToString(stream: any): Promise<string>;
4
+ export declare function toBuffer(view: Uint8Array): Buffer;
5
+ export declare function urlToBuffer(url: string): Promise<Buffer>;
6
+ export declare function getUrlSize(url: string, attempts?: number): Promise<number>;
7
+ export declare function sum(array: ArrayLike<number>): number;
8
+ export declare function getBufferAndNameFromUri(uri: any): Promise<{
9
+ buffer: any;
10
+ name: any;
11
+ }>;
12
+ export declare function stringIsAValidUrl(s: string): boolean;
13
+ export declare function getFileBufferFromUrl(url: string): Promise<Buffer>;
14
+ export declare function getNameFromUrl(uri: string): string;