@alfresco/adf-cli 8.5.0-25850979174 → 8.5.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alfresco/adf-cli",
3
3
  "description": "Alfresco ADF cli and utils",
4
- "version": "8.5.0-25850979174",
4
+ "version": "8.5.0",
5
5
  "author": "Hyland Software, Inc. and its affiliates",
6
6
  "bin": {
7
7
  "adf-cli": "bin/adf-cli",
@@ -14,17 +14,9 @@
14
14
  "bugs": {
15
15
  "url": "https://github.com/Alfresco/alfresco-ng2-components/issues"
16
16
  },
17
- "scripts": {
18
- "build": "tsc -p tsconfig.json",
19
- "develop": "tsc -p tsconfig.json --watch",
20
- "dist": "rm -rf ../../dist/libs/cli && npm run build && cp -R ./bin ../../dist/libs/cli && cp -R ./resources ../../dist/libs/cli && cp -R ./templates ../../dist/libs/cli && cp ./package.json ../../dist/libs/cli",
21
- "link": "npm run dist && cd ../../dist/libs/cli && npm link",
22
- "unlink": "cd ../../dist/libs/cli && npm unlink"
23
- },
24
17
  "dependencies": {
25
- "@alfresco/js-api": ">=9.5.0-25850979174",
18
+ "@alfresco/js-api": ">=9.4.0",
26
19
  "ejs": "^3.1.10",
27
- "license-checker": "^25.0.1",
28
20
  "node-fetch": "^2.7.0",
29
21
  "spdx-license-list": "^5.0.0"
30
22
  },
@@ -36,5 +28,12 @@
36
28
  "@types/ejs": "^3.1.2",
37
29
  "@types/node": "^20.1.7",
38
30
  "typescript": "^4.9.5"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc -p tsconfig.json",
34
+ "develop": "tsc -p tsconfig.json --watch",
35
+ "dist": "rm -rf ../../dist/libs/cli && npm run build && cp -R ./bin ../../dist/libs/cli && cp -R ./resources ../../dist/libs/cli && cp -R ./templates ../../dist/libs/cli && cp ./package.json ../../dist/libs/cli",
36
+ "link": "npm run dist && cd ../../dist/libs/cli && npm link",
37
+ "unlink": "cd ../../dist/libs/cli && npm unlink"
39
38
  }
40
- }
39
+ }
@@ -0,0 +1,192 @@
1
+ /*!
2
+ * @license
3
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ const fs = require('node:fs');
19
+ const path = require('node:path');
20
+
21
+ function readJson(filePath) {
22
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
23
+ }
24
+
25
+ function resolvePackageJson(packageName, fromDirectory) {
26
+ try {
27
+ return require.resolve(`${packageName}/package.json`, {
28
+ paths: [fromDirectory]
29
+ });
30
+ } catch {
31
+ return '';
32
+ }
33
+ }
34
+
35
+ function getRepositoryUrl(repository) {
36
+ if (typeof repository === 'string') {
37
+ return repository;
38
+ }
39
+
40
+ if (repository && typeof repository === 'object' && typeof repository.url === 'string') {
41
+ return repository.url;
42
+ }
43
+
44
+ return '';
45
+ }
46
+
47
+ function getRawLicenseExpression(packageJson) {
48
+ if (typeof packageJson.license === 'string' && packageJson.license.trim()) {
49
+ return packageJson.license.trim();
50
+ }
51
+
52
+ if (packageJson.license && typeof packageJson.license === 'object' && typeof packageJson.license.type === 'string') {
53
+ return packageJson.license.type.trim();
54
+ }
55
+
56
+ if (Array.isArray(packageJson.licenses)) {
57
+ const values = packageJson.licenses
58
+ .map((entry) => {
59
+ if (typeof entry === 'string') {
60
+ return entry.trim();
61
+ }
62
+
63
+ if (entry && typeof entry === 'object' && typeof entry.type === 'string') {
64
+ return entry.type.trim();
65
+ }
66
+
67
+ return '';
68
+ })
69
+ .filter(Boolean);
70
+
71
+ if (values.length > 0) {
72
+ return values.join(' OR ');
73
+ }
74
+ }
75
+
76
+ return 'UNKNOWN';
77
+ }
78
+
79
+ function extractLicenseTokens(rawExpression) {
80
+ const tokens = (rawExpression || '').match(/[A-Za-z0-9-.+]+/g) || [];
81
+ const operators = new Set(['AND', 'OR', 'WITH']);
82
+
83
+ return tokens.map((token) => token.toUpperCase()).filter((token) => !operators.has(token));
84
+ }
85
+
86
+ function hasDeniedLicense(rawExpression, denyList) {
87
+ const licenseTokens = extractLicenseTokens(rawExpression);
88
+
89
+ return denyList.some((rule) => {
90
+ const normalizedRule = (rule || '').trim().toUpperCase();
91
+ if (!normalizedRule) {
92
+ return false;
93
+ }
94
+
95
+ // Treat common copyleft family keywords as SPDX id prefixes.
96
+ if (normalizedRule === 'GPL' || normalizedRule === 'LGPL' || normalizedRule === 'AGPL') {
97
+ return licenseTokens.some((token) => token === normalizedRule || token.startsWith(`${normalizedRule}-`));
98
+ }
99
+
100
+ // Support explicit wildcard rules like "GPL-*".
101
+ if (normalizedRule.endsWith('*')) {
102
+ const prefix = normalizedRule.slice(0, -1);
103
+ return prefix.length > 0 && licenseTokens.some((token) => token.startsWith(prefix));
104
+ }
105
+
106
+ // Otherwise require exact SPDX token match.
107
+ return licenseTokens.includes(normalizedRule);
108
+ });
109
+ }
110
+
111
+ function getDependencyNames(packageJson) {
112
+ return Array.from(new Set([...Object.keys(packageJson.dependencies || {}), ...Object.keys(packageJson.optionalDependencies || {})]));
113
+ }
114
+
115
+ function collectProductionLicenses(packagePath, options = {}) {
116
+ const packageJsonFile = readJson(packagePath);
117
+ const mainDependencies = getDependencyNames(packageJsonFile);
118
+ const packageDirectory = path.dirname(packagePath);
119
+ const missingRepositories = options.missingRepositories || {};
120
+ const denyList = options.denyList || [];
121
+
122
+ const queue = mainDependencies.map((name) => ({
123
+ name,
124
+ fromDirectory: packageDirectory
125
+ }));
126
+
127
+ const visited = new Set();
128
+ const packages = [];
129
+ const deniedPackages = [];
130
+
131
+ for (const current of queue) {
132
+ const packageJsonPath = resolvePackageJson(current.name, current.fromDirectory);
133
+
134
+ if (!packageJsonPath || !fs.existsSync(packageJsonPath)) {
135
+ continue;
136
+ }
137
+
138
+ const currentPackage = readJson(packageJsonPath);
139
+ const name = currentPackage.name || current.name;
140
+ const version = currentPackage.version || 'unknown';
141
+ const packageFolder = path.dirname(packageJsonPath);
142
+ const uniqueKey = `${name}@${version}:${packageFolder}`;
143
+
144
+ if (visited.has(uniqueKey)) {
145
+ continue;
146
+ }
147
+ visited.add(uniqueKey);
148
+
149
+ const rawLicenseExpression = getRawLicenseExpression(currentPackage);
150
+ const packageKey = `${name}@${version}`;
151
+ const repository = getRepositoryUrl(currentPackage.repository) || missingRepositories[name] || '';
152
+
153
+ const record = {
154
+ key: packageKey,
155
+ name,
156
+ version,
157
+ repository,
158
+ rawLicenseExpression,
159
+ dependencies: currentPackage.dependencies || {},
160
+ optionalDependencies: currentPackage.optionalDependencies || {}
161
+ };
162
+
163
+ packages.push(record);
164
+
165
+ if (hasDeniedLicense(rawLicenseExpression, denyList)) {
166
+ deniedPackages.push(`${packageKey}: ${rawLicenseExpression}`);
167
+ }
168
+
169
+ const dependencies = getDependencyNames(currentPackage);
170
+ for (const dependencyName of dependencies) {
171
+ queue.push({
172
+ name: dependencyName,
173
+ fromDirectory: packageFolder
174
+ });
175
+ }
176
+ }
177
+
178
+ packages.sort((a, b) => {
179
+ const nameCompare = a.name.localeCompare(b.name);
180
+ if (nameCompare !== 0) {
181
+ return nameCompare;
182
+ }
183
+
184
+ return a.version.localeCompare(b.version);
185
+ });
186
+
187
+ return { packages, deniedPackages };
188
+ }
189
+
190
+ module.exports = {
191
+ collectProductionLicenses
192
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"init-acs-env.d.ts","sourceRoot":"","sources":["../../../../lib/cli/scripts/init-acs-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAsBH;;GAEG;AACH,wBAA8B,IAAI,kBAwDjC"}
1
+ {"version":3,"file":"init-acs-env.d.ts","sourceRoot":"","sources":["../../../../lib/cli/scripts/init-acs-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA0BH;;GAEG;AACH,wBAA8B,IAAI,kBAkDjC"}
@@ -57,10 +57,13 @@ const fs_1 = require("fs");
57
57
  const path = __importStar(require("path"));
58
58
  const logger_1 = require("./logger");
59
59
  const MAX_RETRY = 10;
60
- let counter = 0;
61
- const TIMEOUT = 6000;
60
+ const RETRY_DELAY_MS = 6000;
62
61
  const ACS_DEFAULT = require('./resources').ACS_DEFAULT;
63
62
  let alfrescoJsApi;
63
+ let nodesApi;
64
+ let uploadApi;
65
+ let sharedlinksApi;
66
+ let favoritesApi;
64
67
  /**
65
68
  * Init ACS environment command
66
69
  */
@@ -72,7 +75,6 @@ Usage: init-acs-env [options]
72
75
  Initialize ACS environment
73
76
 
74
77
  Options:
75
- -v, --version Output the version number
76
78
  --host <host> Remote environment host
77
79
  --clientId <id> SSO client (default: "alfresco")
78
80
  -p, --password <pass> Password
@@ -81,10 +83,6 @@ Options:
81
83
  `);
82
84
  (0, node_process_1.exit)(0);
83
85
  }
84
- if (node_process_1.argv.includes('-v') || node_process_1.argv.includes('--version')) {
85
- console.log('0.1.0');
86
- (0, node_process_1.exit)(0);
87
- }
88
86
  const { values } = (0, node_util_1.parseArgs)({
89
87
  args: node_process_1.argv.slice(2),
90
88
  options: {
@@ -113,156 +111,266 @@ Options:
113
111
  password: values.password
114
112
  };
115
113
  await checkEnv(opts);
116
- logger_1.logger.info(`***** Step initialize ACS *****`);
114
+ logger_1.logger.info('***** Step initialize ACS *****');
117
115
  await initializeDefaultFiles();
118
116
  }
119
117
  /**
120
- * Setup default files
118
+ * Initialize default files. Creates the e2e folder and ensures each file
119
+ * exists with its required state (locked, shared, favorite).
120
+ * Idempotent: only creates/modifies what is missing.
121
121
  */
122
122
  async function initializeDefaultFiles() {
123
- const e2eFolder = ACS_DEFAULT.e2eFolder;
124
- const parentFolder = await createFolder(e2eFolder.name, '-my-');
125
- const parentFolderId = parentFolder.entry.id;
126
- for (let j = 0; j < ACS_DEFAULT.files.length; j++) {
127
- const fileInfo = ACS_DEFAULT.files[j];
128
- switch (fileInfo.action) {
129
- case 'UPLOAD': {
130
- await uploadFile(fileInfo.name, parentFolderId);
131
- break;
132
- }
133
- case 'LOCK': {
134
- const fileToLock = await uploadFile(fileInfo.name, parentFolderId);
135
- await lockFile(fileToLock.entry.id);
136
- break;
137
- }
138
- case 'SHARE': {
139
- const fileToShare = await uploadFile(fileInfo.name, parentFolderId);
140
- await shareFile(fileToShare.entry.id);
141
- break;
142
- }
143
- case 'FAVORITE': {
144
- const fileToFav = await uploadFile(fileInfo.name, parentFolderId);
145
- await favoriteFile(fileToFav.entry.id);
146
- break;
147
- }
148
- default: {
149
- logger_1.logger.error('No action found for file ', fileInfo.name, parentFolderId);
150
- break;
151
- }
152
- }
123
+ const e2eFolderName = ACS_DEFAULT.e2eFolder.name;
124
+ let parentFolder;
125
+ try {
126
+ parentFolder = await withRetry(() => ensureFolder(e2eFolderName, '-my-'), `ensure folder ${e2eFolderName}`);
127
+ }
128
+ catch (error) {
129
+ logger_1.logger.warn(`Skipping file initialization: test-data folder could not be created: ${formatError(error)}`);
130
+ return;
131
+ }
132
+ const parentFolderId = getEntryId(parentFolder, `folder ${e2eFolderName}`);
133
+ for (const fileInfo of ACS_DEFAULT.files) {
134
+ await withRetry(() => processFile(fileInfo, parentFolderId), `initialize ${fileInfo.name}`);
153
135
  }
154
136
  }
155
137
  /**
156
- * Create folder
157
- *
138
+ * Process a single file: upload if missing, then apply its action (lock/share/favorite).
139
+ * @param fileInfo file descriptor from ACS_DEFAULT
140
+ * @param fileInfo.name file name
141
+ * @param fileInfo.action action to apply (LOCK, SHARE, FAVORITE)
142
+ * @param parentFolderId parent folder node id
143
+ */
144
+ async function processFile(fileInfo, parentFolderId) {
145
+ const existingNode = await findNodeByRelativePath(parentFolderId, fileInfo.name);
146
+ let nodeId;
147
+ if (existingNode?.entry?.id) {
148
+ logger_1.logger.info(`File ${fileInfo.name} already exists, verifying required state.`);
149
+ nodeId = existingNode.entry.id;
150
+ }
151
+ else {
152
+ const createdNode = await uploadFile(fileInfo.name, parentFolderId);
153
+ nodeId = getEntryId(createdNode, `file ${fileInfo.name}`);
154
+ }
155
+ switch (fileInfo.action) {
156
+ case 'LOCK':
157
+ await ensureLocked(nodeId, fileInfo.name, existingNode?.entry?.isLocked);
158
+ break;
159
+ case 'SHARE':
160
+ await ensureShared(nodeId, fileInfo.name);
161
+ break;
162
+ case 'FAVORITE':
163
+ await ensureFavorite(nodeId, fileInfo.name);
164
+ break;
165
+ default:
166
+ break;
167
+ }
168
+ }
169
+ /**
170
+ * Ensure a folder exists under the given parent. Creates it if missing.
171
+ * Handles 409 conflict (race condition) by fetching the existing folder.
158
172
  * @param folderName folder name
159
- * @param parentId parent folder id
173
+ * @param parentId parent node id
174
+ * @returns the folder node entry
160
175
  */
161
- async function createFolder(folderName, parentId) {
162
- let createdFolder;
163
- const body = {
164
- name: folderName,
165
- nodeType: 'cm:folder'
166
- };
176
+ async function ensureFolder(folderName, parentId) {
177
+ const existingFolder = await findNodeByRelativePath(parentId, folderName);
178
+ if (existingFolder?.entry?.id) {
179
+ if (!existingFolder.entry.isFolder) {
180
+ throw new Error(`Cannot use ${folderName} as test-data folder: a non-folder node with that name already exists (nodeType: ${existingFolder.entry.nodeType}, id: ${existingFolder.entry.id}).`);
181
+ }
182
+ logger_1.logger.info(`Folder ${folderName} already exists.`);
183
+ return existingFolder;
184
+ }
167
185
  try {
168
- createdFolder = await new js_api_1.NodesApi(alfrescoJsApi).createNode(parentId, body, { overwrite: true });
186
+ const createdFolder = await nodesApi.createNode(parentId, { name: folderName, nodeType: 'cm:folder' }, { overwrite: true });
169
187
  logger_1.logger.info(`Folder ${folderName} was created`);
188
+ return createdFolder;
170
189
  }
171
- catch (err) {
172
- if (err.status === 409) {
173
- const relativePath = `/${folderName}`;
174
- createdFolder = await new js_api_1.NodesApi(alfrescoJsApi).getNode('-my-', { relativePath });
190
+ catch (error) {
191
+ if (error?.status === 409) {
192
+ const conflictingFolder = await findNodeByRelativePath(parentId, folderName);
193
+ if (conflictingFolder?.entry?.id) {
194
+ if (!conflictingFolder.entry.isFolder) {
195
+ throw new Error(`Cannot use ${folderName} as test-data folder: a non-folder node with that name already exists (nodeType: ${conflictingFolder.entry.nodeType}, id: ${conflictingFolder.entry.id}).`);
196
+ }
197
+ logger_1.logger.info(`Folder ${folderName} already exists.`);
198
+ return conflictingFolder;
199
+ }
175
200
  }
201
+ throw new Error(`Failed to ensure folder ${folderName}: ${formatError(error)}`);
176
202
  }
177
- return createdFolder;
178
203
  }
179
204
  /**
180
- * Upload file
181
- *
205
+ * Find a node by relative path under a parent. Returns null if not found (404).
206
+ * @param parentId parent node id
207
+ * @param fileName relative path / file name
208
+ * @returns the node entry or null if not found
209
+ */
210
+ async function findNodeByRelativePath(parentId, fileName) {
211
+ try {
212
+ return await nodesApi.getNode(parentId, { relativePath: `/${fileName}`, include: ['isLocked'] });
213
+ }
214
+ catch (error) {
215
+ if (error?.status === 404) {
216
+ return null;
217
+ }
218
+ throw new Error(`Failed to fetch ${fileName}: ${formatError(error)}`);
219
+ }
220
+ }
221
+ /**
222
+ * Upload a file to the given destination folder.
182
223
  * @param fileName file name
183
- * @param fileDestination destination path
224
+ * @param destinationId destination folder node id
225
+ * @returns the uploaded node entry
184
226
  */
185
- async function uploadFile(fileName, fileDestination) {
227
+ async function uploadFile(fileName, destinationId) {
186
228
  const filePath = `../resources/content/${fileName}`;
187
229
  const file = (0, fs_1.createReadStream)(path.join(__dirname, filePath));
188
- let uploadedFile;
189
230
  try {
190
- uploadedFile = await new js_api_1.UploadApi(alfrescoJsApi).uploadFile(file, '', fileDestination, null, {
231
+ const uploadedFile = await uploadApi.uploadFile(file, '', destinationId, null, {
191
232
  name: fileName,
192
233
  nodeType: 'cm:content',
193
234
  renditions: 'doclib',
194
235
  overwrite: true
195
236
  });
196
237
  logger_1.logger.info(`File ${fileName} was uploaded`);
238
+ return uploadedFile;
197
239
  }
198
- catch (err) {
199
- logger_1.logger.error(`Failed to upload file with error: `, err.stack);
240
+ catch (error) {
241
+ throw new Error(`Failed to upload ${fileName}: ${formatError(error)}`);
200
242
  }
201
- return uploadedFile;
202
243
  }
203
244
  /**
204
- * Lock file node
205
- *
245
+ * Ensure a file is locked. Skips if already locked.
206
246
  * @param nodeId node id
247
+ * @param fileName file name (for logging)
248
+ * @param isAlreadyLocked whether the node is already locked
207
249
  */
208
- async function lockFile(nodeId) {
209
- const data = {
210
- type: 'ALLOW_OWNER_CHANGES'
211
- };
250
+ async function ensureLocked(nodeId, fileName, isAlreadyLocked = false) {
251
+ if (isAlreadyLocked) {
252
+ logger_1.logger.info(`File ${fileName} is already locked.`);
253
+ return;
254
+ }
212
255
  try {
213
- const result = new js_api_1.NodesApi(alfrescoJsApi).lockNode(nodeId, data);
214
- logger_1.logger.info('File was locked');
215
- return result;
256
+ await nodesApi.lockNode(nodeId, { type: 'ALLOW_OWNER_CHANGES' });
257
+ logger_1.logger.info(`File ${fileName} was locked`);
216
258
  }
217
259
  catch (error) {
218
- logger_1.logger.error('Failed to lock file with error: ', error.stack);
219
- return null;
260
+ throw new Error(`Failed to lock ${fileName}: ${formatError(error)}`);
220
261
  }
221
262
  }
222
263
  /**
223
- * Share file node
224
- *
264
+ * Ensure a file is shared. Handles 409 (already shared) gracefully.
225
265
  * @param nodeId node id
266
+ * @param fileName file name (for logging)
226
267
  */
227
- async function shareFile(nodeId) {
228
- const data = {
229
- nodeId
230
- };
268
+ async function ensureShared(nodeId, fileName) {
231
269
  try {
232
- await new js_api_1.SharedlinksApi(alfrescoJsApi).createSharedLink(data);
233
- logger_1.logger.info('File was shared');
270
+ await sharedlinksApi.createSharedLink({ nodeId });
271
+ logger_1.logger.info(`File ${fileName} was shared`);
234
272
  }
235
273
  catch (error) {
236
- logger_1.logger.error('Failed to share file with error: ', error.stack);
274
+ if (error?.status === 409) {
275
+ logger_1.logger.info(`File ${fileName} is already shared.`);
276
+ return;
277
+ }
278
+ throw new Error(`Failed to share ${fileName}: ${formatError(error)}`);
237
279
  }
238
280
  }
239
281
  /**
240
- * Favorite file node
241
- *
282
+ * Ensure a file is favorite. Handles 409 (already favorite) gracefully.
242
283
  * @param nodeId node id
284
+ * @param fileName file name (for logging)
243
285
  */
244
- async function favoriteFile(nodeId) {
245
- const data = {
246
- target: {
247
- ['file']: {
248
- guid: nodeId
286
+ async function ensureFavorite(nodeId, fileName) {
287
+ try {
288
+ await favoritesApi.createFavorite('-me-', {
289
+ target: {
290
+ file: {
291
+ guid: nodeId
292
+ }
249
293
  }
294
+ });
295
+ logger_1.logger.info(`File ${fileName} was added to favorites`);
296
+ }
297
+ catch (error) {
298
+ if (error?.status === 409) {
299
+ logger_1.logger.info(`File ${fileName} is already a favorite.`);
300
+ return;
250
301
  }
251
- };
302
+ throw new Error(`Failed to favorite ${fileName}: ${formatError(error)}`);
303
+ }
304
+ }
305
+ /**
306
+ * Extract entry id from a node response. Throws if missing.
307
+ * @param nodeEntry node entry response
308
+ * @param label label for error message
309
+ * @returns the node id
310
+ */
311
+ function getEntryId(nodeEntry, label) {
312
+ const nodeId = nodeEntry?.entry?.id;
313
+ if (nodeId) {
314
+ return nodeId;
315
+ }
316
+ throw new Error(`Missing ACS response entry for ${label}.`);
317
+ }
318
+ /**
319
+ * Format an error for logging.
320
+ * @param error error object
321
+ * @returns formatted error string
322
+ */
323
+ function formatError(error) {
324
+ if (!error) {
325
+ return 'Unknown error';
326
+ }
327
+ if (typeof error === 'string') {
328
+ return error;
329
+ }
252
330
  try {
253
- await new js_api_1.FavoritesApi(alfrescoJsApi).createFavorite('-me-', data);
254
- logger_1.logger.info('File was add to favorites');
331
+ return error?.message || error?.stack || JSON.stringify(error);
255
332
  }
256
- catch (error) {
257
- logger_1.logger.error('Failed to add the file to favorites with error: ', error.stack);
333
+ catch {
334
+ return 'Unknown error (unable to serialize)';
258
335
  }
259
336
  }
260
337
  /**
261
- * Check environment state
262
- *
338
+ * Retry wrapper for transient failures.
339
+ * @param fn async function to execute
340
+ * @param label label for logging
341
+ * @param maxAttempts maximum retry attempts
342
+ * @returns the result of the function
343
+ */
344
+ async function withRetry(fn, label, maxAttempts = 3) {
345
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
346
+ try {
347
+ return await fn();
348
+ }
349
+ catch (error) {
350
+ if (attempt === maxAttempts) {
351
+ logger_1.logger.error(`${label}: failed after ${maxAttempts} attempts: ${formatError(error)}`);
352
+ throw error;
353
+ }
354
+ logger_1.logger.warn(`${label}: attempt ${attempt} failed, retrying in ${RETRY_DELAY_MS / 1000}s: ${formatError(error)}`);
355
+ await wait(RETRY_DELAY_MS);
356
+ }
357
+ }
358
+ throw new Error(`${label}: exhausted all ${maxAttempts} attempts`);
359
+ }
360
+ /**
361
+ * Async delay.
362
+ * @param ms milliseconds to wait
363
+ * @returns a promise that resolves after the delay
364
+ */
365
+ function wait(ms) {
366
+ return new Promise((resolve) => setTimeout(resolve, ms));
367
+ }
368
+ /**
369
+ * Check environment state and authenticate. Retries on transient failures.
263
370
  * @param opts command options
371
+ * @param attempt current attempt number
264
372
  */
265
- async function checkEnv(opts) {
373
+ async function checkEnv(opts, attempt = 1) {
266
374
  try {
267
375
  alfrescoJsApi = new js_api_1.AlfrescoApi({
268
376
  provider: 'ALL',
@@ -278,32 +386,26 @@ async function checkEnv(opts) {
278
386
  contextRoot: 'alfresco'
279
387
  });
280
388
  await alfrescoJsApi.login(opts.username, opts.password);
389
+ nodesApi = new js_api_1.NodesApi(alfrescoJsApi);
390
+ uploadApi = new js_api_1.UploadApi(alfrescoJsApi);
391
+ sharedlinksApi = new js_api_1.SharedlinksApi(alfrescoJsApi);
392
+ favoritesApi = new js_api_1.FavoritesApi(alfrescoJsApi);
281
393
  }
282
- catch (e) {
283
- if (e.error.code === 'ETIMEDOUT') {
284
- logger_1.logger.error('The env is not reachable. Terminating');
285
- (0, node_process_1.exit)(1);
394
+ catch (error) {
395
+ const errorCode = error?.error?.code;
396
+ if (errorCode === 'ETIMEDOUT' || error?.status === 504) {
397
+ logger_1.logger.warn('Login attempt timed out or received a gateway error, environment may still be starting up.');
398
+ }
399
+ else {
400
+ logger_1.logger.error('Login error, environment down or inaccessible.');
286
401
  }
287
- logger_1.logger.error('Login error environment down or inaccessible');
288
- counter++;
289
- if (MAX_RETRY === counter) {
402
+ if (attempt >= MAX_RETRY) {
290
403
  logger_1.logger.error('Give up');
291
404
  (0, node_process_1.exit)(1);
292
405
  }
293
- else {
294
- logger_1.logger.error(`Retry in 1 minute attempt N ${counter}`);
295
- sleep(TIMEOUT);
296
- await checkEnv(opts);
297
- }
406
+ logger_1.logger.warn(`Retry in ${RETRY_DELAY_MS / 1000} seconds, attempt ${attempt}`);
407
+ await wait(RETRY_DELAY_MS);
408
+ await checkEnv(opts, attempt + 1);
298
409
  }
299
410
  }
300
- /**
301
- * Perform a delay
302
- *
303
- * @param delay timeout in milliseconds
304
- */
305
- function sleep(delay) {
306
- const start = new Date().getTime();
307
- while (new Date().getTime() < start + delay) { }
308
- }
309
411
  //# sourceMappingURL=init-acs-env.js.map