@@ -5,6 +5,55 @@ const path = require('path');
55const fs = require ( 'fs' ) ;
66const archiver = require ( 'archiver' ) ;
77
8+ /**
9+ * Polls an SFCC job execution until it completes.
10+ * @param {string } instance - The SFCC instance host.
11+ * @param {string } jobId - The job ID to poll.
12+ * @param {string } executionId - The job execution ID to poll.
13+ * @param {string } token - The OAuth token to use for authentication.
14+ * @returns {Promise<void> } Resolves when the execution status is 'OK', rejects on error or timeout.
15+ */
16+ function waitForJobCompletion ( instance , jobId , executionId , token ) {
17+ const maxAttempts = 60 ;
18+ const delay = 5000 ; // 5 seconds
19+
20+ return new Promise ( ( resolve , reject ) => {
21+ let attempts = 0 ;
22+
23+ function poll ( ) {
24+ attempts ++ ;
25+ sfcc . job . status ( instance , jobId , executionId , token , ( err , status ) => {
26+ if ( err ) {
27+ reject ( err ) ;
28+ return ;
29+ }
30+
31+ const execStatus = status && status . status ;
32+ console . info ( 'Attempt %d: %s execution status - %s' , attempts , jobId , execStatus ) ;
33+
34+ if ( execStatus === 'OK' ) {
35+ resolve ( ) ;
36+ return ;
37+ }
38+
39+ if ( execStatus === 'ERROR' || execStatus === 'error' || execStatus === 'failed' ) {
40+ reject ( new Error ( `${ jobId } (execution ${ executionId } ) finished with status ${ execStatus } .` ) ) ;
41+ return ;
42+ }
43+
44+ if ( attempts >= maxAttempts ) {
45+ reject ( new Error ( `${ jobId } (execution ${ executionId } ) did not complete within ${ maxAttempts } attempts.` ) ) ;
46+ return ;
47+ }
48+
49+ setTimeout ( poll , delay ) ;
50+ } ) ;
51+ }
52+
53+ poll ( ) ;
54+ } ) ;
55+ }
56+
857async function importPreferences ( ) {
958 try {
1059 const token = await authenticate ( ) ;
@@ -92,18 +141,30 @@ async function importPreferences() {
92141 } ) ;
93142
94143 console . log ( 'Importing site preferences...' ) ;
95- await new Promise ( ( resolve , reject ) => {
96- sfcc . instance . import ( instance , 'site_import.zip' , token , ( err ) => {
144+ const importExecutionId = await new Promise ( ( resolve , reject ) => {
145+ sfcc . instance . import ( instance , 'site_import.zip' , token , ( err , result ) => {
97146 if ( err ) {
98147 console . error ( 'Import error:' , err ) ;
99148 reject ( err ) ;
100- } else {
101- console . log ( 'Site preferences imported successfully.' ) ;
102- resolve ( ) ;
149+ return ;
103150 }
151+ // `sfcc.instance.import` resolves as soon as the `sfcc-site-archive-import` job is
152+ // accepted, not when it finishes. Capture the execution id so we can poll for completion.
153+ const executionId = result && result . id ;
154+ if ( ! executionId ) {
155+ reject ( new Error ( 'Site import was started but no execution id was returned: ' + JSON . stringify ( result ) ) ) ;
156+ return ;
157+ }
158+ console . info ( 'Site preferences import started (execution id: %s).' , executionId ) ;
159+ resolve ( executionId ) ;
104160 } ) ;
105161 } ) ;
106162
163+ // Wait for the import to actually finish before returning. Without this, the caller can run the
164+ // indexing job while the preferences (e.g. Algolia_RecordModel, Algolia_IndexPrefix) are still
165+ // being applied, which produces records in the wrong shape/index.
166+ await waitForJobCompletion ( instance , 'sfcc-site-archive-import' , importExecutionId , token ) ;
167+
107168 console . log ( 'Site preferences import completed successfully.' ) ;
108169 } catch ( error ) {
109170 console . error ( 'Error:' , error ) ;
0 commit comments