2023-08-12 20:30:44 -04:00
import { parse as JSONC } from "https://deno.land/x/jsonct@v0.1.0/mod.ts" ;
type ConfigCheck = { path? :string , text? :string , json? :Record < string , string | Record < string , string | string [ ] > > } ;
type ConfigCheckPair = [ config :ConfigCheck , imports :ConfigCheck ] ;
export const RootHost = import . meta . resolve ( "./" ) ;
export const Root = new URL ( ` file:// ${ Deno . cwd ( ) . replaceAll ( "\\" , "/" ) } ` ) . toString ( ) ;
export async function HuntConfig ( )
{
let path :string , resp :Response , text = "" , json ;
try
{
path = "deno.json"
resp = await fetch ( Root + "/" + path ) ;
text = await resp . text ( ) ;
}
catch ( e )
{
try
{
path = "deno.jsonc" ;
resp = await fetch ( Root + "/" + path ) ;
text = await resp . text ( ) ;
}
catch ( e )
{
try
{
path = ".vscode/settings.json" ;
resp = await fetch ( Root + "/" + path ) ;
json = await resp . json ( ) ;
2023-08-13 11:00:48 -04:00
2023-08-12 20:30:44 -04:00
path = json [ "deno.config" ] ;
json = undefined ;
if ( path )
{
resp = await fetch ( Root + "/" + path ) ;
text = await resp . text ( ) ;
}
}
catch ( e )
{
path = "" ;
}
}
}
2023-08-13 11:00:48 -04:00
if ( path )
2023-08-12 20:30:44 -04:00
{
try
{
json = JSONC ( text ) ;
}
catch ( e )
{
json = undefined ;
}
}
let imports :ConfigCheck = { } ;
if ( json && json . imports )
{
2023-08-13 11:00:48 -04:00
// config.imports
2023-08-12 20:30:44 -04:00
imports . json = json ;
imports . text = JSON . stringify ( json ) ;
imports . path = path ;
}
else if ( json && ! json . imports && json . importMap )
{
2023-08-13 11:00:48 -04:00
// config.importMap
2023-08-12 20:30:44 -04:00
try
{
imports . path = json . importMap ;
resp = await fetch ( Root + "/" + imports . path ) ;
imports . text = await resp . text ( ) ;
2023-08-13 11:00:48 -04:00
try
{
imports . json = JSONC ( imports . text ) ;
}
catch ( e )
{
imports . json = undefined ;
}
2023-08-12 20:30:44 -04:00
}
catch ( e )
{
// malformed import map
}
}
return [ { path , text , json } , imports ] as ConfigCheckPair
}
2023-08-13 11:00:48 -04:00
export async function Install ( file :string , overrideName? :string , handler ? : ( content :string ) = > string )
2023-08-12 20:30:44 -04:00
{
const pathFile = RootHost + "install__/" + file ;
try {
const check = await Deno . readTextFile ( Deno . cwd ( ) + "/" + file ) ;
const replace = confirm ( ` ⚠️🚧 The file " ${ file } " already exists. Replace it? ` ) ;
if ( replace )
{
throw ( "" )
}
console . log ( ` Using pre-existing " ${ file } " for now. ` ) ;
}
catch ( e )
{
const resp = await fetch ( pathFile ) ;
const text = await resp . text ( ) ;
2023-08-13 11:00:48 -04:00
const name = overrideName || file ;
await Deno . writeTextFile ( Deno . cwd ( ) + "/" + name , handler ? handler ( text ) : text ) ;
2023-08-12 20:30:44 -04:00
}
}
export async function Check ( )
{
2023-08-28 22:16:56 -04:00
let [ config , imports ] = await HuntConfig ( ) ;
console . log ( ` Checking directory " ${ Root } " ` ) ;
console . log ( "Found" , config , imports ) ;
2023-08-12 20:30:44 -04:00
try
{
2023-08-28 22:16:56 -04:00
2023-08-13 11:00:48 -04:00
//console.log(config, imports);
2023-08-12 20:30:44 -04:00
if ( ! config . path )
{
2023-08-13 11:00:48 -04:00
console . log ( ` 🛠️ No Deno configuration found. Creating "deno.jsonc" now. ` ) ;
await Deno . writeTextFile ( Deno . cwd ( ) + "/deno.jsonc" , ` {"imports":{}} ` ) ;
Check ( ) ;
return ;
}
else if ( ! config . json )
{
if ( confirm ( ` 🚧 Deno configuration is malformed. Replace " ${ config . path } " with a new one?. ` ) )
2023-08-12 20:30:44 -04:00
{
2023-08-13 11:00:48 -04:00
await Deno . writeTextFile ( Deno . cwd ( ) + "/" + config . path , ` {"imports":{}} ` ) ;
2023-08-12 20:30:44 -04:00
Check ( ) ;
return ;
}
else
{
2023-08-13 11:00:48 -04:00
throw ( "⛔ Invalid configuration." ) ;
2023-08-12 20:30:44 -04:00
}
}
2023-08-13 11:00:48 -04:00
else if ( ! imports . json )
{
if ( imports . path != config . path )
2023-08-12 20:30:44 -04:00
{
2023-08-13 11:00:48 -04:00
if ( confirm ( ` 🚧 External import map " ${ imports . path } " is missing or malformed. Replace it with defaults?. ` ) )
{
await Deno . writeTextFile ( Deno . cwd ( ) + "/" + imports . path , ` {"imports":{}} ` ) ;
Check ( ) ;
return ;
}
else
{
throw ( "⛔ Invalid configuration." ) ;
}
2023-08-12 20:30:44 -04:00
}
}
2023-08-13 11:00:48 -04:00
else if ( ! imports . json ? . imports )
{
imports . json . imports = { } ;
}
2023-08-12 20:30:44 -04:00
2023-08-13 11:00:48 -04:00
if ( config . json && imports . json ? . imports )
2023-08-12 20:30:44 -04:00
{
const importMap = imports . json . imports as Record < string , string > ;
2023-08-13 11:00:48 -04:00
const bake = async ( obj :ConfigCheck ) = > await Deno . writeTextFile ( Deno . cwd ( ) + "/" + obj . path , JSON . stringify ( obj . json , null , "\t" ) ) ;
2023-08-12 20:30:44 -04:00
if ( ! importMap [ "react" ] )
{
2023-08-13 11:00:48 -04:00
console . log ( ` 🛠️ Adding React import specifier ("react") ` ) ;
importMap [ "react" ] = ` https://esm.sh/preact@10.16.0/compat ` ;
importMap [ "react/" ] = ` https://esm.sh/preact@10.16.0/compat/ ` ;
await bake ( imports ) ;
console . log ( ` 🚦 NOTE: Deno will need to cache "react" for intellisense to work properly. ` )
2023-08-12 20:30:44 -04:00
}
if ( ! importMap [ ">able/" ] )
{
2023-08-13 11:00:48 -04:00
console . log ( ` 🛠️ Adding Able import specifier (">able/"). ` ) ;
importMap [ ">able/" ] = ` ${ RootHost } ` ;
await bake ( imports ) ;
2023-08-12 20:30:44 -04:00
}
2023-08-13 11:00:48 -04:00
2023-08-28 22:16:56 -04:00
const tasks :Record < string , string > = {
"check" : ` deno run -A --no-lock ${ RootHost } cli.tsx check ` ,
"local" : ` deno run -A --no-lock ${ RootHost } cli.tsx local ` ,
"debug" : ` deno run -A --no-lock ${ RootHost } cli.tsx debug ` ,
"serve" : ` deno run -A --no-lock ${ RootHost } cli.tsx serve ` ,
"cloud" : ` deno run -A --no-lock ${ RootHost } cli.tsx cloud `
} ;
const confTasks = ( config . json . tasks || { } ) as Record < string , string > ;
for ( const key in tasks )
{
if ( tasks [ key ] !== confTasks [ key ] )
{
if ( confirm ( ` 🤔 OPTIONAL: The tasks defined in your config contain missing or modified values. Update tasks? ` ) )
{
config . json . tasks = { . . . confTasks , . . . tasks } ;
await bake ( config ) ;
}
break ;
}
}
const options =
{
"lib" : [ "deno.window" , "dom" , "dom.asynciterable" ] ,
"jsx" : "react-jsx" ,
"jsxImportSource" : "react"
}
const compOpts = config . json . compilerOptions as Record < string , string | string [ ] > || { } ;
const compJSX = compOpts . jsx == options . jsx ;
const compJSXImportSource = compOpts . jsxImportSource == options . jsxImportSource ;
const compLib :string [ ] = compOpts . lib as string [ ] || [ ] ;
let compLibHasAll = true ;
options . lib . forEach ( item = > ! compLib . includes ( item ) && ( compLibHasAll = false ) )
if ( ! compOpts || ! compJSX || ! compJSXImportSource || ! compLibHasAll )
{
console . log ( ` 🛠️ Adding values to "compilerOptions" configuration. ` ) ;
compOpts . jsx = options . jsx ;
compOpts . jsxImportSource = options . jsxImportSource ;
compOpts . lib = [ . . . compLib , . . . options . lib ] ;
config . json . compilerOptions = compOpts ;
await bake ( config ) ;
}
2023-08-12 20:30:44 -04:00
if ( ! importMap [ ">able/app.tsx" ] )
{
2023-08-13 22:58:15 -04:00
if ( confirm ( ` 🤔 OPTIONAL: Your import map does not override the default/empty FRONT-END app with the specifier ">able/app.tsx". Create this file and add the specifier? ` ) )
2023-08-12 20:30:44 -04:00
{
2023-08-13 11:00:48 -04:00
importMap [ ">able/app.tsx" ] = ` ./app.tsx ` ;
await bake ( imports ) ;
2023-08-12 20:30:44 -04:00
await Install ( "app.tsx" ) ;
}
2023-08-13 11:00:48 -04:00
}
else
{
2023-08-28 22:16:56 -04:00
/ *
2023-08-13 11:00:48 -04:00
try
{
const app = await import ( importMap [ ">able/app.tsx" ] ) ;
// @ts-ignore
const result = app . default ( ) . $ $typeof ;
}
catch ( e )
2023-08-12 20:30:44 -04:00
{
2023-08-13 11:00:48 -04:00
console . log ( e ) ;
2023-08-13 22:58:15 -04:00
if ( confirm ( ` 🚧 Your FRONT-END app (" ${ importMap [ ">able/app.tsx" ] } ") does not export a default function that returns VDOM nodes. Replace it? ` ) )
2023-08-13 11:00:48 -04:00
{
await Install ( "app.tsx" , importMap [ ">able/app.tsx" ] ) ;
}
else
{
2023-08-13 22:58:15 -04:00
throw ( "⛔ Your FRONT-END app has incorrect export types." ) ;
2023-08-13 11:00:48 -04:00
}
2023-08-12 20:30:44 -04:00
}
2023-08-28 22:16:56 -04:00
* /
2023-08-12 20:30:44 -04:00
}
2023-08-13 11:00:48 -04:00
2023-08-12 20:30:44 -04:00
if ( ! importMap [ ">able/api.tsx" ] )
{
2023-08-13 22:58:15 -04:00
if ( confirm ( ` 🤔 OPTIONAL: Your import map does not override the default/empty BACK-END api with the specifier ">able/api.tsx". Create this file and add the specifier? ` ) )
2023-08-12 20:30:44 -04:00
{
2023-08-13 11:00:48 -04:00
importMap [ ">able/api.tsx" ] = "./api.tsx" ;
await bake ( imports ) ;
2023-08-12 20:30:44 -04:00
await Install ( "api.tsx" ) ;
}
}
2023-08-13 11:00:48 -04:00
else
2023-08-12 20:30:44 -04:00
{
2023-08-28 22:16:56 -04:00
/ *
2023-08-13 11:00:48 -04:00
try
2023-08-12 20:30:44 -04:00
{
2023-08-13 11:00:48 -04:00
const api = await import ( importMap [ ">able/api.tsx" ] ) ;
const result = api . default ( new Request ( new URL ( "https://fake-deno-testing-domain.com/" ) ) ) ;
}
catch ( e )
{
if ( confirm ( ` 🚧 Your starter backend app (" ${ importMap [ ">able/api.tsx" ] } ") does not export a default function that accepts a Request. Replace it? ` ) )
{
await Install ( "api.tsx" , importMap [ ">able/api.tsx" ] ) ;
}
else
2023-08-12 20:30:44 -04:00
{
2023-08-13 11:00:48 -04:00
throw ( "⛔ Starter backend app has incorrect export types." ) ;
2023-08-12 20:30:44 -04:00
}
2023-08-13 11:00:48 -04:00
}
2023-08-28 22:16:56 -04:00
* /
2023-08-13 11:00:48 -04:00
}
2023-08-12 20:30:44 -04:00
}
}
catch ( e )
{
console . log ( e , "\n (Able Exiting...)" ) ;
Deno . exit ( ) ;
}
console . log ( ` 🚗 Good to go! ` ) ;
2023-08-28 22:16:56 -04:00
}