0
0
Fork 0

feat(git_*): hard-stop with clear PDE-subscription message on HasGitLicense

The 3 git_* tools previously surfaced the raw CODESYS exception
"Permission denied: Rule 'HasGitLicense' failed with state 'False'."
which is opaque to anyone who does not already know that CODESYS Git
scripting is gated behind the Professional Developer Edition
subscription. Verified 2026-04-25 on this workstation that the
plug-in is installed (PlugIns/GitIntegration.plugin.dll v1.7.0.0,
ScriptDriverGit.plugin.dll, full ScriptLib/Stubs/scriptengine/Git*.pyi
set, and the Git menu visible in the IDE) but the runtime rule still
returns False because no PDE subscription is active. Per the CODESYS
Git store page the subscription is the actual gate (Additional
Requirements / Licensing).

Each script now detects 'HasGitLicense' in the exception or traceback
and rewrites SCRIPT_ERROR to a clean, actionable message pointing at
https://store.codesys.com/en/codesys-git.html with a "what to activate"
hint, falling back to the original generic error formatting on any
non-license failure.

git_status: also adds an early license probe via project.git.has_working_tree()
(license-gated per the SP22 stubs) so the rewrite triggers reliably
instead of being swallowed by the per-method probe loop further down.

Tool descriptions in server.ts updated to advertise the PDE
subscription requirement up front, so MCP clients see it without
having to fail first.
This commit is contained in:
Karstein Phobic Nyvold Kvistad 2026-04-25 19:25:58 +02:00
parent e236a0cfad
commit 3623c45d31
4 changed files with 61 additions and 9 deletions

View file

@ -51,8 +51,21 @@ try:
sys.exit(0)
except Exception as e:
detailed = traceback.format_exc()
msg = "Error in git_commit for project '%s': %s\n%s" % (
PROJECT_FILE_PATH, e, detailed)
raw = "%s" % e
if 'HasGitLicense' in raw or 'HasGitLicense' in detailed:
msg = (
"CODESYS Git scripting requires an active CODESYS Professional "
"Developer Edition subscription license. The plug-in is installed "
"but the runtime 'HasGitLicense' rule returned False, so every "
"project.git.* operation (init/commit/status/...) is gated. "
"Activate a Professional Developer Edition subscription on this "
"CODESYS install (see https://store.codesys.com/en/codesys-git.html, "
"sections 'Additional Requirements' and 'Licensing'). Underlying "
"error from CODESYS: %s" % e
)
else:
msg = "Error in git_commit for project '%s': %s\n%s" % (
PROJECT_FILE_PATH, e, detailed)
print(msg)
print("SCRIPT_ERROR: %s" % msg)
sys.exit(1)

View file

@ -42,8 +42,21 @@ try:
sys.exit(0)
except Exception as e:
detailed = traceback.format_exc()
msg = "Error in git_init for project '%s': %s\n%s" % (
PROJECT_FILE_PATH, e, detailed)
raw = "%s" % e
if 'HasGitLicense' in raw or 'HasGitLicense' in detailed:
msg = (
"CODESYS Git scripting requires an active CODESYS Professional "
"Developer Edition subscription license. The plug-in is installed "
"but the runtime 'HasGitLicense' rule returned False, so every "
"project.git.* operation (init/commit/status/...) is gated. "
"Activate a Professional Developer Edition subscription on this "
"CODESYS install (see https://store.codesys.com/en/codesys-git.html, "
"sections 'Additional Requirements' and 'Licensing'). Underlying "
"error from CODESYS: %s" % e
)
else:
msg = "Error in git_init for project '%s': %s\n%s" % (
PROJECT_FILE_PATH, e, detailed)
print(msg)
print("SCRIPT_ERROR: %s" % msg)
sys.exit(1)

View file

@ -21,6 +21,19 @@ try:
"to initialise one, or open a project that is already in a "
"working tree." % PROJECT_FILE_PATH)
# Early license probe -- has_working_tree is a cheap, license-gated query
# per the SP22 stubs (Stubs/scriptengine/GitScriptProject.pyi). Letting a
# HasGitLicense failure escape here routes through the outer except, which
# rewrites it into a clear "PDE subscription needed" message instead of
# being swallowed by the per-method probe loop below.
if hasattr(git, 'has_working_tree'):
try:
git.has_working_tree()
except Exception as probe_e:
if 'HasGitLicense' in str(probe_e):
raise
print("DEBUG: license probe (has_working_tree) raised non-license error: %s" % probe_e)
git_attrs = sorted([a for a in dir(git) if not a.startswith('_')])
print("DEBUG: project.git attributes: %s" % git_attrs)
@ -63,8 +76,21 @@ try:
sys.exit(0)
except Exception as e:
detailed = traceback.format_exc()
msg = "Error in git_status for project '%s': %s\n%s" % (
PROJECT_FILE_PATH, e, detailed)
raw = "%s" % e
if 'HasGitLicense' in raw or 'HasGitLicense' in detailed:
msg = (
"CODESYS Git scripting requires an active CODESYS Professional "
"Developer Edition subscription license. The plug-in is installed "
"but the runtime 'HasGitLicense' rule returned False, so every "
"project.git.* operation (init/commit/status/...) is gated. "
"Activate a Professional Developer Edition subscription on this "
"CODESYS install (see https://store.codesys.com/en/codesys-git.html, "
"sections 'Additional Requirements' and 'Licensing'). Underlying "
"error from CODESYS: %s" % e
)
else:
msg = "Error in git_status for project '%s': %s\n%s" % (
PROJECT_FILE_PATH, e, detailed)
print(msg)
print("SCRIPT_ERROR: %s" % msg)
sys.exit(1)

View file

@ -1048,7 +1048,7 @@ export async function startMcpServer(config: ServerConfig): Promise<void> {
s.tool(
'git_status',
"Reports the project's git status: current branch, plus a probe of any status/changes/diff methods exposed on project.git. Read-only. Requires the CODESYS Git plug-in and a project bound to a git working tree (use git_init if not). Diagnostic dump of project.git surface is included.",
"Reports the project's git status: current branch, plus a probe of any status/changes/diff methods exposed on project.git. Read-only. Requires the CODESYS Git plug-in (ships with CODESYS) AND an active CODESYS Professional Developer Edition subscription license -- without the subscription, every project.git.* operation is gated by the runtime 'HasGitLicense' rule and the tool fails fast with a clear PDE-required message. Also requires the project to be bound to a git working tree (use git_init if not). Diagnostic dump of project.git surface is included.",
{
projectFilePath: z.string().describe("Path to the project file."),
},
@ -1066,7 +1066,7 @@ export async function startMcpServer(config: ServerConfig): Promise<void> {
s.tool(
'git_init',
"Initialises a Git repository for the project's directory (or a custom path) via project.git.init(). One-shot setup; use git_status afterwards to confirm. Requires CODESYS Git plug-in.",
"Initialises a Git repository for the project's directory (or a custom path) via project.git.init(). One-shot setup; use git_status afterwards to confirm. Requires the CODESYS Git plug-in AND an active CODESYS Professional Developer Edition subscription license -- without the subscription, the tool fails fast with a clear PDE-required message (the runtime 'HasGitLicense' rule gates every project.git.* call).",
{
projectFilePath: z.string().describe("Path to the project file."),
localRepoPath: z.string().optional().describe("Filesystem path to initialise the repo at. Defaults to the project file's parent directory."),
@ -1089,7 +1089,7 @@ export async function startMcpServer(config: ServerConfig): Promise<void> {
s.tool(
'git_commit',
"Stages all working-tree changes and commits them via project.git.commit_complete(message, user, mail). Requires the project to already be bound to a git repo (use git_init first if needed).",
"Stages all working-tree changes and commits them via project.git.commit_complete(message, user, mail). Requires the project to already be bound to a git repo (use git_init first if needed) AND an active CODESYS Professional Developer Edition subscription license -- without the subscription, the tool fails fast with a clear PDE-required message (the runtime 'HasGitLicense' rule gates every project.git.* call).",
{
projectFilePath: z.string().describe("Path to the project file."),
message: z.string().min(1).describe("Commit message. Multiline OK."),