Enhance script resolution logic and update asset metadata

- Refactor the `ResolveScript` function to improve the script path resolution strategy, allowing for better handling of assistant directories and module paths.
- Update asset metadata timestamps in `bindata.go` to reflect recent changes, ensuring accurate tracking of file modifications.
- Modify CSS component name formatting in `build.go` and TypeScript component selectors in `index.ts` for consistency in syntax.
- Improve locale handling in `locale.go` by implementing a fallback mechanism for locale file resolution based on language prefixes.
- Normalize locale values in `request.go` to lowercase for consistency across the application.
- Enhance component name sanitization in `utils.go` to replace additional characters, ensuring valid component naming conventions.
This commit is contained in:
Max 2026-02-09 18:32:24 +08:00
parent a372a56cf0
commit bdcc3585e2
7 changed files with 495 additions and 380 deletions

View file

@ -29,42 +29,68 @@ func NewScriptRunner(opts *Options) *ScriptRunner {
}
// ResolveScript resolves the script path from scripts.xxx.yyy or scripts.xxx.yyy.zzz format
//
// Resolution strategy:
// 1. Find the assistant directory by detecting package.yao from longest to shortest path
// 2. Remaining parts after the assistant boundary map to src/ subdirectories + module name
//
// Examples:
// - scripts.expense.setup -> assistants/expense/src/setup_test.ts
// - scripts.yao.keeper.seed -> assistants/yao/keeper/src/seed_test.ts
// - scripts.yao.keeper.tests.seed -> assistants/yao/keeper/src/tests/seed_test.ts
func ResolveScript(input string) (*ScriptInfo, error) {
// Remove "scripts." prefix
path := strings.TrimPrefix(input, "scripts.")
// Split into parts:
// "expense.setup" -> ["expense", "setup"]
// "expense.submission.validation" -> ["expense", "submission", "validation"]
// "yao.keeper.tests.seed" -> ["yao", "keeper", "tests", "seed"]
parts := strings.Split(path, ".")
if len(parts) < 2 {
return nil, fmt.Errorf("invalid script path: %s (expected format: scripts.assistant.module or scripts.assistant.sub_agent.module)", input)
return nil, fmt.Errorf("invalid script path: %s (expected format: scripts.assistant.module or scripts.assistant.sub.module)", input)
}
// Build paths based on number of parts
var basePaths []string
var assistantDir, moduleName string
// Strategy: detect assistant boundary by looking for package.yao
// Try from the longest possible assistant path down to the shortest
var assistantDir, modulePath string
assistantFound := false
if len(parts) == 2 {
// Format: scripts.expense.setup
// assistantDir: expense
// moduleName: setup
assistantDir = parts[0]
moduleName = parts[1]
basePaths = []string{
filepath.Join("assistants", assistantDir, "src"),
filepath.Join(assistantDir, "src"),
for i := len(parts) - 1; i >= 1; i-- {
candidateDir := strings.Join(parts[:i], "/")
for _, prefix := range []string{"assistants/", ""} {
packagePath := filepath.Join(prefix+candidateDir, "package.yao")
exists, err := application.App.Exists(packagePath)
if err == nil && exists {
assistantDir = candidateDir
// Remaining parts form the module path (may include subdirectories)
// e.g., parts[i:] = ["tests", "seed"] -> "tests/seed"
modulePath = strings.Join(parts[i:], "/")
assistantFound = true
break
}
}
} else {
// Format: scripts.expense.submission.validation (sub-agent)
// assistantDir: expense/submission (or expense.submission)
// moduleName: validation
if assistantFound {
break
}
}
// Fallback: original behavior — last part is module, rest is assistant dir
if !assistantFound {
assistantDir = strings.Join(parts[:len(parts)-1], "/")
moduleName = parts[len(parts)-1]
basePaths = []string{
filepath.Join("assistants", assistantDir, "src"),
filepath.Join(assistantDir, "src"),
}
modulePath = parts[len(parts)-1]
}
// modulePath may contain subdirectories: "tests/seed" -> dir="tests", module="seed"
moduleName := filepath.Base(modulePath)
moduleSubDir := filepath.Dir(modulePath)
if moduleSubDir == "." {
moduleSubDir = ""
}
// Build candidate base paths
basePaths := []string{
filepath.Join("assistants", assistantDir, "src", moduleSubDir),
filepath.Join(assistantDir, "src", moduleSubDir),
}
var scriptPath, testPath string

File diff suppressed because one or more lines are too long

View file

@ -685,7 +685,7 @@ func (page *Page) BuildStyles(ctx *BuildContext, option *BuildOption, component
if option.ComponentName != "" {
code = cssRe.ReplaceAllStringFunc(code, func(css string) string {
return fmt.Sprintf("[s\\:cn=%s] %s", option.ComponentName, css)
return fmt.Sprintf("[s\\:cn=\"%s\"] %s", option.ComponentName, css)
})
res, err := page.CompileCSS([]byte(code), option.StyleMinify)
if err != nil {

View file

@ -75,11 +75,29 @@ func (parser *TemplateParser) Locale() *Locale {
return locale
}
path := filepath.Join("public", parser.option.Root, ".locales", name, strings.TrimPrefix(route, root)+".yml")
if exists, err := application.App.Exists(path); !exists {
if err != nil {
// Try exact locale first, then fallback to language prefix (e.g. zh-cn -> zh), then en-us
routeSuffix := strings.TrimPrefix(route, root) + ".yml"
candidates := []string{name}
if parts := strings.SplitN(name, "-", 2); len(parts) == 2 {
candidates = append(candidates, parts[0])
}
if name != "en-us" {
candidates = append(candidates, "en-us")
}
var path string
found := false
for _, candidate := range candidates {
path = filepath.Join("public", parser.option.Root, ".locales", candidate, routeSuffix)
if exists, err := application.App.Exists(path); exists {
found = true
name = candidate
break
} else if err != nil {
log.Error("[parser] %s Locale %s", route, err.Error())
}
}
if !found {
return nil
}

View file

@ -90,7 +90,7 @@ func (r *Request) NewData() Data {
// GetLocale get the locale
func GetLocale(cookies map[string]string) interface{} {
if lang, has := cookies["locale"]; has {
return lang
return strings.ToLower(lang)
}
return nil
}

View file

@ -70,6 +70,8 @@ func ComponentName(name string, hash ...bool) string {
name = strings.ReplaceAll(name, "/", "_")
name = strings.ReplaceAll(name, "[", "_")
name = strings.ReplaceAll(name, "]", "_")
name = strings.ReplaceAll(name, ".", "_")
name = strings.ReplaceAll(name, "-", "_")
cn := fmt.Sprintf("comp_%s", name)
// Keep the component name | hash will be supported later
// if len(hash) > 0 && hash[0] {

View file

@ -19,7 +19,7 @@ function $$(selector) {
}
function __sui_component_root(elm: Element, name: string) {
return elm.closest(`[s\\:cn=${name}]`);
return elm.closest(`[s\\:cn="${name}"]`);
}
function __sui_state(component) {
@ -210,7 +210,7 @@ function __sui_event_init(elm: Element) {
continue;
}
const component = eventElm.closest(`[s\\:cn=${cn}]`);
const component = eventElm.closest(`[s\\:cn="${cn}"]`);
if (typeof window[cn] !== "function") {
console.error(`[SUI] Component ${cn} not found`, eventElm);
return;