(bugfix): gray out disabled and read-only editor fields.

This commit is contained in:
Maksym Sadovnychyy 2026-05-30 12:09:03 +02:00
parent 6567e01553
commit b0b8fe8614
23 changed files with 200 additions and 105 deletions

View File

@ -4,7 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v0.3.1] - 2026-05-30
## [0.3.2] - 2026-05-30
### Fixed
- Disabled and read-only editors now show consistent muted styling: `CheckBoxComponent` uses `opacity-50` when disabled; `FieldContainer` grays field labels when inactive; checkbox, radio, text, select, date, and secret fields share `getInactiveControlClasses()`.
- Added `tsup` to workspace root `devDependencies` so `npm ci` + `npm run build` resolve the CLI on Windows (subpackage-only hoisting was not always on `PATH` for nested workspace scripts).
### Added
- Storybook **Disabled** story for `CheckBoxComponent`.
## [0.3.1] - 2026-05-30
### Fixed
@ -21,7 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- `lodash` and `@types/lodash` from `@maks-it.com/webui-components`; filter debouncing uses a local `debounce()` helper.
- Duplicate `date-fns` dependency from `@maks-it.com/webui-components` (`date-fns` remains on `@maks-it.com/webui-core` only).
## [v0.3.0] - 2026-05-25
## [0.3.0] - 2026-05-25
### Added
@ -41,7 +52,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- Editor `colspan` uses static Tailwind `col-span-*` classes via `functions/tailwind/gridColSpan.ts` so Storybook and Vite builds apply the 12-column grid correctly.
- Storybook 10 preview: Vite `esbuild` JSX set to `automatic` so decorators and stories no longer throw `React is not defined`.
## [v0.2.0] - 2026-05-24
## [0.2.0] - 2026-05-24
### Added
@ -59,7 +70,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- `uuid` runtime dependency from `@maks-it.com/webui-components`.
## [v0.1.0] - 2026-05-24
## [0.1.0] - 2026-05-24
### Added

16
src/package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "maksit-webui",
"version": "0.3.1",
"version": "0.3.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "maksit-webui",
"version": "0.3.1",
"version": "0.3.2",
"license": "MIT",
"workspaces": [
"packages/*"
@ -10422,10 +10422,10 @@
},
"packages/components": {
"name": "@maks-it.com/webui-components",
"version": "0.3.1",
"version": "0.3.2",
"dependencies": {
"@maks-it.com/webui-contracts": "^0.3.1",
"@maks-it.com/webui-core": "^0.3.1"
"@maks-it.com/webui-contracts": "^0.3.2",
"@maks-it.com/webui-core": "^0.3.2"
},
"devDependencies": {
"@tanstack/react-table": "^8.21.3",
@ -10453,7 +10453,7 @@
},
"packages/contracts": {
"name": "@maks-it.com/webui-contracts",
"version": "0.3.1",
"version": "0.3.2",
"devDependencies": {
"tsup": "^8.5.1",
"typescript": "^6.0.3",
@ -10465,9 +10465,9 @@
},
"packages/core": {
"name": "@maks-it.com/webui-core",
"version": "0.3.1",
"version": "0.3.2",
"dependencies": {
"@maks-it.com/webui-contracts": "^0.3.1",
"@maks-it.com/webui-contracts": "^0.3.2",
"date-fns": "^4.3.0"
},
"devDependencies": {

View File

@ -1,7 +1,7 @@
{
"name": "maksit-webui",
"private": true,
"version": "0.3.1",
"version": "0.3.2",
"description": "Shared React UI library for MaksIT Certs UI and Vault WebUI",
"workspaces": [
"packages/*"
@ -51,6 +51,7 @@
"storybook": "^10.4.1",
"tailwindcss": "^4.3.0",
"ts-jest": "^29.4.11",
"tsup": "^8.5.1",
"vite": "^6.4.2",
"vitest": "^4.1.7"
},

View File

@ -1,6 +1,6 @@
{
"name": "@maks-it.com/webui-components",
"version": "0.3.1",
"version": "0.3.2",
"description": "Shared React components for MaksIT WebUI apps",
"type": "module",
"main": "./dist/index.cjs",
@ -18,7 +18,7 @@
"README.md"
],
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts --clean --tsconfig tsconfig.build.json --external react --external react-dom --external react-router-dom --external lucide-react --external @tanstack/react-table --external react-virtualized",
"build": "npx tsup src/index.ts --format esm,cjs --dts --clean --tsconfig tsconfig.build.json --external react --external react-dom --external react-router-dom --external lucide-react --external @tanstack/react-table --external react-virtualized",
"typecheck": "tsc -p tsconfig.json --noEmit",
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
"prepublishOnly": "npm run build"
@ -33,8 +33,8 @@
"directory": "src/packages/components"
},
"dependencies": {
"@maks-it.com/webui-contracts": "^0.3.1",
"@maks-it.com/webui-core": "^0.3.1"
"@maks-it.com/webui-contracts": "^0.3.2",
"@maks-it.com/webui-core": "^0.3.2"
},
"peerDependencies": {
"@tanstack/react-table": "^8.0.0",

View File

@ -1,6 +1,7 @@
import { type ChangeEvent, type FC, useEffect, useRef } from 'react'
import type { GridColSpan } from '../../functions'
import { FieldContainer } from './FieldContainer'
import { getInactiveControlClasses } from './editorStyles'
interface CheckBoxComponentProps {
colspan?: GridColSpan;
@ -38,14 +39,16 @@ const CheckBoxComponent: FC<CheckBoxComponentProps> = (props) => {
}
return (
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
<input
type={'checkbox'}
checked={value}
onChange={handleOnChange}
className={`mr-2 leading-tight ${errorText ? 'border-red-500' : ''}`}
disabled={disabled}
/>
<FieldContainer colspan={colspan} label={label} errorText={errorText} disabled={disabled}>
<label className={`inline-flex items-center ${getInactiveControlClasses({ disabled })}`}>
<input
type={'checkbox'}
checked={value}
onChange={handleOnChange}
className={`mr-2 leading-tight ${errorText ? 'border-red-500' : ''}`}
disabled={disabled}
/>
</label>
</FieldContainer>
)
}

View File

@ -132,7 +132,7 @@ const DateTimePickerComponent: FC<DateTimePickerComponentProps> = ({
}, [showDropdown])
return (
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
<FieldContainer colspan={colspan} label={label} errorText={errorText} disabled={disabled} readOnly={readOnly}>
<div className={'relative'} ref={dropdownRef}>
<input
type={'text'}

View File

@ -5,6 +5,8 @@ interface FieldContainerProps {
colspan?: GridColSpan;
label?: string;
errorText?: string;
disabled?: boolean;
readOnly?: boolean;
children: ReactNode
}
@ -13,11 +15,15 @@ const FieldContainer: FC<FieldContainerProps> = (props) => {
colspan,
label,
errorText,
disabled = false,
readOnly = false,
children
} = props
const isInactive = disabled || readOnly
return <div className={colSpanClass(colspan)}>
<label className={`block text-gray-700 text-sm font-bold mb-2 ${!label ? 'invisible' : ''}`}>{label || '\u00A0'}</label>
<label className={`block text-sm font-bold mb-2 ${isInactive ? 'text-gray-500' : 'text-gray-700'} ${!label ? 'invisible' : ''}`}>{label || '\u00A0'}</label>
{children}
<p className={`text-red-500 text-xs italic mt-2 ${!errorText ? 'invisible' : ''}`}>{errorText || '\u00A0'}</p>
</div>

View File

@ -1,6 +1,7 @@
import React, { useEffect, useRef, useState } from 'react'
import type { GridColSpan } from '../../functions'
import { FieldContainer } from './FieldContainer'
import { getInactiveControlClasses } from './editorStyles'
interface RadioOption {
value: string
@ -47,15 +48,13 @@ const RadioGroupComponent: React.FC<RadioGroupComponentProps> = (props) => {
}
return (
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
<FieldContainer colspan={colspan} label={label} errorText={errorText} disabled={disabled} readOnly={readOnly}>
<div className={'flex flex-col'}>
{options.map(option => {
// Use default cursor (arrow) if disabled or readOnly, else pointer
const isInactive = disabled || readOnly
return (
<label
key={option.value}
className={`flex items-center mb-2 ${disabled ? 'opacity-50' : ''} ${isInactive ? 'cursor-default' : 'cursor-pointer'}`}
className={`flex items-center mb-2 ${getInactiveControlClasses({ disabled, readOnly })}`}
>
<input
type={'radio'}

View File

@ -66,7 +66,7 @@ const SecretComponent: FC<SecretComponentProps> = ({
const actionButtonClass = 'p-1 text-gray-600 hover:text-gray-800 bg-white'
return (
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
<FieldContainer colspan={colspan} label={label} errorText={errorText} readOnly={readOnly}>
<div className={'relative'}>
<input
type={showPassword ? 'text' : 'password'}

View File

@ -154,7 +154,7 @@ const SelectBoxComponent: FC<SelectBoxComponentProps> = (props) => {
}
return (
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
<FieldContainer colspan={colspan} label={label} errorText={errorText} disabled={disabled} readOnly={readOnly}>
<div className={'relative'}>
<div className={'relative'}>

View File

@ -51,7 +51,7 @@ const TextBoxComponent: FC<TextBoxComponentProps> = (props) => {
// Se il type è "textarea", comportamento invariato
if (type === 'textarea') {
return (
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
<FieldContainer colspan={colspan} label={label} errorText={errorText} disabled={disabled} readOnly={readOnly}>
<textarea
value={value}
onChange={handleOnChange}
@ -68,7 +68,7 @@ const TextBoxComponent: FC<TextBoxComponentProps> = (props) => {
const hasContent = String(value).length > 0
return (
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
<FieldContainer colspan={colspan} label={label} errorText={errorText} disabled={disabled} readOnly={readOnly}>
{type === 'password' ? (
// Wrapper che contiene input e bottone show/hide, ma bottone solo se c'è contenuto
<div className={'relative'}>

View File

@ -23,3 +23,13 @@ export function getInputClasses(options: InputClassOptions): string {
: 'bg-white' + (readOnly ? ' text-gray-500 cursor-text select-text' : '')
return [inputBaseClasses, border, state, extra].filter(Boolean).join(' ')
}
/** Checkbox/radio wrappers: muted when disabled, non-interactive cursor when disabled or read-only. */
export function getInactiveControlClasses(options: { disabled?: boolean; readOnly?: boolean } = {}): string {
const { disabled = false, readOnly = false } = options
const inactive = disabled || readOnly
return [
disabled ? 'opacity-50' : '',
inactive ? 'cursor-default' : 'cursor-pointer',
].filter(Boolean).join(' ')
}

View File

@ -1,6 +1,6 @@
{
"name": "@maks-it.com/webui-contracts",
"version": "0.3.1",
"version": "0.3.2",
"description": "Shared TypeScript contracts for MaksIT WebUI apps",
"type": "module",
"main": "./dist/index.cjs",
@ -18,7 +18,7 @@
"README.md"
],
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts --clean --tsconfig tsconfig.build.json",
"build": "npx tsup src/index.ts --format esm,cjs --dts --clean --tsconfig tsconfig.build.json",
"test": "jest --config ../../jest.config.cjs --testPathPatterns packages/contracts",
"typecheck": "tsc -p tsconfig.json --noEmit",
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",

View File

@ -1,6 +1,6 @@
{
"name": "@maks-it.com/webui-core",
"version": "0.3.1",
"version": "0.3.2",
"description": "Shared utilities and hooks for MaksIT WebUI apps",
"type": "module",
"main": "./dist/index.cjs",
@ -18,7 +18,7 @@
"README.md"
],
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts --clean --tsconfig tsconfig.build.json",
"build": "npx tsup src/index.ts --format esm,cjs --dts --clean --tsconfig tsconfig.build.json",
"test": "jest --config ../../jest.config.cjs",
"typecheck": "tsc -p tsconfig.json --noEmit",
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
@ -34,7 +34,7 @@
"directory": "src/packages/core"
},
"dependencies": {
"@maks-it.com/webui-contracts": "^0.3.1",
"@maks-it.com/webui-contracts": "^0.3.2",
"date-fns": "^4.3.0"
},
"peerDependencies": {

View File

@ -40,6 +40,14 @@ export const WithError: Story = {
},
}
export const Disabled: Story = {
args: {
label: 'Send notifications',
value: true,
disabled: true,
},
}
export const TogglesOnClick: Story = {
args: {
label: 'Enable feature',

View File

@ -0,0 +1,56 @@
#requires -Version 7.0
#requires -PSEdition Core
<#
.SYNOPSIS
Keep a Changelog header parsing and section extraction.
.DESCRIPTION
Supports only the standard Keep a Changelog version line:
## [1.0.0] - 2026-05-24
#>
function Get-ChangelogVersionHeaderPattern {
return '(?m)^##\s+\[(\d+\.\d+\.\d+)\]\s*-\s*\d{4}-\d{2}-\d{2}\s*$'
}
function Get-ChangelogNextVersionHeaderPattern {
return '(?m)^##\s+\[\d+\.\d+\.\d+\]\s*-\s*\d{4}-\d{2}-\d{2}\s*$'
}
function Get-LatestChangelogVersion {
param(
[Parameter(Mandatory = $true)]
[string]$ReleaseNotesContent
)
$match = [regex]::Match($ReleaseNotesContent, (Get-ChangelogVersionHeaderPattern))
if (-not $match.Success) {
return $null
}
return $match.Groups[1].Value
}
function Get-ChangelogReleaseNotesSection {
param(
[Parameter(Mandatory = $true)]
[string]$ReleaseNotesContent,
[Parameter(Mandatory = $true)]
[string]$Version
)
$escapedVersion = [regex]::Escape($Version)
$nextHeaderPattern = Get-ChangelogNextVersionHeaderPattern
$headerPattern = "(?ms)^##\s+\[$escapedVersion\]\s*-\s*\d{4}-\d{2}-\d{2}.*?(?=$nextHeaderPattern|\Z)"
$match = [regex]::Match($ReleaseNotesContent, $headerPattern)
if (-not $match.Success) {
return $null
}
return $match.Value.Trim()
}
Export-ModuleMember -Function Get-ChangelogVersionHeaderPattern, Get-ChangelogNextVersionHeaderPattern, Get-LatestChangelogVersion, Get-ChangelogReleaseNotesSection

View File

@ -8,7 +8,9 @@
.DESCRIPTION
This plugin validates GitHub CLI access, resolves the target
repository, and creates the configured GitHub release using the
shared release artifacts and extracted release notes.
shared release artifacts and release notes from CHANGELOG.md.
Release notes must use Keep a Changelog headers: ## [semver] - YYYY-MM-DD
(see ChangelogSupport.psm1).
#>
if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) {
@ -46,32 +48,6 @@ function Get-GitHubRepositoryInternal {
throw "Could not parse GitHub repo from source: $repoSource. Configure plugins[].repository with 'owner/repo' or a GitHub URL."
}
function Get-ChangelogVersionHeaderPatternInternal {
# Keep a Changelog: ## [1.0.0] - 2026-05-24, bare ## 1.0.0 - 2026-05-24, or legacy ## v1.0.0
return '(?m)^##\s+(?:\[(\d+\.\d+\.\d+)\]|v(\d+\.\d+\.\d+)|(\d+\.\d+\.\d+)(?:\s*-\s*\d{4}-\d{2}-\d{2})?\s*$)'
}
function Get-LatestChangelogVersionInternal {
param(
[Parameter(Mandatory = $true)]
[string]$ReleaseNotesContent
)
$match = [regex]::Match($ReleaseNotesContent, (Get-ChangelogVersionHeaderPatternInternal))
if (-not $match.Success) {
return $null
}
foreach ($groupIndex in 1..3) {
$version = $match.Groups[$groupIndex].Value
if (-not [string]::IsNullOrEmpty($version)) {
return $version
}
}
return $null
}
function Get-ReleaseNotesInternal {
param(
[Parameter(Mandatory = $true)]
@ -87,9 +63,9 @@ function Get-ReleaseNotesInternal {
}
$releaseNotesContent = Get-Content $ReleaseNotesFile -Raw
$releaseNotesVersion = Get-LatestChangelogVersionInternal -ReleaseNotesContent $releaseNotesContent
$releaseNotesVersion = Get-LatestChangelogVersion -ReleaseNotesContent $releaseNotesContent
if ([string]::IsNullOrWhiteSpace($releaseNotesVersion)) {
throw "No version entry found in the configured release notes source."
throw "No version entry found in the configured release notes source. Expected Keep a Changelog header: ## [semver] - YYYY-MM-DD."
}
if ($releaseNotesVersion -ne $Version) {
@ -99,17 +75,14 @@ function Get-ReleaseNotesInternal {
Write-Log -Level "OK" -Message " Release notes version matches: v$releaseNotesVersion"
Write-Log -Level "STEP" -Message "Extracting release notes..."
$escapedVersion = [regex]::Escape($Version)
$nextHeaderPattern = '(?m)^##\s+(?:\[\d+\.\d+\.\d+\]|v\d+\.\d+\.\d+|\d+\.\d+\.\d+(?:\s*-\s*\d{4}-\d{2}-\d{2})?\s*$)'
$pattern = "(?ms)^##\s+(?:\[$escapedVersion\]|v$escapedVersion|$escapedVersion(?:\s*-\s*\d{4}-\d{2}-\d{2})?).*?(?=$nextHeaderPattern|\Z)"
$match = [regex]::Match($releaseNotesContent, $pattern)
$section = Get-ChangelogReleaseNotesSection -ReleaseNotesContent $releaseNotesContent -Version $Version
if (-not $match.Success) {
throw "Release notes entry for version $Version not found."
if ([string]::IsNullOrWhiteSpace($section)) {
throw "Release notes entry for version $Version not found. Expected header: ## [$Version] - YYYY-MM-DD."
}
Write-Log -Level "OK" -Message " Release notes extracted."
return $match.Value.Trim()
return $section
}
function Invoke-Plugin {
@ -120,6 +93,7 @@ function Invoke-Plugin {
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
Import-PluginDependency -ModuleName "ScriptConfig" -RequiredCommand "Assert-Command"
Import-PluginDependency -ModuleName "ChangelogSupport" -RequiredCommand "Get-LatestChangelogVersion"
$pluginSettings = $Settings
$sharedSettings = $Settings.context

View File

@ -340,16 +340,20 @@ function Invoke-ConfiguredPlugin {
[string]$PluginsDirectory,
[Parameter(Mandatory = $false)]
[bool]$ContinueOnError = $true
[bool]$ContinueOnError = $false
)
if (-not (Test-PluginRunnable -Plugin $Plugin -SharedSettings $SharedSettings -PluginsDirectory $PluginsDirectory -WriteLogs:$true)) {
return
if ($Plugin.enabled) {
return $false
}
return $true
}
if ((Test-IsPublishPlugin -Plugin $Plugin) -and ($SharedSettings.PSObject.Properties.Name -contains 'skipPublishPlugins') -and $SharedSettings.skipPublishPlugins) {
Write-Log -Level "INFO" -Message "Skipping plugin '$($Plugin.name)' (ReleasePublishGuard suppressed publish)."
return
return $true
}
$pluginModulePath = Resolve-PluginModulePath -Plugin $Plugin -PluginsDirectory $PluginsDirectory
@ -364,12 +368,11 @@ function Invoke-ConfiguredPlugin {
& $invokeCommand -Settings $pluginSettings
Write-Log -Level "OK" -Message " Plugin '$($Plugin.name)' completed."
return $true
}
catch {
Write-Log -Level "ERROR" -Message " Plugin '$($Plugin.name)' failed: $($_.Exception.Message)"
if (-not $ContinueOnError) {
exit 1
}
return $false
}
}

View File

@ -13,6 +13,8 @@ Canonical source: this folder in **maksit-repoutils**. Product repositories refr
| `ReleaseContext.psm1` | Resolves semver via `Resolve-ReleaseVersion` from `DotNetReleaseVersion.projectFiles` (first `.csproj` `<Version>`) or `NpmReleaseVersion.packageJsonPath`. |
| `EngineSupport.psm1` | Warn-only dirty-tree preflight; default `context.tag` = `v{version}` from the configured version plugin; `Initialize-ReleaseStageContext` sets `releaseDir` only. |
Shared module `../ChangelogSupport.psm1` (repo `src/`) parses release notes for the GitHub plugin: only `## [semver] - YYYY-MM-DD` version lines (Keep a Changelog). The latest header must match `context.version` from the version plugin.
## Plugins
`CorePlugins/` — e.g. `DotNetReleaseVersion`, `NpmReleaseVersion`, `NpmBuild`, `NpmPublish`, `DockerPush`, `HelmPush`, `ReleasePublishGuard`. Optional `CustomPlugins/`.

View File

@ -132,6 +132,7 @@ $sharedPluginSettings = $engineContext
#region Plugin Execution
$releaseStageInitialized = $false
$releaseHadPluginFailures = $false
if ($plugins.Count -eq 0) {
Write-Log -Level "WARN" -Message "No plugins configured in scriptsettings.json."
@ -149,8 +150,14 @@ else {
}
}
$continueOnError = $pluginStageLabel -eq "release"
Invoke-ConfiguredPlugin -Plugin $plugin -SharedSettings $sharedPluginSettings -PluginsDirectory $pluginsDir -ContinueOnError:$continueOnError
$continueOnError = $false
$pluginSucceeded = Invoke-ConfiguredPlugin -Plugin $plugin -SharedSettings $sharedPluginSettings -PluginsDirectory $pluginsDir -ContinueOnError:$continueOnError
if (-not $pluginSucceeded) {
$releaseHadPluginFailures = $true
if (-not $continueOnError) {
break
}
}
}
}
@ -163,7 +170,10 @@ if (-not $releaseStageInitialized) {
#region Summary
Write-Log -Level "OK" -Message "=================================================="
if ($engineContext.PSObject.Properties.Name -contains 'skipPublishPlugins' -and $engineContext.skipPublishPlugins) {
if ($releaseHadPluginFailures) {
Write-Log -Level "ERROR" -Message "RELEASE FAILED"
}
elseif ($engineContext.PSObject.Properties.Name -contains 'skipPublishPlugins' -and $engineContext.skipPublishPlugins) {
Write-Log -Level "OK" -Message "RUN COMPLETE (publish skipped by ReleasePublishGuard)"
}
elseif ($engineContext.isNonReleaseBranch) {
@ -179,6 +189,10 @@ if ($engineContext.isNonReleaseBranch -and -not ($engineContext.PSObject.Propert
Write-Log -Level "INFO" -Message "For publish, use an allowed branch (see ReleasePublishGuard.branches), e.g. '$preferredReleaseBranch', and satisfy the guard requirements."
}
if ($releaseHadPluginFailures) {
exit 1
}
#endregion
#endregion

View File

@ -1,7 +1,7 @@
{
"$schema": "https://json-schema.org/draft-07/schema",
"title": "Release Package Script Settings",
"description": "maksit-webui: npm workspace publish to npmjs. ReleasePublishGuard (before NpmPublish/GitHub) controls allowed branches and tag rules. Set NPMJS_MAKS_IT env var to the npm automation token (same pattern as NUGET_MAKS_IT).",
"description": "maksit-webui: npm workspace publish to npmjs. ReleasePublishGuard (before NpmPublish) controls allowed branches and tag rules. Set NPMJS_MAKS_IT env var to the npm automation token (same pattern as NUGET_MAKS_IT).",
"plugins": [
{
"name": "NpmReleaseVersion",
@ -32,16 +32,6 @@
"ensureTagOnRemote": true,
"remoteName": "origin"
},
{
"name": "GitHub",
"stageLabel": "release",
"enabled": true,
"githubToken": "GITHUB_MAKS_IT_COM",
"repository": "https://github.com/MAKS-IT-COM/maksit-webui",
"releaseNotesFile": "..\\..\\CHANGELOG.md",
"releaseTitlePattern": "Release {version}",
"requireReleaseAssets": false
},
{
"name": "NpmPublish",
"stageLabel": "release",
@ -61,7 +51,7 @@
"plugins": {
"NpmReleaseVersion": "Reads version from src/package.json (workspace root). syncWorkspaceVersions aligns packages/*/package.json before build/publish.",
"NpmBuild": "Runs npm ci + npm run build in workspaceRoot.",
"ReleasePublishGuard": "Place before NpmPublish/GitHub. tagVersionMustMatchReleaseVersion compares git tag on HEAD to NpmReleaseVersion.",
"ReleasePublishGuard": "Place before NpmPublish. tagVersionMustMatchReleaseVersion compares git tag on HEAD to NpmReleaseVersion.",
"NpmPublish": "npmApiKey is the environment variable name holding the npm automation token (NPMJS_MAKS_IT). publishOrder must follow dependency order.",
"maksit-repoutils": "Engine docs: https://github.com/MAKS-IT-COM/maksit-repoutils (src/Release-Package/README.md)."
}

View File

@ -340,16 +340,20 @@ function Invoke-ConfiguredPlugin {
[string]$PluginsDirectory,
[Parameter(Mandatory = $false)]
[bool]$ContinueOnError = $true
[bool]$ContinueOnError = $false
)
if (-not (Test-PluginRunnable -Plugin $Plugin -SharedSettings $SharedSettings -PluginsDirectory $PluginsDirectory -WriteLogs:$true)) {
return
if ($Plugin.enabled) {
return $false
}
return $true
}
if ((Test-IsPublishPlugin -Plugin $Plugin) -and ($SharedSettings.PSObject.Properties.Name -contains 'skipPublishPlugins') -and $SharedSettings.skipPublishPlugins) {
Write-Log -Level "INFO" -Message "Skipping plugin '$($Plugin.name)' (ReleasePublishGuard suppressed publish)."
return
return $true
}
$pluginModulePath = Resolve-PluginModulePath -Plugin $Plugin -PluginsDirectory $PluginsDirectory
@ -364,12 +368,11 @@ function Invoke-ConfiguredPlugin {
& $invokeCommand -Settings $pluginSettings
Write-Log -Level "OK" -Message " Plugin '$($Plugin.name)' completed."
return $true
}
catch {
Write-Log -Level "ERROR" -Message " Plugin '$($Plugin.name)' failed: $($_.Exception.Message)"
if (-not $ContinueOnError) {
exit 1
}
return $false
}
}

View File

@ -67,10 +67,25 @@ if ($configuredPlugins.Count -eq 0) {
exit 0
}
$testHadPluginFailures = $false
foreach ($plugin in $configuredPlugins) {
Invoke-ConfiguredPlugin -Plugin $plugin -SharedSettings $engineContext -PluginsDirectory $pluginsDir -ContinueOnError:$false
$pluginSucceeded = Invoke-ConfiguredPlugin -Plugin $plugin -SharedSettings $engineContext -PluginsDirectory $pluginsDir -ContinueOnError:$false
if (-not $pluginSucceeded) {
$testHadPluginFailures = $true
break
}
}
Write-Log -Level "OK" -Message "=================================================="
Write-Log -Level "OK" -Message "TEST RUN COMPLETE"
if ($testHadPluginFailures) {
Write-Log -Level "ERROR" -Message "TEST RUN FAILED"
}
else {
Write-Log -Level "OK" -Message "TEST RUN COMPLETE"
}
Write-Log -Level "OK" -Message "=================================================="
if ($testHadPluginFailures) {
exit 1
}