Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/unbash-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Watch unbash releases

# unbash is the parser every product is built on and is pinned exactly in package.json.
# This opens an issue when a newer version is on npm, so an upgrade is not left to the monthly Dependabot run.
on:
schedule:
- cron: '17 6 * * 1'
workflow_dispatch:

permissions:
contents: read
issues: write

jobs:
watch:
name: Compare with npm
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Open an issue for a newer version
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
current=$(node -p "require('./package.json').dependencies.unbash")
latest=$(npm view unbash version)
echo "pinned: $current, latest: $latest"
# Only a newer version counts (sort -V puts the highest last); never an older or equal one.
if [ "$current" = "$latest" ] || [ "$(printf '%s\n%s\n' "$current" "$latest" | sort -V | tail -n 1)" != "$latest" ]; then
echo "Up to date."
exit 0
fi
title="unbash $latest is out"
if [ -n "$(gh issue list --state open --search "\"$title\" in:title" --json title --jq ".[] | select(.title == \"$title\") | .title")" ]; then
echo "An issue already exists."
exit 0
fi
body=$(cat <<EOF
unbash $latest is on npm; \`package.json\` pins $current.

- Release: https://github.com/webpro-nl/unbash/releases/tag/$latest
- Changes: https://github.com/webpro-nl/unbash/compare/$current...$latest

Checklist:

- [ ] Read the release notes and the compare above
- [ ] \`npm install unbash@$latest --save-exact\`
- [ ] Run the tests, the fuzz tests and \`npx tsx scripts/extract-snippets.ts --check\`
- [ ] Run the bash-ast test (\`test/bash-ast.test.ts\`) and update \`test/fixtures/bash-ast-known.json\` if a known difference is fixed
- [ ] Compare \`npx tsx test/dump.ts test/fixtures/sample.sh en|ja\` before and after
- [ ] \`npm run notices\` to regenerate THIRD_PARTY_NOTICES.md
- [ ] CHANGELOG line under "Unreleased" if users see a difference
EOF
)
gh issue create --title "$title" --body "$body"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ npm run test:integration:web # …and in VS Code for the Web (needs `npx playw
- CI measures the coverage of the unit tests with every tool installed (the Node.js 24 job): the run's summary lists the least covered files, and the `coverage` artifact holds the HTML report. Measured locally, it is lower when tools are missing, as their tests are skipped; use Node.js 24 as CI does, since older versions count methods that never ran as covered. `npm run coverage` runs in UTC, as CI does, since some branches depend on the time zone. The reports leave out the code that tsx adds when it compiles the sources for the tests (its helpers, the getters of exports), which maps to places no test can run ([scripts/coverage-report.mjs](scripts/coverage-report.mjs)).
- Every line of `src/` must run in a unit test, and every branch must be taken, or CI fails (`npm run coverage:check`). Files not there yet are listed in [coverage-floors.json](coverage-floors.json) with their current share, which must not go down; after adding tests, `node scripts/coverage-check.mjs --update` raises the floors and drops the files that reach 100%. A branch no test can take is removed, or, when it guards against a bug elsewhere, marked with `/* c8 ignore next */` and a comment saying why. The VS Code side (`extension.ts`, `service.ts`, `ui/` except the renderers) is left to the integration tests. The coverage badge at the top is static; it stays true because CI fails below 100%.
- After changing dependencies, run `npm run notices` to regenerate [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) from what the builds include; a test fails while it is out of date, or if something under a license not yet reviewed gets in.
- A weekly workflow ([unbash-release.yml](.github/workflows/unbash-release.yml)) opens an issue when a newer unbash (the parser, pinned exactly in `package.json`) is on npm, with the upgrade checklist; Dependabot's monthly npm run proposes the same bump as a PR.
- Japanese text follows [docs/japanese-style.md](docs/japanese-style.md) (in Japanese); a test checks it.
- To add a command, add an entry to [`src/dict/commands.ts`](src/dict/commands.ts): `cmd(japanese, english, [options…], { positional: […] })`. Write the descriptions in your own words: do not copy them from man pages, `--help` output or other documentation, whose licenses (often the GPL or the GFDL) differ from this project's.
- To release, bump `version` in `package.json` and `cli/package.json`, add a section to `CHANGELOG.md`, and push a tag `v<version>`. The [Release workflow](.github/workflows/release.yml) tests and packages it, waits for approval in the `release` environment, then publishes to the VS Code Marketplace, Open VSX and npm, creates a GitHub Release, moves the `v0` tag the GitHub Action is used by, and updates the web version; see [docs/publishing.md](docs/publishing.md) for the steps and the one-time setup.
Expand Down
46 changes: 23 additions & 23 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ Bash Explainer includes the following third-party software (ISC, MIT). Their lic

| Component | Version | License | Included as |
| --- | --- | --- | --- |
| [unbash](https://github.com/webpro-nl/unbash) | 6.0.0 | ISC | bundled into `dist/extension.js`; bundled into `dist/web/extension.js` |
| [unbash](https://github.com/webpro-nl/unbash) | 6.0.1 | ISC | bundled into `dist/extension.js`; bundled into `dist/web/extension.js` |

## In the web version

| Component | Version | License | Included as |
| --- | --- | --- | --- |
| [unbash](https://github.com/webpro-nl/unbash) | 6.0.1 | ISC | bundled into `app.js` |
| [@codemirror/commands](https://code.haverbeke.berlin/codemirror/commands) | 6.11.1 | MIT | bundled into `app.js` |
| [@codemirror/language](https://code.haverbeke.berlin/codemirror/language) | 6.13.1 | MIT | bundled into `app.js` |
| [@codemirror/legacy-modes](https://code.haverbeke.berlin/codemirror/legacy-modes) | 6.5.5 | MIT | bundled into `app.js` |
Expand All @@ -26,17 +27,36 @@ Bash Explainer includes the following third-party software (ISC, MIT). Their lic
| [@marijn/find-cluster-break](https://code.haverbeke.berlin/marijn/find-cluster-break) | 1.0.4 | MIT | bundled into `app.js` |
| [crelt](https://code.haverbeke.berlin/marijn/crelt) | 1.0.7 | MIT | bundled into `app.js` |
| [style-mod](https://code.haverbeke.berlin/marijn/style-mod) | 4.1.4 | MIT | bundled into `app.js` |
| [unbash](https://github.com/webpro-nl/unbash) | 6.0.0 | ISC | bundled into `app.js` |
| [w3c-keyname](https://github.com/marijnh/w3c-keyname#readme) | 2.2.8 | MIT | bundled into `app.js` |

## In the command-line tool (npm package bash-explainer)

| Component | Version | License | Included as |
| --- | --- | --- | --- |
| [unbash](https://github.com/webpro-nl/unbash) | 6.0.0 | ISC | bundled into `dist/cli.js` |
| [unbash](https://github.com/webpro-nl/unbash) | 6.0.1 | ISC | bundled into `dist/cli.js` |

## License texts

### unbash

```text
ISC License

Copyright (c) Lars Kappert

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
```

### @codemirror/commands, @codemirror/language, @codemirror/legacy-modes, @codemirror/lint, @codemirror/state, @codemirror/streamparser, @codemirror/view

```text
Expand Down Expand Up @@ -163,26 +183,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
```

### unbash

```text
ISC License

Copyright (c) Lars Kappert

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
```

### w3c-keyname

```text
Expand Down
2 changes: 1 addition & 1 deletion docs/design/syntax-tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ The move was made in steps, each a PR with the test suite as the safety net. Mos

### Known gaps to cover in the unbash adapter

These come from a survey of unbash 6.0.0. The adapter fills them; most are small.
These come from a survey of unbash 6.0.0 (6.0.1 only changed ANSI-C byte escapes). The adapter fills them; most are small.

- **No positions for comments, keywords and operators.** The token scanner recovers them (estimated at 400–700 lines with tests).
- **`[ ]` and `test` are plain commands.** The adapter makes `[ … ]` a `test_command` with its words as a flat list; testexpr does the grouping.
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@
"images": "node scripts/capture-readme-images.mjs"
},
"dependencies": {
"unbash": "6.0.0"
"unbash": "6.0.1"
},
"devDependencies": {
"@codemirror/commands": "6.11.1",
Expand Down
13 changes: 10 additions & 3 deletions scripts/notices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ export interface Target {
uses: Map<string, string[]>;
}

/** The parser every product is built on: listed first in every table and under "License texts"; the rest follow in alphabetical order. */
export const FIRST = 'unbash';

/** Package names in the order the notices list them: FIRST, then the others alphabetically. */
export const order = (names: Iterable<string>): string[] =>
[...names].sort((a, b) => Number(b === FIRST) - Number(a === FIRST) || a.localeCompare(b, 'en'));

const packageDir = (name: string) => path.join(root, 'node_modules', name);

export function packageInfo(name: string): Package {
Expand Down Expand Up @@ -80,7 +87,7 @@ export function collect(): Target[] {
}

export function render(targets: Target[]): string {
const names = [...new Set(targets.flatMap((t) => [...t.uses.keys()]))].sort();
const names = order(new Set(targets.flatMap((t) => [...t.uses.keys()])));
const packages = new Map(names.map((n) => [n, packageInfo(n)]));
const licenses = [...new Set([...packages.values()].map((p) => p.license))].sort();
const out: string[] = [
Expand All @@ -92,7 +99,7 @@ export function render(targets: Target[]): string {
];
for (const t of targets) {
out.push('', `## ${t.title}`, '', '| Component | Version | License | Included as |', '| --- | --- | --- | --- |');
for (const name of [...t.uses.keys()].sort()) {
for (const name of order(t.uses.keys())) {
const p = packages.get(name)!;
out.push(`| [${name}](${p.url}) | ${p.version} | ${p.license} | ${t.uses.get(name)!.join('; ')} |`);
}
Expand All @@ -101,7 +108,7 @@ export function render(targets: Target[]): string {
// Packages with the very same license text share one copy.
const byText = new Map<string, string[]>();
for (const p of packages.values()) byText.set(p.licenseText, [...(byText.get(p.licenseText) ?? []), p.name]);
for (const [text, group] of [...byText].sort((a, b) => a[1][0].localeCompare(b[1][0]))) {
for (const [text, group] of [...byText].sort((a, b) => order([a[1][0], b[1][0]])[0] === a[1][0] ? -1 : 1)) {
out.push('', `### ${group.join(', ')}`, '', '```text', text, '```');
}
return out.join('\n') + '\n';
Expand Down
11 changes: 10 additions & 1 deletion test/licenses.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { execFileSync } from 'child_process';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { ALLOWED_LICENSES, collect, NOTICES, packageInfo, render, Target } from '../scripts/notices';
import { ALLOWED_LICENSES, collect, FIRST, NOTICES, order, packageInfo, render, Target } from '../scripts/notices';

const root = path.join(__dirname, '..');

Expand All @@ -21,6 +21,15 @@ describe('licenses', function () {
assert.strictEqual(fs.readFileSync(NOTICES, 'utf8'), render(targets), 'THIRD_PARTY_NOTICES.md is out of date: run `npm run notices` and check the changes');
});

it('lists unbash, the parser, first in every table and under "License texts", then the rest alphabetically', () => {
assert.deepStrictEqual(order(['w3c-keyname', 'unbash', '@codemirror/view', 'crelt']), [FIRST, '@codemirror/view', 'crelt', 'w3c-keyname']);
const text = fs.readFileSync(NOTICES, 'utf8');
for (const section of text.split(/^## /m).slice(1)) {
const first = /^\| \[([^\]]+)\]|^### (.+)$/m.exec(section.replace(/^[^\n]*\n/, ''));
assert.strictEqual(first?.[1] ?? first?.[2], FIRST, section.split('\n')[0]);
}
});

it('includes only software under licenses whose terms reproducing the notice meets', () => {
for (const t of targets) {
for (const name of t.uses.keys()) {
Expand Down
Loading