From 45d07a96568d62adc4f9659aed5c90a9e64086e9 Mon Sep 17 00:00:00 2001 From: Steve Worley Date: Wed, 20 Aug 2025 08:57:10 +1000 Subject: [PATCH 1/3] Feat: Add redirect delete handling. Update parameter collection. --- src/commands/delete.js | 20 ++++-------- src/commands/redirect.js | 62 ++++++++++++++++++++++++++++++++---- src/helper/deleteResponse.js | 30 +++++++++++++++++ 3 files changed, 93 insertions(+), 19 deletions(-) create mode 100644 src/helper/deleteResponse.js diff --git a/src/commands/delete.js b/src/commands/delete.js index 512d27c..155d2cc 100644 --- a/src/commands/delete.js +++ b/src/commands/delete.js @@ -100,19 +100,13 @@ const command = { } catch (err) { // If we have a response in the error message, try to parse it try { - const match = err.message.match(/Response: (.*)/s); - if (match) { - const responseData = JSON.parse(match[1]); - - // Check if this was actually a successful deletion - if (!responseData.error && responseData.meta && responseData.meta[0]) { - const meta = responseData.meta[0]; - if (meta.deleted) { - return color.green(`Successfully removed [${args.path}]`); - } - if (meta.deleted_timestamp) { - return color.dim(`Path [${args.path}] was already deleted`); - } + const [ok, message] = deleteResponse(err); + if (ok) { + if (message === "success") { + return color.green(`Successfully removed [${args.path}]`); + } + if (message === "already deleted") { + return color.dim(`Path [${args.path}] was already deleted`); } } } catch (parseError) { diff --git a/src/commands/redirect.js b/src/commands/redirect.js index 1141297..a6caa1a 100644 --- a/src/commands/redirect.js +++ b/src/commands/redirect.js @@ -4,10 +4,12 @@ * @usage * quant redirect [status] */ -const { text, select, isCancel } = require('@clack/prompts'); +const { text, select, isCancel, confirm } = require('@clack/prompts'); +const color = require('picocolors'); const config = require('../config'); const client = require('../quant-client'); const isMD5Match = require('../helper/is-md5-match'); +const deleteResponse = require('../helper/deleteResponse'); const command = { command: 'redirect [status]', @@ -30,11 +32,34 @@ const command = { type: 'number', default: 302, choices: [301, 302, 303, 307, 308] - }); + }) + .option('delete', { + describe: 'Delete the redirect', + alias: ['d'], + type: 'boolean', + default: false + }) + .option('force', { + describe: 'Delete without confirmation', + alias: ['f'], + type: 'boolean', + default: false + }) }, async promptArgs(providedArgs = {}) { + let isDelete = providedArgs.delete === true; let from = providedArgs.from; + + if (isDelete) { + from = await text({ + message: 'Enter URL to redirect from', + validate: value => !value ? 'From URL is required' : undefined + }); + if (isCancel(from)) return null; + return { from, to: null, status: null, delete: true, force: providedArgs.force} + } + if (!from) { from = await text({ message: 'Enter URL to redirect from', @@ -68,7 +93,7 @@ const command = { if (isCancel(status)) return null; } - return { from, to, status }; + return { from, to, status, delete: false, force: false }; }, async handler(args) { @@ -89,11 +114,36 @@ const command = { const status = args.status || 302; try { - await quant.redirect(args.from, args.to, null, status); - return `Created redirect from ${args.from} to ${args.to} (${status})`; + if (args.delete) { + if (!args.force) { + const shouldDelete = await confirm({ + message: 'This will delete the redirect. Are you sure?', + initialValue: false, + active: 'Yes', + inactive: 'No' + }); + if (isCancel(shouldDelete) || !shouldDelete) { + throw new Error('Operation cancelled'); + } + } + await quant.delete(args.from); + return color.green(`Deleted redirect from ${args.from}`); + } else { + await quant.redirect(args.from, args.to, null, status); + return color.green(`Created redirect from ${args.from} to ${args.to} (${status})`); + } } catch (err) { + const [ok, message] = deleteResponse(err); + if (ok) { + if (message === "success") { + return color.green("Redirect was deleted"); + } + if (message === "already deleted") { + return color.dim("Redirect was already deleted"); + } + } if (isMD5Match(err)) { - return `Skipped redirect from ${args.from} to ${args.to} (already exists)`; + return color.dim(`Skipped redirect from ${args.from} to ${args.to} (already exists)`); } throw new Error(`Failed to create redirect: ${err.message}`); } diff --git a/src/helper/deleteResponse.js b/src/helper/deleteResponse.js new file mode 100644 index 0000000..45808e6 --- /dev/null +++ b/src/helper/deleteResponse.js @@ -0,0 +1,30 @@ +/** + * Handle the delete response. + */ + +/** + * Handle the delete response. + * @param {*} error + * @returns [boolean, string] + * - Success, if the delete was successful + * - Message, the message from the response + */ +const deleteResponse = function(error) { + const reponseData = error.message.match(/Response: (.*)/s); + if (reponseData) { + const responseData = JSON.parse(reponseData[1]); + if (responseData.meta && responseData.meta[0]) { + const meta = responseData.meta[0]; + if (meta.deleted) { + return [true, "success"]; + } + if (meta.deleted_timestamp) { + return [true, "already deleted"]; + } + } + } + + return [false, "unknown error"]; +} + +module.exports = deleteResponse; \ No newline at end of file From dd7c108dfd2dfc72fb9eb04cb8984867bb91386e Mon Sep 17 00:00:00 2001 From: Steve Worley Date: Wed, 20 Aug 2025 12:25:00 +1000 Subject: [PATCH 2/3] Add missing import. --- src/commands/delete.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/delete.js b/src/commands/delete.js index 155d2cc..d07861d 100644 --- a/src/commands/delete.js +++ b/src/commands/delete.js @@ -9,6 +9,7 @@ const { text, confirm, isCancel } = require('@clack/prompts'); const color = require('picocolors'); const config = require('../config'); const client = require('../quant-client'); +const deleteResponse = require('../helper/deleteResponse'); const command = { command: 'delete ', From 1975917380022067240ed91305c7d46061fdb565 Mon Sep 17 00:00:00 2001 From: Steve Worley Date: Thu, 21 Aug 2025 08:04:25 +1000 Subject: [PATCH 3/3] Fix tests with coloured output. --- src/helper/deleteResponse.js | 2 +- tests/unit/commands/redirect.test.mjs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/helper/deleteResponse.js b/src/helper/deleteResponse.js index 45808e6..953e23e 100644 --- a/src/helper/deleteResponse.js +++ b/src/helper/deleteResponse.js @@ -10,7 +10,7 @@ * - Message, the message from the response */ const deleteResponse = function(error) { - const reponseData = error.message.match(/Response: (.*)/s); + const reponseData = error.message && error.message.match(/Response: (.*)/s); if (reponseData) { const responseData = JSON.parse(reponseData[1]); if (responseData.meta && responseData.meta[0]) { diff --git a/tests/unit/commands/redirect.test.mjs b/tests/unit/commands/redirect.test.mjs index fd58f2e..b18ce58 100644 --- a/tests/unit/commands/redirect.test.mjs +++ b/tests/unit/commands/redirect.test.mjs @@ -57,7 +57,7 @@ describe('Redirect Command', () => { }; const result = await redirect.handler.call(context, args); - expect(result).to.equal('Created redirect from /old-path to /new-path (301)'); + expect(result).to.include('Created redirect from /old-path to /new-path (301)'); expect(mockClientInstance._history.post.length).to.equal(1); }); @@ -88,7 +88,7 @@ describe('Redirect Command', () => { }; const result = await redirect.handler.call(context, args); - expect(result).to.equal('Skipped redirect from /old-path to /new-path (already exists)'); + expect(result).to.include('Skipped redirect from /old-path to /new-path (already exists)'); }); it('should handle missing args', async () => { @@ -176,7 +176,7 @@ describe('Redirect Command', () => { }; const result = await redirect.handler.call(context, args); - expect(result).to.equal('Created redirect from /old-path to /new-path (302)'); + expect(result).to.include('Created redirect from /old-path to /new-path (302)'); expect(mockClientInstance._history.post.length).to.equal(1); const [call] = mockClientInstance._history.post; expect(call.headers['Quant-Status']).to.equal(302);