@uns-kit/core 2.0.45 → 2.0.48
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/LICENSE +21 -21
- package/README.md +205 -205
- package/dist/base-path.js.map +1 -1
- package/dist/config/app-config.js.map +1 -1
- package/dist/config/project.config.extension.js.map +1 -1
- package/dist/config-file.js.map +1 -1
- package/dist/graphql/schema.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/tools/auth/auth-client.js.map +1 -1
- package/dist/tools/auth/index.js.map +1 -1
- package/dist/tools/auth/secure-store.js.map +1 -1
- package/dist/tools/base-path.js.map +1 -1
- package/dist/tools/datahub/datahub-client.js.map +1 -1
- package/dist/tools/file-utils.js.map +1 -1
- package/dist/tools/generate-config-schema.js.map +1 -1
- package/dist/tools/generate-uns-dictionary.js +7 -7
- package/dist/tools/generate-uns-dictionary.js.map +1 -1
- package/dist/tools/generate-uns-measurements.js +7 -7
- package/dist/tools/generate-uns-measurements.js.map +1 -1
- package/dist/tools/generate-uns-reference.js +9 -9
- package/dist/tools/generate-uns-reference.js.map +1 -1
- package/dist/tools/pull-request.js.map +1 -1
- package/dist/tools/refresh-uns.d.ts +1 -0
- package/dist/tools/refresh-uns.js +5 -193
- package/dist/tools/refresh-uns.js.map +1 -1
- package/dist/tools/schema.js.map +1 -1
- package/dist/tools/sync-uns-metadata.d.ts +9 -0
- package/dist/tools/sync-uns-metadata.d.ts.map +1 -0
- package/dist/tools/sync-uns-metadata.js +468 -0
- package/dist/tools/sync-uns-metadata.js.map +1 -0
- package/dist/tools/sync-uns-schema.js +21 -17
- package/dist/tools/sync-uns-schema.js.map +1 -1
- package/dist/uns/handover-manager-event-emitter.js.map +1 -1
- package/dist/uns/handover-manager.js.map +1 -1
- package/dist/uns/process-config.js.map +1 -1
- package/dist/uns/service-metadata.js.map +1 -1
- package/dist/uns/status-monitor.js.map +1 -1
- package/dist/uns/uns-asset.js.map +1 -1
- package/dist/uns/uns-attributes.js.map +1 -1
- package/dist/uns/uns-dictionary-registry.js.map +1 -1
- package/dist/uns/uns-dictionary.generated.js.map +1 -1
- package/dist/uns/uns-event-emitter.js.map +1 -1
- package/dist/uns/uns-interfaces.d.ts +40 -1
- package/dist/uns/uns-interfaces.d.ts.map +1 -1
- package/dist/uns/uns-interfaces.js.map +1 -1
- package/dist/uns/uns-measurements.generated.js.map +1 -1
- package/dist/uns/uns-measurements.js.map +1 -1
- package/dist/uns/uns-object.js.map +1 -1
- package/dist/uns/uns-packet.js.map +1 -1
- package/dist/uns/uns-path.js.map +1 -1
- package/dist/uns/uns-proxy-process.js.map +1 -1
- package/dist/uns/uns-proxy.d.ts +9 -0
- package/dist/uns/uns-proxy.d.ts.map +1 -1
- package/dist/uns/uns-proxy.js +105 -4
- package/dist/uns/uns-proxy.js.map +1 -1
- package/dist/uns/uns-tags.js.map +1 -1
- package/dist/uns/uns-topic-matcher.js.map +1 -1
- package/dist/uns/uns-topics.js.map +1 -1
- package/dist/uns-config/config-schema.js.map +1 -1
- package/dist/uns-config/host-placeholders.js.map +1 -1
- package/dist/uns-config/schema-tolls.js.map +1 -1
- package/dist/uns-config/schema-tools.js.map +1 -1
- package/dist/uns-config/secret-placeholders.js.map +1 -1
- package/dist/uns-config/secret-resolver.js.map +1 -1
- package/dist/uns-config/uns-core-schema.js.map +1 -1
- package/dist/uns-grpc/uns-gateway-cli.js.map +1 -1
- package/dist/uns-grpc/uns-gateway-server.js.map +1 -1
- package/dist/uns-grpc/uns-gateway.proto +104 -104
- package/dist/uns-mqtt/mqtt-interfaces.js.map +1 -1
- package/dist/uns-mqtt/mqtt-proxy.js.map +1 -1
- package/dist/uns-mqtt/mqtt-topic-builder.js.map +1 -1
- package/dist/uns-mqtt/mqtt-worker-init.js.map +1 -1
- package/dist/uns-mqtt/mqtt-worker.js.map +1 -1
- package/dist/uns-mqtt/throttled-queue.js.map +1 -1
- package/dist/uns-mqtt/uns-mqtt-proxy.js.map +1 -1
- package/dist/uns-mqtt/ws-proxy.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { access, readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { readTextFileIfExists, writeTextFileIfChanged } from "./file-utils.js";
|
|
7
|
+
const isDirectExecution = process.argv[1]
|
|
8
|
+
? import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href
|
|
9
|
+
: false;
|
|
10
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
11
|
+
const args = await parseArgs(argv);
|
|
12
|
+
if (args.help) {
|
|
13
|
+
printHelp();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const target = await resolveSyncTarget(args);
|
|
17
|
+
const document = await fetchMetadataDocument(args.controllerUrl, args.token);
|
|
18
|
+
const selected = selectedSections(args);
|
|
19
|
+
const results = [];
|
|
20
|
+
if (selected.topics) {
|
|
21
|
+
results.push({
|
|
22
|
+
label: "Topics TS",
|
|
23
|
+
results: await updateGeneratedFiles(target.topicsGenerated, renderUnsTopicsTs(readStringArray(document.topics, "topics").map(normalizeTopicPath).filter(Boolean)), target.rootDir, args.dryRun),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (selected.tags) {
|
|
27
|
+
results.push({
|
|
28
|
+
label: "Tags TS",
|
|
29
|
+
results: await updateGeneratedFiles(target.tagsGenerated, renderUnsTagsTs(readStringArray(document.tags, "tags")), target.rootDir, args.dryRun),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (selected.assets) {
|
|
33
|
+
results.push({
|
|
34
|
+
label: "Assets TS",
|
|
35
|
+
results: await updateGeneratedFiles(target.assetsGenerated, renderUnsAssetsTs(readAssetEntries(document.assets)), target.rootDir, args.dryRun),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
printSummary({
|
|
39
|
+
controllerUrl: args.controllerUrl,
|
|
40
|
+
dryRun: args.dryRun,
|
|
41
|
+
targetLabel: target.label,
|
|
42
|
+
targetRoot: target.rootDir,
|
|
43
|
+
results,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function selectedSections(args) {
|
|
47
|
+
const hasSpecificSelection = args.topicsOnly || args.tagsOnly || args.assetsOnly;
|
|
48
|
+
return {
|
|
49
|
+
topics: !hasSpecificSelection || args.topicsOnly,
|
|
50
|
+
tags: !hasSpecificSelection || args.tagsOnly,
|
|
51
|
+
assets: !hasSpecificSelection || args.assetsOnly,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
async function getControllerUrlFromConfig() {
|
|
55
|
+
const configPath = path.join(process.cwd(), "config.json");
|
|
56
|
+
try {
|
|
57
|
+
const raw = await readFile(configPath, "utf8");
|
|
58
|
+
const config = JSON.parse(raw);
|
|
59
|
+
const restUrl = config.uns?.rest;
|
|
60
|
+
if (typeof restUrl === "string" && restUrl.trim()) {
|
|
61
|
+
return restUrl.endsWith("/api") ? restUrl.slice(0, -4) : restUrl;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
// Missing/invalid config.json is handled by the caller.
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
async function promptForToken() {
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
const stdin = process.stdin;
|
|
72
|
+
const wasRaw = stdin.isRaw;
|
|
73
|
+
stdin.setRawMode(true);
|
|
74
|
+
stdin.resume();
|
|
75
|
+
process.stderr.write("Enter controller token: ");
|
|
76
|
+
let token = "";
|
|
77
|
+
stdin.on("data", onChar);
|
|
78
|
+
function onChar(char) {
|
|
79
|
+
const code = char[0];
|
|
80
|
+
if (code === 13 || code === 10) {
|
|
81
|
+
stdin.removeListener("data", onChar);
|
|
82
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
83
|
+
stdin.pause();
|
|
84
|
+
process.stderr.write("\n");
|
|
85
|
+
resolve(token);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (code === 3) {
|
|
89
|
+
stdin.removeListener("data", onChar);
|
|
90
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
91
|
+
process.stderr.write("\n");
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
if (code === 127 || code === 8) {
|
|
95
|
+
token = token.slice(0, -1);
|
|
96
|
+
}
|
|
97
|
+
else if (code >= 32) {
|
|
98
|
+
token += char.toString("utf8");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async function parseArgs(argv) {
|
|
104
|
+
let controllerUrl = process.env.UNS_CONTROLLER_URL?.trim() ?? "";
|
|
105
|
+
let token = process.env.UNS_CONTROLLER_TOKEN?.trim() ?? "";
|
|
106
|
+
let projectRoot = process.env.UNS_METADATA_PROJECT_ROOT?.trim() ||
|
|
107
|
+
process.env.UNS_SCHEMA_PROJECT_ROOT?.trim() ||
|
|
108
|
+
undefined;
|
|
109
|
+
let dryRun = false;
|
|
110
|
+
let topicsOnly = false;
|
|
111
|
+
let tagsOnly = false;
|
|
112
|
+
let assetsOnly = false;
|
|
113
|
+
let help = false;
|
|
114
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
115
|
+
const arg = argv[index];
|
|
116
|
+
if (arg === "--help" || arg === "-h") {
|
|
117
|
+
help = true;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (arg === "--dry-run") {
|
|
121
|
+
dryRun = true;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (arg === "--topics-only") {
|
|
125
|
+
topicsOnly = true;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (arg === "--tags-only") {
|
|
129
|
+
tagsOnly = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (arg === "--assets-only") {
|
|
133
|
+
assetsOnly = true;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (arg === "--project-root") {
|
|
137
|
+
projectRoot = readRequiredValue(argv, ++index, "--project-root");
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (arg.startsWith("--project-root=")) {
|
|
141
|
+
projectRoot = arg.slice("--project-root=".length);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (arg === "--controller-url") {
|
|
145
|
+
controllerUrl = readRequiredValue(argv, ++index, "--controller-url");
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (arg.startsWith("--controller-url=")) {
|
|
149
|
+
controllerUrl = arg.slice("--controller-url=".length);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (arg === "--token") {
|
|
153
|
+
token = readRequiredValue(argv, ++index, "--token");
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (arg.startsWith("--token=")) {
|
|
157
|
+
token = arg.slice("--token=".length);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
161
|
+
}
|
|
162
|
+
if (!controllerUrl) {
|
|
163
|
+
const configUrl = await getControllerUrlFromConfig();
|
|
164
|
+
if (configUrl) {
|
|
165
|
+
controllerUrl = configUrl;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (!help) {
|
|
169
|
+
if (!controllerUrl) {
|
|
170
|
+
throw new Error("Missing controller URL. Use --controller-url, set UNS_CONTROLLER_URL, or provide config.json with uns.rest.");
|
|
171
|
+
}
|
|
172
|
+
if (!token) {
|
|
173
|
+
if (process.stdin.isTTY) {
|
|
174
|
+
token = await promptForToken();
|
|
175
|
+
}
|
|
176
|
+
if (!token) {
|
|
177
|
+
throw new Error("Missing controller token. Use --token, set UNS_CONTROLLER_TOKEN, or provide it interactively.");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (token.toLowerCase().startsWith("bearer ")) {
|
|
181
|
+
token = token.slice(7).trim();
|
|
182
|
+
}
|
|
183
|
+
controllerUrl = normalizeControllerUrl(controllerUrl);
|
|
184
|
+
assertValidUrl(controllerUrl);
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
controllerUrl,
|
|
188
|
+
token,
|
|
189
|
+
projectRoot,
|
|
190
|
+
dryRun,
|
|
191
|
+
topicsOnly,
|
|
192
|
+
tagsOnly,
|
|
193
|
+
assetsOnly,
|
|
194
|
+
help,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function readRequiredValue(argv, index, flag) {
|
|
198
|
+
const value = argv[index];
|
|
199
|
+
if (!value) {
|
|
200
|
+
throw new Error(`Missing value for ${flag}.`);
|
|
201
|
+
}
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
function assertValidUrl(value) {
|
|
205
|
+
try {
|
|
206
|
+
new URL(value);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
throw new Error(`Invalid controller URL "${value}". Expected an absolute URL such as http://localhost:3200.`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function normalizeControllerUrl(value) {
|
|
213
|
+
return value.replace(/\/+$/, "").replace(/\/api$/i, "");
|
|
214
|
+
}
|
|
215
|
+
function printHelp() {
|
|
216
|
+
console.log(`Usage: tsx packages/uns-core/src/tools/sync-uns-metadata.ts [options]
|
|
217
|
+
|
|
218
|
+
Pull UNS metadata from a controller REST export and refresh generated TypeScript
|
|
219
|
+
topic, tag, and asset artifacts.
|
|
220
|
+
|
|
221
|
+
Options:
|
|
222
|
+
--controller-url <url> Controller base URL (env: UNS_CONTROLLER_URL, or config.json > uns.rest)
|
|
223
|
+
--token <token> Bearer token with export:uns-reference scope (env: UNS_CONTROLLER_TOKEN)
|
|
224
|
+
--project-root <dir> Write into a generated microservice project root (env: UNS_METADATA_PROJECT_ROOT or UNS_SCHEMA_PROJECT_ROOT).
|
|
225
|
+
When omitted, the tool auto-detects a generated project from the current working directory
|
|
226
|
+
and otherwise updates the uns-kit repo templates.
|
|
227
|
+
--dry-run Report file changes without writing anything
|
|
228
|
+
--topics-only Refresh only src/uns/uns-topics.ts
|
|
229
|
+
--tags-only Refresh only src/uns/uns-tags.ts
|
|
230
|
+
--assets-only Refresh only src/uns/uns-assets.ts
|
|
231
|
+
--help, -h Show this help
|
|
232
|
+
`);
|
|
233
|
+
}
|
|
234
|
+
async function resolveSyncTarget(args) {
|
|
235
|
+
if (args.projectRoot) {
|
|
236
|
+
return buildProjectTarget(path.resolve(process.cwd(), args.projectRoot));
|
|
237
|
+
}
|
|
238
|
+
const currentDir = process.cwd();
|
|
239
|
+
if (await looksLikeGeneratedProjectRoot(currentDir)) {
|
|
240
|
+
return buildProjectTarget(currentDir);
|
|
241
|
+
}
|
|
242
|
+
return buildRepoTarget(await findRepoRoot());
|
|
243
|
+
}
|
|
244
|
+
async function findRepoRoot() {
|
|
245
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
246
|
+
let probeDir = currentDir;
|
|
247
|
+
while (true) {
|
|
248
|
+
const packageJsonPath = path.join(probeDir, "package.json");
|
|
249
|
+
try {
|
|
250
|
+
const raw = await readFile(packageJsonPath, "utf8");
|
|
251
|
+
const pkg = JSON.parse(raw);
|
|
252
|
+
if (pkg.name === "@uns-kit/core") {
|
|
253
|
+
return path.resolve(probeDir, "../..");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
// Ignore missing package.json and continue walking upward.
|
|
258
|
+
}
|
|
259
|
+
const parentDir = path.dirname(probeDir);
|
|
260
|
+
if (parentDir === probeDir)
|
|
261
|
+
break;
|
|
262
|
+
probeDir = parentDir;
|
|
263
|
+
}
|
|
264
|
+
throw new Error("Could not locate the @uns-kit/core package directory from sync-uns-metadata.ts.");
|
|
265
|
+
}
|
|
266
|
+
function buildRepoTarget(repoRoot) {
|
|
267
|
+
return {
|
|
268
|
+
rootDir: repoRoot,
|
|
269
|
+
label: "uns-kit repo templates",
|
|
270
|
+
topicsGenerated: [
|
|
271
|
+
path.join(repoRoot, "packages/uns-core/src/uns/uns-topics.ts"),
|
|
272
|
+
path.join(repoRoot, "packages/uns-cli/templates/default/src/uns/uns-topics.ts"),
|
|
273
|
+
],
|
|
274
|
+
tagsGenerated: [
|
|
275
|
+
path.join(repoRoot, "packages/uns-core/src/uns/uns-tags.ts"),
|
|
276
|
+
path.join(repoRoot, "packages/uns-cli/templates/default/src/uns/uns-tags.ts"),
|
|
277
|
+
],
|
|
278
|
+
assetsGenerated: [path.join(repoRoot, "packages/uns-cli/templates/default/src/uns/uns-assets.ts")],
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function buildProjectTarget(projectRoot) {
|
|
282
|
+
return {
|
|
283
|
+
rootDir: projectRoot,
|
|
284
|
+
label: "generated microservice project",
|
|
285
|
+
topicsGenerated: [path.join(projectRoot, "src/uns/uns-topics.ts")],
|
|
286
|
+
tagsGenerated: [path.join(projectRoot, "src/uns/uns-tags.ts")],
|
|
287
|
+
assetsGenerated: [path.join(projectRoot, "src/uns/uns-assets.ts")],
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
async function looksLikeGeneratedProjectRoot(rootDir) {
|
|
291
|
+
const [hasPackageJson, hasConfigJson, hasUnsDir] = await Promise.all([
|
|
292
|
+
pathExists(path.join(rootDir, "package.json")),
|
|
293
|
+
pathExists(path.join(rootDir, "config.json")),
|
|
294
|
+
pathExists(path.join(rootDir, "src/uns")),
|
|
295
|
+
]);
|
|
296
|
+
return hasPackageJson && hasConfigJson && hasUnsDir;
|
|
297
|
+
}
|
|
298
|
+
async function pathExists(targetPath) {
|
|
299
|
+
try {
|
|
300
|
+
await access(targetPath);
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function buildControllerUrl(controllerUrl, relativePath) {
|
|
308
|
+
const base = new URL(controllerUrl);
|
|
309
|
+
if (!base.pathname.endsWith("/")) {
|
|
310
|
+
base.pathname = `${base.pathname}/`;
|
|
311
|
+
}
|
|
312
|
+
return new URL(relativePath, base);
|
|
313
|
+
}
|
|
314
|
+
async function fetchMetadataDocument(controllerUrl, token) {
|
|
315
|
+
const url = buildControllerUrl(controllerUrl, "api/schema/export/uns-metadata");
|
|
316
|
+
const document = await fetchJson(url, token, "UNS metadata");
|
|
317
|
+
if (!document || typeof document !== "object" || Array.isArray(document)) {
|
|
318
|
+
throw new Error(`Controller returned an invalid UNS metadata document from ${url.toString()}.`);
|
|
319
|
+
}
|
|
320
|
+
return document;
|
|
321
|
+
}
|
|
322
|
+
async function fetchJson(url, token, label) {
|
|
323
|
+
let response;
|
|
324
|
+
try {
|
|
325
|
+
response = await fetch(url, {
|
|
326
|
+
headers: {
|
|
327
|
+
Accept: "application/json",
|
|
328
|
+
Authorization: `Bearer ${token}`,
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
throw new Error(`Network failure while fetching ${label} export from ${url.toString()}: ${error.message}`);
|
|
334
|
+
}
|
|
335
|
+
const text = await response.text();
|
|
336
|
+
if (!response.ok) {
|
|
337
|
+
throw new Error(formatHttpError(response, url, label, text));
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
return JSON.parse(text);
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
throw new Error(`Controller returned invalid JSON for the ${label} export from ${url.toString()}.`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function formatHttpError(response, url, label, body) {
|
|
347
|
+
if (response.status === 401) {
|
|
348
|
+
return "Token is invalid or expired (401). Provide a valid token via --token, UNS_CONTROLLER_TOKEN, or the interactive prompt.";
|
|
349
|
+
}
|
|
350
|
+
if (response.status === 403) {
|
|
351
|
+
return `Forbidden (403) while fetching ${label} export from ${url.toString()}. The token must be admin or include export:uns-reference.`;
|
|
352
|
+
}
|
|
353
|
+
const detail = extractErrorDetail(body);
|
|
354
|
+
return `Failed to fetch ${label} export from ${url.toString()}: HTTP ${response.status}${detail ? ` - ${detail}` : ""}`;
|
|
355
|
+
}
|
|
356
|
+
function extractErrorDetail(body) {
|
|
357
|
+
const trimmed = body.trim();
|
|
358
|
+
if (!trimmed)
|
|
359
|
+
return "";
|
|
360
|
+
try {
|
|
361
|
+
const parsed = JSON.parse(trimmed);
|
|
362
|
+
if (typeof parsed.error === "string" && parsed.error.trim())
|
|
363
|
+
return parsed.error.trim();
|
|
364
|
+
if (typeof parsed.message === "string" && parsed.message.trim())
|
|
365
|
+
return parsed.message.trim();
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
// Fall through to plain text handling.
|
|
369
|
+
}
|
|
370
|
+
return trimmed.replace(/\s+/g, " ").slice(0, 200);
|
|
371
|
+
}
|
|
372
|
+
function readStringArray(value, label) {
|
|
373
|
+
if (!Array.isArray(value)) {
|
|
374
|
+
throw new Error(`UNS metadata export is missing a valid ${label} array.`);
|
|
375
|
+
}
|
|
376
|
+
return Array.from(new Set(value.filter((entry) => typeof entry === "string" && entry.trim().length > 0).map(entry => entry.trim()))).sort((left, right) => left.localeCompare(right));
|
|
377
|
+
}
|
|
378
|
+
function readAssetEntries(value) {
|
|
379
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
380
|
+
throw new Error("UNS metadata export is missing a valid assets object.");
|
|
381
|
+
}
|
|
382
|
+
return Object.entries(value)
|
|
383
|
+
.map(([name, metadata]) => ({
|
|
384
|
+
name: name.trim(),
|
|
385
|
+
description: typeof metadata?.description === "string" && metadata.description.trim()
|
|
386
|
+
? metadata.description.trim()
|
|
387
|
+
: null,
|
|
388
|
+
}))
|
|
389
|
+
.filter(entry => entry.name.length > 0)
|
|
390
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
391
|
+
}
|
|
392
|
+
function normalizeTopicPath(value) {
|
|
393
|
+
const normalized = value.trim().replace(/^\/+|\/+$/g, "");
|
|
394
|
+
return normalized ? `${normalized}/` : "";
|
|
395
|
+
}
|
|
396
|
+
export function renderUnsTopicsTs(topics) {
|
|
397
|
+
const uniqueTopics = Array.from(new Set(topics.map(normalizeTopicPath).filter(Boolean)))
|
|
398
|
+
.sort((left, right) => left.localeCompare(right));
|
|
399
|
+
const members = uniqueTopics.map(topic => ` | ${JSON.stringify(topic)}`).join("\n");
|
|
400
|
+
return `// Generated UNS topic union. Run \`pnpm run sync-uns-metadata\` to update.\nexport type UnsTopics =\n${members ? `${members}\n` : ""} | (string & {});\n`;
|
|
401
|
+
}
|
|
402
|
+
export function renderUnsTagsTs(tags) {
|
|
403
|
+
const uniqueTags = Array.from(new Set(tags.map(tag => tag.trim()).filter(Boolean)))
|
|
404
|
+
.sort((left, right) => left.localeCompare(right));
|
|
405
|
+
const members = uniqueTags.map(tag => ` | ${JSON.stringify(tag)}`).join("\n");
|
|
406
|
+
return `// Generated UNS tag union. Run \`pnpm run sync-uns-metadata\` to update.\nexport type UnsTags =\n${members ? `${members}\n` : ""} | (string & {});\n`;
|
|
407
|
+
}
|
|
408
|
+
export function renderUnsAssetsTs(assets) {
|
|
409
|
+
const entries = assets.length
|
|
410
|
+
? assets.map((entry) => {
|
|
411
|
+
const comment = entry.description ? ` /** ${sanitizeComment(entry.description)} */\n` : "";
|
|
412
|
+
return `${comment} ${JSON.stringify(entry.name)}: ${JSON.stringify(entry.name)},`;
|
|
413
|
+
}).join("\n")
|
|
414
|
+
: ` "asset": "asset",`;
|
|
415
|
+
const helpers = `
|
|
416
|
+
export function resolveGeneratedAsset(name: keyof typeof GeneratedAssets): (typeof GeneratedAssets)[keyof typeof GeneratedAssets];
|
|
417
|
+
export function resolveGeneratedAsset<T extends string>(name: T): (typeof GeneratedAssets)[keyof typeof GeneratedAssets] | T;
|
|
418
|
+
export function resolveGeneratedAsset(name: string): string {
|
|
419
|
+
return (GeneratedAssets as Record<string, string>)[name] ?? name;
|
|
420
|
+
}
|
|
421
|
+
`;
|
|
422
|
+
return `// Generated UNS asset list. Run \`pnpm run sync-uns-metadata\` to update.\nexport const GeneratedAssets = {\n${entries}\n} as const;\nexport type GeneratedAssetName = typeof GeneratedAssets[keyof typeof GeneratedAssets];\n${helpers}`;
|
|
423
|
+
}
|
|
424
|
+
function sanitizeComment(text) {
|
|
425
|
+
return text.replace(/\*\//g, "*\\/");
|
|
426
|
+
}
|
|
427
|
+
async function updateGeneratedFiles(filePaths, content, rootDir, dryRun) {
|
|
428
|
+
return Promise.all(filePaths.map((filePath) => updateFile(filePath, content, rootDir, dryRun)));
|
|
429
|
+
}
|
|
430
|
+
async function updateFile(filePath, content, rootDir, dryRun) {
|
|
431
|
+
const current = await readTextFileIfExists(filePath);
|
|
432
|
+
const changed = current !== content;
|
|
433
|
+
const relativePath = path.relative(rootDir, filePath);
|
|
434
|
+
if (dryRun) {
|
|
435
|
+
console.log(`${changed ? "Would update" : "No change"} ${relativePath}`);
|
|
436
|
+
return { changed, relativePath };
|
|
437
|
+
}
|
|
438
|
+
if (!changed) {
|
|
439
|
+
console.log(`Unchanged ${relativePath}`);
|
|
440
|
+
return { changed: false, relativePath };
|
|
441
|
+
}
|
|
442
|
+
await writeTextFileIfChanged(filePath, content);
|
|
443
|
+
console.log(`Updated ${relativePath}`);
|
|
444
|
+
return { changed: true, relativePath };
|
|
445
|
+
}
|
|
446
|
+
function printSummary(summary) {
|
|
447
|
+
console.log("");
|
|
448
|
+
console.log(summary.dryRun ? "Dry-run summary" : "Sync summary");
|
|
449
|
+
console.log(` Controller: ${summary.controllerUrl}`);
|
|
450
|
+
console.log(` Target: ${summary.targetLabel}`);
|
|
451
|
+
console.log(` Root: ${summary.targetRoot}`);
|
|
452
|
+
for (const entry of summary.results) {
|
|
453
|
+
console.log(` ${entry.label}: ${renderGeneratedSummary(entry.results)}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function renderGeneratedSummary(results) {
|
|
457
|
+
if (!results.length)
|
|
458
|
+
return "none";
|
|
459
|
+
const changedCount = results.filter((result) => result.changed).length;
|
|
460
|
+
return `${changedCount}/${results.length} file${results.length === 1 ? "" : "s"} changed`;
|
|
461
|
+
}
|
|
462
|
+
if (isDirectExecution) {
|
|
463
|
+
main().catch((error) => {
|
|
464
|
+
console.error("Failed to sync UNS metadata:", error instanceof Error ? error.message : error);
|
|
465
|
+
process.exitCode = 1;
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
//# sourceMappingURL=sync-uns-metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-uns-metadata.js","sourceRoot":"","sources":["../../src/tools/sync-uns-metadata.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport { access, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { readTextFileIfExists, writeTextFileIfChanged } from \"./file-utils.js\";\n\ntype MetadataDocument = {\n schemaVersion?: number;\n topics?: unknown;\n tags?: unknown;\n assets?: unknown;\n};\n\ntype AssetMetadata = {\n description?: unknown;\n};\n\ntype CliArgs = {\n controllerUrl: string;\n token: string;\n projectRoot?: string;\n dryRun: boolean;\n topicsOnly: boolean;\n tagsOnly: boolean;\n assetsOnly: boolean;\n help: boolean;\n};\n\ntype FileChangeResult = {\n changed: boolean;\n relativePath: string;\n};\n\ntype SyncTarget = {\n rootDir: string;\n label: string;\n topicsGenerated: string[];\n tagsGenerated: string[];\n assetsGenerated: string[];\n};\n\nconst isDirectExecution = process.argv[1]\n ? import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href\n : false;\n\nexport async function main(argv = process.argv.slice(2)): Promise<void> {\n const args = await parseArgs(argv);\n\n if (args.help) {\n printHelp();\n return;\n }\n\n const target = await resolveSyncTarget(args);\n const document = await fetchMetadataDocument(args.controllerUrl, args.token);\n const selected = selectedSections(args);\n\n const results: { label: string; results: FileChangeResult[] }[] = [];\n\n if (selected.topics) {\n results.push({\n label: \"Topics TS\",\n results: await updateGeneratedFiles(\n target.topicsGenerated,\n renderUnsTopicsTs(readStringArray(document.topics, \"topics\").map(normalizeTopicPath).filter(Boolean)),\n target.rootDir,\n args.dryRun,\n ),\n });\n }\n\n if (selected.tags) {\n results.push({\n label: \"Tags TS\",\n results: await updateGeneratedFiles(\n target.tagsGenerated,\n renderUnsTagsTs(readStringArray(document.tags, \"tags\")),\n target.rootDir,\n args.dryRun,\n ),\n });\n }\n\n if (selected.assets) {\n results.push({\n label: \"Assets TS\",\n results: await updateGeneratedFiles(\n target.assetsGenerated,\n renderUnsAssetsTs(readAssetEntries(document.assets)),\n target.rootDir,\n args.dryRun,\n ),\n });\n }\n\n printSummary({\n controllerUrl: args.controllerUrl,\n dryRun: args.dryRun,\n targetLabel: target.label,\n targetRoot: target.rootDir,\n results,\n });\n}\n\nfunction selectedSections(args: CliArgs): { topics: boolean; tags: boolean; assets: boolean } {\n const hasSpecificSelection = args.topicsOnly || args.tagsOnly || args.assetsOnly;\n return {\n topics: !hasSpecificSelection || args.topicsOnly,\n tags: !hasSpecificSelection || args.tagsOnly,\n assets: !hasSpecificSelection || args.assetsOnly,\n };\n}\n\nasync function getControllerUrlFromConfig(): Promise<string | undefined> {\n const configPath = path.join(process.cwd(), \"config.json\");\n try {\n const raw = await readFile(configPath, \"utf8\");\n const config = JSON.parse(raw) as Record<string, unknown>;\n const restUrl = (config.uns as Record<string, unknown> | undefined)?.rest;\n if (typeof restUrl === \"string\" && restUrl.trim()) {\n return restUrl.endsWith(\"/api\") ? restUrl.slice(0, -4) : restUrl;\n }\n } catch (error) {\n // Missing/invalid config.json is handled by the caller.\n }\n return undefined;\n}\n\nasync function promptForToken(): Promise<string> {\n return new Promise((resolve) => {\n const stdin = process.stdin;\n const wasRaw = stdin.isRaw;\n\n stdin.setRawMode(true);\n stdin.resume();\n process.stderr.write(\"Enter controller token: \");\n\n let token = \"\";\n stdin.on(\"data\", onChar);\n\n function onChar(char: Buffer): void {\n const code = char[0];\n if (code === 13 || code === 10) {\n stdin.removeListener(\"data\", onChar);\n stdin.setRawMode(wasRaw ?? false);\n stdin.pause();\n process.stderr.write(\"\\n\");\n resolve(token);\n return;\n }\n if (code === 3) {\n stdin.removeListener(\"data\", onChar);\n stdin.setRawMode(wasRaw ?? false);\n process.stderr.write(\"\\n\");\n process.exit(1);\n }\n if (code === 127 || code === 8) {\n token = token.slice(0, -1);\n } else if (code >= 32) {\n token += char.toString(\"utf8\");\n }\n }\n });\n}\n\nasync function parseArgs(argv: string[]): Promise<CliArgs> {\n let controllerUrl = process.env.UNS_CONTROLLER_URL?.trim() ?? \"\";\n let token = process.env.UNS_CONTROLLER_TOKEN?.trim() ?? \"\";\n let projectRoot =\n process.env.UNS_METADATA_PROJECT_ROOT?.trim() ||\n process.env.UNS_SCHEMA_PROJECT_ROOT?.trim() ||\n undefined;\n let dryRun = false;\n let topicsOnly = false;\n let tagsOnly = false;\n let assetsOnly = false;\n let help = false;\n\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index];\n\n if (arg === \"--help\" || arg === \"-h\") {\n help = true;\n continue;\n }\n if (arg === \"--dry-run\") {\n dryRun = true;\n continue;\n }\n if (arg === \"--topics-only\") {\n topicsOnly = true;\n continue;\n }\n if (arg === \"--tags-only\") {\n tagsOnly = true;\n continue;\n }\n if (arg === \"--assets-only\") {\n assetsOnly = true;\n continue;\n }\n if (arg === \"--project-root\") {\n projectRoot = readRequiredValue(argv, ++index, \"--project-root\");\n continue;\n }\n if (arg.startsWith(\"--project-root=\")) {\n projectRoot = arg.slice(\"--project-root=\".length);\n continue;\n }\n if (arg === \"--controller-url\") {\n controllerUrl = readRequiredValue(argv, ++index, \"--controller-url\");\n continue;\n }\n if (arg.startsWith(\"--controller-url=\")) {\n controllerUrl = arg.slice(\"--controller-url=\".length);\n continue;\n }\n if (arg === \"--token\") {\n token = readRequiredValue(argv, ++index, \"--token\");\n continue;\n }\n if (arg.startsWith(\"--token=\")) {\n token = arg.slice(\"--token=\".length);\n continue;\n }\n\n throw new Error(`Unknown argument: ${arg}`);\n }\n\n if (!controllerUrl) {\n const configUrl = await getControllerUrlFromConfig();\n if (configUrl) {\n controllerUrl = configUrl;\n }\n }\n\n if (!help) {\n if (!controllerUrl) {\n throw new Error(\"Missing controller URL. Use --controller-url, set UNS_CONTROLLER_URL, or provide config.json with uns.rest.\");\n }\n if (!token) {\n if (process.stdin.isTTY) {\n token = await promptForToken();\n }\n if (!token) {\n throw new Error(\"Missing controller token. Use --token, set UNS_CONTROLLER_TOKEN, or provide it interactively.\");\n }\n }\n if (token.toLowerCase().startsWith(\"bearer \")) {\n token = token.slice(7).trim();\n }\n controllerUrl = normalizeControllerUrl(controllerUrl);\n assertValidUrl(controllerUrl);\n }\n\n return {\n controllerUrl,\n token,\n projectRoot,\n dryRun,\n topicsOnly,\n tagsOnly,\n assetsOnly,\n help,\n };\n}\n\nfunction readRequiredValue(argv: string[], index: number, flag: string): string {\n const value = argv[index];\n if (!value) {\n throw new Error(`Missing value for ${flag}.`);\n }\n return value;\n}\n\nfunction assertValidUrl(value: string): void {\n try {\n new URL(value);\n } catch (error) {\n throw new Error(`Invalid controller URL \"${value}\". Expected an absolute URL such as http://localhost:3200.`);\n }\n}\n\nfunction normalizeControllerUrl(value: string): string {\n return value.replace(/\\/+$/, \"\").replace(/\\/api$/i, \"\");\n}\n\nfunction printHelp(): void {\n console.log(`Usage: tsx packages/uns-core/src/tools/sync-uns-metadata.ts [options]\n\nPull UNS metadata from a controller REST export and refresh generated TypeScript\ntopic, tag, and asset artifacts.\n\nOptions:\n --controller-url <url> Controller base URL (env: UNS_CONTROLLER_URL, or config.json > uns.rest)\n --token <token> Bearer token with export:uns-reference scope (env: UNS_CONTROLLER_TOKEN)\n --project-root <dir> Write into a generated microservice project root (env: UNS_METADATA_PROJECT_ROOT or UNS_SCHEMA_PROJECT_ROOT).\n When omitted, the tool auto-detects a generated project from the current working directory\n and otherwise updates the uns-kit repo templates.\n --dry-run Report file changes without writing anything\n --topics-only Refresh only src/uns/uns-topics.ts\n --tags-only Refresh only src/uns/uns-tags.ts\n --assets-only Refresh only src/uns/uns-assets.ts\n --help, -h Show this help\n`);\n}\n\nasync function resolveSyncTarget(args: CliArgs): Promise<SyncTarget> {\n if (args.projectRoot) {\n return buildProjectTarget(path.resolve(process.cwd(), args.projectRoot));\n }\n\n const currentDir = process.cwd();\n if (await looksLikeGeneratedProjectRoot(currentDir)) {\n return buildProjectTarget(currentDir);\n }\n\n return buildRepoTarget(await findRepoRoot());\n}\n\nasync function findRepoRoot(): Promise<string> {\n const currentDir = path.dirname(fileURLToPath(import.meta.url));\n let probeDir = currentDir;\n\n while (true) {\n const packageJsonPath = path.join(probeDir, \"package.json\");\n try {\n const raw = await readFile(packageJsonPath, \"utf8\");\n const pkg = JSON.parse(raw) as { name?: string };\n if (pkg.name === \"@uns-kit/core\") {\n return path.resolve(probeDir, \"../..\");\n }\n } catch (error) {\n // Ignore missing package.json and continue walking upward.\n }\n\n const parentDir = path.dirname(probeDir);\n if (parentDir === probeDir) break;\n probeDir = parentDir;\n }\n\n throw new Error(\"Could not locate the @uns-kit/core package directory from sync-uns-metadata.ts.\");\n}\n\nfunction buildRepoTarget(repoRoot: string): SyncTarget {\n return {\n rootDir: repoRoot,\n label: \"uns-kit repo templates\",\n topicsGenerated: [\n path.join(repoRoot, \"packages/uns-core/src/uns/uns-topics.ts\"),\n path.join(repoRoot, \"packages/uns-cli/templates/default/src/uns/uns-topics.ts\"),\n ],\n tagsGenerated: [\n path.join(repoRoot, \"packages/uns-core/src/uns/uns-tags.ts\"),\n path.join(repoRoot, \"packages/uns-cli/templates/default/src/uns/uns-tags.ts\"),\n ],\n assetsGenerated: [path.join(repoRoot, \"packages/uns-cli/templates/default/src/uns/uns-assets.ts\")],\n };\n}\n\nfunction buildProjectTarget(projectRoot: string): SyncTarget {\n return {\n rootDir: projectRoot,\n label: \"generated microservice project\",\n topicsGenerated: [path.join(projectRoot, \"src/uns/uns-topics.ts\")],\n tagsGenerated: [path.join(projectRoot, \"src/uns/uns-tags.ts\")],\n assetsGenerated: [path.join(projectRoot, \"src/uns/uns-assets.ts\")],\n };\n}\n\nasync function looksLikeGeneratedProjectRoot(rootDir: string): Promise<boolean> {\n const [hasPackageJson, hasConfigJson, hasUnsDir] = await Promise.all([\n pathExists(path.join(rootDir, \"package.json\")),\n pathExists(path.join(rootDir, \"config.json\")),\n pathExists(path.join(rootDir, \"src/uns\")),\n ]);\n\n return hasPackageJson && hasConfigJson && hasUnsDir;\n}\n\nasync function pathExists(targetPath: string): Promise<boolean> {\n try {\n await access(targetPath);\n return true;\n } catch (error) {\n return false;\n }\n}\n\nfunction buildControllerUrl(controllerUrl: string, relativePath: string): URL {\n const base = new URL(controllerUrl);\n if (!base.pathname.endsWith(\"/\")) {\n base.pathname = `${base.pathname}/`;\n }\n return new URL(relativePath, base);\n}\n\nasync function fetchMetadataDocument(controllerUrl: string, token: string): Promise<MetadataDocument> {\n const url = buildControllerUrl(controllerUrl, \"api/schema/export/uns-metadata\");\n const document = await fetchJson(url, token, \"UNS metadata\");\n if (!document || typeof document !== \"object\" || Array.isArray(document)) {\n throw new Error(`Controller returned an invalid UNS metadata document from ${url.toString()}.`);\n }\n return document as MetadataDocument;\n}\n\nasync function fetchJson(url: URL, token: string, label: string): Promise<unknown> {\n let response: Response;\n\n try {\n response = await fetch(url, {\n headers: {\n Accept: \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n });\n } catch (error) {\n throw new Error(`Network failure while fetching ${label} export from ${url.toString()}: ${(error as Error).message}`);\n }\n\n const text = await response.text();\n if (!response.ok) {\n throw new Error(formatHttpError(response, url, label, text));\n }\n\n try {\n return JSON.parse(text);\n } catch (error) {\n throw new Error(`Controller returned invalid JSON for the ${label} export from ${url.toString()}.`);\n }\n}\n\nfunction formatHttpError(response: Response, url: URL, label: string, body: string): string {\n if (response.status === 401) {\n return \"Token is invalid or expired (401). Provide a valid token via --token, UNS_CONTROLLER_TOKEN, or the interactive prompt.\";\n }\n if (response.status === 403) {\n return `Forbidden (403) while fetching ${label} export from ${url.toString()}. The token must be admin or include export:uns-reference.`;\n }\n\n const detail = extractErrorDetail(body);\n return `Failed to fetch ${label} export from ${url.toString()}: HTTP ${response.status}${detail ? ` - ${detail}` : \"\"}`;\n}\n\nfunction extractErrorDetail(body: string): string {\n const trimmed = body.trim();\n if (!trimmed) return \"\";\n\n try {\n const parsed = JSON.parse(trimmed) as { error?: unknown; message?: unknown };\n if (typeof parsed.error === \"string\" && parsed.error.trim()) return parsed.error.trim();\n if (typeof parsed.message === \"string\" && parsed.message.trim()) return parsed.message.trim();\n } catch (error) {\n // Fall through to plain text handling.\n }\n\n return trimmed.replace(/\\s+/g, \" \").slice(0, 200);\n}\n\nfunction readStringArray(value: unknown, label: string): string[] {\n if (!Array.isArray(value)) {\n throw new Error(`UNS metadata export is missing a valid ${label} array.`);\n }\n return Array.from(\n new Set(value.filter((entry): entry is string => typeof entry === \"string\" && entry.trim().length > 0).map(entry => entry.trim())),\n ).sort((left, right) => left.localeCompare(right));\n}\n\nfunction readAssetEntries(value: unknown): Array<{ name: string; description: string | null }> {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(\"UNS metadata export is missing a valid assets object.\");\n }\n\n return Object.entries(value as Record<string, AssetMetadata>)\n .map(([name, metadata]) => ({\n name: name.trim(),\n description: typeof metadata?.description === \"string\" && metadata.description.trim()\n ? metadata.description.trim()\n : null,\n }))\n .filter(entry => entry.name.length > 0)\n .sort((left, right) => left.name.localeCompare(right.name));\n}\n\nfunction normalizeTopicPath(value: string): string {\n const normalized = value.trim().replace(/^\\/+|\\/+$/g, \"\");\n return normalized ? `${normalized}/` : \"\";\n}\n\nexport function renderUnsTopicsTs(topics: string[]): string {\n const uniqueTopics = Array.from(new Set(topics.map(normalizeTopicPath).filter(Boolean)))\n .sort((left, right) => left.localeCompare(right));\n const members = uniqueTopics.map(topic => ` | ${JSON.stringify(topic)}`).join(\"\\n\");\n return `// Generated UNS topic union. Run \\`pnpm run sync-uns-metadata\\` to update.\\nexport type UnsTopics =\\n${members ? `${members}\\n` : \"\"} | (string & {});\\n`;\n}\n\nexport function renderUnsTagsTs(tags: string[]): string {\n const uniqueTags = Array.from(new Set(tags.map(tag => tag.trim()).filter(Boolean)))\n .sort((left, right) => left.localeCompare(right));\n const members = uniqueTags.map(tag => ` | ${JSON.stringify(tag)}`).join(\"\\n\");\n return `// Generated UNS tag union. Run \\`pnpm run sync-uns-metadata\\` to update.\\nexport type UnsTags =\\n${members ? `${members}\\n` : \"\"} | (string & {});\\n`;\n}\n\nexport function renderUnsAssetsTs(assets: Array<{ name: string; description: string | null }>): string {\n const entries = assets.length\n ? assets.map((entry) => {\n const comment = entry.description ? ` /** ${sanitizeComment(entry.description)} */\\n` : \"\";\n return `${comment} ${JSON.stringify(entry.name)}: ${JSON.stringify(entry.name)},`;\n }).join(\"\\n\")\n : ` \"asset\": \"asset\",`;\n\n const helpers = `\nexport function resolveGeneratedAsset(name: keyof typeof GeneratedAssets): (typeof GeneratedAssets)[keyof typeof GeneratedAssets];\nexport function resolveGeneratedAsset<T extends string>(name: T): (typeof GeneratedAssets)[keyof typeof GeneratedAssets] | T;\nexport function resolveGeneratedAsset(name: string): string {\n return (GeneratedAssets as Record<string, string>)[name] ?? name;\n}\n`;\n\n return `// Generated UNS asset list. Run \\`pnpm run sync-uns-metadata\\` to update.\\nexport const GeneratedAssets = {\\n${entries}\\n} as const;\\nexport type GeneratedAssetName = typeof GeneratedAssets[keyof typeof GeneratedAssets];\\n${helpers}`;\n}\n\nfunction sanitizeComment(text: string): string {\n return text.replace(/\\*\\//g, \"*\\\\/\");\n}\n\nasync function updateGeneratedFiles(\n filePaths: string[],\n content: string,\n rootDir: string,\n dryRun: boolean,\n): Promise<FileChangeResult[]> {\n return Promise.all(filePaths.map((filePath) => updateFile(filePath, content, rootDir, dryRun)));\n}\n\nasync function updateFile(\n filePath: string,\n content: string,\n rootDir: string,\n dryRun: boolean,\n): Promise<FileChangeResult> {\n const current = await readTextFileIfExists(filePath);\n const changed = current !== content;\n const relativePath = path.relative(rootDir, filePath);\n\n if (dryRun) {\n console.log(`${changed ? \"Would update\" : \"No change\"} ${relativePath}`);\n return { changed, relativePath };\n }\n\n if (!changed) {\n console.log(`Unchanged ${relativePath}`);\n return { changed: false, relativePath };\n }\n\n await writeTextFileIfChanged(filePath, content);\n console.log(`Updated ${relativePath}`);\n return { changed: true, relativePath };\n}\n\nfunction printSummary(summary: {\n controllerUrl: string;\n targetLabel: string;\n targetRoot: string;\n dryRun: boolean;\n results: { label: string; results: FileChangeResult[] }[];\n}): void {\n console.log(\"\");\n console.log(summary.dryRun ? \"Dry-run summary\" : \"Sync summary\");\n console.log(` Controller: ${summary.controllerUrl}`);\n console.log(` Target: ${summary.targetLabel}`);\n console.log(` Root: ${summary.targetRoot}`);\n for (const entry of summary.results) {\n console.log(` ${entry.label}: ${renderGeneratedSummary(entry.results)}`);\n }\n}\n\nfunction renderGeneratedSummary(results: FileChangeResult[]): string {\n if (!results.length) return \"none\";\n const changedCount = results.filter((result) => result.changed).length;\n return `${changedCount}/${results.length} file${results.length === 1 ? \"\" : \"s\"} changed`;\n}\n\nif (isDirectExecution) {\n main().catch((error) => {\n console.error(\"Failed to sync UNS metadata:\", error instanceof Error ? error.message : error);\n process.exitCode = 1;\n });\n}\n"]}
|
|
@@ -211,6 +211,7 @@ async function parseArgs(argv) {
|
|
|
211
211
|
if (token.toLowerCase().startsWith("bearer ")) {
|
|
212
212
|
token = token.slice(7).trim();
|
|
213
213
|
}
|
|
214
|
+
controllerUrl = normalizeControllerUrl(controllerUrl);
|
|
214
215
|
assertValidUrl(controllerUrl);
|
|
215
216
|
}
|
|
216
217
|
return {
|
|
@@ -249,24 +250,27 @@ function assertValidUrl(value) {
|
|
|
249
250
|
throw new Error(`Invalid controller URL "${value}". Expected an absolute URL such as http://localhost:3200.`);
|
|
250
251
|
}
|
|
251
252
|
}
|
|
253
|
+
function normalizeControllerUrl(value) {
|
|
254
|
+
return value.replace(/\/+$/, "").replace(/\/api$/i, "");
|
|
255
|
+
}
|
|
252
256
|
function printHelp() {
|
|
253
|
-
console.log(`Usage: tsx packages/uns-core/src/tools/sync-uns-schema.ts [options]
|
|
254
|
-
|
|
255
|
-
Pull the canonical UNS schema export from a controller and refresh the local
|
|
256
|
-
JSON files plus generated TypeScript artifacts.
|
|
257
|
-
|
|
258
|
-
Options:
|
|
259
|
-
--controller-url <url> Controller base URL (env: UNS_CONTROLLER_URL, or config.json > uns.rest)
|
|
260
|
-
--token <token> Bearer token for REST export (env: UNS_CONTROLLER_TOKEN)
|
|
261
|
-
--status <value> Dictionary status filter: ${STATUS_VALUES.join("|")} (default: ${DEFAULT_STATUS}, env: UNS_SCHEMA_STATUS)
|
|
262
|
-
--project-root <dir> Write into a generated microservice project root (env: UNS_SCHEMA_PROJECT_ROOT).
|
|
263
|
-
When omitted, the tool auto-detects a generated project from the current working directory
|
|
264
|
-
and otherwise updates the uns-kit repo templates.
|
|
265
|
-
--dry-run Report file changes without writing anything
|
|
266
|
-
--dictionary-only Sync only the UNS dictionary export
|
|
267
|
-
--measurements-only Sync only the UNS measurements export
|
|
268
|
-
--skip-generate Skip generated TypeScript refresh
|
|
269
|
-
--help, -h Show this help
|
|
257
|
+
console.log(`Usage: tsx packages/uns-core/src/tools/sync-uns-schema.ts [options]
|
|
258
|
+
|
|
259
|
+
Pull the canonical UNS schema export from a controller and refresh the local
|
|
260
|
+
JSON files plus generated TypeScript artifacts.
|
|
261
|
+
|
|
262
|
+
Options:
|
|
263
|
+
--controller-url <url> Controller base URL (env: UNS_CONTROLLER_URL, or config.json > uns.rest)
|
|
264
|
+
--token <token> Bearer token for REST export (env: UNS_CONTROLLER_TOKEN)
|
|
265
|
+
--status <value> Dictionary status filter: ${STATUS_VALUES.join("|")} (default: ${DEFAULT_STATUS}, env: UNS_SCHEMA_STATUS)
|
|
266
|
+
--project-root <dir> Write into a generated microservice project root (env: UNS_SCHEMA_PROJECT_ROOT).
|
|
267
|
+
When omitted, the tool auto-detects a generated project from the current working directory
|
|
268
|
+
and otherwise updates the uns-kit repo templates.
|
|
269
|
+
--dry-run Report file changes without writing anything
|
|
270
|
+
--dictionary-only Sync only the UNS dictionary export
|
|
271
|
+
--measurements-only Sync only the UNS measurements export
|
|
272
|
+
--skip-generate Skip generated TypeScript refresh
|
|
273
|
+
--help, -h Show this help
|
|
270
274
|
`);
|
|
271
275
|
}
|
|
272
276
|
async function findRepoRoot() {
|