diff --git a/.vscode/launch.json b/.vscode/launch.json
index a67ee461a..556634c4b 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -17,10 +17,9 @@
"env": {
"cmf_cli_feature__use_repository_clients": "true"
},
- "cwd": "${workspaceFolder}/repo",
+ "cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
- "console": "internalConsole",
- "stopAtEntry": false
+ "console": "internalConsole"
},
{
"name": ".NET Core Attach",
diff --git a/.vscode/scaffold-cli-project.sh b/.vscode/scaffold-cli-project.sh
new file mode 100755
index 000000000..3c3817157
--- /dev/null
+++ b/.vscode/scaffold-cli-project.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+set -euo pipefail
+
+scaffold_project() {
+ local projectVersion="${1:-1.0.0}"
+ local mesVersion="${2:-12.0.0}"
+
+ # Determine the workspace folder (parent directory of this script)
+ local scriptDir
+ scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ local workspaceFolder
+ workspaceFolder="$(dirname "$scriptDir")"
+
+ local cmfPath="${workspaceFolder}/cmf-cli/bin/Debug/cmf"
+
+ if [ ! -x "$cmfPath" ]; then
+ echo "cmf executable not found at $cmfPath"
+ return 1
+ fi
+
+ # Create a fresh temp directory for this run
+ local tmpDir
+ tmpDir="$(mktemp -d -t cli-project-XXXXXX)"
+ cd "$tmpDir"
+
+ local deploymentBaseDir="$tmpDir/deployment"
+ mkdir -p "$deploymentBaseDir"
+
+ # Create infra.json
+ cat > infra.json << 'EOF'
+{
+ "NPMRegistry": "https://dev.criticalmanufacturing.io/repository/npm-public/",
+ "CmfPipelineRepository": "https://dev.criticalmanufacturing.io/repository/npm-public/",
+ "NuGetRegistry": "https://dev.criticalmanufacturing.io/repository/nuget-hosted/index.json"
+}
+EOF
+
+ # Create env.json
+ cat > env.json << 'EOF'
+{
+ "SYSTEM_NAME": "cmftraining",
+ "TENANT_NAME": "cmftraining",
+ "APPLICATION_PUBLIC_HTTP_ADDRESS": "cmftraining.local",
+ "APPLICATION_PUBLIC_HTTP_PORT": "80",
+ "APPLICATION_PUBLIC_HTTP_TLS_ENABLED": "false",
+ "DATABASE_ONLINE_MSSQL_ADDRESS": "db",
+ "DATABASE_ONLINE_MSSQL_ADDRESS": "db",
+ "SECURITY_PORTAL_STRATEGY_LOCAL_AD_DEFAULT_DOMAIN": "",
+ "DATABASE_MSSQL_ALWAYS_ON_ENABLED": "false"
+}
+EOF
+
+ # Run the cmf init command
+ local cmd=(
+ "$cmfPath" init ExampleProject
+ --version "$projectVersion"
+ --infra infra.json
+ --config env.json
+ --MESVersion "$mesVersion"
+ --deploymentDir "$deploymentBaseDir"
+ )
+ echo "Executing: ${cmd[*]}"
+ "${cmd[@]}"
+
+ cd "$tmpDir"
+ echo "Scaffolded project in: $tmpDir"
+ echo "Working directory is now: $(pwd)"
+
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]] && [[ -t 0 ]]; then
+ echo "Opening an interactive shell in the scaffolded project..."
+ exec "${SHELL:-/bin/bash}" -i
+ fi
+}
+
+scaffold_project "$@"
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 6c49d638b..87a8aff26 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -49,6 +49,32 @@
"type": "shell",
"command": "dotnet test --configuration Release -verbosity diagnostic",
"dependsOn": "use-node"
+ },
+ {
+ "type": "shell",
+ "label": "scaffold-cli",
+ "command": "./.vscode/scaffold-cli-project.sh \"${input:ScaffoldProjectVersion}\" \"${input:ScaffoldMesVersion}\" && exec zsh -i",
+ "problemMatcher": [],
+ "presentation": {
+ "echo": false,
+ "focus": true,
+ "group": "scaffold-cli",
+ "panel": "dedicated"
+ },
+ "runOptions": {
+ "runOn": "folderOpen"
+ }
+ },
+ {
+ "type": "shell",
+ "label": "clean-all-scaffold-cli",
+ "command": "find /tmp -maxdepth 1 -type d -name 'cli-project*' -exec rm -rf {} +",
+ "problemMatcher": [],
+ "presentation": {
+ "echo": true,
+ "focus": false,
+ "panel": "dedicated"
+ }
}
],
"inputs": [
@@ -61,6 +87,18 @@
"20"
],
"default": "18"
+ },
+ {
+ "id": "ScaffoldProjectVersion",
+ "type": "promptString",
+ "description": "Insert scaffold project's version",
+ "default": "12.0.0"
+ },
+ {
+ "id": "ScaffoldMesVersion",
+ "type": "promptString",
+ "description": "Insert scaffold project's MES version",
+ "default": "1.0.0"
}
]
}
\ No newline at end of file
diff --git a/CHANGES.md b/CHANGES.md
index 00bedc824..cc4c1dd64 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,6 +2,24 @@
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
+## [6.0.0-2](https://github.com/criticalmanufacturing/cli/compare/6.0.0-1...6.0.0-2) (2026-07-22)
+
+
+### Features
+
+* add intelisense to biz scenarios scripts ([d1ac39e](https://github.com/criticalmanufacturing/cli/commit/d1ac39ed5c31e11042425b3f60493a6d8ebb81a1))
+* update iot task scaffolding with new task bases ([#765](https://github.com/criticalmanufacturing/cli/issues/765)) ([6af4eaa](https://github.com/criticalmanufacturing/cli/commit/6af4eaab73962d4138039776c68cd98663449770))
+* update iot test package scaffolding to use test orchestrator ([9ca59d5](https://github.com/criticalmanufacturing/cli/commit/9ca59d5db7a46763688678353ba3708b292492f2))
+* update new iot driver scaffolding to handle file access ([28f7afc](https://github.com/criticalmanufacturing/cli/commit/28f7afc93210f973e79305cad13890d27d90dc36))
+
+
+### Bug Fixes
+
+* html config overrides oob config.json on deploy ([e7dede5](https://github.com/criticalmanufacturing/cli/commit/e7dede5906b4f1f93dd63be9d042e595e81eaa58))
+* iot tasks scaffolding handle empty choices ([860e6a4](https://github.com/criticalmanufacturing/cli/commit/860e6a47cd940b341c643f200984c0cfbe1e2602))
+* remove deprecated always_auth from npmrc sync ([692d8f6](https://github.com/criticalmanufacturing/cli/commit/692d8f6484cbf9a1cc12ed7de92a4195d46bc06c))
+* **scaffold:** use dynamic TargetFramework for data package Actions project ([a8fa808](https://github.com/criticalmanufacturing/cli/commit/a8fa80871a3a01191a07c7a28ebe568f11694952)), closes [#770](https://github.com/criticalmanufacturing/cli/issues/770)
+
## [6.0.0-1](https://github.com/criticalmanufacturing/cli/compare/6.0.0-0...6.0.0-1) (2026-07-06)
diff --git a/cmf-cli/cmf.csproj b/cmf-cli/cmf.csproj
index 277d3cb3b..51c4bb68b 100644
--- a/cmf-cli/cmf.csproj
+++ b/cmf-cli/cmf.csproj
@@ -15,7 +15,7 @@
Name
Namespace
Namespaces
- 6.0.0-1
+ 6.0.0-2
diff --git a/core/Enums/MessageType.cs b/core/Enums/MessageType.cs
index 2f092614e..f963d8301 100644
--- a/core/Enums/MessageType.cs
+++ b/core/Enums/MessageType.cs
@@ -5,9 +5,7 @@
///
public enum MessageType
{
- ///
- /// The import object
- ///
- ImportObject = 0
+ ImportObject = 0,
+ ExecuteActionCode = 1
}
}
\ No newline at end of file
diff --git a/core/Services/CmfPackageController.cs b/core/Services/CmfPackageController.cs
index 700f426d5..67b148d96 100644
--- a/core/Services/CmfPackageController.cs
+++ b/core/Services/CmfPackageController.cs
@@ -281,6 +281,25 @@ private static List CheckForUnknownAttributes(XElement element, IEnumera
var allAttributes = element.Attributes().Select(a => a.Name.LocalName);
return allAttributes.Except(knownAttributes).ToList();
}
+
+ private static TEnum? ParseStepEnumAttribute(XElement element, string attributeName, bool strictStepParsing, ref bool hasParsingErrors) where TEnum : struct, Enum
+ {
+ var rawValue = element.Attribute(attributeName)?.Value;
+ if (rawValue == null)
+ {
+ return null;
+ }
+
+ if (Enum.TryParse(rawValue, out var parsedValue))
+ {
+ return parsedValue;
+ }
+
+ hasParsingErrors = true;
+ var logMsg = $"Step attribute '{attributeName}' could not be parsed as {typeof(TEnum).Name}: '{rawValue}'";
+ if (strictStepParsing) Log.Error(logMsg); else Log.Debug(logMsg);
+ return null;
+ }
private static CmfPackageV1 FromXmlManifest(string manifest, bool setDefaultValues = false)
{
@@ -373,8 +392,10 @@ public static CmfPackageV1 FromXml(XDocument xml)
throw new CliException("Invalid manifest");
}
+ Log.Debug($"Parsing '{rootNode.Element("packageId", true)?.Value}@{rootNode.Element("version", true)?.Value}' package's XML manifest.");
+
var strictStepParsing = Environment.GetEnvironmentVariable("cmf_cli_internal_strict_step_parsing") != null;
- var parsingErrors = new List();
+ bool hasParsingErrors = false;
PackageType cliPackageType = PackageType.Generic;
if (!Enum.TryParse(rootNode.Element("clipackagetype", true)?.Value, out cliPackageType))
@@ -382,7 +403,7 @@ public static CmfPackageV1 FromXml(XDocument xml)
if (!Enum.TryParse(rootNode.Element("packageType", true)?.Value, out cliPackageType))
{
var logMsg = $"Unknown packageType: '{rootNode.Element("packageType", true)?.Value}'";
- parsingErrors.Add(logMsg);
+ hasParsingErrors = true;
if (strictStepParsing) Log.Error(logMsg); else Log.Debug(logMsg);
}
@@ -401,14 +422,14 @@ public static CmfPackageV1 FromXml(XDocument xml)
if (!typeParsed)
{
var logMsg = $"Step has an unknown type: {typeAttributeValue}";
- parsingErrors.Add(logMsg);
+ hasParsingErrors = true;
if (strictStepParsing) Log.Error(logMsg); else Log.Debug(logMsg);
}
if (unknownAttributes.Count > 0)
{
var logMsg = $"Step (type: {typeAttributeValue}) has unknown attributes. Attributes: {string.Join(", ", unknownAttributes)}";
- parsingErrors.Add(logMsg);
+ hasParsingErrors = true;
if (strictStepParsing) Log.Error(logMsg); else Log.Debug(logMsg);
}
@@ -421,7 +442,7 @@ public static CmfPackageV1 FromXml(XDocument xml)
File = element.Attribute("file")?.Value,
TagFile = bool.TryParse(element.Attribute("tagFile")?.Value, out bool tagFile) ? tagFile : null,
TargetDatabase = element.Attribute("targetDatabase")?.Value,
- MessageType = Enum.TryParse(element.Attribute("messageType")?.Value, out MessageType messageType) ? messageType : null,
+ MessageType = ParseStepEnumAttribute(element, "messageType", strictStepParsing, ref hasParsingErrors),
RelativePath = null,
FilePath = element.Attribute("filePath")?.Value,
OldSystemName = element.Attribute("oldSystemName")?.Value,
@@ -433,7 +454,7 @@ public static CmfPackageV1 FromXml(XDocument xml)
ImportXMLObjectPath = element.Attribute("importXMLObjectPath")?.Value,
AutomationWorkflowFileBasePath = element.Attribute("automationWorkflowFileBasePath")?.Value,
CreateInCollection = bool.TryParse(element.Attribute("createInCollection")?.Value, out bool createInCollection) ? createInCollection : null,
- TargetPlatform = Enum.TryParse(element.Attribute("targetPlatform")?.Value, out MasterDataTargetPlatformType targetPlatform) ? targetPlatform : null,
+ TargetPlatform = ParseStepEnumAttribute(element, "targetPlatform", strictStepParsing, ref hasParsingErrors),
UserKey = element.Attribute("userKey")?.Value,
Quota = element.Attribute("quota")?.Value,
Id = element.Attribute("id")?.Value,
@@ -469,9 +490,9 @@ public static CmfPackageV1 FromXml(XDocument xml)
}
}
- if (strictStepParsing && parsingErrors.Count > 0)
+ if (strictStepParsing && hasParsingErrors)
{
- throw new CliException("CLI encountered unknown metadata when parsing package's manifest.xml! Check error messages for details.");
+ throw new CliException($"CLI encountered unknown metadata when parsing '{rootNode.Element("packageId", true)?.Value}@{rootNode.Element("version", true)?.Value}' package's manifest.xml! Check error messages for details.");
}
DependencyCollection deps = new();
diff --git a/core/core.csproj b/core/core.csproj
index 7fab5077f..6875b304e 100644
--- a/core/core.csproj
+++ b/core/core.csproj
@@ -4,7 +4,7 @@
net10.0
CriticalManufacturing.CLI.Core
Cmf.CLI.Core
- 6.0.0-1
+ 6.0.0-2
CriticalManufacturing
CriticalManufacturing
BSD-3-Clause
diff --git a/npm/package-lock.json b/npm/package-lock.json
index d44bacb1b..3fc615400 100644
--- a/npm/package-lock.json
+++ b/npm/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@criticalmanufacturing/cli",
- "version": "6.0.0-1",
+ "version": "6.0.0-2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@criticalmanufacturing/cli",
- "version": "6.0.0-1",
+ "version": "6.0.0-2",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
diff --git a/npm/package.json b/npm/package.json
index 801588608..d1ee78152 100644
--- a/npm/package.json
+++ b/npm/package.json
@@ -1,6 +1,6 @@
{
"name": "@criticalmanufacturing/cli",
- "version": "6.0.0-1",
+ "version": "6.0.0-2",
"description": "Critical Manufacturing command line client",
"bin": {
"cmf": "run.js"
diff --git a/tests/Specs/CmfPackageController_FromXml.cs b/tests/Specs/CmfPackageController_FromXml.cs
index 3488b7e9a..0f5215e4e 100644
--- a/tests/Specs/CmfPackageController_FromXml.cs
+++ b/tests/Specs/CmfPackageController_FromXml.cs
@@ -799,4 +799,100 @@ public void FromXml_ShouldDefaultToGenericAndNotThrow_WhenStrictStepParsingDisab
pkg.Steps.Should().ContainSingle();
pkg.Steps.Single().Type.Should().Be(StepType.Generic);
}
+
+ [Fact]
+ public void FromXml_ShouldKeepStepEnumAttributeNull_WhenAttributeIsMissing()
+ {
+ var xml = XDocument.Parse(
+ """
+
+ Cmf.Custom.Data
+ 1.0.0
+
+
+
+
+ """);
+
+ var pkg = CmfPackageController.FromXml(xml);
+
+ pkg.Steps.Single().MessageType.Should().BeNull();
+ pkg.Steps.Single().TargetPlatform.Should().BeNull();
+ }
+
+ [Fact]
+ public void FromXml_ShouldKeepStepEnumAttributeNullAndNotThrow_WhenValueIsInvalidAndStrictStepParsingDisabled()
+ {
+ Environment.SetEnvironmentVariable("cmf_cli_internal_strict_step_parsing", null);
+
+ var xml = XDocument.Parse(
+ """
+
+ Cmf.Custom.Data
+ 1.0.0
+
+
+
+
+ """);
+
+ var pkg = CmfPackageController.FromXml(xml);
+
+ pkg.Steps.Single().MessageType.Should().BeNull();
+ pkg.Steps.Single().TargetPlatform.Should().BeNull();
+ }
+
+ [Fact]
+ public void FromXml_ShouldThrowException_WhenStrictStepParsingEnabledAndMessageTypeIsInvalid()
+ {
+ Environment.SetEnvironmentVariable("cmf_cli_internal_strict_step_parsing", "1");
+ try
+ {
+ var xml = XDocument.Parse(
+ """
+
+ Cmf.Custom.Data
+ 1.0.0
+
+
+
+
+ """);
+
+ Action act = () => CmfPackageController.FromXml(xml);
+
+ act.Should().Throw().WithMessage("*CLI encountered unknown metadata*");
+ }
+ finally
+ {
+ Environment.SetEnvironmentVariable("cmf_cli_internal_strict_step_parsing", null);
+ }
+ }
+
+ [Fact]
+ public void FromXml_ShouldThrowException_WhenStrictStepParsingEnabledAndTargetPlatformIsInvalid()
+ {
+ Environment.SetEnvironmentVariable("cmf_cli_internal_strict_step_parsing", "1");
+ try
+ {
+ var xml = XDocument.Parse(
+ """
+
+ Cmf.Custom.Data
+ 1.0.0
+
+
+
+
+ """);
+
+ Action act = () => CmfPackageController.FromXml(xml);
+
+ act.Should().Throw().WithMessage("*CLI encountered unknown metadata*");
+ }
+ finally
+ {
+ Environment.SetEnvironmentVariable("cmf_cli_internal_strict_step_parsing", null);
+ }
+ }
}