@@ -145,66 +145,93 @@ sh_binary(
145145 "package" : name ,
146146 "path" : dep_path ,
147147 "link_packages" : {},
148+ "link_dev_packages" : {},
148149 "deps" : transitive_deps ,
149150 }
150151
151152 # Look for first-party links in importers
152153 for import_path , importer in importers .items ():
153- dependencies = importer .get ("all_deps" )
154- if type (dependencies ) != "dict" :
155- msg = "expected dict of dependencies in processed importer '{}'" .format (import_path )
156- fail (msg )
157154 link_package = helpers .link_package (root_package , import_path )
158- for dep_package , dep_version in dependencies .items ():
159- if dep_version .startswith ("file:" ):
160- dep_key = "{}+{}" .format (dep_package , dep_version )
161- if not dep_key in fp_links .keys ():
162- # Ignore file: dependencies on packages such as file: tarballs
163- # TODO(3.0): remove with pnpm <v9
164- if dep_version in packages :
165- continue
166-
167- # pnpm >=v9 where packages always have name@version
168- if "{}@{}" .format (dep_package , dep_version ) in packages :
169- continue
170-
171- msg = "Expected to file: referenced package {} in first-party links {}" .format (dep_key , fp_links .keys ())
172- fail (msg )
173- fp_links [dep_key ]["link_packages" ][link_package ] = True
174- elif dep_version .startswith ("link:" ):
175- dep_link = dep_version [len ("link:" ):]
176- dep_path = helpers .link_package (root_package , dep_link )
177- dep_key = "{}+{}" .format (dep_package , dep_path )
178- if fp_links .get (dep_key , False ):
179- fp_links [dep_key ]["link_packages" ][link_package ] = True
180- else :
181- transitive_deps = {}
182- raw_deps = {}
183- if importers .get (dep_link , False ):
184- raw_deps = importers .get (dep_link ).get ("deps" )
185- for raw_package , raw_version in raw_deps .items ():
186- package_store_name = utils .package_store_name (raw_package , raw_version )
187- dep_store_target = """"//{root_package}:{package_store_root}/{{}}/{package_store_name}".format(name)""" .format (
188- root_package = root_package ,
189- package_store_name = package_store_name ,
190- package_store_root = utils .package_store_root ,
191- )
192- if dep_store_target not in transitive_deps :
193- transitive_deps [dep_store_target ] = [raw_package ]
194- else :
195- transitive_deps [dep_store_target ].append (raw_package )
196155
197- # collapse link aliases lists into to a comma separated strings
198- for dep_store_target in transitive_deps .keys ():
199- transitive_deps [dep_store_target ] = "," .join (transitive_deps [dep_store_target ])
200- fp_links [dep_key ] = {
201- "package" : dep_package ,
202- "path" : dep_path ,
203- "link_packages" : {link_package : True },
204- "deps" : transitive_deps ,
205- }
156+ prod_deps = importer .get ("deps" , {})
157+ all_deps = importer .get ("all_deps" , {})
158+
159+ dev_deps = {}
160+ for dep_name , dep_version in all_deps .items ():
161+ if dep_name not in prod_deps :
162+ dev_deps [dep_name ] = dep_version
163+
164+ for deps_type , deps in [("link_packages" , prod_deps ), ("link_dev_packages" , dev_deps )]:
165+ for dep_package , dep_version in deps .items ():
166+ if dep_version .startswith ("file:" ):
167+ dep_key = "{}+{}" .format (dep_package , dep_version )
168+ if not dep_key in fp_links .keys ():
169+ # Ignore file: dependencies on packages such as file: tarballs
170+ # TODO(3.0): remove with pnpm <v9
171+ if dep_version in packages :
172+ continue
173+
174+ # pnpm >=v9 where packages always have name@version
175+ if "{}@{}" .format (dep_package , dep_version ) in packages :
176+ continue
177+
178+ msg = "Expected to file: referenced package {} in first-party links {}" .format (dep_key , fp_links .keys ())
179+ fail (msg )
180+ if deps_type not in fp_links [dep_key ]:
181+ fp_links [dep_key ][deps_type ] = {}
182+ fp_links [dep_key ][deps_type ][link_package ] = True
183+ elif dep_version .startswith ("link:" ):
184+ dep_link = dep_version [len ("link:" ):]
185+ dep_path = helpers .link_package (root_package , dep_link )
186+ dep_key = "{}+{}" .format (dep_package , dep_path )
187+ if fp_links .get (dep_key , False ):
188+ if deps_type not in fp_links [dep_key ]:
189+ fp_links [dep_key ][deps_type ] = {}
190+ fp_links [dep_key ][deps_type ][link_package ] = True
191+ else :
192+ transitive_deps = {}
193+ raw_deps = {}
194+ if importers .get (dep_link , False ):
195+ raw_deps = importers .get (dep_link ).get ("deps" )
196+ for raw_package , raw_version in raw_deps .items ():
197+ package_store_name = utils .package_store_name (raw_package , raw_version )
198+ dep_store_target = """"//{root_package}:{package_store_root}/{{}}/{package_store_name}".format(name)""" .format (
199+ root_package = root_package ,
200+ package_store_name = package_store_name ,
201+ package_store_root = utils .package_store_root ,
202+ )
203+ if dep_store_target not in transitive_deps :
204+ transitive_deps [dep_store_target ] = [raw_package ]
205+ else :
206+ transitive_deps [dep_store_target ].append (raw_package )
207+
208+ for dep_store_target in transitive_deps .keys ():
209+ transitive_deps [dep_store_target ] = "," .join (transitive_deps [dep_store_target ])
210+ fp_links [dep_key ] = {
211+ "package" : dep_package ,
212+ "path" : dep_path ,
213+ "link_packages" : {},
214+ "link_dev_packages" : {},
215+ "deps" : transitive_deps ,
216+ }
217+ fp_links [dep_key ][deps_type ][link_package ] = True
218+
219+ importer_deps_map = {}
220+ for import_path , importer in importers .items ():
221+ link_package = helpers .link_package (root_package , import_path )
222+ prod_deps = importer .get ("deps" , {})
223+ all_deps = importer .get ("all_deps" , {})
224+
225+ pkg_deps = {}
226+ for dep_name , dep_version in all_deps .items ():
227+ is_dev = dep_name not in prod_deps
228+ pkg_deps [dep_name ] = {"version" : dep_version , "dev" : is_dev }
206229
207- npm_link_packages_const = """_LINK_PACKAGES = {link_packages}""" .format (link_packages = str (link_packages ))
230+ importer_deps_map [link_package ] = pkg_deps
231+
232+ npm_link_packages_const = """_LINK_PACKAGES = {link_packages}""" .format (
233+ link_packages = str (link_packages ),
234+ )
208235
209236 # Generate visibility configuration first to check if it's actually non-empty
210237 npm_visibility_config_generated = _generate_npm_visibility_config (rctx .attr .package_visibility )
@@ -222,7 +249,10 @@ sh_binary(
222249 npm_link_targets_bzl = [
223250 """\
224251 # buildifier: disable=function-docstring
225- def npm_link_targets(name = "node_modules", package = None):
252+ def npm_link_targets(name = "node_modules", package = None, prod = True, dev = True):
253+ if not prod and not dev:
254+ fail("npm_link_targets: at least one of 'prod' or 'dev' must be True")
255+
226256 bazel_package = package if package != None else native.package_name()
227257 link = bazel_package in _LINK_PACKAGES
228258
@@ -241,7 +271,10 @@ def npm_link_targets(name = "node_modules", package = None):
241271 npm_link_all_packages_bzl = [
242272 """\
243273 # buildifier: disable=function-docstring
244- def npm_link_all_packages(name = "node_modules", imported_links = []):
274+ def npm_link_all_packages(name = "node_modules", imported_links = [], prod = True, dev = True):
275+ if not prod and not dev:
276+ fail("npm_link_all_packages: at least one of 'prod' or 'dev' must be True")
277+
245278 bazel_package = native.package_name()
246279 root_package = "{root_package}"
247280 is_root = bazel_package == root_package
@@ -253,7 +286,7 @@ def npm_link_all_packages(name = "node_modules", imported_links = []):
253286 scope_targets = {{}}
254287
255288 for link_fn in imported_links:
256- new_link_targets, new_scope_targets = link_fn(name)
289+ new_link_targets, new_scope_targets = link_fn(name, prod, dev )
257290 link_targets.extend(new_link_targets)
258291 for _scope, _targets in new_scope_targets.items():
259292 if _scope not in scope_targets:
@@ -299,25 +332,34 @@ def npm_link_all_packages(name = "node_modules", imported_links = []):
299332 if build_file not in rctx_files :
300333 rctx_files [build_file ] = []
301334
302- # the bzl files for the package being linked
303335 if link_package not in links_bzl :
304336 links_bzl [link_package ] = []
305337 if link_package not in links_targets_bzl :
306- links_targets_bzl [link_package ] = []
338+ links_targets_bzl [link_package ] = { "all" : [], "prod" : [], "dev" : []}
307339
308340 # for each alias of this package
309341 for link_alias in link_aliases :
310- # link the alias to the underlying package
311342 links_bzl [link_package ].append (""" link_{i}("{{}}/{alias}".format(name), link_root_name = name, link_alias = "{alias}")""" .format (
312343 i = i ,
313344 alias = link_alias ,
314345 ))
315346
316- # expose the alias if public
317347 if "//visibility:public" in _import .package_visibility :
318- add_to_link_targets = """ link_targets.append(":{{}}/{alias}".format(name))""" .format (alias = link_alias )
319- links_bzl [link_package ].append (add_to_link_targets )
320- links_targets_bzl [link_package ].append (add_to_link_targets )
348+ add_to_link_all = """ link_targets.append(":{{}}/{alias}".format(name))""" .format (alias = link_alias )
349+ links_bzl [link_package ].append (add_to_link_all )
350+
351+ append_stmt_base = """link_targets.append(":{{}}/{alias}".format(name))""" .format (alias = link_alias )
352+
353+ links_targets_bzl [link_package ]["all" ].append (" " + append_stmt_base )
354+
355+ importer_deps = importer_deps_map .get (link_package , {})
356+ dep_info = importer_deps .get (link_alias , {})
357+ is_dev = dep_info .get ("dev" , False )
358+
359+ if is_dev :
360+ links_targets_bzl [link_package ]["dev" ].append (" " + append_stmt_base )
361+ else :
362+ links_targets_bzl [link_package ]["prod" ].append (" " + append_stmt_base )
321363 package_scope = link_alias [:link_alias .find ("/" , 1 )] if link_alias [0 ] == "@" else None
322364 if package_scope :
323365 links_bzl [link_package ].append (_ADD_SCOPE_TARGET3 .format (package_scope = package_scope ))
@@ -379,21 +421,55 @@ def npm_link_all_packages(name = "node_modules", imported_links = []):
379421 npm_link_all_packages_bzl .extend (bzl )
380422 first_link = False
381423
424+ # Add first-party packages to npm_link_targets before generating the function
425+ for fp_link in fp_links .values ():
426+ fp_package = fp_link .get ("package" )
427+ for link_type in ["link_packages" , "link_dev_packages" ]:
428+ fp_link_packages = fp_link .get (link_type , {}).keys ()
429+ if len (fp_link_packages ) > 0 :
430+ # Add first-party package links to npm_link_targets for each package that uses it
431+ for fp_link_package in fp_link_packages :
432+ if fp_link_package not in links_targets_bzl :
433+ links_targets_bzl [fp_link_package ] = {"all" : [], "prod" : [], "dev" : []}
434+
435+ fp_append_stmt = """link_targets.append(":{{}}/{pkg}".format(name))""" .format (pkg = fp_package )
436+
437+ links_targets_bzl [fp_link_package ]["all" ].append (" " + fp_append_stmt )
438+
439+ if link_type == "link_dev_packages" :
440+ links_targets_bzl [fp_link_package ]["dev" ].append (" " + fp_append_stmt )
441+ else :
442+ links_targets_bzl [fp_link_package ]["prod" ].append (" " + fp_append_stmt )
443+
382444 if len (links_targets_bzl ) > 0 :
383445 npm_link_targets_bzl .append (""" if link:""" )
384446 first_link = True
385- for link_package , bzl in links_targets_bzl .items ():
447+ for link_package , lists in links_targets_bzl .items ():
386448 npm_link_targets_bzl .append (""" {els}if bazel_package == "{pkg}":""" .format (
387449 els = "" if first_link else "el" ,
388450 pkg = link_package ,
389451 ))
390- npm_link_targets_bzl .extend (bzl )
452+
453+ if lists ["prod" ] or lists ["dev" ]:
454+ npm_link_targets_bzl .append (""" if prod:""" )
455+ if lists ["prod" ]:
456+ npm_link_targets_bzl .extend (lists ["prod" ])
457+ else :
458+ npm_link_targets_bzl .append (""" pass""" )
459+
460+ npm_link_targets_bzl .append (""" if dev:""" )
461+ if lists ["dev" ]:
462+ npm_link_targets_bzl .extend (lists ["dev" ])
463+ else :
464+ npm_link_targets_bzl .append (""" pass""" )
465+ else :
466+ npm_link_targets_bzl .extend (lists ["all" ])
467+
391468 first_link = False
392469
393470 for fp_link in fp_links .values ():
394471 fp_package = fp_link .get ("package" )
395472 fp_path = fp_link .get ("path" )
396- fp_link_packages = fp_link .get ("link_packages" ).keys ()
397473 fp_deps = fp_link .get ("deps" )
398474 fp_target = "//{}:{}" .format (
399475 fp_path ,
@@ -412,9 +488,17 @@ def npm_link_all_packages(name = "node_modules", imported_links = []):
412488 if len (package_visibility ) == 0 :
413489 package_visibility = ["//visibility:public" ]
414490
415- if len (fp_link_packages ) > 0 :
491+ # Collect all link packages from both prod and dev to avoid duplication
492+ all_fp_link_packages = {}
493+ for link_type in ["link_packages" , "link_dev_packages" ]:
494+ fp_link_packages = fp_link .get (link_type , {}).keys ()
495+ for pkg in fp_link_packages :
496+ all_fp_link_packages [pkg ] = True
497+
498+ # Generate a single _FP_DIRECT_TMPL block with all link packages
499+ if len (all_fp_link_packages ) > 0 :
416500 npm_link_all_packages_bzl .append (_FP_DIRECT_TMPL .format (
417- link_packages = fp_link_packages ,
501+ link_packages = list ( all_fp_link_packages . keys ()) ,
418502 link_visibility = package_visibility ,
419503 pkg = fp_package ,
420504 package_directory_output_group = utils .package_directory_output_group ,
@@ -423,22 +507,53 @@ def npm_link_all_packages(name = "node_modules", imported_links = []):
423507 package_store_root = utils .package_store_root ,
424508 ))
425509
426- npm_link_targets_bzl .append (_FP_DIRECT_TARGET_TMPL .format (
427- link_packages = fp_link_packages ,
510+ # Now handle the prod/dev specific logic for npm_link_targets
511+ for link_type in ["link_packages" , "link_dev_packages" ]:
512+ fp_link_packages = fp_link .get (link_type , {}).keys ()
513+ if len (fp_link_packages ) > 0 :
514+ # Also add the first-party package to its own package context
515+ fp_package_path = fp_link .get ("path" )
516+ if fp_package_path and fp_package_path not in links_targets_bzl :
517+ links_targets_bzl [fp_package_path ] = {"all" : [], "prod" : [], "dev" : []}
518+
519+ if fp_package_path :
520+ append_stmt_self = """link_targets.append(":{{}}/{pkg}".format(name))""" .format (pkg = fp_package )
521+
522+ links_targets_bzl [fp_package_path ]["all" ].append (" " + append_stmt_self )
523+
524+ if link_type == "link_dev_packages" :
525+ links_targets_bzl [fp_package_path ]["dev" ].append (" " + append_stmt_self )
526+ else :
527+ links_targets_bzl [fp_package_path ]["prod" ].append (" " + append_stmt_self )
528+
529+ if "//visibility:public" in package_visibility :
530+ # Also add this first-party package to npm_link_targets for packages that use it
531+ for fp_link_package in fp_link_packages :
532+ if fp_link_package not in links_targets_bzl :
533+ links_targets_bzl [fp_link_package ] = {"all" : [], "prod" : [], "dev" : []}
534+
535+ fp_append_stmt = """link_targets.append(":{{}}/{pkg}".format(name))""" .format (pkg = fp_package )
536+
537+ links_targets_bzl [fp_link_package ]["all" ].append (" " + fp_append_stmt )
538+
539+ if link_type == "link_dev_packages" :
540+ links_targets_bzl [fp_link_package ]["dev" ].append (" " + fp_append_stmt )
541+ else :
542+ links_targets_bzl [fp_link_package ]["prod" ].append (" " + fp_append_stmt )
543+
544+ # Add to link_all and scope targets (only once, not per link_type)
545+ if len (all_fp_link_packages ) > 0 and "//visibility:public" in package_visibility :
546+ add_to_link_all = """ link_targets.append(":{{}}/{pkg}".format(name))""" .format (
428547 pkg = fp_package ,
429- ))
548+ )
549+ npm_link_all_packages_bzl .append (add_to_link_all )
430550
431- # Validation at the top of npm_link_all_packages() already checked access to this package.
432- # If we reached this point, the calling package can access it, so unconditionally add it.
433551 package_scope = fp_package [:fp_package .find ("/" , 1 )] if fp_package [0 ] == "@" else None
434552 if package_scope :
435- npm_link_all_packages_bzl .append (""" link_targets.append(":{{}}/{fp_package}".format(name))
436- if "{package_scope}" not in scope_targets:
553+ npm_link_all_packages_bzl .append (""" if "{package_scope}" not in scope_targets:
437554 scope_targets["{package_scope}"] = [link_targets[-1]]
438555 else:
439- scope_targets["{package_scope}"].append(link_targets[-1])""" .format (fp_package = fp_package , package_scope = package_scope ))
440- else :
441- npm_link_all_packages_bzl .append (""" link_targets.append(":{{}}/{fp_package}".format(name))""" .format (fp_package = fp_package ))
556+ scope_targets["{package_scope}"].append(link_targets[-1])""" .format (package_scope = package_scope ))
442557
443558 # Generate catch all & scoped js_library targets
444559 npm_link_all_packages_bzl .append ("""
0 commit comments