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).
|
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
|
### 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.
|
- `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).
|
- 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
|
### 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.
|
- 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`.
|
- 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
|
### 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`.
|
- `uuid` runtime dependency from `@maks-it.com/webui-components`.
|
||||||
|
|
||||||
## [v0.1.0] - 2026-05-24
|
## [0.1.0] - 2026-05-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
|||||||
16
src/package-lock.json
generated
16
src/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "maksit-webui",
|
"name": "maksit-webui",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "maksit-webui",
|
"name": "maksit-webui",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@ -10422,10 +10422,10 @@
|
|||||||
},
|
},
|
||||||
"packages/components": {
|
"packages/components": {
|
||||||
"name": "@maks-it.com/webui-components",
|
"name": "@maks-it.com/webui-components",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@maks-it.com/webui-contracts": "^0.3.1",
|
"@maks-it.com/webui-contracts": "^0.3.2",
|
||||||
"@maks-it.com/webui-core": "^0.3.1"
|
"@maks-it.com/webui-core": "^0.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
@ -10453,7 +10453,7 @@
|
|||||||
},
|
},
|
||||||
"packages/contracts": {
|
"packages/contracts": {
|
||||||
"name": "@maks-it.com/webui-contracts",
|
"name": "@maks-it.com/webui-contracts",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tsup": "^8.5.1",
|
"tsup": "^8.5.1",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
@ -10465,9 +10465,9 @@
|
|||||||
},
|
},
|
||||||
"packages/core": {
|
"packages/core": {
|
||||||
"name": "@maks-it.com/webui-core",
|
"name": "@maks-it.com/webui-core",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@maks-it.com/webui-contracts": "^0.3.1",
|
"@maks-it.com/webui-contracts": "^0.3.2",
|
||||||
"date-fns": "^4.3.0"
|
"date-fns": "^4.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "maksit-webui",
|
"name": "maksit-webui",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"description": "Shared React UI library for MaksIT Certs UI and Vault WebUI",
|
"description": "Shared React UI library for MaksIT Certs UI and Vault WebUI",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@ -51,6 +51,7 @@
|
|||||||
"storybook": "^10.4.1",
|
"storybook": "^10.4.1",
|
||||||
"tailwindcss": "^4.3.0",
|
"tailwindcss": "^4.3.0",
|
||||||
"ts-jest": "^29.4.11",
|
"ts-jest": "^29.4.11",
|
||||||
|
"tsup": "^8.5.1",
|
||||||
"vite": "^6.4.2",
|
"vite": "^6.4.2",
|
||||||
"vitest": "^4.1.7"
|
"vitest": "^4.1.7"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@maks-it.com/webui-components",
|
"name": "@maks-it.com/webui-components",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"description": "Shared React components for MaksIT WebUI apps",
|
"description": "Shared React components for MaksIT WebUI apps",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
@ -18,7 +18,7 @@
|
|||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"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",
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
@ -33,8 +33,8 @@
|
|||||||
"directory": "src/packages/components"
|
"directory": "src/packages/components"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@maks-it.com/webui-contracts": "^0.3.1",
|
"@maks-it.com/webui-contracts": "^0.3.2",
|
||||||
"@maks-it.com/webui-core": "^0.3.1"
|
"@maks-it.com/webui-core": "^0.3.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tanstack/react-table": "^8.0.0",
|
"@tanstack/react-table": "^8.0.0",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { type ChangeEvent, type FC, useEffect, useRef } from 'react'
|
import { type ChangeEvent, type FC, useEffect, useRef } from 'react'
|
||||||
import type { GridColSpan } from '../../functions'
|
import type { GridColSpan } from '../../functions'
|
||||||
import { FieldContainer } from './FieldContainer'
|
import { FieldContainer } from './FieldContainer'
|
||||||
|
import { getInactiveControlClasses } from './editorStyles'
|
||||||
|
|
||||||
interface CheckBoxComponentProps {
|
interface CheckBoxComponentProps {
|
||||||
colspan?: GridColSpan;
|
colspan?: GridColSpan;
|
||||||
@ -38,14 +39,16 @@ const CheckBoxComponent: FC<CheckBoxComponentProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
|
<FieldContainer colspan={colspan} label={label} errorText={errorText} disabled={disabled}>
|
||||||
<input
|
<label className={`inline-flex items-center ${getInactiveControlClasses({ disabled })}`}>
|
||||||
type={'checkbox'}
|
<input
|
||||||
checked={value}
|
type={'checkbox'}
|
||||||
onChange={handleOnChange}
|
checked={value}
|
||||||
className={`mr-2 leading-tight ${errorText ? 'border-red-500' : ''}`}
|
onChange={handleOnChange}
|
||||||
disabled={disabled}
|
className={`mr-2 leading-tight ${errorText ? 'border-red-500' : ''}`}
|
||||||
/>
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
</FieldContainer>
|
</FieldContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -132,7 +132,7 @@ const DateTimePickerComponent: FC<DateTimePickerComponentProps> = ({
|
|||||||
}, [showDropdown])
|
}, [showDropdown])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
|
<FieldContainer colspan={colspan} label={label} errorText={errorText} disabled={disabled} readOnly={readOnly}>
|
||||||
<div className={'relative'} ref={dropdownRef}>
|
<div className={'relative'} ref={dropdownRef}>
|
||||||
<input
|
<input
|
||||||
type={'text'}
|
type={'text'}
|
||||||
|
|||||||
@ -5,6 +5,8 @@ interface FieldContainerProps {
|
|||||||
colspan?: GridColSpan;
|
colspan?: GridColSpan;
|
||||||
label?: string;
|
label?: string;
|
||||||
errorText?: string;
|
errorText?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,11 +15,15 @@ const FieldContainer: FC<FieldContainerProps> = (props) => {
|
|||||||
colspan,
|
colspan,
|
||||||
label,
|
label,
|
||||||
errorText,
|
errorText,
|
||||||
|
disabled = false,
|
||||||
|
readOnly = false,
|
||||||
children
|
children
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
|
const isInactive = disabled || readOnly
|
||||||
|
|
||||||
return <div className={colSpanClass(colspan)}>
|
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}
|
{children}
|
||||||
<p className={`text-red-500 text-xs italic mt-2 ${!errorText ? 'invisible' : ''}`}>{errorText || '\u00A0'}</p>
|
<p className={`text-red-500 text-xs italic mt-2 ${!errorText ? 'invisible' : ''}`}>{errorText || '\u00A0'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import type { GridColSpan } from '../../functions'
|
import type { GridColSpan } from '../../functions'
|
||||||
import { FieldContainer } from './FieldContainer'
|
import { FieldContainer } from './FieldContainer'
|
||||||
|
import { getInactiveControlClasses } from './editorStyles'
|
||||||
|
|
||||||
interface RadioOption {
|
interface RadioOption {
|
||||||
value: string
|
value: string
|
||||||
@ -47,15 +48,13 @@ const RadioGroupComponent: React.FC<RadioGroupComponentProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
|
<FieldContainer colspan={colspan} label={label} errorText={errorText} disabled={disabled} readOnly={readOnly}>
|
||||||
<div className={'flex flex-col'}>
|
<div className={'flex flex-col'}>
|
||||||
{options.map(option => {
|
{options.map(option => {
|
||||||
// Use default cursor (arrow) if disabled or readOnly, else pointer
|
|
||||||
const isInactive = disabled || readOnly
|
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
key={option.value}
|
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
|
<input
|
||||||
type={'radio'}
|
type={'radio'}
|
||||||
|
|||||||
@ -66,7 +66,7 @@ const SecretComponent: FC<SecretComponentProps> = ({
|
|||||||
const actionButtonClass = 'p-1 text-gray-600 hover:text-gray-800 bg-white'
|
const actionButtonClass = 'p-1 text-gray-600 hover:text-gray-800 bg-white'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
|
<FieldContainer colspan={colspan} label={label} errorText={errorText} readOnly={readOnly}>
|
||||||
<div className={'relative'}>
|
<div className={'relative'}>
|
||||||
<input
|
<input
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
|||||||
@ -154,7 +154,7 @@ const SelectBoxComponent: FC<SelectBoxComponentProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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'}>
|
||||||
|
|
||||||
<div className={'relative'}>
|
<div className={'relative'}>
|
||||||
|
|||||||
@ -51,7 +51,7 @@ const TextBoxComponent: FC<TextBoxComponentProps> = (props) => {
|
|||||||
// Se il type è "textarea", comportamento invariato
|
// Se il type è "textarea", comportamento invariato
|
||||||
if (type === 'textarea') {
|
if (type === 'textarea') {
|
||||||
return (
|
return (
|
||||||
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
|
<FieldContainer colspan={colspan} label={label} errorText={errorText} disabled={disabled} readOnly={readOnly}>
|
||||||
<textarea
|
<textarea
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleOnChange}
|
onChange={handleOnChange}
|
||||||
@ -68,7 +68,7 @@ const TextBoxComponent: FC<TextBoxComponentProps> = (props) => {
|
|||||||
const hasContent = String(value).length > 0
|
const hasContent = String(value).length > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldContainer colspan={colspan} label={label} errorText={errorText}>
|
<FieldContainer colspan={colspan} label={label} errorText={errorText} disabled={disabled} readOnly={readOnly}>
|
||||||
{type === 'password' ? (
|
{type === 'password' ? (
|
||||||
// Wrapper che contiene input e bottone show/hide, ma bottone solo se c'è contenuto
|
// Wrapper che contiene input e bottone show/hide, ma bottone solo se c'è contenuto
|
||||||
<div className={'relative'}>
|
<div className={'relative'}>
|
||||||
|
|||||||
@ -23,3 +23,13 @@ export function getInputClasses(options: InputClassOptions): string {
|
|||||||
: 'bg-white' + (readOnly ? ' text-gray-500 cursor-text select-text' : '')
|
: 'bg-white' + (readOnly ? ' text-gray-500 cursor-text select-text' : '')
|
||||||
return [inputBaseClasses, border, state, extra].filter(Boolean).join(' ')
|
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",
|
"name": "@maks-it.com/webui-contracts",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"description": "Shared TypeScript contracts for MaksIT WebUI apps",
|
"description": "Shared TypeScript contracts for MaksIT WebUI apps",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
@ -18,7 +18,7 @@
|
|||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"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",
|
"test": "jest --config ../../jest.config.cjs --testPathPatterns packages/contracts",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@maks-it.com/webui-core",
|
"name": "@maks-it.com/webui-core",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"description": "Shared utilities and hooks for MaksIT WebUI apps",
|
"description": "Shared utilities and hooks for MaksIT WebUI apps",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
@ -18,7 +18,7 @@
|
|||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"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",
|
"test": "jest --config ../../jest.config.cjs",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
||||||
@ -34,7 +34,7 @@
|
|||||||
"directory": "src/packages/core"
|
"directory": "src/packages/core"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@maks-it.com/webui-contracts": "^0.3.1",
|
"@maks-it.com/webui-contracts": "^0.3.2",
|
||||||
"date-fns": "^4.3.0"
|
"date-fns": "^4.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"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 = {
|
export const TogglesOnClick: Story = {
|
||||||
args: {
|
args: {
|
||||||
label: 'Enable feature',
|
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
|
.DESCRIPTION
|
||||||
This plugin validates GitHub CLI access, resolves the target
|
This plugin validates GitHub CLI access, resolves the target
|
||||||
repository, and creates the configured GitHub release using the
|
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)) {
|
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."
|
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 {
|
function Get-ReleaseNotesInternal {
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
@ -87,9 +63,9 @@ function Get-ReleaseNotesInternal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$releaseNotesContent = Get-Content $ReleaseNotesFile -Raw
|
$releaseNotesContent = Get-Content $ReleaseNotesFile -Raw
|
||||||
$releaseNotesVersion = Get-LatestChangelogVersionInternal -ReleaseNotesContent $releaseNotesContent
|
$releaseNotesVersion = Get-LatestChangelogVersion -ReleaseNotesContent $releaseNotesContent
|
||||||
if ([string]::IsNullOrWhiteSpace($releaseNotesVersion)) {
|
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) {
|
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 "OK" -Message " Release notes version matches: v$releaseNotesVersion"
|
||||||
|
|
||||||
Write-Log -Level "STEP" -Message "Extracting release notes..."
|
Write-Log -Level "STEP" -Message "Extracting release notes..."
|
||||||
$escapedVersion = [regex]::Escape($Version)
|
$section = Get-ChangelogReleaseNotesSection -ReleaseNotesContent $releaseNotesContent -Version $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)
|
|
||||||
|
|
||||||
if (-not $match.Success) {
|
if ([string]::IsNullOrWhiteSpace($section)) {
|
||||||
throw "Release notes entry for version $Version not found."
|
throw "Release notes entry for version $Version not found. Expected header: ## [$Version] - YYYY-MM-DD."
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Log -Level "OK" -Message " Release notes extracted."
|
Write-Log -Level "OK" -Message " Release notes extracted."
|
||||||
return $match.Value.Trim()
|
return $section
|
||||||
}
|
}
|
||||||
|
|
||||||
function Invoke-Plugin {
|
function Invoke-Plugin {
|
||||||
@ -120,6 +93,7 @@ function Invoke-Plugin {
|
|||||||
|
|
||||||
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
|
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
|
||||||
Import-PluginDependency -ModuleName "ScriptConfig" -RequiredCommand "Assert-Command"
|
Import-PluginDependency -ModuleName "ScriptConfig" -RequiredCommand "Assert-Command"
|
||||||
|
Import-PluginDependency -ModuleName "ChangelogSupport" -RequiredCommand "Get-LatestChangelogVersion"
|
||||||
|
|
||||||
$pluginSettings = $Settings
|
$pluginSettings = $Settings
|
||||||
$sharedSettings = $Settings.context
|
$sharedSettings = $Settings.context
|
||||||
|
|||||||
@ -340,16 +340,20 @@ function Invoke-ConfiguredPlugin {
|
|||||||
[string]$PluginsDirectory,
|
[string]$PluginsDirectory,
|
||||||
|
|
||||||
[Parameter(Mandatory = $false)]
|
[Parameter(Mandatory = $false)]
|
||||||
[bool]$ContinueOnError = $true
|
[bool]$ContinueOnError = $false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (-not (Test-PluginRunnable -Plugin $Plugin -SharedSettings $SharedSettings -PluginsDirectory $PluginsDirectory -WriteLogs:$true)) {
|
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) {
|
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)."
|
Write-Log -Level "INFO" -Message "Skipping plugin '$($Plugin.name)' (ReleasePublishGuard suppressed publish)."
|
||||||
return
|
return $true
|
||||||
}
|
}
|
||||||
|
|
||||||
$pluginModulePath = Resolve-PluginModulePath -Plugin $Plugin -PluginsDirectory $PluginsDirectory
|
$pluginModulePath = Resolve-PluginModulePath -Plugin $Plugin -PluginsDirectory $PluginsDirectory
|
||||||
@ -364,12 +368,11 @@ function Invoke-ConfiguredPlugin {
|
|||||||
|
|
||||||
& $invokeCommand -Settings $pluginSettings
|
& $invokeCommand -Settings $pluginSettings
|
||||||
Write-Log -Level "OK" -Message " Plugin '$($Plugin.name)' completed."
|
Write-Log -Level "OK" -Message " Plugin '$($Plugin.name)' completed."
|
||||||
|
return $true
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Log -Level "ERROR" -Message " Plugin '$($Plugin.name)' failed: $($_.Exception.Message)"
|
Write-Log -Level "ERROR" -Message " Plugin '$($Plugin.name)' failed: $($_.Exception.Message)"
|
||||||
if (-not $ContinueOnError) {
|
return $false
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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`. |
|
| `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. |
|
| `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
|
## Plugins
|
||||||
|
|
||||||
`CorePlugins/` — e.g. `DotNetReleaseVersion`, `NpmReleaseVersion`, `NpmBuild`, `NpmPublish`, `DockerPush`, `HelmPush`, `ReleasePublishGuard`. Optional `CustomPlugins/`.
|
`CorePlugins/` — e.g. `DotNetReleaseVersion`, `NpmReleaseVersion`, `NpmBuild`, `NpmPublish`, `DockerPush`, `HelmPush`, `ReleasePublishGuard`. Optional `CustomPlugins/`.
|
||||||
|
|||||||
@ -132,6 +132,7 @@ $sharedPluginSettings = $engineContext
|
|||||||
#region Plugin Execution
|
#region Plugin Execution
|
||||||
|
|
||||||
$releaseStageInitialized = $false
|
$releaseStageInitialized = $false
|
||||||
|
$releaseHadPluginFailures = $false
|
||||||
|
|
||||||
if ($plugins.Count -eq 0) {
|
if ($plugins.Count -eq 0) {
|
||||||
Write-Log -Level "WARN" -Message "No plugins configured in scriptsettings.json."
|
Write-Log -Level "WARN" -Message "No plugins configured in scriptsettings.json."
|
||||||
@ -149,8 +150,14 @@ else {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$continueOnError = $pluginStageLabel -eq "release"
|
$continueOnError = $false
|
||||||
Invoke-ConfiguredPlugin -Plugin $plugin -SharedSettings $sharedPluginSettings -PluginsDirectory $pluginsDir -ContinueOnError:$continueOnError
|
$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
|
#region Summary
|
||||||
Write-Log -Level "OK" -Message "=================================================="
|
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)"
|
Write-Log -Level "OK" -Message "RUN COMPLETE (publish skipped by ReleasePublishGuard)"
|
||||||
}
|
}
|
||||||
elseif ($engineContext.isNonReleaseBranch) {
|
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."
|
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
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft-07/schema",
|
"$schema": "https://json-schema.org/draft-07/schema",
|
||||||
"title": "Release Package Script Settings",
|
"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": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "NpmReleaseVersion",
|
"name": "NpmReleaseVersion",
|
||||||
@ -32,16 +32,6 @@
|
|||||||
"ensureTagOnRemote": true,
|
"ensureTagOnRemote": true,
|
||||||
"remoteName": "origin"
|
"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",
|
"name": "NpmPublish",
|
||||||
"stageLabel": "release",
|
"stageLabel": "release",
|
||||||
@ -61,7 +51,7 @@
|
|||||||
"plugins": {
|
"plugins": {
|
||||||
"NpmReleaseVersion": "Reads version from src/package.json (workspace root). syncWorkspaceVersions aligns packages/*/package.json before build/publish.",
|
"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.",
|
"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.",
|
"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)."
|
"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,
|
[string]$PluginsDirectory,
|
||||||
|
|
||||||
[Parameter(Mandatory = $false)]
|
[Parameter(Mandatory = $false)]
|
||||||
[bool]$ContinueOnError = $true
|
[bool]$ContinueOnError = $false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (-not (Test-PluginRunnable -Plugin $Plugin -SharedSettings $SharedSettings -PluginsDirectory $PluginsDirectory -WriteLogs:$true)) {
|
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) {
|
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)."
|
Write-Log -Level "INFO" -Message "Skipping plugin '$($Plugin.name)' (ReleasePublishGuard suppressed publish)."
|
||||||
return
|
return $true
|
||||||
}
|
}
|
||||||
|
|
||||||
$pluginModulePath = Resolve-PluginModulePath -Plugin $Plugin -PluginsDirectory $PluginsDirectory
|
$pluginModulePath = Resolve-PluginModulePath -Plugin $Plugin -PluginsDirectory $PluginsDirectory
|
||||||
@ -364,12 +368,11 @@ function Invoke-ConfiguredPlugin {
|
|||||||
|
|
||||||
& $invokeCommand -Settings $pluginSettings
|
& $invokeCommand -Settings $pluginSettings
|
||||||
Write-Log -Level "OK" -Message " Plugin '$($Plugin.name)' completed."
|
Write-Log -Level "OK" -Message " Plugin '$($Plugin.name)' completed."
|
||||||
|
return $true
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Log -Level "ERROR" -Message " Plugin '$($Plugin.name)' failed: $($_.Exception.Message)"
|
Write-Log -Level "ERROR" -Message " Plugin '$($Plugin.name)' failed: $($_.Exception.Message)"
|
||||||
if (-not $ContinueOnError) {
|
return $false
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -67,10 +67,25 @@ if ($configuredPlugins.Count -eq 0) {
|
|||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$testHadPluginFailures = $false
|
||||||
|
|
||||||
foreach ($plugin in $configuredPlugins) {
|
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 "=================================================="
|
||||||
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 "=================================================="
|
Write-Log -Level "OK" -Message "=================================================="
|
||||||
|
|
||||||
|
if ($testHadPluginFailures) {
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user