Skip to content

fix: repair four broken scaffolding templates (security roles, app access, quick create forms, form indexes) - #152

Open
metjuperry wants to merge 4 commits into
TALXIS:masterfrom
metjuperry:fix/template-scaffolding-bugs
Open

metjuperry wants to merge 4 commits into
TALXIS:masterfrom
metjuperry:fix/template-scaffolding-bugs

Conversation

@metjuperry

Copy link
Copy Markdown
Member

Four Dataverse templates fail — or silently corrupt their output — when driven through txc workspace component create. All four were hit building a single small model-driven app end to end; each is fixed in its own commit.

What was broken

Template Symptom
pp-security-role-privilege Never worked with the documented input, and silently emitted an invalid privilege name
pp-app-security-role Never worked at all, and destroyed the app module's existing roles on the way out
pp-entity-form FormType=quickCreate never worked
pp-form-*, pp-control-parameter An out-of-range index silently corrupted a valid form

1. pp-security-role-privilege — documented input cannot work

PrivilegeTypeAndLevel is substituted straight into a C# string literal:

var json = "jsonarraystringwhithPrivilegeTypeandandLevel";

The documented value [{"privilegetype":"Read","level":"Global"}] terminates that literal early, so the generator fails to compile with CS1003 and the template aborts. Only an undocumented unquoted form could ever get through.

The quieter half is worse. The payload bound to a Privilege class whose property is PrivilegeType, so the documented key "type" bound to null and produced:

<RolePrivilege name="prvfleet_refuel" level="Global" />

That privilege does not exist in Dataverse — and the template exited 0.

Separately, txc docs get security-roles documents levels User / BusinessUnit / ParentChild, but the role XSD only enumerates Basic / Local / Deep / Global. Following the docs produced a role that fails txc workspace validate.

Now: quotes survive via a raw string literal; both privilegetype and type are accepted quoted or unquoted; the documented level names map onto the XSD's values; None means "omit the privilege"; and an unknown or missing type/level fails with a message naming the valid values.

2. pp-app-security-role — could never succeed

AddRolesToApp.ps1 wraps the roles in <AppModuleRoleMaps> and then reads $rolesXml.AppModuleRoles, which is always null:

Exception calling "ImportNode" with "2" argument(s): "Cannot import a null node."

The existing <AppModuleRoleMaps> was already removed by that point, so the failure also stripped the app module's roles.

Now the wrapper is addressed via DocumentElement, the replacement is built and imported before the existing node is removed, role ids are normalised to the braced form used elsewhere in solution XML, and non-GUID input is rejected.

3. pp-entity-formquickCreate never worked

SetFormId.ps1 derives its path from FormType, looking for FormXml/quickCreate/mainform.xml. The quick create form is emitted to FormXml/quick/ and named after the FormId symbol, so every run failed with Cannot find path and left a stray {unknown}.xml behind after the rollback.

Fixed by addressing the file by its templated name. Note it must not be located by globbing the folder — pp-entity may already have placed its own quick create form there, and a glob picks that one instead (I made exactly that mistake first).

4. Index 0 silently corrupts forms

TabIndex / ColumnIndex / SectionIndex / RowIndex are documented as 1-based and selected with $nodes[$index - 1]. An index of 0 becomes $nodes[-1] — in PowerShell that is the last element, not an error.

So RowIndex=0 on pp-form-cell appended the cell to the last populated row. The following pp-form-control then aborted with Multiple cells found in the target row and rolled back only the control — leaving an orphaned control-less cell inside a row that was previously valid. The command that reported the failure was not the one that did the damage, and the corruption survived it.

Every index lookup now has a lower bound, so 0 and negative values hit the existing not-found error, and that error states the indexes are 1-based.

Verification

Built the package, installed it into the txc template host, and ran the exact commands that previously failed. txc workspace validate passes on the resulting workspace.

  • documented quoted JSON now scaffolds prvReadfleet_refuel / prvCreatefleet_refuel, with User correctly emitted as Basic
  • unquoted form, type key, and level: None all behave; unknown type and missing type exit 1
  • pp-app-security-role writes both roles as braced GUIDs; not-a-guid exits 1
  • FormType=quickCreate works with and without an explicit FormId, leaves no {unknown}.xml, and does not disturb the entity's own quick create form
  • 1-based form chain still composes a clean form (one cell per row); RowIndex=0 now fails with a clear message and leaves existing rows untouched

Not addressed here

  • Failed post-actions leave .template.scripts/ and .template.temp/ behind in the target project. Those then fail txc workspace validate with schema errors unrelated to the user's work (The 'control' element is not declared). That is CLI rollback behaviour rather than template content.
  • txc docs get security-roles still documents the User / BusinessUnit / ParentChild level names. This PR makes the template accept them, but the docs live in the CLI repo and should say what the XSD enumerates.

🤖 Generated with Claude Code

Matěj Samler and others added 4 commits August 28, 2026 15:32
…d levels

PrivilegeTypeAndLevel was substituted straight into a C# string literal, so
the documented value [{"privilegetype":"Read","level":"Global"}] terminated
the literal early and the generator failed to build with CS1003. Only an
undocumented unquoted form could ever work.

Worse, the payload was bound to a Privilege class whose property is
PrivilegeType, so the documented key "type" silently bound to null and
produced <RolePrivilege name="prvfleet_refuel" /> -- a privilege name that
does not exist in Dataverse -- with a zero exit code.

- read the value through a raw string literal so quotes survive
- accept "privilegetype" and "type", quoted or unquoted
- map the documented level names (User/BusinessUnit/ParentChild) onto the
  Basic/Local/Deep values the role XSD actually enumerates, so scaffolded
  roles pass `txc workspace validate`
- treat level None as "omit the privilege" rather than emitting it
- fail with an actionable message on an unknown or missing type/level
  instead of writing a malformed privilege name

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The template could never succeed. AddRolesToApp.ps1 wraps the generated
roles in <AppModuleRoleMaps> but then read $rolesXml.AppModuleRoles, which
is always null, so every run died with "Cannot import a null node".

Because the existing <AppModuleRoleMaps> was removed before that failure,
the script also destroyed the app module's current roles on its way out.

- address the wrapper via DocumentElement so the element name is written
  once and cannot drift
- build and import the replacement before removing the existing node, so a
  failure can no longer leave the app module without any roles
- normalise role ids to the braced form used elsewhere in solution XML and
  reject values that are not GUIDs
- fail clearly when SecurityRolesIds yields no roles

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SetFormId.ps1 built its path from the FormType value, looking for
FormXml/quickCreate/mainform.xml. The quick create form is emitted to
FormXml/quick/ and is named after the FormId symbol, so FormType=quickCreate
failed every time with "Cannot find path", leaving a stray {unknown}.xml
behind after the rollback.

Address the file by its templated name rather than globbing the folder --
pp-entity may already have placed its own quick create form there, and a
glob picks that one instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TabIndex/ColumnIndex/SectionIndex/RowIndex are documented as 1-based and
the scripts select with $nodes[$index - 1]. An index of 0 therefore becomes
$nodes[-1], which in PowerShell is the *last* element rather than an error.

Passing RowIndex=0 to pp-form-cell silently appended the cell to the last
populated row. The following pp-form-control call then aborted with
"Multiple cells found in the target row" and rolled back only the control,
leaving an orphaned control-less cell inside a previously valid row -- a
corrupted form from a command that reported failure elsewhere.

Add a lower bound to every index lookup so 0 and negative values fall
through to the existing not-found error, and say in that error that the
indexes are 1-based.

Affects pp-form-cell, pp-form-column, pp-form-control, pp-form-row,
pp-form-section, pp-form-dialog-tabfooter and pp-control-parameter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant