@kosdev-code/kos-ui-cli 2.0.41 → 2.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kosdev-code/kos-ui-cli",
3
- "version": "2.0.41",
3
+ "version": "2.1.1",
4
4
  "bin": {
5
5
  "kosui": "./src/lib/cli.mjs"
6
6
  },
@@ -9,6 +9,7 @@
9
9
  ],
10
10
  "type": "module",
11
11
  "dependencies": {
12
+ "chalk": "^5.3.0",
12
13
  "prettier": "^2.6.2",
13
14
  "rimraf": "^5.0.1",
14
15
  "plop": "^3.1.2",
@@ -20,7 +21,7 @@
20
21
  "main": "./src/index.js",
21
22
  "kos": {
22
23
  "build": {
23
- "gitHash": "82f501e94dcc050e3ec05bb9fd68f2671112823d"
24
+ "gitHash": "8f34a7c5827b814dc09dd2bcbf41c9195d8b3437"
24
25
  }
25
26
  },
26
27
  "publishConfig": {
package/src/lib/cli.mjs CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import chalk from "chalk";
2
3
  import figlet from "figlet";
3
4
  import minimist from "minimist";
4
5
  import nodePlop from "node-plop";
5
6
  import path, { dirname } from "node:path";
6
7
  import { fileURLToPath } from "node:url";
7
8
  import { clearCache } from "./utils/cache.mjs";
9
+ import { buildNonInteractiveCommand, isRunningInteractively } from "./utils/command-builder.mjs";
8
10
  import { getGeneratorMetadata } from "./utils/generator-loader.mjs";
9
11
 
10
12
  const originalArgs = process.argv.slice(2);
@@ -772,6 +774,15 @@ async function launch() {
772
774
  results.failures.forEach((fail) => {
773
775
  console.error(fail.error || fail.message);
774
776
  });
777
+
778
+ // Output equivalent non-interactive command when running interactively
779
+ if (isRunningInteractively(parsedArgs, hasAnyAnswers) && results.failures.length === 0 && !hasQuiet) {
780
+ const commandString = buildNonInteractiveCommand(command, answers, meta);
781
+ if (commandString) {
782
+ console.log(chalk.cyan("\n[kos-cli] Equivalent non-interactive command:"));
783
+ console.log(chalk.green(` ${commandString}`));
784
+ }
785
+ }
775
786
  } catch (err) {
776
787
  console.error("[kos-cli] Generator run failed:", err.message);
777
788
  process.exit(1);
@@ -20,13 +20,29 @@ export const metadata = {
20
20
  };
21
21
 
22
22
  export default async function (plop) {
23
- const allModels = await getAllModels();
24
- const modelChoices = allModels.map((m) => ({
25
- name: `${m.model} (${m.project})`,
26
- value: m.model,
27
- }));
23
+ // Check if we're in interactive mode by looking at process args
24
+ const isInteractive = process.argv.includes('-i') || process.argv.includes('--interactive');
25
+
26
+ // For interactive mode, use lazy loading. For non-interactive, load immediately.
27
+ let modelChoices;
28
+ if (isInteractive) {
29
+ modelChoices = async () => {
30
+ const allModels = await getAllModels();
31
+ return allModels.map((m) => ({
32
+ name: `${m.model} (${m.project})`,
33
+ value: m.model,
34
+ }));
35
+ };
36
+ } else {
37
+ const allModels = await getAllModels();
38
+ modelChoices = allModels.map((m) => ({
39
+ name: `${m.model} (${m.project})`,
40
+ value: m.model,
41
+ }));
42
+ }
28
43
 
29
44
  plop.setActionType("addFutureToModel", async function (answers) {
45
+ const allModels = await getAllModels();
30
46
  const modelProject = allModels.find(
31
47
  (m) => m.model === answers.modelName
32
48
  )?.project;
@@ -27,21 +27,39 @@ export const metadata = {
27
27
  parentAware: "parentAware",
28
28
  singleton: "singleton",
29
29
  dataServices: "dataServices",
30
+ autoRegister: "autoRegister",
30
31
  dryRun: "dryRun",
31
32
  interactive: "interactive",
32
33
  },
33
34
  };
34
35
 
35
36
  export default async function (plop) {
36
- const allModels = await getAllModels();
37
37
  const libraryProjects = await getModelProjectsWithFallback();
38
- const modelChoices = allModels.map((m) => ({
39
- name: `${m.model} (${m.project})`,
40
- value: m.model,
41
- }));
38
+
39
+ // Check if we're in interactive mode by looking at process args
40
+ const isInteractive = process.argv.includes('-i') || process.argv.includes('--interactive');
41
+
42
+ // For interactive mode, use lazy loading. For non-interactive, load immediately.
43
+ let modelChoices;
44
+ if (isInteractive) {
45
+ modelChoices = async () => {
46
+ const allModels = await getAllModels();
47
+ return allModels.map((m) => ({
48
+ name: `${m.model} (${m.project})`,
49
+ value: m.model,
50
+ }));
51
+ };
52
+ } else {
53
+ const allModels = await getAllModels();
54
+ modelChoices = allModels.map((m) => ({
55
+ name: `${m.model} (${m.project})`,
56
+ value: m.model,
57
+ }));
58
+ }
42
59
 
43
60
  plop.setActionType("createCompanionModel", async function (answers) {
44
61
  const modelProject = await getProjectDetails(answers.modelProject);
62
+ const allModels = await getAllModels();
45
63
  const companionProject = allModels.find(
46
64
  (m) => m.model === answers.companionParent
47
65
  )?.project;
@@ -54,6 +72,7 @@ export default async function (plop) {
54
72
  --dataServices=${!!answers.dataServices} \
55
73
  --singleton=${!!answers.singleton} \
56
74
  --parentAware=${!!answers.parentAware} \
75
+ --autoRegister=${!!answers.autoRegister} \
57
76
  --companion=true \
58
77
  --companionModel=${answers.companionParent} \
59
78
  --companionModelProject=${companionProject} \
@@ -18,20 +18,38 @@ export const metadata = {
18
18
  parentAware: "parentAware",
19
19
  singleton: "singleton",
20
20
  dataServices: "dataServices",
21
+ autoRegister: "autoRegister",
21
22
  dryRun: "dryRun",
22
23
  interactive: "interactive",
23
24
  },
24
25
  };
25
26
 
26
27
  export default async function (plop) {
27
- const allModels = await getAllModels();
28
28
  const allProjects = await getAllProjects();
29
- const modelChoices = allModels.map((m) => ({
30
- name: `${m.model} (${m.project})`,
31
- value: m.model,
32
- }));
29
+
30
+ // Check if we're in interactive mode by looking at process args
31
+ const isInteractive = process.argv.includes('-i') || process.argv.includes('--interactive');
32
+
33
+ // For interactive mode, use lazy loading. For non-interactive, load immediately.
34
+ let modelChoices;
35
+ if (isInteractive) {
36
+ modelChoices = async () => {
37
+ const allModels = await getAllModels();
38
+ return allModels.map((m) => ({
39
+ name: `${m.model} (${m.project})`,
40
+ value: m.model,
41
+ }));
42
+ };
43
+ } else {
44
+ const allModels = await getAllModels();
45
+ modelChoices = allModels.map((m) => ({
46
+ name: `${m.model} (${m.project})`,
47
+ value: m.model,
48
+ }));
49
+ }
33
50
 
34
51
  plop.setActionType("createContainer", async function (answers) {
52
+ const allModels = await getAllModels();
35
53
  const modelProject = allModels.find(
36
54
  (m) => m.model === answers.modelName
37
55
  )?.project;
@@ -43,6 +61,7 @@ export default async function (plop) {
43
61
  --skipRegistration=true \
44
62
  --dataServices=${!!answers.dataServices} \
45
63
  --singleton=${!!answers.singleton} \
64
+ --autoRegister=${!!answers.autoRegister} \
46
65
  --no-interactive ${answers.dryRun ? "--dryRun" : ""} --verbose`;
47
66
 
48
67
  try {
@@ -14,13 +14,30 @@ export const metadata = {
14
14
 
15
15
  export default async function (plop) {
16
16
  const allProjects = await getAllProjects();
17
- const allModels = await getAllModels();
18
- const modelChoices = allModels.map((m) => ({
19
- name: `${m.model} (${m.project})`,
20
- value: m.model,
21
- }));
17
+
18
+ // Check if we're in interactive mode by looking at process args
19
+ const isInteractive = process.argv.includes('-i') || process.argv.includes('--interactive');
20
+
21
+ // For interactive mode, use lazy loading. For non-interactive, load immediately.
22
+ let modelChoices;
23
+ if (isInteractive) {
24
+ modelChoices = async () => {
25
+ const allModels = await getAllModels();
26
+ return allModels.map((m) => ({
27
+ name: `${m.model} (${m.project})`,
28
+ value: m.model,
29
+ }));
30
+ };
31
+ } else {
32
+ const allModels = await getAllModels();
33
+ modelChoices = allModels.map((m) => ({
34
+ name: `${m.model} (${m.project})`,
35
+ value: m.model,
36
+ }));
37
+ }
22
38
 
23
39
  plop.setActionType("createContext", async function (answers) {
40
+ const allModels = await getAllModels();
24
41
  const modelProject = allModels.find(
25
42
  (m) => m.model === answers.modelName
26
43
  )?.project;
@@ -14,13 +14,30 @@ export const metadata = {
14
14
 
15
15
  export default async function (plop) {
16
16
  const allProjects = await getAllProjects();
17
- const allModels = await getAllModels();
18
- const modelChoices = allModels.map((m) => ({
19
- name: `${m.model} (${m.project})`,
20
- value: m.model,
21
- }));
17
+
18
+ // Check if we're in interactive mode by looking at process args
19
+ const isInteractive = process.argv.includes('-i') || process.argv.includes('--interactive');
20
+
21
+ // For interactive mode, use lazy loading. For non-interactive, load immediately.
22
+ let modelChoices;
23
+ if (isInteractive) {
24
+ modelChoices = async () => {
25
+ const allModels = await getAllModels();
26
+ return allModels.map((m) => ({
27
+ name: `${m.model} (${m.project})`,
28
+ value: m.model,
29
+ }));
30
+ };
31
+ } else {
32
+ const allModels = await getAllModels();
33
+ modelChoices = allModels.map((m) => ({
34
+ name: `${m.model} (${m.project})`,
35
+ value: m.model,
36
+ }));
37
+ }
22
38
 
23
39
  plop.setActionType("createHook", async function (answers) {
40
+ const allModels = await getAllModels();
24
41
  const modelProject = allModels.find(
25
42
  (m) => m.model === answers.modelName
26
43
  )?.project;
@@ -15,13 +15,14 @@ export const metadata = {
15
15
  namedArguments: {
16
16
  name: "modelName",
17
17
  modelName: "modelName",
18
- project: "modelProject",
18
+ project: "modelProject",
19
19
  modelProject: "modelProject",
20
20
  container: "container",
21
21
  parentAware: "parentAware",
22
22
  singleton: "singleton",
23
23
  dataServices: "dataServices",
24
24
  futureAware: "futureAware",
25
+ autoRegister: "autoRegister",
25
26
  dryRun: "dryRun",
26
27
  interactive: "interactive"
27
28
  }
@@ -41,6 +42,7 @@ export default async function (plop) {
41
42
  --singleton=${!!answers.singleton} \
42
43
  --parentAware=${!!answers.parentAware} \
43
44
  --futureAware=${answers.futureAware || 'none'} \
45
+ --autoRegister=${!!answers.autoRegister} \
44
46
  --no-interactive ${answers.dryRun ? "--dryRun" : ""} --verbose`;
45
47
 
46
48
  try {
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Builds a non-interactive command string from interactive session answers
3
+ * This helps users understand how to run the same command without prompts
4
+ */
5
+
6
+ /**
7
+ * Converts interactive prompt answers back to CLI command arguments
8
+ * @param {string} command - The generator command name
9
+ * @param {Object} answers - The answers collected from prompts
10
+ * @param {Object} meta - Generator metadata including namedArguments mapping
11
+ * @returns {string|null} The equivalent CLI command string, or null if cannot be built
12
+ */
13
+ export function buildNonInteractiveCommand(command, answers, meta) {
14
+ if (!meta?.namedArguments) {
15
+ return null;
16
+ }
17
+
18
+ const args = [`kosui ${command}`];
19
+ const reverseMap = {};
20
+
21
+ // Create reverse mapping from prompt names to CLI arguments
22
+ Object.entries(meta.namedArguments).forEach(([cliArg, promptName]) => {
23
+ // Skip special flags that don't map to prompts
24
+ if (cliArg !== "interactive" && cliArg !== "dryRun") {
25
+ reverseMap[promptName] = cliArg;
26
+ }
27
+ });
28
+
29
+ // Build command arguments from answers
30
+ Object.entries(answers).forEach(([promptName, value]) => {
31
+ const cliArg = reverseMap[promptName];
32
+ if (cliArg && value !== undefined && value !== null && value !== "") {
33
+ args.push(formatArgument(cliArg, value));
34
+ }
35
+ });
36
+
37
+ // Add dryRun if it was provided
38
+ if (answers.dryRun) {
39
+ args.push("--dryRun");
40
+ }
41
+
42
+ return args.join(" ");
43
+ }
44
+
45
+ /**
46
+ * Formats a single CLI argument based on its value type
47
+ * @param {string} argName - The CLI argument name
48
+ * @param {any} value - The value for the argument
49
+ * @returns {string} The formatted CLI argument string
50
+ */
51
+ function formatArgument(argName, value) {
52
+ // Handle boolean values
53
+ if (typeof value === "boolean") {
54
+ if (value) {
55
+ return `--${argName}`;
56
+ } else {
57
+ // For false booleans, explicitly set to false
58
+ // This ensures the command can be replicated exactly
59
+ return `--${argName}=false`;
60
+ }
61
+ }
62
+
63
+ // Handle string/number values
64
+ const stringValue = String(value);
65
+
66
+ // Check if value needs escaping
67
+ if (needsEscaping(stringValue)) {
68
+ // Use double quotes and escape internal quotes
69
+ return `--${argName}="${stringValue.replace(/"/g, '\\"')}"`;
70
+ }
71
+
72
+ return `--${argName}=${stringValue}`;
73
+ }
74
+
75
+ /**
76
+ * Determines if a string value needs shell escaping
77
+ * @param {string} value - The value to check
78
+ * @returns {boolean} True if the value needs escaping
79
+ */
80
+ function needsEscaping(value) {
81
+ // Check for characters that need escaping in shell
82
+ return /[\s'"$`\\!]/.test(value);
83
+ }
84
+
85
+ /**
86
+ * Determines if the CLI is running in interactive mode
87
+ * @param {Object} parsedArgs - Parsed command line arguments
88
+ * @param {boolean} hasAnyAnswers - Whether any answers were provided via CLI
89
+ * @returns {boolean} True if running in interactive mode
90
+ */
91
+ export function isRunningInteractively(parsedArgs, hasAnyAnswers) {
92
+ const forceInteractive = parsedArgs.interactive || parsedArgs.i;
93
+ return forceInteractive || (!hasAnyAnswers && !parsedArgs.help);
94
+ }