@pipelab/core-node 1.0.0-beta.2 → 1.0.0-beta.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.oxfmtrc.json +1 -1
  2. package/CHANGELOG.md +173 -0
  3. package/dist/config-Bi0ORcTK.mjs +2 -0
  4. package/dist/config-CFgGRD9U.mjs +265 -0
  5. package/dist/config-CFgGRD9U.mjs.map +1 -0
  6. package/dist/index.d.mts +74 -53
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +1705 -576
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +9 -5
  11. package/scratch/simulate-updates.ts +120 -0
  12. package/src/config.test.ts +232 -0
  13. package/src/config.ts +111 -50
  14. package/src/context.ts +117 -5
  15. package/src/handler-func.ts +2 -77
  16. package/src/handlers/build-history.ts +60 -90
  17. package/src/handlers/config.ts +265 -55
  18. package/src/handlers/engine.ts +32 -35
  19. package/src/handlers/history.ts +0 -40
  20. package/src/handlers/index.ts +4 -0
  21. package/src/handlers/migration.ts +621 -0
  22. package/src/handlers/plugins.ts +305 -0
  23. package/src/handlers/system.ts +7 -2
  24. package/src/index.ts +9 -2
  25. package/src/plugins-registry.ts +280 -38
  26. package/src/presets/c3toSteam.ts +13 -7
  27. package/src/presets/demo.ts +9 -9
  28. package/src/presets/list.ts +0 -6
  29. package/src/presets/moreToCome.ts +3 -2
  30. package/src/presets/newProject.ts +3 -2
  31. package/src/presets/test-c3-offline.ts +15 -6
  32. package/src/presets/test-c3-unzip.ts +19 -8
  33. package/src/runner.ts +2 -8
  34. package/src/server.ts +45 -4
  35. package/src/types/runner.ts +2 -30
  36. package/src/utils/fs-extras.ts +6 -24
  37. package/src/utils/github.test.ts +211 -0
  38. package/src/utils/github.ts +90 -10
  39. package/src/utils/remote.test.ts +209 -0
  40. package/src/utils/remote.ts +325 -87
  41. package/src/utils/storage.ts +2 -1
  42. package/src/utils.ts +20 -24
  43. package/src/migrations.ts +0 -72
  44. package/src/presets/if.ts +0 -69
  45. package/src/presets/loop.ts +0 -65
@@ -0,0 +1,305 @@
1
+ import { useAPI } from "../ipc-core";
2
+ import { PipelabContext, isDev, projectRoot } from "../context";
3
+ import pacote from "pacote";
4
+ import { rm } from "node:fs/promises";
5
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { usePlugins } from "@pipelab/shared";
8
+ import { webSocketServer } from "../websocket-server";
9
+ import { fetchPipelabPlugin } from "../utils/remote";
10
+ import { loadCustomPlugin, findInstalledPlugins } from "../plugins-registry";
11
+
12
+ // Maps workspace plugin package names (e.g. "@pipelab/plugin-steam") to their physical directory paths.
13
+ // This is populated at startup in dev mode, supporting plugin folders whose directory names
14
+ // differ from their actual package.json name.
15
+ const localPluginsMap = new Map<string, string>();
16
+
17
+ if (isDev && projectRoot) {
18
+ const pluginsDir = join(projectRoot, "plugins");
19
+ if (existsSync(pluginsDir)) {
20
+ try {
21
+ const entries = readdirSync(pluginsDir, { withFileTypes: true });
22
+ for (const entry of entries) {
23
+ if (entry.isDirectory()) {
24
+ const pkgPath = join(pluginsDir, entry.name, "package.json");
25
+ if (existsSync(pkgPath)) {
26
+ try {
27
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
28
+ if (pkg.name) {
29
+ localPluginsMap.set(pkg.name, join(pluginsDir, entry.name));
30
+ }
31
+ } catch (e) {
32
+ console.error(`[Plugins] Failed to parse package.json for ${entry.name}:`, e);
33
+ }
34
+ }
35
+ }
36
+ }
37
+ } catch (e) {
38
+ console.error(`[Plugins] Failed to scan local workspace plugins directory:`, e);
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Resolves a requested plugin version.
45
+ * If the plugin is present in the local workspace map during development,
46
+ * its version is overridden to "local" so Pipelab loads it from the source files.
47
+ */
48
+ export function resolvePluginVersion(packageName: string, requestedVersion?: string): string {
49
+ if (
50
+ isDev &&
51
+ projectRoot &&
52
+ process.env.PIPELAB_FORCE_NPM !== "true" &&
53
+ localPluginsMap.has(packageName)
54
+ ) {
55
+ return "local";
56
+ }
57
+ return requestedVersion || "latest";
58
+ }
59
+
60
+ export const registerPluginsHandlers = (context: PipelabContext) => {
61
+ const { handle } = useAPI();
62
+
63
+ handle("plugin:search", async (_, { send, value }) => {
64
+ try {
65
+ const query = value.query || "";
66
+ const text = query ? `${query} keywords:pipelab-plugin` : "keywords:pipelab-plugin";
67
+ const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(text)}&size=50`;
68
+
69
+ const response = await fetch(url);
70
+ if (!response.ok) {
71
+ throw new Error(`NPM registry search failed with status ${response.status}`);
72
+ }
73
+
74
+ const data = await response.json();
75
+ const results = (data.objects || []).map((obj: any) => ({
76
+ name: obj.package.name,
77
+ version: obj.package.version,
78
+ description: obj.package.description,
79
+ keywords: obj.package.keywords,
80
+ date: obj.package.date,
81
+ }));
82
+
83
+ send({
84
+ type: "end",
85
+ data: {
86
+ type: "success",
87
+ result: { results },
88
+ },
89
+ });
90
+ } catch (e: any) {
91
+ send({
92
+ type: "end",
93
+ data: {
94
+ type: "error",
95
+ ipcError: e.message || "Failed to search npm registry",
96
+ },
97
+ });
98
+ }
99
+ });
100
+
101
+ handle("plugin:get-details", async (_, { send, value }) => {
102
+ try {
103
+ const { packageName } = value;
104
+ const packument = await pacote.packument(packageName, { fullMetadata: true });
105
+ const latestVersion =
106
+ packument["dist-tags"]?.latest || Object.keys(packument.versions).pop() || "0.0.0";
107
+ const latestPkg = packument.versions[latestVersion];
108
+
109
+ send({
110
+ type: "end",
111
+ data: {
112
+ type: "success",
113
+ result: {
114
+ name: packument.name,
115
+ latestVersion,
116
+ versions: Object.keys(packument.versions).reverse(),
117
+ description: latestPkg?.description,
118
+ },
119
+ },
120
+ });
121
+ } catch (e: any) {
122
+ send({
123
+ type: "end",
124
+ data: {
125
+ type: "error",
126
+ ipcError: e.message || "Failed to get plugin details",
127
+ },
128
+ });
129
+ }
130
+ });
131
+
132
+ handle("plugin:install", async (_, { send, value }) => {
133
+ try {
134
+ const { packageName, version } = value;
135
+ const mappedVersion = resolvePluginVersion(packageName, version);
136
+ console.log(`[Plugins] Installing ${packageName}@${mappedVersion}...`);
137
+
138
+ const { packageDir } = await fetchPipelabPlugin(packageName, mappedVersion, {
139
+ context,
140
+ installDeps: false,
141
+ });
142
+
143
+ const plugin = await loadCustomPlugin(packageName, mappedVersion, { context });
144
+ if (!plugin) {
145
+ throw new Error("Failed to load installed plugin module.");
146
+ }
147
+
148
+ const { registerPlugins } = usePlugins();
149
+ registerPlugins([plugin]);
150
+
151
+ webSocketServer.broadcast("plugin:loaded", { plugin });
152
+
153
+ send({
154
+ type: "end",
155
+ data: {
156
+ type: "success",
157
+ result: { result: "ok" },
158
+ },
159
+ });
160
+ } catch (e: any) {
161
+ console.error(`[Plugins] Installation failed for ${value.packageName}:`, e);
162
+ send({
163
+ type: "end",
164
+ data: {
165
+ type: "error",
166
+ ipcError: e.message || "Failed to install plugin",
167
+ },
168
+ });
169
+ }
170
+ });
171
+
172
+ handle("plugin:uninstall", async (_, { send, value }) => {
173
+ try {
174
+ const { packageName } = value;
175
+ console.log(`[Plugins] Uninstalling plugin ${packageName}...`);
176
+ const targetDir = context.getPackagesPath(packageName);
177
+
178
+ if (existsSync(targetDir)) {
179
+ await rm(targetDir, { recursive: true, force: true });
180
+ }
181
+
182
+ send({
183
+ type: "end",
184
+ data: {
185
+ type: "success",
186
+ result: { result: "ok" },
187
+ },
188
+ });
189
+ } catch (e: any) {
190
+ send({
191
+ type: "end",
192
+ data: {
193
+ type: "error",
194
+ ipcError: e.message || "Failed to uninstall plugin",
195
+ },
196
+ });
197
+ }
198
+ });
199
+
200
+ handle("plugin:list-installed", async (_, { send }) => {
201
+ try {
202
+ const packagesDir = context.getPackagesPath();
203
+ const rawInstalled = await findInstalledPlugins(packagesDir);
204
+
205
+ const DEFAULT_PLUGIN_IDS = [
206
+ "construct",
207
+ "filesystem",
208
+ "system",
209
+ "steam",
210
+ "itch",
211
+ "electron",
212
+ "discord",
213
+ "poki",
214
+ "nvpatch",
215
+ "tauri",
216
+ "minify",
217
+ "netlify",
218
+ ];
219
+
220
+ const installed = rawInstalled
221
+ .filter((item) => {
222
+ const isDefault = DEFAULT_PLUGIN_IDS.some((id) => item.name === `@pipelab/plugin-${id}`);
223
+ return !isDefault;
224
+ })
225
+ .map((item) => ({
226
+ name: item.name,
227
+ version: item.version,
228
+ description: item.description,
229
+ }));
230
+
231
+ send({
232
+ type: "end",
233
+ data: {
234
+ type: "success",
235
+ result: { installed },
236
+ },
237
+ });
238
+ } catch (e: any) {
239
+ send({
240
+ type: "end",
241
+ data: {
242
+ type: "error",
243
+ ipcError: e.message || "Failed to list installed plugins",
244
+ },
245
+ });
246
+ }
247
+ });
248
+
249
+ // Ensures all required plugin IDs are loaded, JIT-installing any that are missing.
250
+ // Called before opening a pipeline in the editor.
251
+ handle("plugin:ensure-loaded", async (_, { send, value }) => {
252
+ const { plugins } = value;
253
+ const { plugins: registeredPlugins, registerPlugins } = usePlugins();
254
+ const loaded: string[] = [];
255
+ const failed: string[] = [];
256
+
257
+ const pluginsToEnsure = new Set<string>();
258
+ if (plugins && typeof plugins === "object") {
259
+ for (const id of Object.keys(plugins)) {
260
+ if (id) pluginsToEnsure.add(id);
261
+ }
262
+ }
263
+
264
+ for (const packageName of pluginsToEnsure) {
265
+ const mappedVersion = resolvePluginVersion(packageName);
266
+
267
+ const isRegistered = registeredPlugins.value.some((p) => {
268
+ if (p.packageName !== packageName) return false;
269
+ if (mappedVersion === "latest") return true;
270
+ return p.version === mappedVersion;
271
+ });
272
+
273
+ if (isRegistered) {
274
+ continue; // already loaded, nothing to do
275
+ }
276
+
277
+ try {
278
+ console.log(
279
+ `[Plugins] JIT loading plugin "${packageName}@${mappedVersion}" for pipeline...`,
280
+ );
281
+ const plugin = await loadCustomPlugin(packageName, mappedVersion, { context });
282
+ if (plugin) {
283
+ registerPlugins([plugin]);
284
+ webSocketServer.broadcast("plugin:loaded", { plugin });
285
+ loaded.push(packageName);
286
+ console.log(`[Plugins] JIT loaded "${packageName}" successfully.`);
287
+ } else {
288
+ console.warn(`[Plugins] JIT load for "${packageName}" returned no plugin.`);
289
+ failed.push(packageName);
290
+ }
291
+ } catch (e: any) {
292
+ console.error(`[Plugins] JIT load failed for "${packageName}":`, e);
293
+ failed.push(packageName);
294
+ }
295
+ }
296
+
297
+ send({
298
+ type: "end",
299
+ data: {
300
+ type: "success",
301
+ result: { loaded, failed },
302
+ },
303
+ });
304
+ });
305
+ };
@@ -1,16 +1,21 @@
1
1
  import { useAPI } from "../ipc-core";
2
- import { PipelabContext } from "../context";
2
+ import { PipelabContext, isDev } from "../context";
3
3
 
4
4
  export const registerSystemHandlers = (options: { version: string; context: PipelabContext }) => {
5
5
  const { handle } = useAPI();
6
6
 
7
7
  handle("agent:version:get", async (_, { send }) => {
8
+ const isStable =
9
+ options.context.userDataPath.endsWith("app") ||
10
+ !options.context.userDataPath.includes("app-beta");
11
+
8
12
  send({
9
13
  type: "end",
10
14
  data: {
11
15
  type: "success",
12
16
  result: {
13
- version: options.version,
17
+ version: isDev ? "workspace" : options.version,
18
+ channel: isDev ? "dev" : isStable ? "stable" : "beta",
14
19
  },
15
20
  },
16
21
  });
package/src/index.ts CHANGED
@@ -2,7 +2,15 @@ export * from "./context";
2
2
  export * from "./websocket-server";
3
3
  export * from "./ipc-core";
4
4
  export * from "./handlers/index";
5
- export * from "./config";
5
+ export {
6
+ setupSettingsConfigFile,
7
+ setupConnectionsConfigFile,
8
+ setupProjectsConfigFile,
9
+ setupPipelineConfigFileByName,
10
+ setupPipelineConfigFileByPath,
11
+ deletePipelineConfigFileByName,
12
+ deletePipelineConfigFileByPath,
13
+ } from "./config";
6
14
  export * from "./paths";
7
15
  export * from "./api";
8
16
  export * from "./heavy";
@@ -11,7 +19,6 @@ export * from "./utils/remote";
11
19
  export * from "./utils/fs-extras";
12
20
  export * from "./types/runner";
13
21
  export * from "./runner";
14
- export * from "./migrations";
15
22
  export * from "./server";
16
23
  export * from "./utils";
17
24
  export * from "./utils/github";