Skip to content

Commit 7614c1b

Browse files
authored
Patch to fully support @jupyterlab/builder (#98)
* patch-to-support-jupyterlab/builder' --no-verify * more-patching * lint * add-backward-compatible-changes * split-test-to-test-both-packages
1 parent fef16b9 commit 7614c1b

3 files changed

Lines changed: 123 additions & 23 deletions

File tree

jupyter_builder/federated_extensions.py

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -228,15 +228,14 @@ def build_labextension( # noqa: PLR0913
228228
if logger:
229229
logger.info("Building extension in %s", path)
230230

231-
builder = _ensure_builder(ext_path, core_package_file)
232-
233-
arguments = [
234-
"node",
235-
builder,
236-
"--core-package-file",
237-
core_package_file,
238-
ext_path,
239-
]
231+
builder, marker_pkg = _ensure_builder(ext_path, core_package_file)
232+
233+
if marker_pkg == "@jupyterlab/builder":
234+
core_flag = ["--core-path", _resolve_core_path_for_jupyterlab_builder(core_package_file)]
235+
else:
236+
core_flag = ["--core-package-file", core_package_file]
237+
238+
arguments = ["node", builder, *core_flag, ext_path]
240239
if static_url is not None:
241240
arguments.extend(["--static-url", static_url])
242241
if development:
@@ -279,15 +278,14 @@ def watch_labextension( # noqa: PLR0913
279278
shutil.rmtree(full_dest)
280279
os.symlink(output_dir, full_dest)
281280

282-
builder = _ensure_builder(ext_path, core_package_file)
283-
arguments = [
284-
"node",
285-
builder,
286-
"--core-package-file",
287-
core_package_file,
288-
"--watch",
289-
ext_path,
290-
]
281+
builder, marker_pkg = _ensure_builder(ext_path, core_package_file)
282+
283+
if marker_pkg == "@jupyterlab/builder":
284+
core_flag = ["--core-path", _resolve_core_path_for_jupyterlab_builder(core_package_file)]
285+
else:
286+
core_flag = ["--core-package-file", core_package_file]
287+
288+
arguments = ["node", builder, *core_flag, "--watch", ext_path]
291289
if development:
292290
arguments.append("--development")
293291
if source_map:
@@ -306,6 +304,21 @@ def watch_labextension( # noqa: PLR0913
306304
_BUILDER_MARKER_CANDIDATES = ("@jupyter/builder", "@jupyterlab/builder")
307305

308306

307+
def _resolve_core_path_for_jupyterlab_builder(core_package_file):
308+
"""Return the core path directory for @jupyterlab/builder.
309+
310+
@jupyterlab/builder's downstream script expects a file named package.json
311+
inside the core path directory. If core_package_file is not named
312+
package.json, a copy named package.json is created in the same directory.
313+
"""
314+
core_dir = str(Path(core_package_file).parent)
315+
if osp.basename(core_package_file) != "package.json":
316+
target = osp.join(core_dir, "package.json")
317+
if not osp.exists(target):
318+
shutil.copy2(core_package_file, target)
319+
return core_dir
320+
321+
309322
def _select_builder_marker(ext_data):
310323
"""Return (marker_pkg, dep_spec) for the builder marker the extension declares.
311324
@@ -369,7 +382,9 @@ def _ensure_builder(ext_path, core_package_file):
369382
)
370383
raise ValueError(msg)
371384

372-
return osp.join(target, "node_modules", *marker_parts, "lib", "build-labextension.js")
385+
return osp.join(
386+
target, "node_modules", *marker_parts, "lib", "build-labextension.js"
387+
), marker_pkg
373388

374389

375390
def _should_copy(src, dest, logger=None):

tests/test_core_path.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,12 +287,31 @@ def fake_get(url, timeout):
287287
)
288288

289289

290-
def test_ensure_builder_reads_custom_core_package_file(tmp_path):
290+
def test_ensure_builder_with_jupyter_builder(tmp_path):
291+
ext_path = tmp_path / "ext"
292+
core_path_dir = tmp_path / "core-meta"
293+
core_package_file = core_path_dir / "core.package.json"
294+
builder_dir = ext_path / "node_modules" / "@jupyter" / "builder"
295+
296+
builder_dir.mkdir(parents=True)
297+
core_path_dir.mkdir()
298+
299+
core_package_file.write_text(json.dumps({"devDependencies": {"@jupyter/builder": "^5.0.0"}}))
300+
(ext_path / "package.json").write_text(
301+
json.dumps({"devDependencies": {"@jupyter/builder": "^5.0.0"}})
302+
)
303+
(builder_dir / "package.json").write_text(json.dumps({"version": "5.0.0"}))
304+
305+
builder_path, marker_pkg = _ensure_builder(str(ext_path), str(core_package_file))
306+
307+
assert builder_path == str(builder_dir / "lib" / "build-labextension.js")
308+
assert marker_pkg == "@jupyter/builder"
309+
310+
311+
def test_ensure_builder_with_jupyterlab_builder(tmp_path):
291312
ext_path = tmp_path / "ext"
292313
core_path_dir = tmp_path / "core-meta"
293314
core_package_file = core_path_dir / "core.package.json"
294-
# Exercises the backwards-compatible @jupyterlab/builder path. The returned
295-
# builder script path matches the marker the extension declares.
296315
builder_dir = ext_path / "node_modules" / "@jupyterlab" / "builder"
297316

298317
builder_dir.mkdir(parents=True)
@@ -304,6 +323,7 @@ def test_ensure_builder_reads_custom_core_package_file(tmp_path):
304323
)
305324
(builder_dir / "package.json").write_text(json.dumps({"version": "5.0.0"}))
306325

307-
builder_path = _ensure_builder(str(ext_path), str(core_package_file))
326+
builder_path, marker_pkg = _ensure_builder(str(ext_path), str(core_package_file))
308327

309328
assert builder_path == str(builder_dir / "lib" / "build-labextension.js")
329+
assert marker_pkg == "@jupyterlab/builder"

tests/test_tpl.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,27 @@ def test_files_build_development(tmp_path):
103103
assert os.path.exists(filepath), f"File {filename} does not exist in {folder_path}!"
104104

105105

106+
def test_files_build_jupyterlab_builder(tmp_path):
107+
extension_folder = tmp_path / "ext"
108+
extension_folder.mkdir()
109+
helper(str(extension_folder))
110+
111+
env = os.environ.copy()
112+
env.update({"YARN_ENABLE_IMMUTABLE_INSTALLS": "false"})
113+
run(["jlpm", "install"], cwd=extension_folder, check=True, env=env)
114+
# Intentionally do NOT add @jupyter/builder; the template already declares
115+
# @jupyterlab/builder, so this exercises the jupyterlab/builder path.
116+
run(["jlpm", "run", "build:lib:prod"], cwd=extension_folder, check=True)
117+
118+
run(["jupyter-builder", "build", str(extension_folder)], cwd=extension_folder, check=True)
119+
120+
folder_path = extension_folder / "myextension/labextension"
121+
expected_files = ["static/style.js", "package.json"]
122+
for filename in expected_files:
123+
filepath = os.path.join(folder_path, filename)
124+
assert os.path.exists(filepath), f"File {filename} does not exist in {folder_path}!"
125+
126+
106127
# --------------------------------- WATCH TESTS ---------------------------------------
107128

108129

@@ -186,6 +207,50 @@ def test_watch_functionality(tmp_path):
186207
# watch_process.wait()
187208

188209

210+
def test_watch_functionality_jupyterlab_builder(tmp_path):
211+
extension_folder = tmp_path / "ext"
212+
extension_folder.mkdir()
213+
helper(str(extension_folder))
214+
215+
env = os.environ.copy()
216+
env.update({"YARN_ENABLE_IMMUTABLE_INSTALLS": "false"})
217+
run(["jlpm", "install"], cwd=extension_folder, check=True, env=env)
218+
# Intentionally do NOT add @jupyter/builder; exercises the jupyterlab/builder path.
219+
run(["jlpm", "run", "build:lib:prod"], cwd=extension_folder, check=True)
220+
221+
index_ts_path = extension_folder / "src/index.ts"
222+
static_dir = extension_folder / "myextension/labextension/static"
223+
assert index_ts_path.exists(), f"File {index_ts_path} does not exist!"
224+
225+
initial_files = list_files_in_static(static_dir)
226+
227+
is_windows = platform.system() == "Windows"
228+
kwargs = {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP} if is_windows else {}
229+
230+
watch_process = Popen(
231+
["jupyter-builder", "watch", str(extension_folder)], cwd=extension_folder, **kwargs
232+
)
233+
234+
time.sleep(100)
235+
236+
try:
237+
with index_ts_path.open("a") as f:
238+
f.write("// Test comment to trigger watch\n")
239+
240+
time.sleep(100)
241+
242+
final_files = list_files_in_static(static_dir)
243+
assert initial_files != final_files, (
244+
"No changes detected in the static directory."
245+
" Watch process may not have triggered correctly!"
246+
)
247+
finally:
248+
watch_process.terminate()
249+
time.sleep(5)
250+
if watch_process.poll() is None:
251+
watch_process.kill()
252+
253+
189254
def test_builder_version_mismatch(tmp_path):
190255
extension_folder = tmp_path / "ext"
191256
extension_folder.mkdir()

0 commit comments

Comments
 (0)