create-halo-plugin-template 1.0.5 → 1.0.6
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/package.json +1 -1
- package/scripts/verify-template.mjs +46 -0
- package/src/main/java/run/halo/plugintemplate/utils/PluginTemplateSeeds.java +3 -3
- package/ui/src/api/generated/api/plugin-template-console-api.ts +1 -1
- package/ui/src/api/generated/api/plugin-template-uc-api.ts +1 -1
- package/ui/src/components/PluginTemplateOverviewPage.vue +6 -7
- package/ui/src/lib/template.spec.ts +12 -1
- package/ui/src/lib/template.ts +25 -0
package/package.json
CHANGED
|
@@ -125,6 +125,28 @@ const walkFiles = async (directory) => {
|
|
|
125
125
|
return files
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
const collectFiles = async (directory, matcher, result = []) => {
|
|
129
|
+
if (!(await exists(directory))) {
|
|
130
|
+
return result
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const entries = await fs.readdir(path.join(ROOT, directory), { withFileTypes: true })
|
|
134
|
+
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
const relativePath = path.posix.join(directory, entry.name)
|
|
137
|
+
if (entry.isDirectory()) {
|
|
138
|
+
await collectFiles(relativePath, matcher, result)
|
|
139
|
+
continue
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (matcher(relativePath)) {
|
|
143
|
+
result.push(relativePath)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result
|
|
148
|
+
}
|
|
149
|
+
|
|
128
150
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
129
151
|
|
|
130
152
|
const readText = async (relativePath) =>
|
|
@@ -261,6 +283,10 @@ const verifyRepo = async (args) => {
|
|
|
261
283
|
const permissionPrefix = `${configuredPermissionPrefix}:`
|
|
262
284
|
const resolvedConsolePath = consolePath || routePrefix
|
|
263
285
|
const resolvedUcPath = ucPath || routePrefix
|
|
286
|
+
const generatedApiFiles = await collectFiles(
|
|
287
|
+
path.posix.join(generatedClientPath, 'api'),
|
|
288
|
+
(filePath) => filePath.endsWith('.ts'),
|
|
289
|
+
)
|
|
264
290
|
|
|
265
291
|
if (!pluginName || !displayName || !basePackage) {
|
|
266
292
|
fail(results, 'Unable to resolve plugin constants from SettingKeys.java or build.gradle')
|
|
@@ -325,6 +351,26 @@ const verifyRepo = async (args) => {
|
|
|
325
351
|
fail(results, `Generated model directory is missing: ${generatedClientPath}/models`)
|
|
326
352
|
}
|
|
327
353
|
|
|
354
|
+
if (generatedApiFiles.length > 0) {
|
|
355
|
+
const generatedApiContent = (
|
|
356
|
+
await Promise.all(generatedApiFiles.map((relativePath) => readText(relativePath)))
|
|
357
|
+
).join('\n')
|
|
358
|
+
assertContains(
|
|
359
|
+
results,
|
|
360
|
+
generatedApiContent,
|
|
361
|
+
`/apis/console.${apiGroupSuffix}/v1alpha1/`,
|
|
362
|
+
'Generated client console path matches API group suffix',
|
|
363
|
+
)
|
|
364
|
+
assertContains(
|
|
365
|
+
results,
|
|
366
|
+
generatedApiContent,
|
|
367
|
+
`/apis/uc.${apiGroupSuffix}/v1alpha1/`,
|
|
368
|
+
'Generated client UC path matches API group suffix',
|
|
369
|
+
)
|
|
370
|
+
} else {
|
|
371
|
+
fail(results, `Generated API files are missing under ${generatedClientPath}/api`)
|
|
372
|
+
}
|
|
373
|
+
|
|
328
374
|
if (args['plugin-name'] && args['plugin-name'] !== pluginName) {
|
|
329
375
|
fail(results, `Expected plugin name ${args['plugin-name']}, got ${pluginName}`)
|
|
330
376
|
} else if (args['plugin-name']) {
|
|
@@ -148,10 +148,10 @@ public final class PluginTemplateSeeds {
|
|
|
148
148
|
return List.of(
|
|
149
149
|
PluginTemplateChecklistItem.builder()
|
|
150
150
|
.key("init-script")
|
|
151
|
-
.title("
|
|
152
|
-
.description("
|
|
151
|
+
.title("确认初始化结果")
|
|
152
|
+
.description("通过 npm create 或 create-project 创建项目时,这一步已经自动完成;只有手工复制模板源码时,才需要单独运行初始化脚本。")
|
|
153
153
|
.audience("all")
|
|
154
|
-
.status("
|
|
154
|
+
.status("done")
|
|
155
155
|
.build(),
|
|
156
156
|
PluginTemplateChecklistItem.builder()
|
|
157
157
|
.key("settings")
|
|
@@ -35,7 +35,7 @@ export const PluginTemplateConsoleApiAxiosParamCreator = function (configuration
|
|
|
35
35
|
* @throws {RequiredError}
|
|
36
36
|
*/
|
|
37
37
|
pluginTemplateOverviewForConsole: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
|
38
|
-
const localVarPath = `/apis/console.
|
|
38
|
+
const localVarPath = `/apis/console.plugintemplate.halo.run/v1alpha1/template-overview/summary`;
|
|
39
39
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
|
40
40
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
|
41
41
|
let baseOptions;
|
|
@@ -35,7 +35,7 @@ export const PluginTemplateUcApiAxiosParamCreator = function (configuration?: Co
|
|
|
35
35
|
* @throws {RequiredError}
|
|
36
36
|
*/
|
|
37
37
|
pluginTemplateOverviewForUc: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
|
38
|
-
const localVarPath = `/apis/uc.
|
|
38
|
+
const localVarPath = `/apis/uc.plugintemplate.halo.run/v1alpha1/template-overview/summary`;
|
|
39
39
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
|
40
40
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
|
41
41
|
let baseOptions;
|
|
@@ -3,7 +3,7 @@ import { stores, utils } from '@halo-dev/ui-shared'
|
|
|
3
3
|
import { ElAlert, ElButton, ElLink, ElSkeleton } from 'element-plus'
|
|
4
4
|
import { storeToRefs } from 'pinia'
|
|
5
5
|
import { computed, onMounted } from 'vue'
|
|
6
|
-
import { buildAudienceLabel, checklistColumns, featureColumns } from '@/lib/template'
|
|
6
|
+
import { buildAudienceLabel, checklistColumns, featureColumns, formatChecklistStatus } from '@/lib/template'
|
|
7
7
|
import { useTemplateOverview } from '@/composables/useTemplateOverview'
|
|
8
8
|
import PluginTemplateCommonTable from './PluginTemplateCommonTable.vue'
|
|
9
9
|
import PluginUiProvider from './ui/PluginUiProvider.vue'
|
|
@@ -142,7 +142,7 @@ onMounted(load)
|
|
|
142
142
|
{{ item.description }}
|
|
143
143
|
</p>
|
|
144
144
|
</div>
|
|
145
|
-
<UiStatusPill :label="item.status" :tone="item.status" />
|
|
145
|
+
<UiStatusPill :label="formatChecklistStatus(item.status)" :tone="item.status" />
|
|
146
146
|
</article>
|
|
147
147
|
</div>
|
|
148
148
|
</UiSectionCard>
|
|
@@ -226,7 +226,7 @@ onMounted(load)
|
|
|
226
226
|
empty-description="暂无初始化清单"
|
|
227
227
|
>
|
|
228
228
|
<template #cell-status="{ value }">
|
|
229
|
-
<UiStatusPill :label="String(value)" :tone="String(value)" />
|
|
229
|
+
<UiStatusPill :label="formatChecklistStatus(String(value))" :tone="String(value)" />
|
|
230
230
|
</template>
|
|
231
231
|
</PluginTemplateCommonTable>
|
|
232
232
|
</UiSectionCard>
|
|
@@ -236,14 +236,13 @@ onMounted(load)
|
|
|
236
236
|
description="模板的真实价值在于后续多个插件项目都沿用同一套工程边界。"
|
|
237
237
|
>
|
|
238
238
|
<ul style="margin: 0; padding-left: 18px; color: var(--halo-plugin-template-text-secondary); line-height: 1.8">
|
|
239
|
-
<li
|
|
240
|
-
<li
|
|
239
|
+
<li>如果你是通过 `npm create halo-plugin-template` 或 `create-project.mjs` 创建项目,初始化和一致性校验已经自动完成。</li>
|
|
240
|
+
<li>只有在手工复制模板源码仓库时,才需要额外运行初始化脚本和校验脚本。</li>
|
|
241
241
|
<li>新接口补好 Springdoc 后,执行 `./gradlew generateApiClient`,再在 `ui/src/api/index.ts` 暴露新增能力。</li>
|
|
242
242
|
<li>按插件实际范围裁剪 UC、附件扩展和工作台部件,避免模板示例残留到正式项目。</li>
|
|
243
243
|
<li>
|
|
244
244
|
如需切换到 Rsbuild,可参考
|
|
245
|
-
<ElLink :href="rsbuildGuideLink" target="_blank">Halo 官方 UI 构建说明</ElLink
|
|
246
|
-
和仓库内的 `docs/rsbuild-switch.md`。
|
|
245
|
+
<ElLink :href="rsbuildGuideLink" target="_blank">Halo 官方 UI 构建说明</ElLink>。
|
|
247
246
|
</li>
|
|
248
247
|
</ul>
|
|
249
248
|
</UiSectionCard>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { buildAudienceLabel, formatCellValue, toTagType } from './template'
|
|
2
|
+
import { buildAudienceLabel, formatCellValue, formatChecklistStatus, toTagType } from './template'
|
|
3
3
|
|
|
4
4
|
describe('template helpers', () => {
|
|
5
5
|
it('maps known audiences to readable labels', () => {
|
|
@@ -17,8 +17,19 @@ describe('template helpers', () => {
|
|
|
17
17
|
|
|
18
18
|
it('maps semantic tones to Element Plus tags', () => {
|
|
19
19
|
expect(toTagType('primary')).toBe('primary')
|
|
20
|
+
expect(toTagType('now')).toBe('primary')
|
|
20
21
|
expect(toTagType('success')).toBe('success')
|
|
22
|
+
expect(toTagType('done')).toBe('success')
|
|
21
23
|
expect(toTagType('recommended')).toBe('warning')
|
|
24
|
+
expect(toTagType('todo')).toBe('warning')
|
|
22
25
|
expect(toTagType('unknown')).toBe('info')
|
|
23
26
|
})
|
|
27
|
+
|
|
28
|
+
it('formats checklist statuses for display', () => {
|
|
29
|
+
expect(formatChecklistStatus('done')).toBe('已完成')
|
|
30
|
+
expect(formatChecklistStatus('recommended')).toBe('建议处理')
|
|
31
|
+
expect(formatChecklistStatus('todo')).toBe('待处理')
|
|
32
|
+
expect(formatChecklistStatus('optional')).toBe('按需处理')
|
|
33
|
+
expect(formatChecklistStatus('other')).toBe('other')
|
|
34
|
+
})
|
|
24
35
|
})
|
package/ui/src/lib/template.ts
CHANGED
|
@@ -18,11 +18,15 @@ export const checklistColumns: ResponsiveColumn[] = [
|
|
|
18
18
|
export const toTagType = (tone?: string): TagProps['type'] => {
|
|
19
19
|
switch (tone) {
|
|
20
20
|
case 'primary':
|
|
21
|
+
case 'now':
|
|
21
22
|
return 'primary'
|
|
22
23
|
case 'success':
|
|
24
|
+
case 'done':
|
|
25
|
+
case 'auto':
|
|
23
26
|
return 'success'
|
|
24
27
|
case 'warning':
|
|
25
28
|
case 'recommended':
|
|
29
|
+
case 'todo':
|
|
26
30
|
return 'warning'
|
|
27
31
|
case 'danger':
|
|
28
32
|
return 'danger'
|
|
@@ -31,6 +35,27 @@ export const toTagType = (tone?: string): TagProps['type'] => {
|
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
export const formatChecklistStatus = (status?: string) => {
|
|
39
|
+
switch (status) {
|
|
40
|
+
case 'done':
|
|
41
|
+
return '已完成'
|
|
42
|
+
case 'auto':
|
|
43
|
+
return '已自动完成'
|
|
44
|
+
case 'recommended':
|
|
45
|
+
return '建议处理'
|
|
46
|
+
case 'now':
|
|
47
|
+
return '当前处理'
|
|
48
|
+
case 'optional':
|
|
49
|
+
return '按需处理'
|
|
50
|
+
case 'todo':
|
|
51
|
+
return '待处理'
|
|
52
|
+
case 'success':
|
|
53
|
+
return '已完成'
|
|
54
|
+
default:
|
|
55
|
+
return formatCellValue(status)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
34
59
|
export const buildAudienceLabel = (audience: string) => {
|
|
35
60
|
if (audience === 'console') {
|
|
36
61
|
return 'Console'
|