@infracraft/pulumi 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/fly/app.cjs.map +1 -1
  2. package/dist/fly/app.mjs.map +1 -1
  3. package/dist/fly/certificate.cjs.map +1 -1
  4. package/dist/fly/certificate.mjs.map +1 -1
  5. package/dist/fly/deploy.cjs +2 -1
  6. package/dist/fly/deploy.cjs.map +1 -1
  7. package/dist/fly/deploy.d.cts +5 -1
  8. package/dist/fly/deploy.d.cts.map +1 -1
  9. package/dist/fly/deploy.d.mts +5 -1
  10. package/dist/fly/deploy.d.mts.map +1 -1
  11. package/dist/fly/deploy.mjs +2 -1
  12. package/dist/fly/deploy.mjs.map +1 -1
  13. package/dist/fly/ip.cjs.map +1 -1
  14. package/dist/fly/ip.mjs.map +1 -1
  15. package/dist/fly/secret.cjs.map +1 -1
  16. package/dist/fly/secret.mjs.map +1 -1
  17. package/dist/fly/volume.cjs.map +1 -1
  18. package/dist/fly/volume.mjs.map +1 -1
  19. package/dist/git-guard.cjs +4 -2
  20. package/dist/git-guard.cjs.map +1 -1
  21. package/dist/git-guard.d.cts.map +1 -1
  22. package/dist/git-guard.d.mts.map +1 -1
  23. package/dist/git-guard.mjs +4 -2
  24. package/dist/git-guard.mjs.map +1 -1
  25. package/dist/neon/branch.cjs.map +1 -1
  26. package/dist/neon/branch.mjs.map +1 -1
  27. package/dist/neon/database.cjs.map +1 -1
  28. package/dist/neon/database.mjs.map +1 -1
  29. package/dist/neon/endpoint.cjs.map +1 -1
  30. package/dist/neon/endpoint.mjs.map +1 -1
  31. package/dist/neon/project.cjs.map +1 -1
  32. package/dist/neon/project.mjs.map +1 -1
  33. package/dist/neon/role.cjs.map +1 -1
  34. package/dist/neon/role.mjs.map +1 -1
  35. package/dist/railway/deploy.cjs +2 -1
  36. package/dist/railway/deploy.cjs.map +1 -1
  37. package/dist/railway/deploy.d.cts +5 -1
  38. package/dist/railway/deploy.d.cts.map +1 -1
  39. package/dist/railway/deploy.d.mts +5 -1
  40. package/dist/railway/deploy.d.mts.map +1 -1
  41. package/dist/railway/deploy.mjs +2 -1
  42. package/dist/railway/deploy.mjs.map +1 -1
  43. package/dist/railway/domain.cjs.map +1 -1
  44. package/dist/railway/domain.mjs.map +1 -1
  45. package/dist/railway/environment.cjs.map +1 -1
  46. package/dist/railway/environment.mjs.map +1 -1
  47. package/dist/railway/project.cjs.map +1 -1
  48. package/dist/railway/project.mjs.map +1 -1
  49. package/dist/railway/service.cjs.map +1 -1
  50. package/dist/railway/service.mjs.map +1 -1
  51. package/dist/railway/variable.cjs.map +1 -1
  52. package/dist/railway/variable.mjs.map +1 -1
  53. package/dist/railway/volume.cjs.map +1 -1
  54. package/dist/railway/volume.mjs.map +1 -1
  55. package/dist/stable-dir.cjs +41 -0
  56. package/dist/stable-dir.cjs.map +1 -0
  57. package/dist/stable-dir.d.cts +34 -0
  58. package/dist/stable-dir.d.cts.map +1 -0
  59. package/dist/stable-dir.d.mts +34 -0
  60. package/dist/stable-dir.d.mts.map +1 -0
  61. package/dist/stable-dir.mjs +39 -0
  62. package/dist/stable-dir.mjs.map +1 -0
  63. package/dist/vercel/deploy.cjs +2 -1
  64. package/dist/vercel/deploy.cjs.map +1 -1
  65. package/dist/vercel/deploy.d.cts +5 -1
  66. package/dist/vercel/deploy.d.cts.map +1 -1
  67. package/dist/vercel/deploy.d.mts +5 -1
  68. package/dist/vercel/deploy.d.mts.map +1 -1
  69. package/dist/vercel/deploy.mjs +2 -1
  70. package/dist/vercel/deploy.mjs.map +1 -1
  71. package/dist/vercel/project.cjs.map +1 -1
  72. package/dist/vercel/project.mjs.map +1 -1
  73. package/dist/vercel/variable.cjs.map +1 -1
  74. package/dist/vercel/variable.mjs.map +1 -1
  75. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"app.cjs","names":["FlyClient","pulumi"],"sources":["../../src/fly/app.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport { FlyClient } from \"./client.js\";\nimport type { FlyProvider } from \"./provider.js\";\n\n/** Resolved inputs for the Fly app dynamic provider. */\nexport interface FlyAppInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name (globally unique). Used as the resource identifier. */\n\tname: string;\n\n\t/** Org slug used only when creating a new app. */\n\torganization?: string;\n}\n\n/** Persisted state for the Fly app. */\ninterface FlyAppOutputs extends FlyAppInputs {\n\t/** App identifier — equals the app name (all child paths key off the name). */\n\tappId: string;\n}\n\n/** Get-app response (only the fields we read). */\ninterface FlyAppResponse {\n\tid: string;\n\tname: string;\n}\n\n/**\n * Dynamic provider implementing adopt-or-create for Fly apps.\n *\n * `create()` does `GET /v1/apps/{name}`; if found it adopts, otherwise it\n * `POST /v1/apps`. `delete()` is a no-op — deleting a Fly app destroys\n * everything in it, so (like Railway/Neon/Vercel top-level resources) Pulumi\n * does not delete apps.\n */\nclass FlyAppResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlyAppInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\t\tconst existing = await client.tryGet<FlyAppResponse>(\n\t\t\t`/v1/apps/${inputs.name}`,\n\t\t);\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(`Adopting existing Fly app \"${inputs.name}\"`);\n\t\t} else {\n\t\t\tif (!inputs.organization) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`FlyApp \"${inputs.name}\": an organization is required to create a new app — set it on FlyProvider or FlyApp args`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tpulumi.log.info(`Fly app \"${inputs.name}\" not found — creating...`);\n\n\t\t\tawait client.post(\"/v1/apps\", {\n\t\t\t\tapp_name: inputs.name,\n\t\t\t\torg_slug: inputs.organization,\n\t\t\t});\n\t\t}\n\n\t\tconst outs: FlyAppOutputs = { ...inputs, appId: inputs.name };\n\n\t\treturn { id: inputs.name, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyAppOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new FlyClient(props.token);\n\t\tconst app = await client.tryGet<FlyAppResponse>(`/v1/apps/${id}`);\n\n\t\tif (!app) {\n\t\t\tthrow new Error(`Fly app \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn { id, props: { ...props, name: app.name, appId: app.name } };\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\t_olds: FlyAppOutputs,\n\t\tnews: FlyAppInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\treturn { outs: { ...news, appId: id } };\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Fly app deletion skipped — apps are not deleted by Pulumi (would destroy all contained resources)\",\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyAppOutputs,\n\t\tnews: FlyAppInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\t\tif (olds.organization !== news.organization) {\n\t\t\treplaces.push(\"organization\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyAppResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly appId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\torganization?: pulumi.Input<string | undefined>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyAppResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, appId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyApp — replaces Pulumi's native `provider` field. */\ntype FlyAppOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n};\n\n/** Args for FlyApp. */\nexport interface FlyAppArgs {\n\t/** App name (globally unique). Used for adoption lookup and as `.id`. */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * Org slug for app creation. Overrides `FlyProvider.organization`.\n\t * Ignored when the app already exists (adoption).\n\t */\n\torganization?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Fly app with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const app = new FlyApp(\"api\", { name: \"rby-api\" }, { provider });\n * ```\n */\nexport class FlyApp extends pulumi.ComponentResource {\n\t/** App identifier (equals the app name). */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyAppArgs, opts: FlyAppOptions) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:App\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyAppResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tname: args.name,\n\t\t\t\torganization: args.organization ?? provider.organization,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.appId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;AAqCA,IAAM,yBAAN,MAAwE;CACvE,MAAM,OAAO,QAA4D;EACxE,MAAM,SAAS,IAAIA,6BAAU,OAAO,KAAK;EAKzC,IAAI,MAJmB,OAAO,OAC7B,YAAY,OAAO,MACpB,GAGC,eAAO,IAAI,KAAK,8BAA8B,OAAO,KAAK,EAAE;OACtD;GACN,IAAI,CAAC,OAAO,cACX,MAAM,IAAI,MACT,WAAW,OAAO,KAAK,0FACxB;GAGD,eAAO,IAAI,KAAK,YAAY,OAAO,KAAK,0BAA0B;GAElE,MAAM,OAAO,KAAK,YAAY;IAC7B,UAAU,OAAO;IACjB,UAAU,OAAO;GAClB,CAAC;EACF;EAEA,MAAM,OAAsB;GAAE,GAAG;GAAQ,OAAO,OAAO;EAAK;EAE5D,OAAO;GAAE,IAAI,OAAO;GAAM;EAAK;CAChC;CAEA,MAAM,KACL,IACA,OACqC;EAErC,MAAM,MAAM,MAAM,IADCA,6BAAU,MAAM,KACZ,EAAE,OAAuB,YAAY,IAAI;EAEhE,IAAI,CAAC,KACJ,MAAM,IAAI,MAAM,YAAY,GAAG,2BAA2B;EAG3D,OAAO;GAAE;GAAI,OAAO;IAAE,GAAG;IAAO,MAAM,IAAI;IAAM,OAAO,IAAI;GAAK;EAAE;CACnE;CAEA,MAAM,OACL,IACA,OACA,MACuC;EACvC,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM,OAAO;EAAG,EAAE;CACvC;CAEA,MAAM,SAAwB;EAC7B,eAAO,IAAI,KACV,mGACD;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAErB,IAAI,KAAK,iBAAiB,KAAK,cAC9B,SAAS,KAAK,cAAc;EAG7B,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,iBAAN,cAA6BC,eAAO,QAAQ,SAAS;CAGpD,YACC,MACA,MAKA,MACC;EACD,MACC,IAAI,uBAAuB,GAC3B,MACA;GAAE,GAAG;GAAM,OAAO;EAAU,GAC5B,IACD;CACD;AACD;;;;;;;;;AA4BA,IAAa,SAAb,cAA4BA,eAAO,kBAAkB;CAIpD,YAAY,MAAc,MAAkB,MAAqB;EAChE,MAAM,EAAE,UAAU,GAAG,eAAe;EAEpC,MAAM,sBAAsB,MAAM,CAAC,GAAG,UAAU;EAEhD,MAAM,WAAW,IAAI,eACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,MAAM,KAAK;GACX,cAAc,KAAK,gBAAgB,SAAS;EAC7C,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}
1
+ {"version":3,"file":"app.cjs","names":["FlyClient","pulumi"],"sources":["../../src/fly/app.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport { FlyClient } from \"./client\";\nimport type { FlyProvider } from \"./provider\";\n\n/** Resolved inputs for the Fly app dynamic provider. */\nexport interface FlyAppInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name (globally unique). Used as the resource identifier. */\n\tname: string;\n\n\t/** Org slug used only when creating a new app. */\n\torganization?: string;\n}\n\n/** Persisted state for the Fly app. */\ninterface FlyAppOutputs extends FlyAppInputs {\n\t/** App identifier — equals the app name (all child paths key off the name). */\n\tappId: string;\n}\n\n/** Get-app response (only the fields we read). */\ninterface FlyAppResponse {\n\tid: string;\n\tname: string;\n}\n\n/**\n * Dynamic provider implementing adopt-or-create for Fly apps.\n *\n * `create()` does `GET /v1/apps/{name}`; if found it adopts, otherwise it\n * `POST /v1/apps`. `delete()` is a no-op — deleting a Fly app destroys\n * everything in it, so (like Railway/Neon/Vercel top-level resources) Pulumi\n * does not delete apps.\n */\nclass FlyAppResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlyAppInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\t\tconst existing = await client.tryGet<FlyAppResponse>(\n\t\t\t`/v1/apps/${inputs.name}`,\n\t\t);\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(`Adopting existing Fly app \"${inputs.name}\"`);\n\t\t} else {\n\t\t\tif (!inputs.organization) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`FlyApp \"${inputs.name}\": an organization is required to create a new app — set it on FlyProvider or FlyApp args`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tpulumi.log.info(`Fly app \"${inputs.name}\" not found — creating...`);\n\n\t\t\tawait client.post(\"/v1/apps\", {\n\t\t\t\tapp_name: inputs.name,\n\t\t\t\torg_slug: inputs.organization,\n\t\t\t});\n\t\t}\n\n\t\tconst outs: FlyAppOutputs = { ...inputs, appId: inputs.name };\n\n\t\treturn { id: inputs.name, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyAppOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new FlyClient(props.token);\n\t\tconst app = await client.tryGet<FlyAppResponse>(`/v1/apps/${id}`);\n\n\t\tif (!app) {\n\t\t\tthrow new Error(`Fly app \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn { id, props: { ...props, name: app.name, appId: app.name } };\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\t_olds: FlyAppOutputs,\n\t\tnews: FlyAppInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\treturn { outs: { ...news, appId: id } };\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Fly app deletion skipped — apps are not deleted by Pulumi (would destroy all contained resources)\",\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyAppOutputs,\n\t\tnews: FlyAppInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\t\tif (olds.organization !== news.organization) {\n\t\t\treplaces.push(\"organization\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyAppResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly appId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\torganization?: pulumi.Input<string | undefined>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyAppResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, appId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyApp — replaces Pulumi's native `provider` field. */\ntype FlyAppOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n};\n\n/** Args for FlyApp. */\nexport interface FlyAppArgs {\n\t/** App name (globally unique). Used for adoption lookup and as `.id`. */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * Org slug for app creation. Overrides `FlyProvider.organization`.\n\t * Ignored when the app already exists (adoption).\n\t */\n\torganization?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Fly app with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const app = new FlyApp(\"api\", { name: \"rby-api\" }, { provider });\n * ```\n */\nexport class FlyApp extends pulumi.ComponentResource {\n\t/** App identifier (equals the app name). */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyAppArgs, opts: FlyAppOptions) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:App\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyAppResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tname: args.name,\n\t\t\t\torganization: args.organization ?? provider.organization,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.appId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;AAqCA,IAAM,yBAAN,MAAwE;CACvE,MAAM,OAAO,QAA4D;EACxE,MAAM,SAAS,IAAIA,6BAAU,OAAO,KAAK;EAKzC,IAAI,MAJmB,OAAO,OAC7B,YAAY,OAAO,MACpB,GAGC,eAAO,IAAI,KAAK,8BAA8B,OAAO,KAAK,EAAE;OACtD;GACN,IAAI,CAAC,OAAO,cACX,MAAM,IAAI,MACT,WAAW,OAAO,KAAK,0FACxB;GAGD,eAAO,IAAI,KAAK,YAAY,OAAO,KAAK,0BAA0B;GAElE,MAAM,OAAO,KAAK,YAAY;IAC7B,UAAU,OAAO;IACjB,UAAU,OAAO;GAClB,CAAC;EACF;EAEA,MAAM,OAAsB;GAAE,GAAG;GAAQ,OAAO,OAAO;EAAK;EAE5D,OAAO;GAAE,IAAI,OAAO;GAAM;EAAK;CAChC;CAEA,MAAM,KACL,IACA,OACqC;EAErC,MAAM,MAAM,MAAM,IADCA,6BAAU,MAAM,KACZ,EAAE,OAAuB,YAAY,IAAI;EAEhE,IAAI,CAAC,KACJ,MAAM,IAAI,MAAM,YAAY,GAAG,2BAA2B;EAG3D,OAAO;GAAE;GAAI,OAAO;IAAE,GAAG;IAAO,MAAM,IAAI;IAAM,OAAO,IAAI;GAAK;EAAE;CACnE;CAEA,MAAM,OACL,IACA,OACA,MACuC;EACvC,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM,OAAO;EAAG,EAAE;CACvC;CAEA,MAAM,SAAwB;EAC7B,eAAO,IAAI,KACV,mGACD;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAErB,IAAI,KAAK,iBAAiB,KAAK,cAC9B,SAAS,KAAK,cAAc;EAG7B,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,iBAAN,cAA6BC,eAAO,QAAQ,SAAS;CAGpD,YACC,MACA,MAKA,MACC;EACD,MACC,IAAI,uBAAuB,GAC3B,MACA;GAAE,GAAG;GAAM,OAAO;EAAU,GAC5B,IACD;CACD;AACD;;;;;;;;;AA4BA,IAAa,SAAb,cAA4BA,eAAO,kBAAkB;CAIpD,YAAY,MAAc,MAAkB,MAAqB;EAChE,MAAM,EAAE,UAAU,GAAG,eAAe;EAEpC,MAAM,sBAAsB,MAAM,CAAC,GAAG,UAAU;EAEhD,MAAM,WAAW,IAAI,eACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,MAAM,KAAK;GACX,cAAc,KAAK,gBAAgB,SAAS;EAC7C,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"app.mjs","names":[],"sources":["../../src/fly/app.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport { FlyClient } from \"./client.js\";\nimport type { FlyProvider } from \"./provider.js\";\n\n/** Resolved inputs for the Fly app dynamic provider. */\nexport interface FlyAppInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name (globally unique). Used as the resource identifier. */\n\tname: string;\n\n\t/** Org slug used only when creating a new app. */\n\torganization?: string;\n}\n\n/** Persisted state for the Fly app. */\ninterface FlyAppOutputs extends FlyAppInputs {\n\t/** App identifier — equals the app name (all child paths key off the name). */\n\tappId: string;\n}\n\n/** Get-app response (only the fields we read). */\ninterface FlyAppResponse {\n\tid: string;\n\tname: string;\n}\n\n/**\n * Dynamic provider implementing adopt-or-create for Fly apps.\n *\n * `create()` does `GET /v1/apps/{name}`; if found it adopts, otherwise it\n * `POST /v1/apps`. `delete()` is a no-op — deleting a Fly app destroys\n * everything in it, so (like Railway/Neon/Vercel top-level resources) Pulumi\n * does not delete apps.\n */\nclass FlyAppResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlyAppInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\t\tconst existing = await client.tryGet<FlyAppResponse>(\n\t\t\t`/v1/apps/${inputs.name}`,\n\t\t);\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(`Adopting existing Fly app \"${inputs.name}\"`);\n\t\t} else {\n\t\t\tif (!inputs.organization) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`FlyApp \"${inputs.name}\": an organization is required to create a new app — set it on FlyProvider or FlyApp args`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tpulumi.log.info(`Fly app \"${inputs.name}\" not found — creating...`);\n\n\t\t\tawait client.post(\"/v1/apps\", {\n\t\t\t\tapp_name: inputs.name,\n\t\t\t\torg_slug: inputs.organization,\n\t\t\t});\n\t\t}\n\n\t\tconst outs: FlyAppOutputs = { ...inputs, appId: inputs.name };\n\n\t\treturn { id: inputs.name, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyAppOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new FlyClient(props.token);\n\t\tconst app = await client.tryGet<FlyAppResponse>(`/v1/apps/${id}`);\n\n\t\tif (!app) {\n\t\t\tthrow new Error(`Fly app \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn { id, props: { ...props, name: app.name, appId: app.name } };\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\t_olds: FlyAppOutputs,\n\t\tnews: FlyAppInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\treturn { outs: { ...news, appId: id } };\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Fly app deletion skipped — apps are not deleted by Pulumi (would destroy all contained resources)\",\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyAppOutputs,\n\t\tnews: FlyAppInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\t\tif (olds.organization !== news.organization) {\n\t\t\treplaces.push(\"organization\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyAppResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly appId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\torganization?: pulumi.Input<string | undefined>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyAppResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, appId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyApp — replaces Pulumi's native `provider` field. */\ntype FlyAppOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n};\n\n/** Args for FlyApp. */\nexport interface FlyAppArgs {\n\t/** App name (globally unique). Used for adoption lookup and as `.id`. */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * Org slug for app creation. Overrides `FlyProvider.organization`.\n\t * Ignored when the app already exists (adoption).\n\t */\n\torganization?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Fly app with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const app = new FlyApp(\"api\", { name: \"rby-api\" }, { provider });\n * ```\n */\nexport class FlyApp extends pulumi.ComponentResource {\n\t/** App identifier (equals the app name). */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyAppArgs, opts: FlyAppOptions) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:App\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyAppResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tname: args.name,\n\t\t\t\torganization: args.organization ?? provider.organization,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.appId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;AAqCA,IAAM,yBAAN,MAAwE;CACvE,MAAM,OAAO,QAA4D;EACxE,MAAM,SAAS,IAAI,UAAU,OAAO,KAAK;EAKzC,IAAI,MAJmB,OAAO,OAC7B,YAAY,OAAO,MACpB,GAGC,OAAO,IAAI,KAAK,8BAA8B,OAAO,KAAK,EAAE;OACtD;GACN,IAAI,CAAC,OAAO,cACX,MAAM,IAAI,MACT,WAAW,OAAO,KAAK,0FACxB;GAGD,OAAO,IAAI,KAAK,YAAY,OAAO,KAAK,0BAA0B;GAElE,MAAM,OAAO,KAAK,YAAY;IAC7B,UAAU,OAAO;IACjB,UAAU,OAAO;GAClB,CAAC;EACF;EAEA,MAAM,OAAsB;GAAE,GAAG;GAAQ,OAAO,OAAO;EAAK;EAE5D,OAAO;GAAE,IAAI,OAAO;GAAM;EAAK;CAChC;CAEA,MAAM,KACL,IACA,OACqC;EAErC,MAAM,MAAM,MAAM,IADC,UAAU,MAAM,KACZ,EAAE,OAAuB,YAAY,IAAI;EAEhE,IAAI,CAAC,KACJ,MAAM,IAAI,MAAM,YAAY,GAAG,2BAA2B;EAG3D,OAAO;GAAE;GAAI,OAAO;IAAE,GAAG;IAAO,MAAM,IAAI;IAAM,OAAO,IAAI;GAAK;EAAE;CACnE;CAEA,MAAM,OACL,IACA,OACA,MACuC;EACvC,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM,OAAO;EAAG,EAAE;CACvC;CAEA,MAAM,SAAwB;EAC7B,OAAO,IAAI,KACV,mGACD;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAErB,IAAI,KAAK,iBAAiB,KAAK,cAC9B,SAAS,KAAK,cAAc;EAG7B,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,iBAAN,cAA6B,OAAO,QAAQ,SAAS;CAGpD,YACC,MACA,MAKA,MACC;EACD,MACC,IAAI,uBAAuB,GAC3B,MACA;GAAE,GAAG;GAAM,OAAO;EAAU,GAC5B,IACD;CACD;AACD;;;;;;;;;AA4BA,IAAa,SAAb,cAA4B,OAAO,kBAAkB;CAIpD,YAAY,MAAc,MAAkB,MAAqB;EAChE,MAAM,EAAE,UAAU,GAAG,eAAe;EAEpC,MAAM,sBAAsB,MAAM,CAAC,GAAG,UAAU;EAEhD,MAAM,WAAW,IAAI,eACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,MAAM,KAAK;GACX,cAAc,KAAK,gBAAgB,SAAS;EAC7C,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}
1
+ {"version":3,"file":"app.mjs","names":[],"sources":["../../src/fly/app.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport { FlyClient } from \"./client\";\nimport type { FlyProvider } from \"./provider\";\n\n/** Resolved inputs for the Fly app dynamic provider. */\nexport interface FlyAppInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name (globally unique). Used as the resource identifier. */\n\tname: string;\n\n\t/** Org slug used only when creating a new app. */\n\torganization?: string;\n}\n\n/** Persisted state for the Fly app. */\ninterface FlyAppOutputs extends FlyAppInputs {\n\t/** App identifier — equals the app name (all child paths key off the name). */\n\tappId: string;\n}\n\n/** Get-app response (only the fields we read). */\ninterface FlyAppResponse {\n\tid: string;\n\tname: string;\n}\n\n/**\n * Dynamic provider implementing adopt-or-create for Fly apps.\n *\n * `create()` does `GET /v1/apps/{name}`; if found it adopts, otherwise it\n * `POST /v1/apps`. `delete()` is a no-op — deleting a Fly app destroys\n * everything in it, so (like Railway/Neon/Vercel top-level resources) Pulumi\n * does not delete apps.\n */\nclass FlyAppResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlyAppInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\t\tconst existing = await client.tryGet<FlyAppResponse>(\n\t\t\t`/v1/apps/${inputs.name}`,\n\t\t);\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(`Adopting existing Fly app \"${inputs.name}\"`);\n\t\t} else {\n\t\t\tif (!inputs.organization) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`FlyApp \"${inputs.name}\": an organization is required to create a new app — set it on FlyProvider or FlyApp args`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tpulumi.log.info(`Fly app \"${inputs.name}\" not found — creating...`);\n\n\t\t\tawait client.post(\"/v1/apps\", {\n\t\t\t\tapp_name: inputs.name,\n\t\t\t\torg_slug: inputs.organization,\n\t\t\t});\n\t\t}\n\n\t\tconst outs: FlyAppOutputs = { ...inputs, appId: inputs.name };\n\n\t\treturn { id: inputs.name, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyAppOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new FlyClient(props.token);\n\t\tconst app = await client.tryGet<FlyAppResponse>(`/v1/apps/${id}`);\n\n\t\tif (!app) {\n\t\t\tthrow new Error(`Fly app \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn { id, props: { ...props, name: app.name, appId: app.name } };\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\t_olds: FlyAppOutputs,\n\t\tnews: FlyAppInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\treturn { outs: { ...news, appId: id } };\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Fly app deletion skipped — apps are not deleted by Pulumi (would destroy all contained resources)\",\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyAppOutputs,\n\t\tnews: FlyAppInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\t\tif (olds.organization !== news.organization) {\n\t\t\treplaces.push(\"organization\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyAppResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly appId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\torganization?: pulumi.Input<string | undefined>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyAppResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, appId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyApp — replaces Pulumi's native `provider` field. */\ntype FlyAppOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n};\n\n/** Args for FlyApp. */\nexport interface FlyAppArgs {\n\t/** App name (globally unique). Used for adoption lookup and as `.id`. */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * Org slug for app creation. Overrides `FlyProvider.organization`.\n\t * Ignored when the app already exists (adoption).\n\t */\n\torganization?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Fly app with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const app = new FlyApp(\"api\", { name: \"rby-api\" }, { provider });\n * ```\n */\nexport class FlyApp extends pulumi.ComponentResource {\n\t/** App identifier (equals the app name). */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyAppArgs, opts: FlyAppOptions) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:App\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyAppResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tname: args.name,\n\t\t\t\torganization: args.organization ?? provider.organization,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.appId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;AAqCA,IAAM,yBAAN,MAAwE;CACvE,MAAM,OAAO,QAA4D;EACxE,MAAM,SAAS,IAAI,UAAU,OAAO,KAAK;EAKzC,IAAI,MAJmB,OAAO,OAC7B,YAAY,OAAO,MACpB,GAGC,OAAO,IAAI,KAAK,8BAA8B,OAAO,KAAK,EAAE;OACtD;GACN,IAAI,CAAC,OAAO,cACX,MAAM,IAAI,MACT,WAAW,OAAO,KAAK,0FACxB;GAGD,OAAO,IAAI,KAAK,YAAY,OAAO,KAAK,0BAA0B;GAElE,MAAM,OAAO,KAAK,YAAY;IAC7B,UAAU,OAAO;IACjB,UAAU,OAAO;GAClB,CAAC;EACF;EAEA,MAAM,OAAsB;GAAE,GAAG;GAAQ,OAAO,OAAO;EAAK;EAE5D,OAAO;GAAE,IAAI,OAAO;GAAM;EAAK;CAChC;CAEA,MAAM,KACL,IACA,OACqC;EAErC,MAAM,MAAM,MAAM,IADC,UAAU,MAAM,KACZ,EAAE,OAAuB,YAAY,IAAI;EAEhE,IAAI,CAAC,KACJ,MAAM,IAAI,MAAM,YAAY,GAAG,2BAA2B;EAG3D,OAAO;GAAE;GAAI,OAAO;IAAE,GAAG;IAAO,MAAM,IAAI;IAAM,OAAO,IAAI;GAAK;EAAE;CACnE;CAEA,MAAM,OACL,IACA,OACA,MACuC;EACvC,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM,OAAO;EAAG,EAAE;CACvC;CAEA,MAAM,SAAwB;EAC7B,OAAO,IAAI,KACV,mGACD;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAErB,IAAI,KAAK,iBAAiB,KAAK,cAC9B,SAAS,KAAK,cAAc;EAG7B,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,iBAAN,cAA6B,OAAO,QAAQ,SAAS;CAGpD,YACC,MACA,MAKA,MACC;EACD,MACC,IAAI,uBAAuB,GAC3B,MACA;GAAE,GAAG;GAAM,OAAO;EAAU,GAC5B,IACD;CACD;AACD;;;;;;;;;AA4BA,IAAa,SAAb,cAA4B,OAAO,kBAAkB;CAIpD,YAAY,MAAc,MAAkB,MAAqB;EAChE,MAAM,EAAE,UAAU,GAAG,eAAe;EAEpC,MAAM,sBAAsB,MAAM,CAAC,GAAG,UAAU;EAEhD,MAAM,WAAW,IAAI,eACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,MAAM,KAAK;GACX,cAAc,KAAK,gBAAgB,SAAS;EAC7C,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"certificate.cjs","names":["FlyClient","pulumi"],"sources":["../../src/fly/certificate.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app.js\";\nimport { FlyClient } from \"./client.js\";\nimport type { FlyProvider } from \"./provider.js\";\n\n/** DNS records the consumer must create for certificate validation. */\nexport interface FlyDnsRequirements {\n\t/** ACME challenge CNAME record. */\n\tacme_challenge?: { name: string; target: string };\n\n\t/** `_fly-ownership` TXT record. */\n\townership?: { name: string; app_value: string };\n\n\t/** CNAME target for the hostname itself. */\n\tcname?: string;\n}\n\n/** Resolved inputs for the Fly certificate dynamic provider. */\nexport interface FlyCertificateInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name the certificate belongs to. */\n\tappName: string;\n\n\t/** Hostname to issue an ACME certificate for. Used as the resource key. */\n\thostname: string;\n}\n\n/** Persisted state for the Fly certificate. */\ninterface FlyCertificateOutputs extends FlyCertificateInputs {\n\t/** Whether the certificate is fully provisioned (DNS correct). */\n\tconfigured: boolean;\n\n\t/** DNS records required for validation. */\n\tdnsRequirements: FlyDnsRequirements;\n}\n\n/** Certificate response (only the fields we read). */\ninterface FlyCertificateResponse {\n\thostname: string;\n\tconfigured: boolean;\n\tdns_requirements?: FlyDnsRequirements;\n}\n\n/**\n * Dynamic provider for Fly ACME (Let's Encrypt) certificates. `create()` checks\n * for an existing cert by hostname and adopts it, otherwise it requests one via\n * `POST /v1/apps/{app}/certificates/acme`. The Machines API returns no `id` —\n * the hostname is the resource key.\n */\nclass FlyCertificateResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: FlyCertificateInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\t\tconst path = `/v1/apps/${inputs.appName}/certificates/${encodeURIComponent(inputs.hostname)}`;\n\n\t\tlet cert = await client.tryGet<FlyCertificateResponse>(path);\n\n\t\tif (cert) {\n\t\t\tpulumi.log.info(`Adopting existing Fly certificate \"${inputs.hostname}\"`);\n\t\t} else {\n\t\t\tcert = await client.post<FlyCertificateResponse>(\n\t\t\t\t`/v1/apps/${inputs.appName}/certificates/acme`,\n\t\t\t\t{ hostname: inputs.hostname },\n\t\t\t);\n\t\t}\n\n\t\tconst outs: FlyCertificateOutputs = {\n\t\t\t...inputs,\n\t\t\tconfigured: cert.configured ?? false,\n\t\t\tdnsRequirements: cert.dns_requirements ?? {},\n\t\t};\n\n\t\treturn { id: inputs.hostname, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyCertificateOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst cert = await client.tryGet<FlyCertificateResponse>(\n\t\t\t`/v1/apps/${props.appName}/certificates/${encodeURIComponent(id)}`,\n\t\t);\n\n\t\tif (!cert) {\n\t\t\tthrow new Error(`Fly certificate \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tconfigured: cert.configured ?? false,\n\t\t\t\tdnsRequirements: cert.dns_requirements ?? {},\n\t\t\t},\n\t\t};\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\t_olds: FlyCertificateOutputs,\n\t\tnews: FlyCertificateInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\t// Hostname/app changes force replacement (see diff); nothing else is updatable.\n\t\treturn {\n\t\t\touts: { ...news, configured: false, dnsRequirements: {} },\n\t\t};\n\t}\n\n\tasync delete(id: string, props: FlyCertificateOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tawait client.delete(\n\t\t\t`/v1/apps/${props.appName}/certificates/${encodeURIComponent(id)}`,\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyCertificateOutputs,\n\t\tnews: FlyCertificateInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\t\tif (olds.hostname !== news.hostname) {\n\t\t\treplaces.push(\"hostname\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyCertificateResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly configured: pulumi.Output<boolean>;\n\tpublic declare readonly dnsRequirements: pulumi.Output<FlyDnsRequirements>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\thostname: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyCertificateResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, configured: undefined, dnsRequirements: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyCertificate. */\ntype FlyCertificateOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the certificate belongs to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyCertificate. */\nexport interface FlyCertificateArgs {\n\t/** Hostname to issue an ACME certificate for (e.g. `\"api.example.com\"`). */\n\thostname: pulumi.Input<string>;\n}\n\n/**\n * Manages a Fly ACME certificate for a custom hostname.\n *\n * Exposes `.configured` and `.dnsRequirements` so the consumer can wire up the\n * required DNS records.\n *\n * @example\n * ```typescript\n * const cert = new FlyCertificate(\"api-cert\", {\n * hostname: \"api.example.com\",\n * }, { provider, app });\n * ```\n */\nexport class FlyCertificate extends pulumi.ComponentResource {\n\t/** Certificate identifier (equals the hostname). */\n\tpublic readonly id: pulumi.Output<string>;\n\n\t/** Whether the certificate is fully provisioned. */\n\tpublic readonly configured: pulumi.Output<boolean>;\n\n\t/** DNS records required for validation. */\n\tpublic readonly dnsRequirements: pulumi.Output<FlyDnsRequirements>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: FlyCertificateArgs,\n\t\topts: FlyCertificateOptions,\n\t) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Certificate\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyCertificateResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\thostname: args.hostname,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = pulumi.output(args.hostname);\n\t\tthis.configured = resource.configured;\n\t\tthis.dnsRequirements = resource.dnsRequirements;\n\n\t\tthis.registerOutputs({\n\t\t\tid: this.id,\n\t\t\tconfigured: this.configured,\n\t\t\tdnsRequirements: this.dnsRequirements,\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;AAoDA,IAAM,iCAAN,MAEA;CACC,MAAM,OACL,QACuC;EACvC,MAAM,SAAS,IAAIA,6BAAU,OAAO,KAAK;EACzC,MAAM,OAAO,YAAY,OAAO,QAAQ,gBAAgB,mBAAmB,OAAO,QAAQ;EAE1F,IAAI,OAAO,MAAM,OAAO,OAA+B,IAAI;EAE3D,IAAI,MACH,eAAO,IAAI,KAAK,sCAAsC,OAAO,SAAS,EAAE;OAExE,OAAO,MAAM,OAAO,KACnB,YAAY,OAAO,QAAQ,qBAC3B,EAAE,UAAU,OAAO,SAAS,CAC7B;EAGD,MAAM,OAA8B;GACnC,GAAG;GACH,YAAY,KAAK,cAAc;GAC/B,iBAAiB,KAAK,oBAAoB,CAAC;EAC5C;EAEA,OAAO;GAAE,IAAI,OAAO;GAAU;EAAK;CACpC;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,OAAO,MAAM,IAFAA,6BAAU,MAAM,KAEX,EAAE,OACzB,YAAY,MAAM,QAAQ,gBAAgB,mBAAmB,EAAE,GAChE;EAEA,IAAI,CAAC,MACJ,MAAM,IAAI,MAAM,oBAAoB,GAAG,2BAA2B;EAGnE,OAAO;GACN;GACA,OAAO;IACN,GAAG;IACH,YAAY,KAAK,cAAc;IAC/B,iBAAiB,KAAK,oBAAoB,CAAC;GAC5C;EACD;CACD;CAEA,MAAM,OACL,KACA,OACA,MACuC;EAEvC,OAAO,EACN,MAAM;GAAE,GAAG;GAAM,YAAY;GAAO,iBAAiB,CAAC;EAAE,EACzD;CACD;CAEA,MAAM,OAAO,IAAY,OAA6C;EAGrE,MAAM,IAFaA,6BAAU,MAAM,KAExB,EAAE,OACZ,YAAY,MAAM,QAAQ,gBAAgB,mBAAmB,EAAE,GAChE;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAExB,IAAI,KAAK,aAAa,KAAK,UAC1B,SAAS,KAAK,UAAU;EAGzB,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,yBAAN,cAAqCC,eAAO,QAAQ,SAAS;CAI5D,YACC,MACA,MAKA,MACC;EACD,MACC,IAAI,+BAA+B,GACnC,MACA;GAAE,GAAG;GAAM,YAAY;GAAW,iBAAiB;EAAU,GAC7D,IACD;CACD;AACD;;;;;;;;;;;;;;AAiCA,IAAa,iBAAb,cAAoCA,eAAO,kBAAkB;CAU5D,YACC,MACA,MACA,MACC;EACD,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,8BAA8B,MAAM,CAAC,GAAG,UAAU;EAExD,MAAM,WAAW,IAAI,uBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,UAAU,KAAK;EAChB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAKA,eAAO,OAAO,KAAK,QAAQ;EACrC,KAAK,aAAa,SAAS;EAC3B,KAAK,kBAAkB,SAAS;EAEhC,KAAK,gBAAgB;GACpB,IAAI,KAAK;GACT,YAAY,KAAK;GACjB,iBAAiB,KAAK;EACvB,CAAC;CACF;AACD"}
1
+ {"version":3,"file":"certificate.cjs","names":["FlyClient","pulumi"],"sources":["../../src/fly/certificate.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app\";\nimport { FlyClient } from \"./client\";\nimport type { FlyProvider } from \"./provider\";\n\n/** DNS records the consumer must create for certificate validation. */\nexport interface FlyDnsRequirements {\n\t/** ACME challenge CNAME record. */\n\tacme_challenge?: { name: string; target: string };\n\n\t/** `_fly-ownership` TXT record. */\n\townership?: { name: string; app_value: string };\n\n\t/** CNAME target for the hostname itself. */\n\tcname?: string;\n}\n\n/** Resolved inputs for the Fly certificate dynamic provider. */\nexport interface FlyCertificateInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name the certificate belongs to. */\n\tappName: string;\n\n\t/** Hostname to issue an ACME certificate for. Used as the resource key. */\n\thostname: string;\n}\n\n/** Persisted state for the Fly certificate. */\ninterface FlyCertificateOutputs extends FlyCertificateInputs {\n\t/** Whether the certificate is fully provisioned (DNS correct). */\n\tconfigured: boolean;\n\n\t/** DNS records required for validation. */\n\tdnsRequirements: FlyDnsRequirements;\n}\n\n/** Certificate response (only the fields we read). */\ninterface FlyCertificateResponse {\n\thostname: string;\n\tconfigured: boolean;\n\tdns_requirements?: FlyDnsRequirements;\n}\n\n/**\n * Dynamic provider for Fly ACME (Let's Encrypt) certificates. `create()` checks\n * for an existing cert by hostname and adopts it, otherwise it requests one via\n * `POST /v1/apps/{app}/certificates/acme`. The Machines API returns no `id` —\n * the hostname is the resource key.\n */\nclass FlyCertificateResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: FlyCertificateInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\t\tconst path = `/v1/apps/${inputs.appName}/certificates/${encodeURIComponent(inputs.hostname)}`;\n\n\t\tlet cert = await client.tryGet<FlyCertificateResponse>(path);\n\n\t\tif (cert) {\n\t\t\tpulumi.log.info(`Adopting existing Fly certificate \"${inputs.hostname}\"`);\n\t\t} else {\n\t\t\tcert = await client.post<FlyCertificateResponse>(\n\t\t\t\t`/v1/apps/${inputs.appName}/certificates/acme`,\n\t\t\t\t{ hostname: inputs.hostname },\n\t\t\t);\n\t\t}\n\n\t\tconst outs: FlyCertificateOutputs = {\n\t\t\t...inputs,\n\t\t\tconfigured: cert.configured ?? false,\n\t\t\tdnsRequirements: cert.dns_requirements ?? {},\n\t\t};\n\n\t\treturn { id: inputs.hostname, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyCertificateOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst cert = await client.tryGet<FlyCertificateResponse>(\n\t\t\t`/v1/apps/${props.appName}/certificates/${encodeURIComponent(id)}`,\n\t\t);\n\n\t\tif (!cert) {\n\t\t\tthrow new Error(`Fly certificate \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tconfigured: cert.configured ?? false,\n\t\t\t\tdnsRequirements: cert.dns_requirements ?? {},\n\t\t\t},\n\t\t};\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\t_olds: FlyCertificateOutputs,\n\t\tnews: FlyCertificateInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\t// Hostname/app changes force replacement (see diff); nothing else is updatable.\n\t\treturn {\n\t\t\touts: { ...news, configured: false, dnsRequirements: {} },\n\t\t};\n\t}\n\n\tasync delete(id: string, props: FlyCertificateOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tawait client.delete(\n\t\t\t`/v1/apps/${props.appName}/certificates/${encodeURIComponent(id)}`,\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyCertificateOutputs,\n\t\tnews: FlyCertificateInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\t\tif (olds.hostname !== news.hostname) {\n\t\t\treplaces.push(\"hostname\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyCertificateResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly configured: pulumi.Output<boolean>;\n\tpublic declare readonly dnsRequirements: pulumi.Output<FlyDnsRequirements>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\thostname: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyCertificateResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, configured: undefined, dnsRequirements: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyCertificate. */\ntype FlyCertificateOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the certificate belongs to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyCertificate. */\nexport interface FlyCertificateArgs {\n\t/** Hostname to issue an ACME certificate for (e.g. `\"api.example.com\"`). */\n\thostname: pulumi.Input<string>;\n}\n\n/**\n * Manages a Fly ACME certificate for a custom hostname.\n *\n * Exposes `.configured` and `.dnsRequirements` so the consumer can wire up the\n * required DNS records.\n *\n * @example\n * ```typescript\n * const cert = new FlyCertificate(\"api-cert\", {\n * hostname: \"api.example.com\",\n * }, { provider, app });\n * ```\n */\nexport class FlyCertificate extends pulumi.ComponentResource {\n\t/** Certificate identifier (equals the hostname). */\n\tpublic readonly id: pulumi.Output<string>;\n\n\t/** Whether the certificate is fully provisioned. */\n\tpublic readonly configured: pulumi.Output<boolean>;\n\n\t/** DNS records required for validation. */\n\tpublic readonly dnsRequirements: pulumi.Output<FlyDnsRequirements>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: FlyCertificateArgs,\n\t\topts: FlyCertificateOptions,\n\t) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Certificate\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyCertificateResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\thostname: args.hostname,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = pulumi.output(args.hostname);\n\t\tthis.configured = resource.configured;\n\t\tthis.dnsRequirements = resource.dnsRequirements;\n\n\t\tthis.registerOutputs({\n\t\t\tid: this.id,\n\t\t\tconfigured: this.configured,\n\t\t\tdnsRequirements: this.dnsRequirements,\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;AAoDA,IAAM,iCAAN,MAEA;CACC,MAAM,OACL,QACuC;EACvC,MAAM,SAAS,IAAIA,6BAAU,OAAO,KAAK;EACzC,MAAM,OAAO,YAAY,OAAO,QAAQ,gBAAgB,mBAAmB,OAAO,QAAQ;EAE1F,IAAI,OAAO,MAAM,OAAO,OAA+B,IAAI;EAE3D,IAAI,MACH,eAAO,IAAI,KAAK,sCAAsC,OAAO,SAAS,EAAE;OAExE,OAAO,MAAM,OAAO,KACnB,YAAY,OAAO,QAAQ,qBAC3B,EAAE,UAAU,OAAO,SAAS,CAC7B;EAGD,MAAM,OAA8B;GACnC,GAAG;GACH,YAAY,KAAK,cAAc;GAC/B,iBAAiB,KAAK,oBAAoB,CAAC;EAC5C;EAEA,OAAO;GAAE,IAAI,OAAO;GAAU;EAAK;CACpC;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,OAAO,MAAM,IAFAA,6BAAU,MAAM,KAEX,EAAE,OACzB,YAAY,MAAM,QAAQ,gBAAgB,mBAAmB,EAAE,GAChE;EAEA,IAAI,CAAC,MACJ,MAAM,IAAI,MAAM,oBAAoB,GAAG,2BAA2B;EAGnE,OAAO;GACN;GACA,OAAO;IACN,GAAG;IACH,YAAY,KAAK,cAAc;IAC/B,iBAAiB,KAAK,oBAAoB,CAAC;GAC5C;EACD;CACD;CAEA,MAAM,OACL,KACA,OACA,MACuC;EAEvC,OAAO,EACN,MAAM;GAAE,GAAG;GAAM,YAAY;GAAO,iBAAiB,CAAC;EAAE,EACzD;CACD;CAEA,MAAM,OAAO,IAAY,OAA6C;EAGrE,MAAM,IAFaA,6BAAU,MAAM,KAExB,EAAE,OACZ,YAAY,MAAM,QAAQ,gBAAgB,mBAAmB,EAAE,GAChE;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAExB,IAAI,KAAK,aAAa,KAAK,UAC1B,SAAS,KAAK,UAAU;EAGzB,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,yBAAN,cAAqCC,eAAO,QAAQ,SAAS;CAI5D,YACC,MACA,MAKA,MACC;EACD,MACC,IAAI,+BAA+B,GACnC,MACA;GAAE,GAAG;GAAM,YAAY;GAAW,iBAAiB;EAAU,GAC7D,IACD;CACD;AACD;;;;;;;;;;;;;;AAiCA,IAAa,iBAAb,cAAoCA,eAAO,kBAAkB;CAU5D,YACC,MACA,MACA,MACC;EACD,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,8BAA8B,MAAM,CAAC,GAAG,UAAU;EAExD,MAAM,WAAW,IAAI,uBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,UAAU,KAAK;EAChB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAKA,eAAO,OAAO,KAAK,QAAQ;EACrC,KAAK,aAAa,SAAS;EAC3B,KAAK,kBAAkB,SAAS;EAEhC,KAAK,gBAAgB;GACpB,IAAI,KAAK;GACT,YAAY,KAAK;GACjB,iBAAiB,KAAK;EACvB,CAAC;CACF;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"certificate.mjs","names":[],"sources":["../../src/fly/certificate.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app.js\";\nimport { FlyClient } from \"./client.js\";\nimport type { FlyProvider } from \"./provider.js\";\n\n/** DNS records the consumer must create for certificate validation. */\nexport interface FlyDnsRequirements {\n\t/** ACME challenge CNAME record. */\n\tacme_challenge?: { name: string; target: string };\n\n\t/** `_fly-ownership` TXT record. */\n\townership?: { name: string; app_value: string };\n\n\t/** CNAME target for the hostname itself. */\n\tcname?: string;\n}\n\n/** Resolved inputs for the Fly certificate dynamic provider. */\nexport interface FlyCertificateInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name the certificate belongs to. */\n\tappName: string;\n\n\t/** Hostname to issue an ACME certificate for. Used as the resource key. */\n\thostname: string;\n}\n\n/** Persisted state for the Fly certificate. */\ninterface FlyCertificateOutputs extends FlyCertificateInputs {\n\t/** Whether the certificate is fully provisioned (DNS correct). */\n\tconfigured: boolean;\n\n\t/** DNS records required for validation. */\n\tdnsRequirements: FlyDnsRequirements;\n}\n\n/** Certificate response (only the fields we read). */\ninterface FlyCertificateResponse {\n\thostname: string;\n\tconfigured: boolean;\n\tdns_requirements?: FlyDnsRequirements;\n}\n\n/**\n * Dynamic provider for Fly ACME (Let's Encrypt) certificates. `create()` checks\n * for an existing cert by hostname and adopts it, otherwise it requests one via\n * `POST /v1/apps/{app}/certificates/acme`. The Machines API returns no `id` —\n * the hostname is the resource key.\n */\nclass FlyCertificateResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: FlyCertificateInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\t\tconst path = `/v1/apps/${inputs.appName}/certificates/${encodeURIComponent(inputs.hostname)}`;\n\n\t\tlet cert = await client.tryGet<FlyCertificateResponse>(path);\n\n\t\tif (cert) {\n\t\t\tpulumi.log.info(`Adopting existing Fly certificate \"${inputs.hostname}\"`);\n\t\t} else {\n\t\t\tcert = await client.post<FlyCertificateResponse>(\n\t\t\t\t`/v1/apps/${inputs.appName}/certificates/acme`,\n\t\t\t\t{ hostname: inputs.hostname },\n\t\t\t);\n\t\t}\n\n\t\tconst outs: FlyCertificateOutputs = {\n\t\t\t...inputs,\n\t\t\tconfigured: cert.configured ?? false,\n\t\t\tdnsRequirements: cert.dns_requirements ?? {},\n\t\t};\n\n\t\treturn { id: inputs.hostname, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyCertificateOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst cert = await client.tryGet<FlyCertificateResponse>(\n\t\t\t`/v1/apps/${props.appName}/certificates/${encodeURIComponent(id)}`,\n\t\t);\n\n\t\tif (!cert) {\n\t\t\tthrow new Error(`Fly certificate \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tconfigured: cert.configured ?? false,\n\t\t\t\tdnsRequirements: cert.dns_requirements ?? {},\n\t\t\t},\n\t\t};\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\t_olds: FlyCertificateOutputs,\n\t\tnews: FlyCertificateInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\t// Hostname/app changes force replacement (see diff); nothing else is updatable.\n\t\treturn {\n\t\t\touts: { ...news, configured: false, dnsRequirements: {} },\n\t\t};\n\t}\n\n\tasync delete(id: string, props: FlyCertificateOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tawait client.delete(\n\t\t\t`/v1/apps/${props.appName}/certificates/${encodeURIComponent(id)}`,\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyCertificateOutputs,\n\t\tnews: FlyCertificateInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\t\tif (olds.hostname !== news.hostname) {\n\t\t\treplaces.push(\"hostname\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyCertificateResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly configured: pulumi.Output<boolean>;\n\tpublic declare readonly dnsRequirements: pulumi.Output<FlyDnsRequirements>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\thostname: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyCertificateResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, configured: undefined, dnsRequirements: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyCertificate. */\ntype FlyCertificateOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the certificate belongs to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyCertificate. */\nexport interface FlyCertificateArgs {\n\t/** Hostname to issue an ACME certificate for (e.g. `\"api.example.com\"`). */\n\thostname: pulumi.Input<string>;\n}\n\n/**\n * Manages a Fly ACME certificate for a custom hostname.\n *\n * Exposes `.configured` and `.dnsRequirements` so the consumer can wire up the\n * required DNS records.\n *\n * @example\n * ```typescript\n * const cert = new FlyCertificate(\"api-cert\", {\n * hostname: \"api.example.com\",\n * }, { provider, app });\n * ```\n */\nexport class FlyCertificate extends pulumi.ComponentResource {\n\t/** Certificate identifier (equals the hostname). */\n\tpublic readonly id: pulumi.Output<string>;\n\n\t/** Whether the certificate is fully provisioned. */\n\tpublic readonly configured: pulumi.Output<boolean>;\n\n\t/** DNS records required for validation. */\n\tpublic readonly dnsRequirements: pulumi.Output<FlyDnsRequirements>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: FlyCertificateArgs,\n\t\topts: FlyCertificateOptions,\n\t) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Certificate\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyCertificateResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\thostname: args.hostname,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = pulumi.output(args.hostname);\n\t\tthis.configured = resource.configured;\n\t\tthis.dnsRequirements = resource.dnsRequirements;\n\n\t\tthis.registerOutputs({\n\t\t\tid: this.id,\n\t\t\tconfigured: this.configured,\n\t\t\tdnsRequirements: this.dnsRequirements,\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;;;;AAoDA,IAAM,iCAAN,MAEA;CACC,MAAM,OACL,QACuC;EACvC,MAAM,SAAS,IAAI,UAAU,OAAO,KAAK;EACzC,MAAM,OAAO,YAAY,OAAO,QAAQ,gBAAgB,mBAAmB,OAAO,QAAQ;EAE1F,IAAI,OAAO,MAAM,OAAO,OAA+B,IAAI;EAE3D,IAAI,MACH,OAAO,IAAI,KAAK,sCAAsC,OAAO,SAAS,EAAE;OAExE,OAAO,MAAM,OAAO,KACnB,YAAY,OAAO,QAAQ,qBAC3B,EAAE,UAAU,OAAO,SAAS,CAC7B;EAGD,MAAM,OAA8B;GACnC,GAAG;GACH,YAAY,KAAK,cAAc;GAC/B,iBAAiB,KAAK,oBAAoB,CAAC;EAC5C;EAEA,OAAO;GAAE,IAAI,OAAO;GAAU;EAAK;CACpC;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,OAAO,MAAM,IAFA,UAAU,MAAM,KAEX,EAAE,OACzB,YAAY,MAAM,QAAQ,gBAAgB,mBAAmB,EAAE,GAChE;EAEA,IAAI,CAAC,MACJ,MAAM,IAAI,MAAM,oBAAoB,GAAG,2BAA2B;EAGnE,OAAO;GACN;GACA,OAAO;IACN,GAAG;IACH,YAAY,KAAK,cAAc;IAC/B,iBAAiB,KAAK,oBAAoB,CAAC;GAC5C;EACD;CACD;CAEA,MAAM,OACL,KACA,OACA,MACuC;EAEvC,OAAO,EACN,MAAM;GAAE,GAAG;GAAM,YAAY;GAAO,iBAAiB,CAAC;EAAE,EACzD;CACD;CAEA,MAAM,OAAO,IAAY,OAA6C;EAGrE,MAAM,IAFa,UAAU,MAAM,KAExB,EAAE,OACZ,YAAY,MAAM,QAAQ,gBAAgB,mBAAmB,EAAE,GAChE;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAExB,IAAI,KAAK,aAAa,KAAK,UAC1B,SAAS,KAAK,UAAU;EAGzB,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,yBAAN,cAAqC,OAAO,QAAQ,SAAS;CAI5D,YACC,MACA,MAKA,MACC;EACD,MACC,IAAI,+BAA+B,GACnC,MACA;GAAE,GAAG;GAAM,YAAY;GAAW,iBAAiB;EAAU,GAC7D,IACD;CACD;AACD;;;;;;;;;;;;;;AAiCA,IAAa,iBAAb,cAAoC,OAAO,kBAAkB;CAU5D,YACC,MACA,MACA,MACC;EACD,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,8BAA8B,MAAM,CAAC,GAAG,UAAU;EAExD,MAAM,WAAW,IAAI,uBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,UAAU,KAAK;EAChB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,OAAO,OAAO,KAAK,QAAQ;EACrC,KAAK,aAAa,SAAS;EAC3B,KAAK,kBAAkB,SAAS;EAEhC,KAAK,gBAAgB;GACpB,IAAI,KAAK;GACT,YAAY,KAAK;GACjB,iBAAiB,KAAK;EACvB,CAAC;CACF;AACD"}
1
+ {"version":3,"file":"certificate.mjs","names":[],"sources":["../../src/fly/certificate.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app\";\nimport { FlyClient } from \"./client\";\nimport type { FlyProvider } from \"./provider\";\n\n/** DNS records the consumer must create for certificate validation. */\nexport interface FlyDnsRequirements {\n\t/** ACME challenge CNAME record. */\n\tacme_challenge?: { name: string; target: string };\n\n\t/** `_fly-ownership` TXT record. */\n\townership?: { name: string; app_value: string };\n\n\t/** CNAME target for the hostname itself. */\n\tcname?: string;\n}\n\n/** Resolved inputs for the Fly certificate dynamic provider. */\nexport interface FlyCertificateInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name the certificate belongs to. */\n\tappName: string;\n\n\t/** Hostname to issue an ACME certificate for. Used as the resource key. */\n\thostname: string;\n}\n\n/** Persisted state for the Fly certificate. */\ninterface FlyCertificateOutputs extends FlyCertificateInputs {\n\t/** Whether the certificate is fully provisioned (DNS correct). */\n\tconfigured: boolean;\n\n\t/** DNS records required for validation. */\n\tdnsRequirements: FlyDnsRequirements;\n}\n\n/** Certificate response (only the fields we read). */\ninterface FlyCertificateResponse {\n\thostname: string;\n\tconfigured: boolean;\n\tdns_requirements?: FlyDnsRequirements;\n}\n\n/**\n * Dynamic provider for Fly ACME (Let's Encrypt) certificates. `create()` checks\n * for an existing cert by hostname and adopts it, otherwise it requests one via\n * `POST /v1/apps/{app}/certificates/acme`. The Machines API returns no `id` —\n * the hostname is the resource key.\n */\nclass FlyCertificateResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: FlyCertificateInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\t\tconst path = `/v1/apps/${inputs.appName}/certificates/${encodeURIComponent(inputs.hostname)}`;\n\n\t\tlet cert = await client.tryGet<FlyCertificateResponse>(path);\n\n\t\tif (cert) {\n\t\t\tpulumi.log.info(`Adopting existing Fly certificate \"${inputs.hostname}\"`);\n\t\t} else {\n\t\t\tcert = await client.post<FlyCertificateResponse>(\n\t\t\t\t`/v1/apps/${inputs.appName}/certificates/acme`,\n\t\t\t\t{ hostname: inputs.hostname },\n\t\t\t);\n\t\t}\n\n\t\tconst outs: FlyCertificateOutputs = {\n\t\t\t...inputs,\n\t\t\tconfigured: cert.configured ?? false,\n\t\t\tdnsRequirements: cert.dns_requirements ?? {},\n\t\t};\n\n\t\treturn { id: inputs.hostname, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyCertificateOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst cert = await client.tryGet<FlyCertificateResponse>(\n\t\t\t`/v1/apps/${props.appName}/certificates/${encodeURIComponent(id)}`,\n\t\t);\n\n\t\tif (!cert) {\n\t\t\tthrow new Error(`Fly certificate \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tconfigured: cert.configured ?? false,\n\t\t\t\tdnsRequirements: cert.dns_requirements ?? {},\n\t\t\t},\n\t\t};\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\t_olds: FlyCertificateOutputs,\n\t\tnews: FlyCertificateInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\t// Hostname/app changes force replacement (see diff); nothing else is updatable.\n\t\treturn {\n\t\t\touts: { ...news, configured: false, dnsRequirements: {} },\n\t\t};\n\t}\n\n\tasync delete(id: string, props: FlyCertificateOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tawait client.delete(\n\t\t\t`/v1/apps/${props.appName}/certificates/${encodeURIComponent(id)}`,\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyCertificateOutputs,\n\t\tnews: FlyCertificateInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\t\tif (olds.hostname !== news.hostname) {\n\t\t\treplaces.push(\"hostname\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyCertificateResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly configured: pulumi.Output<boolean>;\n\tpublic declare readonly dnsRequirements: pulumi.Output<FlyDnsRequirements>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\thostname: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyCertificateResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, configured: undefined, dnsRequirements: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyCertificate. */\ntype FlyCertificateOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the certificate belongs to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyCertificate. */\nexport interface FlyCertificateArgs {\n\t/** Hostname to issue an ACME certificate for (e.g. `\"api.example.com\"`). */\n\thostname: pulumi.Input<string>;\n}\n\n/**\n * Manages a Fly ACME certificate for a custom hostname.\n *\n * Exposes `.configured` and `.dnsRequirements` so the consumer can wire up the\n * required DNS records.\n *\n * @example\n * ```typescript\n * const cert = new FlyCertificate(\"api-cert\", {\n * hostname: \"api.example.com\",\n * }, { provider, app });\n * ```\n */\nexport class FlyCertificate extends pulumi.ComponentResource {\n\t/** Certificate identifier (equals the hostname). */\n\tpublic readonly id: pulumi.Output<string>;\n\n\t/** Whether the certificate is fully provisioned. */\n\tpublic readonly configured: pulumi.Output<boolean>;\n\n\t/** DNS records required for validation. */\n\tpublic readonly dnsRequirements: pulumi.Output<FlyDnsRequirements>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: FlyCertificateArgs,\n\t\topts: FlyCertificateOptions,\n\t) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Certificate\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyCertificateResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\thostname: args.hostname,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = pulumi.output(args.hostname);\n\t\tthis.configured = resource.configured;\n\t\tthis.dnsRequirements = resource.dnsRequirements;\n\n\t\tthis.registerOutputs({\n\t\t\tid: this.id,\n\t\t\tconfigured: this.configured,\n\t\t\tdnsRequirements: this.dnsRequirements,\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;;;;AAoDA,IAAM,iCAAN,MAEA;CACC,MAAM,OACL,QACuC;EACvC,MAAM,SAAS,IAAI,UAAU,OAAO,KAAK;EACzC,MAAM,OAAO,YAAY,OAAO,QAAQ,gBAAgB,mBAAmB,OAAO,QAAQ;EAE1F,IAAI,OAAO,MAAM,OAAO,OAA+B,IAAI;EAE3D,IAAI,MACH,OAAO,IAAI,KAAK,sCAAsC,OAAO,SAAS,EAAE;OAExE,OAAO,MAAM,OAAO,KACnB,YAAY,OAAO,QAAQ,qBAC3B,EAAE,UAAU,OAAO,SAAS,CAC7B;EAGD,MAAM,OAA8B;GACnC,GAAG;GACH,YAAY,KAAK,cAAc;GAC/B,iBAAiB,KAAK,oBAAoB,CAAC;EAC5C;EAEA,OAAO;GAAE,IAAI,OAAO;GAAU;EAAK;CACpC;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,OAAO,MAAM,IAFA,UAAU,MAAM,KAEX,EAAE,OACzB,YAAY,MAAM,QAAQ,gBAAgB,mBAAmB,EAAE,GAChE;EAEA,IAAI,CAAC,MACJ,MAAM,IAAI,MAAM,oBAAoB,GAAG,2BAA2B;EAGnE,OAAO;GACN;GACA,OAAO;IACN,GAAG;IACH,YAAY,KAAK,cAAc;IAC/B,iBAAiB,KAAK,oBAAoB,CAAC;GAC5C;EACD;CACD;CAEA,MAAM,OACL,KACA,OACA,MACuC;EAEvC,OAAO,EACN,MAAM;GAAE,GAAG;GAAM,YAAY;GAAO,iBAAiB,CAAC;EAAE,EACzD;CACD;CAEA,MAAM,OAAO,IAAY,OAA6C;EAGrE,MAAM,IAFa,UAAU,MAAM,KAExB,EAAE,OACZ,YAAY,MAAM,QAAQ,gBAAgB,mBAAmB,EAAE,GAChE;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAExB,IAAI,KAAK,aAAa,KAAK,UAC1B,SAAS,KAAK,UAAU;EAGzB,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,yBAAN,cAAqC,OAAO,QAAQ,SAAS;CAI5D,YACC,MACA,MAKA,MACC;EACD,MACC,IAAI,+BAA+B,GACnC,MACA;GAAE,GAAG;GAAM,YAAY;GAAW,iBAAiB;EAAU,GAC7D,IACD;CACD;AACD;;;;;;;;;;;;;;AAiCA,IAAa,iBAAb,cAAoC,OAAO,kBAAkB;CAU5D,YACC,MACA,MACA,MACC;EACD,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,8BAA8B,MAAM,CAAC,GAAG,UAAU;EAExD,MAAM,WAAW,IAAI,uBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,UAAU,KAAK;EAChB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,OAAO,OAAO,KAAK,QAAQ;EACrC,KAAK,aAAa,SAAS;EAC3B,KAAK,kBAAkB,SAAS;EAEhC,KAAK,gBAAgB;GACpB,IAAI,KAAK;GACT,YAAY,KAAK;GACjB,iBAAiB,KAAK;EACvB,CAAC;CACF;AACD"}
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
+ const require_stable_dir = require('../stable-dir.cjs');
3
4
  const require_fly_toml = require('./toml.cjs');
4
5
  let _pulumi_command = require("@pulumi/command");
5
6
  _pulumi_command = require_chunk.__toESM(_pulumi_command, 1);
@@ -40,7 +41,7 @@ var FlyDeploy = class extends _pulumi_pulumi.ComponentResource {
40
41
  new _pulumi_command.local.Command(`${name}-deploy`, {
41
42
  create: deployCommand,
42
43
  triggers,
43
- dir: args.monorepoRoot,
44
+ dir: require_stable_dir.stableDir(args.monorepoRoot),
44
45
  environment: {
45
46
  FLY_API_TOKEN: provider.token,
46
47
  FLY_TOML_CONTENT: tomlContent
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.cjs","names":["pulumi","generateFlyToml","command"],"sources":["../../src/fly/deploy.ts"],"sourcesContent":["import * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app.js\";\nimport type { FlyProvider } from \"./provider.js\";\nimport { type FlyTomlConfig, generateFlyToml } from \"./toml.js\";\n\n/** Options type for FlyDeploy. */\ntype FlyDeployOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App to deploy into. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyDeploy. */\nexport interface FlyDeployArgs {\n\t/** Absolute path to the repo root (working directory for `fly deploy`). */\n\tmonorepoRoot: string;\n\n\t/**\n\t * fly.toml configuration. `config.app` must equal the FlyApp name. All values\n\t * are plain (not `pulumi.Input`) — resolve Outputs before constructing this.\n\t */\n\tconfig: FlyTomlConfig;\n\n\t/**\n\t * Values that force a redeploy when changed (e.g. a source hash from\n\t * `hashDirectory()` and `FlySecret.version`). The generated toml content is\n\t * appended automatically.\n\t */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\n\t/** `fly deploy --wait-timeout` in seconds (default 300). */\n\twaitTimeout?: number;\n\n\t/** `fly deploy --release-command-timeout` in seconds (default 600). */\n\treleaseCommandTimeout?: number;\n\n\t/** `fly deploy --ha` (default false). */\n\thighAvailability?: boolean;\n}\n\n/**\n * Deploys a Fly app via `fly deploy --remote-only`, driven by a generated\n * fly.toml. The toml is written by the deploy command itself (at execution\n * time, not during `pulumi preview`) to `<monorepoRoot>/.fly/<app>.toml`, and\n * its content is added to the redeploy triggers (so config changes redeploy).\n *\n * @example\n * ```typescript\n * new FlyDeploy(\"api-deploy\", {\n * monorepoRoot,\n * config: { app: \"rby-api\", primaryRegion: \"iad\", build: { dockerfile: \"apps/api/Dockerfile\" } },\n * triggers: [hashDirectory(\"apps/api\"), secrets.version],\n * }, { provider, app, dependsOn: [secrets] });\n * ```\n */\nexport class FlyDeploy extends pulumi.ComponentResource {\n\tconstructor(name: string, args: FlyDeployArgs, opts: FlyDeployOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Deploy\", name, {}, pulumiOpts);\n\n\t\tconst tomlContent = generateFlyToml(args.config);\n\t\tconst configPath = `.fly/${args.config.app}.toml`;\n\n\t\tconst waitTimeout = args.waitTimeout ?? 300;\n\t\tconst releaseCommandTimeout = args.releaseCommandTimeout ?? 600;\n\t\tconst highAvailability = args.highAvailability ?? false;\n\n\t\t// The toml is written by the command at execution time. The content\n\t\t// arrives via the FLY_TOML_CONTENT env var (avoiding shell escaping) so\n\t\t// no file is touched during `pulumi preview`.\n\t\tconst deployCommand = [\n\t\t\t\"mkdir -p .fly\",\n\t\t\t`printf '%s' \"$FLY_TOML_CONTENT\" > ${configPath}`,\n\t\t\t`fly deploy --config ${configPath} --remote-only --ha=${highAvailability} --wait-timeout ${waitTimeout} --release-command-timeout ${releaseCommandTimeout}`,\n\t\t].join(\" && \");\n\n\t\tconst triggers = pulumi\n\t\t\t.output(args.triggers)\n\t\t\t.apply((values) => [...values, tomlContent]);\n\n\t\tnew command.local.Command(\n\t\t\t`${name}-deploy`,\n\t\t\t{\n\t\t\t\tcreate: deployCommand,\n\t\t\t\ttriggers,\n\t\t\t\tdir: args.monorepoRoot,\n\t\t\t\tenvironment: {\n\t\t\t\t\tFLY_API_TOKEN: provider.token,\n\t\t\t\t\tFLY_TOML_CONTENT: tomlContent,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ parent: this, dependsOn: [app] },\n\t\t);\n\n\t\tthis.registerOutputs({});\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA2DA,IAAa,YAAb,cAA+BA,eAAO,kBAAkB;CACvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,cAAcC,iCAAgB,KAAK,MAAM;EAC/C,MAAM,aAAa,QAAQ,KAAK,OAAO,IAAI;EAE3C,MAAM,cAAc,KAAK,eAAe;EACxC,MAAM,wBAAwB,KAAK,yBAAyB;EAC5D,MAAM,mBAAmB,KAAK,oBAAoB;EAKlD,MAAM,gBAAgB;GACrB;GACA,qCAAqC;GACrC,uBAAuB,WAAW,sBAAsB,iBAAiB,kBAAkB,YAAY,6BAA6B;EACrI,EAAE,KAAK,MAAM;EAEb,MAAM,WAAWD,eACf,OAAO,KAAK,QAAQ,EACpB,OAAO,WAAW,CAAC,GAAG,QAAQ,WAAW,CAAC;EAE5C,IAAIE,gBAAQ,MAAM,QACjB,GAAG,KAAK,UACR;GACC,QAAQ;GACR;GACA,KAAK,KAAK;GACV,aAAa;IACZ,eAAe,SAAS;IACxB,kBAAkB;GACnB;EACD,GACA;GAAE,QAAQ;GAAM,WAAW,CAAC,GAAG;EAAE,CAClC;EAEA,KAAK,gBAAgB,CAAC,CAAC;CACxB;AACD"}
1
+ {"version":3,"file":"deploy.cjs","names":["pulumi","generateFlyToml","command","stableDir"],"sources":["../../src/fly/deploy.ts"],"sourcesContent":["import * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { stableDir } from \"../stable-dir\";\nimport type { FlyApp } from \"./app\";\nimport type { FlyProvider } from \"./provider\";\nimport { type FlyTomlConfig, generateFlyToml } from \"./toml\";\n\n/** Options type for FlyDeploy. */\ntype FlyDeployOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App to deploy into. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyDeploy. */\nexport interface FlyDeployArgs {\n\t/**\n\t * Absolute path to the repo root (working directory for `fly deploy`).\n\t * Stored relative to the Pulumi program directory so the command stays stable\n\t * across machines and CI (see {@link stableDir}).\n\t */\n\tmonorepoRoot: string;\n\n\t/**\n\t * fly.toml configuration. `config.app` must equal the FlyApp name. All values\n\t * are plain (not `pulumi.Input`) — resolve Outputs before constructing this.\n\t */\n\tconfig: FlyTomlConfig;\n\n\t/**\n\t * Values that force a redeploy when changed (e.g. a source hash from\n\t * `hashDirectory()` and `FlySecret.version`). The generated toml content is\n\t * appended automatically.\n\t */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\n\t/** `fly deploy --wait-timeout` in seconds (default 300). */\n\twaitTimeout?: number;\n\n\t/** `fly deploy --release-command-timeout` in seconds (default 600). */\n\treleaseCommandTimeout?: number;\n\n\t/** `fly deploy --ha` (default false). */\n\thighAvailability?: boolean;\n}\n\n/**\n * Deploys a Fly app via `fly deploy --remote-only`, driven by a generated\n * fly.toml. The toml is written by the deploy command itself (at execution\n * time, not during `pulumi preview`) to `<monorepoRoot>/.fly/<app>.toml`, and\n * its content is added to the redeploy triggers (so config changes redeploy).\n *\n * @example\n * ```typescript\n * new FlyDeploy(\"api-deploy\", {\n * monorepoRoot,\n * config: { app: \"rby-api\", primaryRegion: \"iad\", build: { dockerfile: \"apps/api/Dockerfile\" } },\n * triggers: [hashDirectory(\"apps/api\"), secrets.version],\n * }, { provider, app, dependsOn: [secrets] });\n * ```\n */\nexport class FlyDeploy extends pulumi.ComponentResource {\n\tconstructor(name: string, args: FlyDeployArgs, opts: FlyDeployOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Deploy\", name, {}, pulumiOpts);\n\n\t\tconst tomlContent = generateFlyToml(args.config);\n\t\tconst configPath = `.fly/${args.config.app}.toml`;\n\n\t\tconst waitTimeout = args.waitTimeout ?? 300;\n\t\tconst releaseCommandTimeout = args.releaseCommandTimeout ?? 600;\n\t\tconst highAvailability = args.highAvailability ?? false;\n\n\t\t// The toml is written by the command at execution time. The content\n\t\t// arrives via the FLY_TOML_CONTENT env var (avoiding shell escaping) so\n\t\t// no file is touched during `pulumi preview`.\n\t\tconst deployCommand = [\n\t\t\t\"mkdir -p .fly\",\n\t\t\t`printf '%s' \"$FLY_TOML_CONTENT\" > ${configPath}`,\n\t\t\t`fly deploy --config ${configPath} --remote-only --ha=${highAvailability} --wait-timeout ${waitTimeout} --release-command-timeout ${releaseCommandTimeout}`,\n\t\t].join(\" && \");\n\n\t\tconst triggers = pulumi\n\t\t\t.output(args.triggers)\n\t\t\t.apply((values) => [...values, tomlContent]);\n\n\t\tnew command.local.Command(\n\t\t\t`${name}-deploy`,\n\t\t\t{\n\t\t\t\tcreate: deployCommand,\n\t\t\t\ttriggers,\n\t\t\t\tdir: stableDir(args.monorepoRoot),\n\t\t\t\tenvironment: {\n\t\t\t\t\tFLY_API_TOKEN: provider.token,\n\t\t\t\t\tFLY_TOML_CONTENT: tomlContent,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ parent: this, dependsOn: [app] },\n\t\t);\n\n\t\tthis.registerOutputs({});\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,IAAa,YAAb,cAA+BA,eAAO,kBAAkB;CACvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,cAAcC,iCAAgB,KAAK,MAAM;EAC/C,MAAM,aAAa,QAAQ,KAAK,OAAO,IAAI;EAE3C,MAAM,cAAc,KAAK,eAAe;EACxC,MAAM,wBAAwB,KAAK,yBAAyB;EAC5D,MAAM,mBAAmB,KAAK,oBAAoB;EAKlD,MAAM,gBAAgB;GACrB;GACA,qCAAqC;GACrC,uBAAuB,WAAW,sBAAsB,iBAAiB,kBAAkB,YAAY,6BAA6B;EACrI,EAAE,KAAK,MAAM;EAEb,MAAM,WAAWD,eACf,OAAO,KAAK,QAAQ,EACpB,OAAO,WAAW,CAAC,GAAG,QAAQ,WAAW,CAAC;EAE5C,IAAIE,gBAAQ,MAAM,QACjB,GAAG,KAAK,UACR;GACC,QAAQ;GACR;GACA,KAAKC,6BAAU,KAAK,YAAY;GAChC,aAAa;IACZ,eAAe,SAAS;IACxB,kBAAkB;GACnB;EACD,GACA;GAAE,QAAQ;GAAM,WAAW,CAAC,GAAG;EAAE,CAClC;EAEA,KAAK,gBAAgB,CAAC,CAAC;CACxB;AACD"}
@@ -12,7 +12,11 @@ type FlyDeployOptions = Omit<pulumi.ComponentResourceOptions, "provider"> & {
12
12
  };
13
13
  /** Args for FlyDeploy. */
14
14
  interface FlyDeployArgs {
15
- /** Absolute path to the repo root (working directory for `fly deploy`). */
15
+ /**
16
+ * Absolute path to the repo root (working directory for `fly deploy`).
17
+ * Stored relative to the Pulumi program directory so the command stays stable
18
+ * across machines and CI (see {@link stableDir}).
19
+ */
16
20
  monorepoRoot: string;
17
21
  /**
18
22
  * fly.toml configuration. `config.app` must equal the FlyApp name. All values
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/fly/deploy.ts"],"mappings":";;;;;;;;KAQK,gBAAA,GAAmB,IAAA,CAAK,MAAA,CAAO,wBAAA;oCAEnC,QAAA,EAAU,WAAA,EAFU;EAKpB,GAAA,EAAK,MAAA;AAAA;;UAIW,aAAA;EAJX;EAML,YAAA;EANW;;;;EAYX,MAAA,EAAQ,aAAA;EAfE;;;;AAGC;EAmBX,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAfD;EAkB7B,WAAA;EAVQ;EAaR,qBAAA;EANU;EASV,gBAAA;AAAA;;;;;;;;;;;;;AAAgB;AAkBjB;;cAAa,SAAA,SAAkB,MAAA,CAAO,iBAAA;cACzB,IAAA,UAAc,IAAA,EAAM,aAAA,EAAe,IAAA,EAAM,gBAAA;AAAA"}
1
+ {"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/fly/deploy.ts"],"mappings":";;;;;;;;KASK,gBAAA,GAAmB,IAAA,CAAK,MAAA,CAAO,wBAAA;oCAEnC,QAAA,EAAU,WAAA,EAFU;EAKpB,GAAA,EAAK,MAAA;AAAA;;UAIW,aAAA;EAJX;;;;;EAUL,YAAA;EAbA;;;;EAmBA,MAAA,EAAQ,aAAA;EAhBG;AAIZ;;;;EAmBC,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAApB;EAGV,WAAA;EAHsB;EAMtB,qBAAA;EAbA;EAgBA,gBAAA;AAAA;;;;;;;;;AAAgB;AAkBjB;;;;;;cAAa,SAAA,SAAkB,MAAA,CAAO,iBAAA;cACzB,IAAA,UAAc,IAAA,EAAM,aAAA,EAAe,IAAA,EAAM,gBAAA;AAAA"}
@@ -12,7 +12,11 @@ type FlyDeployOptions = Omit<pulumi.ComponentResourceOptions, "provider"> & {
12
12
  };
13
13
  /** Args for FlyDeploy. */
14
14
  interface FlyDeployArgs {
15
- /** Absolute path to the repo root (working directory for `fly deploy`). */
15
+ /**
16
+ * Absolute path to the repo root (working directory for `fly deploy`).
17
+ * Stored relative to the Pulumi program directory so the command stays stable
18
+ * across machines and CI (see {@link stableDir}).
19
+ */
16
20
  monorepoRoot: string;
17
21
  /**
18
22
  * fly.toml configuration. `config.app` must equal the FlyApp name. All values
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/fly/deploy.ts"],"mappings":";;;;;;;;KAQK,gBAAA,GAAmB,IAAA,CAAK,MAAA,CAAO,wBAAA;oCAEnC,QAAA,EAAU,WAAA,EAFU;EAKpB,GAAA,EAAK,MAAA;AAAA;;UAIW,aAAA;EAJX;EAML,YAAA;EANW;;;;EAYX,MAAA,EAAQ,aAAA;EAfE;;;;AAGC;EAmBX,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAfD;EAkB7B,WAAA;EAVQ;EAaR,qBAAA;EANU;EASV,gBAAA;AAAA;;;;;;;;;;;;;AAAgB;AAkBjB;;cAAa,SAAA,SAAkB,MAAA,CAAO,iBAAA;cACzB,IAAA,UAAc,IAAA,EAAM,aAAA,EAAe,IAAA,EAAM,gBAAA;AAAA"}
1
+ {"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/fly/deploy.ts"],"mappings":";;;;;;;;KASK,gBAAA,GAAmB,IAAA,CAAK,MAAA,CAAO,wBAAA;oCAEnC,QAAA,EAAU,WAAA,EAFU;EAKpB,GAAA,EAAK,MAAA;AAAA;;UAIW,aAAA;EAJX;;;;;EAUL,YAAA;EAbA;;;;EAmBA,MAAA,EAAQ,aAAA;EAhBG;AAIZ;;;;EAmBC,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAApB;EAGV,WAAA;EAHsB;EAMtB,qBAAA;EAbA;EAgBA,gBAAA;AAAA;;;;;;;;;AAAgB;AAkBjB;;;;;;cAAa,SAAA,SAAkB,MAAA,CAAO,iBAAA;cACzB,IAAA,UAAc,IAAA,EAAM,aAAA,EAAe,IAAA,EAAM,gBAAA;AAAA"}
@@ -1,4 +1,5 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
+ import { stableDir } from "../stable-dir.mjs";
2
3
  import { generateFlyToml } from "./toml.mjs";
3
4
  import * as command from "@pulumi/command";
4
5
  import * as pulumi from "@pulumi/pulumi";
@@ -37,7 +38,7 @@ var FlyDeploy = class extends pulumi.ComponentResource {
37
38
  new command.local.Command(`${name}-deploy`, {
38
39
  create: deployCommand,
39
40
  triggers,
40
- dir: args.monorepoRoot,
41
+ dir: stableDir(args.monorepoRoot),
41
42
  environment: {
42
43
  FLY_API_TOKEN: provider.token,
43
44
  FLY_TOML_CONTENT: tomlContent
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/fly/deploy.ts"],"sourcesContent":["import * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app.js\";\nimport type { FlyProvider } from \"./provider.js\";\nimport { type FlyTomlConfig, generateFlyToml } from \"./toml.js\";\n\n/** Options type for FlyDeploy. */\ntype FlyDeployOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App to deploy into. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyDeploy. */\nexport interface FlyDeployArgs {\n\t/** Absolute path to the repo root (working directory for `fly deploy`). */\n\tmonorepoRoot: string;\n\n\t/**\n\t * fly.toml configuration. `config.app` must equal the FlyApp name. All values\n\t * are plain (not `pulumi.Input`) — resolve Outputs before constructing this.\n\t */\n\tconfig: FlyTomlConfig;\n\n\t/**\n\t * Values that force a redeploy when changed (e.g. a source hash from\n\t * `hashDirectory()` and `FlySecret.version`). The generated toml content is\n\t * appended automatically.\n\t */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\n\t/** `fly deploy --wait-timeout` in seconds (default 300). */\n\twaitTimeout?: number;\n\n\t/** `fly deploy --release-command-timeout` in seconds (default 600). */\n\treleaseCommandTimeout?: number;\n\n\t/** `fly deploy --ha` (default false). */\n\thighAvailability?: boolean;\n}\n\n/**\n * Deploys a Fly app via `fly deploy --remote-only`, driven by a generated\n * fly.toml. The toml is written by the deploy command itself (at execution\n * time, not during `pulumi preview`) to `<monorepoRoot>/.fly/<app>.toml`, and\n * its content is added to the redeploy triggers (so config changes redeploy).\n *\n * @example\n * ```typescript\n * new FlyDeploy(\"api-deploy\", {\n * monorepoRoot,\n * config: { app: \"rby-api\", primaryRegion: \"iad\", build: { dockerfile: \"apps/api/Dockerfile\" } },\n * triggers: [hashDirectory(\"apps/api\"), secrets.version],\n * }, { provider, app, dependsOn: [secrets] });\n * ```\n */\nexport class FlyDeploy extends pulumi.ComponentResource {\n\tconstructor(name: string, args: FlyDeployArgs, opts: FlyDeployOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Deploy\", name, {}, pulumiOpts);\n\n\t\tconst tomlContent = generateFlyToml(args.config);\n\t\tconst configPath = `.fly/${args.config.app}.toml`;\n\n\t\tconst waitTimeout = args.waitTimeout ?? 300;\n\t\tconst releaseCommandTimeout = args.releaseCommandTimeout ?? 600;\n\t\tconst highAvailability = args.highAvailability ?? false;\n\n\t\t// The toml is written by the command at execution time. The content\n\t\t// arrives via the FLY_TOML_CONTENT env var (avoiding shell escaping) so\n\t\t// no file is touched during `pulumi preview`.\n\t\tconst deployCommand = [\n\t\t\t\"mkdir -p .fly\",\n\t\t\t`printf '%s' \"$FLY_TOML_CONTENT\" > ${configPath}`,\n\t\t\t`fly deploy --config ${configPath} --remote-only --ha=${highAvailability} --wait-timeout ${waitTimeout} --release-command-timeout ${releaseCommandTimeout}`,\n\t\t].join(\" && \");\n\n\t\tconst triggers = pulumi\n\t\t\t.output(args.triggers)\n\t\t\t.apply((values) => [...values, tomlContent]);\n\n\t\tnew command.local.Command(\n\t\t\t`${name}-deploy`,\n\t\t\t{\n\t\t\t\tcreate: deployCommand,\n\t\t\t\ttriggers,\n\t\t\t\tdir: args.monorepoRoot,\n\t\t\t\tenvironment: {\n\t\t\t\t\tFLY_API_TOKEN: provider.token,\n\t\t\t\t\tFLY_TOML_CONTENT: tomlContent,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ parent: this, dependsOn: [app] },\n\t\t);\n\n\t\tthis.registerOutputs({});\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2DA,IAAa,YAAb,cAA+B,OAAO,kBAAkB;CACvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,cAAc,gBAAgB,KAAK,MAAM;EAC/C,MAAM,aAAa,QAAQ,KAAK,OAAO,IAAI;EAE3C,MAAM,cAAc,KAAK,eAAe;EACxC,MAAM,wBAAwB,KAAK,yBAAyB;EAC5D,MAAM,mBAAmB,KAAK,oBAAoB;EAKlD,MAAM,gBAAgB;GACrB;GACA,qCAAqC;GACrC,uBAAuB,WAAW,sBAAsB,iBAAiB,kBAAkB,YAAY,6BAA6B;EACrI,EAAE,KAAK,MAAM;EAEb,MAAM,WAAW,OACf,OAAO,KAAK,QAAQ,EACpB,OAAO,WAAW,CAAC,GAAG,QAAQ,WAAW,CAAC;EAE5C,IAAI,QAAQ,MAAM,QACjB,GAAG,KAAK,UACR;GACC,QAAQ;GACR;GACA,KAAK,KAAK;GACV,aAAa;IACZ,eAAe,SAAS;IACxB,kBAAkB;GACnB;EACD,GACA;GAAE,QAAQ;GAAM,WAAW,CAAC,GAAG;EAAE,CAClC;EAEA,KAAK,gBAAgB,CAAC,CAAC;CACxB;AACD"}
1
+ {"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/fly/deploy.ts"],"sourcesContent":["import * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { stableDir } from \"../stable-dir\";\nimport type { FlyApp } from \"./app\";\nimport type { FlyProvider } from \"./provider\";\nimport { type FlyTomlConfig, generateFlyToml } from \"./toml\";\n\n/** Options type for FlyDeploy. */\ntype FlyDeployOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App to deploy into. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyDeploy. */\nexport interface FlyDeployArgs {\n\t/**\n\t * Absolute path to the repo root (working directory for `fly deploy`).\n\t * Stored relative to the Pulumi program directory so the command stays stable\n\t * across machines and CI (see {@link stableDir}).\n\t */\n\tmonorepoRoot: string;\n\n\t/**\n\t * fly.toml configuration. `config.app` must equal the FlyApp name. All values\n\t * are plain (not `pulumi.Input`) — resolve Outputs before constructing this.\n\t */\n\tconfig: FlyTomlConfig;\n\n\t/**\n\t * Values that force a redeploy when changed (e.g. a source hash from\n\t * `hashDirectory()` and `FlySecret.version`). The generated toml content is\n\t * appended automatically.\n\t */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\n\t/** `fly deploy --wait-timeout` in seconds (default 300). */\n\twaitTimeout?: number;\n\n\t/** `fly deploy --release-command-timeout` in seconds (default 600). */\n\treleaseCommandTimeout?: number;\n\n\t/** `fly deploy --ha` (default false). */\n\thighAvailability?: boolean;\n}\n\n/**\n * Deploys a Fly app via `fly deploy --remote-only`, driven by a generated\n * fly.toml. The toml is written by the deploy command itself (at execution\n * time, not during `pulumi preview`) to `<monorepoRoot>/.fly/<app>.toml`, and\n * its content is added to the redeploy triggers (so config changes redeploy).\n *\n * @example\n * ```typescript\n * new FlyDeploy(\"api-deploy\", {\n * monorepoRoot,\n * config: { app: \"rby-api\", primaryRegion: \"iad\", build: { dockerfile: \"apps/api/Dockerfile\" } },\n * triggers: [hashDirectory(\"apps/api\"), secrets.version],\n * }, { provider, app, dependsOn: [secrets] });\n * ```\n */\nexport class FlyDeploy extends pulumi.ComponentResource {\n\tconstructor(name: string, args: FlyDeployArgs, opts: FlyDeployOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Deploy\", name, {}, pulumiOpts);\n\n\t\tconst tomlContent = generateFlyToml(args.config);\n\t\tconst configPath = `.fly/${args.config.app}.toml`;\n\n\t\tconst waitTimeout = args.waitTimeout ?? 300;\n\t\tconst releaseCommandTimeout = args.releaseCommandTimeout ?? 600;\n\t\tconst highAvailability = args.highAvailability ?? false;\n\n\t\t// The toml is written by the command at execution time. The content\n\t\t// arrives via the FLY_TOML_CONTENT env var (avoiding shell escaping) so\n\t\t// no file is touched during `pulumi preview`.\n\t\tconst deployCommand = [\n\t\t\t\"mkdir -p .fly\",\n\t\t\t`printf '%s' \"$FLY_TOML_CONTENT\" > ${configPath}`,\n\t\t\t`fly deploy --config ${configPath} --remote-only --ha=${highAvailability} --wait-timeout ${waitTimeout} --release-command-timeout ${releaseCommandTimeout}`,\n\t\t].join(\" && \");\n\n\t\tconst triggers = pulumi\n\t\t\t.output(args.triggers)\n\t\t\t.apply((values) => [...values, tomlContent]);\n\n\t\tnew command.local.Command(\n\t\t\t`${name}-deploy`,\n\t\t\t{\n\t\t\t\tcreate: deployCommand,\n\t\t\t\ttriggers,\n\t\t\t\tdir: stableDir(args.monorepoRoot),\n\t\t\t\tenvironment: {\n\t\t\t\t\tFLY_API_TOKEN: provider.token,\n\t\t\t\t\tFLY_TOML_CONTENT: tomlContent,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ parent: this, dependsOn: [app] },\n\t\t);\n\n\t\tthis.registerOutputs({});\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAgEA,IAAa,YAAb,cAA+B,OAAO,kBAAkB;CACvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,cAAc,gBAAgB,KAAK,MAAM;EAC/C,MAAM,aAAa,QAAQ,KAAK,OAAO,IAAI;EAE3C,MAAM,cAAc,KAAK,eAAe;EACxC,MAAM,wBAAwB,KAAK,yBAAyB;EAC5D,MAAM,mBAAmB,KAAK,oBAAoB;EAKlD,MAAM,gBAAgB;GACrB;GACA,qCAAqC;GACrC,uBAAuB,WAAW,sBAAsB,iBAAiB,kBAAkB,YAAY,6BAA6B;EACrI,EAAE,KAAK,MAAM;EAEb,MAAM,WAAW,OACf,OAAO,KAAK,QAAQ,EACpB,OAAO,WAAW,CAAC,GAAG,QAAQ,WAAW,CAAC;EAE5C,IAAI,QAAQ,MAAM,QACjB,GAAG,KAAK,UACR;GACC,QAAQ;GACR;GACA,KAAK,UAAU,KAAK,YAAY;GAChC,aAAa;IACZ,eAAe,SAAS;IACxB,kBAAkB;GACnB;EACD,GACA;GAAE,QAAQ;GAAM,WAAW,CAAC,GAAG;EAAE,CAClC;EAEA,KAAK,gBAAgB,CAAC,CAAC;CACxB;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"ip.cjs","names":["FlyClient","pulumi"],"sources":["../../src/fly/ip.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app.js\";\nimport { FlyClient } from \"./client.js\";\nimport type { FlyProvider } from \"./provider.js\";\n\n/**\n * Fly IP address type. Enum keys UPPERCASE; values are Fly's GraphQL enum\n * literals (lowercase wire format).\n */\nexport enum FlyIpType {\n\tV4 = \"v4\",\n\tV6 = \"v6\",\n\tSHARED_V4 = \"shared_v4\",\n\tPRIVATE_V6 = \"private_v6\",\n}\n\n/** Resolved inputs for the Fly IP dynamic provider. */\nexport interface FlyIpInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name (used as GraphQL appId). */\n\tappName: string;\n\n\t/** IP address type. */\n\ttype: FlyIpType;\n\n\t/** Region (IATA code); omit for global. */\n\tregion?: string;\n}\n\n/** Persisted state for the Fly IP. */\ninterface FlyIpOutputs extends FlyIpInputs {\n\t/** Allocated IP address (also the `.id`). */\n\taddress: string;\n\n\t/** GraphQL node ID, when present (absent for `shared_v4`). */\n\tipAddressId?: string;\n}\n\nconst LIST_IPS = `\n\tquery ($appName: String!) {\n\t\tapp(name: $appName) {\n\t\t\tsharedIpAddress\n\t\t\tipAddresses {\n\t\t\t\tnodes { id address type region }\n\t\t\t}\n\t\t}\n\t}\n`;\n\nconst ALLOCATE_IP = `\n\tmutation ($input: AllocateIPAddressInput!) {\n\t\tallocateIpAddress(input: $input) {\n\t\t\tipAddress { id address type region }\n\t\t\tapp { sharedIpAddress }\n\t\t}\n\t}\n`;\n\nconst RELEASE_IP = `\n\tmutation ($input: ReleaseIPAddressInput!) {\n\t\treleaseIpAddress(input: $input) { clientMutationId }\n\t}\n`;\n\ninterface IpNode {\n\tid: string;\n\taddress: string;\n\ttype: string;\n\tregion: string | null;\n}\n\ninterface ListIpsResult {\n\tapp: {\n\t\tsharedIpAddress: string | null;\n\t\tipAddresses: { nodes: IpNode[] };\n\t};\n}\n\ninterface AllocateResult {\n\tallocateIpAddress: {\n\t\tipAddress: IpNode | null;\n\t\tapp: { sharedIpAddress: string | null };\n\t};\n}\n\n/**\n * Dynamic provider for Fly dedicated/shared IP allocation via the Fly GraphQL\n * API. `create()` queries existing IPs and adopts a matching one, otherwise it\n * allocates. `shared_v4` allocations return a null `ipAddress` in the payload —\n * the address is read from `app.sharedIpAddress`.\n */\nclass FlyIpResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlyIpInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\n\t\tconst existing = await this.findExisting(client, inputs);\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Fly ${inputs.type} IP \"${existing.address}\"`,\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tid: existing.address,\n\t\t\t\touts: {\n\t\t\t\t\t...inputs,\n\t\t\t\t\taddress: existing.address,\n\t\t\t\t\tipAddressId: existing.ipAddressId,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst result = await client.graphql<AllocateResult>(ALLOCATE_IP, {\n\t\t\tinput: {\n\t\t\t\tappId: inputs.appName,\n\t\t\t\ttype: inputs.type,\n\t\t\t\tregion: inputs.region,\n\t\t\t},\n\t\t});\n\n\t\tconst node = result.allocateIpAddress.ipAddress;\n\n\t\tconst address =\n\t\t\tinputs.type === FlyIpType.SHARED_V4\n\t\t\t\t? (result.allocateIpAddress.app.sharedIpAddress ?? \"\")\n\t\t\t\t: (node?.address ?? \"\");\n\n\t\tif (!address) {\n\t\t\tthrow new Error(\n\t\t\t\t`Fly IP allocation for app \"${inputs.appName}\" (${inputs.type}) returned no address`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tid: address,\n\t\t\touts: { ...inputs, address, ipAddressId: node?.id },\n\t\t};\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyIpOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\treturn { id, props };\n\t}\n\n\tasync delete(_id: string, props: FlyIpOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst input: Record<string, string> = { appId: props.appName };\n\n\t\tif (props.ipAddressId) {\n\t\t\tinput.ipAddressId = props.ipAddressId;\n\t\t} else {\n\t\t\tinput.ip = props.address;\n\t\t}\n\n\t\tawait client.graphql(RELEASE_IP, { input });\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyIpOutputs,\n\t\tnews: FlyIpInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\t\tif (olds.type !== news.type) {\n\t\t\treplaces.push(\"type\");\n\t\t}\n\t\tif (olds.region !== news.region) {\n\t\t\treplaces.push(\"region\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n\n\tprivate async findExisting(\n\t\tclient: FlyClient,\n\t\tinputs: FlyIpInputs,\n\t): Promise<{ address: string; ipAddressId?: string } | null> {\n\t\tconst result = await client.graphql<ListIpsResult>(LIST_IPS, {\n\t\t\tappName: inputs.appName,\n\t\t});\n\n\t\tif (inputs.type === FlyIpType.SHARED_V4) {\n\t\t\tconst shared = result.app.sharedIpAddress;\n\n\t\t\treturn shared ? { address: shared } : null;\n\t\t}\n\n\t\tconst match = result.app.ipAddresses.nodes.find(\n\t\t\t(node) =>\n\t\t\t\tnode.type === inputs.type &&\n\t\t\t\t(inputs.region === undefined || node.region === inputs.region),\n\t\t);\n\n\t\treturn match ? { address: match.address, ipAddressId: match.id } : null;\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyIpResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly address: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\ttype: pulumi.Input<FlyIpType>;\n\t\t\tregion?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyIpResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, address: undefined, ipAddressId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyIp. */\ntype FlyIpOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the IP belongs to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyIp. */\nexport interface FlyIpArgs {\n\t/** IP address type. */\n\ttype: pulumi.Input<FlyIpType>;\n\n\t/** Region (IATA code); omit for a global address. */\n\tregion?: pulumi.Input<string>;\n}\n\n/**\n * Allocates a Fly IP address (dedicated or shared) via the Fly GraphQL API.\n *\n * @example\n * ```typescript\n * const ip = new FlyIp(\"api-ip\", { type: FlyIpType.SHARED_V4 }, { provider, app });\n * ```\n */\nexport class FlyIp extends pulumi.ComponentResource {\n\t/** Allocated IP address. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyIpArgs, opts: FlyIpOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Ip\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyIpResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\ttype: args.type,\n\t\t\t\tregion: args.region,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.address;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;;;;AAUA,IAAY,YAAL;CACN;CACA;CACA;CACA;;AACD;AA0BA,MAAM,WAAW;;;;;;;;;;AAWjB,MAAM,cAAc;;;;;;;;AASpB,MAAM,aAAa;;;;;;;;;;;AAiCnB,IAAM,wBAAN,MAAuE;CACtE,MAAM,OAAO,QAA2D;EACvE,MAAM,SAAS,IAAIA,6BAAU,OAAO,KAAK;EAEzC,MAAM,WAAW,MAAM,KAAK,aAAa,QAAQ,MAAM;EAEvD,IAAI,UAAU;GACb,eAAO,IAAI,KACV,yBAAyB,OAAO,KAAK,OAAO,SAAS,QAAQ,EAC9D;GAEA,OAAO;IACN,IAAI,SAAS;IACb,MAAM;KACL,GAAG;KACH,SAAS,SAAS;KAClB,aAAa,SAAS;IACvB;GACD;EACD;EAEA,MAAM,SAAS,MAAM,OAAO,QAAwB,aAAa,EAChE,OAAO;GACN,OAAO,OAAO;GACd,MAAM,OAAO;GACb,QAAQ,OAAO;EAChB,EACD,CAAC;EAED,MAAM,OAAO,OAAO,kBAAkB;EAEtC,MAAM,UACL,OAAO,uBACH,OAAO,kBAAkB,IAAI,mBAAmB,KAChD,MAAM,WAAW;EAEtB,IAAI,CAAC,SACJ,MAAM,IAAI,MACT,8BAA8B,OAAO,QAAQ,KAAK,OAAO,KAAK,sBAC/D;EAGD,OAAO;GACN,IAAI;GACJ,MAAM;IAAE,GAAG;IAAQ;IAAS,aAAa,MAAM;GAAG;EACnD;CACD;CAEA,MAAM,KACL,IACA,OACqC;EACrC,OAAO;GAAE;GAAI;EAAM;CACpB;CAEA,MAAM,OAAO,KAAa,OAAoC;EAC7D,MAAM,SAAS,IAAIA,6BAAU,MAAM,KAAK;EAExC,MAAM,QAAgC,EAAE,OAAO,MAAM,QAAQ;EAE7D,IAAI,MAAM,aACT,MAAM,cAAc,MAAM;OAE1B,MAAM,KAAK,MAAM;EAGlB,MAAM,OAAO,QAAQ,YAAY,EAAE,MAAM,CAAC;CAC3C;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAExB,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAErB,IAAI,KAAK,WAAW,KAAK,QACxB,SAAS,KAAK,QAAQ;EAGvB,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;CAEA,MAAc,aACb,QACA,QAC4D;EAC5D,MAAM,SAAS,MAAM,OAAO,QAAuB,UAAU,EAC5D,SAAS,OAAO,QACjB,CAAC;EAED,IAAI,OAAO,sBAA8B;GACxC,MAAM,SAAS,OAAO,IAAI;GAE1B,OAAO,SAAS,EAAE,SAAS,OAAO,IAAI;EACvC;EAEA,MAAM,QAAQ,OAAO,IAAI,YAAY,MAAM,MACzC,SACA,KAAK,SAAS,OAAO,SACpB,OAAO,WAAW,UAAa,KAAK,WAAW,OAAO,OACzD;EAEA,OAAO,QAAQ;GAAE,SAAS,MAAM;GAAS,aAAa,MAAM;EAAG,IAAI;CACpE;AACD;;AAGA,IAAM,gBAAN,cAA4BC,eAAO,QAAQ,SAAS;CAGnD,YACC,MACA,MAMA,MACC;EACD,MACC,IAAI,sBAAsB,GAC1B,MACA;GAAE,GAAG;GAAM,SAAS;GAAW,aAAa;EAAU,GACtD,IACD;CACD;AACD;;;;;;;;;AA4BA,IAAa,QAAb,cAA2BA,eAAO,kBAAkB;CAInD,YAAY,MAAc,MAAiB,MAAoB;EAC9D,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,qBAAqB,MAAM,CAAC,GAAG,UAAU;EAE/C,MAAM,WAAW,IAAI,cACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,MAAM,KAAK;GACX,QAAQ,KAAK;EACd,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}
1
+ {"version":3,"file":"ip.cjs","names":["FlyClient","pulumi"],"sources":["../../src/fly/ip.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app\";\nimport { FlyClient } from \"./client\";\nimport type { FlyProvider } from \"./provider\";\n\n/**\n * Fly IP address type. Enum keys UPPERCASE; values are Fly's GraphQL enum\n * literals (lowercase wire format).\n */\nexport enum FlyIpType {\n\tV4 = \"v4\",\n\tV6 = \"v6\",\n\tSHARED_V4 = \"shared_v4\",\n\tPRIVATE_V6 = \"private_v6\",\n}\n\n/** Resolved inputs for the Fly IP dynamic provider. */\nexport interface FlyIpInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name (used as GraphQL appId). */\n\tappName: string;\n\n\t/** IP address type. */\n\ttype: FlyIpType;\n\n\t/** Region (IATA code); omit for global. */\n\tregion?: string;\n}\n\n/** Persisted state for the Fly IP. */\ninterface FlyIpOutputs extends FlyIpInputs {\n\t/** Allocated IP address (also the `.id`). */\n\taddress: string;\n\n\t/** GraphQL node ID, when present (absent for `shared_v4`). */\n\tipAddressId?: string;\n}\n\nconst LIST_IPS = `\n\tquery ($appName: String!) {\n\t\tapp(name: $appName) {\n\t\t\tsharedIpAddress\n\t\t\tipAddresses {\n\t\t\t\tnodes { id address type region }\n\t\t\t}\n\t\t}\n\t}\n`;\n\nconst ALLOCATE_IP = `\n\tmutation ($input: AllocateIPAddressInput!) {\n\t\tallocateIpAddress(input: $input) {\n\t\t\tipAddress { id address type region }\n\t\t\tapp { sharedIpAddress }\n\t\t}\n\t}\n`;\n\nconst RELEASE_IP = `\n\tmutation ($input: ReleaseIPAddressInput!) {\n\t\treleaseIpAddress(input: $input) { clientMutationId }\n\t}\n`;\n\ninterface IpNode {\n\tid: string;\n\taddress: string;\n\ttype: string;\n\tregion: string | null;\n}\n\ninterface ListIpsResult {\n\tapp: {\n\t\tsharedIpAddress: string | null;\n\t\tipAddresses: { nodes: IpNode[] };\n\t};\n}\n\ninterface AllocateResult {\n\tallocateIpAddress: {\n\t\tipAddress: IpNode | null;\n\t\tapp: { sharedIpAddress: string | null };\n\t};\n}\n\n/**\n * Dynamic provider for Fly dedicated/shared IP allocation via the Fly GraphQL\n * API. `create()` queries existing IPs and adopts a matching one, otherwise it\n * allocates. `shared_v4` allocations return a null `ipAddress` in the payload —\n * the address is read from `app.sharedIpAddress`.\n */\nclass FlyIpResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlyIpInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\n\t\tconst existing = await this.findExisting(client, inputs);\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Fly ${inputs.type} IP \"${existing.address}\"`,\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tid: existing.address,\n\t\t\t\touts: {\n\t\t\t\t\t...inputs,\n\t\t\t\t\taddress: existing.address,\n\t\t\t\t\tipAddressId: existing.ipAddressId,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst result = await client.graphql<AllocateResult>(ALLOCATE_IP, {\n\t\t\tinput: {\n\t\t\t\tappId: inputs.appName,\n\t\t\t\ttype: inputs.type,\n\t\t\t\tregion: inputs.region,\n\t\t\t},\n\t\t});\n\n\t\tconst node = result.allocateIpAddress.ipAddress;\n\n\t\tconst address =\n\t\t\tinputs.type === FlyIpType.SHARED_V4\n\t\t\t\t? (result.allocateIpAddress.app.sharedIpAddress ?? \"\")\n\t\t\t\t: (node?.address ?? \"\");\n\n\t\tif (!address) {\n\t\t\tthrow new Error(\n\t\t\t\t`Fly IP allocation for app \"${inputs.appName}\" (${inputs.type}) returned no address`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tid: address,\n\t\t\touts: { ...inputs, address, ipAddressId: node?.id },\n\t\t};\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyIpOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\treturn { id, props };\n\t}\n\n\tasync delete(_id: string, props: FlyIpOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst input: Record<string, string> = { appId: props.appName };\n\n\t\tif (props.ipAddressId) {\n\t\t\tinput.ipAddressId = props.ipAddressId;\n\t\t} else {\n\t\t\tinput.ip = props.address;\n\t\t}\n\n\t\tawait client.graphql(RELEASE_IP, { input });\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyIpOutputs,\n\t\tnews: FlyIpInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\t\tif (olds.type !== news.type) {\n\t\t\treplaces.push(\"type\");\n\t\t}\n\t\tif (olds.region !== news.region) {\n\t\t\treplaces.push(\"region\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n\n\tprivate async findExisting(\n\t\tclient: FlyClient,\n\t\tinputs: FlyIpInputs,\n\t): Promise<{ address: string; ipAddressId?: string } | null> {\n\t\tconst result = await client.graphql<ListIpsResult>(LIST_IPS, {\n\t\t\tappName: inputs.appName,\n\t\t});\n\n\t\tif (inputs.type === FlyIpType.SHARED_V4) {\n\t\t\tconst shared = result.app.sharedIpAddress;\n\n\t\t\treturn shared ? { address: shared } : null;\n\t\t}\n\n\t\tconst match = result.app.ipAddresses.nodes.find(\n\t\t\t(node) =>\n\t\t\t\tnode.type === inputs.type &&\n\t\t\t\t(inputs.region === undefined || node.region === inputs.region),\n\t\t);\n\n\t\treturn match ? { address: match.address, ipAddressId: match.id } : null;\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyIpResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly address: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\ttype: pulumi.Input<FlyIpType>;\n\t\t\tregion?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyIpResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, address: undefined, ipAddressId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyIp. */\ntype FlyIpOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the IP belongs to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyIp. */\nexport interface FlyIpArgs {\n\t/** IP address type. */\n\ttype: pulumi.Input<FlyIpType>;\n\n\t/** Region (IATA code); omit for a global address. */\n\tregion?: pulumi.Input<string>;\n}\n\n/**\n * Allocates a Fly IP address (dedicated or shared) via the Fly GraphQL API.\n *\n * @example\n * ```typescript\n * const ip = new FlyIp(\"api-ip\", { type: FlyIpType.SHARED_V4 }, { provider, app });\n * ```\n */\nexport class FlyIp extends pulumi.ComponentResource {\n\t/** Allocated IP address. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyIpArgs, opts: FlyIpOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Ip\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyIpResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\ttype: args.type,\n\t\t\t\tregion: args.region,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.address;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ip.mjs","names":[],"sources":["../../src/fly/ip.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app.js\";\nimport { FlyClient } from \"./client.js\";\nimport type { FlyProvider } from \"./provider.js\";\n\n/**\n * Fly IP address type. Enum keys UPPERCASE; values are Fly's GraphQL enum\n * literals (lowercase wire format).\n */\nexport enum FlyIpType {\n\tV4 = \"v4\",\n\tV6 = \"v6\",\n\tSHARED_V4 = \"shared_v4\",\n\tPRIVATE_V6 = \"private_v6\",\n}\n\n/** Resolved inputs for the Fly IP dynamic provider. */\nexport interface FlyIpInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name (used as GraphQL appId). */\n\tappName: string;\n\n\t/** IP address type. */\n\ttype: FlyIpType;\n\n\t/** Region (IATA code); omit for global. */\n\tregion?: string;\n}\n\n/** Persisted state for the Fly IP. */\ninterface FlyIpOutputs extends FlyIpInputs {\n\t/** Allocated IP address (also the `.id`). */\n\taddress: string;\n\n\t/** GraphQL node ID, when present (absent for `shared_v4`). */\n\tipAddressId?: string;\n}\n\nconst LIST_IPS = `\n\tquery ($appName: String!) {\n\t\tapp(name: $appName) {\n\t\t\tsharedIpAddress\n\t\t\tipAddresses {\n\t\t\t\tnodes { id address type region }\n\t\t\t}\n\t\t}\n\t}\n`;\n\nconst ALLOCATE_IP = `\n\tmutation ($input: AllocateIPAddressInput!) {\n\t\tallocateIpAddress(input: $input) {\n\t\t\tipAddress { id address type region }\n\t\t\tapp { sharedIpAddress }\n\t\t}\n\t}\n`;\n\nconst RELEASE_IP = `\n\tmutation ($input: ReleaseIPAddressInput!) {\n\t\treleaseIpAddress(input: $input) { clientMutationId }\n\t}\n`;\n\ninterface IpNode {\n\tid: string;\n\taddress: string;\n\ttype: string;\n\tregion: string | null;\n}\n\ninterface ListIpsResult {\n\tapp: {\n\t\tsharedIpAddress: string | null;\n\t\tipAddresses: { nodes: IpNode[] };\n\t};\n}\n\ninterface AllocateResult {\n\tallocateIpAddress: {\n\t\tipAddress: IpNode | null;\n\t\tapp: { sharedIpAddress: string | null };\n\t};\n}\n\n/**\n * Dynamic provider for Fly dedicated/shared IP allocation via the Fly GraphQL\n * API. `create()` queries existing IPs and adopts a matching one, otherwise it\n * allocates. `shared_v4` allocations return a null `ipAddress` in the payload —\n * the address is read from `app.sharedIpAddress`.\n */\nclass FlyIpResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlyIpInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\n\t\tconst existing = await this.findExisting(client, inputs);\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Fly ${inputs.type} IP \"${existing.address}\"`,\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tid: existing.address,\n\t\t\t\touts: {\n\t\t\t\t\t...inputs,\n\t\t\t\t\taddress: existing.address,\n\t\t\t\t\tipAddressId: existing.ipAddressId,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst result = await client.graphql<AllocateResult>(ALLOCATE_IP, {\n\t\t\tinput: {\n\t\t\t\tappId: inputs.appName,\n\t\t\t\ttype: inputs.type,\n\t\t\t\tregion: inputs.region,\n\t\t\t},\n\t\t});\n\n\t\tconst node = result.allocateIpAddress.ipAddress;\n\n\t\tconst address =\n\t\t\tinputs.type === FlyIpType.SHARED_V4\n\t\t\t\t? (result.allocateIpAddress.app.sharedIpAddress ?? \"\")\n\t\t\t\t: (node?.address ?? \"\");\n\n\t\tif (!address) {\n\t\t\tthrow new Error(\n\t\t\t\t`Fly IP allocation for app \"${inputs.appName}\" (${inputs.type}) returned no address`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tid: address,\n\t\t\touts: { ...inputs, address, ipAddressId: node?.id },\n\t\t};\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyIpOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\treturn { id, props };\n\t}\n\n\tasync delete(_id: string, props: FlyIpOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst input: Record<string, string> = { appId: props.appName };\n\n\t\tif (props.ipAddressId) {\n\t\t\tinput.ipAddressId = props.ipAddressId;\n\t\t} else {\n\t\t\tinput.ip = props.address;\n\t\t}\n\n\t\tawait client.graphql(RELEASE_IP, { input });\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyIpOutputs,\n\t\tnews: FlyIpInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\t\tif (olds.type !== news.type) {\n\t\t\treplaces.push(\"type\");\n\t\t}\n\t\tif (olds.region !== news.region) {\n\t\t\treplaces.push(\"region\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n\n\tprivate async findExisting(\n\t\tclient: FlyClient,\n\t\tinputs: FlyIpInputs,\n\t): Promise<{ address: string; ipAddressId?: string } | null> {\n\t\tconst result = await client.graphql<ListIpsResult>(LIST_IPS, {\n\t\t\tappName: inputs.appName,\n\t\t});\n\n\t\tif (inputs.type === FlyIpType.SHARED_V4) {\n\t\t\tconst shared = result.app.sharedIpAddress;\n\n\t\t\treturn shared ? { address: shared } : null;\n\t\t}\n\n\t\tconst match = result.app.ipAddresses.nodes.find(\n\t\t\t(node) =>\n\t\t\t\tnode.type === inputs.type &&\n\t\t\t\t(inputs.region === undefined || node.region === inputs.region),\n\t\t);\n\n\t\treturn match ? { address: match.address, ipAddressId: match.id } : null;\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyIpResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly address: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\ttype: pulumi.Input<FlyIpType>;\n\t\t\tregion?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyIpResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, address: undefined, ipAddressId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyIp. */\ntype FlyIpOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the IP belongs to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyIp. */\nexport interface FlyIpArgs {\n\t/** IP address type. */\n\ttype: pulumi.Input<FlyIpType>;\n\n\t/** Region (IATA code); omit for a global address. */\n\tregion?: pulumi.Input<string>;\n}\n\n/**\n * Allocates a Fly IP address (dedicated or shared) via the Fly GraphQL API.\n *\n * @example\n * ```typescript\n * const ip = new FlyIp(\"api-ip\", { type: FlyIpType.SHARED_V4 }, { provider, app });\n * ```\n */\nexport class FlyIp extends pulumi.ComponentResource {\n\t/** Allocated IP address. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyIpArgs, opts: FlyIpOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Ip\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyIpResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\ttype: args.type,\n\t\t\t\tregion: args.region,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.address;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ip.mjs","names":[],"sources":["../../src/fly/ip.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app\";\nimport { FlyClient } from \"./client\";\nimport type { FlyProvider } from \"./provider\";\n\n/**\n * Fly IP address type. Enum keys UPPERCASE; values are Fly's GraphQL enum\n * literals (lowercase wire format).\n */\nexport enum FlyIpType {\n\tV4 = \"v4\",\n\tV6 = \"v6\",\n\tSHARED_V4 = \"shared_v4\",\n\tPRIVATE_V6 = \"private_v6\",\n}\n\n/** Resolved inputs for the Fly IP dynamic provider. */\nexport interface FlyIpInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name (used as GraphQL appId). */\n\tappName: string;\n\n\t/** IP address type. */\n\ttype: FlyIpType;\n\n\t/** Region (IATA code); omit for global. */\n\tregion?: string;\n}\n\n/** Persisted state for the Fly IP. */\ninterface FlyIpOutputs extends FlyIpInputs {\n\t/** Allocated IP address (also the `.id`). */\n\taddress: string;\n\n\t/** GraphQL node ID, when present (absent for `shared_v4`). */\n\tipAddressId?: string;\n}\n\nconst LIST_IPS = `\n\tquery ($appName: String!) {\n\t\tapp(name: $appName) {\n\t\t\tsharedIpAddress\n\t\t\tipAddresses {\n\t\t\t\tnodes { id address type region }\n\t\t\t}\n\t\t}\n\t}\n`;\n\nconst ALLOCATE_IP = `\n\tmutation ($input: AllocateIPAddressInput!) {\n\t\tallocateIpAddress(input: $input) {\n\t\t\tipAddress { id address type region }\n\t\t\tapp { sharedIpAddress }\n\t\t}\n\t}\n`;\n\nconst RELEASE_IP = `\n\tmutation ($input: ReleaseIPAddressInput!) {\n\t\treleaseIpAddress(input: $input) { clientMutationId }\n\t}\n`;\n\ninterface IpNode {\n\tid: string;\n\taddress: string;\n\ttype: string;\n\tregion: string | null;\n}\n\ninterface ListIpsResult {\n\tapp: {\n\t\tsharedIpAddress: string | null;\n\t\tipAddresses: { nodes: IpNode[] };\n\t};\n}\n\ninterface AllocateResult {\n\tallocateIpAddress: {\n\t\tipAddress: IpNode | null;\n\t\tapp: { sharedIpAddress: string | null };\n\t};\n}\n\n/**\n * Dynamic provider for Fly dedicated/shared IP allocation via the Fly GraphQL\n * API. `create()` queries existing IPs and adopts a matching one, otherwise it\n * allocates. `shared_v4` allocations return a null `ipAddress` in the payload —\n * the address is read from `app.sharedIpAddress`.\n */\nclass FlyIpResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlyIpInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\n\t\tconst existing = await this.findExisting(client, inputs);\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Fly ${inputs.type} IP \"${existing.address}\"`,\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tid: existing.address,\n\t\t\t\touts: {\n\t\t\t\t\t...inputs,\n\t\t\t\t\taddress: existing.address,\n\t\t\t\t\tipAddressId: existing.ipAddressId,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst result = await client.graphql<AllocateResult>(ALLOCATE_IP, {\n\t\t\tinput: {\n\t\t\t\tappId: inputs.appName,\n\t\t\t\ttype: inputs.type,\n\t\t\t\tregion: inputs.region,\n\t\t\t},\n\t\t});\n\n\t\tconst node = result.allocateIpAddress.ipAddress;\n\n\t\tconst address =\n\t\t\tinputs.type === FlyIpType.SHARED_V4\n\t\t\t\t? (result.allocateIpAddress.app.sharedIpAddress ?? \"\")\n\t\t\t\t: (node?.address ?? \"\");\n\n\t\tif (!address) {\n\t\t\tthrow new Error(\n\t\t\t\t`Fly IP allocation for app \"${inputs.appName}\" (${inputs.type}) returned no address`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tid: address,\n\t\t\touts: { ...inputs, address, ipAddressId: node?.id },\n\t\t};\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyIpOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\treturn { id, props };\n\t}\n\n\tasync delete(_id: string, props: FlyIpOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst input: Record<string, string> = { appId: props.appName };\n\n\t\tif (props.ipAddressId) {\n\t\t\tinput.ipAddressId = props.ipAddressId;\n\t\t} else {\n\t\t\tinput.ip = props.address;\n\t\t}\n\n\t\tawait client.graphql(RELEASE_IP, { input });\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyIpOutputs,\n\t\tnews: FlyIpInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\t\tif (olds.type !== news.type) {\n\t\t\treplaces.push(\"type\");\n\t\t}\n\t\tif (olds.region !== news.region) {\n\t\t\treplaces.push(\"region\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n\n\tprivate async findExisting(\n\t\tclient: FlyClient,\n\t\tinputs: FlyIpInputs,\n\t): Promise<{ address: string; ipAddressId?: string } | null> {\n\t\tconst result = await client.graphql<ListIpsResult>(LIST_IPS, {\n\t\t\tappName: inputs.appName,\n\t\t});\n\n\t\tif (inputs.type === FlyIpType.SHARED_V4) {\n\t\t\tconst shared = result.app.sharedIpAddress;\n\n\t\t\treturn shared ? { address: shared } : null;\n\t\t}\n\n\t\tconst match = result.app.ipAddresses.nodes.find(\n\t\t\t(node) =>\n\t\t\t\tnode.type === inputs.type &&\n\t\t\t\t(inputs.region === undefined || node.region === inputs.region),\n\t\t);\n\n\t\treturn match ? { address: match.address, ipAddressId: match.id } : null;\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyIpResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly address: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\ttype: pulumi.Input<FlyIpType>;\n\t\t\tregion?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyIpResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, address: undefined, ipAddressId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyIp. */\ntype FlyIpOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the IP belongs to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyIp. */\nexport interface FlyIpArgs {\n\t/** IP address type. */\n\ttype: pulumi.Input<FlyIpType>;\n\n\t/** Region (IATA code); omit for a global address. */\n\tregion?: pulumi.Input<string>;\n}\n\n/**\n * Allocates a Fly IP address (dedicated or shared) via the Fly GraphQL API.\n *\n * @example\n * ```typescript\n * const ip = new FlyIp(\"api-ip\", { type: FlyIpType.SHARED_V4 }, { provider, app });\n * ```\n */\nexport class FlyIp extends pulumi.ComponentResource {\n\t/** Allocated IP address. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyIpArgs, opts: FlyIpOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Ip\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyIpResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\ttype: args.type,\n\t\t\t\tregion: args.region,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.address;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"secret.cjs","names":["FlyClient","pulumi"],"sources":["../../src/fly/secret.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app.js\";\nimport { FlyClient } from \"./client.js\";\nimport type { FlyProvider } from \"./provider.js\";\n\n/** Resolved inputs for the Fly secret dynamic provider. */\nexport interface FlySecretInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name the secrets belong to. */\n\tappName: string;\n\n\t/** Secret key/value pairs to set on the app. */\n\tsecrets: Record<string, string>;\n}\n\n/** Persisted state for Fly secrets. */\ninterface FlySecretOutputs extends FlySecretInputs {\n\t/** Fly secrets version (uint64, stored as string). Changes on every mutation. */\n\tversion: string;\n}\n\n/** Response shape of the bulk secrets endpoint. */\ninterface UpdateSecretsResponse {\n\tversion: number;\n}\n\n/**\n * POSTs a `values` map to `/v1/apps/{app}/secrets`. Keys with `null` values are\n * deleted; keys with string values are set. Returns the new version as a string.\n */\nasync function applySecrets(\n\tclient: FlyClient,\n\tappName: string,\n\tvalues: Record<string, string | null>,\n): Promise<string> {\n\tconst response = await client.post<UpdateSecretsResponse>(\n\t\t`/v1/apps/${appName}/secrets`,\n\t\t{ values },\n\t);\n\n\treturn String(response.version);\n}\n\n/**\n * Dynamic provider for Fly app secrets via the Machines REST bulk endpoint.\n *\n * Secret values are stored in state (required to diff them) and wrapped with\n * `pulumi.secret()` by the public resource so they are encrypted at rest.\n * Setting secrets only takes effect on the next machine restart — wire\n * `FlySecret.version` into `FlyDeploy.triggers` to force a redeploy on change.\n */\nclass FlySecretResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlySecretInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\t\tconst version = await applySecrets(client, inputs.appName, inputs.secrets);\n\n\t\tconst outs: FlySecretOutputs = { ...inputs, version };\n\n\t\treturn { id: `${inputs.appName}-secrets`, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlySecretOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\t// Values are write-only (the API returns digests, not plaintext), so we\n\t\t// keep the desired state as the source of truth on refresh.\n\t\treturn { id, props };\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\tolds: FlySecretOutputs,\n\t\tnews: FlySecretInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new FlyClient(news.token);\n\n\t\tconst values: Record<string, string | null> = { ...news.secrets };\n\n\t\tfor (const key of Object.keys(olds.secrets)) {\n\t\t\tif (!(key in news.secrets)) {\n\t\t\t\tvalues[key] = null;\n\t\t\t}\n\t\t}\n\n\t\tconst version = await applySecrets(client, news.appName, values);\n\n\t\treturn { outs: { ...news, version } };\n\t}\n\n\tasync delete(_id: string, props: FlySecretOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst values: Record<string, string | null> = {};\n\n\t\tfor (const key of Object.keys(props.secrets)) {\n\t\t\tvalues[key] = null;\n\t\t}\n\n\t\tawait applySecrets(client, props.appName, values);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlySecretOutputs,\n\t\tnews: FlySecretInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\n\t\tconst oldKeys = Object.keys(olds.secrets).sort().join(\",\");\n\t\tconst newKeys = Object.keys(news.secrets).sort().join(\",\");\n\n\t\tconst valuesChanged = Object.entries(news.secrets).some(\n\t\t\t([key, value]) => olds.secrets[key] !== value,\n\t\t);\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || oldKeys !== newKeys || valuesChanged,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlySecretResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly version: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\tsecrets: pulumi.Input<Record<string, string>>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlySecretResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, version: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlySecret. */\ntype FlySecretOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the secrets belong to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlySecret. */\nexport interface FlySecretArgs {\n\t/** Secret key/value pairs to set on the app. */\n\tsecrets: pulumi.Input<Record<string, string>>;\n}\n\n/**\n * Manages an app's Fly secrets as a single resource.\n *\n * Exposes `.version`, which changes only when the secret set changes — feed it\n * into `FlyDeploy.triggers` so a redeploy fires when secrets change.\n *\n * @example\n * ```typescript\n * const secrets = new FlySecret(\"api-secrets\", {\n * secrets: { JWT_SECRET: jwt, DATABASE_URL: dbUrl },\n * }, { provider, app });\n * ```\n */\nexport class FlySecret extends pulumi.ComponentResource {\n\t/** Fly secrets version. Changes only when the secret set changes. */\n\tpublic readonly version: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlySecretArgs, opts: FlySecretOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Secret\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlySecretResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\tsecrets: pulumi.secret(args.secrets),\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.version = resource.version;\n\n\t\tthis.registerOutputs({ version: this.version });\n\t}\n}\n"],"mappings":";;;;;;;;;;;AAiCA,eAAe,aACd,QACA,SACA,QACkB;CAClB,MAAM,WAAW,MAAM,OAAO,KAC7B,YAAY,QAAQ,WACpB,EAAE,OAAO,CACV;CAEA,OAAO,OAAO,SAAS,OAAO;AAC/B;;;;;;;;;AAUA,IAAM,4BAAN,MAA2E;CAC1E,MAAM,OAAO,QAA+D;EAE3E,MAAM,UAAU,MAAM,aAAa,IADhBA,6BAAU,OAAO,KACI,GAAG,OAAO,SAAS,OAAO,OAAO;EAEzE,MAAM,OAAyB;GAAE,GAAG;GAAQ;EAAQ;EAEpD,OAAO;GAAE,IAAI,GAAG,OAAO,QAAQ;GAAW;EAAK;CAChD;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,OAAO;GAAE;GAAI;EAAM;CACpB;CAEA,MAAM,OACL,KACA,MACA,MACuC;EACvC,MAAM,SAAS,IAAIA,6BAAU,KAAK,KAAK;EAEvC,MAAM,SAAwC,EAAE,GAAG,KAAK,QAAQ;EAEhE,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,OAAO,GACzC,IAAI,EAAE,OAAO,KAAK,UACjB,OAAO,OAAO;EAIhB,MAAM,UAAU,MAAM,aAAa,QAAQ,KAAK,SAAS,MAAM;EAE/D,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM;EAAQ,EAAE;CACrC;CAEA,MAAM,OAAO,KAAa,OAAwC;EACjE,MAAM,SAAS,IAAIA,6BAAU,MAAM,KAAK;EAExC,MAAM,SAAwC,CAAC;EAE/C,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,OAAO,GAC1C,OAAO,OAAO;EAGf,MAAM,aAAa,QAAQ,MAAM,SAAS,MAAM;CACjD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAGxB,MAAM,UAAU,OAAO,KAAK,KAAK,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;EACzD,MAAM,UAAU,OAAO,KAAK,KAAK,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;EAEzD,MAAM,gBAAgB,OAAO,QAAQ,KAAK,OAAO,EAAE,MACjD,CAAC,KAAK,WAAW,KAAK,QAAQ,SAAS,KACzC;EAEA,OAAO;GACN,SAAS,SAAS,SAAS,KAAK,YAAY,WAAW;GACvD;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,oBAAN,cAAgCC,eAAO,QAAQ,SAAS;CAGvD,YACC,MACA,MAKA,MACC;EACD,MACC,IAAI,0BAA0B,GAC9B,MACA;GAAE,GAAG;GAAM,SAAS;EAAU,GAC9B,IACD;CACD;AACD;;;;;;;;;;;;;;AA8BA,IAAa,YAAb,cAA+BA,eAAO,kBAAkB;CAIvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,WAAW,IAAI,kBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,SAASA,eAAO,OAAO,KAAK,OAAO;EACpC,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,UAAU,SAAS;EAExB,KAAK,gBAAgB,EAAE,SAAS,KAAK,QAAQ,CAAC;CAC/C;AACD"}
1
+ {"version":3,"file":"secret.cjs","names":["FlyClient","pulumi"],"sources":["../../src/fly/secret.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app\";\nimport { FlyClient } from \"./client\";\nimport type { FlyProvider } from \"./provider\";\n\n/** Resolved inputs for the Fly secret dynamic provider. */\nexport interface FlySecretInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name the secrets belong to. */\n\tappName: string;\n\n\t/** Secret key/value pairs to set on the app. */\n\tsecrets: Record<string, string>;\n}\n\n/** Persisted state for Fly secrets. */\ninterface FlySecretOutputs extends FlySecretInputs {\n\t/** Fly secrets version (uint64, stored as string). Changes on every mutation. */\n\tversion: string;\n}\n\n/** Response shape of the bulk secrets endpoint. */\ninterface UpdateSecretsResponse {\n\tversion: number;\n}\n\n/**\n * POSTs a `values` map to `/v1/apps/{app}/secrets`. Keys with `null` values are\n * deleted; keys with string values are set. Returns the new version as a string.\n */\nasync function applySecrets(\n\tclient: FlyClient,\n\tappName: string,\n\tvalues: Record<string, string | null>,\n): Promise<string> {\n\tconst response = await client.post<UpdateSecretsResponse>(\n\t\t`/v1/apps/${appName}/secrets`,\n\t\t{ values },\n\t);\n\n\treturn String(response.version);\n}\n\n/**\n * Dynamic provider for Fly app secrets via the Machines REST bulk endpoint.\n *\n * Secret values are stored in state (required to diff them) and wrapped with\n * `pulumi.secret()` by the public resource so they are encrypted at rest.\n * Setting secrets only takes effect on the next machine restart — wire\n * `FlySecret.version` into `FlyDeploy.triggers` to force a redeploy on change.\n */\nclass FlySecretResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlySecretInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\t\tconst version = await applySecrets(client, inputs.appName, inputs.secrets);\n\n\t\tconst outs: FlySecretOutputs = { ...inputs, version };\n\n\t\treturn { id: `${inputs.appName}-secrets`, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlySecretOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\t// Values are write-only (the API returns digests, not plaintext), so we\n\t\t// keep the desired state as the source of truth on refresh.\n\t\treturn { id, props };\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\tolds: FlySecretOutputs,\n\t\tnews: FlySecretInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new FlyClient(news.token);\n\n\t\tconst values: Record<string, string | null> = { ...news.secrets };\n\n\t\tfor (const key of Object.keys(olds.secrets)) {\n\t\t\tif (!(key in news.secrets)) {\n\t\t\t\tvalues[key] = null;\n\t\t\t}\n\t\t}\n\n\t\tconst version = await applySecrets(client, news.appName, values);\n\n\t\treturn { outs: { ...news, version } };\n\t}\n\n\tasync delete(_id: string, props: FlySecretOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst values: Record<string, string | null> = {};\n\n\t\tfor (const key of Object.keys(props.secrets)) {\n\t\t\tvalues[key] = null;\n\t\t}\n\n\t\tawait applySecrets(client, props.appName, values);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlySecretOutputs,\n\t\tnews: FlySecretInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\n\t\tconst oldKeys = Object.keys(olds.secrets).sort().join(\",\");\n\t\tconst newKeys = Object.keys(news.secrets).sort().join(\",\");\n\n\t\tconst valuesChanged = Object.entries(news.secrets).some(\n\t\t\t([key, value]) => olds.secrets[key] !== value,\n\t\t);\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || oldKeys !== newKeys || valuesChanged,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlySecretResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly version: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\tsecrets: pulumi.Input<Record<string, string>>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlySecretResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, version: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlySecret. */\ntype FlySecretOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the secrets belong to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlySecret. */\nexport interface FlySecretArgs {\n\t/** Secret key/value pairs to set on the app. */\n\tsecrets: pulumi.Input<Record<string, string>>;\n}\n\n/**\n * Manages an app's Fly secrets as a single resource.\n *\n * Exposes `.version`, which changes only when the secret set changes — feed it\n * into `FlyDeploy.triggers` so a redeploy fires when secrets change.\n *\n * @example\n * ```typescript\n * const secrets = new FlySecret(\"api-secrets\", {\n * secrets: { JWT_SECRET: jwt, DATABASE_URL: dbUrl },\n * }, { provider, app });\n * ```\n */\nexport class FlySecret extends pulumi.ComponentResource {\n\t/** Fly secrets version. Changes only when the secret set changes. */\n\tpublic readonly version: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlySecretArgs, opts: FlySecretOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Secret\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlySecretResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\tsecrets: pulumi.secret(args.secrets),\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.version = resource.version;\n\n\t\tthis.registerOutputs({ version: this.version });\n\t}\n}\n"],"mappings":";;;;;;;;;;;AAiCA,eAAe,aACd,QACA,SACA,QACkB;CAClB,MAAM,WAAW,MAAM,OAAO,KAC7B,YAAY,QAAQ,WACpB,EAAE,OAAO,CACV;CAEA,OAAO,OAAO,SAAS,OAAO;AAC/B;;;;;;;;;AAUA,IAAM,4BAAN,MAA2E;CAC1E,MAAM,OAAO,QAA+D;EAE3E,MAAM,UAAU,MAAM,aAAa,IADhBA,6BAAU,OAAO,KACI,GAAG,OAAO,SAAS,OAAO,OAAO;EAEzE,MAAM,OAAyB;GAAE,GAAG;GAAQ;EAAQ;EAEpD,OAAO;GAAE,IAAI,GAAG,OAAO,QAAQ;GAAW;EAAK;CAChD;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,OAAO;GAAE;GAAI;EAAM;CACpB;CAEA,MAAM,OACL,KACA,MACA,MACuC;EACvC,MAAM,SAAS,IAAIA,6BAAU,KAAK,KAAK;EAEvC,MAAM,SAAwC,EAAE,GAAG,KAAK,QAAQ;EAEhE,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,OAAO,GACzC,IAAI,EAAE,OAAO,KAAK,UACjB,OAAO,OAAO;EAIhB,MAAM,UAAU,MAAM,aAAa,QAAQ,KAAK,SAAS,MAAM;EAE/D,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM;EAAQ,EAAE;CACrC;CAEA,MAAM,OAAO,KAAa,OAAwC;EACjE,MAAM,SAAS,IAAIA,6BAAU,MAAM,KAAK;EAExC,MAAM,SAAwC,CAAC;EAE/C,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,OAAO,GAC1C,OAAO,OAAO;EAGf,MAAM,aAAa,QAAQ,MAAM,SAAS,MAAM;CACjD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAGxB,MAAM,UAAU,OAAO,KAAK,KAAK,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;EACzD,MAAM,UAAU,OAAO,KAAK,KAAK,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;EAEzD,MAAM,gBAAgB,OAAO,QAAQ,KAAK,OAAO,EAAE,MACjD,CAAC,KAAK,WAAW,KAAK,QAAQ,SAAS,KACzC;EAEA,OAAO;GACN,SAAS,SAAS,SAAS,KAAK,YAAY,WAAW;GACvD;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,oBAAN,cAAgCC,eAAO,QAAQ,SAAS;CAGvD,YACC,MACA,MAKA,MACC;EACD,MACC,IAAI,0BAA0B,GAC9B,MACA;GAAE,GAAG;GAAM,SAAS;EAAU,GAC9B,IACD;CACD;AACD;;;;;;;;;;;;;;AA8BA,IAAa,YAAb,cAA+BA,eAAO,kBAAkB;CAIvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,WAAW,IAAI,kBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,SAASA,eAAO,OAAO,KAAK,OAAO;EACpC,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,UAAU,SAAS;EAExB,KAAK,gBAAgB,EAAE,SAAS,KAAK,QAAQ,CAAC;CAC/C;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"secret.mjs","names":[],"sources":["../../src/fly/secret.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app.js\";\nimport { FlyClient } from \"./client.js\";\nimport type { FlyProvider } from \"./provider.js\";\n\n/** Resolved inputs for the Fly secret dynamic provider. */\nexport interface FlySecretInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name the secrets belong to. */\n\tappName: string;\n\n\t/** Secret key/value pairs to set on the app. */\n\tsecrets: Record<string, string>;\n}\n\n/** Persisted state for Fly secrets. */\ninterface FlySecretOutputs extends FlySecretInputs {\n\t/** Fly secrets version (uint64, stored as string). Changes on every mutation. */\n\tversion: string;\n}\n\n/** Response shape of the bulk secrets endpoint. */\ninterface UpdateSecretsResponse {\n\tversion: number;\n}\n\n/**\n * POSTs a `values` map to `/v1/apps/{app}/secrets`. Keys with `null` values are\n * deleted; keys with string values are set. Returns the new version as a string.\n */\nasync function applySecrets(\n\tclient: FlyClient,\n\tappName: string,\n\tvalues: Record<string, string | null>,\n): Promise<string> {\n\tconst response = await client.post<UpdateSecretsResponse>(\n\t\t`/v1/apps/${appName}/secrets`,\n\t\t{ values },\n\t);\n\n\treturn String(response.version);\n}\n\n/**\n * Dynamic provider for Fly app secrets via the Machines REST bulk endpoint.\n *\n * Secret values are stored in state (required to diff them) and wrapped with\n * `pulumi.secret()` by the public resource so they are encrypted at rest.\n * Setting secrets only takes effect on the next machine restart — wire\n * `FlySecret.version` into `FlyDeploy.triggers` to force a redeploy on change.\n */\nclass FlySecretResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlySecretInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\t\tconst version = await applySecrets(client, inputs.appName, inputs.secrets);\n\n\t\tconst outs: FlySecretOutputs = { ...inputs, version };\n\n\t\treturn { id: `${inputs.appName}-secrets`, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlySecretOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\t// Values are write-only (the API returns digests, not plaintext), so we\n\t\t// keep the desired state as the source of truth on refresh.\n\t\treturn { id, props };\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\tolds: FlySecretOutputs,\n\t\tnews: FlySecretInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new FlyClient(news.token);\n\n\t\tconst values: Record<string, string | null> = { ...news.secrets };\n\n\t\tfor (const key of Object.keys(olds.secrets)) {\n\t\t\tif (!(key in news.secrets)) {\n\t\t\t\tvalues[key] = null;\n\t\t\t}\n\t\t}\n\n\t\tconst version = await applySecrets(client, news.appName, values);\n\n\t\treturn { outs: { ...news, version } };\n\t}\n\n\tasync delete(_id: string, props: FlySecretOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst values: Record<string, string | null> = {};\n\n\t\tfor (const key of Object.keys(props.secrets)) {\n\t\t\tvalues[key] = null;\n\t\t}\n\n\t\tawait applySecrets(client, props.appName, values);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlySecretOutputs,\n\t\tnews: FlySecretInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\n\t\tconst oldKeys = Object.keys(olds.secrets).sort().join(\",\");\n\t\tconst newKeys = Object.keys(news.secrets).sort().join(\",\");\n\n\t\tconst valuesChanged = Object.entries(news.secrets).some(\n\t\t\t([key, value]) => olds.secrets[key] !== value,\n\t\t);\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || oldKeys !== newKeys || valuesChanged,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlySecretResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly version: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\tsecrets: pulumi.Input<Record<string, string>>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlySecretResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, version: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlySecret. */\ntype FlySecretOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the secrets belong to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlySecret. */\nexport interface FlySecretArgs {\n\t/** Secret key/value pairs to set on the app. */\n\tsecrets: pulumi.Input<Record<string, string>>;\n}\n\n/**\n * Manages an app's Fly secrets as a single resource.\n *\n * Exposes `.version`, which changes only when the secret set changes — feed it\n * into `FlyDeploy.triggers` so a redeploy fires when secrets change.\n *\n * @example\n * ```typescript\n * const secrets = new FlySecret(\"api-secrets\", {\n * secrets: { JWT_SECRET: jwt, DATABASE_URL: dbUrl },\n * }, { provider, app });\n * ```\n */\nexport class FlySecret extends pulumi.ComponentResource {\n\t/** Fly secrets version. Changes only when the secret set changes. */\n\tpublic readonly version: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlySecretArgs, opts: FlySecretOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Secret\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlySecretResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\tsecrets: pulumi.secret(args.secrets),\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.version = resource.version;\n\n\t\tthis.registerOutputs({ version: this.version });\n\t}\n}\n"],"mappings":";;;;;;;;;AAiCA,eAAe,aACd,QACA,SACA,QACkB;CAClB,MAAM,WAAW,MAAM,OAAO,KAC7B,YAAY,QAAQ,WACpB,EAAE,OAAO,CACV;CAEA,OAAO,OAAO,SAAS,OAAO;AAC/B;;;;;;;;;AAUA,IAAM,4BAAN,MAA2E;CAC1E,MAAM,OAAO,QAA+D;EAE3E,MAAM,UAAU,MAAM,aAAa,IADhB,UAAU,OAAO,KACI,GAAG,OAAO,SAAS,OAAO,OAAO;EAEzE,MAAM,OAAyB;GAAE,GAAG;GAAQ;EAAQ;EAEpD,OAAO;GAAE,IAAI,GAAG,OAAO,QAAQ;GAAW;EAAK;CAChD;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,OAAO;GAAE;GAAI;EAAM;CACpB;CAEA,MAAM,OACL,KACA,MACA,MACuC;EACvC,MAAM,SAAS,IAAI,UAAU,KAAK,KAAK;EAEvC,MAAM,SAAwC,EAAE,GAAG,KAAK,QAAQ;EAEhE,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,OAAO,GACzC,IAAI,EAAE,OAAO,KAAK,UACjB,OAAO,OAAO;EAIhB,MAAM,UAAU,MAAM,aAAa,QAAQ,KAAK,SAAS,MAAM;EAE/D,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM;EAAQ,EAAE;CACrC;CAEA,MAAM,OAAO,KAAa,OAAwC;EACjE,MAAM,SAAS,IAAI,UAAU,MAAM,KAAK;EAExC,MAAM,SAAwC,CAAC;EAE/C,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,OAAO,GAC1C,OAAO,OAAO;EAGf,MAAM,aAAa,QAAQ,MAAM,SAAS,MAAM;CACjD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAGxB,MAAM,UAAU,OAAO,KAAK,KAAK,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;EACzD,MAAM,UAAU,OAAO,KAAK,KAAK,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;EAEzD,MAAM,gBAAgB,OAAO,QAAQ,KAAK,OAAO,EAAE,MACjD,CAAC,KAAK,WAAW,KAAK,QAAQ,SAAS,KACzC;EAEA,OAAO;GACN,SAAS,SAAS,SAAS,KAAK,YAAY,WAAW;GACvD;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,oBAAN,cAAgC,OAAO,QAAQ,SAAS;CAGvD,YACC,MACA,MAKA,MACC;EACD,MACC,IAAI,0BAA0B,GAC9B,MACA;GAAE,GAAG;GAAM,SAAS;EAAU,GAC9B,IACD;CACD;AACD;;;;;;;;;;;;;;AA8BA,IAAa,YAAb,cAA+B,OAAO,kBAAkB;CAIvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,WAAW,IAAI,kBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,SAAS,OAAO,OAAO,KAAK,OAAO;EACpC,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,UAAU,SAAS;EAExB,KAAK,gBAAgB,EAAE,SAAS,KAAK,QAAQ,CAAC;CAC/C;AACD"}
1
+ {"version":3,"file":"secret.mjs","names":[],"sources":["../../src/fly/secret.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app\";\nimport { FlyClient } from \"./client\";\nimport type { FlyProvider } from \"./provider\";\n\n/** Resolved inputs for the Fly secret dynamic provider. */\nexport interface FlySecretInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name the secrets belong to. */\n\tappName: string;\n\n\t/** Secret key/value pairs to set on the app. */\n\tsecrets: Record<string, string>;\n}\n\n/** Persisted state for Fly secrets. */\ninterface FlySecretOutputs extends FlySecretInputs {\n\t/** Fly secrets version (uint64, stored as string). Changes on every mutation. */\n\tversion: string;\n}\n\n/** Response shape of the bulk secrets endpoint. */\ninterface UpdateSecretsResponse {\n\tversion: number;\n}\n\n/**\n * POSTs a `values` map to `/v1/apps/{app}/secrets`. Keys with `null` values are\n * deleted; keys with string values are set. Returns the new version as a string.\n */\nasync function applySecrets(\n\tclient: FlyClient,\n\tappName: string,\n\tvalues: Record<string, string | null>,\n): Promise<string> {\n\tconst response = await client.post<UpdateSecretsResponse>(\n\t\t`/v1/apps/${appName}/secrets`,\n\t\t{ values },\n\t);\n\n\treturn String(response.version);\n}\n\n/**\n * Dynamic provider for Fly app secrets via the Machines REST bulk endpoint.\n *\n * Secret values are stored in state (required to diff them) and wrapped with\n * `pulumi.secret()` by the public resource so they are encrypted at rest.\n * Setting secrets only takes effect on the next machine restart — wire\n * `FlySecret.version` into `FlyDeploy.triggers` to force a redeploy on change.\n */\nclass FlySecretResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlySecretInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\t\tconst version = await applySecrets(client, inputs.appName, inputs.secrets);\n\n\t\tconst outs: FlySecretOutputs = { ...inputs, version };\n\n\t\treturn { id: `${inputs.appName}-secrets`, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlySecretOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\t// Values are write-only (the API returns digests, not plaintext), so we\n\t\t// keep the desired state as the source of truth on refresh.\n\t\treturn { id, props };\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\tolds: FlySecretOutputs,\n\t\tnews: FlySecretInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new FlyClient(news.token);\n\n\t\tconst values: Record<string, string | null> = { ...news.secrets };\n\n\t\tfor (const key of Object.keys(olds.secrets)) {\n\t\t\tif (!(key in news.secrets)) {\n\t\t\t\tvalues[key] = null;\n\t\t\t}\n\t\t}\n\n\t\tconst version = await applySecrets(client, news.appName, values);\n\n\t\treturn { outs: { ...news, version } };\n\t}\n\n\tasync delete(_id: string, props: FlySecretOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst values: Record<string, string | null> = {};\n\n\t\tfor (const key of Object.keys(props.secrets)) {\n\t\t\tvalues[key] = null;\n\t\t}\n\n\t\tawait applySecrets(client, props.appName, values);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlySecretOutputs,\n\t\tnews: FlySecretInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\n\t\tconst oldKeys = Object.keys(olds.secrets).sort().join(\",\");\n\t\tconst newKeys = Object.keys(news.secrets).sort().join(\",\");\n\n\t\tconst valuesChanged = Object.entries(news.secrets).some(\n\t\t\t([key, value]) => olds.secrets[key] !== value,\n\t\t);\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || oldKeys !== newKeys || valuesChanged,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlySecretResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly version: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\tsecrets: pulumi.Input<Record<string, string>>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlySecretResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, version: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlySecret. */\ntype FlySecretOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the secrets belong to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlySecret. */\nexport interface FlySecretArgs {\n\t/** Secret key/value pairs to set on the app. */\n\tsecrets: pulumi.Input<Record<string, string>>;\n}\n\n/**\n * Manages an app's Fly secrets as a single resource.\n *\n * Exposes `.version`, which changes only when the secret set changes — feed it\n * into `FlyDeploy.triggers` so a redeploy fires when secrets change.\n *\n * @example\n * ```typescript\n * const secrets = new FlySecret(\"api-secrets\", {\n * secrets: { JWT_SECRET: jwt, DATABASE_URL: dbUrl },\n * }, { provider, app });\n * ```\n */\nexport class FlySecret extends pulumi.ComponentResource {\n\t/** Fly secrets version. Changes only when the secret set changes. */\n\tpublic readonly version: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlySecretArgs, opts: FlySecretOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Secret\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlySecretResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\tsecrets: pulumi.secret(args.secrets),\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.version = resource.version;\n\n\t\tthis.registerOutputs({ version: this.version });\n\t}\n}\n"],"mappings":";;;;;;;;;AAiCA,eAAe,aACd,QACA,SACA,QACkB;CAClB,MAAM,WAAW,MAAM,OAAO,KAC7B,YAAY,QAAQ,WACpB,EAAE,OAAO,CACV;CAEA,OAAO,OAAO,SAAS,OAAO;AAC/B;;;;;;;;;AAUA,IAAM,4BAAN,MAA2E;CAC1E,MAAM,OAAO,QAA+D;EAE3E,MAAM,UAAU,MAAM,aAAa,IADhB,UAAU,OAAO,KACI,GAAG,OAAO,SAAS,OAAO,OAAO;EAEzE,MAAM,OAAyB;GAAE,GAAG;GAAQ;EAAQ;EAEpD,OAAO;GAAE,IAAI,GAAG,OAAO,QAAQ;GAAW;EAAK;CAChD;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,OAAO;GAAE;GAAI;EAAM;CACpB;CAEA,MAAM,OACL,KACA,MACA,MACuC;EACvC,MAAM,SAAS,IAAI,UAAU,KAAK,KAAK;EAEvC,MAAM,SAAwC,EAAE,GAAG,KAAK,QAAQ;EAEhE,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,OAAO,GACzC,IAAI,EAAE,OAAO,KAAK,UACjB,OAAO,OAAO;EAIhB,MAAM,UAAU,MAAM,aAAa,QAAQ,KAAK,SAAS,MAAM;EAE/D,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM;EAAQ,EAAE;CACrC;CAEA,MAAM,OAAO,KAAa,OAAwC;EACjE,MAAM,SAAS,IAAI,UAAU,MAAM,KAAK;EAExC,MAAM,SAAwC,CAAC;EAE/C,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,OAAO,GAC1C,OAAO,OAAO;EAGf,MAAM,aAAa,QAAQ,MAAM,SAAS,MAAM;CACjD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAGxB,MAAM,UAAU,OAAO,KAAK,KAAK,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;EACzD,MAAM,UAAU,OAAO,KAAK,KAAK,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;EAEzD,MAAM,gBAAgB,OAAO,QAAQ,KAAK,OAAO,EAAE,MACjD,CAAC,KAAK,WAAW,KAAK,QAAQ,SAAS,KACzC;EAEA,OAAO;GACN,SAAS,SAAS,SAAS,KAAK,YAAY,WAAW;GACvD;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,oBAAN,cAAgC,OAAO,QAAQ,SAAS;CAGvD,YACC,MACA,MAKA,MACC;EACD,MACC,IAAI,0BAA0B,GAC9B,MACA;GAAE,GAAG;GAAM,SAAS;EAAU,GAC9B,IACD;CACD;AACD;;;;;;;;;;;;;;AA8BA,IAAa,YAAb,cAA+B,OAAO,kBAAkB;CAIvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,WAAW,IAAI,kBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,SAAS,OAAO,OAAO,KAAK,OAAO;EACpC,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,UAAU,SAAS;EAExB,KAAK,gBAAgB,EAAE,SAAS,KAAK,QAAQ,CAAC;CAC/C;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"volume.cjs","names":["FlyClient","pulumi"],"sources":["../../src/fly/volume.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app.js\";\nimport { FlyClient } from \"./client.js\";\nimport type { FlyProvider } from \"./provider.js\";\n\n/** Resolved inputs for the Fly volume dynamic provider. */\nexport interface FlyVolumeInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name the volume belongs to. */\n\tappName: string;\n\n\t/** Volume name (used for adoption lookup). */\n\tname: string;\n\n\t/** Region (IATA code). */\n\tregion: string;\n\n\t/** Volume size in GB. */\n\tsizeGb: number;\n}\n\n/** Persisted state for the Fly volume. */\ninterface FlyVolumeOutputs extends FlyVolumeInputs {\n\t/** Fly-assigned volume ID (`vol_…`). */\n\tvolumeId: string;\n}\n\n/** Volume response (only the fields we read). */\ninterface FlyVolumeResponse {\n\tid: string;\n\tname: string;\n\tstate: string;\n\tsize_gb: number;\n\tregion: string;\n}\n\n/**\n * Dynamic provider for Fly volumes. `create()` lists volumes and adopts one\n * matching the name (volume names are not unique, so it adopts the first\n * non-destroyed match); otherwise it creates a new encrypted volume. Growing\n * `sizeGb` extends in place; shrinking is not supported by Fly.\n */\nclass FlyVolumeResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlyVolumeInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\n\t\tconst volumes = await client.get<FlyVolumeResponse[]>(\n\t\t\t`/v1/apps/${inputs.appName}/volumes`,\n\t\t);\n\n\t\tconst existing = volumes.find(\n\t\t\t(volume) => volume.name === inputs.name && volume.state !== \"destroyed\",\n\t\t);\n\n\t\tlet volumeId: string;\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Fly volume \"${inputs.name}\" (${existing.id})`,\n\t\t\t);\n\n\t\t\tvolumeId = existing.id;\n\t\t} else {\n\t\t\tconst created = await client.post<FlyVolumeResponse>(\n\t\t\t\t`/v1/apps/${inputs.appName}/volumes`,\n\t\t\t\t{\n\t\t\t\t\tname: inputs.name,\n\t\t\t\t\tregion: inputs.region,\n\t\t\t\t\tsize_gb: inputs.sizeGb,\n\t\t\t\t\tencrypted: true,\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tvolumeId = created.id;\n\t\t}\n\n\t\tconst outs: FlyVolumeOutputs = { ...inputs, volumeId };\n\n\t\treturn { id: volumeId, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyVolumeOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst volume = await client.tryGet<FlyVolumeResponse>(\n\t\t\t`/v1/apps/${props.appName}/volumes/${id}`,\n\t\t);\n\n\t\tif (!volume) {\n\t\t\tthrow new Error(`Fly volume \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tname: volume.name,\n\t\t\t\tregion: volume.region,\n\t\t\t\tsizeGb: volume.size_gb,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\tolds: FlyVolumeOutputs,\n\t\tnews: FlyVolumeInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tif (news.sizeGb > olds.sizeGb) {\n\t\t\tconst client = new FlyClient(news.token);\n\n\t\t\tawait client.put(`/v1/apps/${news.appName}/volumes/${id}/extend`, {\n\t\t\t\tsize_gb: news.sizeGb,\n\t\t\t});\n\t\t}\n\n\t\treturn { outs: { ...news, volumeId: id } };\n\t}\n\n\tasync delete(id: string, props: FlyVolumeOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tawait client.delete(`/v1/apps/${props.appName}/volumes/${id}`);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyVolumeOutputs,\n\t\tnews: FlyVolumeInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\t\tif (olds.region !== news.region) {\n\t\t\treplaces.push(\"region\");\n\t\t}\n\t\tif (news.sizeGb < olds.sizeGb) {\n\t\t\treplaces.push(\"sizeGb\");\n\t\t}\n\n\t\tconst sizeGrew = news.sizeGb > olds.sizeGb;\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || sizeGrew,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyVolumeResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly volumeId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tregion: pulumi.Input<string>;\n\t\t\tsizeGb: pulumi.Input<number>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyVolumeResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, volumeId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyVolume. */\ntype FlyVolumeOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the volume belongs to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyVolume. */\nexport interface FlyVolumeArgs {\n\t/** Volume name. */\n\tname: pulumi.Input<string>;\n\n\t/** Region (IATA code, e.g. `\"iad\"`). */\n\tregion: pulumi.Input<string>;\n\n\t/** Volume size in GB. Can be grown (extended) but not shrunk. */\n\tsizeGb: pulumi.Input<number>;\n}\n\n/**\n * Manages a Fly volume with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const volume = new FlyVolume(\"api-data\", {\n * name: \"data\",\n * region: \"iad\",\n * sizeGb: 10,\n * }, { provider, app });\n * ```\n */\nexport class FlyVolume extends pulumi.ComponentResource {\n\t/** Fly-assigned volume ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyVolumeArgs, opts: FlyVolumeOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Volume\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyVolumeResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.volumeId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;AA6CA,IAAM,4BAAN,MAA2E;CAC1E,MAAM,OAAO,QAA+D;EAC3E,MAAM,SAAS,IAAIA,6BAAU,OAAO,KAAK;EAMzC,MAAM,YAAW,MAJK,OAAO,IAC5B,YAAY,OAAO,QAAQ,SAC5B,GAEyB,MACvB,WAAW,OAAO,SAAS,OAAO,QAAQ,OAAO,UAAU,WAC7D;EAEA,IAAI;EAEJ,IAAI,UAAU;GACb,eAAO,IAAI,KACV,iCAAiC,OAAO,KAAK,KAAK,SAAS,GAAG,EAC/D;GAEA,WAAW,SAAS;EACrB,OAWC,YAAW,MAVW,OAAO,KAC5B,YAAY,OAAO,QAAQ,WAC3B;GACC,MAAM,OAAO;GACb,QAAQ,OAAO;GACf,SAAS,OAAO;GAChB,WAAW;EACZ,CACD,GAEmB;EAGpB,MAAM,OAAyB;GAAE,GAAG;GAAQ;EAAS;EAErD,OAAO;GAAE,IAAI;GAAU;EAAK;CAC7B;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,SAAS,MAAM,IAFFA,6BAAU,MAAM,KAET,EAAE,OAC3B,YAAY,MAAM,QAAQ,WAAW,IACtC;EAEA,IAAI,CAAC,QACJ,MAAM,IAAI,MAAM,eAAe,GAAG,2BAA2B;EAG9D,OAAO;GACN;GACA,OAAO;IACN,GAAG;IACH,MAAM,OAAO;IACb,QAAQ,OAAO;IACf,QAAQ,OAAO;GAChB;EACD;CACD;CAEA,MAAM,OACL,IACA,MACA,MACuC;EACvC,IAAI,KAAK,SAAS,KAAK,QAGtB,MAAM,IAFaA,6BAAU,KAAK,KAEvB,EAAE,IAAI,YAAY,KAAK,QAAQ,WAAW,GAAG,UAAU,EACjE,SAAS,KAAK,OACf,CAAC;EAGF,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM,UAAU;EAAG,EAAE;CAC1C;CAEA,MAAM,OAAO,IAAY,OAAwC;EAGhE,MAAM,IAFaA,6BAAU,MAAM,KAExB,EAAE,OAAO,YAAY,MAAM,QAAQ,WAAW,IAAI;CAC9D;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAExB,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAErB,IAAI,KAAK,WAAW,KAAK,QACxB,SAAS,KAAK,QAAQ;EAEvB,IAAI,KAAK,SAAS,KAAK,QACtB,SAAS,KAAK,QAAQ;EAGvB,MAAM,WAAW,KAAK,SAAS,KAAK;EAEpC,OAAO;GACN,SAAS,SAAS,SAAS,KAAK;GAChC;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,oBAAN,cAAgCC,eAAO,QAAQ,SAAS;CAGvD,YACC,MACA,MAOA,MACC;EACD,MACC,IAAI,0BAA0B,GAC9B,MACA;GAAE,GAAG;GAAM,UAAU;EAAU,GAC/B,IACD;CACD;AACD;;;;;;;;;;;;;AAmCA,IAAa,YAAb,cAA+BA,eAAO,kBAAkB;CAIvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,WAAW,IAAI,kBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,GAAG;EACJ,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}
1
+ {"version":3,"file":"volume.cjs","names":["FlyClient","pulumi"],"sources":["../../src/fly/volume.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app\";\nimport { FlyClient } from \"./client\";\nimport type { FlyProvider } from \"./provider\";\n\n/** Resolved inputs for the Fly volume dynamic provider. */\nexport interface FlyVolumeInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name the volume belongs to. */\n\tappName: string;\n\n\t/** Volume name (used for adoption lookup). */\n\tname: string;\n\n\t/** Region (IATA code). */\n\tregion: string;\n\n\t/** Volume size in GB. */\n\tsizeGb: number;\n}\n\n/** Persisted state for the Fly volume. */\ninterface FlyVolumeOutputs extends FlyVolumeInputs {\n\t/** Fly-assigned volume ID (`vol_…`). */\n\tvolumeId: string;\n}\n\n/** Volume response (only the fields we read). */\ninterface FlyVolumeResponse {\n\tid: string;\n\tname: string;\n\tstate: string;\n\tsize_gb: number;\n\tregion: string;\n}\n\n/**\n * Dynamic provider for Fly volumes. `create()` lists volumes and adopts one\n * matching the name (volume names are not unique, so it adopts the first\n * non-destroyed match); otherwise it creates a new encrypted volume. Growing\n * `sizeGb` extends in place; shrinking is not supported by Fly.\n */\nclass FlyVolumeResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlyVolumeInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\n\t\tconst volumes = await client.get<FlyVolumeResponse[]>(\n\t\t\t`/v1/apps/${inputs.appName}/volumes`,\n\t\t);\n\n\t\tconst existing = volumes.find(\n\t\t\t(volume) => volume.name === inputs.name && volume.state !== \"destroyed\",\n\t\t);\n\n\t\tlet volumeId: string;\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Fly volume \"${inputs.name}\" (${existing.id})`,\n\t\t\t);\n\n\t\t\tvolumeId = existing.id;\n\t\t} else {\n\t\t\tconst created = await client.post<FlyVolumeResponse>(\n\t\t\t\t`/v1/apps/${inputs.appName}/volumes`,\n\t\t\t\t{\n\t\t\t\t\tname: inputs.name,\n\t\t\t\t\tregion: inputs.region,\n\t\t\t\t\tsize_gb: inputs.sizeGb,\n\t\t\t\t\tencrypted: true,\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tvolumeId = created.id;\n\t\t}\n\n\t\tconst outs: FlyVolumeOutputs = { ...inputs, volumeId };\n\n\t\treturn { id: volumeId, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyVolumeOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst volume = await client.tryGet<FlyVolumeResponse>(\n\t\t\t`/v1/apps/${props.appName}/volumes/${id}`,\n\t\t);\n\n\t\tif (!volume) {\n\t\t\tthrow new Error(`Fly volume \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tname: volume.name,\n\t\t\t\tregion: volume.region,\n\t\t\t\tsizeGb: volume.size_gb,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\tolds: FlyVolumeOutputs,\n\t\tnews: FlyVolumeInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tif (news.sizeGb > olds.sizeGb) {\n\t\t\tconst client = new FlyClient(news.token);\n\n\t\t\tawait client.put(`/v1/apps/${news.appName}/volumes/${id}/extend`, {\n\t\t\t\tsize_gb: news.sizeGb,\n\t\t\t});\n\t\t}\n\n\t\treturn { outs: { ...news, volumeId: id } };\n\t}\n\n\tasync delete(id: string, props: FlyVolumeOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tawait client.delete(`/v1/apps/${props.appName}/volumes/${id}`);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyVolumeOutputs,\n\t\tnews: FlyVolumeInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\t\tif (olds.region !== news.region) {\n\t\t\treplaces.push(\"region\");\n\t\t}\n\t\tif (news.sizeGb < olds.sizeGb) {\n\t\t\treplaces.push(\"sizeGb\");\n\t\t}\n\n\t\tconst sizeGrew = news.sizeGb > olds.sizeGb;\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || sizeGrew,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyVolumeResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly volumeId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tregion: pulumi.Input<string>;\n\t\t\tsizeGb: pulumi.Input<number>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyVolumeResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, volumeId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyVolume. */\ntype FlyVolumeOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the volume belongs to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyVolume. */\nexport interface FlyVolumeArgs {\n\t/** Volume name. */\n\tname: pulumi.Input<string>;\n\n\t/** Region (IATA code, e.g. `\"iad\"`). */\n\tregion: pulumi.Input<string>;\n\n\t/** Volume size in GB. Can be grown (extended) but not shrunk. */\n\tsizeGb: pulumi.Input<number>;\n}\n\n/**\n * Manages a Fly volume with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const volume = new FlyVolume(\"api-data\", {\n * name: \"data\",\n * region: \"iad\",\n * sizeGb: 10,\n * }, { provider, app });\n * ```\n */\nexport class FlyVolume extends pulumi.ComponentResource {\n\t/** Fly-assigned volume ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyVolumeArgs, opts: FlyVolumeOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Volume\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyVolumeResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.volumeId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;AA6CA,IAAM,4BAAN,MAA2E;CAC1E,MAAM,OAAO,QAA+D;EAC3E,MAAM,SAAS,IAAIA,6BAAU,OAAO,KAAK;EAMzC,MAAM,YAAW,MAJK,OAAO,IAC5B,YAAY,OAAO,QAAQ,SAC5B,GAEyB,MACvB,WAAW,OAAO,SAAS,OAAO,QAAQ,OAAO,UAAU,WAC7D;EAEA,IAAI;EAEJ,IAAI,UAAU;GACb,eAAO,IAAI,KACV,iCAAiC,OAAO,KAAK,KAAK,SAAS,GAAG,EAC/D;GAEA,WAAW,SAAS;EACrB,OAWC,YAAW,MAVW,OAAO,KAC5B,YAAY,OAAO,QAAQ,WAC3B;GACC,MAAM,OAAO;GACb,QAAQ,OAAO;GACf,SAAS,OAAO;GAChB,WAAW;EACZ,CACD,GAEmB;EAGpB,MAAM,OAAyB;GAAE,GAAG;GAAQ;EAAS;EAErD,OAAO;GAAE,IAAI;GAAU;EAAK;CAC7B;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,SAAS,MAAM,IAFFA,6BAAU,MAAM,KAET,EAAE,OAC3B,YAAY,MAAM,QAAQ,WAAW,IACtC;EAEA,IAAI,CAAC,QACJ,MAAM,IAAI,MAAM,eAAe,GAAG,2BAA2B;EAG9D,OAAO;GACN;GACA,OAAO;IACN,GAAG;IACH,MAAM,OAAO;IACb,QAAQ,OAAO;IACf,QAAQ,OAAO;GAChB;EACD;CACD;CAEA,MAAM,OACL,IACA,MACA,MACuC;EACvC,IAAI,KAAK,SAAS,KAAK,QAGtB,MAAM,IAFaA,6BAAU,KAAK,KAEvB,EAAE,IAAI,YAAY,KAAK,QAAQ,WAAW,GAAG,UAAU,EACjE,SAAS,KAAK,OACf,CAAC;EAGF,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM,UAAU;EAAG,EAAE;CAC1C;CAEA,MAAM,OAAO,IAAY,OAAwC;EAGhE,MAAM,IAFaA,6BAAU,MAAM,KAExB,EAAE,OAAO,YAAY,MAAM,QAAQ,WAAW,IAAI;CAC9D;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAExB,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAErB,IAAI,KAAK,WAAW,KAAK,QACxB,SAAS,KAAK,QAAQ;EAEvB,IAAI,KAAK,SAAS,KAAK,QACtB,SAAS,KAAK,QAAQ;EAGvB,MAAM,WAAW,KAAK,SAAS,KAAK;EAEpC,OAAO;GACN,SAAS,SAAS,SAAS,KAAK;GAChC;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,oBAAN,cAAgCC,eAAO,QAAQ,SAAS;CAGvD,YACC,MACA,MAOA,MACC;EACD,MACC,IAAI,0BAA0B,GAC9B,MACA;GAAE,GAAG;GAAM,UAAU;EAAU,GAC/B,IACD;CACD;AACD;;;;;;;;;;;;;AAmCA,IAAa,YAAb,cAA+BA,eAAO,kBAAkB;CAIvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,WAAW,IAAI,kBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,GAAG;EACJ,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"volume.mjs","names":[],"sources":["../../src/fly/volume.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app.js\";\nimport { FlyClient } from \"./client.js\";\nimport type { FlyProvider } from \"./provider.js\";\n\n/** Resolved inputs for the Fly volume dynamic provider. */\nexport interface FlyVolumeInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name the volume belongs to. */\n\tappName: string;\n\n\t/** Volume name (used for adoption lookup). */\n\tname: string;\n\n\t/** Region (IATA code). */\n\tregion: string;\n\n\t/** Volume size in GB. */\n\tsizeGb: number;\n}\n\n/** Persisted state for the Fly volume. */\ninterface FlyVolumeOutputs extends FlyVolumeInputs {\n\t/** Fly-assigned volume ID (`vol_…`). */\n\tvolumeId: string;\n}\n\n/** Volume response (only the fields we read). */\ninterface FlyVolumeResponse {\n\tid: string;\n\tname: string;\n\tstate: string;\n\tsize_gb: number;\n\tregion: string;\n}\n\n/**\n * Dynamic provider for Fly volumes. `create()` lists volumes and adopts one\n * matching the name (volume names are not unique, so it adopts the first\n * non-destroyed match); otherwise it creates a new encrypted volume. Growing\n * `sizeGb` extends in place; shrinking is not supported by Fly.\n */\nclass FlyVolumeResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlyVolumeInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\n\t\tconst volumes = await client.get<FlyVolumeResponse[]>(\n\t\t\t`/v1/apps/${inputs.appName}/volumes`,\n\t\t);\n\n\t\tconst existing = volumes.find(\n\t\t\t(volume) => volume.name === inputs.name && volume.state !== \"destroyed\",\n\t\t);\n\n\t\tlet volumeId: string;\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Fly volume \"${inputs.name}\" (${existing.id})`,\n\t\t\t);\n\n\t\t\tvolumeId = existing.id;\n\t\t} else {\n\t\t\tconst created = await client.post<FlyVolumeResponse>(\n\t\t\t\t`/v1/apps/${inputs.appName}/volumes`,\n\t\t\t\t{\n\t\t\t\t\tname: inputs.name,\n\t\t\t\t\tregion: inputs.region,\n\t\t\t\t\tsize_gb: inputs.sizeGb,\n\t\t\t\t\tencrypted: true,\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tvolumeId = created.id;\n\t\t}\n\n\t\tconst outs: FlyVolumeOutputs = { ...inputs, volumeId };\n\n\t\treturn { id: volumeId, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyVolumeOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst volume = await client.tryGet<FlyVolumeResponse>(\n\t\t\t`/v1/apps/${props.appName}/volumes/${id}`,\n\t\t);\n\n\t\tif (!volume) {\n\t\t\tthrow new Error(`Fly volume \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tname: volume.name,\n\t\t\t\tregion: volume.region,\n\t\t\t\tsizeGb: volume.size_gb,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\tolds: FlyVolumeOutputs,\n\t\tnews: FlyVolumeInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tif (news.sizeGb > olds.sizeGb) {\n\t\t\tconst client = new FlyClient(news.token);\n\n\t\t\tawait client.put(`/v1/apps/${news.appName}/volumes/${id}/extend`, {\n\t\t\t\tsize_gb: news.sizeGb,\n\t\t\t});\n\t\t}\n\n\t\treturn { outs: { ...news, volumeId: id } };\n\t}\n\n\tasync delete(id: string, props: FlyVolumeOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tawait client.delete(`/v1/apps/${props.appName}/volumes/${id}`);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyVolumeOutputs,\n\t\tnews: FlyVolumeInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\t\tif (olds.region !== news.region) {\n\t\t\treplaces.push(\"region\");\n\t\t}\n\t\tif (news.sizeGb < olds.sizeGb) {\n\t\t\treplaces.push(\"sizeGb\");\n\t\t}\n\n\t\tconst sizeGrew = news.sizeGb > olds.sizeGb;\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || sizeGrew,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyVolumeResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly volumeId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tregion: pulumi.Input<string>;\n\t\t\tsizeGb: pulumi.Input<number>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyVolumeResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, volumeId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyVolume. */\ntype FlyVolumeOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the volume belongs to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyVolume. */\nexport interface FlyVolumeArgs {\n\t/** Volume name. */\n\tname: pulumi.Input<string>;\n\n\t/** Region (IATA code, e.g. `\"iad\"`). */\n\tregion: pulumi.Input<string>;\n\n\t/** Volume size in GB. Can be grown (extended) but not shrunk. */\n\tsizeGb: pulumi.Input<number>;\n}\n\n/**\n * Manages a Fly volume with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const volume = new FlyVolume(\"api-data\", {\n * name: \"data\",\n * region: \"iad\",\n * sizeGb: 10,\n * }, { provider, app });\n * ```\n */\nexport class FlyVolume extends pulumi.ComponentResource {\n\t/** Fly-assigned volume ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyVolumeArgs, opts: FlyVolumeOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Volume\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyVolumeResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.volumeId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;;;;AA6CA,IAAM,4BAAN,MAA2E;CAC1E,MAAM,OAAO,QAA+D;EAC3E,MAAM,SAAS,IAAI,UAAU,OAAO,KAAK;EAMzC,MAAM,YAAW,MAJK,OAAO,IAC5B,YAAY,OAAO,QAAQ,SAC5B,GAEyB,MACvB,WAAW,OAAO,SAAS,OAAO,QAAQ,OAAO,UAAU,WAC7D;EAEA,IAAI;EAEJ,IAAI,UAAU;GACb,OAAO,IAAI,KACV,iCAAiC,OAAO,KAAK,KAAK,SAAS,GAAG,EAC/D;GAEA,WAAW,SAAS;EACrB,OAWC,YAAW,MAVW,OAAO,KAC5B,YAAY,OAAO,QAAQ,WAC3B;GACC,MAAM,OAAO;GACb,QAAQ,OAAO;GACf,SAAS,OAAO;GAChB,WAAW;EACZ,CACD,GAEmB;EAGpB,MAAM,OAAyB;GAAE,GAAG;GAAQ;EAAS;EAErD,OAAO;GAAE,IAAI;GAAU;EAAK;CAC7B;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,SAAS,MAAM,IAFF,UAAU,MAAM,KAET,EAAE,OAC3B,YAAY,MAAM,QAAQ,WAAW,IACtC;EAEA,IAAI,CAAC,QACJ,MAAM,IAAI,MAAM,eAAe,GAAG,2BAA2B;EAG9D,OAAO;GACN;GACA,OAAO;IACN,GAAG;IACH,MAAM,OAAO;IACb,QAAQ,OAAO;IACf,QAAQ,OAAO;GAChB;EACD;CACD;CAEA,MAAM,OACL,IACA,MACA,MACuC;EACvC,IAAI,KAAK,SAAS,KAAK,QAGtB,MAAM,IAFa,UAAU,KAAK,KAEvB,EAAE,IAAI,YAAY,KAAK,QAAQ,WAAW,GAAG,UAAU,EACjE,SAAS,KAAK,OACf,CAAC;EAGF,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM,UAAU;EAAG,EAAE;CAC1C;CAEA,MAAM,OAAO,IAAY,OAAwC;EAGhE,MAAM,IAFa,UAAU,MAAM,KAExB,EAAE,OAAO,YAAY,MAAM,QAAQ,WAAW,IAAI;CAC9D;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAExB,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAErB,IAAI,KAAK,WAAW,KAAK,QACxB,SAAS,KAAK,QAAQ;EAEvB,IAAI,KAAK,SAAS,KAAK,QACtB,SAAS,KAAK,QAAQ;EAGvB,MAAM,WAAW,KAAK,SAAS,KAAK;EAEpC,OAAO;GACN,SAAS,SAAS,SAAS,KAAK;GAChC;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,oBAAN,cAAgC,OAAO,QAAQ,SAAS;CAGvD,YACC,MACA,MAOA,MACC;EACD,MACC,IAAI,0BAA0B,GAC9B,MACA;GAAE,GAAG;GAAM,UAAU;EAAU,GAC/B,IACD;CACD;AACD;;;;;;;;;;;;;AAmCA,IAAa,YAAb,cAA+B,OAAO,kBAAkB;CAIvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,WAAW,IAAI,kBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,GAAG;EACJ,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}
1
+ {"version":3,"file":"volume.mjs","names":[],"sources":["../../src/fly/volume.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\n\nimport type { FlyApp } from \"./app\";\nimport { FlyClient } from \"./client\";\nimport type { FlyProvider } from \"./provider\";\n\n/** Resolved inputs for the Fly volume dynamic provider. */\nexport interface FlyVolumeInputs {\n\t/** Fly API token. */\n\ttoken: string;\n\n\t/** App name the volume belongs to. */\n\tappName: string;\n\n\t/** Volume name (used for adoption lookup). */\n\tname: string;\n\n\t/** Region (IATA code). */\n\tregion: string;\n\n\t/** Volume size in GB. */\n\tsizeGb: number;\n}\n\n/** Persisted state for the Fly volume. */\ninterface FlyVolumeOutputs extends FlyVolumeInputs {\n\t/** Fly-assigned volume ID (`vol_…`). */\n\tvolumeId: string;\n}\n\n/** Volume response (only the fields we read). */\ninterface FlyVolumeResponse {\n\tid: string;\n\tname: string;\n\tstate: string;\n\tsize_gb: number;\n\tregion: string;\n}\n\n/**\n * Dynamic provider for Fly volumes. `create()` lists volumes and adopts one\n * matching the name (volume names are not unique, so it adopts the first\n * non-destroyed match); otherwise it creates a new encrypted volume. Growing\n * `sizeGb` extends in place; shrinking is not supported by Fly.\n */\nclass FlyVolumeResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(inputs: FlyVolumeInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new FlyClient(inputs.token);\n\n\t\tconst volumes = await client.get<FlyVolumeResponse[]>(\n\t\t\t`/v1/apps/${inputs.appName}/volumes`,\n\t\t);\n\n\t\tconst existing = volumes.find(\n\t\t\t(volume) => volume.name === inputs.name && volume.state !== \"destroyed\",\n\t\t);\n\n\t\tlet volumeId: string;\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Fly volume \"${inputs.name}\" (${existing.id})`,\n\t\t\t);\n\n\t\t\tvolumeId = existing.id;\n\t\t} else {\n\t\t\tconst created = await client.post<FlyVolumeResponse>(\n\t\t\t\t`/v1/apps/${inputs.appName}/volumes`,\n\t\t\t\t{\n\t\t\t\t\tname: inputs.name,\n\t\t\t\t\tregion: inputs.region,\n\t\t\t\t\tsize_gb: inputs.sizeGb,\n\t\t\t\t\tencrypted: true,\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tvolumeId = created.id;\n\t\t}\n\n\t\tconst outs: FlyVolumeOutputs = { ...inputs, volumeId };\n\n\t\treturn { id: volumeId, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: FlyVolumeOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tconst volume = await client.tryGet<FlyVolumeResponse>(\n\t\t\t`/v1/apps/${props.appName}/volumes/${id}`,\n\t\t);\n\n\t\tif (!volume) {\n\t\t\tthrow new Error(`Fly volume \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tname: volume.name,\n\t\t\t\tregion: volume.region,\n\t\t\t\tsizeGb: volume.size_gb,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\tolds: FlyVolumeOutputs,\n\t\tnews: FlyVolumeInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tif (news.sizeGb > olds.sizeGb) {\n\t\t\tconst client = new FlyClient(news.token);\n\n\t\t\tawait client.put(`/v1/apps/${news.appName}/volumes/${id}/extend`, {\n\t\t\t\tsize_gb: news.sizeGb,\n\t\t\t});\n\t\t}\n\n\t\treturn { outs: { ...news, volumeId: id } };\n\t}\n\n\tasync delete(id: string, props: FlyVolumeOutputs): Promise<void> {\n\t\tconst client = new FlyClient(props.token);\n\n\t\tawait client.delete(`/v1/apps/${props.appName}/volumes/${id}`);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: FlyVolumeOutputs,\n\t\tnews: FlyVolumeInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.appName !== news.appName) {\n\t\t\treplaces.push(\"appName\");\n\t\t}\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\t\tif (olds.region !== news.region) {\n\t\t\treplaces.push(\"region\");\n\t\t}\n\t\tif (news.sizeGb < olds.sizeGb) {\n\t\t\treplaces.push(\"sizeGb\");\n\t\t}\n\n\t\tconst sizeGrew = news.sizeGb > olds.sizeGb;\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || sizeGrew,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass FlyVolumeResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly volumeId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tappName: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tregion: pulumi.Input<string>;\n\t\t\tsizeGb: pulumi.Input<number>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew FlyVolumeResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, volumeId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for FlyVolume. */\ntype FlyVolumeOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Fly authentication context. */\n\tprovider: FlyProvider;\n\n\t/** App the volume belongs to. */\n\tapp: FlyApp;\n};\n\n/** Args for FlyVolume. */\nexport interface FlyVolumeArgs {\n\t/** Volume name. */\n\tname: pulumi.Input<string>;\n\n\t/** Region (IATA code, e.g. `\"iad\"`). */\n\tregion: pulumi.Input<string>;\n\n\t/** Volume size in GB. Can be grown (extended) but not shrunk. */\n\tsizeGb: pulumi.Input<number>;\n}\n\n/**\n * Manages a Fly volume with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const volume = new FlyVolume(\"api-data\", {\n * name: \"data\",\n * region: \"iad\",\n * sizeGb: 10,\n * }, { provider, app });\n * ```\n */\nexport class FlyVolume extends pulumi.ComponentResource {\n\t/** Fly-assigned volume ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyVolumeArgs, opts: FlyVolumeOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Volume\", name, {}, pulumiOpts);\n\n\t\tconst resource = new FlyVolumeResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tappName: app.id,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.volumeId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;;;;AA6CA,IAAM,4BAAN,MAA2E;CAC1E,MAAM,OAAO,QAA+D;EAC3E,MAAM,SAAS,IAAI,UAAU,OAAO,KAAK;EAMzC,MAAM,YAAW,MAJK,OAAO,IAC5B,YAAY,OAAO,QAAQ,SAC5B,GAEyB,MACvB,WAAW,OAAO,SAAS,OAAO,QAAQ,OAAO,UAAU,WAC7D;EAEA,IAAI;EAEJ,IAAI,UAAU;GACb,OAAO,IAAI,KACV,iCAAiC,OAAO,KAAK,KAAK,SAAS,GAAG,EAC/D;GAEA,WAAW,SAAS;EACrB,OAWC,YAAW,MAVW,OAAO,KAC5B,YAAY,OAAO,QAAQ,WAC3B;GACC,MAAM,OAAO;GACb,QAAQ,OAAO;GACf,SAAS,OAAO;GAChB,WAAW;EACZ,CACD,GAEmB;EAGpB,MAAM,OAAyB;GAAE,GAAG;GAAQ;EAAS;EAErD,OAAO;GAAE,IAAI;GAAU;EAAK;CAC7B;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,SAAS,MAAM,IAFF,UAAU,MAAM,KAET,EAAE,OAC3B,YAAY,MAAM,QAAQ,WAAW,IACtC;EAEA,IAAI,CAAC,QACJ,MAAM,IAAI,MAAM,eAAe,GAAG,2BAA2B;EAG9D,OAAO;GACN;GACA,OAAO;IACN,GAAG;IACH,MAAM,OAAO;IACb,QAAQ,OAAO;IACf,QAAQ,OAAO;GAChB;EACD;CACD;CAEA,MAAM,OACL,IACA,MACA,MACuC;EACvC,IAAI,KAAK,SAAS,KAAK,QAGtB,MAAM,IAFa,UAAU,KAAK,KAEvB,EAAE,IAAI,YAAY,KAAK,QAAQ,WAAW,GAAG,UAAU,EACjE,SAAS,KAAK,OACf,CAAC;EAGF,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM,UAAU;EAAG,EAAE;CAC1C;CAEA,MAAM,OAAO,IAAY,OAAwC;EAGhE,MAAM,IAFa,UAAU,MAAM,KAExB,EAAE,OAAO,YAAY,MAAM,QAAQ,WAAW,IAAI;CAC9D;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,YAAY,KAAK,SACzB,SAAS,KAAK,SAAS;EAExB,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAErB,IAAI,KAAK,WAAW,KAAK,QACxB,SAAS,KAAK,QAAQ;EAEvB,IAAI,KAAK,SAAS,KAAK,QACtB,SAAS,KAAK,QAAQ;EAGvB,MAAM,WAAW,KAAK,SAAS,KAAK;EAEpC,OAAO;GACN,SAAS,SAAS,SAAS,KAAK;GAChC;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,oBAAN,cAAgC,OAAO,QAAQ,SAAS;CAGvD,YACC,MACA,MAOA,MACC;EACD,MACC,IAAI,0BAA0B,GAC9B,MACA;GAAE,GAAG;GAAM,UAAU;EAAU,GAC/B,IACD;CACD;AACD;;;;;;;;;;;;;AAmCA,IAAa,YAAb,cAA+B,OAAO,kBAAkB;CAIvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,WAAW,IAAI,kBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,SAAS,IAAI;GACb,GAAG;EACJ,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}