{"_id":"@c6fc/spellcraft-aws-terraform","_rev":"3-a4d503e8eaf468530e9f16dc4d07e34b","name":"@c6fc/spellcraft-aws-terraform","dist-tags":{"latest":"2.0.0"},"versions":{"1.0.0":{"name":"@c6fc/spellcraft-aws-terraform","version":"1.0.0","author":{"url":"brad@bradwoodward.io","name":"Brad Woodward"},"license":"MIT","_id":"@c6fc/spellcraft-aws-terraform@1.0.0","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"homepage":"https://github.com/c6fc/spellcraft-aws-terraform#readme","bugs":{"url":"https://github.com/c6fc/spellcraft-aws-terraform/issues"},"dist":{"shasum":"a59c20ddc4c85740a0d2ded3ca420e3c120d5809","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-aws-terraform/-/spellcraft-aws-terraform-1.0.0.tgz","fileCount":9,"integrity":"sha512-c1tiwjhLOohcK5obdUdrL6RD+B3Z/nb5Ndl3Aho0k1YCD/A0ZXYvKx6RTthn/0eny1hpWB1LWUlmNgl5Z7GxKg==","signatures":[{"sig":"MEUCIQCs/JrE5RAZxj8SFb2Ryv5W0IRXo+brCAUzdX+BWw7YwQIgIpaagADlFDttViUhIm3HDrykyyXlmUfLPVhLeWlyjdw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20386},"main":"module.js","config":{"spellcraft_module_default_name":"awsterraform"},"gitHead":"71441dc245596c7832093839490a953008e03627","scripts":{"cli":"utils/cli-test.js","doc":"jsdoc -c utils/jsdoc.json --verbose","test":"node utils/test.js"},"_npmUser":{"name":"c6fc","actor":{"name":"c6fc","type":"user","email":"brad@bradwoodward.io"},"email":"brad@bradwoodward.io"},"repository":{"url":"git+https://github.com/c6fc/spellcraft-aws-terraform.git","type":"git"},"_npmVersion":"10.8.2","description":"A plugin to empower @c6fc/spellcraft with AWS","directories":{},"_nodeVersion":"20.19.0","dependencies":{"ini":"^5.0.0","aws-sdk":"^2.1692.0","@c6fc/spellcraft":"^0.0.5","@c6fc/spellcraft-aws-auth":"^1.0.7","@c6fc/spellcraft-terraform":"^1.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jsdoc":"^4.0.4","yargs":"^18.0.0","clean-jsdoc-theme":"^4.3.0"},"_npmOperationalInternal":{"tmp":"tmp/spellcraft-aws-terraform_1.0.0_1751155530549_0.5328291274525401","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@c6fc/spellcraft-aws-terraform","version":"1.1.1","keywords":["spellcraft","aws","terraform"],"author":{"url":"brad@bradwoodward.io","name":"Brad Woodward"},"license":"MIT","_id":"@c6fc/spellcraft-aws-terraform@1.1.1","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"homepage":"https://github.com/c6fc/spellcraft-aws-terraform#readme","bugs":{"url":"https://github.com/c6fc/spellcraft-aws-terraform/issues"},"dist":{"shasum":"df562149350e5fdf01f71de78130c1faeb3b89f0","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-aws-terraform/-/spellcraft-aws-terraform-1.1.1.tgz","fileCount":8,"integrity":"sha512-l2WCWH9WddqxVZYONsFI6NaRpQjGljzgaHrCy6degN3nDDRb94IwXFlx1ZmQ6erp7yC/tb4HxRiU46pJvKt8Bg==","signatures":[{"sig":"MEQCIFYLOFnTo21qwBv3kCHX4lkCMDT6g3lv6ee1glsGO0kSAiBOwsPJMs3HyAyCEf6I1BUVb+XtMW8Emz29ps1vkEg2qg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20874},"main":"module.js","gitHead":"71441dc245596c7832093839490a953008e03627","scripts":{"cli":"utils/cli-test.js","test":"node utils/test.js"},"_npmUser":{"name":"c6fc","email":"brad@bradwoodward.io"},"repository":{"url":"git+https://github.com/c6fc/spellcraft-aws-terraform.git","type":"git"},"spellcraft":true,"_npmVersion":"10.8.2","description":"A plugin to empower @c6fc/spellcraft with AWS and Terraform","directories":{},"_nodeVersion":"20.19.0","dependencies":{"@c6fc/spellcraft-aws-auth":"^1.1.2","@c6fc/spellcraft-terraform":"^1.1.3"},"_hasShrinkwrap":false,"devDependencies":{"yargs":"^18.0.0","@c6fc/spellcraft":"~0.1.0"},"peerDependencies":{"@c6fc/spellcraft":"~0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/spellcraft-aws-terraform_1.1.1_1766189126151_0.893897239003379","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"_id":"@c6fc/spellcraft-aws-terraform@2.0.0","bugs":{"url":"https://github.com/c6fc/spellcraft-aws-terraform/issues"},"dist":{"shasum":"066036ba7a60aa721815999b9a9ced13bd766632","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-aws-terraform/-/spellcraft-aws-terraform-2.0.0.tgz","fileCount":6,"integrity":"sha512-AX5koxWanqsI3YHbARG060zz0AU3lwO7MsJAq0owPw+gDL/PJdqoHnvvaFRsFqvmH4HQkFmst/2dIAEfVXqIgQ==","signatures":[{"sig":"MEUCIA9Rv/5gJjWgiCCebEHTJji0UI7GznPoi2DQSAmw/LVIAiEA6Rr3bSqWVrzQBduY7UH1VmLDEx+ysiI99PNI0YOkDh0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCl3S30ZfNV+iJfEz64kM/m92muTgNJvapWFHe0PVhV2wIgIpTqEh76Qu9aAeVaBFVZxX/QgFjkNQ3sYroKp+g6t5k="}],"unpackedSize":33335},"main":"module.js","name":"@c6fc/spellcraft-aws-terraform","author":{"url":"brad@bradwoodward.io","name":"Brad Woodward"},"engines":{"node":">=18"},"gitHead":"233d015a7ab2da33457126ed6621dd959c16eb33","license":"MIT","scripts":{"cli":"utils/cli-test.js","doc":"spellcraft doc","test":"node utils/test.js"},"version":"2.0.0","_npmUser":{"name":"c6fc","email":"brad@bradwoodward.io"},"homepage":"https://github.com/c6fc/spellcraft-aws-terraform#readme","keywords":["spellcraft","aws","terraform"],"repository":{"url":"git+https://github.com/c6fc/spellcraft-aws-terraform.git","type":"git"},"spellcraft":true,"_npmVersion":"10.8.2","description":"S3 state backend, remote state lookups and artifact storage, including bootstrapping the bucket that holds them.","directories":{},"maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"_nodeVersion":"20.19.0","dependencies":{"@c6fc/spellcraft-aws-auth":"^2.0.0","@c6fc/spellcraft-terraform":"^2.0.0"},"_hasShrinkwrap":false,"devDependencies":{"yargs":"^18.0.0","@c6fc/spellcraft":"^1.0.0"},"peerDependencies":{"@c6fc/spellcraft":"^1.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/spellcraft-aws-terraform_2.0.0_1788729028878_0.7677242874459993"}}},"time":{"created":"2025-06-29T00:05:30.475Z","modified":"2026-09-06T21:10:29.131Z","1.0.0":"2025-06-29T00:05:30.724Z","1.1.1":"2025-12-20T00:05:26.327Z","2.0.0":"2026-09-06T21:10:28.975Z"},"bugs":{"url":"https://github.com/c6fc/spellcraft-aws-terraform/issues"},"author":{"url":"brad@bradwoodward.io","name":"Brad Woodward"},"license":"MIT","homepage":"https://github.com/c6fc/spellcraft-aws-terraform#readme","keywords":["spellcraft","aws","terraform"],"repository":{"url":"git+https://github.com/c6fc/spellcraft-aws-terraform.git","type":"git"},"description":"S3 state backend, remote state lookups and artifact storage, including bootstrapping the bucket that holds them.","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"readme":"# @c6fc/spellcraft-aws-terraform\n\nS3 state backend, remote state, artifacts and provider aliases for\n[SpellCraft](https://github.com/c6fc/spellcraft).\n\n[![NPM version](https://img.shields.io/npm/v/@c6fc/spellcraft-aws-terraform.svg?style=flat)](https://www.npmjs.com/package/@c6fc/spellcraft-aws-terraform)\n[![License](https://img.shields.io/npm/l/@c6fc/spellcraft-aws-terraform.svg?style=flat)](https://opensource.org/licenses/MIT)\n\nThis is the AWS half of the Terraform story: it decides where state lives, hands\none spell the values another produced, and declares the providers that\nregion-aware plugins bind to. `@c6fc/spellcraft-terraform` runs the apply;\nthis tells it what to apply against.\n\n```bash\nnpm install --save @c6fc/spellcraft-aws-terraform @c6fc/spellcraft-terraform\n```\n\n## A complete spell\n\n```jsonnet\nlocal aws = import \"@c6fc/spellcraft-aws-terraform/module.libsonnet\";\nlocal s3 = import \"@c6fc/spellcraft-aws-s3/module.libsonnet\";\n\n{\n\t// State backend, and the bucket to hold it. Created on first use.\n\t\"backend.tf.json\": aws.bootstrap(\"my-project\"),\n\n\t// One aliased provider per region, plus an unaliased default.\n\t\"providers.tf.json\": { provider: aws.providerAliases(\"us-east-1\") },\n\n\t\"buckets.tf.json\": s3.bucket(\"artifacts\", \"us-west-2\"),\n}\n```\n\n```bash\nnpx spellcraft terraform-apply manifest.jsonnet\n```\n\nThree things happen before Terraform sees anything: credentials resolve, the\nbackend bucket is created if it is missing, and the region list is fetched to\nbuild the providers. The rendered `.tf.json` already contains the answers.\n\n## The bootstrap bucket\n\n`bootstrap(project)` returns the Terraform `backend` block and makes sure the\nbucket behind it exists. There is **one bucket per account**, discovered by\nnaming convention — `spellcraft-<random>-<digits>` — and shared by every spell,\nwhich is why `project` is a required argument: it becomes the key prefix that\nseparates one spell's state from another's.\n\nFinding more than one candidate bucket is an error rather than a guess.\n\n`getArtifact()` and `putArtifact()` (below) both key their object off the\nproject name `bootstrap()` records, so either one throws if it runs before\nsome `bootstrap()` call has set it. Jsonnet doesn't otherwise guarantee that\norder — see the warning under \"Sharing values between spells\" for how to make\nit explicit.\n\n### Skipping bootstrap() entirely\n\nNot every spell needs its project name computed at render time. If it's\nknown ahead of time, set it in `package.json` instead:\n\n```json\n{\n\t\"config\": {\n\t\t\"spellcraftProject\": \"my-project\"\n\t}\n}\n```\n\nThis bootstraps during `init()` — before any Jsonnet evaluation starts — so\nthere's no ordering hazard to navigate at all: no threading a return value\nthrough, no risk of `getArtifact()`/`putArtifact()` running first. It also\nsidesteps a subtler hazard entirely: `bootstrap()`'s state lives in a\nmodule-level object shared by every `SpellFrame` in the process, so two\nrenders for two different projects running concurrently (embedding\n`SpellFrame` as a library, rather than one process per `spellcraft` CLI\ninvocation) could otherwise cross-contaminate. A config-driven project name\nis the same for every render in that process, so there's nothing left to\nrace on.\n\n`config.spellcraftProject` and an explicit `bootstrap()` call are mutually\nexclusive — set the former and the latter throws, rather than risking the\ntwo silently disagreeing about which project is live.\n\n## Sharing values between spells\n\nTwo ways, both resolved while the manifest evaluates rather than at apply time.\n\n**Remote state** reads another spell's outputs:\n\n```jsonnet\nlocal network = aws.getRemoteState(\"network\");\n\n{\n\t\"app.tf.json\": {\n\t\tresource: {\n\t\t\taws_instance: {\n\t\t\t\tapp: { subnet_id: network.outputs.subnet_id.value },\n\t\t\t},\n\t\t},\n\t},\n}\n```\n\n**Artifacts** are arbitrary JSON values written under a project's prefix,\nfor things that aren't Terraform outputs at all. Unlike `getRemoteState()`,\nthey use *this* spell's own project — the one passed to `bootstrap()` — so\n`bootstrap()` has to run first:\n\n```jsonnet\nlocal backend = aws.bootstrap(\"my-project\");\n\n{\n\t\"backend.tf.json\": backend,\n\t\"meta.json\": { ok: if backend != null then aws.putArtifact(\"build\", { image: \"app:1.4.2\" }) else null },\n}\n```\n\n```jsonnet\nlocal build = aws.getArtifact(\"build\");\n```\n\nJsonnet evaluates lazily and in no guaranteed field order, so merely calling\n`bootstrap()` somewhere in the manifest doesn't make it run before\n`putArtifact()`/`getArtifact()` elsewhere in the same manifest — the call that\nneeds it has to *depend on* the result, as `if backend != null then ...`\ndoes above, not merely follow it. Get this wrong and `putArtifact()` /\n`getArtifact()` throw naming the fix, rather than silently writing to\n`spellcraft/false/artifacts/<name>`.\n\nBecause both land during evaluation, the value can *shape* the configuration —\nchoosing how many resources to emit, or which branch to take — not merely appear\ninside it. A Terraform data source can only do the latter.\n\n## Provider aliases\n\n`providerAliases(default)` emits an aliased `aws` provider for every region the\naccount has enabled, with the alias set to the region name, plus an unaliased\ndefault for the region you name. Plugins then take a region as an argument and\nbind to `aws.<region>` without any per-spell wiring.\n\nIt is also the reason a spell only declares providers once, no matter how many\nregion-aware plugins it uses.\n\n## The auth passthrough\n\n`aws.auth` re-exports [`@c6fc/spellcraft-aws-auth`](https://www.npmjs.com/package/@c6fc/spellcraft-aws-auth),\nso a spell that already imports this module can reach the credential helpers\nwithout a second import:\n\n```jsonnet\n{ \"identity.json\": aws.auth.getCallerIdentity() }\n```\n\n<!-- SPELLCRAFT_DOCS_API_START -->\n## API Reference\n\n### `bootstrap(project)`\n\nPrepares the S3 backend for a project, creating the bootstrap bucket if it\ndoes not exist yet, and returns the Terraform `backend` block for it.\n\nThis is the one function here that writes: it creates the bucket on first\nuse. State and artifacts for every project live in that one bucket, keyed\nby project name.\n\n`getArtifact()` and `putArtifact()` key their object off the project name\nthis sets, so either one throws if it runs before this has. Jsonnet does\nnot guarantee that order on its own -- thread this function's result into\nwhatever calls them, the way `enableServices()` is threaded elsewhere in\nthis ecosystem, rather than merely calling both in the same manifest.\n\nA spell that only ever bootstraps one project, known ahead of time, can\nskip calling this from Jsonnet at all: set `config.spellcraftProject` in\n`package.json` and it runs during `init()`, before evaluation starts, so\nthere's no ordering hazard to think about. The two are mutually\nexclusive -- calling this explicitly throws if `config.spellcraftProject`\nalready bootstrapped the spell, rather than letting the two silently\ndisagree about which project is live.\n\nA spell has one project. Calling this again with a *different* name in\nthe same process throws for the same reason -- to read another spell's\nstate, use `getRemoteState()`, not a second `bootstrap()` call. The\nsame name twice is a no-op.\n\n- param {string} project - names the state prefix; use one per spell\n- returns {object} a Terraform block ready to merge into a `.tf.json` file\n\n**Examples:**\n\n```jsonnet\nlocal aws = import \"@c6fc/spellcraft-aws-terraform/module.libsonnet\";\n\n{ \"backend.tf.json\": aws.bootstrap(\"my-project\") }\n\n// Returns:\n// {\n//   \"terraform\": {\n//     \"backend\": {\n//       \"s3\": {\n//         \"bucket\": \"spellcraft-random-0123456789\",\n//         \"key\": \"spellcraft/my-project/terraform.tfstate\",\n//         \"region\": \"us-east-1\"\n//       }\n//     }\n//   }\n// }\n```\n\n---\n### `getArtifact(name)`\n\nReads an artifact previously stored by `putArtifact()`.\n\nArtifacts are how one spell hands a value to another without a Terraform\ndata source — the value is fetched while the manifest evaluates, so it can\nshape the configuration rather than only appear in it.\n\nThrows if `bootstrap()` hasn't set a project name yet -- see `bootstrap()`\nfor why that ordering isn't automatic.\n\n- param {string} name - the artifact name given to `putArtifact()`\n- returns {*} the stored value, parsed back from JSON\n\n**Examples:**\n\n```jsonnet\nlocal aws = import \"@c6fc/spellcraft-aws-terraform/module.libsonnet\";\n\nlocal backend = aws.bootstrap(\"my-project\");\nlocal shared = if backend != null then aws.getArtifact(\"network\") else null;\n\n{ \"app.tf.json\": { resource: { aws_instance: { app: { subnet_id: shared.subnetId } } } } }\n```\n\n---\n### `getBootstrapBucket()`\n\nThe name of the bootstrap bucket, or `false` when none exists yet.\n\nDiscovery is by naming convention rather than by tag, and more than one\nmatch in the account is an error — there is meant to be exactly one.\n\n- returns {string|boolean} the bucket name, or false\n\n**Examples:**\n\n```jsonnet\nlocal aws = import \"@c6fc/spellcraft-aws-terraform/module.libsonnet\";\n\n{ \"state.json\": { bucket: aws.getBootstrapBucket() } }\n```\n\n---\n### `getRemoteState(project)`\n\nReads the Terraform state of another SpellCraft project in the same account.\n\nUse it to consume another spell's outputs at evaluation time. The project\nname is the one passed to that spell's `bootstrap()`.\n\n- param {string} project - the other spell's project name\n- returns {object} that project's Terraform state\n\n**Examples:**\n\n```jsonnet\nlocal aws = import \"@c6fc/spellcraft-aws-terraform/module.libsonnet\";\n\nlocal network = aws.getRemoteState(\"network\");\n\n{ \"app.tf.json\": { output: { vpc: { value: network.outputs.vpc_id.value } } } }\n```\n\n---\n### `putArtifact(name, content)`\n\nStores a value as a JSON artifact in the bootstrap bucket, under this\nproject's prefix. Read it back with `getArtifact()`.\n\nThrows if `bootstrap()` hasn't set a project name yet -- see `bootstrap()`\nfor why that ordering isn't automatic.\n\n- param {string} name - the artifact name\n- param {*} content - any JSON-serialisable value\n- returns {boolean} true\n\n**Examples:**\n\n```jsonnet\nlocal aws = import \"@c6fc/spellcraft-aws-terraform/module.libsonnet\";\n\nlocal backend = aws.bootstrap(\"my-project\");\n\n{\n    \"backend.tf.json\": backend,\n    \"meta.json\": { stored: if backend != null then aws.putArtifact(\"network\", { subnetId: \"subnet-abc123\" }) else null },\n}\n```\n\n---\n### `providerAliases(default)`\n\nBuilds the full set of AWS provider declarations for a spell.\n\nReturns one aliased provider per region your credentials can see — the\nalias is the region name, so resources bind to it as `aws.us-west-2` — plus\nan unaliased default provider for the region you name. This is what lets\nplugins like `@c6fc/spellcraft-aws-s3` take a region as an argument and\nplace resources in it without every spell wiring providers by hand.\n\nThe region list comes from a live `describeRegions` call, so the set\nreflects what the account actually has enabled.\n\n- param {string} default - region for the unaliased default provider\n- returns {object[]} provider declarations, for the `provider` key of a `.tf.json`\n\n**Examples:**\n\n```jsonnet\nlocal aws = import \"@c6fc/spellcraft-aws-terraform/module.libsonnet\";\n\n{ \"providers.tf.json\": { provider: aws.providerAliases(\"us-east-2\") } }\n\n// Returns:\n// [\n//   { \"aws\": { \"alias\": \"us-east-1\", \"region\": \"us-east-1\" } },\n//   { \"aws\": { \"alias\": \"us-west-2\", \"region\": \"us-west-2\" } },\n//   ...\n//   { \"aws\": { \"region\": \"us-east-2\" } }\n// ]\n```\n\n---\n\n<!-- SPELLCRAFT_DOCS_API_END -->\n\n## Development\n\n```bash\nnpm test        # renders test.jsonnet through a real SpellFrame\nnpm run doc     # regenerates the API section above from module.libsonnet\n```\n\n`npm test` **writes**: it creates the bootstrap bucket if your account has none,\nand stores an artifact in it.\n\n## License\n\nMIT © [Brad Woodward](https://github.com/c6fc)\n","readmeFilename":"README.md"}