Skip to content
Open
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
17 changes: 13 additions & 4 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,11 @@ const PullGithubMenu = {
const octokit = initOcto(ctx)
notic(showNotification, 'Pull img from origin...')
try {
const { tree } = await octokit.getPathTree()
const { tree } = octokit.pathByDate
? await octokit.getPathTreeRecursive()
: await octokit.getPathTree()
const imgList: ImgType[] = tree
.filter(each => /\.(jpg|png|jpeg|gif|webp)$/.test(each.path))
.filter(each => /\.(jpg|png|jpeg|gif|webp)$/i.test(each.path))
.map(each => {
const unzipImg = unzip({
f: each.path,
Expand Down Expand Up @@ -132,9 +134,10 @@ const handle = async (ctx: picgo) => {
}
return octokit
.upload(img)
.then(({ imgUrl, sha }) => {
.then(({ imgUrl, sha, fileName: effectiveFileName }) => {
img.imgUrl = imgUrl
img.sha = sha
if (effectiveFileName) img.fileName = effectiveFileName
ret.push(img)
index++
return up()
Expand Down Expand Up @@ -222,6 +225,12 @@ const config = (ctx: picgo): PluginConfig[] => {
default: userConfig.path || '',
required: false
},
{
name: 'pathByDate',
type: 'confirm',
default: userConfig.pathByDate ?? false,
required: false
},
{
name: 'customUrl',
type: 'input',
Expand All @@ -231,7 +240,7 @@ const config = (ctx: picgo): PluginConfig[] => {
{
name: 'origin',
type: 'list',
default: userConfig.type || 'github',
default: userConfig.origin || 'github',
required: true,
choices: [{
name: 'github',
Expand Down
2 changes: 2 additions & 0 deletions lib/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export interface PluginConfig {
token: string,
customUrl?: string
origin?: 'github' | 'gitee'
/** When true, upload to path/year/month (e.g. images/2026/01) */
pathByDate?: boolean
}

export type ImgType = {
Expand Down
42 changes: 37 additions & 5 deletions lib/octokit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Octokit from '@octokit/rest'
import dayjs from 'dayjs'
import { getNow, pathJoin } from './helper'
import { PluginConfig, ImgType } from './interface'
import urlJoin from 'url-join'
Expand All @@ -15,13 +16,15 @@ export class Octo {
customUrl: string = ''
octokit: Octokit = null
origin: PluginConfig['origin']
pathByDate: boolean = false
constructor ({
repo,
branch,
path = '',
token,
customUrl = '',
origin = 'github'
origin = 'github',
pathByDate = false
}: PluginConfig) {
const [owner, r] = repo.split('/')
if (!r) throw new Error('Error in repo name')
Expand All @@ -32,6 +35,7 @@ export class Octo {
this.token = token
this.customUrl = customUrl
this.origin = origin
this.pathByDate = pathByDate
this.octokit = new Octokit({
baseUrl: origin === 'github' ? GithubUrl : GiteeUrl,
auth: token ? `token ${token}` : undefined
Expand Down Expand Up @@ -63,6 +67,29 @@ export class Octo {
}
return { sha, tree }
}
/** Recursively get all files under path (for Pull when pathByDate). Returns flat list with paths relative to this.path. */
async getPathTreeRecursive (): Promise<{ tree: { path: string; sha: string }[] }> {
const { tree } = await this.getPathTree()
const flat: { path: string; sha: string }[] = []
const collect = async (sha: string, prefix: string): Promise<void> => {
const list = await this.getTree(sha)
for (const item of list) {
if (item.type === 'tree') {
await collect(item.sha, prefix + item.path + '/')
} else {
flat.push({ path: prefix + item.path, sha: item.sha })
}
}
}
for (const item of tree) {
if (item.type === 'tree') {
await collect(item.sha, item.path + '/')
} else {
flat.push({ path: item.path, sha: item.sha })
}
}
return { tree: flat }
}
Comment on lines +70 to +92
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

getTree return type is missing the type field used here.

Line 77 accesses item.type on results from this.getTree(sha) (Line 75), but getTree (Line 47) declares its return type as { path: string; sha: string }[] — there's no type property. The GitHub API does return it at runtime, but the type declaration should be fixed to avoid silent undefined checks if TypeScript strict mode is ever enabled.

Proposed fix
-  async getTree (sha): Promise<{ path: string; sha: string }[]> {
+  async getTree (sha): Promise<{ path: string; sha: string; type?: string }[]> {

Additionally, the recursion is sequential (await in a for loop). For repos with many subdirectories this could be slow, but it's acceptable for typical image hosting use cases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/octokit.ts` around lines 70 - 92, getTree's declared return type lacks
the item.type field used by getPathTreeRecursive; update the type signature of
getTree to return items with a type property (e.g., an interface/typedef like {
path: string; sha: string; type: 'blob' | 'tree' | string }) and adjust any
usages accordingly so item.type is typed, then ensure getPathTreeRecursive
continues to consume that typed list from getTree; locate the getTree and
getPathTreeRecursive functions in lib/octokit.ts to apply the change.

createFile(params) {
const { isGithub } = this
const request = this.octokit.request(`/repos/:owner/:repo/contents/:path`, {
Expand Down Expand Up @@ -122,22 +149,27 @@ export class Octo {
content: Buffer.from(JSON.stringify(data)).toString('base64')
})
}
async upload (img: ImgInfo) {
async upload (img: ImgInfo): Promise<{ imgUrl: string; sha: string; fileName?: string }> {
/* istanbul ignore next */
const { owner, repo, branch, path = '' } = this
const { fileName } = img
const effectiveRelativePath = this.pathByDate
? pathJoin(dayjs().format('YYYY'), dayjs().format('MM'), fileName)
: fileName
Comment on lines +156 to +158
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Call dayjs() once to avoid inconsistent year/month at a boundary.

dayjs() is invoked twice on Line 157. If the clock ticks past midnight on a month boundary between the two calls, you could get a path like 2026/02 when you intended 2026/01 (or vice versa). Store the result once:

Proposed fix
-    const effectiveRelativePath = this.pathByDate
-      ? pathJoin(dayjs().format('YYYY'), dayjs().format('MM'), fileName)
-      : fileName
+    const now = dayjs()
+    const effectiveRelativePath = this.pathByDate
+      ? pathJoin(now.format('YYYY'), now.format('MM'), fileName)
+      : fileName
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/octokit.ts` around lines 156 - 158, The path-building uses dayjs() twice
in the expression that sets effectiveRelativePath (when this.pathByDate is
true), which can produce inconsistent year/month across a boundary; fix by
calling dayjs() once, store it in a local variable (e.g., now or currentDate)
and then use currentDate.format('YYYY') and currentDate.format('MM') when
building the path for effectiveRelativePath so both year and month come from the
same instant.

const filePath = pathJoin(path, effectiveRelativePath)
const d = await this.createFile({
owner,
repo,
path: pathJoin(path, fileName),
path: filePath,
message: `Upload ${fileName} by picGo - ${getNow()}`,
content: img.base64Image || Buffer.from(img.buffer).toString('base64'),
branch
})
if (d) {
return {
imgUrl: this.parseUrl(fileName),
sha: d.data.content.sha
imgUrl: this.parseUrl(effectiveRelativePath),
sha: d.data.content.sha,
fileName: this.pathByDate ? effectiveRelativePath : undefined
}
}
/* istanbul ignore next */
Expand Down