From 4bcc7f24d0cda3a58fe6c4abdf1295e4d2501dc9 Mon Sep 17 00:00:00 2001 From: Christian Georgi Date: Tue, 13 Aug 2024 15:22:26 +0200 Subject: [PATCH] `postinstall` script: support monorepos (#191) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This happens in monorepo setups where `cds-types` is alreday installed. --------- Co-authored-by: Jörg Mann <64193442+joergmann@users.noreply.github.com> Co-authored-by: Jörg Mann Co-authored-by: Daniel O'Grady <103028279+daogrady@users.noreply.github.com> --- .github/actions/run-as-non-admin/action.yml | 34 +++++++++++----- .github/workflows/integration-test.yml | 3 ++ CHANGELOG.md | 4 +- package-lock.json | 4 +- package.json | 4 +- scripts/postinstall.js | 20 +++++++--- test/postinstall.integrationtest.js | 43 ++++++++++++++++++++- 7 files changed, 91 insertions(+), 21 deletions(-) diff --git a/.github/actions/run-as-non-admin/action.yml b/.github/actions/run-as-non-admin/action.yml index d67a7e15..20b0d338 100644 --- a/.github/actions/run-as-non-admin/action.yml +++ b/.github/actions/run-as-non-admin/action.yml @@ -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 diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 537c2760..f5903764 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -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 @@ -48,6 +49,8 @@ jobs: with: run: | echo "whoami:$(whoami)" + echo "home:$HOME" + echo "userprofile:$USERPROFILE" npm run test:integration - name: Run integration tests diff --git a/CHANGELOG.md b/CHANGELOG.md index bcb1fd23..3fe8b05d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/package-lock.json b/package-lock.json index ddd728c0..a0103eaa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cap-js/cds-types", - "version": "0.6.4", + "version": "0.6.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cap-js/cds-types", - "version": "0.6.4", + "version": "0.6.5", "hasInstallScript": true, "license": "SEE LICENSE IN LICENSE", "dependencies": { diff --git a/package.json b/package.json index 23b914bb..d77dd708 100644 --- a/package.json +++ b/package.json @@ -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/", @@ -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", diff --git a/scripts/postinstall.js b/scripts/postinstall.js index fdca08a3..2f1a8235 100755 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -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 { @@ -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 + } +} diff --git a/test/postinstall.integrationtest.js b/test/postinstall.integrationtest.js index e8a4d75c..c31d38a6 100644 --- a/test/postinstall.integrationtest.js +++ b/test/postinstall.integrationtest.js @@ -16,6 +16,7 @@ describe('postinstall', () => { beforeEach(async () => { tempFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'postinstall-')) + // console.log(`tempFolder: ${tempFolder}`) }) afterEach(async () => { @@ -39,7 +40,7 @@ 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') @@ -47,4 +48,44 @@ describe('postinstall', () => { 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') + + }) })