1#!/usr/bin/env node
2'use strict';
3
4const debug = require('debug')('workspaces');
5const fs = require('fs');
6const mkdirp = require('mkdirp');
7const path = require('path');
8
9const generatedComment = '// @generated by expo-yarn-workspaces\n';
10
11/**
12 * Makes a module for the entry point of an Expo project based on the entry point specified by
13 * package.json, perhaps implicitly.
14 */
15function makeEntryModule(projectPath) {
16  debug(`Checking the main module for the project at %s`, projectPath);
17
18  const packageJson = readPackageJson(projectPath);
19  if (!packageJson) {
20    debug(`The project doesn't have a package.json file; will not make a main module`);
21    return;
22  }
23
24  const mainModule = packageJson.main || 'index.js';
25  debug(`Using %s as the main module of the project`, mainModule);
26  // For simplicity we don't handle directory module names that assume an "index.js" filename
27  if (!mainModule.endsWith('.js')) {
28    console.error(`The specified main module must be a file path ending with .js`);
29    return;
30  }
31
32  // Look for the main module to see if it already exists. For simplicity we don't consider
33  // platform-specific modules.
34  let mainModuleCode = null;
35  const mainModulePath = path.join(projectPath, mainModule);
36  if (fs.existsSync(mainModulePath)) {
37    mainModuleCode = fs.readFileSync(mainModulePath, 'utf8');
38    if (!mainModuleCode.includes('@generated')) {
39      debug(`The main module at %s already exists; will not make a main module`, mainModule);
40      if (isSymlinkedPath(mainModulePath)) {
41        console.warn(
42          `The main module at %s is symlinked and may not be accessible to Metro`,
43          mainModulePath
44        );
45      }
46      return;
47    }
48  }
49
50  // Generate the main module
51  const mainModuleDirectory = path.dirname(mainModulePath);
52  debug(`Ensuring that the directory for the main module exists: %s`, mainModuleDirectory);
53  mkdirp.sync(mainModuleDirectory);
54  if (isSymlinkedPath(mainModuleDirectory)) {
55    console.warn(
56      `The main module at %s is symlinked and may not be accessible to Metro`,
57      mainModulePath
58    );
59  }
60
61  debug(`Generating main module code from template`);
62  const template = fs.readFileSync(path.join(__dirname, 'AppEntry.template.js'), 'utf8');
63  // We use the physical path of the main module to compute the relative path to the project since
64  // Node's module system resolves symlinks before loading modules
65  let relativeProjectPath = path.relative(fs.realpathSync(mainModuleDirectory), projectPath);
66  if (relativeProjectPath.split(path.sep)[0] !== '..') {
67    relativeProjectPath = path.join('.', relativeProjectPath);
68  }
69
70  const code =
71    generatedComment + '\n' + template.replace('{{relativeProjectPath}}', relativeProjectPath);
72  if (code !== mainModuleCode) {
73    fs.writeFileSync(mainModulePath, code);
74  }
75  debug(`Wrote generated main module to %s`, mainModulePath);
76}
77
78function readPackageJson(projectPath) {
79  let json;
80  try {
81    json = fs.readFileSync(path.join(projectPath, 'package.json'), 'utf8');
82  } catch (e) {
83    if (e.code === 'ENOENT') {
84      return null;
85    }
86    throw e;
87  }
88
89  return JSON.parse(json);
90}
91
92function isSymlinkedPath(filePath) {
93  return fs.realpathSync(filePath) !== path.resolve(filePath);
94}
95
96if (module === require.main) {
97  makeEntryModule(path.resolve());
98}
99