@samebase/alchemy-convex 0.27.3 → 0.29.0
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.
- package/README.md +57 -51
- package/lib/{HubClient-Bs5_j18U.js → HubClient-HeGoVOLO.js} +92 -69
- package/lib/HubClient-HeGoVOLO.js.map +1 -0
- package/lib/cli.js +4 -4
- package/lib/cli.js.map +1 -1
- package/lib/index.d.ts +29 -25
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/package.json +1 -1
- package/src/Deployment.ts +17 -7
- package/src/ManagementApi.ts +84 -26
- package/src/Project.ts +39 -53
- package/src/State.ts +13 -19
- package/src/cli.ts +11 -11
- package/lib/HubClient-Bs5_j18U.js.map +0 -1
package/lib/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/StackComment.ts","../src/cli.ts"],"sourcesContent":["// The command `alchemy-convex stack-comment`: one comment on a pull request with the stacks of the\n// repository, read from the state store after the deploys.\n//\n// A stack sees only itself, so a comment that a stack declares cannot show the other stacks of the\n// repository. This command runs as a step after them and reads each stack named `owner/name` or\n// `owner/name/<folder>` that has the stage `pr-<number>`. A stack without that stage has no card. A\n// run replaces the whole body, so the last run shows the newest state.\n//\n// It reads only the fields of the rows below, because Alchemy state holds secrets in clear text.\n//\n// GitHub keeps a fixed set of HTML in a comment and removes `style` and `class`, so every choice of\n// the markup is a fact about the sanitizer or the CSS of github.com:\n// - A table has `display: block` and `width: max-content`, so a `width` on it does nothing. The\n// spacer, a floated image one pixel high in a block of its own, gives every card one width, and\n// it shrinks on a narrow page.\n// - The CSS colors the even rows of a group, so each row has its own `tbody`.\n// - An HTML block ends at a blank line, so a table has none and is not indented.\n// - A bare image links to its raw file, and one link around an image and text underlines the space\n// between them, so the icon and the name are two links to one place.\n// - An image with `height` gets a gray background, so each icon is square and has only `width`.\n// - `align=\"middle\"` is `-webkit-baseline-middle` in Chrome, which is below the text.\n// `align=\"absmiddle\"` is the CSS value `middle`.\nimport { State } from \"alchemy/State\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\nimport { RUN_RECORD_KEY, runRecord } from \"./RunRecord.ts\";\n\n/** The first text of the comment, which finds it again. */\nconst MARKER = \"<!-- samebase-stacks -->\";\n\n/** GitHub does not proxy this host, so a new icon needs no purge of its image cache. */\nconst ICONS =\n \"https://raw.githubusercontent.com/samebase/samebase/main/packages/alchemy-convex/assets/stack-comment\";\n\nconst GITHUB_API = \"https://api.github.com\";\n\n/** The resources that a card shows. A row of any other type, or without `attr` from an interrupted deploy, is left out. */\nconst Resource = Schema.Union([\n Schema.Struct({\n resourceType: Schema.Literal(\"Convex.Deployment\"),\n attr: Schema.Struct({ name: Schema.String, siteUrl: Schema.String }),\n }),\n Schema.Struct({\n resourceType: Schema.Literal(\"Cloudflare.Worker\"),\n attr: Schema.Struct({ workerName: Schema.String, url: Schema.optional(Schema.String) }),\n }),\n Schema.Struct({\n resourceType: Schema.Literal(\"Cloudflare.R2.Bucket\"),\n attr: Schema.Struct({\n bucketName: Schema.String,\n accountId: Schema.String,\n jurisdiction: Schema.String,\n }),\n }),\n Schema.Struct({\n resourceType: Schema.Literal(\"IdentityHub.SharedClient\"),\n attr: Schema.Struct({ status: Schema.Literals([\"on\", \"off\", \"suspended\"]) }),\n }),\n]);\ntype Resource = typeof Resource.Type;\n\n/** The fields of the run record that a card shows. */\nconst StageOutput = Schema.Struct({\n [RUN_RECORD_KEY]: Schema.Struct({\n headCommit: Schema.String,\n runUrl: Schema.NullOr(Schema.String),\n recordedAt: Schema.String,\n }),\n});\n\nexport interface StackCard {\n readonly name: string;\n readonly stage: string;\n readonly resources: readonly Resource[];\n readonly run: typeof StageOutput.Type | null;\n}\n\nexport class StackCommentError extends Schema.TaggedError<StackCommentError>()(\n \"StackCommentError\",\n { reason: Schema.String },\n) {\n override get message() {\n return `The stack comment cannot be updated: ${this.reason}`;\n }\n}\n\n/** The cards of the stacks of `repository` that have `stage`, in name order. */\nexport const readStackCards = ({ repository, stage }: { repository: string; stage: string }) =>\n Effect.gen(function* () {\n const state = yield* yield* State;\n const names = (yield* state.listStacks()).filter(\n (name) => name === repository || name.startsWith(`${repository}/`),\n );\n const cards: StackCard[] = [];\n for (const name of names) {\n if (!(yield* state.listStages(name)).includes(stage)) {\n continue;\n }\n const request = { stack: name, stage };\n const rows = yield* Effect.forEach(yield* state.list(request), (fqn) =>\n state.get({ ...request, fqn }),\n );\n const output = yield* state.getOutput(request);\n cards.push({\n name,\n stage,\n resources: rows.filter(Schema.is(Resource)),\n run: Schema.is(StageOutput)(output) ? output : null,\n });\n }\n return cards;\n });\n\n/** A stack can create a deployment in one resource and push to it in another: a card shows it once. */\nconst withoutRepeats = (resources: readonly Resource[]) => {\n const deployments = new Set<string>();\n return resources.filter((resource) => {\n if (resource.resourceType !== \"Convex.Deployment\") {\n return true;\n }\n const repeated = deployments.has(resource.attr.name);\n deployments.add(resource.attr.name);\n return !repeated;\n });\n};\n\nconst escapeHtml = (text: string) =>\n text\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\");\n\nconst link = (href: string, text: string) => `<a href=\"${escapeHtml(href)}\">${text}</a>`;\n\nconst named = (href: string, icon: string, alt: string, name: string) => {\n const image = `<img src=\"${ICONS}/${icon}.svg\" width=\"16\" align=\"absmiddle\" alt=\"${alt}\">`;\n return `${link(href, image)} ${link(href, escapeHtml(name))}`;\n};\n\nconst hostOf = (url: string) => escapeHtml(new URL(url).host);\n\n/** Where a stack serves: its Convex deployment, else its first Worker with a URL. */\nconst addressOf = (resources: readonly Resource[]) => {\n for (const resource of resources) {\n if (resource.resourceType === \"Convex.Deployment\") {\n return resource.attr.siteUrl;\n }\n }\n for (const resource of resources) {\n if (resource.resourceType === \"Cloudflare.Worker\" && resource.attr.url !== undefined) {\n return resource.attr.url;\n }\n }\n return null;\n};\n\nconst resourceRow = (resource: Resource, address: string | null) => {\n switch (resource.resourceType) {\n case \"Convex.Deployment\": {\n const { name } = resource.attr;\n return named(\n `https://dashboard.convex.dev/d/${encodeURIComponent(name)}`,\n \"convex\",\n \"Convex\",\n name,\n );\n }\n case \"Cloudflare.Worker\": {\n const { url, workerName } = resource.attr;\n const row = named(\n `https://dash.cloudflare.com/?to=/:account/workers/services/view/${encodeURIComponent(workerName)}`,\n \"worker\",\n \"Worker\",\n workerName,\n );\n return url === undefined || url === address\n ? row\n : `${row}<br><sub>${link(url, hostOf(url))}</sub>`;\n }\n case \"Cloudflare.R2.Bucket\": {\n const { accountId, bucketName, jurisdiction } = resource.attr;\n return named(\n `https://dash.cloudflare.com/${encodeURIComponent(accountId)}/r2/${encodeURIComponent(jurisdiction)}/buckets/${encodeURIComponent(bucketName)}`,\n \"r2\",\n \"R2 bucket\",\n bucketName,\n );\n }\n case \"IdentityHub.SharedClient\": {\n const { status } = resource.attr;\n return `Google sign-in: ${status === \"off\" ? \"not set up\" : status}`;\n }\n }\n};\n\nconst runLine = ({ run }: StackCard) => {\n if (run === null) {\n return \"\";\n }\n const { headCommit, recordedAt, runUrl } = run[RUN_RECORD_KEY];\n const commit = escapeHtml(headCommit.slice(0, 7));\n const time = `${recordedAt.slice(0, 16).replace(\"T\", \" \")} UTC`;\n return `<br><sub>${runUrl === null ? commit : link(runUrl, commit)} ${escapeHtml(time)}</sub>`;\n};\n\nconst SPACER = `<div><img src=\"${ICONS}/spacer.svg\" width=\"460\" align=\"left\" alt=\"\"></div>`;\n\n/** `alone`: the one card of the repository. A card at the root then has no folder, as on the repository page. */\nconst renderCard = (card: StackCard, repository: string, alone: boolean) => {\n const folder = card.name.slice(repository.length + 1);\n const heading = [\n folder === \"\" && alone ? \"\" : `<b>${escapeHtml(folder || \"/\")}</b> `,\n `<sub>${escapeHtml(card.stage)}</sub>`,\n ].join(\"\");\n const resources = withoutRepeats(card.resources);\n const address = addressOf(resources);\n const addressLine = address === null ? \"\" : `<br>${link(address, hostOf(address))}`;\n const rows = resources\n .map((resource) => `<tbody><tr><td>${resourceRow(resource, address)}</td></tr></tbody>`)\n .join(\"\\n\");\n return `<table>\n<tbody><tr><td>${SPACER}${heading}${addressLine}${runLine(card)}</td></tr></tbody>\n${rows}\n</table>`;\n};\n\nexport const renderStackComment = (cards: readonly StackCard[], repository: string) => `${MARKER}\n${cards.map((card) => renderCard(card, repository, cards.length === 1)).join(\"\\n\\n\")}\n`;\n\nconst Comments = Schema.Array(Schema.Struct({ id: Schema.Number, body: Schema.String }));\nconst SavedComment = Schema.Struct({ html_url: Schema.String });\n\nconst github = (token: string, path: string, init: { method: string; body?: string }) =>\n Effect.tryPromise({\n try: async (): Promise<unknown> => {\n const response = await fetch(`${GITHUB_API}${path}`, {\n ...init,\n headers: {\n Accept: \"application/vnd.github+json\",\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n },\n });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n return await response.json();\n },\n catch: (error) =>\n new StackCommentError({\n reason: `GitHub ${init.method} ${path} failed (${error instanceof Error ? error.message : String(error)})`,\n }),\n });\n\nconst decode =\n <S extends Schema.Codec<unknown, unknown>>(schema: S, what: string) =>\n (value: unknown) =>\n Schema.decodeUnknownEffect(schema)(value).pipe(\n Effect.mapError(() => new StackCommentError({ reason: `GitHub gave a bad ${what}` })),\n );\n\nconst findComment = (token: string, path: string) =>\n Effect.gen(function* () {\n for (let page = 1; ; page += 1) {\n const comments = yield* github(token, `${path}?per_page=100&page=${page}`, {\n method: \"GET\",\n }).pipe(Effect.flatMap(decode(Comments, \"comment list\")));\n const comment = comments.find(({ body }) => body.startsWith(MARKER));\n if (comment !== undefined || comments.length < 100) {\n return comment;\n }\n }\n });\n\n/** Replaces the body of the comment of this command, or posts it on the first run, and gives its URL. */\nexport const saveStackComment = (args: {\n readonly token: string;\n readonly repository: string;\n readonly pullRequest: number;\n readonly body: string;\n}) =>\n Effect.gen(function* () {\n const { body, pullRequest, repository, token } = args;\n const comments = `/repos/${repository}/issues/${pullRequest}/comments`;\n const comment = yield* findComment(token, comments);\n const reply = yield* comment === undefined\n ? github(token, comments, { method: \"POST\", body: JSON.stringify({ body }) })\n : github(token, `/repos/${repository}/issues/comments/${comment.id}`, {\n method: \"PATCH\",\n body: JSON.stringify({ body }),\n });\n const saved = yield* decode(SavedComment, \"comment\")(reply);\n return saved.html_url;\n });\n\n/** The step of a workflow: the run record names the repository and the pull request of the run. */\nexport const updateStackComment = Effect.gen(function* () {\n const { pullRequest, repository } = yield* runRecord({ cwd: process.cwd(), env: process.env });\n if (pullRequest === null) {\n return yield* new StackCommentError({ reason: \"this run has no pull request\" });\n }\n const token = process.env[\"GITHUB_TOKEN\"];\n if (token === undefined || token === \"\") {\n return yield* new StackCommentError({ reason: \"GITHUB_TOKEN is not set\" });\n }\n const cards = yield* readStackCards({ repository, stage: `pr-${pullRequest}` });\n return yield* saveStackComment({\n token,\n repository,\n pullRequest,\n body: renderStackComment(cards, repository),\n });\n});\n","#!/usr/bin/env node\n// The commands of the package:\n//\n// alchemy-convex hub-client --team <team> --app <app> --callback-path <path> [--env-prefix HUB]\n// alchemy-convex stack-comment [--team <team>]\n//\n// hub-client runs in the directory of a Convex app at each dev start, and renews the client of the\n// identity hub of the team that the development deployment of that directory has, or gives it a new\n// one: see registerDevelopmentClient. stack-comment runs as a step of a pull request workflow after\n// the deploys, and keeps one comment with the stacks of the repository: see src/StackComment.ts. It\n// needs GITHUB_TOKEN and, with a team access token, no --team. Both use CONVEX_ACCESS_TOKEN or the\n// Convex CLI login.\nimport { execFileSync } from \"node:child_process\";\nimport { parseArgs } from \"node:util\";\nimport * as NodeServices from \"@effect/platform-node/NodeServices\";\nimport * as Effect from \"effect/Effect\";\nimport * as Layer from \"effect/Layer\";\nimport { fromConfig } from \"./Credentials.ts\";\nimport { registerDevelopmentClient } from \"./HubClient.ts\";\nimport { ManagementApiLive } from \"./ManagementApi.ts\";\nimport { installedCli } from \"./Push.ts\";\nimport { updateStackComment } from \"./StackComment.ts\";\nimport { state } from \"./State.ts\";\n\nconst { positionals, values } = parseArgs({\n allowPositionals: true,\n options: {\n team: { type: \"string\" },\n app: { type: \"string\" },\n \"callback-path\": { type: \"string\" },\n \"env-prefix\": { type: \"string\" },\n },\n});\nconst { team, app, \"callback-path\": callbackPath, \"env-prefix\": envPrefix } = values;\nconst USAGE = `Usage:\n alchemy-convex hub-client --team <team> --app <app> --callback-path <path> [--env-prefix HUB]\n alchemy-convex stack-comment [--team <team>]`;\n\nconst hubClient = (hub: { team: string; app: string; callbackPath: string; envPrefix?: string }) =>\n Effect.gen(function* () {\n const [command, ...prefix] = yield* installedCli(process.cwd());\n const callbackUrl = yield* registerDevelopmentClient({\n ...hub,\n convex: (args, input) =>\n execFileSync(command, [...prefix, ...args], {\n encoding: \"utf8\",\n input,\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n }).trim(),\n });\n console.log(\n callbackUrl === undefined\n ? \"Hub client renewed.\"\n : `Hub client registered for ${callbackUrl}.`,\n );\n }).pipe(Effect.provide(ManagementApiLive().pipe(Layer.provide(fromConfig()))));\n\nconst stackComment = (options: { team?: string }) =>\n updateStackComment.pipe(\n Effect.tap((url) => Effect.sync(() => console.log(url))),\n Effect.provide(state(options).pipe(Layer.provide(NodeServices.layer))),\n );\n\nconst subcommand = positionals.join(\" \");\nconst program: Effect.Effect<unknown, unknown> | undefined =\n subcommand === \"stack-comment\"\n ? stackComment(team === undefined ? {} : { team })\n : subcommand === \"hub-client\" &&\n team !== undefined &&\n app !== undefined &&\n callbackPath !== undefined\n ? hubClient({ team, app, callbackPath, ...(envPrefix === undefined ? {} : { envPrefix }) })\n : undefined;\nif (program === undefined) {\n console.error(USAGE);\n process.exit(1);\n}\n\nawait Effect.runPromise(program).catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/StackComment.ts","../src/cli.ts"],"sourcesContent":["// The command `alchemy-convex stack-comment`: one comment on a pull request with the stacks of the\n// repository, read from the state store after the deploys.\n//\n// A stack sees only itself, so a comment that a stack declares cannot show the other stacks of the\n// repository. This command runs as a step after them and reads each stack named `owner/name` or\n// `owner/name/<folder>` that has the stage `pr-<number>`. A stack without that stage has no card. A\n// run replaces the whole body, so the last run shows the newest state.\n//\n// It reads only the fields of the rows below, because Alchemy state holds secrets in clear text.\n//\n// GitHub keeps a fixed set of HTML in a comment and removes `style` and `class`, so every choice of\n// the markup is a fact about the sanitizer or the CSS of github.com:\n// - A table has `display: block` and `width: max-content`, so a `width` on it does nothing. The\n// spacer, a floated image one pixel high in a block of its own, gives every card one width, and\n// it shrinks on a narrow page.\n// - The CSS colors the even rows of a group, so each row has its own `tbody`.\n// - An HTML block ends at a blank line, so a table has none and is not indented.\n// - A bare image links to its raw file, and one link around an image and text underlines the space\n// between them, so the icon and the name are two links to one place.\n// - An image with `height` gets a gray background, so each icon is square and has only `width`.\n// - `align=\"middle\"` is `-webkit-baseline-middle` in Chrome, which is below the text.\n// `align=\"absmiddle\"` is the CSS value `middle`.\nimport { State } from \"alchemy/State\";\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\nimport { RUN_RECORD_KEY, runRecord } from \"./RunRecord.ts\";\n\n/** The first text of the comment, which finds it again. */\nconst MARKER = \"<!-- samebase-stacks -->\";\n\n/** GitHub does not proxy this host, so a new icon needs no purge of its image cache. */\nconst ICONS =\n \"https://raw.githubusercontent.com/samebase/samebase/main/packages/alchemy-convex/assets/stack-comment\";\n\nconst GITHUB_API = \"https://api.github.com\";\n\n/** The resources that a card shows. A row of any other type, or without `attr` from an interrupted deploy, is left out. */\nconst Resource = Schema.Union([\n Schema.Struct({\n resourceType: Schema.Literal(\"Convex.Deployment\"),\n attr: Schema.Struct({ name: Schema.String, siteUrl: Schema.String }),\n }),\n Schema.Struct({\n resourceType: Schema.Literal(\"Cloudflare.Worker\"),\n attr: Schema.Struct({ workerName: Schema.String, url: Schema.optional(Schema.String) }),\n }),\n Schema.Struct({\n resourceType: Schema.Literal(\"Cloudflare.R2.Bucket\"),\n attr: Schema.Struct({\n bucketName: Schema.String,\n accountId: Schema.String,\n jurisdiction: Schema.String,\n }),\n }),\n Schema.Struct({\n resourceType: Schema.Literal(\"IdentityHub.SharedClient\"),\n attr: Schema.Struct({ status: Schema.Literals([\"on\", \"off\", \"suspended\"]) }),\n }),\n]);\ntype Resource = typeof Resource.Type;\n\n/** The fields of the run record that a card shows. */\nconst StageOutput = Schema.Struct({\n [RUN_RECORD_KEY]: Schema.Struct({\n headCommit: Schema.String,\n runUrl: Schema.NullOr(Schema.String),\n recordedAt: Schema.String,\n }),\n});\n\nexport interface StackCard {\n readonly name: string;\n readonly stage: string;\n readonly resources: readonly Resource[];\n readonly run: typeof StageOutput.Type | null;\n}\n\nexport class StackCommentError extends Schema.TaggedError<StackCommentError>()(\n \"StackCommentError\",\n { reason: Schema.String },\n) {\n override get message() {\n return `The stack comment cannot be updated: ${this.reason}`;\n }\n}\n\n/** The cards of the stacks of `repository` that have `stage`, in name order. */\nexport const readStackCards = ({ repository, stage }: { repository: string; stage: string }) =>\n Effect.gen(function* () {\n const state = yield* yield* State;\n const names = (yield* state.listStacks()).filter(\n (name) => name === repository || name.startsWith(`${repository}/`),\n );\n const cards: StackCard[] = [];\n for (const name of names) {\n if (!(yield* state.listStages(name)).includes(stage)) {\n continue;\n }\n const request = { stack: name, stage };\n const rows = yield* Effect.forEach(yield* state.list(request), (fqn) =>\n state.get({ ...request, fqn }),\n );\n const output = yield* state.getOutput(request);\n cards.push({\n name,\n stage,\n resources: rows.filter(Schema.is(Resource)),\n run: Schema.is(StageOutput)(output) ? output : null,\n });\n }\n return cards;\n });\n\n/** A stack can create a deployment in one resource and push to it in another: a card shows it once. */\nconst withoutRepeats = (resources: readonly Resource[]) => {\n const deployments = new Set<string>();\n return resources.filter((resource) => {\n if (resource.resourceType !== \"Convex.Deployment\") {\n return true;\n }\n const repeated = deployments.has(resource.attr.name);\n deployments.add(resource.attr.name);\n return !repeated;\n });\n};\n\nconst escapeHtml = (text: string) =>\n text\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\");\n\nconst link = (href: string, text: string) => `<a href=\"${escapeHtml(href)}\">${text}</a>`;\n\nconst named = (href: string, icon: string, alt: string, name: string) => {\n const image = `<img src=\"${ICONS}/${icon}.svg\" width=\"16\" align=\"absmiddle\" alt=\"${alt}\">`;\n return `${link(href, image)} ${link(href, escapeHtml(name))}`;\n};\n\nconst hostOf = (url: string) => escapeHtml(new URL(url).host);\n\n/** Where a stack serves: its Convex deployment, else its first Worker with a URL. */\nconst addressOf = (resources: readonly Resource[]) => {\n for (const resource of resources) {\n if (resource.resourceType === \"Convex.Deployment\") {\n return resource.attr.siteUrl;\n }\n }\n for (const resource of resources) {\n if (resource.resourceType === \"Cloudflare.Worker\" && resource.attr.url !== undefined) {\n return resource.attr.url;\n }\n }\n return null;\n};\n\nconst resourceRow = (resource: Resource, address: string | null) => {\n switch (resource.resourceType) {\n case \"Convex.Deployment\": {\n const { name } = resource.attr;\n return named(\n `https://dashboard.convex.dev/d/${encodeURIComponent(name)}`,\n \"convex\",\n \"Convex\",\n name,\n );\n }\n case \"Cloudflare.Worker\": {\n const { url, workerName } = resource.attr;\n const row = named(\n `https://dash.cloudflare.com/?to=/:account/workers/services/view/${encodeURIComponent(workerName)}`,\n \"worker\",\n \"Worker\",\n workerName,\n );\n return url === undefined || url === address\n ? row\n : `${row}<br><sub>${link(url, hostOf(url))}</sub>`;\n }\n case \"Cloudflare.R2.Bucket\": {\n const { accountId, bucketName, jurisdiction } = resource.attr;\n return named(\n `https://dash.cloudflare.com/${encodeURIComponent(accountId)}/r2/${encodeURIComponent(jurisdiction)}/buckets/${encodeURIComponent(bucketName)}`,\n \"r2\",\n \"R2 bucket\",\n bucketName,\n );\n }\n case \"IdentityHub.SharedClient\": {\n const { status } = resource.attr;\n return `Google sign-in: ${status === \"off\" ? \"not set up\" : status}`;\n }\n }\n};\n\nconst runLine = ({ run }: StackCard) => {\n if (run === null) {\n return \"\";\n }\n const { headCommit, recordedAt, runUrl } = run[RUN_RECORD_KEY];\n const commit = escapeHtml(headCommit.slice(0, 7));\n const time = `${recordedAt.slice(0, 16).replace(\"T\", \" \")} UTC`;\n return `<br><sub>${runUrl === null ? commit : link(runUrl, commit)} ${escapeHtml(time)}</sub>`;\n};\n\nconst SPACER = `<div><img src=\"${ICONS}/spacer.svg\" width=\"460\" align=\"left\" alt=\"\"></div>`;\n\n/** `alone`: the one card of the repository. A card at the root then has no folder, as on the repository page. */\nconst renderCard = (card: StackCard, repository: string, alone: boolean) => {\n const folder = card.name.slice(repository.length + 1);\n const heading = [\n folder === \"\" && alone ? \"\" : `<b>${escapeHtml(folder || \"/\")}</b> `,\n `<sub>${escapeHtml(card.stage)}</sub>`,\n ].join(\"\");\n const resources = withoutRepeats(card.resources);\n const address = addressOf(resources);\n const addressLine = address === null ? \"\" : `<br>${link(address, hostOf(address))}`;\n const rows = resources\n .map((resource) => `<tbody><tr><td>${resourceRow(resource, address)}</td></tr></tbody>`)\n .join(\"\\n\");\n return `<table>\n<tbody><tr><td>${SPACER}${heading}${addressLine}${runLine(card)}</td></tr></tbody>\n${rows}\n</table>`;\n};\n\nexport const renderStackComment = (cards: readonly StackCard[], repository: string) => `${MARKER}\n${cards.map((card) => renderCard(card, repository, cards.length === 1)).join(\"\\n\\n\")}\n`;\n\nconst Comments = Schema.Array(Schema.Struct({ id: Schema.Number, body: Schema.String }));\nconst SavedComment = Schema.Struct({ html_url: Schema.String });\n\nconst github = (token: string, path: string, init: { method: string; body?: string }) =>\n Effect.tryPromise({\n try: async (): Promise<unknown> => {\n const response = await fetch(`${GITHUB_API}${path}`, {\n ...init,\n headers: {\n Accept: \"application/vnd.github+json\",\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n },\n });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n return await response.json();\n },\n catch: (error) =>\n new StackCommentError({\n reason: `GitHub ${init.method} ${path} failed (${error instanceof Error ? error.message : String(error)})`,\n }),\n });\n\nconst decode =\n <S extends Schema.Codec<unknown, unknown>>(schema: S, what: string) =>\n (value: unknown) =>\n Schema.decodeUnknownEffect(schema)(value).pipe(\n Effect.mapError(() => new StackCommentError({ reason: `GitHub gave a bad ${what}` })),\n );\n\nconst findComment = (token: string, path: string) =>\n Effect.gen(function* () {\n for (let page = 1; ; page += 1) {\n const comments = yield* github(token, `${path}?per_page=100&page=${page}`, {\n method: \"GET\",\n }).pipe(Effect.flatMap(decode(Comments, \"comment list\")));\n const comment = comments.find(({ body }) => body.startsWith(MARKER));\n if (comment !== undefined || comments.length < 100) {\n return comment;\n }\n }\n });\n\n/** Replaces the body of the comment of this command, or posts it on the first run, and gives its URL. */\nexport const saveStackComment = (args: {\n readonly token: string;\n readonly repository: string;\n readonly pullRequest: number;\n readonly body: string;\n}) =>\n Effect.gen(function* () {\n const { body, pullRequest, repository, token } = args;\n const comments = `/repos/${repository}/issues/${pullRequest}/comments`;\n const comment = yield* findComment(token, comments);\n const reply = yield* comment === undefined\n ? github(token, comments, { method: \"POST\", body: JSON.stringify({ body }) })\n : github(token, `/repos/${repository}/issues/comments/${comment.id}`, {\n method: \"PATCH\",\n body: JSON.stringify({ body }),\n });\n const saved = yield* decode(SavedComment, \"comment\")(reply);\n return saved.html_url;\n });\n\n/** The step of a workflow: the run record names the repository and the pull request of the run. */\nexport const updateStackComment = Effect.gen(function* () {\n const { pullRequest, repository } = yield* runRecord({ cwd: process.cwd(), env: process.env });\n if (pullRequest === null) {\n return yield* new StackCommentError({ reason: \"this run has no pull request\" });\n }\n const token = process.env[\"GITHUB_TOKEN\"];\n if (token === undefined || token === \"\") {\n return yield* new StackCommentError({ reason: \"GITHUB_TOKEN is not set\" });\n }\n const cards = yield* readStackCards({ repository, stage: `pr-${pullRequest}` });\n return yield* saveStackComment({\n token,\n repository,\n pullRequest,\n body: renderStackComment(cards, repository),\n });\n});\n","#!/usr/bin/env node\n// The commands of the package:\n//\n// alchemy-convex hub-client --team <team> --app <app> --callback-path <path> [--env-prefix HUB]\n// alchemy-convex stack-comment\n//\n// hub-client runs in the directory of a Convex app at each dev start, and renews the client of the\n// identity hub of the team that the development deployment of that directory has, or gives it a new\n// one: see registerDevelopmentClient. stack-comment runs as a step of a pull request workflow after\n// the deploys, and keeps one comment with the stacks of the repository: see src/StackComment.ts. It\n// needs GITHUB_TOKEN, and reads the store of the team of the token. Both use CONVEX_ACCESS_TOKEN or\n// the Convex CLI login.\nimport { execFileSync } from \"node:child_process\";\nimport { parseArgs } from \"node:util\";\nimport * as NodeServices from \"@effect/platform-node/NodeServices\";\nimport * as Effect from \"effect/Effect\";\nimport * as Layer from \"effect/Layer\";\nimport { fromConfig } from \"./Credentials.ts\";\nimport { registerDevelopmentClient } from \"./HubClient.ts\";\nimport { ManagementApiLive } from \"./ManagementApi.ts\";\nimport { installedCli } from \"./Push.ts\";\nimport { updateStackComment } from \"./StackComment.ts\";\nimport { state } from \"./State.ts\";\n\nconst { positionals, values } = parseArgs({\n allowPositionals: true,\n options: {\n team: { type: \"string\" },\n app: { type: \"string\" },\n \"callback-path\": { type: \"string\" },\n \"env-prefix\": { type: \"string\" },\n },\n});\nconst { team, app, \"callback-path\": callbackPath, \"env-prefix\": envPrefix } = values;\nconst USAGE = `Usage:\n alchemy-convex hub-client --team <team> --app <app> --callback-path <path> [--env-prefix HUB]\n alchemy-convex stack-comment`;\n\nconst hubClient = (hub: { team: string; app: string; callbackPath: string; envPrefix?: string }) =>\n Effect.gen(function* () {\n const [command, ...prefix] = yield* installedCli(process.cwd());\n const callbackUrl = yield* registerDevelopmentClient({\n ...hub,\n convex: (args, input) =>\n execFileSync(command, [...prefix, ...args], {\n encoding: \"utf8\",\n input,\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n }).trim(),\n });\n console.log(\n callbackUrl === undefined\n ? \"Hub client renewed.\"\n : `Hub client registered for ${callbackUrl}.`,\n );\n }).pipe(Effect.provide(ManagementApiLive().pipe(Layer.provide(fromConfig()))));\n\nconst stackComment = updateStackComment.pipe(\n Effect.tap((url) => Effect.sync(() => console.log(url))),\n Effect.provide(state().pipe(Layer.provide(NodeServices.layer))),\n);\n\nconst subcommand = positionals.join(\" \");\nconst program: Effect.Effect<unknown, unknown> | undefined =\n // stack-comment reads the store of the team of the credential, so a --team would be ignored.\n subcommand === \"stack-comment\" && team === undefined\n ? stackComment\n : subcommand === \"hub-client\" &&\n team !== undefined &&\n app !== undefined &&\n callbackPath !== undefined\n ? hubClient({ team, app, callbackPath, ...(envPrefix === undefined ? {} : { envPrefix }) })\n : undefined;\nif (program === undefined) {\n console.error(USAGE);\n process.exit(1);\n}\n\nawait Effect.runPromise(program).catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n});\n"],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
|
@@ -83,13 +83,19 @@ interface ManagementApiService {
|
|
|
83
83
|
/** Run one deployment-scoped call authenticated with a deploy key. */
|
|
84
84
|
readonly deploymentRequest: <T>(operation: string, deploymentUrlOrName: string, deployKey: Redacted.Redacted<string>, run: (client: DeploymentClient) => Promise<FetchResult<T>>) => Effect.Effect<T, ConvexApiError>;
|
|
85
85
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* Convex CLI
|
|
90
|
-
* the dashboard's team list, and nothing is an error.
|
|
86
|
+
* The team of the credential. A team access token and an OAuth token of a
|
|
87
|
+
* team name it (`GET /token_details`). The Convex CLI login answers 403
|
|
88
|
+
* there, so its team is the team of the project of the deployment that the
|
|
89
|
+
* Convex CLI selected (CONVEX_DEPLOYMENT). Read once for each service.
|
|
91
90
|
*/
|
|
92
|
-
readonly
|
|
91
|
+
readonly teamId: Effect.Effect<number, ConvexApiError>;
|
|
92
|
+
/**
|
|
93
|
+
* Team id for a numeric id or a slug, for Convex.HubClient, which names the
|
|
94
|
+
* team of the hub. With a team access token, a slug is checked against the
|
|
95
|
+
* `teamSlug` of the team's projects, so a token of another team fails; with
|
|
96
|
+
* the Convex CLI login, a slug goes to the dashboard's team list.
|
|
97
|
+
*/
|
|
98
|
+
readonly resolveTeamId: (team: string | number) => Effect.Effect<number, ConvexApiError>;
|
|
93
99
|
/**
|
|
94
100
|
* True when a deploy key that Convex returned carries the token of the
|
|
95
101
|
* Management API credential itself. With an OAuth token, create_deploy_key
|
|
@@ -122,7 +128,7 @@ export declare class Providers extends Providers_base {}
|
|
|
122
128
|
* default lookup (`CONVEX_ACCESS_TOKEN`, then the Convex CLI login, and
|
|
123
129
|
* `CONVEX_DEPLOY_KEY`).
|
|
124
130
|
*/
|
|
125
|
-
export declare const providers: (credentials?: Layer.Layer<Credentials>) => Layer.Layer<Providers, never, import("effect/
|
|
131
|
+
export declare const providers: (credentials?: Layer.Layer<Credentials>) => Layer.Layer<Providers, never, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/process/ChildProcessSpawner").ChildProcessSpawner>;
|
|
126
132
|
//#endregion
|
|
127
133
|
//#region src/Push.d.ts
|
|
128
134
|
/** A value of the `env` prop: a string, or a secret such as `Config.Redacted("AUTH_SECRET")`. */
|
|
@@ -234,8 +240,11 @@ type DeploymentProps = (DeploymentInputs & {
|
|
|
234
240
|
/** A dev deployment or a preview deployment that this resource creates. Cannot change. */
|
|
235
241
|
readonly type: "dev" | "preview";
|
|
236
242
|
/**
|
|
237
|
-
* The dev reference, such as `staging`, or the
|
|
238
|
-
*
|
|
243
|
+
* The dev reference, such as `staging`, or the Convex name of a dev
|
|
244
|
+
* deployment that exists, such as `happy-animal-123` from `.env.local`,
|
|
245
|
+
* which `adopt` takes over; a name that matches nothing creates a dev
|
|
246
|
+
* deployment with that reference. Or the preview name, such as the
|
|
247
|
+
* stage name. Unique in the project. Cannot change.
|
|
239
248
|
*/
|
|
240
249
|
readonly name: string;
|
|
241
250
|
});
|
|
@@ -344,19 +353,17 @@ export declare const bindComponent: (options: {
|
|
|
344
353
|
/** Region names the Management API accepts, from the generated schema. */
|
|
345
354
|
type DeploymentRegion = components["schemas"]["RegionName"];
|
|
346
355
|
type ProjectProps = {
|
|
347
|
-
/** Team slug or numeric team id. A slug costs one extra lookup per run. Cannot change. */
|
|
348
|
-
readonly team: string | number;
|
|
349
356
|
/** Display name. Convex derives the slug from it at creation. A change renames the project. */
|
|
350
357
|
readonly name: string;
|
|
358
|
+
readonly slug?: never;
|
|
351
359
|
/** Region of the deployment created with the project. Defaults to the team default. Cannot change. */
|
|
352
360
|
readonly deploymentRegion?: DeploymentRegion;
|
|
353
361
|
/** Deployment created with the project. Defaults to `prod`. Cannot change. */
|
|
354
362
|
readonly deploymentType?: "prod" | "dev";
|
|
355
363
|
} | {
|
|
356
|
-
/** Team slug or numeric team id. Cannot change. */
|
|
357
|
-
readonly team: string | number;
|
|
358
364
|
/** Slug of an existing project, such as `score-four`. The resource never creates a project. Cannot change. */
|
|
359
365
|
readonly slug: string;
|
|
366
|
+
readonly name?: never;
|
|
360
367
|
};
|
|
361
368
|
interface ProjectAttributes {
|
|
362
369
|
readonly projectId: number;
|
|
@@ -373,7 +380,7 @@ declare const ProjectTeamChange_base: Schema.Class<ProjectTeamChange, Schema.Tag
|
|
|
373
380
|
readonly teamId: Schema.Number;
|
|
374
381
|
readonly requestedTeamId: Schema.Number;
|
|
375
382
|
}>, import("effect/Cause").YieldableError>;
|
|
376
|
-
/** The
|
|
383
|
+
/** The credential is of another team than the project. Convex cannot move a project. */
|
|
377
384
|
export declare class ProjectTeamChange extends ProjectTeamChange_base {
|
|
378
385
|
get message(): string;
|
|
379
386
|
}
|
|
@@ -407,7 +414,7 @@ export declare class CreatedProjectNotFound extends CreatedProjectNotFound_base
|
|
|
407
414
|
get message(): string;
|
|
408
415
|
}
|
|
409
416
|
declare const ProjectNotFound_base: Schema.Class<ProjectNotFound, Schema.TaggedStruct<"ProjectNotFound", {
|
|
410
|
-
readonly
|
|
417
|
+
readonly teamId: Schema.Number;
|
|
411
418
|
readonly slug: Schema.String;
|
|
412
419
|
}>, import("effect/Cause").YieldableError>;
|
|
413
420
|
/** No project in the team has the slug. A `slug` resource never creates one. */
|
|
@@ -566,7 +573,7 @@ export declare const SharedHubClient: import("alchemy").ResourceClassWithMethods
|
|
|
566
573
|
readonly clientSecret: Redacted.Redacted<string>;
|
|
567
574
|
readonly registrationAccessToken: Redacted.Redacted<string>;
|
|
568
575
|
readonly registrationClientUri: string;
|
|
569
|
-
readonly deploymentType: "
|
|
576
|
+
readonly deploymentType: "preview" | "production" | "development";
|
|
570
577
|
readonly redirectUri: string;
|
|
571
578
|
readonly initialAccessToken: Redacted.Redacted<string>;
|
|
572
579
|
readonly repository: string;
|
|
@@ -582,16 +589,13 @@ export declare const SharedHubClient: import("alchemy").ResourceClassWithMethods
|
|
|
582
589
|
/** The name of the state project in each team. */
|
|
583
590
|
export declare const STORE_PROJECT_NAME = "alchemy-state-store";
|
|
584
591
|
/**
|
|
585
|
-
* Alchemy state in the `alchemy-state-store` project of
|
|
586
|
-
* in CONVEX_ACCESS_TOKEN or the Convex CLI login
|
|
587
|
-
*
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
* stack never touches Convex.
|
|
592
|
+
* Alchemy state in the `alchemy-state-store` project of the team of the credential: the team token
|
|
593
|
+
* in CONVEX_ACCESS_TOKEN, which names its team, or the Convex CLI login, with the team of the
|
|
594
|
+
* deployment that the CLI selected (CONVEX_DEPLOYMENT, see ManagementApi.teamId). The first use of
|
|
595
|
+
* the run finds or creates the project and pushes the store when it is missing or older.
|
|
596
|
+
* `alchemy plan` on another stack never touches Convex.
|
|
591
597
|
*/
|
|
592
|
-
export declare const state: (
|
|
593
|
-
readonly team?: string | number;
|
|
594
|
-
}) => Layer.Layer<State, never, ChildProcessSpawner.ChildProcessSpawner>;
|
|
598
|
+
export declare const state: () => Layer.Layer<State, never, ChildProcessSpawner.ChildProcessSpawner>;
|
|
595
599
|
//#endregion
|
|
596
600
|
export type { ComponentBinding, DeploymentAttributes, DeploymentInput, DeploymentProps, DeploymentRegion, EnvValue, HubClientAttributes, HubClientProps, HubDeploymentType, ProjectAttributes, ProjectProps, RunRecord, SharedHubClientAttributes, SharedHubClientProps, Source };
|
|
597
601
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/Credentials.ts","../src/ManagementApi.ts","../src/Providers.ts","../src/Push.ts","../src/Deployment.ts","../src/Component.ts","../src/Project.ts","../src/HubClient.ts","../src/RunRecord.ts","../src/SharedHubClient.ts","../src/State.ts"],"mappings":";;;;;;;;;;;;;;;;;UA6BiB;;WAEN,aAAa,SAAS;;WAEtB;;UAGM;;WAEN,aAAa,OAAO,OAAO;;WAE3B,gBAAgB,OAAO;;WAEvB,WAAW,OAAO,OAAO,SAAS;;;qBAGhC,oBAAoB;;qBAKpB;;qBAGA;;;;qBAOA,yBAAyB;;;;;;;;;qBAiCzB,kBAAiB,MAAM,MAAM;;qBA0B7B,aAAc;WAChB,cAAc,SAAS;WACvB,YAAY,SAAS;MAC5B,MAAM,MAAM;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/Credentials.ts","../src/ManagementApi.ts","../src/Providers.ts","../src/Push.ts","../src/Deployment.ts","../src/Component.ts","../src/Project.ts","../src/HubClient.ts","../src/RunRecord.ts","../src/SharedHubClient.ts","../src/State.ts"],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as PushTargetMismatch, B as DEPLOY_KEY_ENV, C as DeploymentProvider, D as ConvexAppNotFound, E as CodePushFailed, F as ManagementApi, H as fromValues, I as ManagementApiLive, L as ACCESS_TOKEN_ENV, N as parseDeployOutput, O as ConvexCliNotFound, P as ConvexApiError, R as Credentials, S as DeploymentIdentityChange, T as ProductionDeploymentNotFound, V as fromConfig, _ as ProjectProvider, b as AmbiguousDeployment, c as RUN_RECORD_KEY, d as withRunRecord, f as AmbiguousProject, g as ProjectNotFound, h as ProjectDeploymentChange, i as HubClientProvider, j as VariablesOwnedBySomeoneElse, k as EnvironmentNeedsDeployKey, l as RunRecordError, m as Project, n as HubClient, o as STORE_PROJECT_NAME, p as CreatedProjectNotFound, r as HubClientFailed, s as state, t as HUB_PROJECT_NAME, u as runRecord, v as ProjectTeamChange, w as DeploymentResource, x as Deployment, y as AbsoluteRootDir, z as CredentialsError } from "./HubClient-
|
|
1
|
+
import { A as PushTargetMismatch, B as DEPLOY_KEY_ENV, C as DeploymentProvider, D as ConvexAppNotFound, E as CodePushFailed, F as ManagementApi, H as fromValues, I as ManagementApiLive, L as ACCESS_TOKEN_ENV, N as parseDeployOutput, O as ConvexCliNotFound, P as ConvexApiError, R as Credentials, S as DeploymentIdentityChange, T as ProductionDeploymentNotFound, V as fromConfig, _ as ProjectProvider, b as AmbiguousDeployment, c as RUN_RECORD_KEY, d as withRunRecord, f as AmbiguousProject, g as ProjectNotFound, h as ProjectDeploymentChange, i as HubClientProvider, j as VariablesOwnedBySomeoneElse, k as EnvironmentNeedsDeployKey, l as RunRecordError, m as Project, n as HubClient, o as STORE_PROJECT_NAME, p as CreatedProjectNotFound, r as HubClientFailed, s as state, t as HUB_PROJECT_NAME, u as runRecord, v as ProjectTeamChange, w as DeploymentResource, x as Deployment, y as AbsoluteRootDir, z as CredentialsError } from "./HubClient-HeGoVOLO.js";
|
|
2
2
|
import { toPath } from "alchemy/FQN";
|
|
3
3
|
import { Stack } from "alchemy/Stack";
|
|
4
4
|
import * as Effect from "effect/Effect";
|
package/package.json
CHANGED
package/src/Deployment.ts
CHANGED
|
@@ -113,8 +113,11 @@ export type DeploymentProps =
|
|
|
113
113
|
/** A dev deployment or a preview deployment that this resource creates. Cannot change. */
|
|
114
114
|
readonly type: "dev" | "preview";
|
|
115
115
|
/**
|
|
116
|
-
* The dev reference, such as `staging`, or the
|
|
117
|
-
*
|
|
116
|
+
* The dev reference, such as `staging`, or the Convex name of a dev
|
|
117
|
+
* deployment that exists, such as `happy-animal-123` from `.env.local`,
|
|
118
|
+
* which `adopt` takes over; a name that matches nothing creates a dev
|
|
119
|
+
* deployment with that reference. Or the preview name, such as the
|
|
120
|
+
* stage name. Unique in the project. Cannot change.
|
|
118
121
|
*/
|
|
119
122
|
readonly name: string;
|
|
120
123
|
});
|
|
@@ -334,7 +337,9 @@ const checkIdentity = (current: DeploymentTarget, news: DeploymentProps) => {
|
|
|
334
337
|
}
|
|
335
338
|
if (current.type !== news.type) return change("type", current.type, news.type);
|
|
336
339
|
const currentName = nameOf(current);
|
|
337
|
-
|
|
340
|
+
// A dev deployment is also named by its Convex name, as `findByName` finds it.
|
|
341
|
+
const named = news.type === "dev" && current.name === news.name;
|
|
342
|
+
if (news.type !== "prod" && currentName !== news.name && !named) {
|
|
338
343
|
return change("name", currentName ?? "(none)", news.name);
|
|
339
344
|
}
|
|
340
345
|
return Effect.void;
|
|
@@ -454,8 +459,10 @@ export const DeploymentProvider = () =>
|
|
|
454
459
|
);
|
|
455
460
|
|
|
456
461
|
/**
|
|
457
|
-
* The one dev deployment with this reference, or the
|
|
458
|
-
* deployment with this preview name, or undefined.
|
|
462
|
+
* The one dev deployment with this reference or this Convex name, or the
|
|
463
|
+
* one preview deployment with this preview name, or undefined. The name
|
|
464
|
+
* form takes the dev deployment that `npx convex dev` made, such as the
|
|
465
|
+
* one `.env.local` selects, which has a reference the stack never chose.
|
|
459
466
|
*/
|
|
460
467
|
const findByName = (
|
|
461
468
|
management: ManagementApiService,
|
|
@@ -467,7 +474,9 @@ export const DeploymentProvider = () =>
|
|
|
467
474
|
const matches = (yield* listDeployments(management, projectId, type)).filter(
|
|
468
475
|
(row) =>
|
|
469
476
|
row.deploymentType === type &&
|
|
470
|
-
(type === "dev"
|
|
477
|
+
(type === "dev"
|
|
478
|
+
? row.reference === name || row.name === name
|
|
479
|
+
: row.previewIdentifier === name),
|
|
471
480
|
);
|
|
472
481
|
if (matches.length > 1) {
|
|
473
482
|
return yield* new AmbiguousDeployment({
|
|
@@ -503,10 +512,11 @@ export const DeploymentProvider = () =>
|
|
|
503
512
|
),
|
|
504
513
|
);
|
|
505
514
|
const reference = info.reference ?? "";
|
|
515
|
+
// A dev address names the reference or the Convex name, as `findByName` finds it.
|
|
506
516
|
if (
|
|
507
517
|
info.projectId !== address.projectId ||
|
|
508
518
|
info.deploymentType !== address.type ||
|
|
509
|
-
(address.type === "dev" && reference !== address.name)
|
|
519
|
+
(address.type === "dev" && reference !== address.name && deployment !== address.name)
|
|
510
520
|
) {
|
|
511
521
|
return undefined;
|
|
512
522
|
}
|
package/src/ManagementApi.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
// `{ data, error, response }`. This service turns that into an Effect with one
|
|
5
5
|
// typed error, resolves credentials lazily, and validates the fields the
|
|
6
6
|
// provider relies on at runtime.
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { parseEnv } from "node:util";
|
|
7
9
|
import { createDeploymentClient, createManagementClient } from "@convex-dev/platform";
|
|
8
10
|
import * as Context from "effect/Context";
|
|
9
11
|
import * as Effect from "effect/Effect";
|
|
@@ -70,15 +72,19 @@ export interface ManagementApiService {
|
|
|
70
72
|
run: (client: DeploymentClient) => Promise<FetchResult<T>>,
|
|
71
73
|
) => Effect.Effect<T, ConvexApiError>;
|
|
72
74
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* Convex CLI
|
|
77
|
-
* the dashboard's team list, and nothing is an error.
|
|
75
|
+
* The team of the credential. A team access token and an OAuth token of a
|
|
76
|
+
* team name it (`GET /token_details`). The Convex CLI login answers 403
|
|
77
|
+
* there, so its team is the team of the project of the deployment that the
|
|
78
|
+
* Convex CLI selected (CONVEX_DEPLOYMENT). Read once for each service.
|
|
78
79
|
*/
|
|
79
|
-
readonly
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
readonly teamId: Effect.Effect<number, ConvexApiError>;
|
|
81
|
+
/**
|
|
82
|
+
* Team id for a numeric id or a slug, for Convex.HubClient, which names the
|
|
83
|
+
* team of the hub. With a team access token, a slug is checked against the
|
|
84
|
+
* `teamSlug` of the team's projects, so a token of another team fails; with
|
|
85
|
+
* the Convex CLI login, a slug goes to the dashboard's team list.
|
|
86
|
+
*/
|
|
87
|
+
readonly resolveTeamId: (team: string | number) => Effect.Effect<number, ConvexApiError>;
|
|
82
88
|
/**
|
|
83
89
|
* True when a deploy key that Convex returned carries the token of the
|
|
84
90
|
* Management API credential itself. With an OAuth token, create_deploy_key
|
|
@@ -199,6 +205,34 @@ const ProjectSlugRows = Schema.Struct({
|
|
|
199
205
|
items: Schema.Array(Schema.Struct({ teamSlug: Schema.String })),
|
|
200
206
|
});
|
|
201
207
|
|
|
208
|
+
/** GET /deployments/{name}: the project of the deployment. */
|
|
209
|
+
const DeploymentProject = Schema.Struct({ projectId: Schema.Number });
|
|
210
|
+
|
|
211
|
+
/** GET /projects/{id}: the team of the project. */
|
|
212
|
+
const ProjectTeam = Schema.Struct({ teamId: Schema.Number });
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The cloud deployment that the Convex CLI selected: CONVEX_DEPLOYMENT in the environment or in
|
|
216
|
+
* `.env.local` of the working directory, as `npx convex dev` writes it (`dev:happy-animal-123`),
|
|
217
|
+
* without the type prefix. Undefined when neither has it, and for a local or anonymous deployment
|
|
218
|
+
* (`local:local-team-app`, `anonymous:anonymous-app`), which runs on this machine and is in no team.
|
|
219
|
+
*/
|
|
220
|
+
const selectedDeployment = () => {
|
|
221
|
+
const fromEnv = process.env["CONVEX_DEPLOYMENT"];
|
|
222
|
+
const value =
|
|
223
|
+
fromEnv !== undefined && fromEnv !== ""
|
|
224
|
+
? fromEnv
|
|
225
|
+
: (() => {
|
|
226
|
+
try {
|
|
227
|
+
return parseEnv(readFileSync(".env.local", "utf8"))["CONVEX_DEPLOYMENT"];
|
|
228
|
+
} catch {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
})();
|
|
232
|
+
const name = /^(?:(?:dev|prod|preview):)?([^:]+)$/.exec(value ?? "")?.[1];
|
|
233
|
+
return name === undefined || name.startsWith("anonymous-") ? undefined : name;
|
|
234
|
+
};
|
|
235
|
+
|
|
202
236
|
/** GET /token_details: the team of a team token, or the project of a project token. */
|
|
203
237
|
const TokenDetails = Schema.Union([
|
|
204
238
|
Schema.Struct({ type: Schema.Literal("teamToken"), teamId: Schema.Number }),
|
|
@@ -263,16 +297,53 @@ const makeService = (source: Effect.Effect<ResolvedCredentials>): ManagementApiS
|
|
|
263
297
|
Effect.map((body) => Schema.decodeUnknownSync(ProjectSlugRows)(body).items[0]?.teamSlug),
|
|
264
298
|
);
|
|
265
299
|
|
|
300
|
+
const projectTokenError = (projectId: number) =>
|
|
301
|
+
new ConvexApiError({
|
|
302
|
+
operation: "resolve team",
|
|
303
|
+
status: 403,
|
|
304
|
+
message: `the access token belongs to project ${projectId}, and a project token cannot create projects. Use a team access token.`,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const teamId: ManagementApiService["teamId"] = Effect.runSync(
|
|
308
|
+
Effect.cached(
|
|
309
|
+
Effect.gen(function* () {
|
|
310
|
+
const details = yield* tokenDetails;
|
|
311
|
+
if (details?.type === "teamToken") return details.teamId;
|
|
312
|
+
if (details?.type === "projectToken") return yield* projectTokenError(details.projectId);
|
|
313
|
+
// The Convex CLI login names no team, but the deployment that `npx convex dev` selected
|
|
314
|
+
// belongs to a project, and the project to a team.
|
|
315
|
+
const selected = selectedDeployment();
|
|
316
|
+
if (selected === undefined) {
|
|
317
|
+
return yield* new ConvexApiError({
|
|
318
|
+
operation: "resolve team",
|
|
319
|
+
status: 0,
|
|
320
|
+
message:
|
|
321
|
+
"the Convex CLI login names no team, and no cloud deployment is selected. Run `npx convex dev` once with a cloud deployment.",
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
const deployment = yield* request("get deployment", (c) =>
|
|
325
|
+
c.GET("/deployments/{deployment_name}", {
|
|
326
|
+
params: { path: { deployment_name: selected } },
|
|
327
|
+
}),
|
|
328
|
+
);
|
|
329
|
+
const { projectId } = Schema.decodeUnknownSync(DeploymentProject)(deployment);
|
|
330
|
+
const project = yield* request("get project", (c) =>
|
|
331
|
+
c.GET("/projects/{project_id}", { params: { path: { project_id: projectId } } }),
|
|
332
|
+
);
|
|
333
|
+
return Schema.decodeUnknownSync(ProjectTeam)(project).teamId;
|
|
334
|
+
}),
|
|
335
|
+
),
|
|
336
|
+
);
|
|
337
|
+
|
|
266
338
|
// The Management API addresses teams by numeric id. The only slug lookup
|
|
267
339
|
// it offers is per project, so a slug falls back to the dashboard's team
|
|
268
340
|
// list, which is unofficial. Pass the numeric team id to skip this.
|
|
269
341
|
const resolveTeamId: ManagementApiService["resolveTeamId"] = (team) =>
|
|
270
342
|
Effect.gen(function* () {
|
|
271
343
|
if (typeof team === "number") return team;
|
|
272
|
-
if (
|
|
344
|
+
if (/^\d+$/.test(team)) return Number(team);
|
|
273
345
|
const details = yield* tokenDetails;
|
|
274
346
|
if (details?.type === "teamToken") {
|
|
275
|
-
if (team === undefined) return details.teamId;
|
|
276
347
|
// The Management API has no team-by-id call, so the slug of the
|
|
277
348
|
// team comes from any of its projects. A team without a project
|
|
278
349
|
// cannot be checked: the numeric id, or no team, is the fix.
|
|
@@ -281,7 +352,7 @@ const makeService = (source: Effect.Effect<ResolvedCredentials>): ManagementApiS
|
|
|
281
352
|
return yield* new ConvexApiError({
|
|
282
353
|
operation: "resolve team",
|
|
283
354
|
status: 0,
|
|
284
|
-
message: `team "${team}" cannot be checked against the access token: team ${details.teamId} has no project yet. Set team to ${details.teamId}
|
|
355
|
+
message: `team "${team}" cannot be checked against the access token: team ${details.teamId} has no project yet. Set team to ${details.teamId}.`,
|
|
285
356
|
});
|
|
286
357
|
}
|
|
287
358
|
if (slug !== team) {
|
|
@@ -293,21 +364,7 @@ const makeService = (source: Effect.Effect<ResolvedCredentials>): ManagementApiS
|
|
|
293
364
|
}
|
|
294
365
|
return details.teamId;
|
|
295
366
|
}
|
|
296
|
-
if (details?.type === "projectToken")
|
|
297
|
-
return yield* new ConvexApiError({
|
|
298
|
-
operation: "resolve team",
|
|
299
|
-
status: 403,
|
|
300
|
-
message: `the access token belongs to project ${details.projectId}, and a project token cannot create projects. Use a team access token.`,
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
if (team === undefined) {
|
|
304
|
-
return yield* new ConvexApiError({
|
|
305
|
-
operation: "resolve team",
|
|
306
|
-
status: 0,
|
|
307
|
-
message:
|
|
308
|
-
"set team to the slug or the id of the Convex team: this access token does not name a team.",
|
|
309
|
-
});
|
|
310
|
-
}
|
|
367
|
+
if (details?.type === "projectToken") return yield* projectTokenError(details.projectId);
|
|
311
368
|
const resolved = yield* credentials;
|
|
312
369
|
const rows = yield* Effect.tryPromise({
|
|
313
370
|
try: async () => {
|
|
@@ -355,6 +412,7 @@ const makeService = (source: Effect.Effect<ResolvedCredentials>): ManagementApiS
|
|
|
355
412
|
request,
|
|
356
413
|
requestVoid,
|
|
357
414
|
deploymentRequest,
|
|
415
|
+
teamId,
|
|
358
416
|
resolveTeamId,
|
|
359
417
|
isCredentialToken,
|
|
360
418
|
isOAuthCredential,
|