storyblok 4.13.0 → 4.14.1
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/dist/index.mjs +2639 -367
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import 'dotenv/config';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
4
|
import { resolve, dirname, isAbsolute, relative as relative$1, join as join$1 } from 'pathe';
|
|
5
5
|
import { existsSync, mkdirSync, appendFileSync, writeFileSync, readdirSync, unlinkSync, readFileSync } from 'node:fs';
|
|
6
6
|
import { homedir } from 'node:os';
|
|
@@ -8,14 +8,14 @@ import { loadConfig as loadConfig$1, SUPPORTED_EXTENSIONS } from 'c12';
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { readPackageUp } from 'read-package-up';
|
|
10
10
|
import { Command } from 'commander';
|
|
11
|
-
import path, { join, resolve as resolve$1, parse, dirname as dirname$1, extname, relative } from 'node:path';
|
|
11
|
+
import path, { join, resolve as resolve$1, parse, dirname as dirname$1, extname, relative, basename } from 'node:path';
|
|
12
12
|
import { MultiBar, Presets } from 'cli-progress';
|
|
13
13
|
import { Spinner } from '@topcli/spinner';
|
|
14
|
-
import fs, { mkdir, writeFile, readFile as readFile$1, appendFile, access, readdir } from 'node:fs/promises';
|
|
14
|
+
import fs, { mkdir, writeFile, readFile as readFile$1, appendFile, access, constants, readdir, unlink, stat } from 'node:fs/promises';
|
|
15
15
|
import filenamify from 'filenamify';
|
|
16
|
-
import { select, password, input, confirm } from '@inquirer/prompts';
|
|
17
16
|
import { ManagementApiClient } from '@storyblok/management-api-client';
|
|
18
17
|
import { RateLimit, Sema } from 'async-sema';
|
|
18
|
+
import { select, password, input, confirm } from '@inquirer/prompts';
|
|
19
19
|
import { exec, spawn } from 'node:child_process';
|
|
20
20
|
import { promisify } from 'node:util';
|
|
21
21
|
import { minimatch } from 'minimatch';
|
|
@@ -24,6 +24,10 @@ import { hash } from 'ohash';
|
|
|
24
24
|
import { compile } from 'json-schema-to-typescript';
|
|
25
25
|
import open from 'open';
|
|
26
26
|
import { Octokit } from 'octokit';
|
|
27
|
+
import { pipeline as pipeline$1 } from 'node:stream/promises';
|
|
28
|
+
import { Buffer } from 'node:buffer';
|
|
29
|
+
import Storyblok from 'storyblok-js-client';
|
|
30
|
+
import { createHash } from 'node:crypto';
|
|
27
31
|
|
|
28
32
|
const commands = {
|
|
29
33
|
LOGIN: "login",
|
|
@@ -37,7 +41,9 @@ const commands = {
|
|
|
37
41
|
DATASOURCES: "datasources",
|
|
38
42
|
CREATE: "create",
|
|
39
43
|
LOGS: "logs",
|
|
40
|
-
REPORTS: "reports"
|
|
44
|
+
REPORTS: "reports",
|
|
45
|
+
ASSETS: "assets",
|
|
46
|
+
STORIES: "stories"
|
|
41
47
|
};
|
|
42
48
|
const colorPalette = {
|
|
43
49
|
PRIMARY: "#8d60ff",
|
|
@@ -55,7 +61,9 @@ const colorPalette = {
|
|
|
55
61
|
PRESETS: "#a855f7",
|
|
56
62
|
DATASOURCES: "#4ade80",
|
|
57
63
|
LOGS: "#4ade80",
|
|
58
|
-
REPORTS: "#4ade80"
|
|
64
|
+
REPORTS: "#4ade80",
|
|
65
|
+
ASSETS: "#f97316",
|
|
66
|
+
STORIES: "#a185ff"
|
|
59
67
|
};
|
|
60
68
|
const regions = {
|
|
61
69
|
EU: "eu",
|
|
@@ -96,10 +104,12 @@ const regionNames = {
|
|
|
96
104
|
SB_Agent_Version: process.env.npm_package_version || "4.x"
|
|
97
105
|
});
|
|
98
106
|
const directories = {
|
|
99
|
-
|
|
100
|
-
report: "reports",
|
|
107
|
+
assets: "assets",
|
|
101
108
|
components: "components",
|
|
102
|
-
datasources: "datasources"
|
|
109
|
+
datasources: "datasources",
|
|
110
|
+
logs: "logs",
|
|
111
|
+
reports: "reports",
|
|
112
|
+
stories: "stories"
|
|
103
113
|
};
|
|
104
114
|
|
|
105
115
|
function isPlainObject(value) {
|
|
@@ -600,7 +610,17 @@ const API_ACTIONS = {
|
|
|
600
610
|
update_component_preset: "Failed to update component preset",
|
|
601
611
|
pull_stories: "Failed to pull stories",
|
|
602
612
|
pull_story: "Failed to pull story",
|
|
613
|
+
create_story: "Failed to create story",
|
|
603
614
|
update_story: "Failed to update story",
|
|
615
|
+
pull_asset: "Failed to pull asset",
|
|
616
|
+
pull_assets: "Failed to pull assets",
|
|
617
|
+
pull_asset_folder: "Failed to pull asset folder",
|
|
618
|
+
pull_asset_folders: "Failed to pull asset folders",
|
|
619
|
+
push_asset_folder: "Failed to push asset folder",
|
|
620
|
+
push_asset_sign: "Failed to sign asset upload",
|
|
621
|
+
push_asset_upload: "Failed to upload asset",
|
|
622
|
+
push_asset_finish: "Failed to finish asset upload",
|
|
623
|
+
push_asset_update: "Failed to update asset",
|
|
604
624
|
pull_datasources: "Failed to pull datasources",
|
|
605
625
|
push_datasource: "Failed to push datasource",
|
|
606
626
|
update_datasource: "Failed to update datasource",
|
|
@@ -860,7 +880,7 @@ function handleVerboseError(error) {
|
|
|
860
880
|
konsola.error(`Unexpected Error`, error);
|
|
861
881
|
}
|
|
862
882
|
}
|
|
863
|
-
function handleError(error, verbose = false) {
|
|
883
|
+
function handleError(error, verbose = false, context) {
|
|
864
884
|
if (error instanceof APIError || error instanceof FileSystemError) {
|
|
865
885
|
const messageStack = error.messageStack;
|
|
866
886
|
messageStack.forEach((message, index) => {
|
|
@@ -883,7 +903,10 @@ function handleError(error, verbose = false) {
|
|
|
883
903
|
if (!process.env.VITEST) {
|
|
884
904
|
console.log("");
|
|
885
905
|
}
|
|
886
|
-
getLogger().error(error.message, { error, errorCode: "code" in error ? String(error.code) : "UNKNOWN_ERROR" });
|
|
906
|
+
getLogger().error(error.message, { error, errorCode: "code" in error ? String(error.code) : "UNKNOWN_ERROR", context });
|
|
907
|
+
}
|
|
908
|
+
function logOnlyError(error, context) {
|
|
909
|
+
getLogger().error(error.message, { error, errorCode: "code" in error ? String(error.code) : "UNKNOWN_ERROR", context });
|
|
887
910
|
}
|
|
888
911
|
|
|
889
912
|
function requireAuthentication(state, verbose = false) {
|
|
@@ -1148,19 +1171,22 @@ const saveToFileSync = (filePath, data, options) => {
|
|
|
1148
1171
|
}
|
|
1149
1172
|
};
|
|
1150
1173
|
const appendToFile = async (filePath, data, options) => {
|
|
1151
|
-
const
|
|
1152
|
-
try {
|
|
1153
|
-
await mkdir(resolvedPath, { recursive: true });
|
|
1154
|
-
} catch (mkdirError) {
|
|
1155
|
-
handleFileSystemError("mkdir", mkdirError);
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
try {
|
|
1159
|
-
const dataWithNewline = data.endsWith("\n") ? data : `${data}
|
|
1174
|
+
const dataWithNewline = data.endsWith("\n") ? data : `${data}
|
|
1160
1175
|
`;
|
|
1176
|
+
try {
|
|
1161
1177
|
await appendFile(filePath, dataWithNewline, options);
|
|
1162
|
-
} catch (
|
|
1163
|
-
|
|
1178
|
+
} catch (maybeError) {
|
|
1179
|
+
const error = toError(maybeError);
|
|
1180
|
+
if ("code" in error && error.code === "ENOENT") {
|
|
1181
|
+
const dir = parse(filePath).dir;
|
|
1182
|
+
await mkdir(dir, { recursive: true });
|
|
1183
|
+
await appendFile(filePath, dataWithNewline, options);
|
|
1184
|
+
} else {
|
|
1185
|
+
handleFileSystemError(
|
|
1186
|
+
"syscall" in error && error.syscall === "mkdir" ? "mkdir" : "write",
|
|
1187
|
+
error
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1164
1190
|
}
|
|
1165
1191
|
};
|
|
1166
1192
|
const appendToFileSync = (filePath, data, options) => {
|
|
@@ -1187,6 +1213,30 @@ const readFile = async (filePath) => {
|
|
|
1187
1213
|
return "";
|
|
1188
1214
|
}
|
|
1189
1215
|
};
|
|
1216
|
+
const loadManifest = async (manifestFile) => {
|
|
1217
|
+
return readFile$1(manifestFile, "utf8").then((manifest) => manifest.split("\n").filter(Boolean).map((entry) => JSON.parse(entry))).catch((error) => {
|
|
1218
|
+
if (error && error.code === "ENOENT") {
|
|
1219
|
+
return [];
|
|
1220
|
+
}
|
|
1221
|
+
throw error;
|
|
1222
|
+
});
|
|
1223
|
+
};
|
|
1224
|
+
const saveManifest = async (manifestFile, entries) => {
|
|
1225
|
+
const content = entries.map((entry) => JSON.stringify(entry)).join("\n");
|
|
1226
|
+
await saveToFile(manifestFile, content ? `${content}
|
|
1227
|
+
` : "");
|
|
1228
|
+
};
|
|
1229
|
+
const deduplicateManifest = async (manifestFile) => {
|
|
1230
|
+
const entries = await loadManifest(manifestFile);
|
|
1231
|
+
if (entries.length === 0) {
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
const uniqueEntries = /* @__PURE__ */ new Map();
|
|
1235
|
+
for (const entry of entries) {
|
|
1236
|
+
uniqueEntries.set(entry.old_id, entry);
|
|
1237
|
+
}
|
|
1238
|
+
await saveManifest(manifestFile, Array.from(uniqueEntries.values()));
|
|
1239
|
+
};
|
|
1190
1240
|
const resolvePath = (path, folder) => {
|
|
1191
1241
|
const basePath = path ?? DEFAULT_STORAGE_DIR;
|
|
1192
1242
|
return resolve$1(process.cwd(), basePath, folder);
|
|
@@ -1205,6 +1255,15 @@ const sanitizeFilename = (filename) => {
|
|
|
1205
1255
|
replacement: "_"
|
|
1206
1256
|
});
|
|
1207
1257
|
};
|
|
1258
|
+
async function readDirectory(directoryPath) {
|
|
1259
|
+
try {
|
|
1260
|
+
const files = await readdir(directoryPath);
|
|
1261
|
+
return files;
|
|
1262
|
+
} catch (maybeError) {
|
|
1263
|
+
handleFileSystemError("read", toError(maybeError));
|
|
1264
|
+
return [];
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1208
1267
|
async function readJsonFile(filePath) {
|
|
1209
1268
|
try {
|
|
1210
1269
|
const content = (await readFile(filePath)).toString();
|
|
@@ -1218,7 +1277,15 @@ async function readJsonFile(filePath) {
|
|
|
1218
1277
|
}
|
|
1219
1278
|
}
|
|
1220
1279
|
function importModule(filePath) {
|
|
1221
|
-
return import(
|
|
1280
|
+
return import(pathToFileURL(filePath).href);
|
|
1281
|
+
}
|
|
1282
|
+
async function fileExists(path) {
|
|
1283
|
+
try {
|
|
1284
|
+
await access(path, constants.F_OK);
|
|
1285
|
+
return true;
|
|
1286
|
+
} catch {
|
|
1287
|
+
return false;
|
|
1288
|
+
}
|
|
1222
1289
|
}
|
|
1223
1290
|
|
|
1224
1291
|
const REPORT_STATUS = {
|
|
@@ -1399,7 +1466,7 @@ class FileTransport {
|
|
|
1399
1466
|
name: value.name,
|
|
1400
1467
|
message: value.message,
|
|
1401
1468
|
httpCode: value.code,
|
|
1402
|
-
httpStatusText: value.error?.response
|
|
1469
|
+
httpStatusText: value.error?.response?.statusText,
|
|
1403
1470
|
stack: value.stack
|
|
1404
1471
|
};
|
|
1405
1472
|
continue;
|
|
@@ -1491,7 +1558,7 @@ class ConsoleTransport {
|
|
|
1491
1558
|
name: value.name,
|
|
1492
1559
|
message: value.message,
|
|
1493
1560
|
httpCode: value.code,
|
|
1494
|
-
httpStatusText: value.error?.response
|
|
1561
|
+
httpStatusText: value.error?.response?.statusText,
|
|
1495
1562
|
stack: value.stack
|
|
1496
1563
|
});
|
|
1497
1564
|
}
|
|
@@ -1512,6 +1579,174 @@ class ConsoleTransport {
|
|
|
1512
1579
|
}
|
|
1513
1580
|
}
|
|
1514
1581
|
|
|
1582
|
+
const getCredentials = async (filePath = join(getStoryblokGlobalPath(), "credentials.json")) => {
|
|
1583
|
+
try {
|
|
1584
|
+
await access(filePath);
|
|
1585
|
+
const content = await readFile(filePath);
|
|
1586
|
+
const parsedContent = JSON.parse(content);
|
|
1587
|
+
if (Object.keys(parsedContent).length === 0) {
|
|
1588
|
+
return null;
|
|
1589
|
+
}
|
|
1590
|
+
return parsedContent;
|
|
1591
|
+
} catch (error) {
|
|
1592
|
+
if (error.code === "ENOENT") {
|
|
1593
|
+
await saveToFile(filePath, JSON.stringify({}, null, 2), { mode: 384 });
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
handleFileSystemError("read", error);
|
|
1597
|
+
return null;
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
const addCredentials = async ({
|
|
1601
|
+
filePath = join(getStoryblokGlobalPath(), "credentials.json"),
|
|
1602
|
+
machineName,
|
|
1603
|
+
login,
|
|
1604
|
+
password,
|
|
1605
|
+
region
|
|
1606
|
+
}) => {
|
|
1607
|
+
const credentials = {
|
|
1608
|
+
...await getCredentials(filePath),
|
|
1609
|
+
[machineName]: {
|
|
1610
|
+
login,
|
|
1611
|
+
password,
|
|
1612
|
+
region
|
|
1613
|
+
}
|
|
1614
|
+
};
|
|
1615
|
+
try {
|
|
1616
|
+
await saveToFile(filePath, JSON.stringify(credentials, null, 2), { mode: 384 });
|
|
1617
|
+
} catch (error) {
|
|
1618
|
+
throw new FileSystemError("invalid_argument", "write", error, `Error adding/updating entry for machine ${machineName} in credentials.json file`);
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
const removeAllCredentials = async (filepath = getStoryblokGlobalPath()) => {
|
|
1622
|
+
const filePath = join(filepath, "credentials.json");
|
|
1623
|
+
await saveToFile(filePath, JSON.stringify({}, null, 2), { mode: 384 });
|
|
1624
|
+
};
|
|
1625
|
+
|
|
1626
|
+
let sessionInstance = null;
|
|
1627
|
+
function createSession() {
|
|
1628
|
+
const state = {
|
|
1629
|
+
isLoggedIn: false
|
|
1630
|
+
};
|
|
1631
|
+
async function initializeSession() {
|
|
1632
|
+
const envCredentials = getEnvCredentials();
|
|
1633
|
+
if (envCredentials) {
|
|
1634
|
+
state.isLoggedIn = true;
|
|
1635
|
+
state.login = envCredentials.login;
|
|
1636
|
+
state.password = envCredentials.password;
|
|
1637
|
+
state.region = envCredentials.region;
|
|
1638
|
+
state.envLogin = true;
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
const credentials = await getCredentials();
|
|
1642
|
+
if (credentials) {
|
|
1643
|
+
const creds = Object.values(credentials)[0];
|
|
1644
|
+
state.isLoggedIn = true;
|
|
1645
|
+
state.login = creds.login;
|
|
1646
|
+
state.password = creds.password;
|
|
1647
|
+
state.region = creds.region;
|
|
1648
|
+
} else {
|
|
1649
|
+
state.isLoggedIn = false;
|
|
1650
|
+
state.login = void 0;
|
|
1651
|
+
state.password = void 0;
|
|
1652
|
+
state.region = void 0;
|
|
1653
|
+
}
|
|
1654
|
+
state.envLogin = false;
|
|
1655
|
+
}
|
|
1656
|
+
function getEnvCredentials() {
|
|
1657
|
+
const envLogin = process.env.STORYBLOK_LOGIN || process.env.TRAVIS_STORYBLOK_LOGIN;
|
|
1658
|
+
const envPassword = process.env.STORYBLOK_TOKEN || process.env.TRAVIS_STORYBLOK_TOKEN;
|
|
1659
|
+
const envRegion = process.env.STORYBLOK_REGION || process.env.TRAVIS_STORYBLOK_REGION;
|
|
1660
|
+
if (envLogin && envPassword && envRegion) {
|
|
1661
|
+
return {
|
|
1662
|
+
login: envLogin,
|
|
1663
|
+
password: envPassword,
|
|
1664
|
+
region: envRegion
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
return null;
|
|
1668
|
+
}
|
|
1669
|
+
async function persistCredentials(region) {
|
|
1670
|
+
if (state.isLoggedIn && state.login && state.password && state.region) {
|
|
1671
|
+
await addCredentials({
|
|
1672
|
+
machineName: regionsDomain[region] || "mapi.storyblok.com",
|
|
1673
|
+
login: state.login,
|
|
1674
|
+
password: state.password,
|
|
1675
|
+
region: state.region
|
|
1676
|
+
});
|
|
1677
|
+
} else {
|
|
1678
|
+
throw new Error("No credentials to save.");
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
function updateSession(login, password, region) {
|
|
1682
|
+
state.isLoggedIn = true;
|
|
1683
|
+
state.login = login;
|
|
1684
|
+
state.password = password;
|
|
1685
|
+
state.region = region;
|
|
1686
|
+
}
|
|
1687
|
+
function logout() {
|
|
1688
|
+
state.isLoggedIn = false;
|
|
1689
|
+
state.login = void 0;
|
|
1690
|
+
state.password = void 0;
|
|
1691
|
+
state.region = void 0;
|
|
1692
|
+
}
|
|
1693
|
+
return {
|
|
1694
|
+
state,
|
|
1695
|
+
initializeSession,
|
|
1696
|
+
updateSession,
|
|
1697
|
+
persistCredentials,
|
|
1698
|
+
logout
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
function session() {
|
|
1702
|
+
if (!sessionInstance) {
|
|
1703
|
+
sessionInstance = createSession();
|
|
1704
|
+
}
|
|
1705
|
+
return sessionInstance;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
let instance = null;
|
|
1709
|
+
let storedConfig = null;
|
|
1710
|
+
let currentLimiterCapacity = Math.max(1, getActiveConfig().api.maxConcurrency);
|
|
1711
|
+
let limiter = RateLimit(currentLimiterCapacity, { uniformDistribution: true });
|
|
1712
|
+
function resolveLimiter() {
|
|
1713
|
+
const desiredCapacity = Math.max(1, getActiveConfig().api.maxConcurrency);
|
|
1714
|
+
if (desiredCapacity !== currentLimiterCapacity) {
|
|
1715
|
+
limiter = RateLimit(desiredCapacity, { uniformDistribution: true });
|
|
1716
|
+
currentLimiterCapacity = desiredCapacity;
|
|
1717
|
+
}
|
|
1718
|
+
return limiter;
|
|
1719
|
+
}
|
|
1720
|
+
function configsAreEqual(config1, config2) {
|
|
1721
|
+
return JSON.stringify(config1) === JSON.stringify(config2);
|
|
1722
|
+
}
|
|
1723
|
+
function applyRateLimit(client) {
|
|
1724
|
+
if (getActiveConfig().api.maxConcurrency > 0) {
|
|
1725
|
+
client.interceptors.request.use(async (request) => {
|
|
1726
|
+
const limit = resolveLimiter();
|
|
1727
|
+
await limit();
|
|
1728
|
+
return request;
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
function creategetMapiClient(options) {
|
|
1733
|
+
const client = new ManagementApiClient(options);
|
|
1734
|
+
applyRateLimit(client);
|
|
1735
|
+
return client;
|
|
1736
|
+
}
|
|
1737
|
+
function getMapiClient(options) {
|
|
1738
|
+
if (!instance && options) {
|
|
1739
|
+
instance = creategetMapiClient(options);
|
|
1740
|
+
storedConfig = options;
|
|
1741
|
+
} else if (!instance) {
|
|
1742
|
+
throw new Error("MAPI client not initialized. Call getMapiClient with configuration first.");
|
|
1743
|
+
} else if (options && storedConfig && !configsAreEqual(options, storedConfig)) {
|
|
1744
|
+
instance = creategetMapiClient(options);
|
|
1745
|
+
storedConfig = options;
|
|
1746
|
+
}
|
|
1747
|
+
return instance;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1515
1750
|
const packageJson = getPackageJson();
|
|
1516
1751
|
let programInstance = null;
|
|
1517
1752
|
function getProgram() {
|
|
@@ -1540,6 +1775,16 @@ function getProgram() {
|
|
|
1540
1775
|
const resolvedConfig = await resolveConfig(targetCommand, ancestry);
|
|
1541
1776
|
applyConfigToCommander(ancestry, resolvedConfig);
|
|
1542
1777
|
setActiveConfig(resolvedConfig);
|
|
1778
|
+
const { state, initializeSession } = session();
|
|
1779
|
+
await initializeSession();
|
|
1780
|
+
if (state.password) {
|
|
1781
|
+
getMapiClient({
|
|
1782
|
+
token: {
|
|
1783
|
+
accessToken: state.password
|
|
1784
|
+
},
|
|
1785
|
+
region: state.region ?? resolvedConfig.region
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1543
1788
|
const options = targetCommand.optsWithGlobals();
|
|
1544
1789
|
const commandPieces = [];
|
|
1545
1790
|
for (let c = targetCommand; c; c = c.parent) {
|
|
@@ -1558,7 +1803,7 @@ function getProgram() {
|
|
|
1558
1803
|
}
|
|
1559
1804
|
if (resolvedConfig.log.file.enabled) {
|
|
1560
1805
|
const logsPath = resolveCommandPath(
|
|
1561
|
-
directories.
|
|
1806
|
+
directories.logs,
|
|
1562
1807
|
options.space,
|
|
1563
1808
|
options.path
|
|
1564
1809
|
);
|
|
@@ -1582,7 +1827,7 @@ function getProgram() {
|
|
|
1582
1827
|
getUI({ enabled: resolvedConfig.ui.enabled });
|
|
1583
1828
|
if (resolvedConfig.report.enabled) {
|
|
1584
1829
|
const reportPath = resolveCommandPath(
|
|
1585
|
-
directories.
|
|
1830
|
+
directories.reports,
|
|
1586
1831
|
options.space,
|
|
1587
1832
|
options.path
|
|
1588
1833
|
);
|
|
@@ -1611,47 +1856,9 @@ const getStoryblokUrl = (region = "eu") => {
|
|
|
1611
1856
|
return `https://${managementApiRegions[region]}/${API_VERSION}`;
|
|
1612
1857
|
};
|
|
1613
1858
|
|
|
1614
|
-
let instance = null;
|
|
1615
|
-
let storedConfig = null;
|
|
1616
|
-
let currentLimiterCapacity = Math.max(1, getActiveConfig().api.maxConcurrency);
|
|
1617
|
-
let limiter = RateLimit(currentLimiterCapacity, { uniformDistribution: true });
|
|
1618
|
-
function resolveLimiter() {
|
|
1619
|
-
const desiredCapacity = Math.max(1, getActiveConfig().api.maxConcurrency);
|
|
1620
|
-
if (desiredCapacity !== currentLimiterCapacity) {
|
|
1621
|
-
limiter = RateLimit(desiredCapacity, { uniformDistribution: true });
|
|
1622
|
-
currentLimiterCapacity = desiredCapacity;
|
|
1623
|
-
}
|
|
1624
|
-
return limiter;
|
|
1625
|
-
}
|
|
1626
|
-
function configsAreEqual(config1, config2) {
|
|
1627
|
-
return JSON.stringify(config1) === JSON.stringify(config2);
|
|
1628
|
-
}
|
|
1629
|
-
function mapiClient(options) {
|
|
1630
|
-
if (!instance && options) {
|
|
1631
|
-
instance = new ManagementApiClient(options);
|
|
1632
|
-
instance.interceptors.request.use(async (request) => {
|
|
1633
|
-
const limit = resolveLimiter();
|
|
1634
|
-
await limit();
|
|
1635
|
-
return request;
|
|
1636
|
-
});
|
|
1637
|
-
storedConfig = options;
|
|
1638
|
-
} else if (!instance) {
|
|
1639
|
-
throw new Error("MAPI client not initialized. Call mapiClient with configuration first.");
|
|
1640
|
-
} else if (options && storedConfig && !configsAreEqual(options, storedConfig)) {
|
|
1641
|
-
instance = new ManagementApiClient(options);
|
|
1642
|
-
instance.interceptors.request.use(async (request) => {
|
|
1643
|
-
const limit = resolveLimiter();
|
|
1644
|
-
await limit();
|
|
1645
|
-
return request;
|
|
1646
|
-
});
|
|
1647
|
-
storedConfig = options;
|
|
1648
|
-
}
|
|
1649
|
-
return instance;
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
1859
|
const getUser = async (token, region) => {
|
|
1653
1860
|
try {
|
|
1654
|
-
const client =
|
|
1861
|
+
const client = creategetMapiClient({
|
|
1655
1862
|
token: {
|
|
1656
1863
|
accessToken: token
|
|
1657
1864
|
},
|
|
@@ -1736,137 +1943,11 @@ const loginWithOtp = async (email, password, otp, region) => {
|
|
|
1736
1943
|
}
|
|
1737
1944
|
};
|
|
1738
1945
|
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
if (Object.keys(parsedContent).length === 0) {
|
|
1745
|
-
return null;
|
|
1746
|
-
}
|
|
1747
|
-
return parsedContent;
|
|
1748
|
-
} catch (error) {
|
|
1749
|
-
if (error.code === "ENOENT") {
|
|
1750
|
-
await saveToFile(filePath, JSON.stringify({}, null, 2), { mode: 384 });
|
|
1751
|
-
return null;
|
|
1752
|
-
}
|
|
1753
|
-
handleFileSystemError("read", error);
|
|
1754
|
-
return null;
|
|
1755
|
-
}
|
|
1756
|
-
};
|
|
1757
|
-
const addCredentials = async ({
|
|
1758
|
-
filePath = join(getStoryblokGlobalPath(), "credentials.json"),
|
|
1759
|
-
machineName,
|
|
1760
|
-
login,
|
|
1761
|
-
password,
|
|
1762
|
-
region
|
|
1763
|
-
}) => {
|
|
1764
|
-
const credentials = {
|
|
1765
|
-
...await getCredentials(filePath),
|
|
1766
|
-
[machineName]: {
|
|
1767
|
-
login,
|
|
1768
|
-
password,
|
|
1769
|
-
region
|
|
1770
|
-
}
|
|
1771
|
-
};
|
|
1772
|
-
try {
|
|
1773
|
-
await saveToFile(filePath, JSON.stringify(credentials, null, 2), { mode: 384 });
|
|
1774
|
-
} catch (error) {
|
|
1775
|
-
throw new FileSystemError("invalid_argument", "write", error, `Error adding/updating entry for machine ${machineName} in credentials.json file`);
|
|
1776
|
-
}
|
|
1777
|
-
};
|
|
1778
|
-
const removeAllCredentials = async (filepath = getStoryblokGlobalPath()) => {
|
|
1779
|
-
const filePath = join(filepath, "credentials.json");
|
|
1780
|
-
await saveToFile(filePath, JSON.stringify({}, null, 2), { mode: 384 });
|
|
1781
|
-
};
|
|
1782
|
-
|
|
1783
|
-
let sessionInstance = null;
|
|
1784
|
-
function createSession() {
|
|
1785
|
-
const state = {
|
|
1786
|
-
isLoggedIn: false
|
|
1787
|
-
};
|
|
1788
|
-
async function initializeSession() {
|
|
1789
|
-
const envCredentials = getEnvCredentials();
|
|
1790
|
-
if (envCredentials) {
|
|
1791
|
-
state.isLoggedIn = true;
|
|
1792
|
-
state.login = envCredentials.login;
|
|
1793
|
-
state.password = envCredentials.password;
|
|
1794
|
-
state.region = envCredentials.region;
|
|
1795
|
-
state.envLogin = true;
|
|
1796
|
-
return;
|
|
1797
|
-
}
|
|
1798
|
-
const credentials = await getCredentials();
|
|
1799
|
-
if (credentials) {
|
|
1800
|
-
const creds = Object.values(credentials)[0];
|
|
1801
|
-
state.isLoggedIn = true;
|
|
1802
|
-
state.login = creds.login;
|
|
1803
|
-
state.password = creds.password;
|
|
1804
|
-
state.region = creds.region;
|
|
1805
|
-
} else {
|
|
1806
|
-
state.isLoggedIn = false;
|
|
1807
|
-
state.login = void 0;
|
|
1808
|
-
state.password = void 0;
|
|
1809
|
-
state.region = void 0;
|
|
1810
|
-
}
|
|
1811
|
-
state.envLogin = false;
|
|
1812
|
-
}
|
|
1813
|
-
function getEnvCredentials() {
|
|
1814
|
-
const envLogin = process.env.STORYBLOK_LOGIN || process.env.TRAVIS_STORYBLOK_LOGIN;
|
|
1815
|
-
const envPassword = process.env.STORYBLOK_TOKEN || process.env.TRAVIS_STORYBLOK_TOKEN;
|
|
1816
|
-
const envRegion = process.env.STORYBLOK_REGION || process.env.TRAVIS_STORYBLOK_REGION;
|
|
1817
|
-
if (envLogin && envPassword && envRegion) {
|
|
1818
|
-
return {
|
|
1819
|
-
login: envLogin,
|
|
1820
|
-
password: envPassword,
|
|
1821
|
-
region: envRegion
|
|
1822
|
-
};
|
|
1823
|
-
}
|
|
1824
|
-
return null;
|
|
1825
|
-
}
|
|
1826
|
-
async function persistCredentials(region) {
|
|
1827
|
-
if (state.isLoggedIn && state.login && state.password && state.region) {
|
|
1828
|
-
await addCredentials({
|
|
1829
|
-
machineName: regionsDomain[region] || "mapi.storyblok.com",
|
|
1830
|
-
login: state.login,
|
|
1831
|
-
password: state.password,
|
|
1832
|
-
region: state.region
|
|
1833
|
-
});
|
|
1834
|
-
} else {
|
|
1835
|
-
throw new Error("No credentials to save.");
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
function updateSession(login, password, region) {
|
|
1839
|
-
state.isLoggedIn = true;
|
|
1840
|
-
state.login = login;
|
|
1841
|
-
state.password = password;
|
|
1842
|
-
state.region = region;
|
|
1843
|
-
}
|
|
1844
|
-
function logout() {
|
|
1845
|
-
state.isLoggedIn = false;
|
|
1846
|
-
state.login = void 0;
|
|
1847
|
-
state.password = void 0;
|
|
1848
|
-
state.region = void 0;
|
|
1849
|
-
}
|
|
1850
|
-
return {
|
|
1851
|
-
state,
|
|
1852
|
-
initializeSession,
|
|
1853
|
-
updateSession,
|
|
1854
|
-
persistCredentials,
|
|
1855
|
-
logout
|
|
1856
|
-
};
|
|
1857
|
-
}
|
|
1858
|
-
function session() {
|
|
1859
|
-
if (!sessionInstance) {
|
|
1860
|
-
sessionInstance = createSession();
|
|
1861
|
-
}
|
|
1862
|
-
return sessionInstance;
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
async function performInteractiveLogin(options) {
|
|
1866
|
-
const { verbose = false, preSelectedRegion, showWelcomeMessage = true } = options || {};
|
|
1867
|
-
const spinner = new Spinner({
|
|
1868
|
-
verbose: !isVitest
|
|
1869
|
-
});
|
|
1946
|
+
async function performInteractiveLogin(options) {
|
|
1947
|
+
const { verbose = false, preSelectedRegion, showWelcomeMessage = true } = options || {};
|
|
1948
|
+
const spinner = new Spinner({
|
|
1949
|
+
verbose: !isVitest
|
|
1950
|
+
});
|
|
1870
1951
|
try {
|
|
1871
1952
|
const strategy = await select({
|
|
1872
1953
|
message: "How would you like to login?",
|
|
@@ -1971,17 +2052,16 @@ async function performInteractiveLogin(options) {
|
|
|
1971
2052
|
}
|
|
1972
2053
|
}
|
|
1973
2054
|
|
|
1974
|
-
const program$
|
|
2055
|
+
const program$j = getProgram();
|
|
1975
2056
|
const allRegionsText = Object.values(regions).join(",");
|
|
1976
|
-
program$
|
|
2057
|
+
program$j.command(commands.LOGIN).description("Login to the Storyblok CLI").option("-t, --token <token>", "Token to login directly without questions, like for CI environments").option(
|
|
1977
2058
|
"-r, --region <region>",
|
|
1978
2059
|
`The region you would like to work in. Please keep in mind that the region must match the region of your space. This region flag will be used for the other cli's commands. You can use the values: ${allRegionsText}.`
|
|
1979
2060
|
).action(async (options) => {
|
|
1980
2061
|
konsola.title(`${commands.LOGIN}`, colorPalette.LOGIN);
|
|
1981
|
-
const verbose = program$
|
|
2062
|
+
const verbose = program$j.opts().verbose;
|
|
1982
2063
|
const { token, region } = options;
|
|
1983
|
-
const { state, updateSession, persistCredentials
|
|
1984
|
-
await initializeSession();
|
|
2064
|
+
const { state, updateSession, persistCredentials } = session();
|
|
1985
2065
|
if (state.isLoggedIn && !state.envLogin) {
|
|
1986
2066
|
konsola.ok(`You are already logged in. If you want to login with a different account, please logout first.`);
|
|
1987
2067
|
return;
|
|
@@ -2037,13 +2117,12 @@ program$h.command(commands.LOGIN).description("Login to the Storyblok CLI").opti
|
|
|
2037
2117
|
konsola.br();
|
|
2038
2118
|
});
|
|
2039
2119
|
|
|
2040
|
-
const program$
|
|
2041
|
-
program$
|
|
2120
|
+
const program$i = getProgram();
|
|
2121
|
+
program$i.command(commands.LOGOUT).description("Logout from the Storyblok CLI").action(async () => {
|
|
2042
2122
|
konsola.title(`${commands.LOGOUT}`, colorPalette.LOGOUT);
|
|
2043
|
-
const verbose = program$
|
|
2123
|
+
const verbose = program$i.opts().verbose;
|
|
2044
2124
|
try {
|
|
2045
|
-
const { state
|
|
2046
|
-
await initializeSession();
|
|
2125
|
+
const { state } = session();
|
|
2047
2126
|
if (!state.isLoggedIn || !state.password || !state.region) {
|
|
2048
2127
|
konsola.warn(`You are already logged out. If you want to login, please use the login command.`);
|
|
2049
2128
|
konsola.br();
|
|
@@ -2088,12 +2167,11 @@ async function openSignupInBrowser(url) {
|
|
|
2088
2167
|
}
|
|
2089
2168
|
}
|
|
2090
2169
|
|
|
2091
|
-
const program$
|
|
2092
|
-
program$
|
|
2170
|
+
const program$h = getProgram();
|
|
2171
|
+
program$h.command(commands.SIGNUP).description("Sign up for Storyblok").action(async () => {
|
|
2093
2172
|
konsola.title(`${commands.SIGNUP}`, colorPalette.SIGNUP);
|
|
2094
|
-
const verbose = program$
|
|
2095
|
-
const { state
|
|
2096
|
-
await initializeSession();
|
|
2173
|
+
const verbose = program$h.opts().verbose;
|
|
2174
|
+
const { state } = session();
|
|
2097
2175
|
if (state.isLoggedIn && !state.envLogin) {
|
|
2098
2176
|
konsola.ok(`You are already logged in. If you want to signup with a different account, please logout first.`);
|
|
2099
2177
|
return;
|
|
@@ -2113,12 +2191,11 @@ program$f.command(commands.SIGNUP).description("Sign up for Storyblok").action(a
|
|
|
2113
2191
|
konsola.br();
|
|
2114
2192
|
});
|
|
2115
2193
|
|
|
2116
|
-
const program$
|
|
2117
|
-
program$
|
|
2194
|
+
const program$g = getProgram();
|
|
2195
|
+
program$g.command(commands.USER).description("Get the current user").action(async () => {
|
|
2118
2196
|
konsola.title(`${commands.USER}`, colorPalette.USER);
|
|
2119
|
-
const verbose = program$
|
|
2120
|
-
const { state
|
|
2121
|
-
await initializeSession();
|
|
2197
|
+
const verbose = program$g.opts().verbose;
|
|
2198
|
+
const { state } = session();
|
|
2122
2199
|
if (!requireAuthentication(state)) {
|
|
2123
2200
|
return;
|
|
2124
2201
|
}
|
|
@@ -2145,8 +2222,8 @@ program$e.command(commands.USER).description("Get the current user").action(asyn
|
|
|
2145
2222
|
konsola.br();
|
|
2146
2223
|
});
|
|
2147
2224
|
|
|
2148
|
-
const program$
|
|
2149
|
-
const componentsCommand = program$
|
|
2225
|
+
const program$f = getProgram();
|
|
2226
|
+
const componentsCommand = program$f.command(commands.COMPONENTS).alias("comp").description(`Manage your space's block schema`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/components");
|
|
2150
2227
|
|
|
2151
2228
|
const DEFAULT_COMPONENTS_FILENAME = "components";
|
|
2152
2229
|
const DEFAULT_GROUPS_FILENAME = "groups";
|
|
@@ -2155,7 +2232,7 @@ const DEFAULT_TAGS_FILENAME = "tags";
|
|
|
2155
2232
|
|
|
2156
2233
|
const fetchComponents = async (spaceId) => {
|
|
2157
2234
|
try {
|
|
2158
|
-
const client =
|
|
2235
|
+
const client = getMapiClient();
|
|
2159
2236
|
const { data } = await client.components.list({
|
|
2160
2237
|
path: {
|
|
2161
2238
|
space_id: spaceId
|
|
@@ -2169,7 +2246,7 @@ const fetchComponents = async (spaceId) => {
|
|
|
2169
2246
|
};
|
|
2170
2247
|
const fetchComponent = async (spaceId, componentName) => {
|
|
2171
2248
|
try {
|
|
2172
|
-
const client =
|
|
2249
|
+
const client = getMapiClient();
|
|
2173
2250
|
const { data } = await client.components.list({
|
|
2174
2251
|
path: {
|
|
2175
2252
|
space_id: spaceId
|
|
@@ -2186,7 +2263,7 @@ const fetchComponent = async (spaceId, componentName) => {
|
|
|
2186
2263
|
};
|
|
2187
2264
|
const fetchComponentGroups = async (spaceId) => {
|
|
2188
2265
|
try {
|
|
2189
|
-
const client =
|
|
2266
|
+
const client = getMapiClient();
|
|
2190
2267
|
const { data } = await client.componentFolders.list({
|
|
2191
2268
|
path: {
|
|
2192
2269
|
space_id: spaceId
|
|
@@ -2199,7 +2276,7 @@ const fetchComponentGroups = async (spaceId) => {
|
|
|
2199
2276
|
};
|
|
2200
2277
|
const fetchComponentPresets = async (spaceId) => {
|
|
2201
2278
|
try {
|
|
2202
|
-
const client =
|
|
2279
|
+
const client = getMapiClient();
|
|
2203
2280
|
const { data } = await client.presets.list({
|
|
2204
2281
|
path: {
|
|
2205
2282
|
space_id: spaceId
|
|
@@ -2212,7 +2289,7 @@ const fetchComponentPresets = async (spaceId) => {
|
|
|
2212
2289
|
};
|
|
2213
2290
|
const fetchComponentInternalTags = async (spaceId) => {
|
|
2214
2291
|
try {
|
|
2215
|
-
const client =
|
|
2292
|
+
const client = getMapiClient();
|
|
2216
2293
|
const { data } = await client.internalTags.list({
|
|
2217
2294
|
path: {
|
|
2218
2295
|
space_id: spaceId
|
|
@@ -2266,7 +2343,7 @@ const saveComponentsToFiles = async (space, spaceData, options) => {
|
|
|
2266
2343
|
|
|
2267
2344
|
const pushComponent = async (space, component) => {
|
|
2268
2345
|
try {
|
|
2269
|
-
const client =
|
|
2346
|
+
const client = getMapiClient();
|
|
2270
2347
|
const { data } = await client.components.create({
|
|
2271
2348
|
path: {
|
|
2272
2349
|
space_id: space
|
|
@@ -2282,7 +2359,7 @@ const pushComponent = async (space, component) => {
|
|
|
2282
2359
|
};
|
|
2283
2360
|
const updateComponent = async (space, componentId, component) => {
|
|
2284
2361
|
try {
|
|
2285
|
-
const client =
|
|
2362
|
+
const client = getMapiClient();
|
|
2286
2363
|
const { data } = await client.components.update({
|
|
2287
2364
|
path: {
|
|
2288
2365
|
space_id: Number(space),
|
|
@@ -2307,7 +2384,7 @@ const upsertComponent = async (space, component, existingId) => {
|
|
|
2307
2384
|
};
|
|
2308
2385
|
const pushComponentGroup = async (space, componentGroup) => {
|
|
2309
2386
|
try {
|
|
2310
|
-
const client =
|
|
2387
|
+
const client = getMapiClient();
|
|
2311
2388
|
const { data } = await client.componentFolders.create({
|
|
2312
2389
|
path: {
|
|
2313
2390
|
space_id: Number(space)
|
|
@@ -2324,7 +2401,7 @@ const pushComponentGroup = async (space, componentGroup) => {
|
|
|
2324
2401
|
};
|
|
2325
2402
|
const updateComponentGroup = async (space, groupId, componentGroup) => {
|
|
2326
2403
|
try {
|
|
2327
|
-
const client =
|
|
2404
|
+
const client = getMapiClient();
|
|
2328
2405
|
const { data } = await client.componentFolders.update({
|
|
2329
2406
|
path: {
|
|
2330
2407
|
space_id: Number(space),
|
|
@@ -2349,7 +2426,7 @@ const upsertComponentGroup = async (space, group, existingId) => {
|
|
|
2349
2426
|
};
|
|
2350
2427
|
const pushComponentPreset = async (space, preset) => {
|
|
2351
2428
|
try {
|
|
2352
|
-
const client =
|
|
2429
|
+
const client = getMapiClient();
|
|
2353
2430
|
const { data } = await client.presets.create({
|
|
2354
2431
|
path: {
|
|
2355
2432
|
space_id: Number(space)
|
|
@@ -2366,7 +2443,7 @@ const pushComponentPreset = async (space, preset) => {
|
|
|
2366
2443
|
};
|
|
2367
2444
|
const updateComponentPreset = async (space, presetId, preset) => {
|
|
2368
2445
|
try {
|
|
2369
|
-
const client =
|
|
2446
|
+
const client = getMapiClient();
|
|
2370
2447
|
const { data } = await client.presets.update({
|
|
2371
2448
|
path: {
|
|
2372
2449
|
space_id: Number(space),
|
|
@@ -2391,7 +2468,7 @@ const upsertComponentPreset = async (space, preset, existingId) => {
|
|
|
2391
2468
|
};
|
|
2392
2469
|
const pushComponentInternalTag = async (space, componentInternalTag) => {
|
|
2393
2470
|
try {
|
|
2394
|
-
const client =
|
|
2471
|
+
const client = getMapiClient();
|
|
2395
2472
|
const { data } = await client.internalTags.create({
|
|
2396
2473
|
path: {
|
|
2397
2474
|
space_id: Number(space)
|
|
@@ -2406,7 +2483,7 @@ const pushComponentInternalTag = async (space, componentInternalTag) => {
|
|
|
2406
2483
|
};
|
|
2407
2484
|
const updateComponentInternalTag = async (space, tagId, componentInternalTag) => {
|
|
2408
2485
|
try {
|
|
2409
|
-
const client =
|
|
2486
|
+
const client = getMapiClient();
|
|
2410
2487
|
const { data } = await client.internalTags.update({
|
|
2411
2488
|
path: {
|
|
2412
2489
|
space_id: Number(space),
|
|
@@ -2531,10 +2608,10 @@ async function readConsolidatedFiles$1(resolvedPath, suffix) {
|
|
|
2531
2608
|
};
|
|
2532
2609
|
}
|
|
2533
2610
|
|
|
2534
|
-
const program$
|
|
2611
|
+
const program$e = getProgram();
|
|
2535
2612
|
componentsCommand.command("pull [componentName]").option("-f, --filename <filename>", "custom name to be used in file(s) name instead of space id").option("--sf, --separate-files", "Argument to create a single file for each component").option("--su, --suffix <suffix>", "suffix to add to the file name (e.g. components.<suffix>.json)").description(`Download your space's components schema as json. Optionally specify a component name to pull a single component.`).action(async (componentName, options) => {
|
|
2536
2613
|
konsola.title(`${commands.COMPONENTS}`, colorPalette.COMPONENTS, componentName ? `Pulling component ${componentName}...` : "Pulling components...");
|
|
2537
|
-
const verbose = program$
|
|
2614
|
+
const verbose = program$e.opts().verbose;
|
|
2538
2615
|
const { space, path } = componentsCommand.opts();
|
|
2539
2616
|
const {
|
|
2540
2617
|
separateFiles = false,
|
|
@@ -2543,8 +2620,7 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
|
|
|
2543
2620
|
} = options;
|
|
2544
2621
|
const actualFilename = filename ?? DEFAULT_COMPONENTS_FILENAME;
|
|
2545
2622
|
const componentsOutputDir = resolveCommandPath(directories.components, space, path);
|
|
2546
|
-
const { state
|
|
2547
|
-
await initializeSession();
|
|
2623
|
+
const { state } = session();
|
|
2548
2624
|
if (!requireAuthentication(state, verbose)) {
|
|
2549
2625
|
return;
|
|
2550
2626
|
}
|
|
@@ -2552,13 +2628,6 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
|
|
|
2552
2628
|
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
|
|
2553
2629
|
return;
|
|
2554
2630
|
}
|
|
2555
|
-
const { password, region } = state;
|
|
2556
|
-
mapiClient({
|
|
2557
|
-
token: {
|
|
2558
|
-
accessToken: password
|
|
2559
|
-
},
|
|
2560
|
-
region
|
|
2561
|
-
});
|
|
2562
2631
|
const spinnerGroups = new Spinner({
|
|
2563
2632
|
verbose: !isVitest
|
|
2564
2633
|
});
|
|
@@ -3576,15 +3645,14 @@ async function pushWithDependencyGraph(space, spaceState, maxConcurrency = getAc
|
|
|
3576
3645
|
return results;
|
|
3577
3646
|
}
|
|
3578
3647
|
|
|
3579
|
-
const program$
|
|
3648
|
+
const program$d = getProgram();
|
|
3580
3649
|
componentsCommand.command("push [componentName]").description(`Push your space's components schema as json`).option("-f, --from <from>", "source space id").option("--fi, --filter <filter>", "glob filter to apply to the components before pushing").option("--sf, --separate-files", "Read from separate files instead of consolidated files", false).option("--su, --suffix <suffix>", "Suffix to add to the component name").action(async (componentName, options) => {
|
|
3581
3650
|
konsola.title(`${commands.COMPONENTS}`, colorPalette.COMPONENTS, componentName ? `Pushing component ${componentName}...` : "Pushing components...");
|
|
3582
|
-
const verbose = program$
|
|
3651
|
+
const verbose = program$d.opts().verbose;
|
|
3583
3652
|
const { space, path } = componentsCommand.opts();
|
|
3584
3653
|
const { filter } = options;
|
|
3585
3654
|
const fromSpace = options.from || space;
|
|
3586
|
-
const { state
|
|
3587
|
-
await initializeSession();
|
|
3655
|
+
const { state } = session();
|
|
3588
3656
|
if (!requireAuthentication(state, verbose)) {
|
|
3589
3657
|
return;
|
|
3590
3658
|
}
|
|
@@ -3594,14 +3662,8 @@ componentsCommand.command("push [componentName]").description(`Push your space's
|
|
|
3594
3662
|
}
|
|
3595
3663
|
konsola.info(`Attempting to push components ${chalk.bold("from")} space ${chalk.hex(colorPalette.COMPONENTS)(fromSpace)} ${chalk.bold("to")} ${chalk.hex(colorPalette.COMPONENTS)(space)}`);
|
|
3596
3664
|
konsola.br();
|
|
3597
|
-
const { password, region } = state;
|
|
3598
3665
|
let requestCount = 0;
|
|
3599
|
-
const client =
|
|
3600
|
-
token: {
|
|
3601
|
-
accessToken: password
|
|
3602
|
-
},
|
|
3603
|
-
region
|
|
3604
|
-
});
|
|
3666
|
+
const client = getMapiClient();
|
|
3605
3667
|
client.interceptors.request.use((config) => {
|
|
3606
3668
|
requestCount++;
|
|
3607
3669
|
return config;
|
|
@@ -3723,7 +3785,7 @@ const DEFAULT_LANGUAGES_FILENAME = "languages";
|
|
|
3723
3785
|
|
|
3724
3786
|
const fetchSpace = async (spaceId) => {
|
|
3725
3787
|
try {
|
|
3726
|
-
const client =
|
|
3788
|
+
const client = getMapiClient();
|
|
3727
3789
|
const { data } = await client.spaces.get({
|
|
3728
3790
|
path: {
|
|
3729
3791
|
space_id: spaceId
|
|
@@ -3737,7 +3799,7 @@ const fetchSpace = async (spaceId) => {
|
|
|
3737
3799
|
};
|
|
3738
3800
|
const createSpace = async (space) => {
|
|
3739
3801
|
try {
|
|
3740
|
-
const client =
|
|
3802
|
+
const client = getMapiClient();
|
|
3741
3803
|
const { data } = await client.spaces.create({
|
|
3742
3804
|
body: {
|
|
3743
3805
|
space
|
|
@@ -3775,15 +3837,14 @@ const saveLanguagesToFile = async (space, internationalizationOptions, options)
|
|
|
3775
3837
|
}
|
|
3776
3838
|
};
|
|
3777
3839
|
|
|
3778
|
-
const program$
|
|
3779
|
-
const languagesCommand = program$
|
|
3840
|
+
const program$c = getProgram();
|
|
3841
|
+
const languagesCommand = program$c.command(commands.LANGUAGES).alias("lang").description(`Manage your space's languages`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/languages");
|
|
3780
3842
|
languagesCommand.command("pull").description(`Download your space's languages schema as json`).option("-f, --filename <filename>", "filename to save the file as <filename>.<suffix>.json").option("--su, --suffix <suffix>", "suffix to add to the file name (e.g. languages.<suffix>.json). By default, the space ID is used.").action(async (options) => {
|
|
3781
3843
|
konsola.title(`${commands.LANGUAGES}`, colorPalette.LANGUAGES);
|
|
3782
|
-
const verbose = program$
|
|
3844
|
+
const verbose = program$c.opts().verbose;
|
|
3783
3845
|
const { space, path } = languagesCommand.opts();
|
|
3784
3846
|
const { filename = "languages", suffix = options.space } = options;
|
|
3785
|
-
const { state
|
|
3786
|
-
await initializeSession();
|
|
3847
|
+
const { state } = session();
|
|
3787
3848
|
if (!requireAuthentication(state, verbose)) {
|
|
3788
3849
|
return;
|
|
3789
3850
|
}
|
|
@@ -3791,13 +3852,6 @@ languagesCommand.command("pull").description(`Download your space's languages sc
|
|
|
3791
3852
|
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
|
|
3792
3853
|
return;
|
|
3793
3854
|
}
|
|
3794
|
-
const { password, region } = state;
|
|
3795
|
-
mapiClient({
|
|
3796
|
-
token: {
|
|
3797
|
-
accessToken: password
|
|
3798
|
-
},
|
|
3799
|
-
region
|
|
3800
|
-
});
|
|
3801
3855
|
const spinner = new Spinner({
|
|
3802
3856
|
verbose: !isVitest
|
|
3803
3857
|
});
|
|
@@ -3826,8 +3880,8 @@ languagesCommand.command("pull").description(`Download your space's languages sc
|
|
|
3826
3880
|
konsola.br();
|
|
3827
3881
|
});
|
|
3828
3882
|
|
|
3829
|
-
const program$
|
|
3830
|
-
const migrationsCommand = program$
|
|
3883
|
+
const program$b = getProgram();
|
|
3884
|
+
const migrationsCommand = program$b.command(commands.MIGRATIONS).alias("mig").description(`Manage your space's migrations`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/migrations");
|
|
3831
3885
|
|
|
3832
3886
|
const getMigrationTemplate = () => {
|
|
3833
3887
|
return `export default function (block) {
|
|
@@ -3872,8 +3926,7 @@ migrationsCommand.command("generate [componentName]").description("Generate a mi
|
|
|
3872
3926
|
handleError(new CommandError(`Please provide the component name as argument ${chalk.hex(colorPalette.MIGRATIONS)("storyblok migrations generate YOUR_COMPONENT_NAME.")}`), verbose);
|
|
3873
3927
|
return;
|
|
3874
3928
|
}
|
|
3875
|
-
const { state
|
|
3876
|
-
await initializeSession();
|
|
3929
|
+
const { state } = session();
|
|
3877
3930
|
if (!requireAuthentication(state, verbose)) {
|
|
3878
3931
|
return;
|
|
3879
3932
|
}
|
|
@@ -3881,13 +3934,6 @@ migrationsCommand.command("generate [componentName]").description("Generate a mi
|
|
|
3881
3934
|
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
|
|
3882
3935
|
return;
|
|
3883
3936
|
}
|
|
3884
|
-
const { password, region } = state;
|
|
3885
|
-
mapiClient({
|
|
3886
|
-
token: {
|
|
3887
|
-
accessToken: password
|
|
3888
|
-
},
|
|
3889
|
-
region
|
|
3890
|
-
});
|
|
3891
3937
|
const spinner = ui.createSpinner(`Generating migration for component ${componentName}...`);
|
|
3892
3938
|
try {
|
|
3893
3939
|
const component = await fetchComponent(space, componentName);
|
|
@@ -3915,7 +3961,7 @@ migrationsCommand.command("generate [componentName]").description("Generate a mi
|
|
|
3915
3961
|
|
|
3916
3962
|
const fetchStories = async (spaceId, params) => {
|
|
3917
3963
|
try {
|
|
3918
|
-
const client =
|
|
3964
|
+
const client = getMapiClient();
|
|
3919
3965
|
const { data, response } = await client.stories.list({
|
|
3920
3966
|
path: {
|
|
3921
3967
|
space_id: spaceId
|
|
@@ -3937,7 +3983,7 @@ const fetchStories = async (spaceId, params) => {
|
|
|
3937
3983
|
};
|
|
3938
3984
|
const fetchStory = async (spaceId, storyId) => {
|
|
3939
3985
|
try {
|
|
3940
|
-
const client =
|
|
3986
|
+
const client = getMapiClient();
|
|
3941
3987
|
const { data } = await client.stories.get({
|
|
3942
3988
|
path: {
|
|
3943
3989
|
space_id: spaceId,
|
|
@@ -3945,14 +3991,32 @@ const fetchStory = async (spaceId, storyId) => {
|
|
|
3945
3991
|
},
|
|
3946
3992
|
throwOnError: true
|
|
3947
3993
|
});
|
|
3948
|
-
return data
|
|
3994
|
+
return data.story;
|
|
3949
3995
|
} catch (error) {
|
|
3950
3996
|
handleAPIError("pull_story", error);
|
|
3951
3997
|
}
|
|
3952
3998
|
};
|
|
3999
|
+
const createStory = async (spaceId, payload) => {
|
|
4000
|
+
try {
|
|
4001
|
+
const client = getMapiClient();
|
|
4002
|
+
const { data } = await client.stories.create({
|
|
4003
|
+
path: {
|
|
4004
|
+
space_id: spaceId
|
|
4005
|
+
},
|
|
4006
|
+
body: {
|
|
4007
|
+
story: payload.story,
|
|
4008
|
+
publish: payload.publish
|
|
4009
|
+
},
|
|
4010
|
+
throwOnError: true
|
|
4011
|
+
});
|
|
4012
|
+
return data?.story;
|
|
4013
|
+
} catch (maybeError) {
|
|
4014
|
+
handleAPIError("create_story", toError(maybeError));
|
|
4015
|
+
}
|
|
4016
|
+
};
|
|
3953
4017
|
const updateStory = async (spaceId, storyId, payload) => {
|
|
3954
4018
|
try {
|
|
3955
|
-
const client =
|
|
4019
|
+
const client = getMapiClient();
|
|
3956
4020
|
const { data } = await client.stories.updateStory({
|
|
3957
4021
|
path: {
|
|
3958
4022
|
space_id: spaceId,
|
|
@@ -3965,9 +4029,14 @@ const updateStory = async (spaceId, storyId, payload) => {
|
|
|
3965
4029
|
},
|
|
3966
4030
|
throwOnError: true
|
|
3967
4031
|
});
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
4032
|
+
const { story } = data;
|
|
4033
|
+
if (!story) {
|
|
4034
|
+
throw new Error("Failed to update story");
|
|
4035
|
+
}
|
|
4036
|
+
return story;
|
|
4037
|
+
} catch (maybeError) {
|
|
4038
|
+
handleAPIError("update_story", toError(maybeError));
|
|
4039
|
+
throw maybeError;
|
|
3971
4040
|
}
|
|
3972
4041
|
};
|
|
3973
4042
|
|
|
@@ -4424,6 +4493,49 @@ const isStoryPublishedWithoutChanges = (story) => {
|
|
|
4424
4493
|
const isStoryWithUnpublishedChanges = (story) => {
|
|
4425
4494
|
return story.published && story.unpublished_changes;
|
|
4426
4495
|
};
|
|
4496
|
+
const toComponent = (maybeComponent) => {
|
|
4497
|
+
if (maybeComponent.component_group_uuid === void 0) {
|
|
4498
|
+
return null;
|
|
4499
|
+
}
|
|
4500
|
+
return maybeComponent;
|
|
4501
|
+
};
|
|
4502
|
+
const findComponentSchemas = async (directoryPath) => {
|
|
4503
|
+
const files = await readdir(directoryPath).catch((error) => {
|
|
4504
|
+
if (error.code === "ENOENT") {
|
|
4505
|
+
return [];
|
|
4506
|
+
}
|
|
4507
|
+
throw error;
|
|
4508
|
+
});
|
|
4509
|
+
const fileContents = files.filter((f) => path.extname(f) === ".json").map((f) => {
|
|
4510
|
+
const filePath = path.join(directoryPath, f);
|
|
4511
|
+
const fileContent = readFileSync(filePath, "utf-8");
|
|
4512
|
+
return JSON.parse(fileContent);
|
|
4513
|
+
});
|
|
4514
|
+
const components = [];
|
|
4515
|
+
for (const content of fileContents) {
|
|
4516
|
+
if (Array.isArray(content)) {
|
|
4517
|
+
for (const maybeComponent of content) {
|
|
4518
|
+
const component2 = toComponent(maybeComponent);
|
|
4519
|
+
if (component2) {
|
|
4520
|
+
components.push(component2);
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
continue;
|
|
4524
|
+
}
|
|
4525
|
+
const component = toComponent(content);
|
|
4526
|
+
if (component) {
|
|
4527
|
+
components.push(component);
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
const schemas = {};
|
|
4531
|
+
for (const component of components) {
|
|
4532
|
+
schemas[component.name] = component.schema;
|
|
4533
|
+
}
|
|
4534
|
+
return schemas;
|
|
4535
|
+
};
|
|
4536
|
+
const getStoryFilename = (story) => {
|
|
4537
|
+
return `${story.slug}_${story.uuid}.json`;
|
|
4538
|
+
};
|
|
4427
4539
|
|
|
4428
4540
|
class UpdateStream extends Writable {
|
|
4429
4541
|
constructor(options) {
|
|
@@ -4556,8 +4668,7 @@ migrationsCommand.command("run [componentName]").description("Run migrations").o
|
|
|
4556
4668
|
}
|
|
4557
4669
|
const verbose = program.opts().verbose;
|
|
4558
4670
|
const { space, path } = migrationsCommand.opts();
|
|
4559
|
-
const { state
|
|
4560
|
-
await initializeSession();
|
|
4671
|
+
const { state } = session();
|
|
4561
4672
|
if (!requireAuthentication(state, verbose)) {
|
|
4562
4673
|
return;
|
|
4563
4674
|
}
|
|
@@ -4566,13 +4677,6 @@ migrationsCommand.command("run [componentName]").description("Run migrations").o
|
|
|
4566
4677
|
return;
|
|
4567
4678
|
}
|
|
4568
4679
|
const { filter, dryRun = false, query, startsWith, publish } = options;
|
|
4569
|
-
const { password, region } = state;
|
|
4570
|
-
mapiClient({
|
|
4571
|
-
token: {
|
|
4572
|
-
accessToken: password
|
|
4573
|
-
},
|
|
4574
|
-
region
|
|
4575
|
-
});
|
|
4576
4680
|
try {
|
|
4577
4681
|
const spinner = ui.createSpinner(`Fetching migration files and stories...`);
|
|
4578
4682
|
const migrationFiles = await readMigrationFiles({
|
|
@@ -4690,8 +4794,7 @@ migrationsCommand.command("rollback [migrationFile]").description("Rollback a mi
|
|
|
4690
4794
|
space,
|
|
4691
4795
|
path
|
|
4692
4796
|
});
|
|
4693
|
-
const { state
|
|
4694
|
-
await initializeSession();
|
|
4797
|
+
const { state } = session();
|
|
4695
4798
|
if (!requireAuthentication(state, verbose)) {
|
|
4696
4799
|
return;
|
|
4697
4800
|
}
|
|
@@ -4699,13 +4802,6 @@ migrationsCommand.command("rollback [migrationFile]").description("Rollback a mi
|
|
|
4699
4802
|
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
|
|
4700
4803
|
return;
|
|
4701
4804
|
}
|
|
4702
|
-
const { password, region } = state;
|
|
4703
|
-
mapiClient({
|
|
4704
|
-
token: {
|
|
4705
|
-
accessToken: password
|
|
4706
|
-
},
|
|
4707
|
-
region
|
|
4708
|
-
});
|
|
4709
4805
|
try {
|
|
4710
4806
|
const rollbackData = await readRollbackFile({
|
|
4711
4807
|
space,
|
|
@@ -4763,8 +4859,8 @@ migrationsCommand.command("rollback [migrationFile]").description("Rollback a mi
|
|
|
4763
4859
|
}
|
|
4764
4860
|
});
|
|
4765
4861
|
|
|
4766
|
-
const program$
|
|
4767
|
-
const typesCommand = program$
|
|
4862
|
+
const program$a = getProgram();
|
|
4863
|
+
const typesCommand = program$a.command(commands.TYPES).alias("ts").description(`Generate types d.ts for your component schemas`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/types");
|
|
4768
4864
|
|
|
4769
4865
|
const getAssetJSONSchema = (title) => ({
|
|
4770
4866
|
$id: "#/asset",
|
|
@@ -5522,7 +5618,7 @@ const DEFAULT_DATASOURCES_FILENAME = "datasources";
|
|
|
5522
5618
|
|
|
5523
5619
|
const pushDatasource = async (spaceId, datasource) => {
|
|
5524
5620
|
try {
|
|
5525
|
-
const client =
|
|
5621
|
+
const client = getMapiClient();
|
|
5526
5622
|
const { data } = await client.datasources.create({
|
|
5527
5623
|
path: {
|
|
5528
5624
|
space_id: spaceId
|
|
@@ -5537,7 +5633,7 @@ const pushDatasource = async (spaceId, datasource) => {
|
|
|
5537
5633
|
};
|
|
5538
5634
|
const updateDatasource = async (spaceId, datasourceId, datasource) => {
|
|
5539
5635
|
try {
|
|
5540
|
-
const client =
|
|
5636
|
+
const client = getMapiClient();
|
|
5541
5637
|
const { data } = await client.datasources.update({
|
|
5542
5638
|
path: {
|
|
5543
5639
|
space_id: spaceId,
|
|
@@ -5562,7 +5658,7 @@ const upsertDatasource = async (space, datasource, existingId) => {
|
|
|
5562
5658
|
};
|
|
5563
5659
|
const pushDatasourceEntry = async (spaceId, datasourceId, entry) => {
|
|
5564
5660
|
try {
|
|
5565
|
-
const client =
|
|
5661
|
+
const client = getMapiClient();
|
|
5566
5662
|
const { data } = await client.datasourceEntries.create({
|
|
5567
5663
|
path: {
|
|
5568
5664
|
space_id: spaceId
|
|
@@ -5582,7 +5678,7 @@ const pushDatasourceEntry = async (spaceId, datasourceId, entry) => {
|
|
|
5582
5678
|
};
|
|
5583
5679
|
const updateDatasourceEntry = async (spaceId, entryId, entry) => {
|
|
5584
5680
|
try {
|
|
5585
|
-
const client =
|
|
5681
|
+
const client = getMapiClient();
|
|
5586
5682
|
await client.datasourceEntries.updateDatasourceEntry({
|
|
5587
5683
|
path: {
|
|
5588
5684
|
space_id: spaceId,
|
|
@@ -5674,13 +5770,13 @@ async function readConsolidatedFiles(resolvedPath, suffix) {
|
|
|
5674
5770
|
};
|
|
5675
5771
|
}
|
|
5676
5772
|
|
|
5677
|
-
const program$
|
|
5773
|
+
const program$9 = getProgram();
|
|
5678
5774
|
typesCommand.command("generate").description("Generate types d.ts for your component schemas").option(
|
|
5679
5775
|
"--filename <name>",
|
|
5680
5776
|
"Base file name for all component types when generating a single declarations file (e.g. components.d.ts). Ignored when using --separate-files."
|
|
5681
5777
|
).option("--sf, --separate-files", "Generate one .d.ts file per component instead of a single combined file").option("--strict", "strict mode, no loose typing").option("--type-prefix <prefix>", "prefix to be prepended to all generated component type names").option("--type-suffix <suffix>", "suffix to be appended to all generated component type names").option("--suffix <suffix>", "Components suffix").option("--custom-fields-parser <path>", "Path to the parser file for Custom Field Types").option("--compiler-options <options>", "path to the compiler options from json-schema-to-typescript").action(async (options) => {
|
|
5682
5778
|
konsola.title(`${commands.TYPES}`, colorPalette.TYPES, "Generating types...");
|
|
5683
|
-
const verbose = program$
|
|
5779
|
+
const verbose = program$9.opts().verbose;
|
|
5684
5780
|
const { space, path } = typesCommand.opts();
|
|
5685
5781
|
const spinner = new Spinner({
|
|
5686
5782
|
verbose: !isVitest
|
|
@@ -5733,8 +5829,8 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
|
|
|
5733
5829
|
}
|
|
5734
5830
|
});
|
|
5735
5831
|
|
|
5736
|
-
const program$
|
|
5737
|
-
const datasourcesCommand = program$
|
|
5832
|
+
const program$8 = getProgram();
|
|
5833
|
+
const datasourcesCommand = program$8.command(commands.DATASOURCES).alias("ds").description(`Manage your space's datasources`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "path to save the file. Default is .storyblok/datasources");
|
|
5738
5834
|
|
|
5739
5835
|
async function fetchAllPages(fetchFunction, extractDataFunction, page = 1, collectedItems = []) {
|
|
5740
5836
|
const { data, response } = await fetchFunction(page);
|
|
@@ -5752,7 +5848,7 @@ async function fetchAllPages(fetchFunction, extractDataFunction, page = 1, colle
|
|
|
5752
5848
|
}
|
|
5753
5849
|
const fetchDatasourceEntries = async (spaceId, datasourceId) => {
|
|
5754
5850
|
try {
|
|
5755
|
-
const client =
|
|
5851
|
+
const client = getMapiClient();
|
|
5756
5852
|
return await fetchAllPages(
|
|
5757
5853
|
(page) => client.datasourceEntries.list({
|
|
5758
5854
|
path: {
|
|
@@ -5772,7 +5868,7 @@ const fetchDatasourceEntries = async (spaceId, datasourceId) => {
|
|
|
5772
5868
|
};
|
|
5773
5869
|
const fetchDatasources = async (spaceId) => {
|
|
5774
5870
|
try {
|
|
5775
|
-
const client =
|
|
5871
|
+
const client = getMapiClient();
|
|
5776
5872
|
const datasources = await fetchAllPages(
|
|
5777
5873
|
(page) => client.datasources.list({
|
|
5778
5874
|
path: {
|
|
@@ -5801,7 +5897,7 @@ const fetchDatasources = async (spaceId) => {
|
|
|
5801
5897
|
};
|
|
5802
5898
|
const fetchDatasource = async (spaceId, datasourceName) => {
|
|
5803
5899
|
try {
|
|
5804
|
-
const client =
|
|
5900
|
+
const client = getMapiClient();
|
|
5805
5901
|
const { data } = await client.datasources.list({
|
|
5806
5902
|
path: {
|
|
5807
5903
|
space_id: spaceId
|
|
@@ -5840,10 +5936,10 @@ const saveDatasourcesToFiles = async (space, datasources, options) => {
|
|
|
5840
5936
|
}
|
|
5841
5937
|
};
|
|
5842
5938
|
|
|
5843
|
-
const program$
|
|
5939
|
+
const program$7 = getProgram();
|
|
5844
5940
|
datasourcesCommand.command("pull [datasourceName]").option("-f, --filename <filename>", "custom name to be used in file(s) name instead of space id").option("--sf, --separate-files", "Argument to create a single file for each datasource").option("--su, --suffix <suffix>", "suffix to add to the file name (e.g. datasources.<suffix>.json)").description("Pull datasources from your space").action(async (datasourceName, options) => {
|
|
5845
5941
|
konsola.title(`${commands.DATASOURCES}`, colorPalette.DATASOURCES, datasourceName ? `Pulling datasource ${datasourceName}...` : "Pulling datasources...");
|
|
5846
|
-
const verbose = program$
|
|
5942
|
+
const verbose = program$7.opts().verbose;
|
|
5847
5943
|
const { space, path } = datasourcesCommand.opts();
|
|
5848
5944
|
const {
|
|
5849
5945
|
separateFiles = false,
|
|
@@ -5852,8 +5948,7 @@ datasourcesCommand.command("pull [datasourceName]").option("-f, --filename <file
|
|
|
5852
5948
|
} = options;
|
|
5853
5949
|
const actualFilename = filename ?? DEFAULT_DATASOURCES_FILENAME;
|
|
5854
5950
|
const datasourcesOutputDir = resolveCommandPath(directories.datasources, space, path);
|
|
5855
|
-
const { state
|
|
5856
|
-
await initializeSession();
|
|
5951
|
+
const { state } = session();
|
|
5857
5952
|
if (!requireAuthentication(state, verbose)) {
|
|
5858
5953
|
return;
|
|
5859
5954
|
}
|
|
@@ -5861,13 +5956,6 @@ datasourcesCommand.command("pull [datasourceName]").option("-f, --filename <file
|
|
|
5861
5956
|
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
|
|
5862
5957
|
return;
|
|
5863
5958
|
}
|
|
5864
|
-
const { password, region } = state;
|
|
5865
|
-
mapiClient({
|
|
5866
|
-
token: {
|
|
5867
|
-
accessToken: password
|
|
5868
|
-
},
|
|
5869
|
-
region
|
|
5870
|
-
});
|
|
5871
5959
|
const spinnerDatasources = new Spinner({
|
|
5872
5960
|
verbose: !isVitest
|
|
5873
5961
|
});
|
|
@@ -5921,15 +6009,14 @@ datasourcesCommand.command("pull [datasourceName]").option("-f, --filename <file
|
|
|
5921
6009
|
}
|
|
5922
6010
|
});
|
|
5923
6011
|
|
|
5924
|
-
const program$
|
|
6012
|
+
const program$6 = getProgram();
|
|
5925
6013
|
datasourcesCommand.command("push [datasourceName]").description(`Push your space's datasources schema as json`).option("-f, --from <from>", "source space id").option("--fi, --filter <filter>", "glob filter to apply to the datasources before pushing").option("--sf, --separate-files", "Read from separate files instead of consolidated files").option("--su, --suffix <suffix>", "Suffix to add to the datasource name").action(async (datasourceName, options) => {
|
|
5926
6014
|
konsola.title(`${commands.DATASOURCES}`, colorPalette.DATASOURCES, datasourceName ? `Pushing datasource ${datasourceName}...` : "Pushing datasources...");
|
|
5927
|
-
const verbose = program$
|
|
6015
|
+
const verbose = program$6.opts().verbose;
|
|
5928
6016
|
const { space, path } = datasourcesCommand.opts();
|
|
5929
6017
|
const { filter } = options;
|
|
5930
6018
|
const fromSpace = options.from || space;
|
|
5931
|
-
const { state
|
|
5932
|
-
await initializeSession();
|
|
6019
|
+
const { state } = session();
|
|
5933
6020
|
if (!requireAuthentication(state, verbose)) {
|
|
5934
6021
|
return;
|
|
5935
6022
|
}
|
|
@@ -5939,13 +6026,6 @@ datasourcesCommand.command("push [datasourceName]").description(`Push your space
|
|
|
5939
6026
|
}
|
|
5940
6027
|
konsola.info(`Attempting to push datasources ${chalk.bold("from")} space ${chalk.hex(colorPalette.DATASOURCES)(fromSpace)} ${chalk.bold("to")} ${chalk.hex(colorPalette.DATASOURCES)(space)}`);
|
|
5941
6028
|
konsola.br();
|
|
5942
|
-
const { password, region } = state;
|
|
5943
|
-
mapiClient({
|
|
5944
|
-
token: {
|
|
5945
|
-
accessToken: password
|
|
5946
|
-
},
|
|
5947
|
-
region
|
|
5948
|
-
});
|
|
5949
6029
|
try {
|
|
5950
6030
|
const spaceState = {
|
|
5951
6031
|
local: await readDatasourcesFiles({
|
|
@@ -6032,7 +6112,7 @@ datasourcesCommand.command("push [datasourceName]").description(`Push your space
|
|
|
6032
6112
|
|
|
6033
6113
|
async function deleteDatasource(spaceId, id) {
|
|
6034
6114
|
try {
|
|
6035
|
-
const client =
|
|
6115
|
+
const client = getMapiClient();
|
|
6036
6116
|
await client.datasources.delete({
|
|
6037
6117
|
path: {
|
|
6038
6118
|
space_id: spaceId,
|
|
@@ -6058,8 +6138,7 @@ datasourcesCommand.command("delete [name]").description("Delete a datasource fro
|
|
|
6058
6138
|
}
|
|
6059
6139
|
const { space } = datasourcesCommand.opts();
|
|
6060
6140
|
const verbose = datasourcesCommand.parent?.opts().verbose;
|
|
6061
|
-
const { state
|
|
6062
|
-
await initializeSession();
|
|
6141
|
+
const { state } = session();
|
|
6063
6142
|
if (!requireAuthentication(state, verbose)) {
|
|
6064
6143
|
return;
|
|
6065
6144
|
}
|
|
@@ -6067,13 +6146,6 @@ datasourcesCommand.command("delete [name]").description("Delete a datasource fro
|
|
|
6067
6146
|
handleError(new CommandError("Please provide the space as argument --space YOUR_SPACE_ID."), verbose);
|
|
6068
6147
|
return;
|
|
6069
6148
|
}
|
|
6070
|
-
const { password, region } = state;
|
|
6071
|
-
mapiClient({
|
|
6072
|
-
token: {
|
|
6073
|
-
accessToken: password
|
|
6074
|
-
},
|
|
6075
|
-
region
|
|
6076
|
-
});
|
|
6077
6149
|
const spinner = new Spinner({
|
|
6078
6150
|
verbose: !isVitest
|
|
6079
6151
|
});
|
|
@@ -6300,13 +6372,13 @@ async function promptForLogin(verbose) {
|
|
|
6300
6372
|
return null;
|
|
6301
6373
|
}
|
|
6302
6374
|
}
|
|
6303
|
-
const program$
|
|
6304
|
-
program$
|
|
6375
|
+
const program$5 = getProgram();
|
|
6376
|
+
program$5.command(`${commands.CREATE} [project-path]`).alias("c").description(`Scaffold a new project using Storyblok`).option("-t, --template <template>", "technology starter template").option("-b, --blueprint <blueprint>", "[DEPRECATED] use --template instead").option("--skip-space", "skip space creation").option("--token <token>", "Storyblok access token (skip space creation and use this token)").option(
|
|
6305
6377
|
"-r, --region <region>",
|
|
6306
6378
|
`The region to apply to the generated project template (does not affect space creation).`
|
|
6307
6379
|
).action(async (projectPath, options) => {
|
|
6308
6380
|
konsola.title(`${commands.CREATE}`, colorPalette.CREATE);
|
|
6309
|
-
const verbose = program$
|
|
6381
|
+
const verbose = program$5.opts().verbose;
|
|
6310
6382
|
const { template, blueprint, token } = options;
|
|
6311
6383
|
if (options.region && !isRegion(options.region)) {
|
|
6312
6384
|
handleError(new CommandError(`The provided region: ${options.region} is not valid. Please use one of the following values: ${Object.values(regions).join(" | ")}`));
|
|
@@ -6320,7 +6392,6 @@ program$3.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
|
|
|
6320
6392
|
konsola.warn(`Both --blueprint and --template provided. Using --template and ignoring --blueprint.`);
|
|
6321
6393
|
}
|
|
6322
6394
|
const { state, initializeSession } = session();
|
|
6323
|
-
await initializeSession();
|
|
6324
6395
|
let password;
|
|
6325
6396
|
let region;
|
|
6326
6397
|
if (state.region) {
|
|
@@ -6341,12 +6412,6 @@ program$3.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
|
|
|
6341
6412
|
handleError(new CommandError(`Cannot create space in region "${options.region}". Your account is configured for region "${region}". Space creation must use your account's region.`));
|
|
6342
6413
|
return;
|
|
6343
6414
|
}
|
|
6344
|
-
mapiClient({
|
|
6345
|
-
token: {
|
|
6346
|
-
accessToken: password
|
|
6347
|
-
},
|
|
6348
|
-
region
|
|
6349
|
-
});
|
|
6350
6415
|
} else if (state.isLoggedIn && state.password) {
|
|
6351
6416
|
password = state.password;
|
|
6352
6417
|
if (state.region) {
|
|
@@ -6532,13 +6597,13 @@ program$3.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
|
|
|
6532
6597
|
konsola.br();
|
|
6533
6598
|
});
|
|
6534
6599
|
|
|
6535
|
-
const program$
|
|
6536
|
-
const logsCommand = program$
|
|
6600
|
+
const program$4 = getProgram();
|
|
6601
|
+
const logsCommand = program$4.command(commands.LOGS).alias("lg").description(`Inspect and manage logs.`).option("-s, --space <space>", "The space ID.").option("-p, --path <path>", "Path to the directory containing the logs directory. Defaults to '.storyblok'.");
|
|
6537
6602
|
|
|
6538
6603
|
logsCommand.command("list").description("List logs").action(async () => {
|
|
6539
6604
|
const { space, path } = logsCommand.opts();
|
|
6540
6605
|
const ui = getUI();
|
|
6541
|
-
const logsPath = resolveCommandPath(directories.
|
|
6606
|
+
const logsPath = resolveCommandPath(directories.logs, space, path);
|
|
6542
6607
|
const logFiles = FileTransport.listLogFiles(logsPath);
|
|
6543
6608
|
if (logFiles.length === 0) {
|
|
6544
6609
|
ui.info(`No logs found for space "${space}".`);
|
|
@@ -6551,18 +6616,18 @@ logsCommand.command("list").description("List logs").action(async () => {
|
|
|
6551
6616
|
logsCommand.command("prune").description("Prune logs").option("--keep <number>", "Max number of log files to keep (default `0`, meaning remove all)", Number.parseInt, 0).action(async ({ keep }) => {
|
|
6552
6617
|
const { space, path } = logsCommand.opts();
|
|
6553
6618
|
const ui = getUI();
|
|
6554
|
-
const logsPath = resolveCommandPath(directories.
|
|
6619
|
+
const logsPath = resolveCommandPath(directories.logs, space, path);
|
|
6555
6620
|
const deletedFilesCount = FileTransport.pruneLogFiles(logsPath, keep);
|
|
6556
6621
|
ui.info(`Deleted ${deletedFilesCount} log file${deletedFilesCount === 1 ? "" : "s"}`);
|
|
6557
6622
|
});
|
|
6558
6623
|
|
|
6559
|
-
const program$
|
|
6560
|
-
const reportsCommand = program$
|
|
6624
|
+
const program$3 = getProgram();
|
|
6625
|
+
const reportsCommand = program$3.command(commands.REPORTS).alias("rp").description("Inspect and manage reports.").option("-s, --space <space>", "The space ID.").option("-p, --path <path>", "Path to the directory containing the reports directory. Defaults to '.storyblok'.");
|
|
6561
6626
|
|
|
6562
6627
|
reportsCommand.command("list").description("List reports").action(async () => {
|
|
6563
6628
|
const { space, path } = reportsCommand.opts();
|
|
6564
6629
|
const ui = getUI();
|
|
6565
|
-
const reportsPath = resolveCommandPath(directories.
|
|
6630
|
+
const reportsPath = resolveCommandPath(directories.reports, space, path);
|
|
6566
6631
|
const reportFiles = Reporter.listReportFiles(reportsPath, ".jsonl");
|
|
6567
6632
|
if (reportFiles.length === 0) {
|
|
6568
6633
|
ui.info(`No reports found for space "${space}".`);
|
|
@@ -6575,11 +6640,2218 @@ reportsCommand.command("list").description("List reports").action(async () => {
|
|
|
6575
6640
|
reportsCommand.command("prune").description("Prune reports").option("--keep <number>", "Max number of report files to keep (default `0`, meaning remove all)", Number.parseInt, 0).action(async ({ keep }) => {
|
|
6576
6641
|
const { space, path } = reportsCommand.opts();
|
|
6577
6642
|
const ui = getUI();
|
|
6578
|
-
const reportsPath = resolveCommandPath(directories.
|
|
6643
|
+
const reportsPath = resolveCommandPath(directories.reports, space, path);
|
|
6579
6644
|
const deletedFilesCount = Reporter.pruneReportFiles(reportsPath, keep, ".jsonl");
|
|
6580
6645
|
ui.info(`Deleted ${deletedFilesCount} report file${deletedFilesCount === 1 ? "" : "s"}`);
|
|
6581
6646
|
});
|
|
6582
6647
|
|
|
6648
|
+
const program$2 = getProgram();
|
|
6649
|
+
const assetsCommand = program$2.command(commands.ASSETS).description(`Manage your space's assets`).option("-s, --space <space>", "space ID").option("-p, --path <path>", "base path to store assets (default .storyblok)");
|
|
6650
|
+
|
|
6651
|
+
const fetchAssets = async ({ spaceId, params }) => {
|
|
6652
|
+
try {
|
|
6653
|
+
const client = getMapiClient();
|
|
6654
|
+
const { data, response } = await client.assets.list({
|
|
6655
|
+
path: {
|
|
6656
|
+
space_id: spaceId
|
|
6657
|
+
},
|
|
6658
|
+
query: {
|
|
6659
|
+
...params,
|
|
6660
|
+
per_page: params?.per_page || 100,
|
|
6661
|
+
page: params?.page || 1
|
|
6662
|
+
},
|
|
6663
|
+
throwOnError: true
|
|
6664
|
+
});
|
|
6665
|
+
const assets = (data?.assets || []).filter((asset) => Boolean(asset?.id && asset?.filename));
|
|
6666
|
+
return {
|
|
6667
|
+
assets,
|
|
6668
|
+
headers: response.headers
|
|
6669
|
+
};
|
|
6670
|
+
} catch (maybeError) {
|
|
6671
|
+
handleAPIError("pull_assets", toError(maybeError));
|
|
6672
|
+
throw maybeError;
|
|
6673
|
+
}
|
|
6674
|
+
};
|
|
6675
|
+
const downloadFile = async (filename) => {
|
|
6676
|
+
const response = await fetch(filename);
|
|
6677
|
+
if (!response.ok) {
|
|
6678
|
+
throw new Error(`Failed to download ${filename}`);
|
|
6679
|
+
}
|
|
6680
|
+
return response.arrayBuffer();
|
|
6681
|
+
};
|
|
6682
|
+
const getSignedAssetUrl = async (filename, assetToken, region) => {
|
|
6683
|
+
try {
|
|
6684
|
+
const client = new Storyblok({
|
|
6685
|
+
accessToken: assetToken,
|
|
6686
|
+
region: region || "eu"
|
|
6687
|
+
});
|
|
6688
|
+
const response = await client.get("cdn/assets/me", {
|
|
6689
|
+
filename
|
|
6690
|
+
});
|
|
6691
|
+
return response.data.asset.signed_url;
|
|
6692
|
+
} catch (maybeError) {
|
|
6693
|
+
handleAPIError("pull_asset", toError(maybeError));
|
|
6694
|
+
throw maybeError;
|
|
6695
|
+
}
|
|
6696
|
+
};
|
|
6697
|
+
const fetchAssetFolders = async ({ spaceId }) => {
|
|
6698
|
+
try {
|
|
6699
|
+
const client = getMapiClient();
|
|
6700
|
+
const { data, response } = await client.assetFolders.list({
|
|
6701
|
+
path: {
|
|
6702
|
+
space_id: spaceId
|
|
6703
|
+
},
|
|
6704
|
+
throwOnError: true
|
|
6705
|
+
});
|
|
6706
|
+
return {
|
|
6707
|
+
asset_folders: data.asset_folders || [],
|
|
6708
|
+
headers: response.headers
|
|
6709
|
+
};
|
|
6710
|
+
} catch (maybeError) {
|
|
6711
|
+
handleAPIError("pull_asset_folders", toError(maybeError));
|
|
6712
|
+
throw maybeError;
|
|
6713
|
+
}
|
|
6714
|
+
};
|
|
6715
|
+
const createAssetFolder = async (folder, {
|
|
6716
|
+
spaceId
|
|
6717
|
+
}) => {
|
|
6718
|
+
try {
|
|
6719
|
+
const client = getMapiClient();
|
|
6720
|
+
const { data } = await client.assetFolders.create({
|
|
6721
|
+
path: {
|
|
6722
|
+
space_id: spaceId
|
|
6723
|
+
},
|
|
6724
|
+
body: { asset_folder: folder },
|
|
6725
|
+
throwOnError: true
|
|
6726
|
+
});
|
|
6727
|
+
const { asset_folder } = data;
|
|
6728
|
+
if (!asset_folder) {
|
|
6729
|
+
throw new Error("Failed to create asset folder");
|
|
6730
|
+
}
|
|
6731
|
+
return asset_folder;
|
|
6732
|
+
} catch (maybeError) {
|
|
6733
|
+
handleAPIError("push_asset_folder", toError(maybeError));
|
|
6734
|
+
throw maybeError;
|
|
6735
|
+
}
|
|
6736
|
+
};
|
|
6737
|
+
const updateAssetFolder = async (folder, {
|
|
6738
|
+
spaceId
|
|
6739
|
+
}) => {
|
|
6740
|
+
try {
|
|
6741
|
+
const client = getMapiClient();
|
|
6742
|
+
await client.assetFolders.update({
|
|
6743
|
+
path: {
|
|
6744
|
+
asset_folder_id: folder.id,
|
|
6745
|
+
space_id: spaceId
|
|
6746
|
+
},
|
|
6747
|
+
body: { asset_folder: folder },
|
|
6748
|
+
throwOnError: true
|
|
6749
|
+
});
|
|
6750
|
+
return folder;
|
|
6751
|
+
} catch (maybeError) {
|
|
6752
|
+
handleAPIError("push_asset_folder", toError(maybeError));
|
|
6753
|
+
throw maybeError;
|
|
6754
|
+
}
|
|
6755
|
+
};
|
|
6756
|
+
const requestAssetUpload = async (asset, { spaceId }) => {
|
|
6757
|
+
try {
|
|
6758
|
+
const client = getMapiClient();
|
|
6759
|
+
const { data } = await client.assets.upload({
|
|
6760
|
+
path: {
|
|
6761
|
+
space_id: spaceId
|
|
6762
|
+
},
|
|
6763
|
+
body: {
|
|
6764
|
+
// @ts-expect-error Our types are wrong, id is optional but allowed.
|
|
6765
|
+
id: asset.id,
|
|
6766
|
+
filename: asset.short_filename,
|
|
6767
|
+
asset_folder_id: asset.asset_folder_id ?? void 0,
|
|
6768
|
+
is_private: asset.is_private
|
|
6769
|
+
},
|
|
6770
|
+
throwOnError: true
|
|
6771
|
+
});
|
|
6772
|
+
const signedUpload = data;
|
|
6773
|
+
if (!signedUpload?.id || !signedUpload?.post_url || !signedUpload?.fields) {
|
|
6774
|
+
throw new Error("Failed to request signed upload!");
|
|
6775
|
+
}
|
|
6776
|
+
return signedUpload;
|
|
6777
|
+
} catch (maybeError) {
|
|
6778
|
+
handleAPIError("push_asset_sign", toError(maybeError));
|
|
6779
|
+
throw maybeError;
|
|
6780
|
+
}
|
|
6781
|
+
};
|
|
6782
|
+
const uploadAssetToS3 = async (asset, fileBuffer, {
|
|
6783
|
+
signedUpload
|
|
6784
|
+
}) => {
|
|
6785
|
+
if (!signedUpload?.id || !signedUpload?.post_url || !signedUpload?.fields) {
|
|
6786
|
+
throw new Error("Invalid signed upload!");
|
|
6787
|
+
}
|
|
6788
|
+
const formData = new FormData();
|
|
6789
|
+
for (const [key, value] of Object.entries(signedUpload.fields)) {
|
|
6790
|
+
formData.append(key, value);
|
|
6791
|
+
}
|
|
6792
|
+
const contentType = signedUpload.fields["Content-Type"] || "application/octet-stream";
|
|
6793
|
+
formData.append("file", new File([Buffer.from(fileBuffer)], asset.short_filename, { type: contentType }));
|
|
6794
|
+
const response = await fetch(signedUpload.post_url, {
|
|
6795
|
+
method: "POST",
|
|
6796
|
+
body: formData
|
|
6797
|
+
});
|
|
6798
|
+
if (!response.ok) {
|
|
6799
|
+
handleAPIError("push_asset_upload", new Error("Failed to upload asset to storage"));
|
|
6800
|
+
return;
|
|
6801
|
+
}
|
|
6802
|
+
return response;
|
|
6803
|
+
};
|
|
6804
|
+
const finishAssetUpload = async (assetId, {
|
|
6805
|
+
spaceId
|
|
6806
|
+
}) => {
|
|
6807
|
+
try {
|
|
6808
|
+
const client = getMapiClient();
|
|
6809
|
+
await client.assets.finalize({
|
|
6810
|
+
path: {
|
|
6811
|
+
space_id: spaceId,
|
|
6812
|
+
signed_response_object_id: String(assetId)
|
|
6813
|
+
},
|
|
6814
|
+
throwOnError: true
|
|
6815
|
+
});
|
|
6816
|
+
const { data } = await client.assets.get({
|
|
6817
|
+
path: {
|
|
6818
|
+
space_id: spaceId,
|
|
6819
|
+
asset_id: assetId
|
|
6820
|
+
},
|
|
6821
|
+
throwOnError: true
|
|
6822
|
+
});
|
|
6823
|
+
return data;
|
|
6824
|
+
} catch (maybeError) {
|
|
6825
|
+
handleAPIError("push_asset_finish", toError(maybeError));
|
|
6826
|
+
throw maybeError;
|
|
6827
|
+
}
|
|
6828
|
+
};
|
|
6829
|
+
const uploadAsset = async (asset, fileBuffer, { spaceId }) => {
|
|
6830
|
+
const signed = await requestAssetUpload(asset, {
|
|
6831
|
+
spaceId
|
|
6832
|
+
});
|
|
6833
|
+
const uploadResponse = await uploadAssetToS3(asset, fileBuffer, {
|
|
6834
|
+
signedUpload: signed
|
|
6835
|
+
});
|
|
6836
|
+
if (!uploadResponse?.ok) {
|
|
6837
|
+
throw new Error("Error uploading asset to S3!");
|
|
6838
|
+
}
|
|
6839
|
+
return finishAssetUpload(Number(signed.id), {
|
|
6840
|
+
spaceId
|
|
6841
|
+
});
|
|
6842
|
+
};
|
|
6843
|
+
const sha256 = (data) => {
|
|
6844
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
6845
|
+
return createHash("sha256").update(buffer).digest("hex");
|
|
6846
|
+
};
|
|
6847
|
+
const downloadAssetFile = async (asset, options) => {
|
|
6848
|
+
let url = asset.filename;
|
|
6849
|
+
if (asset.is_private) {
|
|
6850
|
+
if (!options.assetToken) {
|
|
6851
|
+
throw new Error(`Asset ${asset.filename} is private but no asset token was provided. Use --asset-token to provide a token.`);
|
|
6852
|
+
}
|
|
6853
|
+
url = await getSignedAssetUrl(asset.filename, options.assetToken, options.region);
|
|
6854
|
+
}
|
|
6855
|
+
return downloadFile(url);
|
|
6856
|
+
};
|
|
6857
|
+
const updateAsset = async (asset, fileBuffer, {
|
|
6858
|
+
spaceId
|
|
6859
|
+
}) => {
|
|
6860
|
+
try {
|
|
6861
|
+
const assetWithNewFilename = { ...asset };
|
|
6862
|
+
if (fileBuffer) {
|
|
6863
|
+
const uploadedAsset = await uploadAsset({
|
|
6864
|
+
id: asset.id,
|
|
6865
|
+
asset_folder_id: asset.asset_folder_id,
|
|
6866
|
+
short_filename: asset.short_filename || basename(asset.filename)
|
|
6867
|
+
}, fileBuffer, { spaceId });
|
|
6868
|
+
assetWithNewFilename.filename = uploadedAsset.filename;
|
|
6869
|
+
assetWithNewFilename.short_filename = uploadedAsset.short_filename;
|
|
6870
|
+
}
|
|
6871
|
+
const client = getMapiClient();
|
|
6872
|
+
await client.assets.update({
|
|
6873
|
+
path: {
|
|
6874
|
+
space_id: spaceId,
|
|
6875
|
+
asset_id: assetWithNewFilename.id
|
|
6876
|
+
},
|
|
6877
|
+
body: {
|
|
6878
|
+
asset: assetWithNewFilename
|
|
6879
|
+
},
|
|
6880
|
+
throwOnError: true
|
|
6881
|
+
});
|
|
6882
|
+
return assetWithNewFilename;
|
|
6883
|
+
} catch (maybeError) {
|
|
6884
|
+
handleAPIError("push_asset_update", toError(maybeError));
|
|
6885
|
+
throw maybeError;
|
|
6886
|
+
}
|
|
6887
|
+
};
|
|
6888
|
+
const createAsset = async (asset, fileBuffer, { spaceId }) => {
|
|
6889
|
+
const createdAsset = await uploadAsset({
|
|
6890
|
+
asset_folder_id: asset.asset_folder_id,
|
|
6891
|
+
short_filename: asset.short_filename,
|
|
6892
|
+
alt: asset.alt,
|
|
6893
|
+
title: asset.title,
|
|
6894
|
+
copyright: asset.copyright,
|
|
6895
|
+
source: asset.source,
|
|
6896
|
+
is_private: asset.is_private
|
|
6897
|
+
}, fileBuffer, { spaceId });
|
|
6898
|
+
const hasUpdatableMetadata = Boolean(
|
|
6899
|
+
asset.alt || asset.title || asset.copyright || asset.source || asset.is_private || asset.meta_data && Object.keys(asset.meta_data).length > 0
|
|
6900
|
+
);
|
|
6901
|
+
if (hasUpdatableMetadata) {
|
|
6902
|
+
const updatedAsset = await updateAsset({
|
|
6903
|
+
...asset,
|
|
6904
|
+
id: createdAsset.id,
|
|
6905
|
+
filename: createdAsset.filename
|
|
6906
|
+
}, null, {
|
|
6907
|
+
spaceId
|
|
6908
|
+
});
|
|
6909
|
+
if (!updatedAsset) {
|
|
6910
|
+
throw new Error("Updating the created asset failed!");
|
|
6911
|
+
}
|
|
6912
|
+
return updatedAsset;
|
|
6913
|
+
}
|
|
6914
|
+
return createdAsset;
|
|
6915
|
+
};
|
|
6916
|
+
|
|
6917
|
+
const parseAssetData = (raw) => {
|
|
6918
|
+
if (!raw) {
|
|
6919
|
+
return {};
|
|
6920
|
+
}
|
|
6921
|
+
try {
|
|
6922
|
+
const parsed = JSON.parse(raw);
|
|
6923
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
6924
|
+
throw new Error("Asset data must be a JSON object.");
|
|
6925
|
+
}
|
|
6926
|
+
return parsed;
|
|
6927
|
+
} catch (maybeError) {
|
|
6928
|
+
throw new Error(`Invalid --data JSON: ${toError(maybeError).message}`);
|
|
6929
|
+
}
|
|
6930
|
+
};
|
|
6931
|
+
const getSidecarFilename = (assetBinaryPath) => {
|
|
6932
|
+
return join(dirname$1(assetBinaryPath), `${basename(assetBinaryPath, extname(assetBinaryPath))}.json`);
|
|
6933
|
+
};
|
|
6934
|
+
const loadSidecarAssetData = async (assetBinaryPath) => {
|
|
6935
|
+
const sidecarPath = getSidecarFilename(assetBinaryPath);
|
|
6936
|
+
try {
|
|
6937
|
+
const sidecarRaw = await readFile$1(sidecarPath, "utf8");
|
|
6938
|
+
try {
|
|
6939
|
+
return parseAssetData(sidecarRaw);
|
|
6940
|
+
} catch (maybeError) {
|
|
6941
|
+
throw new Error(`Invalid sidecar JSON: ${toError(maybeError).message}`);
|
|
6942
|
+
}
|
|
6943
|
+
} catch (maybeError) {
|
|
6944
|
+
const error = toError(maybeError);
|
|
6945
|
+
if (error.code === "ENOENT") {
|
|
6946
|
+
return {};
|
|
6947
|
+
}
|
|
6948
|
+
throw new Error(`Failed to read sidecar asset data: ${error.message}`);
|
|
6949
|
+
}
|
|
6950
|
+
};
|
|
6951
|
+
const isRemoteSource = (assetBinaryPath) => {
|
|
6952
|
+
try {
|
|
6953
|
+
const url = new URL(assetBinaryPath);
|
|
6954
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
6955
|
+
} catch {
|
|
6956
|
+
return false;
|
|
6957
|
+
}
|
|
6958
|
+
};
|
|
6959
|
+
const isValidManifestEntry = (entry) => Boolean(typeof entry.old_id === "number" && typeof entry.new_id === "number" && entry.old_filename && entry.new_filename);
|
|
6960
|
+
const loadAssetMap = async (manifestFile) => {
|
|
6961
|
+
const manifest = await loadManifest(manifestFile);
|
|
6962
|
+
return new Map([
|
|
6963
|
+
...manifest.filter(isValidManifestEntry).map((e) => [
|
|
6964
|
+
Number(e.old_id),
|
|
6965
|
+
{
|
|
6966
|
+
old: { id: Number(e.old_id), filename: e.old_filename || "" },
|
|
6967
|
+
new: { id: Number(e.new_id), filename: e.new_filename || "" }
|
|
6968
|
+
}
|
|
6969
|
+
])
|
|
6970
|
+
]);
|
|
6971
|
+
};
|
|
6972
|
+
const loadAssetFolderMap = async (manifestFile) => {
|
|
6973
|
+
const manifest = await loadManifest(manifestFile);
|
|
6974
|
+
return new Map(manifest.map((e) => [Number(e.old_id), Number(e.new_id)]));
|
|
6975
|
+
};
|
|
6976
|
+
const getAssetNameAndExt = (asset) => {
|
|
6977
|
+
const filename = asset.short_filename || (asset.filename ? basename(asset.filename) : void 0);
|
|
6978
|
+
if (!filename) {
|
|
6979
|
+
throw new Error(`Filename for asset with id ${asset.id} could not be determined!`);
|
|
6980
|
+
}
|
|
6981
|
+
const ext = extname(filename);
|
|
6982
|
+
const name = sanitizeFilename(filename.replace(ext, ""));
|
|
6983
|
+
return { name, ext };
|
|
6984
|
+
};
|
|
6985
|
+
const getAssetFilename = (asset) => {
|
|
6986
|
+
const { name } = getAssetNameAndExt(asset);
|
|
6987
|
+
return `${name}_${asset.id}.json`;
|
|
6988
|
+
};
|
|
6989
|
+
const getAssetBinaryFilename = (asset) => {
|
|
6990
|
+
const { name, ext } = getAssetNameAndExt(asset);
|
|
6991
|
+
return `${name}_${asset.id}${ext}`;
|
|
6992
|
+
};
|
|
6993
|
+
const getFolderFilename = (folder) => {
|
|
6994
|
+
const sanitizedName = sanitizeFilename(folder.name || "");
|
|
6995
|
+
const baseName = sanitizedName || folder.uuid;
|
|
6996
|
+
return `${baseName}_${folder.uuid}.json`;
|
|
6997
|
+
};
|
|
6998
|
+
|
|
6999
|
+
const apiConcurrencyLock$1 = new Sema(12);
|
|
7000
|
+
const fetchAssetsStream = ({
|
|
7001
|
+
spaceId,
|
|
7002
|
+
params = {},
|
|
7003
|
+
setTotalAssets,
|
|
7004
|
+
setTotalPages,
|
|
7005
|
+
onIncrement,
|
|
7006
|
+
onPageSuccess,
|
|
7007
|
+
onPageError
|
|
7008
|
+
}) => {
|
|
7009
|
+
const listGenerator = async function* assetListIterator() {
|
|
7010
|
+
let perPage = 100;
|
|
7011
|
+
let page = 1;
|
|
7012
|
+
let totalPages = 1;
|
|
7013
|
+
setTotalPages?.(totalPages);
|
|
7014
|
+
while (page <= totalPages) {
|
|
7015
|
+
try {
|
|
7016
|
+
const result = await fetchAssets({
|
|
7017
|
+
spaceId,
|
|
7018
|
+
params: {
|
|
7019
|
+
...params,
|
|
7020
|
+
per_page: perPage,
|
|
7021
|
+
page
|
|
7022
|
+
}
|
|
7023
|
+
});
|
|
7024
|
+
const { headers, assets } = result;
|
|
7025
|
+
const total = Number(headers.get("Total"));
|
|
7026
|
+
perPage = Number(headers.get("Per-Page")) || perPage;
|
|
7027
|
+
totalPages = Math.max(1, Math.ceil(total / perPage));
|
|
7028
|
+
setTotalAssets?.(total);
|
|
7029
|
+
setTotalPages?.(totalPages);
|
|
7030
|
+
onPageSuccess?.(page, totalPages);
|
|
7031
|
+
for (const asset of assets) {
|
|
7032
|
+
yield asset;
|
|
7033
|
+
}
|
|
7034
|
+
page += 1;
|
|
7035
|
+
} catch (maybeError) {
|
|
7036
|
+
onPageError?.(toError(maybeError), page, totalPages);
|
|
7037
|
+
break;
|
|
7038
|
+
} finally {
|
|
7039
|
+
onIncrement?.();
|
|
7040
|
+
}
|
|
7041
|
+
}
|
|
7042
|
+
};
|
|
7043
|
+
return Readable.from(listGenerator());
|
|
7044
|
+
};
|
|
7045
|
+
const downloadAssetStream = ({
|
|
7046
|
+
assetToken,
|
|
7047
|
+
region,
|
|
7048
|
+
onIncrement,
|
|
7049
|
+
onAssetSuccess,
|
|
7050
|
+
onAssetError
|
|
7051
|
+
}) => {
|
|
7052
|
+
const processing = /* @__PURE__ */ new Set();
|
|
7053
|
+
return new Transform({
|
|
7054
|
+
objectMode: true,
|
|
7055
|
+
async transform(asset, _encoding, callback) {
|
|
7056
|
+
await apiConcurrencyLock$1.acquire();
|
|
7057
|
+
const task = downloadAssetFile(asset, { assetToken, region }).then((fileBuffer) => {
|
|
7058
|
+
if (!fileBuffer) {
|
|
7059
|
+
throw new Error("Invalid asset file!");
|
|
7060
|
+
}
|
|
7061
|
+
onAssetSuccess?.(asset);
|
|
7062
|
+
this.push({ asset, fileBuffer });
|
|
7063
|
+
}).catch((maybeError) => {
|
|
7064
|
+
onAssetError?.(toError(maybeError), asset);
|
|
7065
|
+
}).finally(() => {
|
|
7066
|
+
onIncrement?.();
|
|
7067
|
+
apiConcurrencyLock$1.release();
|
|
7068
|
+
processing.delete(task);
|
|
7069
|
+
});
|
|
7070
|
+
processing.add(task);
|
|
7071
|
+
callback();
|
|
7072
|
+
},
|
|
7073
|
+
flush(callback) {
|
|
7074
|
+
Promise.allSettled(processing).finally(() => callback());
|
|
7075
|
+
}
|
|
7076
|
+
});
|
|
7077
|
+
};
|
|
7078
|
+
const makeWriteAssetFSTransport = ({ directoryPath }) => async (asset, fileBuffer) => {
|
|
7079
|
+
const assetBinaryPath = join(directoryPath, getAssetBinaryFilename(asset));
|
|
7080
|
+
const assetPath = join(directoryPath, getAssetFilename(asset));
|
|
7081
|
+
await saveToFile(assetBinaryPath, Buffer.from(fileBuffer));
|
|
7082
|
+
await saveToFile(assetPath, JSON.stringify(asset, null, 2));
|
|
7083
|
+
return asset;
|
|
7084
|
+
};
|
|
7085
|
+
const writeAssetStream = ({
|
|
7086
|
+
writeAsset,
|
|
7087
|
+
onIncrement,
|
|
7088
|
+
onAssetSuccess,
|
|
7089
|
+
onAssetError
|
|
7090
|
+
}) => {
|
|
7091
|
+
const processing = /* @__PURE__ */ new Set();
|
|
7092
|
+
return new Writable({
|
|
7093
|
+
objectMode: true,
|
|
7094
|
+
async write(payload, _encoding, callback) {
|
|
7095
|
+
await apiConcurrencyLock$1.acquire();
|
|
7096
|
+
const task = (async () => {
|
|
7097
|
+
try {
|
|
7098
|
+
await writeAsset(payload.asset, payload.fileBuffer);
|
|
7099
|
+
onAssetSuccess?.(payload.asset);
|
|
7100
|
+
} catch (maybeError) {
|
|
7101
|
+
onAssetError?.(toError(maybeError), payload.asset);
|
|
7102
|
+
}
|
|
7103
|
+
})();
|
|
7104
|
+
processing.add(task);
|
|
7105
|
+
task.finally(() => {
|
|
7106
|
+
onIncrement?.();
|
|
7107
|
+
apiConcurrencyLock$1.release();
|
|
7108
|
+
processing.delete(task);
|
|
7109
|
+
});
|
|
7110
|
+
callback();
|
|
7111
|
+
},
|
|
7112
|
+
final(callback) {
|
|
7113
|
+
Promise.all(processing).finally(() => callback());
|
|
7114
|
+
}
|
|
7115
|
+
});
|
|
7116
|
+
};
|
|
7117
|
+
const fetchAssetFoldersStream = ({
|
|
7118
|
+
spaceId,
|
|
7119
|
+
setTotalFolders,
|
|
7120
|
+
onSuccess,
|
|
7121
|
+
onError
|
|
7122
|
+
}) => {
|
|
7123
|
+
const listGenerator = async function* folderListIterator() {
|
|
7124
|
+
try {
|
|
7125
|
+
const result = await fetchAssetFolders({ spaceId });
|
|
7126
|
+
const { asset_folders } = result;
|
|
7127
|
+
const total = asset_folders.length;
|
|
7128
|
+
setTotalFolders?.(total);
|
|
7129
|
+
onSuccess?.(asset_folders);
|
|
7130
|
+
for (const folder of asset_folders) {
|
|
7131
|
+
yield folder;
|
|
7132
|
+
}
|
|
7133
|
+
} catch (maybeError) {
|
|
7134
|
+
onError?.(toError(maybeError));
|
|
7135
|
+
}
|
|
7136
|
+
};
|
|
7137
|
+
return Readable.from(listGenerator());
|
|
7138
|
+
};
|
|
7139
|
+
const makeWriteAssetFolderFSTransport = ({ directoryPath }) => async (folder) => {
|
|
7140
|
+
const filename = getFolderFilename(folder);
|
|
7141
|
+
await saveToFile(join(directoryPath, "folders", filename), JSON.stringify(folder, null, 2));
|
|
7142
|
+
return folder;
|
|
7143
|
+
};
|
|
7144
|
+
const writeAssetFolderStream = ({
|
|
7145
|
+
writeAssetFolder,
|
|
7146
|
+
onIncrement,
|
|
7147
|
+
onFolderSuccess,
|
|
7148
|
+
onFolderError
|
|
7149
|
+
}) => {
|
|
7150
|
+
const processing = /* @__PURE__ */ new Set();
|
|
7151
|
+
return new Writable({
|
|
7152
|
+
objectMode: true,
|
|
7153
|
+
async write(folder, _encoding, callback) {
|
|
7154
|
+
await apiConcurrencyLock$1.acquire();
|
|
7155
|
+
const task = (async () => {
|
|
7156
|
+
try {
|
|
7157
|
+
await writeAssetFolder(folder);
|
|
7158
|
+
onFolderSuccess?.(folder);
|
|
7159
|
+
} catch (maybeError) {
|
|
7160
|
+
onFolderError?.(toError(maybeError), folder);
|
|
7161
|
+
}
|
|
7162
|
+
})();
|
|
7163
|
+
processing.add(task);
|
|
7164
|
+
task.finally(() => {
|
|
7165
|
+
onIncrement?.();
|
|
7166
|
+
apiConcurrencyLock$1.release();
|
|
7167
|
+
processing.delete(task);
|
|
7168
|
+
});
|
|
7169
|
+
callback();
|
|
7170
|
+
},
|
|
7171
|
+
final(callback) {
|
|
7172
|
+
Promise.all(processing).finally(() => callback());
|
|
7173
|
+
}
|
|
7174
|
+
});
|
|
7175
|
+
};
|
|
7176
|
+
const readLocalAssetFoldersStream = ({
|
|
7177
|
+
directoryPath,
|
|
7178
|
+
setTotalFolders,
|
|
7179
|
+
onFolderError
|
|
7180
|
+
}) => {
|
|
7181
|
+
const iterator = async function* readFolders() {
|
|
7182
|
+
try {
|
|
7183
|
+
const files = await readdir(directoryPath);
|
|
7184
|
+
const jsonFiles = new Set(files.filter((file) => file.endsWith(".json")));
|
|
7185
|
+
setTotalFolders?.(jsonFiles.size);
|
|
7186
|
+
const processed = /* @__PURE__ */ new Set();
|
|
7187
|
+
let maxIterations = jsonFiles.size * jsonFiles.size;
|
|
7188
|
+
while (jsonFiles.size > 0 && maxIterations-- > 0) {
|
|
7189
|
+
for (const file of jsonFiles) {
|
|
7190
|
+
try {
|
|
7191
|
+
const filePath = join(directoryPath, file);
|
|
7192
|
+
const content = await readFile$1(filePath, "utf8");
|
|
7193
|
+
const folder = JSON.parse(content);
|
|
7194
|
+
jsonFiles.delete(file);
|
|
7195
|
+
if (!folder.parent_id || processed.has(folder.parent_id)) {
|
|
7196
|
+
processed.add(folder.id);
|
|
7197
|
+
yield {
|
|
7198
|
+
folder,
|
|
7199
|
+
context: {
|
|
7200
|
+
localFilePath: filePath
|
|
7201
|
+
}
|
|
7202
|
+
};
|
|
7203
|
+
} else {
|
|
7204
|
+
jsonFiles.add(file);
|
|
7205
|
+
}
|
|
7206
|
+
} catch (maybeError) {
|
|
7207
|
+
onFolderError?.(toError(maybeError));
|
|
7208
|
+
}
|
|
7209
|
+
}
|
|
7210
|
+
}
|
|
7211
|
+
if (jsonFiles.size > 0) {
|
|
7212
|
+
onFolderError?.(new Error(`Unable to resolve folder dependencies for: ${[...jsonFiles].join(", ")}`));
|
|
7213
|
+
}
|
|
7214
|
+
} catch (maybeError) {
|
|
7215
|
+
const error = toError(maybeError);
|
|
7216
|
+
if ("code" in error && error.code === "ENOENT") {
|
|
7217
|
+
return;
|
|
7218
|
+
}
|
|
7219
|
+
onFolderError?.(error);
|
|
7220
|
+
}
|
|
7221
|
+
};
|
|
7222
|
+
return Readable.from(iterator());
|
|
7223
|
+
};
|
|
7224
|
+
const makeCreateAssetFolderAPITransport = ({ spaceId }) => (folder) => createAssetFolder({
|
|
7225
|
+
name: folder.name,
|
|
7226
|
+
parent_id: folder.parent_id ?? void 0
|
|
7227
|
+
}, {
|
|
7228
|
+
spaceId
|
|
7229
|
+
});
|
|
7230
|
+
const makeUpdateAssetFolderAPITransport = ({ spaceId }) => (folder) => updateAssetFolder(folder, { spaceId });
|
|
7231
|
+
const makeGetAssetFolderAPITransport = ({ spaceId }) => async (folderId) => {
|
|
7232
|
+
const { data, response } = await getMapiClient().assetFolders.get({
|
|
7233
|
+
path: {
|
|
7234
|
+
asset_folder_id: folderId,
|
|
7235
|
+
space_id: spaceId
|
|
7236
|
+
}
|
|
7237
|
+
});
|
|
7238
|
+
if (!response.ok && response.status !== 404) {
|
|
7239
|
+
handleAPIError("pull_asset_folder", new FetchError(response.statusText, response));
|
|
7240
|
+
}
|
|
7241
|
+
return data?.asset_folder;
|
|
7242
|
+
};
|
|
7243
|
+
const makeCleanupAssetFolderFSTransport = () => async ({ localFilePath }) => await unlink(localFilePath);
|
|
7244
|
+
const upsertAssetFolderStream = ({
|
|
7245
|
+
transports,
|
|
7246
|
+
maps,
|
|
7247
|
+
onIncrement,
|
|
7248
|
+
onFolderSuccess,
|
|
7249
|
+
onFolderError
|
|
7250
|
+
}) => {
|
|
7251
|
+
return new Writable({
|
|
7252
|
+
objectMode: true,
|
|
7253
|
+
async write({ folder, context }, _encoding, callback) {
|
|
7254
|
+
try {
|
|
7255
|
+
const remoteParentId = folder.parent_id && (maps.assetFolders.get(folder.parent_id) || folder.parent_id);
|
|
7256
|
+
const remoteFolderId = maps.assetFolders.get(folder.id) || folder.id;
|
|
7257
|
+
const upsertFolder = {
|
|
7258
|
+
...folder,
|
|
7259
|
+
id: remoteFolderId,
|
|
7260
|
+
parent_id: remoteParentId
|
|
7261
|
+
};
|
|
7262
|
+
const existingRemoteFolder = await transports.getAssetFolder(remoteFolderId);
|
|
7263
|
+
const newRemoteFolder = existingRemoteFolder ? await transports.updateAssetFolder({ ...upsertFolder, parent_id: remoteParentId !== null ? remoteParentId : void 0 }) : await transports.createAssetFolder(upsertFolder);
|
|
7264
|
+
if (!maps.assetFolders.get(folder.id)) {
|
|
7265
|
+
await transports.appendAssetFolderManifest(folder, newRemoteFolder);
|
|
7266
|
+
}
|
|
7267
|
+
await transports.cleanupAssetFolder?.({ localFilePath: context.localFilePath });
|
|
7268
|
+
onFolderSuccess?.(folder, newRemoteFolder);
|
|
7269
|
+
} catch (maybeError) {
|
|
7270
|
+
onFolderError?.(toError(maybeError), folder);
|
|
7271
|
+
} finally {
|
|
7272
|
+
onIncrement?.();
|
|
7273
|
+
callback();
|
|
7274
|
+
}
|
|
7275
|
+
}
|
|
7276
|
+
});
|
|
7277
|
+
};
|
|
7278
|
+
const readLocalAssetsStream = ({
|
|
7279
|
+
directoryPath,
|
|
7280
|
+
setTotalAssets,
|
|
7281
|
+
onAssetError
|
|
7282
|
+
}) => {
|
|
7283
|
+
const iterator = async function* readAssets() {
|
|
7284
|
+
try {
|
|
7285
|
+
const files = await readdir(directoryPath);
|
|
7286
|
+
const metadataFiles = files.filter((file) => file.endsWith(".json") && file !== "manifest.jsonl");
|
|
7287
|
+
setTotalAssets?.(metadataFiles.length);
|
|
7288
|
+
for (const file of metadataFiles) {
|
|
7289
|
+
const filePath = join(directoryPath, file);
|
|
7290
|
+
try {
|
|
7291
|
+
const statResult = await stat(filePath);
|
|
7292
|
+
if (!statResult.isFile()) {
|
|
7293
|
+
continue;
|
|
7294
|
+
}
|
|
7295
|
+
const metadataContent = await readFile$1(filePath, "utf8");
|
|
7296
|
+
const assetRaw = JSON.parse(metadataContent);
|
|
7297
|
+
const asset = {
|
|
7298
|
+
...assetRaw,
|
|
7299
|
+
short_filename: assetRaw.short_filename || basename(assetRaw.filename)
|
|
7300
|
+
};
|
|
7301
|
+
const baseName = parse(file).name;
|
|
7302
|
+
const extFromMetadata = extname(asset.short_filename || asset.filename) || "";
|
|
7303
|
+
const assetBinaryPath = join(directoryPath, `${baseName}${extFromMetadata}`);
|
|
7304
|
+
const fileBuffer = await readFile$1(assetBinaryPath);
|
|
7305
|
+
yield {
|
|
7306
|
+
asset,
|
|
7307
|
+
context: {
|
|
7308
|
+
fileBuffer,
|
|
7309
|
+
assetBinaryPath,
|
|
7310
|
+
assetPath: filePath
|
|
7311
|
+
}
|
|
7312
|
+
};
|
|
7313
|
+
} catch (maybeError) {
|
|
7314
|
+
onAssetError?.(toError(maybeError));
|
|
7315
|
+
}
|
|
7316
|
+
}
|
|
7317
|
+
} catch (maybeError) {
|
|
7318
|
+
onAssetError?.(toError(maybeError));
|
|
7319
|
+
}
|
|
7320
|
+
};
|
|
7321
|
+
return Readable.from(iterator());
|
|
7322
|
+
};
|
|
7323
|
+
const readSingleAssetStream = ({
|
|
7324
|
+
asset,
|
|
7325
|
+
assetBinaryPath,
|
|
7326
|
+
onAssetError
|
|
7327
|
+
}) => {
|
|
7328
|
+
const iterator = async function* readSingleAsset() {
|
|
7329
|
+
try {
|
|
7330
|
+
if (!isRemoteSource(assetBinaryPath) && !await fileExists(assetBinaryPath)) {
|
|
7331
|
+
throw new Error("Asset path must point to a file.");
|
|
7332
|
+
}
|
|
7333
|
+
const fileBuffer = isRemoteSource(assetBinaryPath) ? await downloadFile(assetBinaryPath) : await readFile$1(assetBinaryPath);
|
|
7334
|
+
yield {
|
|
7335
|
+
asset,
|
|
7336
|
+
context: {
|
|
7337
|
+
fileBuffer,
|
|
7338
|
+
assetBinaryPath
|
|
7339
|
+
}
|
|
7340
|
+
};
|
|
7341
|
+
} catch (maybeError) {
|
|
7342
|
+
onAssetError?.(toError(maybeError));
|
|
7343
|
+
}
|
|
7344
|
+
};
|
|
7345
|
+
return Readable.from(iterator());
|
|
7346
|
+
};
|
|
7347
|
+
const makeCreateAssetAPITransport = ({ spaceId }) => (asset, fileBuffer) => createAsset(asset, fileBuffer, { spaceId });
|
|
7348
|
+
const makeUpdateAssetAPITransport = ({
|
|
7349
|
+
spaceId
|
|
7350
|
+
}) => (asset, fileBuffer) => updateAsset(asset, fileBuffer, {
|
|
7351
|
+
spaceId
|
|
7352
|
+
});
|
|
7353
|
+
const makeAppendAssetManifestFSTransport = ({ manifestFile }) => async (localAsset, remoteAsset) => {
|
|
7354
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7355
|
+
await appendToFile(manifestFile, JSON.stringify({
|
|
7356
|
+
old_id: localAsset.id,
|
|
7357
|
+
new_id: remoteAsset.id,
|
|
7358
|
+
old_filename: localAsset.filename,
|
|
7359
|
+
new_filename: remoteAsset.filename,
|
|
7360
|
+
created_at: createdAt
|
|
7361
|
+
}));
|
|
7362
|
+
};
|
|
7363
|
+
const makeAppendAssetFolderManifestFSTransport = ({ manifestFile }) => async (localFolder, remoteFolder) => {
|
|
7364
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7365
|
+
await appendToFile(manifestFile, JSON.stringify({
|
|
7366
|
+
old_id: localFolder.id,
|
|
7367
|
+
new_id: remoteFolder.id,
|
|
7368
|
+
created_at: createdAt
|
|
7369
|
+
}));
|
|
7370
|
+
};
|
|
7371
|
+
const makeGetAssetAPITransport = ({ spaceId }) => async (assetId) => {
|
|
7372
|
+
const { data, response } = await getMapiClient().assets.get({
|
|
7373
|
+
path: {
|
|
7374
|
+
space_id: spaceId,
|
|
7375
|
+
asset_id: assetId
|
|
7376
|
+
}
|
|
7377
|
+
});
|
|
7378
|
+
if (!response.ok && response.status !== 404) {
|
|
7379
|
+
handleAPIError("pull_asset", new FetchError(response.statusText, response));
|
|
7380
|
+
}
|
|
7381
|
+
if (data?.deleted_at) {
|
|
7382
|
+
return void 0;
|
|
7383
|
+
}
|
|
7384
|
+
return data;
|
|
7385
|
+
};
|
|
7386
|
+
const saveDelete = async (filePath) => {
|
|
7387
|
+
if (await fileExists(filePath)) {
|
|
7388
|
+
await unlink(filePath);
|
|
7389
|
+
}
|
|
7390
|
+
};
|
|
7391
|
+
const makeCleanupAssetFSTransport = () => async ({ assetBinaryPath, assetPath }) => {
|
|
7392
|
+
const assetOrSidecarPath = assetPath || getSidecarFilename(assetBinaryPath);
|
|
7393
|
+
await Promise.all([
|
|
7394
|
+
assetBinaryPath && saveDelete(assetBinaryPath),
|
|
7395
|
+
assetOrSidecarPath && saveDelete(assetOrSidecarPath)
|
|
7396
|
+
]);
|
|
7397
|
+
};
|
|
7398
|
+
const hasId = (a) => {
|
|
7399
|
+
return !!a && typeof a === "object" && "id" in a && typeof a.id === "number";
|
|
7400
|
+
};
|
|
7401
|
+
const hasShortFilename = (a) => {
|
|
7402
|
+
return !!a && typeof a === "object" && "short_filename" in a && typeof a.short_filename === "string";
|
|
7403
|
+
};
|
|
7404
|
+
const isDataUnchanged = (localAsset, remoteAsset) => {
|
|
7405
|
+
if (localAsset.asset_folder_id !== remoteAsset.asset_folder_id) {
|
|
7406
|
+
return false;
|
|
7407
|
+
}
|
|
7408
|
+
if (localAsset.alt !== remoteAsset.alt || localAsset.title !== remoteAsset.title || localAsset.copyright !== remoteAsset.copyright || localAsset.source !== remoteAsset.source || localAsset.is_private !== remoteAsset.is_private) {
|
|
7409
|
+
return false;
|
|
7410
|
+
}
|
|
7411
|
+
const localAssetMetadataEntries = Object.entries(localAsset.meta_data || {});
|
|
7412
|
+
const remoteAssetMetadataEntries = Object.entries(remoteAsset.meta_data || {});
|
|
7413
|
+
if (localAssetMetadataEntries.length !== remoteAssetMetadataEntries.length) {
|
|
7414
|
+
return false;
|
|
7415
|
+
}
|
|
7416
|
+
const hasChanges = localAssetMetadataEntries.some(([k, v]) => remoteAsset.meta_data && remoteAsset.meta_data[k] !== v);
|
|
7417
|
+
return !hasChanges;
|
|
7418
|
+
};
|
|
7419
|
+
const isAssetUnchanged = async (localAsset, remoteAsset, localFileBuffer, downloadAssetFileTransport) => {
|
|
7420
|
+
const remoteFileBuffer = await downloadAssetFileTransport(remoteAsset);
|
|
7421
|
+
const isFileUnchanged = sha256(localFileBuffer) === sha256(remoteFileBuffer);
|
|
7422
|
+
if (!isFileUnchanged) {
|
|
7423
|
+
return false;
|
|
7424
|
+
}
|
|
7425
|
+
return isDataUnchanged(localAsset, remoteAsset);
|
|
7426
|
+
};
|
|
7427
|
+
const makeDownloadAssetFileTransport = ({
|
|
7428
|
+
assetToken,
|
|
7429
|
+
region
|
|
7430
|
+
}) => (asset) => downloadAssetFile(asset, { assetToken, region });
|
|
7431
|
+
const processAsset = async ({
|
|
7432
|
+
localAsset,
|
|
7433
|
+
fileBuffer,
|
|
7434
|
+
assetBinaryPath,
|
|
7435
|
+
assetPath,
|
|
7436
|
+
transports,
|
|
7437
|
+
maps
|
|
7438
|
+
}) => {
|
|
7439
|
+
const remoteFolderId = localAsset.asset_folder_id && (maps.assetFolders.get(localAsset.asset_folder_id) || localAsset.asset_folder_id);
|
|
7440
|
+
const remoteAssetId = hasId(localAsset) ? maps.assets.get(localAsset.id)?.new.id || localAsset.id : void 0;
|
|
7441
|
+
const remoteAsset = remoteAssetId ? await transports.getAsset(remoteAssetId) : null;
|
|
7442
|
+
let newRemoteAsset;
|
|
7443
|
+
let status;
|
|
7444
|
+
if (remoteAsset) {
|
|
7445
|
+
const updatePayload = {
|
|
7446
|
+
...remoteAsset,
|
|
7447
|
+
...localAsset,
|
|
7448
|
+
id: remoteAsset.id,
|
|
7449
|
+
asset_folder_id: remoteFolderId
|
|
7450
|
+
};
|
|
7451
|
+
const canSkip = await isAssetUnchanged(
|
|
7452
|
+
updatePayload,
|
|
7453
|
+
remoteAsset,
|
|
7454
|
+
fileBuffer,
|
|
7455
|
+
transports.downloadAssetFile
|
|
7456
|
+
);
|
|
7457
|
+
if (canSkip) {
|
|
7458
|
+
newRemoteAsset = remoteAsset;
|
|
7459
|
+
status = "skipped";
|
|
7460
|
+
} else {
|
|
7461
|
+
newRemoteAsset = await transports.updateAsset(updatePayload, fileBuffer);
|
|
7462
|
+
status = "updated";
|
|
7463
|
+
}
|
|
7464
|
+
} else if (hasShortFilename(localAsset)) {
|
|
7465
|
+
const createPayload = {
|
|
7466
|
+
...localAsset,
|
|
7467
|
+
asset_folder_id: remoteFolderId
|
|
7468
|
+
};
|
|
7469
|
+
newRemoteAsset = await transports.createAsset(createPayload, fileBuffer);
|
|
7470
|
+
status = "created";
|
|
7471
|
+
} else {
|
|
7472
|
+
throw new Error("Could neither create nor update the asset: Missing ID and Filename");
|
|
7473
|
+
}
|
|
7474
|
+
if (hasId(localAsset)) {
|
|
7475
|
+
await transports.appendAssetManifest(localAsset, newRemoteAsset);
|
|
7476
|
+
}
|
|
7477
|
+
await transports.cleanupAsset?.({ assetBinaryPath, assetPath });
|
|
7478
|
+
return { status, remoteAsset: newRemoteAsset };
|
|
7479
|
+
};
|
|
7480
|
+
const upsertAssetStream = ({
|
|
7481
|
+
transports,
|
|
7482
|
+
maps,
|
|
7483
|
+
onIncrement,
|
|
7484
|
+
onAssetSuccess,
|
|
7485
|
+
onAssetSkipped,
|
|
7486
|
+
onAssetError
|
|
7487
|
+
}) => {
|
|
7488
|
+
const processing = /* @__PURE__ */ new Set();
|
|
7489
|
+
return new Writable({
|
|
7490
|
+
objectMode: true,
|
|
7491
|
+
async write({ asset: localAsset, context }, _encoding, callback) {
|
|
7492
|
+
await apiConcurrencyLock$1.acquire();
|
|
7493
|
+
const task = (async () => {
|
|
7494
|
+
try {
|
|
7495
|
+
const { status, remoteAsset } = await processAsset({
|
|
7496
|
+
localAsset,
|
|
7497
|
+
fileBuffer: context.fileBuffer,
|
|
7498
|
+
assetBinaryPath: context.assetBinaryPath,
|
|
7499
|
+
assetPath: context.assetPath,
|
|
7500
|
+
transports,
|
|
7501
|
+
maps
|
|
7502
|
+
});
|
|
7503
|
+
if (status === "skipped") {
|
|
7504
|
+
onAssetSkipped?.(localAsset, remoteAsset);
|
|
7505
|
+
} else {
|
|
7506
|
+
onAssetSuccess?.(localAsset, remoteAsset);
|
|
7507
|
+
}
|
|
7508
|
+
} catch (maybeError) {
|
|
7509
|
+
onAssetError?.(toError(maybeError), localAsset);
|
|
7510
|
+
}
|
|
7511
|
+
})();
|
|
7512
|
+
processing.add(task);
|
|
7513
|
+
task.finally(() => {
|
|
7514
|
+
onIncrement?.();
|
|
7515
|
+
apiConcurrencyLock$1.release();
|
|
7516
|
+
processing.delete(task);
|
|
7517
|
+
});
|
|
7518
|
+
callback();
|
|
7519
|
+
},
|
|
7520
|
+
final(callback) {
|
|
7521
|
+
Promise.all(processing).finally(() => callback());
|
|
7522
|
+
}
|
|
7523
|
+
});
|
|
7524
|
+
};
|
|
7525
|
+
|
|
7526
|
+
assetsCommand.command("pull").option("-d, --dry-run", "Preview changes without applying them to Storyblok").option("-q, --query <query>", 'Filter assets using Storyblok filter query syntax. Example: --query="search=my-file.jpg&with_tags=tag1,tag2"').option("--asset-token <token>", "Asset token for accessing private assets").description(`Download your space's assets as local files.`).action(async (options, command) => {
|
|
7527
|
+
const ui = getUI();
|
|
7528
|
+
const logger = getLogger();
|
|
7529
|
+
ui.title(`${commands.ASSETS}`, colorPalette.ASSETS, "Pulling assets...");
|
|
7530
|
+
logger.info("Pulling assets started");
|
|
7531
|
+
if (options.dryRun) {
|
|
7532
|
+
ui.warn(`DRY RUN MODE ENABLED: No changes will be made.
|
|
7533
|
+
`);
|
|
7534
|
+
logger.warn("Dry run mode enabled");
|
|
7535
|
+
}
|
|
7536
|
+
const { space, path: basePath, verbose } = command.optsWithGlobals();
|
|
7537
|
+
const assetToken = options.assetToken;
|
|
7538
|
+
const { state } = session();
|
|
7539
|
+
if (!requireAuthentication(state, verbose)) {
|
|
7540
|
+
process.exitCode = 2;
|
|
7541
|
+
return;
|
|
7542
|
+
}
|
|
7543
|
+
if (!space) {
|
|
7544
|
+
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
|
|
7545
|
+
process.exitCode = 2;
|
|
7546
|
+
return;
|
|
7547
|
+
}
|
|
7548
|
+
const { region } = state;
|
|
7549
|
+
const summary = {
|
|
7550
|
+
folderResults: { total: 0, succeeded: 0, failed: 0 },
|
|
7551
|
+
fetchAssetPages: { total: 0, succeeded: 0, failed: 0 },
|
|
7552
|
+
fetchAssets: { total: 0, succeeded: 0, failed: 0 },
|
|
7553
|
+
save: { total: 0, succeeded: 0, failed: 0 }
|
|
7554
|
+
};
|
|
7555
|
+
let fatalError = false;
|
|
7556
|
+
try {
|
|
7557
|
+
const folderProgress = ui.createProgressBar({ title: "Folders...".padEnd(25) });
|
|
7558
|
+
const fetchAssetPagesProgress = ui.createProgressBar({ title: "Fetching Asset Pages...".padEnd(24) });
|
|
7559
|
+
const fetchAssetsProgress = ui.createProgressBar({ title: "Fetching Assets...".padEnd(24) });
|
|
7560
|
+
const saveProgress = ui.createProgressBar({ title: "Saving Assets...".padEnd(24) });
|
|
7561
|
+
await pipeline$1(
|
|
7562
|
+
fetchAssetFoldersStream({
|
|
7563
|
+
spaceId: space,
|
|
7564
|
+
setTotalFolders: (total) => {
|
|
7565
|
+
summary.folderResults.total = total;
|
|
7566
|
+
folderProgress.setTotal(total);
|
|
7567
|
+
},
|
|
7568
|
+
onSuccess: () => {
|
|
7569
|
+
logger.info("Fetched asset folders");
|
|
7570
|
+
},
|
|
7571
|
+
onError: (error) => {
|
|
7572
|
+
summary.folderResults.failed += 1;
|
|
7573
|
+
summary.folderResults.total = summary.folderResults.total || 1;
|
|
7574
|
+
folderProgress.setTotal(summary.folderResults.total);
|
|
7575
|
+
logOnlyError(error);
|
|
7576
|
+
}
|
|
7577
|
+
}),
|
|
7578
|
+
writeAssetFolderStream({
|
|
7579
|
+
writeAssetFolder: options.dryRun ? async (folder) => folder : makeWriteAssetFolderFSTransport({
|
|
7580
|
+
directoryPath: resolveCommandPath(directories.assets, space, basePath)
|
|
7581
|
+
}),
|
|
7582
|
+
onIncrement: () => {
|
|
7583
|
+
folderProgress.increment();
|
|
7584
|
+
},
|
|
7585
|
+
onFolderSuccess: (folder) => {
|
|
7586
|
+
logger.info("Saved folder", { folderId: folder.id });
|
|
7587
|
+
summary.folderResults.succeeded += 1;
|
|
7588
|
+
},
|
|
7589
|
+
onFolderError: (error, folder) => {
|
|
7590
|
+
summary.folderResults.failed += 1;
|
|
7591
|
+
summary.folderResults.total = Math.max(summary.folderResults.total, summary.folderResults.succeeded + summary.folderResults.failed);
|
|
7592
|
+
logOnlyError(error, { folderId: folder.id });
|
|
7593
|
+
}
|
|
7594
|
+
})
|
|
7595
|
+
);
|
|
7596
|
+
await pipeline$1(
|
|
7597
|
+
fetchAssetsStream({
|
|
7598
|
+
spaceId: space,
|
|
7599
|
+
params: options.query ? Object.fromEntries(new URLSearchParams(options.query)) : {},
|
|
7600
|
+
setTotalAssets: (total) => {
|
|
7601
|
+
summary.fetchAssets.total = total;
|
|
7602
|
+
summary.save.total = total;
|
|
7603
|
+
fetchAssetsProgress.setTotal(total);
|
|
7604
|
+
saveProgress.setTotal(total);
|
|
7605
|
+
},
|
|
7606
|
+
setTotalPages: (totalPages) => {
|
|
7607
|
+
summary.fetchAssetPages.total = totalPages;
|
|
7608
|
+
fetchAssetPagesProgress.setTotal(totalPages);
|
|
7609
|
+
},
|
|
7610
|
+
onIncrement: () => {
|
|
7611
|
+
fetchAssetPagesProgress.increment();
|
|
7612
|
+
},
|
|
7613
|
+
onPageSuccess: (page, totalPages) => {
|
|
7614
|
+
logger.info(`Fetched assets page ${page} of ${totalPages}`);
|
|
7615
|
+
summary.fetchAssetPages.succeeded += 1;
|
|
7616
|
+
},
|
|
7617
|
+
onPageError: (error, page, totalPages) => {
|
|
7618
|
+
summary.fetchAssetPages.failed += 1;
|
|
7619
|
+
logOnlyError(error, { page, totalPages });
|
|
7620
|
+
}
|
|
7621
|
+
}),
|
|
7622
|
+
downloadAssetStream({
|
|
7623
|
+
assetToken,
|
|
7624
|
+
region,
|
|
7625
|
+
onIncrement: () => {
|
|
7626
|
+
fetchAssetsProgress.increment();
|
|
7627
|
+
},
|
|
7628
|
+
onAssetSuccess: (asset) => {
|
|
7629
|
+
logger.info("Fetched asset", { assetId: asset.id });
|
|
7630
|
+
summary.fetchAssets.succeeded += 1;
|
|
7631
|
+
},
|
|
7632
|
+
onAssetError: (error, asset) => {
|
|
7633
|
+
summary.fetchAssets.failed += 1;
|
|
7634
|
+
summary.save.total -= 1;
|
|
7635
|
+
saveProgress.setTotal(summary.save.total);
|
|
7636
|
+
logOnlyError(error, { assetId: asset.id });
|
|
7637
|
+
}
|
|
7638
|
+
}),
|
|
7639
|
+
writeAssetStream({
|
|
7640
|
+
writeAsset: options.dryRun ? async (asset) => asset : makeWriteAssetFSTransport({
|
|
7641
|
+
directoryPath: resolveCommandPath(directories.assets, space, basePath)
|
|
7642
|
+
}),
|
|
7643
|
+
onIncrement: () => {
|
|
7644
|
+
saveProgress.increment();
|
|
7645
|
+
},
|
|
7646
|
+
onAssetSuccess: (asset) => {
|
|
7647
|
+
logger.info("Saved asset", { assetId: asset.id });
|
|
7648
|
+
summary.save.succeeded += 1;
|
|
7649
|
+
},
|
|
7650
|
+
onAssetError: (error, asset) => {
|
|
7651
|
+
summary.save.failed += 1;
|
|
7652
|
+
logOnlyError(error, { assetId: asset.id });
|
|
7653
|
+
}
|
|
7654
|
+
})
|
|
7655
|
+
);
|
|
7656
|
+
} catch (maybeError) {
|
|
7657
|
+
fatalError = true;
|
|
7658
|
+
handleError(toError(maybeError));
|
|
7659
|
+
} finally {
|
|
7660
|
+
logger.info("Pulling assets finished", summary);
|
|
7661
|
+
ui.stopAllProgressBars();
|
|
7662
|
+
const failedAssets = Math.max(summary.fetchAssets.failed, summary.save.failed);
|
|
7663
|
+
const folderSummary = {
|
|
7664
|
+
total: summary.folderResults.total,
|
|
7665
|
+
succeeded: summary.folderResults.succeeded,
|
|
7666
|
+
failed: summary.folderResults.failed
|
|
7667
|
+
};
|
|
7668
|
+
ui.info(`Pull results: ${summary.save.total} assets pulled, ${failedAssets} assets failed`);
|
|
7669
|
+
ui.list([
|
|
7670
|
+
`Folders: ${folderSummary.succeeded}/${folderSummary.total} succeeded, ${folderSummary.failed} failed.`,
|
|
7671
|
+
`Fetching pages: ${summary.fetchAssetPages.succeeded}/${summary.fetchAssetPages.total} succeeded, ${summary.fetchAssetPages.failed} failed.`,
|
|
7672
|
+
`Fetching assets: ${summary.fetchAssets.succeeded}/${summary.fetchAssets.total} succeeded, ${summary.fetchAssets.failed} failed.`,
|
|
7673
|
+
`Saving assets: ${summary.save.succeeded}/${summary.save.total} succeeded, ${summary.save.failed} failed.`
|
|
7674
|
+
]);
|
|
7675
|
+
const reporter = getReporter();
|
|
7676
|
+
reporter.addSummary("folderResults", folderSummary);
|
|
7677
|
+
reporter.addSummary("fetchAssetPagesResults", summary.fetchAssetPages);
|
|
7678
|
+
reporter.addSummary("fetchAssetsResults", summary.fetchAssets);
|
|
7679
|
+
reporter.addSummary("saveResults", summary.save);
|
|
7680
|
+
reporter.finalize();
|
|
7681
|
+
const failedTotal = summary.folderResults.failed + summary.fetchAssetPages.failed + summary.fetchAssets.failed + summary.save.failed;
|
|
7682
|
+
process.exitCode = fatalError ? 2 : failedTotal > 0 ? 1 : 0;
|
|
7683
|
+
}
|
|
7684
|
+
});
|
|
7685
|
+
|
|
7686
|
+
const traverseAndMapBySchema = (data, {
|
|
7687
|
+
schemas,
|
|
7688
|
+
maps,
|
|
7689
|
+
fieldRefMappers: fieldRefMappers2,
|
|
7690
|
+
processedFields,
|
|
7691
|
+
missingSchemas
|
|
7692
|
+
}) => {
|
|
7693
|
+
const schema = schemas[data.component];
|
|
7694
|
+
if (!schema) {
|
|
7695
|
+
missingSchemas.add(data.component);
|
|
7696
|
+
return data;
|
|
7697
|
+
}
|
|
7698
|
+
const dataNew = { ...data };
|
|
7699
|
+
for (const [fieldName, fieldValue] of Object.entries(data)) {
|
|
7700
|
+
const fieldSchema = schema[fieldName.replace(/__i18n__.*/, "")];
|
|
7701
|
+
const fieldType = fieldSchema && typeof fieldSchema === "object" && "type" in fieldSchema && fieldSchema.type;
|
|
7702
|
+
const fieldRefMapper = typeof fieldType === "string" && fieldRefMappers2[fieldType];
|
|
7703
|
+
if (fieldSchema) {
|
|
7704
|
+
processedFields.add(fieldSchema);
|
|
7705
|
+
}
|
|
7706
|
+
if (fieldRefMapper) {
|
|
7707
|
+
dataNew[fieldName] = fieldRefMapper(fieldValue, {
|
|
7708
|
+
schema: fieldSchema,
|
|
7709
|
+
schemas,
|
|
7710
|
+
maps,
|
|
7711
|
+
fieldRefMappers: fieldRefMappers2,
|
|
7712
|
+
processedFields,
|
|
7713
|
+
missingSchemas
|
|
7714
|
+
});
|
|
7715
|
+
}
|
|
7716
|
+
}
|
|
7717
|
+
return dataNew;
|
|
7718
|
+
};
|
|
7719
|
+
const traverseAndMapRichtextDoc = (data, {
|
|
7720
|
+
schemas,
|
|
7721
|
+
maps,
|
|
7722
|
+
fieldRefMappers: fieldRefMappers2,
|
|
7723
|
+
processedFields,
|
|
7724
|
+
missingSchemas
|
|
7725
|
+
}) => {
|
|
7726
|
+
if (Array.isArray(data)) {
|
|
7727
|
+
return data.map((item) => traverseAndMapRichtextDoc(item, {
|
|
7728
|
+
schemas,
|
|
7729
|
+
maps,
|
|
7730
|
+
fieldRefMappers: fieldRefMappers2,
|
|
7731
|
+
processedFields,
|
|
7732
|
+
missingSchemas
|
|
7733
|
+
}));
|
|
7734
|
+
}
|
|
7735
|
+
if (data && typeof data === "object") {
|
|
7736
|
+
if (data.type === "link" && data.attrs.linktype === "story") {
|
|
7737
|
+
return {
|
|
7738
|
+
...data,
|
|
7739
|
+
attrs: {
|
|
7740
|
+
...data.attrs,
|
|
7741
|
+
uuid: maps.stories?.get(data.attrs.uuid) || data.attrs.uuid
|
|
7742
|
+
}
|
|
7743
|
+
};
|
|
7744
|
+
}
|
|
7745
|
+
if (data.type === "blok") {
|
|
7746
|
+
return {
|
|
7747
|
+
...data,
|
|
7748
|
+
attrs: {
|
|
7749
|
+
...data.attrs,
|
|
7750
|
+
body: data.attrs.body.map((d) => traverseAndMapBySchema(d, {
|
|
7751
|
+
schemas,
|
|
7752
|
+
maps,
|
|
7753
|
+
fieldRefMappers: fieldRefMappers2,
|
|
7754
|
+
processedFields,
|
|
7755
|
+
missingSchemas
|
|
7756
|
+
}))
|
|
7757
|
+
}
|
|
7758
|
+
};
|
|
7759
|
+
}
|
|
7760
|
+
const newData = {};
|
|
7761
|
+
for (const [k, value] of Object.entries(data)) {
|
|
7762
|
+
newData[k] = traverseAndMapRichtextDoc(value, {
|
|
7763
|
+
schemas,
|
|
7764
|
+
maps,
|
|
7765
|
+
fieldRefMappers: fieldRefMappers2,
|
|
7766
|
+
processedFields,
|
|
7767
|
+
missingSchemas
|
|
7768
|
+
});
|
|
7769
|
+
}
|
|
7770
|
+
return newData;
|
|
7771
|
+
}
|
|
7772
|
+
return data;
|
|
7773
|
+
};
|
|
7774
|
+
const richtextFieldRefMapper = (data, { schemas, maps, fieldRefMappers: fieldRefMappers2, processedFields, missingSchemas }) => traverseAndMapRichtextDoc(data, {
|
|
7775
|
+
schemas,
|
|
7776
|
+
maps,
|
|
7777
|
+
fieldRefMappers: fieldRefMappers2,
|
|
7778
|
+
processedFields,
|
|
7779
|
+
missingSchemas
|
|
7780
|
+
});
|
|
7781
|
+
const multilinkFieldRefMapper = (data, { maps }) => {
|
|
7782
|
+
if (data.linktype !== "story") {
|
|
7783
|
+
return data;
|
|
7784
|
+
}
|
|
7785
|
+
return {
|
|
7786
|
+
...data,
|
|
7787
|
+
id: maps.stories?.get(data.id) || data.id
|
|
7788
|
+
};
|
|
7789
|
+
};
|
|
7790
|
+
const bloksFieldRefMapper = (data, { schemas, maps, fieldRefMappers: fieldRefMappers2, processedFields, missingSchemas }) => {
|
|
7791
|
+
if (!Array.isArray(data)) {
|
|
7792
|
+
throw new TypeError("Invalid data!");
|
|
7793
|
+
}
|
|
7794
|
+
return data.map((d) => traverseAndMapBySchema(d, {
|
|
7795
|
+
schemas,
|
|
7796
|
+
maps,
|
|
7797
|
+
fieldRefMappers: fieldRefMappers2,
|
|
7798
|
+
processedFields,
|
|
7799
|
+
missingSchemas
|
|
7800
|
+
}));
|
|
7801
|
+
};
|
|
7802
|
+
const assetFieldRefMapper = (data, { maps }) => {
|
|
7803
|
+
const mappedAsset = typeof data.id === "number" ? maps.assets?.get(data.id) : void 0;
|
|
7804
|
+
return {
|
|
7805
|
+
...data,
|
|
7806
|
+
...mappedAsset?.new
|
|
7807
|
+
};
|
|
7808
|
+
};
|
|
7809
|
+
const multiassetFieldRefMapper = (data, options) => {
|
|
7810
|
+
if (!Array.isArray(data)) {
|
|
7811
|
+
throw new TypeError("Invalid data!");
|
|
7812
|
+
}
|
|
7813
|
+
return data.map((d) => assetFieldRefMapper(d, options));
|
|
7814
|
+
};
|
|
7815
|
+
const optionsFieldRefMapper = (data, { schema, maps }) => {
|
|
7816
|
+
if (schema.source !== "internal_stories" || !Array.isArray(data)) {
|
|
7817
|
+
return data;
|
|
7818
|
+
}
|
|
7819
|
+
return data.map((d) => maps.stories?.get(d) || d);
|
|
7820
|
+
};
|
|
7821
|
+
const fieldRefMappers = {
|
|
7822
|
+
asset: assetFieldRefMapper,
|
|
7823
|
+
bloks: bloksFieldRefMapper,
|
|
7824
|
+
multiasset: multiassetFieldRefMapper,
|
|
7825
|
+
multilink: multilinkFieldRefMapper,
|
|
7826
|
+
options: optionsFieldRefMapper,
|
|
7827
|
+
richtext: richtextFieldRefMapper
|
|
7828
|
+
};
|
|
7829
|
+
const storyRefMapper = (story, { schemas, maps }) => {
|
|
7830
|
+
const processedFields = /* @__PURE__ */ new Set();
|
|
7831
|
+
const missingSchemas = /* @__PURE__ */ new Set();
|
|
7832
|
+
const alternates = story.alternates ? story.alternates.map((a) => ({
|
|
7833
|
+
...a,
|
|
7834
|
+
id: maps.stories?.get(a.id) || a.id,
|
|
7835
|
+
parent_id: maps.stories?.get(a.parent_id) || a.parent_id
|
|
7836
|
+
})) : story.alternates;
|
|
7837
|
+
const parentId = maps.stories?.get(story.parent_id) || story.parent_id;
|
|
7838
|
+
const mappedStory = {
|
|
7839
|
+
...story,
|
|
7840
|
+
content: traverseAndMapBySchema(story.content, {
|
|
7841
|
+
schemas,
|
|
7842
|
+
maps,
|
|
7843
|
+
fieldRefMappers,
|
|
7844
|
+
processedFields,
|
|
7845
|
+
missingSchemas
|
|
7846
|
+
}),
|
|
7847
|
+
id: Number(maps.stories?.get(story.id) || story.id),
|
|
7848
|
+
uuid: String(maps.stories?.get(story.uuid) || story.uuid),
|
|
7849
|
+
// @ts-expect-error Our types are wrong.
|
|
7850
|
+
parent_id: parentId ? Number(parentId) : null,
|
|
7851
|
+
alternates
|
|
7852
|
+
};
|
|
7853
|
+
return {
|
|
7854
|
+
mappedStory,
|
|
7855
|
+
processedFields,
|
|
7856
|
+
missingSchemas
|
|
7857
|
+
};
|
|
7858
|
+
};
|
|
7859
|
+
|
|
7860
|
+
const apiConcurrencyLock = new Sema(12);
|
|
7861
|
+
const fetchStoriesStream = ({
|
|
7862
|
+
spaceId,
|
|
7863
|
+
params = {},
|
|
7864
|
+
setTotalStories,
|
|
7865
|
+
setTotalPages,
|
|
7866
|
+
onIncrement,
|
|
7867
|
+
onPageSuccess,
|
|
7868
|
+
onPageError
|
|
7869
|
+
}) => {
|
|
7870
|
+
const listGenerator = async function* storyListIterator() {
|
|
7871
|
+
let perPage = 100;
|
|
7872
|
+
let page = 1;
|
|
7873
|
+
let totalPages = 1;
|
|
7874
|
+
setTotalPages?.(totalPages);
|
|
7875
|
+
while (page <= totalPages) {
|
|
7876
|
+
try {
|
|
7877
|
+
const result = await fetchStories(spaceId, {
|
|
7878
|
+
...params,
|
|
7879
|
+
per_page: perPage,
|
|
7880
|
+
page
|
|
7881
|
+
});
|
|
7882
|
+
if (!result) {
|
|
7883
|
+
break;
|
|
7884
|
+
}
|
|
7885
|
+
const { headers } = result;
|
|
7886
|
+
const total = Number(headers.get("Total"));
|
|
7887
|
+
perPage = Number(headers.get("Per-Page"));
|
|
7888
|
+
totalPages = Math.ceil(total / perPage);
|
|
7889
|
+
setTotalStories?.(total);
|
|
7890
|
+
setTotalPages?.(totalPages);
|
|
7891
|
+
onPageSuccess?.(page, totalPages);
|
|
7892
|
+
for (const story of result.stories) {
|
|
7893
|
+
yield story;
|
|
7894
|
+
}
|
|
7895
|
+
page += 1;
|
|
7896
|
+
} catch (maybeError) {
|
|
7897
|
+
onPageError?.(toError(maybeError), page, totalPages);
|
|
7898
|
+
break;
|
|
7899
|
+
} finally {
|
|
7900
|
+
onIncrement?.();
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
};
|
|
7904
|
+
return Readable.from(listGenerator());
|
|
7905
|
+
};
|
|
7906
|
+
const fetchStoryStream = ({
|
|
7907
|
+
spaceId,
|
|
7908
|
+
onIncrement,
|
|
7909
|
+
onStorySuccess,
|
|
7910
|
+
onStoryError
|
|
7911
|
+
}) => {
|
|
7912
|
+
const processing = /* @__PURE__ */ new Set();
|
|
7913
|
+
return new Transform({
|
|
7914
|
+
objectMode: true,
|
|
7915
|
+
async transform(listStory, _encoding, callback) {
|
|
7916
|
+
await apiConcurrencyLock.acquire();
|
|
7917
|
+
const task = fetchStory(spaceId, listStory.id.toString()).then((story) => {
|
|
7918
|
+
if (typeof story === "undefined") {
|
|
7919
|
+
throw new TypeError("Invalid story!");
|
|
7920
|
+
}
|
|
7921
|
+
onStorySuccess?.(story);
|
|
7922
|
+
this.push(story);
|
|
7923
|
+
}).catch((maybeError) => {
|
|
7924
|
+
onStoryError?.(toError(maybeError), listStory);
|
|
7925
|
+
}).finally(() => {
|
|
7926
|
+
onIncrement?.();
|
|
7927
|
+
apiConcurrencyLock.release();
|
|
7928
|
+
processing.delete(task);
|
|
7929
|
+
});
|
|
7930
|
+
processing.add(task);
|
|
7931
|
+
callback();
|
|
7932
|
+
},
|
|
7933
|
+
// Ensure all pending requests finish before closing the stream
|
|
7934
|
+
flush(callback) {
|
|
7935
|
+
Promise.all(processing).finally(() => callback());
|
|
7936
|
+
}
|
|
7937
|
+
});
|
|
7938
|
+
};
|
|
7939
|
+
const getUUIDFromFilename = (filename) => {
|
|
7940
|
+
const uuid = basename(filename, extname(filename)).split("_").at(-1);
|
|
7941
|
+
if (!uuid) {
|
|
7942
|
+
throw new Error(`Unable to extract UUID from local story "${filename}"`);
|
|
7943
|
+
}
|
|
7944
|
+
return uuid;
|
|
7945
|
+
};
|
|
7946
|
+
const readLocalStoriesStream = ({
|
|
7947
|
+
directoryPath,
|
|
7948
|
+
fileFilter = () => true,
|
|
7949
|
+
setTotalStories,
|
|
7950
|
+
onIncrement,
|
|
7951
|
+
onStorySuccess,
|
|
7952
|
+
onStoryError
|
|
7953
|
+
}) => {
|
|
7954
|
+
const listGenerator = async function* localStoryIterator() {
|
|
7955
|
+
const files = (await readDirectory(directoryPath)).filter((f) => extname(f) === ".json" && fileFilter({ uuid: getUUIDFromFilename(f) }));
|
|
7956
|
+
setTotalStories?.(files.length);
|
|
7957
|
+
for (const file of files) {
|
|
7958
|
+
try {
|
|
7959
|
+
const filePath = join(directoryPath, file);
|
|
7960
|
+
const fileContent = await readFile$1(filePath, "utf-8");
|
|
7961
|
+
const story = JSON.parse(fileContent);
|
|
7962
|
+
onStorySuccess?.(story);
|
|
7963
|
+
yield story;
|
|
7964
|
+
} catch (maybeError) {
|
|
7965
|
+
onStoryError?.(toError(maybeError), file);
|
|
7966
|
+
} finally {
|
|
7967
|
+
onIncrement?.();
|
|
7968
|
+
}
|
|
7969
|
+
}
|
|
7970
|
+
};
|
|
7971
|
+
return Readable.from(listGenerator());
|
|
7972
|
+
};
|
|
7973
|
+
const mapReferencesStream = ({
|
|
7974
|
+
schemas,
|
|
7975
|
+
maps,
|
|
7976
|
+
onIncrement,
|
|
7977
|
+
onStorySuccess,
|
|
7978
|
+
onStoryError
|
|
7979
|
+
}) => {
|
|
7980
|
+
return new Transform({
|
|
7981
|
+
objectMode: true,
|
|
7982
|
+
transform(localStory, _encoding, callback) {
|
|
7983
|
+
try {
|
|
7984
|
+
const { mappedStory, processedFields, missingSchemas } = storyRefMapper(localStory, { schemas, maps });
|
|
7985
|
+
onStorySuccess?.(mappedStory, processedFields, missingSchemas);
|
|
7986
|
+
this.push(mappedStory);
|
|
7987
|
+
} catch (maybeError) {
|
|
7988
|
+
onStoryError?.(toError(maybeError), localStory);
|
|
7989
|
+
} finally {
|
|
7990
|
+
onIncrement?.();
|
|
7991
|
+
callback();
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
});
|
|
7995
|
+
};
|
|
7996
|
+
const getRemoteStory = async ({ spaceId, storyId }) => {
|
|
7997
|
+
const { data, response } = await getMapiClient().stories.get({
|
|
7998
|
+
path: {
|
|
7999
|
+
space_id: spaceId,
|
|
8000
|
+
story_id: storyId
|
|
8001
|
+
}
|
|
8002
|
+
});
|
|
8003
|
+
if (!response.ok && response.status !== 404) {
|
|
8004
|
+
handleAPIError("pull_story", new FetchError(response.statusText, response));
|
|
8005
|
+
}
|
|
8006
|
+
if (data?.story?.deleted_at) {
|
|
8007
|
+
return void 0;
|
|
8008
|
+
}
|
|
8009
|
+
return data?.story;
|
|
8010
|
+
};
|
|
8011
|
+
const makeCreateStoryAPITransport = ({ spaceId }) => async (localStory) => {
|
|
8012
|
+
const { id: _id, uuid: _uuid, content, parent_id: _p, ...newStoryData } = localStory;
|
|
8013
|
+
const remoteStory = await createStory(spaceId, {
|
|
8014
|
+
story: {
|
|
8015
|
+
...newStoryData,
|
|
8016
|
+
content: {
|
|
8017
|
+
_uid: "",
|
|
8018
|
+
component: "__tmp__"
|
|
8019
|
+
}
|
|
8020
|
+
},
|
|
8021
|
+
publish: 0
|
|
8022
|
+
});
|
|
8023
|
+
if (!remoteStory) {
|
|
8024
|
+
throw new Error("No response!");
|
|
8025
|
+
}
|
|
8026
|
+
return remoteStory;
|
|
8027
|
+
};
|
|
8028
|
+
const makeAppendToManifestFSTransport = ({ manifestFile }) => async (localStory, remoteStory) => {
|
|
8029
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8030
|
+
await appendToFile(manifestFile, JSON.stringify({
|
|
8031
|
+
old_id: localStory.uuid,
|
|
8032
|
+
new_id: remoteStory.uuid,
|
|
8033
|
+
created_at: createdAt
|
|
8034
|
+
}));
|
|
8035
|
+
await appendToFile(manifestFile, JSON.stringify({
|
|
8036
|
+
old_id: localStory.id,
|
|
8037
|
+
new_id: remoteStory.id,
|
|
8038
|
+
created_at: createdAt
|
|
8039
|
+
}));
|
|
8040
|
+
};
|
|
8041
|
+
const createStoryPlaceholderStream = ({
|
|
8042
|
+
maps,
|
|
8043
|
+
spaceId,
|
|
8044
|
+
transports,
|
|
8045
|
+
onIncrement,
|
|
8046
|
+
onStorySuccess,
|
|
8047
|
+
onStorySkipped,
|
|
8048
|
+
onStoryError
|
|
8049
|
+
}) => {
|
|
8050
|
+
const processing = /* @__PURE__ */ new Set();
|
|
8051
|
+
return new Writable({
|
|
8052
|
+
objectMode: true,
|
|
8053
|
+
async write(localStory, _encoding, callback) {
|
|
8054
|
+
await apiConcurrencyLock.acquire();
|
|
8055
|
+
const task = (async () => {
|
|
8056
|
+
try {
|
|
8057
|
+
const mappedStoryId = maps.stories?.get(localStory.id);
|
|
8058
|
+
const mappedRemoteStory = mappedStoryId && await getRemoteStory({ spaceId, storyId: Number(mappedStoryId) });
|
|
8059
|
+
if (mappedRemoteStory) {
|
|
8060
|
+
onStorySkipped?.(localStory, mappedRemoteStory);
|
|
8061
|
+
return;
|
|
8062
|
+
}
|
|
8063
|
+
const existingRemoteStory = await getRemoteStory({ spaceId, storyId: localStory.id });
|
|
8064
|
+
if (existingRemoteStory && existingRemoteStory.uuid === localStory.uuid) {
|
|
8065
|
+
await transports.appendStoryManifest(localStory, existingRemoteStory);
|
|
8066
|
+
onStorySkipped?.(localStory, existingRemoteStory);
|
|
8067
|
+
return;
|
|
8068
|
+
}
|
|
8069
|
+
const newRemoteStory = await transports.createStory(localStory);
|
|
8070
|
+
await transports.appendStoryManifest(localStory, newRemoteStory);
|
|
8071
|
+
onStorySuccess?.(localStory, newRemoteStory);
|
|
8072
|
+
} catch (maybeError) {
|
|
8073
|
+
onStoryError?.(toError(maybeError), localStory);
|
|
8074
|
+
}
|
|
8075
|
+
})();
|
|
8076
|
+
processing.add(task);
|
|
8077
|
+
task.finally(() => {
|
|
8078
|
+
onIncrement?.();
|
|
8079
|
+
apiConcurrencyLock.release();
|
|
8080
|
+
processing.delete(task);
|
|
8081
|
+
});
|
|
8082
|
+
callback();
|
|
8083
|
+
},
|
|
8084
|
+
final(callback) {
|
|
8085
|
+
Promise.all(processing).finally(() => callback());
|
|
8086
|
+
}
|
|
8087
|
+
});
|
|
8088
|
+
};
|
|
8089
|
+
const makeWriteStoryFSTransport = ({ directoryPath }) => async (story) => {
|
|
8090
|
+
await saveToFile(resolve$1(directoryPath, getStoryFilename(story)), JSON.stringify(story, null, 2));
|
|
8091
|
+
return story;
|
|
8092
|
+
};
|
|
8093
|
+
const makeWriteStoryAPITransport = ({ spaceId, publish }) => (mappedLocalStory) => updateStory(spaceId, mappedLocalStory.id, {
|
|
8094
|
+
story: mappedLocalStory,
|
|
8095
|
+
publish: publish ?? (mappedLocalStory.published ? 1 : 0)
|
|
8096
|
+
});
|
|
8097
|
+
const makeCleanupStoryFSTransport = ({ directoryPath, maps }) => async (mappedStory) => {
|
|
8098
|
+
const mapEntry = maps.stories?.entries().find(([_, v]) => v === mappedStory.uuid);
|
|
8099
|
+
const originalUuid = mapEntry?.[0] && typeof mapEntry?.[0] === "string" ? mapEntry?.[0] : mappedStory.uuid;
|
|
8100
|
+
const storyFilename = getStoryFilename({
|
|
8101
|
+
slug: mappedStory.slug,
|
|
8102
|
+
uuid: originalUuid
|
|
8103
|
+
});
|
|
8104
|
+
const storyFilePath = resolve$1(directoryPath, storyFilename);
|
|
8105
|
+
await unlink(storyFilePath);
|
|
8106
|
+
};
|
|
8107
|
+
const writeStoryStream = ({
|
|
8108
|
+
transports,
|
|
8109
|
+
onIncrement,
|
|
8110
|
+
onStorySuccess,
|
|
8111
|
+
onStoryError
|
|
8112
|
+
}) => {
|
|
8113
|
+
const processing = /* @__PURE__ */ new Set();
|
|
8114
|
+
return new Writable({
|
|
8115
|
+
objectMode: true,
|
|
8116
|
+
async write(mappedLocalStory, _encoding, callback) {
|
|
8117
|
+
await apiConcurrencyLock.acquire();
|
|
8118
|
+
const task = (async () => {
|
|
8119
|
+
try {
|
|
8120
|
+
const remoteStory = await transports.writeStory(mappedLocalStory);
|
|
8121
|
+
await transports.cleanupStory?.(remoteStory);
|
|
8122
|
+
onStorySuccess?.(mappedLocalStory, remoteStory);
|
|
8123
|
+
} catch (maybeError) {
|
|
8124
|
+
onStoryError?.(toError(maybeError), mappedLocalStory);
|
|
8125
|
+
}
|
|
8126
|
+
})();
|
|
8127
|
+
processing.add(task);
|
|
8128
|
+
task.finally(() => {
|
|
8129
|
+
onIncrement?.();
|
|
8130
|
+
apiConcurrencyLock.release();
|
|
8131
|
+
processing.delete(task);
|
|
8132
|
+
});
|
|
8133
|
+
callback();
|
|
8134
|
+
},
|
|
8135
|
+
final(callback) {
|
|
8136
|
+
Promise.all(processing).finally(() => callback());
|
|
8137
|
+
}
|
|
8138
|
+
});
|
|
8139
|
+
};
|
|
8140
|
+
|
|
8141
|
+
const PROGRESS_BAR_PADDING = 23;
|
|
8142
|
+
const upsertAssetFoldersPipeline = async ({
|
|
8143
|
+
directoryPath,
|
|
8144
|
+
logger,
|
|
8145
|
+
maps,
|
|
8146
|
+
transports,
|
|
8147
|
+
ui
|
|
8148
|
+
}) => {
|
|
8149
|
+
const folderProgress = ui.createProgressBar({ title: "Folders...".padEnd(PROGRESS_BAR_PADDING) });
|
|
8150
|
+
const summary = { total: 0, succeeded: 0, failed: 0 };
|
|
8151
|
+
await pipeline$1(
|
|
8152
|
+
readLocalAssetFoldersStream({
|
|
8153
|
+
directoryPath,
|
|
8154
|
+
setTotalFolders: (total) => {
|
|
8155
|
+
summary.total = total;
|
|
8156
|
+
folderProgress.setTotal(total);
|
|
8157
|
+
},
|
|
8158
|
+
onFolderError: (error) => {
|
|
8159
|
+
summary.failed += 1;
|
|
8160
|
+
logOnlyError(error);
|
|
8161
|
+
}
|
|
8162
|
+
}),
|
|
8163
|
+
upsertAssetFolderStream({
|
|
8164
|
+
transports,
|
|
8165
|
+
maps,
|
|
8166
|
+
onIncrement: () => folderProgress.increment(),
|
|
8167
|
+
onFolderSuccess: (localFolder, remoteFolder) => {
|
|
8168
|
+
summary.succeeded += 1;
|
|
8169
|
+
maps.assetFolders.set(localFolder.id, remoteFolder.id);
|
|
8170
|
+
logger.info("Created asset folder", { folderId: remoteFolder.id });
|
|
8171
|
+
},
|
|
8172
|
+
onFolderError: (error, folder) => {
|
|
8173
|
+
summary.failed += 1;
|
|
8174
|
+
logOnlyError(error, { folderId: folder.id });
|
|
8175
|
+
}
|
|
8176
|
+
})
|
|
8177
|
+
);
|
|
8178
|
+
return [["assetFolderResults", summary]];
|
|
8179
|
+
};
|
|
8180
|
+
const upsertAssetsPipeline = async ({
|
|
8181
|
+
assetBinaryPath,
|
|
8182
|
+
assetData,
|
|
8183
|
+
directoryPath,
|
|
8184
|
+
logger,
|
|
8185
|
+
maps,
|
|
8186
|
+
transports,
|
|
8187
|
+
ui
|
|
8188
|
+
}) => {
|
|
8189
|
+
const assetProgress = ui.createProgressBar({ title: "Assets...".padEnd(PROGRESS_BAR_PADDING) });
|
|
8190
|
+
const summary = { total: 0, succeeded: 0, failed: 0, skipped: 0 };
|
|
8191
|
+
const steps = [];
|
|
8192
|
+
if (assetBinaryPath && assetData) {
|
|
8193
|
+
summary.total = 1;
|
|
8194
|
+
assetProgress.setTotal(1);
|
|
8195
|
+
steps.push(readSingleAssetStream({
|
|
8196
|
+
asset: assetData,
|
|
8197
|
+
assetBinaryPath,
|
|
8198
|
+
onAssetError: (error) => {
|
|
8199
|
+
summary.failed += 1;
|
|
8200
|
+
assetProgress.increment();
|
|
8201
|
+
logOnlyError(error);
|
|
8202
|
+
}
|
|
8203
|
+
}));
|
|
8204
|
+
} else {
|
|
8205
|
+
steps.push(readLocalAssetsStream({
|
|
8206
|
+
directoryPath,
|
|
8207
|
+
setTotalAssets: (total) => {
|
|
8208
|
+
summary.total = total;
|
|
8209
|
+
assetProgress.setTotal(total);
|
|
8210
|
+
},
|
|
8211
|
+
onAssetError: (error) => {
|
|
8212
|
+
summary.failed += 1;
|
|
8213
|
+
assetProgress.increment();
|
|
8214
|
+
logOnlyError(error);
|
|
8215
|
+
}
|
|
8216
|
+
}));
|
|
8217
|
+
}
|
|
8218
|
+
steps.push(upsertAssetStream({
|
|
8219
|
+
transports,
|
|
8220
|
+
maps,
|
|
8221
|
+
onIncrement: () => assetProgress.increment(),
|
|
8222
|
+
onAssetSuccess: (localAssetResult, remoteAsset) => {
|
|
8223
|
+
if ("id" in localAssetResult && localAssetResult.id) {
|
|
8224
|
+
maps.assets.set(localAssetResult.id, {
|
|
8225
|
+
old: localAssetResult,
|
|
8226
|
+
new: {
|
|
8227
|
+
id: remoteAsset.id,
|
|
8228
|
+
filename: remoteAsset.filename,
|
|
8229
|
+
meta_data: remoteAsset.meta_data
|
|
8230
|
+
}
|
|
8231
|
+
});
|
|
8232
|
+
}
|
|
8233
|
+
summary.succeeded += 1;
|
|
8234
|
+
logger.info("Uploaded asset", { assetId: remoteAsset.id });
|
|
8235
|
+
},
|
|
8236
|
+
onAssetSkipped: (localAssetResult, remoteAsset) => {
|
|
8237
|
+
if ("id" in localAssetResult && localAssetResult.id) {
|
|
8238
|
+
maps.assets.set(localAssetResult.id, {
|
|
8239
|
+
old: localAssetResult,
|
|
8240
|
+
new: {
|
|
8241
|
+
id: remoteAsset.id,
|
|
8242
|
+
filename: remoteAsset.filename,
|
|
8243
|
+
meta_data: remoteAsset.meta_data
|
|
8244
|
+
}
|
|
8245
|
+
});
|
|
8246
|
+
}
|
|
8247
|
+
summary.skipped += 1;
|
|
8248
|
+
logger.debug("Skipped asset (unchanged)", { assetId: remoteAsset.id });
|
|
8249
|
+
},
|
|
8250
|
+
onAssetError: (error, asset) => {
|
|
8251
|
+
summary.failed += 1;
|
|
8252
|
+
logOnlyError(error, { assetId: asset.id });
|
|
8253
|
+
}
|
|
8254
|
+
}));
|
|
8255
|
+
await pipeline$1(steps);
|
|
8256
|
+
return [["assetResults", summary]];
|
|
8257
|
+
};
|
|
8258
|
+
const mapAssetReferencesInStoriesPipeline = async ({
|
|
8259
|
+
logger,
|
|
8260
|
+
maps,
|
|
8261
|
+
schemas,
|
|
8262
|
+
space,
|
|
8263
|
+
transports,
|
|
8264
|
+
ui
|
|
8265
|
+
}) => {
|
|
8266
|
+
if (Object.keys(schemas).length === 0) {
|
|
8267
|
+
const message = "No components found. Please run `storyblok components pull` to fetch the latest components.";
|
|
8268
|
+
ui.error(message);
|
|
8269
|
+
logger.error(message);
|
|
8270
|
+
return [];
|
|
8271
|
+
}
|
|
8272
|
+
const fetchStoryPagesProgress = ui.createProgressBar({ title: "Fetching Story Pages...".padEnd(PROGRESS_BAR_PADDING) });
|
|
8273
|
+
const fetchStoriesProgress = ui.createProgressBar({ title: "Fetching Stories...".padEnd(PROGRESS_BAR_PADDING) });
|
|
8274
|
+
const processProgress = ui.createProgressBar({ title: "Processing Stories...".padEnd(PROGRESS_BAR_PADDING) });
|
|
8275
|
+
const updateProgress = ui.createProgressBar({ title: "Updating Stories...".padEnd(PROGRESS_BAR_PADDING) });
|
|
8276
|
+
const summaries = {
|
|
8277
|
+
fetchStoryPages: { total: 0, succeeded: 0, failed: 0 },
|
|
8278
|
+
fetchStories: { total: 0, succeeded: 0, failed: 0 },
|
|
8279
|
+
storyProcessResults: { total: 0, succeeded: 0, failed: 0 },
|
|
8280
|
+
storyUpdateResults: { total: 0, succeeded: 0, failed: 0 }
|
|
8281
|
+
};
|
|
8282
|
+
const warnAboutMissingSchemas = (missingSchemas, story) => {
|
|
8283
|
+
const missingSchemaWarnings = /* @__PURE__ */ new Set();
|
|
8284
|
+
for (const schemaName of missingSchemas) {
|
|
8285
|
+
if (missingSchemaWarnings.has(schemaName)) {
|
|
8286
|
+
continue;
|
|
8287
|
+
}
|
|
8288
|
+
const message = `The component "${schemaName}" was not found. Please run \`storyblok components pull\` to fetch the latest components.`;
|
|
8289
|
+
logger.warn(message, { storyId: story.uuid });
|
|
8290
|
+
missingSchemaWarnings.add(schemaName);
|
|
8291
|
+
}
|
|
8292
|
+
};
|
|
8293
|
+
const assetMapValues = [...maps.assets.values()];
|
|
8294
|
+
const reference_search = assetMapValues.length === 1 ? assetMapValues[0].new.filename : void 0;
|
|
8295
|
+
await pipeline$1(
|
|
8296
|
+
fetchStoriesStream({
|
|
8297
|
+
spaceId: space,
|
|
8298
|
+
params: {
|
|
8299
|
+
reference_search
|
|
8300
|
+
},
|
|
8301
|
+
setTotalPages: (totalPages) => {
|
|
8302
|
+
summaries.fetchStoryPages.total = totalPages;
|
|
8303
|
+
fetchStoryPagesProgress.setTotal(totalPages);
|
|
8304
|
+
},
|
|
8305
|
+
setTotalStories: (total) => {
|
|
8306
|
+
summaries.fetchStories.total = total;
|
|
8307
|
+
summaries.storyProcessResults.total = total;
|
|
8308
|
+
summaries.storyUpdateResults.total = total;
|
|
8309
|
+
fetchStoriesProgress.setTotal(total);
|
|
8310
|
+
processProgress.setTotal(total);
|
|
8311
|
+
updateProgress.setTotal(total);
|
|
8312
|
+
},
|
|
8313
|
+
onIncrement: () => fetchStoryPagesProgress.increment(),
|
|
8314
|
+
onPageSuccess: (page, total) => {
|
|
8315
|
+
logger.info(`Fetched stories page ${page} of ${total}`);
|
|
8316
|
+
summaries.fetchStoryPages.succeeded += 1;
|
|
8317
|
+
},
|
|
8318
|
+
onPageError: (error, page, total) => {
|
|
8319
|
+
summaries.fetchStoryPages.failed += 1;
|
|
8320
|
+
logOnlyError(error, { page, total });
|
|
8321
|
+
}
|
|
8322
|
+
}),
|
|
8323
|
+
fetchStoryStream({
|
|
8324
|
+
spaceId: space,
|
|
8325
|
+
onIncrement: () => {
|
|
8326
|
+
fetchStoriesProgress.increment();
|
|
8327
|
+
},
|
|
8328
|
+
onStorySuccess: (story) => {
|
|
8329
|
+
logger.info("Fetched story", { storyId: story.id });
|
|
8330
|
+
summaries.fetchStories.succeeded += 1;
|
|
8331
|
+
},
|
|
8332
|
+
onStoryError: (error, story) => {
|
|
8333
|
+
summaries.fetchStories.failed += 1;
|
|
8334
|
+
summaries.storyProcessResults.total -= 1;
|
|
8335
|
+
summaries.storyUpdateResults.total -= 1;
|
|
8336
|
+
processProgress.setTotal(summaries.storyProcessResults.total);
|
|
8337
|
+
updateProgress.setTotal(summaries.storyProcessResults.total);
|
|
8338
|
+
logOnlyError(error, { storyId: story.id });
|
|
8339
|
+
}
|
|
8340
|
+
}),
|
|
8341
|
+
// Map all references to numeric ids and uuids.
|
|
8342
|
+
mapReferencesStream({
|
|
8343
|
+
schemas,
|
|
8344
|
+
maps: { stories: /* @__PURE__ */ new Map(), ...maps },
|
|
8345
|
+
onIncrement() {
|
|
8346
|
+
processProgress.increment();
|
|
8347
|
+
},
|
|
8348
|
+
onStorySuccess(localStory, _, missingSchemas) {
|
|
8349
|
+
warnAboutMissingSchemas(missingSchemas, localStory);
|
|
8350
|
+
logger.info("Processed story", { storyId: localStory.uuid });
|
|
8351
|
+
summaries.storyProcessResults.succeeded += 1;
|
|
8352
|
+
},
|
|
8353
|
+
onStoryError(error, localStory) {
|
|
8354
|
+
summaries.storyProcessResults.failed += 1;
|
|
8355
|
+
summaries.storyUpdateResults.total -= 1;
|
|
8356
|
+
updateProgress.setTotal(summaries.storyUpdateResults.total);
|
|
8357
|
+
logOnlyError(error, { storyId: localStory.id });
|
|
8358
|
+
}
|
|
8359
|
+
}),
|
|
8360
|
+
// Update remote stories with correct references.
|
|
8361
|
+
writeStoryStream({
|
|
8362
|
+
transports: {
|
|
8363
|
+
writeStory: transports.writeStory
|
|
8364
|
+
},
|
|
8365
|
+
onIncrement() {
|
|
8366
|
+
updateProgress.increment();
|
|
8367
|
+
},
|
|
8368
|
+
onStorySuccess(localStory) {
|
|
8369
|
+
logger.info("Updated story", { storyId: localStory.uuid });
|
|
8370
|
+
summaries.storyUpdateResults.succeeded += 1;
|
|
8371
|
+
},
|
|
8372
|
+
onStoryError(error, localStory) {
|
|
8373
|
+
summaries.storyUpdateResults.failed += 1;
|
|
8374
|
+
logOnlyError(error, { storyId: localStory.id });
|
|
8375
|
+
}
|
|
8376
|
+
})
|
|
8377
|
+
);
|
|
8378
|
+
return Object.entries(summaries);
|
|
8379
|
+
};
|
|
8380
|
+
|
|
8381
|
+
assetsCommand.command("push").argument("[asset]", "path or URL of a single asset to push").option("-f, --from <from>", "source space id").option("--data <data>", "inline asset data as JSON").option("--short-filename <short-filename>", "override the asset filename").option("--folder <folderId>", "destination asset folder ID").option("--cleanup", "delete local assets and metadata after a successful push (note: does not cleanup manifests)").option("--update-stories", "update file references in stories if necessary", false).option("--asset-token <token>", "asset token for accessing private assets").option("-d, --dry-run", "Preview changes without applying them to Storyblok").description(`Push local assets to a Storyblok space.`).action(async (assetInput, options, command) => {
|
|
8382
|
+
const ui = getUI();
|
|
8383
|
+
const logger = getLogger();
|
|
8384
|
+
const reporter = getReporter();
|
|
8385
|
+
ui.title(`${commands.ASSETS}`, colorPalette.ASSETS, "Pushing assets...");
|
|
8386
|
+
logger.info("Pushing assets started");
|
|
8387
|
+
if (options.dryRun) {
|
|
8388
|
+
ui.warn(`DRY RUN MODE ENABLED: No changes will be made.
|
|
8389
|
+
`);
|
|
8390
|
+
logger.warn("Dry run mode enabled");
|
|
8391
|
+
}
|
|
8392
|
+
const { space: targetSpace, path: basePath, verbose } = command.optsWithGlobals();
|
|
8393
|
+
const fromSpace = options.from || targetSpace;
|
|
8394
|
+
const assetToken = options.assetToken;
|
|
8395
|
+
const { state } = session();
|
|
8396
|
+
if (!requireAuthentication(state, verbose)) {
|
|
8397
|
+
process.exitCode = 2;
|
|
8398
|
+
return;
|
|
8399
|
+
}
|
|
8400
|
+
if (!targetSpace) {
|
|
8401
|
+
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
|
|
8402
|
+
process.exitCode = 2;
|
|
8403
|
+
return;
|
|
8404
|
+
}
|
|
8405
|
+
const { region } = state;
|
|
8406
|
+
const summaries = [];
|
|
8407
|
+
let fatalError = false;
|
|
8408
|
+
const manifestFile = join(resolveCommandPath(directories.assets, fromSpace, basePath), "manifest.jsonl");
|
|
8409
|
+
const folderManifestFile = join(resolveCommandPath(directories.assets, fromSpace, basePath), "folders", "manifest.jsonl");
|
|
8410
|
+
try {
|
|
8411
|
+
const [assetMap, assetFolderMap] = await Promise.all([
|
|
8412
|
+
loadAssetMap(manifestFile),
|
|
8413
|
+
loadAssetFolderMap(folderManifestFile)
|
|
8414
|
+
]);
|
|
8415
|
+
const maps = { assets: assetMap, assetFolders: assetFolderMap };
|
|
8416
|
+
const assetsDirectoryPath = resolveCommandPath(directories.assets, fromSpace, basePath);
|
|
8417
|
+
const assetFolderGetTransport = makeGetAssetFolderAPITransport({ spaceId: targetSpace });
|
|
8418
|
+
const assetFolderCreateTransport = options.dryRun ? async (folder) => folder : makeCreateAssetFolderAPITransport({ spaceId: targetSpace });
|
|
8419
|
+
const assetFolderUpdateTransport = options.dryRun ? async (folder) => folder : makeUpdateAssetFolderAPITransport({ spaceId: targetSpace });
|
|
8420
|
+
const assetFolderManifestTransport = options.dryRun ? () => Promise.resolve() : makeAppendAssetFolderManifestFSTransport({ manifestFile: folderManifestFile });
|
|
8421
|
+
const cleanupAssetFolderTransport = options.cleanup && !options.dryRun ? makeCleanupAssetFolderFSTransport() : () => Promise.resolve();
|
|
8422
|
+
summaries.push(...await upsertAssetFoldersPipeline({
|
|
8423
|
+
directoryPath: join(assetsDirectoryPath, "folders"),
|
|
8424
|
+
logger,
|
|
8425
|
+
maps,
|
|
8426
|
+
transports: {
|
|
8427
|
+
getAssetFolder: assetFolderGetTransport,
|
|
8428
|
+
createAssetFolder: assetFolderCreateTransport,
|
|
8429
|
+
updateAssetFolder: assetFolderUpdateTransport,
|
|
8430
|
+
appendAssetFolderManifest: assetFolderManifestTransport,
|
|
8431
|
+
cleanupAssetFolder: cleanupAssetFolderTransport
|
|
8432
|
+
},
|
|
8433
|
+
ui
|
|
8434
|
+
}));
|
|
8435
|
+
const assetBinaryPath = typeof assetInput === "string" && assetInput.trim().length > 0 ? assetInput : void 0;
|
|
8436
|
+
let assetData;
|
|
8437
|
+
if (assetBinaryPath) {
|
|
8438
|
+
const assetDataPartial = options.data ? parseAssetData(options.data) : !isRemoteSource(assetBinaryPath) ? await loadSidecarAssetData(assetBinaryPath) : {};
|
|
8439
|
+
const sourceBasename = isRemoteSource(assetBinaryPath) ? basename(new URL(assetBinaryPath).pathname) : basename(assetBinaryPath);
|
|
8440
|
+
const shortFilename = options.shortFilename || assetDataPartial.short_filename || sourceBasename;
|
|
8441
|
+
const folderId = options.folder ? Number(options.folder) : void 0;
|
|
8442
|
+
assetData = {
|
|
8443
|
+
...assetDataPartial,
|
|
8444
|
+
short_filename: shortFilename,
|
|
8445
|
+
asset_folder_id: folderId
|
|
8446
|
+
};
|
|
8447
|
+
}
|
|
8448
|
+
const getAssetTransport = makeGetAssetAPITransport({ spaceId: targetSpace });
|
|
8449
|
+
const createAssetTransport = options.dryRun ? async (asset) => asset : makeCreateAssetAPITransport({ spaceId: targetSpace });
|
|
8450
|
+
const updateAssetTransport = options.dryRun ? async (asset) => asset : makeUpdateAssetAPITransport({ spaceId: targetSpace });
|
|
8451
|
+
const downloadAssetFileTransport = makeDownloadAssetFileTransport({
|
|
8452
|
+
assetToken,
|
|
8453
|
+
region
|
|
8454
|
+
});
|
|
8455
|
+
const assetManifestTransport = options.dryRun ? () => Promise.resolve() : makeAppendAssetManifestFSTransport({ manifestFile });
|
|
8456
|
+
const cleanupAssetTransport = options.cleanup && !options.dryRun ? makeCleanupAssetFSTransport() : () => Promise.resolve();
|
|
8457
|
+
summaries.push(...await upsertAssetsPipeline({
|
|
8458
|
+
assetBinaryPath,
|
|
8459
|
+
assetData,
|
|
8460
|
+
directoryPath: assetsDirectoryPath,
|
|
8461
|
+
logger,
|
|
8462
|
+
maps,
|
|
8463
|
+
transports: {
|
|
8464
|
+
getAsset: getAssetTransport,
|
|
8465
|
+
createAsset: createAssetTransport,
|
|
8466
|
+
updateAsset: updateAssetTransport,
|
|
8467
|
+
downloadAssetFile: downloadAssetFileTransport,
|
|
8468
|
+
appendAssetManifest: assetManifestTransport,
|
|
8469
|
+
cleanupAsset: cleanupAssetTransport
|
|
8470
|
+
},
|
|
8471
|
+
ui
|
|
8472
|
+
}));
|
|
8473
|
+
const hasUpdatedFilename = (entry) => "filename" in entry.old && entry.old.filename !== entry.new.filename;
|
|
8474
|
+
const hasMetadata = (entry) => "meta_data" in entry.new && entry.new.meta_data;
|
|
8475
|
+
const hasUpdatedAssets = maps.assets.values().some((v) => hasUpdatedFilename(v) || hasMetadata(v));
|
|
8476
|
+
if (hasUpdatedAssets && options.updateStories) {
|
|
8477
|
+
const schemas = await findComponentSchemas(resolveCommandPath(directories.components, fromSpace, basePath));
|
|
8478
|
+
const writeStoryTransport = options.dryRun ? async (story) => story : makeWriteStoryAPITransport({ spaceId: targetSpace });
|
|
8479
|
+
summaries.push(...await mapAssetReferencesInStoriesPipeline({
|
|
8480
|
+
logger,
|
|
8481
|
+
maps,
|
|
8482
|
+
schemas,
|
|
8483
|
+
space: targetSpace,
|
|
8484
|
+
transports: {
|
|
8485
|
+
writeStory: writeStoryTransport
|
|
8486
|
+
},
|
|
8487
|
+
ui
|
|
8488
|
+
}));
|
|
8489
|
+
}
|
|
8490
|
+
if (!options.dryRun) {
|
|
8491
|
+
await deduplicateManifest(manifestFile);
|
|
8492
|
+
}
|
|
8493
|
+
} catch (maybeError) {
|
|
8494
|
+
fatalError = true;
|
|
8495
|
+
handleError(toError(maybeError), verbose);
|
|
8496
|
+
} finally {
|
|
8497
|
+
ui.stopAllProgressBars();
|
|
8498
|
+
const summary = Object.fromEntries(summaries);
|
|
8499
|
+
logger.info("Pushing assets finished", { summary });
|
|
8500
|
+
const assetsTotal = summary.assetResults?.total ?? 0;
|
|
8501
|
+
const assetsSucceeded = summary.assetResults?.succeeded ?? 0;
|
|
8502
|
+
const assetsSkipped = summary.assetResults?.skipped ?? 0;
|
|
8503
|
+
const assetsFailed = summary.assetResults?.failed ?? 0;
|
|
8504
|
+
ui.info(`Push results: ${assetsTotal} processed, ${assetsFailed} assets failed`);
|
|
8505
|
+
ui.list([
|
|
8506
|
+
`Folders: ${summary.assetFolderResults?.succeeded ?? 0}/${summary.assetFolderResults?.total ?? 0} succeeded, ${summary.assetFolderResults?.failed ?? 0} failed.`,
|
|
8507
|
+
`Assets: ${assetsSucceeded}/${assetsTotal} succeeded, ${assetsSkipped} skipped, ${assetsFailed} failed.`
|
|
8508
|
+
]);
|
|
8509
|
+
for (const [name, reportSummary] of summaries) {
|
|
8510
|
+
reporter.addSummary(name, reportSummary);
|
|
8511
|
+
}
|
|
8512
|
+
reporter.finalize();
|
|
8513
|
+
const failedTotal = Object.values(summary).reduce((total, entry) => {
|
|
8514
|
+
if (!entry || typeof entry.failed !== "number") {
|
|
8515
|
+
return total;
|
|
8516
|
+
}
|
|
8517
|
+
return total + entry.failed;
|
|
8518
|
+
}, 0);
|
|
8519
|
+
process.exitCode = fatalError ? 2 : failedTotal > 0 ? 1 : 0;
|
|
8520
|
+
}
|
|
8521
|
+
});
|
|
8522
|
+
|
|
8523
|
+
const program$1 = getProgram();
|
|
8524
|
+
const storiesCommand = program$1.command(commands.STORIES).description(`Manage your space's stories`).option("-s, --space <space>", "space ID");
|
|
8525
|
+
|
|
8526
|
+
storiesCommand.command("pull").option("-d, --dry-run", "Preview changes without applying them to Storyblok").option("-p, --path <path>", "base path to store stories (default .storyblok)").option("-q, --query <query>", 'Filter stories by content attributes using Storyblok filter query syntax. Example: --query="[highlighted][in]=true"').option("--starts-with <path>", 'Filter stories by path. Example: --starts-with="/en/blog/"').description(`Download your space's stories as separate json files.`).action(async (options, command) => {
|
|
8527
|
+
const ui = getUI();
|
|
8528
|
+
const logger = getLogger();
|
|
8529
|
+
const reporter = getReporter();
|
|
8530
|
+
ui.title(`${commands.STORIES}`, colorPalette.STORIES, "Pulling stories...");
|
|
8531
|
+
logger.info("Pulling stories started");
|
|
8532
|
+
if (options.dryRun) {
|
|
8533
|
+
ui.warn(`DRY RUN MODE ENABLED: No changes will be made.
|
|
8534
|
+
`);
|
|
8535
|
+
logger.warn("Dry run mode enabled");
|
|
8536
|
+
}
|
|
8537
|
+
const { space, path: basePath, verbose } = command.optsWithGlobals();
|
|
8538
|
+
const { state } = session();
|
|
8539
|
+
if (!requireAuthentication(state, verbose)) {
|
|
8540
|
+
return;
|
|
8541
|
+
}
|
|
8542
|
+
if (!space) {
|
|
8543
|
+
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
|
|
8544
|
+
return;
|
|
8545
|
+
}
|
|
8546
|
+
const summary = {
|
|
8547
|
+
fetchStoryPages: { total: 0, succeeded: 0, failed: 0 },
|
|
8548
|
+
fetchStories: { total: 0, succeeded: 0, failed: 0 },
|
|
8549
|
+
save: { total: 0, succeeded: 0, failed: 0 }
|
|
8550
|
+
};
|
|
8551
|
+
try {
|
|
8552
|
+
const fetchStoryPagesProgress = ui.createProgressBar({ title: "Fetching Story Pages...".padEnd(23) });
|
|
8553
|
+
const fetchStoriesProgress = ui.createProgressBar({ title: "Fetching Stories...".padEnd(23) });
|
|
8554
|
+
const saveProgress = ui.createProgressBar({ title: "Saving Stories...".padEnd(23) });
|
|
8555
|
+
await pipeline$1(
|
|
8556
|
+
fetchStoriesStream({
|
|
8557
|
+
spaceId: space,
|
|
8558
|
+
params: {
|
|
8559
|
+
filter_query: options.query,
|
|
8560
|
+
starts_with: options.startsWith
|
|
8561
|
+
},
|
|
8562
|
+
setTotalPages: (totalPages) => {
|
|
8563
|
+
summary.fetchStoryPages.total = totalPages;
|
|
8564
|
+
fetchStoryPagesProgress.setTotal(totalPages);
|
|
8565
|
+
},
|
|
8566
|
+
setTotalStories: (total) => {
|
|
8567
|
+
summary.fetchStories.total = total;
|
|
8568
|
+
summary.save.total = total;
|
|
8569
|
+
fetchStoriesProgress.setTotal(total);
|
|
8570
|
+
saveProgress.setTotal(total);
|
|
8571
|
+
},
|
|
8572
|
+
onIncrement: () => {
|
|
8573
|
+
fetchStoryPagesProgress.increment();
|
|
8574
|
+
},
|
|
8575
|
+
onPageSuccess: (page, total) => {
|
|
8576
|
+
logger.info(`Fetched stories page ${page} of ${total}`);
|
|
8577
|
+
summary.fetchStoryPages.succeeded += 1;
|
|
8578
|
+
},
|
|
8579
|
+
onPageError: (error, page, total) => {
|
|
8580
|
+
summary.fetchStoryPages.failed += 1;
|
|
8581
|
+
handleError(error, verbose, { page, total });
|
|
8582
|
+
}
|
|
8583
|
+
}),
|
|
8584
|
+
fetchStoryStream({
|
|
8585
|
+
spaceId: space,
|
|
8586
|
+
onIncrement: () => {
|
|
8587
|
+
fetchStoriesProgress.increment();
|
|
8588
|
+
},
|
|
8589
|
+
onStorySuccess: (story) => {
|
|
8590
|
+
logger.info("Fetched story", { storyId: story.id });
|
|
8591
|
+
summary.fetchStories.succeeded += 1;
|
|
8592
|
+
},
|
|
8593
|
+
onStoryError: (error, story) => {
|
|
8594
|
+
summary.fetchStories.failed += 1;
|
|
8595
|
+
summary.save.total -= 1;
|
|
8596
|
+
saveProgress.setTotal(summary.save.total);
|
|
8597
|
+
handleError(error, verbose, { storyId: story.id });
|
|
8598
|
+
}
|
|
8599
|
+
}),
|
|
8600
|
+
writeStoryStream({
|
|
8601
|
+
transports: {
|
|
8602
|
+
writeStory: options.dryRun ? async (story) => story : makeWriteStoryFSTransport({ directoryPath: resolveCommandPath(directories.stories, space, basePath) })
|
|
8603
|
+
},
|
|
8604
|
+
onIncrement: () => {
|
|
8605
|
+
saveProgress.increment();
|
|
8606
|
+
},
|
|
8607
|
+
onStorySuccess: (story) => {
|
|
8608
|
+
logger.info("Saved story", { storyId: story.id });
|
|
8609
|
+
summary.save.succeeded += 1;
|
|
8610
|
+
},
|
|
8611
|
+
onStoryError: (error, story) => {
|
|
8612
|
+
summary.save.failed += 1;
|
|
8613
|
+
handleError(error, verbose, { storyId: story.id });
|
|
8614
|
+
}
|
|
8615
|
+
})
|
|
8616
|
+
);
|
|
8617
|
+
} catch (maybeError) {
|
|
8618
|
+
handleError(toError(maybeError));
|
|
8619
|
+
} finally {
|
|
8620
|
+
logger.info("Pulling stories finished", summary);
|
|
8621
|
+
ui.stopAllProgressBars();
|
|
8622
|
+
ui.info(`Pull results: ${summary.save.total} stories pulled, ${Math.max(summary.fetchStories.failed, summary.save.failed)} stories failed`);
|
|
8623
|
+
ui.list([
|
|
8624
|
+
`Fetching pages: ${summary.fetchStoryPages.succeeded}/${summary.fetchStoryPages.total} succeeded, ${summary.fetchStoryPages.failed} failed.`,
|
|
8625
|
+
`Fetching stories: ${summary.fetchStories.succeeded}/${summary.fetchStories.total} succeeded, ${summary.fetchStories.failed} failed.`,
|
|
8626
|
+
`Saving stories: ${summary.save.succeeded}/${summary.save.total} succeeded, ${summary.save.failed} failed.`
|
|
8627
|
+
]);
|
|
8628
|
+
reporter.addSummary("fetchStoryPagesResults", summary.fetchStoryPages);
|
|
8629
|
+
reporter.addSummary("fetchStoriesResults", summary.fetchStories);
|
|
8630
|
+
reporter.addSummary("saveResults", summary.save);
|
|
8631
|
+
reporter.finalize();
|
|
8632
|
+
}
|
|
8633
|
+
});
|
|
8634
|
+
|
|
8635
|
+
storiesCommand.command("push").option("-f, --from <from>", "source space id").option("-p, --path <path>", "base path for stories and components (default .storyblok)").option("-d, --dry-run", "Preview changes without applying them to Storyblok").option("--publish", "Publish stories after pushing").option("--cleanup", "delete local stories after a successful push (note: does not cleanup manifests)").description(`Push local stories to a Storyblok space.`).action(async (options, command) => {
|
|
8636
|
+
const ui = getUI();
|
|
8637
|
+
const logger = getLogger();
|
|
8638
|
+
const reporter = getReporter();
|
|
8639
|
+
ui.title(`${commands.STORIES}`, colorPalette.STORIES, "Pushing stories...");
|
|
8640
|
+
logger.info("Pushing stories started");
|
|
8641
|
+
if (options.dryRun) {
|
|
8642
|
+
ui.warn(`DRY RUN MODE ENABLED: No changes will be made.
|
|
8643
|
+
`);
|
|
8644
|
+
logger.warn("Dry run mode enabled");
|
|
8645
|
+
}
|
|
8646
|
+
const { space, path: basePath, verbose } = command.optsWithGlobals();
|
|
8647
|
+
const fromSpace = options.from || space;
|
|
8648
|
+
const { state } = session();
|
|
8649
|
+
if (!requireAuthentication(state, verbose)) {
|
|
8650
|
+
return;
|
|
8651
|
+
}
|
|
8652
|
+
if (!space) {
|
|
8653
|
+
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
|
|
8654
|
+
return;
|
|
8655
|
+
}
|
|
8656
|
+
const warnAboutCustomPlugins = (fields, story) => {
|
|
8657
|
+
const warnedPlugins = /* @__PURE__ */ new Set();
|
|
8658
|
+
for (const field of fields) {
|
|
8659
|
+
if (field.type === "custom" && typeof field.field_type === "string") {
|
|
8660
|
+
if (warnedPlugins.has(field.field_type)) {
|
|
8661
|
+
continue;
|
|
8662
|
+
}
|
|
8663
|
+
warnedPlugins.add(field.field_type);
|
|
8664
|
+
const message = `The custom plugin "${field.field_type}" may contain references that require manual updates.`;
|
|
8665
|
+
ui.warn(message);
|
|
8666
|
+
logger.warn(message, { storyId: story.uuid });
|
|
8667
|
+
}
|
|
8668
|
+
}
|
|
8669
|
+
};
|
|
8670
|
+
const warnAboutMissingSchemas = (missingSchemas, story) => {
|
|
8671
|
+
const missingSchemaWarnings = /* @__PURE__ */ new Set();
|
|
8672
|
+
for (const schemaName of missingSchemas) {
|
|
8673
|
+
if (missingSchemaWarnings.has(schemaName)) {
|
|
8674
|
+
continue;
|
|
8675
|
+
}
|
|
8676
|
+
const message = `The component "${schemaName}" was not found. Please run \`storyblok components pull\` to fetch the latest components.`;
|
|
8677
|
+
ui.warn(message);
|
|
8678
|
+
logger.warn(message, { storyId: story.uuid });
|
|
8679
|
+
missingSchemaWarnings.add(schemaName);
|
|
8680
|
+
}
|
|
8681
|
+
};
|
|
8682
|
+
const summary = {
|
|
8683
|
+
creationResults: { total: 0, succeeded: 0, skipped: 0, failed: 0 },
|
|
8684
|
+
processResults: { total: 0, succeeded: 0, failed: 0 },
|
|
8685
|
+
updateResults: { total: 0, succeeded: 0, failed: 0 }
|
|
8686
|
+
};
|
|
8687
|
+
try {
|
|
8688
|
+
const manifestFile = join(resolveCommandPath(directories.stories, fromSpace, basePath), "manifest.jsonl");
|
|
8689
|
+
const manifest = await loadManifest(manifestFile);
|
|
8690
|
+
const assetManifestFile = join(resolveCommandPath(directories.assets, fromSpace, basePath), "manifest.jsonl");
|
|
8691
|
+
const maps = {
|
|
8692
|
+
assets: await loadAssetMap(assetManifestFile),
|
|
8693
|
+
stories: new Map(manifest.map((e) => [e.old_id, e.new_id]))
|
|
8694
|
+
};
|
|
8695
|
+
const schemas = await findComponentSchemas(resolveCommandPath(directories.components, fromSpace, basePath));
|
|
8696
|
+
if (Object.keys(schemas).length === 0) {
|
|
8697
|
+
const message = "No components found. Please run `storyblok components pull` to fetch the latest components.";
|
|
8698
|
+
ui.error(message);
|
|
8699
|
+
logger.error(message);
|
|
8700
|
+
return;
|
|
8701
|
+
}
|
|
8702
|
+
const storiesDirectoryPath = resolveCommandPath(directories.stories, fromSpace, basePath);
|
|
8703
|
+
const creationProgress = ui.createProgressBar({ title: "Creating Stories...".padEnd(21) });
|
|
8704
|
+
const processProgress = ui.createProgressBar({ title: "Processing Stories...".padEnd(21) });
|
|
8705
|
+
const updateProgress = ui.createProgressBar({ title: "Updating Stories...".padEnd(21) });
|
|
8706
|
+
await pipeline$1(
|
|
8707
|
+
// Read local stories from `.json` files.
|
|
8708
|
+
readLocalStoriesStream({
|
|
8709
|
+
directoryPath: storiesDirectoryPath,
|
|
8710
|
+
setTotalStories(total) {
|
|
8711
|
+
summary.creationResults.total = total;
|
|
8712
|
+
summary.processResults.total = total;
|
|
8713
|
+
summary.updateResults.total = total;
|
|
8714
|
+
creationProgress.setTotal(total);
|
|
8715
|
+
processProgress.setTotal(total);
|
|
8716
|
+
updateProgress.setTotal(total);
|
|
8717
|
+
},
|
|
8718
|
+
onStoryError(error) {
|
|
8719
|
+
summary.creationResults.failed += 1;
|
|
8720
|
+
summary.processResults.total -= 1;
|
|
8721
|
+
summary.updateResults.total -= 1;
|
|
8722
|
+
processProgress.setTotal(summary.processResults.total);
|
|
8723
|
+
updateProgress.setTotal(summary.updateResults.total);
|
|
8724
|
+
creationProgress.increment();
|
|
8725
|
+
handleError(error, verbose);
|
|
8726
|
+
}
|
|
8727
|
+
}),
|
|
8728
|
+
// Create remote stories.
|
|
8729
|
+
createStoryPlaceholderStream({
|
|
8730
|
+
maps,
|
|
8731
|
+
spaceId: space,
|
|
8732
|
+
transports: {
|
|
8733
|
+
createStory: options.dryRun ? async (story) => story : makeCreateStoryAPITransport({
|
|
8734
|
+
maps,
|
|
8735
|
+
spaceId: space
|
|
8736
|
+
}),
|
|
8737
|
+
appendStoryManifest: options.dryRun ? () => Promise.resolve() : makeAppendToManifestFSTransport({
|
|
8738
|
+
manifestFile
|
|
8739
|
+
})
|
|
8740
|
+
},
|
|
8741
|
+
onStorySuccess(localStory, remoteStory) {
|
|
8742
|
+
if (!localStory.uuid || !remoteStory.uuid) {
|
|
8743
|
+
throw new Error("Invalid story provided!");
|
|
8744
|
+
}
|
|
8745
|
+
maps.stories.set(localStory.id, remoteStory.id);
|
|
8746
|
+
maps.stories.set(localStory.uuid, remoteStory.uuid);
|
|
8747
|
+
logger.info("Created story", { storyId: remoteStory.uuid });
|
|
8748
|
+
summary.creationResults.succeeded += 1;
|
|
8749
|
+
},
|
|
8750
|
+
onStorySkipped(localStory, remoteStory) {
|
|
8751
|
+
if (!localStory.uuid || !remoteStory.uuid) {
|
|
8752
|
+
throw new Error("Invalid story provided!");
|
|
8753
|
+
}
|
|
8754
|
+
maps.stories.set(localStory.id, remoteStory.id);
|
|
8755
|
+
maps.stories.set(localStory.uuid, remoteStory.uuid);
|
|
8756
|
+
logger.info("Skipped creating story", { storyId: localStory.uuid });
|
|
8757
|
+
summary.creationResults.skipped += 1;
|
|
8758
|
+
},
|
|
8759
|
+
onStoryError(error) {
|
|
8760
|
+
summary.creationResults.failed += 1;
|
|
8761
|
+
summary.processResults.total -= 1;
|
|
8762
|
+
summary.updateResults.total -= 1;
|
|
8763
|
+
processProgress.setTotal(summary.processResults.total);
|
|
8764
|
+
updateProgress.setTotal(summary.updateResults.total);
|
|
8765
|
+
handleError(error, verbose);
|
|
8766
|
+
},
|
|
8767
|
+
onIncrement() {
|
|
8768
|
+
creationProgress.increment();
|
|
8769
|
+
}
|
|
8770
|
+
})
|
|
8771
|
+
);
|
|
8772
|
+
await pipeline$1(
|
|
8773
|
+
// Read local stories from `.json` files.
|
|
8774
|
+
readLocalStoriesStream({
|
|
8775
|
+
directoryPath: storiesDirectoryPath,
|
|
8776
|
+
fileFilter({ uuid }) {
|
|
8777
|
+
return Boolean(maps.stories.get(uuid));
|
|
8778
|
+
},
|
|
8779
|
+
setTotalStories(total) {
|
|
8780
|
+
summary.processResults.total = total;
|
|
8781
|
+
summary.updateResults.total = total;
|
|
8782
|
+
processProgress.setTotal(total);
|
|
8783
|
+
updateProgress.setTotal(total);
|
|
8784
|
+
},
|
|
8785
|
+
onStoryError(error) {
|
|
8786
|
+
summary.creationResults.failed += 1;
|
|
8787
|
+
summary.processResults.total -= 1;
|
|
8788
|
+
summary.updateResults.total -= 1;
|
|
8789
|
+
processProgress.setTotal(summary.processResults.total);
|
|
8790
|
+
updateProgress.setTotal(summary.updateResults.total);
|
|
8791
|
+
handleError(error, verbose);
|
|
8792
|
+
}
|
|
8793
|
+
}),
|
|
8794
|
+
// Map all references to numeric ids and uuids.
|
|
8795
|
+
mapReferencesStream({
|
|
8796
|
+
schemas,
|
|
8797
|
+
maps,
|
|
8798
|
+
onIncrement() {
|
|
8799
|
+
processProgress.increment();
|
|
8800
|
+
},
|
|
8801
|
+
onStorySuccess(localStory, processedFields, missingSchemas) {
|
|
8802
|
+
warnAboutCustomPlugins(processedFields, localStory);
|
|
8803
|
+
warnAboutMissingSchemas(missingSchemas, localStory);
|
|
8804
|
+
logger.info("Processed story", { storyId: localStory.uuid });
|
|
8805
|
+
summary.processResults.succeeded += 1;
|
|
8806
|
+
},
|
|
8807
|
+
onStoryError(error, localStory) {
|
|
8808
|
+
summary.processResults.failed += 1;
|
|
8809
|
+
summary.updateResults.total -= 1;
|
|
8810
|
+
updateProgress.setTotal(summary.updateResults.total);
|
|
8811
|
+
handleError(error, verbose, { storyId: localStory.uuid });
|
|
8812
|
+
}
|
|
8813
|
+
}),
|
|
8814
|
+
// Update remote stories with correct references.
|
|
8815
|
+
writeStoryStream({
|
|
8816
|
+
transports: {
|
|
8817
|
+
writeStory: options.dryRun ? async (story) => story : makeWriteStoryAPITransport({
|
|
8818
|
+
spaceId: space,
|
|
8819
|
+
publish: options.publish ? 1 : void 0
|
|
8820
|
+
}),
|
|
8821
|
+
cleanupStory: options.cleanup && !options.dryRun ? makeCleanupStoryFSTransport({ directoryPath: storiesDirectoryPath, maps }) : void 0
|
|
8822
|
+
},
|
|
8823
|
+
onIncrement() {
|
|
8824
|
+
updateProgress.increment();
|
|
8825
|
+
},
|
|
8826
|
+
onStorySuccess(localStory) {
|
|
8827
|
+
logger.info("Updated story", { storyId: localStory.uuid });
|
|
8828
|
+
summary.updateResults.succeeded += 1;
|
|
8829
|
+
},
|
|
8830
|
+
onStoryError(error, localStory) {
|
|
8831
|
+
summary.updateResults.failed += 1;
|
|
8832
|
+
handleError(error, verbose, { storyId: localStory.uuid });
|
|
8833
|
+
}
|
|
8834
|
+
})
|
|
8835
|
+
);
|
|
8836
|
+
} catch (maybeError) {
|
|
8837
|
+
handleError(toError(maybeError));
|
|
8838
|
+
} finally {
|
|
8839
|
+
logger.info("Pushing stories finished", summary);
|
|
8840
|
+
ui.stopAllProgressBars();
|
|
8841
|
+
const failedStories = Math.max(summary.creationResults.failed, summary.processResults.failed, summary.updateResults.failed);
|
|
8842
|
+
ui.info(`Push results: ${summary.creationResults.total} ${summary.creationResults.total === 1 ? "story" : "stories"} pushed, ${failedStories} ${failedStories === 1 ? "story" : "stories"} failed`);
|
|
8843
|
+
ui.list([
|
|
8844
|
+
`Creating stories: ${summary.creationResults.succeeded + summary.creationResults.skipped}/${summary.creationResults.total} succeeded, ${summary.creationResults.failed} failed.`,
|
|
8845
|
+
`Processing stories: ${summary.processResults.succeeded}/${summary.processResults.total} succeeded, ${summary.processResults.failed} failed.`,
|
|
8846
|
+
`Updating stories: ${summary.updateResults.succeeded}/${summary.updateResults.total} succeeded, ${summary.updateResults.failed} failed.`
|
|
8847
|
+
]);
|
|
8848
|
+
reporter.addSummary("creationResults", summary.creationResults);
|
|
8849
|
+
reporter.addSummary("processResults", summary.processResults);
|
|
8850
|
+
reporter.addSummary("updateResults", summary.updateResults);
|
|
8851
|
+
reporter.finalize();
|
|
8852
|
+
}
|
|
8853
|
+
});
|
|
8854
|
+
|
|
6583
8855
|
const program = getProgram();
|
|
6584
8856
|
konsola.br();
|
|
6585
8857
|
konsola.br();
|