diff --git a/deno.json b/deno.json index 03bb2fe..f037ef1 100644 --- a/deno.json +++ b/deno.json @@ -58,15 +58,16 @@ "./release-notes/" ], "imports": { - "@std/fs": "jsr:@std/fs@1.0.19", - "@std/path": "jsr:@std/path@1.1.2", - "@std/assert": "jsr:@std/assert@1.0.15", - "@std/streams": "jsr:@std/streams@1.0.13", + "@std/fs": "jsr:@std/fs@1.0.23", + "@std/path": "jsr:@std/path@1.1.4", + "@std/assert": "jsr:@std/assert@1.0.19", + "@std/streams": "jsr:@std/streams@1.0.17", "@cliffy/command": "jsr:@cliffy/command@1.0.0", "@cliffy/prompt": "jsr:@cliffy/prompt@1.0.0", - "neverthrow": "npm:neverthrow@^6.2.2", + "detect-indent": "npm:detect-indent@^7.0.2", + "neverthrow": "npm:neverthrow@^8.2.0", "@kdclients": "jsr:@kinsondigital/kd-clients@1.0.0-preview.15", - "@zod": "npm:zod@4.1.12", + "@zod": "npm:zod@^4.3.6", "@sprocket": "jsr:@kinsondigital/sprocket@2.2.0" }, "exports": { diff --git a/deno.lock b/deno.lock index 6b8b67d..565ea3e 100644 --- a/deno.lock +++ b/deno.lock @@ -2,25 +2,37 @@ "version": "5", "specifiers": { "jsr:@cliffy/ansi@1.0.0": "1.0.0", + "jsr:@cliffy/ansi@1.0.0-rc.8": "1.0.0-rc.8", "jsr:@cliffy/command@1.0.0": "1.0.0", + "jsr:@cliffy/command@1.0.0-rc.8": "1.0.0-rc.8", "jsr:@cliffy/flags@1.0.0": "1.0.0", + "jsr:@cliffy/flags@1.0.0-rc.8": "1.0.0-rc.8", "jsr:@cliffy/internal@1.0.0": "1.0.0", + "jsr:@cliffy/internal@1.0.0-rc.8": "1.0.0-rc.8", "jsr:@cliffy/keycode@1.0.0": "1.0.0", + "jsr:@cliffy/keycode@1.0.0-rc.8": "1.0.0-rc.8", "jsr:@cliffy/prompt@1.0.0": "1.0.0", + "jsr:@cliffy/prompt@1.0.0-rc.8": "1.0.0-rc.8", "jsr:@cliffy/table@1.0.0": "1.0.0", + "jsr:@cliffy/table@1.0.0-rc.8": "1.0.0-rc.8", "jsr:@kinsondigital/kd-clients@1.0.0-preview.15": "1.0.0-preview.15", + "jsr:@kinsondigital/sprocket@2.2.0": "2.2.0", "jsr:@nexterias/twitter-api-fetch@3.0.1": "3.0.1", - "jsr:@std/assert@1.0.15": "1.0.15", - "jsr:@std/assert@^1.0.18": "1.0.18", + "jsr:@std/assert@1.0.19": "1.0.19", + "jsr:@std/assert@^1.0.18": "1.0.19", + "jsr:@std/assert@~1.0.6": "1.0.19", "jsr:@std/bytes@^1.0.6": "1.0.6", "jsr:@std/cli@1.0.23": "1.0.23", "jsr:@std/encoding@1.0.5": "1.0.5", "jsr:@std/encoding@^1.0.10": "1.0.10", "jsr:@std/encoding@~0.219.1": "0.219.1", + "jsr:@std/encoding@~1.0.5": "1.0.10", "jsr:@std/fmt@^1.0.9": "1.0.9", + "jsr:@std/fmt@~1.0.2": "1.0.9", "jsr:@std/fs@1.0.11": "1.0.11", "jsr:@std/fs@1.0.18": "1.0.18", "jsr:@std/fs@1.0.19": "1.0.19", + "jsr:@std/fs@1.0.23": "1.0.23", "jsr:@std/fs@1.0.5": "1.0.5", "jsr:@std/internal@^1.0.10": "1.0.12", "jsr:@std/internal@^1.0.12": "1.0.12", @@ -28,66 +40,121 @@ "jsr:@std/io@~0.225.3": "0.225.3", "jsr:@std/path@1.0.7": "1.0.7", "jsr:@std/path@1.1.2": "1.1.2", + "jsr:@std/path@1.1.4": "1.1.4", "jsr:@std/path@^1.0.7": "1.1.4", "jsr:@std/path@^1.0.8": "1.1.0", "jsr:@std/path@^1.1.0": "1.1.0", "jsr:@std/path@^1.1.1": "1.1.4", "jsr:@std/path@^1.1.4": "1.1.4", + "jsr:@std/path@~1.0.6": "1.0.7", + "jsr:@std/semver@^1.0.8": "1.0.8", "jsr:@std/streams@1.0.13": "1.0.13", + "jsr:@std/streams@1.0.17": "1.0.17", "jsr:@std/text@^1.0.17": "1.0.17", + "jsr:@std/text@~1.0.7": "1.0.17", "npm:@types/node@*": "24.2.0", - "npm:neverthrow@^6.2.2": "6.2.2", - "npm:zod@4.1.12": "4.1.12" + "npm:detect-indent@^7.0.2": "7.0.2", + "npm:neverthrow@^8.2.0": "8.2.0", + "npm:zod@4.1.12": "4.1.12", + "npm:zod@^4.3.6": "4.3.6" }, "jsr": { + "@cliffy/ansi@1.0.0-rc.8": { + "integrity": "ba37f10ce55bbfbdd8ddd987f91f029b17bce88385b98ba3058870f3b007b80c", + "dependencies": [ + "jsr:@cliffy/internal@1.0.0-rc.8", + "jsr:@std/encoding@~1.0.5" + ] + }, "@cliffy/ansi@1.0.0": { "integrity": "987008f74e50aa72cc1517ffccc769711734a14927bc4599e052efe1b9a840e2", "dependencies": [ - "jsr:@cliffy/internal", + "jsr:@cliffy/internal@1.0.0", "jsr:@std/encoding@^1.0.10", "jsr:@std/io" ] }, + "@cliffy/command@1.0.0-rc.8": { + "integrity": "758147790797c74a707e5294cc7285df665422a13d2a483437092ffce40b5557", + "dependencies": [ + "jsr:@cliffy/flags@1.0.0-rc.8", + "jsr:@cliffy/internal@1.0.0-rc.8", + "jsr:@cliffy/table@1.0.0-rc.8", + "jsr:@std/fmt@~1.0.2", + "jsr:@std/text@~1.0.7" + ] + }, "@cliffy/command@1.0.0": { "integrity": "c52a241ea68857fcdaff4f3173eb404f8017d7bc35553b6f533c592b89dde7d2", "dependencies": [ - "jsr:@cliffy/flags", - "jsr:@cliffy/internal", - "jsr:@cliffy/table", - "jsr:@std/fmt", - "jsr:@std/text" + "jsr:@cliffy/flags@1.0.0", + "jsr:@cliffy/internal@1.0.0", + "jsr:@cliffy/table@1.0.0", + "jsr:@std/fmt@^1.0.9", + "jsr:@std/semver", + "jsr:@std/text@^1.0.17" + ] + }, + "@cliffy/flags@1.0.0-rc.8": { + "integrity": "0f1043ce6ef037ba1cb5fe6b1bcecb25dc2f29371a1c17f278ab0f45e4b6f46c", + "dependencies": [ + "jsr:@std/text@~1.0.7" ] }, "@cliffy/flags@1.0.0": { "integrity": "8b57698adc644da8f90422d58976362d41a4ebca39c312ca1c101585d0148feb", "dependencies": [ - "jsr:@cliffy/internal", - "jsr:@std/text" + "jsr:@cliffy/internal@1.0.0", + "jsr:@std/text@^1.0.17" ] }, + "@cliffy/internal@1.0.0-rc.8": { + "integrity": "34cdf2fad9b084b5aed493b138d573f52d4e988767215f7460daf0b918ff43d8" + }, "@cliffy/internal@1.0.0": { "integrity": "1e17ccbcd5420093c0a93e5b3827bbdc9abac5195bacf187edc44665e54bdde6" }, + "@cliffy/keycode@1.0.0-rc.8": { + "integrity": "76dbf85a67ec0aea2e29ca049b8507b6b3f62a2a971bd744d8d3fc447c177cd9" + }, "@cliffy/keycode@1.0.0": { "integrity": "755dbf007be110dcb5625f87eb61b362b6a0ca6835453af03ebd3b34d399cf14" }, + "@cliffy/prompt@1.0.0-rc.8": { + "integrity": "eba403ea1d47b9971bf2210fa35f4dc7ebd2aba87beec9540ae47552806e2f25", + "dependencies": [ + "jsr:@cliffy/ansi@1.0.0-rc.8", + "jsr:@cliffy/internal@1.0.0-rc.8", + "jsr:@cliffy/keycode@1.0.0-rc.8", + "jsr:@std/assert@~1.0.6", + "jsr:@std/fmt@~1.0.2", + "jsr:@std/path@~1.0.6", + "jsr:@std/text@~1.0.7" + ] + }, "@cliffy/prompt@1.0.0": { "integrity": "48b4cd35199fda7832f35e1fe0a3e8bc2b1ea49ba57b4ec0e29e22db44e8ca9f", "dependencies": [ - "jsr:@cliffy/ansi", - "jsr:@cliffy/internal", - "jsr:@cliffy/keycode", + "jsr:@cliffy/ansi@1.0.0", + "jsr:@cliffy/internal@1.0.0", + "jsr:@cliffy/keycode@1.0.0", "jsr:@std/assert@^1.0.18", - "jsr:@std/fmt", + "jsr:@std/fmt@^1.0.9", "jsr:@std/io", "jsr:@std/path@^1.1.4", - "jsr:@std/text" + "jsr:@std/text@^1.0.17" + ] + }, + "@cliffy/table@1.0.0-rc.8": { + "integrity": "8bbcdc2ba5e0061b4b13810a24e6f5c6ab19c09f0cce9eb691ccd76c7c6c9db5", + "dependencies": [ + "jsr:@std/fmt@~1.0.2" ] }, "@cliffy/table@1.0.0": { "integrity": "3fdaa9e1ef1ea62022108adabd826932bdea8dd05497079896febcd41322907f", "dependencies": [ - "jsr:@std/fmt" + "jsr:@std/fmt@^1.0.9" ] }, "@kinsondigital/kd-clients@1.0.0-preview.15": { @@ -99,6 +166,19 @@ "jsr:@std/path@1.0.7" ] }, + "@kinsondigital/sprocket@2.2.0": { + "integrity": "76efac8eba41da38ff6d1dce712e5d79c9641e0b5b450245c0c52c9f68cf72e8", + "dependencies": [ + "jsr:@cliffy/command@1.0.0-rc.8", + "jsr:@cliffy/prompt@1.0.0-rc.8", + "jsr:@kinsondigital/kd-clients", + "jsr:@std/cli", + "jsr:@std/fs@1.0.19", + "jsr:@std/path@1.1.2", + "jsr:@std/streams@1.0.13", + "npm:zod@4.1.12" + ] + }, "@nexterias/twitter-api-fetch@3.0.1": { "integrity": "9fb5a6770ce659b46df02c3b93ed4ab13ce7b3dd9a84232e0bc921fe7bdd1dad", "dependencies": [ @@ -108,15 +188,12 @@ "@std/assert@1.0.13": { "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29" }, - "@std/assert@1.0.15": { - "integrity": "d64018e951dbdfab9777335ecdb000c0b4e3df036984083be219ce5941e4703b", + "@std/assert@1.0.19": { + "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", "dependencies": [ "jsr:@std/internal@^1.0.12" ] }, - "@std/assert@1.0.18": { - "integrity": "270245e9c2c13b446286de475131dc688ca9abcd94fc5db41d43a219b34d1c78" - }, "@std/bytes@1.0.6": { "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a" }, @@ -160,6 +237,13 @@ "jsr:@std/path@^1.1.1" ] }, + "@std/fs@1.0.23": { + "integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37", + "dependencies": [ + "jsr:@std/internal@^1.0.12", + "jsr:@std/path@^1.1.4" + ] + }, "@std/internal@1.0.10": { "integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7" }, @@ -190,31 +274,54 @@ "jsr:@std/internal@^1.0.12" ] }, + "@std/semver@1.0.8": { + "integrity": "dc830e8b8b6a380c895d53fbfd1258dc253704ca57bbe1629ac65fd7830179b7" + }, "@std/streams@1.0.13": { "integrity": "772d208cd0d3e5dac7c1d9e6cdb25842846d136eea4a41a62e44ed4ab0c8dd9e", "dependencies": [ "jsr:@std/bytes" ] }, + "@std/streams@1.0.17": { + "integrity": "7859f3d9deed83cf4b41f19223d4a67661b3d3819e9fc117698f493bf5992140", + "dependencies": [ + "jsr:@std/bytes" + ] + }, "@std/text@1.0.17": { "integrity": "4b2c4ef67ae5b6c1dfd447c81c83a43718f52e3c7e748d8b33f694aba9895f95" } }, "npm": { + "@rollup/rollup-linux-x64-gnu@4.57.1": { + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "os": ["linux"], + "cpu": ["x64"] + }, "@types/node@24.2.0": { "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", "dependencies": [ "undici-types" ] }, - "neverthrow@6.2.2": { - "integrity": "sha512-POR1FACqdK9jH0S2kRPzaZEvzT11wsOxLW520PQV/+vKi9dQe+hXq19EiOvYx7lSRaF5VB9lYGsPInynrnN05w==" + "detect-indent@7.0.2": { + "integrity": "sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==" + }, + "neverthrow@8.2.0": { + "integrity": "sha512-kOCT/1MCPAxY5iUV3wytNFUMUolzuwd/VF/1KCx7kf6CutrOsTie+84zTGTpgQycjvfLdBBdvBvFLqFD2c0wkQ==", + "optionalDependencies": [ + "@rollup/rollup-linux-x64-gnu" + ] }, "undici-types@7.10.0": { "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==" }, "zod@4.1.12": { "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==" + }, + "zod@4.3.6": { + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==" } }, "remote": { @@ -707,12 +814,13 @@ "jsr:@cliffy/prompt@1.0.0", "jsr:@kinsondigital/kd-clients@1.0.0-preview.15", "jsr:@kinsondigital/sprocket@2.2.0", - "jsr:@std/assert@1.0.15", - "jsr:@std/fs@1.0.19", - "jsr:@std/path@1.1.2", - "jsr:@std/streams@1.0.13", - "npm:neverthrow@^6.2.2", - "npm:zod@4.1.12" + "jsr:@std/assert@1.0.19", + "jsr:@std/fs@1.0.23", + "jsr:@std/path@1.1.4", + "jsr:@std/streams@1.0.17", + "npm:detect-indent@^7.0.2", + "npm:neverthrow@^8.2.0", + "npm:zod@^4.3.6" ] } } diff --git a/dev-tools/create-pr.ts b/dev-tools/create-pr.ts index 1df1005..e23d8ab 100644 --- a/dev-tools/create-pr.ts +++ b/dev-tools/create-pr.ts @@ -157,7 +157,7 @@ const featureBranch = await Input.prompt({ const chosenBaseBranch = await Select.prompt({ message: "Choose the pull request base branch:", - options: ["develop", "main"], + options: ["main", "develop"], }); // If the chosen branch exists diff --git a/installation/core/deno.ts b/installation/core/deno.ts new file mode 100644 index 0000000..422ce52 --- /dev/null +++ b/installation/core/deno.ts @@ -0,0 +1,41 @@ +/** + * Represents a standard deno task, which is either a command string or `undefined`. + */ +export type Task = string | undefined; + +/** + * Represents a structured deno task definition with a description, command, and optional dependencies. + */ +export type TaskDefinition = { + /** + * A human-readable description of what the task does. + */ + description: string; + + /** + * The command to execute when the task is run. + */ + command: string; + + /** + * An optional list of task names that must run before this task. + */ + dependencies?: string[]; +}; + +/** + * A record of task names mapped to either a command string, a {@link Task}, or a {@link TaskDefinition}. + */ +export type Tasks = { + [key: string]: Task | TaskDefinition; +}; + +/** + * Represents a deno configuration object, optionally containing a set of {@link Tasks}. + */ +export type DenoConfig = { + /** + * The optional collection of tasks defined in the configuration. + */ + tasks?: Tasks; +}; diff --git a/installation/core/validation.ts b/installation/core/validation.ts new file mode 100644 index 0000000..2097dfc --- /dev/null +++ b/installation/core/validation.ts @@ -0,0 +1,73 @@ +import z from "@zod"; +import type { DenoConfig, Task, TaskDefinition } from "./deno.ts"; + +/** + * Checks whether the given value is a valid {@link DenoConfig} object. + * @param denoConfig The value to validate. + * @returns `true` if the value conforms to the {@link DenoConfig} schema; otherwise, `false`. + */ +export function isDenoConfig(denoConfig: unknown): denoConfig is DenoConfig { + const isValidResult = isDenoConfigValid(denoConfig); + + return isValidResult[0]; +} + +/** + * Validates whether the given value conforms to the {@link DenoConfig} schema. + * @param denoConfig The value to validate. + * @returns A tuple where the first element is `true` if valid or `false` if invalid, + * and the second element is an empty string on success or a detailed error message on failure. + */ +export function isDenoConfigValid(denoConfig: unknown): [boolean, string] { + const schema = z.object({ + tasks: z.record( + z.string(), + z.union([ + z.string(), + z.object({ + description: z.string(), + command: z.string(), + dependencies: z.array(z.string()).optional(), + }), + ]), + ).optional(), + }); + + const validationResult = schema.safeParse(denoConfig); + + if (validationResult.success) { + return [true, ""]; + } else { + const issues = validationResult.error.issues; + const errMsg = issues.map((e) => { + const path = e.path.length > 0 ? e.path.join(" -> ") : "(root)"; + return ` - [${path}]: ${e.message} (code: ${e.code})`; + }).join("\n"); + + return [false, `Validation failed with ${issues.length} error(s):\n${errMsg}`]; + } +} + +/** + * Checks whether the given value is a standard {@link Task}, which is a plain string. + * @param task The value to check. + * @returns `true` if the value is a string; otherwise, `false`. + */ +export function isStandardTask(task: unknown): task is Task { + return task !== undefined && typeof task === "string"; +} + +/** + * Checks whether the given value is a valid {@link TaskDefinition} object + * containing at least `description` and `command` string properties. + * @param taskDef The value to validate. + * @returns `true` if the value conforms to the {@link TaskDefinition} schema; otherwise, `false`. + */ +export function isTaskDefinition(taskDef: unknown): taskDef is TaskDefinition { + const schema = z.object({ + description: z.string(), + command: z.string(), + }); + + return schema.safeParse(taskDef).success; +} diff --git a/installation/install.ts b/installation/install.ts index 25c5fb3..08f92df 100644 --- a/installation/install.ts +++ b/installation/install.ts @@ -1,23 +1,10 @@ // deno-lint-ignore no-import-prefix import { existsSync } from "jsr:@std/fs@1.0.19"; -// deno-lint-ignore no-import-prefix -import { promptSelect } from "jsr:@std/cli@1.0.23/unstable-prompt-select"; import { getLatestVersion } from "./core/jsr.ts"; - -/** - * Represents a Deno configuration file. - */ -interface DenoConfig { - /** - * The tasks defined in the Deno configuration. - */ - tasks: { - /** - * The sprocket task command. - */ - sprocket?: string; - }; -} +import { isDenoConfig, isStandardTask, isTaskDefinition } from "./core/validation.ts"; +import type { DenoConfig } from "./core/deno.ts"; +import detectIndent from "detect-indent"; +import { isNothing } from "../src/core/guards.ts"; const cwd = Deno.cwd(); const denoConfigPath = `${cwd}/deno.json`; @@ -27,80 +14,104 @@ const pkgName = "sprocket"; // Get the latest version of sprocket const latestVersion = await getLatestVersion(scope, pkgName); -const newDefaultTaskValue = `${command} jsr:@${scope}/${pkgName}@${latestVersion} run-job ./dev-tools/sprocket-config.ts`; // If the deno.json does not exist, create it. if (!existsSync(denoConfigPath)) { + const newDefaultTaskValue = `${command} jsr:@${scope}/${pkgName}@${latestVersion} run-job ./dev-tools/sprocket-config.ts`; const newConfigFile: DenoConfig = { tasks: { - sprocket: `${command} jsr:@${scope}/${pkgName}@${latestVersion} run-job ./dev-tools/sprocket-config.ts`, + "run-sprocket-job": newDefaultTaskValue, }, }; - updateTaskValue(newConfigFile, newDefaultTaskValue); - + Deno.writeTextFileSync(denoConfigPath, JSON.stringify(newConfigFile, null, 4)); console.log(`Sprocket has been installed with version 'v${latestVersion}'.`); Deno.exit(); } const configFileContent = await Deno.readTextFile(denoConfigPath); -const denoConfig = JSON.parse(configFileContent) as DenoConfig; +const indent = detectIndent(configFileContent).indent; -// Create the tasks property if it doesn't exist -if (!denoConfig["tasks"]) { - denoConfig["tasks"] = {}; +let denoConfig: unknown; + +try { + denoConfig = JSON.parse(configFileContent); +} catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + console.error(`An error occurred while parsing the deno.json file: ${errorMsg}`); + Deno.exit(1); } -// If the sprocket task exists -if (denoConfig["tasks"]["sprocket"]) { - const sprocketPkgPathRegex = - /deno run .+ jsr:@kinsondigital\/sprocket@[0-9]\.[0-9]\.[0-9] run-job .\/dev-tools\/sprocket-config.ts/gm; - const sprocketPropValue = denoConfig.tasks.sprocket; - const taskSections = sprocketPropValue.split(" ").map((section) => section.trim()); +if (isDenoConfig(denoConfig)) { + // Create the tasks property if it doesn't exist + if (denoConfig.tasks === undefined) { + denoConfig["tasks"] = { + "run-sprocket-job": `${command} jsr:@${scope}/${pkgName}@${latestVersion} run-job ./dev-tools/sprocket-config.ts`, + }; + + const updatedConfigContent = JSON.stringify(denoConfig, null, isNothing(indent) ? 4 : indent); + Deno.writeTextFileSync(denoConfigPath, updatedConfigContent); + Deno.exit(); + } - if (sprocketPkgPathRegex.test(sprocketPropValue)) { - const pgkRefIndex = taskSections.findIndex((section) => { - const regex = /jsr:@kinsondigital\/sprocket@[0-2]\.[0-2]\.[0-2]/gm; + const taskNames = Object.getOwnPropertyNames(denoConfig.tasks); - return regex.test(section); - }); + for (let i = 0; i < taskNames.length; i++) { + const taskName = taskNames[i]; + const task = denoConfig.tasks[taskName]; - if (pgkRefIndex === -1) { - console.error("An error occurred while parsing the sprocket package reference from the deno.json file."); + if (isStandardTask(task)) { + denoConfig.tasks[taskName] = updateSprocketVersion(task as string, latestVersion); + } else if (isTaskDefinition(task)) { + task.command = updateSprocketVersion(task.command, latestVersion); + } else { + console.error( + `The task '${taskName}' in the deno.json file is not valid. Please ensure all tasks are either a string or an object with 'description' and 'command' properties.`, + ); Deno.exit(1); } + } - const version = taskSections[pgkRefIndex].split("@").pop(); - - if (version !== latestVersion) { - const selectionResult = promptSelect(`Are you sure you want to update sprocket to 'v${latestVersion}':`, [ - "Yes", - "No", - ], { clear: true }); - - const selectedOption: "Yes" | "No" = selectionResult === "Yes" ? selectionResult : "No"; - - if (selectedOption === "Yes") { - taskSections[pgkRefIndex] = `jsr:@${scope}/${pkgName}@${latestVersion}`; + const updatedConfigContent = JSON.stringify(denoConfig, null, isNothing(indent) ? 4 : indent); + Deno.writeTextFileSync(denoConfigPath, updatedConfigContent); - updateTaskValue(denoConfig, taskSections.join(" ")); - } - } - } else { - updateTaskValue(denoConfig, newDefaultTaskValue); - } + console.log(`Sprocket has been installed with version 'v${latestVersion}'.`); } else { - updateTaskValue(denoConfig, newDefaultTaskValue); + console.error(`The existing deno.json file is not valid. Please ensure it conforms to the expected schema.`); + Deno.exit(1); } /** - * Updates the sprocket task value in the Deno configuration file. - * @param denoConfig The Deno configuration object. - * @param taskValue The new task value for sprocket. + * Updates the version of the Sprocket package in a given task value. + * @param taskValue The task value containing the Sprocket package reference. + * @param newVersion The new version to update the Sprocket package to. + * @returns The updated task value with the new Sprocket version. + * @remarks If no sprocket package reference is found in the task value, the original task value is returned unchanged. */ -function updateTaskValue(denoConfig: DenoConfig, taskValue: string): void { - denoConfig["tasks"]["sprocket"] = taskValue; +function updateSprocketVersion(taskValue: string, newVersion: string): string { + const sprocketPkgPathRegex = /jsr:@kinsondigital\/sprocket@[0-9]+\.[0-9]+\.[0-9]+/; + + // Pull the sprocket package reference from the task value + const pkgRefMatch = Array.from(taskValue.match(sprocketPkgPathRegex) || []); + + if (pkgRefMatch.length === 0 || !taskValue.includes("run-job")) { + return taskValue; + } + + const pkgRef = pkgRefMatch[0]; + const versionRegex = /[0-9]+\.[0-9]+\.[0-9]+/; + + const pkgRefSections = pkgRef.split("@"); + + const versionSectionIndex = pkgRefSections.findIndex((section) => versionRegex.test(section)); + + if (versionSectionIndex === -1) { + throw new Error("An error occurred while parsing the sprocket package reference from the deno.json file."); + } + + pkgRefSections[versionSectionIndex] = newVersion; + + const updatedPkgRef = pkgRefSections.join("@"); - // Write the updated config back to the file - Deno.writeTextFileSync(denoConfigPath, `${JSON.stringify(denoConfig, null, 4)}\n`); + return taskValue.replace(pkgRef, updatedPkgRef); }