@ldsg/cli 0.3.38 → 0.3.40
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/.rush/temp/package-deps_build.json +36 -33
- package/.rush/temp/shrinkwrap-deps.json +4 -1
- package/.tsbuildinfo +1 -1
- package/CHANGELOG.json +46 -0
- package/CHANGELOG.md +15 -1
- package/README.md +87 -4
- package/dist/__tests__/commands.test.js +68 -12
- package/dist/__tests__/commands.test.js.map +1 -1
- package/dist/__tests__/formatter.test.js +18 -3
- package/dist/__tests__/formatter.test.js.map +1 -1
- package/dist/__tests__/generate-app-index-ts.test.d.ts +1 -0
- package/dist/__tests__/generate-app-index-ts.test.js +109 -0
- package/dist/__tests__/generate-app-index-ts.test.js.map +1 -0
- package/dist/__tests__/get-extend-app-data-params-using-workspace-ldsg-module.test.d.ts +1 -0
- package/dist/__tests__/get-extend-app-data-params-using-workspace-ldsg-module.test.js +81 -0
- package/dist/__tests__/get-extend-app-data-params-using-workspace-ldsg-module.test.js.map +1 -0
- package/dist/__tests__/json-file-mutator.test.js +54 -9
- package/dist/__tests__/json-file-mutator.test.js.map +1 -1
- package/dist/__tests__/mutate.test.d.ts +1 -0
- package/dist/__tests__/mutate.test.js +334 -0
- package/dist/__tests__/mutate.test.js.map +1 -0
- package/dist/cli.js +131 -50
- package/dist/cli.js.map +1 -1
- package/dist/commands/resource/create.js +1 -1
- package/dist/commands/resource/create.js.map +1 -1
- package/dist/commands/resource/list.js +1 -1
- package/dist/commands/resource/list.js.map +1 -1
- package/dist/commands/resource/mutate.d.ts +80 -0
- package/dist/commands/resource/mutate.js +311 -0
- package/dist/commands/resource/mutate.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +4 -1
- package/dist/constants.js.map +1 -1
- package/dist/types/get-extend-app-data-params.d.ts +4 -1
- package/dist/utils/get-extend-app-data-params-using-workspace-ldsg-module/index.js +22 -6
- package/dist/utils/get-extend-app-data-params-using-workspace-ldsg-module/index.js.map +1 -1
- package/jest.config.js +3 -4
- package/package.json +4 -5
- package/src/__tests__/commands.test.ts +68 -12
- package/src/__tests__/formatter.test.ts +18 -3
- package/src/__tests__/generate-app-index-ts.test.ts +141 -0
- package/src/__tests__/get-extend-app-data-params-using-workspace-ldsg-module.test.ts +109 -0
- package/src/__tests__/json-file-mutator.test.ts +54 -9
- package/src/__tests__/mutate.test.ts +392 -0
- package/src/cli.ts +179 -54
- package/src/commands/resource/create.ts +3 -3
- package/src/commands/resource/list.ts +1 -1
- package/src/commands/resource/mutate.ts +542 -0
- package/src/constants.ts +4 -0
- package/src/types/get-extend-app-data-params.ts +4 -1
- package/src/utils/get-extend-app-data-params-using-workspace-ldsg-module/index.ts +43 -7
- package/.rush/temp/chunked-rush-logs/cli.format.chunks.jsonl +0 -2
- package/.rush/temp/package-deps_format.json +0 -38
- package/dist/commands/resource/convert.d.ts +0 -11
- package/dist/commands/resource/convert.js +0 -44
- package/dist/commands/resource/convert.js.map +0 -1
- package/rush-logs/cli.format.log +0 -2
- package/src/commands/resource/convert.ts +0 -54
- package/tsconfig.build.tsbuildinfo +0 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import { generateAppIndexTs, ZipAppDataLayout, zipAppData } from "@ldsg/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* jszip 是 @ldsg/utils 的依赖,不在 @ldsg/cli 的依赖里;
|
|
6
|
+
* 这里直接复用 utils 的 node_modules,避免为测试引入额外依赖。
|
|
7
|
+
*/
|
|
8
|
+
const nodeRequire = createRequire(__filename);
|
|
9
|
+
const JSZip = nodeRequire("../../../../libs/utils/node_modules/jszip");
|
|
10
|
+
|
|
11
|
+
const sampleHandlerRecords = [
|
|
12
|
+
{
|
|
13
|
+
id: "application-resource-handler",
|
|
14
|
+
kind: "handler",
|
|
15
|
+
parentId: null,
|
|
16
|
+
settings: {
|
|
17
|
+
code: 'export * from "@ldsg/application";',
|
|
18
|
+
dependencies: [{ name: "@ldsg/application" }],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "my-handler-1",
|
|
23
|
+
kind: "handler",
|
|
24
|
+
parentId: null,
|
|
25
|
+
settings: {
|
|
26
|
+
code: 'export * from "@ldsg/handler";',
|
|
27
|
+
dependencies: [{ name: "@ldsg/handler" }],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const sampleResourceRecords = [
|
|
33
|
+
{
|
|
34
|
+
id: "application-resource-definition",
|
|
35
|
+
kind: "resource_definition",
|
|
36
|
+
parentId: "root",
|
|
37
|
+
settings: {
|
|
38
|
+
title: "应用资源定义",
|
|
39
|
+
kind: "application",
|
|
40
|
+
handlerResourceId: "application-resource-handler",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
...sampleHandlerRecords,
|
|
44
|
+
{
|
|
45
|
+
id: "main-app",
|
|
46
|
+
kind: "application",
|
|
47
|
+
parentId: "root",
|
|
48
|
+
settings: { title: "主要应用" },
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
describe("generateAppIndexTs", () => {
|
|
53
|
+
it("should generate NESTED handler import paths", () => {
|
|
54
|
+
const content = generateAppIndexTs({
|
|
55
|
+
handlerResourceRecords: sampleHandlerRecords as never,
|
|
56
|
+
getHandlerImportPath: (id) => `./handlers/${id}`,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(content).toContain(
|
|
60
|
+
'import * as handlerModule_application_resource_handler from "./handlers/application-resource-handler";',
|
|
61
|
+
);
|
|
62
|
+
expect(content).toContain(
|
|
63
|
+
'import * as handlerModule_my_handler_1 from "./handlers/my-handler-1";',
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should generate FLAT handler import paths", () => {
|
|
68
|
+
const content = generateAppIndexTs({
|
|
69
|
+
handlerResourceRecords: sampleHandlerRecords as never,
|
|
70
|
+
getHandlerImportPath: (id) => `../${id}`,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(content).toContain(
|
|
74
|
+
'import * as handlerModule_application_resource_handler from "../application-resource-handler";',
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should inject handler into the corresponding handler resource", () => {
|
|
79
|
+
const content = generateAppIndexTs({
|
|
80
|
+
handlerResourceRecords: sampleHandlerRecords as never,
|
|
81
|
+
getHandlerImportPath: (id) => `./handlers/${id}`,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(content).toContain(
|
|
85
|
+
'"application-resource-handler": handlerModule_application_resource_handler',
|
|
86
|
+
);
|
|
87
|
+
expect(content).toContain('"my-handler-1": handlerModule_my_handler_1');
|
|
88
|
+
expect(content).toContain('if (kind === "handler" && handlerModules[id])');
|
|
89
|
+
expect(content).toContain("handler: handlerModules[id].handler");
|
|
90
|
+
expect(content).toContain("resourceRecords: wiredResourceRecords");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should generate a valid base app when there are no handlers", () => {
|
|
94
|
+
const content = generateAppIndexTs({
|
|
95
|
+
handlerResourceRecords: [],
|
|
96
|
+
getHandlerImportPath: (id) => `./handlers/${id}`,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(content).not.toContain("import * as handlerModule_");
|
|
100
|
+
expect(content).toContain("const handlerModules: Record<string, any> = {");
|
|
101
|
+
expect(content).toContain("resourceRecords: wiredResourceRecords");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("zipAppData", () => {
|
|
106
|
+
it("should write a handler-aware app index.ts for NESTED layout", async () => {
|
|
107
|
+
const { archive } = await zipAppData({
|
|
108
|
+
resourceRecords: sampleResourceRecords as never,
|
|
109
|
+
type: "nodebuffer",
|
|
110
|
+
layout: ZipAppDataLayout.NESTED,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const zip = await JSZip.loadAsync(archive);
|
|
114
|
+
|
|
115
|
+
const indexTs = await zip.file("index.ts")?.async("string");
|
|
116
|
+
|
|
117
|
+
expect(indexTs).toBeTruthy();
|
|
118
|
+
expect(indexTs).toContain(
|
|
119
|
+
'import * as handlerModule_application_resource_handler from "./handlers/application-resource-handler";',
|
|
120
|
+
);
|
|
121
|
+
expect(indexTs).toContain("handler: handlerModules[id].handler");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should write a handler-aware app index.ts for FLAT layout", async () => {
|
|
125
|
+
const { archive } = await zipAppData({
|
|
126
|
+
resourceRecords: sampleResourceRecords as never,
|
|
127
|
+
type: "nodebuffer",
|
|
128
|
+
layout: ZipAppDataLayout.FLAT,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const zip = await JSZip.loadAsync(archive);
|
|
132
|
+
|
|
133
|
+
const indexTs = await zip.file("app/index.ts")?.async("string");
|
|
134
|
+
|
|
135
|
+
expect(indexTs).toBeTruthy();
|
|
136
|
+
expect(indexTs).toContain(
|
|
137
|
+
'import * as handlerModule_application_resource_handler from "../application-resource-handler";',
|
|
138
|
+
);
|
|
139
|
+
expect(indexTs).toContain("handler: handlerModules[id].handler");
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { AppData } from "@ldsg/types";
|
|
2
|
+
import { getExtendAppDataParamsUsingWorkspaceLdsgModule } from "../utils/get-extend-app-data-params-using-workspace-ldsg-module";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 拦截 syncRushProjects,避免测试写入真实 rush.json
|
|
6
|
+
*/
|
|
7
|
+
jest.mock("@ldsg/utils", () => {
|
|
8
|
+
const actual = jest.requireActual("@ldsg/utils");
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
...actual,
|
|
12
|
+
RUSH_JSON_PATH: "/mock/rush.json",
|
|
13
|
+
syncRushProjects: jest.fn(),
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
import { syncRushProjects, ZipAppDataLayout } from "@ldsg/utils";
|
|
18
|
+
|
|
19
|
+
const mockedSyncRushProjects = syncRushProjects as jest.Mock;
|
|
20
|
+
|
|
21
|
+
const handlerId = "handler-1";
|
|
22
|
+
|
|
23
|
+
const appData: AppData = {
|
|
24
|
+
resourceRecords: [
|
|
25
|
+
{
|
|
26
|
+
id: handlerId,
|
|
27
|
+
kind: "handler",
|
|
28
|
+
parentId: "root",
|
|
29
|
+
settings: {
|
|
30
|
+
slug: "handler-1",
|
|
31
|
+
title: "应用实例化资源处理程序",
|
|
32
|
+
description: "",
|
|
33
|
+
code: 'export * from "@ldsg/application";',
|
|
34
|
+
dependencies: [
|
|
35
|
+
{ name: "@ldsg/application", version: "^1.0.0" },
|
|
36
|
+
{ name: "lodash", version: "^4.17.21" },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
describe("getExtendAppDataParamsUsingWorkspaceLdsgModule", () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
mockedSyncRushProjects.mockClear();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("NESTED 布局:app 与 handler 项目文件夹按嵌套结构计算", async () => {
|
|
49
|
+
const res = await getExtendAppDataParamsUsingWorkspaceLdsgModule({
|
|
50
|
+
appData,
|
|
51
|
+
layout: ZipAppDataLayout.NESTED,
|
|
52
|
+
outputPath: "../../apps/rrm",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(mockedSyncRushProjects).toHaveBeenCalledWith("/mock/rush.json", [
|
|
56
|
+
{ packageName: "@ldsg/app", projectFolder: "apps/rrm" },
|
|
57
|
+
{ packageName: handlerId, projectFolder: "apps/rrm/handlers/handler-1" },
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const packageJsonFile = res.extraAppData.filesInAppModele.find(
|
|
61
|
+
(file) => file.path === "package.json",
|
|
62
|
+
);
|
|
63
|
+
expect(packageJsonFile).toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("FLAT 布局(默认):app 在输出根目录 app/ 下,handler 平铺", async () => {
|
|
67
|
+
await getExtendAppDataParamsUsingWorkspaceLdsgModule({
|
|
68
|
+
appData,
|
|
69
|
+
outputPath: "../../generated",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(mockedSyncRushProjects).toHaveBeenCalledWith("/mock/rush.json", [
|
|
73
|
+
{ packageName: "@ldsg/app", projectFolder: "generated/app" },
|
|
74
|
+
{ packageName: handlerId, projectFolder: "generated/handler-1" },
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("NESTED 布局:app 包名使用传入的 appPackageName,且 @ldsg 依赖改为 workspace:*", async () => {
|
|
79
|
+
const res = await getExtendAppDataParamsUsingWorkspaceLdsgModule({
|
|
80
|
+
appData,
|
|
81
|
+
layout: ZipAppDataLayout.NESTED,
|
|
82
|
+
outputPath: "../../apps/rrm",
|
|
83
|
+
appPackageName: "@ldsg/rrm",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const packageJsonFile = res.extraAppData.filesInAppModele.find(
|
|
87
|
+
(file) => file.path === "package.json",
|
|
88
|
+
);
|
|
89
|
+
expect(packageJsonFile).toBeDefined();
|
|
90
|
+
expect((packageJsonFile as { data: { name: string } }).data.name).toBe("@ldsg/rrm");
|
|
91
|
+
|
|
92
|
+
const handlerRecord = res.appData.resourceRecords.find((record) => record.id === handlerId);
|
|
93
|
+
expect(handlerRecord).toBeDefined();
|
|
94
|
+
expect(
|
|
95
|
+
(
|
|
96
|
+
handlerRecord as unknown as {
|
|
97
|
+
settings: { dependencies: { name: string; version: string }[] };
|
|
98
|
+
}
|
|
99
|
+
).settings.dependencies,
|
|
100
|
+
).toEqual(
|
|
101
|
+
expect.arrayContaining([
|
|
102
|
+
{ name: "@ldsg/application", version: "workspace:*" },
|
|
103
|
+
{ name: "lodash", version: "^4.17.21" },
|
|
104
|
+
{ name: "@types/node", version: "^24.1.0" },
|
|
105
|
+
{ name: "typescript", version: "^5.3.3" },
|
|
106
|
+
]),
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -9,10 +9,30 @@ const mockedFs = fs as jest.Mocked<typeof fs>;
|
|
|
9
9
|
|
|
10
10
|
describe("json-file-mutator", () => {
|
|
11
11
|
const arrayRecords: ResourceRecords = [
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
{
|
|
13
|
+
id: "1",
|
|
14
|
+
kind: "application",
|
|
15
|
+
parentId: null,
|
|
16
|
+
settings: { slug: "app1", title: "App1", description: "" },
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "2",
|
|
20
|
+
kind: "form",
|
|
21
|
+
parentId: "1",
|
|
22
|
+
settings: { slug: "form1", title: "Form1", description: "" },
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "3",
|
|
26
|
+
kind: "table",
|
|
27
|
+
parentId: "1",
|
|
28
|
+
settings: { slug: "table1", title: "Table1", description: "" },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "4",
|
|
32
|
+
kind: "field",
|
|
33
|
+
parentId: "3",
|
|
34
|
+
settings: { slug: "field1", title: "Field1", description: "" },
|
|
35
|
+
},
|
|
16
36
|
];
|
|
17
37
|
|
|
18
38
|
const arraySourceData: SourceData = {
|
|
@@ -49,7 +69,12 @@ describe("json-file-mutator", () => {
|
|
|
49
69
|
it("should append a new record to array format", async () => {
|
|
50
70
|
mockedFs.readJson.mockResolvedValue([...arrayRecords]);
|
|
51
71
|
|
|
52
|
-
const newRecord = {
|
|
72
|
+
const newRecord = {
|
|
73
|
+
id: "5",
|
|
74
|
+
kind: "form",
|
|
75
|
+
parentId: "1",
|
|
76
|
+
settings: { slug: "form2", title: "Form2", description: "" },
|
|
77
|
+
};
|
|
53
78
|
const result = await appendRecord(arraySourceData, newRecord);
|
|
54
79
|
|
|
55
80
|
expect(result.record.id).toBe("5");
|
|
@@ -64,7 +89,12 @@ describe("json-file-mutator", () => {
|
|
|
64
89
|
it("should append to object format preserving structure", async () => {
|
|
65
90
|
mockedFs.readJson.mockResolvedValue({ resourceRecords: [...arrayRecords] });
|
|
66
91
|
|
|
67
|
-
const newRecord = {
|
|
92
|
+
const newRecord = {
|
|
93
|
+
id: "5",
|
|
94
|
+
kind: "form",
|
|
95
|
+
parentId: "1",
|
|
96
|
+
settings: { slug: "form2", title: "Form2", description: "" },
|
|
97
|
+
};
|
|
68
98
|
await appendRecord(objectSourceData, newRecord);
|
|
69
99
|
|
|
70
100
|
expect(mockedFs.writeJson).toHaveBeenCalledWith(
|
|
@@ -77,12 +107,22 @@ describe("json-file-mutator", () => {
|
|
|
77
107
|
});
|
|
78
108
|
|
|
79
109
|
it("should throw on duplicate id", async () => {
|
|
80
|
-
const dupRecord = {
|
|
110
|
+
const dupRecord = {
|
|
111
|
+
id: "1",
|
|
112
|
+
kind: "application",
|
|
113
|
+
parentId: null,
|
|
114
|
+
settings: { slug: "app1", title: "App1", description: "" },
|
|
115
|
+
};
|
|
81
116
|
await expect(appendRecord(arraySourceData, dupRecord)).rejects.toThrow("already exists");
|
|
82
117
|
});
|
|
83
118
|
|
|
84
119
|
it("should not write file in dry-run mode", async () => {
|
|
85
|
-
const newRecord = {
|
|
120
|
+
const newRecord = {
|
|
121
|
+
id: "5",
|
|
122
|
+
kind: "form",
|
|
123
|
+
parentId: "1",
|
|
124
|
+
settings: { slug: "form2", title: "Form2", description: "" },
|
|
125
|
+
};
|
|
86
126
|
await appendRecord(arraySourceData, newRecord, true);
|
|
87
127
|
|
|
88
128
|
expect(mockedFs.writeJson).not.toHaveBeenCalled();
|
|
@@ -98,7 +138,12 @@ describe("json-file-mutator", () => {
|
|
|
98
138
|
newField: "value",
|
|
99
139
|
});
|
|
100
140
|
|
|
101
|
-
expect(result.record.settings).toEqual({
|
|
141
|
+
expect(result.record.settings).toEqual({
|
|
142
|
+
slug: "app1",
|
|
143
|
+
title: "Updated App",
|
|
144
|
+
description: "",
|
|
145
|
+
newField: "value",
|
|
146
|
+
});
|
|
102
147
|
expect(mockedFs.writeJson).toHaveBeenCalled();
|
|
103
148
|
});
|
|
104
149
|
|