Skip to content

Commit aa1b2e4

Browse files
committed
feat(extension): align embedded frontend with extensions-frontend layout
- Keep generated path as <ext>/frontend/ with workspace root from extension-samples (yarn.lock, babel, configs, tool configs). - Per-extension scaffold under frontend/extensions/<name>/ from templates/frontend/extensions/scaffold. - copyFrontendWorkspace skips scaffold when copying workspace; ExtendAddFrontend uses same materialization. - SystemJS shell lives in src/console-shell.js; dist/index.js via npm run build:shell and Docker stage. - Update tests and .gitignore for new paths. Signed-off-by: peng wu <2030047311@qq.com>
1 parent 9728b1b commit aa1b2e4

35 files changed

Lines changed: 12158 additions & 117 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ coverage.out
2626

2727
# npm in embedded frontend template (never commit; breaks go:embed binary size)
2828
pkg/extension/templates/frontend/node_modules/
29+
pkg/extension/templates/frontend/extensions/scaffold/node_modules/
2930

3031
# Local scratch from `ksbuilder create` in repo root
3132
/my-extension/

pkg/extension/create.go

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ type ConfigApp struct {
4747
ZipName string
4848
}
4949

50-
//go:embed templates templates/.helmignore
50+
//go:embed templates templates/.helmignore templates/frontend
51+
//go:embed templates/frontend/.editorconfig templates/frontend/.eslintignore templates/frontend/.eslintrc.js templates/frontend/.gitignore templates/frontend/.npmrc templates/frontend/.prettierignore templates/frontend/.prettierrc.js
5152
var Templates embed.FS
5253

5354
//go:embed templatessimple
@@ -272,8 +273,8 @@ func CreateFromSpec(root string, s *spec.Spec) error {
272273
return fmt.Errorf("copy static: %w", err)
273274
}
274275
if s.HasFrontend() {
275-
if err := copySubtree(Templates, "templates/frontend", filepath.Join(root, "frontend"), config); err != nil {
276-
return fmt.Errorf("copy frontend scaffold: %w", err)
276+
if err := ensureFrontendWorkspace(root, config); err != nil {
277+
return fmt.Errorf("prepare frontend workspace: %w", err)
277278
}
278279
}
279280
if s.HasBackend() {
@@ -318,6 +319,104 @@ func CreateFromSpec(root string, s *spec.Spec) error {
318319
return nil
319320
}
320321

322+
const (
323+
frontendTemplateDir = "templates/frontend"
324+
frontendTemplateScaffoldRel = "extensions/scaffold" // repo-only; materialized as frontend/extensions/<extName>/
325+
)
326+
327+
// copyFrontendWorkspace copies templates/frontend to dest except the embedded scaffold subtree
328+
// (extensions/scaffold), which is materialized separately under frontend/extensions/<extName>/.
329+
func copyFrontendWorkspace(f embed.FS, destDir string, config any) error {
330+
return fs.WalkDir(f, frontendTemplateDir, func(p string, d fs.DirEntry, err error) error {
331+
if err != nil {
332+
return err
333+
}
334+
if p == frontendTemplateDir {
335+
if d.IsDir() {
336+
return os.MkdirAll(destDir, 0755)
337+
}
338+
return nil
339+
}
340+
rel, _ := strings.CutPrefix(p, frontendTemplateDir+"/")
341+
if rel == "" {
342+
return nil
343+
}
344+
if rel == frontendTemplateScaffoldRel || strings.HasPrefix(rel, frontendTemplateScaffoldRel+"/") {
345+
if d.IsDir() {
346+
return fs.SkipDir
347+
}
348+
return nil
349+
}
350+
dstPath := filepath.Join(destDir, rel)
351+
if d.IsDir() {
352+
return os.MkdirAll(dstPath, 0755)
353+
}
354+
data, err := fs.ReadFile(f, p)
355+
if err != nil {
356+
return err
357+
}
358+
t, err := template.New(filepath.Base(p)).Delims("[[", "]]").Parse(string(data))
359+
if err != nil {
360+
return err
361+
}
362+
if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
363+
return err
364+
}
365+
out, err := os.Create(dstPath)
366+
if err != nil {
367+
return err
368+
}
369+
defer func() { _ = out.Close() }()
370+
return t.Execute(out, config)
371+
})
372+
}
373+
374+
// materializeFrontendWorkspace writes templates/frontend under the extension's **frontend/** directory:
375+
// workspace files at frontend/, scaffold from templates/frontend/extensions/scaffold/ → frontend/extensions/<name>/.
376+
func materializeFrontendWorkspace(root, extName string, tmplConfig any) error {
377+
frontendRoot := filepath.Join(root, "frontend")
378+
if err := copyFrontendWorkspace(Templates, frontendRoot, tmplConfig); err != nil {
379+
return fmt.Errorf("copy frontend template: %w", err)
380+
}
381+
if err := os.MkdirAll(filepath.Join(frontendRoot, "configs", "extensions"), 0755); err != nil {
382+
return err
383+
}
384+
extDir := filepath.Join(frontendRoot, "extensions", extName)
385+
scaffoldSrc := frontendTemplateDir + "/" + frontendTemplateScaffoldRel
386+
if err := copySubtree(Templates, scaffoldSrc, extDir, tmplConfig); err != nil {
387+
return fmt.Errorf("copy frontend scaffold into extensions/%s: %w", extName, err)
388+
}
389+
wrapper := `REGISTRY ?= docker.io
390+
NAMESPACE ?= kubespheredev
391+
NAME ?= ` + extName + `
392+
TAG ?= latest
393+
TAG := $(or $(TAG),latest)
394+
395+
.PHONY: build build-assets push
396+
397+
build:
398+
$(MAKE) -C extensions/$(NAME) build NAME=$(NAME) REGISTRY=$(REGISTRY) NAMESPACE=$(NAMESPACE) TAG=$(TAG)
399+
400+
build-assets:
401+
$(MAKE) -C extensions/$(NAME) build-assets NAME=$(NAME) REGISTRY=$(REGISTRY) NAMESPACE=$(NAMESPACE) TAG=$(TAG)
402+
403+
push:
404+
$(MAKE) -C extensions/$(NAME) push NAME=$(NAME) REGISTRY=$(REGISTRY) NAMESPACE=$(NAMESPACE) TAG=$(TAG)
405+
`
406+
if err := os.WriteFile(filepath.Join(frontendRoot, "Makefile"), []byte(wrapper), 0644); err != nil {
407+
return err
408+
}
409+
return nil
410+
}
411+
412+
func ensureFrontendWorkspace(root string, config struct {
413+
Name string
414+
HasFrontend bool
415+
HasBackend bool
416+
}) error {
417+
return materializeFrontendWorkspace(root, config.Name, config)
418+
}
419+
321420
// copySharedFromTemplates copies static/, README*, CHANGELOG*, .helmignore from templates to root.
322421
// Used by CreateApp and CreateSimple to avoid duplicating these files in templatesapp/templatessimple.
323422
func copySharedFromTemplates(root string, config any) error {

pkg/extension/create_test.go

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -104,33 +104,44 @@ func verifyExtensionInstallationMode(t *testing.T, root, want string) {
104104
}
105105
}
106106

107-
// frontendHelloScaffoldRelPaths matches templates under pkg/extension/templates/frontend (Hello World layout + runtime dist).
108-
var frontendHelloScaffoldRelPaths = []string{
109-
"frontend/Dockerfile",
110-
"frontend/index.html",
111-
"frontend/Makefile",
112-
"frontend/README.md",
113-
"frontend/package.json",
114-
"frontend/package-lock.json",
115-
"frontend/dist/index.js",
116-
"frontend/src/App.jsx",
117-
"frontend/src/iframe.jsx",
118-
"frontend/src/index.js",
119-
"frontend/src/routes/index.js",
120-
"frontend/src/locales/index.js",
121-
"frontend/src/locales/en/index.js",
122-
"frontend/src/locales/en/base.json",
123-
"frontend/src/locales/zh/index.js",
124-
"frontend/src/locales/zh/base.json",
125-
}
126-
127-
func verifyFrontendHelloScaffold(t *testing.T, root string) {
107+
func verifyFrontendHelloScaffold(t *testing.T, root, extName string) {
128108
t.Helper()
129-
for _, p := range frontendHelloScaffoldRelPaths {
109+
paths := []string{
110+
"frontend/package.json",
111+
"frontend/yarn.lock",
112+
"frontend/babel.config.js",
113+
"frontend/tsconfig.json",
114+
"frontend/tsconfig.base.json",
115+
"frontend/configs/console.config.js",
116+
"frontend/.eslintrc.js",
117+
"frontend/.npmrc",
118+
"frontend/Makefile",
119+
"frontend/configs/extensions",
120+
"frontend/extensions/" + extName + "/Dockerfile",
121+
"frontend/extensions/" + extName + "/index.html",
122+
"frontend/extensions/" + extName + "/Makefile",
123+
"frontend/extensions/" + extName + "/README.md",
124+
"frontend/extensions/" + extName + "/package.json",
125+
"frontend/extensions/" + extName + "/package-lock.json",
126+
"frontend/extensions/" + extName + "/src/console-shell.js",
127+
"frontend/extensions/" + extName + "/src/App.jsx",
128+
"frontend/extensions/" + extName + "/src/iframe.jsx",
129+
"frontend/extensions/" + extName + "/src/index.js",
130+
"frontend/extensions/" + extName + "/src/routes/index.js",
131+
"frontend/extensions/" + extName + "/src/locales/index.js",
132+
"frontend/extensions/" + extName + "/src/locales/en/index.js",
133+
"frontend/extensions/" + extName + "/src/locales/en/base.json",
134+
"frontend/extensions/" + extName + "/src/locales/zh/index.js",
135+
"frontend/extensions/" + extName + "/src/locales/zh/base.json",
136+
}
137+
for _, p := range paths {
130138
if _, err := os.Stat(filepath.Join(root, p)); err != nil {
131139
t.Errorf("missing %s: %v", p, err)
132140
}
133141
}
142+
if _, err := os.Stat(filepath.Join(root, "frontend/extensions/scaffold")); err == nil {
143+
t.Error("template placeholder frontend/extensions/scaffold must not appear in generated output")
144+
}
134145
}
135146

136147
func TestCreateFromSpec_CopiesFrontendScaffold(t *testing.T) {
@@ -147,7 +158,7 @@ func TestCreateFromSpec_CopiesFrontendScaffold(t *testing.T) {
147158
if err := CreateFromSpec(root, s); err != nil {
148159
t.Fatalf("CreateFromSpec: %v", err)
149160
}
150-
verifyFrontendHelloScaffold(t, root)
161+
verifyFrontendHelloScaffold(t, root, "scaffold-ext")
151162
if _, err := os.Stat(filepath.Join(root, "Makefile")); err != nil {
152163
t.Errorf("missing root Makefile: %v", err)
153164
}
@@ -205,7 +216,7 @@ func verifyFiles(t *testing.T, root string, wantFrontend, wantBackend bool) {
205216
if _, err := os.Stat(filepath.Join(root, "charts", "frontend", "Chart.yaml")); err != nil {
206217
t.Errorf("missing charts/frontend: %v", err)
207218
}
208-
if _, err := os.Stat(filepath.Join(root, "frontend", "Dockerfile")); err != nil {
219+
if _, err := os.Stat(filepath.Join(root, "frontend", "Makefile")); err != nil {
209220
t.Errorf("missing frontend scaffold: %v", err)
210221
}
211222
if _, err := os.Stat(filepath.Join(root, "frontend", "templates")); err == nil {

pkg/extension/extend.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ func ExtendAddFrontend(root string) error {
208208
if err := copySubtree(Templates, "templates/charts/frontend", filepath.Join(root, "charts", "frontend"), config); err != nil {
209209
return err
210210
}
211-
// 4. Copy frontend scaffold (no rename needed)
212-
if err := copySubtree(Templates, "templates/frontend", filepath.Join(root, "frontend"), config); err != nil {
211+
// 4. Lay out frontend/ (workspace + extensions/<name>/ scaffold)
212+
if err := materializeFrontendWorkspace(root, name, config); err != nil {
213213
return err
214214
}
215215
// 5. Regenerate root Makefile

pkg/extension/extend_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func TestExtendAddFrontend(t *testing.T) {
104104
if !strings.Contains(string(extData), "installationMode: Multicluster") {
105105
t.Error("after ExtendAddFrontend, installationMode should stay Multicluster (backend present)")
106106
}
107-
verifyFrontendHelloScaffold(t, root)
107+
verifyFrontendHelloScaffold(t, root, "be-ext")
108108
verifyFiles(t, root, true, true)
109109
verifyPackage(t, root)
110110
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
root = true
2+
3+
[*.jsx?]
4+
charset = utf-8
5+
indent_style = space
6+
indent_size = 2
7+
end_of_line = lf
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.tsx?]
12+
charset = utf-8
13+
indent_style = space
14+
indent_size = 2
15+
end_of_line = lf
16+
insert_final_newline = true
17+
trim_trailing_whitespace = true
18+
19+
[Makefile]
20+
indent_style = tab
21+
22+
[*.yaml]
23+
indent_style = space
24+
indent_size = 2
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
dist/
2+
build/
3+
cypress/
4+
jest/
5+
scripts/
6+
node_modules/
7+
jest.config.js
8+
babel.config.js
9+
.eslintrc.js
10+
locales/
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// http://eslint.org/docs/user-guide/configuring
2+
3+
const path = require('path');
4+
5+
const resolve = dir => path.resolve(__dirname, dir);
6+
7+
module.exports = {
8+
root: true,
9+
parserOptions: {
10+
project: ['./tsconfig.json'],
11+
},
12+
extends: ['kubesphere'],
13+
settings: {
14+
'import/resolver': {
15+
webpack: {
16+
config: resolve('node_modules/@ks-console/bootstrap/webpack/webpack.base.conf.js'),
17+
},
18+
},
19+
},
20+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# See https://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# dependencies
4+
node_modules
5+
6+
# testing
7+
/coverage
8+
9+
# production
10+
/dist
11+
dist
12+
cjs
13+
esm
14+
15+
# misc
16+
.DS_Store
17+
18+
logs/
19+
npm-debug.log*
20+
yarn-debug.log*
21+
yarn-error.log*
22+
23+
.idea/
24+
25+
# local config
26+
/configs/local_config.yaml
27+
28+
*.rdb
29+
.cache-loader
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@ks-console:registry=https://registry.npm.kubesphere.com.cn

0 commit comments

Comments
 (0)