@ps-generator-bridge/testkit 0.1.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/README.md +15 -0
- package/dist/cli.js +340 -0
- package/dist/cli.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# PS Generator Bridge Testkit
|
|
2
|
+
|
|
3
|
+
Windows-only smoke harness for real Photoshop + Adobe `generator-core` + PS Generator Bridge
|
|
4
|
+
plugins.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
ps-bridge-test setup
|
|
8
|
+
ps-bridge-test run --plugin ./my-plugin --expect-plugin myPlugin
|
|
9
|
+
ps-bridge-test dev --plugins-dir ./plugins
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Photoshop must already be running with Generator and Remote Connections enabled. The CLI manages
|
|
13
|
+
`generator-core` in `%LOCALAPPDATA%\ps-bridge-test\generator-core\master`, starts it against the
|
|
14
|
+
published `@ps-generator-bridge/generator`, waits for `/health`, checks `/plugins`, and runs the SDK
|
|
15
|
+
`getServerInfo` smoke.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core.ts
|
|
4
|
+
import { execFileSync as execFileSync3, spawn } from "child_process";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
7
|
+
import { Connection } from "@ps-generator-bridge/sdk";
|
|
8
|
+
import WebSocketImpl from "ws";
|
|
9
|
+
|
|
10
|
+
// src/generatorCore.ts
|
|
11
|
+
import { execFileSync } from "child_process";
|
|
12
|
+
import { existsSync, mkdirSync } from "fs";
|
|
13
|
+
import { homedir } from "os";
|
|
14
|
+
import { dirname, join } from "path";
|
|
15
|
+
var REPO = "https://github.com/adobe-photoshop/generator-core";
|
|
16
|
+
function generatorCoreDir() {
|
|
17
|
+
const root = process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local");
|
|
18
|
+
return join(root, "ps-bridge-test", "generator-core", "master");
|
|
19
|
+
}
|
|
20
|
+
async function ensureGeneratorCore(options) {
|
|
21
|
+
const dir = generatorCoreDir();
|
|
22
|
+
mkdirSync(dirname(dir), { recursive: true });
|
|
23
|
+
if (!existsSync(join(dir, ".git"))) {
|
|
24
|
+
run("git", ["clone", REPO, dir], void 0);
|
|
25
|
+
} else if (options.update) {
|
|
26
|
+
run("git", ["pull", "--ff-only"], dir);
|
|
27
|
+
}
|
|
28
|
+
run("npm", ["install"], dir);
|
|
29
|
+
}
|
|
30
|
+
function run(command, args, cwd) {
|
|
31
|
+
console.log(`[generator-core] ${command} ${args.join(" ")}`);
|
|
32
|
+
execFileSync(command, args, {
|
|
33
|
+
cwd,
|
|
34
|
+
stdio: "inherit",
|
|
35
|
+
shell: process.platform === "win32"
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/photoshop.ts
|
|
40
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
41
|
+
function ensurePhotoshopRunning() {
|
|
42
|
+
const output = execFileSync2("tasklist", ["/FI", "IMAGENAME eq Photoshop.exe", "/NH"], {
|
|
43
|
+
encoding: "utf8",
|
|
44
|
+
shell: true
|
|
45
|
+
});
|
|
46
|
+
if (!output.toLowerCase().includes("photoshop.exe")) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"Photoshop is not running. Please open Photoshop first, then rerun this command."
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/pluginDirs.ts
|
|
54
|
+
import {
|
|
55
|
+
existsSync as existsSync2,
|
|
56
|
+
lstatSync,
|
|
57
|
+
mkdirSync as mkdirSync2,
|
|
58
|
+
mkdtempSync,
|
|
59
|
+
realpathSync,
|
|
60
|
+
readdirSync,
|
|
61
|
+
rmSync,
|
|
62
|
+
symlinkSync
|
|
63
|
+
} from "fs";
|
|
64
|
+
import { basename, join as join2, resolve } from "path";
|
|
65
|
+
import { tmpdir } from "os";
|
|
66
|
+
async function preparePluginSource(options) {
|
|
67
|
+
if (options.pluginsDir) {
|
|
68
|
+
const pluginsDir2 = requireDirectory(options.pluginsDir, "--plugins-dir");
|
|
69
|
+
return { pluginsDir: pluginsDir2 };
|
|
70
|
+
}
|
|
71
|
+
const pluginDir = requireDirectory(options.plugin, "--plugin");
|
|
72
|
+
requirePackageEntry(pluginDir);
|
|
73
|
+
const tempRoot = mkdtempSync(join2(tmpdir(), "ps-bridge-test-"));
|
|
74
|
+
const pluginsDir = join2(tempRoot, "plugins");
|
|
75
|
+
const linkName = safeName(basename(pluginDir));
|
|
76
|
+
const linkPath = join2(pluginsDir, linkName);
|
|
77
|
+
rmSync(pluginsDir, { recursive: true, force: true });
|
|
78
|
+
mkdirSync2(pluginsDir, { recursive: true });
|
|
79
|
+
symlinkSync(pluginDir, linkPath, process.platform === "win32" ? "junction" : "dir");
|
|
80
|
+
return { pluginsDir, cleanupDir: tempRoot };
|
|
81
|
+
}
|
|
82
|
+
async function cleanupPluginSource(source) {
|
|
83
|
+
if (source.cleanupDir) rmSync(source.cleanupDir, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
function scanPluginCandidates(pluginsDir) {
|
|
86
|
+
return readdirSync(pluginsDir, { withFileTypes: true }).filter((entry) => {
|
|
87
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) return false;
|
|
88
|
+
if (entry.isDirectory()) return true;
|
|
89
|
+
if (!entry.isSymbolicLink()) return false;
|
|
90
|
+
return lstatSync(join2(pluginsDir, entry.name)).isSymbolicLink();
|
|
91
|
+
}).map((entry) => entry.name).filter((name) => existsSync2(join2(pluginsDir, name, "package.json"))).sort();
|
|
92
|
+
}
|
|
93
|
+
function requireDirectory(input, flag) {
|
|
94
|
+
if (!input) throw new Error(`${flag} is required`);
|
|
95
|
+
const dir = realpathSync(resolve(input));
|
|
96
|
+
if (!lstatSync(dir).isDirectory()) throw new Error(`${flag} must point to a directory: ${input}`);
|
|
97
|
+
return dir;
|
|
98
|
+
}
|
|
99
|
+
function requirePackageEntry(dir) {
|
|
100
|
+
const packageJson = join2(dir, "package.json");
|
|
101
|
+
if (!existsSync2(packageJson)) throw new Error(`Plugin directory has no package.json: ${dir}`);
|
|
102
|
+
}
|
|
103
|
+
function safeName(name) {
|
|
104
|
+
return name.replace(/[^A-Za-z0-9_.-]/g, "_") || "plugin";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/core.ts
|
|
108
|
+
var DEFAULT_PORT = 7700;
|
|
109
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
110
|
+
var require2 = createRequire(import.meta.url);
|
|
111
|
+
async function setupGeneratorCore(options = {}) {
|
|
112
|
+
await ensureGeneratorCore({ update: options.update ?? false });
|
|
113
|
+
}
|
|
114
|
+
async function runHarness(options) {
|
|
115
|
+
await withHarness(options, async (ctx) => {
|
|
116
|
+
console.log(`PS Generator Bridge server ready: http://127.0.0.1:${ctx.port}`);
|
|
117
|
+
console.log(`Loaded plugins: ${formatPlugins(ctx.plugins)}`);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async function runDev(options) {
|
|
121
|
+
await withHarness(options, async (ctx) => {
|
|
122
|
+
console.log(`PS Generator Bridge dev server ready: http://127.0.0.1:${ctx.port}`);
|
|
123
|
+
console.log(`WebSocket: ws://127.0.0.1:${ctx.port}/ws`);
|
|
124
|
+
console.log(`Loaded plugins: ${formatPlugins(ctx.plugins)}`);
|
|
125
|
+
console.log("Press Ctrl+C to stop generator-core.");
|
|
126
|
+
await waitForInterrupt();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async function withHarness(options, body) {
|
|
130
|
+
ensureWindows();
|
|
131
|
+
ensurePhotoshopRunning();
|
|
132
|
+
await ensureGeneratorCore({ update: options.updateCore ?? false });
|
|
133
|
+
const port = options.port ?? DEFAULT_PORT;
|
|
134
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
135
|
+
const pluginSource = await preparePluginSource(options);
|
|
136
|
+
const expectedCount = scanPluginCandidates(pluginSource.pluginsDir).length;
|
|
137
|
+
let child;
|
|
138
|
+
try {
|
|
139
|
+
child = startGeneratorCore({
|
|
140
|
+
port,
|
|
141
|
+
pluginsDir: pluginSource.pluginsDir,
|
|
142
|
+
hostPluginDir: resolveHostGeneratorDir()
|
|
143
|
+
});
|
|
144
|
+
await waitForHealth(port, timeoutMs);
|
|
145
|
+
const plugins = await readPlugins(port);
|
|
146
|
+
assertPlugins({ plugins, expectedCount, expectPlugins: options.expectPlugins });
|
|
147
|
+
const info = await smokeServerInfo(port, timeoutMs);
|
|
148
|
+
console.log(
|
|
149
|
+
`Server info: ${info.name} ${info.version}${info.psVersion ? `, Photoshop ${info.psVersion}` : ""}`
|
|
150
|
+
);
|
|
151
|
+
await body({ port, plugins });
|
|
152
|
+
} finally {
|
|
153
|
+
if (child) stopProcessTree(child);
|
|
154
|
+
await cleanupPluginSource(pluginSource);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function ensureWindows() {
|
|
158
|
+
if (process.platform !== "win32") {
|
|
159
|
+
throw new Error("ps-bridge-test only supports Windows in this version.");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function startGeneratorCore(options) {
|
|
163
|
+
const app = join3(generatorCoreDir(), "app.js");
|
|
164
|
+
console.log(`Starting generator-core: ${app}`);
|
|
165
|
+
const child = spawn(process.execPath, [app, "-f", options.hostPluginDir], {
|
|
166
|
+
cwd: generatorCoreDir(),
|
|
167
|
+
env: {
|
|
168
|
+
...process.env,
|
|
169
|
+
PS_BRIDGE_PLUGINS_DIR: options.pluginsDir,
|
|
170
|
+
PS_BRIDGE_PORT: String(options.port)
|
|
171
|
+
},
|
|
172
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
173
|
+
});
|
|
174
|
+
child.stdout?.on("data", (chunk) => process.stdout.write(chunk));
|
|
175
|
+
child.stderr?.on("data", (chunk) => process.stderr.write(chunk));
|
|
176
|
+
child.on("exit", (code, signal) => {
|
|
177
|
+
if (code !== null && code !== 0) console.error(`generator-core exited with code ${code}`);
|
|
178
|
+
if (signal) console.error(`generator-core exited with signal ${signal}`);
|
|
179
|
+
});
|
|
180
|
+
return child;
|
|
181
|
+
}
|
|
182
|
+
async function waitForHealth(port, timeoutMs) {
|
|
183
|
+
const deadline = Date.now() + timeoutMs;
|
|
184
|
+
const url = `http://127.0.0.1:${port}/health`;
|
|
185
|
+
while (Date.now() < deadline) {
|
|
186
|
+
try {
|
|
187
|
+
const response = await fetch(url);
|
|
188
|
+
if (response.ok) return;
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
await delay(500);
|
|
192
|
+
}
|
|
193
|
+
throw new Error(
|
|
194
|
+
"Photoshop is running, but PS Generator Bridge server did not become ready. Verify Generator and Remote Connections are enabled."
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
async function readPlugins(port) {
|
|
198
|
+
const response = await fetch(`http://127.0.0.1:${port}/plugins`);
|
|
199
|
+
if (!response.ok) throw new Error(`GET /plugins failed with HTTP ${response.status}`);
|
|
200
|
+
const body = await response.json();
|
|
201
|
+
return Array.isArray(body.plugins) ? body.plugins : [];
|
|
202
|
+
}
|
|
203
|
+
async function smokeServerInfo(port, timeoutMs) {
|
|
204
|
+
const connection = new Connection({
|
|
205
|
+
url: `ws://127.0.0.1:${port}/ws`,
|
|
206
|
+
WebSocket: WebSocketImpl,
|
|
207
|
+
timeoutMs,
|
|
208
|
+
maxRetries: 0
|
|
209
|
+
});
|
|
210
|
+
try {
|
|
211
|
+
return await connection.getServerInfo();
|
|
212
|
+
} finally {
|
|
213
|
+
connection.close();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function assertPlugins(options) {
|
|
217
|
+
const ids = new Set(options.plugins.map((plugin) => plugin.id));
|
|
218
|
+
if (options.plugins.length !== options.expectedCount) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Expected ${options.expectedCount} plugin(s), but host loaded ${options.plugins.length}: ${formatPlugins(options.plugins)}`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
for (const id of options.expectPlugins) {
|
|
224
|
+
if (!ids.has(id)) throw new Error(`Expected plugin '${id}' was not loaded.`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function resolveHostGeneratorDir() {
|
|
228
|
+
return dirname2(require2.resolve("@ps-generator-bridge/generator"));
|
|
229
|
+
}
|
|
230
|
+
function stopProcessTree(child) {
|
|
231
|
+
if (!child.pid || child.exitCode !== null) return;
|
|
232
|
+
if (process.platform === "win32") {
|
|
233
|
+
execFileSync3("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" });
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
child.kill();
|
|
237
|
+
}
|
|
238
|
+
function formatPlugins(plugins) {
|
|
239
|
+
return plugins.length > 0 ? plugins.map((plugin) => plugin.id).join(", ") : "(none)";
|
|
240
|
+
}
|
|
241
|
+
function waitForInterrupt() {
|
|
242
|
+
return new Promise((resolve2) => {
|
|
243
|
+
process.once("SIGINT", resolve2);
|
|
244
|
+
process.once("SIGTERM", resolve2);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function delay(ms) {
|
|
248
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/cli.ts
|
|
252
|
+
var USAGE = `Usage:
|
|
253
|
+
ps-bridge-test setup [--update]
|
|
254
|
+
ps-bridge-test run (--plugin <dir> | --plugins-dir <dir>) [--expect-plugin <id>] [--port <number>] [--timeout <ms>] [--update-core]
|
|
255
|
+
ps-bridge-test dev (--plugin <dir> | --plugins-dir <dir>) [--expect-plugin <id>] [--port <number>] [--timeout <ms>] [--update-core]`;
|
|
256
|
+
async function main() {
|
|
257
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
258
|
+
if (parsed.command === "setup") {
|
|
259
|
+
await setupGeneratorCore({ update: parsed.options.updateCore ?? parsed.options.update });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (parsed.command === "run") {
|
|
263
|
+
await runHarness(parsed.options);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
await runDev(parsed.options);
|
|
267
|
+
}
|
|
268
|
+
function parseArgs(args) {
|
|
269
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
270
|
+
console.log(USAGE);
|
|
271
|
+
process.exit(0);
|
|
272
|
+
}
|
|
273
|
+
const command = args.shift();
|
|
274
|
+
if (command !== "setup" && command !== "run" && command !== "dev") {
|
|
275
|
+
throw usage(`Unknown command: ${command ?? "(missing)"}`);
|
|
276
|
+
}
|
|
277
|
+
const options = {
|
|
278
|
+
expectPlugins: []
|
|
279
|
+
};
|
|
280
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
281
|
+
const arg = args[i];
|
|
282
|
+
switch (arg) {
|
|
283
|
+
case "--update":
|
|
284
|
+
options.update = true;
|
|
285
|
+
break;
|
|
286
|
+
case "--update-core":
|
|
287
|
+
options.updateCore = true;
|
|
288
|
+
break;
|
|
289
|
+
case "--plugin":
|
|
290
|
+
options.plugin = readValue(args, ++i, arg);
|
|
291
|
+
break;
|
|
292
|
+
case "--plugins-dir":
|
|
293
|
+
options.pluginsDir = readValue(args, ++i, arg);
|
|
294
|
+
break;
|
|
295
|
+
case "--expect-plugin":
|
|
296
|
+
options.expectPlugins.push(readValue(args, ++i, arg));
|
|
297
|
+
break;
|
|
298
|
+
case "--port":
|
|
299
|
+
options.port = readNumber(readValue(args, ++i, arg), arg);
|
|
300
|
+
break;
|
|
301
|
+
case "--timeout":
|
|
302
|
+
options.timeoutMs = readNumber(readValue(args, ++i, arg), arg);
|
|
303
|
+
break;
|
|
304
|
+
case "--help":
|
|
305
|
+
case "-h":
|
|
306
|
+
console.log(USAGE);
|
|
307
|
+
process.exit(0);
|
|
308
|
+
default:
|
|
309
|
+
throw usage(`Unknown option: ${arg}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (command === "setup") return { command, options };
|
|
313
|
+
if (options.plugin && options.pluginsDir) {
|
|
314
|
+
throw usage("--plugin and --plugins-dir are mutually exclusive");
|
|
315
|
+
}
|
|
316
|
+
if (!options.plugin && !options.pluginsDir) {
|
|
317
|
+
throw usage("Either --plugin or --plugins-dir is required");
|
|
318
|
+
}
|
|
319
|
+
return { command, options };
|
|
320
|
+
}
|
|
321
|
+
function readValue(args, index, name) {
|
|
322
|
+
const value = args[index];
|
|
323
|
+
if (!value || value.startsWith("--")) throw usage(`${name} requires a value`);
|
|
324
|
+
return value;
|
|
325
|
+
}
|
|
326
|
+
function readNumber(value, name) {
|
|
327
|
+
const parsed = Number(value);
|
|
328
|
+
if (!Number.isInteger(parsed) || parsed <= 0) throw usage(`${name} must be a positive integer`);
|
|
329
|
+
return parsed;
|
|
330
|
+
}
|
|
331
|
+
function usage(message) {
|
|
332
|
+
return new Error(`${message}
|
|
333
|
+
|
|
334
|
+
${USAGE}`);
|
|
335
|
+
}
|
|
336
|
+
main().catch((error) => {
|
|
337
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
338
|
+
process.exitCode = 1;
|
|
339
|
+
});
|
|
340
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core.ts","../src/generatorCore.ts","../src/photoshop.ts","../src/pluginDirs.ts","../src/cli.ts"],"sourcesContent":["import { execFileSync, spawn, type ChildProcess } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\nimport { Connection, type PluginInfo } from \"@ps-generator-bridge/sdk\";\nimport WebSocketImpl from \"ws\";\nimport { ensureGeneratorCore, generatorCoreDir } from \"./generatorCore\";\nimport { ensurePhotoshopRunning } from \"./photoshop\";\nimport { cleanupPluginSource, preparePluginSource, scanPluginCandidates } from \"./pluginDirs\";\n\nconst DEFAULT_PORT = 7700;\nconst DEFAULT_TIMEOUT_MS = 60_000;\nconst require = createRequire(import.meta.url);\n\nexport interface HarnessOptions {\n plugin?: string;\n pluginsDir?: string;\n expectPlugins: string[];\n port?: number;\n timeoutMs?: number;\n updateCore?: boolean;\n}\n\nexport async function setupGeneratorCore(options: { update?: boolean } = {}): Promise<void> {\n await ensureGeneratorCore({ update: options.update ?? false });\n}\n\nexport async function runHarness(options: HarnessOptions): Promise<void> {\n await withHarness(options, async (ctx) => {\n console.log(`PS Generator Bridge server ready: http://127.0.0.1:${ctx.port}`);\n console.log(`Loaded plugins: ${formatPlugins(ctx.plugins)}`);\n });\n}\n\nexport async function runDev(options: HarnessOptions): Promise<void> {\n await withHarness(options, async (ctx) => {\n console.log(`PS Generator Bridge dev server ready: http://127.0.0.1:${ctx.port}`);\n console.log(`WebSocket: ws://127.0.0.1:${ctx.port}/ws`);\n console.log(`Loaded plugins: ${formatPlugins(ctx.plugins)}`);\n console.log(\"Press Ctrl+C to stop generator-core.\");\n await waitForInterrupt();\n });\n}\n\nasync function withHarness(\n options: HarnessOptions,\n body: (ctx: { port: number; plugins: PluginInfo[] }) => Promise<void>\n): Promise<void> {\n ensureWindows();\n ensurePhotoshopRunning();\n await ensureGeneratorCore({ update: options.updateCore ?? false });\n\n const port = options.port ?? DEFAULT_PORT;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const pluginSource = await preparePluginSource(options);\n const expectedCount = scanPluginCandidates(pluginSource.pluginsDir).length;\n let child: ChildProcess | undefined;\n\n try {\n child = startGeneratorCore({\n port,\n pluginsDir: pluginSource.pluginsDir,\n hostPluginDir: resolveHostGeneratorDir(),\n });\n await waitForHealth(port, timeoutMs);\n const plugins = await readPlugins(port);\n assertPlugins({ plugins, expectedCount, expectPlugins: options.expectPlugins });\n const info = await smokeServerInfo(port, timeoutMs);\n console.log(\n `Server info: ${info.name} ${info.version}${info.psVersion ? `, Photoshop ${info.psVersion}` : \"\"}`\n );\n await body({ port, plugins });\n } finally {\n if (child) stopProcessTree(child);\n await cleanupPluginSource(pluginSource);\n }\n}\n\nfunction ensureWindows(): void {\n if (process.platform !== \"win32\") {\n throw new Error(\"ps-bridge-test only supports Windows in this version.\");\n }\n}\n\nfunction startGeneratorCore(options: {\n port: number;\n pluginsDir: string;\n hostPluginDir: string;\n}): ChildProcess {\n const app = join(generatorCoreDir(), \"app.js\");\n console.log(`Starting generator-core: ${app}`);\n const child = spawn(process.execPath, [app, \"-f\", options.hostPluginDir], {\n cwd: generatorCoreDir(),\n env: {\n ...process.env,\n PS_BRIDGE_PLUGINS_DIR: options.pluginsDir,\n PS_BRIDGE_PORT: String(options.port),\n },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n child.stdout?.on(\"data\", (chunk) => process.stdout.write(chunk));\n child.stderr?.on(\"data\", (chunk) => process.stderr.write(chunk));\n child.on(\"exit\", (code, signal) => {\n if (code !== null && code !== 0) console.error(`generator-core exited with code ${code}`);\n if (signal) console.error(`generator-core exited with signal ${signal}`);\n });\n return child;\n}\n\nasync function waitForHealth(port: number, timeoutMs: number): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n const url = `http://127.0.0.1:${port}/health`;\n while (Date.now() < deadline) {\n try {\n const response = await fetch(url);\n if (response.ok) return;\n } catch {\n // Keep polling until timeout.\n }\n await delay(500);\n }\n throw new Error(\n \"Photoshop is running, but PS Generator Bridge server did not become ready. Verify Generator and Remote Connections are enabled.\"\n );\n}\n\nasync function readPlugins(port: number): Promise<PluginInfo[]> {\n const response = await fetch(`http://127.0.0.1:${port}/plugins`);\n if (!response.ok) throw new Error(`GET /plugins failed with HTTP ${response.status}`);\n const body = (await response.json()) as { plugins?: PluginInfo[] };\n return Array.isArray(body.plugins) ? body.plugins : [];\n}\n\nasync function smokeServerInfo(port: number, timeoutMs: number) {\n const connection = new Connection({\n url: `ws://127.0.0.1:${port}/ws`,\n WebSocket: WebSocketImpl as unknown as typeof WebSocket,\n timeoutMs,\n maxRetries: 0,\n });\n try {\n return await connection.getServerInfo();\n } finally {\n connection.close();\n }\n}\n\nfunction assertPlugins(options: {\n plugins: PluginInfo[];\n expectedCount: number;\n expectPlugins: string[];\n}): void {\n const ids = new Set(options.plugins.map((plugin) => plugin.id));\n if (options.plugins.length !== options.expectedCount) {\n throw new Error(\n `Expected ${options.expectedCount} plugin(s), but host loaded ${options.plugins.length}: ${formatPlugins(options.plugins)}`\n );\n }\n for (const id of options.expectPlugins) {\n if (!ids.has(id)) throw new Error(`Expected plugin '${id}' was not loaded.`);\n }\n}\n\nfunction resolveHostGeneratorDir(): string {\n return dirname(require.resolve(\"@ps-generator-bridge/generator\"));\n}\n\nfunction stopProcessTree(child: ChildProcess): void {\n if (!child.pid || child.exitCode !== null) return;\n if (process.platform === \"win32\") {\n execFileSync(\"taskkill\", [\"/PID\", String(child.pid), \"/T\", \"/F\"], { stdio: \"ignore\" });\n return;\n }\n child.kill();\n}\n\nfunction formatPlugins(plugins: PluginInfo[]): string {\n return plugins.length > 0 ? plugins.map((plugin) => plugin.id).join(\", \") : \"(none)\";\n}\n\nfunction waitForInterrupt(): Promise<void> {\n return new Promise((resolve) => {\n process.once(\"SIGINT\", resolve);\n process.once(\"SIGTERM\", resolve);\n });\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { execFileSync } from \"node:child_process\";\nimport { existsSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nconst REPO = \"https://github.com/adobe-photoshop/generator-core\";\n\nexport function generatorCoreDir(): string {\n const root = process.env.LOCALAPPDATA ?? join(homedir(), \"AppData\", \"Local\");\n return join(root, \"ps-bridge-test\", \"generator-core\", \"master\");\n}\n\nexport async function ensureGeneratorCore(options: { update: boolean }): Promise<void> {\n const dir = generatorCoreDir();\n mkdirSync(dirname(dir), { recursive: true });\n\n if (!existsSync(join(dir, \".git\"))) {\n run(\"git\", [\"clone\", REPO, dir], undefined);\n } else if (options.update) {\n run(\"git\", [\"pull\", \"--ff-only\"], dir);\n }\n\n run(\"npm\", [\"install\"], dir);\n}\n\nfunction run(command: string, args: string[], cwd: string | undefined): void {\n console.log(`[generator-core] ${command} ${args.join(\" \")}`);\n execFileSync(command, args, {\n cwd,\n stdio: \"inherit\",\n shell: process.platform === \"win32\",\n });\n}\n","import { execFileSync } from \"node:child_process\";\n\nexport function ensurePhotoshopRunning(): void {\n const output = execFileSync(\"tasklist\", [\"/FI\", \"IMAGENAME eq Photoshop.exe\", \"/NH\"], {\n encoding: \"utf8\",\n shell: true,\n });\n if (!output.toLowerCase().includes(\"photoshop.exe\")) {\n throw new Error(\n \"Photoshop is not running. Please open Photoshop first, then rerun this command.\"\n );\n }\n}\n","import {\n existsSync,\n lstatSync,\n mkdirSync,\n mkdtempSync,\n realpathSync,\n readdirSync,\n rmSync,\n symlinkSync,\n} from \"node:fs\";\nimport { basename, join, resolve } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport type { HarnessOptions } from \"./core\";\n\nexport interface PluginSource {\n pluginsDir: string;\n cleanupDir?: string;\n}\n\nexport async function preparePluginSource(options: HarnessOptions): Promise<PluginSource> {\n if (options.pluginsDir) {\n const pluginsDir = requireDirectory(options.pluginsDir, \"--plugins-dir\");\n return { pluginsDir };\n }\n\n const pluginDir = requireDirectory(options.plugin, \"--plugin\");\n requirePackageEntry(pluginDir);\n const tempRoot = mkdtempSync(join(tmpdir(), \"ps-bridge-test-\"));\n const pluginsDir = join(tempRoot, \"plugins\");\n const linkName = safeName(basename(pluginDir));\n const linkPath = join(pluginsDir, linkName);\n rmSync(pluginsDir, { recursive: true, force: true });\n mkdirSync(pluginsDir, { recursive: true });\n symlinkSync(pluginDir, linkPath, process.platform === \"win32\" ? \"junction\" : \"dir\");\n return { pluginsDir, cleanupDir: tempRoot };\n}\n\nexport async function cleanupPluginSource(source: PluginSource): Promise<void> {\n if (source.cleanupDir) rmSync(source.cleanupDir, { recursive: true, force: true });\n}\n\nexport function scanPluginCandidates(pluginsDir: string): string[] {\n return readdirSync(pluginsDir, { withFileTypes: true })\n .filter((entry) => {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) return false;\n if (entry.isDirectory()) return true;\n if (!entry.isSymbolicLink()) return false;\n return lstatSync(join(pluginsDir, entry.name)).isSymbolicLink();\n })\n .map((entry) => entry.name)\n .filter((name) => existsSync(join(pluginsDir, name, \"package.json\")))\n .sort();\n}\n\nfunction requireDirectory(input: string | undefined, flag: string): string {\n if (!input) throw new Error(`${flag} is required`);\n const dir = realpathSync(resolve(input));\n if (!lstatSync(dir).isDirectory()) throw new Error(`${flag} must point to a directory: ${input}`);\n return dir;\n}\n\nfunction requirePackageEntry(dir: string): void {\n const packageJson = join(dir, \"package.json\");\n if (!existsSync(packageJson)) throw new Error(`Plugin directory has no package.json: ${dir}`);\n}\n\nfunction safeName(name: string): string {\n return name.replace(/[^A-Za-z0-9_.-]/g, \"_\") || \"plugin\";\n}\n","#!/usr/bin/env node\nimport { runDev, runHarness, setupGeneratorCore, type HarnessOptions } from \"./core\";\n\ntype Command = \"setup\" | \"run\" | \"dev\";\n\ninterface Parsed {\n command: Command;\n options: HarnessOptions & { update?: boolean };\n}\n\nconst USAGE = `Usage:\n ps-bridge-test setup [--update]\n ps-bridge-test run (--plugin <dir> | --plugins-dir <dir>) [--expect-plugin <id>] [--port <number>] [--timeout <ms>] [--update-core]\n ps-bridge-test dev (--plugin <dir> | --plugins-dir <dir>) [--expect-plugin <id>] [--port <number>] [--timeout <ms>] [--update-core]`;\n\nasync function main(): Promise<void> {\n const parsed = parseArgs(process.argv.slice(2));\n if (parsed.command === \"setup\") {\n await setupGeneratorCore({ update: parsed.options.updateCore ?? parsed.options.update });\n return;\n }\n if (parsed.command === \"run\") {\n await runHarness(parsed.options);\n return;\n }\n await runDev(parsed.options);\n}\n\nfunction parseArgs(args: string[]): Parsed {\n if (args.length === 0 || args[0] === \"--help\" || args[0] === \"-h\") {\n console.log(USAGE);\n process.exit(0);\n }\n\n const command = args.shift();\n if (command !== \"setup\" && command !== \"run\" && command !== \"dev\") {\n throw usage(`Unknown command: ${command ?? \"(missing)\"}`);\n }\n\n const options: Parsed[\"options\"] = {\n expectPlugins: [],\n };\n\n for (let i = 0; i < args.length; i += 1) {\n const arg = args[i];\n switch (arg) {\n case \"--update\":\n options.update = true;\n break;\n case \"--update-core\":\n options.updateCore = true;\n break;\n case \"--plugin\":\n options.plugin = readValue(args, ++i, arg);\n break;\n case \"--plugins-dir\":\n options.pluginsDir = readValue(args, ++i, arg);\n break;\n case \"--expect-plugin\":\n options.expectPlugins.push(readValue(args, ++i, arg));\n break;\n case \"--port\":\n options.port = readNumber(readValue(args, ++i, arg), arg);\n break;\n case \"--timeout\":\n options.timeoutMs = readNumber(readValue(args, ++i, arg), arg);\n break;\n case \"--help\":\n case \"-h\":\n console.log(USAGE);\n process.exit(0);\n default:\n throw usage(`Unknown option: ${arg}`);\n }\n }\n\n if (command === \"setup\") return { command, options };\n if (options.plugin && options.pluginsDir) {\n throw usage(\"--plugin and --plugins-dir are mutually exclusive\");\n }\n if (!options.plugin && !options.pluginsDir) {\n throw usage(\"Either --plugin or --plugins-dir is required\");\n }\n return { command, options };\n}\n\nfunction readValue(args: string[], index: number, name: string): string {\n const value = args[index];\n if (!value || value.startsWith(\"--\")) throw usage(`${name} requires a value`);\n return value;\n}\n\nfunction readNumber(value: string, name: string): number {\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed <= 0) throw usage(`${name} must be a positive integer`);\n return parsed;\n}\n\nfunction usage(message: string): Error {\n return new Error(`${message}\\n\\n${USAGE}`);\n}\n\nmain().catch((error) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n});\n"],"mappings":"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","names":["execFileSync","dirname","join","execFileSync","existsSync","mkdirSync","join","pluginsDir","require","join","dirname","execFileSync","resolve"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ps-generator-bridge/testkit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Windows Photoshop + generator-core smoke harness for PS Generator Bridge plugins",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "wojzj57",
|
|
7
|
+
"homepage": "https://github.com/wojzj57/ps-generator-bridge-service#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/wojzj57/ps-generator-bridge-service.git",
|
|
11
|
+
"directory": "packages/testkit"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/wojzj57/ps-generator-bridge-service/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"photoshop",
|
|
18
|
+
"generator",
|
|
19
|
+
"testing",
|
|
20
|
+
"cli"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"bin": {
|
|
27
|
+
"ps-bridge-test": "./dist/cli.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"test": "pnpm typecheck",
|
|
37
|
+
"clean": "rimraf dist *.tsbuildinfo"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@ps-generator-bridge/generator": "workspace:*",
|
|
41
|
+
"@ps-generator-bridge/sdk": "workspace:*",
|
|
42
|
+
"ws": "^8.18.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^22.13.5",
|
|
46
|
+
"@types/ws": "^8.5.12",
|
|
47
|
+
"rimraf": "^6.0.1",
|
|
48
|
+
"tsup": "^8.3.5",
|
|
49
|
+
"typescript": "^5.9.3"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18"
|
|
53
|
+
}
|
|
54
|
+
}
|