-
Notifications
You must be signed in to change notification settings - Fork 65
/
Copy pathbuild.ts
171 lines (141 loc) · 5.08 KB
/
build.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import { stripIndent } from 'common-tags'
import * as FS from 'fs-jetpack'
import * as Path from 'path'
import * as Layout from '../../lib/layout'
import { emitTSProgram, createTSProgram, deleteTSIncrementalFile } from '../../lib/tsc'
import {
createStartModuleContent,
prepareStartModule,
START_MODULE_NAME,
} from '../../runtime/start/start-module'
import { rootLogger } from '../nexus-logger'
import * as Plugin from '../plugin'
import { fatal } from '../process'
import * as Reflection from '../reflection'
import {
computeBuildOutputFromTarget,
logTargetPostBuildMessage,
normalizeTarget,
validateTarget,
} from './deploy-target'
import { bundle } from './bundle'
const log = rootLogger.child('build')
interface BuildSettings {
target?: string
output?: string
stage?: string
entrypoint?: string
asBundle: boolean
cwd?: string
}
export async function buildNexusApp(settings: BuildSettings) {
process.env.NEXUS_BUILD = 'true'
const startTime = Date.now()
const deploymentTarget = normalizeTarget(settings.target)
const buildOutput = settings.output ?? computeBuildOutputFromTarget(deploymentTarget) ?? undefined
const layout = await Layout.create({
buildOutputDir: buildOutput,
asBundle: settings.asBundle,
entrypointPath: settings.entrypoint,
cwd: settings.cwd,
})
/**
* Delete the TS incremental file to make sure we're building from a clean slate
*/
deleteTSIncrementalFile(layout)
if (deploymentTarget) {
const validatedTarget = validateTarget(deploymentTarget, layout)
if (!validatedTarget.valid) {
process.exit(1)
}
}
log.info('get used plugins')
const pluginReflection = await Reflection.reflect(layout, { usedPlugins: true, onMainThread: true })
if (!pluginReflection.success) {
fatal('failed to get used plugins', { error: pluginReflection.error })
}
const { plugins } = pluginReflection
const worktimePlugins = Plugin.importAndLoadWorktimePlugins(plugins, layout)
for (const p of worktimePlugins) {
await p.hooks.build.onStart?.()
}
log.info('starting reflection')
const reflectionResult = await Reflection.reflect(layout, { artifacts: true })
if (!reflectionResult.success) {
fatal('reflection failed', { error: reflectionResult.error })
}
log.info('building typescript program')
const tsBuilder = createTSProgram(layout, { withCache: true })
log.info('compiling a production build')
// Recreate our program instance so that it picks up the typegen. We use
// incremental builder type of program so that the cache from the previous
// run of TypeScript should make re-building up this one cheap.
emitTSProgram(tsBuilder, layout, { removePreviousBuild: false })
const gotManifests = Plugin.getPluginManifests(plugins)
if (gotManifests.errors) Plugin.showManifestErrorsAndExit(gotManifests.errors)
const runtimePluginManifests = gotManifests.data.filter((pm) => pm.runtime)
if (!layout.tsConfig.content.options.noEmit) {
await writeStartModule({
layout: layout,
startModule: prepareStartModule(
tsBuilder,
createStartModuleContent({
internalStage: 'build',
layout: layout,
runtimePluginManifests,
})
),
})
if (layout.build.bundleOutputDir) {
log.info('bundling app')
await bundle({
base: layout.projectRoot,
bundleOutputDir: layout.build.bundleOutputDir,
entrypoint: layout.build.startModuleOutPath,
tsOutputDir: layout.build.tsOutputDir,
tsRootDir: layout.tsConfig.content.options.rootDir!,
plugins: pluginReflection.plugins,
})
await FS.removeAsync(layout.build.tsOutputDir)
}
}
const buildOutputLog =
layout.tsConfig.content.options.noEmit === true
? 'no emit'
: Path.relative(layout.projectRoot, layout.build.bundleOutputDir ?? layout.build.tsOutputDir)
log.info('success', {
buildOutput: buildOutputLog,
time: Date.now() - startTime,
})
if (deploymentTarget) {
logTargetPostBuildMessage(deploymentTarget)
}
delete process.env.NEXUS_BUILD
}
/**
* Output to disk in the build the start module that will be used to boot the
* nexus app.
*/
export async function writeStartModule({
startModule,
layout,
}: {
startModule: string
layout: Layout.Layout
}): Promise<void> {
// TODO we can be more flexible and allow the user to write an index.ts
// module. For example we can alias it, or, we can rename it e.g.
// `index_original.js`. For now we just error out and ask the user to not name
// their module index.ts.
if (FS.exists(layout.build.startModuleInPath)) {
fatal(stripIndent`
Found ${layout.build.startModuleInPath}
Nexus reserves the source root module name ${START_MODULE_NAME}.js for its own use.
Please change your app layout to not have this module.
This is a temporary limitation that we intend to remove in the future.
For more details please see this GitHub issue: https://github.com/graphql-nexus/nexus/issues/139
`)
}
log.trace('Writing start module to disk')
await FS.writeAsync(layout.build.startModuleOutPath, startModule)
}