diff --git a/src/commands/schema/abandon.ts b/src/commands/schema/abandon.ts new file mode 100644 index 00000000..df0a0c0d --- /dev/null +++ b/src/commands/schema/abandon.ts @@ -0,0 +1,96 @@ +import { confirm } from "@inquirer/prompts"; +import SchemaCommand from "../../lib/schema-command"; +import { Flags } from "@oclif/core"; + +export default class CommitSchemaCommand extends SchemaCommand { + static flags = { + ...SchemaCommand.flags, + force: Flags.boolean({ + description: "Push the change without a diff or schema version check", + default: false, + }), + }; + + static description = "Abandons the currently staged schema."; + + static examples = ["$ fauna schema abandon"]; + + async run() { + try { + const { url, secret } = await this.fetchsetup(); + if (this.flags?.force) { + const params = new URLSearchParams({ + force: "true", // Just abandon, don't pass a schema version through. + }); + + const path = new URL(`/schema/1/staged/abandon?${params}`, url); + const res = await fetch(path, { + method: "POST", + headers: { AUTHORIZATION: `Bearer ${secret}` }, + // https://github.com/nodejs/node/issues/46221 + // https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1483 + // @ts-expect-error-next-line + duplex: "half", + }); + + const json = await res.json(); + if (json.error) { + this.error(json.error?.message ?? json.error); + } + + this.log("Schema is abandonded"); + } else { + // Show status to confirm. + const { url, secret } = await this.fetchsetup(); + const res = await fetch( + new URL("/schema/1/staged/status?diff=true", url), + { + method: "GET", + headers: { AUTHORIZATION: `Bearer ${secret}` }, + // @ts-expect-error-next-line + duplex: "half", + } + ); + + const json = await res.json(); + if (json.error) { + this.error(json.error.message); + } + + if (json.status === "none") { + this.error("There is no staged schema to abandon"); + } + + this.log(json.diff); + + const confirmed = await confirm({ + message: "Abandon these changes?", + default: false, + }); + + if (confirmed) { + const params = new URLSearchParams({ version: json.version }); + + const path = new URL(`/schema/1/staged/abandon?${params}`, url); + const res = await fetch(path, { + method: "POST", + headers: { AUTHORIZATION: `Bearer ${secret}` }, + // @ts-expect-error-next-line + duplex: "half", + }); + + const json0 = await res.json(); + if (json0.error) { + this.error(json0.error.message); + } + + this.log("Schema is abandonded"); + } else { + this.log("Commit cancelled"); + } + } + } catch (err) { + this.error(err); + } + } +} diff --git a/src/commands/schema/commit.ts b/src/commands/schema/commit.ts new file mode 100644 index 00000000..61f30ad4 --- /dev/null +++ b/src/commands/schema/commit.ts @@ -0,0 +1,100 @@ +import { confirm } from "@inquirer/prompts"; +import SchemaCommand from "../../lib/schema-command"; +import { Flags } from "@oclif/core"; + +export default class CommitSchemaCommand extends SchemaCommand { + static flags = { + ...SchemaCommand.flags, + force: Flags.boolean({ + description: "Push the change without a diff or schema version check", + default: false, + }), + }; + + static description = "Commits the currently staged schema."; + + static examples = ["$ fauna schema commit"]; + + async run() { + try { + const { url, secret } = await this.fetchsetup(); + if (this.flags?.force) { + const params = new URLSearchParams({ + force: "true", // Just commit, don't pass a schema version through. + }); + + const path = new URL(`/schema/1/staged/commit?${params}`, url); + const res = await fetch(path, { + method: "POST", + headers: { AUTHORIZATION: `Bearer ${secret}` }, + // https://github.com/nodejs/node/issues/46221 + // https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1483 + // @ts-expect-error-next-line + duplex: "half", + }); + + const json = await res.json(); + if (json.error) { + this.error(json.error?.message ?? json.error); + } + + this.log("Schema is comitted"); + } else { + // Show status to confirm. + const { url, secret } = await this.fetchsetup(); + const res = await fetch( + new URL("/schema/1/staged/status?diff=true", url), + { + method: "GET", + headers: { AUTHORIZATION: `Bearer ${secret}` }, + // @ts-expect-error-next-line + duplex: "half", + } + ); + + const json = await res.json(); + if (json.error) { + this.error(json.error.message); + } + + if (json.status === "none") { + this.error("There is no staged schema to commit"); + } + + this.log(json.diff); + + if (json.status !== "ready") { + this.error("Schema is not ready to be committed"); + } + + const confirmed = await confirm({ + message: "Accept and commit these changes?", + default: false, + }); + + if (confirmed) { + const params = new URLSearchParams({ version: json.version }); + + const path = new URL(`/schema/1/staged/commit?${params}`, url); + const res = await fetch(path, { + method: "POST", + headers: { AUTHORIZATION: `Bearer ${secret}` }, + // @ts-expect-error-next-line + duplex: "half", + }); + + const json0 = await res.json(); + if (json0.error) { + this.error(json0.error.message); + } + + this.log("Schema is comitted"); + } else { + this.log("Commit cancelled"); + } + } + } catch (err) { + this.error(err); + } + } +} diff --git a/test/integ/base.ts b/test/integ/base.ts index 7c5ce289..4e9141ca 100644 --- a/test/integ/base.ts +++ b/test/integ/base.ts @@ -59,8 +59,11 @@ export const shellOk = async ( return res.stdout; }; -export const shellErr = async (cmd: string): Promise => { - const res = await shell(cmd); +export const shellErr = async ( + cmd: string, + secret?: string +): Promise => { + const res = await shell(cmd, secret); if (res.ok) { fail(`Command should not have exitted succesfully:\n${res.stdout}`); } diff --git a/test/integ/schema.test.ts b/test/integ/schema.test.ts index 1e64c7ac..ccb95ab7 100644 --- a/test/integ/schema.test.ts +++ b/test/integ/schema.test.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import { evalOk, newDB, shellOk, stripMargin } from "./base"; +import { evalOk, newDB, shellErr, shellOk, stripMargin } from "./base"; it("fauna schema push --stage --force works", async () => { const secret = await newDB(); @@ -96,3 +96,136 @@ it("fauna schema status works", async () => { ) ); }); + +it("fauna schema commit --force works", async () => { + const secret = await newDB(); + + await shellOk( + "fauna schema push --dir test/integ/schema/start --force", + secret + ); + + await evalOk( + "User.create({ id: 0, name: 'Alice', email: 'alice@example.com' })", + secret + ); + + expect( + await evalOk("Collection.all().map(.name).toArray()", secret) + ).to.deep.equal(["User"]); + + await shellOk( + "fauna schema push --dir test/integ/schema/staged_index --force --stage", + secret + ); + + // The index should not be visible on the companion object. + expect( + await evalOk( + stripMargin( + `|let user: Any = User + |user.byName` + ), + secret + ) + ).to.deep.equal(null); + + // Commit the schema + await shellOk("fauna schema commit --dir . --force", secret); + + // Index should now be available on the companion object. + expect( + await evalOk(stripMargin(`User.byName('Alice').toArray().map(.id)`), secret) + ).to.deep.equal(["0"]); + + // Status should be blank now. + expect(await status(secret)).to.equal( + stripMargin( + `|Status: none + |` + ) + ); + + // Comitting when there is nothing staged should return an error. + expect( + await shellErr("fauna schema commit --dir . --force", secret) + ).to.equal("There is no staged schema to commit"); +}); + +it("fauna schema abandon --force works", async () => { + const secret = await newDB(); + + await shellOk( + "fauna schema push --dir test/integ/schema/start --force", + secret + ); + + await evalOk( + "User.create({ id: 0, name: 'Alice', email: 'alice@example.com' })", + secret + ); + + expect( + await evalOk("Collection.all().map(.name).toArray()", secret) + ).to.deep.equal(["User"]); + + await shellOk( + "fauna schema push --dir test/integ/schema/staged_index --force --stage", + secret + ); + + // The index should be visible on the definition object. + expect( + await evalOk("Collection.byName('User')!.indexes.byName", secret) + ).to.deep.equal({ + terms: [ + { + field: ".name", + mva: false, + }, + ], + queryable: true, + status: "complete", + }); + + // But not visible on the companion object. + expect( + await evalOk( + stripMargin( + `|let user: Any = User + |user.byName` + ), + secret + ) + ).to.deep.equal(null); + + // Abandon the schema + await shellOk("fauna schema abandon --dir . --force", secret); + + // Index should no longer be in the definition object. + expect( + await evalOk("Collection.byName('User')!.indexes.byName", secret) + ).to.deep.equal(null); + expect( + await evalOk( + stripMargin( + `|let user: Any = User + |user.byName` + ), + secret + ) + ).to.deep.equal(null); + + // Status should be blank now. + expect(await status(secret)).to.equal( + stripMargin( + `|Status: none + |` + ) + ); + + // Abandoning when there is no staged schema should return an error. + expect( + await shellErr("fauna schema abandon --dir . --force", secret) + ).to.equal("There is no staged schema to abandon"); +});