Skip to content
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
24 changes: 24 additions & 0 deletions .changeset/artifacts-binding-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"wrangler": minor
"miniflare": minor
"@cloudflare/workers-utils": minor
---
Comment thread
dario-piotrowicz marked this conversation as resolved.

Add Artifacts binding support to wrangler

You can now configure Artifacts bindings in your wrangler configuration:

```jsonc
// wrangler.jsonc
{
"artifacts": [{ "binding": "MY_ARTIFACTS", "namespace": "default" }],
}
```

Type generation produces the correct `Artifacts` type reference from the workerd type definitions:

```ts
interface Env {
MY_ARTIFACTS: Artifacts;
}
```
67 changes: 67 additions & 0 deletions packages/miniflare/src/plugins/artifacts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { z } from "zod";
import {
getUserBindingServiceName,
ProxyNodeBinding,
remoteProxyClientWorker,
} from "../shared";
import type { Plugin, RemoteProxyConnectionString } from "../shared";

const ArtifactsSchema = z.object({
namespace: z.string(),
remoteProxyConnectionString: z
.custom<RemoteProxyConnectionString>()
.optional(),
});

export const ArtifactsOptionsSchema = z.object({
artifacts: z.record(ArtifactsSchema).optional(),
});

export const ARTIFACTS_PLUGIN_NAME = "artifacts";

export const ARTIFACTS_PLUGIN: Plugin<typeof ArtifactsOptionsSchema> = {
options: ArtifactsOptionsSchema,
async getBindings(options) {
if (!options.artifacts) {
return [];
}

return Object.entries(options.artifacts).map(([name, config]) => ({
name,
service: {
name: getUserBindingServiceName(
ARTIFACTS_PLUGIN_NAME,
name,
config.remoteProxyConnectionString
),
},
}));
},
getNodeBindings(options: z.infer<typeof ArtifactsOptionsSchema>) {
if (!options.artifacts) {
return {};
}
return Object.fromEntries(
Object.keys(options.artifacts).map((name) => [
name,
new ProxyNodeBinding(),
])
);
},
async getServices({ options }) {
if (!options.artifacts) {
return [];
}

return Object.entries(options.artifacts).map(
([name, { remoteProxyConnectionString }]) => ({
name: getUserBindingServiceName(
ARTIFACTS_PLUGIN_NAME,
name,
remoteProxyConnectionString
),
worker: remoteProxyClientWorker(remoteProxyConnectionString, name),
})
);
},
};
4 changes: 4 additions & 0 deletions packages/miniflare/src/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ANALYTICS_ENGINE_PLUGIN,
ANALYTICS_ENGINE_PLUGIN_NAME,
} from "./analytics-engine";
import { ARTIFACTS_PLUGIN, ARTIFACTS_PLUGIN_NAME } from "./artifacts";
import { ASSETS_PLUGIN } from "./assets";
import { ASSETS_PLUGIN_NAME } from "./assets/constants";
import {
Expand Down Expand Up @@ -76,6 +77,7 @@ export const PLUGINS = {
[MTLS_PLUGIN_NAME]: MTLS_PLUGIN,
[HELLO_WORLD_PLUGIN_NAME]: HELLO_WORLD_PLUGIN,
[FLAGSHIP_PLUGIN_NAME]: FLAGSHIP_PLUGIN,
[ARTIFACTS_PLUGIN_NAME]: ARTIFACTS_PLUGIN,
[WORKER_LOADER_PLUGIN_NAME]: WORKER_LOADER_PLUGIN,
[MEDIA_PLUGIN_NAME]: MEDIA_PLUGIN,
[VERSION_METADATA_PLUGIN_NAME]: VERSION_METADATA_PLUGIN,
Expand Down Expand Up @@ -144,6 +146,7 @@ export type WorkerOptions = z.input<typeof CORE_PLUGIN.options> &
z.input<typeof MTLS_PLUGIN.options> &
z.input<typeof HELLO_WORLD_PLUGIN.options> &
z.input<typeof FLAGSHIP_PLUGIN.options> &
z.input<typeof ARTIFACTS_PLUGIN.options> &
z.input<typeof WORKER_LOADER_PLUGIN.options> &
z.input<typeof MEDIA_PLUGIN.options> &
z.input<typeof VERSION_METADATA_PLUGIN.options>;
Expand Down Expand Up @@ -234,6 +237,7 @@ export * from "./vpc-services";
export * from "./mtls";
export * from "./hello-world";
export * from "./flagship";
export * from "./artifacts";
export * from "./worker-loader";
export * from "./media";
export * from "./version-metadata";
1 change: 1 addition & 0 deletions packages/workers-utils/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ export const defaultWranglerConfig: Config = {
hyperdrive: [],
workflows: [],
secrets_store_secrets: [],
artifacts: [],
services: [],
analytics_engine_datasets: [],
ai: undefined,
Expand Down
21 changes: 21 additions & 0 deletions packages/workers-utils/src/config/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,27 @@ export interface EnvironmentNonInheritable {
secret_name: string;
}[];

/**
* Specifies Artifacts bindings that are bound to this Worker environment.
* Artifacts provides git-compatible file storage on Cloudflare Workers.
*
* NOTE: This field is not automatically inherited from the top level environment,
* and so must be specified in every named environment.
*
* @default []
* @nonInheritable
*/
artifacts: {
/** The binding name used to refer to the Artifacts instance. */
binding: string;

/** The namespace to use. */
namespace: string;

/** Whether to use the remote Artifacts service in local dev. */
remote?: boolean;
}[];

/**
* **DO NOT USE**. Hello World Binding Config to serve as an explanatory example.
*
Expand Down
62 changes: 62 additions & 0 deletions packages/workers-utils/src/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export type ConfigBindingFieldName =
| "workflows"
| "pipelines"
| "secrets_store_secrets"
| "artifacts"
| "ratelimits"
| "assets"
| "unsafe_hello_world"
Expand Down Expand Up @@ -143,6 +144,7 @@ export const friendlyBindingNames: Record<ConfigBindingFieldName, string> = {
workflows: "Workflow",
pipelines: "Pipeline",
secrets_store_secrets: "Secrets Store Secret",
artifacts: "Artifacts",
ratelimits: "Rate Limit",
assets: "Assets",
unsafe_hello_world: "Hello World",
Expand Down Expand Up @@ -187,6 +189,7 @@ const bindingTypeFriendlyNames: Record<Binding["type"], string> = {
mtls_certificate: "mTLS Certificate",
pipeline: "Pipeline",
secrets_store_secret: "Secrets Store Secret",
artifacts: "Artifacts",
logfwdr: "logfwdr",
unsafe_hello_world: "Hello World",
flagship: "Flagship",
Expand Down Expand Up @@ -1878,6 +1881,16 @@ function normalizeAndValidateEnvironment(
validateBindingArray(envName, validateSecretsStoreSecretBinding),
[]
),
artifacts: notInheritable(
diagnostics,
topLevelEnv,
rawConfig,
rawEnv,
envName,
"artifacts",
validateBindingArray(envName, validateArtifactsBinding),
[]
),
unsafe_hello_world: notInheritable(
diagnostics,
topLevelEnv,
Expand Down Expand Up @@ -2997,6 +3010,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => {
"vpc_network",
"stream",
"media",
"artifacts",
];

if (safeBindings.includes(value.type)) {
Expand Down Expand Up @@ -4779,6 +4793,45 @@ const validateSecretsStoreSecretBinding: ValidatorFn = (
return isValid;
};

const validateArtifactsBinding: ValidatorFn = (diagnostics, field, value) => {
if (typeof value !== "object" || value === null) {
diagnostics.errors.push(
`"artifacts" bindings should be objects, but got ${JSON.stringify(value)}`
);
return false;
}
let isValid = true;
if (!isRequiredProperty(value, "binding", "string")) {
diagnostics.errors.push(
`"${field}" bindings must have a string "binding" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}

if (!isRequiredProperty(value, "namespace", "string")) {
diagnostics.errors.push(
`"${field}" bindings must have a string "namespace" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}

validateAdditionalProperties(diagnostics, field, Object.keys(value), [
"binding",
"namespace",
"remote",
]);

if (!isRemoteValid(value, field, diagnostics)) {
isValid = false;
}

return isValid;
};

const validateHelloWorldBinding: ValidatorFn = (diagnostics, field, value) => {
if (typeof value !== "object" || value === null) {
diagnostics.errors.push(
Expand Down Expand Up @@ -5035,6 +5088,7 @@ const validatePreviewsConfig =
"media",
"pipelines",
"secrets_store_secrets",
"artifacts",
"unsafe_hello_world",
"worker_loaders",
"ratelimits",
Expand Down Expand Up @@ -5260,6 +5314,14 @@ const validatePreviewsConfig =
undefined
) && isValid;

isValid =
validateBindingArray(envName, validateArtifactsBinding)(
diagnostics,
`${field}.artifacts`,
previews.artifacts,
undefined
) && isValid;

isValid =
validateBindingArray(envName, validateHelloWorldBinding)(
diagnostics,
Expand Down
11 changes: 11 additions & 0 deletions packages/workers-utils/src/map-worker-metadata-bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,17 @@ export function mapWorkerMetadataBindings(
];
}
break;
case "artifacts":
{
configObj.artifacts = [
...(configObj.artifacts ?? []),
{
binding: binding.name,
namespace: binding.namespace,
},
];
}
break;
case "unsafe_hello_world": {
configObj.unsafe_hello_world = [
...(configObj.unsafe_hello_world ?? []),
Expand Down
7 changes: 7 additions & 0 deletions packages/workers-utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
CfQueue,
CfR2Bucket,
CfRateLimit,
CfArtifacts,
CfSecretsStoreSecrets,
CfSendEmailBindings,
CfService,
Expand Down Expand Up @@ -155,6 +156,11 @@ export type WorkerMetadataBinding =
store_id: string;
secret_name: string;
}
| {
type: "artifacts";
name: string;
namespace: string;
}
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
| {
type: "unsafe_hello_world";
name: string;
Expand Down Expand Up @@ -336,6 +342,7 @@ export type Binding =
| ({ type: "mtls_certificate" } & BindingOmit<CfMTlsCertificate>)
| ({ type: "pipeline" } & BindingOmit<CfPipeline>)
| ({ type: "secrets_store_secret" } & BindingOmit<CfSecretsStoreSecrets>)
| ({ type: "artifacts" } & BindingOmit<CfArtifacts>)
| ({ type: "logfwdr" } & NameOmit<CfLogfwdrBinding>)
| ({ type: "unsafe_hello_world" } & BindingOmit<CfHelloWorld>)
| ({ type: "flagship" } & BindingOmit<CfFlagship>)
Expand Down
6 changes: 6 additions & 0 deletions packages/workers-utils/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ export interface CfSecretsStoreSecrets {
secret_name: string;
}

export interface CfArtifacts {
binding: string;
namespace: string;
remote?: boolean;
}

export interface CfHelloWorld {
binding: string;
enable_timer?: boolean;
Expand Down
Loading
Loading