├── supabase ├── seed.sql ├── migrations │ └── 20241006073107_remote_schema.sql └── .gitignore ├── src ├── shared │ ├── libs │ │ ├── .gitkeep │ │ ├── core-app │ │ │ ├── index.ts │ │ │ └── variables.ts │ │ ├── plugin-construct │ │ │ ├── declarations.d.ts │ │ │ ├── assets │ │ │ │ └── construct.webp │ │ │ ├── index.ts │ │ │ ├── export.test.ts │ │ │ └── export-c3p.ts │ │ ├── plugin-electron │ │ │ ├── declarations.d.ts │ │ │ ├── public │ │ │ │ └── electron.webp │ │ │ ├── fixtures │ │ │ │ └── build │ │ │ │ │ └── index.html │ │ │ ├── configure.ts │ │ │ ├── make.spec.ts │ │ │ ├── make.ts │ │ │ ├── package.ts │ │ │ └── preview.ts │ │ ├── plugin-steam │ │ │ ├── declarations.d.ts │ │ │ ├── steam.webp │ │ │ └── index.ts │ │ ├── plugin-tauri │ │ │ ├── declarations.d.ts │ │ │ ├── model.ts │ │ │ ├── public │ │ │ │ └── tauri.webp │ │ │ ├── fixtures │ │ │ │ └── build │ │ │ │ │ └── index.html │ │ │ ├── package.ts │ │ │ ├── preview.ts │ │ │ ├── configureV2.ts │ │ │ ├── make.spec.ts │ │ │ ├── make.ts │ │ │ └── index.ts │ │ ├── plugin-itch │ │ │ ├── assets │ │ │ │ └── itch-icon.webp │ │ │ └── index.ts │ │ ├── plugin-poki │ │ │ ├── assets │ │ │ │ └── poki-icon.webp │ │ │ └── index.ts │ │ ├── plugin-core │ │ │ ├── create-plugin.ts │ │ │ └── index.ts │ │ ├── migration │ │ │ ├── index.ts │ │ │ ├── utils │ │ │ │ └── object-keys.ts │ │ │ └── models │ │ │ │ ├── createMigrator.ts │ │ │ │ └── createMigration.ts │ │ ├── plugin-nvpatch │ │ │ ├── index.ts │ │ │ └── nvpatch.ts │ │ ├── plugin-system │ │ │ ├── manual.ts │ │ │ ├── log.ts │ │ │ ├── sleep.ts │ │ │ ├── for.ts │ │ │ ├── alert.ts │ │ │ ├── prompt.ts │ │ │ ├── index.ts │ │ │ ├── join.ts │ │ │ └── branch.ts │ │ ├── plugin-minify │ │ │ ├── index.ts │ │ │ ├── images.ts │ │ │ └── code.ts │ │ └── plugin-filesystem │ │ │ ├── unzip.spec.ts │ │ │ ├── is-file.ts │ │ │ ├── open.ts │ │ │ ├── temporary-folder.ts │ │ │ ├── index.ts │ │ │ ├── remove.ts │ │ │ ├── unzip.ts │ │ │ └── list-files.ts │ ├── types.ts │ ├── utils.ts │ ├── tests │ │ └── helpers.ts │ ├── supabase.ts │ ├── validation.ts │ ├── logger.ts │ ├── plugins.ts │ └── save-location.ts ├── main │ ├── presets │ │ ├── preset.model.ts │ │ ├── newProject.ts │ │ ├── moreToCome.ts │ │ ├── list.ts │ │ ├── loop.ts │ │ ├── if.ts │ │ └── test-c3-offline.ts │ ├── paths.ts │ └── utils.ts ├── renderer.ts ├── renderer │ ├── models │ │ ├── constants.ts │ │ ├── error.ts │ │ └── controls.ts │ ├── agent │ │ ├── utils │ │ │ └── json.ts │ │ ├── trpc.ts │ │ └── log.ts │ ├── style │ │ ├── main.ts │ │ └── main.scss │ ├── utils │ │ ├── fmt.ts │ │ ├── graph.ts │ │ ├── evaluator.ts │ │ ├── config.ts │ │ └── quickjs.ts │ ├── plugins │ │ └── posthog.ts │ ├── components │ │ ├── __tests__ │ │ │ └── HelloWorld.spec.ts │ │ ├── AddNodeButton.model.ts │ │ ├── nodes │ │ │ ├── EditorNodeDummy.vue │ │ │ ├── PluginIcon.vue │ │ │ └── EditorNodeEventEmpty.vue │ │ ├── ScenarioListItemRecent.vue │ │ ├── ScenarioListItemFile.vue │ │ └── FileInput.vue │ ├── env.d.ts │ ├── Root.vue │ ├── composables │ │ ├── variables.ts │ │ ├── middleware.ts │ │ ├── handlers.ts │ │ └── api.ts │ ├── store │ │ ├── recents.ts │ │ ├── settings.ts │ │ ├── app.ts │ │ └── files.ts │ ├── pages │ │ ├── team.vue │ │ ├── scenarios.vue │ │ └── project-settings-editor.vue │ └── router │ │ └── router.ts ├── global.d.ts ├── preload.d.ts ├── auto-imports.d.ts ├── constants.ts └── preload.ts ├── assets ├── electron │ └── template │ │ └── app │ │ ├── .npmrc │ │ ├── src │ │ ├── custom-main.js │ │ ├── handlers │ │ │ ├── general │ │ │ │ ├── engine.js │ │ │ │ ├── open.js │ │ │ │ ├── open-in-explorer.js │ │ │ │ └── run.js │ │ │ ├── fs │ │ │ │ ├── delete.js │ │ │ │ ├── file-size.js │ │ │ │ ├── read-binary.js │ │ │ │ ├── folder-create.js │ │ │ │ ├── copy.js │ │ │ │ ├── move.js │ │ │ │ ├── write.js │ │ │ │ ├── exist.js │ │ │ │ ├── list.js │ │ │ │ └── read.js │ │ │ ├── window │ │ │ │ ├── restore.js │ │ │ │ ├── maximize.js │ │ │ │ ├── set-title.js │ │ │ │ ├── minimize.js │ │ │ │ ├── set-resizable.js │ │ │ │ ├── unmaximize.js │ │ │ │ ├── set-always-on-top.js │ │ │ │ ├── request-attention.js │ │ │ │ ├── set-x.js │ │ │ │ ├── set-y.js │ │ │ │ ├── set-maximum-size.js │ │ │ │ ├── set-minimum-size.js │ │ │ │ ├── set-width.js │ │ │ │ ├── set-height.js │ │ │ │ ├── show-dev-tools.js │ │ │ │ └── set-fullscreen.js │ │ │ ├── dialog │ │ │ │ ├── save.js │ │ │ │ ├── open.js │ │ │ │ └── folder.js │ │ │ ├── steam │ │ │ │ └── raw.js │ │ │ └── user │ │ │ │ └── folder.js │ │ ├── utils.js │ │ └── preload.js │ │ ├── config.d.ts │ │ ├── config.cjs │ │ ├── pipelab-plugin.cjs │ │ ├── package.json │ │ ├── .gitignore │ │ └── forge.config.cjs ├── build │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── entitlements.mac.plist │ └── notarize.cjs └── tauri │ └── template │ └── app │ ├── src-tauri │ ├── build.rs │ ├── icons │ │ ├── 32x32.png │ │ ├── icon.icns │ │ ├── icon.ico │ │ ├── icon.png │ │ ├── 128x128.png │ │ ├── 128x128@2x.png │ │ ├── StoreLogo.png │ │ ├── Square30x30Logo.png │ │ ├── Square44x44Logo.png │ │ ├── Square71x71Logo.png │ │ ├── Square89x89Logo.png │ │ ├── Square107x107Logo.png │ │ ├── Square142x142Logo.png │ │ ├── Square150x150Logo.png │ │ ├── Square284x284Logo.png │ │ └── Square310x310Logo.png │ ├── .gitignore │ ├── src │ │ └── main.rs │ ├── capabilities │ │ └── default.json │ ├── tauri.conf.json │ └── Cargo.toml │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── tsconfig.json │ ├── vite.config.ts │ ├── index.html │ └── src │ └── main.ts ├── .mise.toml ├── .prettierrc.yaml ├── .prettierignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── .env.example ├── readme └── full_white_bg_black_text.png ├── tests └── e2e │ ├── fixtures │ ├── c3-export │ │ └── test.c3p │ ├── folder-to-electron │ │ └── index.html │ ├── folder-to-electron.json │ └── c3-export.json │ └── vitest.config.ts ├── .hintrc ├── .editorconfig ├── .npmrc ├── declaration.d.ts ├── .gitattributes ├── docs └── expression.md ├── scripts ├── headless-electron-tester.mts └── migrate-file.mts ├── .changeset └── config.json ├── .github └── dependabot.yml ├── vitest.config.mts ├── .devcontainer └── devcontainer.json ├── tsconfig.json ├── vite.preload.config.ts ├── forge.env.d.ts ├── CHANGELOG.md ├── index.html ├── .gitignore ├── eslint.config.mjs ├── components.d.ts ├── vite.main.config.mts ├── vite.renderer.config.mts └── README.md /supabase/seed.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/shared/libs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/presets/preset.model.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer.ts: -------------------------------------------------------------------------------- 1 | import './renderer/main' 2 | -------------------------------------------------------------------------------- /supabase/migrations/20241006073107_remote_schema.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/electron/template/app/.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted -------------------------------------------------------------------------------- /src/shared/libs/core-app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './variables.js' -------------------------------------------------------------------------------- /supabase/.gitignore: -------------------------------------------------------------------------------- 1 | # Supabase 2 | .branches 3 | .temp 4 | .env 5 | -------------------------------------------------------------------------------- /.mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | node = "22" 3 | 4 | [env] 5 | _.file = '.env' 6 | -------------------------------------------------------------------------------- /src/shared/types.ts: -------------------------------------------------------------------------------- 1 | export type NodeId = string // keyof typeof nodes 2 | -------------------------------------------------------------------------------- /src/renderer/models/constants.ts: -------------------------------------------------------------------------------- 1 | export const PROJECT_EXTENSION = 'plb' 2 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-construct/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.webp' 2 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-electron/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.webp' 2 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-steam/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.webp' 2 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-tauri/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.webp' 2 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/custom-main.js: -------------------------------------------------------------------------------- 1 | // THIS FILE WILL BE REPLACED 2 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | semi: false 3 | printWidth: 100 4 | trailingComma: none 5 | -------------------------------------------------------------------------------- /assets/build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/build/icon.icns -------------------------------------------------------------------------------- /assets/build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/build/icon.ico -------------------------------------------------------------------------------- /assets/build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/build/icon.png -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-tauri/model.ts: -------------------------------------------------------------------------------- 1 | export type TauriConfiguration = { 2 | name: string 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | pnpm-lock.yaml 4 | LICENSE.md 5 | tsconfig.json 6 | tsconfig.*.json 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | 4 | "nrwl.angular-console" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | __POSTHOG_API_KEY__: string 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/renderer/models/error.ts: -------------------------------------------------------------------------------- 1 | export interface ValidationError { 2 | type: 'missing'; 3 | param: string 4 | } 5 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SUPABASE_URL= 2 | SUPABASE_ANON_KEY= 3 | SUPABASE_PROJECT_ID= 4 | EDGE_FUNCTION_ROOT= 5 | POSTHOG_API_KEY= 6 | -------------------------------------------------------------------------------- /readme/full_white_bg_black_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/readme/full_white_bg_black_text.png -------------------------------------------------------------------------------- /tests/e2e/fixtures/c3-export/test.c3p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/tests/e2e/fixtures/c3-export/test.c3p -------------------------------------------------------------------------------- /.hintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "development" 4 | ], 5 | "hints": { 6 | "typescript-config/is-valid": "off" 7 | } 8 | } -------------------------------------------------------------------------------- /src/shared/libs/plugin-steam/steam.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/src/shared/libs/plugin-steam/steam.webp -------------------------------------------------------------------------------- /src/shared/utils.ts: -------------------------------------------------------------------------------- 1 | export const foo = 'bar' 2 | 3 | export type WithId = T extends (string | number) ? never : T & { id: string } 4 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-tauri/public/tauri.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/src/shared/libs/plugin-tauri/public/tauri.webp -------------------------------------------------------------------------------- /src/shared/libs/plugin-itch/assets/itch-icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/src/shared/libs/plugin-itch/assets/itch-icon.webp -------------------------------------------------------------------------------- /src/shared/libs/plugin-poki/assets/poki-icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/src/shared/libs/plugin-poki/assets/poki-icon.webp -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src/shared/libs/plugin-electron/public/electron.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/src/shared/libs/plugin-electron/public/electron.webp -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src/shared/libs/plugin-construct/assets/construct.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/src/shared/libs/plugin-construct/assets/construct.webp -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CynToolkit/pipelab/HEAD/assets/tauri/template/app/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /src/preload.d.ts: -------------------------------------------------------------------------------- 1 | import { ElectronAPI } from '@electron-toolkit/preload' 2 | 3 | declare global { 4 | interface Window { 5 | electron: ElectronAPI 6 | api: unknown 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ 2 | shamefully-hoist=true 3 | save-exact=true 4 | strict-peer-dependencies=false 5 | auto-install-peers=true 6 | node-linker=hoisted 7 | -------------------------------------------------------------------------------- /src/renderer/agent/utils/json.ts: -------------------------------------------------------------------------------- 1 | export const safeParse = (str: string): T | undefined => { 2 | try { 3 | return JSON.parse(str); 4 | } catch (e) { 5 | return undefined 6 | } 7 | } -------------------------------------------------------------------------------- /src/renderer/style/main.ts: -------------------------------------------------------------------------------- 1 | import { darken } from 'polished' 2 | 3 | export const primary = "#3B82F6" 4 | export const primaryDarken1 = darken(0.05)(primary) 5 | export const primaryDarken2 = darken(0.1)(primary) 6 | -------------------------------------------------------------------------------- /src/shared/tests/helpers.ts: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest' 2 | 3 | export const browserWindow = { 4 | setProgressBar: vi.fn() 5 | } satisfies Partial as unknown as Electron.BrowserWindow 6 | -------------------------------------------------------------------------------- /declaration.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'mock-fs' 2 | declare module 'memfs' 3 | declare module 'set-value' 4 | declare module 'get-value' 5 | 6 | declare const __SUPABASE_URL__: string 7 | declare const __SUPABASE_ANON_KEY__: string 8 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-core/create-plugin.ts: -------------------------------------------------------------------------------- 1 | export type Plugin = { 2 | nodes: Record 3 | runtime: () => Promise 4 | } 5 | 6 | export const createPlugin = (plugin: Plugin) => { 7 | return plugin 8 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.icns binary 3 | *.ico binary 4 | *.pdf binary 5 | *.jpg binary 6 | *.jpeg binary 7 | *.png binary 8 | *.gif binary 9 | *.tiff binary 10 | *.bmp binary 11 | *.webp binary 12 | *.c3p binary 13 | -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Generated by Tauri 6 | # will have schema files for capabilities auto-completion 7 | /gen/schemas 8 | -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | fn main() { 5 | tauri_lib::run() 6 | } 7 | -------------------------------------------------------------------------------- /src/shared/libs/core-app/variables.ts: -------------------------------------------------------------------------------- 1 | export interface VariableBase { 2 | value: string 3 | id: string 4 | name: string 5 | description: string 6 | } 7 | 8 | export type Variable = VariableBase 9 | 10 | export const foo = 'aaa' 11 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './pipelab.js' 2 | 3 | export * from './create-plugin.js' 4 | 5 | export * from './utils.js' 6 | 7 | export { z as schema } from 'zod' 8 | 9 | export type NoData = { [index in string]?: unknown } -------------------------------------------------------------------------------- /src/renderer/utils/fmt.ts: -------------------------------------------------------------------------------- 1 | export const fmt = { 2 | param: (value: string, variant?: 'primary' | 'secondary' | undefined, ifEmpty: string = "") => { 3 | return `${value ? value : ifEmpty}` 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/shared/libs/migration/index.ts: -------------------------------------------------------------------------------- 1 | export * from './models/createMigration'; 2 | export * from './models/createMigrator'; 3 | export type { 4 | MigrationSchema, 5 | Migrator, 6 | MigratorConfig, 7 | MigrationFn, 8 | SemVer, 9 | } from './models/migration'; 10 | -------------------------------------------------------------------------------- /src/auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | export {} 7 | declare global { 8 | const Steps: typeof import('primevue/steps')['Steps'] 9 | } 10 | -------------------------------------------------------------------------------- /docs/expression.md: -------------------------------------------------------------------------------- 1 | # Expressions 2 | An expression is of type string that can include any data 3 | 4 | - a number: 5 | 6 | `12` 7 | 8 | - a string 9 | 10 | `"12"` 11 | 12 | - an array 13 | 14 | `[ "a", "b", 12 ]` 15 | 16 | - a custom expression 17 | 18 | `System.Step('aaa') + 12` -------------------------------------------------------------------------------- /src/renderer/plugins/posthog.ts: -------------------------------------------------------------------------------- 1 | //./plugins/posthog.js 2 | import posthog from 'posthog-js' 3 | 4 | export default { 5 | install(app: any) { 6 | app.config.globalProperties.$posthog = posthog.init(__POSTHOG_API_KEY__, { 7 | api_host: 'https://eu.i.posthog.com' 8 | }) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-tauri/fixtures/build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |

Hello

10 | 11 | -------------------------------------------------------------------------------- /src/renderer/components/__tests__/HelloWorld.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | 3 | describe('HelloWorld', () => { 4 | it('renders properly', () => { 5 | // const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } }) 6 | // expect(wrapper.text()).toContain('Hello Vitest') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-electron/fixtures/build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |

Hello

10 | 11 | -------------------------------------------------------------------------------- /src/renderer/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any> 7 | export default component 8 | } 9 | -------------------------------------------------------------------------------- /tests/e2e/fixtures/folder-to-electron/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test 7 | 8 | 9 | OK 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/e2e/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | root: 'tests/e2e', 6 | environment: 'node', 7 | reporters: process.env.GITHUB_ACTIONS 8 | ? ['hanging-process', 'github-actions', 'default'] 9 | : ['hanging-process', 'default'], 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /src/renderer/Root.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 15 | 16 | 19 | -------------------------------------------------------------------------------- /scripts/headless-electron-tester.mts: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa' 2 | import { join } from 'path' 3 | import { cwd, stdin } from 'process' 4 | 5 | await execa({ 6 | cwd: join(import.meta.dirname, '..'), 7 | stdin: 'inherit', 8 | stdout: 'inherit', 9 | stderr: 'inherit' 10 | })`./assets/electron/template/app/node_modules/.bin/electron-forge start -- --headless --port=3000` 11 | -------------------------------------------------------------------------------- /assets/tauri/template/app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/renderer/agent/trpc.ts: -------------------------------------------------------------------------------- 1 | import { initTRPC } from '@trpc/server'; 2 | 3 | /** 4 | * Initialization of tRPC backend 5 | * Should be done only once per backend! 6 | */ 7 | const t = initTRPC.create(); 8 | 9 | /** 10 | * Export reusable router and procedure helpers 11 | * that can be used throughout the router 12 | */ 13 | export const router = t.router; 14 | export const publicProcedure = t.procedure; -------------------------------------------------------------------------------- /src/shared/libs/migration/utils/object-keys.ts: -------------------------------------------------------------------------------- 1 | export type ObjectKeys = `${Exclude}` 2 | 3 | export const objectKeys = Object.keys as ( 4 | value: Type 5 | ) => Array> 6 | export const objectEntries = Object.entries as >( 7 | value: Type 8 | ) => Array<[ObjectKeys, Type[ObjectKeys]]> 9 | -------------------------------------------------------------------------------- /assets/tauri/template/app/README.md: -------------------------------------------------------------------------------- 1 | # Tauri + Vanilla TS 2 | 3 | This template should help get you started developing with Tauri in vanilla HTML, CSS and Typescript. 4 | 5 | ## Recommended IDE Setup 6 | 7 | - [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 8 | -------------------------------------------------------------------------------- /src/shared/supabase.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '@supabase/supabase-js' 2 | import { Database } from './database.types' 3 | 4 | // ensure supabase env variables are available at build time 5 | if (!__SUPABASE_URL__ || !__SUPABASE_ANON_KEY__) { 6 | throw new Error('Supabase environment variables are not configured.') 7 | } 8 | 9 | export const supabase = createClient(__SUPABASE_URL__, __SUPABASE_ANON_KEY__) 10 | -------------------------------------------------------------------------------- /src/renderer/components/AddNodeButton.model.ts: -------------------------------------------------------------------------------- 1 | import { PipelabNode, Event, RendererPluginDefinition } from '../../shared/libs/plugin-core' 2 | 3 | export interface AddNodeEvent { 4 | node: PipelabNode 5 | plugin: RendererPluginDefinition 6 | path: string[] 7 | insertAt: number 8 | } 9 | 10 | export interface AddTriggerEvent { 11 | trigger: Event 12 | plugin: RendererPluginDefinition 13 | path: string[] 14 | insertAt: number 15 | } 16 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-tauri/package.ts: -------------------------------------------------------------------------------- 1 | import { createActionRunner } from '@pipelab/plugin-core' 2 | import { createPackageProps, forge } from './forge' 3 | 4 | export const packageRunner = createActionRunner>( 5 | async (options) => { 6 | const appFolder = options.inputs['input-folder'] 7 | 8 | // @ts-expect-error options is not really compatible 9 | await forge('package', appFolder, options) 10 | } 11 | ) 12 | -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/capabilities/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "default", 4 | "description": "Capability for the main window", 5 | "windows": [ 6 | "main" 7 | ], 8 | "permissions": [ 9 | "core:default", 10 | "shell:allow-open", 11 | { 12 | "identifier": "fs:scope", 13 | "allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**" }, { "path": "$RUNTIME" }] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /assets/build/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.allow-dyld-environment-variables 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-nvpatch/index.ts: -------------------------------------------------------------------------------- 1 | import { NVPatch, NVPatchRunner } from './nvpatch' 2 | 3 | import { createNodeDefinition } from '@pipelab/plugin-core' 4 | 5 | export default createNodeDefinition({ 6 | description: 'NVPatch', 7 | name: 'NVPatch', 8 | id: 'nv-patch', 9 | icon: { 10 | type: 'icon', 11 | icon: 'mdi-wrench' 12 | }, 13 | nodes: [ 14 | // make and package 15 | { 16 | node: NVPatch, 17 | runner: NVPatchRunner 18 | } 19 | ] 20 | }) 21 | -------------------------------------------------------------------------------- /src/shared/validation.ts: -------------------------------------------------------------------------------- 1 | import { InputDefinition } from "@pipelab/plugin-core" 2 | 3 | export const isRequired = (param: InputDefinition) => { 4 | return param.required === true 5 | } 6 | 7 | export const isRenderer = () => { 8 | // running in a web browser 9 | if (typeof process === 'undefined') return true 10 | 11 | // node-integration is disabled 12 | if (!process) return true 13 | 14 | // @ts-expect-error 15 | return process.browser === true || process.title === 'browser' 16 | }; 17 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Main Process", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceFolder}", 9 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", 10 | "windows": { 11 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" 12 | }, 13 | "args" : ["."], 14 | "outputCapture": "std" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/renderer/agent/log.ts: -------------------------------------------------------------------------------- 1 | // import { nanoid } from 'nanoid' 2 | // import { useMessaging } from './ws' 3 | 4 | // export const useWSLogger = () => { 5 | // const messaging = useMessaging() 6 | 7 | // const log = (...args: any[]) => { 8 | // console.log(...args) 9 | // messaging.send({ 10 | // type: "log", 11 | // data: args[0], 12 | // id: nanoid() 13 | // }) 14 | // } 15 | 16 | // return { 17 | // log, 18 | // } 19 | // } 20 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-steam/index.ts: -------------------------------------------------------------------------------- 1 | import { uploadToSteam, uploadToSteamRunner } from './upload-to-steam.js' 2 | import { createNodeDefinition } from '@pipelab/plugin-core' 3 | import icon from './steam.webp' 4 | 5 | export default createNodeDefinition({ 6 | description: 'Steam', 7 | id: 'steam', 8 | name: 'Steam', 9 | icon: { 10 | type: 'image', 11 | image: icon 12 | }, 13 | nodes: [ 14 | { 15 | node: uploadToSteam, 16 | runner: uploadToSteamRunner 17 | } 18 | ] 19 | }) 20 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json", 3 | "_changelog": [ 4 | "@changesets/changelog-github", 5 | { "repo": "CynToolkit/pipelab" } 6 | ], 7 | "privatePackages": { 8 | "tag": true, 9 | "version": true 10 | }, 11 | "changelog": "@changesets/changelog-git", 12 | "commit": false, 13 | "access": "public", 14 | "fixed": [], 15 | "linked": [], 16 | "baseBranch": "main", 17 | "updateInternalDependencies": "patch", 18 | "ignore": [] 19 | } 20 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-poki/index.ts: -------------------------------------------------------------------------------- 1 | import { uploadToPoki, uploadToPokiRunner } from './export' 2 | 3 | import { createNodeDefinition } from '@pipelab/plugin-core' 4 | import icon from './assets/poki-icon.webp' 5 | 6 | export default createNodeDefinition({ 7 | description: 'Poki', 8 | name: 'Poki', 9 | id: 'poki', 10 | icon: { 11 | type: 'image', 12 | image: icon 13 | }, 14 | nodes: [ 15 | // make and package 16 | { 17 | node: uploadToPoki, 18 | runner: uploadToPokiRunner 19 | } 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-itch/index.ts: -------------------------------------------------------------------------------- 1 | import { uploadToItch, uploadToItchRunner } from './export' 2 | 3 | import { createNodeDefinition } from '@pipelab/plugin-core' 4 | import icon from './assets/itch-icon.webp' 5 | 6 | export default createNodeDefinition({ 7 | description: 'Itch.io', 8 | name: 'Itch.io', 9 | id: 'itch.io', 10 | icon: { 11 | type: 'image', 12 | image: icon 13 | }, 14 | nodes: [ 15 | // make and package 16 | { 17 | node: uploadToItch, 18 | runner: uploadToItchRunner 19 | } 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /assets/tauri/template/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tauri", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "tauri": "tauri" 11 | }, 12 | "dependencies": { 13 | "@tauri-apps/api": "^2", 14 | "@tauri-apps/plugin-fs": "~2", 15 | "@tauri-apps/plugin-shell": "^2" 16 | }, 17 | "devDependencies": { 18 | "@tauri-apps/cli": "^2", 19 | "typescript": "^5.2.2", 20 | "vite": "^5.3.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-system/manual.ts: -------------------------------------------------------------------------------- 1 | import { createEvent, createEventRunner } from "@pipelab/plugin-core"; 2 | 3 | export const ID = "manual"; 4 | 5 | export type Data = {}; 6 | 7 | export const manualEvent = createEvent({ 8 | id: ID, 9 | name: "Manual", 10 | description: "Start a pipeline manually", 11 | displayString: "'Start the pipeline manually'", 12 | icon: "", 13 | meta: {}, 14 | params: {}, 15 | outputs: {}, 16 | }); 17 | 18 | export const manualEvaluator = createEventRunner(async () => { 19 | return; 20 | }); 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/general/engine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | */ 5 | export default async (json, ws) => { 6 | /** 7 | * @type {import('@pipelab/core').MakeInputOutput} 8 | */ 9 | const engineResult = { 10 | correlationId: json.correlationId, 11 | url: json.url, 12 | body: { 13 | engine: 'electron' 14 | } 15 | } 16 | ws.send(JSON.stringify(engineResult)); 17 | } 18 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/utils.js: -------------------------------------------------------------------------------- 1 | import pkg from '../package.json' with { type: "json" }; 2 | 3 | /** 4 | * @param {ElectronAppConfig.Config} config 5 | */ 6 | export const getAppName = (config) => { 7 | const platform = process.platform 8 | 9 | let appNameFolder = config.name 10 | if (platform === 'win32') { 11 | appNameFolder = config.name ?? pkg.productName ?? pkg.name 12 | } else if (platform === 'darwin') { 13 | appNameFolder = config.appBundleId 14 | } else if (platform === 'linux') { 15 | appNameFolder = config.appBundleId 16 | } 17 | return appNameFolder 18 | } 19 | -------------------------------------------------------------------------------- /vitest.config.mts: -------------------------------------------------------------------------------- 1 | import { configDefaults, defineConfig, mergeConfig } from 'vitest/config' 2 | import renderer from './vite.renderer.config.mjs' 3 | 4 | export default defineConfig((configEnv) => { 5 | const baseConfig = renderer({ 6 | ...configEnv, 7 | // @ts-expect-error forgeConfigSelf 8 | forgeConfigSelf: { 9 | name: 'Tests' 10 | } 11 | }) 12 | 13 | return mergeConfig( 14 | baseConfig, 15 | defineConfig({ 16 | // plugins: [tsconfigPaths()], 17 | test: { 18 | exclude: [...configDefaults.exclude, 'tests/e2e/**/*.spec.ts'] 19 | } 20 | }) 21 | ) 22 | }) 23 | -------------------------------------------------------------------------------- /src/renderer/composables/variables.ts: -------------------------------------------------------------------------------- 1 | import { Variable } from '@@/libs/core-app' 2 | import { CreateQuickJSFn } from '@renderer/utils/quickjs' 3 | 4 | export const variableToFormattedVariable = async ( 5 | vm: Awaited, 6 | variables: Variable[] 7 | ) => { 8 | const result: Record = {} 9 | for (const variable of variables) { 10 | console.log('variable.value', variable.value) 11 | const variableResult = await vm.run(variable.value, { 12 | params: {} 13 | }) 14 | 15 | result[variable.id] = variableResult 16 | // result[variable.id] = variable.value 17 | } 18 | return result 19 | } 20 | -------------------------------------------------------------------------------- /assets/tauri/template/app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true 21 | }, 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require('electron') 2 | 3 | // Expose a limited API to the renderer process 4 | contextBridge.exposeInMainWorld('electronAPI', { 5 | invoke: async (channel, data) => { 6 | try { 7 | return await ipcRenderer.invoke(channel, data) 8 | } catch (error) { 9 | console.error('IPC invocation error:', error) 10 | throw error 11 | } 12 | }, 13 | isElectron: () => true, 14 | exit: (code) => { 15 | ipcRenderer.send('exit', code) 16 | } 17 | }) 18 | 19 | contextBridge.exposeInMainWorld('isElectron', true) 20 | 21 | console.log('Preload script loaded') 22 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/devcontainers/universal:2", 3 | "features": { 4 | "ghcr.io/devcontainers/features/node": { 5 | "version": "22.4" 6 | }, 7 | "ghcr.io/devcontainers-contrib/features/corepack:1": {}, 8 | "ghcr.io/devcontainers-contrib/features/tsx:1": {}, 9 | "ghcr.io/sebst/devcontainer-features/desktop-novnc:0": {}, 10 | "ghcr.io/sebst/devcontainer-features/desktop-fluxbox:0": {}, 11 | "ghcr.io/devcontainers/features/desktop-lite:1": {} 12 | }, 13 | "forwardPorts": [ 14 | 6080 15 | ], 16 | "portsAttributes": { 17 | "6080": { 18 | "label": "desktop" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/general/open.js: -------------------------------------------------------------------------------- 1 | import { shell } from 'electron' 2 | 3 | /** 4 | * @param {import('@pipelab/core').MakeInputOutput} json 5 | * @param {import('ws').WebSocket} ws 6 | */ 7 | export default async (json, ws) => { 8 | await shell.openPath(json.body.path) 9 | 10 | /** 11 | * @type {import('@pipelab/core').MakeInputOutput} 12 | */ 13 | const runResult = { 14 | correlationId: json.correlationId, 15 | url: json.url, 16 | body: { 17 | success: true, 18 | } 19 | } 20 | ws.send(JSON.stringify(runResult)); 21 | } 22 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-minify/index.ts: -------------------------------------------------------------------------------- 1 | import { minifyCode, minifyCodeRunner } from './code' 2 | import { minifyImages, minifyImagesRunner } from './images' 3 | 4 | import { createNodeDefinition } from '@pipelab/plugin-core' 5 | 6 | export default createNodeDefinition({ 7 | description: 'Minify and compress code and images', 8 | name: 'Minifyer', 9 | id: 'poki', 10 | icon: { 11 | type: 'icon', 12 | icon: 'mdi-zip-box' 13 | }, 14 | nodes: [ 15 | // make and package 16 | { 17 | node: minifyCode, 18 | runner: minifyCodeRunner 19 | }, 20 | { 21 | node: minifyImages, 22 | runner: minifyImagesRunner 23 | } 24 | ] 25 | }) 26 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/fs/delete.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { unlink } from 'node:fs/promises' 4 | 5 | /** 6 | * @param {import('@pipelab/core').MakeInputOutput} json 7 | * @param {import('ws').WebSocket} ws 8 | */ 9 | export default async (json, ws) => { 10 | await unlink(json.body.path) 11 | 12 | /** 13 | * @type {import('@pipelab/core').MakeInputOutput} 14 | */ 15 | const readFileResult = { 16 | correlationId: json.correlationId, 17 | url: json.url, 18 | body: { 19 | success: true, 20 | } 21 | } 22 | ws.send(JSON.stringify(readFileResult)) 23 | } 24 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/general/open-in-explorer.js: -------------------------------------------------------------------------------- 1 | import { shell } from 'electron' 2 | 3 | /** 4 | * @param {import('@pipelab/core').MakeInputOutput} json 5 | * @param {import('ws').WebSocket} ws 6 | */ 7 | export default async (json, ws) => { 8 | await shell.showItemInFolder(json.body.path) 9 | 10 | /** 11 | * @type {import('@pipelab/core').MakeInputOutput} 12 | */ 13 | const runResult = { 14 | correlationId: json.correlationId, 15 | url: json.url, 16 | body: { 17 | success: true, 18 | } 19 | } 20 | ws.send(JSON.stringify(runResult)); 21 | } 22 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/restore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | mainWindow.restore(); 8 | 9 | /** 10 | * @type {import('@pipelab/core').MakeInputOutput} 11 | */ 12 | const restoreResult = { 13 | correlationId: json.correlationId, 14 | url: json.url, 15 | body: { 16 | success: true 17 | } 18 | } 19 | ws.send(JSON.stringify(restoreResult)); 20 | } 21 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/maximize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | mainWindow.maximize(); 8 | 9 | /** 10 | * @type {import('@pipelab/core').MakeInputOutput} 11 | */ 12 | const maximizeResult = { 13 | correlationId: json.correlationId, 14 | url: json.url, 15 | body: { 16 | success: true 17 | } 18 | } 19 | ws.send(JSON.stringify(maximizeResult)); 20 | } 21 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/set-title.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | mainWindow.setTitle(json.body.value) 8 | 9 | /** 10 | * @type {import('@pipelab/core').MakeInputOutput} 11 | */ 12 | const setTitleResult = { 13 | correlationId: json.correlationId, 14 | url: json.url, 15 | body: { 16 | success: true 17 | } 18 | } 19 | ws.send(JSON.stringify(setTitleResult)); 20 | } 21 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/minimize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | mainWindow.minimize(); 8 | 9 | /** 10 | * @type {import('@pipelab/core').MakeInputOutput} 11 | */ 12 | const minimizeResult = { 13 | correlationId: json.correlationId, 14 | url: json.url, 15 | body: { 16 | success: true 17 | } 18 | } 19 | ws.send(JSON.stringify(minimizeResult)); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/set-resizable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | mainWindow.setResizable(true) 8 | 9 | /** 10 | * @type {import('@pipelab/core').MakeInputOutput} 11 | */ 12 | const setResizableResult = { 13 | correlationId: json.correlationId, 14 | url: json.url, 15 | body: { 16 | success: true 17 | } 18 | } 19 | ws.send(JSON.stringify(setResizableResult)); 20 | } 21 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/unmaximize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | mainWindow.unmaximize(); 8 | 9 | /** 10 | * @type {import('@pipelab/core').MakeInputOutput} 11 | */ 12 | const unmaximizeResult = { 13 | correlationId: json.correlationId, 14 | url: json.url, 15 | body: { 16 | success: true 17 | } 18 | } 19 | ws.send(JSON.stringify(unmaximizeResult)); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-electron/configure.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionRunner } from '@pipelab/plugin-core' 2 | import { configureParams } from './forge' 3 | 4 | export const props = createAction({ 5 | id: 'electron:configure', 6 | description: 'Configure electron', 7 | displayString: "'Configure Electron'", 8 | icon: '', 9 | meta: {}, 10 | name: 'Configure Electron', 11 | outputs: { 12 | configuration: { 13 | label: 'Configuration', 14 | value: {} as Partial 15 | } 16 | }, 17 | params: configureParams 18 | }) 19 | 20 | export const configureRunner = createActionRunner(async ({ setOutput, inputs }) => { 21 | setOutput('configuration', inputs) 22 | }) 23 | -------------------------------------------------------------------------------- /src/renderer/store/recents.ts: -------------------------------------------------------------------------------- 1 | import { SaveLocation } from "@@/save-location"; 2 | import { defineStore } from "pinia"; 3 | import { ref } from "vue"; 4 | 5 | export interface Recent { 6 | id: string 7 | saveLocation: SaveLocation 8 | name: string 9 | } 10 | 11 | export const useRecentsStore = defineStore('recent-files', () => { 12 | const recents = ref>({}); 13 | 14 | const addRecent = (recent: Recent) => { 15 | recents.value[recent.id] = recent 16 | } 17 | 18 | const removeRecent = (recentId: Recent['id']) => { 19 | delete recents.value[recentId] 20 | } 21 | 22 | return { 23 | recents, 24 | 25 | addRecent, 26 | removeRecent, 27 | } 28 | }, { 29 | persist: true, 30 | }) 31 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-system/log.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionRunner, createStringParam } from '@pipelab/plugin-core' 2 | 3 | export const ID = 'log' 4 | 5 | export type Data = { 6 | text: string 7 | } 8 | 9 | export const logAction = createAction({ 10 | id: ID, 11 | name: 'Log', 12 | description: 'Log a message', 13 | icon: '', 14 | displayString: '`Log "${fmt.param(params.message)}"`', 15 | meta: {}, 16 | params: { 17 | message: createStringParam('', { 18 | required: true, 19 | label: 'Message' 20 | }) 21 | }, 22 | 23 | outputs: {} 24 | }) 25 | 26 | export const logActionRunner = createActionRunner(async ({ log, inputs }) => { 27 | log(`${inputs.message ?? ''}`) 28 | }) 29 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/set-always-on-top.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | mainWindow.setAlwaysOnTop(true); 8 | 9 | /** 10 | * @type {import('@pipelab/core').MakeInputOutput} 11 | */ 12 | const setAlwaysOnTopResult = { 13 | correlationId: json.correlationId, 14 | url: json.url, 15 | body: { 16 | success: true 17 | } 18 | } 19 | ws.send(JSON.stringify(setAlwaysOnTopResult)); 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/pages/team.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | 39 | -------------------------------------------------------------------------------- /src/renderer/utils/graph.ts: -------------------------------------------------------------------------------- 1 | import { Block } from '@@/model' 2 | 3 | export const walker = async (graph: Array, onNode: (node: Block) => Promise) => { 4 | for (const node of graph) { 5 | /* if (node.type === 'condition') { 6 | await onNode(node) 7 | 8 | await walker(node.branchTrue, onNode) 9 | await walker(node.branchFalse, onNode) 10 | } else */if (node.type === 'action') { 11 | await onNode(node) 12 | } /* else if (node.type === 'loop') { 13 | await onNode(node) 14 | 15 | await walker(node.children, onNode) 16 | } */else if (node.type === 'comment') { 17 | await onNode(node) 18 | } else if (node.type === 'event') { 19 | await onNode(node) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-tauri/preview.ts: -------------------------------------------------------------------------------- 1 | import { createActionRunner, runWithLiveLogs } from '@pipelab/plugin-core' 2 | import { createPreviewProps, forge } from './forge' 3 | 4 | export const previewRunner = createActionRunner>( 5 | async (options) => { 6 | const url = options.inputs['input-url'] 7 | if (url === '') { 8 | throw new Error("URL can't be empty") 9 | } 10 | 11 | // @ts-expect-error options is not really compatible 12 | const output = await forge('package', undefined, options) 13 | options.log('Opening preview', output) 14 | options.log('Opening url', url) 15 | await runWithLiveLogs(output.binary, ['--url', url], {}, options.log) 16 | return 17 | } 18 | ) 19 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/fs/file-size.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { stat } from 'node:fs/promises' 4 | 5 | /** 6 | * @param {import('@pipelab/core').MakeInputOutput} json 7 | * @param {import('ws').WebSocket} ws 8 | */ 9 | export default async (json, ws) => { 10 | const stats = await stat(json.body.path) 11 | 12 | /** 13 | * @type {import('@pipelab/core').MakeInputOutput} 14 | */ 15 | const readFileResult = { 16 | correlationId: json.correlationId, 17 | url: json.url, 18 | body: { 19 | success: true, 20 | size: stats.size, 21 | } 22 | } 23 | ws.send(JSON.stringify(readFileResult)) 24 | } 25 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/request-attention.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | mainWindow.flashFrame(true); 8 | 9 | /** 10 | * @type {import('@pipelab/core').MakeInputOutput} 11 | */ 12 | const requestAttentionResult = { 13 | correlationId: json.correlationId, 14 | url: json.url, 15 | body: { 16 | success: true 17 | } 18 | } 19 | ws.send(JSON.stringify(requestAttentionResult)); 20 | } 21 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/set-x.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | const [x, y] = mainWindow.getPosition() 8 | mainWindow.setPosition(json.body.value, y); 9 | 10 | /** 11 | * @type {import('@pipelab/core').MakeInputOutput} 12 | */ 13 | const setXResult = { 14 | correlationId: json.correlationId, 15 | url: json.url, 16 | body: { 17 | success: true 18 | } 19 | } 20 | ws.send(JSON.stringify(setXResult)); 21 | } 22 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/set-y.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | const [x, y] = mainWindow.getPosition() 8 | mainWindow.setPosition(x, json.body.value); 9 | 10 | /** 11 | * @type {import('@pipelab/core').MakeInputOutput} 12 | */ 13 | const setYResult = { 14 | correlationId: json.correlationId, 15 | url: json.url, 16 | body: { 17 | success: true 18 | } 19 | } 20 | ws.send(JSON.stringify(setYResult)); 21 | } 22 | -------------------------------------------------------------------------------- /src/renderer/pages/scenarios.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | 38 | -------------------------------------------------------------------------------- /src/main/presets/newProject.ts: -------------------------------------------------------------------------------- 1 | import { PresetFn, SavedFile } from '@@/model' 2 | 3 | export const newProjectPreset: PresetFn = async () => { 4 | const startId = 'manual-start' 5 | 6 | const data: SavedFile = { 7 | version: '3.0.0', 8 | name: 'Empty project', 9 | description: 'A default project with no tasks added', 10 | variables: [], 11 | canvas: { 12 | triggers: [ 13 | { 14 | type: 'event', 15 | origin: { 16 | pluginId: 'system', 17 | nodeId: 'manual' 18 | }, 19 | uid: startId, 20 | params: {} 21 | } 22 | ], 23 | blocks: [ 24 | 25 | ] 26 | } 27 | } 28 | 29 | return { 30 | data, 31 | hightlight: true, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/fs/read-binary.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { readFile } from 'node:fs/promises' 4 | 5 | /** 6 | * @param {import('@pipelab/core').MakeInputOutput} json 7 | * @param {import('ws').WebSocket} ws 8 | */ 9 | export default async (json, ws) => { 10 | const file = await readFile(json.body.path) 11 | 12 | /** 13 | * @type {import('@pipelab/core').MakeInputOutput} 14 | */ 15 | const readFileResult = { 16 | correlationId: json.correlationId, 17 | url: json.url, 18 | body: { 19 | success: true, 20 | content: [...file], 21 | } 22 | } 23 | ws.send(JSON.stringify(readFileResult)) 24 | } 25 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/set-maximum-size.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | mainWindow.setMaximumSize(json.body.width, json.body.height) 8 | 9 | /** 10 | * @type {import('@pipelab/core').MakeInputOutput} 11 | */ 12 | const setMaximumSizeResult = { 13 | correlationId: json.correlationId, 14 | url: json.url, 15 | body: { 16 | success: true 17 | } 18 | } 19 | ws.send(JSON.stringify(setMaximumSizeResult)); 20 | } 21 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/set-minimum-size.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | mainWindow.setMinimumSize(json.body.width, json.body.height) 8 | 9 | /** 10 | * @type {import('@pipelab/core').MakeInputOutput} 11 | */ 12 | const setMinimumSizeResult = { 13 | correlationId: json.correlationId, 14 | url: json.url, 15 | body: { 16 | success: true 17 | } 18 | } 19 | ws.send(JSON.stringify(setMinimumSizeResult)); 20 | } 21 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/set-width.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | const [, height] = mainWindow.getSize() 8 | mainWindow.setSize(json.body.value, height); 9 | 10 | /** 11 | * @type {import('@pipelab/core').MakeInputOutput} 12 | */ 13 | const setWidthResult = { 14 | correlationId: json.correlationId, 15 | url: json.url, 16 | body: { 17 | success: true 18 | } 19 | } 20 | ws.send(JSON.stringify(setWidthResult)); 21 | } 22 | -------------------------------------------------------------------------------- /src/shared/libs/migration/models/createMigrator.ts: -------------------------------------------------------------------------------- 1 | import { MigratorConfig, Migrator, MigrationSchema } from './migration' 2 | 3 | export const createMigrator = < 4 | InitialState extends MigrationSchema, 5 | FinalState extends MigrationSchema 6 | >() => { 7 | return { 8 | createDefault(defaultState: InitialState): InitialState { 9 | return defaultState 10 | }, 11 | createMigrations(config: MigratorConfig): Migrator { 12 | return new Migrator(config) 13 | } 14 | } 15 | } 16 | 17 | export type MigratorFactory< 18 | InitialState extends MigrationSchema, 19 | FinalState extends MigrationSchema 20 | > = ReturnType> 21 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/set-height.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | const [width] = mainWindow.getSize() 8 | mainWindow.setSize(width, json.body.value); 9 | 10 | /** 11 | * @type {import('@pipelab/core').MakeInputOutput} 12 | */ 13 | const setHeightResult = { 14 | correlationId: json.correlationId, 15 | url: json.url, 16 | body: { 17 | success: true 18 | } 19 | } 20 | ws.send(JSON.stringify(setHeightResult)); 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "commonjs", 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "noImplicitAny": true, 9 | "baseUrl": ".", 10 | "outDir": "dist", 11 | "moduleResolution": "node", 12 | "inlineSourceMap": true, 13 | "resolveJsonModule": true, 14 | "paths": { 15 | "@@/*": [ 16 | "./src/shared/*" 17 | ], 18 | "@main/*": [ 19 | "./src/main/*" 20 | ], 21 | "@renderer/*": [ 22 | "./src/renderer/*" 23 | ], 24 | "@pipelab/*": [ 25 | "./src/shared/libs/*" 26 | ], 27 | } 28 | }, 29 | "exclude": [ 30 | "node_modules", 31 | "dist", 32 | "out", 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/fs/folder-create.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { mkdir } from 'node:fs/promises' 4 | 5 | /** 6 | * @param {import('@pipelab/core').MakeInputOutput} json 7 | * @param {import('ws').WebSocket} ws 8 | */ 9 | export default async (json, ws) => { 10 | await mkdir(json.body.path, { 11 | recursive: json.body.recursive, 12 | }) 13 | 14 | /** 15 | * @type {import('@pipelab/core').MakeInputOutput} 16 | */ 17 | const folderCreateResult = { 18 | correlationId: json.correlationId, 19 | url: json.url, 20 | body: { 21 | success: true 22 | } 23 | } 24 | ws.send(JSON.stringify(folderCreateResult)) 25 | } 26 | -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.tauri.app/config/2", 3 | "productName": "tauri", 4 | "version": "0.1.0", 5 | "identifier": "com.tauri.app", 6 | "build": { 7 | "beforeDevCommand": "pnpm dev", 8 | "devUrl": "http://localhost:1420", 9 | "beforeBuildCommand": "pnpm build", 10 | "frontendDist": "../dist" 11 | }, 12 | "app": { 13 | "withGlobalTauri": true, 14 | "windows": [], 15 | "security": { 16 | "csp": null 17 | } 18 | }, 19 | "bundle": { 20 | "active": true, 21 | "targets": "all", 22 | "icon": [ 23 | "icons/32x32.png", 24 | "icons/128x128.png", 25 | "icons/128x128@2x.png", 26 | "icons/icon.icns", 27 | "icons/icon.ico" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/dialog/save.js: -------------------------------------------------------------------------------- 1 | import { dialog } from 'electron' 2 | 3 | /** 4 | * @param {import('@pipelab/core').MakeInputOutput} json 5 | * @param {import('ws').WebSocket} ws 6 | */ 7 | export default async (json, ws) => { 8 | const saveDialogResponse = await dialog.showSaveDialog({ 9 | properties: [] 10 | }) 11 | 12 | /** 13 | * @type {import('@pipelab/core').MakeInputOutput} 14 | */ 15 | const dialogOpenResult = { 16 | correlationId: json.correlationId, 17 | url: json.url, 18 | body: { 19 | success: true, 20 | path: saveDialogResponse.filePath 21 | } 22 | } 23 | ws.send(JSON.stringify(dialogOpenResult)); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/presets/moreToCome.ts: -------------------------------------------------------------------------------- 1 | import { PresetFn, SavedFile } from '@@/model' 2 | 3 | export const moreToCome: PresetFn = async () => { 4 | const startId = 'manual-start' 5 | 6 | const data: SavedFile = { 7 | version: '3.0.0', 8 | name: 'More to come!', 9 | description: 'Do not hesitate to suggest templates you would see here', 10 | variables: [], 11 | canvas: { 12 | triggers: [ 13 | { 14 | type: 'event', 15 | origin: { 16 | pluginId: 'system', 17 | nodeId: 'manual' 18 | }, 19 | uid: startId, 20 | params: {} 21 | } 22 | ], 23 | blocks: [ 24 | 25 | ] 26 | } 27 | } 28 | 29 | return { 30 | data, 31 | disabled: true, 32 | hightlight: false 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-system/sleep.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionRunner, createNumberParam } from '@pipelab/plugin-core' 2 | 3 | export const ID = 'system:sleep' 4 | 5 | export const sleepAction = createAction({ 6 | id: ID, 7 | name: 'Wait', 8 | description: 'Wait for a given time (in milliseconds)', 9 | icon: '', 10 | displayString: '`Wait for ${fmt.param(params.duration)}ms`', 11 | meta: {}, 12 | params: { 13 | duration: createNumberParam(2000, { 14 | required: true, 15 | label: 'Duration', 16 | }) 17 | }, 18 | 19 | outputs: {} 20 | }) 21 | 22 | const sleep = (duration: number) => new Promise((resolve) => setTimeout(resolve, duration)) 23 | 24 | export const sleepActionRunner = createActionRunner(async ({ inputs }) => { 25 | await sleep(inputs.duration) 26 | }) 27 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/general/run.js: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | 3 | /** 4 | * @param {import('@pipelab/core').MakeInputOutput} json 5 | * @param {import('ws').WebSocket} ws 6 | */ 7 | export default async (json, ws) => { 8 | const exec = execa({ 9 | cwd: json.body.cwd, 10 | env: json.body.env, 11 | }) 12 | const { stderr, stdout } = await exec(json.body.command, json.body.args) 13 | 14 | /** 15 | * @type {import('@pipelab/core').MakeInputOutput} 16 | */ 17 | const execResult = { 18 | correlationId: json.correlationId, 19 | url: json.url, 20 | body: { 21 | success: true, 22 | stdout, 23 | stderr, 24 | } 25 | } 26 | ws.send(JSON.stringify(execResult)); 27 | } 28 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/show-dev-tools.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | if (json.body.value === true) { 8 | mainWindow.webContents.openDevTools() 9 | } else { 10 | mainWindow.webContents.closeDevTools() 11 | } 12 | 13 | /** 14 | * @type {import('@pipelab/core').MakeInputOutput} 15 | */ 16 | const showDevtoolsResult = { 17 | correlationId: json.correlationId, 18 | url: json.url, 19 | body: { 20 | success: true 21 | } 22 | } 23 | ws.send(JSON.stringify(showDevtoolsResult)); 24 | } 25 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/dialog/open.js: -------------------------------------------------------------------------------- 1 | import { dialog } from 'electron' 2 | 3 | /** 4 | * @param {import('@pipelab/core').MakeInputOutput} json 5 | * @param {import('ws').WebSocket} ws 6 | */ 7 | export default async (json, ws) => { 8 | const openDialogResponse = await dialog.showOpenDialog({ 9 | properties: ['openFile'] 10 | }) 11 | 12 | /** 13 | * @type {import('@pipelab/core').MakeInputOutput} 14 | */ 15 | const dialogOpenResult = { 16 | correlationId: json.correlationId, 17 | url: json.url, 18 | body: { 19 | success: true, 20 | canceled: openDialogResponse.canceled, 21 | paths: openDialogResponse.filePaths 22 | } 23 | } 24 | ws.send(JSON.stringify(dialogOpenResult)); 25 | } 26 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/steam/raw.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {Omit} client 5 | */ 6 | export default async (json, ws, client) => { 7 | console.log('json', json) 8 | 9 | const { body } = json 10 | const { args, method, namespace } = body 11 | 12 | const result = await client[namespace][method](...args) 13 | 14 | /** 15 | * @type {import('@pipelab/core').MakeInputOutput} 16 | */ 17 | const steamResult = { 18 | url: json.url, 19 | correlationId: json.correlationId, 20 | body: { 21 | data: result, 22 | success: true 23 | } 24 | }; 25 | ws.send(JSON.stringify(steamResult)); 26 | } 27 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/dialog/folder.js: -------------------------------------------------------------------------------- 1 | import { dialog } from 'electron' 2 | 3 | /** 4 | * @param {import('@pipelab/core').MakeInputOutput} json 5 | * @param {import('ws').WebSocket} ws 6 | */ 7 | export default async (json, ws) => { 8 | const openFolderDialogResponse = await dialog.showOpenDialog({ 9 | properties: ['openDirectory', 'createDirectory'] 10 | }) 11 | 12 | /** 13 | * @type {import('@pipelab/core').MakeInputOutput} 14 | */ 15 | const dialogOpenResult = { 16 | correlationId: json.correlationId, 17 | url: json.url, 18 | body: { 19 | success: true, 20 | canceled: openFolderDialogResponse.canceled, 21 | paths: openFolderDialogResponse.filePaths 22 | } 23 | } 24 | ws.send(JSON.stringify(dialogOpenResult)) 25 | } 26 | -------------------------------------------------------------------------------- /assets/electron/template/app/config.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace ElectronAppConfig { 2 | interface Config { 3 | enableInProcessGPU: boolean 4 | enableDisableRendererBackgrounding: boolean 5 | width: number 6 | height: number 7 | fullscreen: boolean 8 | frame: boolean 9 | customMainCode: string 10 | transparent: boolean 11 | toolbar: boolean 12 | alwaysOnTop: boolean 13 | name: string 14 | appBundleId: string 15 | appCopyright: string 16 | appVersion: string 17 | author: string 18 | description: string 19 | electronVersion: string 20 | appCategoryType: string 21 | icon: string 22 | disableAsarPackaging: boolean 23 | forceHighPerformanceGpu: boolean 24 | enableExtraLogging: boolean 25 | clearServiceWorkerOnBoot: boolean 26 | enableSteamSupport: boolean 27 | ignore: (string | RegExp)[] 28 | steamGameId: number 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /assets/tauri/template/app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | 3 | // @ts-expect-error process is a nodejs global 4 | const host = process.env.TAURI_DEV_HOST; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig(async () => ({ 8 | 9 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 10 | // 11 | // 1. prevent vite from obscuring rust errors 12 | clearScreen: false, 13 | // 2. tauri expects a fixed port, fail if that port is not available 14 | server: { 15 | port: 1420, 16 | strictPort: true, 17 | host: host || false, 18 | hmr: host 19 | ? { 20 | protocol: "ws", 21 | host, 22 | port: 1421, 23 | } 24 | : undefined, 25 | watch: { 26 | // 3. tell vite to ignore watching `src-tauri` 27 | ignored: ["**/src-tauri/**"], 28 | }, 29 | }, 30 | })); 31 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/fs/copy.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { cp, mkdir } from 'node:fs/promises' 4 | import { dirname } from 'node:path' 5 | 6 | /** 7 | * @param {import('@pipelab/core').MakeInputOutput} json 8 | * @param {import('ws').WebSocket} ws 9 | */ 10 | export default async (json, ws) => { 11 | const destDirName = dirname(json.body.destination) 12 | await mkdir(destDirName, { recursive: true }) 13 | 14 | await cp(json.body.source, json.body.destination, { 15 | force: json.body.overwrite 16 | }) 17 | 18 | /** 19 | * @type {import('@pipelab/core').MakeInputOutput} 20 | */ 21 | const readFileResult = { 22 | correlationId: json.correlationId, 23 | url: json.url, 24 | body: { 25 | success: true, 26 | } 27 | } 28 | ws.send(JSON.stringify(readFileResult)) 29 | } 30 | -------------------------------------------------------------------------------- /src/renderer/components/nodes/EditorNodeDummy.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 27 | 28 | 46 | -------------------------------------------------------------------------------- /src/renderer/store/settings.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { useAPI } from '@renderer/composables/api' 3 | import { AppConfig } from '@main/config' 4 | import { readonly, ref } from 'vue' 5 | 6 | export const useAppSettings = defineStore('settings', () => { 7 | const api = useAPI() 8 | const settings = ref() 9 | 10 | const init = async () => { 11 | const result = await api.execute('settings:load') 12 | if (result.type === 'error') { 13 | console.error(result) 14 | } else { 15 | settings.value = result.result.result 16 | } 17 | } 18 | 19 | const updateSettings = async (_settings: AppConfig) => { 20 | settings.value = _settings 21 | const result = await api.execute('settings:save', _settings) 22 | if (result.type === 'error') { 23 | console.error(result) 24 | } 25 | } 26 | 27 | return { init, updateSettings, settings: readonly(settings) } 28 | }) 29 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/window/set-fullscreen.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('@pipelab/core').MakeInputOutput} json 3 | * @param {import('ws').WebSocket} ws 4 | * @param {import('electron').BrowserWindow} mainWindow 5 | */ 6 | export default async (json, ws, mainWindow) => { 7 | if (json.body.value === 'fullscreen') { 8 | mainWindow.setFullScreen(true) 9 | } else if (json.body.value === 'normal') { 10 | mainWindow.setFullScreen(false) 11 | } else { 12 | throw new Error('Unsupported value ') 13 | } 14 | 15 | /** 16 | * @type {import('@pipelab/core').MakeInputOutput} 17 | */ 18 | const showsetFullscreenResult = { 19 | correlationId: json.correlationId, 20 | url: json.url, 21 | body: { 22 | success: true 23 | } 24 | } 25 | ws.send(JSON.stringify(showsetFullscreenResult)); 26 | } 27 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-filesystem/unzip.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | import { unzipRunner } from './unzip.js' 3 | import { browserWindow } from '@@/tests/helpers.js' 4 | 5 | test('adds 1 + 2 to equal 3', async () => { 6 | const outputs: Record = {} 7 | // await unzipRunner({ 8 | // inputs: { 9 | // file: '' 10 | // }, 11 | // log: (...args) => { 12 | // console.log(...args) 13 | // }, 14 | // setOutput: (key, value) => { 15 | // outputs[key] = value 16 | // }, 17 | // meta: { 18 | // definition: '' 19 | // }, 20 | // setMeta: () => { 21 | // console.log('set meta defined here') 22 | // }, 23 | // cwd: '', 24 | // paths: { 25 | // assets: '', 26 | // unpack: '' 27 | // }, 28 | // api: undefined, 29 | // browserWindow, 30 | // }) 31 | console.log('outputs', outputs) 32 | expect(true).toBe(true) 33 | }, 120_000) 34 | -------------------------------------------------------------------------------- /src/renderer/components/nodes/PluginIcon.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/shared/logger.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from 'tslog' 2 | import { isRenderer } from './validation' 3 | import { usePluginAPI } from '@main/api' 4 | import { BrowserWindow } from 'electron' 5 | 6 | const createDefaultLogger = () => 7 | new Logger({ 8 | minLevel: 3, // INFO 9 | hideLogPositionForProduction: false 10 | }) 11 | 12 | let _logger = createDefaultLogger() 13 | // let _mainWindow: BrowserWindow | undefined = undefined 14 | 15 | export const useLogger = () => { 16 | const setMainWindow = (mainWindow: BrowserWindow) => { 17 | // _mainWindow = mainWindow 18 | _logger = createDefaultLogger() 19 | 20 | // in main, send logs to renderer 21 | if (!isRenderer()) { 22 | _logger.attachTransport((logObj) => { 23 | const api = usePluginAPI(mainWindow) 24 | api.execute('log:message', logObj) 25 | }) 26 | } 27 | } 28 | 29 | return { 30 | logger: () => _logger, 31 | setMainWindow 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/fs/move.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { moveFile } from 'move-file'; 4 | import { mkdir } from 'node:fs/promises'; 5 | import { dirname } from 'node:path'; 6 | 7 | /** 8 | * @param {import('@pipelab/core').MakeInputOutput} json 9 | * @param {import('ws').WebSocket} ws 10 | */ 11 | export default async (json, ws) => { 12 | const destDirName = dirname(json.body.destination) 13 | await mkdir(destDirName, { recursive: true }) 14 | 15 | await moveFile(json.body.source, json.body.destination, { 16 | overwrite: json.body.overwrite 17 | }) 18 | 19 | /** 20 | * @type {import('@pipelab/core').MakeInputOutput} 21 | */ 22 | const readFileResult = { 23 | correlationId: json.correlationId, 24 | url: json.url, 25 | body: { 26 | success: true, 27 | } 28 | } 29 | ws.send(JSON.stringify(readFileResult)) 30 | } 31 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/fs/write.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { mkdir, writeFile } from 'node:fs/promises' 4 | import { dirname } from 'node:path' 5 | 6 | /** 7 | * @param {import('@pipelab/core').MakeInputOutput} json 8 | * @param {import('ws').WebSocket} ws 9 | */ 10 | export default async (json, ws) => { 11 | const destDirName = dirname(json.body.path) 12 | await mkdir(destDirName, { recursive: true }) 13 | 14 | await writeFile(json.body.path, json.body.contents, { 15 | encoding: json.body.encoding, 16 | flag: json.body.flag 17 | }) 18 | 19 | /** 20 | * @type {import('@pipelab/core').MakeInputOutput} 21 | */ 22 | const writeFileResult = { 23 | correlationId: json.correlationId, 24 | url: json.url, 25 | body: { 26 | success: true 27 | } 28 | } 29 | ws.send(JSON.stringify(writeFileResult)) 30 | } 31 | -------------------------------------------------------------------------------- /assets/electron/template/app/config.cjs: -------------------------------------------------------------------------------- 1 | // THIS IS STUB DEFINITION 2 | // WILL BE REPLACED ON BUILD 3 | 4 | /** @type {ElectronAppConfig.Config} */ 5 | const config = { 6 | alwaysOnTop: false, 7 | appBundleId: 'com.pipelab.app', 8 | appCategoryType: '', 9 | appCopyright: 'Copyright © 2024 Pipelab', 10 | appVersion: '1.0.0', 11 | author: 'Pipelab', 12 | customMainCode: '', 13 | description: 'A simple Electron application', 14 | electronVersion: '', 15 | disableAsarPackaging: true, 16 | forceHighPerformanceGpu: false, 17 | enableExtraLogging: false, 18 | clearServiceWorkerOnBoot: false, 19 | enableDisableRendererBackgrounding: false, 20 | enableInProcessGPU: false, 21 | frame: true, 22 | fullscreen: false, 23 | icon: '', 24 | height: 600, 25 | name: 'Pipelab', 26 | toolbar: true, 27 | transparent: false, 28 | width: 800, 29 | enableSteamSupport: false, 30 | ignore: [], 31 | steamGameId: 480 32 | } 33 | 34 | module.exports = config 35 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-filesystem/is-file.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createCondition, 3 | createConditionRunner, 4 | } from "@pipelab/plugin-core"; 5 | 6 | export const ID = "is-file"; 7 | 8 | export const isFileCondition = createCondition({ 9 | id: ID, 10 | icon: "", 11 | name: "Is file", 12 | description: "", 13 | displayString: "`If ${params.path} is a file`", 14 | params: { 15 | path: { 16 | value: '', 17 | label: "Path", 18 | control: { 19 | type: 'input', 20 | options: { 21 | kind: 'text' 22 | } 23 | } 24 | }, 25 | }, 26 | }); 27 | 28 | export const isFileRunner = createConditionRunner< 29 | typeof isFileCondition 30 | >(async ({ log, inputs }) => { 31 | const fs = await import('node:fs/promises') 32 | 33 | const path = inputs.path; 34 | 35 | log('path', path) 36 | 37 | const stats = await fs.stat(path) 38 | 39 | return stats.isFile() 40 | }); 41 | -------------------------------------------------------------------------------- /tests/e2e/fixtures/folder-to-electron.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "", 3 | "name": "Folder to Electron", 4 | "variables": [], 5 | "canvas": { 6 | "triggers": [ 7 | { 8 | "type": "event", 9 | "origin": { 10 | "pluginId": "system", 11 | "nodeId": "manual" 12 | }, 13 | "uid": "manual-start", 14 | "params": {} 15 | } 16 | ], 17 | "blocks": [ 18 | { 19 | "uid": "electron-package-node", 20 | "type": "action", 21 | "origin": { 22 | "nodeId": "electron:package", 23 | "pluginId": "electron" 24 | }, 25 | "params": { 26 | "configuration": { 27 | "editor": "editor", 28 | "value": "{}" 29 | }, 30 | "input-folder": { 31 | "editor": "editor", 32 | "value": "\"./tests/e2e/fixtures/folder-to-electron\"" 33 | } 34 | } 35 | } 36 | ] 37 | }, 38 | "version": "3.0.0" 39 | } 40 | -------------------------------------------------------------------------------- /src/main/paths.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const unpackPath = async () => { 4 | const { join } = await import('path') 5 | const { app } = await import('electron') 6 | const _unpackPath = 7 | !process.env.NODE_ENV || process.env.NODE_ENV === 'production' 8 | ? app.getAppPath() // Live Mode 9 | : process.cwd() // Dev Mode 10 | 11 | return join(_unpackPath) 12 | } 13 | 14 | export const assetsPath = async () => { 15 | const { app } = await import('electron') 16 | const { join } = await import('path') 17 | const _assetsPath = 18 | !process.env.NODE_ENV || process.env.NODE_ENV === 'production' 19 | ? join(app.getAppPath(), '..') // Live Mode 20 | : process.cwd() // Dev Mode 21 | return join(_assetsPath, 'assets') 22 | } 23 | 24 | export const dirname = async () => { 25 | const { join } = await import('path') 26 | const _dirname = 27 | !process.env.NODE_ENV || process.env.NODE_ENV === 'production' 28 | ? __dirname // Live Mode 29 | : __dirname // Dev Mode 30 | return join(_dirname) 31 | } 32 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-tauri/configureV2.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionRunner } from '@pipelab/plugin-core' 2 | import { TauriConfiguration } from './model' 3 | 4 | export const propsConfigureV2 = createAction({ 5 | id: 'electron:configure:v2', 6 | description: 'Configure electron (v2)', 7 | displayString: "'Configure Electron'", 8 | icon: '', 9 | meta: {}, 10 | name: 'Configure Electron (v2)', 11 | outputs: { 12 | configuration: { 13 | label: 'Configuration', 14 | value: {} as TauriConfiguration 15 | } 16 | }, 17 | params: { 18 | configuration: { 19 | label: 'Configuration', 20 | value: {} as TauriConfiguration, 21 | description: 'The configuration of Electron', 22 | control: { 23 | type: 'electron:configure:v2' 24 | } 25 | } 26 | } 27 | }) 28 | 29 | export const configureV2Runner = createActionRunner( 30 | async ({ setOutput, inputs }) => { 31 | setOutput('configuration', inputs.configuration) 32 | } 33 | ) 34 | -------------------------------------------------------------------------------- /src/renderer/composables/middleware.ts: -------------------------------------------------------------------------------- 1 | import type { Awaitable } from '@vueuse/core' 2 | import { nanoid } from 'nanoid' 3 | import type { Ref } from 'vue' 4 | import { ref } from 'vue' 5 | 6 | export const useMiddleware = Awaitable>() => { 7 | type Handler = { 8 | id: string 9 | fn: HANDLER 10 | } 11 | 12 | const handlers = ref([]) as unknown as Ref 13 | 14 | const handlerFn = async (...args: Parameters) => { 15 | for (const handler of handlers.value) { 16 | await handler.fn(...args) 17 | } 18 | } 19 | 20 | const cancel = (id: string) => { 21 | handlers.value = handlers.value.filter(h => h.id !== id) 22 | } 23 | 24 | const use = (callback: HANDLER) => { 25 | const id = nanoid() 26 | 27 | handlers.value.push({ 28 | id, 29 | fn: callback, 30 | }) 31 | 32 | return { 33 | cancel: () => cancel(id), 34 | } 35 | } 36 | 37 | return { 38 | handler: handlerFn, 39 | use, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/renderer/router/router.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, RouterOptions } from 'vue-router' 2 | 3 | const routes: RouterOptions['routes'] = [ 4 | { 5 | path: '/', 6 | name: 'Home', 7 | redirect: "Dashboard", 8 | }, 9 | { 10 | path: '/dashboard', 11 | component: () => import('../pages/index.vue'), 12 | name: 'Dashboard', 13 | }, 14 | { 15 | path: '/scenarios', 16 | name: 'Scenarios', 17 | component: () => import('../pages/scenarios.vue'), 18 | children: [ 19 | 20 | ] 21 | }, 22 | { 23 | path: '/scenarios/editor/:id', 24 | name: 'Editor', 25 | component: () => import('../pages/editor.vue'), 26 | }, 27 | { 28 | path: '/billing', 29 | name: 'Billing', 30 | component: () => import('../pages/editor.vue'), 31 | }, 32 | { 33 | path: '/team', 34 | name: 'Team', 35 | component: () => import('../pages/team.vue'), 36 | }, 37 | ] 38 | 39 | export const router = createRouter({ 40 | history: createWebHashHistory(), 41 | routes 42 | }) 43 | -------------------------------------------------------------------------------- /src/renderer/composables/handlers.ts: -------------------------------------------------------------------------------- 1 | // renderer handler 2 | 3 | import { useLogger } from "@@/logger"; 4 | import { RendererChannels, RendererEvents, RendererData, RendererMessage } from "@main/api"; 5 | 6 | export type HandleListenerRendererSendFn = (events: RendererEvents) => void 7 | 8 | export type HandleListenerRenderer = ( 9 | event: Electron.IpcRendererEvent, 10 | data: { value: RendererData; send: HandleListenerRendererSendFn } 11 | ) => Promise 12 | 13 | export const handle = (channel: KEY, listener: HandleListenerRenderer) => { 14 | return window.electron.ipcRenderer.on(channel, (event, message: RendererMessage) => { 15 | const { data, requestId } = message 16 | 17 | const send: HandleListenerRendererSendFn = (events) => { 18 | return window.electron.ipcRenderer.send(requestId, events) 19 | } 20 | 21 | return listener(event, { 22 | send, 23 | value: data 24 | }) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-construct/index.ts: -------------------------------------------------------------------------------- 1 | import { createNodeDefinition } from '@pipelab/plugin-core' 2 | import { exportAction, ExportActionRunner } from './export-c3p.js' 3 | import { exportProjectAction, ExportProjectActionRunner } from './export-project.js' 4 | import icon from './assets/construct.webp' 5 | import { constructVersionValidator } from './export-shared' 6 | 7 | export default createNodeDefinition({ 8 | description: 'Construct', 9 | name: 'Construct', 10 | id: 'construct', 11 | icon: { 12 | type: 'image', 13 | image: icon 14 | }, 15 | nodes: [ 16 | { 17 | node: exportAction, 18 | runner: ExportActionRunner 19 | }, 20 | { 21 | node: exportProjectAction, 22 | runner: ExportProjectActionRunner 23 | } 24 | ], 25 | validators: [ 26 | // { 27 | // id: 'construct-version', 28 | // description: 'Version must be a valid semver', 29 | // validator: constructVersionValidator 30 | // } 31 | ] 32 | }) 33 | 34 | export type { Params as ExportParams } from './export-c3p.js' 35 | -------------------------------------------------------------------------------- /assets/build/notarize.cjs: -------------------------------------------------------------------------------- 1 | const { notarize } = require('@electron/notarize') 2 | 3 | module.exports = async (context) => { 4 | if (process.platform !== 'darwin') return 5 | 6 | console.log('aftersign hook triggered, start to notarize app.') 7 | 8 | if (!process.env.CI) { 9 | console.log(`skipping notarizing, not in CI.`) 10 | return 11 | } 12 | 13 | if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) { 14 | console.warn('skipping notarizing, APPLE_ID and APPLE_ID_PASS env variables must be set.') 15 | return 16 | } 17 | 18 | const appId = 'com.electron.app' 19 | 20 | const { appOutDir } = context 21 | 22 | const appName = context.packager.appInfo.productFilename 23 | 24 | try { 25 | await notarize({ 26 | appBundleId: appId, 27 | appPath: `${appOutDir}/${appName}.app`, 28 | appleId: process.env.APPLE_ID, 29 | appleIdPassword: process.env.APPLEIDPASS 30 | }) 31 | } catch (error) { 32 | console.error(error) 33 | } 34 | 35 | console.log(`done notarizing ${appId}.`) 36 | } 37 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-construct/export.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | import { ExportActionRunner } from './export-c3p.js' 3 | import { browserWindow } from '@@/tests/helpers.js' 4 | 5 | test('adds 1 + 2 to equal 3', async () => { 6 | const outputs: Record = {} 7 | // await ExportActionRunner({ 8 | // inputs: { 9 | // password: '123', 10 | // headless: false, 11 | // username: 'abc', 12 | // version: '350', 13 | // file: '' 14 | // }, 15 | // log: (...args) => { 16 | // console.log(...args) 17 | // }, 18 | // setOutput: (key, value) => { 19 | // outputs[key] = value 20 | // }, 21 | // meta: { 22 | // definition: '' 23 | // }, 24 | // setMeta: () => { 25 | // console.log('set meta defined here') 26 | // }, 27 | // cwd: '', 28 | // paths: { 29 | // assets: '', 30 | // unpack: '' 31 | // }, 32 | // api: undefined, 33 | // browserWindow 34 | // }) 35 | console.log('outputs', outputs) 36 | expect(true).toBe(true) 37 | }, 120_000) 38 | -------------------------------------------------------------------------------- /vite.preload.config.ts: -------------------------------------------------------------------------------- 1 | import type { ConfigEnv, UserConfig } from 'vite'; 2 | import { defineConfig, mergeConfig } from 'vite'; 3 | import { getBuildConfig, external, pluginHotRestart } from './vite.base.config'; 4 | 5 | // https://vitejs.dev/config 6 | export default defineConfig((env) => { 7 | const forgeEnv = env as ConfigEnv<'build'>; 8 | const { forgeConfigSelf } = forgeEnv; 9 | const config: UserConfig = { 10 | build: { 11 | rollupOptions: { 12 | external, 13 | // Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`. 14 | input: forgeConfigSelf.entry!, 15 | output: { 16 | format: 'cjs', 17 | // It should not be split chunks. 18 | inlineDynamicImports: true, 19 | entryFileNames: '[name].js', 20 | chunkFileNames: '[name].js', 21 | assetFileNames: '[name].[ext]', 22 | }, 23 | }, 24 | }, 25 | plugins: [pluginHotRestart('reload')], 26 | }; 27 | 28 | return mergeConfig(getBuildConfig(forgeEnv), config); 29 | }); 30 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-minify/images.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionRunner, createPathParam } from '@pipelab/plugin-core' 2 | 3 | export const ID = 'minify:images' 4 | 5 | export const minifyImages = createAction({ 6 | id: ID, 7 | name: 'Minify images', 8 | description: '', 9 | icon: '', 10 | displayString: '`Minify`', 11 | meta: {}, 12 | params: { 13 | 'input-folder': createPathParam('', { 14 | required: true, 15 | label: 'Folder to Upload', 16 | control: { 17 | type: 'path', 18 | options: { 19 | properties: ['openDirectory'] 20 | } 21 | } 22 | }) 23 | }, 24 | outputs: {} 25 | }) 26 | 27 | export const minifyImagesRunner = createActionRunner( 28 | async ({ log, inputs, cwd, abortSignal }) => { 29 | const { app } = await import('electron') 30 | const { join, dirname } = await import('node:path') 31 | const { mkdir, access, chmod } = await import('node:fs/promises') 32 | 33 | // TODO: https://github.com/imagemin/imagemin 34 | 35 | log('Minified images') 36 | } 37 | ) 38 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/fs/exist.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { access } from 'node:fs/promises' 4 | 5 | /** 6 | * @param {import('@pipelab/core').MakeInputOutput} json 7 | * @param {import('ws').WebSocket} ws 8 | */ 9 | export default async (json, ws) => { 10 | try { 11 | await access(json.body.path) 12 | 13 | /** 14 | * @type {import('@pipelab/core').MakeInputOutput} 15 | */ 16 | const existResult = { 17 | correlationId: json.correlationId, 18 | url: json.url, 19 | body: { 20 | success: true 21 | } 22 | } 23 | ws.send(JSON.stringify(existResult)) 24 | } catch (e) { 25 | /** 26 | * @type {import('@pipelab/core').MakeInputOutput} 27 | */ 28 | const existResult = { 29 | correlationId: json.correlationId, 30 | url: json.url, 31 | body: { 32 | success: false 33 | } 34 | } 35 | ws.send(JSON.stringify(existResult)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-system/for.ts: -------------------------------------------------------------------------------- 1 | import { Meta, createLoop, createLoopRunner } from "@pipelab/plugin-core"; 2 | 3 | export const ID = "for"; 4 | 5 | export const forLoop = createLoop({ 6 | id: ID, 7 | name: "For", 8 | icon: '', 9 | description: 'A loop', 10 | outputs: { 11 | item: { 12 | value: undefined as any, 13 | label: "Item", 14 | }, 15 | }, 16 | params: { 17 | value: { 18 | value: [] as Array, 19 | label: 'Value', 20 | control: { 21 | type: 'expression', 22 | options: {} 23 | } 24 | } 25 | }, 26 | meta: { 27 | loopindex: 0 28 | }, 29 | displayString: 'Loop for every element of {{params.value}}', 30 | }); 31 | 32 | export const ForLoopRunner = createLoopRunner(async ({ log, meta, setMeta, inputs }) => { 33 | const index = meta?.loopindex 34 | const value = inputs.value 35 | 36 | log('index', index) 37 | 38 | setMeta((meta) => { 39 | return { 40 | ...meta, 41 | loopindex: index + 1 42 | } 43 | }) 44 | 45 | return value.length < index ? 'step' : 'exit'; 46 | }) 47 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/fs/list.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { readdir } from 'node:fs/promises' 4 | import { join } from 'node:path' 5 | import slash from 'slash' 6 | 7 | /** 8 | * @param {import('@pipelab/core').MakeInputOutput} json 9 | * @param {import('ws').WebSocket} ws 10 | */ 11 | export default async (json, ws) => { 12 | const file = await readdir(json.body.path, { 13 | withFileTypes: true, 14 | recursive: json.body.recursive 15 | }) 16 | 17 | console.log('file', file) 18 | 19 | /** 20 | * @type {import('@pipelab/core').MakeInputOutput} 21 | */ 22 | const readFileResult = { 23 | correlationId: json.correlationId, 24 | url: json.url, 25 | body: { 26 | success: true, 27 | list: file.map((x) => ({ 28 | type: x.isDirectory() ? 'folder' : 'file', 29 | name: x.name, 30 | parent: slash(x.parentPath), 31 | path: slash(join(x.parentPath, x.name)) 32 | })) 33 | } 34 | } 35 | ws.send(JSON.stringify(readFileResult)) 36 | } 37 | -------------------------------------------------------------------------------- /assets/tauri/template/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | Document 11 | 12 | 13 | 14 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/main/presets/list.ts: -------------------------------------------------------------------------------- 1 | // import { demoPreset } from './demo' 2 | // import { ifPreset } from './if' 3 | // import { loopPreset } from './loop' 4 | // import { testC3Unzip } from './test-c3-unzip' 5 | // import { testC3Offline } from './test-c3-offline' 6 | import { c3toSteamPreset } from './c3toSteam' 7 | import { newProjectPreset } from './newProject' 8 | import { moreToCome } from './moreToCome' 9 | 10 | export const presets = async () => { 11 | const newProjectVal = await newProjectPreset() 12 | const c3toSteamVal = await c3toSteamPreset() 13 | const moreToComeVal = await moreToCome() 14 | 15 | // const demoPresetVal = await demoPreset() 16 | // const ifPresetVal = await ifPreset() 17 | // const loopPresetVal = await loopPreset() 18 | // const testC3UnzipVal = await testC3Unzip() 19 | // const testC3OfflineVal = await testC3Offline() 20 | return { 21 | newProject: newProjectVal, 22 | c3toSteam: c3toSteamVal, 23 | moreToCome: moreToComeVal, 24 | // demo: demoPresetVal, 25 | // if: ifPresetVal, 26 | // loop: loopPresetVal, 27 | // testC3Unzip: testC3UnzipVal, 28 | // testC3Offline: testC3OfflineVal, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/renderer/pages/project-settings-editor.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 52 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-system/alert.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionRunner, createStringParam } from '@pipelab/plugin-core' 2 | 3 | export const ID = 'system:alert' 4 | 5 | export type Data = { 6 | text: string 7 | } 8 | 9 | export const alertAction = createAction({ 10 | id: ID, 11 | name: 'Alert', 12 | description: 'Alert a message', 13 | icon: '', 14 | displayString: "`Alert ${fmt.param(params.message ?? 'No message')}`", 15 | meta: {}, 16 | params: { 17 | message: createStringParam('', { 18 | required: true, 19 | label: 'Message', 20 | }) 21 | }, 22 | 23 | outputs: { 24 | answer: { 25 | label: 'Answer', 26 | value: '' 27 | } 28 | } 29 | }) 30 | 31 | export const alertActionRunner = createActionRunner( 32 | async ({ log, inputs, api, setOutput, browserWindow }) => { 33 | browserWindow.flashFrame(true) 34 | // 'cancel' | 'ok' 35 | const _answer = await api.execute('dialog:alert', { 36 | message: inputs.message 37 | }) 38 | 39 | if ('content' in _answer) { 40 | setOutput('answer', _answer.content.toString()) 41 | } else { 42 | log('error') 43 | } 44 | } 45 | ) 46 | -------------------------------------------------------------------------------- /src/renderer/components/ScenarioListItemRecent.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { platform } from 'process' 2 | 3 | export const name = 'Pipelab' 4 | 5 | export const outFolderName = (binName: string, platform: NodeJS.Platform, arch: NodeJS.Architecture) => { 6 | let platformName = '' 7 | let archName = '' 8 | 9 | console.log('platform', platform) 10 | 11 | if (platform === 'linux') { 12 | platformName = 'linux' 13 | } else if (platform === 'win32') { 14 | platformName = 'win32' 15 | } else if (platform === 'darwin') { 16 | platformName = 'darwin' 17 | } else { 18 | throw new Error('Unsupported platform') 19 | } 20 | 21 | if (arch === 'x64') { 22 | archName = 'x64' 23 | } else if (arch === 'arm') { 24 | archName = 'arm' 25 | } else if (arch === 'arm64') { 26 | archName = 'arm64' 27 | } else if (arch === 'ia32') { 28 | archName = 'ia32' 29 | } else { 30 | throw new Error('Unsupported architecture') 31 | } 32 | 33 | return `${binName}-${platformName}-${archName}` 34 | } 35 | 36 | export const getBinName = (name: string) => { 37 | if (platform === 'win32') { 38 | return `${name}.exe` 39 | } 40 | if (platform === 'darwin') { 41 | return `${name}.app/Contents/MacOS/${name}` 42 | } 43 | return name 44 | } 45 | -------------------------------------------------------------------------------- /src/renderer/utils/evaluator.ts: -------------------------------------------------------------------------------- 1 | import { BlockAction, Steps } from '@@/model' 2 | import { createQuickJs, CreateQuickJSFn } from './quickjs' 3 | import { useLogger } from '@@/logger' 4 | 5 | export const makeResolvedParams = async ( 6 | data: { 7 | params: BlockAction['params'] 8 | steps: Steps 9 | variables: Record 10 | context: Record 11 | }, 12 | onItem: (item: any) => string = (item: any) => item, 13 | _vm?: Awaited | undefined 14 | ) => { 15 | const { logger } = useLogger() 16 | const vm = _vm ?? (await createQuickJs()) 17 | 18 | const result: Record = {} 19 | 20 | for (const [paramName, param] of Object.entries(data.params)) { 21 | try { 22 | const parameterCodeValue = (param.value ?? '').toString() 23 | 24 | const output = await vm.run(parameterCodeValue, { 25 | steps: data.steps, 26 | params: {}, 27 | variables: data.variables, 28 | extraCode: ` 29 | ` 30 | }) 31 | 32 | const outputResult = onItem(output) 33 | 34 | result[paramName] = outputResult 35 | } catch (e) { 36 | logger().error('error', e) 37 | result[paramName] = '' 38 | } 39 | } 40 | return result 41 | } 42 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-filesystem/open.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionRunner, createPathParam } from '@pipelab/plugin-core' 2 | // import displayString from './displayStringRun.lua?raw' 3 | 4 | export const ID = 'fs:open-in-explorer' 5 | 6 | export const openInExplorer = createAction({ 7 | id: ID, 8 | name: 'Open path in explorer', 9 | displayString: "`Open ${fmt.param(params.path, 'primary', 'No path set')} in explorer`", 10 | params: { 11 | path: createPathParam('', { 12 | required: true, 13 | label: 'Path', 14 | control: { 15 | type: 'path', 16 | options: { 17 | properties: ['openDirectory', 'openFile'] 18 | } 19 | } 20 | }) 21 | }, 22 | 23 | outputs: { 24 | message: { 25 | label: 'Message', 26 | value: '' 27 | } 28 | }, 29 | description: 'Open a file or folder in your explorer', 30 | icon: '', 31 | meta: {} 32 | }) 33 | 34 | export const openInExplorerRunner = createActionRunner( 35 | async ({ log, inputs, setOutput }) => { 36 | const { shell } = await import('electron') 37 | 38 | log(`Opening ${inputs.path}`) 39 | const message = await shell.openPath(inputs.path) 40 | setOutput('message', message) 41 | } 42 | ) 43 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-system/prompt.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionRunner, createStringParam } from '@pipelab/plugin-core' 2 | 3 | export const ID = 'system:prompt' 4 | 5 | export type Data = { 6 | text: string 7 | } 8 | 9 | export const promptAction = createAction({ 10 | id: ID, 11 | name: 'Prompt', 12 | description: 'Prompt a message', 13 | icon: '', 14 | displayString: "`Prompt ${fmt.param(params.message ?? 'No message')}`", 15 | meta: {}, 16 | params: { 17 | message: createStringParam('', { 18 | required: true, 19 | label: 'Message', 20 | }) 21 | }, 22 | 23 | outputs: { 24 | answer: { 25 | label: 'Answer', 26 | value: '' 27 | } 28 | } 29 | }) 30 | 31 | export const promptActionRunner = createActionRunner( 32 | async ({ log, inputs, api, setOutput, browserWindow }) => { 33 | browserWindow.flashFrame(true) 34 | // 'cancel' | 'ok' 35 | const _answer = await api.execute('dialog:prompt', { 36 | message: inputs.message 37 | }) 38 | 39 | log('_answer', _answer) 40 | 41 | if (_answer.type === 'success') { 42 | setOutput('answer', _answer.result.answer) 43 | } else { 44 | throw new Error(_answer.ipcError) 45 | } 46 | } 47 | ) 48 | -------------------------------------------------------------------------------- /src/shared/plugins.ts: -------------------------------------------------------------------------------- 1 | import { shallowRef } from 'vue' 2 | import { createNodeDefinition } from '../shared/libs/plugin-core' 3 | 4 | const builtInPlugins = async () => { 5 | return ( 6 | await Promise.all([ 7 | (await import('../shared/libs/plugin-construct')).default, 8 | (await import('../shared/libs/plugin-filesystem')).default, 9 | (await import('../shared/libs/plugin-system')).default, 10 | (await import('../shared/libs/plugin-steam')).default, 11 | (await import('../shared/libs/plugin-itch')).default, 12 | (await import('../shared/libs/plugin-electron')).default, 13 | (await import('../shared/libs/plugin-poki')).default, 14 | (await import('../shared/libs/plugin-nvpatch')).default, 15 | // (await import('../shared/libs/plugin-tauri')).default 16 | ]) 17 | ).flat() 18 | } 19 | 20 | type Plugin = ReturnType 21 | 22 | const plugins = shallowRef([]) 23 | 24 | export const usePlugins = () => { 25 | const load = () => {} 26 | 27 | const registerBuiltIn = async () => { 28 | const builtIns = (await builtInPlugins()) as Plugin[] 29 | plugins.value.push(...builtIns) 30 | } 31 | 32 | return { 33 | load, 34 | registerBuiltIn, 35 | plugins 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/fs/read.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { readFile } from 'node:fs/promises' 4 | 5 | /** 6 | * @param {import('@pipelab/core').MakeInputOutput} json 7 | * @param {import('ws').WebSocket} ws 8 | */ 9 | export default async (json, ws) => { 10 | try { 11 | const file = await readFile(json.body.path, { 12 | encoding: json.body.encoding 13 | }) 14 | 15 | /** 16 | * @type {import('@pipelab/core').MakeInputOutput} 17 | */ 18 | const readFileResult = { 19 | correlationId: json.correlationId, 20 | url: json.url, 21 | body: { 22 | success: true, 23 | content: file, 24 | } 25 | } 26 | ws.send(JSON.stringify(readFileResult)) 27 | } catch (e) { 28 | console.error('e', e) 29 | /** 30 | * @type {import('@pipelab/core').MakeInputOutput} 31 | */ 32 | const readFileResult = { 33 | correlationId: json.correlationId, 34 | url: json.url, 35 | body: { 36 | success: false, 37 | error: e.message, 38 | } 39 | } 40 | ws.send(JSON.stringify(readFileResult)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /scripts/migrate-file.mts: -------------------------------------------------------------------------------- 1 | import { savedFileMigrator } from '@@/model' 2 | import * as fs from 'fs/promises' 3 | import { join } from 'path' 4 | import { tmpdir } from 'os' 5 | import { nanoid } from 'nanoid' 6 | import { execa } from 'execa' 7 | 8 | const args = process.argv.slice(2) 9 | if (args.length !== 1) { 10 | console.error('Usage: node script.ts ') 11 | process.exit(1) 12 | } 13 | 14 | const filePath = args[0] 15 | 16 | async function migrateFile() { 17 | try { 18 | // const tmpFile = join(tmpdir(), nanoid() + '.json') 19 | 20 | const data = await fs.readFile(filePath, 'utf8') 21 | const json = JSON.parse(data) 22 | const migratedData = await savedFileMigrator.migrate(json) 23 | const str = JSON.stringify(migratedData, null, 2) 24 | console.log('data', data) 25 | console.log('str', str) 26 | await fs.writeFile(filePath, str, 'utf8') 27 | console.log('File successfully migrated to the latest version of the model.') 28 | 29 | // await execa('delta', [filePath, tmpFile], { 30 | // env: { 31 | // DELTA_FEATURES: '+side-by-side' 32 | // } 33 | // }) 34 | } catch (error) { 35 | console.error(`Error migrating the file: ${error}`) 36 | process.exit(1) 37 | } 38 | } 39 | 40 | await migrateFile() 41 | -------------------------------------------------------------------------------- /src/preload.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge } from 'electron' 2 | import { electronAPI } from '@electron-toolkit/preload' 3 | import { version } from '../package.json' 4 | 5 | // Custom APIs for renderer 6 | const api = {} 7 | 8 | // TODO: unify window and contextBridge 9 | 10 | // Use `contextBridge` APIs to expose Electron APIs to 11 | // renderer only if context isolation is enabled, otherwise 12 | // just add to the DOM global. 13 | if (process.contextIsolated) { 14 | try { 15 | contextBridge.exposeInMainWorld('electron', electronAPI) 16 | contextBridge.exposeInMainWorld('api', api) 17 | contextBridge.exposeInMainWorld('version', version) 18 | contextBridge.exposeInMainWorld('isPackaged', process.env.NODE_ENV !== 'development') 19 | contextBridge.exposeInMainWorld('isTest', process.env.TEST === 'true') 20 | // eslint-disable-next-line no-console 21 | console.log('process.env.NODE_ENV', process.env.NODE_ENV) 22 | // contextBridge.exposeInMainWorld('_dirname', __dirname) 23 | } catch (error) {} 24 | } else { 25 | window.electron = electronAPI 26 | window.api = api 27 | window.version = version 28 | window.isPackaged = process.env.NODE_ENV !== 'development' 29 | window.isTest = process.env.TEST === 'true' 30 | // window.__dirname = __dirname 31 | } 32 | -------------------------------------------------------------------------------- /src/renderer/utils/config.ts: -------------------------------------------------------------------------------- 1 | import { useAPI } from '@renderer/composables/api' 2 | import { klona } from 'klona' 3 | 4 | export const loadInternalFile = (name: string) => { 5 | const api = useAPI() 6 | 7 | return api.execute('config:load', { 8 | config: name 9 | }) 10 | } 11 | 12 | export const saveInternalFile = (name: string, data: unknown) => { 13 | const api = useAPI() 14 | 15 | api.execute('config:save', { 16 | config: name, 17 | data: JSON.stringify(klona(data)) 18 | }) 19 | } 20 | 21 | export const loadExternalFile = (path: string) => { 22 | const api = useAPI() 23 | 24 | return api.execute('fs:read', { 25 | path 26 | }) 27 | } 28 | 29 | export const saveExternalFile = (path: string, data: unknown) => { 30 | const api = useAPI() 31 | 32 | api.execute('fs:write', { 33 | path, 34 | content: JSON.stringify(klona(data)) 35 | }) 36 | } 37 | 38 | export const createConfig = (config: string) => { 39 | const api = useAPI() 40 | 41 | const load = async () => { 42 | return api.execute('config:load', { 43 | config 44 | }) 45 | } 46 | 47 | const save = async (data: T) => { 48 | await api.execute('config:save', { 49 | config, 50 | data: JSON.stringify(klona(data)) 51 | }) 52 | } 53 | 54 | return { 55 | save, 56 | load 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /assets/tauri/template/app/src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tauri" 3 | version = "0.1.0" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | edition = "2021" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [profile.dev] 11 | opt-level = 0 # Optimize for size 12 | 13 | [lib] 14 | # The `_lib` suffix may seem redundant but it is necessary 15 | # to make the lib name unique and wouldn't conflict with the bin name. 16 | # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 17 | name = "tauri_lib" 18 | crate-type = ["staticlib", "cdylib", "rlib"] 19 | 20 | [build-dependencies] 21 | tauri-build = { version = "2", features = [] } 22 | 23 | [dependencies] 24 | tauri = { version = "2", features = ["unstable"] } 25 | tauri-plugin-shell = "2" 26 | serde = { version = "1", features = ["derive"] } 27 | serde_json = "1" 28 | warp = "0.3" 29 | 30 | tokio = "1.42.0" 31 | tokio-tungstenite = "0.24.0" 32 | 33 | futures-util = "0.3.31" 34 | futures = "0.3.31" 35 | 36 | tauri-plugin-devtools = "2.0.0" 37 | 38 | wgpu = "0.20" 39 | winit = "0.30.5" 40 | # env_logger = "0.9" 41 | log = "0.4" 42 | # pollster = "0.4.0" 43 | # raw-window-handle = "0.5.2" 44 | steamworks = "0.11.0" 45 | tauri-plugin-fs = "2" 46 | # bytemuck = { version = "1.20.0", features = ["derive"] } 47 | image = "0.24" 48 | -------------------------------------------------------------------------------- /tests/e2e/fixtures/c3-export.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Export from Construct, package with Electron, then upload to Steam", 3 | "name": "From Construct to Steam", 4 | "variables": [], 5 | "canvas": { 6 | "triggers": [ 7 | { 8 | "type": "event", 9 | "origin": { 10 | "pluginId": "system", 11 | "nodeId": "manual" 12 | }, 13 | "uid": "manual-start", 14 | "params": {} 15 | } 16 | ], 17 | "blocks": [ 18 | { 19 | "uid": "export-construct-project", 20 | "type": "action", 21 | "origin": { 22 | "nodeId": "export-construct-project", 23 | "pluginId": "construct" 24 | }, 25 | "params": { 26 | "file": { 27 | "editor": "editor", 28 | "value": "\"./tests/e2e/fixtures/c3-export/test.c3p\"" 29 | }, 30 | "username": { 31 | "editor": "editor", 32 | "value": "" 33 | }, 34 | "password": { 35 | "editor": "editor", 36 | "value": "" 37 | }, 38 | "version": { 39 | "editor": "editor", 40 | "value": "" 41 | }, 42 | "headless": { 43 | "editor": "editor", 44 | "value": true 45 | } 46 | } 47 | } 48 | ] 49 | }, 50 | "version": "3.0.0" 51 | } -------------------------------------------------------------------------------- /src/shared/libs/plugin-filesystem/temporary-folder.ts: -------------------------------------------------------------------------------- 1 | // import { ComputeOutput, Context, CynNode, IODef, createDefinition, path, schema } from '@pipelab/plugin-core'; 2 | // import { nanoid } from 'nanoid' 3 | 4 | // export const ID = 'temporary-folder' 5 | 6 | // export type Data = { 7 | // } 8 | 9 | // export const definition = createDefinition({ 10 | // inputs: { 11 | 12 | // }, 13 | // outputs: { 14 | // value: { 15 | // type: 'data', 16 | // schema: schema.string() 17 | // } 18 | // } 19 | // } satisfies IODef) 20 | 21 | // export class TemporaryFolderNode extends CynNode { 22 | // width = 180; 23 | // height = 250; 24 | 25 | // path: string | undefined 26 | 27 | // constructor(context: Context) { 28 | // super(ID, "Temporary folder", context); 29 | 30 | // const value = new ClassicPreset.Output(path(), "Valeur") 31 | // this.addOutput('value', value); 32 | // } 33 | // run() { } 34 | // compute(): ComputeOutput { 35 | // if (!this.path) { 36 | // this.path = nanoid() 37 | // } 38 | // return { 39 | // value: this.path 40 | // }; 41 | // } 42 | 43 | // load(data: Data) { 44 | // } 45 | 46 | // save() { 47 | // return { 48 | // } 49 | // } 50 | // } 51 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-system/index.ts: -------------------------------------------------------------------------------- 1 | import { createNodeDefinition } from '@pipelab/plugin-core' 2 | import { logAction, logActionRunner } from './log.js' 3 | // import { branchCondition, branchConditionRunner } from './branch.js' 4 | // import { forLoop, ForLoopRunner } from './for.js' 5 | import { manualEvent, manualEvaluator } from './manual.js' 6 | import { alertAction, alertActionRunner } from './alert.js' 7 | import { promptAction, promptActionRunner } from './prompt.js' 8 | import { sleepAction, sleepActionRunner } from './sleep.js' 9 | 10 | export default createNodeDefinition({ 11 | id: 'system', 12 | description: 'System', 13 | name: 'System', 14 | icon: { 15 | type: 'icon', 16 | icon: 'mdi-cog-outline' 17 | }, 18 | nodes: [ 19 | { 20 | node: logAction, 21 | runner: logActionRunner 22 | }, 23 | // { 24 | // node: branchCondition, 25 | // runner: branchConditionRunner, 26 | // }, 27 | // { 28 | // node: forLoop, 29 | // runner: ForLoopRunner 30 | // }, 31 | { 32 | node: manualEvent, 33 | runner: manualEvaluator 34 | }, 35 | { 36 | node: alertAction, 37 | runner: alertActionRunner 38 | }, 39 | { 40 | node: promptAction, 41 | runner: promptActionRunner 42 | }, 43 | { 44 | node: sleepAction, 45 | runner: sleepActionRunner 46 | } 47 | ] 48 | }) 49 | -------------------------------------------------------------------------------- /forge.env.d.ts: -------------------------------------------------------------------------------- 1 | export {} // Make this a module 2 | 3 | declare global { 4 | // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Vite 5 | // plugin that tells the Electron app where to look for the Vite-bundled app code (depending on 6 | // whether you're running in development or production). 7 | const MAIN_WINDOW_VITE_DEV_SERVER_URL: string 8 | const MAIN_WINDOW_VITE_NAME: string 9 | 10 | type ElectronAPI = import('@electron-toolkit/preload').ElectronAPI 11 | 12 | namespace NodeJS { 13 | interface Process { 14 | // Used for hot reload after preload scripts. 15 | viteDevServers: Record 16 | } 17 | } 18 | 19 | interface Window { 20 | version: string 21 | electron: ElectronAPI 22 | api: any 23 | isPackaged: boolean 24 | isTest: boolean 25 | } 26 | 27 | type VitePluginConfig = ConstructorParameters< 28 | typeof import('@electron-forge/plugin-vite').VitePlugin 29 | >[0] 30 | 31 | interface VitePluginRuntimeKeys { 32 | VITE_DEV_SERVER_URL: `${string}_VITE_DEV_SERVER_URL` 33 | VITE_NAME: `${string}_VITE_NAME` 34 | } 35 | } 36 | 37 | declare module 'vite' { 38 | interface ConfigEnv { 39 | root: string 40 | forgeConfig: VitePluginConfig 41 | forgeConfigSelf: VitePluginConfig[K][number] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-system/join.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createExpression, 3 | createExpressionRunner, 4 | } from "@pipelab/plugin-core"; 5 | 6 | export const ID = "join"; 7 | 8 | export type Data = { 9 | value: string; 10 | }; 11 | 12 | const DEFAULT_SEPARATOR = ", "; 13 | 14 | export const join = createExpression({ 15 | id: ID, 16 | name: "Join", 17 | description: 'Join values', 18 | displayString: 'Join {{ params.value }}', 19 | icon: '', 20 | meta: {}, 21 | params: { 22 | input: { 23 | label: 'Input', 24 | value: [], 25 | control: { 26 | type: 'input', 27 | options: { 28 | kind: 'text' 29 | } 30 | } 31 | }, 32 | separator: { 33 | label: 'Separator', 34 | value: DEFAULT_SEPARATOR, 35 | control: { 36 | type: 'input', 37 | options: { 38 | kind: 'text' 39 | } 40 | } 41 | }, 42 | }, 43 | 44 | outputs: { 45 | value: { 46 | label: 'Value', 47 | value: '' 48 | }, 49 | }, 50 | }); 51 | 52 | export const evaluator = createExpressionRunner(async ({ inputs }) => { 53 | const inputArr = inputs?.input; 54 | const separatorArr = inputs?.separator; 55 | 56 | const input = inputArr ? inputArr[0] : []; 57 | const separator = separatorArr?.[0] ?? DEFAULT_SEPARATOR; 58 | 59 | const result = input.join(separator) 60 | return result 61 | } 62 | ); -------------------------------------------------------------------------------- /src/renderer/components/ScenarioListItemFile.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-filesystem/index.ts: -------------------------------------------------------------------------------- 1 | import { createNodeDefinition } from '@pipelab/plugin-core' 2 | import { ListFilesAction, ListFilesActionRun } from './list-files.js' 3 | import { isFileCondition, isFileRunner } from './is-file.js' 4 | import { zipRunner, zip } from './zip.js' 5 | import { unzipRunner, unzip } from './unzip.js' 6 | import { copy, copyRunner } from './copy.js' 7 | import { remove, removeRunner } from './remove.js' 8 | import { run, runRunner } from './run.js' 9 | import { openInExplorer, openInExplorerRunner } from './open.js' 10 | 11 | export default createNodeDefinition({ 12 | description: 'Filesystem', 13 | id: 'filesystem', 14 | name: 'Filesystem', 15 | icon: { 16 | type: 'icon', 17 | icon: 'mdi-folder-zip-outline' 18 | }, 19 | nodes: [ 20 | // { 21 | // node: ListFilesAction, 22 | // runner: ListFilesActionRun 23 | // }, 24 | // { 25 | // node: isFileCondition, 26 | // runner: isFileRunner 27 | // }, 28 | { 29 | node: zip, 30 | runner: zipRunner 31 | }, 32 | { 33 | node: unzip, 34 | runner: unzipRunner 35 | }, 36 | { 37 | node: copy, 38 | runner: copyRunner 39 | }, 40 | { 41 | node: remove, 42 | runner: removeRunner 43 | }, 44 | { 45 | node: run, 46 | runner: runRunner 47 | }, 48 | { 49 | node: openInExplorer, 50 | runner: openInExplorerRunner 51 | } 52 | ] 53 | }) 54 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "references.preferredLocation": "peek", 3 | "workbench.colorCustomizations": { 4 | "gitDecoration.ignoredResourceForeground": "#a0a0a02a", 5 | "editor.lineHighlightBackground": "#1073cf2d", 6 | "editor.lineHighlightBorder": "#9fced11f" 7 | }, 8 | "colorTabs.config": [ 9 | { 10 | "regex": ".*\/agent\/.*", 11 | "color": "#FF0000", 12 | "label": "MOBILE" 13 | }, 14 | { 15 | "regex": ".*\/browser\/.*", 16 | "color": "#00FF00", 17 | }, 18 | { 19 | "regex": ".*\/core\/.*", 20 | "color": "#00FFFF", 21 | }, 22 | { 23 | "regex": ".*\/plugin-\/.*", 24 | "color": "#FFFF00", 25 | }, 26 | ], 27 | "colorTabs.activityBarBackground": false, 28 | "colorTabs.titleLabel": true, 29 | "colorTabs.titleBackground": false, 30 | "cSpell.words": [ 31 | "controlflow" 32 | ], 33 | "editor.defaultFormatter": "esbenp.prettier-vscode", 34 | "[typescript]": { 35 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 36 | }, 37 | "[rust]": { 38 | "editor.defaultFormatter": "rust-lang.rust-analyzer" 39 | }, 40 | "[vue]": { 41 | "editor.defaultFormatter": "Vue.volar" 42 | }, 43 | "debug.javascript.defaultRuntimeExecutable": { 44 | "pwa-node": "c:\\users\\quent\\appdata\\local\\mise\\shims\\node" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/presets/loop.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { PresetFn, SavedFile } from '@@/model' 4 | 5 | export const loopPreset: PresetFn = async () => { 6 | const forId = 'forId' 7 | const arrayId = 'arrayId' 8 | 9 | const logStepId = 'logStepId' 10 | const logExitId = 'logExitId' 11 | 12 | const data: SavedFile = { 13 | version: '1.0.0', 14 | name: 'Loop demo', 15 | description: 'Loop demo', 16 | variables: [ 17 | { 18 | id: arrayId, 19 | description: 'An array', 20 | name: 'Array', 21 | type: 'array', 22 | of: 'string', 23 | value: [] 24 | } 25 | ], 26 | canvas: { 27 | blocks: [ 28 | { 29 | type: 'loop', 30 | origin: { 31 | pluginId: 'system', 32 | nodeId: 'for' 33 | }, 34 | 35 | params: {}, 36 | children: [ 37 | { 38 | type: 'action', 39 | origin: { 40 | pluginId: 'system', 41 | nodeId: 'log' 42 | }, 43 | params: {}, 44 | uid: logStepId 45 | } 46 | ], 47 | uid: forId 48 | }, 49 | { 50 | type: 'action', 51 | origin: { 52 | pluginId: 'system', 53 | nodeId: 'log' 54 | }, 55 | params: {}, 56 | uid: logExitId 57 | } 58 | ] 59 | } 60 | } 61 | 62 | return { 63 | data 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-filesystem/remove.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionRunner, createPathParam } from '@pipelab/plugin-core' 2 | 3 | export const ID = 'fs:remove' 4 | 5 | export const remove = createAction({ 6 | id: ID, 7 | name: 'Remove file/folder', 8 | displayString: '`Remove ${fmt.param(params.from, "primary")}`', 9 | params: { 10 | from: createPathParam('', { 11 | label: 'Path', 12 | required: true, 13 | control: { 14 | type: 'path', 15 | options: { 16 | properties: ['openFile'] 17 | } 18 | } 19 | }), 20 | recursive: { 21 | label: 'Recursive', 22 | required: true, 23 | value: true, 24 | control: { 25 | type: 'boolean' 26 | } 27 | } 28 | }, 29 | 30 | outputs: {}, 31 | description: 'Remove a file or a folder', 32 | icon: '', 33 | meta: {} 34 | }) 35 | 36 | export const removeRunner = createActionRunner(async ({ log, inputs }) => { 37 | const { rm } = await import('node:fs/promises') 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 | }) 59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @pipelab/app 2 | 3 | ## 1.4.6 4 | 5 | ### Patch Changes 6 | 7 | - 94778b9: improvements 8 | add log support 9 | fix config loading 10 | fix step placeholder in parameter editor 11 | improved new project creation 12 | add steam & steam overlay support 13 | 14 | ## 1.4.5 15 | 16 | ### Patch Changes 17 | 18 | - 3ea4f00: fix tests 19 | 20 | ## 1.4.4 21 | 22 | ### Patch Changes 23 | 24 | - a2489dd: fix electron configuration 25 | 26 | ## 1.4.3 27 | 28 | ### Patch Changes 29 | 30 | - improvements 31 | add log support 32 | fix config loading 33 | fix step placeholder in parameter editor 34 | 35 | ## 1.4.1 36 | 37 | ### Patch Changes 38 | 39 | - 48a3569: feat(plugin-construct): add throttle zip log 40 | feat(plugin-construct): add click on addon installation prompts 41 | feat(plugin-construct): add loading project progress 42 | - d1f68b8: feat(electron): support url preview (fix #32) 43 | feat(app): add dialog prompt 44 | feat(app): improve ui 45 | - 665562c: fix(app): fix adding node wrong offset 46 | feat(app): hide sidebar when editing project 47 | - 48a3569: fix(app): add live logs hooks support 48 | fix(plugin-steam): fix steam permission, general improvements & logged out detection 49 | 50 | ## 1.1.26 51 | 52 | ### Patch Changes 53 | 54 | - 25e6bb0: path picker now support options from plugins 55 | - 26be9b1: feat(electron): add custom main code support 56 | feat(electron): add "configure" action 57 | feat: improve display strings 58 | - fadc3cc: Improved boolean parameter 59 | -------------------------------------------------------------------------------- /assets/electron/template/app/pipelab-plugin.cjs: -------------------------------------------------------------------------------- 1 | const { PluginBase } = require('@electron-forge/plugin-base') 2 | 3 | module.exports = class PipelabPlugin extends PluginBase { 4 | getHooks() { 5 | return { 6 | prePackage: [this.prePackage], 7 | generateAssets: [this.generateAssets], 8 | postStart: [this.postStart], 9 | packageAfterCopy: [this.packageAfterCopy], 10 | packageAfterPrune: [this.packageAfterPrune], 11 | packageAfterExtract: [this.packageAfterExtract], 12 | postPackage: [this.postPackage], 13 | preMake: [this.preMake], 14 | postMake: [this.postMake], 15 | readPackageJson: [this.readPackageJson] 16 | } 17 | } 18 | 19 | prePackage() { 20 | // console.log('running prePackage hook') 21 | } 22 | generateAssets() { 23 | // console.log('running generateAssets hook') 24 | } 25 | postStart() { 26 | // console.log('running postStart hook') 27 | } 28 | packageAfterCopy() { 29 | // console.log('running packageAfterCopy hook') 30 | } 31 | packageAfterPrune() { 32 | // console.log('running packageAfterPrune hook') 33 | } 34 | packageAfterExtract() { 35 | // console.log('running packageAfterExtract hook') 36 | } 37 | postPackage() { 38 | // console.log('running postPackage hook') 39 | } 40 | preMake() { 41 | // console.log('running preMake hook') 42 | } 43 | postMake() { 44 | // console.log('running postMake hook') 45 | } 46 | readPackageJson() { 47 | // console.log('running readPackageJson hook') 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /assets/electron/template/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "productName": "app", 4 | "version": "1.0.0", 5 | "description": "My Electron application description", 6 | "type": "module", 7 | "main": "src/index.js", 8 | "scripts": { 9 | "start": "electron-forge start", 10 | "package": "electron-forge package", 11 | "make": "electron-forge make", 12 | "publish": "electron-forge publish", 13 | "lint": "echo \"No linting configured\"" 14 | }, 15 | "devDependencies": { 16 | "@electron-forge/cli": "7.4.0", 17 | "@electron-forge/maker-deb": "7.4.0", 18 | "@electron-forge/maker-dmg": "7.4.0", 19 | "@electron-forge/maker-rpm": "7.4.0", 20 | "@electron-forge/maker-squirrel": "7.4.0", 21 | "@electron-forge/maker-zip": "7.4.0", 22 | "@electron-forge/plugin-auto-unpack-natives": "7.4.0", 23 | "@electron-forge/plugin-fuses": "7.4.0", 24 | "@electron/fuses": "1.8.0", 25 | "@pipelab/core": "1.4.4", 26 | "electron": "34.0.2" 27 | }, 28 | "keywords": [], 29 | "author": { 30 | "name": "Quentin Goinaud", 31 | "email": "quentin.goinaud@gmail.com" 32 | }, 33 | "license": "MIT", 34 | "dependencies": { 35 | "@electron-forge/plugin-base": "7.4.0", 36 | "@electron/asar": "3.2.13", 37 | "electron-serve": "^2.1.1", 38 | "electron-squirrel-startup": "1.0.1", 39 | "execa": "^9.4.0", 40 | "move-file": "^3.1.0", 41 | "mri": "1.2.0", 42 | "serve-handler": "^6.1.5", 43 | "slash": "^5.1.0", 44 | "steamworks.js": "0.4.0", 45 | "ws": "^8.18.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-electron/make.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, vi } from 'vitest' 2 | import { makeRunner } from './make.js' 3 | import { tmpdir } from 'node:os' 4 | import { join } from 'node:path' 5 | import { type fs } from 'memfs' 6 | import { browserWindow } from '@@/tests/helpers.js' 7 | 8 | // ... 9 | 10 | // vi.mock('node:fs/promises', async () => { 11 | // const memfs: { fs: typeof fs } = await vi.importActual('memfs') 12 | 13 | // return memfs.fs.promises 14 | // }) 15 | 16 | test('adds 1 + 2 to equal 3', async () => { 17 | const outputs: Record = {} 18 | 19 | const id = 'ut-electron-build' 20 | const tmpDir = join(tmpdir(), id) 21 | 22 | console.log('tmpDir', tmpDir) 23 | 24 | const inputFolder = join(process.cwd(), 'fixtures', 'build') 25 | 26 | console.log('inputFolder', inputFolder) 27 | 28 | // await makeRunner({ 29 | // inputs: { 30 | // 'input-folder': inputFolder, 31 | // configuration: {}, 32 | // arch: undefined, 33 | // platform: undefined 34 | // }, 35 | // log: (...args) => { 36 | // console.log(...args) 37 | // }, 38 | // setOutput: (key, value) => { 39 | // outputs[key] = value 40 | // }, 41 | // meta: { 42 | // definition: '' 43 | // }, 44 | // setMeta: () => { 45 | // console.log('set meta defined here') 46 | // }, 47 | // cwd: tmpDir, 48 | // paths: { 49 | // assets: '', 50 | // unpack: '' 51 | // }, 52 | // api: undefined, 53 | // browserWindow 54 | // }) 55 | console.log('outputs', outputs) 56 | expect(true).toBe(true) 57 | }, 120_000) 58 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-tauri/make.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, vi } from 'vitest' 2 | import { makeRunner } from './make.js' 3 | import { tmpdir } from 'node:os' 4 | import { join } from 'node:path' 5 | import { type fs } from 'memfs' 6 | import { browserWindow } from '@@/tests/helpers.js' 7 | 8 | // ... 9 | 10 | // vi.mock('node:fs/promises', async () => { 11 | // const memfs: { fs: typeof fs } = await vi.importActual('memfs') 12 | 13 | // return memfs.fs.promises 14 | // }) 15 | 16 | test('adds 1 + 2 to equal 3', async () => { 17 | const outputs: Record = {} 18 | 19 | const id = 'ut-electron-build' 20 | const tmpDir = join(tmpdir(), id) 21 | 22 | console.log('tmpDir', tmpDir) 23 | 24 | const inputFolder = join(process.cwd(), 'fixtures', 'build') 25 | 26 | console.log('inputFolder', inputFolder) 27 | 28 | // await makeRunner({ 29 | // inputs: { 30 | // 'input-folder': inputFolder, 31 | // configuration: {}, 32 | // arch: undefined, 33 | // platform: undefined 34 | // }, 35 | // log: (...args) => { 36 | // console.log(...args) 37 | // }, 38 | // setOutput: (key, value) => { 39 | // outputs[key] = value 40 | // }, 41 | // meta: { 42 | // definition: '' 43 | // }, 44 | // setMeta: () => { 45 | // console.log('set meta defined here') 46 | // }, 47 | // cwd: tmpDir, 48 | // paths: { 49 | // assets: '', 50 | // unpack: '' 51 | // }, 52 | // api: undefined, 53 | // browserWindow 54 | // }) 55 | console.log('outputs', outputs) 56 | expect(true).toBe(true) 57 | }, 120_000) 58 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-filesystem/unzip.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionRunner, createPathParam } from '@pipelab/plugin-core' 2 | 3 | export const ID = 'unzip-file-node' 4 | 5 | export const unzip = createAction({ 6 | id: ID, 7 | name: 'Unzip file', 8 | displayString: '`Unzip ${fmt.param(params.file, "primary", "No file specified")}`', 9 | params: { 10 | file: createPathParam('', { 11 | required: true, 12 | control: { 13 | type: 'path', 14 | options: { 15 | properties: ['openFile'] 16 | } 17 | }, 18 | label: 'File' 19 | }) 20 | }, 21 | 22 | outputs: { 23 | output: { 24 | value: '', 25 | label: 'Output' 26 | } 27 | }, 28 | description: 'Unzip a file to a specified folder', 29 | icon: '', 30 | meta: {} 31 | }) 32 | 33 | export const unzipRunner = createActionRunner( 34 | async ({ log, inputs, setOutput, cwd }) => { 35 | const StreamZip = await import('node-stream-zip') 36 | const { join } = await import('node:path') 37 | 38 | console.log('inputs', inputs) 39 | 40 | console.log('inputs.file', inputs.file) 41 | const file = inputs.file 42 | console.log('file', file) 43 | const output = join(cwd) 44 | 45 | console.log('file', file) 46 | console.log('output', output) 47 | 48 | log('Unzip file', inputs.file, 'to', output) 49 | 50 | const zip = new StreamZip.default.async({ file }) 51 | 52 | const bytes = await zip.extract(null, output) 53 | await zip.close() 54 | 55 | console.log('bytes', bytes) 56 | 57 | setOutput('output', output) 58 | } 59 | ) 60 | -------------------------------------------------------------------------------- /assets/electron/template/app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | .DS_Store 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # TypeScript cache 43 | *.tsbuildinfo 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | .env.test 63 | 64 | # parcel-bundler cache (https://parceljs.org/) 65 | .cache 66 | 67 | # next.js build output 68 | .next 69 | 70 | # nuxt.js build output 71 | .nuxt 72 | 73 | # vuepress build output 74 | .vuepress/dist 75 | 76 | # Serverless directories 77 | .serverless/ 78 | 79 | # FuseBox cache 80 | .fusebox/ 81 | 82 | # DynamoDB Local files 83 | .dynamodb/ 84 | 85 | # Webpack 86 | .webpack/ 87 | 88 | # Vite 89 | .vite/ 90 | 91 | # Electron-Forge 92 | out/ 93 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-tauri/make.ts: -------------------------------------------------------------------------------- 1 | import { createActionRunner } from '@pipelab/plugin-core' 2 | import { forge, createMakeProps } from './forge' 3 | import { merge } from 'ts-deepmerge' 4 | 5 | export const makeRunner = createActionRunner>( 6 | async (options) => { 7 | const appFolder = options.inputs['input-folder'] 8 | 9 | if (!options.inputs.configuration) { 10 | throw new Error('Missing electron configuration') 11 | } 12 | 13 | const completeConfiguration = merge( 14 | { 15 | alwaysOnTop: false, 16 | appBundleId: 'com.pipelab.app', 17 | appCategoryType: '', 18 | appCopyright: 'Copyright © 2024 Pipelab', 19 | appVersion: '1.0.0', 20 | author: 'Pipelab', 21 | customMainCode: '', 22 | description: 'A simple Electron application', 23 | electronVersion: '', 24 | disableAsarPackaging: true, 25 | forceHighPerformanceGpu: false, 26 | enableExtraLogging: false, 27 | clearServiceWorkerOnBoot: false, 28 | enableDisableRendererBackgrounding: false, 29 | enableInProcessGPU: false, 30 | frame: true, 31 | fullscreen: false, 32 | icon: '', 33 | height: 600, 34 | name: 'Pipelab', 35 | toolbar: true, 36 | transparent: false, 37 | width: 800, 38 | enableSteamSupport: false, 39 | steamGameId: 480, 40 | ignore: [] 41 | } satisfies ElectronAppConfig.Config, 42 | options.inputs.configuration 43 | ) as ElectronAppConfig.Config 44 | 45 | await forge('make', appFolder, options, completeConfiguration) 46 | } 47 | ) 48 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-electron/make.ts: -------------------------------------------------------------------------------- 1 | import { createActionRunner } from '@pipelab/plugin-core' 2 | import { forge, createMakeProps } from './forge' 3 | import { merge } from 'ts-deepmerge' 4 | 5 | export const makeRunner = createActionRunner>( 6 | async (options) => { 7 | const appFolder = options.inputs['input-folder'] 8 | 9 | if (!options.inputs.configuration) { 10 | throw new Error('Missing electron configuration') 11 | } 12 | 13 | const completeConfiguration = merge( 14 | { 15 | alwaysOnTop: false, 16 | appBundleId: 'com.pipelab.app', 17 | appCategoryType: '', 18 | appCopyright: 'Copyright © 2024 Pipelab', 19 | appVersion: '1.0.0', 20 | author: 'Pipelab', 21 | customMainCode: '', 22 | description: 'A simple Electron application', 23 | electronVersion: '', 24 | disableAsarPackaging: true, 25 | forceHighPerformanceGpu: false, 26 | enableExtraLogging: false, 27 | clearServiceWorkerOnBoot: false, 28 | enableDisableRendererBackgrounding: false, 29 | enableInProcessGPU: false, 30 | frame: true, 31 | fullscreen: false, 32 | icon: '', 33 | height: 600, 34 | name: 'Pipelab', 35 | toolbar: true, 36 | transparent: false, 37 | width: 800, 38 | enableSteamSupport: false, 39 | steamGameId: 480, 40 | ignore: [] 41 | } satisfies ElectronAppConfig.Config, 42 | options.inputs.configuration 43 | ) as ElectronAppConfig.Config 44 | 45 | await forge('make', appFolder, options, completeConfiguration) 46 | } 47 | ) 48 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pipelab 7 | 8 | 20 | 24 | 29 | 30 | 49 | 50 | 51 | 52 |
53 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/main/presets/if.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { PresetFn, SavedFile } from '@@/model' 3 | 4 | export const ifPreset: PresetFn = async () => { 5 | const branchId = 'branchId' 6 | const logOkId = 'logOkId' 7 | const logKoId = 'logKoId' 8 | const booleanId = 'booleanId' 9 | 10 | const data: SavedFile = { 11 | version: '1.0.0', 12 | name: 'Condition demo', 13 | description: 'Condition demo', 14 | variables: [ 15 | { 16 | type: 'boolean', 17 | id: booleanId, 18 | description: 'The value of the conditon', 19 | name: 'Value', 20 | value: true 21 | } 22 | ], 23 | canvas: { 24 | blocks: [ 25 | { 26 | type: 'condition', 27 | origin: { 28 | pluginId: 'system', 29 | nodeId: 'branch' 30 | }, 31 | uid: branchId, 32 | params: { 33 | condition: '' 34 | }, 35 | branchTrue: [ 36 | { 37 | type: 'action', 38 | origin: { 39 | pluginId: 'system', 40 | nodeId: 'log' 41 | }, 42 | uid: logOkId, 43 | params: { 44 | text: 'OK' 45 | } 46 | } 47 | ], 48 | branchFalse: [ 49 | { 50 | type: 'action', 51 | origin: { 52 | pluginId: 'system', 53 | nodeId: 'log' 54 | }, 55 | uid: logKoId, 56 | params: { 57 | text: 'KO' 58 | } 59 | } 60 | ] 61 | } 62 | ] 63 | } 64 | } 65 | 66 | return { 67 | data 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-filesystem/list-files.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionRunner } from "@pipelab/plugin-core"; 2 | export const ID = "list-files-node"; 3 | 4 | export const ListFilesAction = createAction({ 5 | id: ID, 6 | name: "List files", 7 | displayString: "`List files \"${params.recursive ? 'recursively' : ''}\" from \"${params.folder}`", 8 | params: { 9 | folder: { 10 | control: { 11 | type: 'path', 12 | options: { 13 | properties: ['openDirectory'] 14 | } 15 | }, 16 | value: '', 17 | label: "Folder", 18 | }, 19 | recursive: { 20 | control: { 21 | type: 'boolean', 22 | }, 23 | value: false, 24 | label: "Recursive", 25 | }, 26 | }, 27 | 28 | outputs: { 29 | paths: { 30 | value: [] as Array, 31 | label: "Paths", 32 | }, 33 | }, 34 | description: "List files from a folder", 35 | icon: "", 36 | meta: {}, 37 | }); 38 | 39 | 40 | export const ListFilesActionRun = createActionRunner(async ({ log, inputs, setOutput }) => { 41 | const fs = await import("node:fs/promises"); 42 | const path = await import("node:path"); 43 | 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 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-electron/package.ts: -------------------------------------------------------------------------------- 1 | import { createActionRunner } from '@pipelab/plugin-core' 2 | import { createPackageProps, forge } from './forge' 3 | import { merge } from 'ts-deepmerge' 4 | 5 | export const packageRunner = createActionRunner>( 6 | async (options) => { 7 | const appFolder = options.inputs['input-folder'] 8 | 9 | if (!options.inputs.configuration) { 10 | throw new Error('Missing electron configuration') 11 | } 12 | 13 | const completeConfiguration = merge( 14 | { 15 | alwaysOnTop: false, 16 | appBundleId: 'com.pipelab.app', 17 | appCategoryType: '', 18 | appCopyright: 'Copyright © 2024 Pipelab', 19 | appVersion: '1.0.0', 20 | author: 'Pipelab', 21 | customMainCode: '', 22 | description: 'A simple Electron application', 23 | electronVersion: '', 24 | disableAsarPackaging: true, 25 | forceHighPerformanceGpu: false, 26 | enableExtraLogging: false, 27 | clearServiceWorkerOnBoot: false, 28 | enableDisableRendererBackgrounding: false, 29 | enableInProcessGPU: false, 30 | frame: true, 31 | fullscreen: false, 32 | icon: '', 33 | height: 600, 34 | name: 'Pipelab', 35 | toolbar: true, 36 | transparent: false, 37 | width: 800, 38 | enableSteamSupport: false, 39 | steamGameId: 480, 40 | ignore: [] 41 | } satisfies ElectronAppConfig.Config, 42 | options.inputs.configuration 43 | ) as ElectronAppConfig.Config 44 | 45 | // @ts-expect-error options is not really compatible 46 | await forge('package', appFolder, options, completeConfiguration) 47 | } 48 | ) 49 | -------------------------------------------------------------------------------- /src/main/utils.ts: -------------------------------------------------------------------------------- 1 | import { usePlugins } from '@@/plugins' 2 | import { RendererPluginDefinition } from '../shared/libs/plugin-core' 3 | import { access, mkdir, mkdtemp, realpath, writeFile } from 'node:fs/promises' 4 | import { dirname, join } from 'node:path' 5 | import { tmpdir } from 'node:os' 6 | 7 | export const getFinalPlugins = () => { 8 | const { plugins } = usePlugins() 9 | // console.log('plugins.value', plugins.value) 10 | 11 | const finalPlugins: RendererPluginDefinition[] = [] 12 | 13 | for (const plugin of Object.values(plugins.value)) { 14 | const finalNodes: RendererPluginDefinition['nodes'][number][] = [] 15 | // console.log('/*') 16 | // console.log('node', node.definition) 17 | // console.log('node', JSON.stringify(node.definition, undefined, 2)) 18 | // console.log('*/') 19 | 20 | // send without runner 21 | for (const element of plugin.nodes) { 22 | const { node, disabled, advanced } = element 23 | finalNodes.push({ 24 | node, 25 | disabled: disabled ?? false, 26 | advanced: advanced ?? false 27 | }) 28 | } 29 | 30 | finalPlugins.push({ 31 | ...plugin, 32 | nodes: finalNodes 33 | }) 34 | } 35 | 36 | return finalPlugins 37 | } 38 | 39 | export const ensure = async (filesPath: string, defaultContent = '{}') => { 40 | // create parent folder 41 | await mkdir(dirname(filesPath), { 42 | recursive: true 43 | }) 44 | 45 | // ensure file exist 46 | try { 47 | await access(filesPath) 48 | } catch { 49 | // File doesn't exist, create it 50 | await writeFile(filesPath, defaultContent) // json 51 | } 52 | } 53 | 54 | export const generateTempFolder = async (base = tmpdir()) => { 55 | const realPath = await realpath(base) 56 | console.log('join', join(realPath, 'pipelab-')) 57 | return mkdtemp(join(realPath, 'pipelab-')) 58 | } 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | .DS_Store 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # TypeScript cache 43 | *.tsbuildinfo 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | .env.test 63 | .env.local 64 | 65 | 66 | # parcel-bundler cache (https://parceljs.org/) 67 | .cache 68 | 69 | # next.js build output 70 | .next 71 | 72 | # nuxt.js build output 73 | .nuxt 74 | 75 | # vuepress build output 76 | .vuepress/dist 77 | 78 | # Serverless directories 79 | .serverless/ 80 | 81 | # FuseBox cache 82 | .fusebox/ 83 | 84 | # DynamoDB Local files 85 | .dynamodb/ 86 | 87 | # Webpack 88 | .webpack/ 89 | 90 | # Vite 91 | .vite/ 92 | 93 | # Electron-Forge 94 | out/ 95 | 96 | sentry.properties 97 | 98 | assets/shims 99 | assets/electron/template/app/src/app 100 | playwright/**/* 101 | assets/tauri/template/app/app 102 | assets/tauri/template/app/src-tauri/steam_* 103 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from 'globals' 2 | import path from 'node:path' 3 | import { fileURLToPath } from 'node:url' 4 | import js from '@eslint/js' 5 | import { FlatCompat } from '@eslint/eslintrc' 6 | 7 | const __filename = fileURLToPath(import.meta.url) 8 | const __dirname = path.dirname(__filename) 9 | const compat = new FlatCompat({ 10 | baseDirectory: __dirname, 11 | recommendedConfig: js.configs.recommended, 12 | allConfig: js.configs.all 13 | }) 14 | 15 | export default [ 16 | { 17 | ignores: ['**/node_modules', '**/dist', '**/out', '**/.gitignore'] 18 | }, 19 | ...compat.extends( 20 | 'plugin:vue/vue3-recommended', 21 | 'eslint:recommended', 22 | '@vue/eslint-config-typescript/recommended', 23 | '@vue/eslint-config-prettier' 24 | ), 25 | { 26 | files: ['**/src/**/*.ts', '**/src/**/*.vue'], 27 | 28 | rules: { 29 | 'no-console': ['error'] 30 | } 31 | }, 32 | { 33 | languageOptions: { 34 | globals: { 35 | ...globals.browser, 36 | ...globals.commonjs, 37 | ...globals.node 38 | // ...vue.environments["setup-compiler-macros"]["setup-compiler-macros"], 39 | } 40 | }, 41 | 42 | rules: { 43 | '@typescript-eslint/ban-ts-comment': [ 44 | 'error', 45 | { 46 | 'ts-ignore': 'allow-with-description' 47 | } 48 | ], 49 | 50 | '@typescript-eslint/explicit-module-boundary-types': 'off', 51 | 52 | '@typescript-eslint/no-empty-function': [ 53 | 'error', 54 | { 55 | allow: ['arrowFunctions'] 56 | } 57 | ], 58 | 59 | '@typescript-eslint/no-explicit-any': 'warn', 60 | '@typescript-eslint/no-non-null-assertion': 'off', 61 | '@typescript-eslint/no-var-requires': 'off', 62 | 'vue/require-default-prop': 'off', 63 | 'vue/multi-word-component-names': 'off' 64 | } 65 | }, 66 | { 67 | files: ['**/*.js'], 68 | 69 | rules: { 70 | '@typescript-eslint/explicit-function-return-type': 'off' 71 | } 72 | } 73 | ] 74 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // @ts-nocheck 3 | // Generated by unplugin-vue-components 4 | // Read more: https://github.com/vuejs/core/pull/3399 5 | export {} 6 | 7 | /* prettier-ignore */ 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | Button: typeof import('primevue/button')['default'] 11 | ButtonGroup: typeof import('primevue/buttongroup')['default'] 12 | Checkbox: typeof import('primevue/checkbox')['default'] 13 | Chip: typeof import('primevue/chip')['default'] 14 | ConfirmPopup: typeof import('primevue/confirmpopup')['default'] 15 | Dialog: typeof import('primevue/dialog')['default'] 16 | Divider: typeof import('primevue/divider')['default'] 17 | Drawer: typeof import('primevue/drawer')['default'] 18 | IconField: typeof import('primevue/iconfield')['default'] 19 | Inplace: typeof import('primevue/inplace')['default'] 20 | InputGroup: typeof import('primevue/inputgroup')['default'] 21 | InputGroupAddon: typeof import('primevue/inputgroupaddon')['default'] 22 | InputIcon: typeof import('primevue/inputicon')['default'] 23 | InputNumber: typeof import('primevue/inputnumber')['default'] 24 | InputText: typeof import('primevue/inputtext')['default'] 25 | Listbox: typeof import('primevue/listbox')['default'] 26 | Menu: typeof import('primevue/menu')['default'] 27 | Panel: typeof import('primevue/panel')['default'] 28 | Password: typeof import('primevue/password')['default'] 29 | RouterLink: typeof import('vue-router')['RouterLink'] 30 | RouterView: typeof import('vue-router')['RouterView'] 31 | Select: typeof import('primevue/select')['default'] 32 | SelectButton: typeof import('primevue/selectbutton')['default'] 33 | Skeleton: typeof import('primevue/skeleton')['default'] 34 | Toast: typeof import('primevue/toast')['default'] 35 | ToggleSwitch: typeof import('primevue/toggleswitch')['default'] 36 | } 37 | export interface ComponentCustomProperties { 38 | Tooltip: typeof import('primevue/tooltip')['default'] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /vite.main.config.mts: -------------------------------------------------------------------------------- 1 | import type { ConfigEnv, UserConfig } from 'vite' 2 | import { defineConfig, loadEnv, mergeConfig } from 'vite' 3 | import { getBuildConfig, getBuildDefine, external, pluginHotRestart } from './vite.base.config' 4 | import tsconfigPaths from 'vite-tsconfig-paths' 5 | import { viteStaticCopy } from 'vite-plugin-static-copy' 6 | import { sentryVitePlugin } from '@sentry/vite-plugin' 7 | 8 | // https://vitejs.dev/config 9 | export default defineConfig((env) => { 10 | const forgeEnv = env as ConfigEnv<'build'> 11 | const { forgeConfigSelf } = forgeEnv 12 | const define = getBuildDefine(forgeEnv) 13 | const environment = loadEnv(env.mode, process.cwd(), '') 14 | 15 | const plugins = [ 16 | pluginHotRestart('restart'), 17 | tsconfigPaths(), 18 | viteStaticCopy({ 19 | targets: [ 20 | { 21 | src: 'assets', 22 | dest: '.' 23 | }, 24 | { 25 | src: 'node_modules/@jitl/quickjs-wasmfile-release-sync/dist/emscripten-module.wasm', 26 | dest: '.' 27 | } 28 | ] 29 | }) 30 | ] 31 | 32 | // check if we are in a tag 33 | const tag = process.env.GITHUB_REF?.includes('refs/tags/') 34 | console.log('tag', tag) 35 | if (tag) { 36 | plugins.push( 37 | sentryVitePlugin({ 38 | org: 'armaldio', 39 | project: 'cyn', 40 | authToken: environment.SENTRY_AUTH_TOKEN 41 | }) 42 | ) 43 | } 44 | 45 | const config: UserConfig = { 46 | build: { 47 | sourcemap: true, 48 | lib: { 49 | entry: forgeConfigSelf.entry!, 50 | fileName: () => '[name].js', 51 | formats: ['cjs'] 52 | }, 53 | rollupOptions: { 54 | external 55 | } 56 | }, 57 | plugins, 58 | define: { 59 | ...define, 60 | __POSTHOG_API_KEY__: JSON.stringify(environment.POSTHOG_API_KEY) 61 | }, 62 | resolve: { 63 | // Load the Node.js entry. 64 | mainFields: ['module', 'jsnext:main', 'jsnext'] 65 | } 66 | } 67 | 68 | return mergeConfig(getBuildConfig(forgeEnv), config) 69 | }) 70 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-electron/preview.ts: -------------------------------------------------------------------------------- 1 | import { createActionRunner, runWithLiveLogs } from '@pipelab/plugin-core' 2 | import { createPreviewProps, forge } from './forge' 3 | import { merge } from 'ts-deepmerge' 4 | 5 | export const previewRunner = createActionRunner>( 6 | async (options) => { 7 | const url = options.inputs['input-url'] 8 | if (url === '') { 9 | throw new Error("URL can't be empty") 10 | } 11 | 12 | if (!options.inputs.configuration) { 13 | throw new Error('Missing electron configuration') 14 | } 15 | 16 | const completeConfiguration = merge( 17 | { 18 | alwaysOnTop: false, 19 | appBundleId: 'com.pipelab.app', 20 | appCategoryType: '', 21 | appCopyright: 'Copyright © 2024 Pipelab', 22 | appVersion: '1.0.0', 23 | author: 'Pipelab', 24 | customMainCode: '', 25 | description: 'A simple Electron application', 26 | electronVersion: '', 27 | disableAsarPackaging: true, 28 | forceHighPerformanceGpu: false, 29 | enableExtraLogging: false, 30 | clearServiceWorkerOnBoot: false, 31 | enableDisableRendererBackgrounding: false, 32 | enableInProcessGPU: false, 33 | frame: true, 34 | fullscreen: false, 35 | icon: '', 36 | height: 600, 37 | name: 'Pipelab', 38 | toolbar: true, 39 | transparent: false, 40 | width: 800, 41 | enableSteamSupport: false, 42 | steamGameId: 480, 43 | ignore: [] 44 | } satisfies ElectronAppConfig.Config, 45 | options.inputs.configuration 46 | ) as ElectronAppConfig.Config 47 | 48 | const output = await forge('package', undefined, options, completeConfiguration) 49 | options.log('Opening preview', output) 50 | options.log('Opening url', url) 51 | await runWithLiveLogs( 52 | output.binary, 53 | ['--url', url], 54 | { 55 | cancelSignal: options.abortSignal 56 | }, 57 | options.log 58 | ) 59 | return 60 | } 61 | ) 62 | -------------------------------------------------------------------------------- /src/shared/save-location.ts: -------------------------------------------------------------------------------- 1 | import { array, literal, object, string, union, type InferInput } from 'valibot' 2 | 3 | export const SaveLocationInternalValidator = object({ 4 | path: string(), 5 | lastModified: string(), 6 | type: literal('internal'), 7 | configName: string() 8 | }) 9 | // export interface SaveLocationInternal { 10 | // path: string 11 | // lastModified: string 12 | // type: 'internal' 13 | // configName: string 14 | // } 15 | export type SaveLocationInternal = InferInput 16 | 17 | export const SaveLocationExternalValidator = object({ 18 | path: string(), 19 | lastModified: string(), 20 | type: literal('external'), 21 | summary: object({ 22 | plugins: array(string()), 23 | name: string(), 24 | description: string() 25 | }) 26 | }) 27 | // export interface SaveLocationExternal { 28 | // path: string 29 | // lastModified: string 30 | // type: 'external' 31 | // summary: { 32 | // plugins: string[] 33 | // name: string 34 | // description: string 35 | // } 36 | // } 37 | export type SaveLocationExternal = InferInput 38 | 39 | export const SaveLocationPipelabCloudValidator = object({ 40 | type: literal('pipelab-cloud') 41 | }) 42 | 43 | // export interface SaveLocationExternal { 44 | // path: string 45 | // lastModified: string 46 | // type: 'external' 47 | // summary: { 48 | // plugins: string[] 49 | // name: string 50 | // description: string 51 | // } 52 | // } 53 | 54 | export type SaveLocationPipelabCloud = InferInput 55 | 56 | // export interface SaveLocationPipelabCloud { 57 | // type: 'pipelab-cloud' 58 | // // TODO 59 | // } 60 | 61 | export const SaveLocationValidator = union([ 62 | SaveLocationExternalValidator, 63 | SaveLocationInternalValidator, 64 | SaveLocationPipelabCloudValidator 65 | ]) 66 | 67 | // export type SaveLocation = 68 | // // | SaveLocationInternal 69 | // SaveLocationExternal | SaveLocationPipelabCloud 70 | 71 | export type SaveLocation = InferInput 72 | -------------------------------------------------------------------------------- /src/renderer/components/FileInput.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 73 | 74 | 84 | -------------------------------------------------------------------------------- /src/renderer/models/controls.ts: -------------------------------------------------------------------------------- 1 | import { ControlType } from '@pipelab/plugin-core' 2 | import { match } from 'ts-pattern' 3 | 4 | type PrimitiveConstructor = 5 | | 'string' 6 | | 'number' 7 | | 'boolean' 8 | | 'object' 9 | | 'symbol' 10 | | 'function' 11 | | 'bigint' 12 | 13 | export const controlsToType = (control: ControlType) => { 14 | return match(control) 15 | .returnType() 16 | .with({ type: 'path' }, () => 'string') 17 | .with({ type: 'input', options: { kind: 'number' } }, () => 'number') 18 | .with({ type: 'input', options: { kind: 'text' } }, () => 'string') 19 | .with({ type: 'boolean' }, () => 'boolean') 20 | .with({ type: 'checkbox' }, () => 'boolean') 21 | .with({ type: 'select' }, () => 'string') 22 | .with({ type: 'multi-select' }, () => 'object') 23 | .with({ type: 'json' }, () => 'object') 24 | .with({ type: 'expression' }, () => 'string') 25 | .with({ type: 'array', options: { kind: 'number' } }, () => 'object') 26 | .with({ type: 'array', options: { kind: 'text' } }, () => 'object') 27 | .with({ type: 'electron:configure:v2' }, () => 'object') 28 | .exhaustive() 29 | } 30 | 31 | export const controlsToIcon = (control: ControlType) => { 32 | return match(control) 33 | .returnType() 34 | .with({ type: 'path' }, () => 'mdi-alphabetical-variant') 35 | .with({ type: 'input', options: { kind: 'number' } }, () => 'mdi-numeric') 36 | .with({ type: 'input', options: { kind: 'text' } }, () => 'mdi-alphabetical-variant') 37 | .with({ type: 'boolean' }, () => 'mdi-toggle-switch') 38 | .with({ type: 'checkbox' }, () => 'mdi-toggle-switch') 39 | .with({ type: 'select' }, () => 'mdi-alphabetical-variant') 40 | .with({ type: 'multi-select' }, () => 'mdi-code-brackets') 41 | .with({ type: 'json' }, () => 'mdi-code-braces') 42 | .with({ type: 'expression' }, () => 'mdi-') 43 | .with({ type: 'array', options: { kind: 'number' } }, () => 'mdi-code-brackets') 44 | .with({ type: 'array', options: { kind: 'text' } }, () => 'mdi-code-brackets') 45 | .with({ type: 'electron:configure:v2' }, () => 'mdi-code-braces') 46 | .exhaustive() 47 | } 48 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-system/branch.ts: -------------------------------------------------------------------------------- 1 | import { createCondition, createConditionRunner } from '@pipelab/plugin-core' 2 | 3 | export const ID = 'branch' 4 | 5 | export const branchCondition = createCondition({ 6 | id: ID, 7 | icon: '', 8 | name: 'Branch', 9 | description: '', 10 | displayString: 'If {{ params.valueA }} {{ params.operator }} {{ params.valueB }}', 11 | params: { 12 | valueA: { 13 | value: '', 14 | label: 'First value', 15 | control: { 16 | type: 'input', 17 | options: { 18 | kind: 'text' 19 | } 20 | } 21 | }, 22 | operator: { 23 | value: '', 24 | label: 'Comparison', 25 | control: { 26 | type: 'select', 27 | options: { 28 | placeholder: 'Comparison', 29 | options: [ 30 | { 31 | label: '=', 32 | value: '=' 33 | } 34 | ] 35 | } 36 | } 37 | }, 38 | valueB: { 39 | value: '', 40 | label: 'Second value', 41 | control: { 42 | type: 'input', 43 | options: { 44 | kind: 'text' 45 | } 46 | } 47 | } 48 | } 49 | }) 50 | 51 | export const branchConditionRunner = createConditionRunner( 52 | async ({ log, inputs }) => { 53 | const firstValue = inputs.valueA 54 | const secondValue = inputs.valueB 55 | const operator = inputs.operator as '=' | '!=' | '<' | '<=' | '>' | '>=' 56 | 57 | let result 58 | switch (operator) { 59 | case '!=': 60 | result = firstValue != secondValue 61 | break 62 | 63 | case '<': 64 | result = firstValue < secondValue 65 | break 66 | 67 | case '<=': 68 | result = firstValue <= secondValue 69 | break 70 | 71 | case '=': 72 | result = firstValue === secondValue 73 | break 74 | 75 | case '>': 76 | result = firstValue > secondValue 77 | break 78 | 79 | case '>=': 80 | result = firstValue >= secondValue 81 | break 82 | 83 | default: 84 | throw new Error('Unhandled case') 85 | } 86 | 87 | return result 88 | } 89 | ) 90 | -------------------------------------------------------------------------------- /src/main/presets/test-c3-offline.ts: -------------------------------------------------------------------------------- 1 | import { PresetFn, SavedFile } from '@@/model' 2 | 3 | export const testC3Offline: PresetFn = async () => { 4 | const packageWithElecton = 'electron-package-node' 5 | const steamUpload = 'steam-upload-node' 6 | 7 | const data: SavedFile = { 8 | version: '3.0.0', 9 | variables: [], 10 | name: 'C3 test without export', 11 | description: 'C3 test without export', 12 | canvas: { 13 | triggers: [ 14 | { 15 | type: 'event', 16 | origin: { 17 | pluginId: 'system', 18 | nodeId: 'manual' 19 | }, 20 | uid: 'manual-start', 21 | params: {} 22 | }, 23 | ], 24 | blocks: [ 25 | { 26 | uid: packageWithElecton, 27 | type: 'action', 28 | origin: { 29 | nodeId: 'package-to-electron', 30 | pluginId: 'electron' 31 | }, 32 | params: { 33 | 'input-folder': { 34 | editor: 'editor', 35 | value: `/home/quentin/Documents/Cyn Assets/app` 36 | }, 37 | arch: undefined, 38 | platform: undefined 39 | } 40 | }, 41 | { 42 | uid: steamUpload, 43 | type: 'action', 44 | origin: { 45 | nodeId: 'steam-upload', 46 | pluginId: 'steam' 47 | }, 48 | params: { 49 | folder: { 50 | editor: 'editor', 51 | value: `{{ steps['${packageWithElecton}']['outputs']['output'] }}`, 52 | }, 53 | appId: { 54 | editor: 'editor', 55 | value: '3047200', 56 | }, 57 | depotId: { 58 | editor: 'editor', 59 | value: '3047201', 60 | }, 61 | sdk: { 62 | editor: 'editor', 63 | value: '/home/quentin/Documents/steamworkssdk/sdk', 64 | }, 65 | username: { 66 | editor: 'editor', 67 | value: 'armaldio', 68 | }, 69 | } 70 | } 71 | ] 72 | } 73 | } 74 | 75 | return { 76 | data 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/shared/libs/migration/models/createMigration.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MigrationFn, 3 | MigrationObjInput, 4 | MigrationClass, 5 | MigrationSchema, 6 | OmitVersion, 7 | SemVer 8 | } from './migration' 9 | 10 | export function createMigration< 11 | Down extends MigrationSchema, 12 | Current extends MigrationSchema, 13 | Up extends MigrationSchema 14 | >(migration: MigrationObjInput): MigrationClass { 15 | return { 16 | version: migration.version, 17 | up: async (state, nextVersion) => { 18 | const newState = (await migration.up(state, nextVersion)) as unknown as Up 19 | newState.version = nextVersion as SemVer 20 | return newState 21 | }, 22 | down: async (state, nextVersion) => { 23 | const newState = (await migration.down(state, nextVersion)) as unknown as Down 24 | newState.version = nextVersion as SemVer 25 | return newState 26 | } 27 | } 28 | } 29 | 30 | export const initialVersion = () => { 31 | throw new Error('Unable to go down on the initial version!') 32 | } 33 | 34 | export const finalVersion = () => { 35 | throw new Error('Unable to go up on the final version!') 36 | } 37 | 38 | // export const finalVersion = (state: T) => { 39 | // return state 40 | // } 41 | 42 | /** 43 | * @deprecated 44 | * @param version 45 | * @param migration 46 | * @returns 47 | */ 48 | export function initial( 49 | version: SemVer, 50 | migration: MigrationFn, OmitVersion> 51 | ): MigrationClass { 52 | return createMigration({ 53 | version, 54 | up: migration, 55 | down() { 56 | throw new Error('Unable to go down on the initial version!') 57 | } 58 | }) 59 | } 60 | 61 | export function final( 62 | version: SemVer, 63 | migration: MigrationFn, OmitVersion> 64 | ): MigrationClass { 65 | return createMigration({ 66 | version, 67 | up: () => { 68 | throw new Error(`Unable to go up on the final version "${version}"!`) 69 | }, 70 | down: migration 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /vite.renderer.config.mts: -------------------------------------------------------------------------------- 1 | import type { ConfigEnv, UserConfig } from 'vite' 2 | import { defineConfig, loadEnv } from 'vite' 3 | import { pluginExposeRenderer } from './vite.base.config' 4 | import tsconfigPaths from 'vite-tsconfig-paths' 5 | import vue from '@vitejs/plugin-vue' 6 | import Components from 'unplugin-vue-components/vite' 7 | import { PrimeVueResolver } from '@primevue/auto-import-resolver' 8 | import vitePluginVueDevtool from 'vite-plugin-vue-devtools' 9 | import { sentryVitePlugin } from '@sentry/vite-plugin' 10 | 11 | // https://vitejs.dev/config 12 | export default defineConfig((env) => { 13 | console.log('env', env) 14 | const forgeEnv = env as ConfigEnv<'renderer'> 15 | const { root, mode, forgeConfigSelf } = forgeEnv 16 | const name = forgeConfigSelf.name ?? '' 17 | 18 | const environment = loadEnv(env.mode, process.cwd(), '') 19 | 20 | console.log('environment.SUPABASE_URL', environment.SUPABASE_URL) 21 | 22 | const plugins = [ 23 | pluginExposeRenderer(name), 24 | tsconfigPaths(), 25 | vue(), 26 | Components({ 27 | resolvers: [PrimeVueResolver()] 28 | }), 29 | vitePluginVueDevtool() 30 | // nodePolyfills(), 31 | ] 32 | 33 | const tag = process.env.GITHUB_REF?.includes('refs/tags/') 34 | console.log('tag', tag) 35 | if (tag) { 36 | plugins.push( 37 | sentryVitePlugin({ 38 | org: 'armaldio', 39 | project: 'cyn', 40 | authToken: environment.SENTRY_AUTH_TOKEN 41 | }) 42 | ) 43 | } 44 | 45 | return { 46 | root, 47 | mode, 48 | base: './', 49 | define: { 50 | __SUPABASE_URL__: JSON.stringify(environment.SUPABASE_URL), 51 | __SUPABASE_ANON_KEY__: JSON.stringify(environment.SUPABASE_ANON_KEY), 52 | __SUPABASE_PROJECT_ID__: JSON.stringify(environment.SUPABASE_PROJECT_ID), 53 | __POSTHOG_API_KEY__: JSON.stringify(environment.POSTHOG_API_KEY) 54 | }, 55 | build: { 56 | outDir: `.vite/renderer/${name}`, 57 | sourcemap: true 58 | }, 59 | optimizeDeps: { 60 | include: ['@codemirror/state', '@codemirror/view'] 61 | }, 62 | plugins, 63 | resolve: { 64 | preserveSymlinks: true 65 | }, 66 | clearScreen: false 67 | } as UserConfig 68 | }) 69 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-tauri/index.ts: -------------------------------------------------------------------------------- 1 | import { makeRunner } from './make.js' 2 | import { packageRunner } from './package.js' 3 | import { previewRunner } from './preview.js' 4 | 5 | import { createNodeDefinition } from '@pipelab/plugin-core' 6 | import icon from './public/tauri.webp' 7 | import { 8 | createMakeProps, 9 | createPackageProps, 10 | createPreviewProps, 11 | IDMake, 12 | IDPackage, 13 | IDPreview 14 | } from './forge.js' 15 | import { configureRunner, props } from './configure.js' 16 | 17 | export default createNodeDefinition({ 18 | description: 'Tauri', 19 | name: 'Tauri', 20 | id: 'tauri', 21 | icon: { 22 | type: 'image', 23 | image: icon 24 | }, 25 | nodes: [ 26 | // make and package 27 | { 28 | node: createMakeProps( 29 | IDMake, 30 | 'Create Installer', 31 | 'Create a distributable installer for your chosen platform', 32 | '', 33 | "`Build package for ${fmt.param(params['input-folder'], 'primary', 'Input folder not set')}`" 34 | ), 35 | runner: makeRunner 36 | // disabled: platform === 'linux' ? 'Electron is not supported on Linux' : undefined 37 | }, 38 | // package 39 | { 40 | node: createPackageProps( 41 | IDPackage, 42 | 'Prepare App Bundle', 43 | 'Gather all necessary files and prepare your app for distribution, creating a platform-specific bundle.', 44 | '', 45 | "`Package app from ${fmt.param(params['input-folder'], 'primary', 'Input folder not set')}`", 46 | ), 47 | runner: packageRunner 48 | }, 49 | { 50 | node: createPreviewProps( 51 | IDPreview, 52 | 'Preview app', 53 | 'Package and preview your app from an URL', 54 | '', 55 | "`Preview app from ${fmt.param(params['input-url'], 'primary', 'Input folder not set')}`", 56 | ), 57 | runner: previewRunner 58 | }, 59 | { 60 | node: props, 61 | runner: configureRunner 62 | } 63 | // { 64 | // node: propsConfigureV2, 65 | // runner: configureV2Runner 66 | // } 67 | // make without package 68 | // { 69 | // node: packageApp, 70 | // runner: packageRunner, 71 | // }, 72 | ] 73 | }) 74 | -------------------------------------------------------------------------------- /src/renderer/style/main.scss: -------------------------------------------------------------------------------- 1 | @use "sass:list"; 2 | .node-icon { 3 | font-size: 40px; 4 | } 5 | 6 | .subtitle .param { 7 | // flex: 1 0 auto; 8 | } 9 | 10 | .editor .subtitle .param { 11 | border: 1px solid #ccc; 12 | background-color: #ddd; 13 | padding: 2px 4px; 14 | margin: 2px 4px; 15 | border-radius: 4px; 16 | cursor: pointer; 17 | display: flex; 18 | flex-direction: row; 19 | gap: 4px; 20 | align-items: baseline; 21 | 22 | &.primary { 23 | color: blue; 24 | } 25 | 26 | &:hover { 27 | border: 1px solid #ddd; 28 | background-color: #eee; 29 | 30 | &.primary { 31 | color: blue; 32 | } 33 | } 34 | 35 | .step { 36 | // border: 1px solid #ccc; 37 | // background-color: #ddd; 38 | // padding: 4px; 39 | // margin: 2px; 40 | border-radius: 4px; 41 | cursor: pointer; 42 | display: flex; 43 | flex-direction: row; 44 | gap: 4px; 45 | color: rgb(0, 99, 8); 46 | } 47 | 48 | .variable { 49 | border: 1px solid rgb(139, 197, 255); 50 | background-color: rgb(173, 214, 255); 51 | color: rgb(26, 140, 255); 52 | padding: 4px; 53 | margin: 2px; 54 | border-radius: 4px; 55 | cursor: pointer; 56 | display: flex; 57 | flex-direction: row; 58 | gap: 4px; 59 | } 60 | 61 | 62 | } 63 | 64 | .editor .step-placeholder { 65 | border: 1px solid #ccc; 66 | background-color: #ddd; 67 | padding: 2px; 68 | margin: 2px; 69 | border-radius: 4px; 70 | cursor: pointer; 71 | } 72 | 73 | .step-missing { 74 | color: red; 75 | font-weight: bold; 76 | } 77 | 78 | .bold { 79 | font-weight: 700; 80 | } 81 | 82 | 83 | .p-button { 84 | font-weight: 500; 85 | } 86 | 87 | $sizes: 12, 14, 16, 18, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 72, 80, 96 !default; 88 | @for $i from 1 through list.length($sizes) { 89 | .fs-#{list.nth($sizes, $i)} { 90 | font-size: #{list.nth($sizes, $i)}px; 91 | } 92 | } 93 | 94 | .rotate { 95 | animation: rotate 2s linear infinite; 96 | } 97 | 98 | @keyframes rotate { 99 | 0% { 100 | transform: rotate(0deg); 101 | } 102 | 100% { 103 | transform: rotate(360deg); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-minify/code.ts: -------------------------------------------------------------------------------- 1 | import { createAction, createActionRunner, createPathParam } from '@pipelab/plugin-core' 2 | 3 | export const ID = 'minify:code' 4 | 5 | export const minifyCode = createAction({ 6 | id: ID, 7 | name: 'Minify code', 8 | description: '', 9 | icon: '', 10 | displayString: '`Minify code`', 11 | meta: {}, 12 | params: { 13 | 'input-folder': createPathParam('', { 14 | required: true, 15 | label: 'Folder to compress', 16 | control: { 17 | type: 'path', 18 | options: { 19 | properties: ['openDirectory'] 20 | } 21 | } 22 | }) 23 | }, 24 | outputs: {} 25 | }) 26 | 27 | const getAllJsFiles = async (dir: string): Promise => { 28 | const { readdir } = await import('node:fs/promises') 29 | const { join } = await import('node:path') 30 | 31 | const files = await readdir(dir, { withFileTypes: true }) 32 | return files.flatMap((file) => { 33 | const fullPath = join(dir, file.name) 34 | if (file.isDirectory()) { 35 | return getAllJsFiles(fullPath) 36 | } else if (file.isFile() && fullPath.endsWith('.js')) { 37 | return fullPath 38 | } 39 | return [] 40 | }) 41 | } 42 | 43 | export const minifyCodeRunner = createActionRunner( 44 | async ({ log, inputs, cwd, abortSignal }) => { 45 | const { app } = await import('electron') 46 | const { join, dirname } = await import('node:path') 47 | const { mkdir, access, chmod } = await import('node:fs/promises') 48 | const esbuild = (await import('esbuild')).default 49 | 50 | const jsFiles = await getAllJsFiles(inputs['input-folder']) 51 | 52 | for (const file of jsFiles) { 53 | await esbuild.build({ 54 | entryPoints: [file], // Process one file at a time 55 | outfile: file, // Write directly to the original file 56 | minify: true, // Minify the content 57 | bundle: false, // Don't bundle dependencies 58 | sourcemap: false, // No source maps (optional) 59 | format: 'esm', // Optional: Specify output format 60 | write: true // Ensure the file is overwritten 61 | }) 62 | console.log(`Minified: ${file}`) 63 | } 64 | 65 | log('Minified code') 66 | } 67 | ) 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pipelab 2 | 3 | ![logo](./readme/full_white_bg_black_text.png) 4 | 5 | ## What is Pipelab? 6 | 7 | A visual tool to create task automation workflows. 8 | 9 | ## Why use Pipelab? 10 | 11 | - Create cross-platform desktop applications 12 | - Deploy to popular platforms (Steam, Itch.io, etc.) 13 | - Automate repetitive tasks 14 | 15 | # Getting Started 16 | 17 | # Making a release 18 | ``` 19 | pnpm changeset version 20 | pnpm changeset tag 21 | ``` 22 | 23 | # Architecture 24 | ```mermaid 25 | graph TD 26 | classDef pipelab fill:#0096FF,stroke:#333,stroke-width:4px; 27 | classDef todo stroke:#333,stroke-width:4px, stroke-dasharray: 4px; 28 | 29 | DesktopApp[Desktop App - Pipelab] 30 | GameBundle[Game Editor output] 31 | 32 | subgraph GameEditors 33 | Construct3[Construct 3] 34 | Godot[Godot] 35 | GDevelop[GDevelop] 36 | end 37 | 38 | PipelabPlugin[Pipelab Plugin] 39 | SteamPlugin[Steam Plugin] 40 | CoreMessaging[Core Messaging Library] 41 | Renderers[Renderers] 42 | 43 | subgraph Runtime 44 | Electron 45 | Tauri 46 | Webview 47 | end 48 | 49 | subgraph Platforms 50 | Steam 51 | Itch 52 | Poki 53 | end 54 | 55 | Steamworks[steamworks.js Library] 56 | 57 | GameEditors -->|Bundles to| GameBundle 58 | GameBundle -->|Is imported into| DesktopApp 59 | GameEditors -->|Includes| PipelabPlugin 60 | 61 | PipelabPlugin -->|Is included in| GameBundle 62 | PipelabPlugin -->|Implements| CoreMessaging 63 | 64 | SteamPlugin -->|Is included in| GameBundle 65 | SteamPlugin -->|Implements| CoreMessaging 66 | SteamPlugin -->|Uses| Steamworks 67 | 68 | CoreMessaging -->|Passes messages to| Renderers 69 | Runtime -->|Is embedded in| Renderers 70 | DesktopApp -->|Packages to| Runtime 71 | Runtime -->|Handles events from| CoreMessaging 72 | 73 | DesktopApp -->|Deploys to| Platforms 74 | Platforms -->|Uses| Runtime 75 | 76 | class DesktopApp,PipelabPlugin,SteamPlugin,CoreMessaging pipelab; 77 | class SteamPlugin,Godot,GDevelop,Tauri,Webview,Itch,Poki todo; 78 | ``` 79 | 80 | # Development 81 | ## Enable source maps 82 | ```bash 83 | NODE_OPTIONS=--enable-source-maps pnpm xxx 84 | ``` 85 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-construct/export-c3p.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExtractInputsFromAction, 3 | createAction, 4 | createActionRunner, 5 | createPathParam, 6 | fileExists 7 | } from '@pipelab/plugin-core' 8 | import { exportc3p, sharedParams } from './export-shared.js' 9 | 10 | export const ID = 'export-construct-project' 11 | 12 | export const exportAction = createAction({ 13 | id: ID, 14 | name: 'Export .c3p', 15 | displayString: 16 | "`Export project ${fmt.param(params.file, 'primary', 'No path selected')} ${params.version ? `r${params.version}` : ''}`", 17 | meta: {}, 18 | params: { 19 | file: createPathParam('', { 20 | label: 'File (.c3p)', 21 | required: true, 22 | control: { 23 | type: 'path', 24 | label: 'Pick a file (.c3p)', 25 | options: { 26 | properties: ['openFile'], 27 | filters: [{ name: 'Construct Project', extensions: ['c3p'] }], 28 | title: 'aaaa', 29 | message: 'bbbb' 30 | } 31 | } 32 | }), 33 | ...sharedParams 34 | }, 35 | outputs: { 36 | folder: { 37 | type: 'path', 38 | deprecated: true, 39 | value: undefined as undefined | string, 40 | label: 'Exported zip' 41 | // schema: schema.string() 42 | }, 43 | parentFolder: { 44 | type: 'path', 45 | deprecated: false, 46 | value: undefined as undefined | string, 47 | label: 'Path to parent folder of exported zip' 48 | // schema: schema.string() 49 | }, 50 | zipFile: { 51 | type: 'path', 52 | deprecated: false, 53 | value: undefined as undefined | string, 54 | label: 'Exported zip' 55 | // schema: schema.string() 56 | } 57 | }, 58 | description: 'Export construct project from .c3p file', 59 | icon: '' 60 | }) 61 | 62 | export const ExportActionRunner = createActionRunner(async (options) => { 63 | const file = options.inputs.file 64 | 65 | const c3pFileExists = await fileExists(file) 66 | 67 | if (!c3pFileExists) { 68 | throw new Error('You must specify a valid .c3p file') 69 | } 70 | 71 | await exportc3p(file, options) 72 | options.log('exportc3p done') 73 | }) 74 | 75 | export type Params = ExtractInputsFromAction 76 | -------------------------------------------------------------------------------- /src/renderer/store/app.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { createEventHook } from "@vueuse/core"; 3 | import { ref } from "vue"; 4 | import { useAPI } from "@renderer/composables/api"; 5 | import { RendererPluginDefinition } from "@pipelab/plugin-core"; 6 | import { Presets } from "@@/apis"; 7 | import { useLogger } from "@@/logger"; 8 | 9 | export const useAppStore = defineStore('app', () => { 10 | const { logger } = useLogger() 11 | 12 | /** Presets to load from */ 13 | const presets = ref() 14 | 15 | /** All the plugins definitions */ 16 | const pluginDefinitions = ref>([]) 17 | 18 | const api = useAPI() 19 | 20 | const { on: onPresetsLoaded, trigger: triggerPresetsLoaded } = createEventHook() 21 | 22 | const init = async () => { 23 | // 24 | const nodeGetResult = await api.execute('nodes:get') 25 | 26 | if (nodeGetResult.type === 'error') { 27 | throw new Error(nodeGetResult.ipcError) 28 | } 29 | 30 | const { result } = nodeGetResult 31 | const { nodes: nodeDefs } = result 32 | 33 | pluginDefinitions.value = nodeDefs 34 | 35 | // 36 | const presentResult = await api.execute('presets:get') 37 | if (presentResult.type === 'error') { 38 | throw new Error(presentResult.ipcError) 39 | } 40 | presets.value = presentResult.result 41 | 42 | // 43 | triggerPresetsLoaded() 44 | } 45 | 46 | const getPluginDefinition = (pluginId: string) => { 47 | const result = pluginDefinitions.value.find((nodeDef) => { 48 | if (!pluginId) { 49 | logger().error('Missing origin: node', pluginId) 50 | } 51 | return nodeDef.id === pluginId 52 | }) 53 | return result 54 | } 55 | 56 | const getNodeDefinition = (nodeId: string, pluginId: string) => { 57 | // const getNodeDefinition = (node: T extends Block ? T : never) => { 58 | const plugin = getPluginDefinition(pluginId) 59 | if (plugin) { 60 | return plugin.nodes.find((pluginNode) => pluginNode.node.id === nodeId) 61 | } 62 | return undefined 63 | } 64 | 65 | return { 66 | presets, 67 | onPresetsLoaded, 68 | init, 69 | 70 | pluginDefinitions, 71 | 72 | getPluginDefinition, 73 | getNodeDefinition, 74 | } 75 | }) 76 | -------------------------------------------------------------------------------- /src/renderer/utils/quickjs.ts: -------------------------------------------------------------------------------- 1 | import { useLogger } from '@@/logger' 2 | import { isRenderer } from '@@/validation' 3 | import { newQuickJSWASMModuleFromVariant, newVariant, RELEASE_SYNC } from 'quickjs-emscripten' 4 | import { Arena } from 'quickjs-emscripten-sync' 5 | import { fmt } from './fmt' 6 | 7 | class EvaluationError extends Error { 8 | constructor( 9 | public name: string, 10 | public description: string 11 | ) { 12 | super(description) 13 | this.name = name 14 | } 15 | } 16 | 17 | export const createQuickJs = async () => { 18 | // TODO: could improve 19 | const { logger } = useLogger() 20 | 21 | let location: string 22 | if (isRenderer()) { 23 | location = (await import('@jitl/quickjs-wasmfile-release-sync/wasm?url')).default 24 | } else { 25 | const { join } = await import('node:path') 26 | const { dirname } = await import('@main/paths') 27 | const _dirname = await dirname() 28 | location = join(_dirname, 'emscripten-module.wasm') 29 | } 30 | 31 | const variant = newVariant(RELEASE_SYNC, { 32 | wasmLocation: location 33 | }) 34 | const quickjs = await newQuickJSWASMModuleFromVariant(variant) 35 | 36 | const run = (code: string, params: Record) => { 37 | const vm = quickjs.newContext() 38 | const arena = new Arena(vm, { isMarshalable: true }) 39 | 40 | const exposed = { 41 | console: { 42 | // eslint-disable-next-line no-console 43 | log: console.log 44 | }, 45 | fmt, 46 | ...params 47 | } 48 | arena.expose(exposed) 49 | 50 | // const finalCode = `export const result = ${code}` 51 | const finalCode = `(() => { 52 | // console.log('params', params); 53 | // console.log('params.parameters', params.parameters); 54 | 55 | return ${code}; 56 | })() 57 | ` 58 | 59 | try { 60 | const result = arena.evalCode(finalCode) 61 | 62 | return result 63 | } catch (e) { 64 | logger().error('error', e) 65 | logger().error("Final code was", finalCode) 66 | throw new EvaluationError(e.name, e.message) 67 | } finally { 68 | arena.dispose() 69 | vm.dispose() 70 | } 71 | } 72 | 73 | return { 74 | run 75 | } 76 | } 77 | 78 | export type CreateQuickJSFn = ReturnType 79 | -------------------------------------------------------------------------------- /src/renderer/composables/api.ts: -------------------------------------------------------------------------------- 1 | import { Channels, Data, End, Events, Message, RequestId } from '@@/apis' 2 | import { useLogger } from '@@/logger' 3 | import { klona } from 'klona' 4 | import { nanoid } from 'nanoid' 5 | import { toRaw } from 'vue' 6 | 7 | // type OnOptions = { 8 | // onMessage: (event: Electron.IpcRendererEvent, ...data: any[]) => Promise 9 | // } 10 | 11 | export type Listener = ( 12 | event: Electron.IpcRendererEvent, 13 | data: Events 14 | ) => Promise 15 | 16 | export const useAPI = ( 17 | pipe: { 18 | send: (channel: string, ...args: any[]) => void 19 | on: any 20 | } = window.electron.ipcRenderer 21 | ) => { 22 | const { logger } = useLogger() 23 | /** 24 | * Send an order 25 | */ 26 | const send = (channel: KEY, args?: Data) => pipe.send(channel, args) 27 | 28 | const on = ( 29 | channel: KEY | string, 30 | listener: (event: Electron.IpcRendererEvent, data: Events) => void 31 | ) => { 32 | // console.log('listening for', channel) 33 | return pipe.on(channel, listener) 34 | } 35 | 36 | /** 37 | * Send an order and wait for it's execution 38 | */ 39 | const execute = async ( 40 | channel: KEY, 41 | data?: Data, 42 | listener?: Listener 43 | ) => { 44 | const newId = nanoid() as RequestId 45 | // eslint-disable-next-line no-async-promise-executor 46 | return new Promise>(async (resolve) => { 47 | const message: Message = { 48 | requestId: newId, 49 | data: toRaw(klona(data)) 50 | } 51 | // console.log(`${newId} for channel ${channel}`) 52 | const cancel = on(newId, async (event, data) => { 53 | // console.log('receiving event', event, data) 54 | if (data.type === 'end') { 55 | cancel() 56 | return resolve(data.data) 57 | } else { 58 | await listener?.(event, data) 59 | } 60 | }) 61 | 62 | try { 63 | pipe.send(channel, message) 64 | } catch (e) { 65 | logger().error(e) 66 | logger().error(channel, message) 67 | } 68 | }) 69 | } 70 | 71 | return { 72 | send, 73 | on, 74 | execute 75 | } 76 | } 77 | 78 | export type UseAPI = ReturnType 79 | -------------------------------------------------------------------------------- /assets/electron/template/app/src/handlers/user/folder.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { app } from 'electron' 4 | import { join } from 'path' 5 | 6 | import slash from 'slash'; 7 | import { getAppName } from '../../utils.js'; 8 | 9 | /** 10 | * @param {import('@pipelab/core').MakeInputOutput} json 11 | * @param {import('ws').WebSocket} ws 12 | * @param {ElectronAppConfig.Config} config 13 | */ 14 | export default (json, ws, config) => { 15 | try { 16 | const name = json.body.name 17 | 18 | let folder 19 | 20 | const { env } = process 21 | // windows linux 22 | const { APPDATA, LOCALAPPDATA, XDG_DATA_HOME, XDG_CONFIG_HOME } = env 23 | const appDataBackup = app.getPath('appData') 24 | const localAppData = LOCALAPPDATA ?? XDG_DATA_HOME ?? appDataBackup 25 | const appData = APPDATA ?? XDG_CONFIG_HOME ?? appDataBackup 26 | 27 | const appNameFolder = getAppName(config) 28 | 29 | const localUserData = join(localAppData, appNameFolder) 30 | const userData = join(appData, appNameFolder) 31 | 32 | if (name === 'app') { 33 | folder = app.getAppPath() 34 | } else if (name === 'project') { 35 | folder = join(app.getAppPath(), 'src', 'app') // path to construct files 36 | } else if (name === 'localAppData') { 37 | folder = localAppData 38 | } else if (name === 'localUserData') { 39 | folder = localUserData 40 | } else if (name === 'userData') { 41 | folder = userData 42 | } else { 43 | folder = app.getPath(name) 44 | } 45 | 46 | /** 47 | * @type {import('@pipelab/core').MakeInputOutput} 48 | */ 49 | const userFolderResult = { 50 | url: json.url, 51 | correlationId: json.correlationId, 52 | body: { 53 | data: slash(folder) 54 | } 55 | } 56 | ws.send(JSON.stringify(userFolderResult)) 57 | } catch (e) { 58 | console.error('e', e) 59 | /** 60 | * @type {import('@pipelab/core').MakeInputOutput} 61 | */ 62 | const userFolderResult = { 63 | url: json.url, 64 | correlationId: json.correlationId, 65 | body: { 66 | error: e.message 67 | } 68 | } 69 | ws.send(JSON.stringify(userFolderResult)) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/shared/libs/plugin-nvpatch/nvpatch.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createAction, 3 | createActionRunner, 4 | createPathParam, 5 | runWithLiveLogs 6 | } from '@pipelab/plugin-core' 7 | 8 | export const ID = 'nvpatch' 9 | 10 | export const NVPatch = createAction({ 11 | id: ID, 12 | name: 'Patch binary', 13 | description: '', 14 | icon: '', 15 | displayString: "`Patch binary ${fmt.param(params['input'], 'primary')}`", 16 | meta: {}, 17 | params: { 18 | input: createPathParam('', { 19 | required: true, 20 | label: 'File to patch', 21 | control: { 22 | type: 'path', 23 | options: { 24 | properties: ['openFile'] 25 | } 26 | } 27 | }) 28 | }, 29 | outputs: {} 30 | }) 31 | 32 | export const NVPatchRunner = createActionRunner( 33 | async ({ log, inputs, paths, abortSignal, cwd }) => { 34 | const { join, resolve } = await import('node:path') 35 | const { writeFile, cp, mkdir } = await import('node:fs/promises') 36 | const { shell } = await import('electron') 37 | const StreamZip = await import('node-stream-zip') 38 | 39 | // const peBinFolder = join(cwd, 'EditBinPE') 40 | // const peBinZip = peBinFolder + '.zip' 41 | // const peBinBin = join(peBinFolder, 'EditBinPE.exe') 42 | 43 | // log('Downloading EditBinPE to', peBinFolder) 44 | 45 | // // donwload the zip file 46 | // await downloadFile( 47 | // 'https://github.com/GabrielFrigo4/EditBinPE/releases/download/Release-1.0.1/EditBinPE.zip', 48 | // peBinZip, 49 | // {}, 50 | // abortSignal 51 | // ) 52 | 53 | // log('Unzipping', peBinZip, 'to', peBinFolder) 54 | // await mkdir(peBinFolder, { recursive: true }) 55 | // // unzip the file 56 | // const zip = new StreamZip.default.async({ file: peBinZip }) 57 | // const bytes = await zip.extract(null, peBinFolder) 58 | // console.log('bytes', bytes) 59 | // await zip.close() 60 | 61 | // run 62 | 63 | console.log('process.env', process.env) 64 | 65 | const nvpatch = resolve(join(process.env.USERPROFILE, '.dotnet', 'tools', 'nvpatch.exe')) 66 | 67 | log('Trying nvpatch from', nvpatch) 68 | 69 | await runWithLiveLogs( 70 | nvpatch, 71 | ['--enable', inputs['input']], 72 | { 73 | cancelSignal: abortSignal, 74 | }, 75 | log 76 | ) 77 | log('nvpatch done') 78 | } 79 | ) 80 | -------------------------------------------------------------------------------- /src/renderer/components/nodes/EditorNodeEventEmpty.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 49 | 50 | 95 | -------------------------------------------------------------------------------- /assets/tauri/template/app/src/main.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from '@tauri-apps/api/core' 2 | import { listen } from '@tauri-apps/api/event' 3 | 4 | async function showOverlay() { 5 | // eslint-disable-next-line no-console 6 | console.log('show overlay') 7 | // Invoke the command 8 | await invoke('showOverlay') 9 | } 10 | 11 | async function hideOverlay() { 12 | // eslint-disable-next-line no-console 13 | console.log('hide overlay') 14 | // Invoke the command 15 | await invoke('hideOverlay') 16 | } 17 | 18 | function getRandomColour(){ 19 | var red = Math.floor(Math.random() * 256); 20 | var green = Math.floor(Math.random() * 256); 21 | var blue = Math.floor(Math.random() * 256); 22 | 23 | return "rgb("+red+","+green+"," +blue+" )"; 24 | } 25 | 26 | window.addEventListener('DOMContentLoaded', () => { 27 | const counter = document.querySelector('#counter') 28 | let counterValue = 0 29 | 30 | document.querySelector('#btn')?.addEventListener('click', async (e) => { 31 | e.preventDefault() 32 | await showOverlay() 33 | }) 34 | 35 | document.querySelector('#btn2')?.addEventListener('click', async (e) => { 36 | e.preventDefault() 37 | await hideOverlay() 38 | }) 39 | 40 | const canvas = document.getElementById('wgpuCanvas') as HTMLCanvasElement | null 41 | if (!canvas) { 42 | console.log('canvas not found') 43 | return 44 | } 45 | const ctx = canvas.getContext('2d') 46 | if (!ctx) { 47 | console.log('ctx not found') 48 | return 49 | } 50 | 51 | ctx.fillStyle = getRandomColour(); 52 | ctx.fillRect(70,50,100,100); 53 | 54 | ctx.fillStyle = getRandomColour(); 55 | ctx.fillRect(10,10,100,100); 56 | 57 | setInterval(() => { 58 | if (!counter) { 59 | return 60 | } 61 | counter.innerHTML = counterValue.toString() 62 | counterValue += 1 63 | }, 100) 64 | }) 65 | 66 | listen('frame-data', (event) => { 67 | const data = event.payload 68 | // console.log('data', data) 69 | // Process the frame data (e.g., create an ImageData object and draw it on a canvas) 70 | const canvas = document.getElementById('wgpuCanvas') as HTMLCanvasElement | null 71 | if (!canvas) { 72 | console.log('canvas not found') 73 | return 74 | } 75 | const ctx = canvas.getContext('2d') 76 | if (!ctx) { 77 | console.log('ctx not found') 78 | return 79 | } 80 | const imageData = new ImageData(new Uint8ClampedArray(data), canvas.width, canvas.height) 81 | // console.log('imageData', imageData) 82 | ctx.putImageData(imageData, 0, 0) 83 | }) 84 | -------------------------------------------------------------------------------- /src/renderer/store/files.ts: -------------------------------------------------------------------------------- 1 | import { SavedFile } from '@@/model' 2 | import { defineStore } from 'pinia' 3 | import { ref } from 'vue' 4 | import { Draft, create } from 'mutative' 5 | import { createConfig } from '@renderer/utils/config' 6 | import { klona } from 'klona' 7 | import { SaveLocationValidator } from '@@/save-location' 8 | import { object, string, optional, record, InferInput, parse, ValiError } from 'valibot' 9 | 10 | export interface File { 11 | data: SavedFile 12 | } 13 | 14 | export const FileRepoValidator = object({ 15 | version: optional(string(), '1.0.0'), 16 | data: optional(record(string(), SaveLocationValidator), {}) 17 | }) 18 | 19 | // export interface FileRepo { 20 | // version: string 21 | // data: Record 22 | // } 23 | export type FileRepo = InferInput 24 | 25 | export const useFile = (name: string) => { 26 | // const file = ref() 27 | 28 | const { load, save } = createConfig>(name) 29 | 30 | return { 31 | save, 32 | load 33 | } 34 | } 35 | 36 | const defaultFileRepo: FileRepo = { 37 | version: '1.0.0', 38 | data: {} 39 | } 40 | 41 | export const useFiles = defineStore('files', () => { 42 | const files = ref(defaultFileRepo) 43 | 44 | const { load: loadConfig, save: saveConfig } = createConfig('projects') 45 | 46 | const update = async (callback: (state: Draft) => void) => { 47 | files.value = create(files.value, callback) 48 | console.log('files.value', files.value) 49 | await saveConfig(klona(files.value)) 50 | } 51 | 52 | const load = async () => { 53 | const data = await loadConfig() 54 | 55 | if (data.type === 'success') { 56 | try { 57 | files.value = parse(FileRepoValidator, data.result.result) 58 | } catch (e) { 59 | if (e instanceof ValiError) { 60 | console.log("error", e.issues) 61 | } 62 | console.error('error', e) 63 | files.value = defaultFileRepo 64 | } 65 | } else { 66 | files.value = defaultFileRepo 67 | } 68 | } 69 | 70 | const remove = async (id: string) => { 71 | update((state) => { 72 | delete state.data[id] 73 | }) 74 | } 75 | 76 | const loadFile = (name: string) => { 77 | const { load, save } = createConfig>(name) 78 | return { 79 | load, 80 | save 81 | } 82 | } 83 | 84 | return { 85 | files: files, 86 | // files: readonly(files), 87 | 88 | load, 89 | loadFile, 90 | update, 91 | remove 92 | } 93 | }) 94 | -------------------------------------------------------------------------------- /assets/electron/template/app/forge.config.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { FusesPlugin } = require('@electron-forge/plugin-fuses') 4 | const { FuseV1Options, FuseVersion } = require('@electron/fuses') 5 | const { join } = require('path') 6 | 7 | const PipelabPlugin = require('./pipelab-plugin.cjs') 8 | 9 | const config = require('./config.cjs') 10 | 11 | let unpackFilter = '' 12 | const platform = process.platform 13 | 14 | if (platform === 'win32') { 15 | unpackFilter = join('**', 'steamworks.js', 'dist', 'win64', '*.{node,dll}') 16 | } else if (platform === 'darwin') { 17 | unpackFilter = join('**', 'steamworks.js', 'dist', 'win64', '*.{node,dylib}') 18 | } else if (platform === 'linux') { 19 | unpackFilter = join('**', 'steamworks.js', 'dist', 'win64', '*{node,so,lib}') 20 | } 21 | 22 | /** 23 | * @type {import('@electron-forge/shared-types').ForgeConfig} 24 | */ 25 | module.exports = { 26 | outDir: './out', 27 | packagerConfig: { 28 | asar: config.disableAsarPackaging 29 | ? false 30 | : { 31 | unpack: unpackFilter 32 | }, 33 | name: config.name, 34 | appBundleId: config.appBundleId, 35 | appCopyright: config.appCopyright, 36 | appVersion: config.appVersion, 37 | // icon: './assets/icon', // file extension is ommited (auto completed by platform: darwin: icns, linux, win32) 38 | win32metadata: { 39 | CompanyName: config.author, 40 | FileDescription: config.description 41 | }, 42 | appCategoryType: config.appCategoryType, 43 | icon: config.icon, 44 | ignore: config.ignore 45 | }, 46 | rebuildConfig: {}, 47 | makers: [ 48 | { 49 | name: '@electron-forge/maker-dmg', 50 | config: {} 51 | }, 52 | { 53 | name: '@electron-forge/maker-squirrel', 54 | config: {} 55 | }, 56 | { 57 | name: '@electron-forge/maker-zip' 58 | } 59 | ], 60 | plugins: [ 61 | // { 62 | // name: '@electron-forge/plugin-auto-unpack-natives', 63 | // config: { 64 | // } 65 | // }, 66 | new FusesPlugin({ 67 | version: FuseVersion.V1, 68 | [FuseV1Options.RunAsNode]: false, 69 | [FuseV1Options.EnableCookieEncryption]: true, 70 | [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, 71 | [FuseV1Options.EnableNodeCliInspectArguments]: false, 72 | [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: 73 | config.disableAsarPackaging === true ? false : true, 74 | [FuseV1Options.OnlyLoadAppFromAsar]: config.disableAsarPackaging === true ? false : true 75 | }), 76 | new PipelabPlugin() 77 | ] 78 | } 79 | --------------------------------------------------------------------------------