{"_id":"@c6fc/spellcraft-gcp-auth","_rev":"6-dca1739c4fcffc3f33f2384b3a7e9fe2","name":"@c6fc/spellcraft-gcp-auth","dist-tags":{"latest":"2.0.0"},"versions":{"1.0.0":{"name":"@c6fc/spellcraft-gcp-auth","version":"1.0.0","keywords":["spellcraft"],"author":"","license":"MIT","_id":"@c6fc/spellcraft-gcp-auth@1.0.0","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"homepage":"https://github.com/@c6fc/spellcraft-gcp-auth#readme","bugs":{"url":"https://github.com/@c6fc/spellcraft-gcp-auth/issues"},"dist":{"shasum":"bf25eef1ebb89e04853a1e38cee47562012edc52","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-gcp-auth/-/spellcraft-gcp-auth-1.0.0.tgz","fileCount":7,"integrity":"sha512-IYa3lTDvt5R2FPjXzWjoxMLXh1b/ganqCuJ6IfJZVYe2EfNmjytacnlvoStryMMM8R4IKdSY+1cf3mRo0fsaDg==","signatures":[{"sig":"MEUCIQC21vt4Cpjztlz6SZ872ocgNuXjnSE5kaeAIHvZIImZiwIgT/Kuv38ls2KjOZsGuGIs1Sjwpmv+gVuhj94bFNCVaUk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15351},"main":"module.js","scripts":{"cli":"node 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-auth.git","type":"git"},"spellcraft":true,"_npmVersion":"10.8.2","description":"A plugin to add functionality via @c6fc/spellcraft","directories":{},"_nodeVersion":"20.19.0","dependencies":{"googleapis":"^170.0.0","google-auth-library":"^10.5.0"},"_hasShrinkwrap":false,"devDependencies":{"yargs":"^18.0.0","@c6fc/spellcraft":"~0.1.0"},"peerDependencies":{"@c6fc/spellcraft":"~0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/spellcraft-gcp-auth_1.0.0_1768177672587_0.8042417797969614","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@c6fc/spellcraft-gcp-auth","version":"1.0.1","keywords":["spellcraft"],"author":"","license":"MIT","_id":"@c6fc/spellcraft-gcp-auth@1.0.1","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"homepage":"https://github.com/@c6fc/spellcraft-gcp-auth#readme","bugs":{"url":"https://github.com/@c6fc/spellcraft-gcp-auth/issues"},"dist":{"shasum":"3ebf43f5769e28f75c1b88b813330562723cd4c2","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-gcp-auth/-/spellcraft-gcp-auth-1.0.1.tgz","fileCount":7,"integrity":"sha512-/GB0qL93s6QTeoHAN9U/qIhwOCsYcDvMVR+cgRhOrgv6vlXMur+k5DJ7OctFAbpxjiR7CYM5ClEFkdpY+QnZjw==","signatures":[{"sig":"MEYCIQDn1a/N0AyIu/+JIy5Tg8CaywgCDQuM5i0y2A7WNEuYrAIhAM9sMy5T14x6BSP0eSsZdz1sp+JZoHzsMpleQCh6Hgw9","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15351},"main":"module.js","scripts":{"cli":"node 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-auth.git","type":"git"},"spellcraft":true,"_npmVersion":"10.8.2","description":"A plugin to add functionality via @c6fc/spellcraft","directories":{},"_nodeVersion":"20.19.0","dependencies":{"googleapis":"^170.0.0","google-auth-library":"^10.5.0"},"_hasShrinkwrap":false,"devDependencies":{"yargs":"^18.0.0","@c6fc/spellcraft":"~0.1.0"},"peerDependencies":{"@c6fc/spellcraft":"~0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/spellcraft-gcp-auth_1.0.1_1768688423034_0.0014575486511958502","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@c6fc/spellcraft-gcp-auth","version":"1.0.2","keywords":["spellcraft","gcp"],"author":"","license":"MIT","_id":"@c6fc/spellcraft-gcp-auth@1.0.2","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"homepage":"https://github.com/c6fc/spellcraft-gcp-auth#readme","bugs":{"url":"https://github.com/c6fc/spellcraft-gcp-auth/issues"},"dist":{"shasum":"1085c3ce3dffb85fbd0dbe22577417bd98b1d74c","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-gcp-auth/-/spellcraft-gcp-auth-1.0.2.tgz","fileCount":7,"integrity":"sha512-0vVL9xnC8PX9IEXjCLfSpDsKtXgeEAyAAz1ArAIp8ECIt+8DL+u9725+Bkmia+tHAHwVpcAnCRJlBoOW1y5U8A==","signatures":[{"sig":"MEUCIGt6iEuWSUHXi0+2u+gIEuZDIaDfnjlwqTFdfeogNq5KAiEAqtgQ30x4uK5MRso9rGngZzYCUrU2P/+CckSdpM7NxD0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20621},"main":"module.js","gitHead":"006839ec020600838617521d041d6c4e937ff6d3","scripts":{"cli":"node 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-auth.git","type":"git"},"spellcraft":true,"_npmVersion":"10.8.2","description":"A plugin to add functionality via @c6fc/spellcraft","directories":{},"_nodeVersion":"20.19.0","dependencies":{"googleapis":"^170.0.0","google-auth-library":"^10.5.0"},"_hasShrinkwrap":false,"devDependencies":{"yargs":"^18.0.0","@c6fc/spellcraft":"~0.1.0"},"peerDependencies":{"@c6fc/spellcraft":"~0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/spellcraft-gcp-auth_1.0.2_1770740842740_0.7222597666879123","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@c6fc/spellcraft-gcp-auth","version":"1.0.3","keywords":["spellcraft","gcp"],"author":"","license":"MIT","_id":"@c6fc/spellcraft-gcp-auth@1.0.3","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"homepage":"https://github.com/c6fc/spellcraft-gcp-auth#readme","bugs":{"url":"https://github.com/c6fc/spellcraft-gcp-auth/issues"},"dist":{"shasum":"5d33dd0731aecf67499cbe241f95252a26c3b1e8","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-gcp-auth/-/spellcraft-gcp-auth-1.0.3.tgz","fileCount":7,"integrity":"sha512-taVTvaT/oij8QED1usEc4QERdEJlH7ET59EP54qU/AMdiOZwKaREMvhf2lWjtFNa6hIJ+WOvmCupUIkna+loDg==","signatures":[{"sig":"MEQCIEgUVhEc0/FRb87TbIzyRbwKmEK+NEb1Xy0jRC3w63EAAiB8aG5vpN4YZj7LxKx/QWoIOpLdMXmYXZTXHxvtdFDh1w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20936},"main":"module.js","gitHead":"4365c8d09350abe437ad28d25d02feb8f44f74fb","scripts":{"cli":"node 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-auth.git","type":"git"},"spellcraft":true,"_npmVersion":"10.8.2","description":"A plugin to add functionality via @c6fc/spellcraft","directories":{},"_nodeVersion":"20.19.0","dependencies":{"googleapis":"^170.0.0","google-auth-library":"^10.5.0"},"_hasShrinkwrap":false,"devDependencies":{"yargs":"^18.0.0","@c6fc/spellcraft":"~0.1.0"},"peerDependencies":{"@c6fc/spellcraft":"~0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/spellcraft-gcp-auth_1.0.3_1775057385034_0.23127523657330928","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@c6fc/spellcraft-gcp-auth","version":"1.1.0","keywords":["spellcraft","gcp"],"author":"","license":"MIT","_id":"@c6fc/spellcraft-gcp-auth@1.1.0","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"homepage":"https://github.com/c6fc/spellcraft-gcp-auth#readme","bugs":{"url":"https://github.com/c6fc/spellcraft-gcp-auth/issues"},"dist":{"shasum":"b0fe1f59bf52ebccd32fabae5e9fd682e2ba4e42","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-gcp-auth/-/spellcraft-gcp-auth-1.1.0.tgz","fileCount":7,"integrity":"sha512-dtGoQmkCzCiP5Dnj4QcK8z/v3G124mz23OOYsAIQpa1s5dcOvs3HjT9rKDJ//mVpM3+kY3vzRVxKj9FsT66F2g==","signatures":[{"sig":"MEQCIF+CdaGyXTAGLuIT+5uptP72I1PcxHwK/mUR7JI32ut3AiBIUVP0dug0rMpRhT7zqhfOrgteKgcNT1ueK9S9WhaZtQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20963},"main":"module.js","gitHead":"a2cec043f255e44e582ece9672c4cd799de1d675","scripts":{"cli":"node 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-auth.git","type":"git"},"spellcraft":true,"_npmVersion":"10.8.2","description":"A plugin to add functionality via @c6fc/spellcraft","directories":{},"_nodeVersion":"20.19.0","dependencies":{"googleapis":"^170.0.0","google-auth-library":"^10.5.0"},"_hasShrinkwrap":false,"devDependencies":{"yargs":"^18.0.0","@c6fc/spellcraft":"~0.2.0"},"peerDependencies":{"@c6fc/spellcraft":"~0.2.0"},"_npmOperationalInternal":{"tmp":"tmp/spellcraft-gcp-auth_1.1.0_1780343842693_0.3368792200429518","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"_id":"@c6fc/spellcraft-gcp-auth@2.0.0","bugs":{"url":"https://github.com/c6fc/spellcraft-gcp-auth/issues"},"dist":{"shasum":"4640f15c061aa89c541e4e19255ff36004bd0bec","tarball":"https://registry.npmjs.org/@c6fc/spellcraft-gcp-auth/-/spellcraft-gcp-auth-2.0.0.tgz","fileCount":6,"integrity":"sha512-BpgY7ObfEz9s6t8blaz2/pthBhFAF2Vgv/sFr1Gqps1ZhAuXILMpZD2RKPdMM70a9U4V6K8IDzq4OjHr0xd4PQ==","signatures":[{"sig":"MEQCIGV/bYvGkb2yp5sa+B+SzxqV6iYpDDcvimMcVMo/3kIkAiAYLdq3nhRFK8OPPwkDxmazN0Qp3w6No8sjxQ1vnaM++Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFuLfyNv6LXnm6iJHYvIgm3wVNOBMkkNXCMrB+M4t5U0AiEAyikrE6yn24TP4NnM5g97z0fzI9UHCs6EsfeOvex1JVw="}],"unpackedSize":39228},"main":"module.js","name":"@c6fc/spellcraft-gcp-auth","author":"","engines":{"node":">=18"},"gitHead":"b4d8245f4c438c0a2a0c630808d2fd1117bc782d","license":"MIT","scripts":{"cli":"node 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-gcp-auth#readme","keywords":["spellcraft","gcp"],"repository":{"url":"git+https://github.com/c6fc/spellcraft-gcp-auth.git","type":"git"},"spellcraft":true,"_npmVersion":"10.8.2","description":"GCP credentials with impersonation, the googleapis client, and service enablement that resolves your project properly.","directories":{},"maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"_nodeVersion":"20.19.0","dependencies":{"googleapis":"^170.0.0","google-auth-library":"^10.5.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-gcp-auth_2.0.0_1788729113182_0.6339806372394492"}}},"time":{"created":"2026-01-12T00:27:52.502Z","modified":"2026-09-06T21:11:53.400Z","1.0.0":"2026-01-12T00:27:52.715Z","1.0.1":"2026-01-17T22:20:23.217Z","1.0.2":"2026-02-10T16:27:22.908Z","1.0.3":"2026-04-01T15:29:45.169Z","1.1.0":"2026-06-01T19:57:22.836Z","2.0.0":"2026-09-06T21:11:53.257Z"},"bugs":{"url":"https://github.com/c6fc/spellcraft-gcp-auth/issues"},"license":"MIT","homepage":"https://github.com/c6fc/spellcraft-gcp-auth#readme","keywords":["spellcraft","gcp"],"repository":{"url":"git+https://github.com/c6fc/spellcraft-gcp-auth.git","type":"git"},"description":"GCP credentials with impersonation, the googleapis client, and service enablement that resolves your project properly.","maintainers":[{"name":"c6fc","email":"brad@bradwoodward.io"}],"readme":"# @c6fc/spellcraft-gcp-auth\n\nGCP credentials and the googleapis client for\n[SpellCraft](https://github.com/c6fc/spellcraft), reachable directly from Jsonnet.\n\n[![NPM version](https://img.shields.io/npm/v/@c6fc/spellcraft-gcp-auth.svg?style=flat)](https://www.npmjs.com/package/@c6fc/spellcraft-gcp-auth)\n[![License](https://img.shields.io/npm/l/@c6fc/spellcraft-gcp-auth.svg?style=flat)](https://opensource.org/licenses/MIT)\n\nThis is what lets a manifest ask GCP a question while it renders — which project\nit is bound to, which organization that project sits under, which services are\nlive — instead of being handed an answer someone pasted in.\n\n```bash\nnpm install --save @c6fc/spellcraft-gcp-auth\n```\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-auth/module.libsonnet\";\n\n{\n\t\"project.json\": gcp.getProjectMetadata(),\n}\n```\n\n## Credentials and the bound project\n\nAuthentication uses Application Default Credentials — `gcloud auth\napplication-default login`, a service account key, or the ambient credentials of\nwhatever you're running on.\n\nThe project is resolved separately, in this order:\n\n1. `GOOGLE_CLOUD_PROJECT`\n2. `GCLOUD_PROJECT`\n3. the quota project recorded in your ADC file\n4. `gcloud config get-value project`\n\nADC frequently carries no project even when gcloud has one configured, which is\nwhy gcloud's own setting is consulted last rather than not at all. If none of the\nfour yields a project, the render stops and names all four fixes.\n\n```console\n$ npx spellcraft gcp-identity\n{\n    \"identity\": \"you@example.com\",\n    \"projectId\": \"my-project-1234\",\n    \"authType\": \"User/Authorized Account\",\n    ...\n}\n```\n\n### Impersonation\n\nSet `SPELLFRAME_GCP_IMPERSONATE` to a service account address and the plugin\nwraps the resolved credentials to act as it, for the whole render.\n\n```bash\nexport SPELLFRAME_GCP_IMPERSONATE=\"deployer@my-project-1234.iam.gserviceaccount.com\"\n```\n\n### One identity per process\n\nA process authenticates as exactly one GCP identity, ever. The first\nsuccessful resolution locks it in (project id plus impersonation target, if\nany); any later attempt — a second `SpellFrame`, or the same environment\nresolving differently on a second call — that would authenticate as a\n*different* identity throws, rather than silently replacing the credentials\neverything else in the process is depending on. The same identity resolving\nagain is a no-op, not an error.\n\nThis isn't a technical ceiling so much as a deliberate one: a spell needing a\ndifferent GCP *project* under the same identity should reach for\n`providerAliases()` (the same mechanism that already covers multiple\nregions), not re-authenticate. A genuinely different identity needs a\nseparate process — which, for credentials, is the stronger isolation\nboundary anyway.\n\n### Pinning the project\n\n```jsonnet\n{\n\tassertions:: gcp.assertProject(\"my-project-1234\"),\n\n\t\"main.tf.json\": { ... },\n}\n```\n\n`assertions::` is a SpellCraft convention, not just a hidden field that\nhappens to be read somewhere: `SpellFrame` forces it to evaluate before\nanything is written, whether or not any other field references it. See\ncore's README for how and why.\n\n## Enabling services during a render\n\nTerraform cannot enable the API that a resource it is creating depends on — the\nprovider needs it live before it can plan. `enableServices()` closes that gap by\nturning services on while the manifest evaluates.\n\nYou mostly won't call it directly. `api()`, and `listBuckets()`/`listInstances()`\nbuilt on it, enable their own service internally before making their call:\n\n```jsonnet\n{ \"instances.json\": gcp.listInstances({ project: gcp.getProjectId(), zone: \"us-west1-b\" }) }\n```\n\nNo `enableServices()` call, no threading a return value through — this works\nwith nothing else in the manifest. Every one of these calls, from every\nfunction, checks a process-wide cache first, so the same service being needed\nby ten different calls costs one real check, not ten.\n\nThe one real cost, and it's a property of the underlying GCP API rather than\nof this plugin: activating a service that's never been enabled before waits\n~15 seconds for IAM/quota propagation, once per call that activates something\nnew. A manifest touching many different never-before-enabled services across\nmany separate calls pays that wait once per service rather than once overall\n— noticeable on a cold first run against a project, free on every run after,\nsince confirmed services stay confirmed for the life of the process.\n\nFor a native function that *doesn't* self-enable this way — a different\nplugin's, or a hand-written one — call `enableServices()` yourself first, and\nthread the result through, since Jsonnet's evaluation order guarantees\nnothing here:\n\n```jsonnet\nlocal ready = gcp.enableServices([\"compute.googleapis.com\"]);\n{ \"instances.json\": if ready then someOtherPlugin.rawThing() else null }\n```\n\nFor services needed at apply time rather than render time,\n[`@c6fc/spellcraft-gcp-terraform`](https://www.npmjs.com/package/@c6fc/spellcraft-gcp-terraform)\ncollects them during evaluation and flushes them on\n`@c6fc/spellcraft-terraform:pre-apply`, so the bootstrap orders itself.\n\n## Calling any API\n\n`api()` reaches the whole of googleapis by dot-delimited path — service,\nversion, method:\n\n```jsonnet\ngcp.api(\"cloudresourcemanager.v1.projects.list\", {})\ngcp.api(\"storage.v1.buckets.list\", { project: gcp.getProjectId() })\n```\n\n`listBuckets()` and `listInstances()` are shorthands over it. All three default\n`params` to `{ project: getProjectId() }`, and supplying your own params replaces\nthat default — so pass `project` alongside anything else the method needs.\n\n<!-- SPELLCRAFT_DOCS_CLI_START -->\n## CLI Commands\n\n- **`spellcraft gcp-identity`**\n  Display the GCP identity of the SpellCraft execution context\n\n<!-- SPELLCRAFT_DOCS_CLI_END -->\n\n## What it contributes to a SpellFrame\n\n- **`init()`** — resolves the project, builds the auth client, applies\n  impersonation, and sets the client as the googleapis default.\n- **`functionContext.google`** — the authenticated `googleapis` module, available\n  as `this.google` inside any plugin's native functions. This is the seam\n  `@c6fc/spellcraft-gcp-terraform` uses to reuse these credentials rather than\n  authenticating again.\n\n<!-- SPELLCRAFT_DOCS_API_START -->\n## API Reference\n\n### `getProjectId()`\n\nThe project this render is bound to.\n\nResolved once during `init()` from `GOOGLE_CLOUD_PROJECT`, `GCLOUD_PROJECT`,\nthe ADC file's quota project, then gcloud's configured project — so it costs\nno API call.\n\n- returns {string} the project id\n\n**Examples:**\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-auth/module.libsonnet\";\n\n{ \"project.json\": { id: gcp.getProjectId() } }\n```\n\n---\n### `getProjectMetadata()`\n\nThe project's place in the resource hierarchy, and how it is billed.\n\nEnables `cloudbilling.googleapis.com` on the project if it isn't already, so\nthat the billing account can be read back.\n\n- returns {object} `{ projectId, quotaProject, organizationId, organizationDomain, directoryId, billingAccount }` — the organization and billing fields are false when the project has none\n\n**Examples:**\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-auth/module.libsonnet\";\n\n{ \"billing.json\": gcp.getProjectMetadata() }\n```\n\n---\n### `getCallerIdentity()`\n\nThe identity this render is authenticated as.\n\n- returns {object} `{ identity, projectId, scopes, expiresIn, authType, impersonatedBy }`\n\n**Examples:**\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-auth/module.libsonnet\";\n\n{ \"identity.json\": gcp.getCallerIdentity() }\n```\n\n---\n### `enableServices(services)`\n\nEnables API services during the render, so they are live before any tool\nruns. This is the answer to the stage-zero problem: Terraform cannot enable\nthe API that a resource it is creating depends on.\n\n`api()`, `listBuckets()` and `listInstances()` already call this\ninternally for their own service, so you don't need to call it yourself\nbefore using them. Reach for this directly when a manifest calls a native\nfunction from elsewhere -- a different plugin, or a hand-written one --\nthat doesn't self-enable the way this plugin's own functions do. In that\ncase, Jsonnet evaluates lazily and in no guaranteed field order, so the\ncall that needs the service enabled must *depend on* the result rather\nthan merely follow it -- thread the return value through:\n\n```\nlocal ready = gcp.enableServices([\"compute.googleapis.com\"]);\n{ \"instances.json\": if ready then someOtherPlugin.rawThing() else null }\n```\n\nAlready-enabled services are left alone, and confirmed ones are cached\nfor the life of the process, so calling this -- from as many places as\nyou like, including indirectly through `api()` -- is cheap and safe to\nrepeat. The one cost worth knowing: activating a service that's never\nbeen enabled before waits ~15s for IAM/quota propagation, once per call\nthat activates something new -- so many separate calls each activating\none new service pay that wait separately, rather than once. This only\never affects the first time a given process touches a given service.\n\n- param {string[]} services - fully qualified service names\n- returns {boolean} true once every requested service is enabled\n\n**Examples:**\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-auth/module.libsonnet\";\n\n{ \"instances.json\": gcp.listInstances({\n    project: gcp.getProjectId(),\n    zone: \"us-west1-b\",\n  }) }\n```\n\n---\n### `api(fullpath, params={ project: gcp.getProjectId() })`\n\nCalls any googleapis method and returns its response.\n\nThe path is dot-delimited: service, version, then the method path — so\n`compute.v1.instances.list` or `storage.v1.buckets.list`.\n\nEnables `<service>.googleapis.com` first, best-effort -- that matches\nGoogle's own naming convention for the overwhelming majority of services,\nso most calls need nothing else. It isn't guaranteed for every service,\nbut a wrong or nonexistent guess never blocks the call itself, and a\ncorrect one is free after the first time (see `enableServices()`). Call\n`enableServices()` yourself first for anything this guess doesn't cover.\n\nPassing `params` replaces the default entirely, so add `project` back when\nthe method needs it alongside anything else you supply.\n\n- param {string} fullpath - `<service>.<version>.<...method>`\n- param {object} [params={ project: gcp.getProjectId() }] - request parameters\n- returns {object} the API response body\n\n**Examples:**\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-auth/module.libsonnet\";\n\n{ \"projects.json\": gcp.api(\"cloudresourcemanager.v1.projects.list\", {}) }\n```\n\n---\n### `listBuckets(params={ project: gcp.getProjectId() })`\n\nCloud Storage buckets in the project.\n\n- param {object} [params={ project: gcp.getProjectId() }] - request parameters\n- returns {object} a `storage#buckets` response\n\n**Examples:**\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-auth/module.libsonnet\";\n\n{ \"buckets.json\": gcp.listBuckets() }\n```\n\n---\n### `listInstances(params={ project: gcp.getProjectId() })`\n\nCompute instances in one zone.\n\nA zone is required, and supplying it replaces the default params — so pass\n`project` as well. Enables `compute.googleapis.com` itself, via `api()` --\nnothing to enable yourself first.\n\n- param {object} [params={ project: gcp.getProjectId() }] - must include `zone`\n- returns {object} a `compute#instanceList` response\n\n**Examples:**\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-auth/module.libsonnet\";\n\n{ \"instances.json\": gcp.listInstances({\n    project: gcp.getProjectId(),\n    zone: \"us-west1-b\",\n  }) }\n```\n\n---\n### `assertProject(expectedId)`\n\nAborts the render unless the bound project matches.\n\nA guard for spells that must only ever run against one project.\n\n- param {string} expectedId - the project the render requires\n- returns {string} the same id, so it can be bound or discarded\n\n**Examples:**\n\n```jsonnet\nlocal gcp = import \"@c6fc/spellcraft-gcp-auth/module.libsonnet\";\n\n// `assertions::` is a SpellCraft convention: SpellFrame forces it to\n// evaluate before anything is written, whether or not anything else\n// in the manifest references it.\n{\n    assertions:: gcp.assertProject(\"my-project-1234\"),\n\n    \"main.tf.json\": { ... },\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 cli     # exercises this plugin's CLI commands\nnpm run doc     # regenerates the two sections above from source comments\n```\n\n`npm test` needs GCP credentials and a bound project. It **writes** in one\nrespect: `getProjectMetadata()` enables `cloudbilling.googleapis.com` so the\nbilling account can be read, and the fixture enables `compute.googleapis.com`.\n\n## License\n\nMIT © [Brad Woodward](https://github.com/c6fc)\n","readmeFilename":"README.md"}