@uns-kit/cli 0.0.61 → 0.0.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -3
- package/dist/index.js +229 -43
- package/package.json +6 -6
- package/templates/cron/src/examples/cron-example.ts +30 -20
- package/templates/default/README.md +1 -1
- package/templates/default/config.json +3 -3
package/README.md
CHANGED
|
@@ -46,7 +46,8 @@ When `git` is available on your PATH the scaffold also initializes a fresh repos
|
|
|
46
46
|
## Commands
|
|
47
47
|
|
|
48
48
|
- `uns-kit create <name>` – create a new UNS project in the specified directory.
|
|
49
|
-
- `uns-kit configure [path] [features...]` – run multiple configure templates in sequence (`--all`
|
|
49
|
+
- `uns-kit configure [path] [features...]` – run multiple configure templates in sequence (`--all`, `--overwrite`).
|
|
50
|
+
- `uns-kit configure-templates [path] [templates...]` – copy any template directory (`--all`, `--overwrite`).
|
|
50
51
|
- `uns-kit configure-devops [path]` – add Azure DevOps tooling (dependencies, script, config) to an existing project.
|
|
51
52
|
- `uns-kit configure-vscode [path]` – copy VS Code launch/workspace files into an existing project.
|
|
52
53
|
- `uns-kit configure-codegen [path]` – scaffold GraphQL code generation and UNS refresh scripts.
|
|
@@ -65,7 +66,16 @@ uns-kit configure --all
|
|
|
65
66
|
uns-kit configure ./apps/gateway devops vscode codegen
|
|
66
67
|
```
|
|
67
68
|
|
|
68
|
-
Mix and match feature names after an optional target directory. Use `--all` to apply every available template in one shot.
|
|
69
|
+
Mix and match feature names after an optional target directory. Use `--all` to apply every available template in one shot. Add `--overwrite` to refresh files from newer template versions.
|
|
70
|
+
|
|
71
|
+
### Copy arbitrary templates
|
|
72
|
+
|
|
73
|
+
Need a template that is not wired into a configure feature (or added in a newer release)? Use:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
uns-kit configure-templates --all
|
|
77
|
+
uns-kit configure-templates ./apps/gateway uns-dictionary uns-measurements --overwrite
|
|
78
|
+
```
|
|
69
79
|
|
|
70
80
|
### Configure Azure DevOps
|
|
71
81
|
|
|
@@ -85,7 +95,7 @@ The command prompts for your Azure DevOps organization/project, ensures the remo
|
|
|
85
95
|
uns-kit configure-vscode
|
|
86
96
|
```
|
|
87
97
|
|
|
88
|
-
Copies `.vscode/launch.json` plus a baseline workspace file into the project. Existing files are
|
|
98
|
+
Copies `.vscode/launch.json` plus a baseline workspace file into the project. Existing files are skipped unless you pass `--overwrite`.
|
|
89
99
|
|
|
90
100
|
### Configure GraphQL code generation
|
|
91
101
|
|
|
@@ -106,6 +116,7 @@ pnpm install
|
|
|
106
116
|
```
|
|
107
117
|
|
|
108
118
|
Copies API-oriented examples (under `src/examples/`) and adds `@uns-kit/api` to your project dependencies.
|
|
119
|
+
Use `--overwrite` to refresh the examples after updating `uns-kit`.
|
|
109
120
|
|
|
110
121
|
### Add cron-based scaffolding
|
|
111
122
|
|
|
@@ -115,6 +126,7 @@ pnpm install
|
|
|
115
126
|
```
|
|
116
127
|
|
|
117
128
|
Adds cron-oriented example stubs and installs `@uns-kit/cron`.
|
|
129
|
+
Use `--overwrite` to refresh the examples after updating `uns-kit`.
|
|
118
130
|
|
|
119
131
|
### Add Temporal scaffolding
|
|
120
132
|
|
|
@@ -124,6 +136,7 @@ pnpm install
|
|
|
124
136
|
```
|
|
125
137
|
|
|
126
138
|
Copies Temporal example placeholders and installs `@uns-kit/temporal`.
|
|
139
|
+
Use `--overwrite` to refresh the examples after updating `uns-kit`.
|
|
127
140
|
|
|
128
141
|
### Add Python gateway scaffolding
|
|
129
142
|
|
|
@@ -132,6 +145,7 @@ uns-kit configure-python
|
|
|
132
145
|
```
|
|
133
146
|
|
|
134
147
|
Copies the Python gateway client template (examples, scripts, requirements, proto) into your project so you can iterate on the gRPC gateway from Python alongside your TypeScript project.
|
|
148
|
+
Use `--overwrite` to refresh the examples after updating `uns-kit`.
|
|
135
149
|
|
|
136
150
|
### Extend the Config Schema
|
|
137
151
|
|
package/dist/index.js
CHANGED
|
@@ -24,9 +24,9 @@ async function main() {
|
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
if (command === "configure-config") {
|
|
27
|
-
const targetPath = args
|
|
27
|
+
const { targetPath, overwrite } = parseTemplateCommandArgs(args.slice(1));
|
|
28
28
|
try {
|
|
29
|
-
await configureConfigFiles(targetPath);
|
|
29
|
+
await configureConfigFiles(targetPath, { overwrite });
|
|
30
30
|
}
|
|
31
31
|
catch (error) {
|
|
32
32
|
console.error(error.message);
|
|
@@ -45,10 +45,21 @@ async function main() {
|
|
|
45
45
|
}
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
+
if (command === "configure-templates") {
|
|
49
|
+
const configureArgs = args.slice(1);
|
|
50
|
+
try {
|
|
51
|
+
await runConfigureTemplatesCommand(configureArgs);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error(error.message);
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
48
59
|
if (command === "configure-devops") {
|
|
49
|
-
const targetPath = args
|
|
60
|
+
const { targetPath, overwrite } = parseTemplateCommandArgs(args.slice(1));
|
|
50
61
|
try {
|
|
51
|
-
await configureDevops(targetPath);
|
|
62
|
+
await configureDevops(targetPath, { overwrite });
|
|
52
63
|
}
|
|
53
64
|
catch (error) {
|
|
54
65
|
console.error(error.message);
|
|
@@ -57,9 +68,9 @@ async function main() {
|
|
|
57
68
|
return;
|
|
58
69
|
}
|
|
59
70
|
if (command === "configure-vscode") {
|
|
60
|
-
const targetPath = args
|
|
71
|
+
const { targetPath, overwrite } = parseTemplateCommandArgs(args.slice(1));
|
|
61
72
|
try {
|
|
62
|
-
await configureVscode(targetPath);
|
|
73
|
+
await configureVscode(targetPath, { overwrite });
|
|
63
74
|
}
|
|
64
75
|
catch (error) {
|
|
65
76
|
console.error(error.message);
|
|
@@ -68,9 +79,9 @@ async function main() {
|
|
|
68
79
|
return;
|
|
69
80
|
}
|
|
70
81
|
if (command === "configure-codegen") {
|
|
71
|
-
const targetPath = args
|
|
82
|
+
const { targetPath, overwrite } = parseTemplateCommandArgs(args.slice(1));
|
|
72
83
|
try {
|
|
73
|
-
await configureCodegen(targetPath);
|
|
84
|
+
await configureCodegen(targetPath, { overwrite });
|
|
74
85
|
}
|
|
75
86
|
catch (error) {
|
|
76
87
|
console.error(error.message);
|
|
@@ -79,9 +90,9 @@ async function main() {
|
|
|
79
90
|
return;
|
|
80
91
|
}
|
|
81
92
|
if (command === "configure-api") {
|
|
82
|
-
const targetPath = args
|
|
93
|
+
const { targetPath, overwrite } = parseTemplateCommandArgs(args.slice(1));
|
|
83
94
|
try {
|
|
84
|
-
await configureApi(targetPath);
|
|
95
|
+
await configureApi(targetPath, { overwrite });
|
|
85
96
|
}
|
|
86
97
|
catch (error) {
|
|
87
98
|
console.error(error.message);
|
|
@@ -90,9 +101,9 @@ async function main() {
|
|
|
90
101
|
return;
|
|
91
102
|
}
|
|
92
103
|
if (command === "configure-cron") {
|
|
93
|
-
const targetPath = args
|
|
104
|
+
const { targetPath, overwrite } = parseTemplateCommandArgs(args.slice(1));
|
|
94
105
|
try {
|
|
95
|
-
await configureCron(targetPath);
|
|
106
|
+
await configureCron(targetPath, { overwrite });
|
|
96
107
|
}
|
|
97
108
|
catch (error) {
|
|
98
109
|
console.error(error.message);
|
|
@@ -101,9 +112,9 @@ async function main() {
|
|
|
101
112
|
return;
|
|
102
113
|
}
|
|
103
114
|
if (command === "configure-temporal") {
|
|
104
|
-
const targetPath = args
|
|
115
|
+
const { targetPath, overwrite } = parseTemplateCommandArgs(args.slice(1));
|
|
105
116
|
try {
|
|
106
|
-
await configureTemporal(targetPath);
|
|
117
|
+
await configureTemporal(targetPath, { overwrite });
|
|
107
118
|
}
|
|
108
119
|
catch (error) {
|
|
109
120
|
console.error(error.message);
|
|
@@ -112,9 +123,9 @@ async function main() {
|
|
|
112
123
|
return;
|
|
113
124
|
}
|
|
114
125
|
if (command === "configure-python") {
|
|
115
|
-
const targetPath = args
|
|
126
|
+
const { targetPath, overwrite } = parseTemplateCommandArgs(args.slice(1));
|
|
116
127
|
try {
|
|
117
|
-
await configurePython(targetPath);
|
|
128
|
+
await configurePython(targetPath, { overwrite });
|
|
118
129
|
}
|
|
119
130
|
catch (error) {
|
|
120
131
|
console.error(error.message);
|
|
@@ -123,9 +134,9 @@ async function main() {
|
|
|
123
134
|
return;
|
|
124
135
|
}
|
|
125
136
|
if (command === "configure-uns-reference") {
|
|
126
|
-
const targetPath = args
|
|
137
|
+
const { targetPath, overwrite } = parseTemplateCommandArgs(args.slice(1));
|
|
127
138
|
try {
|
|
128
|
-
await configureUnsReference(targetPath);
|
|
139
|
+
await configureUnsReference(targetPath, { overwrite });
|
|
129
140
|
}
|
|
130
141
|
catch (error) {
|
|
131
142
|
console.error(error.message);
|
|
@@ -158,7 +169,8 @@ function printHelp() {
|
|
|
158
169
|
"\nUsage: uns-kit <command> [options]\n" +
|
|
159
170
|
"\nCommands:\n" +
|
|
160
171
|
" create <name> Scaffold a new UNS application\n" +
|
|
161
|
-
" configure [dir] [features...] Configure multiple templates (--all
|
|
172
|
+
" configure [dir] [features...] Configure multiple templates (--all, --overwrite)\n" +
|
|
173
|
+
" configure-templates [dir] [templates...] Copy any template directory (--all, --overwrite)\n" +
|
|
162
174
|
" configure-config [dir] Copy configuration example files\n" +
|
|
163
175
|
" configure-devops [dir] Configure Azure DevOps tooling in an existing project\n" +
|
|
164
176
|
" configure-vscode [dir] Add VS Code workspace configuration files\n" +
|
|
@@ -235,7 +247,7 @@ async function initGitRepository(targetDir) {
|
|
|
235
247
|
return false;
|
|
236
248
|
}
|
|
237
249
|
}
|
|
238
|
-
async function configureDevops(targetPath) {
|
|
250
|
+
async function configureDevops(targetPath, options) {
|
|
239
251
|
const targetDir = path.resolve(process.cwd(), targetPath ?? ".");
|
|
240
252
|
const packagePath = path.join(targetDir, "package.json");
|
|
241
253
|
const configPath = path.join(targetDir, "config.json");
|
|
@@ -330,9 +342,16 @@ async function configureDevops(targetPath) {
|
|
|
330
342
|
throw new Error("Azure Pipelines template is missing. Please ensure templates/azure-pipelines.yml exists.");
|
|
331
343
|
}
|
|
332
344
|
const pipelineTargetPath = path.join(targetDir, "azure-pipelines.yml");
|
|
345
|
+
const overwrite = !!options?.overwrite;
|
|
333
346
|
let pipelineMessage = "";
|
|
334
347
|
if (await fileExists(pipelineTargetPath)) {
|
|
335
|
-
|
|
348
|
+
if (overwrite) {
|
|
349
|
+
await copyFile(azurePipelineTemplatePath, pipelineTargetPath);
|
|
350
|
+
pipelineMessage = " Overwrote azure-pipelines.yml pipeline definition.";
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
pipelineMessage = " azure-pipelines.yml already exists (skipped).";
|
|
354
|
+
}
|
|
336
355
|
}
|
|
337
356
|
else {
|
|
338
357
|
await copyFile(azurePipelineTemplatePath, pipelineTargetPath);
|
|
@@ -362,7 +381,7 @@ async function configureDevops(targetPath) {
|
|
|
362
381
|
console.log(" Existing package.json already contained required entries.");
|
|
363
382
|
}
|
|
364
383
|
}
|
|
365
|
-
async function configureVscode(targetPath) {
|
|
384
|
+
async function configureVscode(targetPath, options = {}) {
|
|
366
385
|
const targetDir = path.resolve(process.cwd(), targetPath ?? ".");
|
|
367
386
|
const templateDir = path.resolve(__dirname, "../templates/vscode");
|
|
368
387
|
try {
|
|
@@ -371,7 +390,7 @@ async function configureVscode(targetPath) {
|
|
|
371
390
|
catch (error) {
|
|
372
391
|
throw new Error("VS Code template directory is missing. Please ensure templates/vscode is available.");
|
|
373
392
|
}
|
|
374
|
-
const { copied, skipped } = await copyTemplateDirectory(templateDir, targetDir, targetDir);
|
|
393
|
+
const { copied, skipped, overwritten } = await copyTemplateDirectory(templateDir, targetDir, targetDir, options);
|
|
375
394
|
console.log("\nVS Code configuration files processed.");
|
|
376
395
|
if (copied.length) {
|
|
377
396
|
console.log(" Added:");
|
|
@@ -379,6 +398,12 @@ async function configureVscode(targetPath) {
|
|
|
379
398
|
console.log(` ${file}`);
|
|
380
399
|
}
|
|
381
400
|
}
|
|
401
|
+
if (overwritten.length) {
|
|
402
|
+
console.log(" Overwritten:");
|
|
403
|
+
for (const file of overwritten) {
|
|
404
|
+
console.log(` ${file}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
382
407
|
if (skipped.length) {
|
|
383
408
|
console.log(" Skipped (already exists):");
|
|
384
409
|
for (const file of skipped) {
|
|
@@ -389,7 +414,7 @@ async function configureVscode(targetPath) {
|
|
|
389
414
|
console.log(" No files were found in the VS Code template directory.");
|
|
390
415
|
}
|
|
391
416
|
}
|
|
392
|
-
async function configureConfigFiles(targetPath) {
|
|
417
|
+
async function configureConfigFiles(targetPath, options = {}) {
|
|
393
418
|
const targetDir = path.resolve(process.cwd(), targetPath ?? ".");
|
|
394
419
|
const templateDir = path.resolve(__dirname, "../templates/config-files");
|
|
395
420
|
try {
|
|
@@ -398,8 +423,8 @@ async function configureConfigFiles(targetPath) {
|
|
|
398
423
|
catch (error) {
|
|
399
424
|
throw new Error("Configuration template directory is missing. Please ensure templates/config-files is available.");
|
|
400
425
|
}
|
|
401
|
-
const { copied, skipped } = await copyTemplateDirectory(templateDir, targetDir, targetDir);
|
|
402
|
-
const configFilesToAdjust = copied.filter((file) => {
|
|
426
|
+
const { copied, skipped, overwritten } = await copyTemplateDirectory(templateDir, targetDir, targetDir, options);
|
|
427
|
+
const configFilesToAdjust = [...copied, ...overwritten].filter((file) => {
|
|
403
428
|
const filename = path.basename(file);
|
|
404
429
|
return filename.toLowerCase().startsWith("config") && filename.toLowerCase().endsWith(".json");
|
|
405
430
|
});
|
|
@@ -413,6 +438,12 @@ async function configureConfigFiles(targetPath) {
|
|
|
413
438
|
console.log(` ${file}`);
|
|
414
439
|
}
|
|
415
440
|
}
|
|
441
|
+
if (overwritten.length) {
|
|
442
|
+
console.log(" Overwritten:");
|
|
443
|
+
for (const file of overwritten) {
|
|
444
|
+
console.log(` ${file}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
416
447
|
if (skipped.length) {
|
|
417
448
|
console.log(" Skipped (already exists):");
|
|
418
449
|
for (const file of skipped) {
|
|
@@ -436,7 +467,7 @@ async function applyConfigTemplatePlaceholders(targetDir, relativePaths) {
|
|
|
436
467
|
await replaceConfigTemplatePlaceholders(absolutePath, replacements, packageName);
|
|
437
468
|
}
|
|
438
469
|
}
|
|
439
|
-
async function configureCodegen(targetPath) {
|
|
470
|
+
async function configureCodegen(targetPath, options = {}) {
|
|
440
471
|
const targetDir = path.resolve(process.cwd(), targetPath ?? ".");
|
|
441
472
|
const templateDir = path.resolve(__dirname, "../templates/codegen");
|
|
442
473
|
const packagePath = path.join(targetDir, "package.json");
|
|
@@ -457,7 +488,7 @@ async function configureCodegen(targetPath) {
|
|
|
457
488
|
throw error;
|
|
458
489
|
}
|
|
459
490
|
const pkg = JSON.parse(pkgRaw);
|
|
460
|
-
const { copied, skipped } = await copyTemplateDirectory(templateDir, targetDir, targetDir);
|
|
491
|
+
const { copied, skipped, overwritten } = await copyTemplateDirectory(templateDir, targetDir, targetDir, options);
|
|
461
492
|
const devDeps = (pkg.devDependencies ??= {});
|
|
462
493
|
const deps = pkg.dependencies ?? {};
|
|
463
494
|
const requiredDevDeps = {
|
|
@@ -499,6 +530,12 @@ async function configureCodegen(targetPath) {
|
|
|
499
530
|
console.log(` ${file}`);
|
|
500
531
|
}
|
|
501
532
|
}
|
|
533
|
+
if (overwritten.length) {
|
|
534
|
+
console.log(" Overwritten files:");
|
|
535
|
+
for (const file of overwritten) {
|
|
536
|
+
console.log(` ${file}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
502
539
|
if (skipped.length) {
|
|
503
540
|
console.log(" Skipped existing files:");
|
|
504
541
|
for (const file of skipped) {
|
|
@@ -515,50 +552,56 @@ async function configureCodegen(targetPath) {
|
|
|
515
552
|
console.log(" Existing package.json already contained required scripts and dependencies.");
|
|
516
553
|
}
|
|
517
554
|
}
|
|
518
|
-
async function configureApi(targetPath) {
|
|
555
|
+
async function configureApi(targetPath, options = {}) {
|
|
519
556
|
await configurePlugin({
|
|
520
557
|
targetPath,
|
|
521
558
|
templateName: "api",
|
|
522
559
|
dependencyName: "@uns-kit/api",
|
|
523
560
|
dependencySpecifier: resolveUnsPackageSpecifier("@uns-kit/api", "../../uns-api/package.json"),
|
|
524
561
|
label: "UNS API",
|
|
562
|
+
overwrite: options.overwrite,
|
|
525
563
|
});
|
|
526
564
|
}
|
|
527
|
-
async function configureCron(targetPath) {
|
|
565
|
+
async function configureCron(targetPath, options = {}) {
|
|
528
566
|
await configurePlugin({
|
|
529
567
|
targetPath,
|
|
530
568
|
templateName: "cron",
|
|
531
569
|
dependencyName: "@uns-kit/cron",
|
|
532
570
|
dependencySpecifier: resolveUnsPackageSpecifier("@uns-kit/cron", "../../uns-cron/package.json"),
|
|
533
571
|
label: "UNS cron",
|
|
572
|
+
overwrite: options.overwrite,
|
|
534
573
|
});
|
|
535
574
|
}
|
|
536
|
-
async function configureTemporal(targetPath) {
|
|
575
|
+
async function configureTemporal(targetPath, options = {}) {
|
|
537
576
|
await configurePlugin({
|
|
538
577
|
targetPath,
|
|
539
578
|
templateName: "temporal",
|
|
540
579
|
dependencyName: "@uns-kit/temporal",
|
|
541
580
|
dependencySpecifier: resolveUnsPackageSpecifier("@uns-kit/temporal", "../../uns-temporal/package.json"),
|
|
542
581
|
label: "UNS Temporal",
|
|
582
|
+
overwrite: options.overwrite,
|
|
543
583
|
});
|
|
544
584
|
}
|
|
545
|
-
async function configurePython(targetPath) {
|
|
585
|
+
async function configurePython(targetPath, options = {}) {
|
|
546
586
|
await configurePlugin({
|
|
547
587
|
targetPath,
|
|
548
588
|
templateName: "python",
|
|
549
589
|
label: "UNS Python client",
|
|
590
|
+
overwrite: options.overwrite,
|
|
550
591
|
});
|
|
551
592
|
}
|
|
552
|
-
async function configureUnsReference(targetPath) {
|
|
593
|
+
async function configureUnsReference(targetPath, options = {}) {
|
|
553
594
|
await configurePlugin({
|
|
554
595
|
targetPath,
|
|
555
596
|
templateName: "uns-dictionary",
|
|
556
597
|
label: "UNS dictionary (object/attribute metadata)",
|
|
598
|
+
overwrite: options.overwrite,
|
|
557
599
|
});
|
|
558
600
|
await configurePlugin({
|
|
559
601
|
targetPath,
|
|
560
602
|
templateName: "uns-measurements",
|
|
561
603
|
label: "UNS measurements (units)",
|
|
604
|
+
overwrite: options.overwrite,
|
|
562
605
|
});
|
|
563
606
|
}
|
|
564
607
|
const configureFeatureHandlers = {
|
|
@@ -585,7 +628,7 @@ const configureFeatureLabels = {
|
|
|
585
628
|
"uns-reference": "UNS dictionaries (objects/attributes/measurements)",
|
|
586
629
|
};
|
|
587
630
|
async function runConfigureCommand(args) {
|
|
588
|
-
const { targetPath, features } = parseConfigureArgs(args);
|
|
631
|
+
const { targetPath, features, overwrite } = parseConfigureArgs(args);
|
|
589
632
|
if (!features.length) {
|
|
590
633
|
throw new Error("No features specified. Provide feature names or pass --all.");
|
|
591
634
|
}
|
|
@@ -594,18 +637,128 @@ async function runConfigureCommand(args) {
|
|
|
594
637
|
console.log(`Configuring ${featureSummary} in ${location}`);
|
|
595
638
|
for (const feature of features) {
|
|
596
639
|
const handler = configureFeatureHandlers[feature];
|
|
597
|
-
await handler(targetPath);
|
|
640
|
+
await handler(targetPath, { overwrite });
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const DEFAULT_TEMPLATE_NAME = "default";
|
|
644
|
+
async function listTemplateDirectories(templateRoot, options) {
|
|
645
|
+
const entries = await readdir(templateRoot, { withFileTypes: true });
|
|
646
|
+
return entries
|
|
647
|
+
.filter((entry) => entry.isDirectory())
|
|
648
|
+
.map((entry) => entry.name)
|
|
649
|
+
.filter((name) => options?.includeDefault ? true : name !== DEFAULT_TEMPLATE_NAME)
|
|
650
|
+
.sort();
|
|
651
|
+
}
|
|
652
|
+
function parseConfigureTemplatesArgs(args, availableTemplates) {
|
|
653
|
+
const templateMap = new Map(availableTemplates.map((name) => [name.toLowerCase(), name]));
|
|
654
|
+
const templateSet = new Set(templateMap.keys());
|
|
655
|
+
const templateNames = [];
|
|
656
|
+
const unknownTemplates = [];
|
|
657
|
+
let targetPath;
|
|
658
|
+
let overwrite = false;
|
|
659
|
+
let includeAll = false;
|
|
660
|
+
let expectDir = false;
|
|
661
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
662
|
+
const arg = args[i];
|
|
663
|
+
if (expectDir) {
|
|
664
|
+
targetPath = arg;
|
|
665
|
+
expectDir = false;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (arg === "--dir") {
|
|
669
|
+
expectDir = true;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
if (arg === "--all") {
|
|
673
|
+
includeAll = true;
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
if (arg === "--overwrite" || arg === "--force") {
|
|
677
|
+
overwrite = true;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (arg.startsWith("--")) {
|
|
681
|
+
throw new Error(`Unknown option ${arg}.`);
|
|
682
|
+
}
|
|
683
|
+
const normalized = arg.trim().toLowerCase();
|
|
684
|
+
const resolved = templateMap.get(normalized);
|
|
685
|
+
if (resolved) {
|
|
686
|
+
templateNames.push(resolved);
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (!targetPath && (includeAll || i < args.length - 1) && !templateSet.has(normalized)) {
|
|
690
|
+
targetPath = arg;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
unknownTemplates.push(arg);
|
|
694
|
+
}
|
|
695
|
+
if (expectDir) {
|
|
696
|
+
throw new Error("Missing value for --dir.");
|
|
697
|
+
}
|
|
698
|
+
if (unknownTemplates.length) {
|
|
699
|
+
const available = availableTemplates.join(", ");
|
|
700
|
+
throw new Error(`Unknown template(s): ${unknownTemplates.join(", ")}. Available templates: ${available || "none"}.`);
|
|
701
|
+
}
|
|
702
|
+
return { targetPath, overwrite, includeAll, templateNames };
|
|
703
|
+
}
|
|
704
|
+
async function runConfigureTemplatesCommand(args) {
|
|
705
|
+
const templateRoot = path.resolve(__dirname, "../templates");
|
|
706
|
+
const availableTemplates = await listTemplateDirectories(templateRoot, { includeDefault: true });
|
|
707
|
+
const allTemplates = await listTemplateDirectories(templateRoot, { includeDefault: false });
|
|
708
|
+
const { targetPath, overwrite, includeAll, templateNames } = parseConfigureTemplatesArgs(args, availableTemplates);
|
|
709
|
+
const selectedTemplates = new Set();
|
|
710
|
+
if (includeAll) {
|
|
711
|
+
allTemplates.forEach((name) => selectedTemplates.add(name));
|
|
712
|
+
}
|
|
713
|
+
for (const name of templateNames) {
|
|
714
|
+
selectedTemplates.add(name);
|
|
715
|
+
}
|
|
716
|
+
if (!selectedTemplates.size) {
|
|
717
|
+
throw new Error("No templates specified. Provide template names or pass --all.");
|
|
718
|
+
}
|
|
719
|
+
const targetDir = path.resolve(process.cwd(), targetPath ?? ".");
|
|
720
|
+
const sortedTemplates = Array.from(selectedTemplates).sort();
|
|
721
|
+
for (const templateName of sortedTemplates) {
|
|
722
|
+
const templateDir = path.resolve(templateRoot, templateName);
|
|
723
|
+
const { copied, skipped, overwritten } = await copyTemplateDirectory(templateDir, targetDir, targetDir, { overwrite });
|
|
724
|
+
console.log(`\nTemplate "${templateName}" processed.`);
|
|
725
|
+
if (copied.length) {
|
|
726
|
+
console.log(" Added:");
|
|
727
|
+
for (const file of copied) {
|
|
728
|
+
console.log(` ${file}`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (overwritten.length) {
|
|
732
|
+
console.log(" Overwritten:");
|
|
733
|
+
for (const file of overwritten) {
|
|
734
|
+
console.log(` ${file}`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (skipped.length) {
|
|
738
|
+
console.log(" Skipped (already exists):");
|
|
739
|
+
for (const file of skipped) {
|
|
740
|
+
console.log(` ${file}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (!copied.length && !overwritten.length && !skipped.length) {
|
|
744
|
+
console.log(" No template files were copied.");
|
|
745
|
+
}
|
|
598
746
|
}
|
|
599
747
|
}
|
|
600
748
|
function parseConfigureArgs(args) {
|
|
601
749
|
let targetPath;
|
|
602
750
|
let includeAll = false;
|
|
751
|
+
let overwrite = false;
|
|
603
752
|
const featureInputs = [];
|
|
604
753
|
for (const arg of args) {
|
|
605
754
|
if (arg === "--all") {
|
|
606
755
|
includeAll = true;
|
|
607
756
|
continue;
|
|
608
757
|
}
|
|
758
|
+
if (arg === "--overwrite" || arg === "--force") {
|
|
759
|
+
overwrite = true;
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
609
762
|
if (arg.startsWith("--")) {
|
|
610
763
|
throw new Error(`Unknown option ${arg}.`);
|
|
611
764
|
}
|
|
@@ -636,7 +789,26 @@ function parseConfigureArgs(args) {
|
|
|
636
789
|
for (const input of featureInputs) {
|
|
637
790
|
addFeature(resolveConfigureFeatureName(input));
|
|
638
791
|
}
|
|
639
|
-
return { targetPath, features: featureOrder };
|
|
792
|
+
return { targetPath, features: featureOrder, overwrite };
|
|
793
|
+
}
|
|
794
|
+
function parseTemplateCommandArgs(args) {
|
|
795
|
+
let targetPath;
|
|
796
|
+
let overwrite = false;
|
|
797
|
+
for (const arg of args) {
|
|
798
|
+
if (arg === "--overwrite" || arg === "--force") {
|
|
799
|
+
overwrite = true;
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (arg.startsWith("--")) {
|
|
803
|
+
throw new Error(`Unknown option ${arg}.`);
|
|
804
|
+
}
|
|
805
|
+
if (!targetPath) {
|
|
806
|
+
targetPath = arg;
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
throw new Error(`Unexpected argument ${arg}.`);
|
|
810
|
+
}
|
|
811
|
+
return { targetPath, overwrite };
|
|
640
812
|
}
|
|
641
813
|
const configureFeatureAliases = {
|
|
642
814
|
devops: "devops",
|
|
@@ -728,10 +900,11 @@ async function addGitRemote(dir, remoteName, remoteUrl) {
|
|
|
728
900
|
throw new Error(`Failed to add git remote origin: ${error.message}`);
|
|
729
901
|
}
|
|
730
902
|
}
|
|
731
|
-
async function copyTemplateDirectory(sourceDir, targetDir, targetRoot) {
|
|
903
|
+
async function copyTemplateDirectory(sourceDir, targetDir, targetRoot, options = {}) {
|
|
732
904
|
const entries = await readdir(sourceDir, { withFileTypes: true });
|
|
733
905
|
const copied = [];
|
|
734
906
|
const skipped = [];
|
|
907
|
+
const overwritten = [];
|
|
735
908
|
for (const entry of entries) {
|
|
736
909
|
const sourcePath = path.join(sourceDir, entry.name);
|
|
737
910
|
const destinationName = entry.isFile() ? normalizeTemplateFilename(entry.name) : entry.name;
|
|
@@ -739,22 +912,29 @@ async function copyTemplateDirectory(sourceDir, targetDir, targetRoot) {
|
|
|
739
912
|
const relativePath = path.relative(targetRoot, destinationPath) || destinationName;
|
|
740
913
|
if (entry.isDirectory()) {
|
|
741
914
|
await mkdir(destinationPath, { recursive: true });
|
|
742
|
-
const result = await copyTemplateDirectory(sourcePath, destinationPath, targetRoot);
|
|
915
|
+
const result = await copyTemplateDirectory(sourcePath, destinationPath, targetRoot, options);
|
|
743
916
|
copied.push(...result.copied);
|
|
744
917
|
skipped.push(...result.skipped);
|
|
918
|
+
overwritten.push(...result.overwritten);
|
|
745
919
|
continue;
|
|
746
920
|
}
|
|
747
921
|
if (entry.isFile()) {
|
|
748
922
|
await mkdir(path.dirname(destinationPath), { recursive: true });
|
|
749
923
|
if (await fileExists(destinationPath)) {
|
|
750
|
-
|
|
924
|
+
if (options.overwrite) {
|
|
925
|
+
await copyFile(sourcePath, destinationPath);
|
|
926
|
+
overwritten.push(relativePath);
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
skipped.push(relativePath);
|
|
930
|
+
}
|
|
751
931
|
continue;
|
|
752
932
|
}
|
|
753
933
|
await copyFile(sourcePath, destinationPath);
|
|
754
934
|
copied.push(relativePath);
|
|
755
935
|
}
|
|
756
936
|
}
|
|
757
|
-
return { copied, skipped };
|
|
937
|
+
return { copied, skipped, overwritten };
|
|
758
938
|
}
|
|
759
939
|
function normalizeTemplateFilename(filename) {
|
|
760
940
|
if (filename === "gitignore" || filename === ".npmignore") {
|
|
@@ -763,7 +943,7 @@ function normalizeTemplateFilename(filename) {
|
|
|
763
943
|
return filename;
|
|
764
944
|
}
|
|
765
945
|
async function configurePlugin(options) {
|
|
766
|
-
const { targetPath, templateName, dependencyName, dependencySpecifier, label } = options;
|
|
946
|
+
const { targetPath, templateName, dependencyName, dependencySpecifier, label, overwrite } = options;
|
|
767
947
|
const targetDir = path.resolve(process.cwd(), targetPath ?? ".");
|
|
768
948
|
const templateDir = path.resolve(__dirname, `../templates/${templateName}`);
|
|
769
949
|
const packagePath = path.join(targetDir, "package.json");
|
|
@@ -784,7 +964,7 @@ async function configurePlugin(options) {
|
|
|
784
964
|
throw error;
|
|
785
965
|
}
|
|
786
966
|
const pkg = JSON.parse(pkgRaw);
|
|
787
|
-
const { copied, skipped } = await copyTemplateDirectory(templateDir, targetDir, targetDir);
|
|
967
|
+
const { copied, skipped, overwritten } = await copyTemplateDirectory(templateDir, targetDir, targetDir, { overwrite });
|
|
788
968
|
let pkgChanged = false;
|
|
789
969
|
if (dependencyName && dependencySpecifier) {
|
|
790
970
|
const deps = (pkg.dependencies ??= {});
|
|
@@ -803,13 +983,19 @@ async function configurePlugin(options) {
|
|
|
803
983
|
console.log(` ${file}`);
|
|
804
984
|
}
|
|
805
985
|
}
|
|
986
|
+
if (overwritten.length) {
|
|
987
|
+
console.log(" Overwritten files:");
|
|
988
|
+
for (const file of overwritten) {
|
|
989
|
+
console.log(` ${file}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
806
992
|
if (skipped.length) {
|
|
807
993
|
console.log(" Skipped existing files:");
|
|
808
994
|
for (const file of skipped) {
|
|
809
995
|
console.log(` ${file}`);
|
|
810
996
|
}
|
|
811
997
|
}
|
|
812
|
-
if (!copied.length && !skipped.length) {
|
|
998
|
+
if (!copied.length && !overwritten.length && !skipped.length) {
|
|
813
999
|
console.log(" No template files were copied.");
|
|
814
1000
|
}
|
|
815
1001
|
if (dependencyName && dependencySpecifier) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uns-kit/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.63",
|
|
4
4
|
"description": "Command line scaffolding tool for UNS applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"azure-devops-node-api": "^15.1.1",
|
|
29
|
-
"@uns-kit/core": "1.0.
|
|
29
|
+
"@uns-kit/core": "1.0.31"
|
|
30
30
|
},
|
|
31
31
|
"unsKitPackages": {
|
|
32
|
-
"@uns-kit/core": "1.0.
|
|
33
|
-
"@uns-kit/api": "0.0.
|
|
34
|
-
"@uns-kit/cron": "0.0.
|
|
35
|
-
"@uns-kit/temporal": "0.0.
|
|
32
|
+
"@uns-kit/core": "1.0.31",
|
|
33
|
+
"@uns-kit/api": "0.0.41",
|
|
34
|
+
"@uns-kit/cron": "0.0.40",
|
|
35
|
+
"@uns-kit/temporal": "0.0.40"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Change this file according to your specifications and rename it to index.ts
|
|
3
3
|
*/
|
|
4
|
-
import { UnsProxyProcess, ConfigFile, logger } from "@uns-kit/core";
|
|
5
|
-
import
|
|
4
|
+
import { UnsProxyProcess, ConfigFile, logger, UnsEvents } from "@uns-kit/core";
|
|
5
|
+
import { registerAttributeDescriptions, registerObjectTypeDescriptions } from "@uns-kit/core/uns/uns-dictionary-registry.js";
|
|
6
6
|
import "@uns-kit/cron";
|
|
7
|
-
import { type UnsProxyProcessWithCron } from "@uns-kit/cron
|
|
8
|
-
import {
|
|
9
|
-
import { UnsTopics } from "../uns/uns-topics.js";
|
|
10
|
-
import { PhysicalMeasurements } from "@uns-kit/core/uns/uns-measurements.js";
|
|
7
|
+
import { type UnsProxyProcessWithCron } from "@uns-kit/cron";
|
|
8
|
+
import { UnsTopics } from "@uns-kit/core/uns/uns-topics.js";
|
|
11
9
|
import { UnsPacket } from "@uns-kit/core/uns/uns-packet.js";
|
|
12
|
-
import {
|
|
13
|
-
|
|
10
|
+
import {
|
|
11
|
+
GeneratedObjectTypes,
|
|
12
|
+
GeneratedAttributes,
|
|
13
|
+
GeneratedAttributesByType,
|
|
14
|
+
GeneratedAttributeDescriptions,
|
|
15
|
+
GeneratedObjectTypeDescriptions,
|
|
16
|
+
} from "./uns/uns-dictionary.generated.js";
|
|
17
|
+
import { GeneratedPhysicalMeasurements } from "./uns/uns-measurements.generated.js";
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
/**
|
|
@@ -19,12 +23,14 @@ import { EquipmentAttributes } from "@uns-kit/core/uns/uns-attributes.js";
|
|
|
19
23
|
* In the development environment, you are responsible for creating and maintaining this file and its contents.
|
|
20
24
|
*/
|
|
21
25
|
const config = await ConfigFile.loadConfig();
|
|
26
|
+
registerObjectTypeDescriptions(GeneratedObjectTypeDescriptions);
|
|
27
|
+
registerAttributeDescriptions(GeneratedAttributeDescriptions);
|
|
22
28
|
|
|
23
29
|
/**
|
|
24
30
|
* Connect to the output broker and create a crontab proxy
|
|
25
31
|
*/
|
|
26
32
|
const unsProxyProcess = new UnsProxyProcess(config.infra.host!, {processName:config.uns.processName}) as UnsProxyProcessWithCron;;
|
|
27
|
-
const mqttOutput = await unsProxyProcess.createUnsMqttProxy((config.output?.host)!, "templateUnsRttOutput", config.uns.instanceMode
|
|
33
|
+
const mqttOutput = await unsProxyProcess.createUnsMqttProxy((config.output?.host)!, "templateUnsRttOutput", config.uns.instanceMode!, config.uns.handover!, { publishThrottlingDelay: 1000});
|
|
28
34
|
const cronInput = await unsProxyProcess.createCrontabProxy("* * * * * *");
|
|
29
35
|
|
|
30
36
|
/**
|
|
@@ -35,20 +41,24 @@ cronInput.event.on("cronEvent", async (event: UnsEvents["cronEvent"]) => {
|
|
|
35
41
|
try {
|
|
36
42
|
const time = UnsPacket.formatToISO8601(new Date());
|
|
37
43
|
const numberValue: number = 42;
|
|
38
|
-
const message: IUnsMessage = { data: { time, value: numberValue, uom: PhysicalMeasurements.MiliVolt } };
|
|
39
44
|
const topic: UnsTopics = "example/";
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const
|
|
45
|
+
const asset = "asset";
|
|
46
|
+
const assetDescription = "Sample asset";
|
|
47
|
+
|
|
48
|
+
const dataGroup = "sensor";
|
|
49
|
+
|
|
44
50
|
mqttOutput.publishMqttMessage({
|
|
45
51
|
topic,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
asset,
|
|
53
|
+
assetDescription,
|
|
54
|
+
objectType: GeneratedObjectTypes.equipment,
|
|
55
|
+
objectId: "main",
|
|
56
|
+
attributes: [
|
|
57
|
+
{
|
|
58
|
+
attribute: GeneratedAttributesByType["energy-resource"]["current"],
|
|
59
|
+
data: { dataGroup, time, value: numberValue, uom: GeneratedPhysicalMeasurements.Ampere }
|
|
60
|
+
}
|
|
61
|
+
],
|
|
52
62
|
});
|
|
53
63
|
} catch (error) {
|
|
54
64
|
const reason = error instanceof Error ? error : new Error(String(error));
|
|
@@ -32,6 +32,6 @@ Update `config.json` with your broker, UNS URLs, and credentials. The generated
|
|
|
32
32
|
- Run `uns-kit configure-codegen` to scaffold GraphQL code generation and UNS refresh scripts.
|
|
33
33
|
- Edit `uns-dictionary.json` (object types/attributes + descriptions) and run `pnpm run generate-uns-dictionary` to emit `src/uns/uns-dictionary.generated.ts` for IDE hints/metadata; publish calls will automatically fall back to these descriptions when you omit them.
|
|
34
34
|
- Edit `uns-measurements.json` (units + descriptions) and run `pnpm run generate-uns-measurements` to emit `src/uns/uns-measurements.generated.ts` and feed measurement unit IntelliSense.
|
|
35
|
-
- Run `uns-kit configure-api` / `configure-cron` / `configure-temporal` to pull in example stubs and install the matching UNS plugins.
|
|
35
|
+
- Run `uns-kit configure-api` / `configure-cron` / `configure-temporal` to pull in example stubs and install the matching UNS plugins (add `--overwrite` to refresh templates).
|
|
36
36
|
- Run `uns-kit configure-python` to copy the Python gateway client template (examples, scripts, proto).
|
|
37
37
|
- Commit your new project and start building!
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
"password": "123"
|
|
12
12
|
},
|
|
13
13
|
"infra": {
|
|
14
|
-
"host": "localhost
|
|
14
|
+
"host": "localhost"
|
|
15
15
|
},
|
|
16
16
|
"output": {
|
|
17
|
-
"host": "localhost
|
|
17
|
+
"host": "localhost"
|
|
18
18
|
},
|
|
19
19
|
"input": {
|
|
20
|
-
"host": "localhost
|
|
20
|
+
"host": "localhost"
|
|
21
21
|
},
|
|
22
22
|
"devops": {
|
|
23
23
|
"provider": "azure-devops",
|