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 packages/cli/release.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ export default {

// 发布配置
publish: {
// 是否发布到 npm
npm: true,
// npm 发布由 tag workflow 统一负责,避免本地脚本与 CI 重复发布
npm: false,
// npm 发布配置
npmConfig: {
access: 'public',
Expand Down Expand Up @@ -86,12 +86,12 @@ export default {
methods: ['console', 'discord'],
// Discord 配置
discord: {
webhookUrl: process.env.DISCORD_WEBHOOK_URL || 'https://discord.com/api/webhooks/1460226980938125387/5fWgMuGmkGtb6j3eoDaz4JtSFfH8LtFtHK9F2srIHGoXp71zm4sHFPCc729PujDbHJ2F',
webhookUrl: process.env.DISCORD_WEBHOOK_URL,
},
// 通知模板
templates: {
success: '🎉 版本 {{version}} 发布成功!',
failure: '❌ 版本 {{version}} 发布失败:{{error}}',
},
},
};
};
57 changes: 57 additions & 0 deletions packages/cli/tests/unit/scripts/release-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { describe, expect, it } from 'vitest';

const configPath = path.resolve(__dirname, '../../../release.config.js');
const workflowsPath = path.resolve(__dirname, '../../../../../.github/workflows');

async function loadReleaseConfig(cacheKey: string) {
return (await import(`${pathToFileURL(configPath).href}?${cacheKey}`)).default as {
publish: { npm: boolean; git: boolean };
notifications: { discord: { webhookUrl?: string } };
};
}

describe('release ownership contract', () => {
it('publishes npm only from the tag workflow', async () => {
const releaseConfig = await loadReleaseConfig('publish-owner');
const workflowFiles = fs
.readdirSync(workflowsPath)
.filter((file) => /\.ya?ml$/.test(file));
const npmPublishers = workflowFiles.filter((file) =>
fs.readFileSync(path.join(workflowsPath, file), 'utf8').includes('npm publish')
);
const publishWorkflow = fs.readFileSync(
path.join(workflowsPath, 'publish.yml'),
'utf8'
);

expect(releaseConfig.publish.npm).toBe(false);
expect(releaseConfig.publish.git).toBe(true);
expect(npmPublishers).toEqual(['publish.yml']);
expect(publishWorkflow).toContain("- 'v*.*.*'");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
expect(publishWorkflow).toContain('npm publish --access public');
});

it('loads notification credentials only from the environment', async () => {
const previousWebhook = process.env.DISCORD_WEBHOOK_URL;
process.env.DISCORD_WEBHOOK_URL = 'https://example.invalid/test-webhook';

try {
const releaseConfig = await loadReleaseConfig('notification-env');
const configSource = fs.readFileSync(configPath, 'utf8');

expect(releaseConfig.notifications.discord.webhookUrl).toBe(
'https://example.invalid/test-webhook'
);
expect(/https:\/\/discord\.com\/api\/webhooks\//.test(configSource)).toBe(false);
Comment on lines +37 to +48

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Assert behavior when DISCORD_WEBHOOK_URL is absent.

The test verifies that an environment value is selected. It does not verify that no fallback credential exists. A hardcoded or constructed fallback can pass the current test.

Delete the variable, load the configuration with a distinct cache key, and assert that webhookUrl is undefined before testing the configured environment value.

Proposed test update
     try {
+      delete process.env.DISCORD_WEBHOOK_URL;
+      const missingCredentialConfig = await loadReleaseConfig(
+        'notification-no-env'
+      );
+      expect(
+        missingCredentialConfig.notifications.discord.webhookUrl
+      ).toBeUndefined();
+
       const releaseConfig = await loadReleaseConfig('notification-env');
🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 42-42: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(configPath, 'utf8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/tests/unit/scripts/release-config.test.ts` around lines 37 - 48,
Update the `loads notification credentials only from the environment` test to
first remove `DISCORD_WEBHOOK_URL`, load the configuration with a distinct cache
key, and assert `releaseConfig.notifications.discord.webhookUrl` is undefined.
Then restore the environment value, load the configured case, and retain the
existing assertion that it is selected.

} finally {
if (previousWebhook === undefined) {
delete process.env.DISCORD_WEBHOOK_URL;
} else {
process.env.DISCORD_WEBHOOK_URL = previousWebhook;
}
}
});
});
Loading