diff --git a/packages/create-video/src/pkg-managers.ts b/packages/create-video/src/pkg-managers.ts
index a74045692eb..b45fbafd74b 100644
--- a/packages/create-video/src/pkg-managers.ts
+++ b/packages/create-video/src/pkg-managers.ts
@@ -3,7 +3,7 @@ import path from 'node:path';
import {Log} from './log';
import type {Template} from './templates';
-export type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';
+export type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun' | 'nub';
const shouldUseBun = (): boolean => {
if (
@@ -41,6 +41,10 @@ const shouldUsePnpm = (): boolean => {
};
export const selectPackageManager = (): PackageManager => {
+ if (process.env.npm_config_user_agent?.includes('nub/')) {
+ return 'nub';
+ }
+
if (shouldUseYarn()) {
return 'yarn';
}
@@ -72,6 +76,10 @@ export const getInstallCommand = (manager: PackageManager) => {
if (manager === 'bun') {
return `bun install`;
}
+
+ if (manager === 'nub') {
+ return `nub install`;
+ }
};
const getStartCommand = (manager: PackageManager) => {
@@ -90,6 +98,10 @@ const getStartCommand = (manager: PackageManager) => {
if (manager === 'bun') {
return `bun run dev`;
}
+
+ if (manager === 'nub') {
+ return `nub run dev`;
+ }
};
export const getRunCommand = (manager: PackageManager) => {
@@ -109,6 +121,10 @@ export const getRunCommand = (manager: PackageManager) => {
return `bun run`;
}
+ if (manager === 'nub') {
+ return `nub run`;
+ }
+
throw new TypeError('unknown package manager');
};
@@ -129,6 +145,10 @@ export const getRenderCommand = (manager: PackageManager) => {
return `bunx remotion render`;
}
+ if (manager === 'nub') {
+ return `nubx remotion render`;
+ }
+
throw new TypeError('unknown package manager');
};
@@ -149,6 +169,10 @@ export const getUpgradeCommand = (manager: PackageManager) => {
return `bunx remotion upgrade`;
}
+ if (manager === 'nub') {
+ return `nubx remotion upgrade`;
+ }
+
throw new TypeError('unknown package manager');
};
diff --git a/packages/create-video/src/test/patch-package-json.test.ts b/packages/create-video/src/test/patch-package-json.test.ts
index e061c6a4ab4..c3a104ce4ac 100644
--- a/packages/create-video/src/test/patch-package-json.test.ts
+++ b/packages/create-video/src/test/patch-package-json.test.ts
@@ -2,7 +2,7 @@ import {expect, test} from 'bun:test';
import {patchPackageJson} from '../patch-package-json';
import type {PackageManager} from '../pkg-managers';
-const packageManagers: PackageManager[] = ['npm', 'pnpm', 'yarn', 'bun'];
+const packageManagers: PackageManager[] = ['npm', 'pnpm', 'yarn', 'bun', 'nub'];
for (const packageManager of packageManagers) {
test(`Using ${packageManager} package manager provides the correct "packageManager" entry in package.json`, () => {
diff --git a/packages/create-video/src/test/pkg-managers.test.ts b/packages/create-video/src/test/pkg-managers.test.ts
new file mode 100644
index 00000000000..05bf9f042d2
--- /dev/null
+++ b/packages/create-video/src/test/pkg-managers.test.ts
@@ -0,0 +1,63 @@
+import {expect, test} from 'bun:test';
+import {
+ getDevCommand,
+ getInstallCommand,
+ getRenderCommand,
+ getRunCommand,
+ getUpgradeCommand,
+ selectPackageManager,
+} from '../pkg-managers';
+import {FEATURED_TEMPLATES} from '../templates';
+
+test('detects Nub and uses Nub commands', () => {
+ const previousUserAgent = process.env.npm_config_user_agent;
+ const previousExecPath = process.env.npm_execpath;
+ const previousArgv = process.env.npm_config_argv;
+
+ try {
+ process.env.npm_config_user_agent = 'nub/0.8.2 yarn/1.22.22';
+ process.env.npm_execpath = '/path/to/yarn.js';
+ process.env.npm_config_argv = JSON.stringify({remain: ['dlx']});
+
+ expect(selectPackageManager()).toBe('nub');
+
+ const blankTemplate = FEATURED_TEMPLATES.find(
+ (template) => template.cliId === 'blank',
+ );
+ if (!blankTemplate) {
+ throw new Error('Blank template not found');
+ }
+
+ expect([
+ getInstallCommand('nub'),
+ getDevCommand('nub', blankTemplate),
+ getRunCommand('nub'),
+ getRenderCommand('nub'),
+ getUpgradeCommand('nub'),
+ ]).toEqual([
+ 'nub install',
+ 'nub run dev',
+ 'nub run',
+ 'nubx remotion render',
+ 'nubx remotion upgrade',
+ ]);
+ } finally {
+ if (previousUserAgent === undefined) {
+ delete process.env.npm_config_user_agent;
+ } else {
+ process.env.npm_config_user_agent = previousUserAgent;
+ }
+
+ if (previousExecPath === undefined) {
+ delete process.env.npm_execpath;
+ } else {
+ process.env.npm_execpath = previousExecPath;
+ }
+
+ if (previousArgv === undefined) {
+ delete process.env.npm_config_argv;
+ } else {
+ process.env.npm_config_argv = previousArgv;
+ }
+ }
+});
diff --git a/packages/docs/docs/getting-started.mdx b/packages/docs/docs/getting-started.mdx
index 00748a0f6f0..f7616c12f83 100644
--- a/packages/docs/docs/getting-started.mdx
+++ b/packages/docs/docs/getting-started.mdx
@@ -57,6 +57,7 @@ values={[
{ label: 'bun', value: 'bun', },
{ label: 'pnpm', value: 'pnpm', },
{ label: 'yarn', value: 'yarn', },
+{ label: 'nub', value: 'nub', },
]
}>
@@ -82,6 +83,14 @@ yarn create video
+
+
+```bash title="Use Nub as the package manager"
+nubx create-video@latest
+```
+
+
+
```bash title="Use Bun as the package manager and runtime"
diff --git a/packages/docs/src/components/Elements/ElementPage.tsx b/packages/docs/src/components/Elements/ElementPage.tsx
index b70410a2215..ab14c8de74a 100644
--- a/packages/docs/src/components/Elements/ElementPage.tsx
+++ b/packages/docs/src/components/Elements/ElementPage.tsx
@@ -57,7 +57,9 @@ export const ElementPage: React.FC = ({
const [isSourceVisible, setIsSourceVisible] = useState(false);
const [isBrowserStudioActionVisible, setIsBrowserStudioActionVisible] =
useState(false);
- const [isEmbeddedInStudio, setIsEmbeddedInStudio] = useState(false);
+ const [isEmbeddedInStudio, setIsEmbeddedInStudio] = useState(
+ null,
+ );
const posterRef = useRef(null);
const sourceId = useId();
const {height: previewHeight, width: previewWidth} =
@@ -124,7 +126,10 @@ export const ElementPage: React.FC = ({
}
setIsInstallHintVisible(false);
- setInstallStatus({type: 'installing'});
+ if (!isEmbeddedInStudio) {
+ setInstallStatus({type: 'installing'});
+ }
+
const result = await installInStudio({payload: elementPayload});
if (!result.success) {
setInstallStatus({
@@ -135,18 +140,22 @@ export const ElementPage: React.FC = ({
return;
}
- const {target} = result;
- setInstallStatus({
- type: 'success',
- message: `Sent to ${target.projectName ?? 'Remotion Studio'} (currently ${target.compositionId}). Confirm the installation destination in Studio.`,
- });
+ if (isEmbeddedInStudio) {
+ setInstallStatus({type: 'idle'});
+ } else {
+ const {target} = result;
+ setInstallStatus({
+ type: 'success',
+ message: `Sent to ${target.projectName ?? 'Remotion Studio'} (currently ${target.compositionId}). Confirm the installation destination in Studio.`,
+ });
+ }
if (window.location.origin === 'https://www.remotion.dev') {
navigator.sendBeacon(
`https://www.remotion.pro/api/track/element-install-request?slug=${encodeURIComponent(definition.slug)}`,
);
}
- }, [definition.slug, elementPayload]);
+ }, [definition.slug, elementPayload, isEmbeddedInStudio]);
const openInBrowserStudio = useCallback(() => {
if (elementPayload === null) {
diff --git a/packages/example/remotion.config.ts b/packages/example/remotion.config.ts
index 00979089a03..500c7eb36b8 100644
--- a/packages/example/remotion.config.ts
+++ b/packages/example/remotion.config.ts
@@ -14,3 +14,7 @@ Config.addElementLibrary({
url: 'https://remocn.dev/docs/typography',
displayName: 'Remocn',
});
+Config.addElementLibrary({
+ url: 'http://localhost:3002/elements',
+ displayName: 'Local Elements',
+});
diff --git a/packages/renderer/src/options/package-manager.tsx b/packages/renderer/src/options/package-manager.tsx
index c727d783c7c..3eda8c40c02 100644
--- a/packages/renderer/src/options/package-manager.tsx
+++ b/packages/renderer/src/options/package-manager.tsx
@@ -14,7 +14,7 @@ export const packageManagerOption = {
auto-detect the package manager based on your lockfile.
Acceptable values are npm, yarn,{' '}
- pnpm and bun.
+ pnpm, bun and nub.
>
);
},
diff --git a/packages/studio-server/src/helpers/install-command.ts b/packages/studio-server/src/helpers/install-command.ts
index 691f22a3427..ff29d758f29 100644
--- a/packages/studio-server/src/helpers/install-command.ts
+++ b/packages/studio-server/src/helpers/install-command.ts
@@ -25,6 +25,7 @@ export const getInstallCommand = ({
pnpm: ['i', ...additionalArgs, ...pkgList],
yarn: ['add', '--exact', ...additionalArgs, ...pkgList],
bun: ['i', ...additionalArgs, ...pkgList],
+ nub: ['add', ...additionalArgs, ...pkgList],
};
return commands[manager];
diff --git a/packages/studio-server/src/preview-server/get-package-manager.ts b/packages/studio-server/src/preview-server/get-package-manager.ts
index 7aef10e28fb..f28fe9b15a2 100644
--- a/packages/studio-server/src/preview-server/get-package-manager.ts
+++ b/packages/studio-server/src/preview-server/get-package-manager.ts
@@ -30,6 +30,12 @@ export const lockFilePaths: LockfilePath[] = [
installCommand: 'pnpm i',
startCommand: 'pnpm exec remotion studio',
},
+ {
+ path: 'nub.lock',
+ manager: 'nub',
+ installCommand: 'nub add',
+ startCommand: 'nubx remotion studio',
+ },
{
path: 'bun.lock',
manager: 'bun',
diff --git a/packages/studio-server/src/test/install-dependency.test.ts b/packages/studio-server/src/test/install-dependency.test.ts
index bd7e7c76b8a..4ffa80bab7c 100644
--- a/packages/studio-server/src/test/install-dependency.test.ts
+++ b/packages/studio-server/src/test/install-dependency.test.ts
@@ -89,6 +89,7 @@ test('installs without running dependency lifecycle scripts', async () => {
pnpm: 'pnpm-lock.yaml',
yarn: 'yarn.lock',
bun: 'bun.lock',
+ nub: 'nub.lock',
};
const temporaryDirectories: string[] = [];
@@ -125,6 +126,11 @@ test('installs without running dependency lifecycle scripts', async () => {
expect(spawnCalls).toHaveLength(1);
const [call] = spawnCalls;
expect(call.command).toBe(manager);
+ expect(call.args).toContain('lodash@4.17.21');
+ if (manager === 'nub') {
+ expect(call.args[0]).toBe('add');
+ }
+
if (manager === 'yarn') {
expect(call.args).not.toContain('--ignore-scripts');
expect(call.options.env?.YARN_ENABLE_SCRIPTS).toBe('false');
diff --git a/packages/studio-shared/src/package-manager.ts b/packages/studio-shared/src/package-manager.ts
index 4d04f861d70..a3affa8041a 100644
--- a/packages/studio-shared/src/package-manager.ts
+++ b/packages/studio-shared/src/package-manager.ts
@@ -1 +1 @@
-export type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';
+export type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun' | 'nub';
diff --git a/packages/studio/src/components/InspectorPanel/ElementLibraryButton.tsx b/packages/studio/src/components/InspectorPanel/ElementLibraryButton.tsx
index 82843c270c4..a497843c725 100644
--- a/packages/studio/src/components/InspectorPanel/ElementLibraryButton.tsx
+++ b/packages/studio/src/components/InspectorPanel/ElementLibraryButton.tsx
@@ -1,6 +1,5 @@
import React, {useCallback, useContext, useMemo} from 'react';
-import {getBrowserStudioOperations} from '../../helpers/browser-studio-operations';
-import {CURRENT_COLOR, LIGHT_TEXT} from '../../helpers/colors';
+import {LIGHT_TEXT} from '../../helpers/colors';
import {BrowseElementsIcon} from '../../icons/browse-elements';
import {CaretDown} from '../../icons/caret';
import {SetSelectedModalContext} from '../../state/modals';
@@ -24,14 +23,6 @@ const browseElementsIconContainerStyle: React.CSSProperties = {
width: 22,
};
-const browseElementsArrowStyle: React.CSSProperties = {
- display: 'inline-block',
- height: 12,
- marginLeft: 4,
- verticalAlign: -2,
- width: 12,
-};
-
const elementLibraryDropdownStyle: React.CSSProperties = {
borderRadius: 4,
height: 28,
@@ -68,24 +59,18 @@ const elementLibraryDropdownCaretStyle: React.CSSProperties = {
export const ElementLibraryButton: React.FC = () => {
const {setSelectedModal} = useContext(SetSelectedModalContext);
const {studioRuntimeConfig} = useSettings();
- const isBrowserStudio = getBrowserStudioOperations() !== null;
const elementLibraries =
studioRuntimeConfig?.elementLibraries ?? noElementLibraries;
const openElementLibrary = useCallback(
(name: string, url: string) => {
- if (isBrowserStudio) {
- window.open(url, '_blank', 'noopener,noreferrer');
- return;
- }
-
setSelectedModal({
type: 'element-library',
name,
url,
});
},
- [isBrowserStudio, setSelectedModal],
+ [setSelectedModal],
);
const openElementsLibrary = useCallback(() => {
@@ -162,7 +147,7 @@ export const ElementLibraryButton: React.FC = () => {
[elementLibraries, openElementLibrary, openElementsLibrary],
);
- if (elementLibraries.length > 0 && !isBrowserStudio) {
+ if (elementLibraries.length > 0) {
return (
{
renderIcon={(color) => (
)}
- title={
- isBrowserStudio
- ? 'Open the Remotion Elements library in a new tab. Install an Element there to send it to this composition.'
- : 'Browse the Remotion Elements library inside Studio.'
- }
+ title="Browse the Remotion Elements library inside Studio."
>
Browse Elements
- {isBrowserStudio ? (
-
- ) : null}
);
};
diff --git a/packages/studio/src/components/UpdatesSettings.tsx b/packages/studio/src/components/UpdatesSettings.tsx
index 3a6e1268730..990bf0d269b 100644
--- a/packages/studio/src/components/UpdatesSettings.tsx
+++ b/packages/studio/src/components/UpdatesSettings.tsx
@@ -107,6 +107,7 @@ const commands: {
yarn: 'yarn remotion upgrade',
pnpm: 'pnpm exec remotion upgrade',
bun: 'bun remotion upgrade',
+ nub: 'nubx remotion upgrade',
unknown: 'npx remotion upgrade',
};