feat: make CLI node compatible (#4347)

This commit is contained in:
Ruben Fiszel
2024-09-07 02:31:26 +02:00
committed by GitHub
parent d11284feb2
commit fd18f42e0f
19 changed files with 1466 additions and 247 deletions

1
cli/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
npm/

1
cli/build.sh Executable file
View File

@@ -0,0 +1 @@
deno run -A dnt.ts

View File

@@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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";

View File

@@ -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
View 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"
);
},
});

View File

@@ -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 [];
}

View File

@@ -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}`);
});
});
}

View File

@@ -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;

View File

@@ -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.'

View File

@@ -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;
}

View File

@@ -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
View 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;
};
};

View File

@@ -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,

View File

@@ -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;