@pellux/goodvibes-sdk 0.25.17 → 0.25.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/contracts/artifacts/operator-contract.json +1 -1
- package/dist/_internal/contracts/generated/foundation-metadata.d.ts +1 -1
- package/dist/_internal/contracts/generated/foundation-metadata.js +1 -1
- package/dist/_internal/contracts/generated/operator-contract.js +1 -1
- package/dist/_internal/platform/cloudflare/manager.d.ts.map +1 -1
- package/dist/_internal/platform/cloudflare/manager.js +11 -12
- package/dist/_internal/platform/cloudflare/resources.d.ts +2 -20
- package/dist/_internal/platform/cloudflare/resources.d.ts.map +1 -1
- package/dist/_internal/platform/cloudflare/resources.js +246 -116
- package/dist/_internal/platform/cloudflare/worker-settings.d.ts +29 -0
- package/dist/_internal/platform/cloudflare/worker-settings.d.ts.map +1 -0
- package/dist/_internal/platform/cloudflare/worker-settings.js +146 -0
- package/dist/_internal/platform/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Synced from packages/contracts/src/generated/foundation-metadata.ts
|
|
2
2
|
export const FOUNDATION_METADATA = {
|
|
3
3
|
"productId": "goodvibes",
|
|
4
|
-
"productVersion": "0.25.
|
|
4
|
+
"productVersion": "0.25.19",
|
|
5
5
|
"operatorMethodCount": 224,
|
|
6
6
|
"operatorEventCount": 30,
|
|
7
7
|
"peerEndpointCount": 6
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/cloudflare/manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/cloudflare/manager.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAIV,6BAA6B,EAC7B,4BAA4B,EAC5B,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EAGvB,+BAA+B,EAC/B,gCAAgC,EAChC,wBAAwB,EACxB,yBAAyB,EAMzB,gCAAgC,EAChC,iCAAiC,EAEjC,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAyBpB,qBAAa,6BAA6B;IAI5B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqD;IAClF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAEZ,OAAO,EAAE,6BAA6B;IAK7D,cAAc,IAAI,OAAO,CAAC,4BAA4B,CAAC;IA0C7D,iBAAiB,CAAC,KAAK,GAAE,gCAAqC,GAAG,iCAAiC;IAkB5F,sBAAsB,CAAC,KAAK,EAAE,+BAA+B,GAAG,OAAO,CAAC,gCAAgC,CAAC;IAqEzG,QAAQ,CAAC,KAAK,GAAE,uBAA4B,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAuEhF,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAuB3E,SAAS,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC;IA0S9E,MAAM,CAAC,KAAK,GAAE,qBAA0B,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAe1E,OAAO,CAAC,KAAK,GAAE,sBAA2B,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAwBnF,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,iBAAiB;YAYX,eAAe;YAaf,oBAAoB;YAapB,wBAAwB;YAaxB,gBAAgB;YAahB,WAAW;IAOzB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,yBAAyB;YAQnB,WAAW;IAazB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,SAAS;CAMlB"}
|
|
@@ -4,9 +4,10 @@ import { createCloudflareApiClient } from './client.js';
|
|
|
4
4
|
import { readCloudflareConfig } from './config.js';
|
|
5
5
|
import { CLOUDFLARE_API_TOKEN_KEY, CLOUDFLARE_WORKER_CLIENT_TOKEN_KEY, CLOUDFLARE_WORKER_OPERATOR_TOKEN_KEY, DEFAULT_DLQ_NAME, DEFAULT_DO_NAMESPACE_NAME, DEFAULT_KV_NAMESPACE_NAME, DEFAULT_QUEUE_NAME, DEFAULT_R2_BUCKET_NAME, DEFAULT_SECRETS_STORE_NAME, DEFAULT_TUNNEL_NAME, DEFAULT_WORKER_CRON, DEFAULT_WORKER_NAME, } from './constants.js';
|
|
6
6
|
import { discoverZones, resolveZone, selectDiscoveredZone, tryDiscover, } from './discovery.js';
|
|
7
|
-
import { configureDns,
|
|
7
|
+
import { configureDns, ensureAccess, ensureKvNamespace, ensureQueue, ensureQueueConsumer, ensureR2Bucket, ensureSecretsStore, ensureTunnel, findDurableObjectNamespace, } from './resources.js';
|
|
8
8
|
import { CloudflareControlPlaneError } from './types.js';
|
|
9
9
|
import { buildTokenPolicies, buildTokenRequirements, clean, collectAsync, collectSingleAccount, hostnameFromUrl, requireKvNamespaceId, requireQueueId, resolveComponents, resolvePermissionGroups, safeResponseText, stripTrailingSlash, verifyCreatedTokenPolicies, } from './utils.js';
|
|
10
|
+
import { configureWorkerSchedule, configureWorkerSubdomain, disableWorkerSchedule, disableWorkerSubdomain, uploadWorker, } from './worker-settings.js';
|
|
10
11
|
export class CloudflareControlPlaneManager {
|
|
11
12
|
options;
|
|
12
13
|
createClient;
|
|
@@ -285,9 +286,13 @@ export class CloudflareControlPlaneManager {
|
|
|
285
286
|
deadLetterQueueId = requireQueueId(deadLetterQueue, deadLetterQueueName);
|
|
286
287
|
queueId = requireQueueId(queue, queueName);
|
|
287
288
|
}
|
|
288
|
-
const
|
|
289
|
+
const kvName = clean(input.kvNamespaceName) || config.kvNamespaceName || DEFAULT_KV_NAMESPACE_NAME;
|
|
290
|
+
const kvId = clean(input.kvNamespaceId) || config.kvNamespaceId;
|
|
291
|
+
const kv = components.kv ? await ensureKvNamespace(resourceContext, client, accountId, kvName, kvId, persist, steps) : undefined;
|
|
289
292
|
const r2 = components.r2 ? await ensureR2Bucket(resourceContext, client, accountId, clean(input.r2BucketName) || config.r2BucketName || DEFAULT_R2_BUCKET_NAME, persist, steps) : undefined;
|
|
290
|
-
const
|
|
293
|
+
const secretsStoreName = clean(input.secretsStoreName) || config.secretsStoreName || DEFAULT_SECRETS_STORE_NAME;
|
|
294
|
+
const secretsStoreId = clean(input.secretsStoreId) || config.secretsStoreId;
|
|
295
|
+
const secretsStore = components.secretsStore ? await ensureSecretsStore(resourceContext, client, accountId, secretsStoreName, secretsStoreId, persist, steps) : undefined;
|
|
291
296
|
const tunnel = components.zeroTrustTunnel
|
|
292
297
|
? await ensureTunnel(resourceContext, client, {
|
|
293
298
|
accountId,
|
|
@@ -374,11 +379,7 @@ export class CloudflareControlPlaneManager {
|
|
|
374
379
|
});
|
|
375
380
|
}
|
|
376
381
|
if (workerCron) {
|
|
377
|
-
await client
|
|
378
|
-
account_id: accountId,
|
|
379
|
-
body: [{ cron: workerCron }],
|
|
380
|
-
});
|
|
381
|
-
steps.push({ name: 'configure-cron', status: 'ok', message: `Configured Worker cron ${workerCron}.` });
|
|
382
|
+
await configureWorkerSchedule(client, { accountId, workerName, workerCron, steps });
|
|
382
383
|
}
|
|
383
384
|
else {
|
|
384
385
|
steps.push({ name: 'configure-cron', status: 'skipped', message: 'No Worker cron configured.' });
|
|
@@ -524,12 +525,10 @@ export class CloudflareControlPlaneManager {
|
|
|
524
525
|
if (apiToken.value && accountId) {
|
|
525
526
|
const client = await this.createClient(apiToken.value);
|
|
526
527
|
if (input.disableCron !== false) {
|
|
527
|
-
await client
|
|
528
|
-
steps.push({ name: 'disable-cron', status: 'ok', message: `Removed Worker cron schedules from ${workerName}.` });
|
|
528
|
+
await disableWorkerSchedule(client, accountId, workerName, steps);
|
|
529
529
|
}
|
|
530
530
|
if (input.disableWorkerSubdomain) {
|
|
531
|
-
await client
|
|
532
|
-
steps.push({ name: 'disable-worker-subdomain', status: 'ok', message: `Disabled workers.dev route for ${workerName}.` });
|
|
531
|
+
await disableWorkerSubdomain(client, accountId, workerName, steps);
|
|
533
532
|
}
|
|
534
533
|
}
|
|
535
534
|
else {
|
|
@@ -6,9 +6,9 @@ export interface CloudflareProvisioningContext {
|
|
|
6
6
|
readonly storeSecret: (key: string, value: string) => Promise<void>;
|
|
7
7
|
}
|
|
8
8
|
export declare function ensureQueue(client: CloudflareApiClient, accountId: string, queueName: string, steps: CloudflareProvisionStep[], stepName: string): Promise<CloudflareQueueLike>;
|
|
9
|
-
export declare function ensureKvNamespace(context: CloudflareProvisioningContext, client: CloudflareApiClient, accountId: string, namespaceName: string, persist: boolean, steps: CloudflareProvisionStep[]): Promise<CloudflareKvNamespaceLike>;
|
|
9
|
+
export declare function ensureKvNamespace(context: CloudflareProvisioningContext, client: CloudflareApiClient, accountId: string, namespaceName: string, configuredNamespaceId: string, persist: boolean, steps: CloudflareProvisionStep[]): Promise<CloudflareKvNamespaceLike>;
|
|
10
10
|
export declare function ensureR2Bucket(context: CloudflareProvisioningContext, client: CloudflareApiClient, accountId: string, bucketName: string, persist: boolean, steps: CloudflareProvisionStep[]): Promise<CloudflareR2BucketLike>;
|
|
11
|
-
export declare function ensureSecretsStore(context: CloudflareProvisioningContext, client: CloudflareApiClient, accountId: string, storeName: string, persist: boolean, steps: CloudflareProvisionStep[]): Promise<CloudflareSecretsStoreLike>;
|
|
11
|
+
export declare function ensureSecretsStore(context: CloudflareProvisioningContext, client: CloudflareApiClient, accountId: string, storeName: string, configuredStoreId: string, persist: boolean, steps: CloudflareProvisionStep[]): Promise<CloudflareSecretsStoreLike>;
|
|
12
12
|
export declare function ensureTunnel(context: CloudflareProvisioningContext, client: CloudflareApiClient, input: {
|
|
13
13
|
readonly accountId: string;
|
|
14
14
|
readonly tunnelName: string;
|
|
@@ -50,24 +50,6 @@ export declare function configureDns(client: CloudflareApiClient, input: {
|
|
|
50
50
|
readonly steps: CloudflareProvisionStep[];
|
|
51
51
|
}): Promise<readonly CloudflareDnsRecordLike[]>;
|
|
52
52
|
export declare function findDurableObjectNamespace(context: CloudflareProvisioningContext, client: CloudflareApiClient, accountId: string, namespaceName: string, persist: boolean, steps: CloudflareProvisionStep[]): Promise<CloudflareDurableObjectNamespaceLike | undefined>;
|
|
53
|
-
export declare function uploadWorker(client: CloudflareApiClient, input: {
|
|
54
|
-
readonly accountId: string;
|
|
55
|
-
readonly workerName: string;
|
|
56
|
-
readonly queueName: string;
|
|
57
|
-
readonly daemonBaseUrl: string;
|
|
58
|
-
readonly queueJobPayloads: boolean;
|
|
59
|
-
readonly kvNamespaceId: string;
|
|
60
|
-
readonly r2BucketName: string;
|
|
61
|
-
readonly durableObject: boolean;
|
|
62
|
-
}): Promise<void>;
|
|
63
|
-
export declare function configureWorkerSubdomain(context: CloudflareProvisioningContext, client: CloudflareApiClient, input: {
|
|
64
|
-
readonly accountId: string;
|
|
65
|
-
readonly workerName: string;
|
|
66
|
-
readonly requestedSubdomain?: string;
|
|
67
|
-
readonly enableWorkersDev: boolean;
|
|
68
|
-
readonly steps: CloudflareProvisionStep[];
|
|
69
|
-
readonly persist: boolean;
|
|
70
|
-
}): Promise<string>;
|
|
71
53
|
export declare function ensureQueueConsumer(client: CloudflareApiClient, input: {
|
|
72
54
|
readonly accountId: string;
|
|
73
55
|
readonly queueId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/cloudflare/resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/cloudflare/resources.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,71 +1,116 @@
|
|
|
1
1
|
import { summarizeError } from '../utils/error-display.js';
|
|
2
|
-
import { CLOUDFLARE_ACCESS_SERVICE_TOKEN_KEY, CLOUDFLARE_TUNNEL_TOKEN_KEY,
|
|
2
|
+
import { CLOUDFLARE_ACCESS_SERVICE_TOKEN_KEY, CLOUDFLARE_TUNNEL_TOKEN_KEY, } from './constants.js';
|
|
3
3
|
import { CloudflareControlPlaneError } from './types.js';
|
|
4
4
|
import { clean, collectAsync, hostnameFromUrl } from './utils.js';
|
|
5
|
-
import { GOODVIBES_CLOUDFLARE_WORKER_MODULE } from './worker-source.js';
|
|
6
5
|
export async function ensureQueue(client, accountId, queueName, steps, stepName) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
const existing = await findQueueByName(client, accountId, queueName);
|
|
7
|
+
if (existing) {
|
|
8
|
+
steps.push({ name: stepName, status: 'ok', message: `Using existing Cloudflare Queue ${queueName}.`, resourceId: existing.queue_id });
|
|
9
|
+
return existing;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const created = await client.queues.create({ account_id: accountId, queue_name: queueName });
|
|
13
|
+
steps.push({ name: stepName, status: 'ok', message: `Created Cloudflare Queue ${queueName}.`, resourceId: created.queue_id });
|
|
14
|
+
return created;
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
const recovered = await findQueueByName(client, accountId, queueName);
|
|
18
|
+
if (recovered) {
|
|
19
|
+
steps.push({ name: stepName, status: 'ok', message: `Using existing Cloudflare Queue ${queueName} after create retry: ${summarizeError(error)}`, resourceId: recovered.queue_id });
|
|
20
|
+
return recovered;
|
|
11
21
|
}
|
|
22
|
+
throw error;
|
|
12
23
|
}
|
|
13
|
-
const created = await client.queues.create({ account_id: accountId, queue_name: queueName });
|
|
14
|
-
steps.push({ name: stepName, status: 'ok', message: `Created Cloudflare Queue ${queueName}.`, resourceId: created.queue_id });
|
|
15
|
-
return created;
|
|
16
24
|
}
|
|
17
|
-
export async function ensureKvNamespace(context, client, accountId, namespaceName, persist, steps) {
|
|
25
|
+
export async function ensureKvNamespace(context, client, accountId, namespaceName, configuredNamespaceId, persist, steps) {
|
|
18
26
|
if (!client.kv) {
|
|
19
27
|
throw new CloudflareControlPlaneError('The Cloudflare client does not expose KV namespace APIs.', 'CLOUDFLARE_KV_API_UNAVAILABLE', 500);
|
|
20
28
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
const existing = await findKvNamespace(client, accountId, namespaceName, configuredNamespaceId);
|
|
30
|
+
if (existing) {
|
|
31
|
+
persistKvNamespace(context, existing, namespaceName, persist);
|
|
32
|
+
steps.push({ name: 'kv-namespace', status: 'ok', message: `Using existing KV namespace ${namespaceName}.`, resourceId: existing.id });
|
|
33
|
+
return existing;
|
|
34
|
+
}
|
|
35
|
+
if (configuredNamespaceId) {
|
|
36
|
+
const configured = { id: configuredNamespaceId, title: namespaceName };
|
|
37
|
+
persistKvNamespace(context, configured, namespaceName, persist);
|
|
38
|
+
steps.push({ name: 'kv-namespace', status: 'ok', message: `Using configured KV namespace ${namespaceName}.`, resourceId: configuredNamespaceId });
|
|
39
|
+
return configured;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const created = await client.kv.namespaces.create({ account_id: accountId, title: namespaceName });
|
|
43
|
+
persistKvNamespace(context, created, namespaceName, persist);
|
|
44
|
+
steps.push({ name: 'kv-namespace', status: 'ok', message: `Created KV namespace ${namespaceName}.`, resourceId: created.id });
|
|
45
|
+
return created;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
const recovered = await findKvNamespace(client, accountId, namespaceName, configuredNamespaceId);
|
|
49
|
+
if (recovered) {
|
|
50
|
+
persistKvNamespace(context, recovered, namespaceName, persist);
|
|
51
|
+
steps.push({ name: 'kv-namespace', status: 'ok', message: `Using existing KV namespace ${namespaceName} after create retry: ${summarizeError(error)}`, resourceId: recovered.id });
|
|
52
|
+
return recovered;
|
|
28
53
|
}
|
|
54
|
+
throw error;
|
|
29
55
|
}
|
|
30
|
-
const created = await client.kv.namespaces.create({ account_id: accountId, title: namespaceName });
|
|
31
|
-
context.setConfig('cloudflare.kvNamespaceName', namespaceName, persist);
|
|
32
|
-
if (created.id)
|
|
33
|
-
context.setConfig('cloudflare.kvNamespaceId', created.id, persist);
|
|
34
|
-
steps.push({ name: 'kv-namespace', status: 'ok', message: `Created KV namespace ${namespaceName}.`, resourceId: created.id });
|
|
35
|
-
return created;
|
|
36
56
|
}
|
|
37
57
|
export async function ensureR2Bucket(context, client, accountId, bucketName, persist, steps) {
|
|
38
58
|
if (!client.r2) {
|
|
39
59
|
throw new CloudflareControlPlaneError('The Cloudflare client does not expose R2 bucket APIs.', 'CLOUDFLARE_R2_API_UNAVAILABLE', 500);
|
|
40
60
|
}
|
|
41
|
-
const existing =
|
|
61
|
+
const existing = await findR2BucketByName(client, accountId, bucketName);
|
|
42
62
|
if (existing) {
|
|
43
63
|
context.setConfig('cloudflare.r2BucketName', bucketName, persist);
|
|
44
64
|
steps.push({ name: 'r2-bucket', status: 'ok', message: `Using existing R2 Standard bucket ${bucketName}.`, resourceId: bucketName });
|
|
45
65
|
return existing;
|
|
46
66
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
try {
|
|
68
|
+
const created = await client.r2.buckets.create({ account_id: accountId, name: bucketName, storageClass: 'Standard' });
|
|
69
|
+
context.setConfig('cloudflare.r2BucketName', bucketName, persist);
|
|
70
|
+
steps.push({ name: 'r2-bucket', status: 'ok', message: `Created R2 Standard bucket ${bucketName}.`, resourceId: bucketName });
|
|
71
|
+
return created.name ? created : { ...created, name: bucketName, storage_class: 'Standard' };
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const recovered = await findR2BucketByName(client, accountId, bucketName);
|
|
75
|
+
if (recovered) {
|
|
76
|
+
context.setConfig('cloudflare.r2BucketName', bucketName, persist);
|
|
77
|
+
steps.push({ name: 'r2-bucket', status: 'ok', message: `Using existing R2 bucket ${bucketName} after create retry: ${summarizeError(error)}`, resourceId: bucketName });
|
|
78
|
+
return recovered;
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
51
82
|
}
|
|
52
|
-
export async function ensureSecretsStore(context, client, accountId, storeName, persist, steps) {
|
|
83
|
+
export async function ensureSecretsStore(context, client, accountId, storeName, configuredStoreId, persist, steps) {
|
|
53
84
|
if (!client.secretsStore) {
|
|
54
85
|
throw new CloudflareControlPlaneError('The Cloudflare client does not expose Secrets Store APIs.', 'CLOUDFLARE_SECRETS_STORE_API_UNAVAILABLE', 500);
|
|
55
86
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
87
|
+
const existing = await findSecretsStore(client, accountId, storeName, configuredStoreId);
|
|
88
|
+
if (existing) {
|
|
89
|
+
persistSecretsStore(context, existing, persist);
|
|
90
|
+
steps.push({ name: 'secrets-store', status: 'ok', message: `Using existing Cloudflare Secrets Store ${existing.name}.`, resourceId: existing.id });
|
|
91
|
+
return existing;
|
|
92
|
+
}
|
|
93
|
+
if (configuredStoreId) {
|
|
94
|
+
const configured = { id: configuredStoreId, name: storeName };
|
|
95
|
+
persistSecretsStore(context, configured, persist);
|
|
96
|
+
steps.push({ name: 'secrets-store', status: 'warning', message: `Using configured Cloudflare Secrets Store id ${configuredStoreId}; it was not visible during discovery.`, resourceId: configuredStoreId });
|
|
97
|
+
return configured;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
for await (const created of client.secretsStore.stores.create({ account_id: accountId, body: [{ name: storeName }] })) {
|
|
101
|
+
persistSecretsStore(context, created, persist);
|
|
102
|
+
steps.push({ name: 'secrets-store', status: 'ok', message: `Created Cloudflare Secrets Store ${storeName}.`, resourceId: created.id });
|
|
103
|
+
return created;
|
|
62
104
|
}
|
|
63
105
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
106
|
+
catch (error) {
|
|
107
|
+
const recovered = await findRecoverableSecretsStore(client, accountId, storeName, configuredStoreId, error);
|
|
108
|
+
if (recovered) {
|
|
109
|
+
persistSecretsStore(context, recovered, persist);
|
|
110
|
+
steps.push({ name: 'secrets-store', status: recovered.name === storeName ? 'ok' : 'warning', message: `Using existing Cloudflare Secrets Store ${recovered.name} after create retry: ${summarizeError(error)}`, resourceId: recovered.id });
|
|
111
|
+
return recovered;
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
69
114
|
}
|
|
70
115
|
throw new CloudflareControlPlaneError(`Cloudflare Secrets Store '${storeName}' did not return a created store.`, 'CLOUDFLARE_SECRETS_STORE_CREATE_FAILED', 502);
|
|
71
116
|
}
|
|
@@ -86,8 +131,16 @@ export async function ensureTunnel(context, client, input) {
|
|
|
86
131
|
}
|
|
87
132
|
}
|
|
88
133
|
if (!tunnel) {
|
|
89
|
-
|
|
90
|
-
|
|
134
|
+
try {
|
|
135
|
+
tunnel = await api.create({ account_id: input.accountId, name: input.tunnelName, config_src: 'cloudflare' });
|
|
136
|
+
input.steps.push({ name: 'zero-trust-tunnel', status: 'ok', message: `Created Zero Trust Tunnel ${input.tunnelName}.`, resourceId: tunnel.id });
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
tunnel = await findTunnelByName(client, input.accountId, input.tunnelName);
|
|
140
|
+
if (!tunnel)
|
|
141
|
+
throw error;
|
|
142
|
+
input.steps.push({ name: 'zero-trust-tunnel', status: 'ok', message: `Using existing Zero Trust Tunnel ${input.tunnelName} after create retry: ${summarizeError(error)}`, resourceId: tunnel.id });
|
|
143
|
+
}
|
|
91
144
|
}
|
|
92
145
|
else {
|
|
93
146
|
input.steps.push({ name: 'zero-trust-tunnel', status: 'ok', message: `Using existing Zero Trust Tunnel ${input.tunnelName}.`, resourceId: tunnel.id });
|
|
@@ -135,8 +188,16 @@ export async function ensureAccess(context, client, input) {
|
|
|
135
188
|
}
|
|
136
189
|
}
|
|
137
190
|
if (!serviceToken) {
|
|
138
|
-
|
|
139
|
-
|
|
191
|
+
try {
|
|
192
|
+
serviceToken = await api.serviceTokens.create({ account_id: input.accountId, name: 'GoodVibes Daemon', duration: '8760h' });
|
|
193
|
+
input.steps.push({ name: 'zero-trust-access-service-token', status: 'ok', message: 'Created Zero Trust Access service token.', resourceId: serviceToken.id });
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
serviceToken = await findAccessServiceTokenByName(api, input.accountId, 'GoodVibes Daemon');
|
|
197
|
+
if (!serviceToken)
|
|
198
|
+
throw error;
|
|
199
|
+
input.steps.push({ name: 'zero-trust-access-service-token', status: 'ok', message: `Using existing Zero Trust Access service token after create retry: ${summarizeError(error)}`, resourceId: serviceToken.id });
|
|
200
|
+
}
|
|
140
201
|
}
|
|
141
202
|
else {
|
|
142
203
|
input.steps.push({ name: 'zero-trust-access-service-token', status: 'ok', message: 'Using existing Zero Trust Access service token.', resourceId: serviceToken.id });
|
|
@@ -190,9 +251,20 @@ export async function ensureAccess(context, client, input) {
|
|
|
190
251
|
},
|
|
191
252
|
],
|
|
192
253
|
};
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
254
|
+
if (app?.id) {
|
|
255
|
+
app = await api.applications.update(app.id, accessAppParams);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
try {
|
|
259
|
+
app = await api.applications.create(accessAppParams);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
const recovered = await findAccessApplicationByDomain(api, input.accountId, input.daemonHostname);
|
|
263
|
+
if (!recovered?.id)
|
|
264
|
+
throw error;
|
|
265
|
+
app = await api.applications.update(recovered.id, accessAppParams);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
196
268
|
if (app.id)
|
|
197
269
|
context.setConfig('cloudflare.accessAppId', app.id, input.persist);
|
|
198
270
|
input.steps.push({ name: 'zero-trust-access-app', status: 'ok', message: `Configured Zero Trust Access application for ${input.daemonHostname}.`, resourceId: app.id });
|
|
@@ -243,63 +315,6 @@ export async function findDurableObjectNamespace(context, client, accountId, nam
|
|
|
243
315
|
steps.push({ name: 'durable-object-namespace', status: 'warning', message: 'Durable Object migration was included, but the namespace was not visible yet during confirmation.' });
|
|
244
316
|
return { name: namespaceName };
|
|
245
317
|
}
|
|
246
|
-
export async function uploadWorker(client, input) {
|
|
247
|
-
const file = new File([GOODVIBES_CLOUDFLARE_WORKER_MODULE], 'goodvibes-cloudflare-worker.mjs', { type: 'application/javascript+module' });
|
|
248
|
-
const bindings = [
|
|
249
|
-
...(input.queueName ? [{ type: 'queue', name: 'GOODVIBES_BATCH_QUEUE', queue_name: input.queueName }] : []),
|
|
250
|
-
{ type: 'plain_text', name: 'GOODVIBES_DAEMON_URL', text: input.daemonBaseUrl },
|
|
251
|
-
{ type: 'plain_text', name: 'GOODVIBES_QUEUE_JOB_PAYLOADS', text: input.queueJobPayloads ? 'true' : 'false' },
|
|
252
|
-
...(input.kvNamespaceId ? [{ type: 'kv_namespace', name: 'GOODVIBES_KV', namespace_id: input.kvNamespaceId }] : []),
|
|
253
|
-
...(input.r2BucketName ? [{ type: 'r2_bucket', name: 'GOODVIBES_ARTIFACTS', bucket_name: input.r2BucketName }] : []),
|
|
254
|
-
...(input.durableObject ? [{ type: 'durable_object_namespace', name: 'GOODVIBES_COORDINATOR', class_name: DEFAULT_DO_NAMESPACE_NAME }] : []),
|
|
255
|
-
];
|
|
256
|
-
await client.workers.scripts.update(input.workerName, {
|
|
257
|
-
account_id: input.accountId,
|
|
258
|
-
metadata: {
|
|
259
|
-
main_module: 'goodvibes-cloudflare-worker.mjs',
|
|
260
|
-
compatibility_date: '2026-04-25',
|
|
261
|
-
bindings,
|
|
262
|
-
...(input.durableObject ? { migrations: { tag: 'goodvibes-coordinator-v1', new_sqlite_classes: [DEFAULT_DO_NAMESPACE_NAME] } } : {}),
|
|
263
|
-
keep_bindings: ['secret_text'],
|
|
264
|
-
},
|
|
265
|
-
files: [file],
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
export async function configureWorkerSubdomain(context, client, input) {
|
|
269
|
-
if (!input.enableWorkersDev) {
|
|
270
|
-
input.steps.push({ name: 'worker-subdomain', status: 'skipped', message: 'workers.dev subdomain enablement was skipped.' });
|
|
271
|
-
return clean(input.requestedSubdomain) || context.readConfig().workerSubdomain;
|
|
272
|
-
}
|
|
273
|
-
let accountSubdomain = clean(input.requestedSubdomain) || context.readConfig().workerSubdomain;
|
|
274
|
-
if (accountSubdomain) {
|
|
275
|
-
const updated = await client.workers.subdomains.update({ account_id: input.accountId, subdomain: accountSubdomain });
|
|
276
|
-
accountSubdomain = updated.subdomain;
|
|
277
|
-
context.setConfig('cloudflare.workerSubdomain', accountSubdomain, input.persist);
|
|
278
|
-
input.steps.push({ name: 'account-worker-subdomain', status: 'ok', message: `Configured account workers.dev subdomain ${accountSubdomain}.` });
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
try {
|
|
282
|
-
const existing = await client.workers.subdomains.get({ account_id: input.accountId });
|
|
283
|
-
accountSubdomain = existing.subdomain;
|
|
284
|
-
context.setConfig('cloudflare.workerSubdomain', accountSubdomain, input.persist);
|
|
285
|
-
input.steps.push({ name: 'account-worker-subdomain', status: 'ok', message: `Using account workers.dev subdomain ${accountSubdomain}.` });
|
|
286
|
-
}
|
|
287
|
-
catch (error) {
|
|
288
|
-
input.steps.push({
|
|
289
|
-
name: 'account-worker-subdomain',
|
|
290
|
-
status: 'warning',
|
|
291
|
-
message: `Could not read account workers.dev subdomain: ${summarizeError(error)}`,
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
await client.workers.scripts.subdomain.create(input.workerName, {
|
|
296
|
-
account_id: input.accountId,
|
|
297
|
-
enabled: true,
|
|
298
|
-
previews_enabled: false,
|
|
299
|
-
});
|
|
300
|
-
input.steps.push({ name: 'worker-subdomain', status: 'ok', message: `Enabled workers.dev route for ${input.workerName}.` });
|
|
301
|
-
return accountSubdomain;
|
|
302
|
-
}
|
|
303
318
|
export async function ensureQueueConsumer(client, input) {
|
|
304
319
|
const settings = {
|
|
305
320
|
batch_size: 10,
|
|
@@ -320,15 +335,31 @@ export async function ensureQueueConsumer(client, input) {
|
|
|
320
335
|
return updated;
|
|
321
336
|
}
|
|
322
337
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
338
|
+
try {
|
|
339
|
+
const created = await client.queues.consumers.create(input.queueId, {
|
|
340
|
+
account_id: input.accountId,
|
|
341
|
+
type: 'worker',
|
|
342
|
+
script_name: input.workerName,
|
|
343
|
+
dead_letter_queue: input.deadLetterQueueName,
|
|
344
|
+
settings,
|
|
345
|
+
});
|
|
346
|
+
input.steps.push({ name: 'queue-consumer', status: 'ok', message: `Created Queue consumer for ${input.workerName}.`, resourceId: created.consumer_id });
|
|
347
|
+
return created;
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
const recovered = await findQueueConsumer(client, input.accountId, input.queueId, input.workerName);
|
|
351
|
+
if (!recovered?.consumer_id)
|
|
352
|
+
throw error;
|
|
353
|
+
const updated = await client.queues.consumers.update(input.queueId, recovered.consumer_id, {
|
|
354
|
+
account_id: input.accountId,
|
|
355
|
+
type: 'worker',
|
|
356
|
+
script_name: input.workerName,
|
|
357
|
+
dead_letter_queue: input.deadLetterQueueName,
|
|
358
|
+
settings,
|
|
359
|
+
});
|
|
360
|
+
input.steps.push({ name: 'queue-consumer', status: 'ok', message: `Updated existing Queue consumer for ${input.workerName} after create retry: ${summarizeError(error)}`, resourceId: updated.consumer_id });
|
|
361
|
+
return updated;
|
|
362
|
+
}
|
|
332
363
|
}
|
|
333
364
|
async function ensureCnameRecord(client, zoneId, hostname, target, steps, stepName) {
|
|
334
365
|
const existing = await collectAsync(client.dns.records.list({ zone_id: zoneId, type: 'CNAME', name: { exact: hostname } }));
|
|
@@ -347,7 +378,106 @@ async function ensureCnameRecord(client, zoneId, hostname, target, steps, stepNa
|
|
|
347
378
|
steps.push({ name: stepName, status: 'ok', message: `Updated CNAME ${hostname} -> ${target}.`, resourceId: updated.id });
|
|
348
379
|
return updated;
|
|
349
380
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
381
|
+
try {
|
|
382
|
+
const created = await client.dns.records.create(params);
|
|
383
|
+
steps.push({ name: stepName, status: 'ok', message: `Created CNAME ${hostname} -> ${target}.`, resourceId: created.id });
|
|
384
|
+
return created;
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
const recovered = await findCnameRecord(client, zoneId, hostname);
|
|
388
|
+
if (!recovered?.id)
|
|
389
|
+
throw error;
|
|
390
|
+
const updated = await client.dns.records.update(recovered.id, params);
|
|
391
|
+
steps.push({ name: stepName, status: 'ok', message: `Updated existing CNAME ${hostname} -> ${target} after create retry: ${summarizeError(error)}`, resourceId: updated.id });
|
|
392
|
+
return updated;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async function findQueueByName(client, accountId, queueName) {
|
|
396
|
+
for await (const queue of client.queues.list({ account_id: accountId })) {
|
|
397
|
+
if (queue.queue_name === queueName)
|
|
398
|
+
return queue;
|
|
399
|
+
}
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
async function findKvNamespace(client, accountId, namespaceName, namespaceId) {
|
|
403
|
+
for await (const namespace of client.kv.namespaces.list({ account_id: accountId })) {
|
|
404
|
+
if (namespace.title === namespaceName || (namespaceId && namespace.id === namespaceId))
|
|
405
|
+
return namespace;
|
|
406
|
+
}
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
function persistKvNamespace(context, namespace, namespaceName, persist) {
|
|
410
|
+
context.setConfig('cloudflare.kvNamespaceName', namespace.title ?? namespaceName, persist);
|
|
411
|
+
if (namespace.id)
|
|
412
|
+
context.setConfig('cloudflare.kvNamespaceId', namespace.id, persist);
|
|
413
|
+
}
|
|
414
|
+
async function findR2BucketByName(client, accountId, bucketName) {
|
|
415
|
+
const listed = (await client.r2.buckets.list({ account_id: accountId })).buckets?.find((bucket) => bucket.name === bucketName);
|
|
416
|
+
if (listed)
|
|
417
|
+
return listed;
|
|
418
|
+
if (!client.r2.buckets.get)
|
|
419
|
+
return undefined;
|
|
420
|
+
try {
|
|
421
|
+
const bucket = await client.r2.buckets.get(bucketName, { account_id: accountId });
|
|
422
|
+
return bucket.name ? bucket : { ...bucket, name: bucketName };
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
return undefined;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
async function findSecretsStore(client, accountId, storeName, storeId) {
|
|
429
|
+
for await (const store of client.secretsStore.stores.list({ account_id: accountId })) {
|
|
430
|
+
if (store.name === storeName || (storeId && store.id === storeId))
|
|
431
|
+
return store;
|
|
432
|
+
}
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
async function findRecoverableSecretsStore(client, accountId, storeName, storeId, error) {
|
|
436
|
+
const stores = await collectAsync(client.secretsStore.stores.list({ account_id: accountId }));
|
|
437
|
+
const exact = stores.find((store) => store.name === storeName || (storeId && store.id === storeId));
|
|
438
|
+
if (exact)
|
|
439
|
+
return exact;
|
|
440
|
+
if (isMaximumStoresExceeded(error) && stores.length === 1)
|
|
441
|
+
return stores[0];
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
function persistSecretsStore(context, store, persist) {
|
|
445
|
+
context.setConfig('cloudflare.secretsStoreName', store.name, persist);
|
|
446
|
+
context.setConfig('cloudflare.secretsStoreId', store.id, persist);
|
|
447
|
+
}
|
|
448
|
+
function isMaximumStoresExceeded(error) {
|
|
449
|
+
return summarizeError(error).toLowerCase().includes('maximum_stores_exceeded');
|
|
450
|
+
}
|
|
451
|
+
async function findTunnelByName(client, accountId, tunnelName) {
|
|
452
|
+
const api = client.zeroTrust.tunnels.cloudflared;
|
|
453
|
+
for await (const tunnel of api.list({ account_id: accountId, name: tunnelName, is_deleted: false })) {
|
|
454
|
+
if (tunnel.name === tunnelName)
|
|
455
|
+
return tunnel;
|
|
456
|
+
}
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
async function findAccessServiceTokenByName(api, accountId, name) {
|
|
460
|
+
for await (const token of api.serviceTokens.list({ account_id: accountId, name })) {
|
|
461
|
+
if (token.name === name)
|
|
462
|
+
return token;
|
|
463
|
+
}
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
async function findAccessApplicationByDomain(api, accountId, domain) {
|
|
467
|
+
for await (const app of api.applications.list({ account_id: accountId, domain, exact: true })) {
|
|
468
|
+
if (app.domain === domain)
|
|
469
|
+
return app;
|
|
470
|
+
}
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
async function findQueueConsumer(client, accountId, queueId, workerName) {
|
|
474
|
+
for await (const consumer of client.queues.consumers.list(queueId, { account_id: accountId })) {
|
|
475
|
+
if (consumer.type === 'worker' && consumer.script === workerName)
|
|
476
|
+
return consumer;
|
|
477
|
+
}
|
|
478
|
+
return undefined;
|
|
479
|
+
}
|
|
480
|
+
async function findCnameRecord(client, zoneId, hostname) {
|
|
481
|
+
const existing = await collectAsync(client.dns.records.list({ zone_id: zoneId, type: 'CNAME', name: { exact: hostname } }));
|
|
482
|
+
return existing.find((record) => record.name === hostname && record.type === 'CNAME');
|
|
353
483
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CloudflareApiClient, CloudflareProvisionStep } from './types.js';
|
|
2
|
+
import type { CloudflareProvisioningContext } from './resources.js';
|
|
3
|
+
export declare function uploadWorker(client: CloudflareApiClient, input: {
|
|
4
|
+
readonly accountId: string;
|
|
5
|
+
readonly workerName: string;
|
|
6
|
+
readonly queueName: string;
|
|
7
|
+
readonly daemonBaseUrl: string;
|
|
8
|
+
readonly queueJobPayloads: boolean;
|
|
9
|
+
readonly kvNamespaceId: string;
|
|
10
|
+
readonly r2BucketName: string;
|
|
11
|
+
readonly durableObject: boolean;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
export declare function configureWorkerSubdomain(context: CloudflareProvisioningContext, client: CloudflareApiClient, input: {
|
|
14
|
+
readonly accountId: string;
|
|
15
|
+
readonly workerName: string;
|
|
16
|
+
readonly requestedSubdomain?: string;
|
|
17
|
+
readonly enableWorkersDev: boolean;
|
|
18
|
+
readonly steps: CloudflareProvisionStep[];
|
|
19
|
+
readonly persist: boolean;
|
|
20
|
+
}): Promise<string>;
|
|
21
|
+
export declare function configureWorkerSchedule(client: CloudflareApiClient, input: {
|
|
22
|
+
readonly accountId: string;
|
|
23
|
+
readonly workerName: string;
|
|
24
|
+
readonly workerCron: string;
|
|
25
|
+
readonly steps: CloudflareProvisionStep[];
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
export declare function disableWorkerSchedule(client: CloudflareApiClient, accountId: string, workerName: string, steps: CloudflareProvisionStep[]): Promise<void>;
|
|
28
|
+
export declare function disableWorkerSubdomain(client: CloudflareApiClient, accountId: string, workerName: string, steps: CloudflareProvisionStep[]): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=worker-settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-settings.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/cloudflare/worker-settings.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAEpE,wBAAsB,YAAY,CAChC,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE;IACL,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC,GACA,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,6BAA6B,EACtC,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE;IACL,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,uBAAuB,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B,GACA,OAAO,CAAC,MAAM,CAAC,CA4DjB;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE;IACL,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,uBAAuB,EAAE,CAAC;CAC3C,GACA,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,mBAAmB,EAC3B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,uBAAuB,EAAE,GAC/B,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,mBAAmB,EAC3B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,uBAAuB,EAAE,GAC/B,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { summarizeError } from '../utils/error-display.js';
|
|
2
|
+
import { DEFAULT_DO_NAMESPACE_NAME } from './constants.js';
|
|
3
|
+
import { clean } from './utils.js';
|
|
4
|
+
import { GOODVIBES_CLOUDFLARE_WORKER_MODULE } from './worker-source.js';
|
|
5
|
+
export async function uploadWorker(client, input) {
|
|
6
|
+
const file = new File([GOODVIBES_CLOUDFLARE_WORKER_MODULE], 'goodvibes-cloudflare-worker.mjs', { type: 'application/javascript+module' });
|
|
7
|
+
const bindings = [
|
|
8
|
+
...(input.queueName ? [{ type: 'queue', name: 'GOODVIBES_BATCH_QUEUE', queue_name: input.queueName }] : []),
|
|
9
|
+
{ type: 'plain_text', name: 'GOODVIBES_DAEMON_URL', text: input.daemonBaseUrl },
|
|
10
|
+
{ type: 'plain_text', name: 'GOODVIBES_QUEUE_JOB_PAYLOADS', text: input.queueJobPayloads ? 'true' : 'false' },
|
|
11
|
+
...(input.kvNamespaceId ? [{ type: 'kv_namespace', name: 'GOODVIBES_KV', namespace_id: input.kvNamespaceId }] : []),
|
|
12
|
+
...(input.r2BucketName ? [{ type: 'r2_bucket', name: 'GOODVIBES_ARTIFACTS', bucket_name: input.r2BucketName }] : []),
|
|
13
|
+
...(input.durableObject ? [{ type: 'durable_object_namespace', name: 'GOODVIBES_COORDINATOR', class_name: DEFAULT_DO_NAMESPACE_NAME }] : []),
|
|
14
|
+
];
|
|
15
|
+
await client.workers.scripts.update(input.workerName, {
|
|
16
|
+
account_id: input.accountId,
|
|
17
|
+
metadata: {
|
|
18
|
+
main_module: 'goodvibes-cloudflare-worker.mjs',
|
|
19
|
+
compatibility_date: '2026-04-25',
|
|
20
|
+
bindings,
|
|
21
|
+
...(input.durableObject ? { migrations: { tag: 'goodvibes-coordinator-v1', new_sqlite_classes: [DEFAULT_DO_NAMESPACE_NAME] } } : {}),
|
|
22
|
+
keep_bindings: ['secret_text'],
|
|
23
|
+
},
|
|
24
|
+
files: [file],
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export async function configureWorkerSubdomain(context, client, input) {
|
|
28
|
+
const requested = clean(input.requestedSubdomain) || context.readConfig().workerSubdomain;
|
|
29
|
+
if (!input.enableWorkersDev) {
|
|
30
|
+
input.steps.push({ name: 'worker-subdomain', status: 'skipped', message: 'workers.dev subdomain enablement was skipped.' });
|
|
31
|
+
return requested;
|
|
32
|
+
}
|
|
33
|
+
let accountSubdomain = await readAccountWorkerSubdomain(client, input.accountId);
|
|
34
|
+
if (accountSubdomain) {
|
|
35
|
+
context.setConfig('cloudflare.workerSubdomain', accountSubdomain, input.persist);
|
|
36
|
+
input.steps.push({
|
|
37
|
+
name: 'account-worker-subdomain',
|
|
38
|
+
status: requested && requested !== accountSubdomain ? 'warning' : 'ok',
|
|
39
|
+
message: requested && requested !== accountSubdomain
|
|
40
|
+
? `Using existing account workers.dev subdomain ${accountSubdomain}; requested ${requested} was ignored because the account already has an associated subdomain.`
|
|
41
|
+
: `Using account workers.dev subdomain ${accountSubdomain}.`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else if (requested) {
|
|
45
|
+
try {
|
|
46
|
+
const updated = await client.workers.subdomains.update({ account_id: input.accountId, subdomain: requested });
|
|
47
|
+
accountSubdomain = clean(updated.subdomain) || requested;
|
|
48
|
+
context.setConfig('cloudflare.workerSubdomain', accountSubdomain, input.persist);
|
|
49
|
+
input.steps.push({ name: 'account-worker-subdomain', status: 'ok', message: `Configured account workers.dev subdomain ${accountSubdomain}.` });
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const recovered = await readAccountWorkerSubdomain(client, input.accountId);
|
|
53
|
+
if (!recovered)
|
|
54
|
+
throw error;
|
|
55
|
+
accountSubdomain = recovered;
|
|
56
|
+
context.setConfig('cloudflare.workerSubdomain', accountSubdomain, input.persist);
|
|
57
|
+
input.steps.push({
|
|
58
|
+
name: 'account-worker-subdomain',
|
|
59
|
+
status: requested === recovered ? 'ok' : 'warning',
|
|
60
|
+
message: `Using existing account workers.dev subdomain ${accountSubdomain} after configure retry: ${summarizeError(error)}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
input.steps.push({ name: 'account-worker-subdomain', status: 'warning', message: 'No account workers.dev subdomain is configured.' });
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const existing = await client.workers.scripts.subdomain.get(input.workerName, { account_id: input.accountId });
|
|
69
|
+
if (existing.enabled) {
|
|
70
|
+
input.steps.push({ name: 'worker-subdomain', status: 'ok', message: `Using existing workers.dev route for ${input.workerName}.` });
|
|
71
|
+
return accountSubdomain;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Older accounts may not return script-level subdomain state before it is enabled.
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
await client.workers.scripts.subdomain.create(input.workerName, {
|
|
79
|
+
account_id: input.accountId,
|
|
80
|
+
enabled: true,
|
|
81
|
+
previews_enabled: false,
|
|
82
|
+
});
|
|
83
|
+
input.steps.push({ name: 'worker-subdomain', status: 'ok', message: `Enabled workers.dev route for ${input.workerName}.` });
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const recovered = await client.workers.scripts.subdomain.get(input.workerName, { account_id: input.accountId });
|
|
87
|
+
if (!recovered.enabled)
|
|
88
|
+
throw error;
|
|
89
|
+
input.steps.push({ name: 'worker-subdomain', status: 'ok', message: `Using existing workers.dev route for ${input.workerName} after enable retry: ${summarizeError(error)}` });
|
|
90
|
+
}
|
|
91
|
+
return accountSubdomain;
|
|
92
|
+
}
|
|
93
|
+
export async function configureWorkerSchedule(client, input) {
|
|
94
|
+
const body = [{ cron: input.workerCron }];
|
|
95
|
+
try {
|
|
96
|
+
const existing = await client.workers.scripts.schedules.get(input.workerName, { account_id: input.accountId });
|
|
97
|
+
if (sameSchedules(existing.schedules, body)) {
|
|
98
|
+
input.steps.push({ name: 'configure-cron', status: 'ok', message: `Using existing Worker cron ${input.workerCron}.` });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Some accounts return 404 until the script has its first schedule.
|
|
104
|
+
}
|
|
105
|
+
await client.workers.scripts.schedules.update(input.workerName, { account_id: input.accountId, body });
|
|
106
|
+
input.steps.push({ name: 'configure-cron', status: 'ok', message: `Configured Worker cron ${input.workerCron}.` });
|
|
107
|
+
}
|
|
108
|
+
export async function disableWorkerSchedule(client, accountId, workerName, steps) {
|
|
109
|
+
try {
|
|
110
|
+
const existing = await client.workers.scripts.schedules.get(workerName, { account_id: accountId });
|
|
111
|
+
if (existing.schedules.length === 0) {
|
|
112
|
+
steps.push({ name: 'disable-cron', status: 'skipped', message: `No Worker cron schedules were configured for ${workerName}.` });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Keep disable best-effort: if state cannot be read, clear schedules anyway.
|
|
118
|
+
}
|
|
119
|
+
await client.workers.scripts.schedules.update(workerName, { account_id: accountId, body: [] });
|
|
120
|
+
steps.push({ name: 'disable-cron', status: 'ok', message: `Removed Worker cron schedules from ${workerName}.` });
|
|
121
|
+
}
|
|
122
|
+
export async function disableWorkerSubdomain(client, accountId, workerName, steps) {
|
|
123
|
+
try {
|
|
124
|
+
const existing = await client.workers.scripts.subdomain.get(workerName, { account_id: accountId });
|
|
125
|
+
if (!existing.enabled) {
|
|
126
|
+
steps.push({ name: 'disable-worker-subdomain', status: 'skipped', message: `workers.dev route was already disabled for ${workerName}.` });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Keep disable best-effort: if state cannot be read, attempt deletion.
|
|
132
|
+
}
|
|
133
|
+
await client.workers.scripts.subdomain.delete(workerName, { account_id: accountId });
|
|
134
|
+
steps.push({ name: 'disable-worker-subdomain', status: 'ok', message: `Disabled workers.dev route for ${workerName}.` });
|
|
135
|
+
}
|
|
136
|
+
async function readAccountWorkerSubdomain(client, accountId) {
|
|
137
|
+
try {
|
|
138
|
+
return clean((await client.workers.subdomains.get({ account_id: accountId })).subdomain);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function sameSchedules(actual, expected) {
|
|
145
|
+
return actual.length === expected.length && actual.every((schedule, index) => schedule.cron === expected[index]?.cron);
|
|
146
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
let version = '0.25.
|
|
3
|
+
let version = '0.25.19';
|
|
4
4
|
try {
|
|
5
5
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf-8'));
|
|
6
6
|
version = pkg.version ?? version;
|