{"id":23170050,"url":"https://github.com/ghv/print","last_synced_at":"2025-08-18T07:31:17.807Z","repository":{"id":149587599,"uuid":"396511177","full_name":"ghv/print","owner":"ghv","description":"Deploy static files to AWS S3 and CloudFront","archived":false,"fork":false,"pushed_at":"2023-07-24T00:24:39.000Z","size":46,"stargazers_count":5,"open_issues_count":2,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-05T23:25:43.554Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ghv.png","metadata":{"files":{"readme":"readme.markdown","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-15T22:04:42.000Z","updated_at":"2023-06-21T15:10:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"0dcd8420-5318-4080-b384-a472e0d59c9c","html_url":"https://github.com/ghv/print","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ghv/print","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghv%2Fprint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghv%2Fprint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghv%2Fprint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghv%2Fprint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ghv","download_url":"https://codeload.github.com/ghv/print/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghv%2Fprint/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270961498,"owners_count":24675914,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-18T02:00:08.743Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-18T03:25:36.672Z","updated_at":"2025-08-18T07:31:17.791Z","avatar_url":"https://github.com/ghv.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Print](https://media.giphy.com/media/lXiRLb0xFzmreM8k8/source.gif)\n###### *Image credit: [gfaught](https://giphy.com/gfaught)*\n\n**UPDATE:** I renamed the command line tool from `print` to `printer` to avoid the `zsh` shell built-in command. Everything else is unchanged.\n\nPrint is a tool to publish static files to an AWS S3 bucket and invalidate their cached copies in CloudFront.\n\nThis tool caches the file's timestamp when uploaded and only uploads a new copy if the current timestamp does not match the cached timestamp.\n\nYou must create a private S3 bucket that serves static content through the associated CloudFront distribution.\nThis tool only uploads changed files to the S3 bucket and invalidates the related cache nodes.\n\n## Why?\n\n1. Take advantage of the scale, reliability, and performance of S3 and CloudFront\n2. While these are not free services from AWS, they could be cost-effective compared to maintaining and achieving the above on your own\n3. \"Print\" has a simple approach that is easy to set up and use\n4. It might keep you from getting \"✪ Daring Fireballed\"\n\n# Build\n\nRun `make install` in the project's root folder to compile and install this tool in `/usr/local/bin`\n\n# Setup\n\n## Set up your AWS Credentials in Keychain\n\nA one-time setup is required to save the AWS Access keys in your keychain for each AWS key you use. The default Keychain item is \"AWS\", and you can specify an alternate item for each content configuration file.\nTo save the key to an alternate Keychain item, add the `--keychain-item [your_item_name]` in the `[options]` part of the command and add the name in the `keychainItem` property of your content configuration file.\nRun `printer keychain [options] [AWS Access Key ID]` and then paste in the `AWS Access Secret` when prompted.\n\n### Minimal IAM policy\n\nYou can limit the key's scope to the following policy:\n\n```json\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Sid\": \"PrintToolPolicy\",\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"s3:PutObject\",\n                \"s3:DeleteObject\",\n                \"s3:ListBucket\",\n                \"cloudfront:CreateInvalidation\"\n            ],\n            \"Resource\": [\n                \"arn:aws:cloudfront::__AWS_ACCOUNT__:distribution/__DISTRIBUTION_ID__\",\n                \"arn:aws:s3:::__BUCKET_NAME__\",\n                \"arn:aws:s3:::__BUCKET_NAME__/*\"\n            ]\n        }\n    ]\n}\n```\n\n## Content Configuration\n\nYou need to create a `contents.json` file in the root folder of your project.\n\n### JSON Schema\n\nYou can use the [JSON Schema Validator](https://www.jsonschemavalidator.net) to validate your `contents.json` against this schema:\n\n```json\n{\n    \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n    \"type\": \"object\",\n    \"properties\": {\n        \"keychainItem\": { \"type\": \"string\" },\n        \"region\": { \"type\": \"string\" },\n        \"bucket\": { \"type\": \"string\" },\n        \"cloudFront\": { \"type\": \"string\" },\n        \"originPathFolder\": { \"type\": \"string\" },\n        \"contents\": {\n            \"type\": \"array\",\n            \"items\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"folder\": { \"type\": \"string\" },\n                    \"files\": {\n                        \"type\": \"array\",\n                        \"items\": { \n                            \"anyOf\": [\n                                {\n                                    \"type\": \"string\"\n                                },\n                                {\n                                    \"type\": \"array\",\n                                    \"items\": {\n                                        \"type\": \"string\"\n                                    },\n                                    \"minItems\": 2,\n                                    \"maxItems\": 2\n                                }\n                            ]\n                        }\n                    },\n                    \"compactInvalidation\": { \"type\": \"boolean\" },\n                    \"prune\": { \"type\": \"boolean\" }\n                },\n                \"required\": [\"folder\", \"files\"]\n           }   \n        }\n    },\n    \"required\": [\"region\", \"bucket\", \"cloudFront\", \"originPathFolder\", \"contents\"]\n}\n```\n\nThe root object should contain the following properties:\n\n| Property           | Description                                                                                                                                                                                                                 |\n|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `keychainItem`     | Optional keychain item name that stores the AWS credentials. \"AWS\" is used if omitted.                                                                                                                                      |\n| `region`           | The region your S3 bucket was created in (e.g. `us-east-1`.)                                                                                                                                                                |\n| `bucket`           | The name of the S3 bucket to put your files in.                                                                                                                                                                             |\n| `cloudFront`       | The CloudFront Distribution ID that serves content from the specified bucket.                                                                                                                                               |\n| `originPathFolder` | An optional folder for cases where the same bucket could host QA, PROD, or other site contents. The CloudFront distribution should also reflect this origin path. Leave as `\"\"` if files are served from the entire bucket. |\n| `contents`         | An array containing the folders to create in the bucket and the files that should go into each of those folders.                                                                                                            |\n\nYou can reference an environment variable containing the desired value by specifying the environment variable's name with a `$` prefix. You can use the environment variable indirection for all root properties that accept a string value.\n\nEach element in the `contents` array should contain the following:\n\n| Property              | Description                                                                                                                                                                                                                                                                                                                  |\n|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `folder`              | The path in the S3 bucket that will contain the files specified by the `files` property.                                                                                                                                                                                                                                     |\n| `files`               | An array of local file paths to be uploaded in `folder`. Each element in this array can be either a string containing the local file path or an array of two strings where the first element is the local file path and the second is the remote file name. The local file paths are relative to the `contents.json` folder. |\n| `prune`               | When set to `true`, deletes all files from the bucket in this `folder` that are no longer specified when comparing the two `content.json` files. The default value is `false` if the key is omitted.                                                                                                                         |\n| `compactInvalidation` | When set to `true`, invalidates everything under this folder rather than invalidating individual files when two or more files have changed. The default value is `false` if the key is omitted.                                                                                                                              |\n\n### Deleting Files from the Bucket\n\nIf you want to delete files from the bucket that are no longer part of your distribution, you will need to do two things:\n\n1. Add the `prune` property to the `contents` array element for each folder where you want obsolete files deleted from your bucket.\n2. Since the `contents.json` defines the current set of files to upload you must first copy or rename the current one to `contents.old.json` and when making changes or building a new `contents.json`.\n\nThis works best when you generate the `contents.json` file as part of your build process.\n\nEvery time you run Print (command line tool is called `printer`) to deploy changed files, it will compare the two files and any target files that are not in the new `contents.json` file will be deleted from the bucket. The `contents.old.json` file will be deleted after the upload is complete. The delete operation will only be performed if there exists a `contents.old.json` file, and it has at least one folder with the `prune` property set to `true`.\n\n### Sample `contents.json`\n\nThe following is the `contents.json` file from the `TestSite` unit test resource:\n\n```json\n{\n  \"region\": \"$PRINTREGION\",\n  \"bucket\": \"$PRINTBUCKET\",\n  \"cloudFront\": \"$PRINTCLOUDFRONT\",\n  \"originPathFolder\": \"$PRINTORIGINPATH\",\n  \"contents\": [\n    {\n      \"folder\": \"\",\n      \"files\": [\n        \"index.html\"\n      ]\n    },\n    {\n      \"folder\": \"someFolder\",\n      \"files\": [\n        \"someFolderContents/index.html\",\n        [ \"someFolderContents/index.html\", \"copy.html\"]\n      ]\n    },\n    {\n      \"folder\": \"someEmptyFolder\",\n      \"files\": [\n      ]\n    }\n  ]\n}\n```\n\n# Unit Test Environment Variables\n\nThe `TestSite` content configuration references the following environment variables to make it runnable from your AWS account.\nYou must provide these values to run the `DeployerTests` test case.  \n\n| Variable          | Description                                                                                                                                                       |\n|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `PRINTBUCKET`     | The test bucket name                                                                                                                                              |\n| `PRINTREGION`     | The region you created your S3 bucket in                                                                                                                          |\n| `PRINTCLOUDFRONT` | The CloudFront Distribution ID that serves the bucket contents                                                                                                    |\n| `PRINTORIGINPATH` | An optional folder for cases where the same bucket could host QA, PROD, or other site contents. The CloudFront distribution should also specify this origin path. |\n\n# Built-in Environment Variables\n\n| Variable    | Description                                                                                                                                                                                                         |\n|-------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `PRINTROOT` | Path to the root folder that contains the `contents.json` file. Set this variable if you want to run the tool from any folder. Otherwise, you must run this tool from the folder containing a `contents.json` file. |\n\n# License\n\nPrint is licensed under the Apache 2.0 License. Contributions welcome.\n\nSee [LICENSE.txt](LICENSE.txt) for license information.\n\nSee [CONTRIBUTORS.markdown](CONTRIBUTORS.markdown) for authors of the ghv/print project.\n\nSee [NOTICES.markdown](NOTICES.markdown) for dependency license information.\n\n# Thank You!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fghv%2Fprint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fghv%2Fprint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fghv%2Fprint/lists"}