Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions packages/kernel-utils/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add an optional `required` field to `MethodSchema` (mirroring `required` on object `JsonSchema`) naming which arguments are required, and a `{ required }` option on `methodArgsToStruct` that validates unlisted arguments as optional, so a method's argument schema can faithfully represent the optional trailing arguments its guard already allows ([#958](https://github.com/MetaMask/ocap-kernel/pull/958))
- Add `getLibp2pRelayHome()` to the `./nodejs` exports, returning the libp2p relay's bookkeeping directory (default `~/.libp2p-relay`, overridable via `$LIBP2P_RELAY_HOME`) — kept separate from `$OCAP_HOME` so one relay can serve daemons with different OCAP_HOMEs ([#952](https://github.com/MetaMask/ocap-kernel/pull/952))
- `startRelay()` accepts an optional `publicIp` that is fed to libp2p's `appendAnnounce`, so a relay running on a NAT-backed host can announce its publicly-reachable IPv4 alongside its bound NIC addresses ([#952](https://github.com/MetaMask/ocap-kernel/pull/952))
- Add `replaceNodeEnvPlugin` to the `./vite-plugins` exports, a Rolldown transform plugin that inlines `process.env.NODE_ENV` as a string literal (configurable via `{ value }`, defaulting to `'production'`) in any module referencing it — used by `bundleVat` in place of a build-config `define`, so vats that pull in libraries like immer resolve the reference at bundle time ([#967](https://github.com/MetaMask/ocap-kernel/pull/967))

## [0.5.0]

Expand Down
8 changes: 2 additions & 6 deletions packages/kernel-utils/src/vite-plugins/bundle-vat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { build } from 'vite';

import type { VatBundle } from '../vat-bundle.ts';
import { exportMetadataPlugin } from './export-metadata-plugin.ts';
import { replaceNodeEnvPlugin } from './replace-node-env-plugin.ts';
import { stripCommentsPlugin } from './strip-comments-plugin.ts';

export type { VatBundle } from '../vat-bundle.ts';
Expand Down Expand Up @@ -88,12 +89,6 @@ export async function bundleVat(sourcePath: string): Promise<VatBundle> {
result = await build({
configFile: false,
logLevel: 'silent',
// TODO: Remove this define block and add a process shim to VatSupervisor
// workerEndowments instead. This injects into ALL bundles but is only needed
// for libraries like immer that check process.env.NODE_ENV.
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
},
build: {
write: false,
minify: false,
Expand All @@ -109,6 +104,7 @@ export async function bundleVat(sourcePath: string): Promise<VatBundle> {
},
plugins: [
removeDynamicImportsPlugin(),
replaceNodeEnvPlugin(),
stripCommentsPlugin(),
metadataPlugin,
],
Expand Down
2 changes: 2 additions & 0 deletions packages/kernel-utils/src/vite-plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { bundleVat } from './bundle-vat.ts';

export { bundleVat, removeDynamicImportsPlugin } from './bundle-vat.ts';
export type { VatBundle } from './bundle-vat.ts';
export { replaceNodeEnvPlugin } from './replace-node-env-plugin.ts';
export type { ReplaceNodeEnvPluginOptions } from './replace-node-env-plugin.ts';

type VatEntry = {
/** Absolute path to the vat source file */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, it, expect } from 'vitest';

import { replaceNodeEnvPlugin } from './replace-node-env-plugin.ts';

type TransformFn = (
code: string,
id: string,
) => { code: string; map: null } | null;

describe('replaceNodeEnvPlugin', () => {
const plugin = replaceNodeEnvPlugin();
const transform = plugin.transform as TransformFn;

it.each([
['bare reference', `process.env.NODE_ENV`, `"production"`],
[
'reference in a conditional',
`if (process.env.NODE_ENV !== 'production') { warn(); }`,
`if ("production" !== 'production') { warn(); }`,
],
[
'multiple references in one file',
`const a = process.env.NODE_ENV;\nconst b = process.env.NODE_ENV;`,
`const a = "production";\nconst b = "production";`,
],
[
'reference surrounded by other code',
`const x = 1;\nexport const mode = process.env.NODE_ENV;\nconst y = 2;`,
`const x = 1;\nexport const mode = "production";\nconst y = 2;`,
],
])(
'replaces %s with the "production" string literal',
(_name, code, expected) => {
const result = transform(code, 'test.ts');
expect(result).toStrictEqual({ code: expected, map: null });
},
);

it('injects a quoted string literal, not the bare word', () => {
const result = transform(`const mode = process.env.NODE_ENV;`, 'test.ts');
expect(result?.code).toContain(`"production"`);
expect(result?.code).not.toContain(`process.env.NODE_ENV`);
expect(result?.code).not.toContain(`= production;`);
});

it.each([
['no process reference', 'const x = 1;'],
['unrelated process.env key', `const port = process.env.PORT;`],
['empty code', ''],
])('returns null for %s', (_name, code) => {
expect(transform(code, 'test.ts')).toBeNull();
});

it('returns null when the substring matches but the word boundary does not', () => {
const code = `const x = process.env.NODE_ENVIRONMENT;`;
expect(transform(code, 'test.ts')).toBeNull();
});

it('inlines a configured value instead of the default', () => {
const devTransform = replaceNodeEnvPlugin({ value: 'development' })
.transform as TransformFn;
const result = devTransform(
`const mode = process.env.NODE_ENV;`,
'test.ts',
);
expect(result).toStrictEqual({
code: `const mode = "development";`,
map: null,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Plugin as RolldownPlugin } from 'rolldown';

export type ReplaceNodeEnvPluginOptions = {
/**
* The value to inline for `process.env.NODE_ENV`. Defaults to `'production'`.
*/
value?: string;
};

/**
* A Rolldown plugin that inlines `process.env.NODE_ENV` as a string literal in
* any module that references it.
*
* This replaces the former build-config `define` (issue #812). Libraries such
* as immer branch on `process.env.NODE_ENV`, and vats have no `process` global
* at runtime, so the reference must be resolved at bundle time. The plugin
* auto-detects the need: modules without the reference are left untouched.
*
* This is a textual replacement (the same class of approach as the sibling
* {@link removeDynamicImportsPlugin}) and handles the dotted
* `process.env.NODE_ENV` form, consistent with the `define` it replaces.
*
* @param options - Plugin options.
* @param options.value - The value to inline for `process.env.NODE_ENV`.
* Defaults to `'production'`.
* @returns A Rolldown plugin.
*/
export function replaceNodeEnvPlugin({
value = 'production',
}: ReplaceNodeEnvPluginOptions = {}): RolldownPlugin {
const replacement = JSON.stringify(value);
return {
name: 'ocap-kernel:replace-node-env',
transform(code) {
if (!code.includes('process.env.NODE_ENV')) {
return null;
}

const transformed = code.replace(
/\bprocess\.env\.NODE_ENV\b/gu,
replacement,
);

if (transformed === code) {
return null;
}

return { code: transformed, map: null };
},
};
}
Loading