@pipelab/plugin-filesystem 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@pipelab/plugin-filesystem",
3
+ "version": "1.0.0-beta.0",
4
+ "license": "FSL-1.1-MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/CynToolkit/pipelab.git",
8
+ "directory": "plugins/plugin-filesystem"
9
+ },
10
+ "type": "module",
11
+ "main": "./dist/index.cjs",
12
+ "module": "./dist/index.mjs",
13
+ "types": "./dist/index.d.mts",
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "dependencies": {
18
+ "@types/node": "^24.12.2",
19
+ "node-stream-zip": "1.15.0",
20
+ "@pipelab/plugin-core": "1.0.0-beta.0"
21
+ },
22
+ "devDependencies": {
23
+ "tsdown": "0.21.2",
24
+ "typescript": "5.9.3",
25
+ "vitest": "3.1.4",
26
+ "@pipelab/test-utils": "1.0.0-beta.0",
27
+ "@pipelab/tsconfig": "1.0.0-beta.0"
28
+ },
29
+ "test": {
30
+ "env": {
31
+ "NODE_ENV": "development"
32
+ }
33
+ },
34
+ "scripts": {
35
+ "format": "oxfmt .",
36
+ "lint": "oxlint .",
37
+ "build": "tsdown",
38
+ "test": "vitest run -c tests/e2e/vitest.config.mts"
39
+ },
40
+ "exports": {
41
+ ".": {
42
+ "types": "./dist/index.d.mts",
43
+ "import": "./dist/index.mjs",
44
+ "require": "./dist/index.cjs",
45
+ "default": "./dist/index.mjs"
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,100 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { copyRunner } from "./copy.js";
3
+ import mock from "mock-fs";
4
+ import { readFile } from "node:fs/promises";
5
+ import { browserWindow } from "@pipelab/shared";
6
+
7
+ describe("copy", () => {
8
+ test("copy file to file", async () => {
9
+ const outputs: Record<string, unknown> = {};
10
+
11
+ const sourcePath = "/source/source-file.txt";
12
+ const sourceContent = "text";
13
+
14
+ const destinationPath = "/destination/destination-file.txt";
15
+ const files = {
16
+ [sourcePath]: sourceContent,
17
+ "/destination": {},
18
+ };
19
+
20
+ // mock(files)
21
+
22
+ // await copyRunner({
23
+ // cwd: '',
24
+ // inputs: {
25
+ // from: sourcePath,
26
+ // to: destinationPath,
27
+ // recursive: false
28
+ // },
29
+ // log: (...args) => {
30
+ // console.log(...args)
31
+ // },
32
+ // setOutput: (key, value) => {
33
+ // outputs[key] = value
34
+ // },
35
+ // meta: {
36
+ // definition: ''
37
+ // },
38
+ // setMeta: () => {
39
+ // console.log('set meta defined here')
40
+ // },
41
+ // paths: {
42
+ // assets: '',
43
+ // unpack: ''
44
+ // },
45
+ // api: undefined,
46
+ // browserWindow
47
+ // })
48
+
49
+ // const data = await readFile(destinationPath, 'utf-8')
50
+
51
+ // expect(data).toBe(sourceContent)
52
+ expect(true).toBe(true);
53
+ });
54
+
55
+ test("copy file to file where parent folder doesn't exist", async () => {
56
+ const outputs: Record<string, unknown> = {};
57
+
58
+ const sourcePath = "/source/source-file.txt";
59
+ const sourceContent = "text";
60
+
61
+ const destinationPath = "/destination/destination-file.txt";
62
+ const files = {
63
+ [sourcePath]: sourceContent,
64
+ };
65
+
66
+ // mock(files)
67
+
68
+ // await copyRunner({
69
+ // cwd: '',
70
+ // inputs: {
71
+ // from: sourcePath,
72
+ // to: destinationPath,
73
+ // recursive: false
74
+ // },
75
+ // log: (...args) => {
76
+ // console.log(...args)
77
+ // },
78
+ // setOutput: (key, value) => {
79
+ // outputs[key] = value
80
+ // },
81
+ // meta: {
82
+ // definition: ''
83
+ // },
84
+ // setMeta: () => {
85
+ // console.log('set meta defined here')
86
+ // },
87
+ // paths: {
88
+ // assets: '',
89
+ // unpack: ''
90
+ // },
91
+ // api: undefined,
92
+ // browserWindow
93
+ // })
94
+
95
+ // const data = await readFile(destinationPath, 'utf-8')
96
+
97
+ // expect(data).toBe(sourceContent)
98
+ expect(true).toBe(true);
99
+ });
100
+ });
package/src/copy.ts ADDED
@@ -0,0 +1,154 @@
1
+ import { cp, mkdir, rm, stat } from "node:fs/promises";
2
+ import { basename, join, dirname } from "node:path";
3
+ import { createAction, createActionRunner, createPathParam } from "@pipelab/plugin-core";
4
+
5
+ export const ID = "fs:copy";
6
+
7
+ export const copy = createAction({
8
+ id: ID,
9
+ name: "Copy file/folder",
10
+ displayString:
11
+ '`Copy ${fmt.param(params.from, "primary", "Source")} to ${fmt.param(params.to, "primary", "Destination")}`',
12
+ params: {
13
+ from: createPathParam("", {
14
+ label: "From",
15
+ required: true,
16
+ control: {
17
+ type: "path",
18
+ options: {
19
+ properties: ["openFile", "openDirectory"],
20
+ },
21
+ },
22
+ }),
23
+ to: createPathParam("", {
24
+ label: "To",
25
+ required: true,
26
+ control: {
27
+ type: "path",
28
+ options: {
29
+ properties: ["openFile", "openDirectory", "createDirectory", "promptToCreate"],
30
+ },
31
+ },
32
+ }),
33
+ recursive: {
34
+ label: "Recursive",
35
+ required: true,
36
+ value: true,
37
+ control: {
38
+ type: "boolean",
39
+ },
40
+ },
41
+ overwrite: {
42
+ label: "Overwrite",
43
+ required: true,
44
+ value: true,
45
+ control: {
46
+ type: "boolean",
47
+ },
48
+ },
49
+ cleanup: {
50
+ label: "Cleanup",
51
+ required: true,
52
+ description: "Whether to delete the original file/folder",
53
+ value: true,
54
+ control: {
55
+ type: "boolean",
56
+ },
57
+ },
58
+ },
59
+
60
+ outputs: {
61
+ output: {
62
+ label: "Output",
63
+ value: "",
64
+ description: "The copied file/folder",
65
+ },
66
+ input: {
67
+ label: "Input",
68
+ value: "",
69
+ description: "The original file/folder",
70
+ },
71
+ parentDirectory: {
72
+ label: "Parent directory",
73
+ value: "",
74
+ description: "The parent directory of the copied file/folder",
75
+ },
76
+ },
77
+ description: "Copy a file or a folder from one location to another",
78
+ icon: "",
79
+ meta: {},
80
+ });
81
+
82
+ export const copyRunner = createActionRunner<typeof copy>(async ({ log, inputs, setOutput }) => {
83
+ log("");
84
+
85
+ const from = inputs.from;
86
+ let to = inputs.to;
87
+
88
+ let fromIsAFile = false;
89
+ try {
90
+ const stats = await stat(from);
91
+ if (stats.isFile()) {
92
+ fromIsAFile = true;
93
+ }
94
+ } catch (e) {
95
+ log("Error getting file stats", e);
96
+ throw e;
97
+ }
98
+ const fromFileName = fromIsAFile ? basename(from) : "";
99
+
100
+ if (!from) {
101
+ log("From", from);
102
+ throw new Error("Missing source");
103
+ }
104
+
105
+ if (!to) {
106
+ log("To", to);
107
+ throw new Error("Missing destination");
108
+ }
109
+
110
+ // if from is a file, we only add the file name to the destination if 'to' is an existing directory
111
+ if (fromIsAFile) {
112
+ try {
113
+ const toStats = await stat(to);
114
+ if (toStats.isDirectory()) {
115
+ to = join(to, fromFileName);
116
+ }
117
+ } catch (e) {
118
+ // If 'to' doesn't exist, we assume the user provided the full destination path (including filename)
119
+ }
120
+ }
121
+
122
+ log("Copying", from, "to", to, "recursive", inputs.recursive, "overwrite", inputs.overwrite);
123
+
124
+ if (inputs.cleanup) {
125
+ try {
126
+ log("Cleaning up", to);
127
+ process.noAsar = true;
128
+ await rm(to, { recursive: true, force: true, maxRetries: 3 });
129
+ if (!fromIsAFile) {
130
+ await mkdir(to, { recursive: true });
131
+ }
132
+ process.noAsar = false;
133
+ } catch (e) {
134
+ log("Error cleaning up file", e);
135
+ throw e;
136
+ }
137
+ }
138
+
139
+ try {
140
+ process.noAsar = true;
141
+ await cp(from, to, {
142
+ recursive: inputs.recursive && !fromIsAFile,
143
+ force: inputs.overwrite,
144
+ });
145
+ process.noAsar = false;
146
+ setOutput("output", to);
147
+ setOutput("input", from);
148
+ setOutput("parentDirectory", dirname(to));
149
+ log("Copied", from, "to", to);
150
+ } catch (e) {
151
+ log("Error copying file", e);
152
+ throw e;
153
+ }
154
+ });
package/src/index.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { createNodeDefinition } from "@pipelab/plugin-core";
2
+ import { ListFilesAction, ListFilesActionRun } from "./list-files";
3
+ import { isFileCondition, isFileRunner } from "./is-file";
4
+ import { zipRunner, zip } from "./zip";
5
+ import { zipV2Runner, zipV2 } from "./zip-v2";
6
+ import { unzipRunner, unzip } from "./unzip";
7
+ import { copy, copyRunner } from "./copy";
8
+ import { remove, removeRunner } from "./remove";
9
+ import { run, runRunner } from "./run";
10
+ import { openInExplorer, openInExplorerRunner } from "./open";
11
+
12
+ export default createNodeDefinition({
13
+ description: "Filesystem",
14
+ id: "filesystem",
15
+ name: "Filesystem",
16
+ icon: {
17
+ type: "icon",
18
+ icon: "mdi-folder-zip-outline",
19
+ },
20
+ nodes: [
21
+ // {
22
+ // node: ListFilesAction,
23
+ // runner: ListFilesActionRun
24
+ // },
25
+ // {
26
+ // node: isFileCondition,
27
+ // runner: isFileRunner
28
+ // },
29
+ {
30
+ node: zip,
31
+ runner: zipRunner,
32
+ },
33
+ {
34
+ node: zipV2,
35
+ runner: zipV2Runner,
36
+ },
37
+ {
38
+ node: unzip,
39
+ runner: unzipRunner,
40
+ },
41
+ {
42
+ node: copy,
43
+ runner: copyRunner,
44
+ },
45
+ {
46
+ node: remove,
47
+ runner: removeRunner,
48
+ },
49
+ {
50
+ node: run,
51
+ runner: runRunner,
52
+ },
53
+ {
54
+ node: openInExplorer,
55
+ runner: openInExplorerRunner,
56
+ },
57
+ ],
58
+ });
package/src/is-file.ts ADDED
@@ -0,0 +1,36 @@
1
+ import fs from "node:fs/promises";
2
+ import { createCondition, createConditionRunner } from "@pipelab/plugin-core";
3
+
4
+ export const ID = "is-file";
5
+
6
+ export const isFileCondition = createCondition({
7
+ id: ID,
8
+ icon: "",
9
+ name: "Is file",
10
+ description: "",
11
+ displayString: "`If ${params.path} is a file`",
12
+ params: {
13
+ path: {
14
+ value: "",
15
+ label: "Path",
16
+ control: {
17
+ type: "input",
18
+ options: {
19
+ kind: "text",
20
+ },
21
+ },
22
+ },
23
+ },
24
+ });
25
+
26
+ export const isFileRunner = createConditionRunner<typeof isFileCondition>(
27
+ async ({ log, inputs }) => {
28
+ const path = inputs.path;
29
+
30
+ log("path", path);
31
+
32
+ const stats = await fs.stat(path);
33
+
34
+ return stats.isFile();
35
+ },
36
+ );
@@ -0,0 +1,70 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createAction, createActionRunner } from "@pipelab/plugin-core";
4
+ export const ID = "list-files-node";
5
+
6
+ export const ListFilesAction = createAction({
7
+ id: ID,
8
+ name: "List files",
9
+ displayString:
10
+ "`List files \"${params.recursive ? 'recursively' : ''}\" from \"${params.folder}`",
11
+ params: {
12
+ folder: {
13
+ control: {
14
+ type: "path",
15
+ options: {
16
+ properties: ["openDirectory"],
17
+ },
18
+ },
19
+ value: "",
20
+ label: "Folder",
21
+ },
22
+ recursive: {
23
+ control: {
24
+ type: "boolean",
25
+ },
26
+ value: false,
27
+ label: "Recursive",
28
+ },
29
+ },
30
+
31
+ outputs: {
32
+ paths: {
33
+ value: [] as Array<string>,
34
+ label: "Paths",
35
+ },
36
+ },
37
+ description: "List files from a folder",
38
+ icon: "",
39
+ meta: {},
40
+ });
41
+
42
+ export const ListFilesActionRun = createActionRunner<typeof ListFilesAction>(
43
+ async ({ log, inputs, setOutput }) => {
44
+ const readdir = fs.readdir;
45
+
46
+ log("");
47
+
48
+ log("inputs", inputs);
49
+
50
+ const folder = inputs.folder;
51
+ const recursive = inputs.recursive;
52
+
53
+ log("folder", folder);
54
+
55
+ const response = await readdir(folder, {
56
+ withFileTypes: true,
57
+ recursive,
58
+ });
59
+
60
+ log("response", response);
61
+
62
+ const files = response;
63
+
64
+ log("-- setValue('paths')");
65
+ setOutput(
66
+ "paths",
67
+ files.map((x) => path.join(x.path, x.name)),
68
+ );
69
+ },
70
+ );
package/src/open.ts ADDED
@@ -0,0 +1,67 @@
1
+ import { exec } from "node:child_process";
2
+ import { platform } from "node:os";
3
+ import { createRequire } from "node:module";
4
+ import { createAction, createActionRunner, createPathParam } from "@pipelab/plugin-core";
5
+
6
+ const require = createRequire(import.meta.url);
7
+ // import displayString from './displayStringRun.lua?raw'
8
+
9
+ export const ID = "fs:open-in-explorer";
10
+
11
+ export const openInExplorer = createAction({
12
+ id: ID,
13
+ name: "Open path in explorer",
14
+ displayString: "`Open ${fmt.param(params.path, 'primary', 'No path set')} in explorer`",
15
+ params: {
16
+ path: createPathParam("", {
17
+ required: true,
18
+ label: "Path",
19
+ control: {
20
+ type: "path",
21
+ options: {
22
+ properties: ["openDirectory", "openFile"],
23
+ },
24
+ },
25
+ }),
26
+ },
27
+
28
+ outputs: {
29
+ message: {
30
+ label: "Message",
31
+ value: "",
32
+ },
33
+ },
34
+ description: "Open a file or folder in your explorer",
35
+ icon: "",
36
+ meta: {},
37
+ });
38
+
39
+ export const openInExplorerRunner = createActionRunner<typeof openInExplorer>(
40
+ async ({ log, inputs, setOutput }) => {
41
+ log(`Opening ${inputs.path}`);
42
+
43
+ return new Promise((resolve, reject) => {
44
+ let command = "";
45
+ const p = platform();
46
+
47
+ if (p === "win32") {
48
+ command = `start "" "${inputs.path}"`;
49
+ } else if (p === "darwin") {
50
+ command = `open "${inputs.path}"`;
51
+ } else {
52
+ command = `xdg-open "${inputs.path}"`;
53
+ }
54
+
55
+ exec(command, (error) => {
56
+ if (error) {
57
+ log(`Error opening path: ${error.message}`);
58
+ setOutput("message", error.message);
59
+ resolve();
60
+ } else {
61
+ setOutput("message", "success");
62
+ resolve();
63
+ }
64
+ });
65
+ });
66
+ },
67
+ );
package/src/remove.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { createAction, createActionRunner, createPathParam } from "@pipelab/plugin-core";
3
+
4
+ export const ID = "fs:remove";
5
+
6
+ export const remove = createAction({
7
+ id: ID,
8
+ name: "Remove file/folder",
9
+ displayString: '`Remove ${fmt.param(params.from, "primary")}`',
10
+ params: {
11
+ from: createPathParam("", {
12
+ label: "Path",
13
+ required: true,
14
+ control: {
15
+ type: "path",
16
+ options: {
17
+ properties: ["openFile"],
18
+ },
19
+ },
20
+ }),
21
+ recursive: {
22
+ label: "Recursive",
23
+ required: true,
24
+ value: true,
25
+ control: {
26
+ type: "boolean",
27
+ },
28
+ },
29
+ },
30
+
31
+ outputs: {},
32
+ description: "Remove a file or a folder",
33
+ icon: "",
34
+ meta: {},
35
+ });
36
+
37
+ export const removeRunner = createActionRunner<typeof remove>(async ({ log, inputs }) => {
38
+ log("");
39
+
40
+ const from = inputs.from;
41
+
42
+ log("Removing", from, inputs.recursive);
43
+
44
+ if (!from) {
45
+ log("From", from);
46
+ throw new Error("Missing source");
47
+ }
48
+
49
+ try {
50
+ process.noAsar = true;
51
+ await rm(from, { recursive: true, force: true, maxRetries: 3 });
52
+ process.noAsar = false;
53
+ log("Removed", from);
54
+ } catch (e) {
55
+ log("Error removeing file", e);
56
+ throw e;
57
+ }
58
+ });