Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@remotion/lambda-client: New package #4871

Merged
merged 23 commits into from
Feb 8, 2025
Merged
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
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
Loading