{"_id":"@c6fc/spellcraft-gcp-terraform","_rev":"5-c70c40b39e5122a61f46dd6ba9d2bb0d","name":"@c6fc/spellcraft-gcp-terraform","dist-tags":{"latest":"2.0.0"},"versions":{"1.0.1":{"name":"@c6fc/spellcraft-gcp-terraform","version":"1.0.1","keywords":["spellcraft","aws","terraform"],"author":{"url":"brad@bradwoodward.io","name":"Brad Woodward"},"license":"MIT","_id":"@c6fc/spellcraft-gcp-terraform@1.0.1","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"homepage":"https://github.com/c6fc/spellcraft-gcp-terraform#readme","bugs":{"url":"https://github.com/c6fc/spellcraft-gcp-terraform/issues"},"dist":{"shasum":"65c24e895096c9d27a426763c749852709b9f713","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-gcp-terraform/-/spellcraft-gcp-terraform-1.0.1.tgz","fileCount":8,"integrity":"sha512-mPQmYB4H+Kt1iyZd+up7wXo90Tug6fp9LbXJpwDJKVcntOsEqVaJhQWRHjAFegb+p+JumUWT5Jb3/jND4jXgnA==","signatures":[{"sig":"MEQCIA3Y+idl9RfSdlMlUYc//GjVfnMiFV0l6Rl6wXvbBrRsAiAXvHClexeYo3cHO5RrQ2tRtoMShaHh0VKfFNrhpDk3Ug==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20477},"main":"module.js","gitHead":"75158c292e926162e5fdb3f8a6bb11d1e8dc6ec7","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-gcp-terraform.git","type":"git"},"spellcraft":true,"_npmVersion":"10.8.2","description":"A plugin to empower @c6fc/spellcraft with GCP and Terraform","directories":{},"_nodeVersion":"20.19.0","dependencies":{"@c6fc/spellcraft-gcp-auth":"^1.0.1","@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-gcp-terraform_1.0.1_1768177680805_0.22686667106408742","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@c6fc/spellcraft-gcp-terraform","version":"1.0.2","keywords":["spellcraft","gcp","terraform"],"author":{"url":"brad@bradwoodward.io","name":"Brad Woodward"},"license":"MIT","_id":"@c6fc/spellcraft-gcp-terraform@1.0.2","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"homepage":"https://github.com/c6fc/spellcraft-gcp-terraform#readme","bugs":{"url":"https://github.com/c6fc/spellcraft-gcp-terraform/issues"},"dist":{"shasum":"82ce199195bec10cbbb847a7f01a55cf3ccd2297","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-gcp-terraform/-/spellcraft-gcp-terraform-1.0.2.tgz","fileCount":8,"integrity":"sha512-pOpb/MhcQZ8pZYmrNzUjleMTsT0gogpUbEEwzLOG6yuDxU4Ephsp4pVbuhx/VztHJ9HrCcFpXO4TrQ8d7XaESQ==","signatures":[{"sig":"MEQCIHuoSnMCfLB2Xb+ozulNEYbWn1BRQodkg3AxkX2A9n7FAiAYNbrQnz0IGmnEtuZ3VuJGJb/fJ2NJymffd9rXg67bUQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":25500},"main":"module.js","gitHead":"fa10a00a95883acb17f10a93ac2b29e186a88edc","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-gcp-terraform.git","type":"git"},"spellcraft":true,"_npmVersion":"10.8.2","description":"A plugin to empower @c6fc/spellcraft with GCP and Terraform","directories":{},"_nodeVersion":"20.19.0","dependencies":{"@c6fc/spellcraft-gcp-auth":"^1.0.1","@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-gcp-terraform_1.0.2_1770740840936_0.7838215883434085","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@c6fc/spellcraft-gcp-terraform","version":"1.0.3","keywords":["spellcraft","gcp","terraform"],"author":{"url":"brad@bradwoodward.io","name":"Brad Woodward"},"license":"MIT","_id":"@c6fc/spellcraft-gcp-terraform@1.0.3","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"homepage":"https://github.com/c6fc/spellcraft-gcp-terraform#readme","bugs":{"url":"https://github.com/c6fc/spellcraft-gcp-terraform/issues"},"dist":{"shasum":"1f80277f0a1e3a08a3fae378b47ecab3c6a46ce7","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-gcp-terraform/-/spellcraft-gcp-terraform-1.0.3.tgz","fileCount":8,"integrity":"sha512-H7aaY+nVFFSoL4EPSGv+Ldbai+8ws6GFHjPDkB2/VBkRxpD0mFsgguRFkyUdvWbSwTzLCaiz44mrbbVwfHkJJw==","signatures":[{"sig":"MEUCIHVecbqm4DzycYJWp0x1dJXs1mD/DIpHMUXk+oawSjpzAiEAwv2oaCv8bRBNiIZl0zOb8mBa4b7Ajsa207mlFTFHBmw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31921},"main":"module.js","gitHead":"05eb76284090514b57577ac45320802b639b1d7e","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-gcp-terraform.git","type":"git"},"spellcraft":true,"_npmVersion":"10.8.2","description":"A plugin to empower @c6fc/spellcraft with GCP and Terraform","directories":{},"_nodeVersion":"20.19.0","dependencies":{"@inquirer/search":"^4.1.6","@inquirer/prompts":"^8.3.2","@c6fc/spellcraft-gcp-auth":"^1.0.1","@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-gcp-terraform_1.0.3_1775057360824_0.11559391458837176","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@c6fc/spellcraft-gcp-terraform","version":"1.1.0","keywords":["spellcraft","gcp","terraform"],"author":{"url":"brad@bradwoodward.io","name":"Brad Woodward"},"license":"MIT","_id":"@c6fc/spellcraft-gcp-terraform@1.1.0","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"homepage":"https://github.com/c6fc/spellcraft-gcp-terraform#readme","bugs":{"url":"https://github.com/c6fc/spellcraft-gcp-terraform/issues"},"dist":{"shasum":"894c44c64fdafad1194d57812e27081edd9ea068","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-gcp-terraform/-/spellcraft-gcp-terraform-1.1.0.tgz","fileCount":8,"integrity":"sha512-DCHHoN0Xgb/fIsyGyzGUXCkCGIZCj19KUlFn4o4ZSsZ0Cmj+mYNwMjsRNugy50G8veena9Jq3IOWdB+E2B/0aw==","signatures":[{"sig":"MEQCIDo+7sfwmUU+eIuYYPwDX0jzUvkjgmnjAKvguRke91wYAiBW1Xi/pncgoAZTjQeOz0df63SAyOwNpataaK2ugSFwNg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":36852},"main":"module.js","gitHead":"7d76cc3ef80ee2853a0d81c76eca2c0b9ac9e508","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-gcp-terraform.git","type":"git"},"spellcraft":true,"_npmVersion":"10.8.2","description":"A plugin to empower @c6fc/spellcraft with GCP and Terraform","directories":{},"_nodeVersion":"20.19.0","dependencies":{"@inquirer/search":"^4.1.6","@inquirer/prompts":"^8.3.2","@c6fc/spellcraft-gcp-auth":"~1.1.0","@c6fc/spellcraft-terraform":"~1.2.0"},"_hasShrinkwrap":false,"devDependencies":{"yargs":"^18.0.0","@c6fc/spellcraft":"~0.2.0"},"peerDependencies":{"@c6fc/spellcraft":"~0.2.0"},"_npmOperationalInternal":{"tmp":"tmp/spellcraft-gcp-terraform_1.1.0_1780343860908_0.8273936139029887","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@c6fc/spellcraft-gcp-terraform","version":"2.0.0","main":"module.js","scripts":{"cli":"utils/cli-test.js","test":"node utils/test.js","doc":"spellcraft doc"},"repository":{"type":"git","url":"git+https://github.com/c6fc/spellcraft-gcp-terraform.git"},"spellcraft":true,"keywords":["spellcraft","gcp","terraform"],"author":{"name":"Brad Woodward","url":"brad@bradwoodward.io"},"license":"MIT","bugs":{"url":"https://github.com/c6fc/spellcraft-gcp-terraform/issues"},"homepage":"https://github.com/c6fc/spellcraft-gcp-terraform#readme","description":"GCS state backend, remote state and artifacts, plus googleOrgProject for whole org and project trees.","dependencies":{"@c6fc/spellcraft-gcp-auth":"^2.0.0","@c6fc/spellcraft-terraform":"^2.0.0","@inquirer/prompts":"^8.3.2","@inquirer/search":"^4.1.6"},"devDependencies":{"@c6fc/spellcraft":"^1.0.0","yargs":"^18.0.0"},"peerDependencies":{"@c6fc/spellcraft":"^1.0.0"},"engines":{"node":">=18"},"_id":"@c6fc/spellcraft-gcp-terraform@2.0.0","gitHead":"3127c299292412158486b4082f2eb11ab331d668","_nodeVersion":"20.19.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-E17kc/yFeabdHTlOHEsDYy7rd4RyaVB+gqzNosvq5RRKcZeRQxxB9MNHoBtKZxRuViJOGHMoDEASM2jS/rlhyg==","shasum":"51dca939b9f62a2b10b7d97aeb4140c40c6f876f","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-gcp-terraform/-/spellcraft-gcp-terraform-2.0.0.tgz","fileCount":6,"unpackedSize":46185,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCSm0rF60HqzMplNz4xIXCI2dFsvlq2hvWxs49vXao5SAIhAPdhPpyYtEqMt+ir+U58U86gnW+og489GgyqMbcc6dwr"}]},"_npmUser":{"name":"c6fc","email":"brad@bradwoodward.io"},"directories":{},"maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/spellcraft-gcp-terraform_2.0.0_1788729026124_0.7633597593291628"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-12T00:28:00.701Z","modified":"2026-09-06T21:10:26.433Z","1.0.1":"2026-01-12T00:28:00.936Z","1.0.2":"2026-02-10T16:27:21.124Z","1.0.3":"2026-04-01T15:29:20.974Z","1.1.0":"2026-06-01T19:57:41.056Z","2.0.0":"2026-09-06T21:10:26.275Z"},"bugs":{"url":"https://github.com/c6fc/spellcraft-gcp-terraform/issues"},"author":{"name":"Brad Woodward","url":"brad@bradwoodward.io"},"license":"MIT","homepage":"https://github.com/c6fc/spellcraft-gcp-terraform#readme","keywords":["spellcraft","gcp","terraform"],"repository":{"type":"git","url":"git+https://github.com/c6fc/spellcraft-gcp-terraform.git"},"description":"GCS state backend, remote state and artifacts, plus googleOrgProject for whole org and project trees.","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"readme":"# @c6fc/spellcraft-gcp-terraform\n\nGCS state backend, remote state, artifacts, provider aliases and whole\norganization trees for [SpellCraft](https://github.com/c6fc/spellcraft).\n\n[![NPM version](https://img.shields.io/npm/v/@c6fc/spellcraft-gcp-terraform.svg?style=flat)](https://www.npmjs.com/package/@c6fc/spellcraft-gcp-terraform)\n[![License](https://img.shields.io/npm/l/@c6fc/spellcraft-gcp-terraform.svg?style=flat)](https://opensource.org/licenses/MIT)\n\nThis is the GCP half of the Terraform story: it decides where state lives, hands\none spell the values another produced, declares the providers that region-aware\nplugins bind to, and builds folder and project hierarchies from a single nested\ndescription. `@c6fc/spellcraft-terraform` runs the apply; this tells it what to\napply against.\n\n```bash\nnpm install --save @c6fc/spellcraft-gcp-terraform @c6fc/spellcraft-terraform\n```\n\n## A complete spell\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-terraform/module.libsonnet\";\n\n{\n\t// State backend, and the bucket to hold it. Created on first use.\n\t\"backend.tf.json\": gcp.bootstrap(\"my-project\"),\n\n\t// One aliased provider per region, plus an unaliased default.\n\t\"providers.tf.json\": {\n\t\tprovider: gcp.providerAliases(\"us-west2\", {}, \"us-\"),\n\t},\n}\n```\n\n```bash\nnpx spellcraft terraform-apply manifest.jsonnet\n```\n\n## Solving stage zero\n\nTerraform cannot enable the API that a resource it is creating depends on — the\nprovider needs it live before it can plan. That is normally a README step\nsomebody forgets on the second environment.\n\nThis plugin removes the step. As the manifest evaluates, every service the\nconfiguration will need is registered; then it listens for\n`@c6fc/spellcraft-terraform:pre-apply` and enables the whole set in one call,\nbefore Terraform starts. The two plugins know nothing about each other — they\nmeet on the event.\n\nThat handshake is why `googleOrgProject()` can create projects and populate them\nin a single apply.\n\n## Organization and project trees\n\n`googleOrgProject(name, region, map)` takes one nested description and emits the\nfolders, projects, service accounts, IAM bindings and API activations to build\nit, with the dependency ordering already wired:\n\n```jsonnet\n{\n\t\"org.tf.json\": gcp.googleOrgProject(\"platform\", \"us-west2\", {\n\t\ttype: \"folder\",\n\t\tname: \"engineering\",\n\t\tchildren: [\n\t\t\t{ type: \"folder\", name: \"production\", children: [\n\t\t\t\t{ type: \"project\", name: \"api\" },\n\t\t\t] },\n\t\t\t{ type: \"project\", name: \"sandbox\" },\n\t\t],\n\t}),\n}\n```\n\nThe root's parent defaults to the organization your current project belongs to;\nset `parent` on the map to place it somewhere else. `test.jsonnet` in this\npackage is the fully worked example, covering IAM members and per-project\nservices.\n\n## The bootstrap bucket\n\n`bootstrap(project)` returns the Terraform `backend` block and creates the\nbucket behind it if needed. There is one bucket per project —\n`spellcraft-terraform-<project-id>` — and `project` becomes the key prefix\nseparating one spell's state from another's.\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\nBoth resolve while the manifest evaluates, so a value can *shape* the\nconfiguration rather than only appear in it. `getArtifact()`/`putArtifact()`\nuse *this* spell's own project — the one passed to `bootstrap()` — so\n`bootstrap()` has to run first:\n\n```jsonnet\nlocal network = gcp.getRemoteState(\"network\");\n\nlocal backend = gcp.bootstrap(\"my-project\");\n\n{\n\t\"backend.tf.json\": backend,\n\t\"meta.json\": { ok: if backend != null then gcp.putArtifact(\"build\", { image: \"app:1.4.2\" }) else null },\n}\n```\n\n```jsonnet\nlocal build = gcp.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 ...` does\nabove, not merely follow it. Get this wrong and `putArtifact()`/`getArtifact()`\nthrow naming the fix, rather than silently writing to\n`spellcraft/null/artifacts/<name>.json`.\n\n## Provider aliases\n\n`providerAliases(default, options, filter)` emits an aliased `google` provider\nper Compute region, with the alias set to the region name, plus an unaliased\ndefault. `options` is merged into every declaration — a shared `project` or\n`billing_project` goes there — and `filter` keeps only regions whose name\ncontains it, which is how you avoid declaring forty providers to use two.\n\n## The auth passthrough\n\n`gcp.auth` re-exports [`@c6fc/spellcraft-gcp-auth`](https://www.npmjs.com/package/@c6fc/spellcraft-gcp-auth),\nso a spell that already imports this module can reach the credential helpers\nwithout a second import:\n\n```jsonnet\n{ \"org.json\": { domain: gcp.auth.getProjectMetadata().organizationDomain } }\n```\n\n<!-- SPELLCRAFT_DOCS_API_START -->\n## API Reference\n\n### `bootstrap(project)`\n\nPrepares the GCS backend for a spell, 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 spell live in that bucket, keyed by the\nname you pass.\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, 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 gcp = import \"@c6fc/spellcraft-gcp-terraform/module.libsonnet\";\n\n{ \"backend.tf.json\": gcp.bootstrap(\"my-project\") }\n\n// Returns a terraform.backend.gcs block pointing at the bootstrap bucket,\n// prefixed with the project name you passed.\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 gcp = import \"@c6fc/spellcraft-gcp-terraform/module.libsonnet\";\n\nlocal backend = gcp.bootstrap(\"my-project\");\nlocal shared = if backend != null then gcp.getArtifact(\"network\") else null;\n\n{ \"app.tf.json\": { output: { subnet: { value: shared.subnet } } } }\n```\n\n---\n### `getBootstrapBucket()`\n\nThe name of the bootstrap bucket for the current project.\n\n- returns {string} the bucket name, `spellcraft-terraform-<project-id>`\n\n**Examples:**\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-terraform/module.libsonnet\";\n\n{ \"state.json\": { bucket: gcp.getBootstrapBucket() } }\n```\n\n---\n### `getRemoteState(project)`\n\nReads the Terraform state of another SpellCraft spell in the same GCP\nproject. Use it to consume another spell's outputs at evaluation time; the\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 spell's Terraform state\n\n**Examples:**\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-terraform/module.libsonnet\";\n\nlocal network = gcp.getRemoteState(\"network\");\n\n{ \"app.tf.json\": { output: { subnet: { value: network.outputs.subnet.value } } } }\n```\n\n---\n### `googleOrgProject(name, region, map)`\n\nBuilds a whole folder and project tree from one nested description.\n\nEach node is a `{ type: \"folder\" | \"project\", name, children }`, and may\ncarry `iam_members`, `services` and the other per-node settings the tree\nunderstands. The root's parent defaults to the organization the current\nproject belongs to; set `parent` on the map to place it elsewhere.\n\nAlongside the resources it emits the dependency wiring that makes creation\norder correct, and registers the services each project needs so they are\nenabled before `terraform apply` runs.\n\n`test.jsonnet` in this package is the fully worked example.\n\n- param {string} name - prefix for the generated Terraform resource names\n- param {string} region - region for the providers the tree exposes\n- param {object} map - the root node of the folder/project tree\n- returns {object} Terraform `resource` and `output` blocks\n\n**Examples:**\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-terraform/module.libsonnet\";\n\n{ \"org.tf.json\": gcp.googleOrgProject(\"platform\", \"us-west2\", {\n    type: \"folder\",\n    name: \"engineering\",\n    children: [{ type: \"project\", name: \"sandbox\" }],\n  }) }\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 gcp = import \"@c6fc/spellcraft-gcp-terraform/module.libsonnet\";\n\nlocal backend = gcp.bootstrap(\"my-project\");\n\n{\n    \"backend.tf.json\": backend,\n    \"meta.json\": { stored: if backend != null then gcp.putArtifact(\"myArtifact\", { someData: someValue }) else null },\n}\n```\n\n---\n### `providerAliases(default, options, filter=\"\")`\n\nBuilds the full set of Google provider declarations for a spell.\n\nReturns one aliased provider per Compute region — the alias is the region\nname, so resources bind to it as `google.us-west2` — plus an unaliased\ndefault for the region you name. `options` is merged into every provider,\nwhich is where a shared `project` or `billing_project` belongs. Pass a\n`filter` to keep only regions whose name contains it.\n\nThe region list comes from a live `compute.v1.regions.list` call.\n\n- param {string} default - region for the unaliased default provider\n- param {object} options - merged into every provider declaration\n- param {string} [filter=\"\"] - substring the region name must contain\n- returns {object[]} provider declarations, for the `provider` key of a `.tf.json`\n\n**Examples:**\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-terraform/module.libsonnet\";\n\ngcp.providerAliases(\"us-west2\", {},  \"us-\");\n\n// Returns:\n// [\n//   { \"google\": { \"alias\": \"us-east1\", \"region\": \"us-east1\" } },\n//   { \"google\": { \"alias\": \"us-west2\", \"region\": \"us-west2\" } },\n//   ...\n//   { \"google\": { \"region\": \"us-west2\" } }\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 the project has none,\nstores an artifact, and enables APIs on the active project.\n\n## License\n\nMIT © [Brad Woodward](https://github.com/c6fc)\n","readmeFilename":"README.md"}