@@ -35,6 +35,9 @@ inputs:
3535 description : ' JSON object of environment variables for build step'
3636 required : false
3737 default : ' {}'
38+ secrets-json :
39+ description : ' JSON object of secrets to upload to Cloudflare Worker (keys must not overlap with vars in wrangler config)'
40+ required : false
3841 deploy-tag :
3942 description : ' Tag for the deployment (defaults to short SHA)'
4043 required : false
@@ -400,3 +403,61 @@ runs:
400403
401404 # Deploy the version at 100%
402405 npx wrangler versions deploy "${VERSION_ID}@100%" --name "${{ inputs.worker-name }}" -y
406+
407+ - name : Upload secrets to Cloudflare
408+ if : ${{ (inputs.mode == 'full' || inputs.mode == 'deploy') && inputs.secrets-json != '' && inputs.secrets-json != '{}' }}
409+ shell : bash
410+ working-directory : ${{ inputs.working-directory }}
411+ env :
412+ CLOUDFLARE_API_TOKEN : ${{ inputs.cloudflare-api-token }}
413+ CLOUDFLARE_ACCOUNT_ID : ${{ inputs.cloudflare-account-id }}
414+ SECRETS_JSON : ${{ inputs.secrets-json }}
415+ run : |
416+ # Collect var names from wrangler env config to filter out conflicts
417+ VAR_NAMES="[]"
418+ ENV_FILE="wrangler.${{ inputs.github-environment }}.jsonc"
419+ if [ -n "${{ inputs.github-environment }}" ] && [ -f "$ENV_FILE" ]; then
420+ VAR_NAMES=$(ENV_FILE="$ENV_FILE" node --eval '
421+ const fs = require("fs");
422+ function stripJsonComments(str) {
423+ let result = "", inString = false, inLine = false, inBlock = false, i = 0;
424+ while (i < str.length) {
425+ if (!inLine && !inBlock && str[i] === "\"" && (i === 0 || str[i-1] !== "\\")) { inString = !inString; result += str[i]; i++; continue; }
426+ if (inString) { result += str[i]; i++; continue; }
427+ if (!inBlock && str.slice(i,i+2) === "//") { inLine = true; i += 2; continue; }
428+ if (!inLine && str.slice(i,i+2) === "/*") { inBlock = true; i += 2; continue; }
429+ if (inBlock && str.slice(i,i+2) === "*/") { inBlock = false; i += 2; continue; }
430+ if (inLine && (str[i] === "\n" || str[i] === "\r")) { inLine = false; result += str[i]; i++; continue; }
431+ if (inLine || inBlock) { i++; continue; }
432+ result += str[i]; i++;
433+ }
434+ return result;
435+ }
436+ const cfg = JSON.parse(stripJsonComments(fs.readFileSync(process.env.ENV_FILE, "utf8")));
437+ console.log(JSON.stringify(Object.keys(cfg.vars || {})));
438+ ')
439+ fi
440+
441+ # Filter out secrets that conflict with vars
442+ FILTERED=$(echo "$SECRETS_JSON" | VAR_NAMES="$VAR_NAMES" node --eval '
443+ const secrets = JSON.parse(require("fs").readFileSync("/dev/stdin", "utf8"));
444+ const varNames = new Set(JSON.parse(process.env.VAR_NAMES));
445+ const filtered = {};
446+ for (const [k, v] of Object.entries(secrets)) {
447+ if (varNames.has(k)) {
448+ console.error(`Skipping secret "${k}" - already defined as a var in wrangler config`);
449+ } else {
450+ filtered[k] = v;
451+ }
452+ }
453+ console.log(JSON.stringify(filtered));
454+ ')
455+
456+ if [ "$FILTERED" == "{}" ]; then
457+ echo "No secrets to upload (all filtered as var conflicts)"
458+ exit 0
459+ fi
460+
461+ echo "$FILTERED" > /tmp/worker-secrets.json
462+ npx wrangler secret bulk /tmp/worker-secrets.json --name "${{ inputs.worker-name }}"
463+ rm -f /tmp/worker-secrets.json
0 commit comments