Skip to content

Commit 92e3427

Browse files
committed
feat: adds support for detecting NX monorepos
1 parent f5b8803 commit 92e3427

Some content is hidden

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

77 files changed

+46414
-30
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,24 @@ 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+
}) {
15+
const angularJson = getAngularJson({ failPlugin, siteRoot, workspaceType })
16+
const project = getProject(angularJson, failBuild, workspaceType === 'nx')
917

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

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

src/helpers/getAngularJson.js

Lines changed: 23 additions & 1 deletion
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,31 @@ 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
1012
*
1113
* @returns {any}
1214
*/
13-
const getAngularJson = function ({ failPlugin, siteRoot }) {
15+
const getAngularJson = function ({ failPlugin, siteRoot, workspaceType }) {
16+
if (workspaceType === 'nx') {
17+
const selectedProject = process.env.ANGULAR_PROJECT
18+
19+
if (!selectedProject) {
20+
return failPlugin(
21+
`The ANGULAR_PROJECT environment variable is not set. This is needed to determine the project.json to load in an NX workspace`,
22+
)
23+
}
24+
25+
if (!existsSync(join(siteRoot, 'apps', selectedProject, 'project.json'))) {
26+
return failPlugin(`No project.json found in apps/${selectedProject}`)
27+
}
28+
29+
try {
30+
return readJsonSync(join(siteRoot, 'apps', selectedProject, 'project.json'))
31+
} catch {
32+
return failPlugin(`Could not parse the contents of project.json`)
33+
}
34+
}
35+
1436
if (!existsSync(join(siteRoot, 'angular.json'))) {
1537
return failPlugin(`No angular.json found at project root`)
1638
}

src/helpers/getAngularRoot.js

Lines changed: 14 additions & 4 deletions
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

Lines changed: 3 additions & 1 deletion
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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,21 @@ 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
9899
* * @param {(msg: string) => never} obj.failBuild Function to fail the build
99100
*
100101
* @returns {'AppEngine' | 'CommonEngine' | undefined}
101102
*/
102-
const fixServerTs = async function ({ angularVersion, siteRoot, failPlugin, failBuild }) {
103+
const fixServerTs = async function ({ angularVersion, siteRoot, failPlugin, failBuild, workspaceType }) {
103104
if (!satisfies(angularVersion, '>=19.0.0-rc', { includePrerelease: true })) {
104105
// for pre-19 versions, we don't need to do anything
105106
return
106107
}
107108

108-
const angularJson = getAngularJson({ failPlugin, siteRoot })
109+
const angularJson = getAngularJson({ failPlugin, siteRoot, workspaceType })
109110

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

115114
serverModuleLocation = build?.options?.ssr?.entry
116115
if (!serverModuleLocation || !existsSync(serverModuleLocation)) {
@@ -131,7 +130,7 @@ const fixServerTs = async function ({ angularVersion, siteRoot, failPlugin, fail
131130
)
132131
}
133132

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

137136
const usedEngineBasedOnKnownSignatures = getEngineBasedOnKnownSignatures(serverModuleContents)

src/helpers/setUpEdgeFunction.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,17 @@ 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+
if (!selectedProject) {
30+
return failBuild(``)
31+
}
32+
33+
return angularJson
34+
}
35+
2736
if (selectedProject) {
2837
const project = angularJson.projects[selectedProject]
2938
if (!project) {
@@ -98,7 +107,7 @@ const setUpEdgeFunction = async ({ outputDir, constants, failBuild, usedEngine }
98107
const document = Buffer.from(${JSON.stringify(
99108
Buffer.from(html, 'utf-8').toString('base64'),
100109
)}, 'base64').toString("utf-8");
101-
110+
102111
export default async (request, context) => {
103112
const html = await renderApplication(bootstrap, {
104113
url: request.url,
@@ -155,7 +164,7 @@ const setUpEdgeFunction = async ({ outputDir, constants, failBuild, usedEngine }
155164
// reading file is needed for inlining CSS, but failing to do so is
156165
// not causing fatal error so we just ignore it here
157166
}
158-
167+
159168
return originalReadFile.apply(globalThis.Deno, args)
160169
}
161170
} catch {
@@ -194,7 +203,7 @@ const setUpEdgeFunction = async ({ outputDir, constants, failBuild, usedEngine }
194203
ssrFunctionContent = /* javascript */ `
195204
import { netlifyAppEngineHandler } from "${toPosix(relative(edgeFunctionDir, serverDistRoot))}/server.mjs";
196205
import "./fixup-event.mjs";
197-
206+
198207
export default netlifyAppEngineHandler;
199208
`
200209
}

src/index.js

Lines changed: 7 additions & 8 deletions
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,10 @@ module.exports = {
3636
PUBLISH_DIR: constants.PUBLISH_DIR,
3737
IS_LOCAL: constants.IS_LOCAL,
3838
netlifyConfig,
39+
workspaceType,
3940
})
4041

41-
usedEngine = await fixServerTs({ angularVersion, siteRoot, failPlugin, failBuild })
42+
usedEngine = await fixServerTs({ angularVersion, siteRoot, failPlugin, failBuild, workspaceType })
4243
},
4344
async onBuild({ utils, netlifyConfig, constants }) {
4445
await revertServerTsFix()
@@ -48,13 +49,11 @@ module.exports = {
4849

4950
const { failBuild, failPlugin } = utils.build
5051

51-
const siteRoot = getAngularRoot({ failBuild, netlifyConfig })
52-
const angularJson = getAngularJson({ failPlugin, siteRoot })
52+
const { siteRoot, workspaceType } = getAngularRoot({ failBuild, netlifyConfig, onBuild: true })
53+
const angularJson = getAngularJson({ failPlugin, siteRoot, workspaceType })
5354

54-
const project = getProject(angularJson, failBuild)
55-
const {
56-
architect: { build },
57-
} = project
55+
const project = getProject(angularJson, failBuild, workspaceType === 'nx')
56+
const build = workspaceType === 'nx' ? project.targets.build : project.architect.build
5857
const outputDir = build?.options?.outputPath
5958
if (!outputDir || !existsSync(outputDir)) {
6059
return failBuild('Could not find build output directory')
Lines changed: 17 additions & 0 deletions
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
Lines changed: 47 additions & 0 deletions
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
Lines changed: 5 additions & 0 deletions
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
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"singleQuote": true
3+
}
Lines changed: 8 additions & 0 deletions
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+
}
Lines changed: 20 additions & 0 deletions
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)