Skip to content

Commit

Permalink
Merge pull request #4871 from remotion-dev/remotion-lambda-client
Browse files Browse the repository at this point in the history
`@remotion/lambda-client`: New package
  • Loading branch information
JonnyBurger authored Feb 8, 2025
2 parents 7954cb2 + 2af70f3 commit 2863db2
Show file tree
Hide file tree
Showing 264 changed files with 2,712 additions and 2,148 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"license": "SEE LICENSE IN LICENSE.md",
"scripts": {
"test": "turbo run lint test --no-update-notifier",
"ts-build": "tsc -b --verbose",
"ts-build": "tsc -b --verbose --watch",
"stylecheck": "turbo run lint formatting --no-update-notifier",
"build": "turbo run make --no-update-notifier",
"build-docs": "turbo run build-docs --no-update-notifier",
Expand Down
159 changes: 91 additions & 68 deletions packages/.monorepo/builder.ts
Original file line number Diff line number Diff line change
@@ -1,100 +1,120 @@
import {build} from 'bun';
import {existsSync} from 'node:fs';
import path from 'path';
import {Exports, validateExports} from './validate-exports';

if (process.env.NODE_ENV !== 'production') {
throw new Error('This script must be run using NODE_ENV=production');
}

type Format = 'esm' | 'cjs';

const validateExports = (
exports: Record<string, './package.json' | Record<string, string>>,
) => {
const keys = Object.keys(exports);
for (const key of keys) {
const value = exports[key];
if (key === './package.json' && value === './package.json') {
continue;
}
const getExternal = (deps: string[] | 'dependencies'): string[] => {
if (deps === 'dependencies') {
return Object.keys(
require(path.join(process.cwd(), 'package.json')).dependencies,
);
}

if (typeof value === 'string') {
throw new Error(`Invalid export for ${key}`);
}
return deps;
};

if (!value.import || !value.require || !value.module || !value) {
throw new Error(`Missing import or require for ${key}`);
}
const paths = Object.keys(value);
for (const entry of paths) {
if (
entry !== 'import' &&
entry !== 'require' &&
entry !== 'module' &&
entry !== 'types'
) {
throw new Error(`Invalid export: ${entry}`);
}
const pathToCheck = path.join(process.cwd(), value[entry]);
const exists = existsSync(pathToCheck);
if (!exists) {
throw new Error(`Path does not exist: ${pathToCheck}`);
}
}
}
const sortObject = (obj: Record<string, string>) => {
return {
...(obj.types !== undefined && {types: obj.types}),
...(obj.require !== undefined && {require: obj.require}),
...(obj.module !== undefined && {module: obj.module}),
...(obj.import !== undefined && {import: obj.import}),
};
};

type FormatAction = 'do-nothing' | 'build' | 'use-tsc';

type EntryPoint = {
target: 'node' | 'browser';
path: string;
};

export const buildPackage = async ({
formats,
external,
target,
entrypoints,
}: {
formats: Format[];
external: string[];
target: 'node' | 'browser';
entrypoints: string[];
formats: {
esm: FormatAction;
cjs: FormatAction;
};
external: 'dependencies' | string[];
entrypoints: EntryPoint[];
}) => {
console.time(`Generated ${formats.join(', ')}.`);
console.time(`Generated.`);
const pkg = await Bun.file(path.join(process.cwd(), 'package.json')).json();
const newExports = {
...pkg.exports,
const newExports: Exports = {
'./package.json': './package.json',
};
const versions = {};

for (const format of formats) {
const output = await build({
entrypoints: entrypoints.map((e) => path.join(process.cwd(), e)),
naming: `[name].${format === 'esm' ? 'mjs' : 'js'}`,
external,
target,
format,
});

for (const file of output.outputs) {
const text = await file.text();
if (text.includes('jonathanburger')) {
throw new Error('Absolute path was included');
}
const firstNames = entrypoints.map(({path, target}) => {
const splittedBySlash = path.split('/');
const last = splittedBySlash[splittedBySlash.length - 1];
return last.split('.')[0];
});

for (const format of ['cjs', 'esm'] as Format[]) {
const action = formats[format];
if (action === 'do-nothing') {
continue;
} else if (action === 'use-tsc') {
} else if (action === 'build') {
for (const {path: p, target} of entrypoints) {
const output = await build({
entrypoints: [p],
naming: `[name].${format === 'esm' ? 'mjs' : 'js'}`,
external: getExternal(external),
target,
format,
});

const outputPath = './' + path.join('./dist', format, file.path);
await Bun.write(path.join(process.cwd(), outputPath), text);
for (const file of output.outputs) {
const text = await file.text();

const firstName = file.path.split('.')[1].slice(1);
const exportName = firstName === 'index' ? '.' : firstName;
newExports[exportName] = {
...(newExports[exportName] ?? {}),
...(format === 'esm'
const outputPath = `./${path.join('./dist', format, file.path)}`;

await Bun.write(path.join(process.cwd(), outputPath), text);

if (text.includes('jonathanburger')) {
throw new Error('Absolute path was included, see ' + outputPath);
}
}
}
}

for (const firstName of firstNames) {
const exportName = firstName === 'index' ? '.' : './' + firstName;
const outputName =
action === 'use-tsc'
? `./dist/${firstName}.js`
: `./dist/${format}/${firstName}.${format === 'cjs' ? 'js' : 'mjs'}`;
newExports[exportName] = sortObject({
types: `./dist/${firstName}.d.ts`,
...(format === 'cjs'
? {
import: outputPath,
module: outputPath,
require: outputName,
}
: {}),
...(format === 'cjs'
...(format === 'esm'
? {
require: outputPath,
import: outputName,
module: outputName,
}
: {}),
};
...(newExports[exportName] && typeof newExports[exportName] === 'object'
? newExports[exportName]
: {}),
});

if (firstName !== 'index') {
versions[firstName] = [`dist/${firstName}.d.ts`];
}
}
}
validateExports(newExports);
Expand All @@ -104,10 +124,13 @@ export const buildPackage = async ({
{
...pkg,
exports: newExports,
...(Object.keys(versions).length > 0
? {typesVersions: {'>=1.0': versions}}
: {}),
},
null,
'\t',
) + '\n',
);
console.timeEnd(`Generated ${formats.join(', ')}.`);
console.timeEnd(`Generated.`);
};
38 changes: 38 additions & 0 deletions packages/.monorepo/validate-exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {existsSync} from 'node:fs';
import path from 'path';

export type Exports = Record<string, './package.json' | Record<string, string>>;

export const validateExports = (exports: Exports) => {
const keys = Object.keys(exports);
for (const key of keys) {
const value = exports[key];
if (key === './package.json' && value === './package.json') {
continue;
}

if (typeof value === 'string') {
throw new Error(`Invalid export for ${key}`);
}

if (!value.import || !value.require || !value.module || !value) {
throw new Error(`Missing import or require for ${key}`);
}
const paths = Object.keys(value);
for (const entry of paths) {
if (
entry !== 'import' &&
entry !== 'require' &&
entry !== 'module' &&
entry !== 'types'
) {
throw new Error(`Invalid export: ${entry}: ${JSON.stringify(exports)}`);
}
const pathToCheck = path.join(process.cwd(), value[entry]);
const exists = existsSync(pathToCheck);
if (!exists) {
throw new Error(`Path does not exist: ${pathToCheck}`);
}
}
}
};
5 changes: 2 additions & 3 deletions packages/docs/components/lambda/default-frames-per-lambda.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { MINIMUM_FRAMES_PER_LAMBDA } from "@remotion/lambda/defaults";
import React from "react";
import React from 'react';

export const MinimumFramesPerLambda: React.FC = () => {
return <code>{MINIMUM_FRAMES_PER_LAMBDA}</code>;
return <code>4</code>;
};
6 changes: 3 additions & 3 deletions packages/docs/components/lambda/default-log-retention.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DEFAULT_CLOUDWATCH_RETENTION_PERIOD } from "@remotion/lambda/defaults";
import React from "react";
import {DEFAULT_CLOUDWATCH_RETENTION_PERIOD} from '@remotion/lambda-client/constants';
import React from 'react';

export const DefaultLogRetention: React.FC = () => {
return <span>{DEFAULT_CLOUDWATCH_RETENTION_PERIOD}</span>;
return <span>{DEFAULT_CLOUDWATCH_RETENTION_PERIOD}</span>;
};
6 changes: 3 additions & 3 deletions packages/docs/components/lambda/default-memory-size.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DEFAULT_MEMORY_SIZE } from "@remotion/lambda/defaults";
import React from "react";
import {DEFAULT_MEMORY_SIZE} from '@remotion/lambda-client/constants';
import React from 'react';

export const DefaultMemorySize: React.FC = () => {
return <span>{DEFAULT_MEMORY_SIZE}</span>;
return <span>{DEFAULT_MEMORY_SIZE}</span>;
};
6 changes: 3 additions & 3 deletions packages/docs/components/lambda/default-timeout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DEFAULT_TIMEOUT } from "@remotion/lambda/defaults";
import React from "react";
import {DEFAULT_TIMEOUT} from '@remotion/lambda-client/constants';
import React from 'react';

export const DefaultTimeout: React.FC = () => {
return <span>{DEFAULT_TIMEOUT}</span>;
return <span>{DEFAULT_TIMEOUT}</span>;
};
26 changes: 13 additions & 13 deletions packages/docs/components/lambda/regions.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { AWS_REGIONS } from "@remotion/lambda/regions";
import React from "react";
import {AWS_REGIONS} from '@remotion/lambda-client/regions';
import React from 'react';

export const LambdaRegionList: React.FC = () => {
return (
<ul>
{AWS_REGIONS.map((region) => {
return (
<li key={region}>
<code>{region}</code>{" "}
</li>
);
})}
</ul>
);
return (
<ul>
{AWS_REGIONS.map((region) => {
return (
<li key={region}>
<code>{region}</code>{' '}
</li>
);
})}
</ul>
);
};
14 changes: 7 additions & 7 deletions packages/docs/components/lambda/role-permissions.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getRolePolicy } from "@remotion/lambda/policies";
import React from "react";
import {getRolePolicy} from '@remotion/lambda/policies';
import React from 'react';

export const RolePolicy: React.FC = () => {
return (
<div>
<pre>{getRolePolicy()}</pre>
</div>
);
return (
<div>
<pre>{getRolePolicy()}</pre>
</div>
);
};
14 changes: 7 additions & 7 deletions packages/docs/components/lambda/user-permissions.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getUserPolicy } from "@remotion/lambda/policies";
import React from "react";
import {getUserPolicy} from '@remotion/lambda/policies';
import React from 'react';

export const UserPolicy: React.FC = () => {
return (
<div>
<pre>{getUserPolicy()}</pre>
</div>
);
return (
<div>
<pre>{getUserPolicy()}</pre>
</div>
);
};
14 changes: 7 additions & 7 deletions packages/docs/docs/lambda/supabase.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ slug: /lambda/supabase
This page shows how to trigger a Remotion Lambda render from a Supabase Edge Function.
Other than that, the steps to use Remotion Lambda are the same as described in the [Remotion Lambda setup](/docs/lambda/setup).

## Invoking a Remotion Lambda render from Supabase Edge Functions<AvailableFrom v="4.0.258"/>
## Invoking a Remotion Lambda render from Supabase Edge Functions<AvailableFrom v="4.0.261"/>

Install the `@remotion/lambda` package in your Supabase Edge Functions project:
Install the `@remotion/lambda-client` package in your Supabase Edge Functions project:

```bash
deno add @remotion/[email protected].258
deno add @remotion/lambda-client@4.0.261
```

Replace `4.0.258` with the version of Remotion you are using.
Replace `4.0.261` with the version of Remotion you are using.

Set the environment variable `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`:

Expand All @@ -28,8 +28,8 @@ AWS_SECRET_ACCESS_KEY=xxx

```tsx title="supabase/functions/trigger-render.ts"
import 'jsr:@supabase/functions-js/edge-runtime.d.ts';
// FIXME: Replace 4.0.258 with the version of Remotion you are using.
import {renderMediaOnLambda} from 'npm:@remotion/[email protected].258/client';
// FIXME: Replace 4.0.261 with the version of Remotion you are using.
import {renderMediaOnLambda} from 'npm:@remotion/lambda-client@4.0.261';

Deno.serve(async (req) => {
const {props} = await req.json();
Expand Down Expand Up @@ -67,7 +67,7 @@ Create a Supabase Storage bucket and set the environment variables `SUPABASE_ACC
Use an [`s3OutputProvider`](/docs/lambda/custom-destination#saving-to-another-cloud) to store the rendered video in Supabase Storage:

```tsx twoslash
import {renderMediaOnLambda, speculateFunctionName} from '@remotion/lambda/client';
import {renderMediaOnLambda, speculateFunctionName} from '@remotion/lambda-client';

// ---cut---
const {bucketName, renderId, cloudWatchMainLogs} = await renderMediaOnLambda({
Expand Down
Loading

0 comments on commit 2863db2

Please sign in to comment.