browser-commander 0.4.0 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 8b86dd7: Include README.md in npm package
8
+
9
+ Added language-specific README.md files for each implementation:
10
+ - js/README.md: JavaScript/npm-specific documentation with installation and API usage
11
+ - rust/README.md: Rust/Cargo-specific documentation
12
+ - Root README.md: Common overview linking to both implementations
13
+
14
+ The npm package now includes the JavaScript-specific README.md directly from the js/ directory.
15
+
16
+ ## 0.5.2
17
+
18
+ ### Patch Changes
19
+
20
+ - 87224ee: Fix package.json path in version-and-commit.mjs for monorepo structure
21
+
22
+ The git show command uses repository root paths, not the workflow's working directory. Since this is a monorepo with js/ and rust/ folders, the path must be js/package.json instead of just package.json.
23
+
24
+ This was causing "Unexpected end of JSON input" errors when the script tried to read package.json from the repository root (which doesn't exist) instead of js/package.json.
25
+
26
+ ## 0.5.1
27
+
28
+ ### Patch Changes
29
+
30
+ - 2b22f43: Fix PlaywrightAdapter.evaluateOnPage() to spread multiple arguments correctly
31
+
32
+ When using `evaluateOnPage()` with multiple arguments, the arguments are now properly spread to the function in the browser context, matching Puppeteer's behavior.
33
+
34
+ Previously, the function would receive the entire array as its first parameter instead of spread arguments, causing issues like invalid selectors when passing selector + array combinations.
35
+
36
+ ## 0.5.0
37
+
38
+ ### Minor Changes
39
+
40
+ - adfccde: Add Rust implementation with parallel JavaScript codebase reorganization
41
+
42
+ This introduces a complete Rust translation of the browser-commander library alongside the existing JavaScript implementation. The codebase is now organized into two parallel structures:
43
+ - `js/` - JavaScript implementation (all existing functionality preserved)
44
+ - `rust/` - New Rust implementation with the same modular architecture
45
+
46
+ Key features of the Rust implementation:
47
+ - Unified API across multiple browser engines (chromiumoxide, fantoccini)
48
+ - Core types and traits (constants, engine adapter, logger)
49
+ - Element operations (selectors, visibility, content)
50
+ - User interactions (click, scroll, fill)
51
+ - Browser management (launcher, navigation)
52
+ - General utilities (URL handling, wait operations)
53
+ - High-level DRY utilities
54
+ - Comprehensive test coverage with 106 tests
55
+
3
56
  ## 0.4.0
4
57
 
5
58
  ### Minor Changes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Browser Commander
2
2
 
3
- A universal browser automation library that supports both Playwright and Puppeteer with a unified API. The key focus is on **stoppable page triggers** - ensuring automation logic is properly mounted/unmounted during page navigation.
3
+ A universal browser automation library for JavaScript/TypeScript that supports both Playwright and Puppeteer with a unified API. The key focus is on **stoppable page triggers** - ensuring automation logic is properly mounted/unmounted during page navigation.
4
4
 
5
5
  ## Installation
6
6
 
@@ -332,4 +332,4 @@ See [src/ARCHITECTURE.md](src/ARCHITECTURE.md) for detailed architecture documen
332
332
 
333
333
  ## License
334
334
 
335
- [UNLICENSE](LICENSE)
335
+ [UNLICENSE](../LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-commander",
3
- "version": "0.4.0",
3
+ "version": "0.5.3",
4
4
  "description": "Universal browser automation library that supports both Playwright and Puppeteer with a unified API",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -15,7 +15,10 @@ import { readFileSync, readdirSync, existsSync } from 'fs';
15
15
  import { join } from 'path';
16
16
 
17
17
  const PACKAGE_NAME = 'browser-commander';
18
+ // When running from js/ directory, the changeset dir is local
18
19
  const CHANGESET_DIR = '.changeset';
20
+ // But git diff output uses repository-root relative paths
21
+ const GIT_CHANGESET_PATH = 'js/.changeset';
19
22
 
20
23
  /**
21
24
  * Ensure a git commit is available locally, fetching if necessary
@@ -46,13 +49,19 @@ function parseAddedChangesets(diffOutput) {
46
49
  continue;
47
50
  }
48
51
  const [status, filePath] = line.split('\t');
52
+ // Check both local (.changeset/) and repo-root (js/.changeset/) paths
53
+ const isChangeset =
54
+ filePath.startsWith(`${CHANGESET_DIR}/`) ||
55
+ filePath.startsWith(`${GIT_CHANGESET_PATH}/`);
49
56
  if (
50
57
  status === 'A' &&
51
- filePath.startsWith(`${CHANGESET_DIR}/`) &&
58
+ isChangeset &&
52
59
  filePath.endsWith('.md') &&
53
60
  !filePath.endsWith('README.md')
54
61
  ) {
55
- addedChangesets.push(filePath.replace(`${CHANGESET_DIR}/`, ''));
62
+ // Extract just the filename
63
+ const fileName = filePath.split('/').pop();
64
+ addedChangesets.push(fileName);
56
65
  }
57
66
  }
58
67
  return addedChangesets;
@@ -126,7 +126,9 @@ function countChangesets() {
126
126
  */
127
127
  async function getVersion(source = 'local') {
128
128
  if (source === 'remote') {
129
- const result = await $`git show origin/main:package.json`.run({
129
+ // Use js/package.json for monorepo structure
130
+ // The workflow runs with working-directory: js, but git show uses repo root paths
131
+ const result = await $`git show origin/main:js/package.json`.run({
130
132
  capture: true,
131
133
  });
132
134
  return JSON.parse(result.stdout).version;
@@ -304,13 +304,25 @@ export class PlaywrightAdapter extends EngineAdapter {
304
304
 
305
305
  async evaluateOnPage(fn, args = []) {
306
306
  // Playwright only accepts a single argument (can be array/object)
307
+ // To match Puppeteer's behavior where args are spread, we wrap the function
308
+ // and pass all args as a single array, then apply them in the browser context
307
309
  if (args.length === 0) {
308
310
  return await this.page.evaluate(fn);
309
311
  } else if (args.length === 1) {
310
312
  return await this.page.evaluate(fn, args[0]);
311
313
  } else {
312
- // Multiple args - pass as array
313
- return await this.page.evaluate(fn, args);
314
+ // Multiple args - wrap function to accept array and spread them
315
+ // This makes Playwright behave like Puppeteer's spread behavior
316
+ // We pass the function string and args array, then reconstruct and call in browser
317
+ const fnString = fn.toString();
318
+ return await this.page.evaluate(
319
+ ({ fnStr, argsArray }) => {
320
+ // Reconstruct the function in browser context and call with spread args
321
+ const reconstructedFn = new Function(`return (${fnStr})`)();
322
+ return reconstructedFn(...argsArray);
323
+ },
324
+ { fnStr: fnString, argsArray: args }
325
+ );
314
326
  }
315
327
  }
316
328
 
@@ -105,28 +105,15 @@ export function createMockPlaywrightPage(options = {}) {
105
105
  return locs.map((l) => l.evaluate(fn, ...args));
106
106
  },
107
107
  locator: locatorMock,
108
- evaluate: async (fn, ...args) => {
108
+ // Playwright's page.evaluate() only accepts a single argument (not spread)
109
+ // This is the key difference from Puppeteer
110
+ evaluate: async (fn, arg) => {
109
111
  if (evaluateResult !== null) {
110
112
  return evaluateResult;
111
113
  }
112
- // Create mock window/document context
113
- const mockContext = {
114
- innerHeight: 800,
115
- innerWidth: 1200,
116
- sessionStorage: {
117
- _data: {},
118
- getItem: (key) => mockContext.sessionStorage._data[key] || null,
119
- setItem: (key, val) => {
120
- mockContext.sessionStorage._data[key] = val;
121
- },
122
- removeItem: (key) => {
123
- delete mockContext.sessionStorage._data[key];
124
- },
125
- },
126
- querySelectorAll: () => [],
127
- };
128
114
  try {
129
- return fn(...args);
115
+ // Playwright passes exactly one argument (can be object/array)
116
+ return fn(arg);
130
117
  } catch {
131
118
  return fn;
132
119
  }
@@ -78,6 +78,53 @@ describe('engine-adapter', () => {
78
78
  const count = await adapter.count('button');
79
79
  assert.strictEqual(count, 5);
80
80
  });
81
+
82
+ describe('evaluateOnPage', () => {
83
+ it('should handle zero arguments', async () => {
84
+ page = createMockPlaywrightPage();
85
+ adapter = new PlaywrightAdapter(page);
86
+ const result = await adapter.evaluateOnPage(() => 42);
87
+ assert.strictEqual(result, 42);
88
+ });
89
+
90
+ it('should handle single argument', async () => {
91
+ page = createMockPlaywrightPage();
92
+ adapter = new PlaywrightAdapter(page);
93
+ const result = await adapter.evaluateOnPage((x) => x * 2, [5]);
94
+ assert.strictEqual(result, 10);
95
+ });
96
+
97
+ it('should handle multiple arguments by spreading them', async () => {
98
+ page = createMockPlaywrightPage();
99
+ adapter = new PlaywrightAdapter(page);
100
+ // This is the bug case - multiple args should be spread, not passed as array
101
+ const result = await adapter.evaluateOnPage((a, b) => a + b, [3, 7]);
102
+ assert.strictEqual(result, 10);
103
+ });
104
+
105
+ it('should handle selector + array arguments (real-world bug case)', async () => {
106
+ page = createMockPlaywrightPage();
107
+ adapter = new PlaywrightAdapter(page);
108
+ // This reproduces the original bug: selector and processedIds
109
+ const result = await adapter.evaluateOnPage(
110
+ (selector, processedIds) =>
111
+ `Selector: ${selector}, Count: ${processedIds.length}`,
112
+ ['[data-qa="test"]', ['id1', 'id2']]
113
+ );
114
+ assert.ok(result.includes('Selector: [data-qa="test"]'));
115
+ assert.ok(result.includes('Count: 2'));
116
+ });
117
+
118
+ it('should handle multiple arguments of different types', async () => {
119
+ page = createMockPlaywrightPage();
120
+ adapter = new PlaywrightAdapter(page);
121
+ const result = await adapter.evaluateOnPage(
122
+ (str, num, obj) => `${str}-${num}-${obj.key}`,
123
+ ['hello', 42, { key: 'world' }]
124
+ );
125
+ assert.strictEqual(result, 'hello-42-world');
126
+ });
127
+ });
81
128
  });
82
129
 
83
130
  describe('PuppeteerAdapter', () => {
@@ -130,6 +177,42 @@ describe('engine-adapter', () => {
130
177
  const count = await adapter.count('button');
131
178
  assert.strictEqual(count, 5);
132
179
  });
180
+
181
+ describe('evaluateOnPage', () => {
182
+ it('should handle zero arguments', async () => {
183
+ page = createMockPuppeteerPage();
184
+ adapter = new PuppeteerAdapter(page);
185
+ const result = await adapter.evaluateOnPage(() => 42);
186
+ assert.strictEqual(result, 42);
187
+ });
188
+
189
+ it('should handle single argument', async () => {
190
+ page = createMockPuppeteerPage();
191
+ adapter = new PuppeteerAdapter(page);
192
+ const result = await adapter.evaluateOnPage((x) => x * 2, [5]);
193
+ assert.strictEqual(result, 10);
194
+ });
195
+
196
+ it('should handle multiple arguments by spreading them', async () => {
197
+ page = createMockPuppeteerPage();
198
+ adapter = new PuppeteerAdapter(page);
199
+ // Puppeteer natively spreads args - ensure same behavior as Playwright fix
200
+ const result = await adapter.evaluateOnPage((a, b) => a + b, [3, 7]);
201
+ assert.strictEqual(result, 10);
202
+ });
203
+
204
+ it('should handle selector + array arguments (parity with PlaywrightAdapter)', async () => {
205
+ page = createMockPuppeteerPage();
206
+ adapter = new PuppeteerAdapter(page);
207
+ const result = await adapter.evaluateOnPage(
208
+ (selector, processedIds) =>
209
+ `Selector: ${selector}, Count: ${processedIds.length}`,
210
+ ['[data-qa="test"]', ['id1', 'id2']]
211
+ );
212
+ assert.ok(result.includes('Selector: [data-qa="test"]'));
213
+ assert.ok(result.includes('Count: 2'));
214
+ });
215
+ });
133
216
  });
134
217
 
135
218
  describe('createEngineAdapter', () => {
@@ -1,296 +0,0 @@
1
- name: Checks and release
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
- pull_request:
8
- types: [opened, synchronize, reopened]
9
- # Manual release support - consolidated here to work with npm trusted publishing
10
- # npm only allows ONE workflow file as trusted publisher, so all publishing
11
- # must go through this workflow (release.yml)
12
- workflow_dispatch:
13
- inputs:
14
- release_mode:
15
- description: 'Manual release mode'
16
- required: true
17
- type: choice
18
- default: 'instant'
19
- options:
20
- - instant
21
- - changeset-pr
22
- bump_type:
23
- description: 'Manual release type'
24
- required: true
25
- type: choice
26
- options:
27
- - patch
28
- - minor
29
- - major
30
- description:
31
- description: 'Manual release description (optional)'
32
- required: false
33
- type: string
34
-
35
- concurrency: ${{ github.workflow }}-${{ github.ref }}
36
-
37
- jobs:
38
- # Changeset check - only runs on PRs
39
- changeset-check:
40
- name: Check for Changesets
41
- runs-on: ubuntu-latest
42
- if: github.event_name == 'pull_request'
43
- steps:
44
- - uses: actions/checkout@v4
45
- with:
46
- fetch-depth: 0
47
-
48
- - name: Setup Node.js
49
- uses: actions/setup-node@v4
50
- with:
51
- node-version: '20.x'
52
-
53
- - name: Install dependencies
54
- run: npm install
55
-
56
- - name: Check for changesets
57
- env:
58
- # Pass PR context to the validation script
59
- GITHUB_BASE_REF: ${{ github.base_ref }}
60
- GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }}
61
- GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
62
- run: |
63
- # Skip changeset check for automated version PRs
64
- if [[ "${{ github.head_ref }}" == "changeset-release/"* ]]; then
65
- echo "Skipping changeset check for automated release PR"
66
- exit 0
67
- fi
68
-
69
- # Run changeset validation script
70
- # This validates that exactly ONE changeset was ADDED by this PR
71
- # Pre-existing changesets from other merged PRs are ignored
72
- node scripts/validate-changeset.mjs
73
-
74
- # Linting and formatting - runs after changeset check on PRs, immediately on main
75
- lint:
76
- name: Lint and Format Check
77
- runs-on: ubuntu-latest
78
- needs: [changeset-check]
79
- if: always() && (github.event_name == 'push' || needs.changeset-check.result == 'success')
80
- steps:
81
- - uses: actions/checkout@v4
82
-
83
- - name: Setup Node.js
84
- uses: actions/setup-node@v4
85
- with:
86
- node-version: '20.x'
87
-
88
- - name: Install dependencies
89
- run: npm install
90
-
91
- - name: Run ESLint
92
- run: npm run lint
93
-
94
- - name: Check formatting
95
- run: npm run format:check
96
-
97
- - name: Check code duplication
98
- run: npm run check:duplication
99
-
100
- # Test job - runs on Node.js only (browser automation requires specific setup)
101
- test:
102
- name: Test (Node.js on ${{ matrix.os }})
103
- runs-on: ${{ matrix.os }}
104
- needs: [changeset-check]
105
- if: always() && (github.event_name == 'push' || needs.changeset-check.result == 'success')
106
- strategy:
107
- fail-fast: false
108
- matrix:
109
- os: [ubuntu-latest, macos-latest, windows-latest]
110
- steps:
111
- - uses: actions/checkout@v4
112
-
113
- - name: Setup Node.js
114
- uses: actions/setup-node@v4
115
- with:
116
- node-version: '20.x'
117
-
118
- - name: Install dependencies
119
- run: npm install
120
-
121
- - name: Run unit tests
122
- run: npm test
123
-
124
- # Release - only runs on main after tests pass (for push events)
125
- release:
126
- name: Release
127
- needs: [lint, test]
128
- # Use always() to ensure this job runs even if changeset-check was skipped
129
- # This is needed because lint/test jobs have a transitive dependency on changeset-check
130
- if: always() && github.ref == 'refs/heads/main' && github.event_name == 'push' && needs.lint.result == 'success' && needs.test.result == 'success'
131
- runs-on: ubuntu-latest
132
- # Permissions required for npm OIDC trusted publishing
133
- permissions:
134
- contents: write
135
- pull-requests: write
136
- id-token: write
137
- steps:
138
- - uses: actions/checkout@v4
139
- with:
140
- fetch-depth: 0
141
-
142
- - name: Setup Node.js
143
- uses: actions/setup-node@v4
144
- with:
145
- node-version: '20.x'
146
- registry-url: 'https://registry.npmjs.org'
147
-
148
- - name: Install dependencies
149
- run: npm install
150
-
151
- - name: Update npm for OIDC trusted publishing
152
- run: node scripts/setup-npm.mjs
153
-
154
- - name: Check for changesets
155
- id: check_changesets
156
- run: |
157
- # Count changeset files (excluding README.md and config.json)
158
- CHANGESET_COUNT=$(find .changeset -name "*.md" ! -name "README.md" | wc -l)
159
- echo "Found $CHANGESET_COUNT changeset file(s)"
160
- echo "has_changesets=$([[ $CHANGESET_COUNT -gt 0 ]] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT
161
- echo "changeset_count=$CHANGESET_COUNT" >> $GITHUB_OUTPUT
162
-
163
- - name: Merge multiple changesets
164
- if: steps.check_changesets.outputs.has_changesets == 'true' && steps.check_changesets.outputs.changeset_count > 1
165
- run: |
166
- echo "Multiple changesets detected, merging..."
167
- node scripts/merge-changesets.mjs
168
-
169
- - name: Version packages and commit to main
170
- if: steps.check_changesets.outputs.has_changesets == 'true'
171
- id: version
172
- run: node scripts/version-and-commit.mjs --mode changeset
173
-
174
- - name: Publish to npm
175
- # Run if version was committed OR if a previous attempt already committed (for re-runs)
176
- if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
177
- id: publish
178
- run: node scripts/publish-to-npm.mjs --should-pull
179
-
180
- - name: Create GitHub Release
181
- if: steps.publish.outputs.published == 'true'
182
- env:
183
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
184
- run: node scripts/create-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}"
185
-
186
- - name: Format GitHub release notes
187
- if: steps.publish.outputs.published == 'true'
188
- env:
189
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
190
- run: node scripts/format-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" --commit-sha "${{ github.sha }}"
191
-
192
- # Manual Instant Release - triggered via workflow_dispatch with instant mode
193
- # This job is in release.yml because npm trusted publishing
194
- # only allows one workflow file to be registered as a trusted publisher
195
- instant-release:
196
- name: Instant Release
197
- if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'instant'
198
- runs-on: ubuntu-latest
199
- # Permissions required for npm OIDC trusted publishing
200
- permissions:
201
- contents: write
202
- pull-requests: write
203
- id-token: write
204
- steps:
205
- - uses: actions/checkout@v4
206
- with:
207
- fetch-depth: 0
208
-
209
- - name: Setup Node.js
210
- uses: actions/setup-node@v4
211
- with:
212
- node-version: '20.x'
213
- registry-url: 'https://registry.npmjs.org'
214
-
215
- - name: Install dependencies
216
- run: npm install
217
-
218
- - name: Update npm for OIDC trusted publishing
219
- run: node scripts/setup-npm.mjs
220
-
221
- - name: Version packages and commit to main
222
- id: version
223
- run: node scripts/version-and-commit.mjs --mode instant --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}"
224
-
225
- - name: Publish to npm
226
- # Run if version was committed OR if a previous attempt already committed (for re-runs)
227
- if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
228
- id: publish
229
- run: node scripts/publish-to-npm.mjs
230
-
231
- - name: Create GitHub Release
232
- if: steps.publish.outputs.published == 'true'
233
- env:
234
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
235
- run: node scripts/create-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}"
236
-
237
- - name: Format GitHub release notes
238
- if: steps.publish.outputs.published == 'true'
239
- env:
240
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
241
- run: node scripts/format-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" --commit-sha "${{ github.sha }}"
242
-
243
- # Manual Changeset PR - creates a pull request with the changeset for review
244
- changeset-pr:
245
- name: Create Changeset PR
246
- if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'changeset-pr'
247
- runs-on: ubuntu-latest
248
- permissions:
249
- contents: write
250
- pull-requests: write
251
- steps:
252
- - uses: actions/checkout@v4
253
- with:
254
- fetch-depth: 0
255
-
256
- - name: Setup Node.js
257
- uses: actions/setup-node@v4
258
- with:
259
- node-version: '20.x'
260
-
261
- - name: Install dependencies
262
- run: npm install
263
-
264
- - name: Create changeset file
265
- run: node scripts/create-manual-changeset.mjs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}"
266
-
267
- - name: Format changeset with Prettier
268
- run: |
269
- # Run Prettier on the changeset file to ensure it matches project style
270
- npx prettier --write ".changeset/*.md" || true
271
-
272
- echo "Formatted changeset files"
273
-
274
- - name: Create Pull Request
275
- uses: peter-evans/create-pull-request@v7
276
- with:
277
- token: ${{ secrets.GITHUB_TOKEN }}
278
- commit-message: 'chore: add changeset for manual ${{ github.event.inputs.bump_type }} release'
279
- branch: changeset-manual-release-${{ github.run_id }}
280
- delete-branch: true
281
- title: 'chore: manual ${{ github.event.inputs.bump_type }} release'
282
- body: |
283
- ## Manual Release Request
284
-
285
- This PR was created by a manual workflow trigger to prepare a **${{ github.event.inputs.bump_type }}** release.
286
-
287
- ### Release Details
288
- - **Type:** ${{ github.event.inputs.bump_type }}
289
- - **Description:** ${{ github.event.inputs.description || 'Manual release' }}
290
- - **Triggered by:** @${{ github.actor }}
291
-
292
- ### Next Steps
293
- 1. Review the changeset in this PR
294
- 2. Merge this PR to main
295
- 3. The automated release workflow will create a version PR
296
- 4. Merge the version PR to publish to npm and create a GitHub release
package/LICENSE DELETED
@@ -1,24 +0,0 @@
1
- This is free and unencumbered software released into the public domain.
2
-
3
- Anyone is free to copy, modify, publish, use, compile, sell, or
4
- distribute this software, either in source code form or as a compiled
5
- binary, for any purpose, commercial or non-commercial, and by any
6
- means.
7
-
8
- In jurisdictions that recognize copyright laws, the author or authors
9
- of this software dedicate any and all copyright interest in the
10
- software to the public domain. We make this dedication for the benefit
11
- of the public at large and to the detriment of our heirs and
12
- successors. We intend this dedication to be an overt act of
13
- relinquishment in perpetuity of all present and future rights to this
14
- software under copyright law.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
- IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
- OTHER DEALINGS IN THE SOFTWARE.
23
-
24
- For more information, please refer to <https://unlicense.org>