create-halo-plugin-template 1.0.0 → 1.0.1
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/.editorconfig +520 -0
- package/.github/workflows/cd.yaml +20 -0
- package/.github/workflows/ci.yaml +32 -0
- package/.github/workflows/publish-npm.yaml +46 -0
- package/.gitignore +84 -0
- package/LICENSE +674 -0
- package/README.md +191 -0
- package/build.gradle +103 -0
- package/docs/first-npm-release-checklist.md +58 -0
- package/docs/publish-template.md +148 -0
- package/docs/rsbuild-switch.md +90 -0
- package/docs/template-pruning.md +43 -0
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/gradle.properties +1 -0
- package/gradlew +248 -0
- package/gradlew.bat +93 -0
- package/package.json +67 -7
- package/scripts/create-project.mjs +399 -0
- package/scripts/init-template.mjs +281 -0
- package/scripts/publish-check.mjs +97 -0
- package/scripts/release.mjs +278 -0
- package/scripts/verify-template.mjs +407 -0
- package/settings.gradle +7 -0
- package/src/main/java/run/halo/plugintemplate/PluginTemplatePlugin.java +43 -0
- package/src/main/java/run/halo/plugintemplate/config/PluginTemplateConfig.java +14 -0
- package/src/main/java/run/halo/plugintemplate/dto/PluginTemplateChecklistItem.java +30 -0
- package/src/main/java/run/halo/plugintemplate/dto/PluginTemplateFeatureItem.java +30 -0
- package/src/main/java/run/halo/plugintemplate/dto/PluginTemplateOverview.java +73 -0
- package/src/main/java/run/halo/plugintemplate/dto/PluginTemplateStatItem.java +30 -0
- package/src/main/java/run/halo/plugintemplate/endpoint/PluginTemplateConsoleEndpoint.java +33 -0
- package/src/main/java/run/halo/plugintemplate/endpoint/PluginTemplatePublicEndpoint.java +26 -0
- package/src/main/java/run/halo/plugintemplate/endpoint/PluginTemplateUcEndpoint.java +33 -0
- package/src/main/java/run/halo/plugintemplate/endpoint/routes/PluginTemplateOverviewRoutes.java +60 -0
- package/src/main/java/run/halo/plugintemplate/model/PluginTemplateAudience.java +23 -0
- package/src/main/java/run/halo/plugintemplate/query/PluginTemplateOverviewQuery.java +26 -0
- package/src/main/java/run/halo/plugintemplate/reconcile/PluginTemplateSettingsReconciler.java +17 -0
- package/src/main/java/run/halo/plugintemplate/scheme/PluginTemplateRecord.java +43 -0
- package/src/main/java/run/halo/plugintemplate/service/PluginTemplateOverviewService.java +10 -0
- package/src/main/java/run/halo/plugintemplate/service/impl/PluginTemplateOverviewServiceImpl.java +74 -0
- package/src/main/java/run/halo/plugintemplate/setting/PluginTemplateGeneralSetting.java +25 -0
- package/src/main/java/run/halo/plugintemplate/setting/PluginTemplateSettingKeys.java +24 -0
- package/src/main/java/run/halo/plugintemplate/setting/PluginTemplateUiSetting.java +13 -0
- package/src/main/java/run/halo/plugintemplate/utils/PluginTemplateSeeds.java +197 -0
- package/src/main/resources/extensions/roleTemplate-console.yaml +39 -0
- package/src/main/resources/extensions/roleTemplate-uc.yaml +19 -0
- package/src/main/resources/extensions/settings.yaml +47 -0
- package/src/main/resources/logo.png +0 -0
- package/src/main/resources/plugin.yaml +24 -0
- package/src/test/java/run/halo/plugintemplate/PluginTemplatePluginTest.java +34 -0
- package/src/test/java/run/halo/plugintemplate/service/impl/PluginTemplateOverviewServiceImplTest.java +97 -0
- package/ui/build.gradle +41 -0
- package/ui/env.d.ts +2 -0
- package/ui/eslint.config.ts +30 -0
- package/ui/package.json +57 -0
- package/ui/pnpm-lock.yaml +5250 -0
- package/ui/src/api/__tests__/normalizers.spec.ts +65 -0
- package/ui/src/api/generated/.openapi-generator/FILES +23 -0
- package/ui/src/api/generated/.openapi-generator/VERSION +1 -0
- package/ui/src/api/generated/.openapi-generator-ignore +23 -0
- package/ui/src/api/generated/api/plugin-template-console-api.ts +128 -0
- package/ui/src/api/generated/api/plugin-template-uc-api.ts +128 -0
- package/ui/src/api/generated/api.ts +19 -0
- package/ui/src/api/generated/base.ts +86 -0
- package/ui/src/api/generated/common.ts +150 -0
- package/ui/src/api/generated/configuration.ts +110 -0
- package/ui/src/api/generated/git_push.sh +57 -0
- package/ui/src/api/generated/index.ts +18 -0
- package/ui/src/api/generated/models/add-operation.ts +49 -0
- package/ui/src/api/generated/models/copy-operation.ts +49 -0
- package/ui/src/api/generated/models/index.ts +11 -0
- package/ui/src/api/generated/models/json-patch-inner.ts +41 -0
- package/ui/src/api/generated/models/move-operation.ts +49 -0
- package/ui/src/api/generated/models/plugin-template-checklist-item.ts +54 -0
- package/ui/src/api/generated/models/plugin-template-feature-item.ts +54 -0
- package/ui/src/api/generated/models/plugin-template-overview.ts +147 -0
- package/ui/src/api/generated/models/plugin-template-stat-item.ts +54 -0
- package/ui/src/api/generated/models/remove-operation.ts +43 -0
- package/ui/src/api/generated/models/replace-operation.ts +49 -0
- package/ui/src/api/generated/models/test-operation.ts +49 -0
- package/ui/src/api/index.ts +42 -0
- package/ui/src/api/normalizers.ts +65 -0
- package/ui/src/assets/element.scss +24 -0
- package/ui/src/assets/index.css +361 -0
- package/ui/src/assets/logo.svg +1 -0
- package/ui/src/components/PluginTemplateAttachmentTab.vue +69 -0
- package/ui/src/components/PluginTemplateCommonTable.vue +69 -0
- package/ui/src/components/PluginTemplateDashboardWidget.vue +62 -0
- package/ui/src/components/PluginTemplateOverviewPage.vue +254 -0
- package/ui/src/components/ui/PluginUiProvider.vue +40 -0
- package/ui/src/components/ui/UiMetricCard.vue +21 -0
- package/ui/src/components/ui/UiSectionCard.vue +25 -0
- package/ui/src/components/ui/UiStatusPill.vue +18 -0
- package/ui/src/composables/useTemplateOverview.ts +38 -0
- package/ui/src/index.ts +88 -0
- package/ui/src/lib/__tests__/plugin-ui.spec.ts +19 -0
- package/ui/src/lib/plugin-ui.ts +19 -0
- package/ui/src/lib/template.spec.ts +24 -0
- package/ui/src/lib/template.ts +52 -0
- package/ui/src/lib/theme.ts +31 -0
- package/ui/src/types/index.ts +59 -0
- package/ui/src/views/console/ConsoleDashboardView.vue +7 -0
- package/ui/src/views/uc/UcDashboardView.vue +7 -0
- package/ui/tsconfig.app.json +12 -0
- package/ui/tsconfig.json +14 -0
- package/ui/tsconfig.node.json +15 -0
- package/ui/tsconfig.vitest.json +11 -0
- package/ui/vite.config.ts +25 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs/promises'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import process from 'node:process'
|
|
6
|
+
|
|
7
|
+
const ROOT = process.cwd()
|
|
8
|
+
const REQUIRED_FILES = [
|
|
9
|
+
'README.md',
|
|
10
|
+
'build.gradle',
|
|
11
|
+
'settings.gradle',
|
|
12
|
+
'scripts/init-template.mjs',
|
|
13
|
+
'src/main/resources/plugin.yaml',
|
|
14
|
+
'src/main/resources/extensions/settings.yaml',
|
|
15
|
+
'src/main/resources/extensions/roleTemplate-console.yaml',
|
|
16
|
+
'src/main/resources/extensions/roleTemplate-uc.yaml',
|
|
17
|
+
'ui/src/index.ts',
|
|
18
|
+
'ui/src/api/index.ts',
|
|
19
|
+
'ui/src/api/generated/index.ts',
|
|
20
|
+
'docs/rsbuild-switch.md',
|
|
21
|
+
'docs/template-pruning.md',
|
|
22
|
+
]
|
|
23
|
+
const TEXT_EXTENSIONS = new Set([
|
|
24
|
+
'.md',
|
|
25
|
+
'.yaml',
|
|
26
|
+
'.yml',
|
|
27
|
+
'.json',
|
|
28
|
+
'.mjs',
|
|
29
|
+
'.ts',
|
|
30
|
+
'.tsx',
|
|
31
|
+
'.vue',
|
|
32
|
+
'.scss',
|
|
33
|
+
'.css',
|
|
34
|
+
'.gradle',
|
|
35
|
+
'.java',
|
|
36
|
+
'.properties',
|
|
37
|
+
'.html',
|
|
38
|
+
])
|
|
39
|
+
const TEXT_FILE_NAMES = new Set([
|
|
40
|
+
'.editorconfig',
|
|
41
|
+
'.gitignore',
|
|
42
|
+
])
|
|
43
|
+
const SKIP_DIRS = new Set([
|
|
44
|
+
'.git',
|
|
45
|
+
'.gradle',
|
|
46
|
+
'build',
|
|
47
|
+
'node_modules',
|
|
48
|
+
'ui/build',
|
|
49
|
+
'workplace',
|
|
50
|
+
])
|
|
51
|
+
const ORIGINAL_TEMPLATE = {
|
|
52
|
+
pluginName: ['halo', 'plugin', 'template'].join('-'),
|
|
53
|
+
basePackage: ['run', 'halo', 'plugintemplate'].join('.'),
|
|
54
|
+
classPrefix: ['Plugin', 'Template'].join(''),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const usage = `
|
|
58
|
+
Usage:
|
|
59
|
+
node scripts/verify-template.mjs
|
|
60
|
+
|
|
61
|
+
node scripts/verify-template.mjs \\
|
|
62
|
+
--plugin-name hello-world \\
|
|
63
|
+
--base-package com.example.helloworld \\
|
|
64
|
+
--display-name "Hello World" \\
|
|
65
|
+
--author-name "Your Name" \\
|
|
66
|
+
[--author-website "https://github.com/your-name"] \\
|
|
67
|
+
[--repo-owner your-name] \\
|
|
68
|
+
[--description "Hello World - Halo 插件"]
|
|
69
|
+
`.trim()
|
|
70
|
+
|
|
71
|
+
const parseArgs = (argv) => {
|
|
72
|
+
const args = {}
|
|
73
|
+
|
|
74
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
75
|
+
const token = argv[index]
|
|
76
|
+
|
|
77
|
+
if (token === '--help' || token === '-h') {
|
|
78
|
+
args.help = true
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!token.startsWith('--')) {
|
|
83
|
+
throw new Error(`Unknown argument: ${token}`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const key = token.slice(2)
|
|
87
|
+
const value = argv[index + 1]
|
|
88
|
+
|
|
89
|
+
if (!value || value.startsWith('--')) {
|
|
90
|
+
throw new Error(`Missing value for argument: ${token}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
args[key] = value
|
|
94
|
+
index += 1
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return args
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const isTextFile = (filePath) => {
|
|
101
|
+
const extension = path.extname(filePath)
|
|
102
|
+
if (TEXT_EXTENSIONS.has(extension)) {
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
return TEXT_FILE_NAMES.has(path.basename(filePath))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const walkFiles = async (directory) => {
|
|
109
|
+
const entries = await fs.readdir(directory, { withFileTypes: true })
|
|
110
|
+
const files = []
|
|
111
|
+
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
if (SKIP_DIRS.has(entry.name)) {
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const absolutePath = path.join(directory, entry.name)
|
|
118
|
+
if (entry.isDirectory()) {
|
|
119
|
+
files.push(...(await walkFiles(absolutePath)))
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
files.push(absolutePath)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return files
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
130
|
+
|
|
131
|
+
const readText = async (relativePath) =>
|
|
132
|
+
fs.readFile(path.join(ROOT, relativePath), 'utf8')
|
|
133
|
+
|
|
134
|
+
const exists = async (relativePath) => {
|
|
135
|
+
try {
|
|
136
|
+
await fs.access(path.join(ROOT, relativePath))
|
|
137
|
+
return true
|
|
138
|
+
} catch {
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const findFileByPattern = async (pattern) => {
|
|
144
|
+
const files = await walkFiles(ROOT)
|
|
145
|
+
return files
|
|
146
|
+
.map((filePath) => path.relative(ROOT, filePath))
|
|
147
|
+
.find((relativePath) => pattern.test(relativePath))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const extractJavaConstant = (content, constantName) => {
|
|
151
|
+
const pattern = new RegExp(
|
|
152
|
+
`public static final (?:String|boolean) ${escapeRegExp(constantName)} = (?:"([^"]+)"|(true|false));`,
|
|
153
|
+
)
|
|
154
|
+
const match = content.match(pattern)
|
|
155
|
+
return match?.[1] ?? match?.[2] ?? ''
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const extractGradleGroup = (content) => content.match(/group\s+'([^']+)'/)?.[1] ?? ''
|
|
159
|
+
|
|
160
|
+
const extractRootProjectName = (content) =>
|
|
161
|
+
content.match(/rootProject\.name\s*=\s*'([^']+)'/)?.[1] ?? ''
|
|
162
|
+
|
|
163
|
+
const formatResult = (status, message) => `${status} ${message}`
|
|
164
|
+
|
|
165
|
+
const fail = (results, message) => results.push(formatResult('FAIL', message))
|
|
166
|
+
|
|
167
|
+
const pass = (results, message) => results.push(formatResult('PASS', message))
|
|
168
|
+
|
|
169
|
+
const assertContains = (results, content, fragment, description) => {
|
|
170
|
+
if (content.includes(fragment)) {
|
|
171
|
+
pass(results, description)
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
fail(results, `${description} (missing: ${fragment})`)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const verifyNoTemplateResidue = async (results) => {
|
|
178
|
+
const files = await walkFiles(ROOT)
|
|
179
|
+
const textFiles = files.filter(isTextFile)
|
|
180
|
+
const markers = Object.values(ORIGINAL_TEMPLATE)
|
|
181
|
+
const hits = []
|
|
182
|
+
|
|
183
|
+
for (const filePath of textFiles) {
|
|
184
|
+
const relativePath = path.relative(ROOT, filePath)
|
|
185
|
+
const content = await fs.readFile(filePath, 'utf8')
|
|
186
|
+
for (const marker of markers) {
|
|
187
|
+
if (content.includes(marker)) {
|
|
188
|
+
hits.push(`${relativePath}: ${marker}`)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!hits.length) {
|
|
194
|
+
pass(results, 'No original starter markers remain in text files')
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
fail(
|
|
199
|
+
results,
|
|
200
|
+
`Original starter markers still exist after initialization: ${hits.slice(0, 8).join(', ')}`,
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const verifyRepo = async (args) => {
|
|
205
|
+
const results = []
|
|
206
|
+
|
|
207
|
+
for (const requiredFile of REQUIRED_FILES) {
|
|
208
|
+
if (await exists(requiredFile)) {
|
|
209
|
+
pass(results, `Required file exists: ${requiredFile}`)
|
|
210
|
+
} else {
|
|
211
|
+
fail(results, `Required file is missing: ${requiredFile}`)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const settingKeysPath = await findFileByPattern(/src\/main\/java\/.+SettingKeys\.java$/)
|
|
216
|
+
if (!settingKeysPath) {
|
|
217
|
+
fail(results, 'SettingKeys.java was not found under src/main/java')
|
|
218
|
+
return results
|
|
219
|
+
}
|
|
220
|
+
pass(results, `Discovered setting constants: ${settingKeysPath}`)
|
|
221
|
+
|
|
222
|
+
const [
|
|
223
|
+
settingKeysContent,
|
|
224
|
+
buildGradleContent,
|
|
225
|
+
settingsGradleContent,
|
|
226
|
+
pluginYamlContent,
|
|
227
|
+
settingsYamlContent,
|
|
228
|
+
consoleRoleTemplateContent,
|
|
229
|
+
ucRoleTemplateContent,
|
|
230
|
+
uiIndexContent,
|
|
231
|
+
uiApiContent,
|
|
232
|
+
readmeContent,
|
|
233
|
+
providerContent,
|
|
234
|
+
] = await Promise.all([
|
|
235
|
+
readText(settingKeysPath),
|
|
236
|
+
readText('build.gradle'),
|
|
237
|
+
readText('settings.gradle'),
|
|
238
|
+
readText('src/main/resources/plugin.yaml'),
|
|
239
|
+
readText('src/main/resources/extensions/settings.yaml'),
|
|
240
|
+
readText('src/main/resources/extensions/roleTemplate-console.yaml'),
|
|
241
|
+
readText('src/main/resources/extensions/roleTemplate-uc.yaml'),
|
|
242
|
+
readText('ui/src/index.ts'),
|
|
243
|
+
readText('ui/src/api/index.ts'),
|
|
244
|
+
readText('README.md'),
|
|
245
|
+
readText('ui/src/components/ui/PluginUiProvider.vue'),
|
|
246
|
+
])
|
|
247
|
+
|
|
248
|
+
const pluginName = extractJavaConstant(settingKeysContent, 'PLUGIN_NAME')
|
|
249
|
+
const displayName = extractJavaConstant(settingKeysContent, 'DISPLAY_NAME')
|
|
250
|
+
const settingName = extractJavaConstant(settingKeysContent, 'SETTING_NAME')
|
|
251
|
+
const configMapName = extractJavaConstant(settingKeysContent, 'CONFIG_MAP_NAME')
|
|
252
|
+
const consolePath = extractJavaConstant(settingKeysContent, 'CONSOLE_PATH')
|
|
253
|
+
const ucPath = extractJavaConstant(settingKeysContent, 'UC_PATH')
|
|
254
|
+
const generatedClientPath = extractJavaConstant(settingKeysContent, 'GENERATED_CLIENT_PATH')
|
|
255
|
+
const basePackage = extractGradleGroup(buildGradleContent)
|
|
256
|
+
const rootProjectName = extractRootProjectName(settingsGradleContent)
|
|
257
|
+
const packageDirectory = path.posix.join('src/main/java', ...basePackage.split('.'))
|
|
258
|
+
const permissionPrefix = `plugin:${pluginName}:`
|
|
259
|
+
|
|
260
|
+
if (!pluginName || !displayName || !basePackage) {
|
|
261
|
+
fail(results, 'Unable to resolve plugin constants from SettingKeys.java or build.gradle')
|
|
262
|
+
return results
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (rootProjectName === pluginName) {
|
|
266
|
+
pass(results, `settings.gradle project name matches plugin name: ${pluginName}`)
|
|
267
|
+
} else {
|
|
268
|
+
fail(results, `settings.gradle project name mismatch: expected ${pluginName}, got ${rootProjectName || 'empty'}`)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (await exists(packageDirectory)) {
|
|
272
|
+
pass(results, `Java package directory exists: ${packageDirectory}`)
|
|
273
|
+
} else {
|
|
274
|
+
fail(results, `Java package directory is missing: ${packageDirectory}`)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
assertContains(results, pluginYamlContent, `name: ${pluginName}`, 'plugin.yaml metadata name is consistent')
|
|
278
|
+
assertContains(results, pluginYamlContent, `settingName: ${settingName}`, 'plugin.yaml settingName is consistent')
|
|
279
|
+
assertContains(results, pluginYamlContent, `configMapName: ${configMapName}`, 'plugin.yaml configMapName is consistent')
|
|
280
|
+
assertContains(results, pluginYamlContent, `displayName: "${displayName}"`, 'plugin.yaml displayName is consistent')
|
|
281
|
+
assertContains(results, settingsYamlContent, `name: ${settingName}`, 'settings.yaml metadata name is consistent')
|
|
282
|
+
|
|
283
|
+
assertContains(results, buildGradleContent, `/apis/console.${pluginName}.halo.run/v1alpha1/**`, 'OpenAPI console path matches plugin name')
|
|
284
|
+
assertContains(results, buildGradleContent, `/apis/uc.${pluginName}.halo.run/v1alpha1/**`, 'OpenAPI UC path matches plugin name')
|
|
285
|
+
assertContains(results, uiIndexContent, `path: '${consolePath}'`, 'Console route path matches setting constants')
|
|
286
|
+
assertContains(results, uiIndexContent, `path: '${ucPath}'`, 'UC route path matches setting constants')
|
|
287
|
+
assertContains(results, uiIndexContent, permissionPrefix, 'UI permissions use the current plugin prefix')
|
|
288
|
+
assertContains(results, providerContent, `${pluginName}-admin-shell`, 'UI provider namespace follows the plugin prefix')
|
|
289
|
+
assertContains(results, uiApiContent, `from '@/api/generated'`, 'UI API wrapper imports from the generated client')
|
|
290
|
+
|
|
291
|
+
if (!uiApiContent.includes('/apis/')) {
|
|
292
|
+
pass(results, 'UI API wrapper does not contain raw API paths')
|
|
293
|
+
} else {
|
|
294
|
+
fail(results, 'UI API wrapper still contains raw API paths')
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
assertContains(results, consoleRoleTemplateContent, permissionPrefix, 'Console role template uses the plugin permission prefix')
|
|
298
|
+
assertContains(results, ucRoleTemplateContent, permissionPrefix, 'UC role template uses the plugin permission prefix')
|
|
299
|
+
assertContains(results, readmeContent, 'node scripts/verify-template.mjs', 'README documents the verification command')
|
|
300
|
+
assertContains(results, readmeContent, 'docs/template-pruning.md', 'README links to the pruning guide')
|
|
301
|
+
|
|
302
|
+
if (await exists(path.posix.join(generatedClientPath, 'api'))) {
|
|
303
|
+
pass(results, `Generated API directory exists: ${generatedClientPath}/api`)
|
|
304
|
+
} else {
|
|
305
|
+
fail(results, `Generated API directory is missing: ${generatedClientPath}/api`)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (await exists(path.posix.join(generatedClientPath, 'models'))) {
|
|
309
|
+
pass(results, `Generated model directory exists: ${generatedClientPath}/models`)
|
|
310
|
+
} else {
|
|
311
|
+
fail(results, `Generated model directory is missing: ${generatedClientPath}/models`)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (args['plugin-name'] && args['plugin-name'] !== pluginName) {
|
|
315
|
+
fail(results, `Expected plugin name ${args['plugin-name']}, got ${pluginName}`)
|
|
316
|
+
} else if (args['plugin-name']) {
|
|
317
|
+
pass(results, `Expected plugin name matches: ${pluginName}`)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (args['base-package'] && args['base-package'] !== basePackage) {
|
|
321
|
+
fail(results, `Expected base package ${args['base-package']}, got ${basePackage}`)
|
|
322
|
+
} else if (args['base-package']) {
|
|
323
|
+
pass(results, `Expected base package matches: ${basePackage}`)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (args['display-name'] && args['display-name'] !== displayName) {
|
|
327
|
+
fail(results, `Expected display name ${args['display-name']}, got ${displayName}`)
|
|
328
|
+
} else if (args['display-name']) {
|
|
329
|
+
pass(results, `Expected display name matches: ${displayName}`)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (args['author-name']) {
|
|
333
|
+
assertContains(
|
|
334
|
+
results,
|
|
335
|
+
pluginYamlContent,
|
|
336
|
+
`name: ${args['author-name']}`,
|
|
337
|
+
'plugin.yaml author name matches the expected value',
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (args['author-website']) {
|
|
342
|
+
assertContains(
|
|
343
|
+
results,
|
|
344
|
+
pluginYamlContent,
|
|
345
|
+
`website: ${args['author-website']}`,
|
|
346
|
+
'plugin.yaml author website matches the expected value',
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (args.description) {
|
|
351
|
+
assertContains(
|
|
352
|
+
results,
|
|
353
|
+
pluginYamlContent,
|
|
354
|
+
`description: "${args.description}"`,
|
|
355
|
+
'plugin.yaml description matches the expected value',
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (args['repo-owner']) {
|
|
360
|
+
const repoRoot = `https://github.com/${args['repo-owner']}/${pluginName}`
|
|
361
|
+
assertContains(results, pluginYamlContent, `repo: ${repoRoot}`, 'plugin.yaml repo matches the expected owner')
|
|
362
|
+
assertContains(results, pluginYamlContent, `homepage: ${repoRoot}#readme`, 'plugin.yaml homepage matches the expected owner')
|
|
363
|
+
assertContains(results, pluginYamlContent, `issues: ${repoRoot}/issues`, 'plugin.yaml issues URL matches the expected owner')
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const shouldCheckResidue =
|
|
367
|
+
Boolean(args['plugin-name'] || args['base-package'] || args['display-name']) &&
|
|
368
|
+
pluginName !== ORIGINAL_TEMPLATE.pluginName &&
|
|
369
|
+
basePackage !== ORIGINAL_TEMPLATE.basePackage
|
|
370
|
+
|
|
371
|
+
if (shouldCheckResidue) {
|
|
372
|
+
await verifyNoTemplateResidue(results)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return results
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const main = async () => {
|
|
379
|
+
try {
|
|
380
|
+
const args = parseArgs(process.argv.slice(2))
|
|
381
|
+
|
|
382
|
+
if (args.help) {
|
|
383
|
+
console.log(usage)
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const results = await verifyRepo(args)
|
|
388
|
+
const failed = results.filter((line) => line.startsWith('FAIL'))
|
|
389
|
+
|
|
390
|
+
for (const line of results) {
|
|
391
|
+
console.log(line)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (failed.length) {
|
|
395
|
+
process.exitCode = 1
|
|
396
|
+
return
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
console.log('PASS Starter verification completed successfully')
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.error(`FAIL ${error instanceof Error ? error.message : String(error)}`)
|
|
402
|
+
console.error(usage)
|
|
403
|
+
process.exitCode = 1
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
await main()
|
package/settings.gradle
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
package run.halo.plugintemplate;
|
|
2
|
+
|
|
3
|
+
import org.slf4j.Logger;
|
|
4
|
+
import org.slf4j.LoggerFactory;
|
|
5
|
+
import org.springframework.stereotype.Component;
|
|
6
|
+
import run.halo.app.extension.Scheme;
|
|
7
|
+
import run.halo.app.extension.SchemeManager;
|
|
8
|
+
import run.halo.app.extension.index.IndexSpecs;
|
|
9
|
+
import run.halo.app.plugin.BasePlugin;
|
|
10
|
+
import run.halo.app.plugin.PluginContext;
|
|
11
|
+
import run.halo.plugintemplate.scheme.PluginTemplateRecord;
|
|
12
|
+
|
|
13
|
+
@Component
|
|
14
|
+
public class PluginTemplatePlugin extends BasePlugin {
|
|
15
|
+
|
|
16
|
+
private static final Logger log = LoggerFactory.getLogger(PluginTemplatePlugin.class);
|
|
17
|
+
|
|
18
|
+
private final SchemeManager schemeManager;
|
|
19
|
+
|
|
20
|
+
public PluginTemplatePlugin(PluginContext pluginContext, SchemeManager schemeManager) {
|
|
21
|
+
super(pluginContext);
|
|
22
|
+
this.schemeManager = schemeManager;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Override
|
|
26
|
+
public void start() {
|
|
27
|
+
schemeManager.register(PluginTemplateRecord.class, indexSpecs -> {
|
|
28
|
+
indexSpecs.add(IndexSpecs.<PluginTemplateRecord, String>single("id", String.class).unique(true)
|
|
29
|
+
.indexFunc(PluginTemplateRecord::getId));
|
|
30
|
+
indexSpecs.add(IndexSpecs.<PluginTemplateRecord, String>single("title", String.class)
|
|
31
|
+
.indexFunc(PluginTemplateRecord::getTitle));
|
|
32
|
+
indexSpecs.add(IndexSpecs.<PluginTemplateRecord, String>single("status", String.class)
|
|
33
|
+
.indexFunc(PluginTemplateRecord::getStatus));
|
|
34
|
+
});
|
|
35
|
+
log.info("Halo plugin template started.");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Override
|
|
39
|
+
public void stop() {
|
|
40
|
+
schemeManager.unregister(Scheme.buildFromType(PluginTemplateRecord.class));
|
|
41
|
+
log.info("Halo plugin template stopped.");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package run.halo.plugintemplate.config;
|
|
2
|
+
|
|
3
|
+
import java.time.Clock;
|
|
4
|
+
import org.springframework.context.annotation.Bean;
|
|
5
|
+
import org.springframework.context.annotation.Configuration;
|
|
6
|
+
|
|
7
|
+
@Configuration
|
|
8
|
+
public class PluginTemplateConfig {
|
|
9
|
+
|
|
10
|
+
@Bean
|
|
11
|
+
public Clock pluginTemplateClock() {
|
|
12
|
+
return Clock.systemDefaultZone();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package run.halo.plugintemplate.dto;
|
|
2
|
+
|
|
3
|
+
import io.swagger.v3.oas.annotations.media.Schema;
|
|
4
|
+
import lombok.AllArgsConstructor;
|
|
5
|
+
import lombok.Builder;
|
|
6
|
+
import lombok.Data;
|
|
7
|
+
import lombok.NoArgsConstructor;
|
|
8
|
+
|
|
9
|
+
@Data
|
|
10
|
+
@Builder
|
|
11
|
+
@NoArgsConstructor
|
|
12
|
+
@AllArgsConstructor
|
|
13
|
+
@Schema(name = "PluginTemplateChecklistItem", description = "模板初始化检查项")
|
|
14
|
+
public class PluginTemplateChecklistItem {
|
|
15
|
+
|
|
16
|
+
@Schema(description = "检查项键")
|
|
17
|
+
private String key;
|
|
18
|
+
|
|
19
|
+
@Schema(description = "标题")
|
|
20
|
+
private String title;
|
|
21
|
+
|
|
22
|
+
@Schema(description = "说明")
|
|
23
|
+
private String description;
|
|
24
|
+
|
|
25
|
+
@Schema(description = "受众")
|
|
26
|
+
private String audience;
|
|
27
|
+
|
|
28
|
+
@Schema(description = "状态")
|
|
29
|
+
private String status;
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package run.halo.plugintemplate.dto;
|
|
2
|
+
|
|
3
|
+
import io.swagger.v3.oas.annotations.media.Schema;
|
|
4
|
+
import lombok.AllArgsConstructor;
|
|
5
|
+
import lombok.Builder;
|
|
6
|
+
import lombok.Data;
|
|
7
|
+
import lombok.NoArgsConstructor;
|
|
8
|
+
|
|
9
|
+
@Data
|
|
10
|
+
@Builder
|
|
11
|
+
@NoArgsConstructor
|
|
12
|
+
@AllArgsConstructor
|
|
13
|
+
@Schema(name = "PluginTemplateFeatureItem", description = "模板功能项")
|
|
14
|
+
public class PluginTemplateFeatureItem {
|
|
15
|
+
|
|
16
|
+
@Schema(description = "功能键")
|
|
17
|
+
private String key;
|
|
18
|
+
|
|
19
|
+
@Schema(description = "标题")
|
|
20
|
+
private String title;
|
|
21
|
+
|
|
22
|
+
@Schema(description = "区域")
|
|
23
|
+
private String area;
|
|
24
|
+
|
|
25
|
+
@Schema(description = "说明")
|
|
26
|
+
private String description;
|
|
27
|
+
|
|
28
|
+
@Schema(description = "是否默认启用")
|
|
29
|
+
private Boolean enabled;
|
|
30
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package run.halo.plugintemplate.dto;
|
|
2
|
+
|
|
3
|
+
import io.swagger.v3.oas.annotations.media.Schema;
|
|
4
|
+
import java.util.List;
|
|
5
|
+
import lombok.AllArgsConstructor;
|
|
6
|
+
import lombok.Builder;
|
|
7
|
+
import lombok.Data;
|
|
8
|
+
import lombok.NoArgsConstructor;
|
|
9
|
+
|
|
10
|
+
@Data
|
|
11
|
+
@Builder
|
|
12
|
+
@NoArgsConstructor
|
|
13
|
+
@AllArgsConstructor
|
|
14
|
+
@Schema(name = "PluginTemplateOverview", description = "模板概览信息")
|
|
15
|
+
public class PluginTemplateOverview {
|
|
16
|
+
|
|
17
|
+
@Schema(description = "插件名")
|
|
18
|
+
private String pluginName;
|
|
19
|
+
|
|
20
|
+
@Schema(description = "显示名")
|
|
21
|
+
private String displayName;
|
|
22
|
+
|
|
23
|
+
@Schema(description = "当前视角")
|
|
24
|
+
private String audience;
|
|
25
|
+
|
|
26
|
+
@Schema(description = "视角标签")
|
|
27
|
+
private String audienceLabel;
|
|
28
|
+
|
|
29
|
+
@Schema(description = "Console 路由")
|
|
30
|
+
private String consolePath;
|
|
31
|
+
|
|
32
|
+
@Schema(description = "UC 路由")
|
|
33
|
+
private String ucPath;
|
|
34
|
+
|
|
35
|
+
@Schema(description = "Setting 名称")
|
|
36
|
+
private String settingName;
|
|
37
|
+
|
|
38
|
+
@Schema(description = "ConfigMap 名称")
|
|
39
|
+
private String configMapName;
|
|
40
|
+
|
|
41
|
+
@Schema(description = "生成客户端输出目录")
|
|
42
|
+
private String generatedClientPath;
|
|
43
|
+
|
|
44
|
+
@Schema(description = "生成时间")
|
|
45
|
+
private String generatedAt;
|
|
46
|
+
|
|
47
|
+
@Schema(description = "是否启用 Console 页面")
|
|
48
|
+
private Boolean enableConsoleDashboard;
|
|
49
|
+
|
|
50
|
+
@Schema(description = "是否启用 UC 页面")
|
|
51
|
+
private Boolean enableUcDashboard;
|
|
52
|
+
|
|
53
|
+
@Schema(description = "是否启用附件扩展标签页")
|
|
54
|
+
private Boolean enableAttachmentProvider;
|
|
55
|
+
|
|
56
|
+
@Schema(description = "主色")
|
|
57
|
+
private String accentColor;
|
|
58
|
+
|
|
59
|
+
@Schema(description = "页面密度")
|
|
60
|
+
private String density;
|
|
61
|
+
|
|
62
|
+
@Schema(description = "支持文档链接")
|
|
63
|
+
private String supportLink;
|
|
64
|
+
|
|
65
|
+
@Schema(description = "统计信息")
|
|
66
|
+
private List<PluginTemplateStatItem> stats;
|
|
67
|
+
|
|
68
|
+
@Schema(description = "功能矩阵")
|
|
69
|
+
private List<PluginTemplateFeatureItem> features;
|
|
70
|
+
|
|
71
|
+
@Schema(description = "初始化清单")
|
|
72
|
+
private List<PluginTemplateChecklistItem> checklist;
|
|
73
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package run.halo.plugintemplate.dto;
|
|
2
|
+
|
|
3
|
+
import io.swagger.v3.oas.annotations.media.Schema;
|
|
4
|
+
import lombok.AllArgsConstructor;
|
|
5
|
+
import lombok.Builder;
|
|
6
|
+
import lombok.Data;
|
|
7
|
+
import lombok.NoArgsConstructor;
|
|
8
|
+
|
|
9
|
+
@Data
|
|
10
|
+
@Builder
|
|
11
|
+
@NoArgsConstructor
|
|
12
|
+
@AllArgsConstructor
|
|
13
|
+
@Schema(name = "PluginTemplateStatItem", description = "模板统计卡片")
|
|
14
|
+
public class PluginTemplateStatItem {
|
|
15
|
+
|
|
16
|
+
@Schema(description = "统计项键")
|
|
17
|
+
private String key;
|
|
18
|
+
|
|
19
|
+
@Schema(description = "标题")
|
|
20
|
+
private String label;
|
|
21
|
+
|
|
22
|
+
@Schema(description = "值")
|
|
23
|
+
private String value;
|
|
24
|
+
|
|
25
|
+
@Schema(description = "提示")
|
|
26
|
+
private String helper;
|
|
27
|
+
|
|
28
|
+
@Schema(description = "视觉语义")
|
|
29
|
+
private String tone;
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package run.halo.plugintemplate.endpoint;
|
|
2
|
+
|
|
3
|
+
import lombok.RequiredArgsConstructor;
|
|
4
|
+
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
|
5
|
+
import org.springframework.stereotype.Component;
|
|
6
|
+
import org.springframework.web.reactive.function.server.RequestPredicates;
|
|
7
|
+
import org.springframework.web.reactive.function.server.RouterFunction;
|
|
8
|
+
import org.springframework.web.reactive.function.server.ServerResponse;
|
|
9
|
+
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
|
10
|
+
import run.halo.app.extension.GroupVersion;
|
|
11
|
+
import run.halo.plugintemplate.endpoint.routes.PluginTemplateOverviewRoutes;
|
|
12
|
+
|
|
13
|
+
@Component
|
|
14
|
+
@RequiredArgsConstructor
|
|
15
|
+
public class PluginTemplateConsoleEndpoint implements CustomEndpoint {
|
|
16
|
+
|
|
17
|
+
public static final String CONSOLE_GROUP_VERSION = "console.halo-plugin-template.halo.run/v1alpha1";
|
|
18
|
+
public static final String CONSOLE_TAG = "PluginTemplateConsole";
|
|
19
|
+
|
|
20
|
+
private final PluginTemplateOverviewRoutes overviewRoutes;
|
|
21
|
+
|
|
22
|
+
@Override
|
|
23
|
+
public RouterFunction<ServerResponse> endpoint() {
|
|
24
|
+
return SpringdocRouteBuilder.route()
|
|
25
|
+
.nest(RequestPredicates.path("template-overview"), overviewRoutes::consoleRoutes)
|
|
26
|
+
.build();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Override
|
|
30
|
+
public GroupVersion groupVersion() {
|
|
31
|
+
return GroupVersion.parseAPIVersion(CONSOLE_GROUP_VERSION);
|
|
32
|
+
}
|
|
33
|
+
}
|