mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
adf991cd2d
* Basic folder support * Proper Folder Support + deps.ts * Upgrade Versions * Add folder meta to tarball * Remove tarball folders * Minor fixes * Fix typo * Remove extra_perms check * Use new endpoint * Use new untar location * Fix shrinking files
267 lines
8.2 KiB
TypeScript
267 lines
8.2 KiB
TypeScript
// deno-lint-ignore-file no-explicit-any
|
|
import { colors, Command, path } from "./deps.ts";
|
|
import { requireLogin, resolveWorkspace } from "./context.ts";
|
|
import { pushFlow } from "./flow.ts";
|
|
import { pushResource } from "./resource.ts";
|
|
import { findContentFile, pushScript } from "./script.ts";
|
|
import { GlobalOptions } from "./types.ts";
|
|
import { pushVariable } from "./variable.ts";
|
|
import { pushResourceType } from "./resource-type.ts";
|
|
import { pushFolder } from "./folder.ts";
|
|
|
|
type Candidate = {
|
|
path: string;
|
|
namespaceKind: "user" | "group" | "folder";
|
|
namespaceName: string;
|
|
};
|
|
|
|
type ResourceTypeCandidate = {
|
|
path: string;
|
|
};
|
|
|
|
type FolderCandidate = {
|
|
path: string;
|
|
namespaceName: string;
|
|
};
|
|
|
|
async function findCandidateFiles(
|
|
dir: string,
|
|
): Promise<
|
|
{
|
|
normal: Candidate[];
|
|
resourceTypes: ResourceTypeCandidate[];
|
|
folders: FolderCandidate[];
|
|
}
|
|
> {
|
|
dir = path.resolve(dir);
|
|
if (path.dirname(dir).startsWith(".")) {
|
|
return { normal: [], resourceTypes: [], folders: [] };
|
|
}
|
|
const normalCandidates: Candidate[] = [];
|
|
const resourceTypeCandidates: ResourceTypeCandidate[] = [];
|
|
const folderCandidates: FolderCandidate[] = [];
|
|
for await (const e of Deno.readDir(dir)) {
|
|
if (e.isDirectory) {
|
|
if (e.name == "u" || e.name == "g" || e.name == "f") { // TODO: Check version for f
|
|
const newDir = dir + (dir.endsWith("/") ? "" : "/") + e.name;
|
|
for await (const e2 of Deno.readDir(newDir)) {
|
|
if (e2.isDirectory) {
|
|
if (e2.name.startsWith(".")) continue;
|
|
const namespaceName = e2.name;
|
|
const stack: string[] = [];
|
|
{
|
|
const path = newDir + "/" + namespaceName + "/";
|
|
stack.push(path);
|
|
try {
|
|
await Deno.stat(path + "folder.meta.json");
|
|
folderCandidates.push({
|
|
namespaceName,
|
|
path: path + "folder.meta.json",
|
|
});
|
|
} catch {}
|
|
}
|
|
|
|
while (stack.length > 0) {
|
|
const dir2 = stack.pop()!;
|
|
for await (const e3 of Deno.readDir(dir2)) {
|
|
if (e3.isFile) {
|
|
if (e3.name === "folder.meta.json") continue;
|
|
normalCandidates.push({
|
|
path: dir2 + e3.name,
|
|
namespaceKind: e.name == "g"
|
|
? "group"
|
|
: e.name == "u"
|
|
? "user"
|
|
: "folder",
|
|
namespaceName: namespaceName,
|
|
});
|
|
} else {
|
|
stack.push(dir2 + e3.name + "/");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
console.log(
|
|
colors.yellow(
|
|
"Including organizational folder " + e.name + " in push!",
|
|
),
|
|
);
|
|
const { normal, resourceTypes, folders } = await findCandidateFiles(
|
|
path.join(dir, e.name),
|
|
);
|
|
normalCandidates.push(...normal);
|
|
resourceTypeCandidates.push(...resourceTypes);
|
|
folderCandidates.push(...folders);
|
|
}
|
|
} else {
|
|
// handle root files
|
|
if (e.name.endsWith(".resource-type.json")) {
|
|
resourceTypeCandidates.push({
|
|
path: dir + (dir.endsWith("/") ? "" : "/") + e.name,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
normal: normalCandidates,
|
|
folders: folderCandidates,
|
|
resourceTypes: resourceTypeCandidates,
|
|
};
|
|
}
|
|
|
|
async function push(opts: GlobalOptions, dir?: string) {
|
|
dir = dir ?? Deno.cwd();
|
|
const workspace = await resolveWorkspace(opts);
|
|
await requireLogin(opts);
|
|
|
|
console.log(colors.blue("Searching Directory..."));
|
|
const { normal, resourceTypes, folders } = await findCandidateFiles(dir);
|
|
console.log(
|
|
colors.blue(
|
|
"Found " + (normal.length + resourceTypes.length + folders.length) +
|
|
" candidates",
|
|
),
|
|
);
|
|
for (const resourceType of resourceTypes) {
|
|
const fileName = resourceType.path.substring(
|
|
resourceType.path.lastIndexOf("/") + 1,
|
|
);
|
|
const fileNameParts = fileName.split(".");
|
|
// invalid file names, like my.cool.script.script.json. Not valid.
|
|
if (fileNameParts.length != 3) {
|
|
console.log(
|
|
colors.yellow("invalid file name found at " + resourceType.path),
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// filter out non-json files. Note that we filter out script contents above, so this is really an error.
|
|
if (fileNameParts.at(-1) != "json") {
|
|
console.log(colors.yellow("non-JSON file found at " + resourceType.path));
|
|
continue;
|
|
}
|
|
|
|
console.log("pushing resource type " + fileNameParts.at(-3)!);
|
|
await pushResourceType(
|
|
workspace.workspaceId,
|
|
resourceType.path,
|
|
fileNameParts.at(-3)!,
|
|
);
|
|
}
|
|
for (const folder of folders) {
|
|
await pushFolder(
|
|
workspace.workspaceId,
|
|
folder.path,
|
|
"f/" + folder.namespaceName,
|
|
);
|
|
}
|
|
for (const candidate of normal) {
|
|
// full file name. No leading /. includes .type.json
|
|
const fileName = candidate.path.substring(
|
|
candidate.path.lastIndexOf("/") + 1,
|
|
);
|
|
// figure out just the path after ...../u|g/username|group/ (in extra dir)
|
|
const dirParts = candidate.path.split("/").filter((x) => x.length > 0);
|
|
// TODO: check version for folder
|
|
const gIndex = dirParts.findIndex((x) => x == "u" || x == "g" || x == "f");
|
|
const extraDir = dirParts.slice(gIndex + 2, -1).join("/");
|
|
|
|
// file name parts has .json (hopefully) at -1, type at -2, and the actual name at -3. Dots in names are not allowed.
|
|
const fileNameParts = fileName.split(".");
|
|
|
|
// filter out script content files
|
|
if (
|
|
fileNameParts.at(-1) == "ts" ||
|
|
fileNameParts.at(-1) == "py" ||
|
|
fileNameParts.at(-1) == "go"
|
|
) {
|
|
// probably part of a script. Silent ignore.
|
|
continue;
|
|
}
|
|
|
|
// invalid file names, like my.cool.script.script.json. Not valid.
|
|
if (fileNameParts.length != 3) {
|
|
console.log(
|
|
colors.yellow("invalid file name found at " + candidate.path),
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// filter out non-json files. Note that we filter out script contents above, so this is really an error.
|
|
if (fileNameParts.at(-1) != "json") {
|
|
console.log(colors.yellow("non-JSON file found at " + candidate.path));
|
|
continue;
|
|
}
|
|
|
|
// get the type & filter it for valid ones.
|
|
const type = fileNameParts.at(-2);
|
|
if (type == "resource-type") {
|
|
console.log(
|
|
colors.yellow(
|
|
"Found resource type file at " +
|
|
candidate.path +
|
|
" this appears to be inside a path folder. Resource types are not addressed by path. Place them at the root or inside only an organizational folder. Ignoring this file!",
|
|
),
|
|
);
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
type != "flow" &&
|
|
type != "resource" &&
|
|
type != "script" &&
|
|
type != "variable"
|
|
) {
|
|
console.log(
|
|
colors.yellow(
|
|
"file with invalid type " + type + " found at " + candidate.path,
|
|
),
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// create the remotePath for the API
|
|
const remotePath = (candidate.namespaceKind === "group"
|
|
? "g/"
|
|
: (candidate.namespaceKind === "user" ? "u/" : "f/")) +
|
|
candidate.namespaceName +
|
|
"/" +
|
|
(extraDir.length > 0 ? extraDir + "/" : "") +
|
|
fileNameParts.at(-3);
|
|
|
|
console.log("pushing " + type + " to " + remotePath);
|
|
|
|
if (type == "flow") {
|
|
await pushFlow(candidate.path, workspace.workspaceId, remotePath);
|
|
} else if (type == "resource") {
|
|
await pushResource(workspace.workspaceId, candidate.path, remotePath);
|
|
} else if (type == "script") {
|
|
let contentPath: string;
|
|
try {
|
|
contentPath = await findContentFile(candidate.path);
|
|
} catch (e) {
|
|
console.log(colors.red(e.toString()));
|
|
continue;
|
|
}
|
|
await pushScript(
|
|
candidate.path,
|
|
contentPath,
|
|
workspace.workspaceId,
|
|
remotePath,
|
|
);
|
|
} else if (type == "variable") {
|
|
await pushVariable(workspace.workspaceId, candidate.path, remotePath);
|
|
}
|
|
}
|
|
console.log(colors.underline.bold.green("Successfully Pushed all files."));
|
|
}
|
|
|
|
const command = new Command()
|
|
.description("Push all files from a folder")
|
|
.arguments("[dir:string]")
|
|
.action(push as any);
|
|
|
|
export default command;
|