pkg/nodejs/angular.go (58 lines of code) (raw):
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nodejs
import (
"fmt"
"strings"
gcp "github.com/GoogleCloudPlatform/buildpacks/pkg/gcpbuildpack"
"github.com/buildpacks/libcnb/v2"
)
var (
// angularVersionKey is the metadata key used to store the angular build adaptor version in the angular layer.
angularVersionKey = "version"
// PinnedAngularAdapterVersion is the version of the angular adapter that will be used.
PinnedAngularAdapterVersion = "17.2.13"
)
// InstallAngularBuildAdaptor installs the angular build adaptor in the given layer if it is not already cached.
func InstallAngularBuildAdaptor(ctx *gcp.Context, al *libcnb.Layer, version string) error {
layerName := al.Name
version, err := AngularAdaptorVersion(version)
if err != nil {
return err
}
// Check the metadata in the cache layer to determine if we need to proceed.
metaVersion := ctx.GetMetadata(al, angularVersionKey)
if version == metaVersion {
ctx.CacheHit(layerName)
ctx.Logf("angular adaptor cache hit: %q, %q, skipping installation.", version, metaVersion)
} else {
ctx.CacheMiss(layerName)
if err := ctx.ClearLayer(al); err != nil {
return fmt.Errorf("clearing layer %q: %w", layerName, err)
}
// Download and install angular adaptor in layer.
ctx.Logf("Installing angular adaptor %s", version)
if err := downloadAngularAdaptor(ctx, al.Path, version); err != nil {
return gcp.InternalErrorf("downloading angular adapter: %w", err)
}
}
// Store layer flags and metadata.
ctx.SetMetadata(al, angularVersionKey, version)
return nil
}
// AngularAdaptorVersion determines the version of Angular that is needed by an Angular project.
func AngularAdaptorVersion(version string) (string, error) {
// TODO(b/323280044) account for different versions once development is more stable.
adapterVersion := PinnedAngularAdapterVersion
return adapterVersion, nil
}
// downloadAngularAdaptor downloads the Angular build adaptor into the provided directory.
func downloadAngularAdaptor(ctx *gcp.Context, dirPath, version string) error {
// TODO(b/323280044) account for different versions
if _, err := ctx.Exec([]string{"npm", "install", "--prefix", dirPath, "@apphosting/adapter-angular@" + version}); err != nil {
ctx.Logf("Failed to install angular adaptor version: %s. Falling back to latest", version)
if _, err := ctx.Exec([]string{"npm", "install", "--prefix", dirPath, "@apphosting/adapter-angular@latest"}); err != nil {
return gcp.InternalErrorf("installing angular adaptor: %w", err)
}
}
return nil
}
// OverrideAngularBuildScript overrides the build script to be the Angular build script.
func OverrideAngularBuildScript(njsl *libcnb.Layer) {
njsl.BuildEnvironment.Override(AppHostingBuildEnv, fmt.Sprintf("npm exec --prefix %s apphosting-adapter-angular-build", njsl.Path))
}
// ExtractAngularStartCommand inspects the given package.json file for an idiomatic `serve:ssr:APP_NAME`
// command. If one exists, its value is returned. If not, return an empty string.
func ExtractAngularStartCommand(pjs *PackageJSON) string {
for k, v := range pjs.Scripts {
if strings.HasPrefix(k, "serve:ssr:") {
return v
}
}
return ""
}