fix(devserver): static assets sometimes incorrectly injected

Currently whenever the `ts_devserver` tries to inject static
assets which are not part of the current Bazel package, but
part of the current workspace (e.g. file in a parent package),
incorrect asset URLs are injected.

This is because the `ts_devserver` currently uses the `File#path`
property which is relative to the execroot. So files which are not
part of the current Bazel package, but part of the execroot sources,
will be kept relative to the execroot. This is problematic because
the execroot is not part of the devserver root directories and
therfore the injected URLs are incorrect. Instead we should always
explicitly pass the `ts_devserver` implementation-specific manifest
paths to the asset injection function.

To be clear: This currently only affects Windows because if Bazel
symlinks the runfiles, the Go runfile helpers also look for the
requested files in the `working dirctory` (which is always the
the `devserver_runfiles/{workspace_name}` --> so runfiles within
the current workspace can be always resolved even though they
are not part of the root directory. This is something we need
to make consistent in the future, but is not related to that
specific bug (we should always precisely pass manifest paths)

Fixes #409.

Closes #423

PiperOrigin-RevId: 234678783
diff --git a/internal/devserver/ts_devserver.bzl b/internal/devserver/ts_devserver.bzl
index fe377ec..399bd4e 100644
--- a/internal/devserver/ts_devserver.bzl
+++ b/internal/devserver/ts_devserver.bzl
@@ -100,7 +100,7 @@
                 "/".join([ctx.bin_dir.path, ctx.label.package]),
                 "/".join([ctx.genfiles_dir.path, ctx.label.package]),
             ],
-            [f.path for f in ctx.files.static_files] + [bundle_script],
+            [_short_path_to_manifest_path(ctx, f.short_path) for f in ctx.files.static_files] + [bundle_script],
             injected_index,
         )
         devserver_runfiles += [injected_index]
diff --git a/internal/e2e/npm_packages/ts_devserver/BUILD.bazel b/internal/e2e/npm_packages/ts_devserver/BUILD.bazel
index dd6eeef..f7e2f11 100644
--- a/internal/e2e/npm_packages/ts_devserver/BUILD.bazel
+++ b/internal/e2e/npm_packages/ts_devserver/BUILD.bazel
@@ -14,6 +14,11 @@
 
 load("@npm_bazel_typescript//:defs.bzl", "ts_devserver", "ts_library")
 
+exports_files([
+    "red-body-style.css",
+    "tsconfig.json",
+])
+
 ts_library(
     name = "app",
     srcs = ["app.ts"],
diff --git a/internal/e2e/npm_packages/ts_devserver/package-template.json b/internal/e2e/npm_packages/ts_devserver/package-template.json
index 7001bfa..5a9c82e 100644
--- a/internal/e2e/npm_packages/ts_devserver/package-template.json
+++ b/internal/e2e/npm_packages/ts_devserver/package-template.json
@@ -10,6 +10,8 @@
   },
   "scripts": {
     "pretest": "webdriver-manager update $CHROMEDRIVER_VERSION_ARG",
-    "test": "bazel build ... && concurrently \"bazel run //:devserver\" \"while ! nc -z 127.0.0.1 8080; do sleep 1; done && protractor\" --kill-others --success first"
+    "wait-for-servers": "while [[ ! nc -z 127.0.0.1 8080 ]] || [[ ! nc -z 127.0.0.1 8081 ]]; do sleep 1; done",
+    "run-e2e-tests": "concurrently \"bazel run //:devserver\" \"yarn wait-for-servers\" --kill-others --success first",
+    "test": "bazel build ... && yarn run-e2e-tests"
   }
 }
diff --git a/internal/e2e/npm_packages/ts_devserver/protractor.conf.js b/internal/e2e/npm_packages/ts_devserver/protractor.conf.js
index 9691a64..725b81e 100644
--- a/internal/e2e/npm_packages/ts_devserver/protractor.conf.js
+++ b/internal/e2e/npm_packages/ts_devserver/protractor.conf.js
@@ -1,6 +1,6 @@
 exports.config = {
   specs: [
-    'bazel-bin/*_e2e_test.js',
+    'bazel-bin/**/*_e2e_test.js',
   ],
   capabilities: {
     browserName: 'chrome',
diff --git a/internal/e2e/npm_packages/ts_devserver/red-body-style.css b/internal/e2e/npm_packages/ts_devserver/red-body-style.css
new file mode 100644
index 0000000..5c5e887
--- /dev/null
+++ b/internal/e2e/npm_packages/ts_devserver/red-body-style.css
@@ -0,0 +1,3 @@
+body {
+    background: red;
+}
diff --git a/internal/e2e/npm_packages/ts_devserver/subpackage/BUILD.bazel b/internal/e2e/npm_packages/ts_devserver/subpackage/BUILD.bazel
new file mode 100644
index 0000000..b68ac44
--- /dev/null
+++ b/internal/e2e/npm_packages/ts_devserver/subpackage/BUILD.bazel
@@ -0,0 +1,33 @@
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+load("@npm_bazel_typescript//:defs.bzl", "ts_devserver", "ts_library")
+
+ts_devserver(
+    name = "devserver",
+    index_html = "index.html",
+    port = 8081,
+    static_files = ["//:red-body-style.css"],
+)
+
+ts_library(
+    name = "e2e",
+    testonly = 1,
+    srcs = ["subpackage_e2e_test.ts"],
+    deps = [
+        "@npm//@types/jasmine",
+        "@npm//@types/node",
+        "@npm//protractor",
+    ],
+)
diff --git a/internal/e2e/npm_packages/ts_devserver/subpackage/index.html b/internal/e2e/npm_packages/ts_devserver/subpackage/index.html
new file mode 100644
index 0000000..4ea2bf3
--- /dev/null
+++ b/internal/e2e/npm_packages/ts_devserver/subpackage/index.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>Subpackage Devserver</title>
+</head>
+<body>
+  This is the devserver for a Bazel subpackage.
+</body>
+</html>
diff --git a/internal/e2e/npm_packages/ts_devserver/subpackage/subpackage_e2e_test.ts b/internal/e2e/npm_packages/ts_devserver/subpackage/subpackage_e2e_test.ts
new file mode 100644
index 0000000..281ea39
--- /dev/null
+++ b/internal/e2e/npm_packages/ts_devserver/subpackage/subpackage_e2e_test.ts
@@ -0,0 +1,17 @@
+import {browser, by, element} from 'protractor';
+
+describe('subpackage', () => {
+
+  beforeAll(async () => {
+    await browser.waitForAngularEnabled(false);
+    await browser.get('http://127.0.0.1:8081/');
+  });
+
+  // Ensures that the "ts_devserver" properly injects and loads static files which
+  // are in the current workspace, but not part of the current Bazel package. See
+  // related issue: https://github.com/bazelbuild/rules_typescript/issues/409
+  it('should be able to properly load the injected CSS file', async () => {
+    const bodyElement = element(by.css('body'));
+    expect(await bodyElement.getCssValue('background')).toContain('rgb(255, 0, 0)');
+  });
+});
diff --git a/internal/e2e/npm_packages/ts_devserver/tsconfig.json b/internal/e2e/npm_packages/ts_devserver/tsconfig.json
index e69de29..40dd225 100644
--- a/internal/e2e/npm_packages/ts_devserver/tsconfig.json
+++ b/internal/e2e/npm_packages/ts_devserver/tsconfig.json
@@ -0,0 +1,5 @@
+{
+  "compilerOptions": {
+    "lib": ["es2015", "dom"]
+  }
+}