Fix transpiler: function hoisting + re-export paths + DB seed
Transpiler fixes:
- export function declarations now use hoisted function syntax instead
of var assignments, fixing modules that reference functions before
their declaration (e.g., rpc.setCache before export function rpc)
- Re-export paths (export { X } from "./relative") now resolve to
full module names in require() calls
DB seed fixes:
- Remove SQL comments from multi-value INSERT (pgx doesn't support --)
- Split multi-statement setval into individual Exec calls
Middleware:
- Add /web/database/* to public endpoint whitelist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BIN
odoo-server
BIN
odoo-server
Binary file not shown.
@@ -46,10 +46,8 @@ func AuthMiddleware(store *SessionStore, next http.Handler) http.Handler {
|
|||||||
path := r.URL.Path
|
path := r.URL.Path
|
||||||
if path == "/health" ||
|
if path == "/health" ||
|
||||||
path == "/web/login" ||
|
path == "/web/login" ||
|
||||||
path == "/web/setup" ||
|
|
||||||
path == "/web/setup/install" ||
|
|
||||||
path == "/web/session/authenticate" ||
|
path == "/web/session/authenticate" ||
|
||||||
path == "/web/database/list" ||
|
strings.HasPrefix(path, "/web/database/") ||
|
||||||
path == "/web/webclient/version_info" ||
|
path == "/web/webclient/version_info" ||
|
||||||
strings.Contains(path, "/static/") {
|
strings.Contains(path, "/static/") {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ func TranspileJS(urlPath, content string) string {
|
|||||||
deps, requireLines, cleanContent := extractImports(moduleName, content)
|
deps, requireLines, cleanContent := extractImports(moduleName, content)
|
||||||
|
|
||||||
// Transform exports
|
// Transform exports
|
||||||
cleanContent = transformExports(cleanContent)
|
cleanContent, exportedFuncs := transformExports(cleanContent)
|
||||||
|
|
||||||
// Wrap in odoo.define
|
// Wrap in odoo.define
|
||||||
return wrapWithOdooDefine(moduleName, deps, requireLines, cleanContent)
|
return wrapWithOdooDefine(moduleName, deps, requireLines, cleanContent, exportedFuncs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// URLToModuleName converts a URL path to an Odoo module name.
|
// URLToModuleName converts a URL path to an Odoo module name.
|
||||||
@@ -288,8 +288,8 @@ func extractImports(moduleName, content string) (deps []string, requireLines []s
|
|||||||
m := reExportNamedFrom.FindStringSubmatch(match)
|
m := reExportNamedFrom.FindStringSubmatch(match)
|
||||||
if len(m) >= 3 {
|
if len(m) >= 3 {
|
||||||
names := m[1]
|
names := m[1]
|
||||||
dep := m[2]
|
dep := resolve(m[2])
|
||||||
addDep(dep)
|
addDep(m[2])
|
||||||
// Named re-export: export { X } from "dep"
|
// Named re-export: export { X } from "dep"
|
||||||
// Import the dep (using a temp var to avoid redeclaration with existing imports)
|
// Import the dep (using a temp var to avoid redeclaration with existing imports)
|
||||||
// then assign to __exports
|
// then assign to __exports
|
||||||
@@ -352,8 +352,10 @@ func parseImportSpecifiers(raw string) []importSpecifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// transformExports converts export statements to __exports assignments.
|
// transformExports converts export statements to __exports assignments.
|
||||||
|
// Returns the transformed content and a list of exported function names
|
||||||
|
// (these need deferred __exports assignment to preserve hoisting).
|
||||||
// Mirrors: odoo/tools/js_transpiler.py (various export transformers)
|
// Mirrors: odoo/tools/js_transpiler.py (various export transformers)
|
||||||
func transformExports(content string) string {
|
func transformExports(content string) (string, []string) {
|
||||||
// export class Foo { ... } -> const Foo = __exports.Foo = class Foo { ... }
|
// export class Foo { ... } -> const Foo = __exports.Foo = class Foo { ... }
|
||||||
content = reExportClass.ReplaceAllStringFunc(content, func(match string) string {
|
content = reExportClass.ReplaceAllStringFunc(content, func(match string) string {
|
||||||
m := reExportClass.FindStringSubmatch(match)
|
m := reExportClass.FindStringSubmatch(match)
|
||||||
@@ -364,7 +366,13 @@ func transformExports(content string) string {
|
|||||||
return "const " + name + " = __exports." + name + " = class " + name
|
return "const " + name + " = __exports." + name + " = class " + name
|
||||||
})
|
})
|
||||||
|
|
||||||
// export [async] function foo(...) { ... } -> __exports.foo = [async] function foo(...) { ... }
|
// export [async] function foo(...) { ... }
|
||||||
|
// Must preserve function declaration hoisting (not var/const) because some
|
||||||
|
// Odoo modules reference the function before its declaration line
|
||||||
|
// (e.g., rpc.setCache assigned before "export function rpc").
|
||||||
|
// Strategy: keep as hoisted function declaration, collect names, append
|
||||||
|
// __exports assignments at the end.
|
||||||
|
var exportedFuncNames []string
|
||||||
content = reExportFunction.ReplaceAllStringFunc(content, func(match string) string {
|
content = reExportFunction.ReplaceAllStringFunc(content, func(match string) string {
|
||||||
m := reExportFunction.FindStringSubmatch(match)
|
m := reExportFunction.FindStringSubmatch(match)
|
||||||
if len(m) < 3 {
|
if len(m) < 3 {
|
||||||
@@ -372,10 +380,8 @@ func transformExports(content string) string {
|
|||||||
}
|
}
|
||||||
async := m[1] // "async " or ""
|
async := m[1] // "async " or ""
|
||||||
name := m[2]
|
name := m[2]
|
||||||
// Use "var name = __exports.name = function name" so the name is available
|
exportedFuncNames = append(exportedFuncNames, name)
|
||||||
// as a local variable (needed when code references it after declaration,
|
return async + "function " + name
|
||||||
// e.g., uniqueId.nextId = 0)
|
|
||||||
return "var " + name + " = __exports." + name + " = " + async + "function " + name
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// export const foo = ... -> const foo = __exports.foo = ...
|
// export const foo = ... -> const foo = __exports.foo = ...
|
||||||
@@ -439,7 +445,7 @@ func transformExports(content string) string {
|
|||||||
// Must come after other export patterns to avoid double-matching
|
// Must come after other export patterns to avoid double-matching
|
||||||
content = reExportDefault.ReplaceAllString(content, `__exports[Symbol.for("default")] = `)
|
content = reExportDefault.ReplaceAllString(content, `__exports[Symbol.for("default")] = `)
|
||||||
|
|
||||||
return content
|
return content, exportedFuncNames
|
||||||
}
|
}
|
||||||
|
|
||||||
// exportSpecifier holds a single export specifier from "export { X, Y as Z }".
|
// exportSpecifier holds a single export specifier from "export { X, Y as Z }".
|
||||||
@@ -473,7 +479,7 @@ func parseExportSpecifiers(raw string) []exportSpecifier {
|
|||||||
|
|
||||||
// wrapWithOdooDefine wraps the transpiled content in an odoo.define() call.
|
// wrapWithOdooDefine wraps the transpiled content in an odoo.define() call.
|
||||||
// Mirrors: odoo/tools/js_transpiler.py wrap_with_odoo_define()
|
// Mirrors: odoo/tools/js_transpiler.py wrap_with_odoo_define()
|
||||||
func wrapWithOdooDefine(moduleName string, deps []string, requireLines []string, content string) string {
|
func wrapWithOdooDefine(moduleName string, deps []string, requireLines []string, content string, exportedFuncs []string) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
|
|
||||||
// Module definition header
|
// Module definition header
|
||||||
@@ -508,6 +514,11 @@ func wrapWithOdooDefine(moduleName string, deps []string, requireLines []string,
|
|||||||
b.WriteString("\n")
|
b.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deferred __exports assignments for hoisted function declarations
|
||||||
|
for _, name := range exportedFuncs {
|
||||||
|
b.WriteString("__exports." + name + " = " + name + ";\n")
|
||||||
|
}
|
||||||
|
|
||||||
// Return exports
|
// Return exports
|
||||||
b.WriteString("return __exports;\n")
|
b.WriteString("return __exports;\n")
|
||||||
b.WriteString("});\n")
|
b.WriteString("});\n")
|
||||||
|
|||||||
@@ -258,21 +258,17 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
|
|||||||
seedDemoData(ctx, tx)
|
seedDemoData(ctx, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 11. Reset sequences
|
// 12. Reset sequences (each individually — pgx doesn't support multi-statement)
|
||||||
tx.Exec(ctx, `
|
seqs := []string{
|
||||||
SELECT setval('res_currency_id_seq', (SELECT COALESCE(MAX(id),0) FROM res_currency));
|
"res_currency", "res_country", "res_partner", "res_company",
|
||||||
SELECT setval('res_country_id_seq', (SELECT COALESCE(MAX(id),0) FROM res_country));
|
"res_users", "ir_sequence", "account_journal", "account_account",
|
||||||
SELECT setval('res_partner_id_seq', (SELECT COALESCE(MAX(id),0) FROM res_partner));
|
"account_tax", "sale_order", "sale_order_line", "account_move",
|
||||||
SELECT setval('res_company_id_seq', (SELECT COALESCE(MAX(id),0) FROM res_company));
|
}
|
||||||
SELECT setval('res_users_id_seq', (SELECT COALESCE(MAX(id),0) FROM res_users));
|
for _, table := range seqs {
|
||||||
SELECT setval('ir_sequence_id_seq', (SELECT COALESCE(MAX(id),0) FROM ir_sequence));
|
tx.Exec(ctx, fmt.Sprintf(
|
||||||
SELECT setval('account_journal_id_seq', (SELECT COALESCE(MAX(id),0) FROM account_journal));
|
`SELECT setval('%s_id_seq', GREATEST((SELECT COALESCE(MAX(id),0) FROM %q), 1))`,
|
||||||
SELECT setval('account_account_id_seq', (SELECT COALESCE(MAX(id),0) FROM account_account));
|
table, table))
|
||||||
SELECT setval('account_tax_id_seq', (SELECT COALESCE(MAX(id),0) FROM account_tax));
|
}
|
||||||
SELECT setval('sale_order_id_seq', (SELECT COALESCE(MAX(id),0) FROM sale_order));
|
|
||||||
SELECT setval('sale_order_line_id_seq', (SELECT COALESCE(MAX(id),0) FROM sale_order_line));
|
|
||||||
SELECT setval('account_move_id_seq', (SELECT COALESCE(MAX(id),0) FROM account_move));
|
|
||||||
`)
|
|
||||||
|
|
||||||
if err := tx.Commit(ctx); err != nil {
|
if err := tx.Commit(ctx); err != nil {
|
||||||
return fmt.Errorf("db: commit seed: %w", err)
|
return fmt.Errorf("db: commit seed: %w", err)
|
||||||
@@ -287,7 +283,6 @@ func seedViews(ctx context.Context, tx pgx.Tx) {
|
|||||||
log.Println("db: seeding UI views...")
|
log.Println("db: seeding UI views...")
|
||||||
|
|
||||||
tx.Exec(ctx, `INSERT INTO ir_ui_view (name, model, type, arch, priority, active, mode) VALUES
|
tx.Exec(ctx, `INSERT INTO ir_ui_view (name, model, type, arch, priority, active, mode) VALUES
|
||||||
-- res.partner views
|
|
||||||
('partner.list', 'res.partner', 'list', '<list>
|
('partner.list', 'res.partner', 'list', '<list>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="email"/>
|
<field name="email"/>
|
||||||
@@ -332,7 +327,6 @@ func seedViews(ctx context.Context, tx pgx.Tx) {
|
|||||||
<field name="city"/>
|
<field name="city"/>
|
||||||
</search>', 16, true, 'primary'),
|
</search>', 16, true, 'primary'),
|
||||||
|
|
||||||
-- account.move views
|
|
||||||
('invoice.list', 'account.move', 'list', '<list>
|
('invoice.list', 'account.move', 'list', '<list>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="partner_id"/>
|
<field name="partner_id"/>
|
||||||
@@ -343,7 +337,6 @@ func seedViews(ctx context.Context, tx pgx.Tx) {
|
|||||||
<field name="amount_total"/>
|
<field name="amount_total"/>
|
||||||
</list>', 16, true, 'primary'),
|
</list>', 16, true, 'primary'),
|
||||||
|
|
||||||
-- sale.order views
|
|
||||||
('sale.list', 'sale.order', 'list', '<list>
|
('sale.list', 'sale.order', 'list', '<list>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="partner_id"/>
|
<field name="partner_id"/>
|
||||||
@@ -352,7 +345,6 @@ func seedViews(ctx context.Context, tx pgx.Tx) {
|
|||||||
<field name="amount_total"/>
|
<field name="amount_total"/>
|
||||||
</list>', 16, true, 'primary'),
|
</list>', 16, true, 'primary'),
|
||||||
|
|
||||||
-- crm.lead views
|
|
||||||
('lead.list', 'crm.lead', 'list', '<list>
|
('lead.list', 'crm.lead', 'list', '<list>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="partner_name"/>
|
<field name="partner_name"/>
|
||||||
@@ -363,7 +355,6 @@ func seedViews(ctx context.Context, tx pgx.Tx) {
|
|||||||
<field name="user_id"/>
|
<field name="user_id"/>
|
||||||
</list>', 16, true, 'primary'),
|
</list>', 16, true, 'primary'),
|
||||||
|
|
||||||
-- res.partner kanban
|
|
||||||
('partner.kanban', 'res.partner', 'kanban', '<kanban>
|
('partner.kanban', 'res.partner', 'kanban', '<kanban>
|
||||||
<templates>
|
<templates>
|
||||||
<t t-name="card">
|
<t t-name="card">
|
||||||
@@ -377,7 +368,6 @@ func seedViews(ctx context.Context, tx pgx.Tx) {
|
|||||||
</templates>
|
</templates>
|
||||||
</kanban>', 16, true, 'primary'),
|
</kanban>', 16, true, 'primary'),
|
||||||
|
|
||||||
-- crm.lead kanban (pipeline)
|
|
||||||
('lead.kanban', 'crm.lead', 'kanban', '<kanban default_group_by="stage_id">
|
('lead.kanban', 'crm.lead', 'kanban', '<kanban default_group_by="stage_id">
|
||||||
<templates>
|
<templates>
|
||||||
<t t-name="card">
|
<t t-name="card">
|
||||||
|
|||||||
Reference in New Issue
Block a user