From afc646c80fab5bce5298f6802aa35a3b4591c19d Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 9 Jun 2023 09:04:12 +0200 Subject: [PATCH] feat: wmill dev v0 --- cli/dev.ts | 88 +++++++++++++++++++++++------- devfrontend/README.md | 55 +++++-------------- devfrontend/package-lock.json | 9 +++ devfrontend/package.json | 3 + devfrontend/src/App.svelte | 47 +++++++++++----- devfrontend/src/lib/Counter.svelte | 10 ---- devfrontend/vite.config.ts | 16 +++++- 7 files changed, 137 insertions(+), 91 deletions(-) delete mode 100644 devfrontend/src/lib/Counter.svelte diff --git a/cli/dev.ts b/cli/dev.ts index 68d0619308..71d103593a 100644 --- a/cli/dev.ts +++ b/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(); @@ -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; + } } }); diff --git a/devfrontend/README.md b/devfrontend/README.md index e6cd94fce7..f6f71916b3 100644 --- a/devfrontend/README.md +++ b/devfrontend/README.md @@ -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/ ``` diff --git a/devfrontend/package-lock.json b/devfrontend/package-lock.json index a670e2ec55..d97d2c22f7 100644 --- a/devfrontend/package-lock.json +++ b/devfrontend/package-lock.json @@ -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", diff --git a/devfrontend/package.json b/devfrontend/package.json index e55714d137..c67ff17120 100644 --- a/devfrontend/package.json +++ b/devfrontend/package.json @@ -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" } } diff --git a/devfrontend/src/App.svelte b/devfrontend/src/App.svelte index 1197352238..f7b62d51f0 100644 --- a/devfrontend/src/App.svelte +++ b/devfrontend/src/App.svelte @@ -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 { 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 @@ + + + + {@html github} + +
-
{currentScript?.path ?? 'Not editing a script'}
+
{currentScript?.path ?? 'Not editing a script'} {currentScript?.language ?? ''}
{#if !validCode}
Invalid code
{/if} @@ -115,6 +131,7 @@ {:else}
- + diff --git a/devfrontend/src/lib/Counter.svelte b/devfrontend/src/lib/Counter.svelte deleted file mode 100644 index 979b4dfc91..0000000000 --- a/devfrontend/src/lib/Counter.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/devfrontend/vite.config.ts b/devfrontend/vite.config.ts index d70196943d..bd641fb52f 100644 --- a/devfrontend/vite.config.ts +++ b/devfrontend/vite.config.ts @@ -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", + }, + }, + }, +});