B
chat_bubble group bug_report language
DO NOT USE BRACKET IN PRODUCTION!

Bracket

Bracket is a tool for prototyping, building, staging, maintaining, and launching marketing websites at Google. It is built on Flask, uses the App Engine SDK for local development, App Engine ~in the cloud~ for staging work internally, Kintaro for editing and localizing content, and comes with build tools for generating production ready static sites on SCS soooo...

Here are some docs!

Getting Started

Before you do anything, make sure you have the following all installed on your machine and good to go:

Issues?

A common list of issues can be found here.

If you're still having troubles, ask g/bracket-users-external and update the document with your findings.

Install the CLI

To get started on a new bracket project, first you must install the Bracket CLI globally on your machine. You might encounter a couple password prompts during the install:

npm install -g git+https://webmaster.googlesource.com/bracket/

At this point you should have the bracket command available on your machine, test it out by printing out the help page:

bracket -h
Santa errors

If you get a Santa error while installing bracket, temporarily drop down to minimally-protected mode at go/upvote/hosts, then run the install command again.

Initialize a new project

Now, to start a new project, use the init command to start a new Bracket project. It will ask you a series of questions to help you get all set up:

bracket init my-project

Once you've finished the prompts, login, hop into your project, spin up a dev server and get to coding, nerd:

gcloud auth application-default login
cd my-project
npm run dev

Run the dev server

Now that that you've got your git repository all sorted, you can spin up a local dev server.

First, login: language-bash gcloud auth application-default login

This will cause a browser window to pop up. Choose your account, then run the npm run dev script to spin up the local dev server along with your front end compilers, linters, and watchers:

npm run dev

Here you should see some terminal output that ends something like this:

[19:06:08] Starting 'serve'...
[19:06:08] Finished 'serve' after 17 ms
[19:06:08] Starting 'watch'...
[BS] Proxying: http://localhost:8080
[BS] Access URLs:
 ---------------------------------------
       Local: http://localhost:3000/
    External: http://172.31.43.209:3000/
 ---------------------------------------
          UI: http://localhost:3001
 UI External: http://172.31.43.209:3001
 ---------------------------------------

That means we now have a dev server listening on localhost:3000 which is proxying the App Engine dev server on localhost:8080. Go ahead and make a request in your browser to localhost:3000 and you should see the bracket docs page render. Seeing a docs page is probably not what you are after, but don't worry, this is just a default top level handler for when you haven't defined your own. help

Adding a Route/Page

Now we need to make some routes and pages of our own. To create a new page and route with bracket, you need to first include it in the src/meta.py file.

This file's job is to create a variable named meta that defines a dictionary keyed by every page of your site (including locales). In this file you should already see some simple logic building this dictionary from a list of locales and routes.

To configure a new page/route add it to the routes list:

paths = [
  '/' # Make sure this line is uncommented.
  '/demo/'
]

The second part to adding a page is to pair that route to a Python handler and template in the src/routes.py.

When you open this file, you'll see a couple things going on. At the top of the file we import flask, import bracket, then we import a couple things from inside the bracket package. Flask is the web framework we use to register handlers, and Bracket is the package that initializes and configures our Flask instance.

Underneath that, you should see a couple lines of code commented out:

help

# @root.route('/')
# @intl.route('/')
# def index():
#   """Return a polite greeting to the user."""
#
#   return flask.render_template('page.jinja',
#     greeting='Hey there, ya big ole nerd!'
#   )

Here is the same code annotated:

# The two lines starting with @ are called decorators. More specifically,
# these are Flask `blueprints` that define a method named `route` which is used
# to bind python functions to URL routes.
#
# The blueprint @root registers the handler to non-intl paths, and the @intl
# one registers, you guessed it, /intl/<locale>/ style paths.
@root.route('/')
@intl.route('/')
# This is the handler function declaration. The name does not matter since we
# are using the decorator pattern.
def index():
  """Return a polite greeting to the user.""" # Python docstring, hallelujah.

  # This line returns our page content from the server. If you aren't
  # familiar with flask, the `render_template` function takes a path
  # relative to the `/src/` directory, looks up your template with it,
  # and renders it with any subsequent arguments passed in.
  #
  # Some globals are automatically passed in as well but we can get to that
  # later.
  return flask.render_template('index.jinja',
    greeting='Hey there, ya big ole nerd!'
  )

Uncomment these lines and hit your browser again at localhost:3000 to see your wonderful new custom page.

Troubleshooting: My site has no routes (everything returns a 404)

Problem: You generating the list of routes in src/meta.py based on Kintaro data, but when you visit a route that should exist, you get a 404.

Solution: You are probably specifying the static path in meta.py. This is deprecated. Remove the /src/static/ entry from your meta.py meta object. Then, set the static path in env.default.yaml instead.

Bracket utilities

Now I'm sure you're wondering where all that weird styling came from. Not to worry, lets just have a look at the template file at src/index.jinja.

There isn't much in this file, but up at top you should see a line of code:

{% extends 'b/layouts/base.jinja' %}

That line makes the template extend another template hidden in the Bracket code base. Remove this line and replace it with a path to your own layout template to get a truly fresh start.

Looking further, you should notice another odd line of code at the end of the src/index.jinja file:

{% include 'b/utils.jinja' %}

Out of the box, Bracket declares a handful of nifty variables for you that can be used in your templates and route handlers to accomplish common tasks like generating intl paths, relative paths, referencing the current locale or printing l10n data.

These variables are available in template code automatically, under the g namespace. In python handlers, this same variable is attached to the flask context at flask.g.

The b/utils.jinja template template will print an off-canvas table containing all the current global template variables if a ?props url parameter is found in the request, otherwise it's a noop template. Test it out and see by visiting localhost:3000/?props. Clicking the bottom right icon should reveal the page properties rendered inside of an iframe.

Static files

Pretty much all websites need a way to serve static files, and Bracket sites are no different. By default, the src/static/ directory is served at the url /static/ and needs no configuration, just drop files into this directory and link to them from your markup with root relative urls.

Bracket also comes set up with a handy template filter for generating cache invalidating urls for files in your static directory. Just give the |static filter a path to your file, relative to the static directory, and it will intelligently generate the client accessible url to the file along with an sha1 hash of the file contents as a query parameter.

For example:

<link href="{{ 'css/index.min.css'|static }}" rel="stylesheet">
<script src="{{ 'js/index.min.js'|static }}" defer></script>

Will output:

<link href="/static/css/index.min.css?cache=bbabe3f" rel="stylesheet">
<script src="/static/js/index.min.js?cache=da74462" defer></script>

This filter also performs some handy localization logic. Before generating the cacheable url, the filter will look for localized images in the form of my-file.<locale>.jpg before falling back to the given file path. The fallback functions the same as SCS fallbacks do by looking for the full locale code first, then geo specific files, then lang files, then root.

Code Example (line #8)
Locale Demo Geo Demo Lang Demo Root Demo

It's likely, also, that the /static/**/* path won't work on your site, for instance if your site needs to live at a subdirectory of google.com, like www.google.com/my/cool/site/. To change the serving url, change env.default.yaml file to have a 'static_path' attribute:

static_path: /my/cool/site/static/directory/

Adding this new attribute will cause the static file filter to render a path to /my/cool/site/static/directory/**/* rather than just /static/**/*.

Caching

By default, Bracket caches all content fetched over the network to make development cycles faster.

The exact caching policy depends on the environment:

Environment Description

development

Apps running locally in the development SDK. In this environment, data (including kcs content) is cached to memcache and the local datastore indefinitely for snappier code/refresh cycles, under the assumption that content won't be changing very often, or won't matter.

Stopping the server and deleting the .bracket_cache file will clear the datastore.

staging

Apps running on hosted app engine instances. In this environment, data (including kcs content) is cached for 5 seconds. This is so content updates are visible quickly.

Bracket makes flushing the cache in any environment easy as pie, To do that just add a ?flush parameter to a Bracket page request. This will remove the entire cache from memory and disk before any handlers are executed.

If you are a control freak that wants to manage the cache with more granularity, you can set a cache_ttl key in your environment's yaml file to configure the number of seconds to cache content (0 meaning don't cache).

If you are a control freak and don't believe in configuration files, you can also manage cache entries directly in the App Engine Admin Console at localhost:8000/datastore. From there, you can add/edit/remove things from the datastore to your hearts desire. Just make sure to flush memcache as well in order to see fresh content.

Internal Sites

Bracket was primarily made to make building and launching public marketing projects easier, but it turns out that it is also pretty nifty for creating internal sites hosted on App Engine too.

Some of the concepts in Bracket aren't completely relevant or necessary outside the context of static production sites though. Namely the locale and route validation Bracket does against the configuration in the meta.py file.

If you don't need to do a static deployment, and you'd rather not configure locales and pages and just use Flask's dynamic router, you can set a special cowboy_mode key to True in the meta.py file to turn off the strict page/locale validation, like so:

# Declare the meta configuration object.
meta = {
    'pages': pages,
    'cowboy_mode': True
}

From then on, registering routes only needs to be done via the Flask router, in the routes.py file, or wherever else you bind your Python handlers.

Fetching content from KCS

Now that you've got your new hot page up and going, you can probably get prototyping just fine without the need of a Content Management System. Eventually though, the time will come when you'll want to give someone non technical the ability to make edits to copy and image assets, and even later, you might want to translate your content into different languages.

This is where Kintaro comes in. You may already be familiar with the Kintaro Content Server, so I won't cover much here but the gist is that it is an app that stores and translates content that also has a REST API. Official documentation for KCS can be found here.

KCS Permissions

Bracket will use your individual user credentials to fetch content from KCS. To set this up, first run gcloud auth application-default login. Then, add an entry for use_local_credentials: true in your env.default.yaml.

The Bracket service account, bracket.google.com@appspot.gserviceaccount.com, has been deprecated. If you are still using it, you should set up KPS and remove it from your KCS permissions ASAP as the functionality will be completely removed from an upcoming release for security reasons.

KCS API Client

Included as part of Bracket, is a slim and simple Python API client for connecting to Kintaro. Currently the client only supports 3 methods, document(), collection(), and batch():

#document

This method will fetch and return exactly 1 KCS document.

Args:

Returns:

A single document from KCS.

Example:

from bracket import kcs

# Fetches a document.
hero = kcs.document(
  document_id='4618717635805184',
  collection_id='LandingPageHero',
  repo_id='verily',
)
#collection

This method will fetch and return all documents for a KCS collection.

Args:

Returns:

A KCS collection

Example:

from bracket import kcs

# Fetches a KCS collection and returns it as a list.
press = kcs.collection(
  collection_id='LandingPagePressArticles',
  repo_id='verily',
)

# Filter for articles by a given set of authors that are live and order by
# author name (ascending) and then creation date descending.
press = kcs.collection(
  collection_id='LandingPagePressArticles',
  repo_id='verily',
  query='(author__email = "page@google.com" or author__email = "nmarrow@google.com") and live = true',
  sorts=('__meta__created_by', '-__meta__created_on'),
)
#batch

This method will fetch concurrently, both documents and/or collections.

Args:

Returns:

A list of results that corresponds in order to the list of objects passed in.

Example:

from bracket import kcs

# Fetches 2 documents and 3 collections concurrently using threads.
hero, articles, press, people, footer = kcs.batch([
  {
    'document_id': '4618717635805184',
    'collection_id': 'LandingPageHero',
    'repo_id': 'verily',
  },
  {
    'collection_id': 'LandingPageArticles',
    'repo_id': 'verily',
  },
  {
    'collection_id': 'LandingPagePressArticles',
    'repo_id': 'verily',
  },
  {
    'collection_id': 'People',
    'repo_id': 'verily',
  },
  {
    'document_id': '5987066567458816',
    'collection_id': 'Footer',
    'repo_id': 'verily',
  }
])

Add new Kintaro content via Scribe

Scribe is a new workflow in Kintaro that provides site preview, field discovery, and lightweight content updates within a consistent user interface.

One workflow in particular revolves around enabling non-technical users (NTUs) to add new Kintaro content to a site without any Bracket template or code changes.

In order to make this possible, you need to:

Automatically rendering new Kintaro content

When NTUs add new Kintaro content, it should automatically appear in the preview instance without any changes to Bracket template or Python code.

In order to enable this, you need to structure your Kintaro schemas and implement the templates and routes in a certain way:

Specifying which content users can add to in Scribe

For each page / route, you need to specify the appropriate Kintaro collections and repeated fields users should be able to add to in Scribe.

Bracket provides a simple Python API function to accomplish this: kcs.register_scribe_addable_content.

register_scribe_addable_content

This method will register given Collections and repeated fields as Scribe-addable, and any such content will then be listed in the Scribe sidebar

Args:

Returns:

None

Example:

from bracket import kcs

from bracket import root
import flask

@root.route('/')
def index():

    # Fetches a document.
    hero = kcs.document(
      document_id='4618717635805184',
      collection_id='LandingPageHero',
      repo_id='verily',
    )

    # Fetches a KCS collection.
    press = kcs.collection(
      collection_id='LandingPagePressArticles',
      repo_id='verily',
    )

    kcs.register_scribe_addable_content(
        collections=[press],
        fields=[hero['paragraphs']])

    return flask.render_template(...)

For your convenience, the collections argument takes in an iterable of Python dicts that you get from calling kcs.collection and derives the appropriate repo_id, project_id, and collection_id from the dicts.

Similarly, the fields argument takes in an iterable of kcs_document.ContentList entities that you get from calling kcs.document and indexing into the appropriate repeated field. In our particular example, the hero document has a repeated TextField called "paragraphs", and hero['paragraphs'] is a kcs_document.ContentList entity. Under the hood, we use the ContentList attributes to automatically derive the Kintaro field uris to send over to Scribe.

Caveats

For performance reasons, we do not fetch the corresponding schemas for each Kintaro field. We derive schema information based on existing content. If you are registering a repeated reference field, each possible collection that could be referenced by the reference fields must have at least 1 reference field that already points to that collection. Otherwise, that collection will not be addable.

We want to optimize the workflow for NTUs to add new content by minimizing the number of steps required to add new content. In the case of repeated reference fields, we show NTUs the corresponding Add New document page. When the new document is saved, a corresponding reference field is automatically appended to the repeated reference field pointing to the new document.

Pushing your work to App Engine

Pushing your work to App Engine is pretty simple, but you have to take care of four small steps before you can start sharing URLs.

  1. Create a Google Cloud project and App Engine app in the App Engine Console.
    • Your Project name and Project ID can be the same or different, just note that the Project ID will be your permanent appspot subdomain. These names should reflect the work you are doing, preferably similar to your code repository name. We recommend you use short names; later, you may need to prepend this with the version name, and there is a total character limit of 63.
    • Create your app on appspot, not googleplex.
  2. Make sure that the bracket.appId field in package.json is set to your App Engine Project ID.
  3. Set up Kintaro Preview Server (KPS).
  4. Deploy a new version of your app with bracket push <version>, where version is just a string to identify a particular snapshot of your code. Follow any prompts and select us-central if it asks you to select a region. It should end with a url pointing to your newly deployed App Engine app.
    • I like to map version names to my git branch names like master and develop, but you are free to use any identifiers you like.

Kintaro Preview Server

Bracket sites using KCS content must be set up for Kintaro Preview Server (KPS). KPS allows you to see a fully rendered preview of your site before pushing content, as well as unlocking editing capabilities via Kintaro Scribe, making it easy to correlate content on a rendered site page and content in the Kintaro content management system.

You can read more about the features that the Preview Server enables in this document, which gives an overview of what a complete Kintaro project should look like.

After setting up your Bracket site for KPS, you must access it through https://<App Engine Project ID>.proxy.preview.kintaro.goog instead of the appspot.com URLs.

Setup

KPS setup is now part of the bracket init process. However, if you created your site before this update, here's how to set it up:

Full instructions. tl;dr:

  1. Install the gcloud CLI.
  2. Run gcloud auth application-default login.
  3. Install the KPS set up CLI: virtualenv <folder_name (should not exist yet)> source <folder_name>/bin/activate pip install git+https://kintaro.googlesource.com/preview_scripts deactivate
  4. Alias the script: alias kps_set_up_preview_client="<folder_name>/bin/kps_set_up_preview_client"
  5. Run the script: kps_set_up_preview_client --run_wizard --preview_client_id=<App Engine Project ID> --app_path=<full local path to your project folder>

Permissions

Viewing Pages

KPS automatically grants viewing access to your Bracket project on App Engine to all users who can view the KCS site and KCS workspace specified in the project env.<staging/default>.yaml + URL.

Pushing Code

If you would like to give users access to push new code to your App Engine project, go to the IAM & Admin page for your project, and give them the "Project > Editor" permission.

Versions

App Engine supports hosting different instances of a project via versions. Versions are deployed by using --version flag in the gcloud CLI. Versions are primarily useful in the staging environment, and don't come into play in the local development environment or when running a build.

You can access different versions of an app with https://[version]-dot-[project id].proxy.preview.kintaro.goog. For instance, the develop version of the bracket app engine project id is https://develop-dot-bracket.proxy.preview.kintaro.goog.

What if I'm not using KPS?

This use case is possible, but is not officially supported. You will need to control viewing permissions in the GCP Console instead.

Pushing your work to SCS

You can ignore this section if you don't have Google machine access.

In order to push a site to SCS, you submit a Changelist (CL) with the generated static files. The Bracket CLI can automate this process for you with bracket deploy. You must run this command from a machine with Google access.

Setup

Define the following variables in your project's package.json (the "."s indicate nesting, i.e. repository.url indicates {"repository": {"url": <VALUE>}}).

Common Workflows

For a full list of flags, run bracket deploy --help.

Deploy existing files in ./dist
bracket deploy
Deploy a fresh build from your local files
bracket deploy --build
Deploy version uploaded to remote Git repository
bracket deploy --build --git=https://webmaster.googlesource.com/bs/about --branch=develop

Build Mode

You may wish to make certain optimizations in production code (for instance, base64 encoding some images) that you don't want to appear in development or staging environments. For that reason, Bracket includes an is_build_mode function you can use to determine if the current request is part of a build process:

from bracket import config

print config.is_build_mode()
# >> False

Environments

There are 2 environments for bracket sites, development, and staging. This configuration is stored as ENV on the config module inside the bracket package. You may import and read this value from anywhere in the app like so:

from bracket import config

print config.ENV
# >> development
Configuring Environments

Apps can be configured to behave differently under different environments and versions. In the root of the project you will find 3 different env.*.yaml files, two for the environments listed above, and an env.default.yaml that the each environment inherits from.

The env.development.yaml file is git ignored so that multiple developers may configure their dev environment without bothering anyone else. However, env.development.sample.yaml is included as a convenient scaffold for this file. Copy it to env.development.yaml and modify it to configure your local workspace.

There are currently three configurable settings in these files:

Kintaro Workspace configs

Keys inside of the kcs object refer to App Engine versions, the values are the the kcs workspace id to use in that version. The special key default is used when running a version name that isn't defined in this file:

# Keys refer to App Engine versions, values are the kcs project ids.
#
# The `default` key is reserved for specifying the project ids for unspecified
# App Engine versions.

kcs:
  master: published
  develop: bracket-site
  default: published
Cache TTL

The cache settings for Bracket can be set up in these file as well via the cache_ttl key. Currently you can only set this differently per environment, but per version cache settings may be enabled if there is demand for such control.

The value of cache_ttl is an integer representing the number of seconds to keep the cache alive. For example:

# Will keep the cache alive for 27 years, or until flushed manually.
cache_ttl: 864000000

# Will not cache at all.
cache_ttl: 0
Root Locale

Kintaro treats 'root' as the root locale for all sites. If you want to use a different locale as root (strongly discouraged!) you can do so:

# This is the default value, and makes it so that Bracket pages registered
# with @root will request content from Kintaro's root locale.
root_locale: root

# This will make it such that pages registered with @root will instead fetch
# from an ALL_ALL locale in Kintaro. Users will need to make sure that an
# ALL_ALL locale exists in Kintaro.
root_locale: ALL_ALL

Front End Tools

As of v0.3.0, Bracket ships with a scaffolding tool, accessible via the command line with bracket init. Currently three scaffolds are available, named vanilla, Web Standard Closure Modules + SCSS, and Web Standard CDN. More scaffolding options are on the roadmap, with support for things like Typescript, so stay tuned for updates.

Vanilla Scaffold

As of 5/30/2019, the vanilla scaffold ships with a TypeScript set up with Webpack, Closure Compiler, and Sass. The files src/index.scss and src/index.ts are the entry point files for Sass and TypeScript respectively.

The scaffold also provides a testing set up with Karma/Jasmine, and uses Browsersync in dev mode to enable live reloads on file changes.

Web Standard ES Modules + SCSS Scaffold

The Web Standard ES Modules + SCSS scaffold uses comes with the latest version of Glue along with a dev set up similar to the Vanilla scaffold. An exception to this is that it uses ES Modules as opposed to Closure Modules in the Vanilla scaffold.

YouTube Scaffold

Scaffold maintained by the YouTube marketing eng team. Spits out a nice set up for building YT branded projects.

Angular Scaffold

Scaffold for building out static "apps" with Angular and Bracket. Useful for projects that need a single page app-like experience but would like to store content in Kintaro.

Is essentially the output from Angular CLI's ng new, modified slightly to integrate with Bracket as an application server and content API.

For more context about this scaffold, have a read through the announcement thread.

Task Reference

All the commands you should need to work with Bracket are defined as npm Scripts. Although many commands are just wrappers around other CLI's like gcloud and Gulp, using npm scripts allows us to define a uniform and easy to remember convention for all of our project related tasks, regardless of the language or tools used by the task, be it Node.js, Python, or even Bash.

Each scaffold has different tasks and commands declared, but a few important ones are shared between all of them:

npm start (alias)

Alias to npm run dev

npm run dev

Spins up a Bracket server and asset compilers in watch mode for development.

npm run dev:tdd

Spins up a Bracket server and asset compilers in watch mode for development, as well as a karma test runner.

npm run stage <version>

Builds the front end code for your project and pushes your project to an App Engine version for your project.

npm run build

Builds all front end code and static HTML for you site, and writes it to the dist/ directory.

For all other tasks, see the scripts entry in the package.json file for your project.

Project Owners

Gerrit setup

Bracket assumes that you are using Git/Gerrit for Version Control and Code Review, as these are the canonical Git tools at Google. Once you've started a new Bracket project, you'll want to set up your own repository in Gerrit to collaborate and perform Code Reviews.

If you are using the webmaster Git host, there is a special All-Projects-Bracket ACL with a reasonable configuration out of the box to inherit from.

When creating your repository, in the "Rights Inherit From" field, begin typing "All-Projects-Bracket" and select it from the drop down. Then hit Create Project and you are good to go. You may override the built in ACL but most projects shouldn't need to.

Code Reviews

The All-Projects-Bracket ACL is intended to standardize the review process across Bracket projects, define privileges for approvers and contributors, custom labels for presubmits, and potentially (but not currently), language readability enforcement.

There are many nuances to the ACL and who has access to what, but the gist is that the master branch is restricted and all commits to it must be reviewed and merged through Gerrit. Contributors to each project will belong to one of two groups:

Bracket Approvers

This Group is intentionally small (for now) but the goal is to expand the group as people get experience with Bracket and learn the ins and outs of what to do and not do, and how to facilitate things like upgrading versions. You can think of it as a "Bracket Readability".

If you have had some experience with Bracket already and would like to be added to this group, contact nmosher@google.

Alternatively, if you would like extra groups to be approvers on your project, just give the group Owners access to refs/* on your repo. That will grant them all the same rights as bracket-approvers.

Everyone

This is literally the Google group everyone@google.com

Continuous Integration

Out of the box Bracket comes with a continuous integration set up, configured to run on Google's internal Jenkins, named Kokoro. All that is needed to get CI running is to turn it on by adding your repository to the Kokoro configs in Piper.

The only integration baked into Bracket is a Presubmit that runs on all new Gerrit changes and Patchsets. It does a Sass lint/compile, and a Javascript lint/compile, but only the compile step will fail the build and block submission in Gerrit. Lint errors are logged as warnings in Sponge, and are not persisted to Gerrit.

Stay tuned for more integrations with Kokoro in the future, like automated pushes to App Engine and Karma/E2E test runners.

Kokoro ACL

It takes two separate CLs in piper to get it running, the first is to add your repository to the Git on Borg ACL file for Kokoro. Just add your repo to the repo_url array in this CL:

# google3/devtools/kokoro/config/data/git_on_borg_resource_acl.gcl
...
{
  entity = [
    {
      job_prefix = "bracket"
    },
  ],
  git_on_borg_resource = {
    repo_url = [
      "rpc://webmaster/bracket",
      "rpc://webmaster/bracket-acl-test",
      "rpc://webmaster/your-repo-here",
    ]
  },
},
...

Then add kokoro-team to reviewers, and submit once they LGTM.

Kokoro Job Config

Once that has been submitted, you will need to add a presubmit job config for your project. This CL adds a presubmit job for a project called bracket-acl-test.

Copy that file to google3/devtools/kokoro/config/prod/bracket/<project_name>/presubmit.cfg, replace the url, email, and admin values with your own, get it LGTM'd and submit it.

Once it is submitted, Kokoro will begin watching your repo in Gerrit for changes, and running a Javascript and Sass build for all of your Gerrit Code Reviews. How easy is that?

Upgrading Bracket

As of v0.3.0, Bracket is distributed as a global and locally installed npm package to make updating as simple as possible.

For existing bracket projects, to upgrade non breaking changes (patch and minor updates, since we are pre 1.0.0), all users need to do is update the semver number on the @google/bracket dependency listed in package.json and run npm install again. For instance, to update a project from v0.3.0 to v0.3.2, users should change:

// Inside `package.json`.
devDependencies: {
  "@google/bracket": "git+https://webmaster.googlesource.com/bracket#semver:0.3.0",
}

to:

devDependencies: {
  "@google/bracket": "git+https://webmaster.googlesource.com/bracket#semver:0.3.2",
  ...
}

then run:

npm install

For breaking change updates, after following the above steps, re-initialize their projects to assist in the upgrade process:

# Inside your project, run `bracket init` and follow the prompts.
bracket init

# See what if any changes were made to the originally scaffolded
# files, if any.
git diff

# Fix any unwanted changes and add them.
git add <file>

# Revert any unwanted files or changes.
git checkout <file>

Upgrade Guides

0.4.x -> 0.5.x

Bracket is now Python 3-only. All your site code will need to be updated to be Python 3 compatible.

The following is a non-comprehensive list of what might need to be changed in your site code:

In its current state, Bracket v0.5.0 should be considered stable enough to build and deploy your site. One thing to keep in mind, however, is that we had to code around the fact that there isn't yet a dev_appserver for Python 3, so we aren't able to fully emulate the App Engine functionality locally such as yaml parsing, datastore caching, etc that would take place when deployed. Thus, building / running your local application in Python 3 will likely be slower than in Python 2 for now.

This release is mainly intended to bridge the potential gap so users can continue site development if the gLinux team were to remove and block the Python 2 binary on gLinux installations before the dev_appserver for Python 3 is released as part of the google-cloud-sdk.

0.3.x -> 0.4.x

The kcs methods (kcs.document, kcs.collection, kcs.search, kcs.batch) now return KcsDocument objects instead of dicts. This should be backwards-compatible for the vast majority of sites, since the new KcsDocument objects should have the same API as the old dicts, and have all the same special meta keys (collection_id, document_id, locale, project_id, repo_id, _kitana_doc). If you are getting the meta keys the old way, though (e.g., some_doc['repo_id']), you should update your code to use the new meta methods instead (e.g., some_doc.GetRepo()). To see the API for the new new object check out the KCS API DOCUMENTATION

0.2.x -> 0.3.x

Upgrading from 0.2.x to 0.3.x is for the most part, just transitioning your project to using the @google/bracket npm package instead of having all Bracket code checked in to your repository.

To get upgraded, just install the Bracket CLI, cd into your 0.2.x project, and run bracket init.

Follow the prompts, and let it overwrite the files in your working directory with the 0.3.x versions. Check out the diffs it made and revert anything that clearly breaks your project, like src/routes.py or src/meta.py.

The most important changes are to app.yaml, appengine_config.py, and package.json so if you've made mods to these files for your project, you'll need to reconcile the changes manually.

Hopefully, at this point, npm run dev will spin up a server like normal without any errors.

The last step is to rm -rf bracket, since the local bracket server code will no longer be used for anything.

Then git add ., git commit -m 'Upgrade to 0.3.x', and push that change wherever it needs to go!

If you run through this process and run into hiccups or have questions, don't hesitate reaching out to g/bracket-users-external or to nmosher@google.com directly!

Specifying a Bracket Server version

Under the hood Bracket is technically two projects: Bracket and Bracket Server. Developers generally don't have to think about this as they just install Bracket and it installs the corresponding version of Bracket Server automatically. But in some cases you may want to use a specific version of Bracket Server, for instance to run a version that has a bug fix or other patch. In package.json there is a section with the key "bracket". There you can specify a version of Bracket Server using the following syntax. The value for serverVersion is commit-ish meaning it can be a branch, hash, or tag.

"bracket": {
  "appId": "your-gae-app-id",
  "serverVersion": "git+https://webmaster.googlesource.com/bracket-server@unique-server-version"
}

Tour teh Codes

File/Directory Description
Your code
src/* Directory that contains all the source code for you project. Static files, Jinja templates, Python handlers, and your site meta configuration all go here. Jinja template look ups all happen from this directory
src/static/* Static files to be served in prod, like minified js, css, and images. By default this directory is served at /static/ but this can be changed in the env.default.yaml
src/routes.py Entry point to the Flask app. All routes/controllers should be defined in this file, but you may refactor your handlers to a directory structure that makes sense for your project as it grows.
src/meta.py Must define a function meta dictionary containing a pages dict of all your pages for each locale. The value(s) assigned to each key in meta['pages'] will be set to the g.meta global template variable.
package.json Contains meta data for the project, and defines npm tasks for convenience.
app.yaml Contains the app engine configuration for the project. Typically you will not need to modify this file.
.bracket_cache Git ignored disk location used as a cache for KCS content. Removing this file will empty cached responses from KCS so new content may be fetched.
dist/* Git ignored directory where the build task writes static output. This directory is "owned" by the build script, so don't expect changes there to last.

Feedback & Support

Bracket is supported by the Corp Eng Kintaro Team.

Issues and questions should be directed towards either the Bracket Users group for official support from Kintaro, or the Bracket Hangouts Chat Room for help from Bracket users. If you or people on your team are using Bracket, it's probably a good idea to subscribe and join these groups.

Major announcements are posted to the Google Group, and the Chat Room functions as a place for general ramblings about problems, fixes, ideas, and features.

For Bracket issues/bugs, please report them to the Kintaro Buganizer Component. For support from the Kintaro on-call engineer, post your questions to the Bracket Users Group.

Made w/ 😈 by Nathan Mosher