From e1befbf2d72a47e4e14ae911b048728be0875574 Mon Sep 17 00:00:00 2001 From: Quentin Goinaud Date: Mon, 4 May 2026 14:16:54 +0200 Subject: [PATCH 001/105] moved to monorepo --- .changeset/config.json | 9 +- .changeset/free-animals-bet.md | 32 + .devcontainer/devcontainer.json | 6 +- .env.example | 5 - .gemini/settings.json | 7 +- .github/workflows/pipeline.yml | 452 + .github/workflows/push.yml | 161 - .gitignore | 8 + .prettierignore | 6 - .prettierrc.yaml | 4 - ARCHITECTURE.md | 91 + CHANGELOG.md | 58 - GEMINI.md | 71 +- LICENSE.md => LICENSE | 2 +- README.md | 123 +- TODO | 74 - TODO_MONOREPO.md | 127 + apps/cli/.npmrc | 1 + apps/cli/README.md | 23 + apps/cli/package.json | 55 + apps/cli/src/commands/history.ts | 43 + apps/cli/src/commands/maintenance.ts | 61 + apps/cli/src/commands/pipelines.ts | 250 + apps/cli/src/commands/setup.ts | 108 + apps/cli/src/index.ts | 234 + apps/cli/src/paths.ts | 1 + apps/cli/tests/e2e/tests/history.spec.ts | 57 + apps/cli/tests/e2e/tests/integration.spec.ts | 73 + apps/cli/tests/e2e/vitest.config.mts | 20 + apps/cli/tsconfig.json | 33 + apps/cli/tsdown.config.ts | 24 + .editorconfig => apps/desktop/.editorconfig | 0 .hintrc => apps/desktop/.hintrc | 0 .mise.toml => apps/desktop/.mise.toml | 5 +- apps/desktop/.npmrc | 1 + FAQ.md => apps/desktop/FAQ.md | 0 apps/desktop/README.md | 30 + .../assets}/build/entitlements.mac.plist | 0 .../desktop/assets}/build/icon.icns | Bin .../desktop/assets}/build/icon.ico | Bin .../desktop/assets}/build/icon.png | Bin apps/desktop/assets/build/notarize.cjs | 36 + crowdin.yml => apps/desktop/crowdin.yml | 0 apps/desktop/forge.config.ts | 123 + apps/desktop/forge.env.d.ts | 44 + apps/desktop/index.html | 53 + apps/desktop/package.json | 57 + .../scripts/headless-electron-tester.mts | 10 + apps/desktop/scripts/migrate-file.mts | 40 + apps/desktop/scripts/test-update-server.mts | 101 + apps/desktop/src/main.ts | 254 + apps/desktop/src/main/ipc-handlers.ts | 36 + apps/desktop/src/main/server-process.ts | 122 + apps/desktop/src/preload.d.ts | 8 + apps/desktop/src/preload.ts | 52 + apps/desktop/tsconfig.json | 32 + apps/desktop/vite.base.config.mts | 103 + apps/desktop/vite.main.config.mts | 70 + apps/desktop/vite.preload.config.mts | 37 + apps/desktop/vite.renderer.config.mts | 33 + apps/ui/.gitignore | 1 + apps/ui/README.md | 34 + apps/ui/auto-imports.d.ts | 88 + apps/ui/components.d.ts | 82 + apps/ui/index.html | 32 + apps/ui/package.json | 91 + apps/ui/src/App.vue | 309 + {src/renderer => apps/ui/src}/Root.vue | 2 +- {src/renderer => apps/ui/src}/agent/log.ts | 0 {src/renderer => apps/ui/src}/agent/trpc.ts | 4 +- apps/ui/src/agent/utils/json.ts | 7 + .../ui/src}/assets/css/styles.less | 14 +- .../renderer => apps/ui/src}/assets/icons.svg | 0 {src => apps/ui/src}/auto-imports.d.ts | 2 +- apps/ui/src/components/AddNodeButton.model.ts | 15 + .../ui/src}/components/AddNodeButton.vue | 151 +- .../ui/src}/components/AddTriggerButton.vue | 126 +- .../ui/src}/components/BuildDetailsModal.vue | 345 +- .../ui/src}/components/BuildHistoryDialog.vue | 158 +- .../ui/src}/components/BuildHistoryItem.vue | 135 +- .../ui/src}/components/BuildHistoryList.vue | 158 +- .../ui/src}/components/BuildHistoryView.vue | 74 +- .../ui/src}/components/BuildStatusBadge.vue | 92 +- .../ui/src}/components/ColorPicker.vue | 8 +- apps/ui/src/components/ConnectingPage.vue | 267 + .../src}/components/DevBenefitsOverride.vue | 48 +- apps/ui/src/components/DisconnectedPage.vue | 346 + apps/ui/src/components/FileInput.vue | 83 + .../ui/src}/components/Layout.vue | 556 +- .../ui/src}/components/ScenarioListItem.vue | 84 +- .../src/components/ScenarioListItemFile.vue | 54 + .../components/ScenarioListItemRecent.vue | 22 +- apps/ui/src/components/Settings.vue | 729 + .../SubscriptionLoadingIndicator.vue | 16 +- .../ui/src}/components/UpgradeDialog.vue | 114 +- .../ui/src}/components/UpgradeNowButton.vue | 30 +- apps/ui/src/components/WebFilePicker.vue | 244 + .../components/__tests__/HelloWorld.spec.ts | 10 +- .../components/nodes/EditorNodeAction.vue | 232 +- .../components/nodes/EditorNodeCondition.vue | 73 +- .../src}/components/nodes/EditorNodeDummy.vue | 8 +- .../src}/components/nodes/EditorNodeEvent.vue | 110 +- .../components/nodes/EditorNodeEventEmpty.vue | 35 +- .../src}/components/nodes/EditorNodeLoop.vue | 72 +- .../ui/src}/components/nodes/ParamEditor.vue | 338 +- .../src}/components/nodes/ParamEditorBody.vue | 201 +- .../ui/src}/components/nodes/PluginIcon.vue | 14 +- apps/ui/src/composables/api.ts | 201 + .../ui/src}/composables/handlers.ts | 35 +- .../ui/src}/composables/middleware.ts | 36 +- apps/ui/src/composables/use-shell.ts | 134 + apps/ui/src/composables/usePipeline.ts | 81 + apps/ui/src/composables/usePostHog.ts | 9 + apps/ui/src/composables/useTour.ts | 36 + apps/ui/src/composables/websocket-client.ts | 435 + apps/ui/src/composables/websocket-manager.ts | 143 + apps/ui/src/env.d.ts | 8 + apps/ui/src/i18n.ts | 16 + apps/ui/src/main.ts | 146 + apps/ui/src/models/constants.ts | 1 + apps/ui/src/models/controls.ts | 51 + {src/renderer => apps/ui/src}/models/error.ts | 4 +- .../renderer => apps/ui/src}/pages/editor.vue | 682 +- .../ui/src}/pages/environement-editor.vue | 78 +- {src/renderer => apps/ui/src}/pages/index.vue | 769 +- .../ui/src}/pages/nodes-editor.vue | 34 +- .../ui/src}/pages/project-settings-editor.vue | 7 +- .../ui/src}/pages/scenarios.vue | 8 +- .../ui/src}/pages/simple-editor.vue | 95 +- {src/renderer => apps/ui/src}/pages/team.vue | 10 +- .../ui/src}/pages/variables-editor.vue | 88 +- apps/ui/src/plugins/posthog.ts | 26 + apps/ui/src/preload.d.ts | 8 + apps/ui/src/router/router.ts | 66 + apps/ui/src/store/app.ts | 77 + apps/ui/src/store/auth.ts | 397 + apps/ui/src/store/build-history.ts | 402 + .../ui/src}/store/editor.spec.ts | 18 +- {src/renderer => apps/ui/src}/store/editor.ts | 530 +- apps/ui/src/store/files.ts | 70 + apps/ui/src/store/recents.ts | 34 + apps/ui/src/store/settings.ts | 116 + apps/ui/src/store/ui.ts | 46 + apps/ui/src/store/user.ts | 12 + {src/renderer => apps/ui/src}/style/main.scss | 5 +- apps/ui/src/style/main.ts | 5 + .../ui/src}/utils/code-editor.ts | 80 +- .../ui/src}/utils/code-editor/step-plugin.ts | 88 +- apps/ui/src/utils/config.ts | 76 + {src/renderer => apps/ui/src}/utils/graph.ts | 16 +- apps/ui/tsconfig.json | 26 + apps/ui/vite.config.ts | 82 + assets/asset-discord/package.json | 18 + .../template}/.gitignore | 0 .../template}/.npmrc | 0 .../template}/netlify.toml | 0 assets/asset-discord/template/nitro.config.ts | 9 + .../template}/package.json | 6 +- .../template}/pnpm-lock.yaml | 0 .../template}/server/api/index.ts | 0 .../server/middleware/security-header.ts | 8 + .../template/server/public/index.html | 34 + .../template}/tsconfig.json | 0 assets/asset-electron/package.json | 18 + assets/asset-electron/template/.npmrc | 4 + .../template}/config.cjs | 30 +- .../template}/forge.config.cjs | 42 +- .../template}/package.json | 49 +- .../template}/pipelab-plugin.cjs | 64 +- .../template}/src/custom-main.js | 0 .../template}/src/handlers/dialog/folder.js | 16 +- .../template}/src/handlers/dialog/open.js | 14 +- .../template}/src/handlers/dialog/save.js | 14 +- .../src/handlers/discord/set-activity.js | 52 +- .../template}/src/handlers/fs/copy.js | 20 +- .../template}/src/handlers/fs/delete.js | 12 +- .../template}/src/handlers/fs/exist.js | 22 +- .../template}/src/handlers/fs/file-size.js | 12 +- .../src/handlers/fs/folder-create.js | 14 +- .../template}/src/handlers/fs/list.js | 34 +- .../template}/src/handlers/fs/move.js | 22 +- .../template}/src/handlers/fs/read-binary.js | 12 +- .../template}/src/handlers/fs/read.js | 22 +- .../template}/src/handlers/fs/write-base64.js | 24 +- .../template}/src/handlers/fs/write.js | 22 +- .../template}/src/handlers/general/engine.js | 8 +- .../template}/src/handlers/general/exit.js | 14 +- .../template}/src/handlers/general/infos.js | 16 +- .../src/handlers/general/open-in-explorer.js | 12 +- .../template}/src/handlers/general/open.js | 12 +- .../template}/src/handlers/general/run.js | 12 +- .../src/handlers/steam/deleteItem.js | 16 +- .../template}/src/handlers/steam/download.js | 16 +- .../src/handlers/steam/downloadInfo.js | 16 +- .../src/handlers/steam/downloadScore.js | 22 +- .../template}/src/handlers/steam/getItem.js | 16 +- .../template}/src/handlers/steam/getItems.js | 16 +- .../src/handlers/steam/installInfo.js | 16 +- .../template}/src/handlers/steam/raw.js | 18 +- .../src/handlers/steam/saveScreenshot.js | 54 +- .../template}/src/handlers/steam/state.js | 16 +- .../template}/src/handlers/steam/subscribe.js | 16 +- .../src/handlers/steam/unsubscribe.js | 16 +- .../src/handlers/steam/updateItem.js | 16 +- .../src/handlers/steam/uploadScore.js | 18 +- .../template}/src/handlers/steam/utils.js | 56 +- .../template/src/handlers/user/folder.js | 71 + .../template}/src/handlers/window/maximize.js | 8 +- .../template}/src/handlers/window/minimize.js | 9 +- .../src/handlers/window/request-attention.js | 8 +- .../template}/src/handlers/window/restore.js | 8 +- .../src/handlers/window/set-always-on-top.js | 8 +- .../src/handlers/window/set-fullscreen.js | 18 +- .../src/handlers/window/set-height.js | 10 +- .../window/set-ignore-mouse-events.js | 36 + .../src/handlers/window/set-maximum-size.js | 10 +- .../src/handlers/window/set-minimum-size.js | 10 +- .../src/handlers/window/set-resizable.js | 10 +- .../src/handlers/window/set-title.js | 10 +- .../src/handlers/window/set-width.js | 10 +- .../template}/src/handlers/window/set-x.js | 10 +- .../template}/src/handlers/window/set-y.js | 10 +- .../src/handlers/window/show-dev-tools.js | 12 +- .../src/handlers/window/unmaximize.js | 9 +- assets/asset-electron/template/src/index.js | 958 + assets/asset-electron/template/src/preload.js | 27 + assets/asset-electron/template/src/utils.js | 18 + assets/asset-netlify/package.json | 18 + .../template}/.gitignore | 0 .../static => asset-netlify/template}/.npmrc | 0 .../template}/netlify.toml | 0 .../template}/package.json | 0 .../template}/pnpm-lock.yaml | 0 .../template}/pnpm-workspace.yaml | 2 +- assets/asset-tauri/package.json | 18 + .../app => asset-tauri/template}/README.md | 0 assets/asset-tauri/template/index.html | 45 + .../app => asset-tauri/template}/package.json | 10 +- .../template}/pnpm-lock.yaml | 0 .../template}/src-tauri/.gitignore | 0 .../template}/src-tauri/Cargo.lock | 0 .../template}/src-tauri/Cargo.toml | 0 .../template}/src-tauri/build.rs | 0 .../src-tauri/capabilities/default.json | 9 +- .../template}/src-tauri/icons/128x128.png | Bin .../template}/src-tauri/icons/128x128@2x.png | Bin .../template}/src-tauri/icons/32x32.png | Bin .../src-tauri/icons/Square107x107Logo.png | Bin .../src-tauri/icons/Square142x142Logo.png | Bin .../src-tauri/icons/Square150x150Logo.png | Bin .../src-tauri/icons/Square284x284Logo.png | Bin .../src-tauri/icons/Square30x30Logo.png | Bin .../src-tauri/icons/Square310x310Logo.png | Bin .../src-tauri/icons/Square44x44Logo.png | Bin .../src-tauri/icons/Square71x71Logo.png | Bin .../src-tauri/icons/Square89x89Logo.png | Bin .../template}/src-tauri/icons/StoreLogo.png | Bin .../template}/src-tauri/icons/icon.icns | Bin .../template}/src-tauri/icons/icon.ico | Bin .../template}/src-tauri/icons/icon.png | Bin .../template}/src-tauri/src/lib copy 2.rs | 0 .../template}/src-tauri/src/lib copy.rs | 0 .../template}/src-tauri/src/lib.rs | 0 .../src-tauri/src/lib_webview_screenshot.rs | 0 .../template}/src-tauri/src/main.rs | 0 .../template}/src-tauri/tauri.conf.json | 0 .../template}/tsconfig.json | 0 assets/build/notarize.cjs | 36 - .../templates/nitro-app/nitro.config.ts | 9 - .../server/middleware/security-header.ts | 8 - .../nitro-app/server/public/index.html | 41 - assets/electron/template/app/.gitignore | 92 - assets/electron/template/app/.npmrc | 1 - assets/electron/template/app/pnpm-lock.yaml | 3982 ---- .../template/app/src/handlers/user/folder.js | 71 - .../window/set-ignore-mouse-events.js | 22 - assets/electron/template/app/src/index.js | 958 - assets/electron/template/app/src/preload.js | 27 - assets/electron/template/app/src/utils.js | 18 - assets/tauri/template/app/.gitignore | 25 - assets/tauri/template/app/index.html | 50 - components.d.ts | 49 - declaration.d.ts | 7 - docs/expression.md | 3 +- eslint.config.mjs | 73 - forge.config.ts | 137 - forge.env.d.ts | 44 - index.html | 59 - mise.toml | 3 + package.json | 218 +- packages/cloud-azure/package.json | 34 + packages/cloud-azure/src/azure-provider.ts | 134 + packages/cloud-azure/src/index.ts | 1 + packages/cloud/package.json | 27 + packages/cloud/src/index.ts | 1 + packages/cloud/src/provider.ts | 29 + packages/constants/README.md | 3 + packages/constants/package.json | 51 + packages/constants/src/index.ts | 70 + packages/constants/tsconfig.json | 9 + packages/core-node/README.md | 10 + packages/core-node/package.json | 74 + packages/core-node/src/api.ts | 115 + packages/core-node/src/config.ts | 105 + packages/core-node/src/context.ts | 68 + packages/core-node/src/handler-func.ts | 234 + packages/core-node/src/handlers.ts | 2 + packages/core-node/src/handlers/agents.ts | 32 + packages/core-node/src/handlers/auth.ts | 95 + .../core-node/src/handlers/build-history.ts | 381 + packages/core-node/src/handlers/config.ts | 109 + packages/core-node/src/handlers/engine.ts | 229 + packages/core-node/src/handlers/fs.ts | 97 + packages/core-node/src/handlers/history.ts | 329 + packages/core-node/src/handlers/index.ts | 41 + packages/core-node/src/handlers/shell.ts | 57 + packages/core-node/src/handlers/system.ts | 18 + packages/core-node/src/heavy.ts | 4 + packages/core-node/src/index.ts | 16 + packages/core-node/src/ipc-core.ts | 77 + packages/core-node/src/migrations.ts | 72 + packages/core-node/src/paths.ts | 1 + packages/core-node/src/plugins-registry.ts | 78 + packages/core-node/src/presets/c3toSteam.ts | 272 + packages/core-node/src/presets/demo.ts | 123 + packages/core-node/src/presets/if.ts | 69 + .../core-node/src}/presets/list.ts | 16 +- packages/core-node/src/presets/loop.ts | 65 + packages/core-node/src/presets/moreToCome.ts | 32 + packages/core-node/src/presets/newProject.ts | 31 + .../core-node/src}/presets/preset.model.ts | 0 .../core-node/src/presets/test-c3-offline.ts | 78 + .../core-node/src/presets/test-c3-unzip.ts | 124 + packages/core-node/src/runner.ts | 160 + packages/core-node/src/server.ts | 99 + packages/core-node/src/types/runner.ts | 101 + packages/core-node/src/utils.ts | 268 + packages/core-node/src/utils/fs-extras.ts | 211 + packages/core-node/src/utils/remote.ts | 497 + packages/core-node/src/utils/storage.ts | 99 + packages/core-node/src/websocket-server.ts | 288 + packages/core-node/tsconfig.json | 19 + packages/migration/README.md | 3 + packages/migration/package.json | 55 + packages/migration/src/index.ts | 11 + .../migration/src}/models/createMigration.ts | 46 +- .../migration/src/models/createMigrator.ts | 22 + .../migration/src/models/migration.spec.ts | 343 + packages/migration/src/models/migration.ts | 157 + .../migration/src}/utils/object-keys.ts | 10 +- packages/migration/tsconfig.json | 13 + packages/shared/README.md | 3 + packages/shared/package.json | 63 + packages/shared/src/apis.ts | 196 + packages/shared/src/build-history.ts | 131 + packages/shared/src/config.schema.ts | 147 + packages/shared/src/config/migrators.ts | 308 + .../shared/src/config/projects-definition.ts | 25 + packages/shared/src/config/projects-types.ts | 25 + packages/shared/src/config/projects.ts | 1 + .../shared/src/config/settings-definition.ts | 21 + packages/shared/src/config/settings.ts | 21 + .../shared/src}/database.types.ts | 72 +- packages/shared/src/errors.ts | 11 + packages/shared/src/evaluator.ts | 58 + packages/shared/src/fmt.ts | 5 + {src/shared => packages/shared/src}/graph.ts | 150 +- packages/shared/src/i18n-utils.ts | 10 + .../shared/src}/i18n/de_DE.json | 2 +- .../shared/src}/i18n/en_US.json | 17 +- .../shared/src}/i18n/es_ES.json | 2 +- .../shared/src}/i18n/fr_FR.json | 2 +- .../shared/src}/i18n/pt_BR.json | 2 +- .../shared/src}/i18n/zh_CN.json | 2 +- packages/shared/src/index.ts | 51 + packages/shared/src/ipc.types.ts | 73 + packages/shared/src/logger.ts | 20 + packages/shared/src/migrations/model.ts | 1 + packages/shared/src/migrations/projects.ts | 1 + packages/shared/src/migrations/settings.ts | 1 + packages/shared/src/model.test.ts | 214 + packages/shared/src/model.ts | 233 + packages/shared/src/plugins.ts | 20 + packages/shared/src/plugins/definitions.ts | 472 + packages/shared/src/quickjs.ts | 98 + .../shared/src}/save-location.ts | 34 +- packages/shared/src/subscription-errors.ts | 87 + packages/shared/src/supabase.ts | 28 + .../shared/src}/tests/helpers.ts | 6 +- packages/shared/src/types.ts | 3 + packages/shared/src/utils.ts | 3 + packages/shared/src/validation.ts | 16 + packages/shared/src/variables.ts | 27 + packages/shared/src/wasm.d.ts | 9 + .../shared/src}/websocket.types.ts | 171 +- packages/shared/tsconfig.json | 13 + packages/test-utils/package.json | 53 + packages/test-utils/src/index.ts | 223 + packages/tsconfig/README.md | 3 + packages/tsconfig/base.json | 14 + packages/tsconfig/commonjs.json | 8 + packages/tsconfig/node.json | 7 + packages/tsconfig/package.json | 23 + packages/tsconfig/vue.json | 11 + plugins/plugin-construct/README.md | 3 + plugins/plugin-construct/package.json | 53 + .../src}/assets/construct.webp | Bin plugins/plugin-construct/src/assets/script.ts | 288 + .../plugin-construct/src/declarations.d.ts | 1 + .../plugin-construct/src}/export-c3p.ts | 66 +- .../plugin-construct/src}/export-project.ts | 67 +- plugins/plugin-construct/src/export-shared.ts | 286 + .../plugin-construct/src}/export.test.ts | 16 +- plugins/plugin-construct/src/index.ts | 34 + .../tests/e2e/construct.spec.ts | 60 + .../tests}/e2e/fixtures/c3-export.json | 2 +- .../tests}/e2e/fixtures/c3-export/test.c3p | Bin .../e2e/fixtures/html-export/index.html | 16 +- .../tests/e2e/vitest.config.mts | 20 + plugins/plugin-construct/tsconfig.json | 10 + plugins/plugin-construct/tsdown.config.ts | 15 + plugins/plugin-core/README.md | 3 + plugins/plugin-core/package.json | 66 + plugins/plugin-core/src/archive-utils.ts | 127 + plugins/plugin-core/src/create-plugin.ts | 8 + plugins/plugin-core/src/custom-errors.ts | 11 + plugins/plugin-core/src/fs-utils.ts | 31 + plugins/plugin-core/src/index.ts | 10 + plugins/plugin-core/src/node-utils.ts | 52 + plugins/plugin-core/src/pipelab.ts | 95 + plugins/plugin-core/src/utils.ts | 26 + plugins/plugin-core/tsconfig.json | 9 + plugins/plugin-core/tsdown.config.ts | 15 + plugins/plugin-discord/README.md | 3 + plugins/plugin-discord/package.json | 46 + plugins/plugin-discord/src/declarations.d.ts | 1 + plugins/plugin-discord/src/discord.ts | 338 + plugins/plugin-discord/src/index.ts | 43 + .../plugin-discord/src}/make.spec.ts | 32 +- plugins/plugin-discord/src/package.ts | 37 + plugins/plugin-discord/src/preview.ts | 25 + .../plugin-discord/src}/public/discord.webp | Bin .../plugin-discord/src}/utils.ts | 24 +- plugins/plugin-discord/tsconfig.json | 10 + plugins/plugin-discord/tsdown.config.ts | 15 + plugins/plugin-electron/README.md | 3 + plugins/plugin-electron/package.json | 55 + plugins/plugin-electron/src/configure.ts | 23 + plugins/plugin-electron/src/declarations.d.ts | 1 + .../src}/fixtures/build/index.html | 16 +- plugins/plugin-electron/src/forge.ts | 851 + plugins/plugin-electron/src/index.ts | 94 + .../plugin-electron/src}/make.spec.ts | 32 +- plugins/plugin-electron/src/make.ts | 22 + plugins/plugin-electron/src/package-v2.ts | 52 + plugins/plugin-electron/src/package.ts | 22 + plugins/plugin-electron/src/preview.ts | 35 + .../plugin-electron/src}/public/electron.webp | Bin .../plugin-electron/src}/utils.ts | 28 +- .../tests/e2e/electron.spec.ts | 64 + .../e2e/fixtures/folder-to-electron.json | 0 .../tests}/e2e/fixtures/folder-to-tauri.json | 0 .../tests/e2e/vitest.config.mts | 20 + plugins/plugin-electron/tsconfig.json | 10 + plugins/plugin-electron/tsdown.config.ts | 15 + plugins/plugin-filesystem/README.md | 3 + plugins/plugin-filesystem/package.json | 51 + .../plugin-filesystem/src}/copy.spec.ts | 48 +- plugins/plugin-filesystem/src/copy.ts | 154 + plugins/plugin-filesystem/src/index.ts | 58 + plugins/plugin-filesystem/src/is-file.ts | 36 + plugins/plugin-filesystem/src/list-files.ts | 70 + plugins/plugin-filesystem/src/open.ts | 67 + plugins/plugin-filesystem/src/remove.ts | 58 + plugins/plugin-filesystem/src/run.ts | 138 + .../src}/temporary-folder.ts | 0 .../plugin-filesystem/src}/unzip.spec.ts | 16 +- plugins/plugin-filesystem/src/unzip.ts | 58 + plugins/plugin-filesystem/src/zip-v2.ts | 120 + plugins/plugin-filesystem/src/zip.ts | 133 + .../tests/e2e/filesystem.spec.ts | 103 + .../tests/e2e/vitest.config.mts | 20 + plugins/plugin-filesystem/tsconfig.json | 8 + plugins/plugin-filesystem/tsdown.config.ts | 15 + plugins/plugin-itch/README.md | 3 + plugins/plugin-itch/package.json | 43 + .../plugin-itch/src}/assets/itch-icon.webp | Bin plugins/plugin-itch/src/ensure.ts | 62 + plugins/plugin-itch/src/export.ts | 116 + plugins/plugin-itch/src/index.ts | 21 + plugins/plugin-itch/tsconfig.json | 10 + plugins/plugin-itch/tsdown.config.ts | 15 + plugins/plugin-minify/README.md | 3 + plugins/plugin-minify/package.json | 42 + plugins/plugin-minify/src/code.ts | 62 + plugins/plugin-minify/src/images.ts | 35 + plugins/plugin-minify/src/index.ts | 25 + plugins/plugin-minify/tsconfig.json | 8 + plugins/plugin-minify/tsdown.config.ts | 15 + plugins/plugin-netlify/README.md | 3 + plugins/plugin-netlify/package.json | 44 + .../src}/assets/netlify-icon.webp | Bin .../plugin-netlify/src}/build.ts | 137 +- plugins/plugin-netlify/src/index.ts | 26 + .../plugin-netlify/src}/publish.ts | 192 +- plugins/plugin-netlify/tsconfig.json | 8 + plugins/plugin-netlify/tsdown.config.ts | 15 + plugins/plugin-nvpatch/README.md | 3 + plugins/plugin-nvpatch/package.json | 43 + plugins/plugin-nvpatch/src/ensure.ts | 31 + plugins/plugin-nvpatch/src/index.ts | 20 + plugins/plugin-nvpatch/src/nvpatch.ts | 87 + plugins/plugin-nvpatch/tsconfig.json | 8 + plugins/plugin-nvpatch/tsdown.config.ts | 15 + plugins/plugin-poki/README.md | 3 + plugins/plugin-poki/package.json | 47 + .../plugin-poki/src}/assets/poki-icon.webp | Bin plugins/plugin-poki/src/export.test.ts | 66 + plugins/plugin-poki/src/export.ts | 125 + plugins/plugin-poki/src/index.ts | 21 + plugins/plugin-poki/tsconfig.json | 10 + plugins/plugin-poki/tsdown.config.ts | 15 + plugins/plugin-steam/README.md | 3 + plugins/plugin-steam/package.json | 51 + plugins/plugin-steam/src/declarations.d.ts | 1 + plugins/plugin-steam/src/index.ts | 19 + .../plugin-steam/src}/steam.webp | Bin plugins/plugin-steam/src/upload-to-steam.ts | 299 + .../plugin-steam/src}/utils.ts | 92 +- plugins/plugin-steam/tests/e2e/steam.spec.ts | 103 + .../plugin-steam/tests/e2e/vitest.config.mts | 20 + plugins/plugin-steam/tsconfig.json | 8 + plugins/plugin-steam/tsdown.config.ts | 15 + plugins/plugin-system/README.md | 3 + plugins/plugin-system/package.json | 42 + plugins/plugin-system/src/alert.ts | 45 + plugins/plugin-system/src/branch.ts | 89 + plugins/plugin-system/src/for.ts | 48 + plugins/plugin-system/src/index.ts | 48 + plugins/plugin-system/src/join.ts | 58 + .../plugin-system/src}/log.ts | 28 +- .../plugin-system/src}/manual.ts | 0 plugins/plugin-system/src/prompt.ts | 47 + .../plugin-system/src}/sleep.ts | 26 +- plugins/plugin-system/tsconfig.json | 8 + plugins/plugin-system/tsdown.config.ts | 15 + plugins/plugin-tauri/README.md | 3 + plugins/plugin-tauri/package.json | 47 + plugins/plugin-tauri/src/configure.ts | 23 + plugins/plugin-tauri/src/declarations.d.ts | 1 + .../src}/fixtures/build/index.html | 16 +- plugins/plugin-tauri/src/index.ts | 68 + .../plugin-tauri/src}/make.spec.ts | 32 +- plugins/plugin-tauri/src/make.ts | 21 + plugins/plugin-tauri/src/package.ts | 43 + plugins/plugin-tauri/src/preview.ts | 51 + .../plugin-tauri/src}/public/tauri.webp | Bin plugins/plugin-tauri/src/tauri.ts | 842 + .../plugin-tauri/src}/utils.ts | 24 +- plugins/plugin-tauri/tsconfig.json | 10 + plugins/plugin-tauri/tsdown.config.ts | 15 + pnpm-lock.yaml | 16039 ++++++++-------- pnpm-workspace.yaml | 5 + scripts/delete_tags.sh | 102 + scripts/detect-changes.ts | 71 + scripts/headless-electron-tester.mts | 10 - scripts/migrate-file.mts | 40 - scripts/network-interceptor.mjs | 60 + scripts/publish-app.mjs | 106 + scripts/upload-windows.sh | 47 + src/components.d.ts | 41 - src/constants.ts | 50 - src/definitions.d.ts | 52 - src/global.d.ts | 5 - src/main.ts | 427 - src/main/api.ts | 151 - src/main/config.ts | 260 - src/main/handler-func.ts | 223 - src/main/handlers.ts | 856 - src/main/handlers/build-history.ts | 258 - src/main/paths.ts | 29 - src/main/presets/c3toSteam.ts | 272 - src/main/presets/demo.ts | 123 - src/main/presets/if.ts | 69 - src/main/presets/loop.ts | 65 - src/main/presets/moreToCome.ts | 34 - src/main/presets/newProject.ts | 33 - src/main/presets/test-c3-offline.ts | 78 - src/main/presets/test-c3-unzip.ts | 124 - src/main/utils.ts | 580 - src/main/websocket-server.ts | 184 - src/preload.d.ts | 8 - src/preload.ts | 35 - src/renderer.ts | 1 - src/renderer/App.vue | 255 - src/renderer/agent/utils/json.ts | 7 - .../components/AddNodeButton.model.ts | 15 - src/renderer/components/FileInput.vue | 83 - .../components/ScenarioListItemFile.vue | 54 - src/renderer/components/Settings.vue | 547 - src/renderer/composables/api.ts | 59 - src/renderer/composables/usePipeline.ts | 81 - src/renderer/composables/usePostHog.ts | 9 - src/renderer/composables/useTour.ts | 36 - src/renderer/composables/variables.ts | 19 - src/renderer/composables/websocket-client.ts | 369 - src/renderer/composables/websocket-manager.ts | 143 - src/renderer/env.d.ts | 8 - src/renderer/i18n.ts | 16 - src/renderer/main.ts | 165 - src/renderer/models/constants.ts | 1 - src/renderer/models/controls.ts | 51 - src/renderer/plugins/posthog.ts | 10 - src/renderer/router/router.ts | 66 - src/renderer/store/app.ts | 77 - src/renderer/store/auth.ts | 439 - src/renderer/store/build-history.ts | 369 - src/renderer/store/files.ts | 187 - src/renderer/store/recents.ts | 30 - src/renderer/store/settings.ts | 41 - src/renderer/store/user.ts | 12 - src/renderer/style/main.ts | 5 - src/renderer/utils/config.ts | 74 - src/renderer/utils/evaluator.ts | 48 - src/renderer/utils/fmt.ts | 5 - src/renderer/utils/quickjs.ts | 95 - src/shared/apis.ts | 164 - src/shared/build-history.ts | 119 - src/shared/i18n-utils.ts | 10 - src/shared/libs/.gitkeep | 0 src/shared/libs/core-app/index.ts | 1 - src/shared/libs/core-app/variables.ts | 10 - src/shared/libs/migration/index.ts | 9 - .../libs/migration/models/createMigrator.ts | 22 - .../libs/migration/models/migration.spec.ts | 343 - src/shared/libs/migration/models/migration.ts | 157 - .../libs/plugin-construct/assets/script.ts | 297 - .../libs/plugin-construct/declarations.d.ts | 1 - .../libs/plugin-construct/export-shared.ts | 270 - src/shared/libs/plugin-construct/index.ts | 34 - src/shared/libs/plugin-core/create-plugin.ts | 8 - src/shared/libs/plugin-core/custom-errors.ts | 12 - src/shared/libs/plugin-core/index.ts | 11 - src/shared/libs/plugin-core/pipelab.ts | 748 - src/shared/libs/plugin-core/utils.ts | 183 - .../libs/plugin-discord/declarations.d.ts | 1 - src/shared/libs/plugin-discord/discord.ts | 419 - src/shared/libs/plugin-discord/index.ts | 43 - src/shared/libs/plugin-discord/package.ts | 38 - src/shared/libs/plugin-discord/preview.ts | 25 - src/shared/libs/plugin-electron/configure.ts | 23 - .../libs/plugin-electron/declarations.d.ts | 1 - src/shared/libs/plugin-electron/forge.ts | 918 - src/shared/libs/plugin-electron/index.ts | 94 - src/shared/libs/plugin-electron/make.ts | 21 - src/shared/libs/plugin-electron/package-v2.ts | 52 - src/shared/libs/plugin-electron/package.ts | 22 - src/shared/libs/plugin-electron/preview.ts | 35 - src/shared/libs/plugin-filesystem/copy.ts | 147 - src/shared/libs/plugin-filesystem/index.ts | 58 - src/shared/libs/plugin-filesystem/is-file.ts | 40 - .../libs/plugin-filesystem/list-files.ts | 69 - src/shared/libs/plugin-filesystem/open.ts | 42 - src/shared/libs/plugin-filesystem/remove.ts | 58 - src/shared/libs/plugin-filesystem/run.ts | 138 - src/shared/libs/plugin-filesystem/unzip.ts | 59 - src/shared/libs/plugin-filesystem/zip-v2.ts | 121 - src/shared/libs/plugin-filesystem/zip.ts | 134 - src/shared/libs/plugin-itch/export.ts | 192 - src/shared/libs/plugin-itch/index.ts | 21 - src/shared/libs/plugin-minify/code.ts | 67 - src/shared/libs/plugin-minify/images.ts | 37 - src/shared/libs/plugin-minify/index.ts | 25 - src/shared/libs/plugin-netlify/index.ts | 26 - src/shared/libs/plugin-nvpatch/index.ts | 20 - src/shared/libs/plugin-nvpatch/nvpatch.ts | 110 - src/shared/libs/plugin-poki/export.ts | 113 - src/shared/libs/plugin-poki/index.ts | 21 - .../libs/plugin-steam/declarations.d.ts | 1 - src/shared/libs/plugin-steam/index.ts | 19 - .../libs/plugin-steam/upload-to-steam.ts | 263 - src/shared/libs/plugin-system/alert.ts | 45 - src/shared/libs/plugin-system/branch.ts | 89 - src/shared/libs/plugin-system/for.ts | 46 - src/shared/libs/plugin-system/index.ts | 48 - src/shared/libs/plugin-system/join.ts | 62 - src/shared/libs/plugin-system/prompt.ts | 47 - src/shared/libs/plugin-tauri/configure.ts | 23 - .../libs/plugin-tauri/declarations.d.ts | 1 - src/shared/libs/plugin-tauri/index.ts | 68 - src/shared/libs/plugin-tauri/make.ts | 21 - src/shared/libs/plugin-tauri/package.ts | 43 - src/shared/libs/plugin-tauri/preview.ts | 51 - src/shared/libs/plugin-tauri/tauri.ts | 864 - src/shared/logger.ts | 33 - src/shared/model.test.ts | 214 - src/shared/model.ts | 346 - src/shared/plugins.ts | 79 - src/shared/subscription-errors.ts | 87 - src/shared/supabase.ts | 12 - src/shared/types.ts | 1 - src/shared/utils.ts | 3 - src/shared/validation.ts | 16 - supabase/functions/cloud-run/index.ts | 76 + tests/e2e/tests/basic.spec.ts | 178 - tests/e2e/vitest.config.mts | 11 - tsconfig.base.json | 13 + tsconfig.json | 44 +- turbo.json | 46 + vite.base.config.mts | 100 - vite.main.config.mts | 67 - vite.preload.config.mts | 29 - vite.renderer.config.mts | 66 - vitest.config.mts | 22 - website | 1 - 715 files changed, 37476 insertions(+), 34848 deletions(-) create mode 100644 .changeset/free-animals-bet.md delete mode 100644 .env.example create mode 100644 .github/workflows/pipeline.yml delete mode 100644 .github/workflows/push.yml delete mode 100644 .prettierignore delete mode 100644 .prettierrc.yaml create mode 100644 ARCHITECTURE.md delete mode 100644 CHANGELOG.md rename LICENSE.md => LICENSE (98%) delete mode 100644 TODO create mode 100644 TODO_MONOREPO.md create mode 100644 apps/cli/.npmrc create mode 100644 apps/cli/README.md create mode 100644 apps/cli/package.json create mode 100644 apps/cli/src/commands/history.ts create mode 100644 apps/cli/src/commands/maintenance.ts create mode 100644 apps/cli/src/commands/pipelines.ts create mode 100644 apps/cli/src/commands/setup.ts create mode 100644 apps/cli/src/index.ts create mode 100644 apps/cli/src/paths.ts create mode 100644 apps/cli/tests/e2e/tests/history.spec.ts create mode 100644 apps/cli/tests/e2e/tests/integration.spec.ts create mode 100644 apps/cli/tests/e2e/vitest.config.mts create mode 100644 apps/cli/tsconfig.json create mode 100644 apps/cli/tsdown.config.ts rename .editorconfig => apps/desktop/.editorconfig (100%) rename .hintrc => apps/desktop/.hintrc (100%) rename .mise.toml => apps/desktop/.mise.toml (77%) create mode 100644 apps/desktop/.npmrc rename FAQ.md => apps/desktop/FAQ.md (100%) create mode 100644 apps/desktop/README.md rename {assets => apps/desktop/assets}/build/entitlements.mac.plist (100%) rename {assets => apps/desktop/assets}/build/icon.icns (100%) rename {assets => apps/desktop/assets}/build/icon.ico (100%) rename {assets => apps/desktop/assets}/build/icon.png (100%) create mode 100644 apps/desktop/assets/build/notarize.cjs rename crowdin.yml => apps/desktop/crowdin.yml (100%) create mode 100644 apps/desktop/forge.config.ts create mode 100644 apps/desktop/forge.env.d.ts create mode 100644 apps/desktop/index.html create mode 100644 apps/desktop/package.json create mode 100644 apps/desktop/scripts/headless-electron-tester.mts create mode 100644 apps/desktop/scripts/migrate-file.mts create mode 100644 apps/desktop/scripts/test-update-server.mts create mode 100644 apps/desktop/src/main.ts create mode 100644 apps/desktop/src/main/ipc-handlers.ts create mode 100644 apps/desktop/src/main/server-process.ts create mode 100644 apps/desktop/src/preload.d.ts create mode 100644 apps/desktop/src/preload.ts create mode 100644 apps/desktop/tsconfig.json create mode 100644 apps/desktop/vite.base.config.mts create mode 100644 apps/desktop/vite.main.config.mts create mode 100644 apps/desktop/vite.preload.config.mts create mode 100644 apps/desktop/vite.renderer.config.mts create mode 100644 apps/ui/.gitignore create mode 100644 apps/ui/README.md create mode 100644 apps/ui/auto-imports.d.ts create mode 100644 apps/ui/components.d.ts create mode 100644 apps/ui/index.html create mode 100644 apps/ui/package.json create mode 100644 apps/ui/src/App.vue rename {src/renderer => apps/ui/src}/Root.vue (78%) rename {src/renderer => apps/ui/src}/agent/log.ts (100%) rename {src/renderer => apps/ui/src}/agent/trpc.ts (74%) create mode 100644 apps/ui/src/agent/utils/json.ts rename {src/renderer => apps/ui/src}/assets/css/styles.less (94%) rename {src/renderer => apps/ui/src}/assets/icons.svg (100%) rename {src => apps/ui/src}/auto-imports.d.ts (73%) create mode 100644 apps/ui/src/components/AddNodeButton.model.ts rename {src/renderer => apps/ui/src}/components/AddNodeButton.vue (76%) rename {src/renderer => apps/ui/src}/components/AddTriggerButton.vue (73%) rename {src/renderer => apps/ui/src}/components/BuildDetailsModal.vue (50%) rename {src/renderer => apps/ui/src}/components/BuildHistoryDialog.vue (66%) rename {src/renderer => apps/ui/src}/components/BuildHistoryItem.vue (74%) rename {src/renderer => apps/ui/src}/components/BuildHistoryList.vue (67%) rename {src => apps/ui/src}/components/BuildHistoryView.vue (84%) rename {src/renderer => apps/ui/src}/components/BuildStatusBadge.vue (62%) rename {src/renderer => apps/ui/src}/components/ColorPicker.vue (82%) create mode 100644 apps/ui/src/components/ConnectingPage.vue rename {src/renderer => apps/ui/src}/components/DevBenefitsOverride.vue (77%) create mode 100644 apps/ui/src/components/DisconnectedPage.vue create mode 100644 apps/ui/src/components/FileInput.vue rename {src/renderer => apps/ui/src}/components/Layout.vue (53%) rename {src/renderer => apps/ui/src}/components/ScenarioListItem.vue (60%) create mode 100644 apps/ui/src/components/ScenarioListItemFile.vue rename {src/renderer => apps/ui/src}/components/ScenarioListItemRecent.vue (67%) create mode 100644 apps/ui/src/components/Settings.vue rename {src/renderer => apps/ui/src}/components/SubscriptionLoadingIndicator.vue (87%) rename {src/renderer => apps/ui/src}/components/UpgradeDialog.vue (74%) rename {src/renderer => apps/ui/src}/components/UpgradeNowButton.vue (62%) create mode 100644 apps/ui/src/components/WebFilePicker.vue rename {src/renderer => apps/ui/src}/components/__tests__/HelloWorld.spec.ts (54%) rename {src/renderer => apps/ui/src}/components/nodes/EditorNodeAction.vue (74%) rename {src/renderer => apps/ui/src}/components/nodes/EditorNodeCondition.vue (82%) rename {src/renderer => apps/ui/src}/components/nodes/EditorNodeDummy.vue (93%) rename {src/renderer => apps/ui/src}/components/nodes/EditorNodeEvent.vue (72%) rename {src/renderer => apps/ui/src}/components/nodes/EditorNodeEventEmpty.vue (70%) rename {src/renderer => apps/ui/src}/components/nodes/EditorNodeLoop.vue (78%) rename {src/renderer => apps/ui/src}/components/nodes/ParamEditor.vue (72%) rename {src/renderer => apps/ui/src}/components/nodes/ParamEditorBody.vue (64%) rename {src/renderer => apps/ui/src}/components/nodes/PluginIcon.vue (81%) create mode 100644 apps/ui/src/composables/api.ts rename {src/renderer => apps/ui/src}/composables/handlers.ts (52%) rename {src/renderer => apps/ui/src}/composables/middleware.ts (60%) create mode 100644 apps/ui/src/composables/use-shell.ts create mode 100644 apps/ui/src/composables/usePipeline.ts create mode 100644 apps/ui/src/composables/usePostHog.ts create mode 100644 apps/ui/src/composables/useTour.ts create mode 100644 apps/ui/src/composables/websocket-client.ts create mode 100644 apps/ui/src/composables/websocket-manager.ts create mode 100644 apps/ui/src/env.d.ts create mode 100644 apps/ui/src/i18n.ts create mode 100644 apps/ui/src/main.ts create mode 100644 apps/ui/src/models/constants.ts create mode 100644 apps/ui/src/models/controls.ts rename {src/renderer => apps/ui/src}/models/error.ts (50%) rename {src/renderer => apps/ui/src}/pages/editor.vue (66%) rename {src/renderer => apps/ui/src}/pages/environement-editor.vue (75%) rename {src/renderer => apps/ui/src}/pages/index.vue (65%) rename {src/renderer => apps/ui/src}/pages/nodes-editor.vue (80%) rename {src/renderer => apps/ui/src}/pages/project-settings-editor.vue (83%) rename {src/renderer => apps/ui/src}/pages/scenarios.vue (82%) rename {src/renderer => apps/ui/src}/pages/simple-editor.vue (66%) rename {src/renderer => apps/ui/src}/pages/team.vue (80%) rename {src/renderer => apps/ui/src}/pages/variables-editor.vue (70%) create mode 100644 apps/ui/src/plugins/posthog.ts create mode 100644 apps/ui/src/preload.d.ts create mode 100644 apps/ui/src/router/router.ts create mode 100644 apps/ui/src/store/app.ts create mode 100644 apps/ui/src/store/auth.ts create mode 100644 apps/ui/src/store/build-history.ts rename {src/renderer => apps/ui/src}/store/editor.spec.ts (93%) rename {src/renderer => apps/ui/src}/store/editor.ts (62%) create mode 100644 apps/ui/src/store/files.ts create mode 100644 apps/ui/src/store/recents.ts create mode 100644 apps/ui/src/store/settings.ts create mode 100644 apps/ui/src/store/ui.ts create mode 100644 apps/ui/src/store/user.ts rename {src/renderer => apps/ui/src}/style/main.scss (98%) create mode 100644 apps/ui/src/style/main.ts rename {src/renderer => apps/ui/src}/utils/code-editor.ts (53%) rename {src/renderer => apps/ui/src}/utils/code-editor/step-plugin.ts (55%) create mode 100644 apps/ui/src/utils/config.ts rename {src/renderer => apps/ui/src}/utils/graph.ts (61%) create mode 100644 apps/ui/tsconfig.json create mode 100644 apps/ui/vite.config.ts create mode 100644 assets/asset-discord/package.json rename assets/{discord/templates/nitro-app => asset-discord/template}/.gitignore (100%) rename assets/{discord/templates/nitro-app => asset-discord/template}/.npmrc (100%) rename assets/{discord/templates/nitro-app => asset-discord/template}/netlify.toml (100%) create mode 100644 assets/asset-discord/template/nitro.config.ts rename assets/{discord/templates/nitro-app => asset-discord/template}/package.json (100%) rename assets/{discord/templates/nitro-app => asset-discord/template}/pnpm-lock.yaml (100%) rename assets/{discord/templates/nitro-app => asset-discord/template}/server/api/index.ts (100%) create mode 100644 assets/asset-discord/template/server/middleware/security-header.ts create mode 100644 assets/asset-discord/template/server/public/index.html rename assets/{discord/templates/nitro-app => asset-discord/template}/tsconfig.json (100%) create mode 100644 assets/asset-electron/package.json create mode 100644 assets/asset-electron/template/.npmrc rename assets/{electron/template/app => asset-electron/template}/config.cjs (66%) rename assets/{electron/template/app => asset-electron/template}/forge.config.cjs (65%) rename assets/{electron/template/app => asset-electron/template}/package.json (66%) rename assets/{electron/template/app => asset-electron/template}/pipelab-plugin.cjs (70%) rename assets/{electron/template/app => asset-electron/template}/src/custom-main.js (100%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/dialog/folder.js (73%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/dialog/open.js (82%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/dialog/save.js (82%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/discord/set-activity.js (63%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/fs/copy.js (63%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/fs/delete.js (76%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/fs/exist.js (73%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/fs/file-size.js (76%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/fs/folder-create.js (79%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/fs/list.js (70%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/fs/move.js (60%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/fs/read-binary.js (76%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/fs/read.js (77%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/fs/write-base64.js (57%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/fs/write.js (63%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/general/engine.js (91%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/general/exit.js (74%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/general/infos.js (71%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/general/open-in-explorer.js (82%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/general/open.js (82%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/general/run.js (90%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/deleteItem.js (69%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/download.js (75%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/downloadInfo.js (77%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/downloadScore.js (73%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/getItem.js (72%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/getItems.js (71%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/installInfo.js (77%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/raw.js (77%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/saveScreenshot.js (55%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/state.js (70%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/subscribe.js (69%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/unsubscribe.js (83%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/updateItem.js (73%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/uploadScore.js (81%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/steam/utils.js (70%) create mode 100644 assets/asset-electron/template/src/handlers/user/folder.js rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/maximize.js (93%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/minimize.js (93%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/request-attention.js (94%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/restore.js (93%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/set-always-on-top.js (94%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/set-fullscreen.js (70%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/set-height.js (88%) create mode 100644 assets/asset-electron/template/src/handlers/window/set-ignore-mouse-events.js rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/set-maximum-size.js (84%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/set-minimum-size.js (84%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/set-resizable.js (88%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/set-title.js (87%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/set-width.js (87%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/set-x.js (87%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/set-y.js (87%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/show-dev-tools.js (82%) rename assets/{electron/template/app => asset-electron/template}/src/handlers/window/unmaximize.js (93%) create mode 100644 assets/asset-electron/template/src/index.js create mode 100644 assets/asset-electron/template/src/preload.js create mode 100644 assets/asset-electron/template/src/utils.js create mode 100644 assets/asset-netlify/package.json rename assets/{netlify/templates/static => asset-netlify/template}/.gitignore (100%) rename assets/{netlify/templates/static => asset-netlify/template}/.npmrc (100%) rename assets/{netlify/templates/static => asset-netlify/template}/netlify.toml (100%) rename assets/{netlify/templates/static => asset-netlify/template}/package.json (100%) rename assets/{netlify/templates/static => asset-netlify/template}/pnpm-lock.yaml (100%) rename assets/{netlify/templates/static => asset-netlify/template}/pnpm-workspace.yaml (77%) create mode 100644 assets/asset-tauri/package.json rename assets/{tauri/template/app => asset-tauri/template}/README.md (100%) create mode 100644 assets/asset-tauri/template/index.html rename assets/{tauri/template/app => asset-tauri/template}/package.json (73%) rename assets/{tauri/template/app => asset-tauri/template}/pnpm-lock.yaml (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/.gitignore (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/Cargo.lock (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/Cargo.toml (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/build.rs (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/capabilities/default.json (58%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/128x128.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/128x128@2x.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/32x32.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/Square107x107Logo.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/Square142x142Logo.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/Square150x150Logo.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/Square284x284Logo.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/Square30x30Logo.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/Square310x310Logo.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/Square44x44Logo.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/Square71x71Logo.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/Square89x89Logo.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/StoreLogo.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/icon.icns (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/icon.ico (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/icons/icon.png (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/src/lib copy 2.rs (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/src/lib copy.rs (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/src/lib.rs (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/src/lib_webview_screenshot.rs (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/src/main.rs (100%) rename assets/{tauri/template/app => asset-tauri/template}/src-tauri/tauri.conf.json (100%) rename assets/{tauri/template/app => asset-tauri/template}/tsconfig.json (100%) delete mode 100644 assets/build/notarize.cjs delete mode 100644 assets/discord/templates/nitro-app/nitro.config.ts delete mode 100644 assets/discord/templates/nitro-app/server/middleware/security-header.ts delete mode 100644 assets/discord/templates/nitro-app/server/public/index.html delete mode 100644 assets/electron/template/app/.gitignore delete mode 100644 assets/electron/template/app/.npmrc delete mode 100644 assets/electron/template/app/pnpm-lock.yaml delete mode 100644 assets/electron/template/app/src/handlers/user/folder.js delete mode 100644 assets/electron/template/app/src/handlers/window/set-ignore-mouse-events.js delete mode 100644 assets/electron/template/app/src/index.js delete mode 100644 assets/electron/template/app/src/preload.js delete mode 100644 assets/electron/template/app/src/utils.js delete mode 100644 assets/tauri/template/app/.gitignore delete mode 100644 assets/tauri/template/app/index.html delete mode 100644 components.d.ts delete mode 100644 declaration.d.ts delete mode 100644 eslint.config.mjs delete mode 100644 forge.config.ts delete mode 100644 forge.env.d.ts delete mode 100644 index.html create mode 100644 mise.toml create mode 100644 packages/cloud-azure/package.json create mode 100644 packages/cloud-azure/src/azure-provider.ts create mode 100644 packages/cloud-azure/src/index.ts create mode 100644 packages/cloud/package.json create mode 100644 packages/cloud/src/index.ts create mode 100644 packages/cloud/src/provider.ts create mode 100644 packages/constants/README.md create mode 100644 packages/constants/package.json create mode 100644 packages/constants/src/index.ts create mode 100644 packages/constants/tsconfig.json create mode 100644 packages/core-node/README.md create mode 100644 packages/core-node/package.json create mode 100644 packages/core-node/src/api.ts create mode 100644 packages/core-node/src/config.ts create mode 100644 packages/core-node/src/context.ts create mode 100644 packages/core-node/src/handler-func.ts create mode 100644 packages/core-node/src/handlers.ts create mode 100644 packages/core-node/src/handlers/agents.ts create mode 100644 packages/core-node/src/handlers/auth.ts create mode 100644 packages/core-node/src/handlers/build-history.ts create mode 100644 packages/core-node/src/handlers/config.ts create mode 100644 packages/core-node/src/handlers/engine.ts create mode 100644 packages/core-node/src/handlers/fs.ts create mode 100644 packages/core-node/src/handlers/history.ts create mode 100644 packages/core-node/src/handlers/index.ts create mode 100644 packages/core-node/src/handlers/shell.ts create mode 100644 packages/core-node/src/handlers/system.ts create mode 100644 packages/core-node/src/heavy.ts create mode 100644 packages/core-node/src/index.ts create mode 100644 packages/core-node/src/ipc-core.ts create mode 100644 packages/core-node/src/migrations.ts create mode 100644 packages/core-node/src/paths.ts create mode 100644 packages/core-node/src/plugins-registry.ts create mode 100644 packages/core-node/src/presets/c3toSteam.ts create mode 100644 packages/core-node/src/presets/demo.ts create mode 100644 packages/core-node/src/presets/if.ts rename {src/main => packages/core-node/src}/presets/list.ts (72%) create mode 100644 packages/core-node/src/presets/loop.ts create mode 100644 packages/core-node/src/presets/moreToCome.ts create mode 100644 packages/core-node/src/presets/newProject.ts rename {src/main => packages/core-node/src}/presets/preset.model.ts (100%) create mode 100644 packages/core-node/src/presets/test-c3-offline.ts create mode 100644 packages/core-node/src/presets/test-c3-unzip.ts create mode 100644 packages/core-node/src/runner.ts create mode 100644 packages/core-node/src/server.ts create mode 100644 packages/core-node/src/types/runner.ts create mode 100644 packages/core-node/src/utils.ts create mode 100644 packages/core-node/src/utils/fs-extras.ts create mode 100644 packages/core-node/src/utils/remote.ts create mode 100644 packages/core-node/src/utils/storage.ts create mode 100644 packages/core-node/src/websocket-server.ts create mode 100644 packages/core-node/tsconfig.json create mode 100644 packages/migration/README.md create mode 100644 packages/migration/package.json create mode 100644 packages/migration/src/index.ts rename {src/shared/libs/migration => packages/migration/src}/models/createMigration.ts (69%) create mode 100644 packages/migration/src/models/createMigrator.ts create mode 100644 packages/migration/src/models/migration.spec.ts create mode 100644 packages/migration/src/models/migration.ts rename {src/shared/libs/migration => packages/migration/src}/utils/object-keys.ts (63%) create mode 100644 packages/migration/tsconfig.json create mode 100644 packages/shared/README.md create mode 100644 packages/shared/package.json create mode 100644 packages/shared/src/apis.ts create mode 100644 packages/shared/src/build-history.ts create mode 100644 packages/shared/src/config.schema.ts create mode 100644 packages/shared/src/config/migrators.ts create mode 100644 packages/shared/src/config/projects-definition.ts create mode 100644 packages/shared/src/config/projects-types.ts create mode 100644 packages/shared/src/config/projects.ts create mode 100644 packages/shared/src/config/settings-definition.ts create mode 100644 packages/shared/src/config/settings.ts rename {src/shared => packages/shared/src}/database.types.ts (70%) create mode 100644 packages/shared/src/errors.ts create mode 100644 packages/shared/src/evaluator.ts create mode 100644 packages/shared/src/fmt.ts rename {src/shared => packages/shared/src}/graph.ts (59%) create mode 100644 packages/shared/src/i18n-utils.ts rename {src/shared => packages/shared/src}/i18n/de_DE.json (99%) rename {src/shared => packages/shared/src}/i18n/en_US.json (90%) rename {src/shared => packages/shared/src}/i18n/es_ES.json (99%) rename {src/shared => packages/shared/src}/i18n/fr_FR.json (99%) rename {src/shared => packages/shared/src}/i18n/pt_BR.json (99%) rename {src/shared => packages/shared/src}/i18n/zh_CN.json (99%) create mode 100644 packages/shared/src/index.ts create mode 100644 packages/shared/src/ipc.types.ts create mode 100644 packages/shared/src/logger.ts create mode 100644 packages/shared/src/migrations/model.ts create mode 100644 packages/shared/src/migrations/projects.ts create mode 100644 packages/shared/src/migrations/settings.ts create mode 100644 packages/shared/src/model.test.ts create mode 100644 packages/shared/src/model.ts create mode 100644 packages/shared/src/plugins.ts create mode 100644 packages/shared/src/plugins/definitions.ts create mode 100644 packages/shared/src/quickjs.ts rename {src/shared => packages/shared/src}/save-location.ts (63%) create mode 100644 packages/shared/src/subscription-errors.ts create mode 100644 packages/shared/src/supabase.ts rename {src/shared => packages/shared/src}/tests/helpers.ts (56%) create mode 100644 packages/shared/src/types.ts create mode 100644 packages/shared/src/utils.ts create mode 100644 packages/shared/src/validation.ts create mode 100644 packages/shared/src/variables.ts create mode 100644 packages/shared/src/wasm.d.ts rename {src/shared => packages/shared/src}/websocket.types.ts (50%) create mode 100644 packages/shared/tsconfig.json create mode 100644 packages/test-utils/package.json create mode 100644 packages/test-utils/src/index.ts create mode 100644 packages/tsconfig/README.md create mode 100644 packages/tsconfig/base.json create mode 100644 packages/tsconfig/commonjs.json create mode 100644 packages/tsconfig/node.json create mode 100644 packages/tsconfig/package.json create mode 100644 packages/tsconfig/vue.json create mode 100644 plugins/plugin-construct/README.md create mode 100644 plugins/plugin-construct/package.json rename {src/shared/libs/plugin-construct => plugins/plugin-construct/src}/assets/construct.webp (100%) create mode 100644 plugins/plugin-construct/src/assets/script.ts create mode 100644 plugins/plugin-construct/src/declarations.d.ts rename {src/shared/libs/plugin-construct => plugins/plugin-construct/src}/export-c3p.ts (50%) rename {src/shared/libs/plugin-construct => plugins/plugin-construct/src}/export-project.ts (53%) create mode 100644 plugins/plugin-construct/src/export-shared.ts rename {src/shared/libs/plugin-construct => plugins/plugin-construct/src}/export.test.ts (65%) create mode 100644 plugins/plugin-construct/src/index.ts create mode 100644 plugins/plugin-construct/tests/e2e/construct.spec.ts rename {tests => plugins/plugin-construct/tests}/e2e/fixtures/c3-export.json (99%) rename {tests => plugins/plugin-construct/tests}/e2e/fixtures/c3-export/test.c3p (100%) rename {tests => plugins/plugin-construct/tests}/e2e/fixtures/html-export/index.html (71%) create mode 100644 plugins/plugin-construct/tests/e2e/vitest.config.mts create mode 100644 plugins/plugin-construct/tsconfig.json create mode 100644 plugins/plugin-construct/tsdown.config.ts create mode 100644 plugins/plugin-core/README.md create mode 100644 plugins/plugin-core/package.json create mode 100644 plugins/plugin-core/src/archive-utils.ts create mode 100644 plugins/plugin-core/src/create-plugin.ts create mode 100644 plugins/plugin-core/src/custom-errors.ts create mode 100644 plugins/plugin-core/src/fs-utils.ts create mode 100644 plugins/plugin-core/src/index.ts create mode 100644 plugins/plugin-core/src/node-utils.ts create mode 100644 plugins/plugin-core/src/pipelab.ts create mode 100644 plugins/plugin-core/src/utils.ts create mode 100644 plugins/plugin-core/tsconfig.json create mode 100644 plugins/plugin-core/tsdown.config.ts create mode 100644 plugins/plugin-discord/README.md create mode 100644 plugins/plugin-discord/package.json create mode 100644 plugins/plugin-discord/src/declarations.d.ts create mode 100644 plugins/plugin-discord/src/discord.ts create mode 100644 plugins/plugin-discord/src/index.ts rename {src/shared/libs/plugin-tauri => plugins/plugin-discord/src}/make.spec.ts (55%) create mode 100644 plugins/plugin-discord/src/package.ts create mode 100644 plugins/plugin-discord/src/preview.ts rename {src/shared/libs/plugin-discord => plugins/plugin-discord/src}/public/discord.webp (100%) rename {src/shared/libs/plugin-tauri => plugins/plugin-discord/src}/utils.ts (52%) create mode 100644 plugins/plugin-discord/tsconfig.json create mode 100644 plugins/plugin-discord/tsdown.config.ts create mode 100644 plugins/plugin-electron/README.md create mode 100644 plugins/plugin-electron/package.json create mode 100644 plugins/plugin-electron/src/configure.ts create mode 100644 plugins/plugin-electron/src/declarations.d.ts rename {src/shared/libs/plugin-tauri => plugins/plugin-electron/src}/fixtures/build/index.html (54%) create mode 100644 plugins/plugin-electron/src/forge.ts create mode 100644 plugins/plugin-electron/src/index.ts rename {src/shared/libs/plugin-electron => plugins/plugin-electron/src}/make.spec.ts (55%) create mode 100644 plugins/plugin-electron/src/make.ts create mode 100644 plugins/plugin-electron/src/package-v2.ts create mode 100644 plugins/plugin-electron/src/package.ts create mode 100644 plugins/plugin-electron/src/preview.ts rename {src/shared/libs/plugin-electron => plugins/plugin-electron/src}/public/electron.webp (100%) rename {src/shared/libs/plugin-electron => plugins/plugin-electron/src}/utils.ts (59%) create mode 100644 plugins/plugin-electron/tests/e2e/electron.spec.ts rename {tests => plugins/plugin-electron/tests}/e2e/fixtures/folder-to-electron.json (100%) rename {tests => plugins/plugin-electron/tests}/e2e/fixtures/folder-to-tauri.json (100%) create mode 100644 plugins/plugin-electron/tests/e2e/vitest.config.mts create mode 100644 plugins/plugin-electron/tsconfig.json create mode 100644 plugins/plugin-electron/tsdown.config.ts create mode 100644 plugins/plugin-filesystem/README.md create mode 100644 plugins/plugin-filesystem/package.json rename {src/shared/libs/plugin-filesystem => plugins/plugin-filesystem/src}/copy.spec.ts (65%) create mode 100644 plugins/plugin-filesystem/src/copy.ts create mode 100644 plugins/plugin-filesystem/src/index.ts create mode 100644 plugins/plugin-filesystem/src/is-file.ts create mode 100644 plugins/plugin-filesystem/src/list-files.ts create mode 100644 plugins/plugin-filesystem/src/open.ts create mode 100644 plugins/plugin-filesystem/src/remove.ts create mode 100644 plugins/plugin-filesystem/src/run.ts rename {src/shared/libs/plugin-filesystem => plugins/plugin-filesystem/src}/temporary-folder.ts (100%) rename {src/shared/libs/plugin-filesystem => plugins/plugin-filesystem/src}/unzip.spec.ts (61%) create mode 100644 plugins/plugin-filesystem/src/unzip.ts create mode 100644 plugins/plugin-filesystem/src/zip-v2.ts create mode 100644 plugins/plugin-filesystem/src/zip.ts create mode 100644 plugins/plugin-filesystem/tests/e2e/filesystem.spec.ts create mode 100644 plugins/plugin-filesystem/tests/e2e/vitest.config.mts create mode 100644 plugins/plugin-filesystem/tsconfig.json create mode 100644 plugins/plugin-filesystem/tsdown.config.ts create mode 100644 plugins/plugin-itch/README.md create mode 100644 plugins/plugin-itch/package.json rename {src/shared/libs/plugin-itch => plugins/plugin-itch/src}/assets/itch-icon.webp (100%) create mode 100644 plugins/plugin-itch/src/ensure.ts create mode 100644 plugins/plugin-itch/src/export.ts create mode 100644 plugins/plugin-itch/src/index.ts create mode 100644 plugins/plugin-itch/tsconfig.json create mode 100644 plugins/plugin-itch/tsdown.config.ts create mode 100644 plugins/plugin-minify/README.md create mode 100644 plugins/plugin-minify/package.json create mode 100644 plugins/plugin-minify/src/code.ts create mode 100644 plugins/plugin-minify/src/images.ts create mode 100644 plugins/plugin-minify/src/index.ts create mode 100644 plugins/plugin-minify/tsconfig.json create mode 100644 plugins/plugin-minify/tsdown.config.ts create mode 100644 plugins/plugin-netlify/README.md create mode 100644 plugins/plugin-netlify/package.json rename {src/shared/libs/plugin-netlify => plugins/plugin-netlify/src}/assets/netlify-icon.webp (100%) rename {src/shared/libs/plugin-netlify => plugins/plugin-netlify/src}/build.ts (50%) create mode 100644 plugins/plugin-netlify/src/index.ts rename {src/shared/libs/plugin-netlify => plugins/plugin-netlify/src}/publish.ts (55%) create mode 100644 plugins/plugin-netlify/tsconfig.json create mode 100644 plugins/plugin-netlify/tsdown.config.ts create mode 100644 plugins/plugin-nvpatch/README.md create mode 100644 plugins/plugin-nvpatch/package.json create mode 100644 plugins/plugin-nvpatch/src/ensure.ts create mode 100644 plugins/plugin-nvpatch/src/index.ts create mode 100644 plugins/plugin-nvpatch/src/nvpatch.ts create mode 100644 plugins/plugin-nvpatch/tsconfig.json create mode 100644 plugins/plugin-nvpatch/tsdown.config.ts create mode 100644 plugins/plugin-poki/README.md create mode 100644 plugins/plugin-poki/package.json rename {src/shared/libs/plugin-poki => plugins/plugin-poki/src}/assets/poki-icon.webp (100%) create mode 100644 plugins/plugin-poki/src/export.test.ts create mode 100644 plugins/plugin-poki/src/export.ts create mode 100644 plugins/plugin-poki/src/index.ts create mode 100644 plugins/plugin-poki/tsconfig.json create mode 100644 plugins/plugin-poki/tsdown.config.ts create mode 100644 plugins/plugin-steam/README.md create mode 100644 plugins/plugin-steam/package.json create mode 100644 plugins/plugin-steam/src/declarations.d.ts create mode 100644 plugins/plugin-steam/src/index.ts rename {src/shared/libs/plugin-steam => plugins/plugin-steam/src}/steam.webp (100%) create mode 100644 plugins/plugin-steam/src/upload-to-steam.ts rename {src/shared/libs/plugin-steam => plugins/plugin-steam/src}/utils.ts (62%) create mode 100644 plugins/plugin-steam/tests/e2e/steam.spec.ts create mode 100644 plugins/plugin-steam/tests/e2e/vitest.config.mts create mode 100644 plugins/plugin-steam/tsconfig.json create mode 100644 plugins/plugin-steam/tsdown.config.ts create mode 100644 plugins/plugin-system/README.md create mode 100644 plugins/plugin-system/package.json create mode 100644 plugins/plugin-system/src/alert.ts create mode 100644 plugins/plugin-system/src/branch.ts create mode 100644 plugins/plugin-system/src/for.ts create mode 100644 plugins/plugin-system/src/index.ts create mode 100644 plugins/plugin-system/src/join.ts rename {src/shared/libs/plugin-system => plugins/plugin-system/src}/log.ts (57%) rename {src/shared/libs/plugin-system => plugins/plugin-system/src}/manual.ts (100%) create mode 100644 plugins/plugin-system/src/prompt.ts rename {src/shared/libs/plugin-system => plugins/plugin-system/src}/sleep.ts (53%) create mode 100644 plugins/plugin-system/tsconfig.json create mode 100644 plugins/plugin-system/tsdown.config.ts create mode 100644 plugins/plugin-tauri/README.md create mode 100644 plugins/plugin-tauri/package.json create mode 100644 plugins/plugin-tauri/src/configure.ts create mode 100644 plugins/plugin-tauri/src/declarations.d.ts rename {src/shared/libs/plugin-electron => plugins/plugin-tauri/src}/fixtures/build/index.html (54%) create mode 100644 plugins/plugin-tauri/src/index.ts rename {src/shared/libs/plugin-discord => plugins/plugin-tauri/src}/make.spec.ts (55%) create mode 100644 plugins/plugin-tauri/src/make.ts create mode 100644 plugins/plugin-tauri/src/package.ts create mode 100644 plugins/plugin-tauri/src/preview.ts rename {src/shared/libs/plugin-tauri => plugins/plugin-tauri/src}/public/tauri.webp (100%) create mode 100644 plugins/plugin-tauri/src/tauri.ts rename {src/shared/libs/plugin-discord => plugins/plugin-tauri/src}/utils.ts (52%) create mode 100644 plugins/plugin-tauri/tsconfig.json create mode 100644 plugins/plugin-tauri/tsdown.config.ts create mode 100644 pnpm-workspace.yaml create mode 100644 scripts/delete_tags.sh create mode 100644 scripts/detect-changes.ts delete mode 100644 scripts/headless-electron-tester.mts delete mode 100644 scripts/migrate-file.mts create mode 100644 scripts/network-interceptor.mjs create mode 100644 scripts/publish-app.mjs create mode 100644 scripts/upload-windows.sh delete mode 100644 src/components.d.ts delete mode 100644 src/constants.ts delete mode 100644 src/definitions.d.ts delete mode 100644 src/global.d.ts delete mode 100644 src/main.ts delete mode 100644 src/main/api.ts delete mode 100644 src/main/config.ts delete mode 100644 src/main/handler-func.ts delete mode 100644 src/main/handlers.ts delete mode 100644 src/main/handlers/build-history.ts delete mode 100644 src/main/paths.ts delete mode 100644 src/main/presets/c3toSteam.ts delete mode 100644 src/main/presets/demo.ts delete mode 100644 src/main/presets/if.ts delete mode 100644 src/main/presets/loop.ts delete mode 100644 src/main/presets/moreToCome.ts delete mode 100644 src/main/presets/newProject.ts delete mode 100644 src/main/presets/test-c3-offline.ts delete mode 100644 src/main/presets/test-c3-unzip.ts delete mode 100644 src/main/utils.ts delete mode 100644 src/main/websocket-server.ts delete mode 100644 src/preload.d.ts delete mode 100644 src/preload.ts delete mode 100644 src/renderer.ts delete mode 100644 src/renderer/App.vue delete mode 100644 src/renderer/agent/utils/json.ts delete mode 100644 src/renderer/components/AddNodeButton.model.ts delete mode 100644 src/renderer/components/FileInput.vue delete mode 100644 src/renderer/components/ScenarioListItemFile.vue delete mode 100644 src/renderer/components/Settings.vue delete mode 100644 src/renderer/composables/api.ts delete mode 100644 src/renderer/composables/usePipeline.ts delete mode 100644 src/renderer/composables/usePostHog.ts delete mode 100644 src/renderer/composables/useTour.ts delete mode 100644 src/renderer/composables/variables.ts delete mode 100644 src/renderer/composables/websocket-client.ts delete mode 100644 src/renderer/composables/websocket-manager.ts delete mode 100644 src/renderer/env.d.ts delete mode 100644 src/renderer/i18n.ts delete mode 100644 src/renderer/main.ts delete mode 100644 src/renderer/models/constants.ts delete mode 100644 src/renderer/models/controls.ts delete mode 100644 src/renderer/plugins/posthog.ts delete mode 100644 src/renderer/router/router.ts delete mode 100644 src/renderer/store/app.ts delete mode 100644 src/renderer/store/auth.ts delete mode 100644 src/renderer/store/build-history.ts delete mode 100644 src/renderer/store/files.ts delete mode 100644 src/renderer/store/recents.ts delete mode 100644 src/renderer/store/settings.ts delete mode 100644 src/renderer/store/user.ts delete mode 100644 src/renderer/style/main.ts delete mode 100644 src/renderer/utils/config.ts delete mode 100644 src/renderer/utils/evaluator.ts delete mode 100644 src/renderer/utils/fmt.ts delete mode 100644 src/renderer/utils/quickjs.ts delete mode 100644 src/shared/apis.ts delete mode 100644 src/shared/build-history.ts delete mode 100644 src/shared/i18n-utils.ts delete mode 100644 src/shared/libs/.gitkeep delete mode 100644 src/shared/libs/core-app/index.ts delete mode 100644 src/shared/libs/core-app/variables.ts delete mode 100644 src/shared/libs/migration/index.ts delete mode 100644 src/shared/libs/migration/models/createMigrator.ts delete mode 100644 src/shared/libs/migration/models/migration.spec.ts delete mode 100644 src/shared/libs/migration/models/migration.ts delete mode 100644 src/shared/libs/plugin-construct/assets/script.ts delete mode 100644 src/shared/libs/plugin-construct/declarations.d.ts delete mode 100644 src/shared/libs/plugin-construct/export-shared.ts delete mode 100644 src/shared/libs/plugin-construct/index.ts delete mode 100644 src/shared/libs/plugin-core/create-plugin.ts delete mode 100644 src/shared/libs/plugin-core/custom-errors.ts delete mode 100644 src/shared/libs/plugin-core/index.ts delete mode 100644 src/shared/libs/plugin-core/pipelab.ts delete mode 100644 src/shared/libs/plugin-core/utils.ts delete mode 100644 src/shared/libs/plugin-discord/declarations.d.ts delete mode 100644 src/shared/libs/plugin-discord/discord.ts delete mode 100644 src/shared/libs/plugin-discord/index.ts delete mode 100644 src/shared/libs/plugin-discord/package.ts delete mode 100644 src/shared/libs/plugin-discord/preview.ts delete mode 100644 src/shared/libs/plugin-electron/configure.ts delete mode 100644 src/shared/libs/plugin-electron/declarations.d.ts delete mode 100644 src/shared/libs/plugin-electron/forge.ts delete mode 100644 src/shared/libs/plugin-electron/index.ts delete mode 100644 src/shared/libs/plugin-electron/make.ts delete mode 100644 src/shared/libs/plugin-electron/package-v2.ts delete mode 100644 src/shared/libs/plugin-electron/package.ts delete mode 100644 src/shared/libs/plugin-electron/preview.ts delete mode 100644 src/shared/libs/plugin-filesystem/copy.ts delete mode 100644 src/shared/libs/plugin-filesystem/index.ts delete mode 100644 src/shared/libs/plugin-filesystem/is-file.ts delete mode 100644 src/shared/libs/plugin-filesystem/list-files.ts delete mode 100644 src/shared/libs/plugin-filesystem/open.ts delete mode 100644 src/shared/libs/plugin-filesystem/remove.ts delete mode 100644 src/shared/libs/plugin-filesystem/run.ts delete mode 100644 src/shared/libs/plugin-filesystem/unzip.ts delete mode 100644 src/shared/libs/plugin-filesystem/zip-v2.ts delete mode 100644 src/shared/libs/plugin-filesystem/zip.ts delete mode 100644 src/shared/libs/plugin-itch/export.ts delete mode 100644 src/shared/libs/plugin-itch/index.ts delete mode 100644 src/shared/libs/plugin-minify/code.ts delete mode 100644 src/shared/libs/plugin-minify/images.ts delete mode 100644 src/shared/libs/plugin-minify/index.ts delete mode 100644 src/shared/libs/plugin-netlify/index.ts delete mode 100644 src/shared/libs/plugin-nvpatch/index.ts delete mode 100644 src/shared/libs/plugin-nvpatch/nvpatch.ts delete mode 100644 src/shared/libs/plugin-poki/export.ts delete mode 100644 src/shared/libs/plugin-poki/index.ts delete mode 100644 src/shared/libs/plugin-steam/declarations.d.ts delete mode 100644 src/shared/libs/plugin-steam/index.ts delete mode 100644 src/shared/libs/plugin-steam/upload-to-steam.ts delete mode 100644 src/shared/libs/plugin-system/alert.ts delete mode 100644 src/shared/libs/plugin-system/branch.ts delete mode 100644 src/shared/libs/plugin-system/for.ts delete mode 100644 src/shared/libs/plugin-system/index.ts delete mode 100644 src/shared/libs/plugin-system/join.ts delete mode 100644 src/shared/libs/plugin-system/prompt.ts delete mode 100644 src/shared/libs/plugin-tauri/configure.ts delete mode 100644 src/shared/libs/plugin-tauri/declarations.d.ts delete mode 100644 src/shared/libs/plugin-tauri/index.ts delete mode 100644 src/shared/libs/plugin-tauri/make.ts delete mode 100644 src/shared/libs/plugin-tauri/package.ts delete mode 100644 src/shared/libs/plugin-tauri/preview.ts delete mode 100644 src/shared/libs/plugin-tauri/tauri.ts delete mode 100644 src/shared/logger.ts delete mode 100644 src/shared/model.test.ts delete mode 100644 src/shared/model.ts delete mode 100644 src/shared/plugins.ts delete mode 100644 src/shared/subscription-errors.ts delete mode 100644 src/shared/supabase.ts delete mode 100644 src/shared/types.ts delete mode 100644 src/shared/utils.ts delete mode 100644 src/shared/validation.ts create mode 100644 supabase/functions/cloud-run/index.ts delete mode 100644 tests/e2e/tests/basic.spec.ts delete mode 100644 tests/e2e/vitest.config.mts create mode 100644 tsconfig.base.json create mode 100644 turbo.json delete mode 100644 vite.base.config.mts delete mode 100644 vite.main.config.mts delete mode 100644 vite.preload.config.mts delete mode 100644 vite.renderer.config.mts delete mode 100644 vitest.config.mts delete mode 160000 website diff --git a/.changeset/config.json b/.changeset/config.json index 11fb2a9b..8d365c08 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -2,7 +2,9 @@ "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json", "_changelog": [ "@changesets/changelog-github", - { "repo": "CynToolkit/pipelab" } + { + "repo": "CynToolkit/pipelab" + } ], "privatePackages": { "tag": true, @@ -11,9 +13,8 @@ "changelog": "@changesets/changelog-git", "commit": false, "access": "public", - "fixed": [], + "fixed": [["@pipelab/ui", "@pipelab/cli"]], "linked": [], "baseBranch": "main", - "updateInternalDependencies": "patch", - "ignore": [] + "updateInternalDependencies": "patch" } diff --git a/.changeset/free-animals-bet.md b/.changeset/free-animals-bet.md new file mode 100644 index 00000000..634b6844 --- /dev/null +++ b/.changeset/free-animals-bet.md @@ -0,0 +1,32 @@ +--- +"@pipelab/plugin-filesystem": major +"@pipelab/plugin-construct": major +"@pipelab/plugin-electron": major +"@pipelab/plugin-discord": major +"@pipelab/plugin-netlify": major +"@pipelab/plugin-nvpatch": major +"@pipelab/asset-electron": major +"@pipelab/plugin-minify": major +"@pipelab/plugin-system": major +"@pipelab/asset-discord": major +"@pipelab/asset-netlify": major +"@pipelab/cloud-azure": major +"@pipelab/plugin-steam": major +"@pipelab/plugin-tauri": major +"@pipelab/test-utils": major +"@pipelab/plugin-core": major +"@pipelab/plugin-itch": major +"@pipelab/plugin-poki": major +"@pipelab/asset-tauri": major +"@pipelab/constants": major +"@pipelab/core-node": major +"@pipelab/migration": major +"@pipelab/tsconfig": major +"@pipelab/shared": major +"@pipelab/cloud": major +"@pipelab/app": major +"@pipelab/cli": major +"@pipelab/ui": major +--- + +initial release diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7fb32ef9..0595b8d0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,12 +10,10 @@ "ghcr.io/sebst/devcontainer-features/desktop-fluxbox:0": {}, "ghcr.io/devcontainers/features/desktop-lite:1": {} }, - "forwardPorts": [ - 6080 - ], + "forwardPorts": [6080], "portsAttributes": { "6080": { "label": "desktop" } } -} \ No newline at end of file +} diff --git a/.env.example b/.env.example deleted file mode 100644 index 0b1c7eef..00000000 --- a/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -SUPABASE_URL= -SUPABASE_ANON_KEY= -SUPABASE_PROJECT_ID= -EDGE_FUNCTION_ROOT= -POSTHOG_API_KEY= diff --git a/.gemini/settings.json b/.gemini/settings.json index 7bd98b24..c2691c88 100644 --- a/.gemini/settings.json +++ b/.gemini/settings.json @@ -2,13 +2,10 @@ "mcpServers": { "primevue": { "command": "npx", - "args": [ - "-y", - "@primevue/mcp" - ] + "args": ["-y", "@primevue/mcp"] }, "supabase": { "httpUrl": "https://mcp.supabase.com/mcp?project_ref=uehmyyeqheqnxmzhctcd" } } -} \ No newline at end of file +} diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml new file mode 100644 index 00000000..1e24f47d --- /dev/null +++ b/.github/workflows/pipeline.yml @@ -0,0 +1,452 @@ +name: Pipeline + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + workflow_dispatch: + inputs: + forcePublish: + description: "Force publish apps" + required: false + type: boolean + default: false + prerelease: + description: "Mark as pre-release" + required: false + type: boolean + default: true + +permissions: + contents: write + pull-requests: write + id-token: write + +jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + outputs: + affected: ${{ steps.detect.outputs.affected }} + needs_build: ${{ steps.detect.outputs.needs_build }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: Use Node.js 24 + uses: actions/setup-node@v4 + with: + node-version: 24 + + - name: Detect Changes + id: detect + run: | + if [ "${{ github.event_name }}" == "pull_request" ]; then + BASE_REF="origin/${{ github.base_ref }}" + elif [ "${{ github.ref_name }}" != "main" ] && [ "${{ github.ref_name }}" != "develop" ]; then + # On feature/integration branches, compare against main to see the full PR history + BASE_REF="origin/main" + else + # On main/develop push, only compare against the previous commit (delta) + BASE_REF="${{ github.event.before }}" + fi + + # Fallback if BASE_REF is null or empty (initial push) + if [ -z "$BASE_REF" ] || [ "$BASE_REF" == "0000000000000000000000000000000000000000" ]; then + BASE_REF=$(git describe --tags --abbrev=0 2>/dev/null || echo "HEAD~10") + fi + + echo "Comparing against: $BASE_REF" + + # Use turbo to list affected packages + # We use npx to avoid full install if possible, but turbo needs the graph + npx turbo ls -F "[$BASE_REF]" --output=json > affected.json + + # Run our detection script + npx tsx scripts/detect-changes.ts affected.json + + verify-lint: + name: Verify Lint + needs: changes + if: needs.changes.outputs.affected != '[]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + run_install: false + - name: Use Node.js 24 + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: "pnpm" + - name: Install dependencies + run: pnpm install + - name: Cache Turbo + uses: actions/cache@v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.job }}- + - name: Lint + run: pnpm turbo lint --cache-dir=".turbo" + continue-on-error: true + + verify-typecheck: + name: Verify Typecheck + needs: changes + if: needs.changes.outputs.affected != '[]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + run_install: false + - name: Use Node.js 24 + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: "pnpm" + - name: Install dependencies + run: pnpm install + - name: Cache Turbo + uses: actions/cache@v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.job }}- + - name: Typecheck + run: pnpm turbo typecheck --cache-dir=".turbo" + continue-on-error: true + + build-all: + name: Build All + needs: [verify-lint, verify-typecheck, test-matrix, changes] + if: needs.changes.outputs.affected != '[]' && needs.changes.outputs.needs_build == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + run_install: false + - name: Use Node.js 24 + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: "pnpm" + - name: Install dependencies + run: pnpm install + - name: Cache Turbo + uses: actions/cache@v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.job }}- + - name: Build packages + env: + SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} + SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }} + POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} + run: pnpm build --cache-dir=".turbo" + - name: Upload monorepo-dist + uses: actions/upload-artifact@v4 + with: + name: monorepo-dist + path: | + packages/*/dist/** + plugins/*/dist/** + apps/*/dist/** + apps/desktop/.vite/** + apps/ui/dist/** + + publish-preview: + name: Publish Preview + needs: build-all + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + run_install: false + - name: Use Node.js 24 + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: "pnpm" + - name: Install dependencies + run: pnpm install + - name: Download monorepo-dist + uses: actions/download-artifact@v4 + with: + name: monorepo-dist + path: . + - name: Publish to pkg.pr.new + run: | + npx pkg-pr-new publish \ + packages/shared \ + packages/core-node \ + plugins/plugin-* \ + assets/asset-* \ + packages/migration \ + packages/constants \ + apps/cli \ + --comment=update + + release: + name: Release & Deploy + needs: [build-all, verify-lint, verify-typecheck, test-matrix, changes, build-desktop] + if: | + always() && + (needs.build-desktop.result == 'success' || needs.build-desktop.result == 'skipped') && + (needs.build-all.result == 'success' || needs.build-all.result == 'skipped') && + needs.verify-lint.result == 'success' && + needs.verify-typecheck.result == 'success' && + needs.test-matrix.result == 'success' && + (github.event_name == 'workflow_dispatch' || + (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'develop'))) + runs-on: ubuntu-latest + outputs: + published: ${{ steps.changesets.outputs.published }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: Use Node.js 24 + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Download monorepo-dist + if: needs.changes.outputs.needs_build == 'true' + uses: actions/download-artifact@v4 + with: + name: monorepo-dist + path: . + + - name: Enter/Exit Changeset Pre-mode + run: | + if [ "${{ github.ref_name }}" == "develop" ]; then + if [ ! -f .changeset/pre.json ]; then + pnpm changeset pre enter beta + fi + elif [ "${{ github.ref_name }}" == "main" ]; then + if [ -f .changeset/pre.json ]; then + pnpm changeset pre exit + fi + fi + + - name: Publish to NPM & Create GitHub Release + id: changesets + uses: changesets/action@v1 + with: + publish: ${{ github.ref_name == 'develop' && 'pnpm changeset publish --tag beta --provenance' || 'pnpm changeset publish --provenance' }} + createGithubReleases: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: true + + + + - name: Download all artifacts + if: steps.changesets.outputs.published == 'true' || github.event.inputs.forcePublish == 'true' + uses: actions/download-artifact@v4 + with: + path: artifacts + continue-on-error: true + + - name: Debug Artifacts + run: ls -R artifacts + continue-on-error: true + + - name: Get Versions + id: versions + run: | + APP_VERSION=$(node -p "require('./apps/desktop/package.json').version") + CLI_VERSION=$(node -p "require('./apps/cli/package.json').version") + ASSET_ELECTRON_VERSION=$(node -p "require('./assets/asset-electron/package.json').version") + ASSET_DISCORD_VERSION=$(node -p "require('./assets/asset-discord/package.json').version") + ASSET_NETLIFY_VERSION=$(node -p "require('./assets/asset-netlify/package.json').version") + ASSET_TAURI_VERSION=$(node -p "require('./assets/asset-tauri/package.json').version") + UI_VERSION=$(node -p "require('./apps/ui/package.json').version") + echo "app_version=$APP_VERSION" >> $GITHUB_OUTPUT + echo "cli_version=$CLI_VERSION" >> $GITHUB_OUTPUT + echo "asset_electron_version=$ASSET_ELECTRON_VERSION" >> $GITHUB_OUTPUT + echo "asset_discord_version=$ASSET_DISCORD_VERSION" >> $GITHUB_OUTPUT + echo "asset_netlify_version=$ASSET_NETLIFY_VERSION" >> $GITHUB_OUTPUT + echo "asset_tauri_version=$ASSET_TAURI_VERSION" >> $GITHUB_OUTPUT + echo "ui_version=$UI_VERSION" >> $GITHUB_OUTPUT + + IS_PRERELEASE="false" + if [[ $APP_VERSION == *"-"* ]] || [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.prerelease }}" == "true" ]]; then + IS_PRERELEASE="true" + fi + + echo "prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT + + - name: Upload Desktop Binaries + if: steps.changesets.outputs.published == 'true' || github.event.inputs.forcePublish == 'true' + uses: softprops/action-gh-release@v2 + with: + tag_name: "@pipelab/app@${{ steps.versions.outputs.app_version }}" + prerelease: ${{ steps.versions.outputs.prerelease == 'true' }} + files: | + artifacts/**/*.dmg + artifacts/**/*.exe + artifacts/**/*.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish @pipelab/ui dynamically + if: steps.changesets.outputs.published == 'true' || github.event_name == 'workflow_dispatch' + run: node scripts/publish-app.mjs ui + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} + + - name: Publish @pipelab/cli dynamically + if: steps.changesets.outputs.published == 'true' || github.event_name == 'workflow_dispatch' + run: node scripts/publish-app.mjs cli + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} + + build-desktop: + name: Build Desktop (${{ matrix.os }} - ${{ matrix.arch }}) + needs: [build-all, test-matrix, changes] + if: | + always() && + (needs.build-all.result == 'success') && + (needs.test-matrix.result == 'success' || needs.test-matrix.result == 'skipped') && + (contains(fromJson(needs.changes.outputs.affected), '@pipelab/app') || github.event.inputs.forcePublish == 'true') + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + arch: x64 + - os: windows-latest + arch: x64 + - os: macos-15-intel + arch: x64 + - os: macos-latest + arch: arm64 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + run_install: false + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: "pnpm" + - run: pnpm install + + - name: Download monorepo-dist + if: needs.changes.outputs.needs_build == 'true' + uses: actions/download-artifact@v4 + with: + name: monorepo-dist + path: . + + - name: Import Apple Code Signing Certificate + uses: apple-actions/import-codesign-certs@v3 + if: startsWith(matrix.os, 'macos-') + with: + p12-file-base64: ${{ secrets.CERTIFICATE_OSX_APPLICATION }} + p12-password: ${{ secrets.CERTIFICATE_OSX_APPLICATION_PASSWORD }} + + - name: Debug Architectures + run: | + echo "Host Architecture (uname -m): $(uname -m)" + echo "Host Architecture (node process.arch): $(node -p 'process.arch')" + echo "Target Architecture (matrix.arch): ${{ matrix.arch }}" + echo "Target Architecture (env.TARGET_ARCH): $TARGET_ARCH" + echo "Target Architecture (env.npm_config_arch): $npm_config_arch" + env: + TARGET_ARCH: ${{ matrix.arch }} + npm_config_arch: ${{ matrix.arch }} + + - name: Package & Make Desktop + env: + DEBUG: "electron-osx-sign*,electron-forge:*" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} + SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }} + POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + TARGET_ARCH: ${{ matrix.arch }} + npm_config_arch: ${{ matrix.arch }} + run: | + cd apps/desktop + pnpm run make -- --arch=${{ matrix.arch }} + + - name: Debug Output + run: ls -R apps/desktop/out/make + continue-on-error: true + + - uses: actions/upload-artifact@v4 + with: + name: desktop-artifacts-${{ matrix.os }}-${{ matrix.arch }} + path: apps/desktop/out/make/**/* + + test-matrix: + name: Test Matrix (${{ matrix.os }}) + needs: [changes] + if: | + always() && + needs.changes.outputs.affected != '[]' + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + run_install: false + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: "pnpm" + - run: pnpm install + + - name: Cache Turbo + uses: actions/cache@v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.job }}- + - name: Run Tests + run: pnpm turbo test --cache-dir=".turbo" --log-order=grouped --concurrency 1 diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml deleted file mode 100644 index 58d89887..00000000 --- a/.github/workflows/push.yml +++ /dev/null @@ -1,161 +0,0 @@ -name: Electron Forge CI/CD - -on: - push: - tags: - - "*" - branches: - - main - - develop - pull_request: - branches: [main, develop] - merge_group: - workflow_dispatch: - -jobs: - build-and-test: - runs-on: ${{ matrix.os }} - env: - CI: true - DEBUG: "*" - - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - arch: [x64, arm64] - exclude: - - os: ubuntu-latest - arch: arm64 - - os: windows-latest - arch: arm64 - - steps: - - uses: actions/checkout@v4 - - - uses: pnpm/action-setup@v4 - name: Install pnpm - with: - run_install: false - - - name: Use Node.js 22.5.1 - uses: actions/setup-node@v4 - with: - node-version: "22.5.1" - cache: "pnpm" - - - name: Set up Python 3.10 - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - - name: Install dependencies - run: pnpm install - - - name: rebuild - run: npm rebuild - - - name: Import Apple Code Signing Certificate - uses: apple-actions/import-codesign-certs@v3 - if: matrix.os == 'macos-latest' - with: - p12-file-base64: ${{ secrets.CERTIFICATE_OSX_APPLICATION }} - p12-password: ${{ secrets.CERTIFICATE_OSX_APPLICATION_PASSWORD }} - - - name: Verify certificates - if: matrix.os == 'macos-latest' - run: | - security find-identity -v -p codesigning - codesign --verify --verbose /Applications/Xcode.app - - - name: Package application - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SUPABASE_URL: ${{ secrets.SUPABASE_URL }} - SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} - SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }} - POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - run: pnpm electron-forge publish --dry-run --arch ${{ matrix.arch }} - - - name: Disable AppArmor restriction - if: matrix.os == 'ubuntu-latest' - run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - - - name: Run tests - uses: coactions/setup-xvfb@v1 - continue-on-error: true - with: - run: pnpm test:e2e:raw - - - name: Upload Playwright artifacts - uses: actions/upload-artifact@v4 - continue-on-error: true - with: - name: ${{ matrix.os }}-playwright - path: playwright/**/* - - - name: windows workaround - if: matrix.os == 'windows-latest' - run: rd -r "out/Pipelab-win32-x64/resources/app/node_modules" - - - name: Upload artifacts - if: success() - continue-on-error: true - uses: actions/upload-artifact@v4 - with: - name: artifact-${{ matrix.os }}-${{ matrix.arch }}-out - path: out/**/* - - release: - permissions: - contents: write - discussions: write - needs: build-and-test - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - arch: [x64, arm64] - exclude: - - os: ubuntu-latest - arch: arm64 - - os: windows-latest - arch: arm64 - if: | - !cancelled() && - (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') - - steps: - - uses: actions/checkout@v4 - - - name: Download artifacts - uses: actions/download-artifact@v5 - with: - name: artifact-${{ matrix.os }}-${{ matrix.arch }}-out - path: out - - - uses: pnpm/action-setup@v4 - name: Install pnpm - with: - run_install: false - - - name: Use Node.js 22.5.1 - uses: actions/setup-node@v4 - with: - node-version: "22.5.1" - cache: "pnpm" - - - name: Install dependencies - run: pnpm install --prod=false - - - name: windows workaround - if: matrix.os == 'windows-latest' - run: cd out/Pipelab-win32-x64/resources/app && pnpm i - - - name: Publish release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: pnpm electron-forge publish --from-dry-run diff --git a/.gitignore b/.gitignore index 3a025da9..919a21cd 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,9 @@ typings/ # Vite .vite/ +# Build output +dist/ + # Electron-Forge out/ @@ -106,3 +109,8 @@ assets/tauri/template/app/src/app # Local Netlify folder .netlify +apps/cli/bin + +.turbo +apps/desktop/bin/ +apps/cli/assets/ui \ No newline at end of file diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 9c6b791d..00000000 --- a/.prettierignore +++ /dev/null @@ -1,6 +0,0 @@ -out -dist -pnpm-lock.yaml -LICENSE.md -tsconfig.json -tsconfig.*.json diff --git a/.prettierrc.yaml b/.prettierrc.yaml deleted file mode 100644 index 35893b3b..00000000 --- a/.prettierrc.yaml +++ /dev/null @@ -1,4 +0,0 @@ -singleQuote: true -semi: false -printWidth: 100 -trailingComma: none diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 00000000..17fb80cb --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,91 @@ +# Pipelab Architecture + +Pipelab has a modular architecture designed to support both a rich desktop experience and standalone execution in headless environments (like CI/CD pipelines). + +## High-Level Diagram + +The following diagram illustrates how the different packages in the monorepo interact, especially in the context of the Desktop Application. + +```mermaid +graph TD + %% Define Packages + subgraph apps["Apps Workspace"] + Desktop["@pipelab/app
(Electron Shell)"] + UI["@pipelab/ui
(Vue 3 Web App)"] + CLI["@pipelab/cli
(Node.js Server/CLI)"] + end + + subgraph packages["Packages Workspace"] + CoreNode["@pipelab/core-node
(Business Logic & Plugins)"] + Shared["@pipelab/shared
(Types, APIs, Utilities)"] + Constants["@pipelab/constants"] + end + + %% Dependencies & Data Flow + User([User]) -->|Interacts with| UI + + UI -->|1. Native Dialogs/OS Integration| Desktop + note1[IPC via Preload] -.-> UI + + UI -->|2. Graph Execution & File IO| CLI + note2[WebSocket Connection] -.-> UI + + Desktop -->|Spawns & Manages Lifecycle| CLI + + CLI -->|Executes Logic| CoreNode + Desktop -->|Hooks Native Capabilities| CoreNode + + %% Shared Dependencies + UI -.-> Shared + Desktop -.-> Shared + CLI -.-> Shared + CoreNode -.-> Shared + + UI -.-> Constants + Desktop -.-> Constants + CLI -.-> Constants + CoreNode -.-> Constants +``` + +## Core Components + +### 1. `@pipelab/ui` (The Frontend) + +A standalone Vite + Vue 3 Single Page Application (SPA). + +- **Responsibility**: Rendering the visual node editor, settings, and pipeline management interfaces. +- **Agnostic**: It does not import any Node.js or Electron-specific code. +- **Routing**: It uses an intelligent API composable (`useAPI`) that routes requests: + - **Native OS Tasks** (e.g., Opening a file picker dialog) are sent to the Electron shell via IPC. + - **Core Tasks** (e.g., Executing a pipeline, reading/writing project files) are sent to the standalone CLI server via WebSockets. + +### 2. `@pipelab/app` (The Desktop Shell) + +A thin Electron wrapper around the UI and the CLI. + +- **Responsibility**: Providing OS-level integration (File Dialogs, Auto-updates, System Tray) and managing the lifecycle of the underlying CLI server. +- **Startup Flow**: + 1. Electron starts up extremely fast as it loads minimal dependencies. + 2. It spawns the `@pipelab/cli` server as a background child process. + 3. It loads the `@pipelab/ui` web application in a `BrowserWindow`. +- **Context Injection**: It injects native Electron capabilities (like `BrowserWindow` focus and `dialog` modules) into the shared `SystemContext` so the core logic can request UI prompts if necessary. + +### 3. `@pipelab/cli` (The Standalone Server & CLI) + +A Node.js command-line interface, bundled into standalone binaries using `pkg` for production. + +- **Responsibility**: Running the WebSocket server that the UI connects to, and eventually serving as a headless runner for CI/CD environments. +- **Capabilities**: It has full file-system access and runs the heavy Node.js plugins (Docker, zip extraction, external command execution). + +### 4. `@pipelab/core-node` (The Brains) + +A shared library containing all the Node.js specific business logic. + +- **Responsibility**: Defining the WebSocket server, IPC handlers, plugin execution engine, and file system operations. +- **Environment Agnostic**: It relies on an injected `SystemContext` to abstract away whether it is running inside an Electron main process or a headless CLI. + +### 5. `@pipelab/shared` & `@pipelab/constants` + +Shared libraries containing code that is safe to run in both Node.js and Browser environments. + +- **Responsibility**: Defining data models, IPC definitions, validation schemas, and common utilities used across the entire monorepo. diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index cbe78641..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,58 +0,0 @@ -# @pipelab/app - -## 1.4.6 - -### Patch Changes - -- 94778b9: improvements - add log support - fix config loading - fix step placeholder in parameter editor - improved new project creation - add steam & steam overlay support - -## 1.4.5 - -### Patch Changes - -- 3ea4f00: fix tests - -## 1.4.4 - -### Patch Changes - -- a2489dd: fix electron configuration - -## 1.4.3 - -### Patch Changes - -- improvements - add log support - fix config loading - fix step placeholder in parameter editor - -## 1.4.1 - -### Patch Changes - -- 48a3569: feat(plugin-construct): add throttle zip log - feat(plugin-construct): add click on addon installation prompts - feat(plugin-construct): add loading project progress -- d1f68b8: feat(electron): support url preview (fix #32) - feat(app): add dialog prompt - feat(app): improve ui -- 665562c: fix(app): fix adding node wrong offset - feat(app): hide sidebar when editing project -- 48a3569: fix(app): add live logs hooks support - fix(plugin-steam): fix steam permission, general improvements & logged out detection - -## 1.1.26 - -### Patch Changes - -- 25e6bb0: path picker now support options from plugins -- 26be9b1: feat(electron): add custom main code support - feat(electron): add "configure" action - feat: improve display strings -- fadc3cc: Improved boolean parameter diff --git a/GEMINI.md b/GEMINI.md index cbce0ca2..03c6b128 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -4,12 +4,13 @@ Pipelab is a visual automation tool designed to create task automation workflows ## Architecture Overview -The project is an Electron-based application built with Vue 3 and TypeScript. +The project is an Electron-based application built with Vue 3 and TypeScript, organized as a Turbo monorepo. -- **Main Process (`src/main.ts`)**: Manages the Electron lifecycle, IPC handlers, a WebSocket server for real-time communication, and handles headless pipeline execution via command-line arguments. -- **Renderer Process (`src/renderer/`)**: A Vue 3 application using PrimeVue for UI components. It communicates with the main process via IPC and WebSockets. -- **Shared (`src/shared/`)**: Contains models, types, and utilities shared between the main and renderer processes. -- **Core Engine**: Leverages `@pipelab/core` and QuickJS (via WebAssembly) for executing automation logic. +- **Desktop App (`apps/desktop/`)**: Manages the Electron lifecycle, IPC handlers, a WebSocket server for real-time communication, and handles headless pipeline execution. +- **UI Application (`apps/ui/`)**: A Vue 3 application using PrimeVue for UI components. It communicates with the desktop main process via IPC and WebSockets. +- **CLI App (`apps/cli/`)**: A command-line interface for Pipelab. +- **Packages (`packages/`)**: Contains shared logic, constants, plugins (Steam, Itch, etc.), and the core node system. +- **Core Engine (`packages/core-node/` & `@pipelab/plugin-core`)**: Leverages `@pipelab/core` and QuickJS (via WebAssembly) for executing automation logic. ## Tech Stack @@ -17,57 +18,65 @@ The project is an Electron-based application built with Vue 3 and TypeScript. - **Frontend**: [Vue 3](https://vuejs.org/) (Composition API) - **UI Library**: [PrimeVue v4](https://primevue.org/) with Tailwind/PrimeFlex - **State Management**: [Pinia](https://pinia.vuejs.org/) -- **Build Tool**: [Vite](https://vitejs.dev/) with [Electron Forge](https://www.electronforge.io/) +- **Build Tool**: [Vite](https://vitejs.dev/) with [Electron Forge](https://www.electronforge.io/) and [Turborepo](https://turbo.build/) - **Database/Backend**: [Supabase](https://supabase.com/) - **Testing**: [Vitest](https://vitest.dev/) (Unit), [Playwright](https://playwright.dev/) (E2E) +- **Linting & Formatting**: [oxlint](https://oxc-project.github.io/docs/guide/usage/linter.html) & [oxfmt](https://oxc-project.github.io/docs/guide/usage/formatter.html) - **Analytics/Monitoring**: [PostHog](https://posthog.com/), [Sentry](https://sentry.io/) ## Key Directories -- `src/main/`: Electron main process logic, IPC handlers, and API. -- `src/renderer/`: Vue application source code (components, pages, store, etc.). -- `src/shared/`: Shared types, constants, and utilities. -- `assets/`: Static assets and build-related resources (icons, entitlements). +- `apps/desktop/`: Electron main process logic, IPC handlers, and API. +- `apps/ui/`: Vue application source code (components, pages, store, etc.). +- `apps/cli/`: CLI entrypoints and commands. +- `packages/`: Shared workspace packages (constants, migration code, and plugins). - `scripts/`: Maintenance and migration scripts. -- `tests/`: Unit and E2E tests. +- `tests/`: End-to-end and unit tests. ## Building and Running -The project uses `pnpm` as its package manager. - -| Task | Command | -| :--- | :--- | -| **Development** | `pnpm dev` (starts Electron with source maps enabled) | -| **Build (All)** | `pnpm build` | -| **Build (Linux)** | `pnpm build:linux` | -| **Build (Windows)** | `pnpm build:win` | -| **Build (Mac)** | `pnpm build:mac` | -| **Package** | `pnpm package` | -| **Unit Tests** | `pnpm test:unit` | -| **E2E Tests** | `pnpm test:e2e:local` | -| **Linting** | `pnpm lint` | -| **Type Checking**| `pnpm typecheck` | +The project uses `pnpm` as its package manager and Turborepo for task orchestration. + +| Task | Command | +| :---------------- | :------------------------------------------ | +| **Development** | `turbo dev` or `pnpm dev` | +| **Build (All)** | `turbo build` or `pnpm build` | +| **Package** | `turbo package` or `pnpm package` | +| **Unit Tests** | `turbo test` or `pnpm test:unit` | +| **Linting** | `turbo lint` or `pnpm lint` (runs `oxlint`) | +| **Type Checking** | `turbo typecheck` or `pnpm typecheck` | +| **Format** | `pnpm format` (runs `oxfmt .`) | ## Development Conventions - **Typing**: Strict TypeScript usage across the codebase. - **Components**: Functional and modular Vue components. PrimeVue is used for most UI elements. - **State**: Persistent state management using Pinia with `pinia-plugin-persistedstate`. -- **Communication**: Use the defined IPC handlers (`src/main/handlers.ts`) and the WebSocket manager for renderer-to-main or external communication. +- **Communication**: Use the defined IPC handlers and the WebSocket manager for renderer-to-main or external communication. - **Versioning**: Uses [Changesets](https://github.com/changesets/changesets) for managing versions and changelogs. -- **Code Style**: Enforced by ESLint and Prettier. Run `pnpm format` to auto-format. +- **Code Style**: Enforced by `oxlint` and `oxfmt`. Run `pnpm format` to auto-format. ## Common Workflows -- **Adding a new Node/Block**: Investigate `src/shared/model.ts` and how nodes are registered in the core engine. -- **Modifying the UI**: Most views are located in `src/renderer/pages/` or `src/renderer/components/`. -- **Database Changes**: Update Supabase types using `pnpm supa:types` after modifying the remote schema. +- **Adding a new Node/Block**: Investigate `plugins/plugin-core` or related plugins and how nodes are registered in the core engine. +- `plugins/`: Contains all Pipelab plugins. +- `assets/`: Contains all Pipelab assets. +- **Modifying the UI**: Most views are located in `apps/ui/src/pages/` or `apps/ui/src/components/`. +- **Database Changes**: Update Supabase types after modifying the remote schema. - **Releases**: 1. `pnpm changeset` to document changes. 2. `pnpm changeset version` to bump versions. 3. `pnpm changeset tag` to tag the release. +# General recommendations -# General recomandations - When modifying code, do not attempt to typecheck it. - When modifying code, do not attempt to lint it. +- When creating packages, ensure files are **not** accessed via subpaths (e.g. `import { useAPI } from "@pipelab/shared/api"`). +- Do not read docs from packages irectly, use context7 mcp +- Prefer your native file read tools over cat command +- Do not grep unless strictly necessary +- Use pnpm instead of npm +- Never grep in gitignored folders (like node_modules or dist or out folders) +- Think twice before proposing a solution or a plan: there may be evident flaws or simpler alternatives +- Use rg/ripgrep instead of grep diff --git a/LICENSE.md b/LICENSE similarity index 98% rename from LICENSE.md rename to LICENSE index bc7df19b..3f204d7d 100644 --- a/LICENSE.md +++ b/LICENSE @@ -6,7 +6,7 @@ FSL-1.1-MIT ## Notice -Copyright 2024-present Quentin Goinaud - Armaldio +Copyright 2026 CynToolkit ## Terms and Conditions diff --git a/README.md b/README.md index 87217926..649a0b9f 100644 --- a/README.md +++ b/README.md @@ -2,83 +2,92 @@ ![logo](./readme/full_white_bg_black_text.png) -## What is Pipelab? +Pipelab is a visual automation tool designed to create task automation workflows and cross-platform desktop applications. -A visual tool to create task automation workflows. +## 🏗️ Orchestration Overview -## Why use Pipelab? +Pipelab is built as a monorepo with three primary application layers that work together to provide a seamless automation experience: -- Create cross-platform desktop applications -- Deploy to popular platforms (Steam, Itch.io, etc.) -- Automate repetitive tasks +```mermaid +graph TD + classDef main fill:#0096FF,stroke:#333,stroke-width:2px; -# Getting Started + subgraph UserInterface["User Interface"] + UI["@pipelab/ui (Vue 3)"] + end -# Making a release -``` -pnpm changeset version -pnpm changeset tag + subgraph Container["Desktop Container"] + Electron["@pipelab/app (Electron)"] + end + + subgraph Engine["The Engine"] + CLI["@pipelab/cli (Node.js)"] + end + + UI <-->|WebSocket| CLI + UI <-->|IPC| Electron + Electron ---|Spawns Sidecar| CLI + CLI ---|Executes| Pipelines[Automation Pipelines] + + class UI,Electron,CLI main; ``` -# Architecture -```mermaid -graph TD - classDef pipelab fill:#0096FF,stroke:#333,stroke-width:4px; - classDef todo stroke:#333,stroke-width:4px, stroke-dasharray: 4px; +- **The Engine (@pipelab/cli)**: A standalone Node.js server that handles the heavy lifting. It executes pipelines, manages plugin logic, and exposes a WebSocket API. +- **The Interface (@pipelab/ui)**: A Vue 3 application that provides the visual graph editor. It connects to the CLI via WebSockets for real-time execution feedback. +- **The Container (@pipelab/app)**: An Electron wrapper that provides native OS integration (file dialogs, system tray). In production, it automatically manages the CLI as a "sidecar" process. - DesktopApp[Desktop App - Pipelab] - GameBundle[Game Editor output] +--- - subgraph GameEditors - Construct3[Construct 3] - Godot[Godot] - GDevelop[GDevelop] - end +## 🛠️ Setup & Development - PipelabPlugin[Pipelab Plugin] - SteamPlugin[Steam Plugin] - CoreMessaging[Core Messaging Library] - Renderers[Renderers] +### 1. Prerequisites - subgraph Runtime - Electron - Tauri - Webview - end +Tool versions are managed via **mise**. Check [`.mise.toml`](.mise.toml) for the current requirements. - subgraph Platforms - Steam - Itch - Poki - end +### 2. Environment Configuration - Steamworks[steamworks.js Library] +Create a `.env` file in the **root directory**. This is the single source of truth for all packages: - GameEditors -->|Bundles to| GameBundle - GameBundle -->|Is imported into| DesktopApp - GameEditors -->|Includes| PipelabPlugin +```env +SUPABASE_URL=your_project_url +SUPABASE_ANON_KEY=your_key +POSTHOG_API_KEY=your_key +``` - PipelabPlugin -->|Is included in| GameBundle - PipelabPlugin -->|Implements| CoreMessaging +### 3. Installation - SteamPlugin -->|Is included in| GameBundle - SteamPlugin -->|Implements| CoreMessaging - SteamPlugin -->|Uses| Steamworks +```bash +pnpm install +``` - CoreMessaging -->|Passes messages to| Renderers - Runtime -->|Is embedded in| Renderers - DesktopApp -->|Packages to| Runtime - Runtime -->|Handles events from| CoreMessaging +### 4. Running Development Mode - DesktopApp -->|Deploys to| Platforms - Platforms -->|Uses| Runtime +The fastest way to start the entire ecosystem is from the root: - class DesktopApp,PipelabPlugin,SteamPlugin,CoreMessaging pipelab; - class SteamPlugin,Godot,GDevelop,Tauri,Webview,Itch,Poki todo; +```bash +pnpm dev ``` -# Development -## Enable source maps +> [!TIP] +> This command uses **Turborepo** to start the UI dev server, the Electron process, and the CLI server concurrently. + +--- + +## 📦 Project Structure + +- **`apps/cli`**: The headless engine and WebSocket server. +- **`apps/desktop`**: The Electron lifecycle and IPC handlers. +- **`apps/ui`**: Rendering and visual graph interaction. +- **`packages/*`**: Shared logic, standard constants, and modular plugins (Steam, Discord, etc.). + +--- + +## 🚀 Releases & Versioning + +We use **Changesets** to manage versions and changelogs: + ```bash -NODE_OPTIONS=--enable-source-maps pnpm xxx +pnpm changeset # Document a change +pnpm changeset version # Bump versions +pnpm changeset tag # Create git tags ``` diff --git a/TODO b/TODO deleted file mode 100644 index 157eb323..00000000 --- a/TODO +++ /dev/null @@ -1,74 +0,0 @@ -☐ IMPORTANT - ☐ To cross compile tauri or electron, I don't want a backend or my own CI - ☐ The idea is to have confgurations store online on your account - ☐ On Github Actions or any other CI service, have an action that is - ☐ pipelab/setup and pipelab/run that install all dependencies and that run a config - ☐ Infos are from the variables: account, pipeline & api key - ☐ It will run the pipeline as if it was your computer - -process.noAsar = true is here because electron fs can't deal correctly with asar (for example, like when copying an asar archive) - -https://github.com/nocode-js/sequential-workflow-editor -https://github.com/xyflow/awesome-node-based-uis -https://github.com/CatalystCode/windows-registry-node -https://github.com/tympanix/electron-regedit - -☐ Target: - Stage 1: - ☐ Autosave - ☐ Optimize/minimize images - ☐ https://www.google.com/search?q=sharp+lossless+compression+png&client=firefox-b-d&sxsrf=AB5stBg5O1Uihp_JBHyJnrcJEeF5sm2ZAg%3A1690961809752&ei=kQfKZImzLc2JkdUPqKWVkAs&ved=0ahUKEwjJv_7Pu72AAxXNRKQEHahSBbIQ4dUDCA4&uact=5&oq=sharp+lossless+compression+png&gs_lp=Egxnd3Mtd2l6LXNlcnAiHnNoYXJwIGxvc3NsZXNzIGNvbXByZXNzaW9uIHBuZzIKEAAYRxjWBBiwAzIKEAAYRxjWBBiwAzIKEAAYRxjWBBiwAzIKEAAYRxjWBBiwAzIKEAAYRxjWBBiwAzIKEAAYRxjWBBiwAzIKEAAYRxjWBBiwAzIKEAAYRxjWBBiwA0i5GVDSBFixGHABeAGQAQCYAQCgAQCqAQC4AQPIAQD4AQHiAwQYACBBiAYBkAYI&sclient=gws-wiz-serp - ☐ Package as electron (+ notarize + sign + expose api) - Stage 2: - ☐ CLI - ☐ Github Action integration - ☐ Online Account - ☐ Package as tauri - ☐ Godot export - ☐ Package to playable ad - ☐ Package as discord activity - ☐ Load sensible datas from environnement variables for security purposes - ☐ Command palette - ☐ Undo/Redo - ☐ Upload to - ☐ poki - ☐ itch -. - -EULA -https://codemirror.net/examples/lint/ -https://remixicon.com/ - - ---- - -Tests: -☐ View logs in realtime: started -☐ env (= copy variables) -☐ handle pipeline error gracefully - ☐ display what happened in dialog -☐ process quickjs in webworker - -New version of the c3 export addons for improved version selection -Edge case: old version of the app vs new version of c3p -Activate save only when needed -Active cancel button to not be locked out -Simple mode: also allow to pick variables & such -QuickJS: memory leak, open close action -Add own timers for electorn export & such -C3 Preview ne fonctionne pas -Steam achievement test c3 example -Filesystem plugin: overwrite parameter + better sentences/descriptions -cancel running -cleanup temp directories - -Convert dektop app to web app + node backend -Communication via rest api (for now) or websocket - -vérifier que le fichier custom main existe sinon ca lance une erreur -https://playwright.dev/docs/browsers#opt-in-to-new-headless-mode -Docs sur Electron -Docs sur les logs - -Support path `.local/share`: https://www.construct.net/en/make-games/manuals/construct-3/plugin-reference/filesystem -Personalize tmp folder diff --git a/TODO_MONOREPO.md b/TODO_MONOREPO.md new file mode 100644 index 00000000..02928d1b --- /dev/null +++ b/TODO_MONOREPO.md @@ -0,0 +1,127 @@ +# Pipelab Monorepo Migration - Roadmap & TODO + +The project has undergone a major architectural shift: splitting the monolithic Electron app into a standalone UI package (`@pipelab/ui`), a headless Node.js logic package (`@pipelab/core-node`), and a thin Electron shell (`@pipelab/app`). + +--- + +pipielab run, must return the output as json if "--json" otherwise just return 0 if all done or number for error codee +define a list of error codes + +## 🟢 Phase 1: Stabilization (CURRENT PRIORITY) + +Goal: Ensure the application remains 100% functional in both development and production environments. + +### 1. Dev Workflow Verification + +- [ ] Run `pnpm dev` from the root. +- [ ] Verify UI server starts on `http://localhost:5173`. +- [ ] Verify Electron window opens and successfully loads the UI. +- [ ] Verify "Core" connection: Create a new pipeline and save it (should go through CLI server via WebSocket). +- [ ] Verify "Shell" connection: Use "Choose a new path" in settings/project creation (should trigger Electron's native dialog via IPC). + +### 2. Production Build Verification + +- [ ] Run `pnpm turbo build`. +- [ ] Run `pnpm --filter @pipelab/app package` (Electron Forge). +- [ ] Verify that the `prePackage` hook correctly builds CLI binaries in `apps/cli/bin`. +- [ ] Verify that CLI binaries are copied into the Electron `out/` resource folder. +- [ ] Install/Run the packaged app and verify it can spawn the embedded CLI server. + +### 3. Critical Fixes + +- [ ] **Template Prefetch**: Implement prefetching of all necessary templates (`@pipelab/asset-electron`, etc.) before pipeline execution starts to fail fast with clear errors instead of failing mid-pipeline. +- [ ] **Type Safety**: Resolve the 8 remaining type errors in `apps/ui` reported during `turbo typecheck` (mostly related to PrimeVue components and optional refs). +- [ ] **External Deps**: Audit `apps/desktop/vite.base.config.mts`'s `external` list. Ensure no `@pipelab/*` packages are accidentally externalized in the bundle. +- [ ] **IPC Routing**: Ensure `dialog:showOpenDialog` and `dialog:showSaveDialog` are correctly routed to Electron even when the UI is loaded from a remote URL. + +--- + +## 🟡 Phase 2: Monorepo Excellence + +Goal: Improve build speed, enforce boundaries, and standardize the developer experience. + +### 1. Centralized Configuration + +- [ ] Create `packages/tsconfig` to store base `tsconfig.json` configurations. +- [ ] Create `packages/eslint-config` to share linting rules between UI and Node packages. +- [ ] Update all `package.json` files to use these shared configs. + +### 2. TS Project References + +- [ ] Enable `composite: true` in all packages. +- [ ] Add `references` arrays to `tsconfig.json` files to reflect the actual dependency graph. +- [ ] Switch to `tsc --build` for lightning-fast incremental typechecking. + +### 4. Quality Gates + +- [ ] Implement `syncpack` to keep dependency versions identical across all packages. +- [ ] Set up a GitHub Actions workflow to run `turbo build lint typecheck test` on every PR. +- [ ] Ensure `pnpm run typecheck` passes with 0 errors at the root level. + +--- + +## 🔵 Phase 3: Architecture & Communication + +Goal: Modernize internal communication between UI, Core-Node, and Shell. + +### 1. RPC Migration + +- [ ] Investigate replacing manual WebSocket/IPC message serialization with [tRPC](https://trpc.io/). +- [ ] Define shared tRPC routers in `@pipelab/shared`. +- [ ] Implement tRPC server in `@pipelab/core-node`. +- [ ] Update `@pipelab/ui` to consume tRPC hooks/composables instead of raw WebSockets. + +### 2. CLI Authentication & Backend-First Auth [IN PROGRESS] + +- [ ] Implement `pipelab login` command for interactive CLI authentication. +- [ ] Implement `pipelab login --token ` command for headless/CI environments. +- [ ] Implement database schema/API for generating and validating long-lived Personal Access Tokens (PATs). + +### 3. Benefits Management Centralization + +- [ ] Move benefits mapping (IDs to names) and entitlement logic to `@pipelab/core-node`. +- [ ] Implement `BenefitsManager` in the backend to calculate user entitlements based on Supabase subscriptions. +- [ ] Implement persistent dev overrides in the backend (stored in a JSON file instead of `localStorage`). +- [ ] Define IPC channels (`benefits:get`, `benefits:setOverride`) for UI interaction. +- [ ] Update UI (`useAuth` store) to react to `benefits:updated` WebSocket events from the backend. + +--- + +## 🟣 Phase 4: Feature Parity & Stability + +Goal: Reach feature parity with the legacy monolithic app and improve core functionality. + +### 1. Core Engine Improvements + +- [ ] **Autosave**: Implement automatic pipeline saving. +- [ ] **Undo/Redo**: Add history support for the node editor. +- [ ] **Error Handling**: Gracefully handle and display pipeline execution errors in dialogs. +- [ ] **Async Execution**: Investigate processing QuickJS logic in Web Workers to prevent UI blocking. +- [ ] **Memory Management**: Audit and resolve potential memory leaks in QuickJS (open/close actions). +- [ ] **Cleanup**: Implement automatic temporary directory cleanup (`tmp/`). +- [ ] **ASAR Handling**: Ensure `process.noAsar = true` is correctly set and handled for cross-app file copies. + +### 2. Node & Plugin Enhancements + +- [ ] **Image Optimization**: Integrate `sharp` for lossless compression in relevant nodes. +- [ ] **Filesystem Expansion**: Support `.local/share` paths and better overwrite parameters. +- [ ] **C3 Integration**: Fix C3 Preview issues and improve version selection for C3P exports. +- [ ] **Steam/Discord**: Verify Steam achievement integrations and investigate "Discord Activity" packaging. + +--- + +## ☁️ Phase 5: Cloud & Ecosystem + +Goal: Expand Pipelab beyond the local desktop environment. + +### 1. CI/CD Integration + +- [ ] **GitHub Actions**: Create `pipelab/setup` and `pipelab/run` actions for automated pipeline runs. +- [ ] **Interactive CLI**: Implement `pipelab login` for local CLI authentication. +- [ ] **Headless Execution**: Ensure pipelines can run in CI environments using environment variables/PATs for sensitive data. + +### 2. Cross-Platform & Distribution + +- [ ] **Cloud Sync**: Store configurations and pipelines in the user's online account for cross-machine access. +- [ ] **Alternative Packaging**: Investigate packaging as **Tauri** for smaller binary sizes or **Godot Export** for game-integrated pipelines. +- [ ] **Publishing**: Expand specialized upload nodes for **Poki**, **Itch**, etc. diff --git a/apps/cli/.npmrc b/apps/cli/.npmrc new file mode 100644 index 00000000..c483022c --- /dev/null +++ b/apps/cli/.npmrc @@ -0,0 +1 @@ +shamefully-hoist=true \ No newline at end of file diff --git a/apps/cli/README.md b/apps/cli/README.md new file mode 100644 index 00000000..cdf3922a --- /dev/null +++ b/apps/cli/README.md @@ -0,0 +1,23 @@ +# @pipelab/cli (The Engine) + +The standalone engine of Pipelab. It provides the core automation logic and serves as the backend for the visual editor. + +## ⚙️ Core Roles + +1. **WebSocket Server**: When started with `serve`, it acts as a backend for the `@pipelab/ui`. It handles graph execution, real-time logging, and state synchronization. +2. **Headless runner**: When started with `run `, it can execute a Pipelab pipeline (.json) directly from the terminal without any UI. +3. **Plugin Host**: Manages the execution context for all Pipelab plugins, including the QuickJS-based virtual environment. + +## 🛠️ Development + +### Setup + +The CLI requires Supabase variables to be "baked in" during the build process. Ensure your root `.env` is populated before building or running `pnpm dev`. + +### Commands + +```bash +pnpm dev # Start the server in development mode +pnpm build # Generate the production CJS bundle +pnpm pkg # Create a standalone executable in the /bin folder +``` diff --git a/apps/cli/package.json b/apps/cli/package.json new file mode 100644 index 00000000..62621e51 --- /dev/null +++ b/apps/cli/package.json @@ -0,0 +1,55 @@ +{ + "name": "@pipelab/cli", + "version": "1.9.9", + "private": true, + "description": "The command line interface for Pipelab", + "license": "FSL-1.1-MIT", + "author": "CynToolkit", + "repository": { + "type": "git", + "url": "https://github.com/CynToolkit/pipelab.git", + "directory": "apps/cli" + }, + "bin": { + "pipelab": "./src/index.ts", + "plab": "./src/index.ts" + }, + "type": "module", + "scripts": { + "build": "cross-env NODE_ENV=production tsdown", + "dev": "cross-env NODE_ENV=development tsx watch src/index.ts", + "serve": "cross-env NODE_ENV=development tsx watch src/index.ts serve", + "watch": "tsdown --watch", + "start": "tsx src/index.ts", + "test": "vitest run -c tests/e2e/vitest.config.mts", + "format": "oxfmt .", + "lint": "oxlint .", + "typecheck": "tsc --noEmit -p tsconfig.json --composite false" + }, + "dependencies": { + "@clack/prompts": "1.2.0", + "@pipelab/constants": "workspace:*", + "@pipelab/core-node": "workspace:*", + "@pipelab/shared": "workspace:*", + "commander": "^12.1.0", + "dotenv": "17.4.2", + "execa": "9.5.1", + "nanoid": "5.0.8", + "posthog-node": "5.30.6", + "serve-handler": "6.1.7" + }, + "devDependencies": { + "@pipelab/test-utils": "workspace:*", + "@pipelab/tsconfig": "workspace:*", + "@types/node": "24.12.2", + "cross-env": "7.0.3", + "tsdown": "0.21.2", + "typescript": "^5.0.0", + "vitest": "3.1.4" + }, + "test": { + "env": { + "NODE_ENV": "development" + } + } +} diff --git a/apps/cli/src/commands/history.ts b/apps/cli/src/commands/history.ts new file mode 100644 index 00000000..0dc73c59 --- /dev/null +++ b/apps/cli/src/commands/history.ts @@ -0,0 +1,43 @@ +import { PipelabContext, BuildHistoryStorage } from "@pipelab/core-node"; +import { getDefaultUserDataPath } from "../paths"; +import { BuildHistoryEntry } from "@pipelab/shared"; + +export async function historyCommand( + pipelineId: string | undefined, + options: { get?: string; userData?: string; limit?: number }, +) { + const userDataPath = options.userData || getDefaultUserDataPath(); + const context = new PipelabContext({ userDataPath }); + const storage = new BuildHistoryStorage(context); + + if (options.get) { + const entry = await storage.get(options.get, pipelineId); + if (entry) { + console.log(JSON.stringify(entry, null, 2)); + } else { + console.error(`Build history entry with ID "${options.get}" not found.`); + process.exit(1); + } + } else if (pipelineId) { + const entries = await storage.getByPipeline(pipelineId); + if (entries.length > 0) { + const limit = options.limit || 10; + const recentEntries = entries.slice(-limit); + + const formatted = recentEntries.map((e: BuildHistoryEntry) => ({ + ID: e.id, + Status: e.status, + "Start Time": new Date(e.startTime).toLocaleString(), + Duration: e.duration ? `${(e.duration / 1000).toFixed(2)}s` : "N/A", + })); + console.table(formatted); + } else { + console.log(`No history found for pipeline "${pipelineId}".`); + } + } else { + console.error( + "Please provide a pipeline ID to list its history, or use the --get option with a build ID.", + ); + process.exit(1); + } +} diff --git a/apps/cli/src/commands/maintenance.ts b/apps/cli/src/commands/maintenance.ts new file mode 100644 index 00000000..f62dd9de --- /dev/null +++ b/apps/cli/src/commands/maintenance.ts @@ -0,0 +1,61 @@ +import { PipelabContext, BuildHistoryStorage } from "@pipelab/core-node"; +import { getDefaultUserDataPath } from "../paths"; + +function formatBytes(bytes: number, decimals = 2) { + if (bytes === 0) return "0 Bytes"; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; +} + +export async function usageCommand(options: { userData?: string }) { + const userDataPath = options.userData || getDefaultUserDataPath(); + const context = new PipelabContext({ userDataPath }); + const storage = new BuildHistoryStorage(context); + + const info = await storage.getStorageInfo(); + + console.log("Build History Storage Usage:"); + console.table({ + "Total Entries": info.totalEntries, + "Pipelines with History": info.numberOfPipelines, + "Total Size": formatBytes(info.totalSize), + "Oldest Entry": info.oldestEntry ? new Date(info.oldestEntry).toLocaleString() : "N/A", + "Newest Entry": info.newestEntry ? new Date(info.newestEntry).toLocaleString() : "N/A", + "User Data Path": info.userDataPath, + "Cache Path": info.cachePath, + }); + + console.log("\nRetention Policy:"); + console.table({ + Enabled: info.retentionPolicy.enabled ? "Yes" : "No", + "Max Entries": info.retentionPolicy.maxEntries > 0 ? info.retentionPolicy.maxEntries : "Unlimited", + "Max Age (days)": info.retentionPolicy.maxAge > 0 ? info.retentionPolicy.maxAge : "Unlimited", + }); +} + +export async function purgeCommand( + pipelineId: string | undefined, + options: { force?: boolean; userData?: string }, +) { + if (!options.force) { + console.error( + "This is a destructive operation. Please use the --force flag to confirm you want to purge the history.", + ); + process.exit(1); + } + + const userDataPath = options.userData || getDefaultUserDataPath(); + const context = new PipelabContext({ userDataPath }); + const storage = new BuildHistoryStorage(context); + + if (pipelineId) { + await storage.clearByPipeline(pipelineId); + console.log(`Successfully purged history for pipeline: ${pipelineId}`); + } else { + await storage.clear(); + console.log("Successfully purged all build history."); + } +} diff --git a/apps/cli/src/commands/pipelines.ts b/apps/cli/src/commands/pipelines.ts new file mode 100644 index 00000000..01231336 --- /dev/null +++ b/apps/cli/src/commands/pipelines.ts @@ -0,0 +1,250 @@ +import { PipelabContext, setupConfigFile } from "@pipelab/core-node"; +import { FileRepo, SaveLocation } from "@pipelab/shared"; +import { readFile, unlink } from "node:fs/promises"; +import { getDefaultUserDataPath } from "../paths"; + +export async function listPipelinesCommand(options: { userData?: string }) { + const userDataPath = options.userData || getDefaultUserDataPath(); + const context = new PipelabContext({ userDataPath }); + + try { + const projectsConfig = await setupConfigFile("projects", { context }); + const repo = await projectsConfig.getConfig(); + + if (!repo.pipelines || repo.pipelines.length === 0) { + console.log("No pipelines found."); + return; + } + + console.log(`Found ${repo.pipelines.length} pipelines:\n`); + + for (const pipeline of repo.pipelines) { + let content: any; + try { + if (pipeline.type === "internal") { + const path = context.getConfigPath(`${pipeline.configName}.json`); + content = JSON.parse(await readFile(path, "utf-8")); + } else if (pipeline.type === "external") { + content = JSON.parse(await readFile(pipeline.path, "utf-8")); + } + } catch (e: any) { + console.log(`--------------------------------------------------------------------------------`); + console.log(`Error: Could not read pipeline ${pipeline.id}`); + if (pipeline.type === "external") { + console.log(` Expected Path: ${pipeline.path}`); + } + console.log(` Reason: ${e.message}`); + continue; + } + + const plugins = new Set(); + if (content?.canvas?.blocks) { + for (const block of content.canvas.blocks) { + if (block.origin?.pluginId) { + plugins.add(block.origin.pluginId); + } + } + } + + const name = content?.name || "Unnamed Pipeline"; + + console.log(`--------------------------------------------------------------------------------`); + console.log(`${name} (${pipeline.id})`); + if (pipeline.type === "external") { + console.log(` Path: ${pipeline.path}`); + } + console.log(` Plugins: ${Array.from(plugins).join(", ") || "None"}`); + } + } catch (error: any) { + console.error("Failed to list pipelines:", error.message); + } +} + +export async function showPipelineCommand( + idOrName: string, + options: { detailed?: boolean; userData?: string }, +) { + const userDataPath = options.userData || getDefaultUserDataPath(); + const context = new PipelabContext({ userDataPath }); + + try { + const projectsConfig = await setupConfigFile("projects", { context }); + const repo = await projectsConfig.getConfig(); + + const pipeline = repo.pipelines.find( + (p) => + p.id === idOrName || + (p.type === "internal" && p.configName === idOrName) || + (p.type === "external" && p.path.includes(idOrName)), + ); + + if (!pipeline) { + console.error(`Pipeline "${idOrName}" not found.`); + process.exit(1); + } + + let content: any; + try { + if (pipeline.type === "internal") { + const path = context.getConfigPath(`${pipeline.configName}.json`); + content = JSON.parse(await readFile(path, "utf-8")); + } else if (pipeline.type === "external") { + content = JSON.parse(await readFile(pipeline.path, "utf-8")); + } + } catch (e: any) { + console.error(`Error: Could not read pipeline file: ${e.message}`); + process.exit(1); + } + + console.log(`\nPipeline: ${content?.name || "Unnamed"}`); + console.log(`ID: ${pipeline.id}`); + console.log(`Type: ${pipeline.type}`); + if (pipeline.type === "external") { + console.log(`Path: ${pipeline.path}`); + } else if (pipeline.type === "internal") { + console.log(`Config: ${pipeline.configName}.json`); + } + + const plugins = new Set(); + const blockCount = content?.canvas?.blocks?.length || 0; + const triggerCount = content?.canvas?.triggers?.length || 0; + + if (content?.canvas?.blocks) { + for (const block of content.canvas.blocks) { + if (block.origin?.pluginId) { + plugins.add(block.origin.pluginId); + } + } + } + + console.log(`Plugins: ${Array.from(plugins).join(", ") || "None"}`); + console.log(`Blocks: ${blockCount}`); + console.log(`Triggers: ${triggerCount}`); + + if (options.detailed) { + console.log(`\n--- Detailed Information ---`); + console.log(`Version: ${content?.version || "Unknown"}`); + console.log(`Description: ${content?.description || "No description"}`); + + if (content?.variables && content.variables.length > 0) { + console.log(`\nVariables:`); + for (const v of content.variables) { + console.log(` - ${v.name} (${v.type}): ${v.value}`); + } + } else { + console.log(`\nVariables: None`); + } + + if (content?.canvas?.blocks && content.canvas.blocks.length > 0) { + console.log(`\nBlock Breakdown:`); + const counts: Record = {}; + for (const block of content.canvas.blocks) { + const pid = block.origin?.pluginId || "core"; + counts[pid] = (counts[pid] || 0) + 1; + } + for (const [pid, count] of Object.entries(counts)) { + console.log(` - ${pid}: ${count} blocks`); + } + } + + const triggers = content?.canvas?.triggers || []; + const blocks = content?.canvas?.blocks || []; + + const humanize = (origin: any, customName?: string) => { + if (customName) return customName; + if (!origin) return "Unknown"; + const parts = origin.nodeId + .split(":") + .filter((p: string) => !(p.startsWith("v") && !isNaN(parseInt(p.substring(1))))); + + const nodeName = parts + .map((p: string) => p.replace(/-/g, " ").replace(/_/g, " ")) + .map((p: string) => p.replace(/\b\w/g, (c: string) => c.toUpperCase())) + .join(": "); + + return `${nodeName} [${origin.pluginId}]`; + }; + + console.log(`\nWorkflow:`); + if (triggers.length > 0) { + for (const t of triggers) { + console.log(` (Trigger) ${humanize(t.origin, t.name)}`); + } + } else { + console.log(` (No triggers defined)`); + } + + if (blocks.length > 0) { + for (let i = 0; i < blocks.length; i++) { + const b = blocks[i]; + const status = b.disabled ? " [DISABLED]" : ""; + console.log(` │`); + console.log(` ▼`); + console.log(` (Action) ${humanize(b.origin, b.name)}${status}`); + if (b.description) { + console.log(` ${b.description}`); + } + } + } else { + console.log(` │`); + console.log(` ▼`); + console.log(` (No actions defined)`); + } + } + } catch (error: any) { + console.error("Failed to show pipeline:", error.message); + process.exit(1); + } +} + +export async function deletePipelineCommand( + id: string, + options: { force?: boolean; userData?: string }, +) { + if (!options.force) { + console.error( + "This is a destructive operation. Please use the --force flag to confirm you want to delete the pipeline.", + ); + process.exit(1); + } + + const userDataPath = options.userData || getDefaultUserDataPath(); + const context = new PipelabContext({ userDataPath }); + + try { + const projectsConfig = await setupConfigFile("projects", { context }); + const repo = await projectsConfig.getConfig(); + + const index = repo.pipelines.findIndex( + (p) => p.id === id || (p.type === "internal" && p.configName === id), + ); + + if (index === -1) { + console.error(`Pipeline "${id}" not found.`); + process.exit(1); + } + + const pipeline = repo.pipelines[index] as SaveLocation; + + // 1. Delete internal file if applicable + if (pipeline.type === "internal") { + try { + const path = context.getConfigPath(`${pipeline.configName}.json`); + await unlink(path); + console.log(`Deleted pipeline file: ${pipeline.configName}.json`); + } catch (e: any) { + if (e.code !== "ENOENT") { + console.warn(`Warning: Could not delete pipeline file: ${e.message}`); + } + } + } + + // 2. Remove from projects.json + repo.pipelines.splice(index, 1); + await projectsConfig.setConfig(repo); + console.log(`Successfully removed pipeline "${id}" from Pipelab.`); + } catch (error: any) { + console.error("Failed to delete pipeline:", error.message); + process.exit(1); + } +} diff --git a/apps/cli/src/commands/setup.ts b/apps/cli/src/commands/setup.ts new file mode 100644 index 00000000..a0e7f357 --- /dev/null +++ b/apps/cli/src/commands/setup.ts @@ -0,0 +1,108 @@ +import * as p from "@clack/prompts"; +import { setTimeout } from "node:timers/promises"; +import { PipelabContext } from "@pipelab/core-node"; +import { setupConfigFile } from "@pipelab/core-node"; +import { AppConfig } from "@pipelab/shared"; +import { getDefaultUserDataPath } from "../paths"; + +export async function setupCommand(options: { userData?: string }) { + p.intro(`Welcome to Pipelab CLI Setup Wizard`); + + const userDataPath = options.userData || getDefaultUserDataPath(); + const context = new PipelabContext({ userDataPath }); + + // 1. Authentication + const authType = await p.select({ + message: "How would you like to authenticate?", + options: [ + { value: "token", label: "Access Token (Recommended)" }, + { value: "credentials", label: "Email & Password" }, + { value: "skip", label: "Skip for now" }, + ], + }); + + if (p.isCancel(authType)) { + p.cancel("Setup cancelled."); + process.exit(0); + } + + if (authType === "token") { + const token = await p.password({ + message: "Enter your Pipelab Access Token", + validate: (value) => { + if (!value) return "Token is required"; + if (value.length < 10) return "Token is too short"; + }, + }); + if (p.isCancel(token)) return; + + const s = p.spinner(); + s.start("Authenticating with Pipelab..."); + await setTimeout(1500); // Fake delay + s.stop("Authenticated successfully!"); + } + + // 2. Configuration + const settings = await setupConfigFile("settings", { context }); + const config = await settings.getConfig(); + + const configChanges = await p.group({ + theme: () => + p.select({ + message: "Select your preferred theme:", + initialValue: config.theme, + options: [ + { value: "dark", label: "Dark" }, + { value: "light", label: "Light" }, + ], + }), + locale: () => + p.select({ + message: "Select your language:", + initialValue: config.locale, + options: [ + { value: "en-US", label: "English" }, + { value: "fr-FR", label: "French" }, + { value: "pt-BR", label: "Portuguese" }, + ], + }), + }); + + if (p.isCancel(configChanges)) { + p.cancel("Setup aborted."); + process.exit(0); + } + + // Save config (Fake save for now to avoid side effects in this demo if needed, but let's do it real) + await settings.setConfig({ + ...config, + theme: configChanges.theme, + locale: configChanges.locale, + }); + + // 3. Integrations + const integrations = await p.multiselect({ + message: "Which integrations would you like to enable?", + options: [ + { value: "path", label: "Add 'pipelab' to your PATH", hint: "recommended" }, + { value: "completion", label: "Enable shell autocompletion" }, + { value: "telemetry", label: "Anonymous telemetry", hint: "helps us improve" }, + ], + }); + + if (p.isCancel(integrations)) return; + + if (integrations.length > 0) { + const s = p.spinner(); + s.start("Setting up integrations..."); + await setTimeout(2000); // Fake delay + s.stop("Integrations configured!"); + } + + p.note( + `You're all set! You can now use Pipelab CLI to manage your automation.\n\nType 'pipelab --help' to get started.`, + "Setup Complete" + ); + + p.outro("Happy automating! 🚀"); +} diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts new file mode 100644 index 00000000..cb255789 --- /dev/null +++ b/apps/cli/src/index.ts @@ -0,0 +1,234 @@ +#!/usr/bin/env node +import { isDev, runPipelineCommand, serveCommand } from "@pipelab/core-node"; +import { historyCommand } from "./commands/history"; +import { usageCommand, purgeCommand } from "./commands/maintenance"; +import { + listPipelinesCommand, + deletePipelineCommand, + showPipelineCommand, +} from "./commands/pipelines"; +import { setupCommand } from "./commands/setup"; +import { readFileSync, existsSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { isSupabaseAvailable } from "@pipelab/shared"; +import { config } from "dotenv"; +import { PostHog } from "posthog-node"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Only load .env in development as values are bundled in production by tsdown +if (isDev) { + const envPath = join(__dirname, "../../../.env"); + if (existsSync(envPath)) { + config({ path: envPath }); + } +} + +const isProduction = !isDev && process.env.TEST !== "true"; + +let posthog: PostHog | undefined; +if (isProduction && process.env.POSTHOG_API_KEY) { + posthog = new PostHog(process.env.POSTHOG_API_KEY, { + host: "https://eu.i.posthog.com", + }); +} +import { Command } from "commander"; +import { getDefaultUserDataPath } from "./paths"; + +// Resolve version from package.json with fallbacks for production +let version = "0.0.0"; +try { + const packageJsonPath = existsSync(join(__dirname, "package.json")) + ? join(__dirname, "package.json") + : join(__dirname, "..", "package.json"); + + if (existsSync(packageJsonPath)) { + const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); + version = packageJson.version; + } +} catch (e) { + console.warn("[CLI] Could not resolve version from package.json, using fallback."); +} + +const program = new Command(); + +program + .name("pipelab") + .description("The command line interface for Pipelab") + .version(version); + +if (!isSupabaseAvailable()) { + console.warn("\x1b[33m%s\x1b[0m", "Warning: Authentication is currently disabled (Cloud services not configured)."); +} + +program + .command("serve") + .description("Start the standalone WebSocket server") + .option("-p, --port ", "Port to listen on", "33753") + .option("--user-data ", "Custom user data path") + .action(async (options) => { + try { + options.userData = options.userData || getDefaultUserDataPath(); + await serveCommand(options, version, __dirname); + } catch (e) { + console.error(e); + process.exit(1); + } + }); + +program + .command("run ") + .description("Run a pipeline from a JSON file") + .option("--user-data ", "Custom user data path") + .option("--variables ", "JSON string of variables to override") + .option("-o, --output ", "Path to write the result file") + .option("--cloud", "Run the pipeline in a cloud environment") + .action(async (file, options) => { + try { + options.userData = options.userData || getDefaultUserDataPath(); + await runPipelineCommand(file, options, version); + process.exit(0); + } catch (e) { + console.error("Pipeline execution failed:", e); + process.exit(1); + } + }); + +program + .command("history [pipeline-id]") + .description("View build history for a pipeline") + .option("--get ", "Get a specific build entry by ID") + .option("--user-data ", "Custom user data path") + .option("--limit ", "Limit the number of history entries to show", "10") + .action(async (pipelineId, options) => { + try { + await historyCommand(pipelineId, options); + } catch (e) { + console.error(e); + process.exit(1); + } + }); + +program + .command("usage") + .description("Show build history storage usage") + .option("--user-data ", "Custom user data path") + .action(async (options) => { + try { + await usageCommand(options); + } catch (e) { + console.error(e); + process.exit(1); + } + }); + +program + .command("purge [pipeline-id]") + .description("Purge build history") + .option("--user-data ", "Custom user data path") + .option("-f, --force", "Force the destructive operation") + .action(async (pipelineId, options) => { + try { + await purgeCommand(pipelineId, options); + } catch (e) { + console.error(e); + process.exit(1); + } + }); + +program + .command("setup") + .description("Run the interactive setup wizard") + .option("--user-data ", "Custom user data path") + .action(async (options) => { + try { + await setupCommand(options); + } catch (e) { + console.error(e); + process.exit(1); + } + }); + +const pipelines = program + .command("pipelines") + .alias("pipeline") + .description("Manage pipelines"); + +pipelines + .command("ls") + .alias("list") + .description("List all pipelines") + .option("--user-data ", "Custom user data path") + .action(async (options) => { + try { + await listPipelinesCommand(options); + } catch (e) { + console.error(e); + process.exit(1); + } + }); + +pipelines + .command("show ") + .alias("shw") + .description("Display basic information about a pipeline") + .option("--user-data ", "Custom user data path") + .action(async (idOrName, options) => { + try { + await showPipelineCommand(idOrName, { ...options, detailed: false }); + } catch (e) { + console.error(e); + process.exit(1); + } + }); + +pipelines + .command("detail ") + .alias("details") + .description("Display detailed information about a pipeline") + .option("--user-data ", "Custom user data path") + .action(async (idOrName, options) => { + try { + await showPipelineCommand(idOrName, { ...options, detailed: true }); + } catch (e) { + console.error(e); + process.exit(1); + } + }); + +pipelines + .command("rm ") + .alias("remove") + .alias("delete") + .description("Delete a pipeline") + .option("--user-data ", "Custom user data path") + .option("-f, --force", "Force the destructive operation") + .action(async (id, options) => { + try { + await deletePipelineCommand(id, options); + } catch (e) { + console.error(e); + process.exit(1); + } + }); + +program.hook("postAction", (thisCommand) => { + if (posthog) { + posthog.capture({ + distinctId: "cli-user", + event: "command_executed", + properties: { + command: thisCommand.name(), + args: thisCommand.args, + }, + }); + } +}); + +program.parse(process.argv); + +if (!process.argv.slice(2).length) { + program.outputHelp(); +} diff --git a/apps/cli/src/paths.ts b/apps/cli/src/paths.ts new file mode 100644 index 00000000..29f93fa9 --- /dev/null +++ b/apps/cli/src/paths.ts @@ -0,0 +1 @@ +export { getDefaultUserDataPath } from "@pipelab/core-node"; diff --git a/apps/cli/tests/e2e/tests/history.spec.ts b/apps/cli/tests/e2e/tests/history.spec.ts new file mode 100644 index 00000000..eee75ae2 --- /dev/null +++ b/apps/cli/tests/e2e/tests/history.spec.ts @@ -0,0 +1,57 @@ +import { expect, test, describe, afterEach } from "vitest"; +import { writeFile, access, readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { createSandbox, runCLI } from "@pipelab/test-utils"; + +describe("End-to-End: Build History", () => { + let sandbox: Awaited>; + + afterEach(async () => { + if (sandbox) { + await sandbox.remove(); + } + }); + + test( + "should generate and save a build history when PIPELAB_DISABLE_HISTORY is false", + async () => { + sandbox = await createSandbox("history-e2e"); + + const pipelineId = "test-history-pipeline"; + const pipeline = { + graph: [], // Empty graph will execute successfully + projectPath: sandbox.path, + projectName: "History E2E Test", + pipelineId, + }; + + const pipelineFile = join(sandbox.path, "pipeline.json"); + await writeFile(pipelineFile, JSON.stringify(pipeline, null, 2)); + + // Run the CLI using the helper but explicitly set disable history to false + await runCLI(["run", pipelineFile, "--user-data", sandbox.paths.userData], { + env: { + PIPELAB_DISABLE_HISTORY: "false", + }, + }); + + // The build history should be generated in the user-data folder + // Based on BuildHistoryStorage.getPipelinePath, the filename should be pipeline-.json + const historyFile = join(sandbox.paths.userData, "build-history", `pipeline-${pipelineId}.json`); + + // Verification: file must exist + await expect(access(historyFile)).resolves.not.toThrow(); + + const historyContent = await readFile(historyFile, "utf-8"); + const history = JSON.parse(historyContent); + + expect(Array.isArray(history)).toBe(true); + expect(history.length).toBeGreaterThan(0); + + const lastEntry = history[history.length - 1]; + expect(lastEntry.pipelineId).toBe(pipelineId); + expect(lastEntry.status).toBe("completed"); + }, + 5 * 60 * 1000, + ); +}); diff --git a/apps/cli/tests/e2e/tests/integration.spec.ts b/apps/cli/tests/e2e/tests/integration.spec.ts new file mode 100644 index 00000000..72a38150 --- /dev/null +++ b/apps/cli/tests/e2e/tests/integration.spec.ts @@ -0,0 +1,73 @@ +import { expect, test, describe, afterEach } from "vitest"; +import { writeFile, access, readFile, mkdir } from "node:fs/promises"; +import { join } from "node:path"; +import { createSandbox, runCLI } from "@pipelab/test-utils"; + +describe("End-to-End: Multi-Plugin Integration Test", () => { + let sandbox: Awaited>; + + afterEach(async () => { + if (sandbox) { + await sandbox.remove(); + } + }); + + test( + "should run a pipeline with filesystem nodes", + async () => { + sandbox = await createSandbox("integration-e2e"); + const { paths } = sandbox; + + const projectSourcePath = join(sandbox.path, "my-app-source"); + const projectStagingPath = join(sandbox.path, "my-app-staging"); + await mkdir(projectSourcePath, { recursive: true }); + + // Create initial source files + await writeFile( + join(projectSourcePath, "package.json"), + JSON.stringify({ name: "my-app", version: "1.0.0", main: "index.js" }), + ); + await writeFile(join(projectSourcePath, "index.js"), "console.log('hello integration test')"); + + const pipeline = { + graph: [ + { + uid: "copy-to-staging", + name: "Copy to Staging", + type: "action", + origin: { pluginId: "filesystem", nodeId: "fs:copy" }, + params: { + from: { value: JSON.stringify(projectSourcePath) }, + to: { value: JSON.stringify(projectStagingPath) }, + recursive: { value: JSON.stringify(true) }, + overwrite: { value: JSON.stringify(true) }, + cleanup: { value: JSON.stringify(false) }, + }, + }, + ], + projectPath: sandbox.path, + projectName: "Integration E2E Test", + }; + + const pipelineFile = join(sandbox.path, "pipeline.json"); + const resultFile = join(sandbox.path, "result.json"); + await writeFile(pipelineFile, JSON.stringify(pipeline, null, 2)); + + // Run the CLI using the helper + await runCLI(["run", pipelineFile, "--output", resultFile]); + + const resultJson = JSON.parse(await readFile(resultFile, "utf-8")); + + // Verification + expect(resultJson.steps["copy-to-staging"]).toBeDefined(); + + const outputs = resultJson.steps["copy-to-staging"].outputs; + expect(outputs).toBeDefined(); + expect(outputs.output).toEqual(projectStagingPath); + + // Verify output exists + await expect(access(outputs.output as string)).resolves.not.toThrow(); + }, + 5 * 60 * 1000, + ); +}); diff --git a/apps/cli/tests/e2e/vitest.config.mts b/apps/cli/tests/e2e/vitest.config.mts new file mode 100644 index 00000000..56b7ba78 --- /dev/null +++ b/apps/cli/tests/e2e/vitest.config.mts @@ -0,0 +1,20 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + fileParallelism: false, + testTimeout: 60000, + hookTimeout: 60000, + maxWorkers: 1, + minWorkers: 1, + poolOptions: { + forks: { + singleFork: true, + }, + }, + include: ["**/*.spec.ts"], + root: "tests/e2e", + environment: "node", + env: { NODE_ENV: "development", PIPELAB_DISABLE_HISTORY: "true" }, + }, +}); diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json new file mode 100644 index 00000000..4f5189ad --- /dev/null +++ b/apps/cli/tsconfig.json @@ -0,0 +1,33 @@ +{ + "extends": "../../packages/tsconfig/node.json", + "compilerOptions": { + "composite": false, + "moduleResolution": "bundler", + "outDir": "dist", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "baseUrl": ".", + "paths": { + "@pipelab/shared": ["../../packages/shared/src/index.ts"], + "@pipelab/constants": ["../../packages/constants/src/index.ts"], + "@pipelab/core-node": ["../../packages/core-node/src/index.ts"], + "@pipelab/migration": ["../../packages/migration/src/index.ts"], + "@pipelab/*": ["../../packages/shared/src/libs/*"], + "@core-node/*": ["../../packages/core-node/src/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"], + "references": [ + { + "path": "../../packages/constants" + }, + { + "path": "../../packages/shared" + }, + { + "path": "../../packages/core-node" + } + ] +} diff --git a/apps/cli/tsdown.config.ts b/apps/cli/tsdown.config.ts new file mode 100644 index 00000000..483fb909 --- /dev/null +++ b/apps/cli/tsdown.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from "tsdown"; +import { resolve } from "path"; +import { existsSync } from "node:fs"; +import { config } from "dotenv"; + +const envFile = resolve(import.meta.dirname, "../../.env"); + +config({ path: envFile }); + +export default defineConfig({ + entry: ["src/index.ts"], + shims: true, + env: { + NODE_ENV: process.env.NODE_ENV || "production", + SUPABASE_URL: process.env.SUPABASE_URL || "", + SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY || "", + POSTHOG_API_KEY: process.env.POSTHOG_API_KEY || "", + }, + envFile: existsSync(envFile) ? envFile : undefined, + envPrefix: ["SUPABASE_", "POSTHOG_", "NODE_ENV"], + deps: { + alwaysBundle: [/.*/], + }, +}); diff --git a/.editorconfig b/apps/desktop/.editorconfig similarity index 100% rename from .editorconfig rename to apps/desktop/.editorconfig diff --git a/.hintrc b/apps/desktop/.hintrc similarity index 100% rename from .hintrc rename to apps/desktop/.hintrc diff --git a/.mise.toml b/apps/desktop/.mise.toml similarity index 77% rename from .mise.toml rename to apps/desktop/.mise.toml index 394be3e8..8754dd96 100644 --- a/.mise.toml +++ b/apps/desktop/.mise.toml @@ -1,5 +1,4 @@ [tools] -node = "22" [env] _.file = '.env' @@ -16,5 +15,5 @@ run = "pnpm package" depends = ["install"] [tasks.test-e2e-local] -run = "pnpm test:e2e:raw" -depends = ["package"] \ No newline at end of file +run = "pnpm test:e2e" +depends = ["package"] diff --git a/apps/desktop/.npmrc b/apps/desktop/.npmrc new file mode 100644 index 00000000..d67f3748 --- /dev/null +++ b/apps/desktop/.npmrc @@ -0,0 +1 @@ +node-linker=hoisted diff --git a/FAQ.md b/apps/desktop/FAQ.md similarity index 100% rename from FAQ.md rename to apps/desktop/FAQ.md diff --git a/apps/desktop/README.md b/apps/desktop/README.md new file mode 100644 index 00000000..be87c3fb --- /dev/null +++ b/apps/desktop/README.md @@ -0,0 +1,30 @@ +# @pipelab/app (Desktop) + +The Desktop version of Pipelab, built with Electron. + +## 🔄 Lifecycle & Orchestration + +The Desktop application serves as the native shell for the Pipelab ecosystem. Its responsibilities include: + +1. **Process Management**: In production, the main process is responsible for spawning and managing the `@pipelab/cli` server as a "sidecar" process. +2. **Native Bridge**: Provides IPC handlers for native OS features (file system dialogs, system notifications, auto-updates) that are not available to the web renderer. +3. **UI Container**: Loads and displays the `@pipelab/ui` interface. + +### Communication Flow + +- **Main to UI**: Via standard Electron IPC. +- **UI to CLI**: Via WebSockets (even when running inside the desktop app). + +## 🛠️ Development + +### Setup + +Ensure you have the root-level `.env` file configured. + +### Commands + +```bash +pnpm dev # Starts the Electron app in development mode +pnpm package # Packages the application for the current platform (production) +pnpm make # Creates installers (dmg, exe, deb, zip) +``` diff --git a/assets/build/entitlements.mac.plist b/apps/desktop/assets/build/entitlements.mac.plist similarity index 100% rename from assets/build/entitlements.mac.plist rename to apps/desktop/assets/build/entitlements.mac.plist diff --git a/assets/build/icon.icns b/apps/desktop/assets/build/icon.icns similarity index 100% rename from assets/build/icon.icns rename to apps/desktop/assets/build/icon.icns diff --git a/assets/build/icon.ico b/apps/desktop/assets/build/icon.ico similarity index 100% rename from assets/build/icon.ico rename to apps/desktop/assets/build/icon.ico diff --git a/assets/build/icon.png b/apps/desktop/assets/build/icon.png similarity index 100% rename from assets/build/icon.png rename to apps/desktop/assets/build/icon.png diff --git a/apps/desktop/assets/build/notarize.cjs b/apps/desktop/assets/build/notarize.cjs new file mode 100644 index 00000000..0e96c5b9 --- /dev/null +++ b/apps/desktop/assets/build/notarize.cjs @@ -0,0 +1,36 @@ +const { notarize } = require("@electron/notarize"); + +module.exports = async (context) => { + if (process.platform !== "darwin") return; + + console.log("aftersign hook triggered, start to notarize app."); + + if (!process.env.CI) { + console.log(`skipping notarizing, not in CI.`); + return; + } + + if (!("APPLE_ID" in process.env && "APPLE_ID_PASS" in process.env)) { + console.warn("skipping notarizing, APPLE_ID and APPLE_ID_PASS env variables must be set."); + return; + } + + const appId = "com.electron.app"; + + const { appOutDir } = context; + + const appName = context.packager.appInfo.productFilename; + + try { + await notarize({ + appBundleId: appId, + appPath: `${appOutDir}/${appName}.app`, + appleId: process.env.APPLE_ID, + appleIdPassword: process.env.APPLEIDPASS, + }); + } catch (error) { + console.error(error); + } + + console.log(`done notarizing ${appId}.`); +}; diff --git a/crowdin.yml b/apps/desktop/crowdin.yml similarity index 100% rename from crowdin.yml rename to apps/desktop/crowdin.yml diff --git a/apps/desktop/forge.config.ts b/apps/desktop/forge.config.ts new file mode 100644 index 00000000..8af855b7 --- /dev/null +++ b/apps/desktop/forge.config.ts @@ -0,0 +1,123 @@ +import type { ForgeConfig } from "@electron-forge/shared-types"; +import { MakerSquirrel } from "@electron-forge/maker-squirrel"; +import { MakerZIP } from "@electron-forge/maker-zip"; +import { VitePlugin } from "@electron-forge/plugin-vite"; +import { FusesPlugin } from "@electron-forge/plugin-fuses"; +import { MakerDMG } from "@electron-forge/maker-dmg"; +import { FuseV1Options, FuseVersion } from "@electron/fuses"; +import { name } from "@pipelab/constants"; +import fs from "node:fs/promises"; +import path from "path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +console.log(`[Forge Config] Host arch: ${process.arch}`); +console.log(`[Forge Config] Target arch (env.TARGET_ARCH): ${process.env.TARGET_ARCH}`); +console.log(`[Forge Config] npm_config_arch: ${process.env.npm_config_arch}`); + +const getStandardOs = (p: string) => ({ win32: "win", darwin: "macos", linux: "linux" })[p] || p; + +/** + * Renames Forge-generated installers in /out/make. + */ +async function renameInstallers(platform: string, arch: string) { + const pkgPath = path.join(__dirname, "package.json"); + const { version } = JSON.parse(await fs.readFile(pkgPath, "utf-8")); + const os = getStandardOs(platform); + const makeDir = path.join(__dirname, "out/make"); + + const exists = await fs + .access(makeDir) + .then(() => true) + .catch(() => false); + if (!exists) return; + + const files = await fs.readdir(makeDir, { recursive: true }); + + for (const relFile of files) { + const file = path.join(makeDir, relFile); + if ((await fs.stat(file)).isDirectory()) continue; + + const ext = path.extname(file); + const basename = path.basename(file); + if ([".zip", ".dmg", ".exe", ".deb", ".rpm"].includes(ext) && !basename.includes("-v")) { + const newName = `pipelab-desktop-v${version}-${os}-${arch}${ext}`; + await fs.rename(file, path.join(path.dirname(file), newName)); + } + } +} + +const config: ForgeConfig = { + packagerConfig: { + // @ts-expect-error - Force architecture as Forge CLI sometimes ignores --arch flag in CI + arch: process.env.TARGET_ARCH || process.env.npm_config_arch || process.arch, + prune: false, + appBundleId: "app.pipelab.desktop", + asar: true, + extraResource: [], + name, + icon: path.join(__dirname, "assets/build/icon"), + extendInfo: { + NSAppleEventsUsageDescription: "This app need to run commands through Terminal.", + }, + osxNotarize: { + appleId: process.env.APPLE_ID || "", + appleIdPassword: process.env.APPLE_ID_PASSWORD || "", + teamId: process.env.APPLE_TEAM_ID || "", + }, + osxSign: { + identity: `Developer ID Application: Quentin Goinaud (${process.env.APPLE_TEAM_ID})`, + hardenedRuntime: true, + entitlements: path.join(__dirname, "assets/build/entitlements.mac.plist"), + "entitlements-inherit": path.join(__dirname, "assets/build/entitlements.mac.plist"), + strictVerify: false, + } as any, + }, + makers: [ + new MakerSquirrel({ name, setupIcon: path.join(__dirname, "assets/build/icon.ico") }), + new MakerZIP(undefined, ["linux", "win32"]), + new MakerDMG(), + ], + publishers: [ + { + name: "@electron-forge/publisher-github", + config: { + repository: { owner: "CynToolkit", name: "pipelab" }, + prerelease: process.env.PRERELEASE === "true", + draft: false, + generateReleaseNotes: true, + }, + }, + ], + hooks: { + postMake: async (_, makeResults) => { + for (const target of new Set(makeResults.map((r) => `${r.platform}:${r.arch}`))) { + const [p, a] = target.split(":"); + await renameInstallers(p, a); + } + return makeResults; + }, + }, + plugins: [ + new VitePlugin({ + build: [ + { entry: "src/main.ts", config: "vite.main.config.mts" }, + { entry: "src/preload.ts", config: "vite.preload.config.mts" }, + ], + renderer: [{ name: "main_window", config: "vite.renderer.config.mts" }], + }), + new FusesPlugin({ + version: FuseVersion.V1, + [FuseV1Options.RunAsNode]: true, + [FuseV1Options.EnableCookieEncryption]: true, + [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: true, + [FuseV1Options.EnableNodeCliInspectArguments]: false, + [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: false, + [FuseV1Options.OnlyLoadAppFromAsar]: false, + }), + ], +}; + +export default config; diff --git a/apps/desktop/forge.env.d.ts b/apps/desktop/forge.env.d.ts new file mode 100644 index 00000000..ff48335b --- /dev/null +++ b/apps/desktop/forge.env.d.ts @@ -0,0 +1,44 @@ +export {}; // Make this a module + +declare global { + // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Vite + // plugin that tells the Electron app where to look for the Vite-bundled app code (depending on + // whether you're running in development or production). + const MAIN_WINDOW_VITE_DEV_SERVER_URL: string; + const MAIN_WINDOW_VITE_NAME: string; + + type ElectronAPI = import("@electron-toolkit/preload").ElectronAPI; + + namespace NodeJS { + interface Process { + // Used for hot reload after preload scripts. + viteDevServers: Record; + } + } + + interface Window { + version: string; + electron: ElectronAPI; + api: any; + isPackaged: boolean; + isTest: boolean; + isCI: boolean; + } + + type VitePluginConfig = ConstructorParameters< + typeof import("@electron-forge/plugin-vite").VitePlugin + >[0]; + + interface VitePluginRuntimeKeys { + VITE_DEV_SERVER_URL: `${string}_VITE_DEV_SERVER_URL`; + VITE_NAME: `${string}_VITE_NAME`; + } +} + +declare module "vite" { + interface ConfigEnv { + root: string; + forgeConfig: VitePluginConfig; + forgeConfigSelf: VitePluginConfig[K][number]; + } +} diff --git a/apps/desktop/index.html b/apps/desktop/index.html new file mode 100644 index 00000000..9666b78f --- /dev/null +++ b/apps/desktop/index.html @@ -0,0 +1,53 @@ + + + + + Pipelab + + + + + + + + + +
+ + + diff --git a/apps/desktop/package.json b/apps/desktop/package.json new file mode 100644 index 00000000..c9594855 --- /dev/null +++ b/apps/desktop/package.json @@ -0,0 +1,57 @@ +{ + "name": "@pipelab/app", + "version": "1.9.9", + "private": true, + "description": "-", + "homepage": "https://pipelab.app", + "license": "FSL-1.1-MIT", + "author": "Armaldio", + "repository": { + "type": "git", + "url": "https://github.com/CynToolkit/pipelab.git", + "directory": "apps/desktop" + }, + "main": ".vite/build/main.js", + "scripts": { + "format": "oxfmt .", + "lint": "oxlint .", + "typecheck": "tsc --noEmit -p tsconfig.json --composite false", + "start": "electron-forge start", + "start:args": "electron-forge start -- --project ./tests/e2e/fixtures/folder-to-electron.json --action run", + "dev": "cross-env NODE_OPTIONS=--enable-source-maps electron-forge start", + "package": "cross-env DEBUG='electron-osx-sign*,electron-forge:*' electron-forge package", + "make": "electron-forge make", + "publish": "electron-forge publish", + "make-dev-prerelease": "pnpm version prerelease --preid=dev", + "migrate-file": "tsx scripts/migrate-file.mts" + }, + "dependencies": { + "electron-squirrel-startup": "1.0.1", + "posthog-node": "5.30.6" + }, + "devDependencies": { + "@electron-forge/cli": "7.11.1", + "@electron-forge/maker-dmg": "7.11.1", + "@electron-forge/maker-squirrel": "7.11.1", + "@electron-forge/maker-zip": "7.11.1", + "@electron-forge/plugin-fuses": "7.11.1", + "@electron-forge/plugin-vite": "7.11.1", + "@electron-forge/publisher-github": "7.11.1", + "@electron-toolkit/preload": "3.0.2", + "@electron-toolkit/utils": "4.0.0", + "@electron/fuses": "1.8.0", + "@electron/notarize": "2.5.0", + "@pipelab/constants": "workspace:*", + "@pipelab/core-node": "workspace:*", + "@pipelab/shared": "workspace:*", + "@pipelab/tsconfig": "workspace:*", + "@types/electron-squirrel-startup": "1.0.2", + "@types/node": "24.12.2", + "cross-env": "7.0.3", + "electron": "32.1.2", + "tsx": "4.19.1", + "typescript": "5.8.3", + "vite": "6.3.3", + "vite-tsconfig-paths": "5.1.4" + } +} diff --git a/apps/desktop/scripts/headless-electron-tester.mts b/apps/desktop/scripts/headless-electron-tester.mts new file mode 100644 index 00000000..b72b53a4 --- /dev/null +++ b/apps/desktop/scripts/headless-electron-tester.mts @@ -0,0 +1,10 @@ +import { execa } from "execa"; +import { join } from "path"; +import { cwd, stdin } from "process"; + +await execa({ + cwd: join(import.meta.dirname, ".."), + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", +})`./assets/electron/template/app/node_modules/.bin/electron-forge start -- --headless --port=3000`; diff --git a/apps/desktop/scripts/migrate-file.mts b/apps/desktop/scripts/migrate-file.mts new file mode 100644 index 00000000..befdd9be --- /dev/null +++ b/apps/desktop/scripts/migrate-file.mts @@ -0,0 +1,40 @@ +import { savedFileMigrator } from "@pipelab/shared"; +import * as fs from "fs/promises"; +import { join } from "path"; +import { tmpdir } from "os"; +import { nanoid } from "nanoid"; +import { execa } from "execa"; + +const args = process.argv.slice(2); +if (args.length !== 1) { + console.error("Usage: node script.ts "); + process.exit(1); +} + +const filePath = args[0]; + +async function migrateFile() { + try { + // const tmpFile = join(tmpdir(), nanoid() + '.json') + + const data = await fs.readFile(filePath, "utf8"); + const json = JSON.parse(data); + const migratedData = await savedFileMigrator.migrate(json); + const str = JSON.stringify(migratedData, null, 2); + console.log("data", data); + console.log("str", str); + await fs.writeFile(filePath, str, "utf8"); + console.log("File successfully migrated to the latest version of the model."); + + // await execa('delta', [filePath, tmpFile], { + // env: { + // DELTA_FEATURES: '+side-by-side' + // } + // }) + } catch (error) { + console.error(`Error migrating the file: ${error}`); + process.exit(1); + } +} + +await migrateFile(); diff --git a/apps/desktop/scripts/test-update-server.mts b/apps/desktop/scripts/test-update-server.mts new file mode 100644 index 00000000..42e5e4e5 --- /dev/null +++ b/apps/desktop/scripts/test-update-server.mts @@ -0,0 +1,101 @@ +import http from "node:http"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const PORT = 8080; + +// Path to the 'make' directory where forge puts build artifacts +const MAKE_DIR = path.resolve(__dirname, "..", "out", "make"); + +const server = http.createServer((req, res) => { + console.log(`[Mock Server] Requested: ${req.url}`); + + const url = new URL(req.url || "/", `http://localhost:${PORT}`); + const pathname = url.pathname; + + // 1. Handle Squirrel.Windows RELEASES file + if (pathname === "/RELEASES" || pathname.endsWith("/RELEASES")) { + // Try to find the RELEASES file in the squirrel directory + const squirrelDir = path.join(MAKE_DIR, "squirrel.windows", "x64"); + const releasesPath = path.join(squirrelDir, "RELEASES"); + + if (fs.existsSync(releasesPath)) { + res.writeHead(200, { "Content-Type": "text/plain" }); + fs.createReadStream(releasesPath).pipe(res); + return; + } + } + + // 2. Handle macOS Update JSON (Native autoUpdater protocol) + if (pathname === "/update/macos") { + // This is a simplified mock for macOS native protocol + // It expects a JSON with a 'url' property if an update is available + // or a 204 No Content if no update. + const version = process.env.APP_VERSION || "1.0.1"; + const dmgPath = path.join(MAKE_DIR, "zip", "darwin", "x64", `pipelab-v${version}.zip`); + + if (fs.existsSync(dmgPath)) { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + url: `http://localhost:${PORT}/download/macos/pipelab-v${version}.zip`, + }), + ); + } else { + res.writeHead(204); + res.end(); + } + return; + } + + // 3. Serve static files (.nupkg, .zip, .exe, etc.) + // We search recursively in the MAKE_DIR for the file basename + const basename = path.basename(pathname); + const findFile = (dir: string): string | null => { + const files = fs.readdirSync(dir); + for (const file of files) { + const fullPath = path.join(dir, file); + if (fs.statSync(fullPath).isDirectory()) { + const found = findFile(fullPath); + if (found) return found; + } else if (file === basename) { + return fullPath; + } + } + return null; + }; + + try { + const filePath = findFile(MAKE_DIR); + if (filePath) { + const ext = path.extname(filePath); + const contentType = + { + ".nupkg": "application/zip", + ".zip": "application/zip", + ".exe": "application/x-msdownload", + ".dmg": "application/x-apple-diskimage", + }[ext] || "application/octet-stream"; + + res.writeHead(200, { "Content-Type": contentType }); + fs.createReadStream(filePath).pipe(res); + return; + } + } catch (e) { + console.error(`[Mock Server] Error searching for file: ${e}`); + } + + res.writeHead(404); + res.end("Not Found"); +}); + +server.listen(PORT, () => { + console.log(`\n🚀 Mock Update Server running at http://localhost:${PORT}`); + console.log(`📂 Serving artifacts from: ${MAKE_DIR}`); + console.log(`\nInstructions for Windows VM:`); + console.log(`1. Identify your Host IP (e.g., 10.0.2.2 or 192.168.x.x)`); + console.log(`2. Run Pipelab in VM with env: APP_UPDATE_URL=http://:${PORT}`); + console.log(`\nPress Ctrl+C to stop.\n`); +}); diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts new file mode 100644 index 00000000..5bab15c6 --- /dev/null +++ b/apps/desktop/src/main.ts @@ -0,0 +1,254 @@ +import { app, shell, BrowserWindow, dialog, autoUpdater, screen, protocol, net } from "electron"; +import { join, resolve, dirname } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { platform } from "node:os"; +import { electronApp, optimizer, is } from "@electron-toolkit/utils"; +import { startServer, stopServer } from "./main/server-process"; +import { websocketPort, uiDevPort } from "@pipelab/constants"; +import { registerIpcHandlers } from "./main/ipc-handlers"; +import { getDefaultUserDataPath } from "@pipelab/core-node"; +import started from "electron-squirrel-startup"; +import { PostHog } from "posthog-node"; + +const isProduction = app.isPackaged && process.env.TEST !== "true"; + +let posthog: PostHog | undefined; +if (isProduction) { + posthog = new PostHog(process.env.POSTHOG_API_KEY || "", { + host: "https://eu.i.posthog.com", + }); +} + +process.on("uncaughtException", (error) => { + console.error("Uncaught Exception in Main Process:", error); + if (posthog) { + posthog.capture({ + distinctId: "desktop-main-process", + event: "uncaught_exception", + properties: { + message: error.message, + stack: error.stack, + platform: process.platform, + version: app.getVersion(), + }, + }); + } +}); + +app.setPath("userData", join(getDefaultUserDataPath(), "desktop")); + +protocol.registerSchemesAsPrivileged([ + { + scheme: "media", + privileges: { + secure: true, + standard: true, + supportFetchAPI: true, + corsEnabled: true, + stream: true, + }, + }, +]); + +function getIconPath() { + let ext = ".png"; + if (platform() === "win32") ext = ".ico"; + else if (platform() === "darwin") ext = ".icns"; + return join("./assets", "build", `icon${ext}`); +} + +if (process.platform === "win32" && process.env.TEST !== "true" && app.isPackaged) { + if (started) { + app.quit(); + } +} + +let mainWindow: BrowserWindow | undefined; + +function createWindow(): void { + const displays = screen.getAllDisplays(); + const externalDisplay = displays.find((display) => { + return display.bounds.x !== 0 || display.bounds.y !== 0; + }); + + const position = + externalDisplay && is.dev + ? { + x: externalDisplay.bounds.x + 50, + y: externalDisplay.bounds.y + 50, + } + : {}; + + mainWindow = new BrowserWindow({ + width: 1280, + height: 720, + show: false, + icon: getIconPath(), + autoHideMenuBar: true, + ...position, + webPreferences: { + preload: join(__dirname, "preload.cjs"), + sandbox: false, + devTools: is.dev, + }, + }); + + if (is.dev) { + mainWindow.webContents.openDevTools(); + } + + mainWindow.on("close", function () { + app.quit(); + }); + + mainWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url); + return { action: "deny" }; + }); +} + +if (is.dev && process.platform === "win32") { + app.setAsDefaultProtocolClient("pipelab", process.execPath, [resolve(process.argv[1])]); +} else { + app.setAsDefaultProtocolClient("pipelab"); +} + +const sendUpdateStatus = (status: string) => { + mainWindow?.webContents.send("update:set-status", { + data: { status }, + requestId: "shell-update", + }); +}; + +app.whenReady().then(async () => { + if (!is.dev || process.env.APP_UPDATE_URL) { + const updateUrl = + process.env.APP_UPDATE_URL || + "https://github.com/CynToolkit/pipelab/releases/latest/download"; + + console.log(`[Update] Setting up auto-updater with feed URL: ${updateUrl}`); + + autoUpdater.setFeedURL({ + url: updateUrl, + headers: { + "Cache-Control": "no-cache", + }, + }); + + autoUpdater.on("checking-for-update", () => { + console.log("[Update] Checking for update..."); + sendUpdateStatus("checking-for-update"); + }); + autoUpdater.on("update-available", () => { + console.log("[Update] Update available!"); + sendUpdateStatus("update-available"); + }); + autoUpdater.on("update-not-available", () => { + console.log("[Update] Update not available."); + sendUpdateStatus("update-not-available"); + }); + + autoUpdater.on("update-downloaded", (event, releaseNotes, releaseName) => { + console.log("[Update] Update downloaded:", releaseName); + sendUpdateStatus("update-downloaded"); + + const dialogOpts: Electron.MessageBoxOptions = { + type: "info", + buttons: ["Restart", "Later"], + title: "Application Update", + message: process.platform === "win32" ? releaseNotes : releaseName, + detail: "A new version has been downloaded. Restart the application to apply the updates.", + }; + + dialog.showMessageBox(dialogOpts).then((returnValue) => { + if (returnValue.response === 0) autoUpdater.quitAndInstall(); + }); + }); + + autoUpdater.on("error", (message) => { + sendUpdateStatus("error"); + console.error("[Update] There was a problem updating the application:", message); + }); + } + + electronApp.setAppUserModelId("com.pipelab"); + createWindow(); + + if (mainWindow) { + registerIpcHandlers(); + + // Show a splash screen/loading state while waiting for the server + if (is.dev) { + // In dev, we might already have the dev server up + mainWindow.loadURL(`http://localhost:${uiDevPort}`); + } else { + // In prod, load the local bundled index.html as a splash screen + // The Forge Vite plugin exposes these globals + if (typeof MAIN_WINDOW_VITE_DEV_SERVER_URL !== "undefined") { + mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); + } else { + mainWindow.loadFile(join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)); + } + } + + mainWindow.once("ready-to-show", () => { + mainWindow?.show(); + mainWindow?.maximize(); + }); + } + + // Start the background server (this might include downloading the CLI on first run) + try { + console.info("[Main] Starting standalone server..."); + await startServer(); + console.info("[Main] Standalone server is ready"); + + // Once server is ready, load the real UI + if (!is.dev) { + console.info(`[Main] Loading production UI from localhost:${websocketPort}`); + mainWindow?.loadURL(`http://localhost:${websocketPort}`); + } + } catch (error) { + console.error("Failed to start standalone server:", error); + dialog.showErrorBox( + "Startup Error", + "Failed to start the background server. This is required for Pipelab to function.\n\n" + + (error instanceof Error ? error.message : String(error)) + ); + app.quit(); + return; + } + + if (app.isPackaged) { + setTimeout(() => { + autoUpdater.checkForUpdates(); + }, 10000); + } + + protocol.handle("media", (request) => { + const path = decodeURIComponent(request.url.replace(/^media:\/\/+/, "/")); + return net.fetch(pathToFileURL(path).toString()); + }); + + app.on("browser-window-created", (_, window) => { + optimizer.watchWindowShortcuts(window); + }); + + + app.on("activate", function () { + if (BrowserWindow.getAllWindows().length === 0) createWindow(); + }); +}); + +app.on("window-all-closed", async () => { + if (process.platform !== "darwin") { + stopServer(); + app.quit(); + } +}); + +app.on("before-quit", async (event) => { + event.preventDefault(); + stopServer(); + app.exit(0); +}); diff --git a/apps/desktop/src/main/ipc-handlers.ts b/apps/desktop/src/main/ipc-handlers.ts new file mode 100644 index 00000000..a628fd25 --- /dev/null +++ b/apps/desktop/src/main/ipc-handlers.ts @@ -0,0 +1,36 @@ +import { ipcMain, dialog, BrowserWindow, shell } from "electron"; + +export const registerIpcHandlers = () => { + console.log("[Main] Registering IPC handlers"); + + const OPEN_DIALOG_CHANNEL = "dialog:showOpenDialog"; + const SAVE_DIALOG_CHANNEL = "dialog:showSaveDialog"; + + ipcMain.removeHandler(OPEN_DIALOG_CHANNEL); + ipcMain.handle(OPEN_DIALOG_CHANNEL, async (event, options) => { + const win = BrowserWindow.fromWebContents(event.sender) || undefined; + const result = await dialog.showOpenDialog(win!, options); + return { + canceled: result.canceled, + filePaths: result.filePaths, + }; + }); + + ipcMain.removeHandler(SAVE_DIALOG_CHANNEL); + ipcMain.handle(SAVE_DIALOG_CHANNEL, async (event, options) => { + const win = BrowserWindow.fromWebContents(event.sender) || undefined; + const result = await dialog.showSaveDialog(win!, options); + return { + canceled: result.canceled, + filePath: result.filePath, + }; + }); + + ipcMain.handle("shell:openExternal", async (event, url) => { + return await shell.openExternal(url); + }); + + ipcMain.handle("shell:showItemInFolder", (event, path) => { + shell.showItemInFolder(path); + }); +}; diff --git a/apps/desktop/src/main/server-process.ts b/apps/desktop/src/main/server-process.ts new file mode 100644 index 00000000..f0ca9d67 --- /dev/null +++ b/apps/desktop/src/main/server-process.ts @@ -0,0 +1,122 @@ +import { spawn, ChildProcess } from "node:child_process"; +import { join, dirname } from "node:path"; +import { app } from "electron"; +import { is } from "@electron-toolkit/utils"; +import http from "node:http"; +import fs from "node:fs"; +import { websocketPort, uiDevPort, getUiDevServerFatalError } from "@pipelab/constants"; +import { fetchPipelabCli, projectRoot, PipelabContext, getDefaultUserDataPath } from "@pipelab/core-node"; + +let serverProcess: ChildProcess | null = null; + +const isUp = (port: number, delay = 1000, shouldContinue?: () => boolean, silent = false): Promise => + new Promise((resolve) => { + const attempt = () => { + const req = http.get(`http://localhost:${port}`, (res) => { + res.resume(); + if (!silent) console.info(`[Server Check] Server is up on port ${port}`); + resolve(true); + }); + + req.on("error", () => { + if (!shouldContinue || shouldContinue()) { + if (!silent) { + console.info(`[Server Check] Waiting for server on port ${port}...`); + } + setTimeout(attempt, delay); + } else { + if (!silent) { + console.error(`[Server Check] Server check aborted for port ${port}`); + } + resolve(false); + } + }); + }; + attempt(); + }); + +export const startServer = async () => { + if (serverProcess) { + return; + } + + const userDataPath = getDefaultUserDataPath(); + + // 0. In dev mode, ensure UI dev server is running BEFORE anything else + if (is.dev) { + let retries = 5; + const isUIUp = await isUp(uiDevPort, 500, () => retries-- > 0, true); + if (!isUIUp) { + console.error(getUiDevServerFatalError(uiDevPort)); + throw new Error("UI dev server not found. App cannot start in development mode."); + } + } + + // 1. Check if server is already running + let initialRetries = 1; + const alreadyUp = await isUp(websocketPort, 500, () => initialRetries-- > 0, true); + if (alreadyUp) { + console.info(`[Server] Server already running on port ${websocketPort}`); + return; + } + + if (is.dev) { + console.info(" [DEVELOPMENT MODE] CLI server is starting automatically."); + } + + // 2. Resolve the CLI + const context = new PipelabContext({ userDataPath }); + const { entryPoint, isLocal, packageDir } = await fetchPipelabCli("latest", { context }); + + let serverPath = process.execPath; + let args = [entryPoint, "serve"]; + + if (isLocal && entryPoint.endsWith(".ts")) { + console.info( + `[Server] Local CLI detected at ${packageDir}, starting with hot-reload (tsx watch)`, + ); + const tsxPath = projectRoot ? join(projectRoot, "node_modules", ".bin", "tsx") : "tsx"; + // When using tsx, we still use Electron as the runner + args = [tsxPath, "watch", entryPoint, "serve"]; + } else { + console.info(`[Server] Starting CLI server from: ${entryPoint}`); + } + + let isServerRunning = true; + serverProcess = spawn(serverPath, args, { + env: { + ...process.env, + ELECTRON_RUN_AS_NODE: "1", + NODE_ENV: is.dev ? "development" : "production", + PORT: websocketPort.toString(), + IS_SERVER: "true", + }, + stdio: ["inherit", "inherit", "inherit"], + }); + + serverProcess.on("error", (err) => console.error("ERROR: Failed to spawn server:", err)); + serverProcess.stdout?.on("data", (d) => console.info(`[Server] ${d.toString().trim()}`)); + serverProcess.stderr?.on("data", (d) => console.error(`[Server Error] ${d.toString().trim()}`)); + + serverProcess.on("close", (code) => { + console.info(`Server process exited with code ${code}`); + serverProcess = null; + isServerRunning = false; + }); + + // Wait for the server to be listening on the port + console.log(`[Server] Waiting for server on port ${websocketPort}...`); + // Wait indefinitely as long as the server process is running + const up = await isUp(websocketPort, 1000, () => isServerRunning); + if (!up) { + throw new Error(`Server failed to start on port ${websocketPort} (process exited)`); + } + console.log(`[Server] CLI server is listening!`); +}; + +export const stopServer = () => { + if (serverProcess) { + serverProcess.kill(); + serverProcess = null; + } +}; diff --git a/apps/desktop/src/preload.d.ts b/apps/desktop/src/preload.d.ts new file mode 100644 index 00000000..ab1657ae --- /dev/null +++ b/apps/desktop/src/preload.d.ts @@ -0,0 +1,8 @@ +import { ElectronAPI } from "@electron-toolkit/preload"; + +declare global { + interface Window { + electron: ElectronAPI; + api: unknown; + } +} diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts new file mode 100644 index 00000000..0095b8ba --- /dev/null +++ b/apps/desktop/src/preload.ts @@ -0,0 +1,52 @@ +import { contextBridge, ipcRenderer } from "electron"; +import { electronAPI } from "@electron-toolkit/preload"; +import { version } from "../package.json"; + +// Custom APIs for renderer +// TODO: unify window and contextBridge + +// Use `contextBridge` APIs to expose Electron APIs to +// renderer only if context isolation is enabled, otherwise +// just add to the DOM global. +if (process.contextIsolated) { + try { + contextBridge.exposeInMainWorld("electron", electronAPI); + + const versions = { + electron: process.versions.electron, + chrome: process.versions.chrome, + node: process.versions.node, + app: version, + }; + console.log("[Preload] Exposing versions:", versions); + + contextBridge.exposeInMainWorld("pipelab", { + versions, + showOpenDialog: (options: any) => ipcRenderer.invoke("dialog:showOpenDialog", options), + showSaveDialog: (options: any) => ipcRenderer.invoke("dialog:showSaveDialog", options), + openExternal: (url: string) => ipcRenderer.invoke("shell:openExternal", url), + showItemInFolder: (path: string) => ipcRenderer.invoke("shell:showItemInFolder", path), + }); + contextBridge.exposeInMainWorld("version", version); + contextBridge.exposeInMainWorld("isPackaged", process.env.NODE_ENV !== "development"); + } catch (error) { + console.error(error); + } +} else { + window.electron = electronAPI; + const versions = { + electron: process.versions.electron, + chrome: process.versions.chrome, + node: process.versions.node, + app: version, + }; + window.pipelab = { + versions, + showOpenDialog: (options: any) => ipcRenderer.invoke("dialog:showOpenDialog", options), + showSaveDialog: (options: any) => ipcRenderer.invoke("dialog:showSaveDialog", options), + openExternal: (url: string) => ipcRenderer.invoke("shell:openExternal", url), + showItemInFolder: (path: string) => ipcRenderer.invoke("shell:showItemInFolder", path), + }; + window.version = version; + window.isPackaged = process.env.NODE_ENV !== "development"; +} diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json new file mode 100644 index 00000000..d860e9ce --- /dev/null +++ b/apps/desktop/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "@pipelab/tsconfig/commonjs.json", + "compilerOptions": { + "emitDeclarationOnly": true, + "baseUrl": ".", + "outDir": "dist", + "inlineSourceMap": true, + "paths": { + "@pipelab/shared": ["../../packages/shared/src/index.ts"], + "@pipelab/constants": ["../../packages/constants/src/index.ts"], + "@pipelab/migration": ["../../packages/migration/src"], + "@pipelab/*": ["../../packages/shared/src/libs/*"] + } + }, + "include": ["src/**/*", "tests/**/*", "*.ts", "*.mts", "package.json"], + "exclude": [ + "node_modules", + "./assets/tauri/template/app/src-tauri", + "dist", + "out", + "scripts", + "./assets" + ], + "references": [ + { + "path": "../../packages/constants" + }, + { + "path": "../../packages/shared" + } + ] +} diff --git a/apps/desktop/vite.base.config.mts b/apps/desktop/vite.base.config.mts new file mode 100644 index 00000000..0bb17acd --- /dev/null +++ b/apps/desktop/vite.base.config.mts @@ -0,0 +1,103 @@ +import { builtinModules } from "node:module"; +import type { AddressInfo } from "node:net"; +import { loadEnv, type ConfigEnv, type Plugin, type UserConfig } from "vite"; +import pkg from "./package.json" with { type: "json" }; + +export const builtins = ["electron", ...builtinModules.map((m) => [m, `node:${m}`]).flat()]; + +export const external = [ + ...builtins, + ...Object.keys("dependencies" in pkg ? (pkg.dependencies as Record) : {}), +]; + +export function getBuildConfig(env: ConfigEnv<"build">): UserConfig { + const { root, mode, command } = env; + + return { + root, + mode, + build: { + // Prevent multiple builds from interfering with each other. + emptyOutDir: false, + // 🚧 Multiple builds may conflict. + outDir: ".vite/build", + watch: command === "serve" ? {} : null, + minify: command === "build", + }, + clearScreen: false, + }; +} + +export function getDefineKeys(names: string[]) { + const define: { [name: string]: VitePluginRuntimeKeys } = {}; + + return names.reduce((acc, name) => { + const NAME = name.toUpperCase(); + const keys: VitePluginRuntimeKeys = { + VITE_DEV_SERVER_URL: `${NAME}_VITE_DEV_SERVER_URL`, + VITE_NAME: `${NAME}_VITE_NAME`, + }; + + return { ...acc, [name]: keys }; + }, define); +} + +export function getBuildDefine(env: ConfigEnv<"build">) { + const { command, forgeConfig } = env; + const names = + forgeConfig?.renderer?.filter(({ name }) => name != null).map(({ name }) => name!) || []; + const defineKeys = getDefineKeys(names); + const define = Object.entries(defineKeys).reduce( + (acc, [name, keys]) => { + const { VITE_DEV_SERVER_URL, VITE_NAME } = keys; + const def = { + [VITE_DEV_SERVER_URL]: + command === "serve" ? JSON.stringify(process.env[VITE_DEV_SERVER_URL]) : undefined, + [VITE_NAME]: JSON.stringify(name), + }; + return { ...acc, ...def }; + }, + {} as Record, + ); + + return define; +} + +export function pluginExposeRenderer(name: string): Plugin { + const { VITE_DEV_SERVER_URL } = getDefineKeys([name])[name]; + + return { + name: "@electron-forge/plugin-vite:expose-renderer", + configureServer(server) { + process.viteDevServers ??= {}; + // Expose server for preload scripts hot reload. + process.viteDevServers[name] = server; + + server.httpServer?.once("listening", () => { + const addressInfo = server.httpServer!.address() as AddressInfo; + // Expose env constant for main process use. + process.env[VITE_DEV_SERVER_URL] = `http://localhost:${addressInfo?.port}`; + }); + }, + }; +} + +export function pluginHotRestart(command: "reload" | "restart"): Plugin { + return { + name: "@electron-forge/plugin-vite:hot-restart", + closeBundle() { + if (command === "reload") { + if (process.viteDevServers) { + for (const server of Object.values(process.viteDevServers)) { + // Preload scripts hot reload. + server.ws.send({ type: "full-reload" }); + } + } + } else { + // Main process hot restart. + // https://github.com/electron/forge/blob/v7.2.0/packages/api/core/src/api/start.ts#L216-L223 + process.stdin.emit("data", "rs"); + } + }, + }; +} diff --git a/apps/desktop/vite.main.config.mts b/apps/desktop/vite.main.config.mts new file mode 100644 index 00000000..1fe4f1a1 --- /dev/null +++ b/apps/desktop/vite.main.config.mts @@ -0,0 +1,70 @@ +import type { ConfigEnv, UserConfig } from "vite"; +import { defineConfig, loadEnv, mergeConfig } from "vite"; +import { getBuildConfig, getBuildDefine, external, pluginHotRestart } from "./vite.base.config.mts"; +import tsconfigPaths from "vite-tsconfig-paths"; +import { resolve } from "path"; + +// https://vitejs.dev/config +export default defineConfig((env) => { + const forgeEnv = env as ConfigEnv<"build">; + const { forgeConfigSelf } = forgeEnv; + const define = getBuildDefine(forgeEnv); + const rootPath = resolve(__dirname, "../../"); + const environment = loadEnv(env.mode, rootPath, ""); + + const plugins = [ + pluginHotRestart("restart"), + tsconfigPaths({ + projects: ["./tsconfig.json"], + }), + ]; + + // check if we are in a tag + const tag = process.env.GITHUB_REF?.includes("refs/tags/"); + console.log("tag", tag); + + const config: UserConfig = { + envDir: rootPath, + build: { + sourcemap: true, + rollupOptions: { + input: forgeConfigSelf?.entry || "src/main.ts", + output: { + format: "cjs", + entryFileNames: "[name].js", + chunkFileNames: "[name].js", + assetFileNames: "[name].[ext]", + }, + external: [ + ...external.filter((dep) => { + const isPipelab = dep.startsWith("@pipelab/"); + const isElectronToolkit = dep.startsWith("@electron-toolkit/"); + const isSquirrel = dep === "electron-squirrel-startup"; + const isPosthog = dep === "posthog-node"; + return !isPipelab && !isElectronToolkit && !isSquirrel && !isPosthog; + }), + "bufferutil", + "utf-8-validate", + ], + }, + }, + plugins, + define: { + ...define, + "process.env.POSTHOG_API_KEY": JSON.stringify(environment.POSTHOG_API_KEY), + "process.env.SUPABASE_URL": JSON.stringify(environment.SUPABASE_URL), + "process.env.SUPABASE_ANON_KEY": JSON.stringify(environment.SUPABASE_ANON_KEY), + }, + ssr: { + // Ensure we target Node.js for the main process + target: "node", + }, + resolve: { + // Load the Node.js entry. + mainFields: ["module", "jsnext:main", "jsnext"], + conditions: ["node"], + }, + }; + + return mergeConfig(getBuildConfig(forgeEnv), config); +}); diff --git a/apps/desktop/vite.preload.config.mts b/apps/desktop/vite.preload.config.mts new file mode 100644 index 00000000..7cf77ef7 --- /dev/null +++ b/apps/desktop/vite.preload.config.mts @@ -0,0 +1,37 @@ +import type { ConfigEnv, UserConfig } from "vite"; +import { defineConfig, mergeConfig } from "vite"; +import { getBuildConfig, external, pluginHotRestart } from "./vite.base.config.mts"; +import tsconfigPaths from "vite-tsconfig-paths"; +import { resolve } from "path"; + +// https://vitejs.dev/config +export default defineConfig((env) => { + const forgeEnv = env as ConfigEnv<"build">; + const { forgeConfigSelf } = forgeEnv; + const config: UserConfig = { + build: { + rollupOptions: { + external: external.filter((dep) => !dep.startsWith("@pipelab/")), + // Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`. + input: forgeConfigSelf?.entry || "src/preload.ts", + output: { + format: "cjs", + // It should not be split chunks. + inlineDynamicImports: true, + entryFileNames: "[name].cjs", + chunkFileNames: "[name].js", + assetFileNames: "[name].[ext]", + }, + }, + }, + plugins: [ + pluginHotRestart("reload"), + tsconfigPaths({ + projects: ["./tsconfig.json"], + }), + ], + resolve: {}, + }; + + return mergeConfig(getBuildConfig(forgeEnv), config); +}); diff --git a/apps/desktop/vite.renderer.config.mts b/apps/desktop/vite.renderer.config.mts new file mode 100644 index 00000000..0ca6f959 --- /dev/null +++ b/apps/desktop/vite.renderer.config.mts @@ -0,0 +1,33 @@ +import type { ConfigEnv, UserConfig } from "vite"; +import { defineConfig, loadEnv } from "vite"; +import { pluginExposeRenderer } from "./vite.base.config.mts"; +import tsconfigPaths from "vite-tsconfig-paths"; + +export default defineConfig((env) => { + const forgeEnv = env as ConfigEnv<"renderer">; + const { root, mode, forgeConfigSelf } = forgeEnv; + const name = forgeConfigSelf.name ?? ""; + + const plugins = [ + pluginExposeRenderer(name), + tsconfigPaths({ + projects: ["./tsconfig.json"], + }), + ]; + + return { + root, + mode, + base: "./", + build: { + outDir: `.vite/renderer/${name}`, + sourcemap: true, + }, + server: { + port: 5183, + strictPort: true, + }, + plugins, + clearScreen: false, + } as UserConfig; +}); diff --git a/apps/ui/.gitignore b/apps/ui/.gitignore new file mode 100644 index 00000000..53c37a16 --- /dev/null +++ b/apps/ui/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/apps/ui/README.md b/apps/ui/README.md new file mode 100644 index 00000000..1d977c6a --- /dev/null +++ b/apps/ui/README.md @@ -0,0 +1,34 @@ +# @pipelab/ui (The Interface) + +The main visual interface for Pipelab, built with Vue 3 and PrimeVue. + +## 🎨 Role & Connectivity + +The UI is a "thin client" designed to visualize and edit automation pipelines. It does not execute logic directly; instead, it orchestrates the `@pipelab/cli` engine. + +### Connection Discovery + +When the UI starts, it attempts to connect to the Pipelab Engine (CLI process): + +1. **Discovery**: It looks for a running engine on the configured port (default: `33753`). +2. **Streaming**: Once connected, it receives real-time execution logs and progress updates via WebSockets. +3. **Authentication**: Syncs its authentication state with the engine's Supabase instance. + +## 🛠️ Development + +### Tech Stack + +- **Framework**: Vue 3 (Composition API) +- **UI Library**: PrimeVue v4 +- **State**: Pinia +- **Icons**: PrimeIcons / Lucide + +### Commands + +```bash +pnpm dev # Starts the Vite development server (port 5173) +pnpm build # Generates the production assets in the /dist folder +``` + +> [!NOTE] +> During development, the UI expects the `@pipelab/cli` to be running separately (via `pnpm dev` at the root). diff --git a/apps/ui/auto-imports.d.ts b/apps/ui/auto-imports.d.ts new file mode 100644 index 00000000..f6e2bab3 --- /dev/null +++ b/apps/ui/auto-imports.d.ts @@ -0,0 +1,88 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +// biome-ignore lint: disable +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const createPinia: typeof import('pinia')['createPinia'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineStore: typeof import('pinia')['defineStore'] + const effectScope: typeof import('vue')['effectScope'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] + const triggerRef: typeof import('vue')['triggerRef'] + const unref: typeof import('vue')['unref'] + const useAttrs: typeof import('vue')['useAttrs'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useId: typeof import('vue')['useId'] + const useLink: typeof import('vue-router')['useLink'] + const useModel: typeof import('vue')['useModel'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSlots: typeof import('vue')['useSlots'] + const useTemplateRef: typeof import('vue')['useTemplateRef'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/apps/ui/components.d.ts b/apps/ui/components.d.ts new file mode 100644 index 00000000..d4f3016f --- /dev/null +++ b/apps/ui/components.d.ts @@ -0,0 +1,82 @@ +/* eslint-disable */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + AddNodeButton: typeof import('./src/components/AddNodeButton.vue')['default'] + AddTriggerButton: typeof import('./src/components/AddTriggerButton.vue')['default'] + BuildDetailsModal: typeof import('./src/components/BuildDetailsModal.vue')['default'] + BuildHistoryDialog: typeof import('./src/components/BuildHistoryDialog.vue')['default'] + BuildHistoryItem: typeof import('./src/components/BuildHistoryItem.vue')['default'] + BuildHistoryList: typeof import('./src/components/BuildHistoryList.vue')['default'] + BuildHistoryView: typeof import('./src/components/BuildHistoryView.vue')['default'] + BuildStatusBadge: typeof import('./src/components/BuildStatusBadge.vue')['default'] + Button: typeof import('primevue/button')['default'] + Checkbox: typeof import('primevue/checkbox')['default'] + Chip: typeof import('primevue/chip')['default'] + ColorPicker: typeof import('./src/components/ColorPicker.vue')['default'] + Column: typeof import('primevue/column')['default'] + ConfirmDialog: typeof import('primevue/confirmdialog')['default'] + ConfirmPopup: typeof import('primevue/confirmpopup')['default'] + ConnectingPage: typeof import('./src/components/ConnectingPage.vue')['default'] + DataTable: typeof import('primevue/datatable')['default'] + DevBenefitsOverride: typeof import('./src/components/DevBenefitsOverride.vue')['default'] + Dialog: typeof import('primevue/dialog')['default'] + DisconnectedPage: typeof import('./src/components/DisconnectedPage.vue')['default'] + Divider: typeof import('primevue/divider')['default'] + Drawer: typeof import('primevue/drawer')['default'] + Dropdown: typeof import('primevue/dropdown')['default'] + EditorNodeAction: typeof import('./src/components/nodes/EditorNodeAction.vue')['default'] + EditorNodeCondition: typeof import('./src/components/nodes/EditorNodeCondition.vue')['default'] + EditorNodeDummy: typeof import('./src/components/nodes/EditorNodeDummy.vue')['default'] + EditorNodeEvent: typeof import('./src/components/nodes/EditorNodeEvent.vue')['default'] + EditorNodeEventEmpty: typeof import('./src/components/nodes/EditorNodeEventEmpty.vue')['default'] + EditorNodeLoop: typeof import('./src/components/nodes/EditorNodeLoop.vue')['default'] + FileInput: typeof import('./src/components/FileInput.vue')['default'] + IconField: typeof import('primevue/iconfield')['default'] + Inplace: typeof import('primevue/inplace')['default'] + InputGroup: typeof import('primevue/inputgroup')['default'] + InputIcon: typeof import('primevue/inputicon')['default'] + InputNumber: typeof import('primevue/inputnumber')['default'] + InputText: typeof import('primevue/inputtext')['default'] + Layout: typeof import('./src/components/Layout.vue')['default'] + Listbox: typeof import('primevue/listbox')['default'] + Menu: typeof import('primevue/menu')['default'] + Message: typeof import('primevue/message')['default'] + Panel: typeof import('primevue/panel')['default'] + ParamEditor: typeof import('./src/components/nodes/ParamEditor.vue')['default'] + ParamEditorBody: typeof import('./src/components/nodes/ParamEditorBody.vue')['default'] + Password: typeof import('primevue/password')['default'] + PluginIcon: typeof import('./src/components/nodes/PluginIcon.vue')['default'] + ProgressBar: typeof import('primevue/progressbar')['default'] + ProgressSpinner: typeof import('primevue/progressspinner')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + ScenarioListItem: typeof import('./src/components/ScenarioListItem.vue')['default'] + ScenarioListItemFile: typeof import('./src/components/ScenarioListItemFile.vue')['default'] + ScenarioListItemRecent: typeof import('./src/components/ScenarioListItemRecent.vue')['default'] + Select: typeof import('primevue/select')['default'] + SelectButton: typeof import('primevue/selectbutton')['default'] + Settings: typeof import('./src/components/Settings.vue')['default'] + Skeleton: typeof import('primevue/skeleton')['default'] + SubscriptionLoadingIndicator: typeof import('./src/components/SubscriptionLoadingIndicator.vue')['default'] + Tab: typeof import('primevue/tab')['default'] + TabList: typeof import('primevue/tablist')['default'] + TabPanel: typeof import('primevue/tabpanel')['default'] + TabPanels: typeof import('primevue/tabpanels')['default'] + Tabs: typeof import('primevue/tabs')['default'] + TabView: typeof import('primevue/tabview')['default'] + Toast: typeof import('primevue/toast')['default'] + ToggleSwitch: typeof import('primevue/toggleswitch')['default'] + UpgradeDialog: typeof import('./src/components/UpgradeDialog.vue')['default'] + UpgradeNowButton: typeof import('./src/components/UpgradeNowButton.vue')['default'] + WebFilePicker: typeof import('./src/components/WebFilePicker.vue')['default'] + } + export interface ComponentCustomProperties { + Tooltip: typeof import('primevue/tooltip')['default'] + } +} diff --git a/apps/ui/index.html b/apps/ui/index.html new file mode 100644 index 00000000..8931f2ff --- /dev/null +++ b/apps/ui/index.html @@ -0,0 +1,32 @@ + + + + + + + Pipelab UI + + + + + + +
+ + + diff --git a/apps/ui/package.json b/apps/ui/package.json new file mode 100644 index 00000000..ca99771f --- /dev/null +++ b/apps/ui/package.json @@ -0,0 +1,91 @@ +{ + "name": "@pipelab/ui", + "version": "1.9.9", + "private": true, + "license": "FSL-1.1-MIT", + "repository": { + "type": "git", + "url": "https://github.com/CynToolkit/pipelab.git", + "directory": "apps/ui" + }, + "type": "module", + "main": "src/main.ts", + "scripts": { + "dev": "vite", + "build": "vite build", + "typecheck": "vue-tsc --noEmit -p tsconfig.json --composite false", + "preview": "vite preview", + "format": "oxfmt .", + "lint": "oxlint ." + }, + "dependencies": { + "@codemirror/autocomplete": "6.18.1", + "@codemirror/commands": "6.6.2", + "@codemirror/lang-javascript": "6.2.2", + "@codemirror/lang-liquid": "6.2.1", + "@codemirror/lint": "6.8.2", + "@codemirror/state": "6.4.1", + "@codemirror/view": "6.34.0", + "@floating-ui/vue": "1.1.5", + "@jitl/quickjs-wasmfile-release-sync": "0.31.0", + "@mdi/font": "7.4.47", + "@pipelab/constants": "workspace:*", + "@pipelab/shared": "workspace:*", + "@primevue/themes": "4.1.1", + "@trpc/client": "10.45.2", + "@vee-validate/valibot": "4.13.2", + "@vuelidate/core": "2.0.3", + "@vuelidate/validators": "2.0.4", + "@vueuse/components": "11.1.0", + "@vueuse/core": "11.1.0", + "@vueuse/router": "11.1.0", + "change-case": "5.4.4", + "date-fns": "4.1.0", + "dompurify": "3.1.7", + "driver.js": "1.4.0", + "es-toolkit": "1.25.2", + "fancy-ansi": "0.1.3", + "geist": "1.3.1", + "get-value": "3.0.1", + "klona": "2.0.6", + "mutative": "1.0.11", + "nanoid": "5.0.7", + "path-browserify": "1.0.1", + "pinia": "2.2.4", + "pinia-plugin-persistedstate": "4.1.1", + "polished": "4.3.1", + "posthog-js": "1.215.0", + "primeflex": "4.0.0", + "primeicons": "7.0.0", + "primevue": "4.5.4", + "quickjs-emscripten": "0.31.0", + "quickjs-emscripten-sync": "1.6.0", + "set-value": "4.1.0", + "string-strip-html": "13.4.8", + "thememirror": "2.0.1", + "tinykeys": "2.1.0", + "ts-pattern": "5.5.0", + "tslog": "4.9.3", + "type-fest": "4.26.1", + "valibot": "0.42.1", + "vee-validate": "4.14.4", + "vue": "3.5.13", + "vue-dompurify-html": "5.1.0", + "vue-i18n": "10", + "vue-router": "4.4.5" + }, + "devDependencies": { + "@pipelab/tsconfig": "workspace:*", + "@primevue/auto-import-resolver": "4.1.1", + "@types/node": "24.12.2", + "@vitejs/devtools": "0.1.13", + "@vitejs/plugin-vue": "6.0.5", + "sass-embedded": "^1.83.4", + "typescript": "5.8.3", + "unplugin-auto-import": "0.18.3", + "unplugin-vue-components": "0.27.4", + "vite": "8.0.8", + "vite-plugin-vue-devtools": "8.1.1", + "vue-tsc": "2.1.6" + } +} diff --git a/apps/ui/src/App.vue b/apps/ui/src/App.vue new file mode 100644 index 00000000..75a5b4ec --- /dev/null +++ b/apps/ui/src/App.vue @@ -0,0 +1,309 @@ + + + + + diff --git a/src/renderer/Root.vue b/apps/ui/src/Root.vue similarity index 78% rename from src/renderer/Root.vue rename to apps/ui/src/Root.vue index 014536e6..b0b1d134 100644 --- a/src/renderer/Root.vue +++ b/apps/ui/src/Root.vue @@ -3,7 +3,7 @@ diff --git a/src/renderer/agent/log.ts b/apps/ui/src/agent/log.ts similarity index 100% rename from src/renderer/agent/log.ts rename to apps/ui/src/agent/log.ts diff --git a/src/renderer/agent/trpc.ts b/apps/ui/src/agent/trpc.ts similarity index 74% rename from src/renderer/agent/trpc.ts rename to apps/ui/src/agent/trpc.ts index 9ba7243a..56fa3b0f 100644 --- a/src/renderer/agent/trpc.ts +++ b/apps/ui/src/agent/trpc.ts @@ -1,4 +1,4 @@ -import { initTRPC } from '@trpc/server'; +import { initTRPC } from "@trpc/server"; /** * Initialization of tRPC backend @@ -11,4 +11,4 @@ const t = initTRPC.create(); * that can be used throughout the router */ export const router = t.router; -export const publicProcedure = t.procedure; \ No newline at end of file +export const publicProcedure = t.procedure; diff --git a/apps/ui/src/agent/utils/json.ts b/apps/ui/src/agent/utils/json.ts new file mode 100644 index 00000000..85a02202 --- /dev/null +++ b/apps/ui/src/agent/utils/json.ts @@ -0,0 +1,7 @@ +export const safeParse = (str: string): T | undefined => { + try { + return JSON.parse(str); + } catch (e) { + return undefined; + } +}; diff --git a/src/renderer/assets/css/styles.less b/apps/ui/src/assets/css/styles.less similarity index 94% rename from src/renderer/assets/css/styles.less rename to apps/ui/src/assets/css/styles.less index 2cf09d25..3a08b314 100644 --- a/src/renderer/assets/css/styles.less +++ b/apps/ui/src/assets/css/styles.less @@ -5,12 +5,12 @@ body { Roboto, -apple-system, BlinkMacSystemFont, - 'Helvetica Neue', - 'Segoe UI', - 'Oxygen', - 'Ubuntu', - 'Cantarell', - 'Open Sans', + "Helvetica Neue", + "Segoe UI", + "Oxygen", + "Ubuntu", + "Cantarell", + "Open Sans", sans-serif; color: #86a5b1; background-color: #2f3241; @@ -67,7 +67,7 @@ a:hover { float: none; clear: both; overflow: hidden; - font-family: 'Menlo', 'Lucida Console', monospace; + font-family: "Menlo", "Lucida Console", monospace; color: #c2f5ff; line-height: 1; transition: all 0.3s; diff --git a/src/renderer/assets/icons.svg b/apps/ui/src/assets/icons.svg similarity index 100% rename from src/renderer/assets/icons.svg rename to apps/ui/src/assets/icons.svg diff --git a/src/auto-imports.d.ts b/apps/ui/src/auto-imports.d.ts similarity index 73% rename from src/auto-imports.d.ts rename to apps/ui/src/auto-imports.d.ts index d2c795cf..704c3707 100644 --- a/src/auto-imports.d.ts +++ b/apps/ui/src/auto-imports.d.ts @@ -5,5 +5,5 @@ // Generated by unplugin-auto-import export {} declare global { - const Steps: typeof import('primevue/steps')['Steps'] + const Steps: (typeof import("primevue/steps"))["Steps"]; } diff --git a/apps/ui/src/components/AddNodeButton.model.ts b/apps/ui/src/components/AddNodeButton.model.ts new file mode 100644 index 00000000..ab6b43ac --- /dev/null +++ b/apps/ui/src/components/AddNodeButton.model.ts @@ -0,0 +1,15 @@ +import { PipelabNode, Event, RendererPluginDefinition } from "@pipelab/shared"; + +export interface AddNodeEvent { + node: PipelabNode; + plugin: RendererPluginDefinition; + path: string[]; + insertAt: number; +} + +export interface AddTriggerEvent { + trigger: Event; + plugin: RendererPluginDefinition; + path: string[]; + insertAt: number; +} diff --git a/src/renderer/components/AddNodeButton.vue b/apps/ui/src/components/AddNodeButton.vue similarity index 76% rename from src/renderer/components/AddNodeButton.vue rename to apps/ui/src/components/AddNodeButton.vue index 58aaa699..7d4059b2 100644 --- a/src/renderer/components/AddNodeButton.vue +++ b/apps/ui/src/components/AddNodeButton.vue @@ -8,7 +8,7 @@ size="small" :pt="{ root: { style: { fontSize: '10px', width: '24px', height: '24px' } }, - icon: { style: { fontSize: '10px' } } + icon: { style: { fontSize: '10px' } }, }" @click="addNode" > @@ -66,7 +66,8 @@ @@ -99,7 +100,7 @@ @@ -38,40 +38,40 @@ diff --git a/apps/ui/src/components/FileInput.vue b/apps/ui/src/components/FileInput.vue new file mode 100644 index 00000000..f561a0fd --- /dev/null +++ b/apps/ui/src/components/FileInput.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/src/renderer/components/Layout.vue b/apps/ui/src/components/Layout.vue similarity index 53% rename from src/renderer/components/Layout.vue rename to apps/ui/src/components/Layout.vue index b03bb937..3f9b58aa 100644 --- a/src/renderer/components/Layout.vue +++ b/apps/ui/src/components/Layout.vue @@ -2,22 +2,62 @@
{{ headerSentence }}
+
- + + +
@@ -156,8 +160,10 @@ :class="`log-${log.level || 'info'}`" > {{ formatTime(log.timestamp) }} - {{ (log.level || 'info').toUpperCase() }} - {{ Array.isArray(log.message) ? log.message.join(' ') : log.message }} + {{ (log.level || "info").toUpperCase() }} + {{ + Array.isArray(log.message) ? log.message.join(" ") : log.message + }} ({{ log.source }}) diff --git a/apps/ui/src/components/Settings.vue b/apps/ui/src/components/Settings.vue index c0d2fb25..ca3bf2f6 100644 --- a/apps/ui/src/components/Settings.vue +++ b/apps/ui/src/components/Settings.vue @@ -24,11 +24,13 @@ /> - +
-
Toggle between light and dark mode for the application interface.
+
+ Toggle between light and dark mode for the application interface. +
- -
Select your preferred language for the application UI.
+ +
+ Select your preferred language for the application UI. +