We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
modifyable api_page_default_limit variable on the admin page
<!-- Thank you for contributing. These HTML comments will not render in the issue, but you can delete them once you've read them if you prefer! -->

### Proposed change

It is rather annoying that the number of the listed servers (`api_page_default_limit`?) cannot be changed on the admin page,
but only use the number fixed in `jupyterhub_config.py`.

It would help the administration of jupyter HUB very much if this number could be changed on the admin page.

### Alternative options
<!-- Use this section to describe alternative options and why you've decided on the proposed feature above. -->

The variable `api_page_default_limit` can be changed in `jupyterhub_config.py`, however, this requires restarting the server.

### Who would use this feature?
<!-- Describe who would benefit from using this feature. -->

Most of the users with _admin_ access.

### (Optional): Suggest a solution
<!-- Describe what you think needs to be done. Doing that is an excellent first step to get the feature implemented. -->

Completing the admin page template(?) with a choice box (50|100|200|500) or input box.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `setup.py`
Content:
```
1 #!/usr/bin/env python3
2 # Copyright (c) Jupyter Development Team.
3 # Distributed under the terms of the Modified BSD License.
4 
5 import os
6 import shutil
7 import sys
8 from subprocess import check_call
9 
10 from setuptools import Command, setup
11 from setuptools.command.bdist_egg import bdist_egg
12 from setuptools.command.build_py import build_py
13 from setuptools.command.develop import develop
14 from setuptools.command.sdist import sdist
15 
16 shell = False
17 if os.name in ('nt', 'dos'):
18     shell = True
19     warning = "WARNING: Windows is not officially supported"
20     print(warning, file=sys.stderr)
21 
22 
23 pjoin = os.path.join
24 
25 here = os.path.abspath(os.path.dirname(__file__))
26 share_jupyterhub = pjoin(here, 'share', 'jupyterhub')
27 static = pjoin(share_jupyterhub, 'static')
28 
29 is_repo = os.path.exists(pjoin(here, '.git'))
30 
31 # Build basic package data, etc.
32 
33 
34 def get_data_files():
35     """Get data files in share/jupyter"""
36 
37     data_files = []
38     for d, dirs, filenames in os.walk(share_jupyterhub):
39         rel_d = os.path.relpath(d, here)
40         data_files.append((rel_d, [os.path.join(rel_d, f) for f in filenames]))
41     return data_files
42 
43 
44 def mtime(path):
45     """shorthand for mtime"""
46     return os.stat(path).st_mtime
47 
48 
49 def recursive_mtime(path):
50     """Recursively get newest mtime of files"""
51     if os.path.isfile(path):
52         return mtime(path)
53     current = 0
54     for dirname, _, filenames in os.walk(path):
55         if filenames:
56             current = max(
57                 current, max(mtime(os.path.join(dirname, f)) for f in filenames)
58             )
59     return current
60 
61 
62 class BaseCommand(Command):
63     """Dumb empty command because Command needs subclasses to override too much"""
64 
65     user_options = []
66 
67     def initialize_options(self):
68         pass
69 
70     def finalize_options(self):
71         pass
72 
73     def get_inputs(self):
74         return []
75 
76     def get_outputs(self):
77         return []
78 
79 
80 class NPM(BaseCommand):
81     description = "fetch static client-side components with bower"
82 
83     user_options = []
84     node_modules = pjoin(here, 'node_modules')
85     bower_dir = pjoin(static, 'components')
86 
87     def should_run(self):
88         if not os.path.exists(self.bower_dir):
89             return True
90         if not os.path.exists(self.node_modules):
91             return True
92         if mtime(self.bower_dir) < mtime(self.node_modules):
93             return True
94         return mtime(self.node_modules) < mtime(pjoin(here, 'package.json'))
95 
96     def run(self):
97         if not self.should_run():
98             print("npm dependencies up to date")
99             return
100 
101         print("installing js dependencies with npm")
102         check_call(
103             ['npm', 'install', '--progress=false', '--unsafe-perm'],
104             cwd=here,
105             shell=shell,
106         )
107         os.utime(self.node_modules)
108 
109         os.utime(self.bower_dir)
110         # update data-files in case this created new files
111         self.distribution.data_files = get_data_files()
112         assert not self.should_run(), 'NPM.run failed'
113 
114 
115 class CSS(BaseCommand):
116     description = "compile CSS from LESS"
117 
118     def should_run(self):
119         """Does less need to run?"""
120         # from IPython.html.tasks.py
121 
122         css_targets = [pjoin(static, 'css', 'style.min.css')]
123         css_maps = [t + '.map' for t in css_targets]
124         targets = css_targets + css_maps
125         if not all(os.path.exists(t) for t in targets):
126             # some generated files don't exist
127             return True
128         earliest_target = sorted(mtime(t) for t in targets)[0]
129 
130         # check if any .less files are newer than the generated targets
131         for dirpath, dirnames, filenames in os.walk(static):
132             for f in filenames:
133                 if f.endswith('.less'):
134                     path = pjoin(static, dirpath, f)
135                     timestamp = mtime(path)
136                     if timestamp > earliest_target:
137                         return True
138 
139         return False
140 
141     def run(self):
142         if not self.should_run():
143             print("CSS up-to-date")
144             return
145 
146         self.run_command('js')
147         print("Building css with less")
148 
149         style_less = pjoin(static, 'less', 'style.less')
150         style_css = pjoin(static, 'css', 'style.min.css')
151         sourcemap = style_css + '.map'
152 
153         args = [
154             'npm',
155             'run',
156             'lessc',
157             '--',
158             '--clean-css',
159             f'--source-map-basepath={static}',
160             f'--source-map={sourcemap}',
161             '--source-map-rootpath=../',
162             style_less,
163             style_css,
164         ]
165         try:
166             check_call(args, cwd=here, shell=shell)
167         except OSError as e:
168             print("Failed to run lessc: %s" % e, file=sys.stderr)
169             print("You can install js dependencies with `npm install`", file=sys.stderr)
170             raise
171         # update data-files in case this created new files
172         self.distribution.data_files = get_data_files()
173         assert not self.should_run(), 'CSS.run failed'
174 
175 
176 class JSX(BaseCommand):
177     description = "build admin app"
178 
179     jsx_dir = pjoin(here, 'jsx')
180     js_target = pjoin(static, 'js', 'admin-react.js')
181 
182     def should_run(self):
183         if os.getenv('READTHEDOCS'):
184             # yarn not available on RTD
185             return False
186 
187         if not os.path.exists(self.js_target):
188             return True
189 
190         js_target_mtime = mtime(self.js_target)
191         jsx_mtime = recursive_mtime(self.jsx_dir)
192         if js_target_mtime < jsx_mtime:
193             return True
194         return False
195 
196     def run(self):
197         if not self.should_run():
198             print("JSX admin app is up to date")
199             return
200 
201         if not shutil.which('npm'):
202             raise Exception('JSX needs to be updated but npm is not installed')
203 
204         print("Installing JSX admin app requirements")
205         check_call(
206             ['npm', 'install', '--progress=false', '--unsafe-perm'],
207             cwd=self.jsx_dir,
208             shell=shell,
209         )
210 
211         print("Building JSX admin app")
212         check_call(
213             ["npm", "run", "build"],
214             cwd=self.jsx_dir,
215             shell=shell,
216         )
217 
218         print("Copying JSX admin app to static/js")
219         check_call(
220             ["npm", "run", "place"],
221             cwd=self.jsx_dir,
222             shell=shell,
223         )
224 
225         # update data-files in case this created new files
226         self.distribution.data_files = get_data_files()
227         assert not self.should_run(), 'JSX.run failed'
228 
229 
230 def js_css_first(cls, strict=True):
231     class Command(cls):
232         def run(self):
233             try:
234                 self.run_command('js')
235                 self.run_command('css')
236                 self.run_command('jsx')
237             except Exception:
238                 if strict:
239                     raise
240                 else:
241                     pass
242             return super().run()
243 
244     return Command
245 
246 
247 class bdist_egg_disabled(bdist_egg):
248     """Disabled version of bdist_egg
249 
250     Prevents setup.py install from performing setuptools' default easy_install,
251     which it should never ever do.
252     """
253 
254     def run(self):
255         sys.exit(
256             "Aborting implicit building of eggs. Use `pip install .` to install from source."
257         )
258 
259 
260 class develop_js_css(develop):
261     def run(self):
262         if not self.uninstall:
263             self.distribution.run_command('js')
264             self.distribution.run_command('css')
265         super().run()
266 
267 
268 cmdclass = {
269     'js': NPM,
270     'css': CSS,
271     'jsx': JSX,
272     'build_py': js_css_first(build_py, strict=is_repo),
273     'sdist': js_css_first(sdist, strict=True),
274     'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled,
275     'develop': develop_js_css,
276 }
277 
278 # run setup
279 
280 
281 def main():
282     setup(
283         cmdclass=cmdclass,
284         data_files=get_data_files(),
285     )
286 
287 
288 if __name__ == '__main__':
289     main()
290
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -215,13 +215,6 @@
             shell=shell,
         )
 
-        print("Copying JSX admin app to static/js")
-        check_call(
-            ["npm", "run", "place"],
-            cwd=self.jsx_dir,
-            shell=shell,
-        )
-
         # update data-files in case this created new files
         self.distribution.data_files = get_data_files()
         assert not self.should_run(), 'JSX.run failed'

{"golden_diff": "diff --git a/setup.py b/setup.py\n--- a/setup.py\n+++ b/setup.py\n@@ -215,13 +215,6 @@\n             shell=shell,\n         )\n \n-        print(\"Copying JSX admin app to static/js\")\n-        check_call(\n-            [\"npm\", \"run\", \"place\"],\n-            cwd=self.jsx_dir,\n-            shell=shell,\n-        )\n-\n         # update data-files in case this created new files\n         self.distribution.data_files = get_data_files()\n         assert not self.should_run(), 'JSX.run failed'\n", "issue": "modifyable api_page_default_limit variable on the admin page\n<!-- Thank you for contributing. These HTML comments will not render in the issue, but you can delete them once you've read them if you prefer! -->\r\n\r\n### Proposed change\r\n\r\nIt is rather annoying that the number of the listed servers (`api_page_default_limit`?) cannot be changed on the admin page,\r\nbut only use the number fixed in `jupyterhub_config.py`.\r\n\r\nIt would help the administration of jupyter HUB very much if this number could be changed on the admin page.\r\n\r\n### Alternative options\r\n<!-- Use this section to describe alternative options and why you've decided on the proposed feature above. -->\r\n\r\nThe variable `api_page_default_limit` can be changed in `jupyterhub_config.py`, however, this requires restarting the server.\r\n\r\n### Who would use this feature?\r\n<!-- Describe who would benefit from using this feature. -->\r\n\r\nMost of the users with _admin_ access.\r\n\r\n### (Optional): Suggest a solution\r\n<!-- Describe what you think needs to be done. Doing that is an excellent first step to get the feature implemented. -->\r\n\r\nCompleting the admin page template(?) with a choice box (50|100|200|500) or input box.\n", "before_files": [{"content": "#!/usr/bin/env python3\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nimport os\nimport shutil\nimport sys\nfrom subprocess import check_call\n\nfrom setuptools import Command, setup\nfrom setuptools.command.bdist_egg import bdist_egg\nfrom setuptools.command.build_py import build_py\nfrom setuptools.command.develop import develop\nfrom setuptools.command.sdist import sdist\n\nshell = False\nif os.name in ('nt', 'dos'):\n    shell = True\n    warning = \"WARNING: Windows is not officially supported\"\n    print(warning, file=sys.stderr)\n\n\npjoin = os.path.join\n\nhere = os.path.abspath(os.path.dirname(__file__))\nshare_jupyterhub = pjoin(here, 'share', 'jupyterhub')\nstatic = pjoin(share_jupyterhub, 'static')\n\nis_repo = os.path.exists(pjoin(here, '.git'))\n\n# Build basic package data, etc.\n\n\ndef get_data_files():\n    \"\"\"Get data files in share/jupyter\"\"\"\n\n    data_files = []\n    for d, dirs, filenames in os.walk(share_jupyterhub):\n        rel_d = os.path.relpath(d, here)\n        data_files.append((rel_d, [os.path.join(rel_d, f) for f in filenames]))\n    return data_files\n\n\ndef mtime(path):\n    \"\"\"shorthand for mtime\"\"\"\n    return os.stat(path).st_mtime\n\n\ndef recursive_mtime(path):\n    \"\"\"Recursively get newest mtime of files\"\"\"\n    if os.path.isfile(path):\n        return mtime(path)\n    current = 0\n    for dirname, _, filenames in os.walk(path):\n        if filenames:\n            current = max(\n                current, max(mtime(os.path.join(dirname, f)) for f in filenames)\n            )\n    return current\n\n\nclass BaseCommand(Command):\n    \"\"\"Dumb empty command because Command needs subclasses to override too much\"\"\"\n\n    user_options = []\n\n    def initialize_options(self):\n        pass\n\n    def finalize_options(self):\n        pass\n\n    def get_inputs(self):\n        return []\n\n    def get_outputs(self):\n        return []\n\n\nclass NPM(BaseCommand):\n    description = \"fetch static client-side components with bower\"\n\n    user_options = []\n    node_modules = pjoin(here, 'node_modules')\n    bower_dir = pjoin(static, 'components')\n\n    def should_run(self):\n        if not os.path.exists(self.bower_dir):\n            return True\n        if not os.path.exists(self.node_modules):\n            return True\n        if mtime(self.bower_dir) < mtime(self.node_modules):\n            return True\n        return mtime(self.node_modules) < mtime(pjoin(here, 'package.json'))\n\n    def run(self):\n        if not self.should_run():\n            print(\"npm dependencies up to date\")\n            return\n\n        print(\"installing js dependencies with npm\")\n        check_call(\n            ['npm', 'install', '--progress=false', '--unsafe-perm'],\n            cwd=here,\n            shell=shell,\n        )\n        os.utime(self.node_modules)\n\n        os.utime(self.bower_dir)\n        # update data-files in case this created new files\n        self.distribution.data_files = get_data_files()\n        assert not self.should_run(), 'NPM.run failed'\n\n\nclass CSS(BaseCommand):\n    description = \"compile CSS from LESS\"\n\n    def should_run(self):\n        \"\"\"Does less need to run?\"\"\"\n        # from IPython.html.tasks.py\n\n        css_targets = [pjoin(static, 'css', 'style.min.css')]\n        css_maps = [t + '.map' for t in css_targets]\n        targets = css_targets + css_maps\n        if not all(os.path.exists(t) for t in targets):\n            # some generated files don't exist\n            return True\n        earliest_target = sorted(mtime(t) for t in targets)[0]\n\n        # check if any .less files are newer than the generated targets\n        for dirpath, dirnames, filenames in os.walk(static):\n            for f in filenames:\n                if f.endswith('.less'):\n                    path = pjoin(static, dirpath, f)\n                    timestamp = mtime(path)\n                    if timestamp > earliest_target:\n                        return True\n\n        return False\n\n    def run(self):\n        if not self.should_run():\n            print(\"CSS up-to-date\")\n            return\n\n        self.run_command('js')\n        print(\"Building css with less\")\n\n        style_less = pjoin(static, 'less', 'style.less')\n        style_css = pjoin(static, 'css', 'style.min.css')\n        sourcemap = style_css + '.map'\n\n        args = [\n            'npm',\n            'run',\n            'lessc',\n            '--',\n            '--clean-css',\n            f'--source-map-basepath={static}',\n            f'--source-map={sourcemap}',\n            '--source-map-rootpath=../',\n            style_less,\n            style_css,\n        ]\n        try:\n            check_call(args, cwd=here, shell=shell)\n        except OSError as e:\n            print(\"Failed to run lessc: %s\" % e, file=sys.stderr)\n            print(\"You can install js dependencies with `npm install`\", file=sys.stderr)\n            raise\n        # update data-files in case this created new files\n        self.distribution.data_files = get_data_files()\n        assert not self.should_run(), 'CSS.run failed'\n\n\nclass JSX(BaseCommand):\n    description = \"build admin app\"\n\n    jsx_dir = pjoin(here, 'jsx')\n    js_target = pjoin(static, 'js', 'admin-react.js')\n\n    def should_run(self):\n        if os.getenv('READTHEDOCS'):\n            # yarn not available on RTD\n            return False\n\n        if not os.path.exists(self.js_target):\n            return True\n\n        js_target_mtime = mtime(self.js_target)\n        jsx_mtime = recursive_mtime(self.jsx_dir)\n        if js_target_mtime < jsx_mtime:\n            return True\n        return False\n\n    def run(self):\n        if not self.should_run():\n            print(\"JSX admin app is up to date\")\n            return\n\n        if not shutil.which('npm'):\n            raise Exception('JSX needs to be updated but npm is not installed')\n\n        print(\"Installing JSX admin app requirements\")\n        check_call(\n            ['npm', 'install', '--progress=false', '--unsafe-perm'],\n            cwd=self.jsx_dir,\n            shell=shell,\n        )\n\n        print(\"Building JSX admin app\")\n        check_call(\n            [\"npm\", \"run\", \"build\"],\n            cwd=self.jsx_dir,\n            shell=shell,\n        )\n\n        print(\"Copying JSX admin app to static/js\")\n        check_call(\n            [\"npm\", \"run\", \"place\"],\n            cwd=self.jsx_dir,\n            shell=shell,\n        )\n\n        # update data-files in case this created new files\n        self.distribution.data_files = get_data_files()\n        assert not self.should_run(), 'JSX.run failed'\n\n\ndef js_css_first(cls, strict=True):\n    class Command(cls):\n        def run(self):\n            try:\n                self.run_command('js')\n                self.run_command('css')\n                self.run_command('jsx')\n            except Exception:\n                if strict:\n                    raise\n                else:\n                    pass\n            return super().run()\n\n    return Command\n\n\nclass bdist_egg_disabled(bdist_egg):\n    \"\"\"Disabled version of bdist_egg\n\n    Prevents setup.py install from performing setuptools' default easy_install,\n    which it should never ever do.\n    \"\"\"\n\n    def run(self):\n        sys.exit(\n            \"Aborting implicit building of eggs. Use `pip install .` to install from source.\"\n        )\n\n\nclass develop_js_css(develop):\n    def run(self):\n        if not self.uninstall:\n            self.distribution.run_command('js')\n            self.distribution.run_command('css')\n        super().run()\n\n\ncmdclass = {\n    'js': NPM,\n    'css': CSS,\n    'jsx': JSX,\n    'build_py': js_css_first(build_py, strict=is_repo),\n    'sdist': js_css_first(sdist, strict=True),\n    'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled,\n    'develop': develop_js_css,\n}\n\n# run setup\n\n\ndef main():\n    setup(\n        cmdclass=cmdclass,\n        data_files=get_data_files(),\n    )\n\n\nif __name__ == '__main__':\n    main()\n", "path": "setup.py"}], "after_files": [{"content": "#!/usr/bin/env python3\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nimport os\nimport shutil\nimport sys\nfrom subprocess import check_call\n\nfrom setuptools import Command, setup\nfrom setuptools.command.bdist_egg import bdist_egg\nfrom setuptools.command.build_py import build_py\nfrom setuptools.command.develop import develop\nfrom setuptools.command.sdist import sdist\n\nshell = False\nif os.name in ('nt', 'dos'):\n    shell = True\n    warning = \"WARNING: Windows is not officially supported\"\n    print(warning, file=sys.stderr)\n\n\npjoin = os.path.join\n\nhere = os.path.abspath(os.path.dirname(__file__))\nshare_jupyterhub = pjoin(here, 'share', 'jupyterhub')\nstatic = pjoin(share_jupyterhub, 'static')\n\nis_repo = os.path.exists(pjoin(here, '.git'))\n\n# Build basic package data, etc.\n\n\ndef get_data_files():\n    \"\"\"Get data files in share/jupyter\"\"\"\n\n    data_files = []\n    for d, dirs, filenames in os.walk(share_jupyterhub):\n        rel_d = os.path.relpath(d, here)\n        data_files.append((rel_d, [os.path.join(rel_d, f) for f in filenames]))\n    return data_files\n\n\ndef mtime(path):\n    \"\"\"shorthand for mtime\"\"\"\n    return os.stat(path).st_mtime\n\n\ndef recursive_mtime(path):\n    \"\"\"Recursively get newest mtime of files\"\"\"\n    if os.path.isfile(path):\n        return mtime(path)\n    current = 0\n    for dirname, _, filenames in os.walk(path):\n        if filenames:\n            current = max(\n                current, max(mtime(os.path.join(dirname, f)) for f in filenames)\n            )\n    return current\n\n\nclass BaseCommand(Command):\n    \"\"\"Dumb empty command because Command needs subclasses to override too much\"\"\"\n\n    user_options = []\n\n    def initialize_options(self):\n        pass\n\n    def finalize_options(self):\n        pass\n\n    def get_inputs(self):\n        return []\n\n    def get_outputs(self):\n        return []\n\n\nclass NPM(BaseCommand):\n    description = \"fetch static client-side components with bower\"\n\n    user_options = []\n    node_modules = pjoin(here, 'node_modules')\n    bower_dir = pjoin(static, 'components')\n\n    def should_run(self):\n        if not os.path.exists(self.bower_dir):\n            return True\n        if not os.path.exists(self.node_modules):\n            return True\n        if mtime(self.bower_dir) < mtime(self.node_modules):\n            return True\n        return mtime(self.node_modules) < mtime(pjoin(here, 'package.json'))\n\n    def run(self):\n        if not self.should_run():\n            print(\"npm dependencies up to date\")\n            return\n\n        print(\"installing js dependencies with npm\")\n        check_call(\n            ['npm', 'install', '--progress=false', '--unsafe-perm'],\n            cwd=here,\n            shell=shell,\n        )\n        os.utime(self.node_modules)\n\n        os.utime(self.bower_dir)\n        # update data-files in case this created new files\n        self.distribution.data_files = get_data_files()\n        assert not self.should_run(), 'NPM.run failed'\n\n\nclass CSS(BaseCommand):\n    description = \"compile CSS from LESS\"\n\n    def should_run(self):\n        \"\"\"Does less need to run?\"\"\"\n        # from IPython.html.tasks.py\n\n        css_targets = [pjoin(static, 'css', 'style.min.css')]\n        css_maps = [t + '.map' for t in css_targets]\n        targets = css_targets + css_maps\n        if not all(os.path.exists(t) for t in targets):\n            # some generated files don't exist\n            return True\n        earliest_target = sorted(mtime(t) for t in targets)[0]\n\n        # check if any .less files are newer than the generated targets\n        for dirpath, dirnames, filenames in os.walk(static):\n            for f in filenames:\n                if f.endswith('.less'):\n                    path = pjoin(static, dirpath, f)\n                    timestamp = mtime(path)\n                    if timestamp > earliest_target:\n                        return True\n\n        return False\n\n    def run(self):\n        if not self.should_run():\n            print(\"CSS up-to-date\")\n            return\n\n        self.run_command('js')\n        print(\"Building css with less\")\n\n        style_less = pjoin(static, 'less', 'style.less')\n        style_css = pjoin(static, 'css', 'style.min.css')\n        sourcemap = style_css + '.map'\n\n        args = [\n            'npm',\n            'run',\n            'lessc',\n            '--',\n            '--clean-css',\n            f'--source-map-basepath={static}',\n            f'--source-map={sourcemap}',\n            '--source-map-rootpath=../',\n            style_less,\n            style_css,\n        ]\n        try:\n            check_call(args, cwd=here, shell=shell)\n        except OSError as e:\n            print(\"Failed to run lessc: %s\" % e, file=sys.stderr)\n            print(\"You can install js dependencies with `npm install`\", file=sys.stderr)\n            raise\n        # update data-files in case this created new files\n        self.distribution.data_files = get_data_files()\n        assert not self.should_run(), 'CSS.run failed'\n\n\nclass JSX(BaseCommand):\n    description = \"build admin app\"\n\n    jsx_dir = pjoin(here, 'jsx')\n    js_target = pjoin(static, 'js', 'admin-react.js')\n\n    def should_run(self):\n        if os.getenv('READTHEDOCS'):\n            # yarn not available on RTD\n            return False\n\n        if not os.path.exists(self.js_target):\n            return True\n\n        js_target_mtime = mtime(self.js_target)\n        jsx_mtime = recursive_mtime(self.jsx_dir)\n        if js_target_mtime < jsx_mtime:\n            return True\n        return False\n\n    def run(self):\n        if not self.should_run():\n            print(\"JSX admin app is up to date\")\n            return\n\n        if not shutil.which('npm'):\n            raise Exception('JSX needs to be updated but npm is not installed')\n\n        print(\"Installing JSX admin app requirements\")\n        check_call(\n            ['npm', 'install', '--progress=false', '--unsafe-perm'],\n            cwd=self.jsx_dir,\n            shell=shell,\n        )\n\n        print(\"Building JSX admin app\")\n        check_call(\n            [\"npm\", \"run\", \"build\"],\n            cwd=self.jsx_dir,\n            shell=shell,\n        )\n\n        # update data-files in case this created new files\n        self.distribution.data_files = get_data_files()\n        assert not self.should_run(), 'JSX.run failed'\n\n\ndef js_css_first(cls, strict=True):\n    class Command(cls):\n        def run(self):\n            try:\n                self.run_command('js')\n                self.run_command('css')\n                self.run_command('jsx')\n            except Exception:\n                if strict:\n                    raise\n                else:\n                    pass\n            return super().run()\n\n    return Command\n\n\nclass bdist_egg_disabled(bdist_egg):\n    \"\"\"Disabled version of bdist_egg\n\n    Prevents setup.py install from performing setuptools' default easy_install,\n    which it should never ever do.\n    \"\"\"\n\n    def run(self):\n        sys.exit(\n            \"Aborting implicit building of eggs. Use `pip install .` to install from source.\"\n        )\n\n\nclass develop_js_css(develop):\n    def run(self):\n        if not self.uninstall:\n            self.distribution.run_command('js')\n            self.distribution.run_command('css')\n        super().run()\n\n\ncmdclass = {\n    'js': NPM,\n    'css': CSS,\n    'jsx': JSX,\n    'build_py': js_css_first(build_py, strict=is_repo),\n    'sdist': js_css_first(sdist, strict=True),\n    'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled,\n    'develop': develop_js_css,\n}\n\n# run setup\n\n\ndef main():\n    setup(\n        cmdclass=cmdclass,\n        data_files=get_data_files(),\n    )\n\n\nif __name__ == '__main__':\n    main()\n", "path": "setup.py"}]}