Skip to content

feat: treat fist package.json as project root #1034

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions docs/guides/project-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@

- Nexus honours settings within `tsconfig.json`.
- This ensures that Nexus and your IDE perform identical static analysis.
- If no `tsconfig.json` is present then Nexus will scaffold one for you.
- If no `tsconfig.json` is present in the project root then Nexus will scaffold one for you. This will make ([VSCode treat it as the project root too](https://vscode.readthedocs.io/en/latest/languages/typescript/#typescript-files-and-projects)).
- Nexus interacts with `tsconfig.json` in the following ways.

##### Project Root

- Project Root is the CWD (current working directory) for all CLI invocations.
- Nexus ([like VSCode](https://vscode.readthedocs.io/en/latest/languages/typescript/#typescript-files-and-projects)) considers the folder containing a `tsconfig.json` to be the project root.

##### Source Root

- Source Root is the base from which your source code layout starts. So, all of your app code must live within the source root. Your JavaScript build output layout will mirror it.
Expand Down Expand Up @@ -44,6 +39,16 @@ Autocomplete with Nexus TS LSP:

Nexus imposes a few requirements about how you structure your codebase.

### Project Root

The project root is the directory from which all all Nexus CLI commands base their CWD upon. It is also the directory that configuration paths in Nexus (e.g. `--entrypoint` flag) are often relative to as well (in other cases it can be source root).

To find the project root Nexus starts with the current working directory (CWD). This usually means the current directory you're in when invoking the Nexus CLI. From this location Nexus will do the following:

1. If a directory in the current hierarchy, including CWD, contains a [valid](https://docs.npmjs.com/creating-a-package-json-file#required-name-and-version-fields) `package.json` then it will be considered the project root. In case multiple such files are present in the hierarchy, only the first one is considered (in other words the one closest to CWD).

2. If no `package.json` files exist then the CWD itself is taken to be the project root.

### Nexus module(s)

##### Pattern
Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/create/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ async function scaffoldBaseFiles(options: InternalConfig) {
'tsconfig.json',
tsconfigTemplate({
sourceRootRelative,
outRootRelative: Layout.DEFAULT_BUILD_FOLDER_PATH_RELATIVE_TO_PROJECT_ROOT,
outRootRelative: Layout.DEFAULT_BUILD_DIR_PATH_RELATIVE_TO_PROJECT_ROOT,
})
),

Expand Down
2 changes: 1 addition & 1 deletion src/lib/build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function buildNexusApp(settings: BuildSettings) {
buildOutputDir: buildOutput,
asBundle: settings.asBundle,
entrypointPath: settings.entrypoint,
cwd: settings.cwd,
projectRoot: settings.cwd,
})
)

Expand Down
4 changes: 2 additions & 2 deletions src/lib/build/deploy-target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import chalk from 'chalk'
import { stripIndent } from 'common-tags'
import * as fs from 'fs-jetpack'
import * as Path from 'path'
import { DEFAULT_BUILD_FOLDER_PATH_RELATIVE_TO_PROJECT_ROOT, Layout } from '../../lib/layout'
import { DEFAULT_BUILD_DIR_PATH_RELATIVE_TO_PROJECT_ROOT, Layout } from '../../lib/layout'
import { findFileRecurisvelyUpwardSync } from '../fs'
import { rootLogger } from '../nexus-logger'
import { fatal } from '../process'
Expand Down Expand Up @@ -41,7 +41,7 @@ export function normalizeTarget(inputDeployTarget: string | undefined): Supporte

const TARGET_TO_BUILD_OUTPUT: Record<SupportedTargets, string> = {
vercel: 'dist',
heroku: DEFAULT_BUILD_FOLDER_PATH_RELATIVE_TO_PROJECT_ROOT,
heroku: DEFAULT_BUILD_DIR_PATH_RELATIVE_TO_PROJECT_ROOT,
}

export function computeBuildOutputFromTarget(target: SupportedTargets | null) {
Expand Down
118 changes: 118 additions & 0 deletions src/lib/layout/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import * as Path from 'path'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All build layout concerns are in own module now.

import { START_MODULE_NAME } from '../../runtime/start/start-module'
import { ScanResult } from './layout'

/**
* The temporary ts build folder used when bundling is enabled
*
* Note: It **should not** be nested in a sub-folder. This might "corrupt" the relative paths of the bundle build.
*/
export const TMP_TS_BUILD_FOLDER_PATH_RELATIVE_TO_PROJECT_ROOT = '.tmp_build'

export const DEFAULT_BUILD_DIR_PATH_RELATIVE_TO_PROJECT_ROOT = '.nexus/build'

export type BuildLayout = {
startModuleOutPath: string
startModuleInPath: string
tsOutputDir: string
bundleOutputDir: string | null
/**
* The final path to the start module. When bundling disbaled, same as `startModuleOutPath`.
*/
startModule: string
/**
* The final output dir. If bundler is enabled then this is `bundleOutputDir`.
* Otherwise it is `tsOutputDir`.
*
* When bundle case, this accounts for the bundle environment, which makes it
* **DIFFERENT** than the source root. For example:
*
* ```
* <out_root>/node_modules/
* <out_root>/api/app.ts
* <out_root>/api/index.ts
* ```
*/
root: string
/**
* If bundler is enabled then the final output dir where the **source** is
* located. Otherwise same as `tsOutputDir`.
*
* When bundle case, this is different than `root` because it tells you where
* the source starts, not the build environment.
*
* For example, here `source_root` is `<out_root>/api` becuase the user has
* set their root dir to `api`:
*
* ```
* <out_root>/node_modules/
* <out_root>/api/app.ts
* <out_root>/api/index.ts
* ```
*
* But here, `source_root` is `<out_root>` because the user has set their root
* dir to `.`:
*
* ```
* <out_source_root>/node_modules/
* <out_source_root>/app.ts
* <out_source_root>/index.ts
* ```
*/
sourceRoot: string
}

export function getBuildLayout(
buildOutput: string | undefined,
scanResult: ScanResult,
asBundle?: boolean
): BuildLayout {
const tsOutputDir = getBuildOutputDir(scanResult.projectRoot, buildOutput, scanResult)
const startModuleInPath = Path.join(scanResult.sourceRoot, START_MODULE_NAME + '.ts')
const startModuleOutPath = Path.join(tsOutputDir, START_MODULE_NAME + '.js')

if (!asBundle) {
return {
tsOutputDir,
startModuleInPath,
startModuleOutPath,
bundleOutputDir: null,
startModule: startModuleOutPath,
root: tsOutputDir,
sourceRoot: tsOutputDir,
}
}

const tsBuildInfo = getBuildLayout(TMP_TS_BUILD_FOLDER_PATH_RELATIVE_TO_PROJECT_ROOT, scanResult, false)
const relativeRootDir = Path.relative(scanResult.projectRoot, scanResult.tsConfig.content.options.rootDir!)
const sourceRoot = Path.join(tsOutputDir, relativeRootDir)

return {
...tsBuildInfo,
bundleOutputDir: tsOutputDir,
root: tsOutputDir,
startModule: Path.join(sourceRoot, START_MODULE_NAME + '.js'),
sourceRoot,
}
}

/**
* Get the absolute build output dir
* Precedence: User's input > tsconfig.json's outDir > default
*/
function getBuildOutputDir(
projectRoot: string,
buildOutput: string | undefined,
scanResult: ScanResult
): string {
const output =
buildOutput ??
scanResult.tsConfig.content.options.outDir ??
DEFAULT_BUILD_DIR_PATH_RELATIVE_TO_PROJECT_ROOT

if (Path.isAbsolute(output)) {
return output
}

return Path.join(projectRoot, output)
}
32 changes: 32 additions & 0 deletions src/lib/layout/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Either, right } from 'fp-ts/lib/Either'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All cache concerns are in own module now.

import { rootLogger } from '../nexus-logger'
import { create, createFromData, Data, Layout } from './layout'

const ENV_VAR_DATA_NAME = 'NEXUS_LAYOUT'

const log = rootLogger.child('layout')

export function saveDataForChildProcess(layout: Layout): { NEXUS_LAYOUT: string } {
return {
[ENV_VAR_DATA_NAME]: JSON.stringify(layout.data),
}
}

/**
* Load the layout data from a serialized version stored in the environment. If
* it is not found then a warning will be logged and it will be recalculated.
* For this reason the function is async however under normal circumstances it
* should be as-if sync.
*/
export async function loadDataFromParentProcess(): Promise<Either<Error, Layout>> {
const savedData: undefined | string = process.env[ENV_VAR_DATA_NAME]
if (!savedData) {
log.trace(
'WARNING an attempt to load saved layout data was made but no serialized data was found in the environment. This may represent a bug. Layout is being re-calculated as a fallback solution. This should result in the same layout data (if not, another probably bug, compounding confusion) but at least adds latentency to user experience.'
)
return create({}) // todo no build output...
} else {
// todo guard against corrupted env data
return right(createFromData(JSON.parse(savedData) as Data))
}
}
Loading