@magentrix-corp/magentrix-cli 1.0.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/LICENSE +25 -0
- package/README.md +471 -0
- package/actions/autopublish.js +283 -0
- package/actions/autopublish.old.js +293 -0
- package/actions/autopublish.v2.js +447 -0
- package/actions/create.js +329 -0
- package/actions/help.js +165 -0
- package/actions/main.js +81 -0
- package/actions/publish.js +567 -0
- package/actions/pull.js +139 -0
- package/actions/setup.js +61 -0
- package/actions/status.js +17 -0
- package/bin/magentrix.js +159 -0
- package/package.json +61 -0
- package/utils/cacher.js +112 -0
- package/utils/cli/checkInstanceUrl.js +29 -0
- package/utils/cli/helpers/compare.js +281 -0
- package/utils/cli/helpers/ensureApiKey.js +57 -0
- package/utils/cli/helpers/ensureCredentials.js +60 -0
- package/utils/cli/helpers/ensureInstanceUrl.js +63 -0
- package/utils/cli/writeRecords.js +223 -0
- package/utils/compare.js +135 -0
- package/utils/compress.js +18 -0
- package/utils/config.js +451 -0
- package/utils/diff.js +49 -0
- package/utils/downloadAssets.js +75 -0
- package/utils/filetag.js +115 -0
- package/utils/hash.js +14 -0
- package/utils/magentrix/api/assets.js +145 -0
- package/utils/magentrix/api/auth.js +56 -0
- package/utils/magentrix/api/createEntity.js +61 -0
- package/utils/magentrix/api/deleteEntity.js +55 -0
- package/utils/magentrix/api/meqlQuery.js +31 -0
- package/utils/magentrix/api/retrieveEntity.js +32 -0
- package/utils/magentrix/api/updateEntity.js +66 -0
- package/utils/magentrix/fetch.js +154 -0
- package/utils/merge.js +22 -0
- package/utils/preferences.js +40 -0
- package/utils/spinner.js +43 -0
- package/utils/template.js +52 -0
- package/utils/updateFileBase.js +103 -0
- package/vars/config.js +1 -0
- package/vars/global.js +33 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { EXPORT_ROOT } from "../vars/global.js";
|
|
2
|
+
import { downloadAssetsZip, listAssets } from "./magentrix/api/assets.js";
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import extract from 'extract-zip';
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
import fspath from 'path';
|
|
7
|
+
import { setFileTag } from "./filetag.js";
|
|
8
|
+
|
|
9
|
+
export const walkAssets = async (instanceUrl, token, assetPath) => {
|
|
10
|
+
const assetResults = await listAssets(instanceUrl, token, assetPath);
|
|
11
|
+
const walkedAssets = [];
|
|
12
|
+
|
|
13
|
+
for (const asset of assetResults.Assets) {
|
|
14
|
+
if (asset.Type === 'Folder') {
|
|
15
|
+
walkedAssets.push({
|
|
16
|
+
...asset,
|
|
17
|
+
Path: asset.Path.replace('/contents/assets', '/Contents/Assets'),
|
|
18
|
+
Children: await walkAssets(instanceUrl, token, asset.Path),
|
|
19
|
+
ParentFolder: assetResults.CurrentPath.replace("/contents/assets", "/Contents/Assets"),
|
|
20
|
+
})
|
|
21
|
+
} else {
|
|
22
|
+
walkedAssets.push({
|
|
23
|
+
...asset,
|
|
24
|
+
Path: asset.Path.replace("/contents/assets", "/Contents/Assets"),
|
|
25
|
+
ParentFolder: assetResults.CurrentPath.replace("/contents/assets", "/Contents/Assets"),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return walkedAssets;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const downloadAssets = async (instanceUrl, token, path) => {
|
|
34
|
+
const allAssets = await walkAssets(instanceUrl, token, path);
|
|
35
|
+
|
|
36
|
+
const iterateDownload = async (assets) => {
|
|
37
|
+
const parentOfAssets = assets?.[0]?.ParentFolder;
|
|
38
|
+
const folders = assets.filter(asset => asset.Type === 'Folder');
|
|
39
|
+
const files = assets.filter(asset => asset.Type === 'File');
|
|
40
|
+
|
|
41
|
+
for (const folder of folders) {
|
|
42
|
+
fs.mkdirSync(fspath.join(EXPORT_ROOT, folder.Path), { recursive: true });
|
|
43
|
+
await iterateDownload(folder?.Children || []);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (files.length > 0) {
|
|
47
|
+
const savedAs = await downloadAssetsZip({
|
|
48
|
+
baseUrl: instanceUrl,
|
|
49
|
+
token: token, // "Bearer" prefix added in code
|
|
50
|
+
path: parentOfAssets,
|
|
51
|
+
names: files.map(file => file.Name),
|
|
52
|
+
outFile: fspath.join(EXPORT_ROOT, parentOfAssets, 'assets.zip'), // optional
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await extract(savedAs, {
|
|
56
|
+
dir: fspath.resolve(fspath.join(EXPORT_ROOT, parentOfAssets))
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// for (const file of files) {
|
|
60
|
+
// // Ensure the file was created properly
|
|
61
|
+
// // TODO: Create some error handling incase it wasn't
|
|
62
|
+
// const downloadedPath = fspath.join(EXPORT_ROOT, file.Path);
|
|
63
|
+
// if (!fs.existsSync(downloadedPath)) continue;
|
|
64
|
+
// }
|
|
65
|
+
|
|
66
|
+
fs.rmSync(savedAs);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await iterateDownload(allAssets);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
tree: allAssets
|
|
74
|
+
};
|
|
75
|
+
}
|
package/utils/filetag.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import Config from './config.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const platform = os.platform();
|
|
8
|
+
const config = new Config(); // uses default project config
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generates a unique key from a file's stat object
|
|
12
|
+
* @param {fs.Stats} stats
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
function getFileKey(stats) {
|
|
16
|
+
return `${stats.dev}:${stats.ino}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Tags a file with a persistent ID using platform-specific methods and stores it via Config.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} filePath - Path to the file.
|
|
23
|
+
* @param {string} tag - Tag to assign.
|
|
24
|
+
* @returns {Promise<void>}
|
|
25
|
+
*/
|
|
26
|
+
export async function setFileTag(filePath, tag) {
|
|
27
|
+
const absPath = path.resolve(filePath);
|
|
28
|
+
const stats = fs.statSync(absPath);
|
|
29
|
+
const key = getFileKey(stats);
|
|
30
|
+
|
|
31
|
+
// Try platform tagging
|
|
32
|
+
try {
|
|
33
|
+
if (platform === 'darwin' || platform === 'linux') {
|
|
34
|
+
const xattr = await import('xattr');
|
|
35
|
+
await xattr.set(absPath, 'user.fileTag', Buffer.from(tag));
|
|
36
|
+
} else if (platform === 'win32') {
|
|
37
|
+
const streamPath = `${absPath}:fileTag`;
|
|
38
|
+
fs.writeFileSync(streamPath, tag);
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// xattr/ADS is optional; we fall back to config
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const tracked = config.read('trackedFileTags', { global: false, filename: 'fileIdIndex.json' }) || {};
|
|
45
|
+
tracked[key] = { tag, lastKnownPath: absPath };
|
|
46
|
+
config.save('trackedFileTags', tracked, { global: false, filename: 'fileIdIndex.json' });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Retrieves a file tag from metadata or Config, even after renames.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} filePath - Current path to the file.
|
|
53
|
+
* @returns {Promise<string|null>}
|
|
54
|
+
*/
|
|
55
|
+
export async function getFileTag(filePath) {
|
|
56
|
+
const absPath = path.resolve(filePath);
|
|
57
|
+
let stats;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
stats = fs.statSync(absPath);
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const key = getFileKey(stats);
|
|
66
|
+
const tracked = config.read('trackedFileTags', { global: false, filename: 'fileIdIndex.json' }) || {};
|
|
67
|
+
|
|
68
|
+
// Try platform method first
|
|
69
|
+
try {
|
|
70
|
+
if (platform === 'darwin' || platform === 'linux') {
|
|
71
|
+
const xattr = await import('xattr');
|
|
72
|
+
const buf = await xattr.get(absPath, 'user.fileTag');
|
|
73
|
+
return buf.toString();
|
|
74
|
+
} else if (platform === 'win32') {
|
|
75
|
+
const streamPath = `${absPath}:fileTag`;
|
|
76
|
+
return fs.readFileSync(streamPath, 'utf8');
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// fallback below
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return tracked[key]?.tag ?? null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Retrieves a file path from its tag.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} tag - The file tag.
|
|
89
|
+
* @returns {Promise<string|null>}
|
|
90
|
+
*/
|
|
91
|
+
export function findFileByTag(tag) {
|
|
92
|
+
const tracked = config.read('trackedFileTags', { global: false, filename: 'fileIdIndex.json' }) || {};
|
|
93
|
+
for (const record of Object.values(tracked)) {
|
|
94
|
+
if (record.tag === tag) return record.lastKnownPath;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Checks if the given file path is tracked in fileIdIndex.json as a lastKnownPath.
|
|
101
|
+
* Returns the tag if found, or null if not linked.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} filePath - Path to check.
|
|
104
|
+
* @returns {string|null} The tag if the path is tracked, otherwise null.
|
|
105
|
+
*/
|
|
106
|
+
export function isPathLinkedToTagByLastKnownPath(filePath) {
|
|
107
|
+
const absPath = path.resolve(filePath);
|
|
108
|
+
const tracked = config.read('trackedFileTags', { global: false, filename: 'fileIdIndex.json' }) || {};
|
|
109
|
+
for (const record of Object.values(tracked)) {
|
|
110
|
+
if (record.lastKnownPath === absPath) {
|
|
111
|
+
return record.tag;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
package/utils/hash.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hash any data to a lowercase hex SHA-256 string.
|
|
5
|
+
* @param {string|Buffer} input
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
export function sha256(input) {
|
|
9
|
+
return crypto.createHash('sha256').update(input).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// --- Usage Example ---
|
|
13
|
+
// import sha256 from './lib/sha256.js';
|
|
14
|
+
// const hash = sha256('some string to hash');
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { fetchMagentrix } from "../fetch.js"
|
|
2
|
+
import { createWriteStream, readFileSync } from "node:fs";
|
|
3
|
+
import { pipeline } from "node:stream/promises";
|
|
4
|
+
import { Readable } from "node:stream";
|
|
5
|
+
import { Blob, File } from "node:buffer";
|
|
6
|
+
import fsPath from 'path';
|
|
7
|
+
|
|
8
|
+
async function fileToFileObj(fp, type = "application/octet-stream") {
|
|
9
|
+
let buf = readFileSync(fp);
|
|
10
|
+
|
|
11
|
+
// If file is empty, inject a single space (or any character)
|
|
12
|
+
if (!buf || buf.length === 0) {
|
|
13
|
+
buf = Buffer.from(" "); // could also use Buffer.from("x") or Buffer.alloc(1)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return new File([buf], fsPath.basename(fp), { type });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const listAssets = async (instanceUrl, token, path) => {
|
|
20
|
+
if (!instanceUrl || !token) {
|
|
21
|
+
throw new Error('Missing required Magentrix instanceUrl or token');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let reqPath = `/api/3.0/staticassets`;
|
|
25
|
+
|
|
26
|
+
if (path) {
|
|
27
|
+
reqPath += `?path=${encodeURIComponent(path)}`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const response = await fetchMagentrix({
|
|
31
|
+
instanceUrl,
|
|
32
|
+
token,
|
|
33
|
+
path: reqPath,
|
|
34
|
+
method: "GET",
|
|
35
|
+
returnErrorObject: true
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return response;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const uploadAsset = async (instanceUrl, token, targetPath, uploadPaths = []) => {
|
|
42
|
+
if (!instanceUrl || !token) {
|
|
43
|
+
throw new Error('Missing required Magentrix instanceUrl or token');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let reqPath = `/api/3.0/staticassets`;
|
|
47
|
+
|
|
48
|
+
if (targetPath) {
|
|
49
|
+
reqPath += `?path=${targetPath}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const formData = new FormData();
|
|
53
|
+
for (const fp of uploadPaths) {
|
|
54
|
+
formData.append("files", await fileToFileObj(fp));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const response = await fetchMagentrix({
|
|
58
|
+
instanceUrl,
|
|
59
|
+
token,
|
|
60
|
+
path: reqPath,
|
|
61
|
+
ignoreContentType: true,
|
|
62
|
+
// headers: {
|
|
63
|
+
// 'Content-Type': "multipart/form-data"
|
|
64
|
+
// },
|
|
65
|
+
body: formData,
|
|
66
|
+
method: "POST",
|
|
67
|
+
returnErrorObject: true
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return response;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Name = asset names (including folders)
|
|
74
|
+
// Path = path to assets
|
|
75
|
+
export const deleteAsset = async (instanceUrl, token, path = '/contents/assets', names = []) => {
|
|
76
|
+
if (!instanceUrl || !token) {
|
|
77
|
+
throw new Error('Missing required Magentrix instanceUrl or token');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!path) throw new Error("Path is required when deleting assets.");
|
|
81
|
+
if (!Array.isArray(names) || names?.length === 0) throw new Error("At least one file name is required when deleting static assets.");
|
|
82
|
+
|
|
83
|
+
let reqPath = `/api/3.0/staticassets?path=${path}&names=${names.join(",")}`;
|
|
84
|
+
|
|
85
|
+
const response = await fetchMagentrix({
|
|
86
|
+
instanceUrl,
|
|
87
|
+
token,
|
|
88
|
+
path: reqPath,
|
|
89
|
+
method: "DELETE",
|
|
90
|
+
returnErrorObject: true
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return response;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Download multiple static assets as a ZIP.
|
|
98
|
+
* @param {object} opts
|
|
99
|
+
* @param {string} opts.baseUrl - e.g. "https://your-app.com"
|
|
100
|
+
* @param {string} opts.token - Bearer token
|
|
101
|
+
* @param {string} opts.path - e.g. "/contents/assets/images"
|
|
102
|
+
* @param {string[]} opts.names - e.g. ["logo.png","banner.jpg"]
|
|
103
|
+
* @param {string} [opts.outFile] - Optional explicit output filename
|
|
104
|
+
*/
|
|
105
|
+
export async function downloadAssetsZip({ baseUrl, token, path, names, outFile }) {
|
|
106
|
+
if (!Array.isArray(names) || names.length === 0) {
|
|
107
|
+
throw new Error("names must be a non-empty string[]");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const url = new URL("/api/3.0/staticassets/download", baseUrl);
|
|
111
|
+
if (path) url.searchParams.set("path", path);
|
|
112
|
+
url.searchParams.set('download-format', 'zip'); // Ensure even one file gets downloaded as a zip
|
|
113
|
+
url.searchParams.set("names", names.join(",")); // API expects comma-separated names
|
|
114
|
+
|
|
115
|
+
const res = await fetch(url, {
|
|
116
|
+
method: "GET",
|
|
117
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!res.ok) {
|
|
121
|
+
// Try to surface server error body if JSON
|
|
122
|
+
let detail = "";
|
|
123
|
+
try {
|
|
124
|
+
const data = await res.json();
|
|
125
|
+
detail = data?.message || JSON.stringify(data);
|
|
126
|
+
} catch {
|
|
127
|
+
/* ignore */
|
|
128
|
+
}
|
|
129
|
+
throw new Error(`Download failed (${res.status}): ${detail || res.statusText}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Determine filename: use header if present, else fallback
|
|
133
|
+
const cd = res.headers.get("content-disposition") || "";
|
|
134
|
+
const match = /filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i.exec(cd);
|
|
135
|
+
const filename = outFile || (match ? decodeURIComponent(match[1]) : "assets.zip");
|
|
136
|
+
|
|
137
|
+
// Stream to disk (no buffering whole file)
|
|
138
|
+
const fileStream = createWriteStream(filename);
|
|
139
|
+
const body = res.body; // ReadableStream (web)
|
|
140
|
+
// Convert web stream -> node stream for pipeline
|
|
141
|
+
const nodeReadable = Readable.fromWeb ? Readable.fromWeb(body) : body;
|
|
142
|
+
|
|
143
|
+
await pipeline(nodeReadable, fileStream);
|
|
144
|
+
return filename;
|
|
145
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { fetchMagentrix } from "../fetch.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Authenticates with Magentrix and retrieves an access token using the API key as a refresh token.
|
|
5
|
+
*
|
|
6
|
+
* @async
|
|
7
|
+
* @param {string} apiKey - The Magentrix API key (used as refresh_token).
|
|
8
|
+
* @param {string} instanceUrl - The full Magentrix instance URL (e.g., https://yourorg.magentrixcloud.com).
|
|
9
|
+
* @throws {Error} If the request fails, the server returns an error, or the response is invalid.
|
|
10
|
+
* @returns {Promise<string>} Resolves to the Magentrix access token string.
|
|
11
|
+
*/
|
|
12
|
+
export const getAccessToken = async (apiKey, instanceUrl) => {
|
|
13
|
+
try {
|
|
14
|
+
const data = await fetchMagentrix({
|
|
15
|
+
instanceUrl,
|
|
16
|
+
path: '/api/3.0/token',
|
|
17
|
+
body: {
|
|
18
|
+
grant_type: "refresh_token",
|
|
19
|
+
refresh_token: apiKey
|
|
20
|
+
},
|
|
21
|
+
method: "POST"
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// Success
|
|
25
|
+
return {
|
|
26
|
+
token: data.token,
|
|
27
|
+
validUntil: data.validUntil
|
|
28
|
+
};
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new Error(`Error retrieving Magentrix access token: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Attempts to authenticate with the given API key and instance URL.
|
|
36
|
+
* If authentication fails, throws an error with a friendly message and error details.
|
|
37
|
+
*
|
|
38
|
+
* @async
|
|
39
|
+
* @param {string} apiKey - The Magentrix API key to authenticate with.
|
|
40
|
+
* @param {string} instanceUrl - The Magentrix instance URL to authenticate against.
|
|
41
|
+
* @returns {Promise<Object>} The token data returned by getAccessToken if successful.
|
|
42
|
+
* @throws {Error} If authentication fails, with a message explaining the failure.
|
|
43
|
+
*/
|
|
44
|
+
export const tryAuthenticate = async (apiKey, instanceUrl) => {
|
|
45
|
+
try {
|
|
46
|
+
return await getAccessToken(apiKey, instanceUrl);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`❌ Failed to authenticate with Magentrix:\n` +
|
|
50
|
+
`The API key and/or instance URL you provided are incorrect or do not match.\n` +
|
|
51
|
+
`Please double-check both values and try again.\n\n` +
|
|
52
|
+
`Details: ${error.message || error}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { fetchMagentrix } from "../fetch.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a new ActiveClass or ActivePage entity in Magentrix via the REST API.
|
|
5
|
+
*
|
|
6
|
+
* @async
|
|
7
|
+
* @function createEntity
|
|
8
|
+
* @param {string} instanceUrl - The base URL of the Magentrix instance.
|
|
9
|
+
* @param {string} token - The OAuth2 bearer token for authentication.
|
|
10
|
+
* @param {string} entityName - The Magentrix entity type. Allowed: 'ActiveClass' or 'ActivePage' (case-insensitive).
|
|
11
|
+
* @param {Object} data - The entity data to create. Must include 'Name' and 'Type'.
|
|
12
|
+
* @returns {Promise<Object>} The response data from Magentrix API if successful.
|
|
13
|
+
* @throws {Error} Throws if required parameters are missing, entityName is invalid, or if required fields in data are missing.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const entity = await createEntity('https://your.magentrix.com', 'yourToken', 'ActiveClass', {
|
|
17
|
+
* Name: "MyNewClass",
|
|
18
|
+
* Body: "...",
|
|
19
|
+
* Description: "A test class",
|
|
20
|
+
* Type: "Class"
|
|
21
|
+
* });
|
|
22
|
+
*/
|
|
23
|
+
export const createEntity = async (instanceUrl, token, entityName, data) => {
|
|
24
|
+
// --- Validate required parameters ---
|
|
25
|
+
if (!instanceUrl || !token) {
|
|
26
|
+
throw new Error('Missing required Magentrix instanceUrl or token');
|
|
27
|
+
}
|
|
28
|
+
if (!entityName || typeof entityName !== 'string') {
|
|
29
|
+
throw new Error("Missing required 'entityName' (must be 'ActiveClass' or 'ActivePage')");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Allowed entity types
|
|
33
|
+
const allowedEntities = ['activeclass', 'activepage'];
|
|
34
|
+
const entity = entityName.trim().toLowerCase();
|
|
35
|
+
if (!allowedEntities.includes(entity)) {
|
|
36
|
+
throw new Error("Invalid 'entityName'. Allowed values: 'ActiveClass' or 'ActivePage'");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Validate data object and required fields
|
|
40
|
+
if (!data || typeof data !== 'object') {
|
|
41
|
+
throw new Error('Missing required data object for entity creation');
|
|
42
|
+
}
|
|
43
|
+
if (!data.Name) {
|
|
44
|
+
throw new Error("Missing required field: 'Name' in data");
|
|
45
|
+
}
|
|
46
|
+
if (!data.Type) {
|
|
47
|
+
throw new Error("Missing required field: 'Type' in data");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const response = await fetchMagentrix({
|
|
51
|
+
instanceUrl,
|
|
52
|
+
token,
|
|
53
|
+
path: `/api/3.0/entity/${entity}`,
|
|
54
|
+
method: "POST",
|
|
55
|
+
body: data,
|
|
56
|
+
returnErrorObject: true
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// --- Success: return parsed response ---
|
|
60
|
+
return response;
|
|
61
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { fetchMagentrix } from "../fetch.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deletes an existing ActiveClass or ActivePage entity in Magentrix via the REST API.
|
|
5
|
+
*
|
|
6
|
+
* @async
|
|
7
|
+
* @function deleteEntity
|
|
8
|
+
* @param {string} instanceUrl - The base URL of the Magentrix instance (e.g. "https://your.magentrix.com").
|
|
9
|
+
* @param {string} token - The OAuth2 bearer token for authentication.
|
|
10
|
+
* @param {string} entityName - The Magentrix entity type. Allowed: "ActiveClass" or "ActivePage" (case-insensitive).
|
|
11
|
+
* @param {string} recordId - The unique Magentrix record ID of the entity to delete.
|
|
12
|
+
* @returns {Promise<Object>} The API response object, or an error object if the deletion fails.
|
|
13
|
+
* @throws {Error} If required parameters are missing or invalid.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const deleted = await deleteEntity(
|
|
17
|
+
* "https://your.magentrix.com",
|
|
18
|
+
* "yourToken",
|
|
19
|
+
* "ActiveClass",
|
|
20
|
+
* "06bdc45e-8222-44f5-9ed2-40f5a7bc6cb3"
|
|
21
|
+
* );
|
|
22
|
+
*/
|
|
23
|
+
export const deleteEntity = async (instanceUrl, token, entityName, recordId) => {
|
|
24
|
+
// --- Validate required parameters ---
|
|
25
|
+
if (!instanceUrl || typeof instanceUrl !== 'string') {
|
|
26
|
+
throw new Error('Missing or invalid Magentrix instanceUrl');
|
|
27
|
+
}
|
|
28
|
+
if (!token || typeof token !== 'string') {
|
|
29
|
+
throw new Error('Missing or invalid Magentrix token');
|
|
30
|
+
}
|
|
31
|
+
if (!entityName || typeof entityName !== 'string') {
|
|
32
|
+
throw new Error("Missing or invalid 'entityName' (must be 'ActiveClass' or 'ActivePage')");
|
|
33
|
+
}
|
|
34
|
+
if (!recordId || typeof recordId !== 'string') {
|
|
35
|
+
throw new Error("Missing or invalid 'recordId' (must be a Magentrix record GUID string)");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- Validate entity type ---
|
|
39
|
+
const allowedEntities = ['activeclass', 'activepage'];
|
|
40
|
+
const entity = entityName.trim().toLowerCase();
|
|
41
|
+
if (!allowedEntities.includes(entity)) {
|
|
42
|
+
throw new Error("Invalid 'entityName'. Allowed: 'ActiveClass' or 'ActivePage'");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// --- Make DELETE request to Magentrix API ---
|
|
46
|
+
const response = await fetchMagentrix({
|
|
47
|
+
instanceUrl,
|
|
48
|
+
token,
|
|
49
|
+
path: `/api/3.0/entity/${entity}/${recordId}`,
|
|
50
|
+
method: "DELETE",
|
|
51
|
+
returnErrorObject: true
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return response;
|
|
55
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { fetchMagentrix } from "../fetch.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Executes a Magentrix Entity Query Language (MEQL) query via the REST API v3.
|
|
5
|
+
*
|
|
6
|
+
* @async
|
|
7
|
+
* @param {string} instanceUrl - Base URL of the Magentrix instance.
|
|
8
|
+
* @param {string} token - Bearer token string.
|
|
9
|
+
* @param {string} query - MEQL query string.
|
|
10
|
+
* @returns {Promise<object>} The API response data.
|
|
11
|
+
* @throws {Error} On HTTP or API-level error.
|
|
12
|
+
*/
|
|
13
|
+
export const meqlQuery = async (instanceUrl, token, query = '') => {
|
|
14
|
+
if (!instanceUrl || !token) {
|
|
15
|
+
throw new Error('Missing required Magentrix instanceUrl or token');
|
|
16
|
+
}
|
|
17
|
+
if (!query.trim()) {
|
|
18
|
+
throw new Error('MEQL query string is required');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const data = await fetchMagentrix({
|
|
22
|
+
instanceUrl,
|
|
23
|
+
token,
|
|
24
|
+
method: "POST",
|
|
25
|
+
path: '/api/3.0/query',
|
|
26
|
+
body: query
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// --- Success ---
|
|
30
|
+
return data;
|
|
31
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { fetchMagentrix } from "../fetch.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lists all entities available from the Magentrix API.
|
|
5
|
+
*
|
|
6
|
+
* Makes an authenticated GET request to `/api/3.0/entity` to retrieve metadata
|
|
7
|
+
* about all available entities (objects) in the Magentrix instance.
|
|
8
|
+
*
|
|
9
|
+
* Handles and throws both network errors and API-level errors with detailed messages.
|
|
10
|
+
*
|
|
11
|
+
* @async
|
|
12
|
+
* @param {string} instanceUrl - The base URL of the Magentrix instance.
|
|
13
|
+
* @param {string} token - The OAuth or access token for authentication.
|
|
14
|
+
* @returns {Promise<Object>} Parsed JSON response with entities metadata.
|
|
15
|
+
* @throws {Error} If required arguments are missing, network error occurs,
|
|
16
|
+
* HTTP error is returned, or API-level errors are found in the response.
|
|
17
|
+
*/
|
|
18
|
+
export const listEntities = async (instanceUrl, token) => {
|
|
19
|
+
// --- Validate required input parameters ---
|
|
20
|
+
if (!instanceUrl || !token) {
|
|
21
|
+
throw new Error('Missing required Magentrix instanceUrl or token');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const data = await fetchMagentrix({
|
|
25
|
+
instanceUrl,
|
|
26
|
+
token,
|
|
27
|
+
path: '/api/3.0/entity',
|
|
28
|
+
method: "GET"
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return data;
|
|
32
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { fetchMagentrix } from "../fetch.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Updates an existing ActiveClass or ActivePage entity in Magentrix via the REST API.
|
|
5
|
+
*
|
|
6
|
+
* @async
|
|
7
|
+
* @function updateEntity
|
|
8
|
+
* @param {string} instanceUrl - The base URL of the Magentrix instance (e.g. "https://your.magentrix.com").
|
|
9
|
+
* @param {string} token - The OAuth2 bearer token for authentication.
|
|
10
|
+
* @param {string} entityName - The Magentrix entity type. Allowed: "ActiveClass" or "ActivePage" (case-insensitive).
|
|
11
|
+
* @param {string} recordId - The unique Magentrix record ID to update.
|
|
12
|
+
* @param {Object} data - The fields to update on the entity. Provide any subset of updatable fields (e.g. Name, Description, Body, Type, etc).
|
|
13
|
+
* @returns {Promise<Object>} The API response object containing updated record data.
|
|
14
|
+
* @throws {Error} If required parameters are missing, entityName is invalid, recordId is not provided, or data is not an object.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const updated = await updateEntity(
|
|
18
|
+
* "https://your.magentrix.com",
|
|
19
|
+
* "yourToken",
|
|
20
|
+
* "ActiveClass",
|
|
21
|
+
* "06bdc45e-8222-44f5-9ed2-40f5a7bc6cb3",
|
|
22
|
+
* { Name: "RenamedClass", Description: "Updated" }
|
|
23
|
+
* );
|
|
24
|
+
*/
|
|
25
|
+
export const updateEntity = async (instanceUrl, token, entityName, recordId, data) => {
|
|
26
|
+
// --- Validate required parameters ---
|
|
27
|
+
if (!instanceUrl || typeof instanceUrl !== 'string') {
|
|
28
|
+
throw new Error('Missing or invalid Magentrix instanceUrl');
|
|
29
|
+
}
|
|
30
|
+
if (!token || typeof token !== 'string') {
|
|
31
|
+
throw new Error('Missing or invalid Magentrix token');
|
|
32
|
+
}
|
|
33
|
+
if (!entityName || typeof entityName !== 'string') {
|
|
34
|
+
throw new Error("Missing or invalid 'entityName' (must be 'ActiveClass' or 'ActivePage')");
|
|
35
|
+
}
|
|
36
|
+
if (!recordId || typeof recordId !== 'string') {
|
|
37
|
+
throw new Error("Missing or invalid 'recordId' (must be a Magentrix record GUID string)");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// --- Validate entity type ---
|
|
41
|
+
const allowedEntities = ['activeclass', 'activepage'];
|
|
42
|
+
const entity = entityName.trim().toLowerCase();
|
|
43
|
+
if (!allowedEntities.includes(entity)) {
|
|
44
|
+
throw new Error("Invalid 'entityName'. Allowed: 'ActiveClass' or 'ActivePage'");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Validate update data ---
|
|
48
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
49
|
+
throw new Error('Missing or invalid data object for entity update');
|
|
50
|
+
}
|
|
51
|
+
if (Object.keys(data).length === 0) {
|
|
52
|
+
throw new Error('No fields provided to update');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// --- Make PATCH request to Magentrix API ---
|
|
56
|
+
const response = await fetchMagentrix({
|
|
57
|
+
instanceUrl,
|
|
58
|
+
token,
|
|
59
|
+
path: `/api/3.0/entity/${entity}/${recordId}`,
|
|
60
|
+
method: "PATCH",
|
|
61
|
+
body: data,
|
|
62
|
+
returnErrorObject: true
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return response;
|
|
66
|
+
};
|