nsbp-cli 0.2.18 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/templates/basic/gitignore +53 -0
- package/templates/basic/src/externals/window.d.ts +11 -1
- package/templates/basic/src/reducers/index.ts +1 -6
- package/templates/basic/src/reducers/photo.ts +8 -12
- package/templates/basic/src/server/index.ts +3 -2
- package/templates/basic/src/server/photo.ts +17 -6
- package/templates/basic/src/server/utils.tsx +16 -27
- package/templates/basic/src/utils/config.ts +3 -3
- package/templates/basic/src/utils/index.ts +12 -20
- package/templates/basic/tsconfig.json +15 -11
package/README.md
CHANGED
|
@@ -147,7 +147,7 @@ node ./bin/nsbp.js --help # Test CLI locally
|
|
|
147
147
|
|
|
148
148
|
- **Package Name**: `nsbp-cli`
|
|
149
149
|
- **Bin Command**: `nsbp` (install globally and run `nsbp --help`)
|
|
150
|
-
- **Version**: `0.2.
|
|
150
|
+
- **Version**: `0.2.20`
|
|
151
151
|
- **Dependencies**: chalk, commander, fs-extra, inquirer
|
|
152
152
|
- **Package Manager**: Uses pnpm (also compatible with npm)
|
|
153
153
|
- **Node Version**: >=16.0.0
|
package/package.json
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
Thumbs.db
|
|
3
|
+
|
|
4
|
+
# Dependencies
|
|
5
|
+
node_modules
|
|
6
|
+
package-lock.json
|
|
7
|
+
yarn.lock
|
|
8
|
+
pnpm-lock.yaml
|
|
9
|
+
|
|
10
|
+
# Build outputs
|
|
11
|
+
build
|
|
12
|
+
dist
|
|
13
|
+
public/js
|
|
14
|
+
public/css
|
|
15
|
+
public/client.*
|
|
16
|
+
public/*.js
|
|
17
|
+
public/*.js.map
|
|
18
|
+
public/*.txt
|
|
19
|
+
public/*.json
|
|
20
|
+
.temp_cache
|
|
21
|
+
|
|
22
|
+
# Environment variables
|
|
23
|
+
.env
|
|
24
|
+
.env.local
|
|
25
|
+
.env.*.local
|
|
26
|
+
|
|
27
|
+
# Logs
|
|
28
|
+
logs
|
|
29
|
+
*.log
|
|
30
|
+
npm-debug.log*
|
|
31
|
+
yarn-debug.log*
|
|
32
|
+
yarn-error.log*
|
|
33
|
+
pnpm-debug.log*
|
|
34
|
+
|
|
35
|
+
# Testing
|
|
36
|
+
coverage
|
|
37
|
+
.nyc_output
|
|
38
|
+
|
|
39
|
+
# Development tools
|
|
40
|
+
.serena
|
|
41
|
+
.vscode
|
|
42
|
+
.idea
|
|
43
|
+
*.swp
|
|
44
|
+
*.swo
|
|
45
|
+
*~
|
|
46
|
+
|
|
47
|
+
# OS
|
|
48
|
+
.DS_Store
|
|
49
|
+
.DS_Store?
|
|
50
|
+
._*
|
|
51
|
+
.Spotlight-V100
|
|
52
|
+
.Trashes
|
|
53
|
+
ehthumbs.db
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
interface ServerState {
|
|
2
|
+
photo?: {
|
|
3
|
+
data?: [number, number, string][]
|
|
4
|
+
menu?: Record<string, any> | Array<{ name: string; cover?: string; count?: number }>
|
|
5
|
+
}
|
|
6
|
+
query?: Record<string, any>
|
|
7
|
+
}
|
|
8
|
+
|
|
1
9
|
declare interface Window {
|
|
2
|
-
context:
|
|
10
|
+
context: {
|
|
11
|
+
state: ServerState
|
|
12
|
+
}
|
|
3
13
|
}
|
|
@@ -4,15 +4,10 @@ import { REQUEST_QUERY } from '../store/constants'
|
|
|
4
4
|
|
|
5
5
|
const queryReducer = (state = {}, action: any) => {
|
|
6
6
|
const { type, query } = action
|
|
7
|
-
let newState = null
|
|
8
7
|
|
|
9
8
|
switch (type) {
|
|
10
9
|
case REQUEST_QUERY:
|
|
11
|
-
|
|
12
|
-
...query
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return newState
|
|
10
|
+
return { ...query }
|
|
16
11
|
default:
|
|
17
12
|
return state
|
|
18
13
|
}
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
import { GET_PHOTO_MENU, GET_PHOTO_WIDTH_HEIGHT } from '../store/constants'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
interface PhotoState {
|
|
4
|
+
data: [number, number, string][]
|
|
5
|
+
menu: Record<string, any> | Array<{ name: string; cover?: string; count?: number }>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const photoReducer = (state: PhotoState = { data: [[0, 0]], menu: {} }, action: any) => {
|
|
4
9
|
const { type, data, menu } = action
|
|
5
|
-
let newState = null
|
|
6
10
|
|
|
7
11
|
switch (type) {
|
|
8
12
|
case GET_PHOTO_MENU:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (menu) newState.menu = menu
|
|
12
|
-
|
|
13
|
-
return newState
|
|
13
|
+
return { ...state, menu: menu !== undefined ? menu : state.menu }
|
|
14
14
|
case GET_PHOTO_WIDTH_HEIGHT:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (data) newState.data = data
|
|
18
|
-
|
|
19
|
-
return newState
|
|
15
|
+
return { ...state, data: data !== undefined ? data : state.data }
|
|
20
16
|
default:
|
|
21
17
|
return state
|
|
22
18
|
}
|
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
import fs from "fs"
|
|
3
3
|
import path from "path"
|
|
4
4
|
import probe from 'probe-image-size'
|
|
5
|
-
import { useCurrentFlag, outPhotoDic, outPhotoSep, outPhotoDicPath } from '../utils/config'
|
|
6
|
-
|
|
7
|
-
const CURRENT_DIC = 'images'
|
|
8
5
|
|
|
9
6
|
// 获取项目根目录(无论从哪个目录运行服务器)
|
|
10
7
|
const getProjectRoot = () => {
|
|
@@ -67,12 +64,24 @@ export const getPhotoWH = (req: any, res: any) => {
|
|
|
67
64
|
const dic = req.query.dic || ''
|
|
68
65
|
const photosDicPath = getPhotosDicPath()
|
|
69
66
|
|
|
67
|
+
// 验证 dic 参数,只允许字母数字、下划线和短横线
|
|
68
|
+
if (dic && !/^[a-zA-Z0-9_-]+$/.test(dic)) {
|
|
69
|
+
return res.status(400).json({ error: 'Invalid directory name' })
|
|
70
|
+
}
|
|
71
|
+
|
|
70
72
|
const fileList: any = []
|
|
71
73
|
let photoPath = photosDicPath
|
|
72
74
|
if (dic) {
|
|
73
75
|
photoPath = path.join(photosDicPath, dic)
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
// 确保路径在允许的目录内,防止路径遍历攻击
|
|
79
|
+
const resolvedPhotoPath = path.resolve(photoPath)
|
|
80
|
+
const resolvedPhotosDicPath = path.resolve(photosDicPath)
|
|
81
|
+
if (!resolvedPhotoPath.startsWith(resolvedPhotosDicPath)) {
|
|
82
|
+
return res.status(403).json({ error: 'Access denied' })
|
|
83
|
+
}
|
|
84
|
+
|
|
76
85
|
const getFileList = (dir: string, list: string[]) => {
|
|
77
86
|
const arr = fs.readdirSync(dir)
|
|
78
87
|
arr.forEach((item) => {
|
|
@@ -99,12 +108,13 @@ export const getPhotoWH = (req: any, res: any) => {
|
|
|
99
108
|
|
|
100
109
|
// 按前端期望的格式包装
|
|
101
110
|
res.json({ data: whArr })
|
|
102
|
-
} catch (err) {
|
|
111
|
+
} catch (err: any) {
|
|
112
|
+
console.error('getPhotoWH error:', err)
|
|
103
113
|
res.status(500).json({ error: 'Internal Server Error', details: err.message })
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
116
|
|
|
107
|
-
export const getPhotoMenu = (
|
|
117
|
+
export const getPhotoMenu = (_req: any, res: any) => {
|
|
108
118
|
try {
|
|
109
119
|
const photosDicPath = getPhotosDicPath()
|
|
110
120
|
|
|
@@ -112,7 +122,8 @@ export const getPhotoMenu = (req: any, res: any) => {
|
|
|
112
122
|
|
|
113
123
|
// 按前端期望的格式包装
|
|
114
124
|
res.json({ data: fileMenu })
|
|
115
|
-
} catch (err) {
|
|
125
|
+
} catch (err: any) {
|
|
126
|
+
console.error('getPhotoMenu error:', err)
|
|
116
127
|
res.status(500).json({ error: 'Internal Server Error', details: err.message })
|
|
117
128
|
}
|
|
118
129
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { renderToString } from 'react-dom/server'
|
|
3
|
-
import { StaticRouter,
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { renderToString } from 'react-dom/server'
|
|
3
|
+
import { StaticRouter, Route, matchPath } from 'react-router-dom'
|
|
4
|
+
// @ts-ignore - Routes is available in react-router-dom v7 but not in types
|
|
5
|
+
import { Routes } from 'react-router-dom'
|
|
4
6
|
import routers from '../Routers'
|
|
7
|
+
// @ts-ignore
|
|
5
8
|
import { Provider } from 'react-redux'
|
|
9
|
+
// @ts-ignore
|
|
6
10
|
import getStore from '../store'
|
|
7
11
|
import serialize from 'serialize-javascript'
|
|
12
|
+
// @ts-ignore
|
|
8
13
|
import { REQUEST_QUERY } from '../store/constants'
|
|
9
14
|
import { Helmet } from 'react-helmet'
|
|
10
15
|
import { ServerStyleSheet } from 'styled-components'
|
|
@@ -12,14 +17,9 @@ import Theme from '../component/Theme'
|
|
|
12
17
|
import path from 'path'
|
|
13
18
|
import { ChunkExtractor } from '@loadable/server'
|
|
14
19
|
|
|
15
|
-
const removeCommentsAndSpacing = (str = '') =>
|
|
16
|
-
str.replace(/\/\*.*\*\//g, ' ').replace(/\s+/g, ' ')
|
|
17
|
-
|
|
18
|
-
const removeSpacing = (str = '') => str.replace(/\s+/g, ' ')
|
|
19
|
-
|
|
20
20
|
export const render = (req: any, res: any) => {
|
|
21
21
|
const store = getStore()
|
|
22
|
-
const { path:reqPath, query
|
|
22
|
+
const { path:reqPath, query } = req
|
|
23
23
|
const matchRoutes: any = []
|
|
24
24
|
const promises = []
|
|
25
25
|
|
|
@@ -30,21 +30,7 @@ export const render = (req: any, res: any) => {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
routers.some((route) => {
|
|
33
|
-
matchPath(
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
matchRoutes.forEach((item: any) => {
|
|
37
|
-
if (item?.loadData) {
|
|
38
|
-
const promise = new Promise((resolve, reject) => {
|
|
39
|
-
try {
|
|
40
|
-
store.dispatch(item?.loadData(resolve))
|
|
41
|
-
} catch (e) {
|
|
42
|
-
reject()
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
promises.push(promise)
|
|
47
|
-
}
|
|
33
|
+
matchPath(reqPath, route.path) ? matchRoutes.push(route) : ''
|
|
48
34
|
})
|
|
49
35
|
|
|
50
36
|
matchRoutes.forEach((item: any) => {
|
|
@@ -148,11 +134,14 @@ export const render = (req: any, res: any) => {
|
|
|
148
134
|
`
|
|
149
135
|
|
|
150
136
|
res.send(html)
|
|
151
|
-
} catch (e) {
|
|
137
|
+
} catch (e: any) {
|
|
138
|
+
console.error('SSR rendering error:', e)
|
|
152
139
|
sheet.seal()
|
|
140
|
+
res.status(500).send('Internal Server Error')
|
|
153
141
|
}
|
|
154
142
|
})
|
|
155
|
-
.catch((e) => {
|
|
156
|
-
|
|
143
|
+
.catch((e: any) => {
|
|
144
|
+
console.error('Data loading error:', e)
|
|
145
|
+
res.status(500).send('Data loading failed')
|
|
157
146
|
})
|
|
158
147
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
1
3
|
export const useCurrentFlag = true // 是否使用当前目录环境
|
|
2
4
|
|
|
3
5
|
export const outPhotoDic = 'Photos'
|
|
4
6
|
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
export const outPhotoDicPath = `D:${outPhotoSep}${outPhotoDic}`
|
|
7
|
+
export const outPhotoDicPath = path.join('D:', outPhotoDic)
|
|
@@ -1,31 +1,23 @@
|
|
|
1
|
-
export const getLocationParams = (param:
|
|
2
|
-
|
|
3
|
-
if (param !== '') {
|
|
4
|
-
const href = window?.location?.href
|
|
5
|
-
if (href && href.indexOf('?') !== -1) {
|
|
6
|
-
const query = href.split('?')[1]
|
|
7
|
-
const queryArr = query.split('&')
|
|
1
|
+
export const getLocationParams = (param: string) => {
|
|
2
|
+
if (typeof window === 'undefined') return ''
|
|
8
3
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
})
|
|
17
|
-
}
|
|
4
|
+
try {
|
|
5
|
+
const url = new URL(window.location.href)
|
|
6
|
+
const value = url.searchParams.get(param)
|
|
7
|
+
return value || ''
|
|
8
|
+
} catch (e) {
|
|
9
|
+
console.error('Failed to parse URL:', e)
|
|
10
|
+
return ''
|
|
18
11
|
}
|
|
19
|
-
return result
|
|
20
12
|
}
|
|
21
13
|
|
|
22
14
|
export const isSEO = () => {
|
|
23
15
|
if (typeof window !== 'undefined') {
|
|
24
|
-
|
|
16
|
+
const seo = getLocationParams('seo')
|
|
25
17
|
if (seo !== '') {
|
|
26
|
-
|
|
18
|
+
return parseInt(seo, 10)
|
|
27
19
|
}
|
|
28
|
-
return
|
|
20
|
+
return 0
|
|
29
21
|
}
|
|
30
22
|
return 0
|
|
31
23
|
}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"target": "esnext",
|
|
4
|
-
"lib": ["es5", "es6", "dom"],
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
3
|
+
"target": "esnext",
|
|
4
|
+
"lib": ["es5", "es6", "dom"],
|
|
5
|
+
"module": "esnext",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"removeComments": false,
|
|
9
|
+
"jsx": "react",
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"outDir": "./build/",
|
|
12
|
+
"noImplicitAny": true,
|
|
13
|
+
"allowSyntheticDefaultImports": true,
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"preserveConstEnums": true,
|
|
16
|
+
"skipLibCheck": true
|
|
13
17
|
},
|
|
14
|
-
"include":[
|
|
18
|
+
"include":[
|
|
15
19
|
"./src/**/*",
|
|
16
20
|
"./src/externals/*.d.ts"
|
|
17
21
|
]
|