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
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ jobs:
# Lower bound must match "engines.node" in package.json.
node: ['20', '22', '24']
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node }}
cache: npm
Expand All @@ -40,9 +40,9 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: npm
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Publish to npm

# Publishes @cloudinary/cloud to the PUBLIC npm registry
# (https://registry.npmjs.org) — never to an internal Nexus mirror.
# (https://registry.npmjs.org) — never to an internal registry mirror.
# `registry-url` below plus `publishConfig.registry` in package.json pin the
# target so a misconfigured ~/.npmrc cannot redirect a release.

Expand Down Expand Up @@ -38,17 +38,21 @@ jobs:
# Configure under Settings → Environments → npm-publish → Required reviewers.
environment: npm-publish
steps:
- uses: actions/checkout@v4
# Actions are pinned to commit SHAs so a hijacked upstream tag cannot
# run code in the job that holds the publish credential.
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: npm
registry-url: 'https://registry.npmjs.org'

# Trusted publishing (OIDC, no long-lived token) needs npm >= 11.5.1.
# Pinned (not @latest) so the publish job never runs a freshly-minted,
# unreviewed npm release.
- name: Use an npm that supports trusted publishing
run: npm install -g npm@latest
run: npm install -g npm@12.0.2

- run: npm ci

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
},
"homepage": "https://github.com/cloudinary/cloudinary-cloud#readme",
"scripts": {
"build": "tsc",
"build": "npm run clean && tsc",
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
"dev": "tsc --watch",
"test": "npm run build && node --test",
"prepack": "npm run build"
Expand Down
7 changes: 6 additions & 1 deletion src/lib/env-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ export function writeCloudEnv(
if (entries.expiresAt) pairs.push([EXPIRES_AT_KEY, entries.expiresAt]);

if (!existsSync(envPath)) {
writeFileSync(envPath, pairs.map(([k, v]) => `${k}=${v}`).join('\n') + '\n', 'utf-8');
// Owner-only: the file holds a live API secret. Applies on creation only —
// a pre-existing .env keeps whatever permissions the user gave it.
writeFileSync(envPath, pairs.map(([k, v]) => `${k}=${v}`).join('\n') + '\n', {
encoding: 'utf-8',
mode: 0o600,
});
return { action: 'created' };
}

Expand Down
8 changes: 7 additions & 1 deletion test/env-file.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { mkdtempSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, dirname } from 'node:path';
import { writeCloudEnv, readCloudEnv, hasCloudinaryUrl, isEnvExposedToGit } from '../dist/lib/env-file.js';
Expand All @@ -19,6 +19,12 @@ test('creates .env when missing', () => {
assert.equal(readFileSync(envPath, 'utf-8'), `CLOUDINARY_URL=${URL_A}\n`);
});

test('creates .env owner-readable only', { skip: process.platform === 'win32' }, () => {
const envPath = tempEnvPath();
writeCloudEnv(envPath, { cloudinaryUrl: URL_A });
assert.equal(statSync(envPath).mode & 0o777, 0o600);
});

test('appends to existing .env without CLOUDINARY_URL', () => {
const envPath = tempEnvPath();
writeFileSync(envPath, 'OTHER_VAR=hello\n');
Expand Down
Loading