Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ NEXT_PUBLIC_LARK_WIKI_URL = https://open-source-bazaar.feishu.cn/wiki/space/7052

NEXT_PUBLIC_LARK_BITABLE_ID = PNOGbGqhPacsHOsvJqHctS77nje
NEXT_PUBLIC_ACTIVITY_TABLE_ID = tblREEMxDOECZZrK
NEXT_PUBLIC_PROJECT_TABLE_ID = tblGnY6Hm0nTSBR9

NEXT_PUBLIC_STRAPI_API_HOST = https://china-ngo-db.onrender.com/api/
39 changes: 23 additions & 16 deletions .github/scripts/share-reward.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,37 @@ interface PRMeta {
assignees: components['schemas']['simple-user'][];
}

const PR_URL = await $`gh api graphql -f query='{
const PR_DATA = await $`gh api graphql -f query='{
repository(owner: "${repositoryOwner}", name: "${repositoryName}") {
issue(number: ${issueNumber}) {
closedByPullRequestsReferences(first: 10) {
nodes {
url
merged
mergeCommit {
oid
}
}
}
}
}
}' --jq '.data.repository.issue.closedByPullRequestsReferences.nodes[] | select(.merged == true) | .url' | head -n 1`;
}' --jq '.data.repository.issue.closedByPullRequestsReferences.nodes[] | select(.merged == true) | {url: .url, mergeCommitSha: .mergeCommit.oid}' | head -n 1`;

if (!PR_URL.text().trim())
throw new ReferenceError('No merged PR is found for the given issue number.');
const prData = PR_DATA.text().trim();

if (!prData) throw new ReferenceError('No merged PR is found for the given issue number.');

const { url: PR_URL, mergeCommitSha } = JSON.parse(prData);

if (!PR_URL || !mergeCommitSha) throw new Error('Missing required fields in PR data');

console.table({ PR_URL, mergeCommitSha });

const { author, assignees }: PRMeta = await (
await $`gh pr view ${PR_URL} --json author,assignees`
).json();

// Function to check if a user is a Copilot/bot user
function isCopilotUser(login: string): boolean {
function isBotUser(login: string) {
const lowerLogin = login.toLowerCase();
return (
lowerLogin.includes('copilot') ||
Expand All @@ -50,19 +59,17 @@ function isCopilotUser(login: string): boolean {
);
}

// Filter out Copilot and bot users from the list
// Filter out Bot users from the list
const allUsers = [author.login, ...assignees.map(({ login }) => login)];
const users = allUsers.filter(login => !isCopilotUser(login));
const users = allUsers.filter(login => !isBotUser(login));

console.log(`All users: ${allUsers.join(', ')}`);
console.log(`Filtered users (excluding bots/copilot): ${users.join(', ')}`);
console.log(`Filtered users (excluding bots): ${users.join(', ')}`);

// Handle case where all users are bots/copilot
if (users.length === 0) {
console.log('No real users found (all users are bots/copilot). Skipping reward distribution.');
console.log(`Filtered users: ${allUsers.join(', ')}`);
process.exit(0);
}
if (!users[0])
throw new ReferenceError(
'No real users found (all users are bots). Skipping reward distribution.',
);

const rewardNumber = parseFloat(reward);

Expand All @@ -86,7 +93,7 @@ console.log(listText);

await $`git config --global user.name "github-actions[bot]"`;
await $`git config --global user.email "github-actions[bot]@users.noreply.github.com"`;
await $`git tag -a "reward-${issueNumber}" -m ${listText}`;
await $`git tag -a "reward-${issueNumber}" ${mergeCommitSha} -m ${listText}`;
await $`git push origin --tags`;

const commentBody = `## Reward data
Expand Down
3 changes: 2 additions & 1 deletion components/Navigator/MainNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ const topNavBarMenu = ({ t }: typeof i18n): MenuItem[] => [
{
title: t('open_source_projects'),
subs: [
{ href: '/project', title: t('open_source_projects') },
{ href: '/project', title: t('self_developed_projects') },
{ href: '/search/project', title: t('bazaar_projects') },
{ href: '/issue', title: 'GitHub issues' },
{ href: '/license-filter', title: t('license_filter') },
{ href: '/finance', title: t('finance_page_title') },
Expand Down
15 changes: 15 additions & 0 deletions components/Project/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FC } from 'react';

import { Project } from '../../models/Project';
import { GitCard } from '../Git/Card';

export const ProjectCard: FC<Project> = ({ name, sourceLink, link, languages, tags, summary }) => (
<GitCard
full_name={name as string}
html_url={sourceLink as string}
homepage={link as string}
languages={languages as string[]}
topics={tags as string[]}
description={summary as string}
/>
Comment on lines +6 to +14
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

类型断言存在安全隐患,且缺少 MobX observer 包装。

此组件存在两个问题:

  1. 类型安全问题(关键):所有属性都使用 as stringas string[] 强制类型转换。根据 models/Project.ts 的定义,这些字段的类型是 TableCellValue,它可能是多种类型(number、boolean、array 等)。强制转换会绕过 TypeScript 的类型检查,可能导致运行时错误。

  2. 缺少 MobX observer:参考 ActivityCardOrganizationCard,卡片组件应该用 observer() 包装以响应数据变化。如果 Project 数据是可观察的(observable),当前实现将无法正确更新 UI。

🔧 建议的修复方案

方案 1(推荐):类型守卫 + observer 包装

-import { FC } from 'react';
+import { observer } from 'mobx-react';
+import { FC } from 'react';

 import { Project } from '../../models/Project';
 import { GitCard } from '../Git/Card';

-export const ProjectCard: FC<Project> = ({ name, sourceLink, link, languages, tags, summary }) => (
-  <GitCard
-    full_name={name as string}
-    html_url={sourceLink as string}
-    homepage={link as string}
-    languages={languages as string[]}
-    topics={tags as string[]}
-    description={summary as string}
-  />
-);
+export const ProjectCard: FC<Project> = observer(
+  ({ name, sourceLink, link, languages, tags, summary }) => {
+    const safeString = (value: unknown) => (typeof value === 'string' ? value : '');
+    const safeArray = (value: unknown) => (Array.isArray(value) ? value : []);
+
+    return (
+      <GitCard
+        full_name={safeString(name)}
+        html_url={safeString(sourceLink)}
+        homepage={safeString(link)}
+        languages={safeArray(languages) as string[]}
+        topics={safeArray(tags) as string[]}
+        description={safeString(summary)}
+      />
+    );
+  },
+);

方案 2:在 Project 模型层添加类型转换

如果 TableCellValue 总是返回正确类型,可在 models/Project.ts 中添加类型断言辅助函数,然后在组件中使用这些安全的转换。

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @components/Project/Card.tsx around lines 6 - 14, ProjectCard currently
force-casts all props (name, sourceLink, link, languages, tags, summary) to
string/string[] which bypasses type checks for TableCellValue and can cause
runtime errors; update ProjectCard to perform safe type guards/parsers for each
prop (e.g., check typeof or Array.isArray and coerce/format values only when
valid, handle missing/invalid cases with fallbacks) or call a safe conversion
helper from models/Project.ts that returns the expected string/string[] types,
and wrap the component export with MobX observer() (same pattern used by
ActivityCard and OrganizationCard) so the card reacts to observable changes.

);
66 changes: 66 additions & 0 deletions models/Project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
BiDataQueryOptions,
BiDataTable,
BiSearch,
TableCellLink,
TableCellValue,
TableRecord,
} from 'mobx-lark';

import { LarkBase, larkClient } from './Base';
import { LarkBitableId, ProjectTableId } from './configuration';

export type Project = LarkBase &
Record<
| 'name'
| 'type'
| 'sourceLink'
| 'link'
| 'license'
| 'languages'
| 'tags'
| 'summary'
| 'logo'
| 'status'
| 'reason',
TableCellValue
>;

export class ProjectModel extends BiDataTable<Project>() {
client = larkClient;

queryOptions: BiDataQueryOptions = { text_field_as_array: false };

constructor(appId = LarkBitableId, tableId = ProjectTableId) {
super(appId, tableId);
}

extractFields({
fields: { sourceLink, link, languages, tags, ...fields },
...meta
}: TableRecord<Project>) {
return {
...meta,
...fields,
sourceLink: (sourceLink as TableCellLink)?.link,
link: (link as TableCellLink)?.link,
languages: languages?.toString().split(/\s*,\s*/) || [],
tags: tags?.toString().split(/\s*,\s*/) || [],
};
}
Comment on lines +38 to +50
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

修复字段提取逻辑中的运行时错误风险。

关键问题(第 47-48 行):
languagestagsundefined 时,languages?.toString().split(...) 会抛出 TypeError。可选链只保护了 toString() 调用,但 split() 仍然会在 undefined 上调用导致错误。

次要问题(第 45-46 行):
类型断言后访问 .link 属性缺少可选链,如果 sourceLinklink 不是 TableCellLink 类型,可能返回 undefined

🔧 建议的修复方案
 extractFields({
   fields: { sourceLink, link, languages, tags, ...fields },
   ...meta
 }: TableRecord<Project>) {
   return {
     ...meta,
     ...fields,
-    sourceLink: (sourceLink as TableCellLink)?.link,
-    link: (link as TableCellLink)?.link,
-    languages: languages?.toString().split(/\s*,\s*/) || [],
-    tags: tags?.toString().split(/\s*,\s*/) || [],
+    sourceLink: (sourceLink as TableCellLink | undefined)?.link,
+    link: (link as TableCellLink | undefined)?.link,
+    languages: (languages?.toString() ?? '').split(/\s*,\s*/).filter(Boolean),
+    tags: (tags?.toString() ?? '').split(/\s*,\s*/).filter(Boolean),
   };
 }

或使用更简洁的写法:

-    languages: languages?.toString().split(/\s*,\s*/) || [],
-    tags: tags?.toString().split(/\s*,\s*/) || [],
+    languages: languages ? String(languages).split(/\s*,\s*/) : [],
+    tags: tags ? String(tags).split(/\s*,\s*/) : [],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
extractFields({
fields: { sourceLink, link, languages, tags, ...fields },
...meta
}: TableRecord<Project>) {
return {
...meta,
...fields,
sourceLink: (sourceLink as TableCellLink)?.link,
link: (link as TableCellLink)?.link,
languages: languages?.toString().split(/\s*,\s*/) || [],
tags: tags?.toString().split(/\s*,\s*/) || [],
};
}
extractFields({
fields: { sourceLink, link, languages, tags, ...fields },
...meta
}: TableRecord<Project>) {
return {
...meta,
...fields,
sourceLink: (sourceLink as TableCellLink | undefined)?.link,
link: (link as TableCellLink | undefined)?.link,
languages: (languages?.toString() ?? '').split(/\s*,\s*/).filter(Boolean),
tags: (tags?.toString() ?? '').split(/\s*,\s*/).filter(Boolean),
};
}
🤖 Prompt for AI Agents
In @models/Project.ts around lines 38 - 50, In extractFields, guard against
calling split on undefined and ensure safe access to link properties: for
languages and tags replace languages?.toString().split(...) and
tags?.toString().split(...) with a nullish-safe expression like (languages ??
'').toString().split(/\s*,\s*/).filter(Boolean) and (tags ??
'').toString().split(/\s*,\s*/).filter(Boolean); and ensure sourceLink and link
use optional chaining on the casted TableCellLink (e.g. (sourceLink as
TableCellLink)?.link and (link as TableCellLink)?.link) so accessing .link
cannot throw if the value is not a TableCellLink.

}

export class SearchProjectModel extends BiSearch<Project>(ProjectModel) {
searchKeys = [
'name',
'type',
'sourceLink',
'link',
'license',
'languages',
'tags',
'summary',
'status',
'reason',
];
}
2 changes: 2 additions & 0 deletions models/System.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Constructor } from 'web-utility';
import { SearchActivityModel } from './Activity';
import { ownClient } from './Base';
import { OrganizationModel } from './Organization';
import { SearchProjectModel } from './Project';

export type SearchableFilter<D extends DataObject> = Filter<D> & {
keywords?: string;
Expand All @@ -22,6 +23,7 @@ export type CityCoordinateMap = Record<string, [number, number]>;
export class SystemModel extends BaseModel {
searchMap = {
activity: SearchActivityModel,
project: SearchProjectModel,
NGO: OrganizationModel,
} as Record<string, Constructor<SearchModel<DataObject>>>;

Expand Down
3 changes: 2 additions & 1 deletion models/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ export const LarkWikiDomain = hostname;
export const LarkWikiId = pathname.split('/').pop()!;

export const LarkBitableId = process.env.NEXT_PUBLIC_LARK_BITABLE_ID!,
ActivityTableId = process.env.NEXT_PUBLIC_ACTIVITY_TABLE_ID!;
ActivityTableId = process.env.NEXT_PUBLIC_ACTIVITY_TABLE_ID!,
ProjectTableId = process.env.NEXT_PUBLIC_PROJECT_TABLE_ID!;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n models/configuration.ts | head -50

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 1725


🏁 Script executed:

# Check for imports in configuration.ts to verify GitHub client handling
rg "import" models/configuration.ts -A 2

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 195


🏁 Script executed:

# Search for environment variable configuration documentation
fd -t f "{\.env,.env.example,\.env.local,README}" --exec cat {} \; 2>/dev/null | head -100

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 73


🏁 Script executed:

# Check if NEXT_PUBLIC_PROJECT_TABLE_ID is documented anywhere
rg "NEXT_PUBLIC_PROJECT_TABLE_ID|PROJECT_TABLE_ID" --type ts --type tsx --type md

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 118


🏁 Script executed:

# Check if models/Base.ts exists
ls -la models/Base.ts 2>/dev/null || echo "Base.ts not found"

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 154


🏁 Script executed:

# Search for where configuration.ts is imported
rg "from.*configuration|import.*configuration" --type ts --type tsx -B 1 -A 1

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 118


🏁 Script executed:

# Look for environment variable documentation in common config files
rg "NEXT_PUBLIC_PROJECT_TABLE_ID|PROJECT_TABLE_ID" -B 2 -A 2

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 359


🏁 Script executed:

# Check deployment/build configuration files
fd -t f "vercel|github|docker|build|deploy" --exec cat {} \; 2>/dev/null | head -50

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 73


🏁 Script executed:

# Search for .env examples or documentation
fd -type f -name "*env*" -o -name "*.example" 2>/dev/null

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 73


🏁 Script executed:

cat -n models/Base.ts

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 2812


🏁 Script executed:

# Check which model files import Base.ts
rg "from.*['\"]./Base['\"]|import.*Base.*from" models/ --type ts

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 699


🏁 Script executed:

# Check how configuration.ts is used in models directory
rg "configuration" models/ --type ts -B 1 -A 1

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 654


🏁 Script executed:

# Check if there's a .env.example or similar documentation
find . -name "*.example" -o -name ".env*" -o -name "README*" | head -20

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 73


🏁 Script executed:

# Check deployment/build configuration files for env variable documentation
fd -type f "vercel|next.config" | xargs cat 2>/dev/null | head -100

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 261


🏁 Script executed:

# Check if Lark table IDs are used in documentation
rg "ACTIVITY_TABLE_ID|PROJECT_TABLE_ID|LARK_BITABLE" . --type md -B 1 -A 1

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 73


🏁 Script executed:

# Search for any README or documentation files
find . -maxdepth 3 -type f \( -name "README*" -o -name "*.md" \) | head -20

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 73


🏁 Script executed:

# Check for next.config.js or vercel.json
find . -maxdepth 2 -type f \( -name "next.config*" -o -name "vercel.json" -o -name ".env.example" \)

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 73


🏁 Script executed:

# Look for any documentation mentioning environment variables
rg "NEXT_PUBLIC|环境变量|environment variable" . --type md -l 2>/dev/null || echo "No markdown files found with env var documentation"

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 141


🏁 Script executed:

# Check how other projects in this repo handle environment variables
rg -l "NEXT_PUBLIC" . --type ts | head -5 | xargs -I {} grep -l "NEXT_PUBLIC" {}

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 116


🏁 Script executed:

# Look at package.json or CI/CD configuration
find . -maxdepth 2 \( -name "package.json" -o -name ".github" -o -name "Dockerfile" \)

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 73


补充完善环境变量文档配置。

代码模式本身一致,ProjectTableId 的导出遵循了既有约定。然而,缺少关键的部署配置文档:NEXT_PUBLIC_PROJECT_TABLE_ID(同时包括 NEXT_PUBLIC_ACTIVITY_TABLE_ID 等)未在 .env.example 或部署说明中记录,这会影响部署过程。建议:

  1. 在项目根目录创建 .env.example 文件,列出所有 NEXT_PUBLIC_* 环境变量及其说明(特别是 Lark Bitable 表 ID)
  2. 在 README.md 或贡献指南中补充环境变量配置说明
  3. 确保部署流程(Vercel、CI/CD)正确配置这些变量
🤖 Prompt for AI Agents
In @models/configuration.ts around lines 38 - 39, Add documentation and example
env file for the NEXT_PUBLIC_* vars referenced by ActivityTableId and
ProjectTableId: create a .env.example at project root that lists
NEXT_PUBLIC_PROJECT_TABLE_ID, NEXT_PUBLIC_ACTIVITY_TABLE_ID (and any other
NEXT_PUBLIC_* keys) with brief descriptions (e.g., Lark Bitable table IDs) and
placeholder values; update README.md or CONTRIBUTING.md with a short section
explaining how to populate these vars and where to obtain the table IDs; and
ensure CI/CD / Vercel deployment instructions include steps to add these
environment variables to the deployment settings.

3 changes: 3 additions & 0 deletions pages/search/[model]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { CardPage, CardPageProps } from '../../../components/Layout/CardPage';
import { PageHead } from '../../../components/Layout/PageHead';
import { SearchBar } from '../../../components/Navigator/SearchBar';
import { OrganizationCard } from '../../../components/Organization/Card';
import { ProjectCard } from '../../../components/Project/Card';
import systemStore, { SearchPageMeta } from '../../../models/System';
import { i18n, I18nContext } from '../../../models/Translation';

Expand Down Expand Up @@ -40,11 +41,13 @@ export const getServerSideProps = compose<{ model: string }, SearchModelPageProp

const SearchNameMap = ({ t }: typeof i18n): Record<string, string> => ({
activity: t('activity'),
project: t('open_source_projects'),
NGO: t('NGO'),
});

const SearchCardMap: Record<string, CardPageProps['Card']> = {
activity: ActivityCard,
project: ProjectCard,
NGO: OrganizationCard,
};

Expand Down
2 changes: 2 additions & 0 deletions translation/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export default {
hackathon: 'Hackathon',
bounty: 'Open Source Bounty',
open_source_projects: 'Open Source projects',
self_developed_projects: 'Self-developed projects',
bazaar_projects: 'Bazaar projects',
open_source_bazaar: 'Open Source Bazaar',
home_page: 'Home Page',
wiki: 'Wiki',
Expand Down
2 changes: 2 additions & 0 deletions translation/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export default {
join_us: '参与',
open_collaborator_award: '开放协作人奖',
open_source_projects: '开源项目',
self_developed_projects: '自研项目',
bazaar_projects: '市集项目',
activity: '活动',
hackathon: '黑客马拉松',
bounty: '开源悬赏',
Expand Down
2 changes: 2 additions & 0 deletions translation/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export default {
join_us: '參與',
open_collaborator_award: '開放協作人獎',
open_source_projects: '開源項目',
self_developed_projects: '自研項目',
bazaar_projects: '市集項目',
activity: '活動',
hackathon: '黑客馬拉松',
bounty: '開源懸賞',
Expand Down
Loading