feat: wmill dev v0
This commit is contained in:
88
cli/dev.ts
88
cli/dev.ts
@@ -1,13 +1,25 @@
|
||||
import getPort from "https://deno.land/x/getport@v2.1.2/mod.ts";
|
||||
import { Application, Command, Router, log, path } from "./deps.ts";
|
||||
import {
|
||||
Application,
|
||||
Command,
|
||||
Router,
|
||||
UserService,
|
||||
log,
|
||||
path,
|
||||
} from "./deps.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { ignoreF } from "./sync.ts";
|
||||
import { requireLogin, resolveWorkspace } from "./context.ts";
|
||||
import { mimelite } from "https://deno.land/x/mimetypes@v1.0.0/mod.ts";
|
||||
|
||||
async function dev(opts: GlobalOptions & { filter?: string }) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
const { default: readFileSync } = await import("./bundle.js");
|
||||
const username = (await UserService.whoami({ workspace: workspace.name }))
|
||||
.username;
|
||||
|
||||
log.info("Started dev mode");
|
||||
let currentLastEdit: LastEdit | undefined = undefined;
|
||||
|
||||
@@ -17,7 +29,7 @@ async function dev(opts: GlobalOptions & { filter?: string }) {
|
||||
const ignore = await ignoreF();
|
||||
|
||||
for await (const event of watcher) {
|
||||
log.debug(">>>> event", event);
|
||||
log.info(">>>> event", event);
|
||||
// Example event: { kind: "create", paths: [ "/home/alice/deno/foo.txt" ] }
|
||||
const paths = event.paths.filter(
|
||||
(path) =>
|
||||
@@ -26,13 +38,15 @@ async function dev(opts: GlobalOptions & { filter?: string }) {
|
||||
path.endsWith(".py") ||
|
||||
path.endsWith(".sh")
|
||||
);
|
||||
if (paths.length == 0) {
|
||||
return;
|
||||
}
|
||||
const cpath = (await Deno.realPath(paths[0])).replace(
|
||||
base + path.sep,
|
||||
""
|
||||
);
|
||||
console.log("Detected change in " + cpath);
|
||||
if (!ignore(cpath, false)) {
|
||||
console.log("FOO");
|
||||
const content = await Deno.readTextFile(cpath);
|
||||
const splitted = cpath.split(".");
|
||||
const wmPath = splitted[0];
|
||||
@@ -45,14 +59,26 @@ async function dev(opts: GlobalOptions & { filter?: string }) {
|
||||
: ext == "go"
|
||||
? "go"
|
||||
: "bash";
|
||||
currentLastEdit = { content, path: wmPath, language: lang };
|
||||
currentLastEdit = {
|
||||
content,
|
||||
path: wmPath,
|
||||
language: lang,
|
||||
workspace: workspace.workspaceId,
|
||||
username,
|
||||
};
|
||||
broadcast_changes(currentLastEdit);
|
||||
log.info("Updated " + wmPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type LastEdit = { content: string; path: string; language?: string };
|
||||
type LastEdit = {
|
||||
content: string;
|
||||
path: string;
|
||||
language: string;
|
||||
workspace: string;
|
||||
username: string;
|
||||
};
|
||||
|
||||
const connectedClients = new Set<WebSocket>();
|
||||
|
||||
@@ -84,28 +110,48 @@ async function dev(opts: GlobalOptions & { filter?: string }) {
|
||||
});
|
||||
|
||||
app.use(router.routes());
|
||||
app.use(router.allowedMethods());
|
||||
app.use(async (ctx) => {
|
||||
const req = ctx.request;
|
||||
const path = new URL(req.url).pathname;
|
||||
const url = new URL(req.url);
|
||||
let path = url.pathname;
|
||||
if (path.startsWith("/api")) {
|
||||
console.log("Proxying to " + workspace.remote + path);
|
||||
const proxyRes = await fetch(
|
||||
workspace.remote.substring(0, workspace.remote.length - 1) + path,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer " + workspace.token,
|
||||
},
|
||||
method: req.method,
|
||||
body: await req.body().value,
|
||||
}
|
||||
);
|
||||
const fpath =
|
||||
workspace.remote.substring(0, workspace.remote.length - 1) +
|
||||
path +
|
||||
"?" +
|
||||
url.searchParams.toString();
|
||||
console.log(fpath);
|
||||
console.log("Proxying to " + fpath);
|
||||
const proxyRes = await fetch(fpath, {
|
||||
headers: {
|
||||
Authorization: "Bearer " + workspace.token,
|
||||
...Object.fromEntries(req.headers.entries()),
|
||||
},
|
||||
method: req.method,
|
||||
body: JSON.stringify(await req.body().value),
|
||||
});
|
||||
ctx.response.body = proxyRes.body;
|
||||
ctx.response.status = proxyRes.status;
|
||||
ctx.response.headers = proxyRes.headers;
|
||||
} else {
|
||||
await ctx.send({
|
||||
root: `${Deno.cwd()}/`,
|
||||
index: "public/index.html",
|
||||
});
|
||||
console.log("Serving " + path);
|
||||
if (path == "/") {
|
||||
path = "dist/index.html";
|
||||
} else {
|
||||
path = "dist" + path;
|
||||
}
|
||||
try {
|
||||
ctx.response.body = readFileSync(path);
|
||||
ctx.response.headers.set(
|
||||
"Content-Type",
|
||||
mimelite.getType(path.split(".").pop() ?? "txt") ?? "text/plain"
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(`${path}: ${e}`);
|
||||
ctx.response.body = "404";
|
||||
ctx.response.status = 404;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,47 +1,18 @@
|
||||
# Svelte + TS + Vite
|
||||
# devfrontend
|
||||
|
||||
This template should help get you started developing with Svelte and TypeScript in Vite.
|
||||
The frontend for `wmill dev`
|
||||
|
||||
## Recommended IDE Setup
|
||||
## Quickstart
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
|
||||
### dev
|
||||
|
||||
## Need an official Svelte framework?
|
||||
|
||||
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
|
||||
|
||||
## Technical considerations
|
||||
|
||||
**Why use this over SvelteKit?**
|
||||
|
||||
- It brings its own routing solution which might not be preferable for some users.
|
||||
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
||||
|
||||
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
||||
|
||||
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
||||
|
||||
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
||||
|
||||
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
||||
|
||||
**Why include `.vscode/extensions.json`?**
|
||||
|
||||
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
||||
|
||||
**Why enable `allowJs` in the TS template?**
|
||||
|
||||
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
|
||||
|
||||
**Why is HMR not preserving my local component state?**
|
||||
|
||||
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
||||
|
||||
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
||||
|
||||
```ts
|
||||
// store.ts
|
||||
// An extremely simple external store
|
||||
import { writable } from 'svelte/store'
|
||||
export default writable(0)
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### deploy
|
||||
|
||||
```
|
||||
denobundle dist
|
||||
mv bundle.js ../cli/src/
|
||||
```
|
||||
|
||||
9
devfrontend/package-lock.json
generated
9
devfrontend/package-lock.json
generated
@@ -7,6 +7,9 @@
|
||||
"": {
|
||||
"name": "devfrontend",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"windmill-client": "^0.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.0.4",
|
||||
@@ -30,6 +33,7 @@
|
||||
}
|
||||
},
|
||||
"../backend/parsers/windmill-parser-wasm/pkg": {
|
||||
"name": "windmill-parser-wasm",
|
||||
"version": "1.109.1",
|
||||
"dev": true
|
||||
},
|
||||
@@ -3346,6 +3350,11 @@
|
||||
"npm": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/windmill-client": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/windmill-client/-/windmill-client-0.3.1.tgz",
|
||||
"integrity": "sha512-RpsLlp+fJion0jQ7dAPFS+e5Xk+6bVVWW66IXnSgjqkOIPZkWWPwu46IQa+rYWjrvEf0Scyuhx6sd8Lxakn8Sg=="
|
||||
},
|
||||
"node_modules/windmill-components": {
|
||||
"version": "1.109.8",
|
||||
"resolved": "https://registry.npmjs.org/windmill-components/-/windmill-components-1.109.8.tgz",
|
||||
|
||||
@@ -29,5 +29,8 @@
|
||||
"vite": "^4.3.9",
|
||||
"windmill-components": "^1.109.8",
|
||||
"windmill-parser-wasm": "file:../backend/parsers/windmill-parser-wasm/pkg"
|
||||
},
|
||||
"dependencies": {
|
||||
"windmill-client": "^0.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
import Button from 'windmill-components/components/common/button/Button.svelte'
|
||||
import { emptySchema, getModifierKey } from 'windmill-components/utils'
|
||||
import { inferArgs } from 'windmill-components/infer'
|
||||
import github from 'svelte-highlight/styles/github'
|
||||
|
||||
import type { CompletedJob, Job, Preview } from 'windmill-components/package/gen';
|
||||
import { Pane, Splitpanes} from 'svelte-splitpanes'
|
||||
import { faPlay } from '@fortawesome/free-solid-svg-icons'
|
||||
import { CompletedJob, Job, JobService } from 'windmill-client'
|
||||
import { workspaceStore } from 'windmill-components/stores';
|
||||
|
||||
let testJobLoader: TestJobLoader
|
||||
|
||||
@@ -22,11 +24,11 @@
|
||||
let testIsLoading = false
|
||||
let testJob: Job | undefined
|
||||
let pastPreviews: CompletedJob[] = []
|
||||
let lang = 'deno' as Preview.language
|
||||
let validCode = true
|
||||
|
||||
type LastEdit = { content: string; path: string; language: string, workspace: string, username: string};
|
||||
|
||||
let currentScript: {path: string, content: string} | undefined = undefined
|
||||
let currentScript: LastEdit | undefined = undefined
|
||||
|
||||
let schema = emptySchema()
|
||||
const href = window.location.href;
|
||||
@@ -54,30 +56,38 @@
|
||||
});
|
||||
|
||||
function runTest() {
|
||||
testJobLoader.runPreview(path, code, lang, args,)
|
||||
$workspaceStore = currentScript.workspace
|
||||
//@ts-ignore
|
||||
testJobLoader.runPreview(currentScript.path, currentScript.content, currentScript.language, args, undefined)
|
||||
}
|
||||
|
||||
async function loadPastTests(): Promise<void> {
|
||||
pastPreviews = await JobService.listCompletedJobs({
|
||||
workspace: $workspaceStore!,
|
||||
workspace: currentScript.workspace,
|
||||
jobKinds: 'preview',
|
||||
createdBy: $userStore?.username,
|
||||
scriptPathExact: path
|
||||
createdBy: currentScript.username,
|
||||
scriptPathExact: currentScript.path,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key == 'Enter') {
|
||||
event.preventDefault()
|
||||
runTest()
|
||||
}
|
||||
}
|
||||
|
||||
let lastPath = undefined
|
||||
async function replaceScript({path, content, language}: {path: string, content: string, language: "deno" | "python3" | "go" | "bash"}) {
|
||||
currentScript = {path, content}
|
||||
if (lastPath !== path) {
|
||||
async function replaceScript(LastEdit: LastEdit) {
|
||||
currentScript = LastEdit
|
||||
if (lastPath !== LastEdit.path) {
|
||||
schema = emptySchema()
|
||||
}
|
||||
try {
|
||||
await inferArgs(language, content, schema )
|
||||
//@ts-ignore
|
||||
await inferArgs(LastEdit.language, LastEdit.content, schema )
|
||||
schema = schema
|
||||
lastPath = path
|
||||
lastPath = LastEdit.path
|
||||
validCode = true
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
@@ -87,6 +97,8 @@
|
||||
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
<TestJobLoader
|
||||
on:done={loadPastTests}
|
||||
bind:this={testJobLoader}
|
||||
@@ -95,9 +107,13 @@
|
||||
/>
|
||||
|
||||
|
||||
<svelte:head>
|
||||
{@html github}
|
||||
</svelte:head>
|
||||
|
||||
<main class="h-screen w-full">
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="text-center w-full text-lg truncate py-1">{currentScript?.path ?? 'Not editing a script'}</div>
|
||||
<div class="text-center w-full text-lg truncate py-1">{currentScript?.path ?? 'Not editing a script'} {currentScript?.language ?? ''}</div>
|
||||
{#if !validCode}
|
||||
<div class="text-center w-full text-lg truncate py-1 text-red-500">Invalid code</div>
|
||||
{/if}
|
||||
@@ -115,6 +131,7 @@
|
||||
</Button>
|
||||
{:else}
|
||||
<Button
|
||||
disabled={currentScript === undefined}
|
||||
color="dark"
|
||||
on:click={() => {
|
||||
runTest()
|
||||
@@ -144,7 +161,7 @@
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={67}>
|
||||
<LogPanel {lang} previewJob={testJob} {pastPreviews} previewIsLoading={testIsLoading} />
|
||||
<LogPanel lang={currentScript?.language} previewJob={testJob} {pastPreviews} previewIsLoading={testIsLoading} />
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<script lang="ts">
|
||||
let count: number = 0
|
||||
const increment = () => {
|
||||
count += 1
|
||||
}
|
||||
</script>
|
||||
|
||||
<button on:click={increment}>
|
||||
count is {count}
|
||||
</button>
|
||||
@@ -1,7 +1,17 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
import { defineConfig } from "vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
})
|
||||
server: {
|
||||
port: 5173,
|
||||
proxy: {
|
||||
"^/api/.*": {
|
||||
target: process.env.REMOTE ?? "http://localhost:3000/",
|
||||
changeOrigin: true,
|
||||
cookieDomainRewrite: "localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user