5 Commits
1.0.1 ... 1.1.0

Author SHA1 Message Date
Simon Lecoq
5a5795cfe2 doc: update readme 2023-11-08 05:32:54 +00:00
Simon Lecoq
b54cabac16 feat: support for args/flags 2023-11-08 05:31:25 +00:00
Simon Lecoq
f9bc2a62a6 chore: update gitignore 2023-11-08 04:08:05 +00:00
Simon Lecoq
e13766ac57 feat: add flags support 2023-11-08 00:09:11 +00:00
Simon Lecoq
bd3e9cb3a4 fix: --unstable array join 2023-11-07 22:39:41 +00:00
7 changed files with 212 additions and 15 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
.deno-make.json .deno-make.json
tests/.* tests/.*
tests/tests.exe tests/tests.exe
tests/tests

View File

@@ -11,6 +11,7 @@ features to the [deno](https://deno.land) task runner.
- Write long tasks using arrays to improve readability - Write long tasks using arrays to improve readability
- Add descriptions to your tasks - Add descriptions to your tasks
- List and preview available tasks - List and preview available tasks
- Replace positional and arguments easily
![Advanced task configuration](/demo/config.png) ![Advanced task configuration](/demo/config.png)

View File

@@ -64,6 +64,41 @@
} }
} }
}, },
"demo:flags": {
"description": "🎬 Demo: flags",
"task": [
"echo '0: $<0>' &&",
"echo '1: $<1>' &&",
"echo 'foo: $<foo>' &&",
"echo 'bar: $<bar>'"
],
"args": [
{
"alias": "0",
"required": true,
"description": "Example of required argument"
},
{
"alias": "1",
"default": true,
"description": "Example of optional argument"
}
],
"flags": {
"foo": {
"alias": "f",
"description": "Example of a standard flag"
},
"bar": {
"alias": "b",
"default": "bar",
"description": "Example of a defaulted flag"
},
"baz": {
"description": "Example of a non-aliased flag"
}
}
},
"ci": { "ci": {
"description": "🤖 CI checks", "description": "🤖 CI checks",
"task": [ "task": [

4
deno.lock generated
View File

@@ -13,6 +13,7 @@
}, },
"redirects": { "redirects": {
"https://esm.sh/v132/@types/chai@~4.3/index.d.ts": "https://esm.sh/v132/@types/chai@4.3.8/index.d.ts", "https://esm.sh/v132/@types/chai@~4.3/index.d.ts": "https://esm.sh/v132/@types/chai@4.3.8/index.d.ts",
"https://esm.sh/v133/@types/chai-as-promised@~7.1/index.d.ts": "https://esm.sh/v133/@types/chai-as-promised@7.1.8/index.d.ts",
"https://esm.sh/v133/@types/chai@~4.3/index.d.ts": "https://esm.sh/v133/@types/chai@4.3.9/index.d.ts" "https://esm.sh/v133/@types/chai@~4.3/index.d.ts": "https://esm.sh/v133/@types/chai@4.3.9/index.d.ts"
}, },
"remote": { "remote": {
@@ -177,6 +178,7 @@
"https://deno.land/std@0.204.0/path/windows/to_namespaced_path.ts": "e0f4d4a5e77f28a5708c1a33ff24360f35637ba6d8f103d19661255ef7bfd50d", "https://deno.land/std@0.204.0/path/windows/to_namespaced_path.ts": "e0f4d4a5e77f28a5708c1a33ff24360f35637ba6d8f103d19661255ef7bfd50d",
"https://deno.land/std@0.205.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", "https://deno.land/std@0.205.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee",
"https://deno.land/std@0.205.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", "https://deno.land/std@0.205.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56",
"https://deno.land/std@0.205.0/flags/mod.ts": "0948466fc437f017f00c0b972a422b3dc3317a790bcf326429d23182977eaf9f",
"https://deno.land/std@0.205.0/fmt/colors.ts": "c51c4642678eb690dcf5ffee5918b675bf01a33fba82acf303701ae1a4f8c8d9", "https://deno.land/std@0.205.0/fmt/colors.ts": "c51c4642678eb690dcf5ffee5918b675bf01a33fba82acf303701ae1a4f8c8d9",
"https://deno.land/std@0.205.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978", "https://deno.land/std@0.205.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978",
"https://deno.land/std@0.205.0/fs/copy.ts": "ca19e4837965914471df38fbd61e16f9e8adfe89f9cffb0c83615c83ea3fc2bf", "https://deno.land/std@0.205.0/fs/copy.ts": "ca19e4837965914471df38fbd61e16f9e8adfe89f9cffb0c83615c83ea3fc2bf",
@@ -276,6 +278,7 @@
"https://deno.land/x/zod@v3.21.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", "https://deno.land/x/zod@v3.21.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c",
"https://deno.land/x/zod@v3.21.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4", "https://deno.land/x/zod@v3.21.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4",
"https://deno.land/x/zod@v3.21.4/types.ts": "b5d061babea250de14fc63764df5b3afa24f2b088a1d797fc060ba49a0ddff28", "https://deno.land/x/zod@v3.21.4/types.ts": "b5d061babea250de14fc63764df5b3afa24f2b088a1d797fc060ba49a0ddff28",
"https://esm.sh/chai-as-promised@7.1.1?pin=v133": "c94bdef9e8697e6cca465af6ec3437cea7cf53e44ae62e8a869f976d0ab3f2c7",
"https://esm.sh/chai@4.3.10": "e413ea40e1248a0a06e8812f22ec265c4a41cb57829c19017af5cb1def62880f", "https://esm.sh/chai@4.3.10": "e413ea40e1248a0a06e8812f22ec265c4a41cb57829c19017af5cb1def62880f",
"https://esm.sh/chai@4.3.10?pin=v133": "e25839044ba92464bd0194939276cc97cbdec7f42ad2c49609d0092eaac1c8e1", "https://esm.sh/chai@4.3.10?pin=v133": "e25839044ba92464bd0194939276cc97cbdec7f42ad2c49609d0092eaac1c8e1",
"https://esm.sh/string-argv@0.3.2": "ba1d5479e1ed6293e5a7ffd3297e0eb55a23a5d5d7ac71306ee829fba4243402", "https://esm.sh/string-argv@0.3.2": "ba1d5479e1ed6293e5a7ffd3297e0eb55a23a5d5d7ac71306ee829fba4243402",
@@ -290,6 +293,7 @@
"https://esm.sh/v132/string-argv@0.3.2/denonext/string-argv.mjs": "a7977920da1d72bf89b1db36d2c24fdfa82df6ce4f6c2b59a930b641a6ba9b37", "https://esm.sh/v132/string-argv@0.3.2/denonext/string-argv.mjs": "a7977920da1d72bf89b1db36d2c24fdfa82df6ce4f6c2b59a930b641a6ba9b37",
"https://esm.sh/v132/type-detect@4.0.8/denonext/type-detect.mjs": "deb58bd7203992249a5795f7da35d00b67077fe6c03019349cb614ba22ef52ad", "https://esm.sh/v132/type-detect@4.0.8/denonext/type-detect.mjs": "deb58bd7203992249a5795f7da35d00b67077fe6c03019349cb614ba22ef52ad",
"https://esm.sh/v133/assertion-error@1.1.0/denonext/assertion-error.mjs": "0a4a5dccfb89070dd1e09fad036e706aa51d9dd3236ab019aed08bef1841695b", "https://esm.sh/v133/assertion-error@1.1.0/denonext/assertion-error.mjs": "0a4a5dccfb89070dd1e09fad036e706aa51d9dd3236ab019aed08bef1841695b",
"https://esm.sh/v133/chai-as-promised@7.1.1/denonext/chai-as-promised.mjs": "bbcd90c4502fe553f17aa1923cb07c22cb2302c1da1d50a046796ab71b9f7f2e",
"https://esm.sh/v133/chai@4.3.10/denonext/chai.mjs": "fa4ea11c224f9f3abc5272c8917c0c629ec5ae2bec2fafe4edee09dfddcc4f68", "https://esm.sh/v133/chai@4.3.10/denonext/chai.mjs": "fa4ea11c224f9f3abc5272c8917c0c629ec5ae2bec2fafe4edee09dfddcc4f68",
"https://esm.sh/v133/check-error@1.0.3/denonext/check-error.mjs": "04b0b4e7d4470a991f1211e35075d68ad3d96602236853f615527af0e889a265", "https://esm.sh/v133/check-error@1.0.3/denonext/check-error.mjs": "04b0b4e7d4470a991f1211e35075d68ad3d96602236853f615527af0e889a265",
"https://esm.sh/v133/deep-eql@4.1.3/denonext/deep-eql.mjs": "3f406af09e31cfb3d403689e277eb392ee18361ca682c26a3955db094ba94802", "https://esm.sh/v133/deep-eql@4.1.3/denonext/deep-eql.mjs": "3f406af09e31cfb3d403689e277eb392ee18361ca682c26a3955db094ba94802",

120
mod.ts
View File

@@ -2,7 +2,16 @@
import * as JSONC from "https://deno.land/std@0.205.0/jsonc/mod.ts" import * as JSONC from "https://deno.land/std@0.205.0/jsonc/mod.ts"
import { z as is } from "https://deno.land/x/zod@v3.21.4/mod.ts" import { z as is } from "https://deno.land/x/zod@v3.21.4/mod.ts"
import { fromZodError } from "https://esm.sh/zod-validation-error@1.5.0?pin=v133" import { fromZodError } from "https://esm.sh/zod-validation-error@1.5.0?pin=v133"
import { bgBrightBlue, bold, gray, italic, underline, yellow } from "https://deno.land/std@0.205.0/fmt/colors.ts" import {
bgBrightBlue,
bold,
gray,
italic,
magenta,
underline,
yellow,
} from "https://deno.land/std@0.205.0/fmt/colors.ts"
import { parse } from "https://deno.land/std@0.205.0/flags/mod.ts"
// Structure flags ========================================================================================================= // Structure flags =========================================================================================================
@@ -10,7 +19,7 @@ import { bgBrightBlue, bold, gray, italic, underline, yellow } from "https://den
const common = is.object({ const common = is.object({
unstable: is.union([ unstable: is.union([
is.boolean().transform((v) => v ? "--unstable" : ""), is.boolean().transform((v) => v ? "--unstable" : ""),
is.array(is.string()).transform((v) => v.length ? v.map((w) => `--unstable-${w}`) : ""), is.array(is.string()).transform((v) => v.length ? v.map((w) => `--unstable-${w}`).join(" ") : ""),
]).optional(), ]).optional(),
quiet: is.boolean().optional().transform((v) => v ? "--quiet" : ""), quiet: is.boolean().optional().transform((v) => v ? "--quiet" : ""),
config: is.union([ config: is.union([
@@ -174,7 +183,7 @@ const inspect = is.union([
listen: is.string().min(1).optional().transform((v) => v ? `--inspect='${v}'` : ""), listen: is.string().min(1).optional().transform((v) => v ? `--inspect='${v}'` : ""),
break: is.string().min(1).optional().transform((v) => v ? `--inspect-brk='${v}'` : ""), break: is.string().min(1).optional().transform((v) => v ? `--inspect-brk='${v}'` : ""),
wait: is.string().min(1).optional().transform((v) => v ? `--inspect-wait='${v}'` : ""), wait: is.string().min(1).optional().transform((v) => v ? `--inspect-wait='${v}'` : ""),
}).transform((v) => Object.values(v ?? {}).filter(Boolean).join(" ")), }).transform((v) => Object.values(v).filter(Boolean).join(" ")),
]).optional() ]).optional()
/** Watch flags */ /** Watch flags */
@@ -184,7 +193,7 @@ const watch = is.union([
is.object({ is.object({
files: is.array(is.string()).optional().transform((v) => v?.length ? `--watch='${v.join(",")}'` : ""), files: is.array(is.string()).optional().transform((v) => v?.length ? `--watch='${v.join(",")}'` : ""),
clearScreen: is.boolean().optional().transform((v) => v === false ? "--no-clear-screen" : ""), clearScreen: is.boolean().optional().transform((v) => v === false ? "--no-clear-screen" : ""),
}).transform((v) => Object.values(v ?? {}).filter(Boolean).join(" ")), }).transform((v) => Object.values(v).filter(Boolean).join(" ")),
]).optional() ]).optional()
// Deno flags ========================================================================================================= // Deno flags =========================================================================================================
@@ -378,6 +387,24 @@ const _make = is.object({
) )
).default(() => ({})), ).default(() => ({})),
cwd: is.string().optional(), cwd: is.string().optional(),
args: is.array(
is.object({
alias: is.string(),
default: is.unknown().optional(),
required: is.boolean().default(false),
description: is.string().default(""),
}).refine((value) => !(("default" in value) && (value.required)), {
message: "Cannot have default when value is required",
}),
).default(() => []),
flags: is.record(
is.string(),
is.object({
alias: is.string().optional(),
default: is.unknown().optional(),
description: is.string().default(""),
}),
).default(() => ({})),
deno: is.object({ deno: is.object({
bench, bench,
bundle, bundle,
@@ -403,15 +430,50 @@ const _make = is.object({
/** Compute command to execute after applying deno flags */ /** Compute command to execute after applying deno flags */
export function command( export function command(
raw: string, raw: string,
{ deno }: Pick<is.infer<typeof _make>, "deno">, { flags, args, deno, argv = [] }: Pick<is.infer<typeof _make>, "deno" | "flags" | "args"> & { argv?: string[] },
{ colors = false } = {}, { colors = false, parseArgv = true } = {},
) { ) {
for (const [subcommand, flags] of Object.entries(deno)) { for (const [subcommand, options] of Object.entries(deno)) {
raw = raw.replaceAll( raw = raw.replaceAll(
`deno ${subcommand}`, `deno ${subcommand}`,
`deno ${subcommand} ${colors ? italic(underline(flags)) : flags}`, `deno ${subcommand} ${colors ? italic(underline(options)) : options}`,
) )
} }
const { _, ...options } = parse(argv, {
alias: Object.fromEntries(
Object.entries(flags).filter(([_, { alias }]) => alias).map(([key, { alias }]) => [alias, key]),
),
default: Object.fromEntries(
Object.entries(flags).filter(([_, options]) => "default" in options).map((
[key, options],
) => [key, options.default]),
),
})
for (let i = 0; i < args.length; i++) {
const { alias, required, default: defaults } = args[i]
if (parseArgv) {
if (required && (!(i in argv))) {
throw new ReferenceError(`Missing argument: ${alias}`)
}
raw = raw.replaceAll(`$<${alias}>`, `${argv[i] ?? defaults}`)
continue
}
if (colors) {
raw = raw
.replaceAll(`$<${alias}>`, italic(underline(`$<${alias}>`)))
.replaceAll(`$<${i}>`, italic(underline(`$<${alias}>`)))
}
}
for (const alias of Object.keys(flags)) {
if (parseArgv) {
raw = raw.replaceAll(`$<${alias}>`, `${options[alias]}`)
continue
}
if (colors) {
raw = raw
.replaceAll(`$<${alias}>`, italic(underline(`$<${alias}>`)))
}
}
return raw return raw
} }
@@ -419,6 +481,7 @@ export function command(
export async function make( export async function make(
{ {
task = "", task = "",
argv = [] as string[],
config = "deno.jsonc", config = "deno.jsonc",
log = console.log, log = console.log,
exit = true, exit = true,
@@ -440,11 +503,11 @@ export async function make(
}), }),
) )
if (task) { if (task) {
const { task: raw, env, deno, cwd } = tasks[task] const { task: raw, env, deno, flags, args, cwd } = tasks[task]
const temp = ".deno-make.json" const temp = ".deno-make.json"
const decoder = new TextDecoder() const decoder = new TextDecoder()
try { try {
const make = command(raw, { deno }) const make = command(raw, { deno, flags, args, argv })
await Deno.writeTextFile(temp, JSON.stringify({ tasks: { make } })) await Deno.writeTextFile(temp, JSON.stringify({ tasks: { make } }))
const process = new Deno.Command("deno", { const process = new Deno.Command("deno", {
args: ["task", ...(cwd ? ["--cwd", cwd] : []), "--config", temp, "make"], args: ["task", ...(cwd ? ["--cwd", cwd] : []), "--config", temp, "make"],
@@ -470,7 +533,7 @@ export async function make(
} }
} else if (Object.keys(tasks).length) { } else if (Object.keys(tasks).length) {
for ( for (
const [name, { task, description, env, cwd, deno }] of Object.entries( const [name, { task, description, env, cwd, deno, flags, args }] of Object.entries(
tasks, tasks,
) )
) { ) {
@@ -485,17 +548,45 @@ export async function make(
log( log(
gray( gray(
` ${k}=${v}${inherited ? underline(italic("→ inherited")) : ""}` ` ${k}=${v}${inherited ? underline(italic("→ inherited")) : ""}`
.trim(), .trimEnd(),
), ),
) )
} }
} }
if (args.length) {
log(magenta(`Arguments:`))
for (const { alias, required, description, ...options } of args) {
log(
` ${
magenta(
`${required ? `<${alias}>` : `[${alias}${"default" in options ? `=${options.default}` : ""}]`}`.padEnd(
24,
),
)
}${description}`,
)
}
}
if (Object.keys(flags).length) {
log(magenta(`Flags:`))
for (const [key, { alias, description, ...options }] of Object.entries(flags)) {
log(
` ${
magenta(
`${alias ? `-${alias},` : " "} --${key}${"default" in options ? `[=${options.default}]` : ""}`.padEnd(
24,
),
)
}${description}`,
)
}
}
if (cwd) { if (cwd) {
log(gray(`Working directory:`)) log(gray(`Working directory:`))
log(gray(` ${cwd}`)) log(gray(` ${cwd}`))
} }
log(gray(`Task:`)) log(gray(`Task:`))
log(gray(` ${command(task, { deno }, { colors: true })}`)) log(gray(` ${command(task, { deno, args, flags }, { colors: true, parseArgv: false })}`))
log("") log("")
} }
return { code: 0 } return { code: 0 }
@@ -505,5 +596,6 @@ export async function make(
} }
if (import.meta.main) { if (import.meta.main) {
await make({ task: Deno.args[0] }) const [task, ...argv] = Deno.args
await make({ task, argv })
} }

View File

@@ -1,7 +1,9 @@
import { make } from "./mod.ts" import { make } from "./mod.ts"
import { expandGlob } from "https://deno.land/std@0.205.0/fs/mod.ts" import { expandGlob } from "https://deno.land/std@0.205.0/fs/mod.ts"
import * as JSONC from "https://deno.land/std@0.205.0/jsonc/mod.ts" import * as JSONC from "https://deno.land/std@0.205.0/jsonc/mod.ts"
import { expect } from "https://esm.sh/chai@4.3.10?pin=v133" import chai from "https://esm.sh/chai@4.3.10?pin=v133"
import chaiAsPromised from "https://esm.sh/chai-as-promised@7.1.1?pin=v133"
const { expect } = chai.use(chaiAsPromised)
for await (const { path, name: _name } of expandGlob("tests/*.jsonc")) { for await (const { path, name: _name } of expandGlob("tests/*.jsonc")) {
const name = _name.replace(".jsonc", "").replaceAll("_", " ") const name = _name.replace(".jsonc", "").replaceAll("_", " ")
@@ -36,6 +38,39 @@ Deno.test("deno task make: print tasks", async () => {
expect(code).to.equal(0) expect(code).to.equal(0)
}) })
Deno.test("deno task make: args", async () => {
const stdio = [] as string[]
const { code } = await make({
task: "make:args",
argv: ["🦕"],
config: "tests/deno_make.jsonc",
log: (message) => stdio.push(message),
stdio: "piped",
exit: false,
})
expect(stdio.join("\n")).to.include("🦕").and.to.include("🦖")
expect(code).to.equal(0)
})
Deno.test("deno task make: missing args required throw", async () => {
await expect(make({ task: "make:args", argv: [], config: "tests/deno_make.jsonc", stdio: "null", exit: false })).to.be
.rejectedWith(Error, /missing argument/i)
})
Deno.test("deno task make: flags", async () => {
const stdio = [] as string[]
const { code } = await make({
task: "make:flags",
argv: ["--foo", "🦕"],
config: "tests/deno_make.jsonc",
log: (message) => stdio.push(message),
stdio: "piped",
exit: false,
})
expect(stdio.join("\n")).to.include("🦕").and.to.include("🦖")
expect(code).to.equal(0)
})
Deno.test("deno task make: exit code", async () => { Deno.test("deno task make: exit code", async () => {
const { exit } = Deno const { exit } = Deno
try { try {

View File

@@ -20,6 +20,35 @@
"TEST_INHERIT": true "TEST_INHERIT": true
} }
}, },
"make:args": {
"task": "echo '$<foo> / $<bar>'",
"cwd": "tests",
"args": [
{
"alias": "foo",
"required": true
},
{
"alias": "bar",
"default": "🦖"
},
{
"alias": "baz"
}
]
},
"make:flags": {
"task": "echo '$<foo> / $<bar>'",
"cwd": "tests",
"flags": {
"foo": {
},
"bar": {
"alias": "b",
"default": "🦖"
}
}
},
"make:multiline": { "make:multiline": {
"task": ["deno help"], "task": ["deno help"],
"cwd": "tests", "cwd": "tests",