Skip to content

Commit

Permalink
postinstall script: support monorepos (#191)
Browse files Browse the repository at this point in the history
This happens in monorepo setups where `cds-types` is alreday installed.

---------

Co-authored-by: Jörg Mann <[email protected]>
Co-authored-by: Jörg Mann <[email protected]>
Co-authored-by: Daniel O'Grady <[email protected]>
  • Loading branch information
4 people authored Aug 13, 2024
1 parent 5bbc5c4 commit 4bcc7f2
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 21 deletions.
34 changes: 24 additions & 10 deletions .github/actions/run-as-non-admin/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,43 @@ runs:
# fail if not on windows
if ($env:OS -ne "Windows") { exit 1 }
# make temp folder writable for all users
icacls $env:TEMP /grant "Everyone:(OI)(CI)F"
$username = "nonadminuser"
# random password fulfilling win requirements
$password = ConvertTo-SecureString "abcdEFGH123$%" -AsPlainText -Force
$newHomeDir = "C:\Users\$username"
New-LocalUser $username -Password $password
Add-LocalGroupMember -Group "Users" -Member $username
New-LocalUser $username -Password $password | Out-Null
Add-LocalGroupMember -Group "Users" -Member $username | Out-Null
$credential = New-Object System.Management.Automation.PSCredential ($username, $password)
# remove dev mode so symlink fails if called without junction
reg delete "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /f
# call command using non admin user
# create temp folder
New-Item -ItemType Directory -Path "$newHomeDir\AppData\Local\Temp" -Force
# make temp folder writable for nonadmin user
icacls "$newHomeDir" /grant "${username}:(OI)(CI)F" /T
# using start-process to run command as non admin user requires setting env vars
$envVars = @{
HOME = $newHomeDir
HOMEPATH = "\Users\$username"
TEMP = "$newHomeDir\AppData\Local\Temp"
TMP = "$newHomeDir\AppData\Local\Temp"
USERNAME = $username
USERPROFILE = $newHomeDir
}
# call command using non admin user credentials
$process = Start-Process -FilePath "pwsh" `
-ArgumentList "-NoLogo", "-NonInteractive", "-NoProfile", "-Command", $env:RUN `
-Credential $credential `
-PassThru `
-Wait `
-Environment $envVars `
-NoNewWindow `
-RedirectStandardOutput "output.txt" `
-PassThru `
-RedirectStandardError "error.txt" `
-RedirectStandardOutput "output.txt" `
-Wait `
Get-Content output.txt
Get-Content error.txt
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
node-version: ${{ matrix.version }}

- run: |
npm i -g npm
npm ci
npm install file:. --no-save --force
npm run prerelease:ci-fix
Expand All @@ -48,6 +49,8 @@ jobs:
with:
run: |
echo "whoami:$(whoami)"
echo "home:$HOME"
echo "userprofile:$USERPROFILE"
npm run test:integration
- name: Run integration tests
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
The format is based on [Keep a Changelog](http://keepachangelog.com/).

## Version 0.7.0 - TBD
## Version 0.6.5 - TBD
### Fixed
- The `@types/sap__cds` link created by the `postinstall` script now also works in monorepo setups where the target `@cap-js/cds-types` might already preinstalled (often hoisted some levels up).

## Version 0.6.4 - 2024-08-05
### Added
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cap-js/cds-types",
"version": "0.6.4",
"version": "0.6.5",
"description": "Type definitions for main packages of CAP, like `@sap/cds`",
"repository": "github:cap-js/cds-types",
"homepage": "https://cap.cloud.sap/",
Expand All @@ -20,7 +20,7 @@
],
"scripts": {
"test": "jest --silent",
"test:integration": "jest --silent --testMatch \"**/test/**/*.integrationtest.js\"",
"test:integration": "jest --testMatch \"**/test/**/*.integrationtest.js\"",
"test:rollup": "npm run rollup; npm run rollup:on; npm run test; npm run rollup:off",
"rollup": "rm -rf dist/ && mkdir -p etc/ && npx -y @microsoft/api-extractor run --local --verbose && .github/rollup-patch.js",
"rollup:on": "npm pkg set typings=dist/cds-types.d.ts && [ -d 'apis' ] && mv -- apis -apis || true",
Expand Down
20 changes: 15 additions & 5 deletions scripts/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-require-imports */
const fs = require('node:fs')
const { join, relative, dirname } = require('node:path')
const { join, relative, dirname, resolve } = require('node:path')

if (!process.env.INIT_CWD) return
// TODO: check if were in a local install

const nodeModules = join(process.env.INIT_CWD, 'node_modules')
if (!fs.existsSync(nodeModules)) return
const typesDir = join(nodeModules, '@types')
if (!fs.existsSync(typesDir)) fs.mkdirSync(typesDir)
// we may have to create node_modules altogether in case of a mono repo
if (!fs.existsSync(typesDir)) fs.mkdirSync(typesDir, {recursive: true})

// use a relative target, in case the user moves the project
const target = join(typesDir, 'sap__cds')
const src = join(nodeModules, '@cap-js/cds-types')
const src = resolvePkg('@cap-js/cds-types') ?? join(nodeModules, '@cap-js/cds-types')
const rel = relative(dirname(target), src) // need dirname or we'd land one level above node_modules (one too many "../")
console.log(`Creating symlink ${target} -> ${rel}`)

// remove the existing symlink
try {
Expand All @@ -31,3 +32,12 @@ try {
if (e.code !== 'EEXIST') throw e
// else: symlink exists (the previous unlink hasn't worked), ignore
}

function resolvePkg(pkg) {
try {
const pjson = require.resolve(join(pkg, 'package.json'), { paths: [process.env.INIT_CWD] })
return resolve(pjson, '..')
} catch {
return null
}
}
43 changes: 42 additions & 1 deletion test/postinstall.integrationtest.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('postinstall', () => {

beforeEach(async () => {
tempFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'postinstall-'))
// console.log(`tempFolder: ${tempFolder}`)
})

afterEach(async () => {
Expand All @@ -39,12 +40,52 @@ describe('postinstall', () => {

// after renaming the project folder, the symlink must be recreated on windows
if (IS_WIN) {
await execAsync('npm i', { cwd: newProjectFolder })
await execAsync('npm i --foreground-scripts', { cwd: newProjectFolder })
}

typesPackageJsonFile = path.join(newProjectFolder, 'node_modules/@types/sap__cds/package.json')
typesPackageJsonFileContent = await fs.readFile(typesPackageJsonFile, 'utf8')
packageJson = JSON.parse(typesPackageJsonFileContent)
expect(packageJson.name).toBe('@cap-js/cds-types')
})

test('create symlink in monorepo', async () => {
const rootFolder = path.join(tempFolder, 'monorepo')
await fs.mkdir(rootFolder, { recursive: true, force: true })
await fs.writeFile(path.join(rootFolder, 'package.json'), JSON.stringify({
name: 'monorepo', workspaces: [ 'packages/**' ]
}, null, 2))

// create a first project, add the dependency to cds-types
const project1 = path.join(rootFolder, 'packages/project1')
await fs.mkdir(project1, { recursive: true, force: true })
await fs.writeFile(path.join(project1, 'package.json'), JSON.stringify({
name: 'project1'
}, null, 2))
{
// const {stdout, stderr} =
await execAsync(`npm i --foreground-scripts -dd -D ${cdsTypesRoot}`, { cwd: project1 })
// console.log(stdout, stderr)
}
let packageJson = require(path.join(project1, 'node_modules/@types/sap__cds/package.json'))
expect(packageJson.name).toBe('@cap-js/cds-types')

// now create a second project with the dependency
const project2 = path.join(rootFolder, 'packages/project2')
await fs.mkdir(project2, { recursive: true, force: true })
await fs.writeFile(path.join(project2, 'package.json'), JSON.stringify({
name: 'project2',
devDependencies: {
'@cap-js/cds-types': `file:${cdsTypesRoot}`
}
}, null, 2))
{
// const {stdout, stderr} =
await execAsync(`npm i --foreground-scripts -dd`, { cwd: project2 })
// console.log(stdout, stderr)
}
packageJson = require(path.join(project2, 'node_modules/@types/sap__cds/package.json'))
expect(packageJson.name).toBe('@cap-js/cds-types')

})
})

0 comments on commit 4bcc7f2

Please sign in to comment.