mirror of
https://github.com/MAKS-IT-COM/maksit-webui.git
synced 2026-06-30 20:06:43 +02:00
(bugfix): gray out disabled and read-only editor fields.
This commit is contained in:
parent
6567e01553
commit
b0b8fe8614
19
CHANGELOG.md
19
CHANGELOG.md
@ -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
16
src/package-lock.json
generated
@ -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": {
|
||||
|
||||
@ -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"
|
||||
},
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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'}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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'}
|
||||
|
||||
@ -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'}
|
||||
|
||||
@ -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'}>
|
||||
|
||||
@ -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'}>
|
||||
|
||||
@ -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(' ')
|
||||
}
|
||||
|
||||
@ -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})\"",
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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',
|
||||
|
||||
56
utils/ChangelogSupport.psm1
Normal file
56
utils/ChangelogSupport.psm1
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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/`.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)."
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user