cli #6

Merged
SethTrowbridge merged 11 commits from cli into master 2023-09-24 09:33:29 -04:00
12 changed files with 48 additions and 320 deletions
Showing only changes of commit 4ef3cc47a5 - Show all commits

View File

@ -1,4 +1,3 @@
{ {
"deno.enable": true, "deno.enable": true
"deno.unstable": true
} }

View File

@ -7,6 +7,7 @@ export const RootHost = import.meta.resolve("./");
export const Root = new URL(`file://${Deno.cwd().replaceAll("\\", "/")}`).toString(); export const Root = new URL(`file://${Deno.cwd().replaceAll("\\", "/")}`).toString();
export async function HuntConfig() export async function HuntConfig()
{ {
console.log("hunting in", Root);
let path:string, resp:Response, text="", json; let path:string, resp:Response, text="", json;
try try
{ {
@ -116,16 +117,14 @@ export async function Install(file:string, overrideName?:string, handler?:(conte
export async function Check() export async function Check()
{ {
let [config, imports] = await HuntConfig(); let [config, imports] = await HuntConfig();
console.log(`Checking directory "${Root}"`);
console.log("Found", config, imports);
try try
{ {
//console.log(config, imports); //console.log(config, imports);
if(!config.path) if(!config.path)
{ {
console.log(`🛠️ No Deno configuration found. Creating "deno.jsonc" now.`); console.log(`🛠️ No Deno configuration found. Creating "deno.json" now.`);
await Deno.writeTextFile(Deno.cwd()+"/deno.jsonc", `{"imports":{}}`); await Deno.writeTextFile(Deno.cwd()+"/deno.json", `{"imports":{}}`);
Check(); Check();
return; return;
} }
@ -168,20 +167,22 @@ export async function Check()
const importMap = imports.json.imports as Record<string, string>; const importMap = imports.json.imports as Record<string, string>;
const bake =async(obj:ConfigCheck)=> await Deno.writeTextFile(Deno.cwd()+"/"+obj.path, JSON.stringify(obj.json, null, "\t")); const bake =async(obj:ConfigCheck)=> await Deno.writeTextFile(Deno.cwd()+"/"+obj.path, JSON.stringify(obj.json, null, "\t"));
if(!importMap["react"]) importMap["react"] = `https://esm.sh/preact@10.17.1/compat`;
importMap["react/"] = `https://esm.sh/preact@10.17.1/compat/`;
importMap["@preact/signals"] = `https://esm.sh/@preact/signals@1.2.1`;
importMap[">able/"] = `${RootHost}`;
if(!importMap[">able/app.tsx"])
{ {
console.log(`🛠️ Adding React import specifier ("react")`); importMap[">able/app.tsx"] = `./app.tsx`;
importMap["react"] = `https://esm.sh/preact@10.16.0/compat`; await Install("app.tsx");
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.`)
} }
if(!importMap[">able/"]) if(!importMap[">able/api.tsx"])
{ {
console.log(`🛠️ Adding Able import specifier (">able/").`); if(confirm(`🤔 OPTIONAL: Add backend ">able/api.tsx"?`))
importMap[">able/"] = `${RootHost}`; {
await bake(imports); importMap[">able/api.tsx"] = "./api.tsx";
await Install("api.tsx");
}
} }
const tasks:Record<string, string> = { const tasks:Record<string, string> = {
@ -191,21 +192,8 @@ export async function Check()
"serve": `deno run -A --no-lock ${RootHost}cli.tsx serve`, "serve": `deno run -A --no-lock ${RootHost}cli.tsx serve`,
"cloud": `deno run -A --no-lock ${RootHost}cli.tsx cloud` "cloud": `deno run -A --no-lock ${RootHost}cli.tsx cloud`
}; };
const confTasks = (config.json.tasks || {}) as Record<string, string>; const confTasks = (config.json.tasks || {}) as Record<string, string>;
for(const key in tasks) config.json.tasks = {...confTasks, ...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 = const options =
{ {
@ -213,89 +201,15 @@ export async function Check()
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "react" "jsxImportSource": "react"
} }
const compOpts = config.json.compilerOptions as Record<string, string|string[]> || {}; 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[] || []; const compLib:string[] = compOpts.lib as string[] || [];
let compLibHasAll = true; compOpts.jsx = options.jsx;
options.lib.forEach(item=> !compLib.includes(item) && (compLibHasAll = false)) compOpts.jsxImportSource = options.jsxImportSource;
compOpts.lib = [...compLib, ...options.lib];
if(!compOpts || !compJSX || !compJSXImportSource || !compLibHasAll) config.json.compilerOptions = compOpts;
{
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);
}
if(!importMap[">able/app.tsx"])
{
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?`))
{
importMap[">able/app.tsx"] = `./app.tsx`;
await bake(imports);
await Install("app.tsx");
}
}
else
{
/*
try
{
const app = await import(importMap[">able/app.tsx"]);
// @ts-ignore
const result = app.default().$$typeof;
}
catch(e)
{
console.log(e);
if(confirm(`🚧 Your FRONT-END app ("${importMap[">able/app.tsx"]}") does not export a default function that returns VDOM nodes. Replace it?`))
{
await Install("app.tsx", importMap[">able/app.tsx"]);
}
else
{
throw("⛔ Your FRONT-END app has incorrect export types.");
}
}
*/
}
if(!importMap[">able/api.tsx"])
{
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?`))
{
importMap[">able/api.tsx"] = "./api.tsx";
await bake(imports);
await Install("api.tsx");
}
}
else
{
/*
try
{
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
{
throw("⛔ Starter backend app has incorrect export types.");
}
}
*/
}
await bake(imports);
await bake(config);
} }
} }
catch(e) catch(e)

17
cli.tsx
View File

@ -68,7 +68,9 @@ if(arg._.length)
{ {
const [config, imports] = await HuntConfig(); const [config, imports] = await HuntConfig();
console.log("able subprocesses running with ", config.path);
switch(arg._[0]) switch(arg._[0])
{ {
case "check" : case "check" :
@ -80,17 +82,19 @@ if(arg._.length)
case "local" : case "local" :
{ {
await SubProcess(["run", `--config=${config.path}`, RootHost+"run.tsx", "--dev", ...Deno.args]); await SubProcess(["run", `-A`, `--no-lock`, `--config=${config.path}`, RootHost+"run.tsx", "--dev", ...Deno.args]);
break; break;
} }
case "debug" : case "debug" :
{ {
await SubProcess(["run", `--config=${config.path}`, `--inspect-brk`, RootHost+"run.tsx", "--dev", ...Deno.args]); await SubProcess(["run", `-A`, `--no-lock`, `--config=${config.path}`, `--inspect-brk`, RootHost+"run.tsx", "--dev", ...Deno.args]);
break; break;
} }
case "serve" : case "serve" :
{ {
await SubProcess(["run", `--config=${config.path}`, RootHost+"run.tsx", ...Deno.args]); const args = ["run", `-A`, `--no-lock`, `--config=${config.path}`, RootHost+"run.tsx", ...Deno.args];
console.log("args are", args);
await SubProcess(args);
break; break;
} }
case "cloud" : case "cloud" :
@ -123,5 +127,10 @@ if(arg._.length)
...scanProd, ...scanProd,
...Deno.args]); ...Deno.args]);
} }
case "upgrade" :
{
await SubProcess(["install", `-A`, `-r`, `-f`, `--no-lock`, `--config=${config.path}`, RootHost+"cli.tsx", ...Deno.args]);
break;
}
} }
} }

View File

@ -1,16 +1,18 @@
{ {
"imports": { "imports": {
"react": "https://esm.sh/preact@10.16.0/compat", "react": "https://esm.sh/preact@10.17.1/compat",
"react/": "https://esm.sh/preact@10.16.0/compat/", "react/": "https://esm.sh/preact@10.17.1/compat/",
">able/": "http://localhost:4507/", ">able/": "http://localhost:4507/",
">able/app.tsx": "./app.tsx" ">able/app.tsx": "./app.tsx",
"@preact/signals": "https://esm.sh/@preact/signals@1.2.1"
}, },
"tasks": { "tasks": {
"check": "deno run -A --no-lock http://localhost:4507/cli.tsx check", "check": "deno run -A --no-lock http://localhost:4507/cli.tsx check",
"local": "deno run -A --no-lock http://localhost:4507/cli.tsx local", "local": "deno run -A --no-lock http://localhost:4507/cli.tsx local",
"debug": "deno run -A --no-lock http://localhost:4507/cli.tsx debug", "debug": "deno run -A --no-lock http://localhost:4507/cli.tsx debug",
"serve": "deno run -A --no-lock http://localhost:4507/cli.tsx serve", "serve": "deno run -A --no-lock http://localhost:4507/cli.tsx serve",
"cloud": "deno run -A --no-lock http://localhost:4507/cli.tsx cloud" "cloud": "deno run -A --no-lock http://localhost:4507/cli.tsx cloud",
"install": "deno install -A -r -f http://localhost:4507/cli.tsx"
}, },
"compilerOptions": { "compilerOptions": {
"jsx": "react-jsx", "jsx": "react-jsx",

View File

@ -1,56 +0,0 @@
import { Router, Switch, Case } from ">able/iso-elements.tsx";
import React from "react";
const CTXString = React.createContext("lol");
type StateBinding<T> = [get:T, set:React.StateUpdater<T>];
const CTXState = React.createContext(null) as React.Context<StateBinding<number>|null>;
const Outer =(props:{children:React.JSX.Element})=>
{
const binding = React.useState(11);
return <CTXState.Provider value={binding}>
{props.children}
</CTXState.Provider>
};
const Inner =()=>
{
const [stateGet, stateSet] = React.useContext(CTXState) || ["default", ()=>{}];
return <button onClick={e=>stateSet((old)=>old+1)}>count: {stateGet} :)</button>
};
type Store = {name:string, age:number}
const reducer =(inState:Store, inAction:number)=>
{
return {...inState, age:inState.age+inAction};
}
const builder =(inState:Store):Store=>
{
inState.age = 100;
return inState;
}
export default ()=>
{
const [Store, Dispatch] = React.useReducer(reducer, {name:"seth", age:24} as Store, builder)
return <CTXString.Provider value="intradestink">
<Router.Provider>
<div class="my-4 font-sans">
<h1 class="font-black text-xl text-red-500">Title</h1>
<h2 class="font-black text-blue-500 p-4">subtitle</h2>
<p>
<button onClick={e=>Dispatch(1)}>{Store.name}|{Store.age}?</button>
</p>
</div>
<Outer>
<Inner/>
</Outer>
<Outer>
<Inner/>
</Outer>
</Router.Provider>
</CTXString.Provider>;
}

View File

@ -1,19 +0,0 @@
{
"imports":
{
"react": "https://esm.sh/preact@10.15.1/compat",
">able/": "http://localhost:4507/",
">able/app.tsx": "./app.tsx"
},
"tasks":
{
"serve": "deno run -A --no-lock --config=deno.json --reload=http://localhost:4507 http://localhost:4507/run.tsx",
"local": "deno run -A --no-lock --config=deno.json --reload=http://localhost:4507 http://localhost:4507/run.tsx --dev",
"debug": "deno run -A --no-lock --config=deno.json --reload=http://localhost:4507 --inspect-wait http://localhost:4507/run.tsx",
"cloud": "deno run -A --no-lock --config=deno.json --reload=http://localhost:4507 http://localhost:4507/run.tsx --dep"
},
"compilerOptions":
{
"lib": ["deno.window", "dom"]
}
}

View File

@ -1,56 +0,0 @@
import { Router, Switch, Case } from ">able/iso-elements.tsx";
import React from "react";
const CTXString = React.createContext("lol");
type StateBinding<T> = [get:T, set:React.StateUpdater<T>];
const CTXState = React.createContext(null) as React.Context<StateBinding<number>|null>;
const Outer =(props:{children:React.JSX.Element})=>
{
const binding = React.useState(11);
return <CTXState.Provider value={binding}>
{props.children}
</CTXState.Provider>
};
const Inner =()=>
{
const [stateGet, stateSet] = React.useContext(CTXState) || ["default", ()=>{}];
return <button onClick={e=>stateSet((old)=>old+1)}>count: {stateGet} :)</button>
};
type Store = {name:string, age:number}
const reducer =(inState:Store, inAction:number)=>
{
return {...inState, age:inState.age+inAction};
}
const builder =(inState:Store):Store=>
{
inState.age = 100;
return inState;
}
export default ()=>
{
const [Store, Dispatch] = React.useReducer(reducer, {name:"seth", age:24} as Store, builder)
return <CTXString.Provider value="intradestink">
<Router.Provider>
<div class="my-4 font-sans">
<h1 class="font-black text-xl text-red-500">Title</h1>
<h2 class="font-black text-blue-500 p-4">subtitle</h2>
<p>
<button onClick={e=>Dispatch(1)}>{Store.name}|{Store.age}?</button>
</p>
</div>
<Outer>
<Inner/>
</Outer>
<Outer>
<Inner/>
</Outer>
</Router.Provider>
</CTXString.Provider>;
}

View File

@ -1,15 +0,0 @@
{
"compilerOptions": { "lib": ["deno.window", "dom"] },
"imports":
{
"react": "https://esm.sh/preact@10.15.1/compat",
">able/": "http://localhost:4507/"
},
"tasks":
{
"local": "deno run -A --no-lock --reload=http://localhost:4507 run__.tsx --dev",
"serve": "deno run -A --no-lock --reload=http://localhost:4507 run__.tsx",
"debug": "deno run -A --no-lock --reload=http://localhost:4507 --inspect-wait run__.tsx",
"cloud": "deno run -A --no-lock --reload=http://localhost:4507 run__.tsx --dep"
}
}

View File

@ -1,9 +0,0 @@
import Configure from ">able/run.tsx";
Configure({
Start:"/app.tsx",
Serve()
{
return false;
}
})

View File

@ -1,43 +0,0 @@
{
"imports":
{
"react":"https://esm.sh/preact@10.15.1/compat", // (required) Specifier for 'react'
"react/":"https://esm.sh/preact@10.15.1/compat/", // (conditional) This allows the use of JSX without explicitly importing React into a module. If you choose to remove this (and make importing react required), also remove "jsx" and "jsxImportSource" from "compilerOptions" (below)
">able/": "{{server}}", // (required) Specifier 'able'. (See note below about "isomorphic proxies")
{{commentApp}}">able/app.tsx": "{{app}}",
{{commentApi}}">able/api.tsx": "{{api}}"
},
"tasks":
{
"local": "deno run -A --reload=http://localhost:4507 --no-lock ./run-local.tsx --port=1234",
"serve": "deno run -A --reload=http://localhost:4507 --no-lock ./run-serve.tsx --port=1234",
"cloud": "deno run -A --reload=http://localhost:4507 --no-lock ./run-deploy.tsx",
"debug": "deno run -A --reload=http://localhost:4507 --no-lock --inspect-wait ./run-serve.tsx --port=1234"
},
"compilerOptions":
{
"lib": ["deno.window", "dom"], // makes the Deno Language Server OK with browser-specific code
"jsx": "react-jsx", // see "react/" import above
"jsxImportSource": "react" // ^
}
/*
Imports prefixed with ">" are "isomorphic proxies."
In addition to functioning normally as bare module specifiers for Deno, **these imports are added as routes when the server starts**.
Assuming the specifier points to remotely a hosted directory containing typescript files, requests to your running Able server on these proxy routes are actually fetched from the remote, then transpiled (and cached), then send back as a response.
For example, after the Able server starts, if it sees a web request to '/>able/iso-elements.tsx' it would actually return a browser-friendly transpiled copy of what was on the remote.
Conversely, if the Deno Language Server were to see: `import * as Iso from ">able/iso-elements.tsx";` in one of your modules,
that will be resolved normally with the import map and Deno will just receive the tsx file as-is from the remote, typings and all, so intellisense will work in your IDE.
While ">able/" is a required "import proxy" to pull in Able source code, you are free to use this convention to also add your own proxies as you see fit.
E.g. adding this record to imports:
">your-import/": "https://raw.githubusercontent.com/your-name/your-lib/master/"
will give both Deno and browsers running your Able project everything they need
import CoolComponent from ">your-import/cc.tsx";
...
*/
}

View File

@ -16,6 +16,10 @@ Configure({
{ {
syntax: "typescript", syntax: "typescript",
tsx: true, tsx: true,
},
transform:
{
react: { runtime: "automatic" }
} }
} }
}, },

View File

@ -5,7 +5,6 @@ import CustomServe from ">able/api.tsx";
export const Root = new URL(`file://${Deno.cwd().replaceAll("\\", "/")}`).toString(); export const Root = new URL(`file://${Deno.cwd().replaceAll("\\", "/")}`).toString();
type DenoConfig = {imports:Record<string, string>}; type DenoConfig = {imports:Record<string, string>};
const ImportMap:DenoConfig = {imports:{}}; const ImportMap:DenoConfig = {imports:{}};
let ImportMapProxies:Record<string, string> = {}; let ImportMapProxies:Record<string, string> = {};
@ -248,9 +247,9 @@ export default async()=>
if(running){return}; if(running){return};
running = true; running = true;
await ImportMapReload();
try try
{ {
await ImportMapReload();
await SWCW.default(); await SWCW.default();
} }
catch(e) catch(e)
@ -258,7 +257,6 @@ export default async()=>
console.log("swc init error:", e); console.log("swc init error:", e);
} }
const server = Deno.serve({port:parseInt(Deno.env.get("port")||"8000")}, async(req: Request)=> const server = Deno.serve({port:parseInt(Deno.env.get("port")||"8000")}, async(req: Request)=>
{ {
const url:URL = new URL(req.url); const url:URL = new URL(req.url);