@essential-apps/shopify-test-runner 1.0.12 → 1.0.13
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/dist/lib/guestVnc.d.ts +31 -0
- package/dist/lib/guestVnc.d.ts.map +1 -0
- package/dist/lib/guestVnc.js +111 -0
- package/dist/lib/guestVnc.js.map +1 -0
- package/dist/probes/runProbe.js +0 -0
- package/dist/scripts/addStore.js +0 -0
- package/dist/scripts/buildImage.d.ts +3 -0
- package/dist/scripts/buildImage.d.ts.map +1 -0
- package/dist/scripts/{buildDockerImage.js → buildImage.js} +12 -10
- package/dist/scripts/buildImage.js.map +1 -0
- package/dist/scripts/captureAuth.js +0 -0
- package/dist/scripts/captureContracts.js +0 -0
- package/dist/scripts/captureRestContracts.js +0 -0
- package/dist/scripts/captureSharedContracts.d.ts +3 -0
- package/dist/scripts/captureSharedContracts.d.ts.map +1 -0
- package/dist/scripts/captureSharedContracts.js +209 -0
- package/dist/scripts/captureSharedContracts.js.map +1 -0
- package/dist/scripts/checkOperationCoverage.js +0 -0
- package/dist/scripts/cleanupStores.js +0 -0
- package/dist/scripts/createStores.js +0 -0
- package/dist/scripts/deployAppVersion.js +0 -0
- package/dist/scripts/devOnlineBackend.js +0 -0
- package/dist/scripts/installApp.js +0 -0
- package/dist/scripts/listStores.js +0 -0
- package/dist/scripts/runOffline.js +78 -1
- package/dist/scripts/runOffline.js.map +1 -1
- package/dist/scripts/runOfflineFullTests.js +49 -21
- package/dist/scripts/runOfflineFullTests.js.map +1 -1
- package/dist/scripts/runTests.js +0 -0
- package/dist/scripts/runVm.js +0 -0
- package/dist/scripts/runVmAuth.js +0 -0
- package/dist/scripts/setupTestDb.js +0 -0
- package/dist/scripts/verifyContracts.js +20 -29
- package/dist/scripts/verifyContracts.js.map +1 -1
- package/dist/scripts/verifyRestContracts.js +17 -30
- package/dist/scripts/verifyRestContracts.js.map +1 -1
- package/package.json +11 -9
- package/src/lib/guestVnc.ts +147 -0
- package/src/scripts/{buildDockerImage.ts → buildImage.ts} +11 -9
- package/src/scripts/captureSharedContracts.ts +228 -0
- package/src/scripts/runOffline.ts +82 -1
- package/src/scripts/runOfflineFullTests.ts +56 -21
- package/src/scripts/verifyContracts.ts +22 -38
- package/src/scripts/verifyRestContracts.ts +23 -42
- package/dist/edge/nodeShim.d.ts +0 -2
- package/dist/edge/nodeShim.d.ts.map +0 -1
- package/dist/edge/nodeShim.js +0 -217
- package/dist/edge/nodeShim.js.map +0 -1
- package/dist/scripts/_probeSourceUrl.d.ts +0 -3
- package/dist/scripts/_probeSourceUrl.d.ts.map +0 -1
- package/dist/scripts/_probeSourceUrl.js +0 -119
- package/dist/scripts/_probeSourceUrl.js.map +0 -1
- package/dist/scripts/buildDockerImage.d.ts +0 -3
- package/dist/scripts/buildDockerImage.d.ts.map +0 -1
- package/dist/scripts/buildDockerImage.js.map +0 -1
- package/dist/scripts/devE2eBackend.d.ts +0 -3
- package/dist/scripts/devE2eBackend.d.ts.map +0 -1
- package/dist/scripts/devE2eBackend.js +0 -117
- package/dist/scripts/devE2eBackend.js.map +0 -1
- package/dist/scripts/runDocker.d.ts +0 -3
- package/dist/scripts/runDocker.d.ts.map +0 -1
- package/dist/scripts/runDocker.js +0 -88
- package/dist/scripts/runDocker.js.map +0 -1
- package/dist/scripts/runDockerAuth.d.ts +0 -3
- package/dist/scripts/runDockerAuth.d.ts.map +0 -1
- package/dist/scripts/runDockerAuth.js +0 -108
- package/dist/scripts/runDockerAuth.js.map +0 -1
- package/dist/scripts/runDockerOffline.d.ts +0 -3
- package/dist/scripts/runDockerOffline.d.ts.map +0 -1
- package/dist/scripts/runDockerOffline.js +0 -129
- package/dist/scripts/runDockerOffline.js.map +0 -1
- package/dist/scripts/runDockerOfflineExplore.d.ts +0 -3
- package/dist/scripts/runDockerOfflineExplore.d.ts.map +0 -1
- package/dist/scripts/runDockerOfflineExplore.js +0 -116
- package/dist/scripts/runDockerOfflineExplore.js.map +0 -1
- package/dist/scripts/runIsolatedDockerOffline.d.ts +0 -3
- package/dist/scripts/runIsolatedDockerOffline.d.ts.map +0 -1
- package/dist/scripts/runIsolatedDockerOffline.js +0 -351
- package/dist/scripts/runIsolatedDockerOffline.js.map +0 -1
- package/dist/scripts/runOfflineE2e.d.ts +0 -3
- package/dist/scripts/runOfflineE2e.d.ts.map +0 -1
- package/dist/scripts/runOfflineE2e.js +0 -408
- package/dist/scripts/runOfflineE2e.js.map +0 -1
- package/dist/scripts/runSupermachine.d.ts +0 -3
- package/dist/scripts/runSupermachine.d.ts.map +0 -1
- package/dist/scripts/runSupermachine.js +0 -474
- package/dist/scripts/runSupermachine.js.map +0 -1
- package/dist/scripts/runSupermachineAuth.d.ts +0 -3
- package/dist/scripts/runSupermachineAuth.d.ts.map +0 -1
- package/dist/scripts/runSupermachineAuth.js +0 -454
- package/dist/scripts/runSupermachineAuth.js.map +0 -1
- package/dist/vite/offlineConfig.d.ts +0 -34
- package/dist/vite/offlineConfig.d.ts.map +0 -1
- package/dist/vite/offlineConfig.js +0 -61
- package/dist/vite/offlineConfig.js.map +0 -1
- package/src/scripts/runDockerAuth.ts +0 -120
|
@@ -56,6 +56,7 @@ import { dirname, resolve } from 'node:path';
|
|
|
56
56
|
import { fileURLToPath } from 'node:url';
|
|
57
57
|
import { pickApp, prepareOciArchive, envFileArgs, computeBuildHash } from '@essential-apps/shopify-test-core';
|
|
58
58
|
import { buildExtensions } from '../lib/buildExtensions.js';
|
|
59
|
+
import { exposeGuestVnc } from '../lib/guestVnc.js';
|
|
59
60
|
|
|
60
61
|
const repoRoot = process.cwd();
|
|
61
62
|
|
|
@@ -338,6 +339,27 @@ async function main(): Promise<void> {
|
|
|
338
339
|
}
|
|
339
340
|
})();
|
|
340
341
|
const runtimeTag = `sm${runtimeVersion.replace(/[^0-9a-z]+/gi, '')}`;
|
|
342
|
+
|
|
343
|
+
// Fold THIS runner package's own version into the snapshot key. The
|
|
344
|
+
// warm snapshot bakes the in-VM `npm install` (which installs the
|
|
345
|
+
// orchestrator + fixtures this same shopify-test version ships). Those
|
|
346
|
+
// run INSIDE the VM, so a version bump that changes orchestrator/
|
|
347
|
+
// fixture behaviour (e.g. screen-video support) would otherwise
|
|
348
|
+
// warm-restore a STALE in-VM install — the consumer updates the
|
|
349
|
+
// package but keeps running the old in-VM code until they manually
|
|
350
|
+
// `rm -rf ~/.cache/<app>-test`. Keying on the runner version makes a
|
|
351
|
+
// bump auto-rebake (cold install of the new version) — same
|
|
352
|
+
// no-manual-bust philosophy as manifestHash + runtimeTag.
|
|
353
|
+
const runnerVersion = ((): string => {
|
|
354
|
+
try {
|
|
355
|
+
return (
|
|
356
|
+
createRequire(import.meta.url)('../../package.json') as { version: string }
|
|
357
|
+
).version;
|
|
358
|
+
} catch {
|
|
359
|
+
return 'unknown';
|
|
360
|
+
}
|
|
361
|
+
})();
|
|
362
|
+
const runnerTag = `r${runnerVersion.replace(/[^0-9a-z]+/gi, '')}`;
|
|
341
363
|
// Scope the snapshot to the consuming app. supermachine keys WARM
|
|
342
364
|
// snapshots by (image + warmupTag), but the image is shared across
|
|
343
365
|
// apps and the rest of the tag is identical for any two apps vendoring
|
|
@@ -347,7 +369,7 @@ async function main(): Promise<void> {
|
|
|
347
369
|
// identity (observed: essential-upsell booting with essential-seo's
|
|
348
370
|
// DB). The cache paths are already appName-namespaced; this aligns the
|
|
349
371
|
// snapshot key with them.
|
|
350
|
-
const warmupTag = `offline-v13-${appName}-${runtimeTag}-${manifestHash}`;
|
|
372
|
+
const warmupTag = `offline-v13-${appName}-${runtimeTag}-${runnerTag}-${manifestHash}`;
|
|
351
373
|
|
|
352
374
|
// Dynamic import — @supermachine/core is heavy and only needed
|
|
353
375
|
// when actually running tests, not for `--help` etc.
|
|
@@ -513,6 +535,15 @@ async function main(): Promise<void> {
|
|
|
513
535
|
// orchestrator's Xvfb startup + the offlineFullStack screen-video
|
|
514
536
|
// fixture).
|
|
515
537
|
'TEST_OFFLINE_VIDEO',
|
|
538
|
+
// Offline runs headed-under-Xvfb by default; opt out to headless.
|
|
539
|
+
'TEST_OFFLINE_HEADLESS',
|
|
540
|
+
// Interactive explore mode: orchestrator skips Playwright and
|
|
541
|
+
// instead opens a headed Chromium (admin + storefront tabs) on
|
|
542
|
+
// the guest Xvfb display, which we surface over VNC (see the
|
|
543
|
+
// explore branch below).
|
|
544
|
+
'TEST_OFFLINE_EXPLORE',
|
|
545
|
+
'TEST_OFFLINE_EXPLORE_URL',
|
|
546
|
+
'TEST_OFFLINE_EXPLORE_NO_DEVTOOLS',
|
|
516
547
|
'CI',
|
|
517
548
|
'DEBUG',
|
|
518
549
|
]) {
|
|
@@ -585,6 +616,56 @@ async function main(): Promise<void> {
|
|
|
585
616
|
forwardEnv['PRISMA_QUERY_ENGINE_LIBRARY'] =
|
|
586
617
|
'/workspace/node_modules/.prisma/client/libquery_engine-linux-arm64-openssl-3.0.x.so.node';
|
|
587
618
|
|
|
619
|
+
// ── Explore mode (interactive, VNC) ───────────────────────────
|
|
620
|
+
// `TEST_OFFLINE_EXPLORE=true` boots the full offline mock stack
|
|
621
|
+
// and hands the developer a real browser inside the VM instead of
|
|
622
|
+
// running Playwright. We surface the guest's Xvfb display over VNC
|
|
623
|
+
// (supermachine `vm.exposeTcp`, NOT docker --publish) so they can
|
|
624
|
+
// click through the admin app + storefront against the mock stack.
|
|
625
|
+
// The orchestrator opens both tabs (see runOfflineFullTests explore
|
|
626
|
+
// branch) and holds until the browser window is closed / Ctrl-C.
|
|
627
|
+
if (process.env['TEST_OFFLINE_EXPLORE'] === 'true') {
|
|
628
|
+
let forwarder: { stop?: () => Promise<void> | void } | undefined;
|
|
629
|
+
try {
|
|
630
|
+
forwarder = (await exposeGuestVnc(vm, {
|
|
631
|
+
logPrefix: '[runOffline]',
|
|
632
|
+
})) as { stop?: () => Promise<void> | void };
|
|
633
|
+
|
|
634
|
+
console.error('[runOffline] launching explore session…');
|
|
635
|
+
const exploreProc = await vm.spawn({
|
|
636
|
+
argv: ['sh', '-c', `
|
|
637
|
+
cd /workspace
|
|
638
|
+
export PATH=/workspace/node_modules/.bin:$PATH
|
|
639
|
+
export DISPLAY=:99
|
|
640
|
+
node ${envFileArgs(repoRoot)} node_modules/@essential-apps/shopify-test-runner/dist/scripts/runOfflineFullTests.js
|
|
641
|
+
`],
|
|
642
|
+
env: forwardEnv,
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// Stream guest output verbatim with NO stall/hard timeout — an
|
|
646
|
+
// explore session legitimately sits idle for as long as the
|
|
647
|
+
// developer is poking around. It ends when they close the
|
|
648
|
+
// browser (orchestrator exits) or Ctrl-C here.
|
|
649
|
+
const pump = (async () => {
|
|
650
|
+
while (true) {
|
|
651
|
+
const out = await exploreProc.readStdout(64 * 1024);
|
|
652
|
+
if (out.length === 0) {
|
|
653
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
process.stdout.write(out);
|
|
657
|
+
}
|
|
658
|
+
})();
|
|
659
|
+
pump.catch(() => {});
|
|
660
|
+
|
|
661
|
+
const wait = await exploreProc.wait();
|
|
662
|
+
process.exitCode = wait.exitCode ?? 0;
|
|
663
|
+
return;
|
|
664
|
+
} finally {
|
|
665
|
+
if (forwarder?.stop) await Promise.resolve(forwarder.stop()).catch(() => {});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
588
669
|
// Optional --grep forwarding for fast iteration on a single
|
|
589
670
|
// scenario or spec without writing a one-off bench script.
|
|
590
671
|
const grep = process.env['PLAYWRIGHT_GREP'];
|
|
@@ -1229,13 +1229,15 @@ async function main(): Promise<void> {
|
|
|
1229
1229
|
|
|
1230
1230
|
// ── Explore mode (no Playwright; manual click-around) ──
|
|
1231
1231
|
// When `TEST_OFFLINE_EXPLORE=true` is set, skip Playwright
|
|
1232
|
-
// entirely. Instead, launch a
|
|
1233
|
-
//
|
|
1234
|
-
//
|
|
1235
|
-
// `
|
|
1232
|
+
// entirely. Instead, launch a non-headless Chrome on the guest's
|
|
1233
|
+
// Xvfb :99 display with TWO tabs — the admin app and the storefront
|
|
1234
|
+
// — both pointed at the in-VM mock stack. The host-side runner
|
|
1235
|
+
// (`runOffline.ts`) surfaces :99 over VNC via supermachine's
|
|
1236
|
+
// `vm.exposeTcp` (NOT docker --publish — we don't use docker for
|
|
1237
|
+
// anything), so the developer connects from macOS via
|
|
1236
1238
|
// open vnc://localhost:5900 (password: `test`)
|
|
1237
|
-
// and
|
|
1238
|
-
//
|
|
1239
|
+
// and clicks through admin + storefront against the mock stack.
|
|
1240
|
+
// The process holds open until the browser is closed / SIGINT.
|
|
1239
1241
|
//
|
|
1240
1242
|
// This is the "interactive Shopify dev store" mode — useful
|
|
1241
1243
|
// for visually inspecting funnel rendering, debugging admin
|
|
@@ -1342,6 +1344,26 @@ async function main(): Promise<void> {
|
|
|
1342
1344
|
exploreCtx.pages()[0] ?? (await exploreCtx.newPage());
|
|
1343
1345
|
await explorePage.goto(adminUrl, { waitUntil: 'domcontentloaded' });
|
|
1344
1346
|
|
|
1347
|
+
// Second tab: the storefront (app embed + widget render against
|
|
1348
|
+
// the seeded catalog). Opening both up-front means the developer
|
|
1349
|
+
// lands on a ready admin + storefront without typing URLs. Errors
|
|
1350
|
+
// are swallowed — a slow/failed storefront load shouldn't abort
|
|
1351
|
+
// the whole explore session (the admin tab is still useful).
|
|
1352
|
+
const storefrontUrl =
|
|
1353
|
+
process.env['TEST_OFFLINE_EXPLORE_STOREFRONT_URL'] ??
|
|
1354
|
+
`https://${DEFAULT_SHOP_DOMAIN}/`;
|
|
1355
|
+
try {
|
|
1356
|
+
const sfPage = await exploreCtx.newPage();
|
|
1357
|
+
await sfPage.goto(storefrontUrl, { waitUntil: 'domcontentloaded' });
|
|
1358
|
+
} catch (err) {
|
|
1359
|
+
console.error(
|
|
1360
|
+
`[runOfflineFull] explore: storefront tab failed to load (${(err as Error).message}) — continuing`,
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
// Leave the admin tab focused as the active one.
|
|
1364
|
+
await explorePage.bringToFront().catch(() => {});
|
|
1365
|
+
console.log(` Storefront tab opened: ${storefrontUrl}`);
|
|
1366
|
+
|
|
1345
1367
|
// Wait indefinitely. Exit triggers:
|
|
1346
1368
|
// - Developer closes the browser window → context emits `close`.
|
|
1347
1369
|
// - Container receives SIGINT/SIGTERM → we close the context
|
|
@@ -1361,16 +1383,20 @@ async function main(): Promise<void> {
|
|
|
1361
1383
|
|
|
1362
1384
|
// ── Playwright ─────────────────────────────────────────
|
|
1363
1385
|
console.log('[runOfflineFull] running Playwright…');
|
|
1364
|
-
//
|
|
1365
|
-
//
|
|
1366
|
-
//
|
|
1367
|
-
//
|
|
1386
|
+
// Headed-under-Xvfb is the DEFAULT for offline runs (production-
|
|
1387
|
+
// faithful, ~4% slower than headless). Opt OUT with
|
|
1388
|
+
// TEST_OFFLINE_HEADLESS=true. Full-screen recording (TEST_OFFLINE_VIDEO)
|
|
1389
|
+
// and TEST_VISIBLE always force headed. Xvfb ships in the Playwright
|
|
1390
|
+
// base image; ffmpeg (for recording) is baked in too. We start Xvfb
|
|
1391
|
+
// :99 whenever headed, and only require ffmpeg when recording.
|
|
1368
1392
|
let videoOn = process.env['TEST_OFFLINE_VIDEO'] === 'true';
|
|
1393
|
+
const headlessOn = process.env['TEST_OFFLINE_HEADLESS'] === 'true';
|
|
1394
|
+
let headedOn = videoOn || process.env['TEST_VISIBLE'] === 'true' || !headlessOn;
|
|
1395
|
+
let headedReady = false;
|
|
1369
1396
|
if (videoOn) {
|
|
1370
|
-
|
|
1371
|
-
//
|
|
1372
|
-
//
|
|
1373
|
-
// clear rebuild hint rather than failing every test's recording.
|
|
1397
|
+
// Recording needs ffmpeg (with x11grab). An image built before
|
|
1398
|
+
// video support won't have it — degrade to headed-no-record with
|
|
1399
|
+
// a clear rebuild hint rather than failing every test's recording.
|
|
1374
1400
|
let hasFfmpeg = false;
|
|
1375
1401
|
try {
|
|
1376
1402
|
const { execSync: execSyncCheck } = await import('node:child_process');
|
|
@@ -1384,19 +1410,19 @@ async function main(): Promise<void> {
|
|
|
1384
1410
|
'[runOfflineFull] TEST_OFFLINE_VIDEO set but `ffmpeg` is missing from the ' +
|
|
1385
1411
|
'VM image — rebuild it (it predates video support): ' +
|
|
1386
1412
|
'`container image rm essential-apps/shopify-test-vm:latest` then re-run. ' +
|
|
1387
|
-
'Continuing WITHOUT
|
|
1413
|
+
'Continuing headed WITHOUT recording.',
|
|
1388
1414
|
);
|
|
1389
1415
|
videoOn = false;
|
|
1390
1416
|
process.env['TEST_OFFLINE_VIDEO'] = 'false';
|
|
1391
1417
|
}
|
|
1392
1418
|
}
|
|
1393
|
-
if (
|
|
1419
|
+
if (headedOn) {
|
|
1394
1420
|
const { existsSync } = await import('node:fs');
|
|
1395
1421
|
if (!existsSync('/tmp/.X11-unix/X99')) {
|
|
1396
1422
|
const { spawn: spawnXvfb } = await import('node:child_process');
|
|
1397
1423
|
const xv = spawnXvfb(
|
|
1398
1424
|
'Xvfb',
|
|
1399
|
-
[':99', '-screen', '0', '
|
|
1425
|
+
[':99', '-screen', '0', '1400x900x24', '-nolisten', 'tcp', '-ac'],
|
|
1400
1426
|
{ detached: true, stdio: 'ignore' },
|
|
1401
1427
|
);
|
|
1402
1428
|
xv.unref();
|
|
@@ -1405,17 +1431,26 @@ async function main(): Promise<void> {
|
|
|
1405
1431
|
await new Promise((r) => setTimeout(r, 100));
|
|
1406
1432
|
}
|
|
1407
1433
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1434
|
+
headedReady = existsSync('/tmp/.X11-unix/X99');
|
|
1435
|
+
if (!headedReady) {
|
|
1436
|
+
console.error(
|
|
1437
|
+
'[runOfflineFull] WARNING: Xvfb :99 did not start — falling back to headless',
|
|
1438
|
+
);
|
|
1439
|
+
videoOn = false;
|
|
1440
|
+
headedOn = false;
|
|
1441
|
+
process.env['TEST_OFFLINE_VIDEO'] = 'false';
|
|
1442
|
+
process.env['TEST_OFFLINE_HEADED'] = 'false';
|
|
1410
1443
|
} else {
|
|
1411
|
-
console.error(
|
|
1444
|
+
console.error(
|
|
1445
|
+
`[runOfflineFull] headed under Xvfb :99 (${videoOn ? 'recording' : 'no recording'})`,
|
|
1446
|
+
);
|
|
1412
1447
|
}
|
|
1413
1448
|
}
|
|
1414
1449
|
|
|
1415
1450
|
const playwrightEnv: NodeJS.ProcessEnv = {
|
|
1416
1451
|
...process.env,
|
|
1417
1452
|
// Point headed Chromium + the screen-video fixture at the Xvfb display.
|
|
1418
|
-
...(
|
|
1453
|
+
...(headedReady ? { DISPLAY: ':99' } : {}),
|
|
1419
1454
|
// Expose the per-run test DB to specs so they can seed/inspect the
|
|
1420
1455
|
// app's OWN Postgres directly (e.g. seed Article rows for
|
|
1421
1456
|
// list/detail flows). The mock ShopState covers Shopify resources,
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
*
|
|
30
30
|
* Run: `npm run test:online:verify-contracts`.
|
|
31
31
|
*/
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
32
|
+
import { resolve } from 'node:path';
|
|
33
|
+
import { resolveContracts } from '@essential-apps/shopify-test-contracts/operation-contract';
|
|
34
34
|
import {
|
|
35
35
|
createAdminApi,
|
|
36
36
|
createStorefrontApi,
|
|
@@ -112,16 +112,6 @@ function buildOfflineExecutor(api: ApiType, state: ShopState): Executor {
|
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
interface Contract {
|
|
116
|
-
operationName: string;
|
|
117
|
-
source: string;
|
|
118
|
-
variables: Record<string, unknown>;
|
|
119
|
-
response: unknown;
|
|
120
|
-
capturedFrom: 'offline' | 'live';
|
|
121
|
-
capturedAt: string;
|
|
122
|
-
warning?: string;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
115
|
/**
|
|
126
116
|
* Same deterministic ShopState seed `captureContracts` uses. The
|
|
127
117
|
* contracts were captured against this state; verification must
|
|
@@ -214,21 +204,24 @@ function diff(
|
|
|
214
204
|
async function main(): Promise<void> {
|
|
215
205
|
const args = parseArgs();
|
|
216
206
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
207
|
+
// Merged view: shared package goldens (the centralized generic ops)
|
|
208
|
+
// overlaid by the app's local contracts dir, which overrides by
|
|
209
|
+
// operation name. An app vendors a local file only when its shape
|
|
210
|
+
// genuinely diverges from the shared golden.
|
|
211
|
+
const resolved = resolveContracts({ api: args.api, appDir: args.contractsDir });
|
|
212
|
+
if (resolved.length === 0) {
|
|
221
213
|
console.error(
|
|
222
|
-
`[verify-contracts] no contracts
|
|
223
|
-
`
|
|
214
|
+
`[verify-contracts] no contracts for api=${args.api} ` +
|
|
215
|
+
`(shared package + ${args.contractsDir}).`,
|
|
224
216
|
);
|
|
225
217
|
process.exit(2);
|
|
226
218
|
}
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
219
|
+
const sharedCount = resolved.filter((r) => r.origin === 'shared').length;
|
|
220
|
+
const appCount = resolved.length - sharedCount;
|
|
221
|
+
console.log(
|
|
222
|
+
`[verify-contracts] api=${args.api}: ${resolved.length} operations ` +
|
|
223
|
+
`(${sharedCount} shared, ${appCount} app-local override)`,
|
|
224
|
+
);
|
|
232
225
|
|
|
233
226
|
const state = buildSeededState();
|
|
234
227
|
const executor: Executor =
|
|
@@ -239,16 +232,9 @@ async function main(): Promise<void> {
|
|
|
239
232
|
let skipped = 0;
|
|
240
233
|
const failures: { contract: string; diff: string }[] = [];
|
|
241
234
|
|
|
242
|
-
for (const
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
try {
|
|
246
|
-
st = statSync(path);
|
|
247
|
-
} catch {
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
if (!st.isFile()) continue;
|
|
251
|
-
const contract = JSON.parse(readFileSync(path, 'utf8')) as Contract;
|
|
235
|
+
for (const entry of resolved) {
|
|
236
|
+
const contract = entry.contract;
|
|
237
|
+
const label = `${entry.operationName} [${entry.origin}]`;
|
|
252
238
|
// Contracts captured with a warning never executed cleanly;
|
|
253
239
|
// skip them in verify (they need fixtures.json before they can
|
|
254
240
|
// be verified).
|
|
@@ -262,7 +248,7 @@ async function main(): Promise<void> {
|
|
|
262
248
|
} catch (err) {
|
|
263
249
|
drift++;
|
|
264
250
|
failures.push({
|
|
265
|
-
contract:
|
|
251
|
+
contract: label,
|
|
266
252
|
diff: `executor threw: ${(err as Error).message}`,
|
|
267
253
|
});
|
|
268
254
|
continue;
|
|
@@ -280,7 +266,7 @@ async function main(): Promise<void> {
|
|
|
280
266
|
pass++;
|
|
281
267
|
} else {
|
|
282
268
|
drift++;
|
|
283
|
-
failures.push({ contract:
|
|
269
|
+
failures.push({ contract: label, diff: d });
|
|
284
270
|
}
|
|
285
271
|
}
|
|
286
272
|
|
|
@@ -291,9 +277,7 @@ async function main(): Promise<void> {
|
|
|
291
277
|
console.log('');
|
|
292
278
|
console.log('[verify-contracts] drift:');
|
|
293
279
|
for (const f of failures) {
|
|
294
|
-
console.log(
|
|
295
|
-
` ${relative(args.cwd, resolve(args.contractsDir, f.contract))}`,
|
|
296
|
-
);
|
|
280
|
+
console.log(` ${f.contract}`);
|
|
297
281
|
console.log(` ${f.diff}`);
|
|
298
282
|
}
|
|
299
283
|
console.log('');
|
|
@@ -20,8 +20,11 @@
|
|
|
20
20
|
* GraphQL verifier uses. Offline-offline matches byte-for-byte
|
|
21
21
|
* anyway; the normaliser only matters for live-vs-contract.
|
|
22
22
|
*/
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
import { resolve } from 'node:path';
|
|
24
|
+
import {
|
|
25
|
+
resolveRestContracts,
|
|
26
|
+
type RestContract,
|
|
27
|
+
} from '@essential-apps/shopify-test-contracts/operation-contract';
|
|
25
28
|
import {
|
|
26
29
|
createAdminApi,
|
|
27
30
|
} from '@essential-apps/shopify-test-shopify-api';
|
|
@@ -33,21 +36,7 @@ interface Args {
|
|
|
33
36
|
cwd: string;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
operationName: string;
|
|
38
|
-
protocol: 'rest';
|
|
39
|
-
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
40
|
-
path: string;
|
|
41
|
-
pathParams?: Record<string, string>;
|
|
42
|
-
query?: Record<string, string>;
|
|
43
|
-
body?: unknown;
|
|
44
|
-
response: {
|
|
45
|
-
status: number;
|
|
46
|
-
body: unknown;
|
|
47
|
-
};
|
|
48
|
-
capturedFrom: 'offline';
|
|
49
|
-
warning?: string;
|
|
50
|
-
}
|
|
39
|
+
type Contract = RestContract;
|
|
51
40
|
|
|
52
41
|
const ADMIN_API_VERSION = '2025-07';
|
|
53
42
|
|
|
@@ -173,21 +162,20 @@ function diff(
|
|
|
173
162
|
|
|
174
163
|
async function main(): Promise<void> {
|
|
175
164
|
const args = parseArgs();
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
165
|
+
// Merged view: shared package REST goldens overlaid by the app's
|
|
166
|
+
// local admin-rest dir (app wins by operation name).
|
|
167
|
+
const resolved = resolveRestContracts({ appDir: args.contractsDir });
|
|
168
|
+
if (resolved.length === 0) {
|
|
180
169
|
console.error(
|
|
181
|
-
`[verify-rest] no contracts
|
|
182
|
-
`Run \`npm run test:online:capture-rest-contracts\` first.`,
|
|
170
|
+
`[verify-rest] no REST contracts (shared package + ${args.contractsDir}).`,
|
|
183
171
|
);
|
|
184
172
|
process.exit(2);
|
|
185
173
|
}
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
174
|
+
const sharedCount = resolved.filter((r) => r.origin === 'shared').length;
|
|
175
|
+
console.log(
|
|
176
|
+
`[verify-rest] ${resolved.length} operations ` +
|
|
177
|
+
`(${sharedCount} shared, ${resolved.length - sharedCount} app-local override)`,
|
|
178
|
+
);
|
|
191
179
|
|
|
192
180
|
const state = buildSeededState();
|
|
193
181
|
const executor: RestExecutor =
|
|
@@ -198,16 +186,9 @@ async function main(): Promise<void> {
|
|
|
198
186
|
let skipped = 0;
|
|
199
187
|
const failures: { contract: string; diff: string }[] = [];
|
|
200
188
|
|
|
201
|
-
for (const
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
st = statSync(path);
|
|
206
|
-
} catch {
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
if (!st.isFile()) continue;
|
|
210
|
-
const contract = JSON.parse(readFileSync(path, 'utf8')) as Contract;
|
|
189
|
+
for (const entry of resolved) {
|
|
190
|
+
const contract = entry.contract as Contract;
|
|
191
|
+
const label = `${entry.operationName} [${entry.origin}]`;
|
|
211
192
|
if (contract.warning) {
|
|
212
193
|
skipped++;
|
|
213
194
|
continue;
|
|
@@ -217,14 +198,14 @@ async function main(): Promise<void> {
|
|
|
217
198
|
actual = await executor(contract);
|
|
218
199
|
} catch (err) {
|
|
219
200
|
drift++;
|
|
220
|
-
failures.push({ contract:
|
|
201
|
+
failures.push({ contract: label, diff: `executor threw: ${(err as Error).message}` });
|
|
221
202
|
continue;
|
|
222
203
|
}
|
|
223
204
|
// Status-code mismatch is a hard diff — surface it directly.
|
|
224
205
|
if (actual.status !== contract.response.status) {
|
|
225
206
|
drift++;
|
|
226
207
|
failures.push({
|
|
227
|
-
contract:
|
|
208
|
+
contract: label,
|
|
228
209
|
diff: `$.status: expected ${contract.response.status}, got ${actual.status}`,
|
|
229
210
|
});
|
|
230
211
|
continue;
|
|
@@ -238,7 +219,7 @@ async function main(): Promise<void> {
|
|
|
238
219
|
pass++;
|
|
239
220
|
} else {
|
|
240
221
|
drift++;
|
|
241
|
-
failures.push({ contract:
|
|
222
|
+
failures.push({ contract: label, diff: d });
|
|
242
223
|
}
|
|
243
224
|
}
|
|
244
225
|
|
|
@@ -249,7 +230,7 @@ async function main(): Promise<void> {
|
|
|
249
230
|
console.log('');
|
|
250
231
|
console.log('[verify-rest] drift:');
|
|
251
232
|
for (const f of failures) {
|
|
252
|
-
console.log(` ${
|
|
233
|
+
console.log(` ${f.contract}`);
|
|
253
234
|
console.log(` ${f.diff}`);
|
|
254
235
|
}
|
|
255
236
|
console.log('');
|
package/dist/edge/nodeShim.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nodeShim.d.ts","sourceRoot":"","sources":["../../src/edge/nodeShim.ts"],"names":[],"mappings":""}
|