storyblok 4.0.5 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +313 -49
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -4
package/dist/index.mjs
CHANGED
|
@@ -7,24 +7,27 @@ import { Command } from 'commander';
|
|
|
7
7
|
import { readPackageUp } from 'read-package-up';
|
|
8
8
|
import { Spinner } from '@topcli/spinner';
|
|
9
9
|
import { select, password, input } from '@inquirer/prompts';
|
|
10
|
-
import { mkdir, writeFile, readFile as readFile$1, access, readdir } from 'node:fs/promises';
|
|
11
|
-
import { join, parse, resolve } from 'node:path';
|
|
12
|
-
import { exec } from 'node:child_process';
|
|
10
|
+
import fs, { mkdir, writeFile, readFile as readFile$1, access, readdir } from 'node:fs/promises';
|
|
11
|
+
import path, { join, parse, resolve } from 'node:path';
|
|
12
|
+
import { exec, spawn } from 'node:child_process';
|
|
13
13
|
import { promisify } from 'node:util';
|
|
14
14
|
import { minimatch } from 'minimatch';
|
|
15
15
|
import { hash } from 'ohash';
|
|
16
16
|
import { compile } from 'json-schema-to-typescript';
|
|
17
17
|
import { readFileSync } from 'node:fs';
|
|
18
|
+
import open from 'open';
|
|
19
|
+
import { Octokit } from 'octokit';
|
|
18
20
|
|
|
19
21
|
const commands = {
|
|
20
22
|
LOGIN: "login",
|
|
21
23
|
LOGOUT: "logout",
|
|
22
24
|
SIGNUP: "signup",
|
|
23
25
|
USER: "user",
|
|
24
|
-
COMPONENTS: "
|
|
26
|
+
COMPONENTS: "components",
|
|
25
27
|
LANGUAGES: "languages",
|
|
26
|
-
MIGRATIONS: "
|
|
27
|
-
TYPES: "
|
|
28
|
+
MIGRATIONS: "migrations",
|
|
29
|
+
TYPES: "types",
|
|
30
|
+
CREATE: "create"
|
|
28
31
|
};
|
|
29
32
|
const colorPalette = {
|
|
30
33
|
PRIMARY: "#8d60ff",
|
|
@@ -36,6 +39,7 @@ const colorPalette = {
|
|
|
36
39
|
LANGUAGES: "#f5c003",
|
|
37
40
|
MIGRATIONS: "#8CE2FF",
|
|
38
41
|
TYPES: "#3178C6",
|
|
42
|
+
CREATE: "#ffb3ba",
|
|
39
43
|
GROUPS: "#4ade80",
|
|
40
44
|
TAGS: "#fbbf24",
|
|
41
45
|
PRESETS: "#a855f7"
|
|
@@ -54,6 +58,13 @@ const regionsDomain = {
|
|
|
54
58
|
ca: "api-ca.storyblok.com",
|
|
55
59
|
ap: "api-ap.storyblok.com"
|
|
56
60
|
};
|
|
61
|
+
const appDomains = {
|
|
62
|
+
eu: "app.storyblok.com",
|
|
63
|
+
us: "app-us.storyblok.com",
|
|
64
|
+
cn: "app.storyblokchina.cn",
|
|
65
|
+
ca: "app-ca.storyblok.com",
|
|
66
|
+
ap: "app-ap.storyblok.com"
|
|
67
|
+
};
|
|
57
68
|
const regionNames = {
|
|
58
69
|
eu: "Europe",
|
|
59
70
|
us: "United States",
|
|
@@ -159,7 +170,9 @@ const API_ACTIONS = {
|
|
|
159
170
|
update_component_preset: "Failed to update component preset",
|
|
160
171
|
pull_stories: "Failed to pull stories",
|
|
161
172
|
pull_story: "Failed to pull story",
|
|
162
|
-
update_story: "Failed to update story"
|
|
173
|
+
update_story: "Failed to update story",
|
|
174
|
+
create_space: "Failed to create space",
|
|
175
|
+
fetch_blueprints: "Failed to fetch blueprints from GitHub"
|
|
163
176
|
};
|
|
164
177
|
const API_ERRORS = {
|
|
165
178
|
unauthorized: "The user is not authorized to access the API",
|
|
@@ -388,6 +401,9 @@ const toCamelCase = (str) => {
|
|
|
388
401
|
const capitalize = (str) => {
|
|
389
402
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
390
403
|
};
|
|
404
|
+
const toHumanReadable = (str) => {
|
|
405
|
+
return str.replace(/[-_]/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ").replace(/\s+/g, " ").trim();
|
|
406
|
+
};
|
|
391
407
|
function maskToken(token) {
|
|
392
408
|
if (token.length <= 4) {
|
|
393
409
|
return token;
|
|
@@ -718,7 +734,7 @@ function session() {
|
|
|
718
734
|
return sessionInstance;
|
|
719
735
|
}
|
|
720
736
|
|
|
721
|
-
const program$
|
|
737
|
+
const program$f = getProgram();
|
|
722
738
|
const allRegionsText = Object.values(regions).join(",");
|
|
723
739
|
const loginStrategy = {
|
|
724
740
|
message: "How would you like to login?",
|
|
@@ -735,12 +751,12 @@ const loginStrategy = {
|
|
|
735
751
|
}
|
|
736
752
|
]
|
|
737
753
|
};
|
|
738
|
-
program$
|
|
754
|
+
program$f.command(commands.LOGIN).description("Login to the Storyblok CLI").option("-t, --token <token>", "Token to login directly without questions, like for CI environments").option(
|
|
739
755
|
"-r, --region <region>",
|
|
740
756
|
`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}.`
|
|
741
757
|
).action(async (options) => {
|
|
742
758
|
konsola.title(` ${commands.LOGIN} `, colorPalette.LOGIN);
|
|
743
|
-
const verbose = program$
|
|
759
|
+
const verbose = program$f.opts().verbose;
|
|
744
760
|
const { token, region } = options;
|
|
745
761
|
const { state, updateSession, persistCredentials, initializeSession } = session();
|
|
746
762
|
await initializeSession();
|
|
@@ -859,10 +875,10 @@ program$e.command(commands.LOGIN).description("Login to the Storyblok CLI").opti
|
|
|
859
875
|
konsola.br();
|
|
860
876
|
});
|
|
861
877
|
|
|
862
|
-
const program$
|
|
863
|
-
program$
|
|
878
|
+
const program$e = getProgram();
|
|
879
|
+
program$e.command(commands.LOGOUT).description("Logout from the Storyblok CLI").action(async () => {
|
|
864
880
|
konsola.title(` ${commands.LOGOUT} `, colorPalette.LOGOUT);
|
|
865
|
-
const verbose = program$
|
|
881
|
+
const verbose = program$e.opts().verbose;
|
|
866
882
|
try {
|
|
867
883
|
const { state, initializeSession } = session();
|
|
868
884
|
await initializeSession();
|
|
@@ -910,10 +926,10 @@ async function openSignupInBrowser(url) {
|
|
|
910
926
|
}
|
|
911
927
|
}
|
|
912
928
|
|
|
913
|
-
const program$
|
|
914
|
-
program$
|
|
929
|
+
const program$d = getProgram();
|
|
930
|
+
program$d.command(commands.SIGNUP).description("Sign up for Storyblok").action(async () => {
|
|
915
931
|
konsola.title(` ${commands.SIGNUP} `, colorPalette.SIGNUP);
|
|
916
|
-
const verbose = program$
|
|
932
|
+
const verbose = program$d.opts().verbose;
|
|
917
933
|
const { state, initializeSession } = session();
|
|
918
934
|
await initializeSession();
|
|
919
935
|
if (state.isLoggedIn && !state.envLogin) {
|
|
@@ -959,8 +975,8 @@ const getUser = async (token, region) => {
|
|
|
959
975
|
}
|
|
960
976
|
};
|
|
961
977
|
|
|
962
|
-
const program$
|
|
963
|
-
program$
|
|
978
|
+
const program$c = getProgram();
|
|
979
|
+
program$c.command(commands.USER).description("Get the current user").action(async () => {
|
|
964
980
|
konsola.title(` ${commands.USER} `, colorPalette.USER);
|
|
965
981
|
const { state, initializeSession } = session();
|
|
966
982
|
await initializeSession();
|
|
@@ -985,8 +1001,8 @@ program$b.command(commands.USER).description("Get the current user").action(asyn
|
|
|
985
1001
|
konsola.br();
|
|
986
1002
|
});
|
|
987
1003
|
|
|
988
|
-
const program$
|
|
989
|
-
const componentsCommand = program$
|
|
1004
|
+
const program$b = getProgram();
|
|
1005
|
+
const componentsCommand = program$b.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");
|
|
990
1006
|
|
|
991
1007
|
let instance = null;
|
|
992
1008
|
const createMapiClient = (options) => {
|
|
@@ -1448,10 +1464,10 @@ async function readConsolidatedFiles(resolvedPath, suffix) {
|
|
|
1448
1464
|
};
|
|
1449
1465
|
}
|
|
1450
1466
|
|
|
1451
|
-
const program$
|
|
1467
|
+
const program$a = getProgram();
|
|
1452
1468
|
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) => {
|
|
1453
1469
|
konsola.title(` ${commands.COMPONENTS} `, colorPalette.COMPONENTS, componentName ? `Pulling component ${componentName}...` : "Pulling components...");
|
|
1454
|
-
const verbose = program$
|
|
1470
|
+
const verbose = program$a.opts().verbose;
|
|
1455
1471
|
const { space, path } = componentsCommand.opts();
|
|
1456
1472
|
const { separateFiles, suffix, filename = "components" } = options;
|
|
1457
1473
|
const { state, initializeSession } = session();
|
|
@@ -2472,10 +2488,10 @@ async function pushWithDependencyGraph(space, spaceState, maxConcurrency = 5) {
|
|
|
2472
2488
|
return results;
|
|
2473
2489
|
}
|
|
2474
2490
|
|
|
2475
|
-
const program$
|
|
2491
|
+
const program$9 = getProgram();
|
|
2476
2492
|
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) => {
|
|
2477
2493
|
konsola.title(` ${commands.COMPONENTS} `, colorPalette.COMPONENTS, componentName ? `Pushing component ${componentName}...` : "Pushing components...");
|
|
2478
|
-
const verbose = program$
|
|
2494
|
+
const verbose = program$9.opts().verbose;
|
|
2479
2495
|
const { space, path } = componentsCommand.opts();
|
|
2480
2496
|
const { from, filter } = options;
|
|
2481
2497
|
const { state, initializeSession } = session();
|
|
@@ -2617,11 +2633,11 @@ const saveLanguagesToFile = async (space, internationalizationOptions, options)
|
|
|
2617
2633
|
}
|
|
2618
2634
|
};
|
|
2619
2635
|
|
|
2620
|
-
const program$
|
|
2621
|
-
const languagesCommand = program$
|
|
2636
|
+
const program$8 = getProgram();
|
|
2637
|
+
const languagesCommand = program$8.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");
|
|
2622
2638
|
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) => {
|
|
2623
2639
|
konsola.title(` ${commands.LANGUAGES} `, colorPalette.LANGUAGES);
|
|
2624
|
-
const verbose = program$
|
|
2640
|
+
const verbose = program$8.opts().verbose;
|
|
2625
2641
|
const { space, path } = languagesCommand.opts();
|
|
2626
2642
|
const { filename = "languages", suffix = options.space } = options;
|
|
2627
2643
|
const { state, initializeSession } = session();
|
|
@@ -2662,8 +2678,8 @@ languagesCommand.command("pull").description(`Download your space's languages sc
|
|
|
2662
2678
|
konsola.br();
|
|
2663
2679
|
});
|
|
2664
2680
|
|
|
2665
|
-
const program$
|
|
2666
|
-
const migrationsCommand = program$
|
|
2681
|
+
const program$7 = getProgram();
|
|
2682
|
+
const migrationsCommand = program$7.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");
|
|
2667
2683
|
|
|
2668
2684
|
const getMigrationTemplate = () => {
|
|
2669
2685
|
return `export default function (block) {
|
|
@@ -2691,10 +2707,10 @@ const generateMigration = async (space, path, component, suffix) => {
|
|
|
2691
2707
|
}
|
|
2692
2708
|
};
|
|
2693
2709
|
|
|
2694
|
-
const program$
|
|
2710
|
+
const program$6 = getProgram();
|
|
2695
2711
|
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) => {
|
|
2696
2712
|
konsola.title(` ${commands.MIGRATIONS} `, colorPalette.MIGRATIONS, componentName ? `Generating migration for component ${componentName}...` : "Generating migrations...");
|
|
2697
|
-
const verbose = program$
|
|
2713
|
+
const verbose = program$6.opts().verbose;
|
|
2698
2714
|
const { space, path } = migrationsCommand.opts();
|
|
2699
2715
|
const { suffix } = options;
|
|
2700
2716
|
if (!componentName) {
|
|
@@ -3110,10 +3126,10 @@ const isStoryWithUnpublishedChanges = (story) => {
|
|
|
3110
3126
|
return story.published && story.unpublished_changes;
|
|
3111
3127
|
};
|
|
3112
3128
|
|
|
3113
|
-
const program$
|
|
3129
|
+
const program$5 = getProgram();
|
|
3114
3130
|
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) => {
|
|
3115
3131
|
konsola.title(` ${commands.MIGRATIONS} `, colorPalette.MIGRATIONS, componentName ? `Running migrations for component ${componentName}...` : "Running migrations...");
|
|
3116
|
-
const verbose = program$
|
|
3132
|
+
const verbose = program$5.opts().verbose;
|
|
3117
3133
|
const { filter, dryRun = false, query, startsWith, publish } = options;
|
|
3118
3134
|
const { space, path } = migrationsCommand.opts();
|
|
3119
3135
|
const { state, initializeSession } = session();
|
|
@@ -3267,10 +3283,10 @@ migrationsCommand.command("run [componentName]").description("Run migrations").o
|
|
|
3267
3283
|
}
|
|
3268
3284
|
});
|
|
3269
3285
|
|
|
3270
|
-
const program$
|
|
3286
|
+
const program$4 = getProgram();
|
|
3271
3287
|
migrationsCommand.command("rollback [migrationFile]").description("Rollback a migration").action(async (migrationFile) => {
|
|
3272
3288
|
konsola.title(` ${commands.MIGRATIONS} `, colorPalette.MIGRATIONS, `Rolling back migration ${chalk.hex(colorPalette.MIGRATIONS)(migrationFile)}...`);
|
|
3273
|
-
const verbose = program$
|
|
3289
|
+
const verbose = program$4.opts().verbose;
|
|
3274
3290
|
const { space, path } = migrationsCommand.opts();
|
|
3275
3291
|
const { state, initializeSession } = session();
|
|
3276
3292
|
await initializeSession();
|
|
@@ -3309,8 +3325,8 @@ migrationsCommand.command("rollback [migrationFile]").description("Rollback a mi
|
|
|
3309
3325
|
}
|
|
3310
3326
|
});
|
|
3311
3327
|
|
|
3312
|
-
const program$
|
|
3313
|
-
const typesCommand = program$
|
|
3328
|
+
const program$3 = getProgram();
|
|
3329
|
+
const typesCommand = program$3.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");
|
|
3314
3330
|
|
|
3315
3331
|
const getAssetJSONSchema = (title) => ({
|
|
3316
3332
|
$id: "#/asset",
|
|
@@ -4009,20 +4025,11 @@ const generateStoryblokTypes = async (options = {}) => {
|
|
|
4009
4025
|
}
|
|
4010
4026
|
};
|
|
4011
4027
|
|
|
4012
|
-
const program$
|
|
4028
|
+
const program$2 = getProgram();
|
|
4013
4029
|
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) => {
|
|
4014
4030
|
konsola.title(` ${commands.TYPES} `, colorPalette.TYPES, "Generating types...");
|
|
4015
|
-
const verbose = program$
|
|
4031
|
+
const verbose = program$2.opts().verbose;
|
|
4016
4032
|
const { space, path } = typesCommand.opts();
|
|
4017
|
-
const { state, initializeSession } = session();
|
|
4018
|
-
await initializeSession();
|
|
4019
|
-
if (!requireAuthentication(state, verbose)) {
|
|
4020
|
-
return;
|
|
4021
|
-
}
|
|
4022
|
-
if (!space) {
|
|
4023
|
-
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
|
|
4024
|
-
return;
|
|
4025
|
-
}
|
|
4026
4033
|
const spinner = new Spinner({
|
|
4027
4034
|
verbose: !isVitest
|
|
4028
4035
|
});
|
|
@@ -4057,7 +4064,264 @@ typesCommand.command("generate").description("Generate types d.ts for your compo
|
|
|
4057
4064
|
}
|
|
4058
4065
|
});
|
|
4059
4066
|
|
|
4060
|
-
|
|
4067
|
+
let octokit;
|
|
4068
|
+
let lastToken;
|
|
4069
|
+
const createOctokit = (token) => {
|
|
4070
|
+
if (!octokit || token !== lastToken) {
|
|
4071
|
+
const options = {
|
|
4072
|
+
request: {
|
|
4073
|
+
fetch
|
|
4074
|
+
}
|
|
4075
|
+
};
|
|
4076
|
+
octokit = new Octokit(options);
|
|
4077
|
+
}
|
|
4078
|
+
return octokit;
|
|
4079
|
+
};
|
|
4080
|
+
|
|
4081
|
+
const generateProject = async (blueprint, projectName, targetPath = process.cwd()) => {
|
|
4082
|
+
try {
|
|
4083
|
+
const projectPath = path.join(targetPath, projectName);
|
|
4084
|
+
const templateRepo = `storyblok/blueprint-core-${blueprint}`;
|
|
4085
|
+
try {
|
|
4086
|
+
await fs.access(projectPath);
|
|
4087
|
+
const existsError = new Error(`Directory ${projectName} already exists`);
|
|
4088
|
+
existsError.code = "ENOTEMPTY";
|
|
4089
|
+
existsError.path = projectPath;
|
|
4090
|
+
throw new FileSystemError("directory_not_empty", "mkdir", existsError, `Directory ${projectName} already exists`);
|
|
4091
|
+
} catch (error) {
|
|
4092
|
+
const fsError = error;
|
|
4093
|
+
if (fsError.code === "ENOENT") {
|
|
4094
|
+
} else {
|
|
4095
|
+
handleFileSystemError("read", fsError);
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
const degitProcess = spawn("npx", ["degit", templateRepo, projectPath], {
|
|
4099
|
+
stdio: "inherit",
|
|
4100
|
+
shell: true
|
|
4101
|
+
});
|
|
4102
|
+
return new Promise((resolve, reject) => {
|
|
4103
|
+
degitProcess.on("close", (code) => {
|
|
4104
|
+
if (code === 0) {
|
|
4105
|
+
resolve();
|
|
4106
|
+
} else {
|
|
4107
|
+
reject(new Error(`Failed to clone template. Process exited with code ${code}`));
|
|
4108
|
+
}
|
|
4109
|
+
});
|
|
4110
|
+
degitProcess.on("error", (error) => {
|
|
4111
|
+
reject(new Error(`Failed to spawn degit process: ${error.message}`));
|
|
4112
|
+
});
|
|
4113
|
+
});
|
|
4114
|
+
} catch (error) {
|
|
4115
|
+
handleFileSystemError("read", error);
|
|
4116
|
+
}
|
|
4117
|
+
};
|
|
4118
|
+
const createEnvFile = async (projectPath, accessToken, additionalVars) => {
|
|
4119
|
+
try {
|
|
4120
|
+
const envPath = path.join(projectPath, ".env");
|
|
4121
|
+
let envContent = `# Storyblok Configuration
|
|
4122
|
+
STORYBLOK_DELIVERY_API_TOKEN=${accessToken}
|
|
4123
|
+
`;
|
|
4124
|
+
if (additionalVars && Object.keys(additionalVars).length > 0) ;
|
|
4125
|
+
await saveToFile(envPath, envContent);
|
|
4126
|
+
} catch (error) {
|
|
4127
|
+
throw new Error(`Failed to create .env file: ${error.message}`);
|
|
4128
|
+
}
|
|
4129
|
+
};
|
|
4130
|
+
const generateSpaceUrl = (spaceId, region) => {
|
|
4131
|
+
const domain = appDomains[region];
|
|
4132
|
+
return `https://${domain}/#/me/spaces/${spaceId}/dashboard`;
|
|
4133
|
+
};
|
|
4134
|
+
const openSpaceInBrowser = async (spaceId, region) => {
|
|
4135
|
+
try {
|
|
4136
|
+
const spaceUrl = generateSpaceUrl(spaceId, region);
|
|
4137
|
+
await open(spaceUrl);
|
|
4138
|
+
} catch (error) {
|
|
4139
|
+
throw new Error(`Failed to open space in browser: ${error.message}`);
|
|
4140
|
+
}
|
|
4141
|
+
};
|
|
4142
|
+
const extractPortFromTopics = (topics) => {
|
|
4143
|
+
const portTopic = topics.find((topic) => topic.startsWith("port-"));
|
|
4144
|
+
if (portTopic) {
|
|
4145
|
+
const port = portTopic.replace("port-", "");
|
|
4146
|
+
if (/^\d+$/.test(port) && Number.parseInt(port) > 0 && Number.parseInt(port) <= 65535) {
|
|
4147
|
+
return port;
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
return "3000";
|
|
4151
|
+
};
|
|
4152
|
+
const repositoryToBlueprint = (repo) => {
|
|
4153
|
+
const technology = repo.name.replace("blueprint-core-", "");
|
|
4154
|
+
const port = extractPortFromTopics(repo.topics || []);
|
|
4155
|
+
return {
|
|
4156
|
+
name: technology.charAt(0).toUpperCase() + technology.slice(1),
|
|
4157
|
+
value: technology,
|
|
4158
|
+
template: repo.clone_url,
|
|
4159
|
+
location: port ? `https://localhost:${port}/` : "https://localhost:3000/",
|
|
4160
|
+
description: repo.description,
|
|
4161
|
+
updated_at: repo.updated_at
|
|
4162
|
+
};
|
|
4163
|
+
};
|
|
4164
|
+
const fetchBlueprintRepositories = async () => {
|
|
4165
|
+
try {
|
|
4166
|
+
const octokit = createOctokit();
|
|
4167
|
+
const { data } = await octokit.rest.search.repos({
|
|
4168
|
+
q: "org:storyblok blueprint-core-",
|
|
4169
|
+
sort: "updated",
|
|
4170
|
+
order: "desc",
|
|
4171
|
+
per_page: 100
|
|
4172
|
+
});
|
|
4173
|
+
const blueprints = data.items.filter((repo) => repo.name.startsWith("blueprint-core-")).map(repositoryToBlueprint).sort((a, b) => a.name.localeCompare(b.name));
|
|
4174
|
+
return blueprints;
|
|
4175
|
+
} catch (error) {
|
|
4176
|
+
handleAPIError("fetch_blueprints", error, "Failed to fetch blueprints from GitHub");
|
|
4177
|
+
}
|
|
4178
|
+
};
|
|
4179
|
+
|
|
4180
|
+
const createSpace = async (space) => {
|
|
4181
|
+
try {
|
|
4182
|
+
const client = mapiClient();
|
|
4183
|
+
const { data } = await client.post("spaces", {
|
|
4184
|
+
body: JSON.stringify(space)
|
|
4185
|
+
});
|
|
4186
|
+
return data.space;
|
|
4187
|
+
} catch (error) {
|
|
4188
|
+
handleAPIError("create_space", error, `Failed to create space ${space.name}`);
|
|
4189
|
+
}
|
|
4190
|
+
};
|
|
4191
|
+
|
|
4192
|
+
const program$1 = getProgram();
|
|
4193
|
+
program$1.command(`${commands.CREATE} [project-path]`).alias("c").description(`Scaffold a new project using Storyblok`).option("-b, --blueprint <blueprint>", "technology starter blueprint").option("--skip-space", "skip space creation").action(async (projectPath, options) => {
|
|
4194
|
+
konsola.title(` ${commands.CREATE} `, colorPalette.CREATE);
|
|
4195
|
+
const verbose = program$1.opts().verbose;
|
|
4196
|
+
const { blueprint } = options;
|
|
4197
|
+
const { state, initializeSession } = session();
|
|
4198
|
+
await initializeSession();
|
|
4199
|
+
if (!requireAuthentication(state, verbose)) {
|
|
4200
|
+
return;
|
|
4201
|
+
}
|
|
4202
|
+
const { password, region } = state;
|
|
4203
|
+
mapiClient({
|
|
4204
|
+
token: password,
|
|
4205
|
+
region
|
|
4206
|
+
});
|
|
4207
|
+
const spinnerBlueprints = new Spinner({
|
|
4208
|
+
verbose: !isVitest
|
|
4209
|
+
});
|
|
4210
|
+
const spinnerSpace = new Spinner({
|
|
4211
|
+
verbose: !isVitest
|
|
4212
|
+
});
|
|
4213
|
+
try {
|
|
4214
|
+
spinnerBlueprints.start("Fetching starter blueprints...");
|
|
4215
|
+
const blueprints = await fetchBlueprintRepositories();
|
|
4216
|
+
spinnerBlueprints.succeed("Starter blueprints fetched successfully");
|
|
4217
|
+
if (!blueprints) {
|
|
4218
|
+
spinnerBlueprints.failed();
|
|
4219
|
+
konsola.warn("No starter blueprints found. Please contact support@storyblok.com");
|
|
4220
|
+
konsola.br();
|
|
4221
|
+
return;
|
|
4222
|
+
}
|
|
4223
|
+
let technologyBlueprint = blueprint;
|
|
4224
|
+
if (blueprint) {
|
|
4225
|
+
const validBlueprints = blueprints;
|
|
4226
|
+
const isValidBlueprint = validBlueprints.find((bp) => bp.value === blueprint);
|
|
4227
|
+
if (!isValidBlueprint) {
|
|
4228
|
+
const validOptions = validBlueprints.map((bp) => bp.value).join(", ");
|
|
4229
|
+
konsola.warn(`Invalid blueprint "${chalk.hex(colorPalette.CREATE)(blueprint)}". Valid options are: ${chalk.hex(colorPalette.CREATE)(validOptions)}`);
|
|
4230
|
+
konsola.br();
|
|
4231
|
+
technologyBlueprint = void 0;
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
if (!technologyBlueprint) {
|
|
4235
|
+
technologyBlueprint = await select({
|
|
4236
|
+
message: "Please select the technology you would like to use:",
|
|
4237
|
+
choices: blueprints.map((blueprint2) => ({
|
|
4238
|
+
name: blueprint2.name,
|
|
4239
|
+
value: blueprint2.value
|
|
4240
|
+
}))
|
|
4241
|
+
});
|
|
4242
|
+
}
|
|
4243
|
+
let finalProjectPath = projectPath;
|
|
4244
|
+
if (!projectPath) {
|
|
4245
|
+
finalProjectPath = await input({
|
|
4246
|
+
message: "What is the path for your project?",
|
|
4247
|
+
default: `./my-${technologyBlueprint}-project`,
|
|
4248
|
+
validate: (value) => {
|
|
4249
|
+
if (!value.trim()) {
|
|
4250
|
+
return "Project path is required";
|
|
4251
|
+
}
|
|
4252
|
+
const projectName2 = path.basename(value);
|
|
4253
|
+
if (!/^[\w-]+$/.test(projectName2)) {
|
|
4254
|
+
return "Project name (last part of the path) can only contain letters, numbers, hyphens, and underscores";
|
|
4255
|
+
}
|
|
4256
|
+
return true;
|
|
4257
|
+
}
|
|
4258
|
+
});
|
|
4259
|
+
}
|
|
4260
|
+
const resolvedPath = path.resolve(finalProjectPath);
|
|
4261
|
+
const targetDirectory = path.dirname(resolvedPath);
|
|
4262
|
+
const projectName = path.basename(resolvedPath);
|
|
4263
|
+
konsola.br();
|
|
4264
|
+
konsola.info(`Scaffolding your project using the ${chalk.hex(colorPalette.CREATE)(technologyBlueprint)} blueprint...`);
|
|
4265
|
+
await generateProject(technologyBlueprint, projectName, targetDirectory);
|
|
4266
|
+
konsola.ok(`Project ${chalk.hex(colorPalette.PRIMARY)(projectName)} created successfully in ${chalk.hex(colorPalette.PRIMARY)(finalProjectPath)}`, true);
|
|
4267
|
+
let createdSpace;
|
|
4268
|
+
if (!options.skipSpace) {
|
|
4269
|
+
try {
|
|
4270
|
+
spinnerSpace.start(`Creating space "${toHumanReadable(projectName)}"`);
|
|
4271
|
+
const selectedBlueprint = blueprints.find((bp) => bp.value === technologyBlueprint);
|
|
4272
|
+
const blueprintDomain = selectedBlueprint?.location || "https://localhost:3000/";
|
|
4273
|
+
createdSpace = await createSpace({
|
|
4274
|
+
name: toHumanReadable(projectName),
|
|
4275
|
+
domain: blueprintDomain
|
|
4276
|
+
});
|
|
4277
|
+
spinnerSpace.succeed(`Space "${chalk.hex(colorPalette.PRIMARY)(toHumanReadable(projectName))}" created successfully`);
|
|
4278
|
+
} catch (error) {
|
|
4279
|
+
spinnerSpace.failed();
|
|
4280
|
+
konsola.br();
|
|
4281
|
+
handleError(error, verbose);
|
|
4282
|
+
return;
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
if (createdSpace?.first_token) {
|
|
4286
|
+
try {
|
|
4287
|
+
await createEnvFile(resolvedPath, createdSpace.first_token);
|
|
4288
|
+
konsola.ok(`Created .env file with Storyblok access token`, true);
|
|
4289
|
+
} catch (error) {
|
|
4290
|
+
konsola.warn(`Failed to create .env file: ${error.message}`);
|
|
4291
|
+
konsola.info(`You can manually add this token to your .env file: ${createdSpace.first_token}`);
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
if (createdSpace?.id) {
|
|
4295
|
+
try {
|
|
4296
|
+
await openSpaceInBrowser(createdSpace.id, region);
|
|
4297
|
+
konsola.info(`Opened space in your browser`);
|
|
4298
|
+
} catch (error) {
|
|
4299
|
+
konsola.warn(`Failed to open browser: ${error.message}`);
|
|
4300
|
+
const spaceUrl = generateSpaceUrl(createdSpace.id, region);
|
|
4301
|
+
konsola.info(`You can manually open your space at: ${chalk.hex(colorPalette.PRIMARY)(spaceUrl)}`);
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
konsola.br();
|
|
4305
|
+
konsola.ok(`Your ${chalk.hex(colorPalette.PRIMARY)(technologyBlueprint)} project is ready \u{1F389} !`);
|
|
4306
|
+
if (createdSpace?.first_token) {
|
|
4307
|
+
konsola.ok(`Storyblok space created, preview url and .env configured automatically`);
|
|
4308
|
+
}
|
|
4309
|
+
konsola.br();
|
|
4310
|
+
konsola.info(`Next steps:
|
|
4311
|
+
cd ${finalProjectPath}
|
|
4312
|
+
npm install
|
|
4313
|
+
npm run dev
|
|
4314
|
+
`);
|
|
4315
|
+
} catch (error) {
|
|
4316
|
+
spinnerSpace.failed();
|
|
4317
|
+
spinnerBlueprints.failed();
|
|
4318
|
+
konsola.br();
|
|
4319
|
+
handleError(error, verbose);
|
|
4320
|
+
}
|
|
4321
|
+
konsola.br();
|
|
4322
|
+
});
|
|
4323
|
+
|
|
4324
|
+
const version = "4.1.0";
|
|
4061
4325
|
const pkg = {
|
|
4062
4326
|
version: version};
|
|
4063
4327
|
|