@ldsg/cli 0.3.43 → 0.3.44
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 +15 -18
- package/.rush/temp/shrinkwrap-deps.json +1 -1
- package/.tsbuildinfo +1 -1
- package/CHANGELOG.json +23 -0
- package/CHANGELOG.md +8 -1
- package/README.md +3 -3
- package/dist/__tests__/bootstrap-generator.test.js +120 -0
- package/dist/__tests__/bootstrap-generator.test.js.map +1 -0
- package/dist/__tests__/generate-bootstrap.test.d.ts +1 -0
- package/dist/__tests__/generate-bootstrap.test.js +97 -0
- package/dist/__tests__/generate-bootstrap.test.js.map +1 -0
- package/dist/__tests__/resolve-module-builder.test.d.ts +1 -0
- package/dist/__tests__/resolve-module-builder.test.js +83 -0
- package/dist/__tests__/resolve-module-builder.test.js.map +1 -0
- package/dist/__tests__/resolve-root.test.d.ts +1 -0
- package/dist/__tests__/resolve-root.test.js +32 -0
- package/dist/__tests__/resolve-root.test.js.map +1 -0
- package/dist/cli.js +4 -431
- package/dist/cli.js.map +1 -1
- package/dist/commands/generate/index.d.ts +2 -0
- package/dist/commands/generate/index.js +35 -0
- package/dist/commands/generate/index.js.map +1 -0
- package/dist/commands/generate/types/generate.d.ts +12 -0
- package/dist/commands/generate/types/generate.js +3 -0
- package/dist/commands/generate/types/generate.js.map +1 -0
- package/dist/commands/generate/types/generator.d.ts +4 -0
- package/dist/commands/generate/types/generator.js +3 -0
- package/dist/commands/generate/types/generator.js.map +1 -0
- package/dist/commands/generate/types/index.d.ts +2 -0
- package/dist/commands/generate/types/index.js +19 -0
- package/dist/commands/generate/types/index.js.map +1 -0
- package/dist/commands/generate/types.d.ts +8 -0
- package/dist/{types/get-extend-app-data-params.js → commands/generate/types.js} +1 -1
- package/dist/commands/generate/types.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/index.d.ts +6 -0
- package/dist/commands/generate/utils/generate-application/index.js +28 -0
- package/dist/commands/generate/utils/generate-application/index.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/types/generator.d.ts +9 -0
- package/dist/commands/generate/utils/generate-application/types/generator.js +3 -0
- package/dist/commands/generate/utils/generate-application/types/generator.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/types/index.d.ts +1 -0
- package/dist/commands/generate/utils/generate-application/types/index.js +18 -0
- package/dist/commands/generate/utils/generate-application/types/index.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/build-app-generator/index.d.ts +9 -0
- package/dist/commands/generate/utils/generate-application/utils/build-app-generator/index.js +30 -0
- package/dist/commands/generate/utils/generate-application/utils/build-app-generator/index.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/index.d.ts +13 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/index.js +125 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/index.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/constants.d.ts +3 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/constants.js +7 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/constants.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/index.d.ts +4 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/index.js +69 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/index.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/prepare-bootstrap-app-files.d.ts +3 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/prepare-bootstrap-app-files.js +32 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/prepare-bootstrap-app-files.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/render-bootstrap-entry.d.ts +2 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/render-bootstrap-entry.js +62 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/render-bootstrap-entry.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/resolve-dependencies.d.ts +11 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/resolve-dependencies.js +84 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/generate-bootstrap/resolve-dependencies.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/resource-record/get-resource-record-by-id.d.ts +2 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/resource-record/get-resource-record-by-id.js +15 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/resource-record/get-resource-record-by-id.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/resource-record/resolve-resource-definition-resource-record.d.ts +2 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/resource-record/resolve-resource-definition-resource-record.js +21 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/resource-record/resolve-resource-definition-resource-record.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/resource-record/resolve-root-resource-record.d.ts +2 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/resource-record/resolve-root-resource-record.js +18 -0
- package/dist/commands/generate/utils/generate-application/utils/build-bootstrap/utils/resource-record/resolve-root-resource-record.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/build-meta-generator/index.d.ts +8 -0
- package/dist/commands/generate/utils/generate-application/utils/build-meta-generator/index.js +32 -0
- package/dist/commands/generate/utils/generate-application/utils/build-meta-generator/index.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/generate-app/index.d.ts +8 -0
- package/dist/commands/generate/utils/generate-application/utils/generate-app/index.js +10 -0
- package/dist/commands/generate/utils/generate-application/utils/generate-app/index.js.map +1 -0
- package/dist/commands/generate/utils/generate-application/utils/index.d.ts +3 -0
- package/dist/commands/generate/utils/generate-application/utils/index.js +20 -0
- package/dist/commands/generate/utils/generate-application/utils/index.js.map +1 -0
- package/dist/commands/generate/utils/parse-extra-options.d.ts +5 -0
- package/dist/commands/generate/utils/parse-extra-options.js +59 -0
- package/dist/commands/generate/utils/parse-extra-options.js.map +1 -0
- package/dist/commands/resource/index.d.ts +2 -0
- package/dist/commands/resource/index.js +392 -0
- package/dist/commands/resource/index.js.map +1 -0
- package/dist/generator/bootstrap-generator.d.ts +19 -0
- package/dist/generator/bootstrap-generator.js +59 -0
- package/dist/generator/bootstrap-generator.js.map +1 -0
- package/dist/generator/module-builder.d.ts +2 -0
- package/dist/generator/module-builder.js +284 -0
- package/dist/generator/module-builder.js.map +1 -0
- package/dist/generator/resolve-module-builder.d.ts +8 -0
- package/dist/generator/resolve-module-builder.js +41 -0
- package/dist/generator/resolve-module-builder.js.map +1 -0
- package/dist/generator/resolve-root.d.ts +5 -0
- package/dist/generator/resolve-root.js +48 -0
- package/dist/generator/resolve-root.js.map +1 -0
- package/jest.config.js +4 -1
- package/package.json +5 -5
- package/src/cli.ts +4 -510
- package/src/commands/generate/index.ts +54 -0
- package/src/commands/generate/utils/generate-application/index.ts +50 -0
- package/src/commands/generate/utils/generate-application/utils/build-app-generator/index.ts +39 -0
- package/src/commands/generate/utils/generate-application/utils/build-meta-generator/index.ts +45 -0
- package/src/commands/generate/utils/generate-application/utils/generate-app/index.ts +17 -0
- package/src/commands/generate/utils/generate-application/utils/index.ts +3 -0
- package/src/commands/generate/utils/parse-extra-options.ts +80 -0
- package/src/commands/resource/index.ts +455 -0
- package/dist/__tests__/get-extend-app-data-params-using-workspace-ldsg-module.test.js +0 -81
- package/dist/__tests__/get-extend-app-data-params-using-workspace-ldsg-module.test.js.map +0 -1
- package/dist/types/get-extend-app-data-params.d.ts +0 -10
- package/dist/types/get-extend-app-data-params.js.map +0 -1
- package/dist/utils/get-extend-app-data-params-using-npm-ldsg-module.d.ts +0 -2
- package/dist/utils/get-extend-app-data-params-using-npm-ldsg-module.js +0 -27
- package/dist/utils/get-extend-app-data-params-using-npm-ldsg-module.js.map +0 -1
- package/dist/utils/get-extend-app-data-params-using-workspace-ldsg-module/index.d.ts +0 -2
- package/dist/utils/get-extend-app-data-params-using-workspace-ldsg-module/index.js +0 -98
- package/dist/utils/get-extend-app-data-params-using-workspace-ldsg-module/index.js.map +0 -1
- package/src/__tests__/add-pnpm-overrides.test.ts +0 -91
- package/src/__tests__/commands.test.ts +0 -318
- package/src/__tests__/formatter.test.ts +0 -151
- package/src/__tests__/generate-app-index-ts.test.ts +0 -141
- package/src/__tests__/get-extend-app-data-params-using-workspace-ldsg-module.test.ts +0 -109
- package/src/__tests__/json-file-mutator.test.ts +0 -186
- package/src/__tests__/mutate.test.ts +0 -392
- package/src/__tests__/source-resolver.test.ts +0 -125
- package/src/types/get-extend-app-data-params.ts +0 -13
- package/src/utils/get-extend-app-data-params-using-npm-ldsg-module.ts +0 -34
- package/src/utils/get-extend-app-data-params-using-workspace-ldsg-module/index.ts +0 -159
- /package/dist/__tests__/{get-extend-app-data-params-using-workspace-ldsg-module.test.d.ts → bootstrap-generator.test.d.ts} +0 -0
package/src/cli.ts
CHANGED
|
@@ -1,39 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
import { AppData } from "@ldsg/types";
|
|
5
|
-
import {
|
|
6
|
-
extendAppData,
|
|
7
|
-
ExtendAppDataParams,
|
|
8
|
-
loadAppResourceRecords,
|
|
9
|
-
prepare,
|
|
10
|
-
ZipAppDataLayout,
|
|
11
|
-
} from "@ldsg/utils";
|
|
12
4
|
import { Command } from "commander";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { getExtendAppDataParamsUsingWorkspaceLdsgModule } from "./utils/get-extend-app-data-params-using-workspace-ldsg-module";
|
|
16
|
-
import { resolveSource, isStdinSource, getSourceDescription } from "./utils/source-resolver";
|
|
17
|
-
import {
|
|
18
|
-
formatSuccess,
|
|
19
|
-
formatError,
|
|
20
|
-
formatJson,
|
|
21
|
-
formatPrettyList,
|
|
22
|
-
formatPrettyTree,
|
|
23
|
-
formatPrettyDiff,
|
|
24
|
-
} from "./utils/formatter";
|
|
25
|
-
import { createResource } from "./commands/resource/create";
|
|
26
|
-
import { updateResource } from "./commands/resource/update";
|
|
27
|
-
import { deleteResource } from "./commands/resource/delete";
|
|
28
|
-
import { listResources, getTree } from "./commands/resource/list";
|
|
29
|
-
import { getResource } from "./commands/resource/get";
|
|
30
|
-
import { diffResource } from "./commands/resource/diff";
|
|
31
|
-
import {
|
|
32
|
-
mutateRecordsDirect,
|
|
33
|
-
mutateResources,
|
|
34
|
-
pullRemoteResourceRecords,
|
|
35
|
-
purifyResourceRecord,
|
|
36
|
-
} from "./commands/resource/mutate";
|
|
5
|
+
import { registerGenerateCommand } from "./commands/generate";
|
|
6
|
+
import { registerResourceCommand } from "./commands/resource";
|
|
37
7
|
|
|
38
8
|
const { version } = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
39
9
|
|
|
@@ -41,483 +11,7 @@ const program = new Command();
|
|
|
41
11
|
|
|
42
12
|
program.name("ldsg").description("LDSG CLI tools").version(version);
|
|
43
13
|
|
|
44
|
-
program
|
|
45
|
-
|
|
46
|
-
.aliases(["g", "gen"])
|
|
47
|
-
.description("Generate standalone application from resource records")
|
|
48
|
-
.option("--app-data-uri <uri>", "runtime app data URI")
|
|
49
|
-
.option(
|
|
50
|
-
"--app-data-expression <expression>",
|
|
51
|
-
"runtime app data expression (jsonata), used to convert source data to standard ldsg resource records",
|
|
52
|
-
)
|
|
53
|
-
.option("--using-workspace-ldsg-module", "use workspace ldsg module mode")
|
|
54
|
-
.option(
|
|
55
|
-
"--layout <layout>",
|
|
56
|
-
`app layout: nested or flat (default: flat for workspace mode, nested otherwise)`,
|
|
57
|
-
)
|
|
58
|
-
.option("--app-package-name <name>", "app package name (default: @ldsg/app)")
|
|
59
|
-
.option(
|
|
60
|
-
"-o, --output <path>",
|
|
61
|
-
`output directory for prepared app code (default: ${DEFAULT_OUTPUT_PATH})`,
|
|
62
|
-
)
|
|
63
|
-
.action(async (options) => {
|
|
64
|
-
const outputPath = options.output || DEFAULT_OUTPUT_PATH;
|
|
65
|
-
|
|
66
|
-
const usingWorkspaceLdsgModule = options.usingWorkspaceLdsgModule ?? false;
|
|
67
|
-
|
|
68
|
-
const layout =
|
|
69
|
-
options.layout === "nested"
|
|
70
|
-
? ZipAppDataLayout.NESTED
|
|
71
|
-
: options.layout === "flat"
|
|
72
|
-
? ZipAppDataLayout.FLAT
|
|
73
|
-
: undefined;
|
|
74
|
-
if (options.layout && layout === undefined) {
|
|
75
|
-
throw new Error(`invalid layout: ${options.layout} (expected nested or flat)`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const { resourceRecords } = await loadAppResourceRecords({
|
|
79
|
-
appDataUri: options.appDataUri,
|
|
80
|
-
appDataExpression: options.appDataExpression,
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const appData: AppData = { resourceRecords };
|
|
84
|
-
|
|
85
|
-
let extendAppDataParams: ExtendAppDataParams;
|
|
86
|
-
|
|
87
|
-
if (usingWorkspaceLdsgModule) {
|
|
88
|
-
extendAppDataParams = await getExtendAppDataParamsUsingWorkspaceLdsgModule({
|
|
89
|
-
appData,
|
|
90
|
-
...(layout ? { layout } : {}),
|
|
91
|
-
...(options.appPackageName ? { appPackageName: options.appPackageName } : {}),
|
|
92
|
-
outputPath,
|
|
93
|
-
});
|
|
94
|
-
} else {
|
|
95
|
-
extendAppDataParams = await getExtendAppDataParamsUsingNpmLdsgModule({ appData });
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const extendAppDataRes = extendAppData(extendAppDataParams);
|
|
99
|
-
|
|
100
|
-
await prepare({
|
|
101
|
-
...extendAppDataRes,
|
|
102
|
-
outputPath,
|
|
103
|
-
...(layout ? { layout } : usingWorkspaceLdsgModule ? { layout: ZipAppDataLayout.FLAT } : {}),
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const resource = program.command("resource").aliases(["r"]).description("Manage resource records");
|
|
108
|
-
|
|
109
|
-
resource
|
|
110
|
-
.command("list")
|
|
111
|
-
.description("List resource records by filter")
|
|
112
|
-
.option("--source <path>", `data source (default: ${DEFAULT_SOURCE_PATH})`)
|
|
113
|
-
.option("--kind <kind>", "filter by resource kind")
|
|
114
|
-
.option("--parent-id <id>", "filter by parent id")
|
|
115
|
-
.option("--root-id <id>", "filter by root resource id")
|
|
116
|
-
.option("--pretty", "human-readable output")
|
|
117
|
-
.action(async (options) => {
|
|
118
|
-
try {
|
|
119
|
-
const sourceDesc = getSourceDescription(options.source);
|
|
120
|
-
const sourceData = await resolveSource(options.source);
|
|
121
|
-
const { total, resources } = await listResources(sourceData.resourceRecords, {
|
|
122
|
-
kind: options.kind,
|
|
123
|
-
parentId: options.parentId,
|
|
124
|
-
rootId: options.rootId,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
if (options.pretty) {
|
|
128
|
-
console.log(
|
|
129
|
-
formatPrettyList(
|
|
130
|
-
resources.map((r) => ({
|
|
131
|
-
id: r.id,
|
|
132
|
-
kind: r.kind,
|
|
133
|
-
parentId: r.parentId,
|
|
134
|
-
title: (r.settings as Record<string, unknown>)?.title as string | undefined,
|
|
135
|
-
})),
|
|
136
|
-
),
|
|
137
|
-
);
|
|
138
|
-
} else {
|
|
139
|
-
console.log(formatJson(formatSuccess({ source: sourceDesc, total, resources })));
|
|
140
|
-
}
|
|
141
|
-
} catch (error) {
|
|
142
|
-
console.error(formatJson(formatError("LIST_ERROR", (error as Error).message)));
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
resource
|
|
148
|
-
.command("get <id>")
|
|
149
|
-
.description("Get a resource record by id")
|
|
150
|
-
.option("--source <path>", `data source (default: ${DEFAULT_SOURCE_PATH})`)
|
|
151
|
-
.option("--children", "include direct children")
|
|
152
|
-
.option("--tree", "include full subtree")
|
|
153
|
-
.option("--pretty", "human-readable output")
|
|
154
|
-
.action(async (id, options) => {
|
|
155
|
-
try {
|
|
156
|
-
const sourceDesc = getSourceDescription(options.source);
|
|
157
|
-
const sourceData = await resolveSource(options.source);
|
|
158
|
-
const record = await getResource(sourceData.resourceRecords, id, {
|
|
159
|
-
children: options.children,
|
|
160
|
-
tree: options.tree,
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
if (!record) {
|
|
164
|
-
console.error(formatJson(formatError("NOT_FOUND", `Resource with id "${id}" not found`)));
|
|
165
|
-
process.exit(1);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (options.pretty) {
|
|
169
|
-
console.log(formatJson(record));
|
|
170
|
-
} else {
|
|
171
|
-
console.log(formatJson(formatSuccess({ source: sourceDesc, record })));
|
|
172
|
-
}
|
|
173
|
-
} catch (error) {
|
|
174
|
-
console.error(formatJson(formatError("GET_ERROR", (error as Error).message)));
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
resource
|
|
180
|
-
.command("tree [root-id]")
|
|
181
|
-
.description("Display resource tree")
|
|
182
|
-
.option("--source <path>", `data source (default: ${DEFAULT_SOURCE_PATH})`)
|
|
183
|
-
.option("--show-kind", "show resource kind prefix")
|
|
184
|
-
.option("--pretty", "human-readable output")
|
|
185
|
-
.action(async (rootId, options) => {
|
|
186
|
-
try {
|
|
187
|
-
const sourceDesc = getSourceDescription(options.source);
|
|
188
|
-
const sourceData = await resolveSource(options.source);
|
|
189
|
-
const result = await getTree(sourceData.resourceRecords, rootId, options.showKind);
|
|
190
|
-
|
|
191
|
-
if (options.pretty) {
|
|
192
|
-
console.log(formatPrettyTree(result.tree as never, options.showKind ?? false));
|
|
193
|
-
} else {
|
|
194
|
-
console.log(formatJson(formatSuccess({ source: sourceDesc, ...result })));
|
|
195
|
-
}
|
|
196
|
-
} catch (error) {
|
|
197
|
-
console.error(formatJson(formatError("TREE_ERROR", (error as Error).message)));
|
|
198
|
-
process.exit(1);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
resource
|
|
203
|
-
.command("create")
|
|
204
|
-
.description("Create a new resource record")
|
|
205
|
-
.option("--source <path>", `data source (default: ${DEFAULT_SOURCE_PATH})`)
|
|
206
|
-
.option("--kind <kind>", "resource kind")
|
|
207
|
-
.option("--parent-id <id>", "parent resource id")
|
|
208
|
-
.option("--settings <json>", "resource settings as JSON string")
|
|
209
|
-
.option("--from-file <path>", "read full resource record from JSON file")
|
|
210
|
-
.option("--from-stdin", "read full resource record from stdin")
|
|
211
|
-
.option("--dry-run", "preview without writing")
|
|
212
|
-
.action(async (options) => {
|
|
213
|
-
try {
|
|
214
|
-
if (isStdinSource(options.source)) {
|
|
215
|
-
console.error(
|
|
216
|
-
formatJson(
|
|
217
|
-
formatError("STDIN_NOT_WRITABLE", "stdin source does not support write operations"),
|
|
218
|
-
),
|
|
219
|
-
);
|
|
220
|
-
process.exit(1);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const sourceData = await resolveSource(options.source);
|
|
224
|
-
|
|
225
|
-
let fromFileRecord: Record<string, unknown> | undefined;
|
|
226
|
-
|
|
227
|
-
if (options.fromFile) {
|
|
228
|
-
const fs = await import("fs-extra");
|
|
229
|
-
fromFileRecord = await fs.readJson(options.fromFile);
|
|
230
|
-
} else if (options.fromStdin) {
|
|
231
|
-
const chunks: Buffer[] = [];
|
|
232
|
-
process.stdin.setEncoding("utf-8");
|
|
233
|
-
for await (const chunk of process.stdin) {
|
|
234
|
-
chunks.push(Buffer.from(chunk));
|
|
235
|
-
}
|
|
236
|
-
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
237
|
-
fromFileRecord = JSON.parse(raw);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
let settings: Record<string, unknown> | undefined;
|
|
241
|
-
if (options.settings) {
|
|
242
|
-
settings = JSON.parse(options.settings);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const { record } = await createResource({
|
|
246
|
-
sourceData,
|
|
247
|
-
kind: options.kind,
|
|
248
|
-
parentId: options.parentId,
|
|
249
|
-
settings,
|
|
250
|
-
fromFileRecord: fromFileRecord as never,
|
|
251
|
-
dryRun: options.dryRun,
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
console.log(formatJson(formatSuccess({ record, dryRun: options.dryRun ?? false })));
|
|
255
|
-
} catch (error) {
|
|
256
|
-
console.error(formatJson(formatError("CREATE_ERROR", (error as Error).message)));
|
|
257
|
-
process.exit(1);
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
resource
|
|
262
|
-
.command("update <id>")
|
|
263
|
-
.description("Update a resource record")
|
|
264
|
-
.option("--source <path>", `data source (default: ${DEFAULT_SOURCE_PATH})`)
|
|
265
|
-
.option("--settings <json>", "resource settings to merge (JSON string, required)")
|
|
266
|
-
.option("--dry-run", "preview without writing")
|
|
267
|
-
.action(async (id, options) => {
|
|
268
|
-
try {
|
|
269
|
-
if (isStdinSource(options.source)) {
|
|
270
|
-
console.error(
|
|
271
|
-
formatJson(
|
|
272
|
-
formatError("STDIN_NOT_WRITABLE", "stdin source does not support write operations"),
|
|
273
|
-
),
|
|
274
|
-
);
|
|
275
|
-
process.exit(1);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const sourceData = await resolveSource(options.source);
|
|
279
|
-
|
|
280
|
-
if (!options.settings) {
|
|
281
|
-
console.error(
|
|
282
|
-
formatJson(formatError("MISSING_SETTINGS", "Required option: --settings <json>")),
|
|
283
|
-
);
|
|
284
|
-
process.exit(1);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const settings = JSON.parse(options.settings);
|
|
288
|
-
const { record } = await updateResource({
|
|
289
|
-
sourceData,
|
|
290
|
-
id,
|
|
291
|
-
settings,
|
|
292
|
-
dryRun: options.dryRun,
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
console.log(formatJson(formatSuccess({ record, dryRun: options.dryRun ?? false })));
|
|
296
|
-
} catch (error) {
|
|
297
|
-
console.error(formatJson(formatError("UPDATE_ERROR", (error as Error).message)));
|
|
298
|
-
process.exit(1);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
resource
|
|
303
|
-
.command("delete <id>")
|
|
304
|
-
.description("Delete a resource record and its children")
|
|
305
|
-
.option("--source <path>", `data source (default: ${DEFAULT_SOURCE_PATH})`)
|
|
306
|
-
.option("--dry-run", "preview deletion scope without writing")
|
|
307
|
-
.action(async (id, options) => {
|
|
308
|
-
try {
|
|
309
|
-
if (isStdinSource(options.source)) {
|
|
310
|
-
console.error(
|
|
311
|
-
formatJson(
|
|
312
|
-
formatError("STDIN_NOT_WRITABLE", "stdin source does not support write operations"),
|
|
313
|
-
),
|
|
314
|
-
);
|
|
315
|
-
process.exit(1);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const sourceData = await resolveSource(options.source);
|
|
319
|
-
const { removed } = await deleteResource({
|
|
320
|
-
sourceData,
|
|
321
|
-
id,
|
|
322
|
-
dryRun: options.dryRun,
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
console.log(
|
|
326
|
-
formatJson(
|
|
327
|
-
formatSuccess({
|
|
328
|
-
dryRun: options.dryRun ?? false,
|
|
329
|
-
removedCount: removed.length,
|
|
330
|
-
removed: removed.map((r) => ({ id: r.id, kind: r.kind })),
|
|
331
|
-
}),
|
|
332
|
-
),
|
|
333
|
-
);
|
|
334
|
-
} catch (error) {
|
|
335
|
-
console.error(formatJson(formatError("DELETE_ERROR", (error as Error).message)));
|
|
336
|
-
process.exit(1);
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
resource
|
|
341
|
-
.command("diff <id>")
|
|
342
|
-
.description("Diff a resource between two sources")
|
|
343
|
-
.option("--source-a <path>", "first source file (required)")
|
|
344
|
-
.option("--source-b <path>", "second source file (required)")
|
|
345
|
-
.option("--pretty", "human-readable output")
|
|
346
|
-
.action(async (id, options) => {
|
|
347
|
-
try {
|
|
348
|
-
if (!options.sourceA || !options.sourceB) {
|
|
349
|
-
console.error(
|
|
350
|
-
formatJson(formatError("MISSING_ARGS", "Required options: --source-a, --source-b")),
|
|
351
|
-
);
|
|
352
|
-
process.exit(1);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const sourceDataA = await resolveSource(options.sourceA);
|
|
356
|
-
const sourceDataB = await resolveSource(options.sourceB);
|
|
357
|
-
|
|
358
|
-
const result = await diffResource({
|
|
359
|
-
sourceA: getSourceDescription(options.sourceA),
|
|
360
|
-
sourceB: getSourceDescription(options.sourceB),
|
|
361
|
-
id,
|
|
362
|
-
recordsA: sourceDataA.resourceRecords,
|
|
363
|
-
recordsB: sourceDataB.resourceRecords,
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
if (options.pretty) {
|
|
367
|
-
console.log(formatPrettyDiff(result));
|
|
368
|
-
} else {
|
|
369
|
-
console.log(formatJson(formatSuccess(result)));
|
|
370
|
-
}
|
|
371
|
-
} catch (error) {
|
|
372
|
-
console.error(formatJson(formatError("DIFF_ERROR", (error as Error).message)));
|
|
373
|
-
process.exit(1);
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
resource
|
|
378
|
-
.command("pull")
|
|
379
|
-
.description("Pull resource records from RRM to a local file (read-only)")
|
|
380
|
-
.option("--endpoint <url>", "RRM GraphQL endpoint")
|
|
381
|
-
.option("--path <regex>", "scope remote records to a path regex (e.g. application id)")
|
|
382
|
-
.option("-o, --output <path>", `output file (default: ${DEFAULT_SOURCE_PATH})`)
|
|
383
|
-
.action(async (options) => {
|
|
384
|
-
try {
|
|
385
|
-
const result = await pullRemoteResourceRecords({
|
|
386
|
-
endpoint: options.endpoint,
|
|
387
|
-
path: options.path,
|
|
388
|
-
output: options.output,
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
console.log(formatJson(formatSuccess(result)));
|
|
392
|
-
} catch (error) {
|
|
393
|
-
console.error(formatJson(formatError("PULL_ERROR", (error as Error).message)));
|
|
394
|
-
process.exit(1);
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
async function parseExplicitRecords(
|
|
399
|
-
value?: string,
|
|
400
|
-
file?: string,
|
|
401
|
-
): Promise<
|
|
402
|
-
| { id: string; kind: string; parentId: string | null; settings: Record<string, unknown> }[]
|
|
403
|
-
| undefined
|
|
404
|
-
> {
|
|
405
|
-
if (value && file) {
|
|
406
|
-
throw new Error("Cannot use both an inline value and a file for the same record list");
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
let data: unknown;
|
|
410
|
-
|
|
411
|
-
if (file) {
|
|
412
|
-
const fs = await import("fs-extra");
|
|
413
|
-
data = await fs.readJson(file);
|
|
414
|
-
} else if (value) {
|
|
415
|
-
data = JSON.parse(value);
|
|
416
|
-
} else {
|
|
417
|
-
return undefined;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const list = Array.isArray(data) ? data : [data];
|
|
421
|
-
return list.map((record) => purifyResourceRecord(record as never));
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
resource
|
|
425
|
-
.command("mutate")
|
|
426
|
-
.description("Mutate local resource records on RRM via mutateResourceRecords")
|
|
427
|
-
.option("--source <path>", `data source (default: ${DEFAULT_SOURCE_PATH})`)
|
|
428
|
-
.option("--endpoint <url>", "RRM GraphQL endpoint")
|
|
429
|
-
.option("--token <token>", "RRM Bearer token (or RRM_ACCESS_TOKEN env)")
|
|
430
|
-
.option(
|
|
431
|
-
"--path <regex>",
|
|
432
|
-
"scope remote records to a path regex (e.g. application id) before splitting",
|
|
433
|
-
)
|
|
434
|
-
.option(
|
|
435
|
-
"--no-delete-missing",
|
|
436
|
-
"do not soft-delete remote-only records (default: soft-delete like console)",
|
|
437
|
-
)
|
|
438
|
-
.option("--no-write-back", "do not write server-assigned ids back to the local source file")
|
|
439
|
-
.option("--yes", "confirm soft-deletion without prompting")
|
|
440
|
-
.option("--dry-run", "preview the plan without sending")
|
|
441
|
-
.option("--create <json>", "explicit create records as JSON (skip auto split)")
|
|
442
|
-
.option("--update <json>", "explicit update records as JSON (skip auto split)")
|
|
443
|
-
.option("--create-file <path>", "explicit create records from JSON file (skip auto split)")
|
|
444
|
-
.option("--update-file <path>", "explicit update records from JSON file (skip auto split)")
|
|
445
|
-
.action(async (options) => {
|
|
446
|
-
try {
|
|
447
|
-
const explicitCreate = await parseExplicitRecords(options.create, options.createFile);
|
|
448
|
-
const explicitUpdate = await parseExplicitRecords(options.update, options.updateFile);
|
|
449
|
-
|
|
450
|
-
if (explicitCreate !== undefined || explicitUpdate !== undefined) {
|
|
451
|
-
const result = await mutateRecordsDirect({
|
|
452
|
-
endpoint: options.endpoint,
|
|
453
|
-
token: options.token,
|
|
454
|
-
createResourceRecords: explicitCreate ?? [],
|
|
455
|
-
updateResourceRecords: explicitUpdate ?? [],
|
|
456
|
-
dryRun: options.dryRun,
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
console.log(
|
|
460
|
-
formatJson(
|
|
461
|
-
formatSuccess({
|
|
462
|
-
mode: "explicit",
|
|
463
|
-
dryRun: result.dryRun,
|
|
464
|
-
createCount: result.createCount,
|
|
465
|
-
updateCount: result.updateCount,
|
|
466
|
-
createdResourceRecords: result.createdResourceRecords,
|
|
467
|
-
updatedResourceRecords: result.updatedResourceRecords,
|
|
468
|
-
}),
|
|
469
|
-
),
|
|
470
|
-
);
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if (isStdinSource(options.source)) {
|
|
475
|
-
console.error(
|
|
476
|
-
formatJson(
|
|
477
|
-
formatError(
|
|
478
|
-
"STDIN_NOT_SUPPORTED",
|
|
479
|
-
"stdin source is not supported for mutate; use a local JSON file",
|
|
480
|
-
),
|
|
481
|
-
),
|
|
482
|
-
);
|
|
483
|
-
process.exit(1);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const sourceData = await resolveSource(options.source);
|
|
487
|
-
|
|
488
|
-
const result = await mutateResources({
|
|
489
|
-
sourceData,
|
|
490
|
-
endpoint: options.endpoint,
|
|
491
|
-
token: options.token,
|
|
492
|
-
path: options.path,
|
|
493
|
-
deleteMissing: options.deleteMissing,
|
|
494
|
-
writeBack: options.writeBack,
|
|
495
|
-
yes: options.yes,
|
|
496
|
-
dryRun: options.dryRun,
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
const { plan } = result;
|
|
500
|
-
|
|
501
|
-
console.log(
|
|
502
|
-
formatJson(
|
|
503
|
-
formatSuccess({
|
|
504
|
-
mode: "auto",
|
|
505
|
-
dryRun: result.dryRun,
|
|
506
|
-
source: getSourceDescription(options.source),
|
|
507
|
-
remoteCount: plan.remoteCount,
|
|
508
|
-
localCount: plan.localCount,
|
|
509
|
-
createCount: plan.createResourceRecords.length,
|
|
510
|
-
updateCount: plan.updateResourceRecords.length,
|
|
511
|
-
deleteCount: plan.deleteResourceRecords.length,
|
|
512
|
-
idMap: result.idMap,
|
|
513
|
-
writeBackPath: result.writeBackPath,
|
|
514
|
-
}),
|
|
515
|
-
),
|
|
516
|
-
);
|
|
517
|
-
} catch (error) {
|
|
518
|
-
console.error(formatJson(formatError("MUTATE_ERROR", (error as Error).message)));
|
|
519
|
-
process.exit(1);
|
|
520
|
-
}
|
|
521
|
-
});
|
|
14
|
+
registerGenerateCommand(program);
|
|
15
|
+
registerResourceCommand(program);
|
|
522
16
|
|
|
523
17
|
program.parse(process.argv);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { loadData } from "@ldsg/utils";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { DEFAULT_OUTPUT_PATH } from "../../constants";
|
|
4
|
+
import { parseExtraOptions } from "./utils/parse-extra-options";
|
|
5
|
+
import { GenerateParams } from "@ldsg/types";
|
|
6
|
+
import { generateApplication } from "./utils/generate-application";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 注册 generate 命令
|
|
10
|
+
*
|
|
11
|
+
* 从 ldsg 数据源生成独立应用:三阶段构建(bootstrap generator → application generator → buildApp)
|
|
12
|
+
*/
|
|
13
|
+
export function registerGenerateCommand(program: Command): void {
|
|
14
|
+
program
|
|
15
|
+
.command("generate")
|
|
16
|
+
.aliases(["g", "gen"])
|
|
17
|
+
.description("Generate standalone application from ldsg data source")
|
|
18
|
+
// output 为位置参数(放在命令最后),声明在 usage 中用于帮助信息展示;
|
|
19
|
+
// 实际取值由 parseExtraOptions 从 command.args 中解析(未知选项会导致 commander 位置参数错位)
|
|
20
|
+
.usage(
|
|
21
|
+
`[options] [output]\n\n output: output directory for prepared source code, or zip file path when --zip (default: ${DEFAULT_OUTPUT_PATH})`,
|
|
22
|
+
)
|
|
23
|
+
.option("--data-uri <uri>", "ldsg source data URI")
|
|
24
|
+
.option(
|
|
25
|
+
"--data-expression <expression>",
|
|
26
|
+
"ldsg data expression (jsonata), used to convert source data to ldsg data",
|
|
27
|
+
)
|
|
28
|
+
.option("-z, --zip", "write the prepared source code as a zip file")
|
|
29
|
+
.allowUnknownOption(true)
|
|
30
|
+
.action(async (options, command) => {
|
|
31
|
+
const { dataUri, dataExpression, zip: isZip = false } = options;
|
|
32
|
+
|
|
33
|
+
// 解析额外未知选项与位置参数(最后一个位置参数为输出目录)
|
|
34
|
+
const { options: extraOptions, positionals } = parseExtraOptions(command.args);
|
|
35
|
+
const outputPath = positionals[positionals.length - 1] ?? DEFAULT_OUTPUT_PATH;
|
|
36
|
+
|
|
37
|
+
const data = await loadData({
|
|
38
|
+
dataUri,
|
|
39
|
+
dataExpression,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 生成器选项
|
|
44
|
+
*/
|
|
45
|
+
const generateParams: GenerateParams = {
|
|
46
|
+
data,
|
|
47
|
+
outputPath,
|
|
48
|
+
isZip,
|
|
49
|
+
extraOptions,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
await generateApplication(generateParams);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { extractZipToFolder } from "@ldsg/utils";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { GenerateParams } from "@ldsg/types";
|
|
5
|
+
import { buildAppGenerator, buildMetaGenerator, generateApp } from "./utils";
|
|
6
|
+
|
|
7
|
+
interface GenerateApplicationParams extends GenerateParams {}
|
|
8
|
+
|
|
9
|
+
type GenerateApplication = (params: GenerateApplicationParams) => Promise<void>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 生成应用
|
|
13
|
+
* 1. 基于资源记录,使用此应用(@ldsg/cli)内置能力构建(含生成、依赖安装、构建)元生成器(Meta Generator);
|
|
14
|
+
* 2. 基于资源记录,使用元生成器(Meta Generator)能力构建(含生成、依赖安装、构建)应用生成器(App Generator);
|
|
15
|
+
* 3. 基于资源记录,使用应用生成器(App Generator)能力生成(仅含生成,不含依赖安装、构建)生成应用(App)压缩包;
|
|
16
|
+
* 4. 输出压缩包或解压至目标输出路径。
|
|
17
|
+
*/
|
|
18
|
+
export const generateApplication: GenerateApplication = async (params) => {
|
|
19
|
+
const { outputPath, isZip } = params;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 1. 基于资源记录,使用此应用(@ldsg/cli)内置能力构建(含生成、依赖安装、构建)元生成器(Meta Generator);
|
|
23
|
+
*/
|
|
24
|
+
const { metaGenerator } = await buildMetaGenerator(params);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 2. 基于资源记录,使用元生成器(Meta Generator)能力构建(含生成、依赖安装、构建)应用生成器(App Generator);
|
|
28
|
+
*/
|
|
29
|
+
const { appGenerator } = await buildAppGenerator({ metaGenerator, ...params });
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 3. 基于资源记录,使用应用生成器(App Generator)能力生成(仅含生成,不含依赖安装、构建)生成应用(App)压缩包;
|
|
33
|
+
*/
|
|
34
|
+
const generateAppRes = await generateApp({ appGenerator, ...params });
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 4. 输出压缩包或解压至目标输出路径。
|
|
38
|
+
*/
|
|
39
|
+
{
|
|
40
|
+
const { archive } = generateAppRes;
|
|
41
|
+
|
|
42
|
+
if (isZip) {
|
|
43
|
+
const zipOutputPath = path.join(outputPath, "app.zip");
|
|
44
|
+
|
|
45
|
+
await fs.outputFile(zipOutputPath, archive);
|
|
46
|
+
} else {
|
|
47
|
+
extractZipToFolder({ archive, outputPath });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { GenerateParams, Generator } from "@ldsg/types";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { extractZipToFolder } from "@ldsg/utils";
|
|
5
|
+
|
|
6
|
+
export interface BuildAppGeneratorParams extends GenerateParams {
|
|
7
|
+
metaGenerator: Generator;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface BuildAppGeneratorRes {
|
|
11
|
+
appGenerator: Generator;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type BuildAppGenerator = (params: BuildAppGeneratorParams) => Promise<BuildAppGeneratorRes>;
|
|
15
|
+
|
|
16
|
+
export const buildAppGenerator: BuildAppGenerator = async (params) => {
|
|
17
|
+
const { metaGenerator, ...restParams } = params;
|
|
18
|
+
|
|
19
|
+
const { archive } = await metaGenerator.generate(restParams);
|
|
20
|
+
|
|
21
|
+
const outputPath = path.join(os.homedir(), ".ldsg", "app-generator");
|
|
22
|
+
|
|
23
|
+
extractZipToFolder({
|
|
24
|
+
archive,
|
|
25
|
+
outputPath,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const res: BuildAppGeneratorRes = {
|
|
29
|
+
appGenerator: {
|
|
30
|
+
generate: async () => {
|
|
31
|
+
return {
|
|
32
|
+
archive,
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return res;
|
|
39
|
+
};
|