Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import type { IConstruct } from 'constructs';
Expand Down Expand Up @@ -286,9 +287,17 @@ export class Bundling implements cdk.BundlingOptions {
const isPnpm = this.packageManager.lockFile === LockFile.PNPM;
const isBun = this.packageManager.lockFile === LockFile.BUN_LOCK || this.packageManager.lockFile === LockFile.BUN;

// Copy .npmrc for pnpm to inherit private registry auth. Uses base64 to handle monorepos
// where .npmrc is outside projectRoot (inaccessible in Docker) and to avoid shell escaping issues.
const npmrcFilePath = isPnpm ? findUp('.npmrc', this.projectRoot) : undefined;
const npmrcBase64 = npmrcFilePath && fs.existsSync(npmrcFilePath)
? Buffer.from(fs.readFileSync(npmrcFilePath, 'utf-8')).toString('base64')
: undefined;

// Create dummy package.json, copy lock file if any and then install
depsCommand = chain([
isPnpm ? osCommand.write(pathJoin(options.outputDir, 'pnpm-workspace.yaml'), ''): '', // Ensure node_modules directory is installed locally by creating local 'pnpm-workspace.yaml' file
npmrcBase64 ? osCommand.writeBase64(pathJoin(options.outputDir, '.npmrc'), npmrcBase64) : '', // Write .npmrc for pnpm to inherit registry authentication
osCommand.writeJson(pathJoin(options.outputDir, 'package.json'), { dependencies }),
osCommand.copy(lockFilePath, pathJoin(options.outputDir, this.packageManager.lockFile)),
osCommand.changeDirectory(options.outputDir),
Expand Down Expand Up @@ -386,6 +395,13 @@ class OsCommand {
return this.write(filePath, stringifiedData);
}

public writeBase64(filePath: string, base64Data: string): string {
if (this.osPlatform === 'win32') {
return `powershell -Command "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${base64Data}')) | Set-Content -Path '${filePath}' -NoNewline"`;
}
return `echo '${base64Data}' | base64 -d > "${filePath}"`;
}

public copy(src: string, dest: string): string {
if (this.osPlatform === 'win32') {
return `copy "${src}" "${dest}"`;
Expand Down
1 change: 1 addition & 0 deletions packages/aws-cdk-lib/aws-lambda-nodejs/test/.testnpmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@myorg:registry=https://registry.example.com/
65 changes: 65 additions & 0 deletions packages/aws-cdk-lib/aws-lambda-nodejs/test/bundling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,71 @@ test('Detects pnpm-lock.yaml', () => {
});
});

test('pnpm with nodeModules writes .npmrc using base64 for private registry authentication', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. Can you please add coverage for the win32 platform?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Added and re-based.

const pnpmLock = '/project/pnpm-lock.yaml';
const npmrcFixture = path.join(__dirname, '.testnpmrc');
const npmrcBase64 = Buffer.from(fs.readFileSync(npmrcFixture, 'utf-8')).toString('base64');

const originalFindUp = util.findUp;
jest.spyOn(util, 'findUp').mockImplementation((name, dir) =>
name === '.npmrc' ? npmrcFixture : originalFindUp(name, dir),
);

Bundling.bundle(stack, {
entry: __filename,
projectRoot,
depsLockFilePath: pnpmLock,
runtime: STANDARD_RUNTIME,
architecture: Architecture.X86_64,
nodeModules: ['delay'],
forceDockerBundling: true,
});

expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(pnpmLock), {
assetHashType: AssetHashType.OUTPUT,
bundling: expect.objectContaining({
command: expect.arrayContaining([
expect.stringContaining(`echo '${npmrcBase64}' | base64 -d`),
]),
}),
});
});

test('pnpm with nodeModules writes .npmrc using base64 for private registry authentication on win32', () => {
const osPlatformMock = jest.spyOn(os, 'platform').mockReturnValue('win32');
const pnpmLock = 'C:\\project\\pnpm-lock.yaml';
const npmrcFixture = path.join(__dirname, '.testnpmrc');
const npmrcBase64 = Buffer.from(fs.readFileSync(npmrcFixture, 'utf-8')).toString('base64');

const originalFindUp = util.findUp;
jest.spyOn(util, 'findUp').mockImplementation((name, dir) =>
name === '.npmrc' ? npmrcFixture : originalFindUp(name, dir),
);
jest.spyOn(path, 'basename').mockReturnValueOnce('pnpm-lock.yaml');
jest.spyOn(path, 'relative').mockReturnValueOnce('test\\bundling.test.ts').mockReturnValueOnce('pnpm-lock.yaml');

Bundling.bundle(stack, {
entry: __filename,
projectRoot: 'C:\\project',
depsLockFilePath: pnpmLock,
runtime: STANDARD_RUNTIME,
architecture: Architecture.X86_64,
nodeModules: ['delay'],
forceDockerBundling: true,
});

expect(Code.fromAsset).toHaveBeenCalledWith(expect.any(String), {
assetHashType: AssetHashType.OUTPUT,
bundling: expect.objectContaining({
command: expect.arrayContaining([
expect.stringContaining(`echo '${npmrcBase64}' | base64 -d`),

@lpizzinidev lpizzinidev Feb 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird. I'd expect this to be powershell -Command .... I suspect we should

Bundling.bundle(stack, { platform: 'win32', ... });

Instead of spyOn(os, 'platform')?
Am I missing something?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's completely my fault for rushing this and trying to copy the test from 158. I'll try and fix this today. Thanks for catching it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. So digging into this, the BundlingProps interface doesn't currently expose a platform option - the platform is detected at runtime via os.platform(). The existing win32 test ("esbuild with Windows paths") uses the same jest.spyOn(os, 'platform') pattern.

The reason I missed the bash thing is because only local bundling on Windows will run the PowerShell command, in Docker it will always be bash. I'll try and make a cheeky test by calling tryBundle() directly to force local bundling and mock spawnSync to capture the cmd /c call.

]),
}),
});

osPlatformMock.mockRestore();
});

test('Detects bun.lockb', () => {
const bunLock = path.join(__dirname, '..', 'bun.lockb');
Bundling.bundle(stack, {
Expand Down
Loading