Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/app.main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { usePrefix, useExec, useVirtualEnv, useVersion, usePrint, useConfig, useLogger } from "hooks"
import { usePrefix, useVirtualEnv, useVersion, usePrint, useConfig, useLogger } from "hooks"
import { VirtualEnv } from "./hooks/useVirtualEnv.ts"
import { Verbosity } from "./hooks/useConfig.ts"
import { Path, utils, semver, hooks } from "tea"
import { basename } from "deno/path/mod.ts"
import exec, { repl } from "./app.exec.ts"
import provides from "./app.provides.ts"
import setup from "./prefab/setup.ts"
import magic from "./app.magic.ts"
import dump from "./app.dump.ts"
import help from "./app.help.ts"
Expand Down Expand Up @@ -46,7 +47,7 @@ export async function run(args: Args) {
const wut_ = wut(args)

const venv = await injection(args)
const {cmd, env, pkgs, installations} = await useExec({...args, inject: venv})
const {cmd, env, pkgs, installations} = await setup({...args, inject: venv})

const full_path = () => [...env["PATH"]?.split(':') ?? [], ...PATH?.split(':') ?? []].uniq()

Expand Down
2 changes: 1 addition & 1 deletion src/app.provides.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ExitError } from "./hooks/useErrorHandler.ts"
import { which } from "./hooks/useExec.ts"
import which from "tea/plumbing/which.ts"

export default async function provides(args: string[]) {
let status = 0;
Expand Down
12 changes: 4 additions & 8 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@

import useExec from "./useExec.ts"
import useVirtualEnv from "./useVirtualEnv.ts"
import usePackageYAML, { usePackageYAMLFrontMatter } from "./usePackageYAML.ts"
import useVirtualEnv, { VirtualEnv } from "./useVirtualEnv.ts"
import useYAMLFrontMatter from "./useYAMLFrontMatter.ts"
import useVersion from "./useVersion.ts"
import useErrorHandler, { ExitError } from "./useErrorHandler.ts"
import usePrint from "./usePrint.ts"
Expand All @@ -14,10 +12,8 @@ function usePrefix() {
}

export {
useExec,
useVirtualEnv,
usePackageYAML,
usePackageYAMLFrontMatter,
useYAMLFrontMatter,
useVersion,
useErrorHandler,
usePrint,
Expand All @@ -30,7 +26,7 @@ export {
ExitError
}

export type { RunOptions }
export type { RunOptions, VirtualEnv }

declare global {
interface Array<T> {
Expand Down
15 changes: 8 additions & 7 deletions src/hooks/useConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,20 @@ export function ConfigDefault(flags?: Flags, arg0 = Deno.execPath(), env = Deno.
PATH
} = env

// we only output color & control sequences to stderr
const isTTY = () => Deno.isatty(Deno.stderr.rid)

return {
...defaults,
arg0: new Path(arg0),
UserAgent: `tea.cli/${useVersion()}`,
logger: {
prefix: undefined,
color: getColor(env)
color: getColor(env, isTTY)
},
modifiers: {
dryrun: flags?.dryrun ?? false,
verbosity: flags?.verbosity ?? getVerbosity(env),
verbosity: flags?.verbosity ?? getVerbosity(env, isTTY()),
json: flags?.json ?? false,
keepGoing: flags?.keepGoing ?? false,
},
Expand Down Expand Up @@ -115,7 +118,7 @@ export enum Verbosity {
trace = 3
}

function getVerbosity(env: Record<string, string>): Verbosity {
function getVerbosity(env: Record<string, string>, sequences_ok: boolean): Verbosity {
const { DEBUG, GITHUB_ACTIONS, RUNNER_DEBUG, VERBOSE, CI } = env

if (DEBUG == '1') return Verbosity.debug
Expand All @@ -124,17 +127,15 @@ function getVerbosity(env: Record<string, string>): Verbosity {
const verbosity = flatmap(VERBOSE, parseInt)
if (isNumber(verbosity)) {
return verbosity
} else if (parseBool(CI)) {
} else if (parseBool(CI) || !sequences_ok) {
// prevents dumping 100s of lines of download progress
return Verbosity.quiet
} else {
return Verbosity.normal
}
}

function getColor(env: Record<string, string>) {
const isTTY = () => Deno.isatty(Deno.stdout.rid) && Deno.isatty(Deno.stdout.rid)

function getColor(env: Record<string, string>, isTTY: () => boolean) {
if ((env.CLICOLOR ?? '1') != '0' && isTTY()){
//https://bixense.com/clicolors/
return true
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useVersion.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//NOTE we statically replace this file at deployment

import { README } from "./useVirtualEnv.ts"
import { Path } from "tea"

Expand All @@ -11,5 +13,3 @@ const version = `${(
export default function() {
return version
}

// we statically replace this file at deployment
2 changes: 1 addition & 1 deletion src/hooks/useVirtualEnv.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { usePackageYAMLFrontMatter, refineFrontMatter, FrontMatter } from "./usePackageYAML.ts"
import usePackageYAMLFrontMatter, { refineFrontMatter, FrontMatter } from "./useYAMLFrontMatter.ts"
import { PackageRequirement, Path, SemVer, utils, TeaError, hooks, semver } from "tea"
const { flatmap, pkg, validate } = utils
import { isPlainObject } from "is-what"
Expand Down
33 changes: 17 additions & 16 deletions src/hooks/usePackageYAML.ts → src/hooks/useYAMLFrontMatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,26 @@ const { validatePackageRequirement } = hacks
const { useMoustaches, useConfig } = hooks
const { validate } = utils

export interface FrontMatter {
args: string[]
pkgs: PackageRequirement[]
env: Record<string, string>
}

export default async function(script: Path, srcroot?: Path): Promise<FrontMatter | undefined> {
const yaml = await readYAMLFrontMatter(script)
if (!yaml) return
return refineFrontMatter(yaml, srcroot)
}



interface Return1 {
getDeps: (wbuild: boolean) => PackageRequirement[]
yaml: PlainObject
}

export default function usePackageYAML(yaml: unknown): Return1 {
function parseYAMLFrontMatter(yaml: unknown): Return1 {
//TODO do magic: if (err == "no-front-matter")

if (!isPlainObject(yaml)) throw new Error("bad-yaml")
Expand All @@ -28,14 +42,8 @@ export default function usePackageYAML(yaml: unknown): Return1 {
return { getDeps, yaml }
}

export interface FrontMatter {
args: string[]
pkgs: PackageRequirement[]
env: Record<string, string>
}

export function refineFrontMatter(obj: unknown, srcroot?: Path): FrontMatter {
const rv = usePackageYAML(obj)
const rv = parseYAMLFrontMatter(obj)

const getArgs = () => {
const fn1 = () => {
Expand Down Expand Up @@ -78,14 +86,7 @@ export function refineFrontMatter(obj: unknown, srcroot?: Path): FrontMatter {
}
}

export async function usePackageYAMLFrontMatter(script: Path, srcroot?: Path): Promise<FrontMatter | undefined> {
const yaml = await readYAMLFrontMatter(script)
if (!yaml) return
return refineFrontMatter(yaml, srcroot)
}


import { parse as parseYaml } from "https://deno.land/std@0.182.0/encoding/yaml.ts"
import { parse as parseYaml } from "https://deno.land/std@0.190.0/yaml/parse.ts"
import { readLines } from "deno/io/read_lines.ts"

async function readYAMLFrontMatter(path: Path): Promise<PlainObject | undefined> {
Expand Down
46 changes: 32 additions & 14 deletions src/hooks/useExec.install.ts → src/prefab/install.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { PackageSpecification, Package, utils, Installation, plumbing, Path } from "tea"
import { Logger as InstallLogger } from "tea/plumbing/install.ts"
const { hydrate, link, resolve, install } = plumbing
import useLogger, { Logger } from "./useLogger.ts"
import { ExitError } from "./useErrorHandler.ts"
import useConfig from "./useConfig.ts"
import useConfig, { Verbosity } from "../hooks/useConfig.ts"
import useLogger, { Logger } from "../hooks/useLogger.ts"
import { ExitError } from "../hooks/useErrorHandler.ts"
const { hydrate, link: base_link, resolve, install } = plumbing
import undent from "outdent"
import { Verbosity } from "./index.ts"
import usePantry from "https://raw.githubusercontent.com/teaxyz/lib/v0.3.1/src/hooks/usePantry.ts"

//TODO we should use even more plumbing to ensure pkgs aren’t moved into
// TEA_PREFIX until all their deps are moved in
Expand Down Expand Up @@ -62,7 +62,7 @@ export default async function(pkgs: PackageSpecification[], update: boolean) {
: QuietLogger(logger)
const ops = pending
.map(pkg => install(pkg, mlogger)
.then(async i => { await link(i); return i }))
.then(link))
installed.push(...await Promise.all(ops))
logger.clear() // clears install progress
} else for (const pkg of pending) {
Expand All @@ -76,20 +76,18 @@ export default async function(pkgs: PackageSpecification[], update: boolean) {

const log_installed_msg = (pkg: Package, title: string, logger: Logger) => {
const { prefix, modifiers: { json } } = useConfig()
const { gray, logJSON } = useLogger()
const { gray } = useLogger()

console.assert(!json)

const pkg_prefix_str = (pkg: Package) => [
gray(prefix.prettyString()),
pkg.project,
`${gray('v')}${pkg.version}`
].join(gray('/'))

if (json) {
logJSON({status: title, pkg: utils.pkg.str(pkg)})
} else {
const str = pkg_prefix_str(pkg)
logger!.replace(`${title}: ${str}`, { prefix: false })
}
const str = pkg_prefix_str(pkg)
logger!.replace(`${title}: ${str}`, { prefix: false })
}


Expand Down Expand Up @@ -221,4 +219,24 @@ function QuietLogger(logger: Logger): InstallLogger {
log_installed_msg(installation.pkg, 'installed', logger)
}
}
}
}

async function link(installation: Installation) {
const pp: Promise<void>[] = [base_link(installation)]

const bin = useConfig().prefix.join("local/bin")
const tea = useConfig().prefix.join("tea.xyz/v*/bin/tea").isExecutableFile()?.relative({ to: bin })

/// we only do auto-POSIX symlinking if tea is installed properly
if (tea) for (const provides of await usePantry().project(installation.pkg).provides()) {
const target = bin.mkdir('p').join(provides)
if (!target.exists()) {
const p = Deno.symlink(tea, target.string)
pp.push(p)
}
}

await Promise.all(pp)

return installation
}
108 changes: 17 additions & 91 deletions src/hooks/useExec.ts → src/prefab/setup.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { plumbing, utils, hooks, PackageSpecification, Installation, PackageRequirement, Path, semver, TeaError } from "tea"
import { usePackageYAMLFrontMatter } from "./usePackageYAML.ts"
import { VirtualEnv } from "./useVirtualEnv.ts"
import install from "./useExec.install.ts"
import useConfig from "./useConfig.ts"
import { plumbing, utils, hooks, PackageSpecification, Installation, Path, semver, TeaError } from "tea"
import { useYAMLFrontMatter, VirtualEnv, useConfig } from "hooks"
import base_which from "tea/plumbing/which.ts"
import install from "../prefab/install.ts"

const { usePantry, useCellar, useDownload, useShellEnv } = hooks
const { hydrate } = plumbing
Expand Down Expand Up @@ -35,7 +34,7 @@ export default async function({ pkgs, inject, sync, ...opts }: Parameters) {

const precmd: string[] = []

const yaml = await usePackageYAMLFrontMatter(arg0, inject?.srcroot)
const yaml = await useYAMLFrontMatter(arg0, inject?.srcroot)
if (yaml) {
precmd.unshift(...yaml.args)
Object.assign(env, yaml.env) //FIXME should override env from pkgs
Expand Down Expand Up @@ -183,96 +182,23 @@ function urlify(arg0: string) {
}
}

type WhichResult = PackageRequirement & {
shebang?: string
}

export async function which(arg0: string | undefined) {
if (!arg0?.trim() || arg0.includes("/")) {
// no shell we know allows searching for subdirectories off PATH
return false
}

const { TEA_PKGS, TEA_MAGIC } = useConfig().env
const pantry = usePantry()
let found: { project: string, constraint: semver.Range, shebang: string[] } | undefined
const promises: Promise<void>[] = []
if (!arg0) return
const { TEA_MAGIC } = useConfig().env
const abracadabra = TEA_MAGIC?.split(":").includes("abracadabra")

for await (const entry of pantry.ls()) {
if (found) break
const p = pantry.project(entry).provides().then(providers => {
for (const provider of providers) {
if (found) {
return
} else if (provider == arg0 || (!abracadabra && provider == `tea-${arg0}`)) {
const inenv = TEA_PKGS?.split(":")
.map(utils.pkg.parse)
.find(x => x.project == entry.project)
if (inenv) {
// we are being executed via the command not found handler inside a dev-env
// so let’s use the version that was already calculated for this dev-env
if ("version" in inenv) {
found = {...inenv, constraint: new semver.Range(`=${inenv.version}`), shebang: [provider] }
} else {
found = {...inenv, shebang: [provider] }
}
} else {
const constraint = new semver.Range("*")
found = {...entry, constraint, shebang: [provider] }
}
} else if (arg0.startsWith(provider)) {
// eg. `node^16` symlink
try {
const constraint = new semver.Range(arg0.substring(provider.length))
found = {...entry, constraint, shebang: [provider] }
} catch {
// not a valid semver range; fallthrough
}
} else {
//TODO more efficient to check the prefix fits arg0 first
// eg. if python3 then check if the provides starts with python before
// doing all the regex shit. Matters because there's a *lot* of YAMLs

let rx = /({{\s*version\.(marketing|major)\s*}})/
let match = provider.match(rx)
if (!match?.index) continue
const regx = match[2] == 'major' ? '\\d+' : '\\d+\\.\\d+'
const foo = subst(match.index, match.index + match[1].length, provider, `(${regx})`)
rx = new RegExp(`^${foo}$`)
match = arg0.match(rx)
if (match) {
const constraint = new semver.Range(`~${match[1]}`)
found = {...entry, constraint, shebang: [arg0] }
}
}
}
}).swallow(/^parser: pantry: package.yml/)
promises.push(p)

const pp = pantry.project(entry).provider().then(f => {
if (!f) return
const rv = f(arg0)
if (rv) found = {
...entry,
constraint: new semver.Range('*'),
shebang: [...rv, arg0]
}
})
promises.push(pp)
///FIXME is slow to scan all package.ymls twice :/
if (!abracadabra) {
const found = await base_which(`tea-${arg0}`, { providers: false })
if (found) return found
}

if (!found) {
// if we didn’t find anything yet then we have to wait on the promises
// otherwise we can ignore them
await Promise.all(promises)
}
const found = await base_which(arg0, { providers: true })
if (!found) return

if (found) {
return found
const inenv = useConfig().env.TEA_PKGS?.split(":").map(utils.pkg.parse).find(x => x.project == found?.project)
if (inenv) {
found.constraint = inenv.constraint
}
}

const subst = function(start: number, end: number, input: string, what: string) {
return input.substring(0, start) + what + input.substring(end)
return found
}
Loading