Skip to content

Commit 922e522

Browse files
committed
add deploy report
1 parent 915bf66 commit 922e522

11 files changed

Lines changed: 146 additions & 72 deletions

File tree

bin/pos-cli-sync.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ program
5050
// Continue with watch mode
5151
const { watcher, liveReloadServer } = await watchStart(env, params.directAssetsUpload, params.livereload);
5252

53-
setupGracefulShutdown({ watcher, liveReloadServer, context: 'Sync' });
54-
5553
if (params.open) {
5654
try {
5755
const open = (await import('open')).default;
@@ -64,6 +62,8 @@ program
6462
}
6563
}
6664
}
65+
66+
setupGracefulShutdown({ watcher, liveReloadServer, context: 'Sync' });
6767
});
6868

6969
program.parse(process.argv);

lib/assets.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ const deployAssets = async gateway => {
4545
const manifest = await manifestGenerate();
4646
logger.Debug(manifest);
4747
files.writeJSON('tmp/assets_manifest.json', manifest);
48-
await gateway.sendManifest(manifest);
48+
const response = await gateway.sendManifest(manifest);
4949
logger.Debug('Uploading assets');
50+
return response;
5051
} catch (e) {
5152
logger.Debug(e);
5253
logger.Debug(e.message);

lib/deploy/directAssetsUploadStrategy.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,18 @@ const deployAndUploadAssets = async (authData) => {
1919
logger.Warn('There are no assets to deploy, skipping.');
2020
return;
2121
}
22-
await deployAssets(new Gateway(authData));
22+
return deployAssets(new Gateway(authData));
23+
};
24+
25+
const printAssetsReport = (response) => {
26+
if (!response || !response.report) return;
27+
const { upserted = 0, deleted = 0 } = response.report;
28+
const parts = [];
29+
if (upserted > 0) parts.push(`${upserted} upserted`);
30+
if (deleted > 0) parts.push(`${deleted} deleted`);
31+
if (parts.length > 0) {
32+
logger.Success(`Assets: ${parts.join(', ')}`, { hideTimestamp: true });
33+
}
2334
};
2435

2536
const strategy = async ({ env, authData, _params }) => {
@@ -33,14 +44,15 @@ const strategy = async ({ env, authData, _params }) => {
3344
spinner.start();
3445

3546
const t0 = performance.now();
47+
const assetsResult = await deployAndUploadAssets(authData);
48+
printAssetsReport(assetsResult);
49+
3650
if (numberOfFiles > 0) {
3751
await uploadArchive(env);
3852
} else {
3953
logger.Warn('There are no files in release file, skipping.');
4054
}
4155

42-
await deployAndUploadAssets(authData);
43-
4456
spinner.succeed(`Deploy succeeded after ${duration(t0, performance.now())}`);
4557
} catch (e) {
4658
if (ServerError.isNetworkError(e)) {

lib/push.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,44 @@
11
import fs from 'fs';
22
import { performance } from 'perf_hooks';
3+
import chalk from 'chalk';
34

45
import logger from './logger.js';
56
import report from './logger/report.js';
67
import Gateway from '../lib/proxy.js';
78
import duration from '../lib/duration.js';
89
let gateway;
910

11+
const toCount = (val) => Array.isArray(val) ? val.length : (typeof val === 'number' ? val : 0);
12+
13+
const printDeployReport = (deployReport) => {
14+
if (!deployReport) return;
15+
16+
const lines = [];
17+
for (const [category, data] of Object.entries(deployReport)) {
18+
const { upserted = [], deleted = [] } = data || {};
19+
const upsertedCount = toCount(upserted);
20+
const deletedCount = toCount(deleted);
21+
22+
if (upsertedCount === 0 && deletedCount === 0) continue;
23+
24+
const parts = [];
25+
if (upsertedCount > 0) parts.push(`${upsertedCount} upserted`);
26+
if (deletedCount > 0) parts.push(`${deletedCount} deleted`);
27+
lines.push(` ${category}: ${parts.join(', ')}`);
28+
29+
if (Array.isArray(upserted)) {
30+
upserted.forEach(p => lines.push(` + ${p}`));
31+
}
32+
if (Array.isArray(deleted)) {
33+
deleted.forEach(p => lines.push(chalk.red(` - ${p}`)));
34+
}
35+
}
36+
37+
if (lines.length === 0) return;
38+
39+
logger.Success(['\nDeploy report:', ...lines].join('\n'), { hideTimestamp: true });
40+
};
41+
1042
const getDeploymentStatus = ({ id }) => {
1143
return new Promise((resolve, reject) => {
1244
let getStatus = () => {
@@ -56,6 +88,7 @@ const push = async env => {
5688
if (response.warning) {
5789
logger.Warn(response.warning);
5890
}
91+
printDeployReport(response.report);
5992
const t1 = performance.now();
6093
return duration(t0, t1);
6194
});

lib/watch.js

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,7 @@ const pushFile = async (gateway, syncedFilePath) => {
6969
logger.Warn('[Sync] WARNING: Data schema was updated. It will take a while for the change to be applied.');
7070
}
7171

72-
if (body) {
73-
logger.Success(`[Sync] Synced: ${filePath}`);
74-
}
72+
logger.Success(`[Sync] Synced: ${filePath}`);
7573
} catch (e) {
7674
// Handle validation errors (422) with custom formatting
7775
if (e.statusCode === 422 && e.response && e.response.body) {
@@ -87,8 +85,8 @@ const pushFile = async (gateway, syncedFilePath) => {
8785
}
8886
};
8987

90-
const deleteFile = (gateway, syncedFilePath) => {
91-
let filePath = filePathUnixified(syncedFilePath);
88+
const deleteFile = async (gateway, syncedFilePath) => {
89+
const filePath = filePathUnixified(syncedFilePath);
9290
const formData = {
9391
path: filePath,
9492
primary_key: filePath
@@ -188,15 +186,7 @@ const start = async (env, directAssetsUpload, liveReload) => {
188186
push(gateway, task.path)
189187
.then(reload)
190188
.then(callback)
191-
.catch(error => {
192-
// If error was already logged, just continue processing queue
193-
if (error.alreadyLogged) {
194-
callback();
195-
} else {
196-
// For other errors, still continue queue processing
197-
callback();
198-
}
199-
});
189+
.catch(() => callback());
200190
break;
201191
case 'delete':
202192
deleteFile(gateway, task.path).then(reload).then(callback);
@@ -222,12 +212,11 @@ const start = async (env, directAssetsUpload, liveReload) => {
222212
'**/.DS_Store'
223213
]
224214
})
215+
.on('ready', () => logger.Info(`[Sync] Synchronizing changes to: ${program.url}`))
225216
.on('change', fp => shouldBeSynced(fp, ignoreList) && enqueuePush(fp))
226217
.on('add', fp => shouldBeSynced(fp, ignoreList) && enqueuePush(fp))
227218
.on('unlink', fp => shouldBeSynced(fp, ignoreList) && enqueueDelete(fp));
228219

229-
logger.Info(`[Sync] Synchronizing changes to: ${program.url}`);
230-
231220
return { watcher, liveReloadServer };
232221
});
233222
};

test/global-setup.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { execSync } from 'child_process';
2+
import path from 'path';
3+
import { fileURLToPath } from 'url';
4+
import dotenv from 'dotenv';
5+
6+
const __filename = fileURLToPath(import.meta.url);
7+
const __dirname = path.dirname(__filename);
8+
9+
const cliPath = `node ${path.join(__dirname, '../bin/pos-cli.js')}`;
10+
11+
export async function setup() {
12+
dotenv.config();
13+
14+
const { MPKIT_URL, MPKIT_TOKEN, MPKIT_EMAIL } = process.env;
15+
if (!MPKIT_URL || !MPKIT_TOKEN || !MPKIT_EMAIL || MPKIT_URL.includes('example.com')) {
16+
console.log('[Global Setup] No real credentials found, skipping instance cleanup');
17+
return;
18+
}
19+
20+
console.log(`[Global Setup] Cleaning instance: ${MPKIT_URL}`);
21+
execSync(`${cliPath} data clean --include-schema --auto-confirm`, {
22+
env: process.env,
23+
stdio: 'inherit'
24+
});
25+
console.log('[Global Setup] Instance cleaned');
26+
}

test/integration/sync.test.js

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,21 @@ import 'dotenv/config';
22
import { describe, test, expect, afterAll, afterEach, vi } from 'vitest';
33
import exec from '#test/utils/exec';
44
import cliPath from '#test/utils/cliPath';
5+
import waitForOutput from '#test/utils/waitForOutput';
56
import path from 'path';
67
import fs from 'fs';
78
import { requireRealCredentials } from '#test/utils/credentials';
89

910
vi.setConfig({ testTimeout: 30000 });
1011

11-
// Force this test file to run in sequence to avoid race conditions with fixture files
12-
// @vitest-environment node
13-
14-
const stepTimeout = 3500;
15-
1612
const cwd = name => path.join(process.cwd(), 'test', 'fixtures', 'deploy', name);
1713
const run = (fixtureName, options, callback) => {
1814
return exec(
19-
`${cliPath} sync ${options}`,
15+
`${cliPath} sync ${options || ''}`,
2016
{ cwd: cwd(fixtureName), env: process.env },
2117
callback
2218
);
2319
};
24-
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
2520

2621
const kill = p => {
2722
p.stdout.destroy();
@@ -50,15 +45,14 @@ afterAll(() => {
5045
}
5146
});
5247

53-
// Skip all tests if credentials aren't available
5448
describe('Happy path', () => {
5549
test('sync assets', { retry: 2 }, async () => {
5650
requireRealCredentials();
5751

5852
const steps = async (child) => {
59-
await sleep(stepTimeout);
53+
await waitForOutput(child, /Synchronizing changes to/);
6054
exec('echo "x" >> app/assets/bar.js', { cwd: cwd('correct_with_assets') });
61-
await sleep(stepTimeout);
55+
await waitForOutput(child, /\[Sync\] Synced asset: app\/assets\/bar\.js/);
6256
kill(child);
6357
};
6458

@@ -70,9 +64,9 @@ describe('Happy path', () => {
7064

7165
test('sync with direct assets upload', { retry: 2 }, async () => {
7266
const steps = async (child) => {
73-
await sleep(stepTimeout);
67+
await waitForOutput(child, /Synchronizing changes to/);
7468
exec('echo "x" >> app/assets/bar.js', { cwd: cwd('correct_with_assets') });
75-
await sleep(stepTimeout);
69+
await waitForOutput(child, /\[Sync\] Synced asset: app\/assets\/bar\.js/);
7670
kill(child);
7771
};
7872
const { stdout } = await run('correct_with_assets', '-d', steps);
@@ -94,53 +88,45 @@ properties:
9488
const testDir = path.join(cwd('correct_with_assets'), 'app', dir);
9589
const testFile = path.join(cwd('correct_with_assets'), 'app', fileName);
9690

97-
// Wait for sync to initialize before creating file
98-
await sleep(stepTimeout);
91+
await waitForOutput(child, /Synchronizing changes to/);
9992

100-
// Use Node.js fs for cross-platform compatibility
10193
if (!fs.existsSync(testDir)) {
10294
fs.mkdirSync(testDir, { recursive: true });
10395
}
10496

10597
fs.writeFileSync(testFile, validYML);
106-
// Wait longer for sync to complete (stabilityThreshold 500ms + network time + queue processing)
107-
await sleep(stepTimeout * 2);
98+
await waitForOutput(child, new RegExp(`\\[Sync\\] Synced: ${fileName.replace(/\//g, '[/\\\\]')}`));
10899

109100
fs.unlinkSync(testFile);
110-
await sleep(stepTimeout);
101+
await waitForOutput(child, new RegExp(`\\[Sync\\] Deleted: ${fileName.replace(/\//g, '[/\\\\]')}`));
102+
111103
kill(child);
112104
};
113105
const { stdout } = await run('correct_with_assets', null, steps);
114106

115107
expect(stdout).toMatch(process.env.MPKIT_URL);
116-
// Use regex to handle potential path separator differences
117108
expect(stdout).toMatch(new RegExp(`\\[Sync\\] Synced: ${fileName.replace(/\//g, '[/\\\\]')}`));
118109
expect(stdout).toMatch(new RegExp(`\\[Sync\\] Deleted: ${fileName.replace(/\//g, '[/\\\\]')}`));
119110
});
120111

121112
test('sync single file with -f option', { retry: 2 }, async () => {
122113
requireRealCredentials();
123114

124-
// Create a temporary file to sync
125115
const testFilePath = 'app/views/pages/test-single-sync.liquid';
126116
const fullTestPath = path.join(cwd('correct_with_assets'), testFilePath);
127117
const testContent = '<!-- Test single file sync -->\n<h1>Test Page</h1>\n';
128118

129-
// Write test file
130119
fs.writeFileSync(fullTestPath, testContent);
131120

132121
try {
133-
// Run sync with -f option (without callback, so it runs to completion)
134122
const { stdout, code } = await exec(
135123
`${cliPath} sync -f ${testFilePath}`,
136124
{ cwd: cwd('correct_with_assets'), env: process.env }
137125
);
138126

139-
// Verify output - note that filePathUnixified removes the app/ prefix
140127
expect(code).toBe(0);
141128
expect(stdout).toMatch(/\[Sync\] Synced: views\/pages\/test-single-sync\.liquid/);
142129
} finally {
143-
// Clean up test file
144130
if (fs.existsSync(fullTestPath)) {
145131
fs.unlinkSync(fullTestPath);
146132
}
@@ -150,26 +136,21 @@ properties:
150136
test('sync single asset file with -f option', { retry: 2 }, async () => {
151137
requireRealCredentials();
152138

153-
// Create a temporary asset file to sync
154139
const testFilePath = 'app/assets/test-single-sync.js';
155140
const fullTestPath = path.join(cwd('correct_with_assets'), testFilePath);
156141
const testContent = '// Test single asset file sync\nconsole.log("test");\n';
157142

158-
// Write test file
159143
fs.writeFileSync(fullTestPath, testContent);
160144

161145
try {
162-
// Run sync with -f option (without callback, so it runs to completion)
163146
const { stdout, code } = await exec(
164147
`${cliPath} sync -f ${testFilePath}`,
165148
{ cwd: cwd('correct_with_assets'), env: process.env }
166149
);
167150

168-
// Verify output
169151
expect(code).toBe(0);
170152
expect(stdout).toMatch(/\[Sync\] Synced asset: app\/assets\/test-single-sync\.js/);
171153
} finally {
172-
// Clean up test file
173154
if (fs.existsSync(fullTestPath)) {
174155
fs.unlinkSync(fullTestPath);
175156
}
@@ -179,27 +160,18 @@ properties:
179160
test('422 validation error shows proper format with single error message', { retry: 2 }, async () => {
180161
requireRealCredentials();
181162

182-
// Use fixture with invalid schema file that triggers 422 validation error
183163
const testFilePath = 'app/schema/invalid-property-type.yml';
184164

185-
// Run sync with -f option - this should fail with validation error
186165
const { stderr, code } = await exec(
187166
`${cliPath} sync -f ${testFilePath}`,
188167
{ cwd: cwd('invalid_schema'), env: process.env }
189168
);
190169

191-
// Verify error output
192170
expect(code).toBe(1);
193-
194-
// Should show [Sync] Failed to sync with timestamp and file path
195171
expect(stderr).toMatch(/\[\d{2}:\d{2}:\d{2}\] \[Sync\] Failed to sync: schema\/invalid-property-type\.yml/);
196-
197-
// Should include the validation error message
198172
expect(stderr).toMatch(/Validation failed/);
199173

200-
// Verify error is NOT duplicated - count occurrences of "Failed to sync"
201174
const failedToSyncMatches = (stderr.match(/Failed to sync/g) || []).length;
202175
expect(failedToSyncMatches).toBe(1);
203176
});
204-
205177
});

0 commit comments

Comments
 (0)