[
  {
    "path": ".github/workflows/pr.yml",
    "content": "name: Test\non: push\njobs:\n  test:\n    name: Run tests\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-node@v3\n        with:\n          node-version: 18\n      - run: yarn install --frozen-lockfile\n      - run: yarn lint\n      - run: yarn test\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Dependency directory\nnode_modules\n\n# Unwanted\n.idea\n.DS_Store\n\n# Build files\ndist/*\ntest/*/dest.js\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"arrowParens\": \"avoid\",\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"trailingComma\": \"es5\"\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to `rollup-plugin-livereload` will be documented in this file.\n\n## [Unreleased]\n\n## [2.0.0] - 2020-08-28\n\n### Removed\n\n- Dropped support for Node.js below v8.3\n\n### Changed\n\n- Will automatically find an available port if the requested one is in use. ([#23](https://github.com/thgh/rollup-plugin-livereload/issues/23)) @appsforartists\n\n## [1.3.0] - 2020-04-28\n\n### Changed\n\n- Fix codesandbox support\n\n## [1.2.0] - 2020-03-19\n\n### Added\n\n- Add support for IE8 ([#43](https://github.com/thgh/rollup-plugin-livereload/issues/43)) @thgh\n\n### Changed\n\n- Only keep 1 server instance running ([#33](https://github.com/thgh/rollup-plugin-livereload/issues/33)) @rixo\n\n## [1.1.0] - 2020-03-19\n\n### Added\n\n- Add support for codesandbox.io ([#37](https://github.com/thgh/rollup-plugin-livereload/issues/37)) @jakobrosenberg\n\n## [1.0.4] - 2019-10-05\n\n### Changed\n\n- Update livereload to 0.8.0 || ^0.8.2\n\n## [1.0.0] - 2019-01-27\n\n### Changed\n\n- Add support for Rollup 1 @yohangz\n\n## [0.0.1] - 2016-09-24\n\n### Added\n\n- Initial version\n\n[unreleased]: https://github.com/thgh/rollup-plugin-livereload/compare/v2.0.0...HEAD\n[2.0.0]: https://github.com/thgh/rollup-plugin-livereload/compare/v1.3.0...v2.0.0\n[1.3.0]: https://github.com/thgh/rollup-plugin-livereload/compare/v1.2.0...v1.3.0\n[0.0.2]: https://github.com/thgh/rollup-plugin-livereload/compare/v0.0.1...v0.0.2\n[0.0.1]: https://github.com/thgh/rollup-plugin-livereload/releases\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Thomas Ghysels\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Rollup plugin LiveReload\n\n<a href=\"LICENSE\">\n  <img src=\"https://img.shields.io/badge/license-MIT-brightgreen.svg\" alt=\"Software License\" />\n</a>\n<a href=\"https://github.com/thgh/rollup-plugin-livereload/issues\">\n  <img src=\"https://img.shields.io/github/issues/thgh/rollup-plugin-livereload.svg\" alt=\"Issues\" />\n</a>\n<a href=\"http://standardjs.com/\">\n  <img src=\"https://img.shields.io/badge/code%20style-standard-brightgreen.svg\" alt=\"JavaScript Style Guide\" />\n</a>\n<a href=\"https://npmjs.org/package/rollup-plugin-livereload\">\n  <img src=\"https://img.shields.io/npm/v/rollup-plugin-livereload.svg?style=flat-squar\" alt=\"NPM\" />\n</a>\n<a href=\"https://github.com/thgh/rollup-plugin-livereload/releases\">\n  <img src=\"https://img.shields.io/github/release/thgh/rollup-plugin-livereload.svg\" alt=\"Latest Version\" />\n</a>\n\n## Installation\n\n```\nnpm install --save-dev rollup-plugin-livereload\n```\n\n## Usage\n\n```js\n// rollup.config.js\nimport livereload from 'rollup-plugin-livereload'\n\nexport default {\n  input: 'entry.js',\n  output: { file: 'bundle.js' },\n  plugins: [livereload()],\n}\n```\n\nTo make it a real dev-server, combine this plugin with [rollup-plugin-serve].\n\n```js\n// rollup.config.js\nimport serve from 'rollup-plugin-serve'\nimport livereload from 'rollup-plugin-livereload'\n\nexport default {\n  input: 'entry.js',\n  output: { file: 'bundle.js' },\n  plugins: [\n    serve(), // index.html should be in root of project\n    livereload(),\n  ],\n}\n```\n\n### Options\n\nBy default, it watches the current directory. If you also have css output, pass the folder to which the build files are written.\n\nThis plugin supports the following options:\n\n- `clientUrl`: provide an alternative URL to the `livereload.js` script/resource. This URL is always preferred over all other generated URLs.\n- `clientHostname`: alternative hostname used instead of `localhost` or the site's current host, where the bundle is fetched from. Use this option when you include your bundle from a different host.\n\nAll remaining options are passed to [`livereload.createServer()`][livereload].\n\nExample:\n\n```\nlivereload('dist')\n\n// --- OR ---\n\nlivereload({\n  watch: 'dist',\n  verbose: false, // Disable console output\n\n  // other livereload options\n  port: 12345,\n  delay: 300,\n  https: {\n      key: fs.readFileSync('keys/agent2-key.pem'),\n      cert: fs.readFileSync('keys/agent2-cert.pem')\n  }\n})\n```\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.\n\n## Contributing\n\nContributions and feedback are very welcome.\n\nTo get it running:\n\n1. Clone the project.\n2. `npm install`\n3. `npm run build`\n\n## Credits\n\n- [Thomas Ghysels](https://github.com/thgh)\n- [All Contributors][link-contributors]\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE) for more information.\n\n[link-author]: https://github.com/thgh\n[link-contributors]: ../../contributors\n[livereload]: https://www.npmjs.com/package/livereload\n[rollup-plugin-serve]: https://www.npmjs.com/package/rollup-plugin-serve\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"rollup-plugin-livereload\",\n  \"version\": \"2.0.5\",\n  \"description\": \"Rollup plugin for LiveReload that watches the bundle and reloads the page on change\",\n  \"type\": \"module\",\n  \"main\": \"dist/index.cjs\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.cts\",\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"default\": \"./dist/index.js\",\n        \"types\": \"./dist/index.d.ts\"\n      },\n      \"require\": {\n        \"default\": \"./dist/index.cjs\",\n        \"types\": \"./dist/index.d.cts\"\n      }\n    }\n  },\n  \"scripts\": {\n    \"build\": \"tsup src/index.ts --clean --format esm,cjs --cjsInterop --dts\",\n    \"dev\": \"npm run build -- --watch\",\n    \"lint\": \"prettier -l .\",\n    \"fix\": \"prettier --write .\",\n    \"test\": \"vitest --single-thread\",\n    \"prepare\": \"npm run build\"\n  },\n  \"keywords\": [\n    \"rollup\",\n    \"rollup-plugin\",\n    \"livereload\",\n    \"lr\"\n  ],\n  \"license\": \"MIT\",\n  \"author\": \"Thomas Ghysels <info@thomasg.be>\",\n  \"homepage\": \"https://github.com/thgh/rollup-plugin-livereload\",\n  \"bugs\": {\n    \"url\": \"https://github.com/thgh/rollup-plugin-livereload/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/thgh/rollup-plugin-livereload\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"engines\": {\n    \"node\": \">=16\"\n  },\n  \"dependencies\": {\n    \"@types/livereload\": \"^0.9.1\",\n    \"livereload\": \"^0.9.1\"\n  },\n  \"devDependencies\": {\n    \"@types/ws\": \"^8.5.5\",\n    \"port-authority\": \"^1.1.1\",\n    \"prettier\": \"^3.0.1\",\n    \"rollup\": \"3\",\n    \"rollup-plugin-serve\": \"1\",\n    \"tsup\": \"^7.2.0\",\n    \"typescript\": \"^5.1.6\",\n    \"vitest\": \"^0.34.1\"\n  }\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import {\n  createServer,\n  type LiveReloadServer,\n  type CreateServerConfig,\n} from 'livereload'\nimport { resolve } from 'path'\nimport { find } from 'port-authority'\nimport { type Plugin } from 'rollup'\n\ndeclare global {\n  var PLUGIN_LIVERELOAD: { server: LiveReloadServer | null }\n}\n\nconst state = (global.PLUGIN_LIVERELOAD = global.PLUGIN_LIVERELOAD || {\n  server: null,\n})\n\n/**\n * Find all `livereload` options here:\n * https://www.npmjs.com/package/livereload#user-content-server-api\n */\nexport interface RollupLivereloadOptions extends CreateServerConfig {\n  /**\n   * A directory or a set of directories to watch for changes.\n   * @default the current directory\n   */\n  watch?: string | string[]\n\n  /**\n   * Whether or not to inject the `livereload` snippet into the bundle which\n   * will enable `livereload` in your web app.\n   * @default true\n   */\n  inject?: boolean\n\n  /**\n   * Log a message to console when `livereload` is ready.\n   * @default true\n   */\n  verbose?: boolean\n\n  /**\n   * Override the snippet URL\n   * @example \"//localhost:35729/livereload.js?snipver=1\"\n   */\n  clientUrl?: string\n\n  /**\n   * Override the hostname of the snippet URL\n   * @example \"example.test\"\n   */\n  clientHostname?: string\n}\n\n/**\n * 🔄 A Rollup plugin for including `livereload` in your web app.\n */\nexport default function livereload(\n  options?: RollupLivereloadOptions | string\n): Plugin {\n  const parsedOptions = options ? parseOptions(options) : {}\n\n  // release previous server instance if rollup is reloading configuration\n  // in watch mode\n  if (state.server) {\n    state.server.close()\n  }\n\n  let enabled = parsedOptions.verbose === false\n  const portPromise = find(parsedOptions.port || 35729)\n\n  portPromise.then(port => {\n    state.server = createServer({ ...parsedOptions, port })\n\n    // Start watching\n    if (Array.isArray(parsedOptions.watch)) {\n      state.server.watch(\n        parsedOptions.watch.map(w => resolve(process.cwd(), w))\n      )\n    } else {\n      state.server.watch(resolve(process.cwd(), parsedOptions.watch || ''))\n    }\n  })\n\n  return {\n    name: 'livereload',\n    async banner() {\n      if (parsedOptions.inject === false) {\n        return ''\n      }\n      const port = await portPromise\n      const snippetSrc = parsedOptions.clientUrl\n        ? JSON.stringify(parsedOptions.clientUrl)\n        : parsedOptions.clientHostname\n        ? `'//${parsedOptions.clientHostname}:${port}/livereload.js?snipver=1'`\n        : process.env.CODESANDBOX_SSE\n        ? `'//' + (self.location.hostname.replace(/^([^.]+)-\\\\d+/,\"$1\").replace(/^([^.]+)/, \"$1-${port}\")) + '/livereload.js?snipver=1&port=443'`\n        : `(self.location.protocol.startsWith('http') ? '' : 'http:') + '//' + (self.location.hostname || 'localhost') + ':${port}/livereload.js?snipver=1'`\n      return `(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = ${snippetSrc}; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document);`\n    },\n    async generateBundle() {\n      if (!enabled) {\n        enabled = true\n        const port = await portPromise\n        const customPort = port !== 35729 ? ' on port ' + port : ''\n        console.log(green('LiveReload enabled' + customPort))\n      }\n    },\n  }\n}\n\nfunction parseOptions(\n  options: RollupLivereloadOptions | string\n): RollupLivereloadOptions {\n  if (typeof options === 'string') {\n    return {\n      watch: options,\n    }\n  }\n\n  return options\n}\n\nfunction green(text: string) {\n  return '\\u001b[1m\\u001b[32m' + text + '\\u001b[39m\\u001b[22m'\n}\n"
  },
  {
    "path": "test/config/entry.js",
    "content": "window.onload = () =>\n  (document.body.innerHTML +=\n    '<br>Path: ' + window.location.pathname + '<br>Date: ' + Date.now())\n"
  },
  {
    "path": "test/config/index.html",
    "content": "<head>\n  <title>test</title>\n</head>\n<script src=\"dest.js\"></script>\n"
  },
  {
    "path": "test/config/rollup.config.js",
    "content": "import serve from 'rollup-plugin-serve'\nimport livereload from '../../dist/index.js'\n\nexport default {\n  input: 'entry.js',\n  output: {\n    file: 'dest.js',\n    format: 'cjs',\n  },\n  plugins: [\n    serve({ contentBase: '', port: Math.round(Math.random() * 10000) + 40000 }),\n    livereload(),\n  ],\n}\n"
  },
  {
    "path": "test/config.test.ts",
    "content": "import { afterAll, beforeAll, describe, expect, vi } from 'vitest'\nimport { appendFileSync, readFileSync, writeFileSync } from 'node:fs'\nimport { DELAY, createContext } from './shared'\n\nconst FILE_TO_MODIFY = 'test/config/rollup.config.js'\n\ndescribe('config update', test => {\n  let ctx = createContext('config')\n  let originalFile: string\n  beforeAll(() => {\n    originalFile = readFileSync(FILE_TO_MODIFY, 'utf-8')\n  })\n\n  test('should trigger a reload', async t => {\n    expect(ctx.reload).toHaveBeenCalledTimes(0)\n\n    // Update the config file and check if it reloads\n    appendFileSync(FILE_TO_MODIFY, '\\nconsole.log(\"append\")')\n    await new Promise(resolve => setTimeout(resolve, 1000 + DELAY))\n    expect(ctx.reload).toHaveBeenCalledWith(\n      expect.stringContaining('rollup.config.js')\n    )\n    expect(ctx.reload).toHaveBeenCalledTimes(1)\n  })\n\n  afterAll(() => {\n    writeFileSync(FILE_TO_MODIFY, originalFile, 'utf-8')\n  })\n})\n"
  },
  {
    "path": "test/entry/entry.js",
    "content": "window.onload = () =>\n  (document.body.innerHTML +=\n    '<br>Path: ' + window.location.pathname + '<br>Date: ' + Date.now())\n"
  },
  {
    "path": "test/entry/index.html",
    "content": "<head>\n  <title>test</title>\n</head>\n<script src=\"dest.js\"></script>\n"
  },
  {
    "path": "test/entry/rollup.config.js",
    "content": "import serve from 'rollup-plugin-serve'\nimport livereload from '../../dist/index.js'\n\nexport default {\n  input: 'entry.js',\n  output: {\n    file: 'dest.js',\n    format: 'cjs',\n  },\n  plugins: [\n    serve({ contentBase: '', port: Math.round(Math.random() * 10000) + 40000 }),\n    livereload(),\n  ],\n}\n"
  },
  {
    "path": "test/entry.test.ts",
    "content": "import { afterAll, beforeAll, describe, expect, vi } from 'vitest'\nimport { appendFileSync, readFileSync, writeFileSync } from 'node:fs'\nimport { DELAY, createContext } from './shared'\n\nconst FILE_TO_MODIFY = 'test/entry/entry.js'\n\ndescribe('entry update', test => {\n  let ctx = createContext('entry')\n  let originalFile: string\n  beforeAll(() => {\n    originalFile = readFileSync(FILE_TO_MODIFY, 'utf-8')\n  })\n  test('should trigger a reload', async t => {\n    expect(ctx.reload).toHaveBeenCalledTimes(0)\n\n    // Update the entry file and check if it reloads\n    appendFileSync(FILE_TO_MODIFY, '\\nconsole.log(\"append\")')\n    await new Promise(resolve => setTimeout(resolve, DELAY))\n    // since livereload is watching the entire folder, it will send two reload\n    // commands, one for the entry file and one for the bundle.\n    expect(ctx.reload).toHaveBeenNthCalledWith(\n      1,\n      expect.stringContaining('entry.js')\n    )\n    expect(ctx.reload).toHaveBeenNthCalledWith(\n      2,\n      expect.stringContaining('dest.js')\n    )\n    // Update the entry file and check if it reloads\n    appendFileSync(FILE_TO_MODIFY, '\\nconsole.log(\"append\")')\n    await new Promise(resolve => setTimeout(resolve, DELAY))\n    expect(ctx.reload).toHaveBeenNthCalledWith(\n      3,\n      expect.stringContaining('entry.js')\n    )\n    expect(ctx.reload).toHaveBeenNthCalledWith(\n      4,\n      expect.stringContaining('dest.js')\n    )\n    expect(ctx.reload).toHaveBeenCalledTimes(4)\n  })\n\n  afterAll(() => {\n    writeFileSync(FILE_TO_MODIFY, originalFile, 'utf-8')\n  })\n})\n"
  },
  {
    "path": "test/shared.ts",
    "content": "import { ChildProcessWithoutNullStreams, spawn } from 'node:child_process'\nimport WebSocket from 'ws'\nimport { afterAll, beforeAll, vi } from 'vitest'\n\nexport const DELAY = 400\n\nexport function createContext(folder = 'entry') {\n  const start = performance.now()\n  const ctx = {\n    url: '',\n    server: null as unknown as ChildProcessWithoutNullStreams,\n    client: null as unknown as WebSocket,\n    reload: (path: string) => {},\n  }\n\n  beforeAll(async () => {\n    ctx.server = spawn('rollup', ['-cw'], { cwd: 'test/' + folder })\n    const serving = await new Promise(resolve => {\n      let created = false\n      let live = false\n      ctx.server.stdout.on('data', data => {\n        const line = data.toString().trim()\n        if (line.split(' -> ').length > 1 && line.startsWith('http')) {\n          ctx.url = line.split(' -> ')[0]\n        }\n        if (line.includes('LiveReload enabled')) {\n          live = true\n          if (created && live) resolve(true)\n        }\n      })\n      ctx.server.stderr.on('data', data => {\n        if (data.toString().trim().includes('created')) created = true\n        if (created && live) resolve(true)\n      })\n    })\n\n    await new Promise(resolve => {\n      const client = (ctx.client = new WebSocket('ws://localhost:35729'))\n      client.addEventListener('open', () => {\n        client.send(\n          '{\"command\":\"hello\",\"protocols\":[\"http://livereload.com/protocols/official-6\",\"http://livereload.com/protocols/official-7\"],\"ver\":\"3.3.2\",\"snipver\":1}'\n        )\n        resolve(true)\n      })\n      client.addEventListener('message', evt => {\n        const data = JSON.parse(evt.data.toString())\n        if (data.command === 'hello') {\n          client.send(JSON.stringify({ command: 'info', url: ctx.url + '/' }))\n          resolve(client)\n        }\n        if (data.command === 'reload') {\n          ctx.reload(data.path)\n        }\n      })\n    })\n\n    ctx.reload = vi.fn((path: string) => {})\n  })\n\n  afterAll(() => {\n    ctx.client?.close()\n    ctx.server.kill()\n  })\n\n  return ctx\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"strict\": true\n  }\n}\n"
  }
]