Repository: vercel/nft (javascript)

Issue:
Needs CI for Windows
To prevent regressions in `now dev` on Windows (i.e. #30).

Recommend [Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/).


Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ node_modules
 dist
 coverage
 test/**/dist
+test/**/actual.js
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -16,8 +16,5 @@ steps:
     versionSpec: '10.x'
   displayName: 'Install Node.js'
 
-- script: |
-    node azure-prepare.js
-    yarn install
-    yarn test
+- script: node azure-prepare.js && yarn install && yarn test
   displayName: 'Install Dependencies & Test'
diff --git a/azure-prepare.js b/azure-prepare.js
--- a/azure-prepare.js
+++ b/azure-prepare.js
@@ -11,6 +11,7 @@ if (process.platform === 'win32') {
   unlinkSync(join(__dirname, 'test', 'integration', 'highlights.js'));
   unlinkSync(join(__dirname, 'test', 'integration', 'hot-shots.js'));
   unlinkSync(join(__dirname, 'test', 'integration', 'yoga-layout.js'));
+  unlinkSync(join(__dirname, 'test', 'integration', 'loopback.js'));
   delete pkg.devDependencies['@tensorflow/tfjs-node'];
   delete pkg.devDependencies['highlights'];
   delete pkg.devDependencies['hot-shots'];
diff --git a/jest.config.js b/jest.config.js
--- a/jest.config.js
+++ b/jest.config.js
@@ -2,5 +2,5 @@ module.exports = {
   collectCoverageFrom: ["src/**/*.js"],
   coverageReporters: ["html", "lcov"],
   testEnvironment: "node",
-  testMatch: ["<rootDir>/test/?(*.)+(spec|test).js?(x)"]
+  testMatch: ["<rootDir>/test/*(*.)@(spec|test).js?(x)"]
 };
diff --git a/src/resolve-dependency.js b/src/resolve-dependency.js
--- a/src/resolve-dependency.js
+++ b/src/resolve-dependency.js
@@ -19,7 +19,7 @@ function resolvePath (path, parent, job) {
 
 function resolveFile (path, parent, job) {
   path = job.realpath(path, parent);
-  if (path.endsWith('/')) return;
+  if (path.endsWith(sep)) return;
   if (job.isFile(path)) return path;
   if (job.ts && path.startsWith(job.base) && path.substr(job.base.length).indexOf(sep + 'node_modules' + sep) === -1 && job.isFile(path + '.ts')) return path + '.ts';
   if (job.ts && path.startsWith(job.base) && path.substr(job.base.length).indexOf(sep + 'node_modules' + sep) === -1 && job.isFile(path + '.tsx')) return path + '.tsx';
@@ -30,7 +30,7 @@ function resolveFile (path, parent, job) {
 
 function resolveDir (path, parent, job) {
   if (!job.isDir(path)) return;
-  const realPjsonPath = job.realpath(path + '/package.json', parent);
+  const realPjsonPath = job.realpath(path + sep + 'package.json', parent);
   const pjsonSource = job.readFile(realPjsonPath);
   if (pjsonSource) {
     try {
@@ -60,13 +60,13 @@ function resolvePackage (name, parent, job) {
   let packageParent = parent;
   if (nodeBuiltins.has(name)) return 'node:' + name;
   let separatorIndex;
-  const rootSeparatorIndex = packageParent.indexOf('/');
-  while ((separatorIndex = packageParent.lastIndexOf('/')) > rootSeparatorIndex) {
+  const rootSeparatorIndex = packageParent.indexOf(sep);
+  while ((separatorIndex = packageParent.lastIndexOf(sep)) > rootSeparatorIndex) {
     packageParent = packageParent.substr(0, separatorIndex);
-    const nodeModulesDir = packageParent + '/node_modules';
+    const nodeModulesDir = packageParent + sep + 'node_modules';
     const stat = job.stat(nodeModulesDir);
     if (!stat || !stat.isDirectory()) continue;
-    const resolved = resolveFile(nodeModulesDir + '/' + name, parent, job) || resolveDir(nodeModulesDir + '/' + name, parent, job);
+    const resolved = resolveFile(nodeModulesDir + sep + name, parent, job) || resolveDir(nodeModulesDir + sep + name, parent, job);
     if (resolved) return resolved;
   }
   notFound(name, parent);
diff --git a/src/utils/get-package-base.js b/src/utils/get-package-base.js
--- a/src/utils/get-package-base.js
+++ b/src/utils/get-package-base.js
@@ -18,7 +18,9 @@ module.exports.getPackageName = function (id) {
       (id[pkgIndex - 1] === '/' || id[pkgIndex - 1] === '\\') &&
       (id[pkgIndex + 12] === '/' || id[pkgIndex + 12] === '\\')) {
     const pkgNameMatch = id.substr(pkgIndex + 13).match(pkgNameRegEx);
-    if (pkgNameMatch) return pkgNameMatch[0];
+    if (pkgNameMatch && pkgNameMatch.length > 0) {
+      return pkgNameMatch[0].replace(/\\/g,  '/');
+    }
   }
 };
 
diff --git a/src/utils/special-cases.js b/src/utils/special-cases.js
--- a/src/utils/special-cases.js
+++ b/src/utils/special-cases.js
@@ -164,5 +164,6 @@ const specialCases = {
 module.exports = function ({ id, ast, emitAsset, emitAssetDirectory, job }) {
   const pkgName = getPackageName(id);
   const specialCase = specialCases[pkgName];
+  id = id.replace(/\\/g,  '/');
   if (specialCase) specialCase({ id, ast, emitAsset, emitAssetDirectory, job });
 };


Test patch:
diff --git a/test/integration.test.js b/test/integration.test.js
--- a/test/integration.test.js
+++ b/test/integration.test.js
@@ -35,7 +35,7 @@ for (const integrationTest of fs.readdirSync(`${__dirname}/integration`)) {
         var symlinkPath = await readlink(inPath);
       }
       catch (e) {
-        if (e.code !== 'EINVAL') throw e;
+        if (e.code !== 'EINVAL' && e.code !== 'UNKNOWN') throw e;
       }
       mkdirp.sync(path.dirname(outPath));
       if (symlinkPath) {
@@ -45,7 +45,8 @@ for (const integrationTest of fs.readdirSync(`${__dirname}/integration`)) {
         await writeFile(outPath, await readFile(inPath), { mode: 0o777 });
       }
     }));
-    const ps = fork(`${tmpdir}/test/integration/${integrationTest}`, {
+    const testFile = path.join(tmpdir, 'test', 'integration', integrationTest);
+    const ps = fork(testFile, {
       stdio: fails ? 'pipe' : 'inherit'
     });
     const code = await new Promise(resolve => ps.on('close', resolve));
diff --git a/test/integration/typescript.js b/test/integration/typescript.js
--- a/test/integration/typescript.js
+++ b/test/integration/typescript.js
@@ -1,14 +1,22 @@
 const { spawn } = require('child_process');
-const path = require('path');
-
 const tsc = require.resolve('typescript/bin/tsc');
+const tscjs = require.resolve('typescript/lib/tsc.js');
+const cwd = __dirname;
+
+if (!tsc.endsWith('tsc')) {
+  throw new Error('Expected tsc cli but found ' + tsc);
+}
+
+if (!tscjs.endsWith('tsc.js')) {
+  throw new Error('Expected tsc.js but found ' + tscjs);
+}
 
-const child = spawn(tsc, { cwd: path.resolve(__dirname, '../fixtures') });
+const child = spawn('node', [tscjs, '--version'], { cwd });
 child.stdout.on('data', data => {
-  console.error(data.toString());
-  throw new Error('Unexpected output.');
+  if (!data || data.toString().length === 0) {
+    throw new Error('Expected stdout output but found none');
+  }
 });
 child.stderr.on('data', data => {
-  console.error(data.toString());
-  throw new Error('Unexpected output.')
+  throw new Error('Unexpected stderr output: ' + data.toString());
 });
diff --git a/test/unit.test.js b/test/unit.test.js
--- a/test/unit.test.js
+++ b/test/unit.test.js
@@ -1,31 +1,40 @@
 const fs = require('fs');
+const { join } = require('path');
 const nodeFileTrace = require('../src/node-file-trace');
 
 global._unit = true;
 
 function tryCreateSymlink (target, path) {
+  if (process.platform === 'win32') {
+    console.log('skipping create symlink on Windows');
+    return;
+  }
   try {
     fs.symlinkSync(target, path);
   }
   catch (e) {
-    if (e.code !== 'EEXIST') throw e;
+    if (e.code !== 'EEXIST' && e.code !== 'UNKNOWN') throw e;
   }
 }
 
 // ensure test/yarn-workspaces/node_modules/x -> test/yarn-workspaces/packages/x
 try {
-  fs.mkdirSync(`${__dirname}/unit/yarn-workspaces/node_modules`);
+  fs.mkdirSync(join(__dirname, 'unit', 'yarn-workspaces', 'node_modules'));
 }
 catch (e) {
-  if (e.code !== 'EEXIST') throw e;
+  if (e.code !== 'EEXIST' && e.code !== 'UNKNOWN') throw e;
 }
-tryCreateSymlink('../packages/x', `${__dirname}/unit/yarn-workspaces/node_modules/x`);
-tryCreateSymlink('./asset1.txt', `${__dirname}/unit/asset-symlink/asset.txt`);
+tryCreateSymlink('../packages/x', join(__dirname, 'unit', 'yarn-workspaces', 'node_modules', 'x'));
+tryCreateSymlink('./asset1.txt',  join(__dirname, 'unit', 'asset-symlink', 'asset.txt'));
 
-for (const unitTest of fs.readdirSync(`${__dirname}/unit`)) {
+for (const unitTest of fs.readdirSync(join(__dirname, 'unit'))) {
+  if (process.platform === 'win32' && ['yarn-workspaces', 'asset-symlink', 'require-symlink'].includes(unitTest)) {
+    console.log('skipping symlink test on Windows: ' + unitTest);
+    continue;
+  }
   it(`should correctly trace ${unitTest}`, async () => {
-    const unitPath = `${__dirname}/unit/${unitTest}`;
-    const { fileList, reasons } = await nodeFileTrace([`${unitPath}/input.js`], {
+    const unitPath = join(__dirname, 'unit', unitTest);
+    const { fileList, reasons } = await nodeFileTrace([join(unitPath, 'input.js')], {
       base: `${__dirname}/../`,
       ts: true,
       log: true,
@@ -34,7 +43,11 @@ for (const unitTest of fs.readdirSync(`${__dirname}/unit`)) {
     });
     let expected;
     try {
-      expected = JSON.parse(fs.readFileSync(`${unitPath}/output.js`).toString());
+      expected = JSON.parse(fs.readFileSync(join(unitPath, 'output.js')).toString());
+      if (process.platform === 'win32') {
+        // When using Windows, the expected output should use backslash
+        expected = expected.map(str => str.replace(/\//g, '\\'));
+      }
     }
     catch (e) {
       console.warn(e);
@@ -45,7 +58,7 @@ for (const unitTest of fs.readdirSync(`${__dirname}/unit`)) {
     }
     catch (e) {
       console.warn(reasons);
-      fs.writeFileSync(`${unitPath}/actual.js`, JSON.stringify(fileList, null, 2));
+      fs.writeFileSync(join(unitPath, 'actual.js'), JSON.stringify(fileList, null, 2));
       throw e;
     }
   });
diff --git a/test/unit/ts-filter/input.js b/test/unit/ts-filter/input.js
--- a/test/unit/ts-filter/input.js
+++ b/test/unit/ts-filter/input.js
@@ -1,5 +1,6 @@
+const { join } = require('path');
 require('pkg');
 
 // asset reference to ts file in node_modules
 // should not cause ts file to be compiled
-fs.readFileSync(__dirname + '/node_modules/pkg/index.ts');
\ No newline at end of file
+fs.readFileSync(join(__dirname, 'node_modules', 'pkg', 'index.ts'));
\ No newline at end of file