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
112 changes: 112 additions & 0 deletions apps/cli/scripts/buildSharedDeps.contract.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { normalize, resolve } from 'node:path';

import {
bundledWorkspacePackages,
resolveBundledWorkspaceBuildEntry,
syncBundledWorkspaceDist,
} from './buildSharedDeps.mjs';
import { createBundledWorkspaceBundles } from './workspaceBundleManifest.mjs';

test('buildSharedDeps builds every bundled workspace package needed by the published CLI', () => {
assert.deepEqual(bundledWorkspacePackages, ['agents', 'cli-common', 'protocol', 'release-runtime']);
});

test('workspace bundle manifest derives bundle metadata from a single package list', () => {
const repoRoot = resolve('repo');
const targetRoot = resolve(repoRoot, 'apps', 'cli');

assert.deepEqual(createBundledWorkspaceBundles({ repoRoot, targetRoot }), [
{
packageName: '@happier-dev/agents',
srcDir: resolve(repoRoot, 'packages', 'agents'),
destDir: resolve(targetRoot, 'node_modules', '@happier-dev', 'agents'),
},
{
packageName: '@happier-dev/cli-common',
srcDir: resolve(repoRoot, 'packages', 'cli-common'),
destDir: resolve(targetRoot, 'node_modules', '@happier-dev', 'cli-common'),
},
{
packageName: '@happier-dev/protocol',
srcDir: resolve(repoRoot, 'packages', 'protocol'),
destDir: resolve(targetRoot, 'node_modules', '@happier-dev', 'protocol'),
},
{
packageName: '@happier-dev/release-runtime',
srcDir: resolve(repoRoot, 'packages', 'release-runtime'),
destDir: resolve(targetRoot, 'node_modules', '@happier-dev', 'release-runtime'),
},
]);
});

test('buildSharedDeps verifies each package using its declared main entry when available', () => {
const repoRoot = resolve('repo');

assert.equal(
resolveBundledWorkspaceBuildEntry('release-runtime', {
repoRoot,
readFileSync: () => JSON.stringify({ main: './dist/github.js' }),
}),
resolve(repoRoot, 'packages', 'release-runtime', 'dist', 'github.js'),
);

assert.equal(
resolveBundledWorkspaceBuildEntry('release-runtime', {
repoRoot,
readFileSync: () => JSON.stringify({}),
}),
resolve(repoRoot, 'packages', 'release-runtime', 'dist', 'index.js'),
);
});

test('syncBundledWorkspaceDist defaults include release-runtime', () => {
const copyCalls = [];
const writeCalls = [];
const repoRoot = resolve('repo');
const releaseRuntimeDist = resolve(repoRoot, 'apps', 'cli', 'node_modules', '@happier-dev', 'release-runtime', 'dist');
const releaseRuntimePackageJson = resolve(
repoRoot,
'apps',
'cli',
'node_modules',
'@happier-dev',
'release-runtime',
'package.json',
);
const releaseRuntimeSourceDist = resolve(repoRoot, 'packages', 'release-runtime', 'dist');
const toPlatformPath = (path) => normalize(String(path));

syncBundledWorkspaceDist({
repoRoot,
existsSync: (candidate) =>
[releaseRuntimeDist, releaseRuntimePackageJson].includes(toPlatformPath(candidate)),
cpSync: (src, dest, opts) => {
copyCalls.push({ src: toPlatformPath(src), dest: toPlatformPath(dest), opts });
},
readFileSync: () =>
JSON.stringify({
name: '@happier-dev/release-runtime',
version: '0.0.0',
type: 'module',
exports: { './github': { default: './dist/github.js' } },
}),
writeFileSync: (path, payload) => {
writeCalls.push({ path: toPlatformPath(path), payload: String(payload) });
},
});

assert.deepEqual(copyCalls, [
{
src: releaseRuntimeSourceDist,
dest: releaseRuntimeDist,
opts: { recursive: true, force: true },
},
]);

assert.equal(writeCalls.length, 1);
assert.equal(writeCalls[0]?.path, releaseRuntimePackageJson);
assert.match(writeCalls[0]?.payload ?? '', /\"name\": \"@happier-dev\/release-runtime\"/);
assert.match(writeCalls[0]?.payload ?? '', /\"private\": true/);
});
36 changes: 28 additions & 8 deletions apps/cli/scripts/buildSharedDeps.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { cpSync, existsSync, readFileSync, realpathSync, writeFileSync } from 'n
import { dirname, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
import { bundledWorkspacePackages } from './workspaceBundleManifest.mjs';
export { bundledWorkspacePackages };


function findRepoRoot(startDir) {
let dir = startDir;
Expand Down Expand Up @@ -78,7 +80,8 @@ export function syncBundledWorkspaceDist(opts = {}) {
const cp = opts.cpSync ?? cpSync;
const readFile = opts.readFileSync ?? readFileSync;
const writeFile = opts.writeFileSync ?? writeFileSync;
const packages = Array.isArray(opts.packages) && opts.packages.length > 0 ? opts.packages : ['agents', 'cli-common', 'protocol'];
const packages =
Array.isArray(opts.packages) && opts.packages.length > 0 ? opts.packages : bundledWorkspacePackages;

for (const pkg of packages) {
const srcDist = resolve(repoRoot, 'packages', pkg, 'dist');
Expand All @@ -102,6 +105,21 @@ export function syncBundledWorkspaceDist(opts = {}) {
}
}

export function resolveBundledWorkspaceBuildEntry(pkg, opts = {}) {
const repoRootArg = opts.repoRoot;
const repoRoot = typeof repoRootArg === 'string' && repoRootArg.trim() ? repoRootArg : findRepoRoot(__dirname);
const readFile = opts.readFileSync ?? readFileSync;
const packageJsonPath = resolve(repoRoot, 'packages', pkg, 'package.json');

try {
const packageJson = JSON.parse(readFile(packageJsonPath, 'utf8'));
const mainEntry = typeof packageJson?.main === 'string' && packageJson.main.trim() ? packageJson.main : './dist/index.js';
return resolve(repoRoot, 'packages', pkg, mainEntry);
} catch {
return resolve(repoRoot, 'packages', pkg, 'dist', 'index.js');
}
}

function sanitizeBundledWorkspacePackageJson(raw) {
const {
name,
Expand Down Expand Up @@ -134,13 +152,15 @@ function sanitizeBundledWorkspacePackageJson(raw) {
}

export function main() {
runTsc(resolve(repoRoot, 'packages', 'agents', 'tsconfig.json'));
runTsc(resolve(repoRoot, 'packages', 'cli-common', 'tsconfig.json'));
runTsc(resolve(repoRoot, 'packages', 'protocol', 'tsconfig.json'));
for (const pkg of bundledWorkspacePackages) {
runTsc(resolve(repoRoot, 'packages', pkg, 'tsconfig.json'));
}

const protocolDist = resolve(repoRoot, 'packages', 'protocol', 'dist', 'index.js');
if (!existsSync(protocolDist)) {
throw new Error(`Expected @happier-dev/protocol build output missing: ${protocolDist}`);
for (const pkg of bundledWorkspacePackages) {
const distEntry = resolveBundledWorkspaceBuildEntry(pkg, { repoRoot });
if (!existsSync(distEntry)) {
throw new Error(`Expected @happier-dev/${pkg} build output missing: ${distEntry}`);
}
}

// If the CLI currently has bundled workspace deps under apps/cli/node_modules,
Expand Down
26 changes: 3 additions & 23 deletions apps/cli/scripts/bundleWorkspaceDeps.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,14 @@ import {
vendorBundledPackageRuntimeDependencies,
} from '../../../packages/cli-common/dist/workspaces/index.js';

import { createBundledWorkspaceBundles } from './workspaceBundleManifest.mjs';

const __dirname = dirname(fileURLToPath(import.meta.url));

export function bundleWorkspaceDeps(opts = {}) {
const repoRoot = opts.repoRoot ?? findRepoRoot(__dirname);
const happyCliDir = opts.happyCliDir ?? resolve(repoRoot, 'apps', 'cli');

const bundles = [
{
packageName: '@happier-dev/agents',
srcDir: resolve(repoRoot, 'packages', 'agents'),
destDir: resolve(happyCliDir, 'node_modules', '@happier-dev', 'agents'),
},
{
packageName: '@happier-dev/cli-common',
srcDir: resolve(repoRoot, 'packages', 'cli-common'),
destDir: resolve(happyCliDir, 'node_modules', '@happier-dev', 'cli-common'),
},
{
packageName: '@happier-dev/protocol',
srcDir: resolve(repoRoot, 'packages', 'protocol'),
destDir: resolve(happyCliDir, 'node_modules', '@happier-dev', 'protocol'),
},
{
packageName: '@happier-dev/release-runtime',
srcDir: resolve(repoRoot, 'packages', 'release-runtime'),
destDir: resolve(happyCliDir, 'node_modules', '@happier-dev', 'release-runtime'),
},
];
const bundles = createBundledWorkspaceBundles({ repoRoot, targetRoot: happyCliDir });
bundleWorkspacePackages({ bundles });

for (const b of bundles) {
Expand Down
11 changes: 11 additions & 0 deletions apps/cli/scripts/workspaceBundleManifest.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { resolve } from 'node:path';

export const bundledWorkspacePackages = ['agents', 'cli-common', 'protocol', 'release-runtime'];

export function createBundledWorkspaceBundles({ repoRoot, targetRoot }) {
return bundledWorkspacePackages.map((pkg) => ({
packageName: `@happier-dev/${pkg}`,
srcDir: resolve(repoRoot, 'packages', pkg),
destDir: resolve(targetRoot, 'node_modules', '@happier-dev', pkg),
}));
}