[
  {
    "path": ".editorconfig",
    "content": "# editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules/\n*.log\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": ".npmrc",
    "content": "package-lock=false\n"
  },
  {
    "path": ".nvmrc",
    "content": "lts/*\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\ncache:\n  directories:\n    - node_modules\nnotifications:\n  email: false\nbefore_script:\n  - npm prune\nscript:\n  - npm test\nafter_success:\n  - npm run travis-deploy-once \"npm run semantic-release\"\nbranches:\n  except:\n    - /^v\\d+\\.\\d+\\.\\d+$/\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 SEEK\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": "[![Build Status](https://img.shields.io/travis/seek-oss/css-modules-typescript-loader/master.svg?style=flat-square)](http://travis-ci.org/seek-oss/css-modules-typescript-loader) [![npm](https://img.shields.io/npm/v/css-modules-typescript-loader.svg?style=flat-square)](https://www.npmjs.com/package/css-modules-typescript-loader) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)](https://github.com/semantic-release/semantic-release) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat-square)](http://commitizen.github.io/cz-cli/)\n\n# css-modules-typescript-loader\n\n[Webpack](https://webpack.js.org/) loader to create [TypeScript](https://www.typescriptlang.org/) declarations for [CSS Modules](https://github.com/css-modules/css-modules).\n\nEmits TypeScript declaration files matching your CSS Modules in the same location as your source files, e.g. `src/Component.css` will generate `src/Component.css.d.ts`.\n\n## Why?\n\nThere are currently a lot of [solutions to this problem](https://www.npmjs.com/search?q=css%20modules%20typescript%20loader). However, this package differs in the following ways:\n\n- Encourages generated TypeScript declarations to be checked into source control, which allows `webpack` and `tsc` commands to be run in parallel in CI.\n\n- Ensures committed TypeScript declarations are in sync with the code that generated them via the [`verify` mode](#verify-mode).\n\n## Usage\n\nPlace `css-modules-typescript-loader` directly after `css-loader` in your webpack config.\n\n```js\nmodule.exports = {\n  module: {\n    rules: [\n      {\n        test: /\\.css$/,\n        use: [\n          'css-modules-typescript-loader',\n          {\n            loader: 'css-loader',\n            options: {\n              modules: true\n            }\n          }\n        ]\n      }\n    ]\n  }\n};\n```\n\n### Verify Mode\n\nSince the TypeScript declarations are generated by `webpack`, they may potentially be out of date by the time you run `tsc`. To ensure your types are up to date, you can run the loader in `verify` mode, which is particularly useful in CI.\n\nFor example:\n\n```js\n{\n  loader: 'css-modules-typescript-loader',\n  options: {\n    mode: process.env.CI ? 'verify' : 'emit'\n  }\n}\n```\n\nInstead of emitting new TypeScript declarations, this will throw an error if a generated declaration doesn't match the committed one. This allows `tsc` and `webpack` to run in parallel in CI, if desired.\n\nThis workflow is similar to using the [Prettier](https://github.com/prettier/prettier) [`--list-different` option](https://prettier.io/docs/en/cli.html#list-different).\n\n## With Thanks\n\nThis package borrows heavily from [typings-for-css-modules-loader](https://github.com/Jimdo/typings-for-css-modules-loader).\n\n## License\n\nMIT.\n"
  },
  {
    "path": "index.js",
    "content": "const fs = require('fs');\nconst path = require('path');\nconst loaderUtils = require('loader-utils');\nconst LineDiff = require('line-diff');\n\nconst bannerMessage =\n  '// This file is automatically generated.\\n// Please do not change this file!';\n\nconst cssModuleExport = 'export const cssExports: CssExports;\\nexport default cssExports;\\n';\n\nconst getNoDeclarationFileError = ({ filename }) =>\n  new Error(\n    `Generated type declaration does not exist. Run webpack and commit the type declaration for '${filename}'`\n  );\n\nconst getTypeMismatchError = ({ filename, expected, actual }) => {\n  const diff = new LineDiff(enforceLFLineSeparators(actual), expected).toString();\n\n  return new Error(\n    `Generated type declaration file is outdated. Run webpack and commit the updated type declaration for '${filename}'\\n\\n${diff}`\n  );\n};\n\nconst cssModuleToInterface = (cssModuleKeys) => {\n  const interfaceFields = cssModuleKeys\n    .sort()\n    .map(key => `  '${key}': string;`)\n    .join('\\n');\n\n  return `interface CssExports {\\n${interfaceFields}\\n}`;\n};\n\nconst filenameToTypingsFilename = filename => {\n  const dirName = path.dirname(filename);\n  const baseName = path.basename(filename);\n  return path.join(dirName, `${baseName}.d.ts`);\n};\n\nconst enforceLFLineSeparators = text => {\n  if (text) {\n    // replace all CRLFs (Windows) by LFs (Unix)\n    return text.replace(/\\r\\n/g, \"\\n\");\n  } else {\n    return text;\n  }\n};\n\nconst compareText = (contentA, contentB) => {\n  return enforceLFLineSeparators(contentA) === enforceLFLineSeparators(contentB);\n};\n\nconst validModes = ['emit', 'verify'];\n\nconst isFileNotFound = err => err && err.code === 'ENOENT';\n\nconst makeDoneHandlers = (callback, content, rest) => ({\n  failed: e => callback(e),\n  success: () => callback(null, content, ...rest)\n});\n\nconst makeFileHandlers = filename => ({\n  read: handler => fs.readFile(filename, { encoding: 'utf-8' }, handler),\n  write: (content, handler) =>\n    fs.writeFile(filename, content, { encoding: 'utf-8' }, handler)\n});\n\nconst extractLocalExports = (content) => {\n  let localExports = content.split('exports.locals')[1];\n  if (!localExports) {\n    localExports = content.split('___CSS_LOADER_EXPORT___.locals')[1];\n  }\n  return localExports;\n}\n\nmodule.exports = function(content, ...rest) {\n  const { failed, success } = makeDoneHandlers(this.async(), content, rest);\n\n  const filename = this.resourcePath;\n  const { mode = 'emit' } = loaderUtils.getOptions(this) || {};\n  if (!validModes.includes(mode)) {\n    return failed(new Error(`Invalid mode option: ${mode}`));\n  }\n\n  const cssModuleInterfaceFilename = filenameToTypingsFilename(filename);\n  const { read, write } = makeFileHandlers(cssModuleInterfaceFilename);\n\n  const keyRegex = /\"([^\\\\\"]+)\":/g;\n  let match;\n  const cssModuleKeys = [];\n\n  const localExports = extractLocalExports(content);\n\n  while ((match = keyRegex.exec(localExports))) {\n    if (cssModuleKeys.indexOf(match[1]) < 0) {\n      cssModuleKeys.push(match[1]);\n    }\n  }\n\n  const cssModuleDefinition = `${bannerMessage}\\n${cssModuleToInterface(cssModuleKeys)}\\n${cssModuleExport}`;\n\n  if (mode === 'verify') {\n    read((err, fileContents) => {\n      if (isFileNotFound(err)) {\n        return failed(\n          getNoDeclarationFileError({\n            filename: cssModuleInterfaceFilename\n          })\n        );\n      }\n\n      if (err) {\n        return failed(err);\n      }\n\n      if (!compareText(cssModuleDefinition, fileContents)) {\n        return failed(\n          getTypeMismatchError({\n            filename: cssModuleInterfaceFilename,\n            expected: cssModuleDefinition,\n            actual: fileContents\n          })\n        );\n      }\n\n      return success();\n    });\n  } else {\n    read((_, fileContents) => {\n      if (!compareText(cssModuleDefinition, fileContents)) {\n        write(cssModuleDefinition, err => {\n          if (err) {\n            failed(err);\n          } else {\n            success();\n          }\n        });\n      } else {\n        success();\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"css-modules-typescript-loader\",\n  \"version\": \"0.0.0-development\",\n  \"description\": \"Webpack loader to create TypeScript declarations for CSS Modules\",\n  \"main\": \"index.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/seek-oss/css-modules-typescript-loader.git\"\n  },\n  \"author\": \"SEEK\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/seek-oss/css-modules-typescript-loader/issues\"\n  },\n  \"scripts\": {\n    \"commit\": \"git-cz\",\n    \"travis-deploy-once\": \"travis-deploy-once\",\n    \"semantic-release\": \"semantic-release\",\n    \"test\": \"jest\"\n  },\n  \"jest\": {\n    \"testEnvironment\": \"node\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"commit-msg\": \"commitlint --edit --extends seek\"\n    }\n  },\n  \"homepage\": \"https://github.com/seek-oss/css-modules-typescript-loader#readme\",\n  \"dependencies\": {\n    \"line-diff\": \"^2.0.1\",\n    \"loader-utils\": \"^1.2.3\"\n  },\n  \"devDependencies\": {\n    \"@commitlint/cli\": \"^7.2.1\",\n    \"commitizen\": \"^3.0.2\",\n    \"commitlint-config-seek\": \"^1.0.0\",\n    \"css-loader\": \"^4.2.1\",\n    \"cz-conventional-changelog\": \"^2.1.0\",\n    \"husky\": \"^1.1.2\",\n    \"jest\": \"^24.7.1\",\n    \"memory-fs\": \"^0.4.1\",\n    \"semantic-release\": \"^15.9.17\",\n    \"travis-deploy-once\": \"^5.0.9\",\n    \"webpack\": \"^4.21.0\"\n  },\n  \"release\": {\n    \"success\": false\n  },\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"./node_modules/cz-conventional-changelog\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/compiler.js",
    "content": "const path = require('path');\nconst webpack = require('webpack');\nconst memoryfs = require('memory-fs');\n\nmodule.exports = (entry, options = {}) => {\n  const { sourceMap, ...loaderOptions } = options;\n  const compiler = webpack({\n    context: path.dirname(entry),\n    entry,\n    output: {\n      path: path.dirname(entry),\n      filename: 'bundle.js'\n    },\n    module: {\n      rules: [\n        {\n          test: /\\.css$/,\n          use: [\n            {\n              loader: require.resolve('../index.js'),\n              options: loaderOptions\n            },\n            {\n              loader: 'css-loader',\n              options: {\n                modules: true,\n                sourceMap: !!sourceMap\n              }\n            }\n          ]\n        }\n      ]\n    }\n  });\n\n  compiler.outputFileSystem = new memoryfs();\n\n  return new Promise((resolve, reject) => {\n    compiler.run((err, stats) => {\n      if (err || stats.hasErrors()) {\n        reject({\n          failed: true,\n          errors: err || stats.toJson().errors\n        });\n      }\n\n      resolve(stats);\n    });\n  });\n};\n"
  },
  {
    "path": "test/emit-declaration/.gitignore",
    "content": "index.css.d.ts\n"
  },
  {
    "path": "test/emit-declaration/__snapshots__/emit-declaration.test.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Can emit valid declaration with sourceMap 1`] = `\n\"// This file is automatically generated.\n// Please do not change this file!\ninterface CssExports {\n  'composedClass': string;\n  'otherClass': string;\n  'someClass': string;\n  'validClass': string;\n}\nexport const cssExports: CssExports;\nexport default cssExports;\n\"\n`;\n\nexports[`Can emit valid declaration without sourceMaps 1`] = `\n\"// This file is automatically generated.\n// Please do not change this file!\ninterface CssExports {\n  'composedClass': string;\n  'otherClass': string;\n  'someClass': string;\n  'validClass': string;\n}\nexport const cssExports: CssExports;\nexport default cssExports;\n\"\n`;\n"
  },
  {
    "path": "test/emit-declaration/common.css",
    "content": ".baseClass {\n  position: relative;\n}\n"
  },
  {
    "path": "test/emit-declaration/emit-declaration.test.js",
    "content": "const fs = require('fs');\nconst compiler = require('../compiler.js');\n\ntest('Can emit valid declaration without sourceMaps', async () => {\n  await compiler(require.resolve('./index.js'));\n\n  const declaration = fs.readFileSync(\n    require.resolve('./index.css.d.ts'),\n    'utf-8'\n  );\n\n  expect(declaration).toMatchSnapshot();\n});\n\ntest('Can emit valid declaration with sourceMap', async () => {\n  await compiler(require.resolve('./index.js'), { sourceMap: true });\n\n  const declaration = fs.readFileSync(\n    require.resolve('./index.css.d.ts'),\n    'utf-8'\n  );\n\n  expect(declaration).toMatchSnapshot();\n});\n\n"
  },
  {
    "path": "test/emit-declaration/index.css",
    "content": ".validClass {\n  position: relative;\n}\n\n.someClass {\n  position: relative;\n}\n\n.otherClass {\n  display: block;\n}\n\n.composedClass {\n  composes: baseClass from './common.css';\n  color: cornflowerblue;\n}\n"
  },
  {
    "path": "test/emit-declaration/index.js",
    "content": "import styles from './index.css';\n"
  },
  {
    "path": "test/emit-empty-declaration/.gitignore",
    "content": "index.css.d.ts\n"
  },
  {
    "path": "test/emit-empty-declaration/__snapshots__/emit-empty-declaration.test.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Can emit valid declaration without classes 1`] = `\n\"// This file is automatically generated.\n// Please do not change this file!\ninterface CssExports {\n\n}\nexport const cssExports: CssExports;\nexport default cssExports;\n\"\n`;\n"
  },
  {
    "path": "test/emit-empty-declaration/emit-empty-declaration.test.js",
    "content": "const fs = require('fs');\nconst compiler = require('../compiler.js');\n\ntest('Can emit valid declaration without classes', async () => {\n  await compiler(require.resolve('./index.js'));\n  \n  const declaration = fs.readFileSync(\n    require.resolve('./index.css.d.ts'),\n    'utf-8'\n  );\n\n  expect(declaration).toMatchSnapshot();\n});\n"
  },
  {
    "path": "test/emit-empty-declaration/index.css",
    "content": "html {\n  /* No class selectors in this file ... */\n}\n"
  },
  {
    "path": "test/emit-empty-declaration/index.js",
    "content": "import \"./index.css\";\n"
  },
  {
    "path": "test/getErrorMessage.js",
    "content": "module.exports = error => error\n  .split(process.cwd())\n  .join('')\n  .match(/(Error: .*?)\\s{4}at /s)[1];\n"
  },
  {
    "path": "test/verify-invalid-declaration/__snapshots__/verify-invalid-declaration.test.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Can error on invalid declaration 1`] = `\n\"Error: Generated type declaration file is outdated. Run webpack and commit the updated type declaration for '/test/verify-invalid-declaration/index.css.d.ts'\n\n   // This file is automatically generated.\n   // Please do not change this file!\n   interface CssExports {\n     'classInBothFiles': string;\n -   'classInTypeScriptFile': string;\n +   'classInCssFile': string;\n   }\n   export const cssExports: CssExports;\n   export default cssExports;\n   \n\n\"\n`;\n"
  },
  {
    "path": "test/verify-invalid-declaration/index.css",
    "content": ".classInBothFiles {\n  position: relative;\n}\n\n.classInCssFile {\n  display: block;\n}\n"
  },
  {
    "path": "test/verify-invalid-declaration/index.css.d.ts",
    "content": "// This file is automatically generated.\n// Please do not change this file!\ninterface CssExports {\n  'classInBothFiles': string;\n  'classInTypeScriptFile': string;\n}\nexport const cssExports: CssExports;\nexport default cssExports;\n"
  },
  {
    "path": "test/verify-invalid-declaration/index.js",
    "content": "import styles from './index.css';\n"
  },
  {
    "path": "test/verify-invalid-declaration/verify-invalid-declaration.test.js",
    "content": "const compiler = require('../compiler.js');\nconst getErrorMessage = require('../getErrorMessage');\n\ntest('Can error on invalid declaration', async () => {\n  expect.assertions(1);\n\n  try {\n    await compiler(require.resolve('./index.js'), {\n      mode: 'verify'\n    });\n  } catch (err) {\n    // make test robust for Windows by replacing backslashes in the file path with slashes\n    let errorMessage = getErrorMessage(err.errors[0]).replace(/(?<=Error:.*)\\\\/g, \"/\");\n\n    expect(errorMessage).toMatchSnapshot();\n  }\n});\n"
  },
  {
    "path": "test/verify-missing-declaration/__snapshots__/verify-missing-declaration.test.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Can error on invalid declaration 1`] = `\n\"Error: Generated type declaration does not exist. Run webpack and commit the type declaration for '/test/verify-missing-declaration/index.css.d.ts'\n\"\n`;\n"
  },
  {
    "path": "test/verify-missing-declaration/index.css",
    "content": ".someClass {\n  position: relative;\n}\n\n.otherClass {\n  display: block;\n}\n"
  },
  {
    "path": "test/verify-missing-declaration/index.js",
    "content": "import styles from './index.css';\n"
  },
  {
    "path": "test/verify-missing-declaration/verify-missing-declaration.test.js",
    "content": "const compiler = require('../compiler.js');\nconst getErrorMessage = require('../getErrorMessage');\n\ntest('Can error on invalid declaration', async () => {\n  expect.assertions(1);\n\n  try {\n    await compiler(require.resolve('./index.js'), {\n      mode: 'verify'\n    });\n  } catch (err) {\n    // make test robust for Windows by replacing backslashes in the file path with slashes\n    let errorMessage = getErrorMessage(err.errors[0]).replace(/(?<=Error:.*)\\\\/g, \"/\");\n\n    expect(errorMessage).toMatchSnapshot();\n  }\n});\n"
  },
  {
    "path": "test/verify-valid-declaration/index.css",
    "content": ".validClass {\n  position: relative;\n}\n\n.someClass {\n  position: relative;\n}\n\n.otherClass {\n  display: block;\n}\n\n.hyphened-classname,\n.underscored_classname {\n  color: papayawhip;\n}\n"
  },
  {
    "path": "test/verify-valid-declaration/index.css.d.ts",
    "content": "// This file is automatically generated.\n// Please do not change this file!\ninterface CssExports {\n  'hyphened-classname': string;\n  'otherClass': string;\n  'someClass': string;\n  'underscored_classname': string;\n  'validClass': string;\n}\nexport const cssExports: CssExports;\nexport default cssExports;\n"
  },
  {
    "path": "test/verify-valid-declaration/index.js",
    "content": "import styles from './index.css';\n"
  },
  {
    "path": "test/verify-valid-declaration/verify-valid-declaration.test.js",
    "content": "const compiler = require('../compiler.js');\n\ntest('Can verify valid declaration', async () => {\n  // just validate webpack build passes\n  await compiler(require.resolve('./index.js'), {\n    mode: 'verify'\n  });\n});\n"
  }
]