Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c335c7ed2 | ||
|
|
00934a7c0c |
No files matched your search
@@ -1,5 +1,20 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.6.0-2
|
||||||
|
|
||||||
|
fix: release.mjs reads a summary wrapped over several lines.
|
||||||
|
|
||||||
|
- `release.mjs` takes the summary of the newest entry as the paragraph after its heading and joins
|
||||||
|
its lines with single spaces into the commit message.
|
||||||
|
|
||||||
|
## 0.6.0
|
||||||
|
|
||||||
|
feature: RowDialog validates a row as part of a larger value, such as a whole document with the row put in.
|
||||||
|
|
||||||
|
- `RowDialog` takes `body`, the value posted to `validatePath` for the row, and `select`, the
|
||||||
|
row's problems by field and message taken from the answer.
|
||||||
|
- `useValidation` and `RowDialog` take an answer of any shape.
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
feature: the entry point /shared exports the types and constants a server and its web application share.
|
feature: the entry point /shared exports the types and constants a server and its web application share.
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ API types imported by both, without the code of either: `CredentialField`, `Cred
|
|||||||
| `ThemeMenu` | the button cycling light, dark and system theme |
|
| `ThemeMenu` | the button cycling light, dark and system theme |
|
||||||
| `SwitcherMenu`, `SwitcherItem` | a button naming the item chosen, whose menu chooses another, such as a page or a connection |
|
| `SwitcherMenu`, `SwitcherItem` | a button naming the item chosen, whose menu chooses another, such as a page or a connection |
|
||||||
| `TextInput` | a text field with clear and show/hide buttons |
|
| `TextInput` | a text field with clear and show/hide buttons |
|
||||||
| `RowDialog`, `useValidation` | a dialog that edits a value validated by the server as it changes, and the validation hook |
|
| `RowDialog`, `useValidation` | a dialog that edits a value validated by the server as it changes, and the validation hook; with `body` and `select` the dialog posts another value, such as a whole document with the row put in, and takes the row's problems from the answer |
|
||||||
| `createBrowserSettings(keys, event)` | switches kept in localStorage and followed across tabs |
|
| `createBrowserSettings(keys, event)` | switches kept in localStorage and followed across tabs |
|
||||||
| `CredentialsButton` | the dialog of a credential store; see [Credential store](#credential-store) |
|
| `CredentialsButton` | the dialog of a credential store; see [Credential store](#credential-store) |
|
||||||
| `TfModule`, `TfInstance`, `TfvarsFileStatus`, `TF_NAME_PATTERN`, `TFVARS_FILE_PATTERN`, `STATE_SCHEMA_PATTERN` | the types and name patterns of the [Tfvars store](#tfvars-store) |
|
| `TfModule`, `TfInstance`, `TfvarsFileStatus`, `TF_NAME_PATTERN`, `TFVARS_FILE_PATTERN`, `STATE_SCHEMA_PATTERN` | the types and name patterns of the [Tfvars store](#tfvars-store) |
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@koszewscy/sk-react-toolkit",
|
"name": "@koszewscy/sk-react-toolkit",
|
||||||
"version": "0.5.0",
|
"version": "0.6.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@koszewscy/sk-react-toolkit",
|
"name": "@koszewscy/sk-react-toolkit",
|
||||||
"version": "0.5.0",
|
"version": "0.6.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@koszewscy/sk-react-toolkit",
|
"name": "@koszewscy/sk-react-toolkit",
|
||||||
"version": "0.5.0",
|
"version": "0.6.0",
|
||||||
"description": "Shared code of the React and Fastify web applications: web components, server helpers and the Vite font plugin.",
|
"description": "Shared code of the React and Fastify web applications: web components, server helpers and the Vite font plugin.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+12
-7
@@ -10,8 +10,9 @@ const CHANGELOG = `${import.meta.dirname}/CHANGELOG.md`;
|
|||||||
|
|
||||||
const usage = `Usage: release.mjs [-h|--help] [-n|--dry-run] [--no-push] [-m|--message <message>]
|
const usage = `Usage: release.mjs [-h|--help] [-n|--dry-run] [--no-push] [-m|--message <message>]
|
||||||
|
|
||||||
-m, --message commit message; default: the summary line of the newest CHANGELOG.md entry,
|
-m, --message commit message; default: the summary of the newest CHANGELOG.md entry, the
|
||||||
the line between blank lines right after its "## x.y.z" or "## x.y.z-n" heading
|
paragraph between blank lines right after its "## x.y.z" or "## x.y.z-n"
|
||||||
|
heading, its lines joined with single spaces
|
||||||
|
|
||||||
The tag is the version of the heading: x.y.z for a release of the code, which must be the version
|
The tag is the version of the heading: x.y.z for a release of the code, which must be the version
|
||||||
of package.json and which the build workflow publishes, x.y.z-n, n from 2, for a later release
|
of package.json and which the build workflow publishes, x.y.z-n, n from 2, for a later release
|
||||||
@@ -48,8 +49,8 @@ function git(...args) {
|
|||||||
if (status !== 0) process.exit(status ?? 1);
|
if (status !== 0) process.exit(status ?? 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The newest entry: its version from the first "## x.y.z" or "## x.y.z-n" heading, n from 2, and its summary line when
|
// The newest entry: its version from the first "## x.y.z" or "## x.y.z-n" heading, n from 2, and its summary when the
|
||||||
// the heading is followed by a blank line, one line that is not a "-" item, and another blank line.
|
// heading is followed by a blank line and a paragraph that is not a "-" item; the paragraph's lines are joined with spaces.
|
||||||
const lines = readFileSync(CHANGELOG, 'utf8').split('\n');
|
const lines = readFileSync(CHANGELOG, 'utf8').split('\n');
|
||||||
const heading = lines.findIndex((line) => line.startsWith('## '));
|
const heading = lines.findIndex((line) => line.startsWith('## '));
|
||||||
if (heading === -1) fail(`${CHANGELOG} has no "## x.y.z" entry`);
|
if (heading === -1) fail(`${CHANGELOG} has no "## x.y.z" entry`);
|
||||||
@@ -65,12 +66,16 @@ const packageVersion = JSON.parse(readFileSync(`${import.meta.dirname}/package.j
|
|||||||
if (revision === undefined && version !== packageVersion) {
|
if (revision === undefined && version !== packageVersion) {
|
||||||
fail(`${CHANGELOG}: ${tag} is not the version of package.json, ${packageVersion}`);
|
fail(`${CHANGELOG}: ${tag} is not the version of package.json, ${packageVersion}`);
|
||||||
}
|
}
|
||||||
const [above, line, below] = lines.slice(heading + 1, heading + 4);
|
const end = lines.findIndex((line, index) => index > heading + 1 && line.trim() === '');
|
||||||
const summary = above?.trim() === '' && below?.trim() === '' && line?.trim() && !line.startsWith('-') && !line.startsWith('#') ? line.trim() : undefined;
|
const paragraph = end === -1 ? [] : lines.slice(heading + 2, end);
|
||||||
|
const summary =
|
||||||
|
lines[heading + 1]?.trim() === '' && paragraph.length > 0 && !paragraph[0].startsWith('-') && !paragraph.some((line) => line.startsWith('#'))
|
||||||
|
? paragraph.map((line) => line.trim()).join(' ')
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const message = values.message ?? summary;
|
const message = values.message ?? summary;
|
||||||
if (!message) {
|
if (!message) {
|
||||||
fail(`no commit message: pass -m, or put a summary line between blank lines right after the newest heading of ${CHANGELOG}`);
|
fail(`no commit message: pass -m, or put a summary paragraph between blank lines right after the newest heading of ${CHANGELOG}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spawnSync('git', ['rev-parse', '-q', '--verify', `refs/tags/${tag}`], { cwd: import.meta.dirname }).status === 0) {
|
if (spawnSync('git', ['rev-parse', '-q', '--verify', `refs/tags/${tag}`], { cwd: import.meta.dirname }).status === 0) {
|
||||||
|
|||||||
+10
-5
@@ -6,12 +6,16 @@ import { useValidation } from './useValidation.ts'
|
|||||||
/**
|
/**
|
||||||
* A dialog that edits one row. The server validates the value as it changes (validatePath); onSave writes it and
|
* A dialog that edits one row. The server validates the value as it changes (validatePath); onSave writes it and
|
||||||
* the dialog closes when it succeeds. fields renders the inputs from the value, a setter and the problems by field;
|
* the dialog closes when it succeeds. fields renders the inputs from the value, a setter and the problems by field;
|
||||||
* below shows extra information from the validation, such as subnet placements.
|
* below shows extra information from the validation, such as subnet placements. body gives the value posted to
|
||||||
|
* validatePath, such as a whole document with the row put in, and select the problems by field and the message of
|
||||||
|
* the row taken from the answer; without them the row itself is posted and the answer is its validation.
|
||||||
*/
|
*/
|
||||||
export default function RowDialog<T, V extends Validation = Validation>(props: {
|
export default function RowDialog<T, V = Validation>(props: {
|
||||||
title: string
|
title: string
|
||||||
initial: T
|
initial: T
|
||||||
validatePath: string
|
validatePath: string
|
||||||
|
body?: (value: T) => unknown
|
||||||
|
select?: (result: V) => Validation
|
||||||
saveLabel: string
|
saveLabel: string
|
||||||
onSave: (value: T) => Promise<void>
|
onSave: (value: T) => Promise<void>
|
||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
@@ -21,9 +25,10 @@ export default function RowDialog<T, V extends Validation = Validation>(props: {
|
|||||||
const [value, setValue] = useState<T>(props.initial)
|
const [value, setValue] = useState<T>(props.initial)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const validation = useValidation<V>(props.validatePath, value)
|
const validation = useValidation<V>(props.validatePath, props.body ? props.body(value) : value)
|
||||||
const problems = validation.result?.problems ?? {}
|
const selected = validation.result === null ? null : props.select ? props.select(validation.result) : validation.result as Validation
|
||||||
const message = validation.result?.message ?? ''
|
const problems = selected?.problems ?? {}
|
||||||
|
const message = selected?.message ?? ''
|
||||||
const blocked = validation.pending || Object.keys(problems).length > 0 || message !== ''
|
const blocked = validation.pending || Object.keys(problems).length > 0 || message !== ''
|
||||||
|
|
||||||
async function submit(event: FormEvent) {
|
async function submit(event: FormEvent) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { request, type Validation } from './api.ts'
|
|||||||
* Asks the server to validate value at path (a .../validate endpoint) 250 ms after it last
|
* Asks the server to validate value at path (a .../validate endpoint) 250 ms after it last
|
||||||
* changed. pending is true until the answer for the current value arrives; until then the previous result stays.
|
* changed. pending is true until the answer for the current value arrives; until then the previous result stays.
|
||||||
*/
|
*/
|
||||||
export function useValidation<T extends Validation>(path: string | null, value: unknown): { result: T | null; error: string; pending: boolean } {
|
export function useValidation<T = Validation>(path: string | null, value: unknown): { result: T | null; error: string; pending: boolean } {
|
||||||
const key = path === null ? '' : `${path}\n${JSON.stringify(value)}`
|
const key = path === null ? '' : `${path}\n${JSON.stringify(value)}`
|
||||||
const [answer, setAnswer] = useState<{ key: string; result: T | null; error: string } | null>(null)
|
const [answer, setAnswer] = useState<{ key: string; result: T | null; error: string } | null>(null)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user