feat: make CLI node compatible (#4347)
This commit is contained in:
1
cli/.gitignore
vendored
Normal file
1
cli/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
npm/
|
||||
1
cli/build.sh
Executable file
1
cli/build.sh
Executable file
@@ -0,0 +1 @@
|
||||
deno run -A dnt.ts
|
||||
@@ -44,13 +44,13 @@ export async function readConfigFile(): Promise<SyncOptions> {
|
||||
await Deno.readTextFile("wmill.yaml")
|
||||
) as SyncOptions;
|
||||
if (conf?.defaultTs == undefined) {
|
||||
log.warning(
|
||||
log.warn(
|
||||
"No defaultTs defined in your wmill.yaml. Using 'bun' as default."
|
||||
);
|
||||
}
|
||||
return typeof conf == "object" ? conf : ({} as SyncOptions);
|
||||
} catch (e) {
|
||||
log.warning(
|
||||
log.warn(
|
||||
"No wmill.yaml found. Use 'wmill init' to bootstrap it. Using 'bun' as default typescript runtime."
|
||||
);
|
||||
return {};
|
||||
|
||||
17
cli/deno.json
Normal file
17
cli/deno.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"imports": {
|
||||
"@cliffy/ansi": "jsr:@windmill-labs/cliffy-ansi@^1.0.0-rc.5",
|
||||
"@cliffy/command": "jsr:@windmill-labs/cliffy-command@^1.0.0-rc.5",
|
||||
"@cliffy/prompt": "jsr:@windmill-labs/cliffy-prompt@^1.0.0-rc.5",
|
||||
"@cliffy/table": "jsr:@windmill-labs/cliffy-table@^1.0.0-rc.5",
|
||||
"@deno/dnt": "jsr:@deno/dnt@^0.41.3",
|
||||
"@std/encoding": "jsr:@std/encoding@^1.0.4",
|
||||
"@std/fs": "jsr:@std/fs@^1.0.3",
|
||||
"@std/io": "jsr:@std/io@^0.224.7",
|
||||
"@std/log": "jsr:@std/log@^0.224.7",
|
||||
"@std/net": "jsr:@std/net@^1.0.2",
|
||||
"@std/path": "jsr:@std/path@^1.0.4",
|
||||
"@std/streams": "jsr:@std/streams@^1.0.4",
|
||||
"@std/yaml": "jsr:@std/yaml@^1.0.5"
|
||||
}
|
||||
}
|
||||
1014
cli/deno.lock
generated
Normal file
1014
cli/deno.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
69
cli/deps.ts
69
cli/deps.ts
@@ -2,51 +2,40 @@
|
||||
export * from "npm:windmill-client@1.364.0";
|
||||
|
||||
// cliffy
|
||||
export { Command } from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts";
|
||||
export { Table } from "https://deno.land/x/cliffy@v1.0.0-rc.4/table/table.ts";
|
||||
export { colors } from "https://deno.land/x/cliffy@v1.0.0-rc.4/ansi/colors.ts";
|
||||
export { Secret } from "https://deno.land/x/cliffy@v1.0.0-rc.4/prompt/secret.ts";
|
||||
export { Select } from "https://deno.land/x/cliffy@v1.0.0-rc.4/prompt/select.ts";
|
||||
export { Confirm } from "https://deno.land/x/cliffy@v1.0.0-rc.4/prompt/confirm.ts";
|
||||
export { Input } from "https://deno.land/x/cliffy@v1.0.0-rc.4/prompt/input.ts";
|
||||
export {
|
||||
DenoLandProvider,
|
||||
UpgradeCommand,
|
||||
} from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/upgrade/mod.ts";
|
||||
export { CompletionsCommand } from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/completions/mod.ts";
|
||||
export { Command } from "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5";
|
||||
export { Table } from "jsr:@windmill-labs/cliffy-table@1.0.0-rc.5";
|
||||
export { colors } from "jsr:@windmill-labs/cliffy-ansi@1.0.0-rc.5/colors";
|
||||
export { Secret } from "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.5/secret";
|
||||
export { Select } from "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.5/select";
|
||||
export { Confirm } from "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.5/confirm";
|
||||
export { Input } from "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.5/input";
|
||||
export { UpgradeCommand } from "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5/upgrade";
|
||||
export { NpmProvider } from "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5/upgrade/provider/npm";
|
||||
export { Provider } from "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5/upgrade";
|
||||
|
||||
export { CompletionsCommand } from "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5/completions";
|
||||
// std
|
||||
export * as path from "https://deno.land/std@0.207.0/path/mod.ts";
|
||||
export { ensureDir } from "https://deno.land/std@0.207.0/fs/ensure_dir.ts";
|
||||
export {
|
||||
copy,
|
||||
readAll,
|
||||
readerFromStreamReader,
|
||||
} from "https://deno.land/std@0.207.0/streams/mod.ts";
|
||||
export { SEP } from "https://deno.land/std@0.207.0/path/separator.ts";
|
||||
export { DelimiterStream } from "https://deno.land/std@0.207.0/streams/mod.ts";
|
||||
export { iterateReader } from "https://deno.land/std@0.207.0/streams/iterate_reader.ts";
|
||||
export { writeAllSync } from "https://deno.land/std@0.207.0/streams/mod.ts";
|
||||
export { encodeHex } from "https://deno.land/std@0.207.0/encoding/hex.ts";
|
||||
export * as log from "https://deno.land/std@0.207.0/log/mod.ts";
|
||||
export {
|
||||
stringify as yamlStringify,
|
||||
parse as yamlParse,
|
||||
} from "https://deno.land/std@0.207.0/yaml/mod.ts";
|
||||
export { ensureDir } from "jsr:@std/fs";
|
||||
export { SEPARATOR as SEP } from "jsr:@std/path";
|
||||
export * as path from "jsr:@std/path/";
|
||||
export { encodeHex } from "jsr:@std/encoding";
|
||||
export { getAvailablePort } from "jsr:@std/net";
|
||||
export { writeAllSync } from "jsr:@std/io/write-all";
|
||||
export { copy } from "jsr:@std/io/copy";
|
||||
export { readAll } from "jsr:@std/io/read-all";
|
||||
|
||||
export * as log from "jsr:@std/log";
|
||||
export { stringify as yamlStringify, parse as yamlParse } from "jsr:@std/yaml";
|
||||
|
||||
// other
|
||||
export { Application, Router } from "https://deno.land/x/oak@v12.5.0/mod.ts";
|
||||
|
||||
export { getPort } from "https://deno.land/x/getport@v2.1.2/mod.ts";
|
||||
export { getAvailablePort } from "https://deno.land/x/port@1.0.0/mod.ts";
|
||||
export { default as dir } from "https://deno.land/x/dir@1.5.1/mod.ts";
|
||||
export { passwordGenerator } from "https://deno.land/x/password_generator@latest/mod.ts"; // TODO: I think the version is called latest, but it's still pinned.
|
||||
export { nanoid } from "https://deno.land/x/nanoid@v3.0.0/mod.ts";
|
||||
export * as cbor from "https://deno.land/x/cbor@v1.4.1/index.js";
|
||||
export { default as Murmurhash3 } from "https://deno.land/x/murmurhash@v1.0.0/mod.ts";
|
||||
export { default as microdiff } from "https://deno.land/x/microdiff@v1.3.1/index.ts";
|
||||
export { default as objectHash } from "https://deno.land/x/object_hash@2.0.3.1/mod.ts";
|
||||
export { minimatch } from "npm:minimatch";
|
||||
export { default as JSZip } from "npm:jszip@3.7.1";
|
||||
|
||||
export { open } from "https://deno.land/x/open@v0.0.5/index.ts";
|
||||
export * as express from "npm:express";
|
||||
export * as http from "node:http";
|
||||
export { WebSocketServer, WebSocket } from "npm:ws";
|
||||
|
||||
export * as open from "npm:open";
|
||||
export { default as gitignore_parser } from "npm:gitignore-parser";
|
||||
export * as esMain from "npm:es-main";
|
||||
|
||||
92
cli/dev.ts
92
cli/dev.ts
@@ -1,5 +1,14 @@
|
||||
import getPort from "https://deno.land/x/getport@v2.1.2/mod.ts";
|
||||
import { Application, Command, Router, log, open, path } from "./deps.ts";
|
||||
import {
|
||||
Command,
|
||||
SEP,
|
||||
WebSocketServer,
|
||||
express,
|
||||
getAvailablePort,
|
||||
http,
|
||||
log,
|
||||
open,
|
||||
WebSocket,
|
||||
} from "./deps.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { ignoreF } from "./sync.ts";
|
||||
import { requireLogin, resolveWorkspace } from "./context.ts";
|
||||
@@ -40,7 +49,7 @@ async function dev(opts: GlobalOptions & SyncOptions) {
|
||||
if (paths.length == 0) {
|
||||
return;
|
||||
}
|
||||
const cpath = (await Deno.realPath(paths[0])).replace(base + path.sep, "");
|
||||
const cpath = (await Deno.realPath(paths[0])).replace(base + SEP, "");
|
||||
console.log("Detected change in " + cpath);
|
||||
if (!ignore(cpath, false)) {
|
||||
const content = await Deno.readTextFile(cpath);
|
||||
@@ -52,7 +61,7 @@ async function dev(opts: GlobalOptions & SyncOptions) {
|
||||
path: wmPath,
|
||||
language: lang,
|
||||
};
|
||||
broadcast_changes(currentLastEdit);
|
||||
broadcastChanges(currentLastEdit);
|
||||
log.info("Updated " + wmPath);
|
||||
}
|
||||
}
|
||||
@@ -62,67 +71,74 @@ async function dev(opts: GlobalOptions & SyncOptions) {
|
||||
language: string;
|
||||
};
|
||||
|
||||
const connectedClients = new Set<WebSocket>();
|
||||
const connectedClients: Set<WebSocket> = new Set();
|
||||
|
||||
const app = new Application();
|
||||
const router: Router = new Router();
|
||||
|
||||
// send a message to all connected clients
|
||||
function broadcast_changes(lastEdit: LastEdit) {
|
||||
// Function to send a message to all connected clients
|
||||
function broadcastChanges(lastEdit: LastEdit) {
|
||||
for (const client of connectedClients.values()) {
|
||||
client.send(JSON.stringify(lastEdit));
|
||||
}
|
||||
}
|
||||
|
||||
async function startApp() {
|
||||
router.get("/ws", async (ctx) => {
|
||||
const socket = await ctx.upgrade();
|
||||
connectedClients.add(socket);
|
||||
log.info(`New client connected`);
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const wss = new WebSocketServer({ server });
|
||||
|
||||
socket.onopen = () => {
|
||||
// WebSocket server event listeners
|
||||
wss.on("connection", (ws: WebSocket) => {
|
||||
connectedClients.add(ws);
|
||||
console.log("New client connected");
|
||||
|
||||
ws.on("open", () => {
|
||||
if (currentLastEdit) {
|
||||
broadcast_changes(currentLastEdit);
|
||||
broadcastChanges(currentLastEdit);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
socket.onclose = () => {
|
||||
connectedClients.delete(socket);
|
||||
};
|
||||
ws.on("close", () => {
|
||||
connectedClients.delete(ws);
|
||||
console.log("Client disconnected");
|
||||
});
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
let data: any | undefined = undefined;
|
||||
ws.on("message", (message: WebSocket.RawData) => {
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(event.data);
|
||||
} catch {
|
||||
console.log("Received invalid JSON: " + event.data);
|
||||
data = JSON.parse(message);
|
||||
} catch (e) {
|
||||
console.log("Received invalid JSON: " + message + " " + e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type == "load") {
|
||||
loadPaths([data.path] as string[]);
|
||||
if (data.type === "load") {
|
||||
loadPaths([data.path]);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
app.use(router.routes());
|
||||
app.use(router.allowedMethods());
|
||||
|
||||
const port = getPort(PORT);
|
||||
// Start the server
|
||||
const port = await getAvailablePort({ preferredPort: 3001 });
|
||||
const url =
|
||||
`${workspace.remote}scripts/dev?workspace=${workspace.workspaceId}&local=true` +
|
||||
(port == PORT ? "" : "&port=" + port);
|
||||
(port === PORT ? "" : `&port=${port}`);
|
||||
|
||||
console.log(`Go to ${url}`);
|
||||
try {
|
||||
await open(url);
|
||||
log.info("Opened browser for you");
|
||||
} catch {
|
||||
console.error(`Failed to open browser, please navigate to ${url}`);
|
||||
open.openApp(open.apps.browser, { arguments: [url] });
|
||||
console.log("Opened browser for you");
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to open browser, please navigate to ${url}, ${error}`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Dev server will automatically point to the last script edited locally"
|
||||
);
|
||||
await app.listen({ port });
|
||||
|
||||
server.listen(port, () => {
|
||||
console.log(`Server listening on port ${port}`);
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all([startApp(), watchChanges()]);
|
||||
|
||||
47
cli/dnt.ts
Normal file
47
cli/dnt.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
// ex. scripts/build_npm.ts
|
||||
import { build, emptyDir } from "jsr:@deno/dnt@0.41.3";
|
||||
import { VERSION } from "./main.ts";
|
||||
await emptyDir("./npm");
|
||||
|
||||
await build({
|
||||
entryPoints: [
|
||||
{
|
||||
kind: "bin",
|
||||
name: "wmill", // command name
|
||||
path: "./main.ts",
|
||||
},
|
||||
],
|
||||
outDir: "./npm",
|
||||
shims: {
|
||||
// see JS docs for overview and more options
|
||||
deno: true,
|
||||
},
|
||||
typeCheck: false,
|
||||
scriptModule: false,
|
||||
package: {
|
||||
// package.json properties
|
||||
name: "windmill-cli",
|
||||
version: VERSION,
|
||||
description: "CLI for Windmill",
|
||||
license: "Apache 2.0",
|
||||
repository: {
|
||||
type: "git",
|
||||
url: "git+https://github.com/windmill-labs/windmill.git",
|
||||
},
|
||||
bugs: {
|
||||
url: "https://github.com/windmill-labs/windmill/issues",
|
||||
},
|
||||
},
|
||||
|
||||
postBuild() {
|
||||
// steps to run after building and before running the tests
|
||||
// add shebang to npm/esm/main.js
|
||||
|
||||
Deno.copyFileSync("../LICENSE", "npm/LICENSE");
|
||||
Deno.copyFileSync("README.md", "npm/README.md");
|
||||
Deno.copyFileSync(
|
||||
"wasm/windmill_parser_wasm_bg.wasm",
|
||||
"npm/esm/wasm/windmill_parser_wasm_bg.wasm"
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
Command,
|
||||
setClient,
|
||||
} from "./deps.ts";
|
||||
import { DelimiterStream, Input, colors, log } from "./deps.ts";
|
||||
import { Input, colors, log } from "./deps.ts";
|
||||
import { loginInteractive } from "./login.ts";
|
||||
import { getRootStore } from "./store.ts";
|
||||
import { push, pull } from "./sync.ts";
|
||||
@@ -43,46 +43,20 @@ export interface Instance {
|
||||
prefix: string;
|
||||
}
|
||||
|
||||
function makeInstanceStream(
|
||||
readable: ReadableStream<Uint8Array>
|
||||
): ReadableStream<Instance> {
|
||||
return readable
|
||||
.pipeThrough(new DelimiterStream(new TextEncoder().encode("\n")))
|
||||
.pipeThrough(new TextDecoderStream())
|
||||
.pipeThrough(
|
||||
new TransformStream({
|
||||
transform(line, controller) {
|
||||
try {
|
||||
if (line.length <= 2) {
|
||||
return;
|
||||
}
|
||||
const instance = JSON.parse(line) as Instance;
|
||||
controller.enqueue(instance);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async function getInstanceStream() {
|
||||
const file = await Deno.open((await getRootStore()) + "instances.ndjson", {
|
||||
write: false,
|
||||
read: true,
|
||||
});
|
||||
return makeInstanceStream(file.readable);
|
||||
}
|
||||
|
||||
export async function allInstances(): Promise<Instance[]> {
|
||||
try {
|
||||
const instanceStream = await getInstanceStream();
|
||||
const instances: Instance[] = [];
|
||||
for await (const instance of instanceStream) {
|
||||
instances.push(instance);
|
||||
}
|
||||
|
||||
return instances;
|
||||
const file = (await getRootStore()) + "instances.ndjson";
|
||||
const txt = await Deno.readTextFile(file);
|
||||
return txt
|
||||
.split("\n")
|
||||
.map((line) => {
|
||||
if (line.length <= 2) {
|
||||
return;
|
||||
}
|
||||
const instance = JSON.parse(line) as Instance;
|
||||
return instance;
|
||||
})
|
||||
.filter(Boolean) as Instance[];
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
|
||||
86
cli/login.ts
86
cli/login.ts
@@ -1,5 +1,6 @@
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { colors, getAvailablePort, log, open, Secret, Select } from "./deps.ts";
|
||||
import * as http from "node:http";
|
||||
|
||||
export async function loginInteractive(remote: string) {
|
||||
let token: string | undefined;
|
||||
@@ -51,28 +52,67 @@ export async function browserLogin(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const server = Deno.listen({ transport: "tcp", port });
|
||||
const url = `${baseUrl}user/cli?port=${port}`;
|
||||
log.info(`Login by going to ${url}`);
|
||||
try {
|
||||
await open(url);
|
||||
log.info("Opened browser for you");
|
||||
} catch {
|
||||
console.error(`Failed to open browser, please navigate to ${url}`);
|
||||
}
|
||||
const firstConnection = await server.accept();
|
||||
const httpFirstConnection = Deno.serveHttp(firstConnection);
|
||||
const firstRequest = (await httpFirstConnection.nextRequest())!;
|
||||
const params = new URL(firstRequest.request.url!).searchParams;
|
||||
const token = params.get("token");
|
||||
// const _workspace = params.get("workspace");
|
||||
await firstRequest?.respondWith(
|
||||
Response.redirect(baseUrl + "user/cli-success", 302)
|
||||
);
|
||||
// const server = Deno.listen({ transport: "tcp", port });
|
||||
// const url = `${baseUrl}user/cli?port=${port}`;
|
||||
// log.info(`Login by going to ${url}`);
|
||||
// try {
|
||||
// await open.openApp(open.apps.browser, { arguments: [url] });
|
||||
|
||||
setTimeout(() => {
|
||||
httpFirstConnection.close();
|
||||
server.close();
|
||||
}, 10);
|
||||
return token ?? undefined;
|
||||
// log.info("Opened browser for you");
|
||||
// } catch {
|
||||
// console.error(`Failed to open browser, please navigate to ${url}`);
|
||||
// }
|
||||
// const firstConnection = await server.accept();
|
||||
// const httpFirstConnection = Deno.serveHttp(firstConnection);
|
||||
// const firstRequest = (await httpFirstConnection.nextRequest())!;
|
||||
// const params = new URL(firstRequest.request.url!).searchParams;
|
||||
// const token = params.get("token");
|
||||
// // const _workspace = params.get("workspace");
|
||||
// await firstRequest?.respondWith(
|
||||
// Response.redirect(baseUrl + "user/cli-success", 302)
|
||||
// );
|
||||
|
||||
// setTimeout(() => {
|
||||
// httpFirstConnection.close();
|
||||
// server.close();
|
||||
// }, 10);
|
||||
// return token ?? undefined;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const server = http.createServer((req, res) => {
|
||||
const params = new URL(req.url!, `http://${req.headers.host}`)
|
||||
.searchParams;
|
||||
const token = params.get("token");
|
||||
|
||||
// Redirect the user to the success page
|
||||
res.writeHead(302, { Location: `${baseUrl}user/cli-success` });
|
||||
res.end();
|
||||
|
||||
// Close the server after a short delay
|
||||
setTimeout(() => {
|
||||
server.close();
|
||||
}, 10);
|
||||
|
||||
// Resolve the promise with the token
|
||||
resolve(token ?? undefined);
|
||||
});
|
||||
|
||||
const url = `${baseUrl}user/cli?port=${port}`;
|
||||
log.info(`Login by going to ${url}`);
|
||||
|
||||
try {
|
||||
open.openApp(open.apps.browser, { arguments: [url] });
|
||||
|
||||
log.info("Opened browser for you");
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to open browser, please navigate to ${url}, error: ${error}`
|
||||
);
|
||||
}
|
||||
|
||||
// Start the server
|
||||
server.listen(port, () => {
|
||||
console.log(`Listening on port ${port}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
117
cli/main.ts
117
cli/main.ts
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
Command,
|
||||
CompletionsCommand,
|
||||
DenoLandProvider,
|
||||
UpgradeCommand,
|
||||
colors,
|
||||
esMain,
|
||||
log,
|
||||
yamlStringify,
|
||||
} from "./deps.ts";
|
||||
@@ -22,19 +22,21 @@ import schedule from "./schedule.ts";
|
||||
import sync from "./sync.ts";
|
||||
import instance from "./instance.ts";
|
||||
import dev from "./dev.ts";
|
||||
import { fetchVersion, tryResolveVersion } from "./context.ts";
|
||||
import { fetchVersion } from "./context.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { OpenAPI } from "./deps.ts";
|
||||
import { getHeaders } from "./utils.ts";
|
||||
import { NpmProvider } from "./upgrade.ts";
|
||||
|
||||
addEventListener("error", (event) => {
|
||||
if (event.error) {
|
||||
console.error("Error details of: " + event.error.message);
|
||||
console.error(JSON.stringify(event.error, null, 4));
|
||||
}
|
||||
});
|
||||
// addEventListener("error", (event) => {
|
||||
// if (event.error) {
|
||||
// console.error("Error details of: " + event.error.message);
|
||||
// console.error(JSON.stringify(event.error, null, 4));
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.392.0";
|
||||
|
||||
export const VERSION = "v1.392.0";
|
||||
|
||||
let command: any = new Command()
|
||||
.name("wmill")
|
||||
@@ -108,15 +110,7 @@ let command: any = new Command()
|
||||
.command(
|
||||
"upgrade",
|
||||
new UpgradeCommand({
|
||||
main: "main.ts",
|
||||
args: [
|
||||
"--allow-net",
|
||||
"--allow-read",
|
||||
"--allow-write",
|
||||
"--allow-env",
|
||||
"-q",
|
||||
],
|
||||
provider: new DenoLandProvider({ name: "wmill" }),
|
||||
provider: new NpmProvider({ package: "windmill-cli" }),
|
||||
})
|
||||
)
|
||||
.command("completions", new CompletionsCommand());
|
||||
@@ -125,42 +119,65 @@ if (Number.parseInt(VERSION.replace("v", "").replace(".", "")) > 1700) {
|
||||
}
|
||||
|
||||
export let showDiffs = false;
|
||||
try {
|
||||
if (Deno.args.length === 0) {
|
||||
command.showHelp();
|
||||
}
|
||||
const LOG_LEVEL =
|
||||
Deno.args.includes("--verbose") || Deno.args.includes("--debug")
|
||||
? "DEBUG"
|
||||
: "INFO";
|
||||
// const NO_COLORS = Deno.args.includes("--no-colors");
|
||||
showDiffs = Deno.args.includes("--show-diffs");
|
||||
async function main() {
|
||||
try {
|
||||
if (Deno.args.length === 0) {
|
||||
command.showHelp();
|
||||
}
|
||||
const LOG_LEVEL =
|
||||
Deno.args.includes("--verbose") || Deno.args.includes("--debug")
|
||||
? "DEBUG"
|
||||
: "INFO";
|
||||
// const NO_COLORS = Deno.args.includes("--no-colors");
|
||||
showDiffs = Deno.args.includes("--show-diffs");
|
||||
|
||||
log.setup({
|
||||
handlers: {
|
||||
console: new log.handlers.ConsoleHandler(LOG_LEVEL, {
|
||||
formatter: "{msg}",
|
||||
}),
|
||||
},
|
||||
loggers: {
|
||||
default: {
|
||||
level: LOG_LEVEL,
|
||||
handlers: ["console"],
|
||||
log.setup({
|
||||
handlers: {
|
||||
console: new log.ConsoleHandler(LOG_LEVEL, {
|
||||
formatter: ({ msg }) => `${msg}`,
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
log.debug("Debug logging enabled. CLI build against " + VERSION);
|
||||
loggers: {
|
||||
default: {
|
||||
level: LOG_LEVEL,
|
||||
handlers: ["console"],
|
||||
},
|
||||
},
|
||||
});
|
||||
log.debug("Debug logging enabled. CLI build against " + VERSION);
|
||||
|
||||
const extraHeaders = getHeaders();
|
||||
if (extraHeaders) {
|
||||
OpenAPI.HEADERS = extraHeaders;
|
||||
const extraHeaders = getHeaders();
|
||||
if (extraHeaders) {
|
||||
OpenAPI.HEADERS = extraHeaders;
|
||||
}
|
||||
await command.parse(Deno.args);
|
||||
} catch (e) {
|
||||
if (e.name === "ApiError") {
|
||||
console.log("Server failed. " + e.statusText + ": " + e.body);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
await command.parse(Deno.args);
|
||||
} catch (e) {
|
||||
if (e.name === "ApiError") {
|
||||
console.log("Server failed. " + e.statusText + ": " + e.body);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
if (esMain.default(import.meta)) {
|
||||
main();
|
||||
// test1();
|
||||
// test2();
|
||||
// module was not imported but called directly
|
||||
}
|
||||
|
||||
// function test1() {
|
||||
// // dnt-shim-ignore deno-lint-ignore no-explicit-any
|
||||
// const { Deno, process } = globalThis as any;
|
||||
|
||||
// console.log(Deno);
|
||||
// }
|
||||
|
||||
// function test2() {
|
||||
// const { Deno, process } = globalThis as any;
|
||||
|
||||
// console.log(Deno);
|
||||
// }
|
||||
|
||||
export default command;
|
||||
|
||||
@@ -59,13 +59,10 @@ export async function downloadZip(
|
||||
log.debug(`Downloaded zip/tarball successfully`);
|
||||
}
|
||||
const blob = await zipResponse.blob();
|
||||
return await JSZip.loadAsync(blob as any);
|
||||
return await JSZip.loadAsync((await blob.arrayBuffer()) as any);
|
||||
}
|
||||
|
||||
async function stub(
|
||||
_opts: GlobalOptions & { override: boolean },
|
||||
_dir: string
|
||||
) {
|
||||
function stub(_opts: GlobalOptions & { override: boolean }, _dir: string) {
|
||||
console.log(
|
||||
colors.red.underline(
|
||||
'Pull is deprecated. Use "sync pull --raw" instead. See <TODO_LINK_HERE> for more information.'
|
||||
|
||||
54
cli/store.ts
54
cli/store.ts
@@ -1,4 +1,4 @@
|
||||
import { dir, ensureDir } from "./deps.ts";
|
||||
import { ensureDir } from "./deps.ts";
|
||||
|
||||
function hash_string(str: string): number {
|
||||
let hash = 0,
|
||||
@@ -14,7 +14,7 @@ function hash_string(str: string): number {
|
||||
}
|
||||
|
||||
export async function getRootStore(): Promise<string> {
|
||||
const store = (dir("config") ?? dir("tmp") ?? "/tmp/") + "/windmill/";
|
||||
const store = (config_dir() ?? tmp_dir() ?? "/tmp/") + "/windmill/";
|
||||
await ensureDir(store);
|
||||
return store;
|
||||
}
|
||||
@@ -25,3 +25,53 @@ export async function getStore(baseUrl: string): Promise<string> {
|
||||
await ensureDir(baseStore);
|
||||
return baseStore;
|
||||
}
|
||||
|
||||
//inlined import dir from "https://deno.land/x/dir/mod.ts";
|
||||
function tmp_dir(): string | null {
|
||||
switch (Deno.build.os) {
|
||||
case "linux": {
|
||||
const xdg = Deno.env.get("XDG_RUNTIME_DIR");
|
||||
if (xdg) return `${xdg}-/tmp`;
|
||||
|
||||
const tmpDir = Deno.env.get("TMPDIR");
|
||||
if (tmpDir) return tmpDir;
|
||||
|
||||
const tempDir = Deno.env.get("TEMP");
|
||||
if (tempDir) return tempDir;
|
||||
|
||||
const tmp = Deno.env.get("TMP");
|
||||
if (tmp) return tmp;
|
||||
|
||||
return "/var/tmp";
|
||||
}
|
||||
case "darwin":
|
||||
return Deno.env.get("TMPDIR") ?? null;
|
||||
case "windows":
|
||||
return Deno.env.get("TMP") ?? Deno.env.get("TEMP") ?? null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function config_dir(): string | null {
|
||||
switch (Deno.build.os) {
|
||||
case "linux": {
|
||||
const xdg = Deno.env.get("XDG_CONFIG_HOME");
|
||||
if (xdg) return xdg;
|
||||
|
||||
const home = Deno.env.get("HOME");
|
||||
if (home) return `${home}/.config`;
|
||||
break;
|
||||
}
|
||||
|
||||
case "darwin": {
|
||||
const home = Deno.env.get("HOME");
|
||||
if (home) return `${home}/Library/Preferences`;
|
||||
break;
|
||||
}
|
||||
|
||||
case "windows":
|
||||
return Deno.env.get("APPDATA") ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
19
cli/sync.ts
19
cli/sync.ts
@@ -158,7 +158,7 @@ export async function FSFSElement(
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
log.warning(`Error reading dir: ${localP}, ${e}`);
|
||||
log.warn(`Error reading dir: ${localP}, ${e}`);
|
||||
}
|
||||
},
|
||||
// async getContentBytes(): Promise<Uint8Array> {
|
||||
@@ -621,7 +621,7 @@ export async function elementsToMap(
|
||||
continue;
|
||||
}
|
||||
} catch (e) {
|
||||
log.warning(`Error reading variable ${path} to check for secrets`);
|
||||
log.warn(`Error reading variable ${path} to check for secrets`);
|
||||
}
|
||||
}
|
||||
map[entry.path] = content;
|
||||
@@ -920,6 +920,7 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
|
||||
const conflicts = [];
|
||||
const changedScripts: string[] = [];
|
||||
const changedFlows: string[] = [];
|
||||
const changedApps: string[] = [];
|
||||
|
||||
// deno-lint-ignore no-inner-declarations
|
||||
async function addToChangedIfNotExists(p: string) {
|
||||
@@ -931,6 +932,11 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
|
||||
if (!changedFlows.includes(folder)) {
|
||||
changedFlows.push(folder);
|
||||
}
|
||||
} else if (p.includes(".app" + SEP)) {
|
||||
const folder = p.substring(0, p.indexOf(".app" + SEP)) + ".app" + SEP;
|
||||
if (!changedApps.includes(folder)) {
|
||||
changedApps.push(folder);
|
||||
}
|
||||
} else {
|
||||
if (!changedScripts.includes(p)) {
|
||||
changedScripts.push(p);
|
||||
@@ -1072,6 +1078,13 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
|
||||
log.info(`Updating lock for flow ${change}`);
|
||||
await generateFlowLockInternal(change, false, workspace, true);
|
||||
}
|
||||
if (changedApps.length > 0) {
|
||||
log.info(
|
||||
`Apps ${changedApps.join(
|
||||
", "
|
||||
)} scripts were changed but ignoring for now`
|
||||
);
|
||||
}
|
||||
log.info(
|
||||
colors.bold.green.underline(
|
||||
`\nDone! All ${changes.length} changes applied locally and wmill-lock.yaml updated.`
|
||||
@@ -1327,7 +1340,7 @@ export async function push(opts: GlobalOptions & SyncOptions) {
|
||||
case "folder":
|
||||
await FolderService.deleteFolder({
|
||||
workspace: workspaceId,
|
||||
name: change.path.split(path.sep)[1],
|
||||
name: change.path.split(SEP)[1],
|
||||
});
|
||||
break;
|
||||
case "resource":
|
||||
|
||||
65
cli/upgrade.ts
Normal file
65
cli/upgrade.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Provider } from "./deps.ts";
|
||||
|
||||
export type NpmProviderOptions = { main?: string; logger?: any } & (
|
||||
| {
|
||||
package: string;
|
||||
}
|
||||
| {
|
||||
scope: string;
|
||||
name?: string;
|
||||
}
|
||||
);
|
||||
|
||||
export class NpmProvider extends Provider {
|
||||
name = "npm";
|
||||
private readonly repositoryUrl = "https://npmjs.org/";
|
||||
private readonly apiUrl = "https://registry.npmjs.org/";
|
||||
private readonly packageName?: string;
|
||||
|
||||
constructor({ main, logger, ...options }: NpmProviderOptions) {
|
||||
super({ main, logger });
|
||||
|
||||
this.packageName = "package" in options ? options.package : options.name;
|
||||
}
|
||||
|
||||
async getVersions(name: string): Promise<any> {
|
||||
const response = await fetch(
|
||||
new URL(`${this.packageName ?? name}`, this.apiUrl)
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
"couldn't fetch the latest version - try again after sometime"
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
"dist-tags": { latest },
|
||||
versions,
|
||||
} = (await response.json()) as NpmApiPackageMetadata;
|
||||
|
||||
return {
|
||||
latest,
|
||||
versions: Object.keys(versions).reverse(),
|
||||
};
|
||||
}
|
||||
|
||||
getRepositoryUrl(name: string, version?: string): string {
|
||||
return new URL(
|
||||
`package/${this.packageName ?? name}${version ? `/v/${version}` : ""}`,
|
||||
this.repositoryUrl
|
||||
).href;
|
||||
}
|
||||
|
||||
getRegistryUrl(name: string, version: string): string {
|
||||
return `npm:${this.packageName ?? name}@${version}`;
|
||||
}
|
||||
}
|
||||
|
||||
type NpmApiPackageMetadata = {
|
||||
"dist-tags": {
|
||||
latest: string;
|
||||
};
|
||||
versions: {
|
||||
[version: string]: unknown;
|
||||
};
|
||||
};
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
Command,
|
||||
GlobalUserInfo,
|
||||
log,
|
||||
passwordGenerator,
|
||||
Table,
|
||||
UserService,
|
||||
GroupService,
|
||||
@@ -57,6 +56,10 @@ async function list(opts: GlobalOptions) {
|
||||
.render();
|
||||
}
|
||||
|
||||
function rdString() {
|
||||
return Math.random().toString(36).slice(2, 7);
|
||||
}
|
||||
|
||||
async function add(
|
||||
opts: GlobalOptions & {
|
||||
superadmin?: boolean;
|
||||
@@ -67,7 +70,7 @@ async function add(
|
||||
password?: string
|
||||
) {
|
||||
await requireLogin(opts);
|
||||
const password_final = password ?? passwordGenerator("*", 15);
|
||||
const password_final = password ?? rdString();
|
||||
await UserService.createUserGlobally({
|
||||
requestBody: {
|
||||
email,
|
||||
|
||||
@@ -5,7 +5,6 @@ import { loginInteractive, tryGetLoginInfo } from "./login.ts";
|
||||
import {
|
||||
colors,
|
||||
Command,
|
||||
DelimiterStream,
|
||||
Input,
|
||||
log,
|
||||
setClient,
|
||||
@@ -23,47 +22,20 @@ export interface Workspace {
|
||||
token: string;
|
||||
}
|
||||
|
||||
function makeWorkspaceStream(
|
||||
readable: ReadableStream<Uint8Array>
|
||||
): ReadableStream<Workspace> {
|
||||
return readable
|
||||
.pipeThrough(new DelimiterStream(new TextEncoder().encode("\n")))
|
||||
.pipeThrough(new TextDecoderStream())
|
||||
.pipeThrough(
|
||||
new TransformStream({
|
||||
transform(line, controller) {
|
||||
try {
|
||||
if (line.length <= 2) {
|
||||
return;
|
||||
}
|
||||
const workspace = JSON.parse(line) as Workspace;
|
||||
workspace.remote = new URL(workspace.remote).toString(); // add trailing slash in all cases!
|
||||
controller.enqueue(workspace);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function getWorkspaceStream() {
|
||||
const file = await Deno.open((await getRootStore()) + "remotes.ndjson", {
|
||||
write: false,
|
||||
read: true,
|
||||
});
|
||||
return makeWorkspaceStream(file.readable);
|
||||
}
|
||||
|
||||
export async function allWorkspaces(): Promise<Workspace[]> {
|
||||
try {
|
||||
const workspaceStream = await getWorkspaceStream();
|
||||
const workspaces: Workspace[] = [];
|
||||
for await (const workspace of workspaceStream) {
|
||||
workspaces.push(workspace);
|
||||
}
|
||||
|
||||
return workspaces;
|
||||
const file = (await getRootStore()) + "remotes.ndjson";
|
||||
const txt = await Deno.readTextFile(file);
|
||||
return txt
|
||||
.split("\n")
|
||||
.map((line) => {
|
||||
if (line.length <= 2) {
|
||||
return;
|
||||
}
|
||||
const instance = JSON.parse(line) as Workspace;
|
||||
return instance;
|
||||
})
|
||||
.filter(Boolean) as Workspace[];
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
@@ -95,7 +67,7 @@ export async function getActiveWorkspace(
|
||||
export async function getWorkspaceByName(
|
||||
workspaceName: string
|
||||
): Promise<Workspace | undefined> {
|
||||
const workspaceStream = await getWorkspaceStream();
|
||||
const workspaceStream = await allWorkspaces();
|
||||
for await (const workspace of workspaceStream) {
|
||||
if (workspace.name === workspaceName) {
|
||||
return workspace;
|
||||
|
||||
Reference in New Issue
Block a user