Skip to content

Commit 52b8c59

Browse files
Cephyric-ghpieh
andauthored
feat: adds support for detecting NX monorepos (#263)
* feat: adds support for detecting NX monorepos * fix: adds server.ts files from NX monorepo builds to tools folder * feat: changes NX project.json detection to rely on the user config for their package path * fix: fix fail message to correctly reference project.json Co-authored-by: Michal Piechowiak <[email protected]> * fix: remove unnecessary reference to selectedProject --------- Co-authored-by: Michal Piechowiak <[email protected]>
1 parent f5b8803 commit 52b8c59

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+46544
-30
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,5 @@ Temporary Items
143143

144144
.netlify
145145
.angular
146+
.nx
147+
.idea

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
"pretest:fixtures:angular-19-common-engine": "cd tests/fixtures/angular-19-common-engine && npm ci",
4848
"pretest:fixtures:angular-19-app-engine": "cd tests/fixtures/angular-19-app-engine && npm ci",
4949
"pretest:fixtures:angular-19-prerender-false": "cd tests/fixtures/angular-19-prerender-false && npm ci",
50+
"pretest:fixtures:nx-angular-19-common-engine": "cd tests/fixtures/nx-angular-19-common-engine && npm ci",
51+
"pretest:fixtures:nx-angular-19-app-engine": "cd tests/fixtures/nx-angular-19-app-engine && npm ci",
5052
"pretest": "run-s pretest:*",
5153
"test": "node --test"
5254
},

src/helpers/fixOutputDir.js

+17-5
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,25 @@ const { join } = require('path')
33
const getAngularJson = require('./getAngularJson')
44
const { getProject } = require('./setUpEdgeFunction')
55

6-
const fixOutputDir = async function ({ failBuild, failPlugin, siteRoot, PUBLISH_DIR, IS_LOCAL, netlifyConfig }) {
7-
const angularJson = getAngularJson({ failPlugin, siteRoot })
8-
const project = getProject(angularJson, failBuild)
6+
const fixOutputDir = async function ({
7+
failBuild,
8+
failPlugin,
9+
siteRoot,
10+
PUBLISH_DIR,
11+
IS_LOCAL,
12+
netlifyConfig,
13+
workspaceType,
14+
packagePath,
15+
}) {
16+
const angularJson = getAngularJson({ failPlugin, siteRoot, workspaceType, packagePath })
17+
const project = getProject(angularJson, failBuild, workspaceType === 'nx')
918

10-
const { outputPath } = project.architect.build.options
19+
const { outputPath } = workspaceType === 'nx' ? project.targets.build.options : project.architect.build.options
1120

12-
const isApplicationBuilder = project.architect.build.builder.endsWith(':application')
21+
const isApplicationBuilder =
22+
workspaceType === 'nx'
23+
? project.targets.build.executor.endsWith(':application')
24+
: project.architect.build.builder.endsWith(':application')
1325
const correctPublishDir = isApplicationBuilder ? join(outputPath, 'browser') : outputPath
1426
if (correctPublishDir === PUBLISH_DIR) {
1527
return

src/helpers/getAngularJson.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { join } = require('node:path')
2+
const process = require('process')
23

34
const { existsSync, readJsonSync } = require('fs-extra')
45

@@ -7,10 +8,30 @@ const { existsSync, readJsonSync } = require('fs-extra')
78
* @param {Object} obj
89
* @param {string} obj.siteRoot Root directory of an app
910
* @param {(msg: string) => never} obj.failPlugin Function to fail the plugin
11+
* @param {'nx' | 'default'} obj.workspaceType Type of monorepo, dictates what json file to open
12+
* @param {string} obj.packagePath The path to the package directory
1013
*
1114
* @returns {any}
1215
*/
13-
const getAngularJson = function ({ failPlugin, siteRoot }) {
16+
const getAngularJson = function ({ failPlugin, siteRoot, workspaceType, packagePath }) {
17+
if (workspaceType === 'nx') {
18+
if ((packagePath ?? '').length === 0) {
19+
return failPlugin(
20+
`packagePath must be set to the location of the project.json being built when deploying an NX monorepo, e.g. "apps/{project-name}"`,
21+
)
22+
}
23+
24+
if (!existsSync(join(siteRoot, packagePath, 'project.json'))) {
25+
return failPlugin(`No project.json found in ${packagePath}`)
26+
}
27+
28+
try {
29+
return readJsonSync(join(siteRoot, packagePath, 'project.json'))
30+
} catch {
31+
return failPlugin(`Could not parse the contents of project.json`)
32+
}
33+
}
34+
1435
if (!existsSync(join(siteRoot, 'angular.json'))) {
1536
return failPlugin(`No angular.json found at project root`)
1637
}

src/helpers/getAngularRoot.js

+14-4
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,33 @@ const process = require('process')
55
/**
66
* If we're in a monorepo then the site root may not be the same as the base directory
77
* If there's no angular.json in the root, we instead look for it 2 levels up from the publish dir
8+
*
9+
* @returns {{siteRoot: string, workspaceType: 'nx' | 'default'}}
810
*/
911
const getAngularRoot = ({ failBuild, netlifyConfig }) => {
1012
let angularRoot = process.cwd()
13+
14+
// This could be an NX repo, so check for the existence of nx.json too
15+
let angularJsonExists = existsSync(path.join(angularRoot, 'angular.json'))
16+
let nxJsonExists = existsSync(path.join(angularRoot, 'nx.json'))
17+
1118
if (
12-
!existsSync(path.join(angularRoot, 'angular.json')) &&
19+
!angularJsonExists &&
20+
!nxJsonExists &&
1321
netlifyConfig.build.publish &&
1422
netlifyConfig.build.publish !== angularRoot
1523
) {
1624
angularRoot = path.dirname(path.resolve(path.join(netlifyConfig.build.publish, '..', '..')))
25+
angularJsonExists = existsSync(path.join(angularRoot, 'angular.json'))
26+
nxJsonExists = existsSync(path.join(angularRoot, 'nx.json'))
1727

18-
if (!existsSync(path.join(angularRoot, 'angular.json'))) {
28+
if (!angularJsonExists && !nxJsonExists) {
1929
return failBuild(
20-
`Could not locate your angular.json at your project root or two levels above your publish directory. Make sure your publish directory is set to "{PATH_TO_YOUR_SITE}/dist/{YOUR_PROJECT_NAME}/browser", where {YOUR_PROJECT_NAME} is 'defaultProject' in your angular.json.`,
30+
`Could not locate your angular.json/nx.json at your project root or two levels above your publish directory. Make sure your publish directory is set to "{PATH_TO_YOUR_SITE}/dist/{YOUR_PROJECT_NAME}/browser", where {YOUR_PROJECT_NAME} is 'defaultProject' in your angular.json.`,
2131
)
2232
}
2333
}
24-
return angularRoot
34+
return { siteRoot: angularRoot, workspaceType: nxJsonExists ? 'nx' : 'default' }
2535
}
2636

2737
module.exports = getAngularRoot

src/helpers/knownServerTsSignatures.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@
44
"0e451aa946aca10c9d6782ac008748fcd39236d3ad1cc9868100a2981105e010": "CommonEngine",
55
"577f7bc87c16bd10bac499e228ef24d23dc4dd516e469b5db3eefae4edcf6345": "CommonEngine",
66
"5678601ed12556305074503967b44ae42c45c268579db057c25cbf4b21a7212e": "CommonEngine",
7-
"76419eb94b4b8672ba3bd79d34c5a66c7c30ff173995ecc6e0adc5808b86822d": "AppEngine"
7+
"33d360cdf4819d90afeecd49952241191ee490900fa919a46f990186be3e8b5f": "CommonEngine",
8+
"76419eb94b4b8672ba3bd79d34c5a66c7c30ff173995ecc6e0adc5808b86822d": "AppEngine",
9+
"a5aad843a116e34ce61264117cba981cff5eea3e6672815a4db08e7b4e5599d6": "AppEngine"
810
}

src/helpers/serverModuleHelpers.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,22 @@ const guessUsedEngine = function (serverModuleContents) {
9595
* @param {string} obj.angularVersion Angular version
9696
* @param {string} obj.siteRoot Root directory of an app
9797
* @param {(msg: string) => never} obj.failPlugin Function to fail the plugin
98+
* @param {'nx' | 'default'} obj.workspaceType The workspace type being parsed
99+
* @param {string} obj.packagePath The path to the package directory
98100
* * @param {(msg: string) => never} obj.failBuild Function to fail the build
99101
*
100102
* @returns {'AppEngine' | 'CommonEngine' | undefined}
101103
*/
102-
const fixServerTs = async function ({ angularVersion, siteRoot, failPlugin, failBuild }) {
104+
const fixServerTs = async function ({ angularVersion, siteRoot, failPlugin, failBuild, workspaceType, packagePath }) {
103105
if (!satisfies(angularVersion, '>=19.0.0-rc', { includePrerelease: true })) {
104106
// for pre-19 versions, we don't need to do anything
105107
return
106108
}
107109

108-
const angularJson = getAngularJson({ failPlugin, siteRoot })
110+
const angularJson = getAngularJson({ failPlugin, siteRoot, workspaceType, packagePath })
109111

110-
const project = getProject(angularJson, failBuild)
111-
const {
112-
architect: { build },
113-
} = project
112+
const project = getProject(angularJson, failBuild, workspaceType === 'nx')
113+
const build = workspaceType === 'nx' ? project.targets.build : project.architect.build
114114

115115
serverModuleLocation = build?.options?.ssr?.entry
116116
if (!serverModuleLocation || !existsSync(serverModuleLocation)) {
@@ -131,7 +131,7 @@ const fixServerTs = async function ({ angularVersion, siteRoot, failPlugin, fail
131131
)
132132
}
133133

134-
// check wether project is using stable CommonEngine or Developer Preview AppEngine
134+
// check whether project is using stable CommonEngine or Developer Preview AppEngine
135135
const serverModuleContents = await readFile(serverModuleLocation, 'utf8')
136136

137137
const usedEngineBasedOnKnownSignatures = getEngineBasedOnKnownSignatures(serverModuleContents)

src/helpers/setUpEdgeFunction.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@ const getAllFilesIn = (dir) =>
2222

2323
const toPosix = (path) => path.split(sep).join(posix.sep)
2424

25-
const getProject = (angularJson, failBuild) => {
25+
const getProject = (angularJson, failBuild, isNxWorkspace = false) => {
2626
const selectedProject = process.env.ANGULAR_PROJECT
27+
28+
if (isNxWorkspace) {
29+
return angularJson
30+
}
31+
2732
if (selectedProject) {
2833
const project = angularJson.projects[selectedProject]
2934
if (!project) {
@@ -98,7 +103,7 @@ const setUpEdgeFunction = async ({ outputDir, constants, failBuild, usedEngine }
98103
const document = Buffer.from(${JSON.stringify(
99104
Buffer.from(html, 'utf-8').toString('base64'),
100105
)}, 'base64').toString("utf-8");
101-
106+
102107
export default async (request, context) => {
103108
const html = await renderApplication(bootstrap, {
104109
url: request.url,
@@ -155,7 +160,7 @@ const setUpEdgeFunction = async ({ outputDir, constants, failBuild, usedEngine }
155160
// reading file is needed for inlining CSS, but failing to do so is
156161
// not causing fatal error so we just ignore it here
157162
}
158-
163+
159164
return originalReadFile.apply(globalThis.Deno, args)
160165
}
161166
} catch {
@@ -194,7 +199,7 @@ const setUpEdgeFunction = async ({ outputDir, constants, failBuild, usedEngine }
194199
ssrFunctionContent = /* javascript */ `
195200
import { netlifyAppEngineHandler } from "${toPosix(relative(edgeFunctionDir, serverDistRoot))}/server.mjs";
196201
import "./fixup-event.mjs";
197-
202+
198203
export default netlifyAppEngineHandler;
199204
`
200205
}

src/index.js

+15-8
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ let usedEngine
1616
module.exports = {
1717
async onPreBuild({ netlifyConfig, utils, constants }) {
1818
const { failBuild, failPlugin } = utils.build
19-
const siteRoot = getAngularRoot({ failBuild, netlifyConfig })
19+
const { siteRoot, workspaceType } = getAngularRoot({ failBuild, netlifyConfig })
2020
const angularVersion = await getAngularVersion(siteRoot)
2121
isValidAngularProject = validateAngularVersion(angularVersion)
2222

@@ -36,9 +36,18 @@ module.exports = {
3636
PUBLISH_DIR: constants.PUBLISH_DIR,
3737
IS_LOCAL: constants.IS_LOCAL,
3838
netlifyConfig,
39+
workspaceType,
40+
packagePath: constants.PACKAGE_PATH,
3941
})
4042

41-
usedEngine = await fixServerTs({ angularVersion, siteRoot, failPlugin, failBuild })
43+
usedEngine = await fixServerTs({
44+
angularVersion,
45+
siteRoot,
46+
failPlugin,
47+
failBuild,
48+
workspaceType,
49+
packagePath: constants.PACKAGE_PATH,
50+
})
4251
},
4352
async onBuild({ utils, netlifyConfig, constants }) {
4453
await revertServerTsFix()
@@ -48,13 +57,11 @@ module.exports = {
4857

4958
const { failBuild, failPlugin } = utils.build
5059

51-
const siteRoot = getAngularRoot({ failBuild, netlifyConfig })
52-
const angularJson = getAngularJson({ failPlugin, siteRoot })
60+
const { siteRoot, workspaceType } = getAngularRoot({ failBuild, netlifyConfig, onBuild: true })
61+
const angularJson = getAngularJson({ failPlugin, siteRoot, workspaceType, packagePath: constants.PACKAGE_PATH })
5362

54-
const project = getProject(angularJson, failBuild)
55-
const {
56-
architect: { build },
57-
} = project
63+
const project = getProject(angularJson, failBuild, workspaceType === 'nx')
64+
const build = workspaceType === 'nx' ? project.targets.build : project.architect.build
5865
const outputDir = build?.options?.outputPath
5966
if (!outputDir || !existsSync(outputDir)) {
6067
return failBuild('Could not find build output directory')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Editor configuration, see https://editorconfig.org
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
indent_style = space
7+
indent_size = 2
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.ts]
12+
quote_type = single
13+
ij_typescript_use_double_quotes = false
14+
15+
[*.md]
16+
max_line_length = off
17+
trim_trailing_whitespace = false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
2+
3+
# Compiled output
4+
/dist
5+
/tmp
6+
/out-tsc
7+
/bazel-out
8+
9+
# Node
10+
/node_modules
11+
npm-debug.log
12+
yarn-error.log
13+
14+
# IDEs and editors
15+
.idea/
16+
.project
17+
.classpath
18+
.c9/
19+
*.launch
20+
.settings/
21+
*.sublime-workspace
22+
23+
# Visual Studio Code
24+
.vscode/*
25+
!.vscode/settings.json
26+
!.vscode/tasks.json
27+
!.vscode/launch.json
28+
!.vscode/extensions.json
29+
.history/*
30+
31+
# Miscellaneous
32+
/.angular/cache
33+
.sass-cache/
34+
/connect.lock
35+
/coverage
36+
/libpeerconnection.log
37+
testem.log
38+
/typings
39+
40+
# System files
41+
.DS_Store
42+
Thumbs.db
43+
44+
45+
46+
.nx/cache
47+
.nx/workspace-data
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Add files here to ignore them from prettier formatting
2+
/dist
3+
/coverage
4+
/.nx/cache
5+
/.nx/workspace-data
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"singleQuote": true
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"recommendations": [
3+
"angular.ng-template",
4+
"nrwl.angular-console",
5+
"dbaeumer.vscode-eslint",
6+
"esbenp.prettier-vscode"
7+
]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3+
"version": "0.2.0",
4+
"configurations": [
5+
{
6+
"name": "ng serve",
7+
"type": "chrome",
8+
"request": "launch",
9+
"preLaunchTask": "npm: start",
10+
"url": "http://localhost:4200/"
11+
},
12+
{
13+
"name": "ng test",
14+
"type": "chrome",
15+
"request": "launch",
16+
"preLaunchTask": "npm: test",
17+
"url": "http://localhost:9876/debug.html"
18+
}
19+
]
20+
}

0 commit comments

Comments
 (0)