@@ -21,13 +21,77 @@ const (
2121 modFileName = "go.mod"
2222)
2323
24+ var errInvalidApplicationPlan = fmt .Errorf ("application plan was not constructed by appgen planning" )
25+
2426// Generate writes a self-contained Go app that embeds outputDir.
2527func Generate (outputDir , appDir string ) (Result , error ) {
2628 return GenerateWithOptions (outputDir , appDir , Options {})
2729}
2830
31+ // PlanApplication resolves and validates generated app options before emission.
32+ func PlanApplication (outputDir string , options Options ) (ApplicationPlan , error ) {
33+ if strings .TrimSpace (outputDir ) == "" {
34+ return ApplicationPlan {}, fmt .Errorf ("build output directory is required" )
35+ }
36+ absOutput , err := filepath .Abs (outputDir )
37+ if err != nil {
38+ return ApplicationPlan {}, err
39+ }
40+ planned , err := resolveOptions (absOutput , options )
41+ if err != nil {
42+ return ApplicationPlan {}, err
43+ }
44+ if err := validateAppPlanOptions (planned , true ); err != nil {
45+ return ApplicationPlan {}, err
46+ }
47+ return ApplicationPlan {options : planned , outputDir : absOutput , valid : true }, nil
48+ }
49+
50+ // PlanBackendApplication resolves and validates backend-only generated app
51+ // options before emission.
52+ func PlanBackendApplication (options Options ) (ApplicationPlan , error ) {
53+ planned , err := resolveBackendOptions (options )
54+ if err != nil {
55+ return ApplicationPlan {}, err
56+ }
57+ if err := validateAppPlanOptions (planned , false ); err != nil {
58+ return ApplicationPlan {}, err
59+ }
60+ return ApplicationPlan {options : planned , backendOnly : true , valid : true }, nil
61+ }
62+
63+ func validateAppPlanOptions (options Options , includeSSR bool ) error {
64+ if err := validateActionEndpoints (options .Actions ); err != nil {
65+ return err
66+ }
67+ if err := validateAPIEndpoints (options .APIs ); err != nil {
68+ return err
69+ }
70+ if err := validateFragmentEndpoints (options .Fragments ); err != nil {
71+ return err
72+ }
73+ if err := validateContractRoutes (options .IR ); err != nil {
74+ return err
75+ }
76+ if includeSSR {
77+ if err := validateSSRRoutes (options .SSR ); err != nil {
78+ return err
79+ }
80+ }
81+ return validateCORSConfig (options )
82+ }
83+
2984// GenerateWithOptions writes a self-contained Go app that embeds outputDir.
3085func GenerateWithOptions (outputDir , appDir string , options Options ) (result Result , err error ) {
86+ plan , err := PlanApplication (outputDir , options )
87+ if err != nil {
88+ return Result {}, err
89+ }
90+ return GenerateWithPlan (outputDir , appDir , plan )
91+ }
92+
93+ // GenerateWithPlan writes a self-contained Go app from an application plan.
94+ func GenerateWithPlan (outputDir , appDir string , plan ApplicationPlan ) (result Result , err error ) {
3195 defer recoverGeneratedIdentifierError (& err )
3296
3397 if strings .TrimSpace (outputDir ) == "" {
@@ -48,26 +112,14 @@ func GenerateWithOptions(outputDir, appDir string, options Options) (result Resu
48112 if err := validateDirectories (absOutput , absApp ); err != nil {
49113 return Result {}, err
50114 }
51- options , err = resolveOptions (absOutput , options )
52- if err != nil {
53- return Result {}, err
54- }
55- if err := validateActionEndpoints (options .Actions ); err != nil {
56- return Result {}, err
57- }
58- if err := validateAPIEndpoints (options .APIs ); err != nil {
59- return Result {}, err
60- }
61- if err := validateFragmentEndpoints (options .Fragments ); err != nil {
62- return Result {}, err
115+ if plan .backendOnly {
116+ return Result {}, fmt .Errorf ("backend application plan cannot be used for embedded app generation" )
63117 }
64- if err := validateContractRoutes (options .IR ); err != nil {
65- return Result {}, err
66- }
67- if err := validateSSRRoutes (options .SSR ); err != nil {
68- return Result {}, err
118+ if plan .outputDir != "" && filepath .Clean (plan .outputDir ) != filepath .Clean (absOutput ) {
119+ return Result {}, fmt .Errorf ("application plan output directory %q does not match build output directory %q" , plan .outputDir , absOutput )
69120 }
70- if err := validateCORSConfig (options ); err != nil {
121+ options , err := plan .optionsForEmit ()
122+ if err != nil {
71123 return Result {}, err
72124 }
73125
@@ -153,6 +205,16 @@ func GenerateWithOptions(outputDir, appDir string, options Options) (result Resu
153205// GenerateBackendWithOptions writes a generated Go app that serves only
154206// request-time backend routes for feature-bound actions and APIs.
155207func GenerateBackendWithOptions (appDir string , options Options ) (result Result , err error ) {
208+ plan , err := PlanBackendApplication (options )
209+ if err != nil {
210+ return Result {}, err
211+ }
212+ return GenerateBackendWithPlan (appDir , plan )
213+ }
214+
215+ // GenerateBackendWithPlan writes a generated Go app that serves only
216+ // request-time backend routes from an application plan.
217+ func GenerateBackendWithPlan (appDir string , plan ApplicationPlan ) (result Result , err error ) {
156218 defer recoverGeneratedIdentifierError (& err )
157219
158220 if strings .TrimSpace (appDir ) == "" {
@@ -162,24 +224,12 @@ func GenerateBackendWithOptions(appDir string, options Options) (result Result,
162224 if err != nil {
163225 return Result {}, err
164226 }
165- options , err = resolveBackendOptions ( options )
227+ options , err := plan . optionsForEmit ( )
166228 if err != nil {
167229 return Result {}, err
168230 }
169- if err := validateActionEndpoints (options .Actions ); err != nil {
170- return Result {}, err
171- }
172- if err := validateAPIEndpoints (options .APIs ); err != nil {
173- return Result {}, err
174- }
175- if err := validateFragmentEndpoints (options .Fragments ); err != nil {
176- return Result {}, err
177- }
178- if err := validateContractRoutes (options .IR ); err != nil {
179- return Result {}, err
180- }
181- if err := validateCORSConfig (options ); err != nil {
182- return Result {}, err
231+ if ! plan .backendOnly {
232+ return Result {}, fmt .Errorf ("embedded application plan cannot be used for backend app generation" )
183233 }
184234 if err := os .MkdirAll (absApp , 0o755 ); err != nil {
185235 return Result {}, err
0 commit comments