create-halo-plugin-template 1.0.4 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-halo-plugin-template",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Create and initialize reusable Halo plugin starter projects.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0",
@@ -9,6 +9,7 @@ import {
9
9
  normalizePluginName,
10
10
  normalizeRoutePrefix,
11
11
  reverseBasePackage,
12
+ slugify,
12
13
  toClassPrefix,
13
14
  toKebabToken,
14
15
  } from './lib/template-meta.mjs'
@@ -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("todo")
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.halo-plugin-template.halo.run/v1alpha1/template-overview/summary`;
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.halo-plugin-template.halo.run/v1alpha1/template-overview/summary`;
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>先运行初始化脚本,再修改 `plugin.yaml`、`settings.yaml`、角色模板和 Java 包名。</li>
240
- <li>初始化后执行 `node scripts/verify-template.mjs`,确认插件名、包名、权限前缀和文档入口已经全部收敛。</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
  })
@@ -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'