@pipelab/plugin-construct 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +110 -0
- package/README.md +3 -0
- package/dist/index.cjs +433 -0
- package/dist/index.d.cts +132 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +132 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +428 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +50 -0
- package/src/assets/construct.webp +0 -0
- package/src/assets/script.ts +288 -0
- package/src/declarations.d.ts +1 -0
- package/src/export-c3p.ts +75 -0
- package/src/export-project.ts +75 -0
- package/src/export-shared.ts +286 -0
- package/src/export.test.ts +37 -0
- package/src/index.ts +34 -0
- package/tests/e2e/construct.spec.ts +60 -0
- package/tests/e2e/fixtures/c3-export/test.c3p +0 -0
- package/tests/e2e/fixtures/c3-export.json +51 -0
- package/tests/e2e/fixtures/html-export/index.html +29 -0
- package/tests/e2e/vitest.config.mts +20 -0
- package/tsconfig.json +10 -0
- package/tsdown.config.ts +15 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { ExtractInputsFromAction } from "@pipelab/plugin-core";
|
|
2
|
+
|
|
3
|
+
//#region src/export-c3p.d.ts
|
|
4
|
+
declare const exportAction: {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
displayString: string;
|
|
8
|
+
meta: {};
|
|
9
|
+
params: {
|
|
10
|
+
username: {
|
|
11
|
+
control: {
|
|
12
|
+
type: "input";
|
|
13
|
+
options: {
|
|
14
|
+
kind: "text";
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
value: string;
|
|
18
|
+
description?: string | undefined;
|
|
19
|
+
platforms?: NodeJS.Platform[] | undefined;
|
|
20
|
+
label: string;
|
|
21
|
+
validator?: (() => any) | undefined;
|
|
22
|
+
required: boolean;
|
|
23
|
+
onNodeUpdate?: ((value: any, settings: undefined<any>) => void) | undefined;
|
|
24
|
+
};
|
|
25
|
+
password: {
|
|
26
|
+
control: {
|
|
27
|
+
type: "input";
|
|
28
|
+
options: {
|
|
29
|
+
kind: "text";
|
|
30
|
+
password: true;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
value: string;
|
|
34
|
+
description?: string | undefined;
|
|
35
|
+
platforms?: NodeJS.Platform[] | undefined;
|
|
36
|
+
label: string;
|
|
37
|
+
validator?: (() => any) | undefined;
|
|
38
|
+
required: boolean;
|
|
39
|
+
onNodeUpdate?: ((value: any, settings: undefined<any>) => void) | undefined;
|
|
40
|
+
};
|
|
41
|
+
version: {
|
|
42
|
+
control: {
|
|
43
|
+
type: "input";
|
|
44
|
+
options: {
|
|
45
|
+
kind: "text";
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
value: string;
|
|
49
|
+
description?: string | undefined;
|
|
50
|
+
platforms?: NodeJS.Platform[] | undefined;
|
|
51
|
+
label: string;
|
|
52
|
+
validator?: (() => any) | undefined;
|
|
53
|
+
required: boolean;
|
|
54
|
+
onNodeUpdate?: ((value: any, settings: undefined<any>) => void) | undefined;
|
|
55
|
+
};
|
|
56
|
+
headless: {
|
|
57
|
+
description: string;
|
|
58
|
+
required: false;
|
|
59
|
+
control: {
|
|
60
|
+
type: "boolean";
|
|
61
|
+
};
|
|
62
|
+
value: boolean;
|
|
63
|
+
label: string;
|
|
64
|
+
};
|
|
65
|
+
timeout: {
|
|
66
|
+
control: {
|
|
67
|
+
type: "input";
|
|
68
|
+
options: {
|
|
69
|
+
kind: "number";
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
value: number;
|
|
73
|
+
description?: string | undefined;
|
|
74
|
+
platforms?: NodeJS.Platform[] | undefined;
|
|
75
|
+
label: string;
|
|
76
|
+
validator?: (() => any) | undefined;
|
|
77
|
+
required: boolean;
|
|
78
|
+
onNodeUpdate?: ((value: any, settings: undefined<any>) => void) | undefined;
|
|
79
|
+
};
|
|
80
|
+
customProfile: {
|
|
81
|
+
value: string | undefined;
|
|
82
|
+
description?: string | undefined;
|
|
83
|
+
platforms?: NodeJS.Platform[] | undefined;
|
|
84
|
+
label: string;
|
|
85
|
+
validator?: (() => any) | undefined;
|
|
86
|
+
required: boolean;
|
|
87
|
+
control: undefined;
|
|
88
|
+
onNodeUpdate?: ((value: any, settings: undefined<any>) => void) | undefined;
|
|
89
|
+
};
|
|
90
|
+
file: {
|
|
91
|
+
value: string | undefined;
|
|
92
|
+
description?: string | undefined;
|
|
93
|
+
platforms?: NodeJS.Platform[] | undefined;
|
|
94
|
+
label: string;
|
|
95
|
+
validator?: (() => any) | undefined;
|
|
96
|
+
required: boolean;
|
|
97
|
+
control: undefined;
|
|
98
|
+
onNodeUpdate?: ((value: any, settings: undefined<any>) => void) | undefined;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
outputs: {
|
|
102
|
+
folder: {
|
|
103
|
+
type: string;
|
|
104
|
+
deprecated: true;
|
|
105
|
+
value: undefined | string;
|
|
106
|
+
label: string;
|
|
107
|
+
};
|
|
108
|
+
parentFolder: {
|
|
109
|
+
type: string;
|
|
110
|
+
deprecated: false;
|
|
111
|
+
value: undefined | string;
|
|
112
|
+
label: string;
|
|
113
|
+
};
|
|
114
|
+
zipFile: {
|
|
115
|
+
type: string;
|
|
116
|
+
deprecated: false;
|
|
117
|
+
value: undefined | string;
|
|
118
|
+
label: string;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
description: string;
|
|
122
|
+
icon: string;
|
|
123
|
+
} & {
|
|
124
|
+
type: "action";
|
|
125
|
+
};
|
|
126
|
+
type Params = ExtractInputsFromAction<typeof exportAction>;
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/index.d.ts
|
|
129
|
+
declare const _default: undefined;
|
|
130
|
+
//#endregion
|
|
131
|
+
export { type Params as ExportParams, _default as default };
|
|
132
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/export-c3p.ts","../src/index.ts"],"mappings":";;;cAWa,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA+DD,MAAA,GAAS,uBAAA,QAA+B,YAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { createAction, createActionRunner, createNodeDefinition, createNumberParam, createPasswordParam, createPathParam, createStringParam, fetchPackage, fileExists, runWithLiveLogs, zipFolder } from "@pipelab/plugin-core";
|
|
3
|
+
import { delimiter, dirname, join } from "node:path";
|
|
4
|
+
import "valibot";
|
|
5
|
+
import { cp, mkdir } from "node:fs/promises";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
//#region src/assets/script.ts
|
|
8
|
+
const registerInstallButtonListener = (page, log) => {
|
|
9
|
+
const installBtn = page.locator("#addonConfirmInstallDialog").locator(".okButton");
|
|
10
|
+
installBtn.waitFor({ timeout: 0 }).then(async () => {
|
|
11
|
+
await installBtn.click();
|
|
12
|
+
log("installBtn clicked");
|
|
13
|
+
registerInstallButtonListener(page, log);
|
|
14
|
+
}).catch(async (e) => {
|
|
15
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
16
|
+
log("installBtn.click() failed", e.message);
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
const registerSaveLoginExpiredistener = (page, log) => {
|
|
20
|
+
const cancelBtn = page.locator("#confirmDialog").locator(".cancelConfirmButton");
|
|
21
|
+
cancelBtn.waitFor({ timeout: 0 }).then(async () => {
|
|
22
|
+
await cancelBtn.click();
|
|
23
|
+
log("cancelBtn clicked");
|
|
24
|
+
registerSaveLoginExpiredistener(page, log);
|
|
25
|
+
}).catch(async (e) => {
|
|
26
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
27
|
+
log("cancelBtn.click() failed", e.message);
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
const registerWebglErrorListener = (page, log) => {
|
|
31
|
+
const okDialog = page.locator("#okDialog");
|
|
32
|
+
const webglErrorButton = okDialog.locator(".okButton");
|
|
33
|
+
webglErrorButton.waitFor({ timeout: 0 }).then(async () => {
|
|
34
|
+
if ((await okDialog.allInnerTexts()).join().toLowerCase().includes("webgl")) {
|
|
35
|
+
await webglErrorButton.click();
|
|
36
|
+
log("webglErrorButton clicked");
|
|
37
|
+
registerWebglErrorListener(page, log);
|
|
38
|
+
}
|
|
39
|
+
}).catch(async (e) => {
|
|
40
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
41
|
+
log("webglErrorButton.click() failed", e.message);
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
const registerDeprecatedFeatures = (page, log) => {
|
|
45
|
+
const okButton = page.locator("#deprecatedFeaturesDialog").locator(".okButton");
|
|
46
|
+
okButton.waitFor({ timeout: 0 }).then(async () => {
|
|
47
|
+
await okButton.click();
|
|
48
|
+
log("okButton clicked");
|
|
49
|
+
registerDeprecatedFeatures(page, log);
|
|
50
|
+
}).catch(async (e) => {
|
|
51
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
52
|
+
log("deprecatedFeatures.okButton.click() failed", e.message);
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
const registerWelcomeToConstructListener = (page, log) => {
|
|
56
|
+
const okButton = page.locator("#welcomeTourDialog").locator(".noThanksLink");
|
|
57
|
+
okButton.waitFor({ timeout: 0 }).then(async () => {
|
|
58
|
+
await okButton.click();
|
|
59
|
+
log("okButton clicked");
|
|
60
|
+
}).catch(async (e) => {
|
|
61
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
62
|
+
log("welcomeTour.okButton.click() failed", e.message);
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
const registerMissingAddonErrorListener = (page, log) => {
|
|
66
|
+
page.locator("#missingAddonsDialog").locator(".okButton").waitFor({ timeout: 0 }).then(async () => {
|
|
67
|
+
throw new Error("Missing addon. You should bundle addons with your project");
|
|
68
|
+
}).catch(async (e) => {
|
|
69
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
70
|
+
log("missingAddon.okButton.waitFor() failed", e.message);
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
const registerNewVersionAvailableListener = (page, log) => {
|
|
74
|
+
const cancelButton = page.locator("#confirmDialog").locator(".cancelConfirmButton");
|
|
75
|
+
cancelButton.waitFor({ timeout: 0 }).then(async () => {
|
|
76
|
+
await cancelButton.click();
|
|
77
|
+
log("cancelButton clicked");
|
|
78
|
+
registerNewVersionAvailableListener(page, log);
|
|
79
|
+
}).catch(async (e) => {
|
|
80
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
81
|
+
log("cancelButton.click() failed", e.message);
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
const script = async (page, log, filePath, username, password, version, downloadDir) => {
|
|
85
|
+
let url = "https://editor.construct.net/";
|
|
86
|
+
if (version) url += version;
|
|
87
|
+
log("Navigating to URL", url);
|
|
88
|
+
await page.goto(url);
|
|
89
|
+
log("after navigating");
|
|
90
|
+
registerWelcomeToConstructListener(page, log);
|
|
91
|
+
registerNewVersionAvailableListener(page, log);
|
|
92
|
+
log("after event");
|
|
93
|
+
await page.waitForTimeout(2e3);
|
|
94
|
+
log("after wait");
|
|
95
|
+
if (username && password) {
|
|
96
|
+
log("Authenticating");
|
|
97
|
+
await page.getByTitle("User account").locator("ui-icon").click();
|
|
98
|
+
await page.getByRole("menuitem", { name: "Log in" }).locator("span").click();
|
|
99
|
+
await page.frameLocator("#loginDialog iframe").getByLabel("Username").fill(username);
|
|
100
|
+
await page.frameLocator("#loginDialog iframe").getByLabel("Password").fill(password);
|
|
101
|
+
const tokenPromise = page.waitForResponse(/https:\/\/account.*\.construct\.net\/login.json/i);
|
|
102
|
+
await page.frameLocator("#loginDialog iframe").getByRole("button", { name: "Log in" }).click();
|
|
103
|
+
if ((await (await tokenPromise).json()).request.status === "error") {
|
|
104
|
+
await page.close();
|
|
105
|
+
throw new Error("Invalid credentials");
|
|
106
|
+
}
|
|
107
|
+
log("Authenticated");
|
|
108
|
+
}
|
|
109
|
+
await page.waitForTimeout(2e3);
|
|
110
|
+
const [fileChooser] = await Promise.all([page.waitForEvent("filechooser"), page.keyboard.press("ControlOrMeta+O")]);
|
|
111
|
+
log("filechooser");
|
|
112
|
+
console.log("filePath", filePath);
|
|
113
|
+
await fileChooser.setFiles([filePath]);
|
|
114
|
+
log("Set file");
|
|
115
|
+
const progressDialog = page.locator("#progressDialog");
|
|
116
|
+
const progessBar = progressDialog.locator(".progressBar");
|
|
117
|
+
log("Waiting for progress dialog");
|
|
118
|
+
await progressDialog.waitFor({ timeout: 0 });
|
|
119
|
+
log("Got loading progress dialog");
|
|
120
|
+
const progressInterval = setInterval(async () => {
|
|
121
|
+
const text = await progessBar.getAttribute("value");
|
|
122
|
+
const textAsNumber = parseFloat(text);
|
|
123
|
+
log("progress", `${(Number.isNaN(textAsNumber) ? 0 : textAsNumber) * 100}%`);
|
|
124
|
+
}, 500);
|
|
125
|
+
const notNowBtn = page.getByText("Not now");
|
|
126
|
+
notNowBtn.waitFor({ timeout: 0 }).then(async () => {
|
|
127
|
+
return notNowBtn.click();
|
|
128
|
+
}).then(() => {
|
|
129
|
+
log("notNowBtn clicked");
|
|
130
|
+
}).catch(async (e) => {
|
|
131
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
132
|
+
log("notNowBtn.click() failed", e.message);
|
|
133
|
+
});
|
|
134
|
+
registerInstallButtonListener(page, log);
|
|
135
|
+
registerWebglErrorListener(page, log);
|
|
136
|
+
registerMissingAddonErrorListener(page, log);
|
|
137
|
+
registerDeprecatedFeatures(page, log);
|
|
138
|
+
registerSaveLoginExpiredistener(page, log);
|
|
139
|
+
log("Waiting for progress dialog to disapear");
|
|
140
|
+
await progressDialog.waitFor({
|
|
141
|
+
state: "detached",
|
|
142
|
+
timeout: 0
|
|
143
|
+
});
|
|
144
|
+
log("Got progress dialog to disapear");
|
|
145
|
+
clearTimeout(progressInterval);
|
|
146
|
+
await page.getByRole("button", { name: "Menu" }).click();
|
|
147
|
+
await page.getByRole("menuitem", { name: "Project" }).click();
|
|
148
|
+
await page.getByRole("menuitem", { name: "Export" }).click();
|
|
149
|
+
log("\"Export\" clicked");
|
|
150
|
+
await page.locator("ui-iconviewitem").filter({ hasText: "Web (HTML5)" }).locator("ui-icon").click();
|
|
151
|
+
log("\"Web\" clicked");
|
|
152
|
+
await page.locator("#exportSelectPlatformDialog").getByRole("button", { name: "Next" }).click();
|
|
153
|
+
await page.getByLabel("Offline support").uncheck();
|
|
154
|
+
log("Disabled offline support");
|
|
155
|
+
await page.locator("#exportStandardOptionsDialog").getByRole("button", { name: "Next" }).click();
|
|
156
|
+
log("\"Next\" clicked");
|
|
157
|
+
const downloadPromise = page.waitForEvent("download");
|
|
158
|
+
await page.locator(".downloadExportedProject").click();
|
|
159
|
+
const download = await downloadPromise;
|
|
160
|
+
await page.getByRole("button", { name: "OK" }).click();
|
|
161
|
+
log("\"Download\" clicked");
|
|
162
|
+
const finalPath = join(downloadDir, download.suggestedFilename());
|
|
163
|
+
await download.saveAs(finalPath);
|
|
164
|
+
log("File Downloaded");
|
|
165
|
+
await page.close();
|
|
166
|
+
return finalPath;
|
|
167
|
+
};
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/export-shared.ts
|
|
170
|
+
const platform = process.platform;
|
|
171
|
+
const { LOCALAPPDATA, XDG_CONFIG_HOME } = process.env;
|
|
172
|
+
const isCI = process.env.CI === "true";
|
|
173
|
+
let baseProfile;
|
|
174
|
+
if (platform === "win32") baseProfile = join(LOCALAPPDATA ?? "", "Google", "Chrome", "User Data");
|
|
175
|
+
else if (platform === "linux") baseProfile = join(XDG_CONFIG_HOME ?? "", "google-chrome");
|
|
176
|
+
else if (platform === "darwin") baseProfile = join(homedir(), "Library", "Application Support", "Google", "Chrome");
|
|
177
|
+
const sharedParams = {
|
|
178
|
+
username: createStringParam("", {
|
|
179
|
+
label: "Username",
|
|
180
|
+
required: false,
|
|
181
|
+
description: "Your Construct username"
|
|
182
|
+
}),
|
|
183
|
+
password: createPasswordParam("", {
|
|
184
|
+
description: "Your Construct password. Will only be used locally to automate the export on Construct website via a local browser. Will not be sent to any server.",
|
|
185
|
+
required: false,
|
|
186
|
+
label: "Password"
|
|
187
|
+
}),
|
|
188
|
+
version: createStringParam("", {
|
|
189
|
+
description: "The Construct version you want to use",
|
|
190
|
+
label: "Version",
|
|
191
|
+
required: false
|
|
192
|
+
}),
|
|
193
|
+
headless: {
|
|
194
|
+
description: "Whether to show the browser while export",
|
|
195
|
+
required: false,
|
|
196
|
+
control: { type: "boolean" },
|
|
197
|
+
value: false,
|
|
198
|
+
label: "Start headless"
|
|
199
|
+
},
|
|
200
|
+
timeout: createNumberParam(120, {
|
|
201
|
+
description: "The timeout (in seconds) to close the browser if it's stuck",
|
|
202
|
+
required: false,
|
|
203
|
+
label: "Timeout"
|
|
204
|
+
}),
|
|
205
|
+
customProfile: createPathParam(void 0, {
|
|
206
|
+
required: false,
|
|
207
|
+
description: "Use your own profile (X:\\Users\\XXX\\AppData\\Local\\Google\\Chrome\\User Data). Usefull if you want to reuse plugins installed in your current browser",
|
|
208
|
+
control: {
|
|
209
|
+
type: "path",
|
|
210
|
+
options: {
|
|
211
|
+
properties: ["openDirectory"],
|
|
212
|
+
defaultPath: baseProfile
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
label: "Custom profile"
|
|
216
|
+
})
|
|
217
|
+
};
|
|
218
|
+
const exportc3p = async (file, { cwd, log, inputs, setOutput, paths, abortSignal, context: ctx }) => {
|
|
219
|
+
let browserContext = void 0;
|
|
220
|
+
let browser = void 0;
|
|
221
|
+
abortSignal.addEventListener("abort", () => {
|
|
222
|
+
console.error("aborted");
|
|
223
|
+
browserContext?.close();
|
|
224
|
+
});
|
|
225
|
+
const newInputs = inputs;
|
|
226
|
+
const { thirdparty, node, pnpm } = paths;
|
|
227
|
+
const browserName = "chromium";
|
|
228
|
+
const { packageDir: playwrightPkgPath } = await fetchPackage("playwright-core", "1.48.2", {
|
|
229
|
+
installDeps: true,
|
|
230
|
+
context: ctx
|
|
231
|
+
});
|
|
232
|
+
const playwrightCli = join(playwrightPkgPath, "cli.js");
|
|
233
|
+
const browsersPath = join(thirdparty, "playwright-browsers");
|
|
234
|
+
process.env.PLAYWRIGHT_BROWSERS_PATH = browsersPath;
|
|
235
|
+
log("Downloading browser to", browsersPath);
|
|
236
|
+
await runWithLiveLogs(node, [
|
|
237
|
+
playwrightCli,
|
|
238
|
+
"install",
|
|
239
|
+
browserName
|
|
240
|
+
], {
|
|
241
|
+
env: {
|
|
242
|
+
...process.env,
|
|
243
|
+
PLAYWRIGHT_BROWSERS_PATH: browsersPath,
|
|
244
|
+
PATH: `${dirname(node)}${delimiter}${process.env.PATH}`
|
|
245
|
+
},
|
|
246
|
+
cancelSignal: abortSignal
|
|
247
|
+
}, log, {
|
|
248
|
+
onStdout(data) {
|
|
249
|
+
log(data);
|
|
250
|
+
},
|
|
251
|
+
onStderr(data) {
|
|
252
|
+
log(data);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
const playwrightModule = createRequire(import.meta.url)(join(playwrightPkgPath, "index.js"));
|
|
256
|
+
const playwright = playwrightModule.default || playwrightModule;
|
|
257
|
+
const downloadDir = join(cwd, "playwright");
|
|
258
|
+
log("Browser downloaded to", downloadDir);
|
|
259
|
+
log("Exporting construct project");
|
|
260
|
+
console.log("newInputs", newInputs);
|
|
261
|
+
const browserInstance = playwright[browserName];
|
|
262
|
+
let version = newInputs.version;
|
|
263
|
+
if (version && /^\d+$/.test(version)) version = `r${version}`;
|
|
264
|
+
const headless = newInputs.headless;
|
|
265
|
+
if (newInputs.customProfile) {
|
|
266
|
+
const customProfile = join(cwd, "playwright-profile");
|
|
267
|
+
await mkdir(customProfile, { recursive: true });
|
|
268
|
+
const indexedDbPathSource = join(newInputs.customProfile, "Default", "IndexedDB");
|
|
269
|
+
const indexedDbPathDestination = join(customProfile, "Default", "IndexedDB");
|
|
270
|
+
for (const p of ["https_editor.construct.net_0.indexeddb.blob", "https_editor.construct.net_0.indexeddb.leveldb"]) await cp(join(indexedDbPathSource, p), join(indexedDbPathDestination, p), { recursive: true });
|
|
271
|
+
browserContext = await browserInstance.launchPersistentContext(customProfile, {
|
|
272
|
+
headless,
|
|
273
|
+
locale: "en-US",
|
|
274
|
+
recordVideo: isCI ? { dir: join(process.cwd(), "playwright") } : void 0
|
|
275
|
+
});
|
|
276
|
+
} else {
|
|
277
|
+
browser = await browserInstance.launch({ headless });
|
|
278
|
+
browserContext = await browser.newContext({
|
|
279
|
+
locale: "en-US",
|
|
280
|
+
recordVideo: isCI ? { dir: join(process.cwd(), "playwright") } : void 0
|
|
281
|
+
});
|
|
282
|
+
await browserContext?.clearPermissions();
|
|
283
|
+
}
|
|
284
|
+
if (!browserContext) throw new Error("Failed to initialize browser context");
|
|
285
|
+
const page = await browserContext.newPage();
|
|
286
|
+
page.setDefaultTimeout(newInputs.timeout * 1e3);
|
|
287
|
+
await page.addInitScript(() => {
|
|
288
|
+
delete self.showOpenFilePicker;
|
|
289
|
+
});
|
|
290
|
+
page.on("filechooser", (worker) => {
|
|
291
|
+
console.log("filechooser created: " + worker.page.name);
|
|
292
|
+
});
|
|
293
|
+
try {
|
|
294
|
+
const result = await script(page, log, file, newInputs.username, newInputs.password, version, downloadDir);
|
|
295
|
+
log("Setting output result to ", result);
|
|
296
|
+
setOutput("folder", result);
|
|
297
|
+
setOutput("parentFolder", dirname(result));
|
|
298
|
+
setOutput("zipFile", result);
|
|
299
|
+
} catch (e) {
|
|
300
|
+
log("error, no result, crashed", e);
|
|
301
|
+
throw new Error("ConstructExport failed: " + e.message);
|
|
302
|
+
} finally {
|
|
303
|
+
if (browserContext) await browserContext.close();
|
|
304
|
+
if (browser) await browser.close();
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
const exportAction = createAction({
|
|
308
|
+
id: "export-construct-project",
|
|
309
|
+
name: "Export .c3p",
|
|
310
|
+
displayString: "`Export project ${fmt.param(params.file, 'primary', 'No path selected')} with version ${params.version ? params.version : 'stable'}`",
|
|
311
|
+
meta: {},
|
|
312
|
+
params: {
|
|
313
|
+
file: createPathParam("", {
|
|
314
|
+
label: "File (.c3p)",
|
|
315
|
+
required: true,
|
|
316
|
+
control: {
|
|
317
|
+
type: "path",
|
|
318
|
+
label: "Pick a file (.c3p)",
|
|
319
|
+
options: {
|
|
320
|
+
properties: ["openFile"],
|
|
321
|
+
filters: [{
|
|
322
|
+
name: "Construct Project",
|
|
323
|
+
extensions: ["c3p"]
|
|
324
|
+
}],
|
|
325
|
+
title: "aaaa",
|
|
326
|
+
message: "bbbb"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}),
|
|
330
|
+
...sharedParams
|
|
331
|
+
},
|
|
332
|
+
outputs: {
|
|
333
|
+
folder: {
|
|
334
|
+
type: "path",
|
|
335
|
+
deprecated: true,
|
|
336
|
+
value: void 0,
|
|
337
|
+
label: "Exported zip"
|
|
338
|
+
},
|
|
339
|
+
parentFolder: {
|
|
340
|
+
type: "path",
|
|
341
|
+
deprecated: false,
|
|
342
|
+
value: void 0,
|
|
343
|
+
label: "Path to parent folder of exported zip"
|
|
344
|
+
},
|
|
345
|
+
zipFile: {
|
|
346
|
+
type: "path",
|
|
347
|
+
deprecated: false,
|
|
348
|
+
value: void 0,
|
|
349
|
+
label: "Exported zip"
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
description: "Export construct project from .c3p file",
|
|
353
|
+
icon: ""
|
|
354
|
+
});
|
|
355
|
+
const ExportActionRunner = createActionRunner(async (options) => {
|
|
356
|
+
const file = options.inputs.file;
|
|
357
|
+
if (!await fileExists(file)) throw new Error("You must specify a valid .c3p file");
|
|
358
|
+
await exportc3p(file, options);
|
|
359
|
+
options.log("exportc3p done");
|
|
360
|
+
});
|
|
361
|
+
const exportProjectAction = createAction({
|
|
362
|
+
id: "export-construct-project-folder",
|
|
363
|
+
name: "Export folder",
|
|
364
|
+
displayString: "`Export project ${params.version ? `r${params.version}` : ''}`",
|
|
365
|
+
meta: {},
|
|
366
|
+
params: {
|
|
367
|
+
folder: createPathParam("", {
|
|
368
|
+
required: true,
|
|
369
|
+
label: "Folder",
|
|
370
|
+
control: {
|
|
371
|
+
type: "path",
|
|
372
|
+
options: { properties: ["openDirectory"] }
|
|
373
|
+
}
|
|
374
|
+
}),
|
|
375
|
+
...sharedParams
|
|
376
|
+
},
|
|
377
|
+
outputs: {
|
|
378
|
+
folder: {
|
|
379
|
+
type: "path",
|
|
380
|
+
deprecated: true,
|
|
381
|
+
value: void 0,
|
|
382
|
+
label: "Exported zip"
|
|
383
|
+
},
|
|
384
|
+
parentFolder: {
|
|
385
|
+
type: "path",
|
|
386
|
+
deprecated: false,
|
|
387
|
+
value: void 0,
|
|
388
|
+
label: "Path to parent folder of exported zip"
|
|
389
|
+
},
|
|
390
|
+
zipFile: {
|
|
391
|
+
type: "path",
|
|
392
|
+
deprecated: false,
|
|
393
|
+
value: void 0,
|
|
394
|
+
label: "Exported zip"
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
description: "Export construct project from folder",
|
|
398
|
+
icon: ""
|
|
399
|
+
});
|
|
400
|
+
const ExportProjectActionRunner = createActionRunner(async (options) => {
|
|
401
|
+
if (!await fileExists(options.inputs.folder)) throw new Error("You must specify a valid construct project folder");
|
|
402
|
+
const outputPath = join(options.cwd, "c3_tmp_proj.c3p");
|
|
403
|
+
await exportc3p(await zipFolder(options.inputs.folder, outputPath, options.log), options);
|
|
404
|
+
});
|
|
405
|
+
//#endregion
|
|
406
|
+
//#region src/index.ts
|
|
407
|
+
const icon = new URL("./assets/construct.webp", import.meta.url).href;
|
|
408
|
+
var src_default = createNodeDefinition({
|
|
409
|
+
description: "Construct",
|
|
410
|
+
name: "Construct",
|
|
411
|
+
id: "construct",
|
|
412
|
+
icon: {
|
|
413
|
+
type: "image",
|
|
414
|
+
image: icon
|
|
415
|
+
},
|
|
416
|
+
nodes: [{
|
|
417
|
+
node: exportAction,
|
|
418
|
+
runner: ExportActionRunner
|
|
419
|
+
}, {
|
|
420
|
+
node: exportProjectAction,
|
|
421
|
+
runner: ExportProjectActionRunner
|
|
422
|
+
}],
|
|
423
|
+
validators: []
|
|
424
|
+
});
|
|
425
|
+
//#endregion
|
|
426
|
+
export { src_default as default };
|
|
427
|
+
|
|
428
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["ID"],"sources":["../src/assets/script.ts","../src/export-shared.ts","../src/export-c3p.ts","../src/export-project.ts","../src/index.ts"],"sourcesContent":["import { Page } from \"playwright\";\nimport { join } from \"node:path\";\n\nconst registerInstallButtonListener = (page: Page, log: typeof console.log) => {\n // as soon as it appear, without blocking flow\n // accept installing plugins\n const installDialog = page.locator(\"#addonConfirmInstallDialog\");\n const installBtn = installDialog.locator(\".okButton\");\n installBtn\n .waitFor({\n timeout: 0,\n })\n .then(async () => {\n await installBtn.click();\n log(\"installBtn clicked\");\n registerInstallButtonListener(page, log);\n })\n .catch(async (e) => {\n if (e.message.includes(\"Target page, context or browser has been closed\")) return;\n log(\"installBtn.click() failed\", e.message);\n });\n};\n\nconst registerSaveLoginExpiredistener = (page: Page, log: typeof console.log) => {\n // as soon as it appear, without blocking flow\n // accept installing plugins\n const installDialog = page.locator(\"#confirmDialog\");\n const cancelBtn = installDialog.locator(\".cancelConfirmButton\");\n cancelBtn\n .waitFor({\n timeout: 0,\n })\n .then(async () => {\n await cancelBtn.click();\n log(\"cancelBtn clicked\");\n registerSaveLoginExpiredistener(page, log);\n })\n .catch(async (e) => {\n if (e.message.includes(\"Target page, context or browser has been closed\")) return;\n log(\"cancelBtn.click() failed\", e.message);\n });\n};\n\nconst registerWebglErrorListener = (page: Page, log: typeof console.log) => {\n // as soon as it appear, without blocking flow\n // ignore webgl error\n const okDialog = page.locator(\"#okDialog\");\n const webglErrorButton = okDialog.locator(\".okButton\");\n webglErrorButton\n .waitFor({\n timeout: 0,\n })\n .then(async () => {\n const text = await okDialog.allInnerTexts();\n\n if (text.join().toLowerCase().includes(\"webgl\")) {\n await webglErrorButton.click();\n log(\"webglErrorButton clicked\");\n registerWebglErrorListener(page, log);\n }\n })\n .catch(async (e) => {\n if (e.message.includes(\"Target page, context or browser has been closed\")) return;\n log(\"webglErrorButton.click() failed\", e.message);\n });\n};\nconst registerDeprecatedFeatures = (page: Page, log: typeof console.log) => {\n // as soon as it appear, without blocking flow\n // ignore deprecated feature\n const deprecatedFeaturesDialog = page.locator(\"#deprecatedFeaturesDialog\");\n const okButton = deprecatedFeaturesDialog.locator(\".okButton\");\n okButton\n .waitFor({\n timeout: 0,\n })\n .then(async () => {\n await okButton.click();\n log(\"okButton clicked\");\n registerDeprecatedFeatures(page, log);\n })\n .catch(async (e) => {\n if (e.message.includes(\"Target page, context or browser has been closed\")) return;\n log(\"deprecatedFeatures.okButton.click() failed\", e.message);\n });\n};\nconst registerWelcomeToConstructListener = (page: Page, log: typeof console.log) => {\n // as soon as it appear, without blocking flow\n // ignore deprecated feature\n const welcomeTourDialog = page.locator(\"#welcomeTourDialog\");\n const okButton = welcomeTourDialog.locator(\".noThanksLink\");\n okButton\n .waitFor({\n timeout: 0,\n })\n .then(async () => {\n await okButton.click();\n log(\"okButton clicked\");\n // registerWelcomeToConstructListener(page, log); // usually only once\n })\n .catch(async (e) => {\n if (e.message.includes(\"Target page, context or browser has been closed\")) return;\n log(\"welcomeTour.okButton.click() failed\", e.message);\n });\n};\n\nconst registerMissingAddonErrorListener = (page: Page, log: typeof console.log) => {\n // as soon as it appear, without blocking flow\n // ignore missing addon and throw\n const okDialog = page.locator(\"#missingAddonsDialog\");\n const webglErrorButton = okDialog.locator(\".okButton\");\n webglErrorButton\n .waitFor({\n timeout: 0,\n })\n .then(async () => {\n throw new Error(\"Missing addon. You should bundle addons with your project\");\n })\n .catch(async (e) => {\n if (e.message.includes(\"Target page, context or browser has been closed\")) return;\n log(\"missingAddon.okButton.waitFor() failed\", e.message);\n });\n};\n\nconst registerNewVersionAvailableListener = (page: Page, log: typeof console.log) => {\n // as soon as it appear, without blocking flow\n // ignore new version available and throw\n const newVersionAvailableDialog = page.locator(\"#confirmDialog\");\n const cancelButton = newVersionAvailableDialog.locator(\".cancelConfirmButton\");\n cancelButton\n .waitFor({\n timeout: 0,\n })\n .then(async () => {\n await cancelButton.click();\n log(\"cancelButton clicked\");\n registerNewVersionAvailableListener(page, log);\n })\n .catch(async (e) => {\n if (e.message.includes(\"Target page, context or browser has been closed\")) return;\n log(\"cancelButton.click() failed\", e.message);\n });\n};\n\nexport const script = async (\n page: Page,\n log: typeof console.log,\n filePath: string,\n username: string | undefined,\n password: string | undefined,\n version: string | undefined,\n downloadDir: string,\n) => {\n let url = \"https://editor.construct.net/\";\n if (version) {\n url += version;\n }\n log(\"Navigating to URL\", url);\n // const serviceWorkerPromise = page.waitForEvent(\"serviceworker\");\n await page.goto(url);\n log(\"after navigating\");\n\n // const serviceworker = await serviceWorkerPromise;\n registerWelcomeToConstructListener(page, log);\n registerNewVersionAvailableListener(page, log);\n\n log(\"after event\");\n\n await page.waitForTimeout(2000);\n log(\"after wait\");\n\n if (username && password) {\n log(\"Authenticating\");\n await page.getByTitle(\"User account\").locator(\"ui-icon\").click();\n await page.getByRole(\"menuitem\", { name: \"Log in\" }).locator(\"span\").click();\n await page.frameLocator(\"#loginDialog iframe\").getByLabel(\"Username\").fill(username);\n await page.frameLocator(\"#loginDialog iframe\").getByLabel(\"Password\").fill(password);\n\n const tokenPromise = page.waitForResponse(/https:\\/\\/account.*\\.construct\\.net\\/login.json/i);\n\n await page.frameLocator(\"#loginDialog iframe\").getByRole(\"button\", { name: \"Log in\" }).click();\n\n const response = await tokenPromise;\n const jsonResponse = await response.json();\n\n if (jsonResponse.request.status === \"error\") {\n await page.close();\n\n throw new Error(\"Invalid credentials\");\n }\n log(\"Authenticated\");\n }\n\n await page.waitForTimeout(2000);\n\n const [fileChooser] = await Promise.all([\n page.waitForEvent(\"filechooser\"),\n page.keyboard.press(\"ControlOrMeta+O\"),\n ]);\n log(\"filechooser\");\n\n console.log(\"filePath\", filePath);\n await fileChooser.setFiles([filePath]);\n log(\"Set file\");\n\n // await page.getByText(\"Not now\").click({\n // timeout: 1000\n // });\n\n const progressDialog = page.locator(\"#progressDialog\");\n // <progress class=\"progressBar\" value=\"0.293996941070648\" max=\"1\"></progress>\n const progessBar = progressDialog.locator(\".progressBar\");\n\n log(\"Waiting for progress dialog\");\n await progressDialog.waitFor({\n timeout: 0,\n });\n log(\"Got loading progress dialog\");\n\n const progressInterval = setInterval(async () => {\n const text = await progessBar.getAttribute(\"value\");\n const textAsNumber = parseFloat(text);\n const finalText = Number.isNaN(textAsNumber) ? 0 : textAsNumber;\n log(\"progress\", `${finalText * 100}%`);\n }, 500);\n\n // as soon as it appear, without blocking flow\n // ignore asking for update\n const notNowBtn = page.getByText(\"Not now\");\n notNowBtn\n .waitFor({\n timeout: 0,\n })\n .then(async () => {\n return notNowBtn.click();\n })\n .then(() => {\n log(\"notNowBtn clicked\");\n })\n .catch(async (e) => {\n if (e.message.includes(\"Target page, context or browser has been closed\")) return;\n log(\"notNowBtn.click() failed\", e.message);\n });\n\n registerInstallButtonListener(page, log);\n registerWebglErrorListener(page, log);\n registerMissingAddonErrorListener(page, log);\n registerDeprecatedFeatures(page, log);\n registerSaveLoginExpiredistener(page, log);\n\n log(\"Waiting for progress dialog to disapear\");\n await progressDialog.waitFor({\n state: \"detached\",\n timeout: 0,\n });\n log(\"Got progress dialog to disapear\");\n clearTimeout(progressInterval);\n\n await page.getByRole(\"button\", { name: \"Menu\" }).click();\n await page.getByRole(\"menuitem\", { name: \"Project\" }).click();\n await page.getByRole(\"menuitem\", { name: \"Export\" }).click();\n log('\"Export\" clicked');\n await page\n .locator(\"ui-iconviewitem\")\n .filter({ hasText: \"Web (HTML5)\" })\n .locator(\"ui-icon\")\n .click();\n log('\"Web\" clicked');\n\n await page.locator(\"#exportSelectPlatformDialog\").getByRole(\"button\", { name: \"Next\" }).click();\n\n await page.getByLabel(\"Offline support\").uncheck();\n log(\"Disabled offline support\");\n\n await page.locator(\"#exportStandardOptionsDialog\").getByRole(\"button\", { name: \"Next\" }).click();\n log('\"Next\" clicked');\n const downloadPromise = page.waitForEvent(\"download\");\n await page.locator(\".downloadExportedProject\").click();\n const download = await downloadPromise;\n await page.getByRole(\"button\", { name: \"OK\" }).click();\n log('\"Download\" clicked');\n\n const finalPath = join(downloadDir, download.suggestedFilename());\n await download.saveAs(finalPath);\n log(\"File Downloaded\");\n\n await page.close();\n return finalPath;\n};\n","import {\n Action,\n ActionRunnerData,\n createNumberParam,\n createPasswordParam,\n createPathParam,\n createStringParam,\n InputsDefinition,\n ParamsToInput,\n runWithLiveLogs,\n fetchPackage,\n} from \"@pipelab/plugin-core\";\nimport { script } from \"./assets/script.js\";\nimport * as v from \"valibot\";\nimport { BrowserContext } from \"playwright\";\nimport { dirname, join, delimiter } from \"node:path\";\nimport { cp, mkdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { createRequire } from \"node:module\";\n\nconst platform = process.platform;\nconst { LOCALAPPDATA, XDG_CONFIG_HOME } = process.env;\n\nconst isCI = process.env.CI === \"true\";\n\nlet baseProfile;\nif (platform === \"win32\") {\n baseProfile = join(LOCALAPPDATA ?? \"\", \"Google\", \"Chrome\", \"User Data\");\n} else if (platform === \"linux\") {\n baseProfile = join(XDG_CONFIG_HOME ?? \"\", \"google-chrome\");\n} else if (platform === \"darwin\") {\n baseProfile = join(homedir(), \"Library\", \"Application Support\", \"Google\", \"Chrome\");\n}\n\nexport const sharedParams = {\n username: createStringParam(\"\", {\n label: \"Username\",\n required: false,\n description: \"Your Construct username\",\n }),\n password: createPasswordParam(\"\", {\n description:\n \"Your Construct password. Will only be used locally to automate the export on Construct website via a local browser. Will not be sent to any server.\",\n required: false,\n label: \"Password\",\n }),\n version: createStringParam(\"\", {\n description: \"The Construct version you want to use\",\n label: \"Version\",\n required: false,\n }),\n headless: {\n description: \"Whether to show the browser while export\",\n required: false,\n control: {\n type: \"boolean\",\n },\n value: false,\n label: \"Start headless\",\n },\n timeout: createNumberParam(120, {\n description: \"The timeout (in seconds) to close the browser if it's stuck\",\n required: false,\n label: \"Timeout\",\n }),\n // customBrowser: {\n // description: 'Start your own browser rather than the predefined one',\n // control: {\n // type: 'path',\n // options: {\n // properties: ['openFile']\n // }\n // },\n // label: 'Custom browser',\n // value: ''\n // },\n customProfile: createPathParam(undefined, {\n required: false,\n description:\n \"Use your own profile (X:\\\\Users\\\\XXX\\\\AppData\\\\Local\\\\Google\\\\Chrome\\\\User Data). Usefull if you want to reuse plugins installed in your current browser\",\n control: {\n type: \"path\",\n options: {\n properties: [\"openDirectory\"],\n defaultPath: baseProfile,\n },\n },\n label: \"Custom profile\",\n }),\n // addonsFolder: {\n // description: 'Folder containing addons to import in the editor',\n // required: false,\n // control: {\n // type: 'path',\n // options: {\n // buttonLabel: 'Addons folder',\n // properties: ['openDirectory']\n // }\n // },\n // value: '',\n // label: 'Addons folder'\n // }\n} satisfies InputsDefinition;\n\ntype Inputs = ParamsToInput<typeof sharedParams>;\n\nexport const exportc3p = async <ACTION extends Action>(\n file: string,\n { cwd, log, inputs, setOutput, paths, abortSignal, context: ctx }: ActionRunnerData<ACTION>,\n) => {\n let browserContext: BrowserContext | undefined = undefined;\n let browser: any | undefined = undefined;\n\n abortSignal.addEventListener(\"abort\", () => {\n console.error(\"aborted\");\n\n browserContext?.close();\n });\n const newInputs = inputs as Inputs;\n\n // const { addonsFolder } = newInputs\n\n const { thirdparty, node, pnpm } = paths;\n\n const browserName: \"chromium\" | \"firefox\" | \"webkit\" = \"chromium\";\n\n const { packageDir: playwrightPkgPath } = await fetchPackage(\"playwright-core\", \"1.48.2\", {\n installDeps: true,\n context: ctx,\n });\n const playwrightCli = join(playwrightPkgPath, \"cli.js\");\n const browsersPath = join(thirdparty, \"playwright-browsers\");\n\n process.env.PLAYWRIGHT_BROWSERS_PATH = browsersPath;\n\n log(\"Downloading browser to\", browsersPath);\n await runWithLiveLogs(\n node,\n [playwrightCli, \"install\", browserName],\n {\n env: {\n ...process.env,\n PLAYWRIGHT_BROWSERS_PATH: browsersPath,\n PATH: `${dirname(node)}${delimiter}${process.env.PATH}`,\n },\n cancelSignal: abortSignal,\n },\n log,\n {\n onStdout(data) {\n log(data);\n },\n onStderr(data) {\n log(data);\n },\n },\n );\n\n const require = createRequire(import.meta.url);\n const playwrightModule = require(join(playwrightPkgPath, \"index.js\"));\n const playwright = playwrightModule.default || playwrightModule;\n\n const downloadDir = join(cwd, \"playwright\");\n\n log(\"Browser downloaded to\", downloadDir);\n\n log(\"Exporting construct project\");\n\n console.log(\"newInputs\", newInputs);\n\n const browserInstance = playwright[browserName];\n\n let version = newInputs.version;\n // if version is full digit, prepend \"r\", otherwise, use as is\n if (version && /^\\d+$/.test(version as string)) {\n version = `r${version}`;\n }\n const headless = newInputs.headless;\n\n // if (newInputs.customBrowser && !newInputs.customProfile) {\n // throw new Error('You must specify a custom profile when using a custom browser')\n // }\n\n // if (!newInputs.customBrowser && newInputs.customProfile) {\n // throw new Error('You must specify a custom browser when using a custom profile')\n // }\n\n // if (newInputs.customBrowser && newInputs.customProfile) {\n if (newInputs.customProfile) {\n const customProfile = join(cwd, \"playwright-profile\");\n\n await mkdir(customProfile, {\n recursive: true,\n });\n\n const indexedDbPathSource = join(newInputs.customProfile as string, \"Default\", \"IndexedDB\");\n const indexedDbPathDestination = join(customProfile, \"Default\", \"IndexedDB\");\n const pathsToCopy = [\n \"https_editor.construct.net_0.indexeddb.blob\",\n \"https_editor.construct.net_0.indexeddb.leveldb\",\n ];\n\n for (const p of pathsToCopy) {\n const from = join(indexedDbPathSource, p);\n const to = join(indexedDbPathDestination, p);\n await cp(from, to, { recursive: true });\n }\n\n browserContext = await browserInstance.launchPersistentContext(customProfile, {\n headless: headless as boolean,\n locale: \"en-US\",\n recordVideo: isCI\n ? {\n dir: join(process.cwd(), \"playwright\"),\n }\n : undefined,\n });\n } else {\n browser = await browserInstance.launch({\n headless: headless as boolean,\n });\n\n browserContext = await browser.newContext({\n locale: \"en-US\",\n recordVideo: isCI\n ? {\n dir: join(process.cwd(), \"playwright\"),\n }\n : undefined,\n });\n await browserContext?.clearPermissions();\n }\n\n if (!browserContext) {\n throw new Error(\"Failed to initialize browser context\");\n }\n\n const page = await browserContext.newPage();\n\n page.setDefaultTimeout((newInputs.timeout as number) * 1000);\n\n // this exact sequn=ence make it work\n await page.addInitScript(() => {\n // @ts-expect-error dds\n delete self.showOpenFilePicker;\n });\n page.on(\"filechooser\", (worker) => {\n console.log(\"filechooser created: \" + worker.page.name);\n });\n // ---------------------------------\n\n try {\n const result = await script(\n page,\n log,\n file,\n newInputs.username as string,\n newInputs.password as string,\n version as string,\n downloadDir,\n // addonsFolder,\n );\n\n log(\"Setting output result to \", result);\n\n setOutput(\"folder\", result); // deprecated\n\n setOutput(\"parentFolder\", dirname(result));\n setOutput(\"zipFile\", result);\n } catch (e: any) {\n log(\"error, no result, crashed\", e);\n throw new Error(\"ConstructExport failed: \" + e.message);\n } finally {\n if (browserContext) {\n await browserContext.close();\n }\n if (browser) {\n await browser.close();\n }\n }\n};\n\nexport const constructVersionValidator = (options: any) => {\n void options;\n return v.pipe(v.string(), v.regex(/^\\d+(-\\d+)?$/, \"Invalid version\"));\n};\n","import {\n ExtractInputsFromAction,\n createAction,\n createActionRunner,\n createPathParam,\n fileExists,\n} from \"@pipelab/plugin-core\";\nimport { exportc3p, sharedParams } from \"./export-shared.js\";\n\nexport const ID = \"export-construct-project\";\n\nexport const exportAction = createAction({\n id: ID,\n name: \"Export .c3p\",\n displayString:\n \"`Export project ${fmt.param(params.file, 'primary', 'No path selected')} with version ${params.version ? params.version : 'stable'}`\",\n meta: {},\n params: {\n file: createPathParam(\"\", {\n label: \"File (.c3p)\",\n required: true,\n control: {\n type: \"path\",\n label: \"Pick a file (.c3p)\",\n options: {\n properties: [\"openFile\"],\n filters: [{ name: \"Construct Project\", extensions: [\"c3p\"] }],\n title: \"aaaa\",\n message: \"bbbb\",\n },\n },\n }),\n ...sharedParams,\n },\n outputs: {\n folder: {\n type: \"path\",\n deprecated: true,\n value: undefined as undefined | string,\n label: \"Exported zip\",\n // schema: schema.string()\n },\n parentFolder: {\n type: \"path\",\n deprecated: false,\n value: undefined as undefined | string,\n label: \"Path to parent folder of exported zip\",\n // schema: schema.string()\n },\n zipFile: {\n type: \"path\",\n deprecated: false,\n value: undefined as undefined | string,\n label: \"Exported zip\",\n // schema: schema.string()\n },\n },\n description: \"Export construct project from .c3p file\",\n icon: \"\",\n});\n\nexport const ExportActionRunner = createActionRunner<typeof exportAction>(async (options) => {\n const file = options.inputs.file;\n\n const c3pFileExists = await fileExists(file);\n\n if (!c3pFileExists) {\n throw new Error(\"You must specify a valid .c3p file\");\n }\n\n await exportc3p(file, options);\n options.log(\"exportc3p done\");\n});\n\nexport type Params = ExtractInputsFromAction<typeof exportAction>;\n","import { join } from \"node:path\";\nimport {\n ExtractInputsFromAction,\n createAction,\n createActionRunner,\n createPathParam,\n fileExists,\n} from \"@pipelab/plugin-core\";\nimport { exportc3p, sharedParams } from \"./export-shared.js\";\nimport { throttle } from \"es-toolkit\";\nimport { zipFolder } from \"@pipelab/plugin-core\";\n\nexport const ID = \"export-construct-project-folder\";\n\nexport const exportProjectAction = createAction({\n id: ID,\n name: \"Export folder\",\n displayString: \"`Export project ${params.version ? `r${params.version}` : ''}`\",\n meta: {},\n params: {\n folder: createPathParam(\"\", {\n required: true,\n label: \"Folder\",\n control: {\n type: \"path\",\n options: {\n properties: [\"openDirectory\"],\n },\n },\n }),\n ...sharedParams,\n },\n outputs: {\n folder: {\n type: \"path\",\n deprecated: true,\n value: undefined as undefined | string,\n label: \"Exported zip\",\n // schema: schema.string()\n },\n parentFolder: {\n type: \"path\",\n deprecated: false,\n value: undefined as undefined | string,\n label: \"Path to parent folder of exported zip\",\n // schema: schema.string()\n },\n zipFile: {\n type: \"path\",\n deprecated: false,\n value: undefined as undefined | string,\n label: \"Exported zip\",\n // schema: schema.string()\n },\n },\n description: \"Export construct project from folder\",\n icon: \"\",\n});\n\nexport const ExportProjectActionRunner = createActionRunner<typeof exportProjectAction>(\n async (options) => {\n const c3pFolderExists = await fileExists(options.inputs.folder);\n if (!c3pFolderExists) {\n throw new Error(\"You must specify a valid construct project folder\");\n }\n\n const outputPath = join(options.cwd, \"c3_tmp_proj.c3p\");\n\n const to = await zipFolder(options.inputs.folder, outputPath, options.log);\n\n await exportc3p(to, options);\n },\n);\n\nexport type Params = ExtractInputsFromAction<typeof exportProjectAction>;\n","import { createNodeDefinition } from \"@pipelab/plugin-core\";\nimport { exportAction, ExportActionRunner } from \"./export-c3p\";\nimport { exportProjectAction, ExportProjectActionRunner } from \"./export-project\";\nconst icon = new URL(\"./assets/construct.webp\", import.meta.url).href;\nimport { constructVersionValidator } from \"./export-shared\";\n\nexport default createNodeDefinition({\n description: \"Construct\",\n name: \"Construct\",\n id: \"construct\",\n icon: {\n type: \"image\",\n image: icon,\n },\n nodes: [\n {\n node: exportAction,\n runner: ExportActionRunner,\n },\n {\n node: exportProjectAction,\n runner: ExportProjectActionRunner,\n },\n ],\n validators: [\n // {\n // id: 'construct-version',\n // description: 'Version must be a valid semver',\n // validator: constructVersionValidator\n // }\n ],\n});\n\nexport type { Params as ExportParams } from \"./export-c3p\";\n"],"mappings":"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"}
|