@alfresco/adf-cli 8.5.0-24716296101 → 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 +10 -11
- package/resources/license-collector.cjs +192 -0
- package/scripts/init-acs-env.d.ts.map +1 -1
- package/scripts/init-acs-env.js +221 -119
- package/scripts/init-acs-env.js.map +1 -1
- package/scripts/init-aps-env.d.ts.map +1 -1
- package/scripts/init-aps-env.js +124 -71
- package/scripts/init-aps-env.js.map +1 -1
- package/scripts/licenses.d.ts.map +1 -1
- package/scripts/licenses.js +44 -56
- package/scripts/licenses.js.map +1 -1
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
|
|
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.
|
|
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;
|
|
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"}
|
package/scripts/init-acs-env.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
114
|
+
logger_1.logger.info('***** Step initialize ACS *****');
|
|
117
115
|
await initializeDefaultFiles();
|
|
118
116
|
}
|
|
119
117
|
/**
|
|
120
|
-
*
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
*
|
|
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
|
|
173
|
+
* @param parentId parent node id
|
|
174
|
+
* @returns the folder node entry
|
|
160
175
|
*/
|
|
161
|
-
async function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
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 (
|
|
172
|
-
if (
|
|
173
|
-
const
|
|
174
|
-
|
|
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
|
-
*
|
|
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
|
|
224
|
+
* @param destinationId destination folder node id
|
|
225
|
+
* @returns the uploaded node entry
|
|
184
226
|
*/
|
|
185
|
-
async function uploadFile(fileName,
|
|
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
|
|
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 (
|
|
199
|
-
|
|
240
|
+
catch (error) {
|
|
241
|
+
throw new Error(`Failed to upload ${fileName}: ${formatError(error)}`);
|
|
200
242
|
}
|
|
201
|
-
return uploadedFile;
|
|
202
243
|
}
|
|
203
244
|
/**
|
|
204
|
-
*
|
|
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
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
214
|
-
logger_1.logger.info(
|
|
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
|
-
|
|
219
|
-
return null;
|
|
260
|
+
throw new Error(`Failed to lock ${fileName}: ${formatError(error)}`);
|
|
220
261
|
}
|
|
221
262
|
}
|
|
222
263
|
/**
|
|
223
|
-
*
|
|
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
|
|
228
|
-
const data = {
|
|
229
|
-
nodeId
|
|
230
|
-
};
|
|
268
|
+
async function ensureShared(nodeId, fileName) {
|
|
231
269
|
try {
|
|
232
|
-
await
|
|
233
|
-
logger_1.logger.info(
|
|
270
|
+
await sharedlinksApi.createSharedLink({ nodeId });
|
|
271
|
+
logger_1.logger.info(`File ${fileName} was shared`);
|
|
234
272
|
}
|
|
235
273
|
catch (error) {
|
|
236
|
-
|
|
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
|
-
*
|
|
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
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
254
|
-
logger_1.logger.info('File was add to favorites');
|
|
331
|
+
return error?.message || error?.stack || JSON.stringify(error);
|
|
255
332
|
}
|
|
256
|
-
catch
|
|
257
|
-
|
|
333
|
+
catch {
|
|
334
|
+
return 'Unknown error (unable to serialize)';
|
|
258
335
|
}
|
|
259
336
|
}
|
|
260
337
|
/**
|
|
261
|
-
*
|
|
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 (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
(
|
|
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
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|