storyblok 4.1.0 → 4.2.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 +624 -73
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -4
package/dist/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import chalk from 'chalk';
|
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import { readPackageUp } from 'read-package-up';
|
|
8
8
|
import { Spinner } from '@topcli/spinner';
|
|
9
|
-
import { select, password, input } from '@inquirer/prompts';
|
|
9
|
+
import { select, password, input, confirm } from '@inquirer/prompts';
|
|
10
10
|
import fs, { mkdir, writeFile, readFile as readFile$1, access, readdir } from 'node:fs/promises';
|
|
11
11
|
import path, { join, parse, resolve } from 'node:path';
|
|
12
12
|
import { exec, spawn } from 'node:child_process';
|
|
@@ -25,8 +25,9 @@ const commands = {
|
|
|
25
25
|
USER: "user",
|
|
26
26
|
COMPONENTS: "components",
|
|
27
27
|
LANGUAGES: "languages",
|
|
28
|
-
MIGRATIONS: "
|
|
29
|
-
TYPES: "
|
|
28
|
+
MIGRATIONS: "Migrations",
|
|
29
|
+
TYPES: "Types",
|
|
30
|
+
DATASOURCES: "Datasources",
|
|
30
31
|
CREATE: "create"
|
|
31
32
|
};
|
|
32
33
|
const colorPalette = {
|
|
@@ -42,7 +43,8 @@ const colorPalette = {
|
|
|
42
43
|
CREATE: "#ffb3ba",
|
|
43
44
|
GROUPS: "#4ade80",
|
|
44
45
|
TAGS: "#fbbf24",
|
|
45
|
-
PRESETS: "#a855f7"
|
|
46
|
+
PRESETS: "#a855f7",
|
|
47
|
+
DATASOURCES: "#4ade80"
|
|
46
48
|
};
|
|
47
49
|
const regions = {
|
|
48
50
|
EU: "eu",
|
|
@@ -171,6 +173,10 @@ const API_ACTIONS = {
|
|
|
171
173
|
pull_stories: "Failed to pull stories",
|
|
172
174
|
pull_story: "Failed to pull story",
|
|
173
175
|
update_story: "Failed to update story",
|
|
176
|
+
pull_datasources: "Failed to pull datasources",
|
|
177
|
+
push_datasource: "Failed to push datasource",
|
|
178
|
+
update_datasource: "Failed to update datasource",
|
|
179
|
+
delete_datasource: "Failed to delete datasource",
|
|
174
180
|
create_space: "Failed to create space",
|
|
175
181
|
fetch_blueprints: "Failed to fetch blueprints from GitHub"
|
|
176
182
|
};
|
|
@@ -607,6 +613,18 @@ const resolvePath = (path, folder) => {
|
|
|
607
613
|
const getComponentNameFromFilename = (filename) => {
|
|
608
614
|
return filename.replace(/\.js$/, "");
|
|
609
615
|
};
|
|
616
|
+
async function readJsonFile(filePath) {
|
|
617
|
+
try {
|
|
618
|
+
const content = (await readFile(filePath)).toString();
|
|
619
|
+
if (!content) {
|
|
620
|
+
return { data: [] };
|
|
621
|
+
}
|
|
622
|
+
const parsed = JSON.parse(content);
|
|
623
|
+
return { data: Array.isArray(parsed) ? parsed : [parsed] };
|
|
624
|
+
} catch (error) {
|
|
625
|
+
return { data: [], error };
|
|
626
|
+
}
|
|
627
|
+
}
|
|
610
628
|
|
|
611
629
|
const getCredentials = async (filePath = join(getStoryblokGlobalPath(), "credentials.json")) => {
|
|
612
630
|
try {
|
|
@@ -734,7 +752,7 @@ function session() {
|
|
|
734
752
|
return sessionInstance;
|
|
735
753
|
}
|
|
736
754
|
|
|
737
|
-
const program$
|
|
755
|
+
const program$i = getProgram();
|
|
738
756
|
const allRegionsText = Object.values(regions).join(",");
|
|
739
757
|
const loginStrategy = {
|
|
740
758
|
message: "How would you like to login?",
|
|
@@ -751,12 +769,12 @@ const loginStrategy = {
|
|
|
751
769
|
}
|
|
752
770
|
]
|
|
753
771
|
};
|
|
754
|
-
program$
|
|
772
|
+
program$i.command(commands.LOGIN).description("Login to the Storyblok CLI").option("-t, --token <token>", "Token to login directly without questions, like for CI environments").option(
|
|
755
773
|
"-r, --region <region>",
|
|
756
774
|
`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}.`
|
|
757
775
|
).action(async (options) => {
|
|
758
776
|
konsola.title(` ${commands.LOGIN} `, colorPalette.LOGIN);
|
|
759
|
-
const verbose = program$
|
|
777
|
+
const verbose = program$i.opts().verbose;
|
|
760
778
|
const { token, region } = options;
|
|
761
779
|
const { state, updateSession, persistCredentials, initializeSession } = session();
|
|
762
780
|
await initializeSession();
|
|
@@ -875,10 +893,10 @@ program$f.command(commands.LOGIN).description("Login to the Storyblok CLI").opti
|
|
|
875
893
|
konsola.br();
|
|
876
894
|
});
|
|
877
895
|
|
|
878
|
-
const program$
|
|
879
|
-
program$
|
|
896
|
+
const program$h = getProgram();
|
|
897
|
+
program$h.command(commands.LOGOUT).description("Logout from the Storyblok CLI").action(async () => {
|
|
880
898
|
konsola.title(` ${commands.LOGOUT} `, colorPalette.LOGOUT);
|
|
881
|
-
const verbose = program$
|
|
899
|
+
const verbose = program$h.opts().verbose;
|
|
882
900
|
try {
|
|
883
901
|
const { state, initializeSession } = session();
|
|
884
902
|
await initializeSession();
|
|
@@ -926,10 +944,10 @@ async function openSignupInBrowser(url) {
|
|
|
926
944
|
}
|
|
927
945
|
}
|
|
928
946
|
|
|
929
|
-
const program$
|
|
930
|
-
program$
|
|
947
|
+
const program$g = getProgram();
|
|
948
|
+
program$g.command(commands.SIGNUP).description("Sign up for Storyblok").action(async () => {
|
|
931
949
|
konsola.title(` ${commands.SIGNUP} `, colorPalette.SIGNUP);
|
|
932
|
-
const verbose = program$
|
|
950
|
+
const verbose = program$g.opts().verbose;
|
|
933
951
|
const { state, initializeSession } = session();
|
|
934
952
|
await initializeSession();
|
|
935
953
|
if (state.isLoggedIn && !state.envLogin) {
|
|
@@ -975,8 +993,8 @@ const getUser = async (token, region) => {
|
|
|
975
993
|
}
|
|
976
994
|
};
|
|
977
995
|
|
|
978
|
-
const program$
|
|
979
|
-
program$
|
|
996
|
+
const program$f = getProgram();
|
|
997
|
+
program$f.command(commands.USER).description("Get the current user").action(async () => {
|
|
980
998
|
konsola.title(` ${commands.USER} `, colorPalette.USER);
|
|
981
999
|
const { state, initializeSession } = session();
|
|
982
1000
|
await initializeSession();
|
|
@@ -1001,8 +1019,8 @@ program$c.command(commands.USER).description("Get the current user").action(asyn
|
|
|
1001
1019
|
konsola.br();
|
|
1002
1020
|
});
|
|
1003
1021
|
|
|
1004
|
-
const program$
|
|
1005
|
-
const componentsCommand = program$
|
|
1022
|
+
const program$e = getProgram();
|
|
1023
|
+
const componentsCommand = program$e.command("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");
|
|
1006
1024
|
|
|
1007
1025
|
let instance = null;
|
|
1008
1026
|
const createMapiClient = (options) => {
|
|
@@ -1053,14 +1071,18 @@ const createMapiClient = (options) => {
|
|
|
1053
1071
|
...fetchOptions
|
|
1054
1072
|
});
|
|
1055
1073
|
let data;
|
|
1056
|
-
|
|
1057
|
-
data =
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1074
|
+
if (res.status === 204 || res.headers.get("content-length") === "0") {
|
|
1075
|
+
data = null;
|
|
1076
|
+
} else {
|
|
1077
|
+
try {
|
|
1078
|
+
data = await res.json();
|
|
1079
|
+
} catch {
|
|
1080
|
+
throw new FetchError("Non-JSON response", {
|
|
1081
|
+
status: res.status,
|
|
1082
|
+
statusText: res.statusText,
|
|
1083
|
+
data: null
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1064
1086
|
}
|
|
1065
1087
|
options?.onResponse?.({
|
|
1066
1088
|
path,
|
|
@@ -1127,11 +1149,15 @@ const createMapiClient = (options) => {
|
|
|
1127
1149
|
const put = async (path, fetchOptions) => {
|
|
1128
1150
|
return request(path, { ...fetchOptions, method: "PUT" });
|
|
1129
1151
|
};
|
|
1152
|
+
const _delete = async (path, fetchOptions) => {
|
|
1153
|
+
return request(path, { ...fetchOptions, method: "DELETE" });
|
|
1154
|
+
};
|
|
1130
1155
|
instance = {
|
|
1131
1156
|
uuid: state.uuid,
|
|
1132
1157
|
get,
|
|
1133
1158
|
post,
|
|
1134
1159
|
put,
|
|
1160
|
+
delete: _delete,
|
|
1135
1161
|
dispose: () => {
|
|
1136
1162
|
instance = null;
|
|
1137
1163
|
}
|
|
@@ -1348,20 +1374,8 @@ const upsertComponentInternalTag = async (space, tag, existingId) => {
|
|
|
1348
1374
|
return await pushComponentInternalTag(space, tag);
|
|
1349
1375
|
}
|
|
1350
1376
|
};
|
|
1351
|
-
async function readJsonFile(filePath) {
|
|
1352
|
-
try {
|
|
1353
|
-
const content = (await readFile$1(filePath)).toString();
|
|
1354
|
-
if (!content) {
|
|
1355
|
-
return { data: [] };
|
|
1356
|
-
}
|
|
1357
|
-
const parsed = JSON.parse(content);
|
|
1358
|
-
return { data: Array.isArray(parsed) ? parsed : [parsed] };
|
|
1359
|
-
} catch (error) {
|
|
1360
|
-
return { data: [], error };
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
1377
|
const readComponentsFiles = async (options) => {
|
|
1364
|
-
const { from, path, separateFiles = false, suffix
|
|
1378
|
+
const { from, path, separateFiles = false, suffix } = options;
|
|
1365
1379
|
const resolvedPath = resolvePath(path, `components/${from}`);
|
|
1366
1380
|
try {
|
|
1367
1381
|
await readdir(resolvedPath);
|
|
@@ -1372,7 +1386,7 @@ const readComponentsFiles = async (options) => {
|
|
|
1372
1386
|
${chalk.cyan(`storyblok components pull --space ${from}`)}
|
|
1373
1387
|
|
|
1374
1388
|
2. Then try pushing again:
|
|
1375
|
-
${chalk.cyan(`storyblok components push --space
|
|
1389
|
+
${chalk.cyan(`storyblok components push --space <target_space> --from ${from}`)}`;
|
|
1376
1390
|
throw new FileSystemError(
|
|
1377
1391
|
"file_not_found",
|
|
1378
1392
|
"read",
|
|
@@ -1381,11 +1395,11 @@ const readComponentsFiles = async (options) => {
|
|
|
1381
1395
|
);
|
|
1382
1396
|
}
|
|
1383
1397
|
if (separateFiles) {
|
|
1384
|
-
return await readSeparateFiles(resolvedPath, suffix);
|
|
1398
|
+
return await readSeparateFiles$1(resolvedPath, suffix);
|
|
1385
1399
|
}
|
|
1386
|
-
return await readConsolidatedFiles(resolvedPath, suffix);
|
|
1400
|
+
return await readConsolidatedFiles$1(resolvedPath, suffix);
|
|
1387
1401
|
};
|
|
1388
|
-
async function readSeparateFiles(resolvedPath, suffix) {
|
|
1402
|
+
async function readSeparateFiles$1(resolvedPath, suffix) {
|
|
1389
1403
|
const files = await readdir(resolvedPath);
|
|
1390
1404
|
const components = [];
|
|
1391
1405
|
const presets = [];
|
|
@@ -1440,7 +1454,7 @@ async function readSeparateFiles(resolvedPath, suffix) {
|
|
|
1440
1454
|
internalTags
|
|
1441
1455
|
};
|
|
1442
1456
|
}
|
|
1443
|
-
async function readConsolidatedFiles(resolvedPath, suffix) {
|
|
1457
|
+
async function readConsolidatedFiles$1(resolvedPath, suffix) {
|
|
1444
1458
|
const componentsPath = join(resolvedPath, suffix ? `components.${suffix}.json` : "components.json");
|
|
1445
1459
|
const componentsResult = await readJsonFile(componentsPath);
|
|
1446
1460
|
if (componentsResult.error || !componentsResult.data.length) {
|
|
@@ -1464,10 +1478,10 @@ async function readConsolidatedFiles(resolvedPath, suffix) {
|
|
|
1464
1478
|
};
|
|
1465
1479
|
}
|
|
1466
1480
|
|
|
1467
|
-
const program$
|
|
1481
|
+
const program$d = getProgram();
|
|
1468
1482
|
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) => {
|
|
1469
1483
|
konsola.title(` ${commands.COMPONENTS} `, colorPalette.COMPONENTS, componentName ? `Pulling component ${componentName}...` : "Pulling components...");
|
|
1470
|
-
const verbose = program$
|
|
1484
|
+
const verbose = program$d.opts().verbose;
|
|
1471
1485
|
const { space, path } = componentsCommand.opts();
|
|
1472
1486
|
const { separateFiles, suffix, filename = "components" } = options;
|
|
1473
1487
|
const { state, initializeSession } = session();
|
|
@@ -1555,6 +1569,153 @@ componentsCommand.command("pull [componentName]").option("-f, --filename <filena
|
|
|
1555
1569
|
}
|
|
1556
1570
|
});
|
|
1557
1571
|
|
|
1572
|
+
const pushDatasource = async (space, datasource) => {
|
|
1573
|
+
try {
|
|
1574
|
+
const client = mapiClient();
|
|
1575
|
+
const { data } = await client.post(`spaces/${space}/datasources`, {
|
|
1576
|
+
body: JSON.stringify(datasource)
|
|
1577
|
+
});
|
|
1578
|
+
return data.datasource;
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
handleAPIError("push_datasource", error, `Failed to push datasource ${datasource.name}`);
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
const updateDatasource = async (space, datasourceId, datasource) => {
|
|
1584
|
+
try {
|
|
1585
|
+
const client = mapiClient();
|
|
1586
|
+
const { data } = await client.put(`spaces/${space}/datasources/${datasourceId}`, {
|
|
1587
|
+
body: JSON.stringify(datasource)
|
|
1588
|
+
});
|
|
1589
|
+
return data.datasource;
|
|
1590
|
+
} catch (error) {
|
|
1591
|
+
handleAPIError("update_datasource", error, `Failed to update datasource ${datasource.name}`);
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
const upsertDatasource = async (space, datasource, existingId) => {
|
|
1595
|
+
if (existingId) {
|
|
1596
|
+
return await updateDatasource(space, existingId, datasource);
|
|
1597
|
+
} else {
|
|
1598
|
+
return await pushDatasource(space, datasource);
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
const pushDatasourceEntry = async (space, datasourceId, entry) => {
|
|
1602
|
+
try {
|
|
1603
|
+
const client = mapiClient();
|
|
1604
|
+
const { data } = await client.post(`spaces/${space}/datasource_entries`, {
|
|
1605
|
+
body: JSON.stringify({
|
|
1606
|
+
datasource_entry: {
|
|
1607
|
+
...entry,
|
|
1608
|
+
datasource_id: datasourceId
|
|
1609
|
+
}
|
|
1610
|
+
})
|
|
1611
|
+
});
|
|
1612
|
+
return data.datasource_entry;
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
handleAPIError("push_datasource", error, `Failed to push datasource entry ${entry.name}`);
|
|
1615
|
+
}
|
|
1616
|
+
};
|
|
1617
|
+
const updateDatasourceEntry = async (space, entryId, entry) => {
|
|
1618
|
+
try {
|
|
1619
|
+
const client = mapiClient();
|
|
1620
|
+
await client.put(`spaces/${space}/datasource_entries/${entryId}`, {
|
|
1621
|
+
body: JSON.stringify({
|
|
1622
|
+
datasource_entry: entry
|
|
1623
|
+
})
|
|
1624
|
+
});
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
handleAPIError("update_datasource", error, `Failed to update datasource entry ${entry.name}`);
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
const upsertDatasourceEntry = async (space, datasourceId, entry, existingId) => {
|
|
1630
|
+
if (existingId) {
|
|
1631
|
+
await updateDatasourceEntry(space, existingId, entry);
|
|
1632
|
+
return void 0;
|
|
1633
|
+
} else {
|
|
1634
|
+
return await pushDatasourceEntry(space, datasourceId, entry);
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
const readDatasourcesFiles = async (options) => {
|
|
1638
|
+
const { from, path, separateFiles = false, suffix, space } = options;
|
|
1639
|
+
const resolvedPath = resolvePath(path, `datasources/${from}`);
|
|
1640
|
+
try {
|
|
1641
|
+
await readdir(resolvedPath);
|
|
1642
|
+
} catch (error) {
|
|
1643
|
+
const message = `No local datasources found for space ${chalk.bold(from)}. To push datasources, you need to pull them first:
|
|
1644
|
+
|
|
1645
|
+
1. Pull the datasources from your source space:
|
|
1646
|
+
${chalk.cyan(`storyblok datasources pull --space ${from}`)}
|
|
1647
|
+
|
|
1648
|
+
2. Then try pushing again:
|
|
1649
|
+
${chalk.cyan(`storyblok datasources push --space ${space} --from ${from}`)}`;
|
|
1650
|
+
throw new FileSystemError(
|
|
1651
|
+
"file_not_found",
|
|
1652
|
+
"read",
|
|
1653
|
+
error,
|
|
1654
|
+
message
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
if (separateFiles) {
|
|
1658
|
+
return await readSeparateFiles(resolvedPath, suffix);
|
|
1659
|
+
}
|
|
1660
|
+
return await readConsolidatedFiles(resolvedPath, suffix);
|
|
1661
|
+
};
|
|
1662
|
+
async function readSeparateFiles(resolvedPath, suffix) {
|
|
1663
|
+
const files = await readdir(resolvedPath);
|
|
1664
|
+
const datasources = [];
|
|
1665
|
+
const filteredFiles = files.filter((file) => {
|
|
1666
|
+
if (suffix) {
|
|
1667
|
+
return file.endsWith(`.${suffix}.json`);
|
|
1668
|
+
} else {
|
|
1669
|
+
return !/\.\w+\.json$/.test(file);
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
for (const file of filteredFiles) {
|
|
1673
|
+
const filePath = join(resolvedPath, file);
|
|
1674
|
+
if (file.endsWith(".json") || file.endsWith(`${suffix}.json`)) {
|
|
1675
|
+
if (file === "datasources.json" || /^datasources\.\w+\.json$/.test(file)) {
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
const result = await readJsonFile(filePath);
|
|
1679
|
+
if (result.error) {
|
|
1680
|
+
handleFileSystemError("read", result.error);
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
datasources.push(...result.data);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
return {
|
|
1687
|
+
datasources
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
async function readConsolidatedFiles(resolvedPath, suffix) {
|
|
1691
|
+
const datasourcesPath = join(resolvedPath, suffix ? `datasources.${suffix}.json` : "datasources.json");
|
|
1692
|
+
const datasourcesResult = await readJsonFile(datasourcesPath);
|
|
1693
|
+
if (datasourcesResult.error || !datasourcesResult.data.length) {
|
|
1694
|
+
throw new FileSystemError(
|
|
1695
|
+
"file_not_found",
|
|
1696
|
+
"read",
|
|
1697
|
+
datasourcesResult.error || new Error("Datasources file is empty"),
|
|
1698
|
+
`No datasources found in ${datasourcesPath}. Please make sure you have pulled the datasources first.`
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
return {
|
|
1702
|
+
datasources: datasourcesResult.data
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
function createStubDatasource(name) {
|
|
1707
|
+
return {
|
|
1708
|
+
id: 0,
|
|
1709
|
+
// Will be set by API
|
|
1710
|
+
name,
|
|
1711
|
+
slug: name,
|
|
1712
|
+
dimensions: [],
|
|
1713
|
+
entries: [],
|
|
1714
|
+
// Empty entries for stub
|
|
1715
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1716
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1558
1719
|
function buildDependencyGraph(context) {
|
|
1559
1720
|
const { spaceState } = context;
|
|
1560
1721
|
const graph = { nodes: /* @__PURE__ */ new Map() };
|
|
@@ -1566,6 +1727,22 @@ function buildDependencyGraph(context) {
|
|
|
1566
1727
|
dependency.dependents.add(dependentId);
|
|
1567
1728
|
}
|
|
1568
1729
|
}
|
|
1730
|
+
const referencedDatasources = /* @__PURE__ */ new Set();
|
|
1731
|
+
spaceState.local.components.forEach((component) => {
|
|
1732
|
+
if (component.schema) {
|
|
1733
|
+
const dependencies = collectWhitelistDependencies(component.schema);
|
|
1734
|
+
dependencies.datasourceNames.forEach((datasourceName) => {
|
|
1735
|
+
referencedDatasources.add(datasourceName);
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
referencedDatasources.forEach((datasourceName) => {
|
|
1740
|
+
const nodeId = `datasource:${datasourceName}`;
|
|
1741
|
+
const targetDatasource = spaceState.target.datasources?.get(datasourceName);
|
|
1742
|
+
const stubDatasource = createStubDatasource(datasourceName);
|
|
1743
|
+
const node = new DatasourceNode(nodeId, stubDatasource, targetDatasource);
|
|
1744
|
+
graph.nodes.set(nodeId, node);
|
|
1745
|
+
});
|
|
1569
1746
|
spaceState.local.internalTags.forEach((tag) => {
|
|
1570
1747
|
const nodeId = `tag:${tag.id}`;
|
|
1571
1748
|
const targetTag = spaceState.target.tags.get(tag.name);
|
|
@@ -1641,6 +1818,10 @@ function buildDependencyGraph(context) {
|
|
|
1641
1818
|
const dependencyId = `component:${componentName}`;
|
|
1642
1819
|
addDependency(componentId, dependencyId);
|
|
1643
1820
|
});
|
|
1821
|
+
dependencies.datasourceNames.forEach((datasourceName) => {
|
|
1822
|
+
const datasourceId = `datasource:${datasourceName}`;
|
|
1823
|
+
addDependency(componentId, datasourceId);
|
|
1824
|
+
});
|
|
1644
1825
|
}
|
|
1645
1826
|
});
|
|
1646
1827
|
spaceState.local.presets.forEach((preset) => {
|
|
@@ -1657,6 +1838,7 @@ function collectWhitelistDependencies(schema) {
|
|
|
1657
1838
|
const groupUuids = /* @__PURE__ */ new Set();
|
|
1658
1839
|
const tagIds = /* @__PURE__ */ new Set();
|
|
1659
1840
|
const componentNames = /* @__PURE__ */ new Set();
|
|
1841
|
+
const datasourceNames = /* @__PURE__ */ new Set();
|
|
1660
1842
|
function traverseField(field) {
|
|
1661
1843
|
if (field.type === "bloks") {
|
|
1662
1844
|
if (field.component_group_whitelist && Array.isArray(field.component_group_whitelist)) {
|
|
@@ -1669,6 +1851,11 @@ function collectWhitelistDependencies(schema) {
|
|
|
1669
1851
|
field.component_whitelist.forEach((name) => componentNames.add(name));
|
|
1670
1852
|
}
|
|
1671
1853
|
}
|
|
1854
|
+
if ((field.type === "option" || field.type === "options") && field.source === "internal") {
|
|
1855
|
+
if (field.datasource_slug && typeof field.datasource_slug === "string") {
|
|
1856
|
+
datasourceNames.add(field.datasource_slug);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1672
1859
|
Object.values(field).forEach((value) => {
|
|
1673
1860
|
if (Array.isArray(value)) {
|
|
1674
1861
|
value.forEach((item) => {
|
|
@@ -1686,7 +1873,7 @@ function collectWhitelistDependencies(schema) {
|
|
|
1686
1873
|
traverseField(field);
|
|
1687
1874
|
}
|
|
1688
1875
|
});
|
|
1689
|
-
return { groupUuids, tagIds, componentNames };
|
|
1876
|
+
return { groupUuids, tagIds, componentNames, datasourceNames };
|
|
1690
1877
|
}
|
|
1691
1878
|
function detectProblematicCycles(graph) {
|
|
1692
1879
|
const problematicCycles = [];
|
|
@@ -2017,6 +2204,15 @@ class ComponentNode extends GraphNode {
|
|
|
2017
2204
|
});
|
|
2018
2205
|
}
|
|
2019
2206
|
}
|
|
2207
|
+
if ((resolvedField.type === "option" || resolvedField.type === "options") && resolvedField.source === "internal") {
|
|
2208
|
+
if (resolvedField.datasource_slug && typeof resolvedField.datasource_slug === "string") {
|
|
2209
|
+
const datasourceNodeId = `datasource:${resolvedField.datasource_slug}`;
|
|
2210
|
+
const datasourceNode = graph.nodes.get(datasourceNodeId);
|
|
2211
|
+
if (datasourceNode?.targetData) {
|
|
2212
|
+
resolvedField.datasource_slug = datasourceNode.targetData.resource.slug;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2020
2216
|
Object.keys(resolvedField).forEach((key) => {
|
|
2021
2217
|
if (typeof resolvedField[key] === "object" && resolvedField[key] !== null) {
|
|
2022
2218
|
resolvedField[key] = resolveField(resolvedField[key]);
|
|
@@ -2088,6 +2284,23 @@ class PresetNode {
|
|
|
2088
2284
|
};
|
|
2089
2285
|
}
|
|
2090
2286
|
}
|
|
2287
|
+
class DatasourceNode extends GraphNode {
|
|
2288
|
+
constructor(id, data, targetDatasource) {
|
|
2289
|
+
super(id, "datasource", data.name, data, targetDatasource);
|
|
2290
|
+
}
|
|
2291
|
+
resolveReferences(_graph) {
|
|
2292
|
+
}
|
|
2293
|
+
async upsert(space) {
|
|
2294
|
+
const existingDatasource = this.targetData?.resource;
|
|
2295
|
+
const existingId = existingDatasource?.id;
|
|
2296
|
+
const { entries, ...datasourceDefinition } = this.sourceData;
|
|
2297
|
+
const result = await upsertDatasource(space, datasourceDefinition, existingId);
|
|
2298
|
+
if (!result) {
|
|
2299
|
+
throw new Error(`Failed to upsert datasource ${this.name}`);
|
|
2300
|
+
}
|
|
2301
|
+
return result;
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2091
2304
|
|
|
2092
2305
|
function collectAllDependencies(components, allComponents, allGroups, allTags) {
|
|
2093
2306
|
const requiredComponents = /* @__PURE__ */ new Set();
|
|
@@ -2488,10 +2701,67 @@ async function pushWithDependencyGraph(space, spaceState, maxConcurrency = 5) {
|
|
|
2488
2701
|
return results;
|
|
2489
2702
|
}
|
|
2490
2703
|
|
|
2491
|
-
const
|
|
2704
|
+
const fetchDatasourceEntries = async (space, datasourceId) => {
|
|
2705
|
+
try {
|
|
2706
|
+
const client = mapiClient();
|
|
2707
|
+
const { data } = await client.get(`spaces/${space}/datasource_entries?datasource_id=${datasourceId}`);
|
|
2708
|
+
return data.datasource_entries;
|
|
2709
|
+
} catch (error) {
|
|
2710
|
+
handleAPIError("pull_datasources", error);
|
|
2711
|
+
}
|
|
2712
|
+
};
|
|
2713
|
+
const fetchDatasources = async (space) => {
|
|
2714
|
+
try {
|
|
2715
|
+
const client = mapiClient();
|
|
2716
|
+
const { data } = await client.get(`spaces/${space}/datasources`);
|
|
2717
|
+
const datasources = data.datasources;
|
|
2718
|
+
const datasourcesWithEntries = await Promise.all(
|
|
2719
|
+
datasources.map(async (ds) => {
|
|
2720
|
+
const entries = await fetchDatasourceEntries(space, ds.id);
|
|
2721
|
+
return { ...ds, entries };
|
|
2722
|
+
})
|
|
2723
|
+
);
|
|
2724
|
+
return datasourcesWithEntries;
|
|
2725
|
+
} catch (error) {
|
|
2726
|
+
handleAPIError("pull_datasources", error);
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
const fetchDatasource = async (space, datasourceName) => {
|
|
2730
|
+
try {
|
|
2731
|
+
const client = mapiClient();
|
|
2732
|
+
const { data } = await client.get(`spaces/${space}/datasources?search=${encodeURIComponent(datasourceName)}`);
|
|
2733
|
+
const found = data.datasources?.find((d) => d.name === datasourceName);
|
|
2734
|
+
if (!found) {
|
|
2735
|
+
return void 0;
|
|
2736
|
+
}
|
|
2737
|
+
const entries = await fetchDatasourceEntries(space, found.id);
|
|
2738
|
+
return { ...found, entries };
|
|
2739
|
+
} catch (error) {
|
|
2740
|
+
handleAPIError("pull_datasources", error, `Failed to fetch datasource ${datasourceName}`);
|
|
2741
|
+
}
|
|
2742
|
+
};
|
|
2743
|
+
const saveDatasourcesToFiles = async (space, datasources, options) => {
|
|
2744
|
+
const { filename = "datasources", suffix, path, separateFiles } = options;
|
|
2745
|
+
const resolvedPath = path ? resolve(process.cwd(), path, "datasources", space) : resolvePath(path, `datasources/${space}`);
|
|
2746
|
+
try {
|
|
2747
|
+
if (separateFiles) {
|
|
2748
|
+
for (const datasource of datasources) {
|
|
2749
|
+
const datasourceFilePath = join(resolvedPath, suffix ? `${datasource.name}.${suffix}.json` : `${datasource.name}.json`);
|
|
2750
|
+
await saveToFile(datasourceFilePath, JSON.stringify(datasource, null, 2));
|
|
2751
|
+
}
|
|
2752
|
+
return;
|
|
2753
|
+
}
|
|
2754
|
+
const datasourcesFilePath = join(resolvedPath, suffix ? `${filename}.${suffix}.json` : `${filename}.json`);
|
|
2755
|
+
await saveToFile(datasourcesFilePath, JSON.stringify(datasources, null, 2));
|
|
2756
|
+
} catch (error) {
|
|
2757
|
+
handleFileSystemError("write", error);
|
|
2758
|
+
}
|
|
2759
|
+
};
|
|
2760
|
+
|
|
2761
|
+
const program$c = getProgram();
|
|
2492
2762
|
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").option("--su, --suffix <suffix>", "Suffix to add to the component name").action(async (componentName, options) => {
|
|
2493
2763
|
konsola.title(` ${commands.COMPONENTS} `, colorPalette.COMPONENTS, componentName ? `Pushing component ${componentName}...` : "Pushing components...");
|
|
2494
|
-
const verbose = program$
|
|
2764
|
+
const verbose = program$c.opts().verbose;
|
|
2495
2765
|
const { space, path } = componentsCommand.opts();
|
|
2496
2766
|
const { from, filter } = options;
|
|
2497
2767
|
const { state, initializeSession } = session();
|
|
@@ -2518,26 +2788,33 @@ componentsCommand.command("push [componentName]").description(`Push your space's
|
|
|
2518
2788
|
}
|
|
2519
2789
|
});
|
|
2520
2790
|
try {
|
|
2791
|
+
const componentsData = await readComponentsFiles({
|
|
2792
|
+
...options,
|
|
2793
|
+
path,
|
|
2794
|
+
space
|
|
2795
|
+
});
|
|
2796
|
+
const localData = {
|
|
2797
|
+
...componentsData,
|
|
2798
|
+
datasources: []
|
|
2799
|
+
};
|
|
2521
2800
|
const spaceState = {
|
|
2522
|
-
local:
|
|
2523
|
-
...options,
|
|
2524
|
-
path,
|
|
2525
|
-
space
|
|
2526
|
-
}),
|
|
2801
|
+
local: localData,
|
|
2527
2802
|
target: {
|
|
2528
2803
|
components: /* @__PURE__ */ new Map(),
|
|
2529
2804
|
groups: /* @__PURE__ */ new Map(),
|
|
2530
2805
|
tags: /* @__PURE__ */ new Map(),
|
|
2531
|
-
presets: /* @__PURE__ */ new Map()
|
|
2806
|
+
presets: /* @__PURE__ */ new Map(),
|
|
2807
|
+
datasources: /* @__PURE__ */ new Map()
|
|
2532
2808
|
}
|
|
2533
2809
|
};
|
|
2534
2810
|
const promises = [
|
|
2535
2811
|
fetchComponents(space),
|
|
2536
2812
|
fetchComponentGroups(space),
|
|
2537
2813
|
fetchComponentPresets(space),
|
|
2538
|
-
fetchComponentInternalTags(space)
|
|
2814
|
+
fetchComponentInternalTags(space),
|
|
2815
|
+
fetchDatasources(space)
|
|
2539
2816
|
];
|
|
2540
|
-
const [components, groups, presets, internalTags] = await Promise.all(promises);
|
|
2817
|
+
const [components, groups, presets, internalTags, datasources] = await Promise.all(promises);
|
|
2541
2818
|
if (components) {
|
|
2542
2819
|
components.forEach((component) => {
|
|
2543
2820
|
spaceState.target.components.set(component.name, component);
|
|
@@ -2562,6 +2839,11 @@ componentsCommand.command("push [componentName]").description(`Push your space's
|
|
|
2562
2839
|
spaceState.target.tags.set(tag.name, tag);
|
|
2563
2840
|
});
|
|
2564
2841
|
}
|
|
2842
|
+
if (datasources) {
|
|
2843
|
+
datasources.forEach((datasource) => {
|
|
2844
|
+
spaceState.target.datasources.set(datasource.name, datasource);
|
|
2845
|
+
});
|
|
2846
|
+
}
|
|
2565
2847
|
if (componentName) {
|
|
2566
2848
|
spaceState.local = filterSpaceDataByComponent(spaceState.local, componentName);
|
|
2567
2849
|
if (!spaceState.local.components.length) {
|
|
@@ -2633,11 +2915,11 @@ const saveLanguagesToFile = async (space, internationalizationOptions, options)
|
|
|
2633
2915
|
}
|
|
2634
2916
|
};
|
|
2635
2917
|
|
|
2636
|
-
const program$
|
|
2637
|
-
const languagesCommand = program$
|
|
2918
|
+
const program$b = getProgram();
|
|
2919
|
+
const languagesCommand = program$b.command("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");
|
|
2638
2920
|
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) => {
|
|
2639
2921
|
konsola.title(` ${commands.LANGUAGES} `, colorPalette.LANGUAGES);
|
|
2640
|
-
const verbose = program$
|
|
2922
|
+
const verbose = program$b.opts().verbose;
|
|
2641
2923
|
const { space, path } = languagesCommand.opts();
|
|
2642
2924
|
const { filename = "languages", suffix = options.space } = options;
|
|
2643
2925
|
const { state, initializeSession } = session();
|
|
@@ -2678,8 +2960,8 @@ languagesCommand.command("pull").description(`Download your space's languages sc
|
|
|
2678
2960
|
konsola.br();
|
|
2679
2961
|
});
|
|
2680
2962
|
|
|
2681
|
-
const program$
|
|
2682
|
-
const migrationsCommand = program$
|
|
2963
|
+
const program$a = getProgram();
|
|
2964
|
+
const migrationsCommand = program$a.command("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");
|
|
2683
2965
|
|
|
2684
2966
|
const getMigrationTemplate = () => {
|
|
2685
2967
|
return `export default function (block) {
|
|
@@ -2707,10 +2989,10 @@ const generateMigration = async (space, path, component, suffix) => {
|
|
|
2707
2989
|
}
|
|
2708
2990
|
};
|
|
2709
2991
|
|
|
2710
|
-
const program$
|
|
2992
|
+
const program$9 = getProgram();
|
|
2711
2993
|
migrationsCommand.command("generate [componentName]").description("Generate a migration file").option("--su, --suffix <suffix>", "suffix to add to the file name (e.g. {component-name}.<suffix>.js)").action(async (componentName, options) => {
|
|
2712
2994
|
konsola.title(` ${commands.MIGRATIONS} `, colorPalette.MIGRATIONS, componentName ? `Generating migration for component ${componentName}...` : "Generating migrations...");
|
|
2713
|
-
const verbose = program$
|
|
2995
|
+
const verbose = program$9.opts().verbose;
|
|
2714
2996
|
const { space, path } = migrationsCommand.opts();
|
|
2715
2997
|
const { suffix } = options;
|
|
2716
2998
|
if (!componentName) {
|
|
@@ -3126,10 +3408,10 @@ const isStoryWithUnpublishedChanges = (story) => {
|
|
|
3126
3408
|
return story.published && story.unpublished_changes;
|
|
3127
3409
|
};
|
|
3128
3410
|
|
|
3129
|
-
const program$
|
|
3411
|
+
const program$8 = getProgram();
|
|
3130
3412
|
migrationsCommand.command("run [componentName]").description("Run migrations").option("--fi, --filter <filter>", "glob filter to apply to the components before pushing").option("-d, --dry-run", "Preview changes without applying them to 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/"').option("--publish <publish>", "Options for publication mode: all | published | published-with-changes").action(async (componentName, options) => {
|
|
3131
3413
|
konsola.title(` ${commands.MIGRATIONS} `, colorPalette.MIGRATIONS, componentName ? `Running migrations for component ${componentName}...` : "Running migrations...");
|
|
3132
|
-
const verbose = program$
|
|
3414
|
+
const verbose = program$8.opts().verbose;
|
|
3133
3415
|
const { filter, dryRun = false, query, startsWith, publish } = options;
|
|
3134
3416
|
const { space, path } = migrationsCommand.opts();
|
|
3135
3417
|
const { state, initializeSession } = session();
|
|
@@ -3283,10 +3565,10 @@ migrationsCommand.command("run [componentName]").description("Run migrations").o
|
|
|
3283
3565
|
}
|
|
3284
3566
|
});
|
|
3285
3567
|
|
|
3286
|
-
const program$
|
|
3568
|
+
const program$7 = getProgram();
|
|
3287
3569
|
migrationsCommand.command("rollback [migrationFile]").description("Rollback a migration").action(async (migrationFile) => {
|
|
3288
3570
|
konsola.title(` ${commands.MIGRATIONS} `, colorPalette.MIGRATIONS, `Rolling back migration ${chalk.hex(colorPalette.MIGRATIONS)(migrationFile)}...`);
|
|
3289
|
-
const verbose = program$
|
|
3571
|
+
const verbose = program$7.opts().verbose;
|
|
3290
3572
|
const { space, path } = migrationsCommand.opts();
|
|
3291
3573
|
const { state, initializeSession } = session();
|
|
3292
3574
|
await initializeSession();
|
|
@@ -3325,8 +3607,8 @@ migrationsCommand.command("rollback [migrationFile]").description("Rollback a mi
|
|
|
3325
3607
|
}
|
|
3326
3608
|
});
|
|
3327
3609
|
|
|
3328
|
-
const program$
|
|
3329
|
-
const typesCommand = program$
|
|
3610
|
+
const program$6 = getProgram();
|
|
3611
|
+
const typesCommand = program$6.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");
|
|
3330
3612
|
|
|
3331
3613
|
const getAssetJSONSchema = (title) => ({
|
|
3332
3614
|
$id: "#/asset",
|
|
@@ -4025,10 +4307,10 @@ const generateStoryblokTypes = async (options = {}) => {
|
|
|
4025
4307
|
}
|
|
4026
4308
|
};
|
|
4027
4309
|
|
|
4028
|
-
const program$
|
|
4310
|
+
const program$5 = getProgram();
|
|
4029
4311
|
typesCommand.command("generate").description("Generate types d.ts for your component schemas").option("--sf, --separate-files", "").option("--strict", "strict mode, no loose typing").option("--type-prefix <prefix>", "prefix to be prepended 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) => {
|
|
4030
4312
|
konsola.title(` ${commands.TYPES} `, colorPalette.TYPES, "Generating types...");
|
|
4031
|
-
const verbose = program$
|
|
4313
|
+
const verbose = program$5.opts().verbose;
|
|
4032
4314
|
const { space, path } = typesCommand.opts();
|
|
4033
4315
|
const spinner = new Spinner({
|
|
4034
4316
|
verbose: !isVitest
|
|
@@ -4044,7 +4326,11 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
|
|
|
4044
4326
|
...options,
|
|
4045
4327
|
path
|
|
4046
4328
|
});
|
|
4047
|
-
const
|
|
4329
|
+
const spaceDataWithDatasources = {
|
|
4330
|
+
...spaceData,
|
|
4331
|
+
datasources: []
|
|
4332
|
+
};
|
|
4333
|
+
const typedefString = await generateTypes(spaceDataWithDatasources, {
|
|
4048
4334
|
...options,
|
|
4049
4335
|
path
|
|
4050
4336
|
});
|
|
@@ -4064,6 +4350,271 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
|
|
|
4064
4350
|
}
|
|
4065
4351
|
});
|
|
4066
4352
|
|
|
4353
|
+
const program$4 = getProgram();
|
|
4354
|
+
const datasourcesCommand = program$4.command("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");
|
|
4355
|
+
|
|
4356
|
+
const program$3 = getProgram();
|
|
4357
|
+
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) => {
|
|
4358
|
+
konsola.title(` ${commands.DATASOURCES} `, colorPalette.DATASOURCES, datasourceName ? `Pulling datasource ${datasourceName}...` : "Pulling datasources...");
|
|
4359
|
+
const verbose = program$3.opts().verbose;
|
|
4360
|
+
const { space, path } = datasourcesCommand.opts();
|
|
4361
|
+
const { separateFiles, suffix, filename = "datasources" } = options;
|
|
4362
|
+
const { state, initializeSession } = session();
|
|
4363
|
+
await initializeSession();
|
|
4364
|
+
if (!requireAuthentication(state, verbose)) {
|
|
4365
|
+
return;
|
|
4366
|
+
}
|
|
4367
|
+
if (!space) {
|
|
4368
|
+
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
|
|
4369
|
+
return;
|
|
4370
|
+
}
|
|
4371
|
+
const { password, region } = state;
|
|
4372
|
+
mapiClient({
|
|
4373
|
+
token: password,
|
|
4374
|
+
region
|
|
4375
|
+
});
|
|
4376
|
+
const spinnerDatasources = new Spinner({
|
|
4377
|
+
verbose: !isVitest
|
|
4378
|
+
});
|
|
4379
|
+
try {
|
|
4380
|
+
spinnerDatasources.start(`Fetching ${chalk.hex(colorPalette.DATASOURCES)("datasources")}`);
|
|
4381
|
+
let datasources;
|
|
4382
|
+
if (datasourceName) {
|
|
4383
|
+
const datasource = await fetchDatasource(space, datasourceName);
|
|
4384
|
+
if (!datasource) {
|
|
4385
|
+
konsola.warn(`No datasource found with name "${datasourceName}"`);
|
|
4386
|
+
return;
|
|
4387
|
+
}
|
|
4388
|
+
datasources = [datasource];
|
|
4389
|
+
} else {
|
|
4390
|
+
datasources = await fetchDatasources(space);
|
|
4391
|
+
if (!datasources || datasources.length === 0) {
|
|
4392
|
+
konsola.warn(`No datasources found in the space ${space}`);
|
|
4393
|
+
return;
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
spinnerDatasources.succeed(`${chalk.hex(colorPalette.DATASOURCES)("Datasources")} - Completed in ${spinnerDatasources.elapsedTime.toFixed(2)}ms`);
|
|
4397
|
+
await saveDatasourcesToFiles(
|
|
4398
|
+
space,
|
|
4399
|
+
datasources,
|
|
4400
|
+
{ ...options, path, separateFiles: separateFiles || !!datasourceName }
|
|
4401
|
+
);
|
|
4402
|
+
konsola.br();
|
|
4403
|
+
if (separateFiles) {
|
|
4404
|
+
if (filename !== "datasources") {
|
|
4405
|
+
konsola.warn(`The --filename option is ignored when using --separate-files`);
|
|
4406
|
+
}
|
|
4407
|
+
const filePath = path ? `${path}/datasources/${space}/` : `.storyblok/datasources/${space}/`;
|
|
4408
|
+
konsola.ok(`Datasources downloaded successfully to ${chalk.hex(colorPalette.PRIMARY)(filePath)}`);
|
|
4409
|
+
} else if (datasourceName) {
|
|
4410
|
+
const fileName = suffix ? `${filename}.${suffix}.json` : `${datasourceName}.json`;
|
|
4411
|
+
const filePath = path ? `${path}/datasources/${space}/${fileName}` : `.storyblok/datasources/${space}/${fileName}`;
|
|
4412
|
+
konsola.ok(`Datasource ${chalk.hex(colorPalette.PRIMARY)(datasourceName)} downloaded successfully in ${chalk.hex(colorPalette.PRIMARY)(filePath)}`);
|
|
4413
|
+
} else {
|
|
4414
|
+
const fileName = suffix ? `${filename}.${suffix}.json` : `${filename}.json`;
|
|
4415
|
+
const filePath = path ? `${path}/datasources/${space}/${fileName}` : `.storyblok/datasources/${space}/${fileName}`;
|
|
4416
|
+
konsola.ok(`Datasources downloaded successfully to ${chalk.hex(colorPalette.PRIMARY)(filePath)}`);
|
|
4417
|
+
}
|
|
4418
|
+
konsola.br();
|
|
4419
|
+
} catch (error) {
|
|
4420
|
+
spinnerDatasources.failed(`Fetching ${chalk.hex(colorPalette.DATASOURCES)("Datasources")} - Failed`);
|
|
4421
|
+
konsola.br();
|
|
4422
|
+
handleError(error, verbose);
|
|
4423
|
+
}
|
|
4424
|
+
});
|
|
4425
|
+
|
|
4426
|
+
const program$2 = getProgram();
|
|
4427
|
+
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) => {
|
|
4428
|
+
konsola.title(` ${commands.DATASOURCES} `, colorPalette.DATASOURCES, datasourceName ? `Pushing datasource ${datasourceName}...` : "Pushing datasources...");
|
|
4429
|
+
const verbose = program$2.opts().verbose;
|
|
4430
|
+
const { space, path } = datasourcesCommand.opts();
|
|
4431
|
+
const { from, filter } = options;
|
|
4432
|
+
const { state, initializeSession } = session();
|
|
4433
|
+
await initializeSession();
|
|
4434
|
+
if (!requireAuthentication(state, verbose)) {
|
|
4435
|
+
return;
|
|
4436
|
+
}
|
|
4437
|
+
if (!space) {
|
|
4438
|
+
handleError(new CommandError(`Please provide the target space as argument --space TARGET_SPACE_ID.`), verbose);
|
|
4439
|
+
return;
|
|
4440
|
+
}
|
|
4441
|
+
if (!from) {
|
|
4442
|
+
options.from = space;
|
|
4443
|
+
}
|
|
4444
|
+
konsola.info(`Attempting to push datasources ${chalk.bold("from")} space ${chalk.hex(colorPalette.DATASOURCES)(options.from || space)} ${chalk.bold("to")} ${chalk.hex(colorPalette.DATASOURCES)(space)}`);
|
|
4445
|
+
konsola.br();
|
|
4446
|
+
const { password, region } = state;
|
|
4447
|
+
mapiClient({
|
|
4448
|
+
token: password,
|
|
4449
|
+
region
|
|
4450
|
+
});
|
|
4451
|
+
try {
|
|
4452
|
+
const spaceState = {
|
|
4453
|
+
local: await readDatasourcesFiles({
|
|
4454
|
+
...options,
|
|
4455
|
+
path,
|
|
4456
|
+
space
|
|
4457
|
+
}),
|
|
4458
|
+
target: {
|
|
4459
|
+
datasources: /* @__PURE__ */ new Map()
|
|
4460
|
+
}
|
|
4461
|
+
};
|
|
4462
|
+
const targetSpaceDatasources = await fetchDatasources(space);
|
|
4463
|
+
if (targetSpaceDatasources) {
|
|
4464
|
+
targetSpaceDatasources.forEach((datasource) => {
|
|
4465
|
+
spaceState.target.datasources.set(datasource.name, datasource);
|
|
4466
|
+
});
|
|
4467
|
+
}
|
|
4468
|
+
if (datasourceName) {
|
|
4469
|
+
spaceState.local = {
|
|
4470
|
+
datasources: [spaceState.local.datasources.find((datasource) => datasource.name === datasourceName) || []]
|
|
4471
|
+
};
|
|
4472
|
+
if (!spaceState.local.datasources.length) {
|
|
4473
|
+
handleError(new CommandError(`Datasource "${datasourceName}" not found.`), verbose);
|
|
4474
|
+
return;
|
|
4475
|
+
}
|
|
4476
|
+
} else if (filter) {
|
|
4477
|
+
spaceState.local.datasources = spaceState.local.datasources.filter((datasource) => datasource.name.includes(filter));
|
|
4478
|
+
if (!spaceState.local.datasources.length) {
|
|
4479
|
+
handleError(new CommandError(`No datasources found matching pattern "${filter}".`), verbose);
|
|
4480
|
+
return;
|
|
4481
|
+
}
|
|
4482
|
+
konsola.info(`Filter applied: ${filter}`);
|
|
4483
|
+
}
|
|
4484
|
+
if (!spaceState.local.datasources.length) {
|
|
4485
|
+
konsola.warn("No datasources found. Please make sure you have pulled the datasources first.");
|
|
4486
|
+
return;
|
|
4487
|
+
}
|
|
4488
|
+
const results = {
|
|
4489
|
+
successful: [],
|
|
4490
|
+
failed: []
|
|
4491
|
+
};
|
|
4492
|
+
for (const datasource of spaceState.local.datasources) {
|
|
4493
|
+
const spinner = new Spinner({
|
|
4494
|
+
verbose: !isVitest
|
|
4495
|
+
});
|
|
4496
|
+
spinner.start(`Pushing ${chalk.hex(colorPalette.DATASOURCES)(datasource.name)}`);
|
|
4497
|
+
const existingDatasource = spaceState.target.datasources.get(datasource.name);
|
|
4498
|
+
const existingId = existingDatasource?.id;
|
|
4499
|
+
const { entries, ...datasourceDefinition } = datasource;
|
|
4500
|
+
const result = await upsertDatasource(space, datasourceDefinition, existingId);
|
|
4501
|
+
if (result) {
|
|
4502
|
+
results.successful.push(datasource.name);
|
|
4503
|
+
if (entries && entries.length > 0) {
|
|
4504
|
+
for (const entry of entries) {
|
|
4505
|
+
const existingEntryId = existingDatasource?.entries?.find((e) => e.name === entry.name)?.id;
|
|
4506
|
+
try {
|
|
4507
|
+
const { id, ...entryData } = entry;
|
|
4508
|
+
await upsertDatasourceEntry(space, result.id, entryData, existingEntryId);
|
|
4509
|
+
} catch (entryError) {
|
|
4510
|
+
results.failed.push({ name: datasource.name, error: entryError });
|
|
4511
|
+
spinner.failed(`${chalk.hex(colorPalette.DATASOURCES)(datasource.name)} - Failed in ${spinner.elapsedTime.toFixed(2)}ms`);
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
spinner.succeed(`${chalk.hex(colorPalette.DATASOURCES)(datasource.name)} - Completed in ${spinner.elapsedTime.toFixed(2)}ms`);
|
|
4516
|
+
} else {
|
|
4517
|
+
results.failed.push({ name: datasource.name, error: result });
|
|
4518
|
+
spinner.failed(`${chalk.hex(colorPalette.DATASOURCES)(datasource.name)} - Failed in ${spinner.elapsedTime.toFixed(2)}ms`);
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
if (results.failed.length > 0) {
|
|
4522
|
+
if (!verbose) {
|
|
4523
|
+
konsola.br();
|
|
4524
|
+
konsola.info("For more information about the error, run the command with the `--verbose` flag");
|
|
4525
|
+
} else {
|
|
4526
|
+
results.failed.forEach((failed) => {
|
|
4527
|
+
handleError(failed.error, verbose);
|
|
4528
|
+
});
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
} catch (error) {
|
|
4532
|
+
handleError(error, verbose);
|
|
4533
|
+
}
|
|
4534
|
+
});
|
|
4535
|
+
|
|
4536
|
+
async function deleteDatasource(space, id) {
|
|
4537
|
+
try {
|
|
4538
|
+
const client = mapiClient();
|
|
4539
|
+
await client.delete(`spaces/${space}/datasources/${id}`);
|
|
4540
|
+
} catch (error) {
|
|
4541
|
+
handleAPIError("delete_datasource", error, `Datasource with id '${id}' not found in space ${space}.`);
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4545
|
+
datasourcesCommand.command("delete [name]").description("Delete a datasource from your space by name or id").option("--id <id>", "Delete by datasource id instead of name").option("--force", "Skip confirmation prompt for deletion (useful for CI)").action(async (name, options) => {
|
|
4546
|
+
konsola.title(
|
|
4547
|
+
` ${commands.DATASOURCES} `,
|
|
4548
|
+
colorPalette.DATASOURCES,
|
|
4549
|
+
options.id ? `Deleting datasource with id ${options.id}...` : `Deleting datasource with name ${name}...`
|
|
4550
|
+
);
|
|
4551
|
+
if (name && options.id) {
|
|
4552
|
+
konsola.warn(
|
|
4553
|
+
"Both a datasource name and an id were provided. Only one is required. The id will be used as the source of truth."
|
|
4554
|
+
);
|
|
4555
|
+
}
|
|
4556
|
+
const { space } = datasourcesCommand.opts();
|
|
4557
|
+
const verbose = datasourcesCommand.parent?.opts().verbose;
|
|
4558
|
+
const { state, initializeSession } = session();
|
|
4559
|
+
await initializeSession();
|
|
4560
|
+
if (!requireAuthentication(state, verbose)) {
|
|
4561
|
+
return;
|
|
4562
|
+
}
|
|
4563
|
+
if (!space) {
|
|
4564
|
+
handleError(new CommandError("Please provide the space as argument --space YOUR_SPACE_ID."), verbose);
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
4567
|
+
const { password, region } = state;
|
|
4568
|
+
mapiClient({
|
|
4569
|
+
token: password,
|
|
4570
|
+
region
|
|
4571
|
+
});
|
|
4572
|
+
const spinner = new Spinner({
|
|
4573
|
+
verbose: !isVitest
|
|
4574
|
+
});
|
|
4575
|
+
try {
|
|
4576
|
+
if (options.id) {
|
|
4577
|
+
spinner.start(`Deleting datasource...`);
|
|
4578
|
+
await deleteDatasource(space, options.id);
|
|
4579
|
+
spinner.succeed();
|
|
4580
|
+
konsola.ok(`Datasource ${chalk.hex(colorPalette.DATASOURCES)(options.id)} deleted successfully from space ${space}.`);
|
|
4581
|
+
} else {
|
|
4582
|
+
const datasource = await fetchDatasource(space, name);
|
|
4583
|
+
if (!datasource) {
|
|
4584
|
+
throw new CommandError(`Datasource with name '${name}' not found in space ${space}.`);
|
|
4585
|
+
}
|
|
4586
|
+
if (!options.force) {
|
|
4587
|
+
konsola.info(`Datasource details:`);
|
|
4588
|
+
console.log(` Name: ${chalk.hex(colorPalette.DATASOURCES)(datasource.name)}`);
|
|
4589
|
+
console.log(` ID: ${chalk.hex(colorPalette.DATASOURCES)(datasource.id)}`);
|
|
4590
|
+
console.log(` Space: ${chalk.hex(colorPalette.DATASOURCES)(space)}`);
|
|
4591
|
+
console.log(` Slug: ${chalk.hex(colorPalette.DATASOURCES)(datasource.slug)}`);
|
|
4592
|
+
console.log(` Created at: ${chalk.hex(colorPalette.DATASOURCES)(datasource.created_at)}`);
|
|
4593
|
+
console.log(` Updated at: ${chalk.hex(colorPalette.DATASOURCES)(datasource.updated_at)}`);
|
|
4594
|
+
konsola.br();
|
|
4595
|
+
const confirmed = await confirm({
|
|
4596
|
+
message: `\u26A0\uFE0F ${chalk.yellow(` Are you sure you want to delete the ${datasource.name} datasource from space ${space}? This action cannot be undone.`)} `,
|
|
4597
|
+
default: false
|
|
4598
|
+
});
|
|
4599
|
+
if (!confirmed) {
|
|
4600
|
+
spinner.failed("Deletion aborted by user.");
|
|
4601
|
+
konsola.warn("Deletion aborted by user.");
|
|
4602
|
+
return;
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
spinner.start(`Deleting datasource...`);
|
|
4606
|
+
await deleteDatasource(space, datasource.id.toString());
|
|
4607
|
+
spinner.succeed();
|
|
4608
|
+
konsola.ok(`Datasource ${chalk.hex(colorPalette.DATASOURCES)(name)} deleted successfully from space ${space}.`);
|
|
4609
|
+
}
|
|
4610
|
+
} catch (error) {
|
|
4611
|
+
spinner.failed(
|
|
4612
|
+
`Failed to delete datasource ${chalk.hex(colorPalette.DATASOURCES)(options.id ? options.id : name)}`
|
|
4613
|
+
);
|
|
4614
|
+
handleError(error, verbose);
|
|
4615
|
+
}
|
|
4616
|
+
});
|
|
4617
|
+
|
|
4067
4618
|
let octokit;
|
|
4068
4619
|
let lastToken;
|
|
4069
4620
|
const createOctokit = (token) => {
|
|
@@ -4321,7 +4872,7 @@ program$1.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
|
|
|
4321
4872
|
konsola.br();
|
|
4322
4873
|
});
|
|
4323
4874
|
|
|
4324
|
-
const version = "4.1
|
|
4875
|
+
const version = "4.2.1";
|
|
4325
4876
|
const pkg = {
|
|
4326
4877
|
version: version};
|
|
4327
4878
|
|