Skip to content
Merged
2 changes: 2 additions & 0 deletions .claude/commands/upgrade-mediabunny.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
- Look in the root package.json and update the version of Mediabunny to that version.
- Also upgrade @mediabunny/mp3-encoder to the same version.
- Look in packages/template-\*/package.json and update the version of Mediabunny to the desired version.
- Update `packages/cli/src/extra-packages.ts` with the new Mediabunny version.
- Update `packages/studio-shared/src/package-info.ts` with the new Mediabunny version in `extraPackages`.
- Update `packages/docs/docs/mediabunny/version.mdx` compatiblity table. To find the next version this upgrade is going to be applied, look in the root package.json for the version and increment the patch version by one
- Run `bun i` in the end.
137 changes: 100 additions & 37 deletions packages/cli/src/add.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import {RenderInternals, type LogLevel} from '@remotion/renderer';
import {StudioServerInternals} from '@remotion/studio-server';
import {spawn} from 'node:child_process';
import fs from 'node:fs';
import {chalk} from './chalk';
import {EXTRA_PACKAGES} from './extra-packages';
import {listOfRemotionPackages} from './list-of-remotion-packages';
import {Log} from './log';
import {resolveFrom} from './resolve-from';

const getInstalledVersion = (
remotionRoot: string,
pkg: string,
): string | null => {
try {
const pkgJsonPath = resolveFrom(remotionRoot, `${pkg}/package.json`);
const file = fs.readFileSync(pkgJsonPath, 'utf-8');
const packageJson = JSON.parse(file);
return packageJson.version;
} catch {
return null;
}
};

export const addCommand = async ({
remotionRoot,
Expand All @@ -20,11 +37,11 @@ export const addCommand = async ({
}) => {
// Validate that all package names are Remotion packages
const invalidPackages = packageNames.filter(
(pkg) => !listOfRemotionPackages.includes(pkg),
(pkg) => !listOfRemotionPackages.includes(pkg) && !EXTRA_PACKAGES[pkg],
);
if (invalidPackages.length > 0) {
throw new Error(
`The following packages are not Remotion packages: ${invalidPackages.join(', ')}. Must be one of the Remotion packages.`,
`The following packages are not Remotion packages: ${invalidPackages.join(', ')}. Must be one of the Remotion packages or one of the supported extra packages: ${Object.keys(EXTRA_PACKAGES).join(', ')}.`,
);
}

Expand All @@ -43,8 +60,33 @@ export const addCommand = async ({
...peerDependencies,
];

const alreadyInstalled = packageNames.filter((pkg) => allDeps.includes(pkg));
const toInstall = packageNames.filter((pkg) => !allDeps.includes(pkg));
const alreadyInstalled: string[] = [];
const toInstall: string[] = [];
const toUpgrade: {pkg: string; from: string; to: string}[] = [];

for (const pkg of packageNames) {
const isInstalled = allDeps.includes(pkg);
const requiredVersion = EXTRA_PACKAGES[pkg];

if (!isInstalled) {
toInstall.push(pkg);
} else if (requiredVersion) {
// For extra packages, check if the version is correct
const installedVersion = getInstalledVersion(remotionRoot, pkg);
if (installedVersion !== requiredVersion) {
toUpgrade.push({
pkg,
from: installedVersion ?? 'unknown',
to: requiredVersion,
});
toInstall.push(pkg);
} else {
alreadyInstalled.push(pkg);
}
} else {
alreadyInstalled.push(pkg);
}
}

// Log already installed packages
for (const pkg of alreadyInstalled) {
Expand All @@ -54,41 +96,49 @@ export const addCommand = async ({
);
}

// Log packages that will be upgraded
for (const {pkg, from, to} of toUpgrade) {
Log.info(
{indent: false, logLevel},
`↑ ${pkg} ${chalk.yellow(`${from} → ${to}`)}`,
);
}

// If nothing to install, return early
if (toInstall.length === 0) {
return;
}

// Find the version of installed Remotion packages
const installedRemotionPackages = listOfRemotionPackages.filter((pkg) =>
allDeps.includes(pkg),
);

if (installedRemotionPackages.length === 0) {
throw new Error(
'No Remotion packages found in your project. Install Remotion first.',
);
}

// Get the version from the first installed Remotion package
const packageJsonPath = `${remotionRoot}/node_modules/${installedRemotionPackages[0]}/package.json`;
let targetVersion: string;

try {
const packageJson = require(packageJsonPath);
targetVersion = packageJson.version;
const packageList =
toInstall.length === 1
? toInstall[0]
: `${toInstall.length} packages (${toInstall.join(', ')})`;
Log.info(
{indent: false, logLevel},
`Installing ${packageList}@${targetVersion} to match your other Remotion packages`,
);
} catch (err) {
throw new Error(
`Could not determine version of installed Remotion packages: ${(err as Error).message}`,
);
let targetVersion: string | null = null;

if (installedRemotionPackages.length > 0) {
try {
const packageJson = require(packageJsonPath);
targetVersion = packageJson.version;
const packageList =
toInstall.length === 1
? toInstall[0]
: `${toInstall.length} packages (${toInstall.join(', ')})`;
Log.info({indent: false, logLevel}, `Installing ${packageList}`);
} catch (err) {
throw new Error(
`Could not determine version of installed Remotion packages: ${(err as Error).message}`,
);
}
} else {
// If no Remotion packages are installed, we can only install extra packages
const notExtraPackages = toInstall.filter((pkg) => !EXTRA_PACKAGES[pkg]);
if (notExtraPackages.length > 0) {
throw new Error(
'No Remotion packages found in your project. Install Remotion first.',
);
}
}

const manager = StudioServerInternals.getPackageManager(
Expand All @@ -105,10 +155,18 @@ export const addCommand = async ({
);
}

const packagesWithVersions = toInstall.map((pkg) => {
if (EXTRA_PACKAGES[pkg]) {
return `${pkg}@${EXTRA_PACKAGES[pkg]}`;
}

return `${pkg}@${targetVersion}`;
});

const command = StudioServerInternals.getInstallCommand({
manager: manager.manager,
packages: toInstall,
version: targetVersion,
packages: packagesWithVersions,
version: '',
additionalArgs: args,
});

Expand All @@ -122,6 +180,7 @@ export const addCommand = async ({
...process.env,
ADBLOCK: '1',
DISABLE_OPENCOLLECTIVE: '1',
npm_config_loglevel: 'error',
},
stdio: RenderInternals.isEqualOrBelowLogLevel(logLevel, 'info')
? 'inherit'
Expand All @@ -142,14 +201,18 @@ export const addCommand = async ({
});
});

for (const pkg of alreadyInstalled) {
Log.info(
{indent: false, logLevel},
`○ ${pkg}@${targetVersion} ${chalk.gray('(already installed)')}`,
);
}
const upgradedPkgs = new Set(toUpgrade.map((u) => u.pkg));

for (const pkg of toInstall) {
Log.info({indent: false, logLevel}, `+ ${pkg}@${targetVersion}`);
if (upgradedPkgs.has(pkg)) {
// Already logged as upgrade
continue;
}

if (EXTRA_PACKAGES[pkg]) {
Log.info({indent: false, logLevel}, `+ ${pkg}@${EXTRA_PACKAGES[pkg]}`);
} else {
Log.info({indent: false, logLevel}, `+ ${pkg}@${targetVersion}`);
}
}
};
9 changes: 9 additions & 0 deletions packages/cli/src/extra-packages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const EXTRA_PACKAGES: Record<string, string> = {
zod: '3.22.3',
mediabunny: '1.29.0',
};

export const EXTRA_PACKAGES_DOCS: Record<string, string> = {
zod: 'https://www.remotion.dev/docs/schemas#prerequisites',
mediabunny: 'https://www.remotion.dev/docs/mediabunny/version',
};
70 changes: 61 additions & 9 deletions packages/cli/src/upgrade.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
import {RenderInternals, type LogLevel} from '@remotion/renderer';
import {StudioServerInternals} from '@remotion/studio-server';
import {spawn} from 'node:child_process';
import {execSync, spawn} from 'node:child_process';
import {chalk} from './chalk';
import {EXTRA_PACKAGES} from './extra-packages';
import {listOfRemotionPackages} from './list-of-remotion-packages';
import {Log} from './log';

const getExtraPackageVersionsForRemotionVersion = (
remotionVersion: string,
): Record<string, string> => {
try {
const output = execSync(
`npm view @remotion/studio@${remotionVersion} dependencies --json`,
{encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']},
);
const dependencies = JSON.parse(output) as Record<string, string>;

const extraVersions: Record<string, string> = {};
for (const pkg of Object.keys(EXTRA_PACKAGES)) {
if (dependencies[pkg]) {
extraVersions[pkg] = dependencies[pkg];
}
}

return extraVersions;
} catch {
// If we can't fetch the versions, return the default versions from EXTRA_PACKAGES
return EXTRA_PACKAGES;
}
};

export const upgradeCommand = async ({
remotionRoot,
packageManager,
Expand Down Expand Up @@ -55,18 +80,45 @@ export const upgradeCommand = async ({
);
}

const toUpgrade = listOfRemotionPackages.filter(
(u) =>
dependencies.includes(u) ||
devDependencies.includes(u) ||
optionalDependencies.includes(u) ||
peerDependencies.includes(u),
const allDeps = [
...dependencies,
...devDependencies,
...optionalDependencies,
...peerDependencies,
];

const remotionToUpgrade = listOfRemotionPackages.filter((u) =>
allDeps.includes(u),
);

// Check if extra packages (zod, mediabunny) are installed
const installedExtraPackages = Object.keys(EXTRA_PACKAGES).filter((pkg) =>
allDeps.includes(pkg),
);

// Get the correct versions for extra packages for this Remotion version
const extraPackageVersions =
getExtraPackageVersionsForRemotionVersion(targetVersion);

// Build the list of packages to upgrade
const packagesWithVersions = [
...remotionToUpgrade.map((pkg) => `${pkg}@${targetVersion}`),
...installedExtraPackages.map(
(pkg) => `${pkg}@${extraPackageVersions[pkg]}`,
),
];

if (installedExtraPackages.length > 0) {
Log.info(
{indent: false, logLevel},
`Also upgrading extra packages: ${installedExtraPackages.map((pkg) => `${pkg}@${extraPackageVersions[pkg]}`).join(', ')}`,
);
}

const command = StudioServerInternals.getInstallCommand({
manager: manager.manager,
packages: toUpgrade,
version: targetVersion,
packages: packagesWithVersions,
version: '',
additionalArgs: args,
});

Expand Down
Loading
Loading