storyblok 4.12.0 → 4.13.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 CHANGED
@@ -1862,23 +1862,117 @@ function session() {
1862
1862
  return sessionInstance;
1863
1863
  }
1864
1864
 
1865
+ async function performInteractiveLogin(options) {
1866
+ const { verbose = false, preSelectedRegion, showWelcomeMessage = true } = options || {};
1867
+ const spinner = new Spinner({
1868
+ verbose: !isVitest
1869
+ });
1870
+ try {
1871
+ const strategy = await select({
1872
+ message: "How would you like to login?",
1873
+ choices: [
1874
+ {
1875
+ name: "With email",
1876
+ value: "login-with-email",
1877
+ short: "Email"
1878
+ },
1879
+ {
1880
+ name: "With Token (Personal Access Token \u2013 works also for SSO accounts)",
1881
+ value: "login-with-token",
1882
+ short: "Token"
1883
+ }
1884
+ ]
1885
+ });
1886
+ let userToken;
1887
+ let userRegion;
1888
+ if (strategy === "login-with-token") {
1889
+ konsola.info([
1890
+ "\u{1F511} You can use a Personal Access Token to log in.",
1891
+ "This works for all accounts, including SSO accounts.",
1892
+ `Generate one in your Storyblok account settings: ${chalk.underline.blue("https://app.storyblok.com/#/me/account?tab=token")}`
1893
+ ].join("\n"));
1894
+ userToken = await password({
1895
+ message: "Please enter your Personal Access Token:",
1896
+ validate: (value) => {
1897
+ return value.length > 0;
1898
+ }
1899
+ });
1900
+ userRegion = preSelectedRegion || await select({
1901
+ message: "Please select the region you would like to work in:",
1902
+ choices: Object.values(regions).map((region) => ({
1903
+ name: regionNames[region],
1904
+ value: region
1905
+ })),
1906
+ default: regions.EU
1907
+ });
1908
+ spinner.start(`Logging in with token`);
1909
+ const user = await loginWithToken(userToken, userRegion);
1910
+ spinner.succeed();
1911
+ if (user) {
1912
+ const { updateSession, persistCredentials } = session();
1913
+ updateSession(user.email, userToken, userRegion);
1914
+ await persistCredentials(userRegion);
1915
+ if (showWelcomeMessage) {
1916
+ konsola.ok(`Successfully logged in to region ${chalk.hex(colorPalette.PRIMARY)(`${regionNames[userRegion]} (${userRegion})`)}. Welcome ${chalk.hex(colorPalette.PRIMARY)(user.friendly_name)}.`, true);
1917
+ }
1918
+ return { token: userToken, region: userRegion };
1919
+ }
1920
+ } else {
1921
+ const userEmail = await input({
1922
+ message: "Please enter your email address:",
1923
+ required: true,
1924
+ validate: (value) => {
1925
+ const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/;
1926
+ return emailRegex.test(value);
1927
+ }
1928
+ });
1929
+ const userPassword = await password({
1930
+ message: "Please enter your password:"
1931
+ });
1932
+ userRegion = preSelectedRegion || await select({
1933
+ message: "Please select the region you would like to work in:",
1934
+ choices: Object.values(regions).map((region) => ({
1935
+ name: regionNames[region],
1936
+ value: region
1937
+ })),
1938
+ default: regions.EU
1939
+ });
1940
+ spinner.start(`Logging in with email`);
1941
+ spinner.succeed();
1942
+ const response = await loginWithEmailAndPassword(userEmail, userPassword, userRegion);
1943
+ if (response?.otp_required) {
1944
+ const otp = await input({
1945
+ message: "Add the code from your Authenticator app, or the one we sent to your e-mail / phone:",
1946
+ required: true
1947
+ });
1948
+ const otpResponse = await loginWithOtp(userEmail, userPassword, otp, userRegion);
1949
+ if (otpResponse?.access_token) {
1950
+ userToken = otpResponse.access_token;
1951
+ }
1952
+ } else if (response?.access_token) {
1953
+ userToken = response.access_token;
1954
+ }
1955
+ if (userToken) {
1956
+ const { updateSession, persistCredentials } = session();
1957
+ updateSession(userEmail, userToken, userRegion);
1958
+ await persistCredentials(userRegion);
1959
+ if (showWelcomeMessage) {
1960
+ konsola.ok(`Successfully logged in to region ${chalk.hex(colorPalette.PRIMARY)(`${regionNames[userRegion]} (${userRegion})`)}. Welcome ${chalk.hex(colorPalette.PRIMARY)(userEmail)}.`, true);
1961
+ }
1962
+ return { token: userToken, region: userRegion };
1963
+ }
1964
+ }
1965
+ return null;
1966
+ } catch (error) {
1967
+ spinner.failed();
1968
+ konsola.br();
1969
+ handleError(error, verbose);
1970
+ return null;
1971
+ }
1972
+ }
1973
+
1865
1974
  const program$h = getProgram();
1866
1975
  const allRegionsText = Object.values(regions).join(",");
1867
- const loginStrategy = {
1868
- message: "How would you like to login?",
1869
- choices: [
1870
- {
1871
- name: "With email",
1872
- value: "login-with-email",
1873
- short: "Email"
1874
- },
1875
- {
1876
- name: "With Token (Personal Access Token \u2013 works also for SSO accounts)",
1877
- value: "login-with-token",
1878
- short: "Token"
1879
- }
1880
- ]
1881
- };
1882
1976
  program$h.command(commands.LOGIN).description("Login to the Storyblok CLI").option("-t, --token <token>", "Token to login directly without questions, like for CI environments").option(
1883
1977
  "-r, --region <region>",
1884
1978
  `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}.`
@@ -1926,85 +2020,16 @@ program$h.command(commands.LOGIN).description("Login to the Storyblok CLI").opti
1926
2020
  handleError(error, verbose);
1927
2021
  }
1928
2022
  } else {
1929
- const spinner = new Spinner({
1930
- verbose: !isVitest
1931
- });
1932
2023
  try {
1933
- const strategy = await select(loginStrategy);
1934
- if (strategy === "login-with-token") {
1935
- konsola.info([
1936
- "\u{1F511} You can use a Personal Access Token to log in.",
1937
- "This works for all accounts, including SSO accounts.",
1938
- `Generate one in your Storyblok account settings: ${chalk.underline.blue("https://app.storyblok.com/#/me/account?tab=token")}`
1939
- ].join("\n"));
1940
- const userToken = await password({
1941
- message: "Please enter your Personal Access Token:",
1942
- validate: (value) => {
1943
- return value.length > 0;
1944
- }
1945
- });
1946
- let userRegion = region;
1947
- if (!userRegion) {
1948
- userRegion = await select({
1949
- message: "Please select the region you would like to work in:",
1950
- choices: Object.values(regions).map((region2) => ({
1951
- name: regionNames[region2],
1952
- value: region2
1953
- })),
1954
- default: regions.EU
1955
- });
1956
- }
1957
- spinner.start(`Logging in with token`);
1958
- const user = await loginWithToken(userToken, userRegion);
1959
- spinner.succeed();
1960
- if (user) {
1961
- updateSession(user.email, userToken, userRegion);
1962
- await persistCredentials(userRegion);
1963
- konsola.ok(`Successfully logged in to region ${chalk.hex(colorPalette.PRIMARY)(`${regionNames[userRegion]} (${userRegion})`)}. Welcome ${chalk.hex(colorPalette.PRIMARY)(user.friendly_name)}.`, true);
1964
- }
1965
- } else {
1966
- const userEmail = await input({
1967
- message: "Please enter your email address:",
1968
- required: true,
1969
- validate: (value) => {
1970
- const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/;
1971
- return emailRegex.test(value);
1972
- }
1973
- });
1974
- const userPassword = await password({
1975
- message: "Please enter your password:"
1976
- });
1977
- let userRegion = region;
1978
- if (!userRegion) {
1979
- userRegion = await select({
1980
- message: "Please select the region you would like to work in:",
1981
- choices: Object.values(regions).map((region2) => ({
1982
- name: regionNames[region2],
1983
- value: region2
1984
- })),
1985
- default: regions.EU
1986
- });
1987
- }
1988
- spinner.start(`Logging in with email`);
1989
- spinner.succeed();
1990
- const response = await loginWithEmailAndPassword(userEmail, userPassword, userRegion);
1991
- if (response?.otp_required) {
1992
- const otp = await input({
1993
- message: "Add the code from your Authenticator app, or the one we sent to your e-mail / phone:",
1994
- required: true
1995
- });
1996
- const otpResponse = await loginWithOtp(userEmail, userPassword, otp, userRegion);
1997
- if (otpResponse?.access_token) {
1998
- updateSession(userEmail, otpResponse?.access_token, userRegion);
1999
- }
2000
- } else if (response?.access_token) {
2001
- updateSession(userEmail, response.access_token, userRegion);
2002
- }
2003
- await persistCredentials(region);
2004
- konsola.ok(`Successfully logged in to region ${chalk.hex(colorPalette.PRIMARY)(`${regionNames[userRegion]} (${userRegion})`)}. Welcome ${chalk.hex(colorPalette.PRIMARY)(userEmail)}.`, true);
2024
+ const result = await performInteractiveLogin({
2025
+ verbose,
2026
+ preSelectedRegion: region,
2027
+ showWelcomeMessage: true
2028
+ });
2029
+ if (!result) {
2030
+ konsola.warn("Login cancelled or failed.");
2005
2031
  }
2006
2032
  } catch (error) {
2007
- spinner.failed();
2008
2033
  konsola.br();
2009
2034
  handleError(error, verbose);
2010
2035
  }
@@ -6257,6 +6282,24 @@ function showNextSteps(technologyTemplate, finalProjectPath) {
6257
6282
  `);
6258
6283
  konsola.info(`Or check the dedicated guide at: ${chalk.hex(colorPalette.PRIMARY)(`https://www.storyblok.com/docs/guides/${technologyTemplate}`)}`);
6259
6284
  }
6285
+ async function promptForLogin(verbose) {
6286
+ try {
6287
+ konsola.br();
6288
+ const shouldLogin = await confirm({
6289
+ message: "Would you like to login now?",
6290
+ default: true
6291
+ });
6292
+ if (!shouldLogin) {
6293
+ konsola.warn('Login cancelled. You can login later using the "storyblok login" command.');
6294
+ return null;
6295
+ }
6296
+ return await performInteractiveLogin({ verbose, showWelcomeMessage: true });
6297
+ } catch (error) {
6298
+ konsola.br();
6299
+ handleError(error, verbose);
6300
+ return null;
6301
+ }
6302
+ }
6260
6303
  const program$3 = getProgram();
6261
6304
  program$3.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(
6262
6305
  "-r, --region <region>",
@@ -6278,20 +6321,38 @@ program$3.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
6278
6321
  }
6279
6322
  const { state, initializeSession } = session();
6280
6323
  await initializeSession();
6281
- if (!requireAuthentication(state, verbose)) {
6282
- return;
6283
- }
6284
- const { password, region } = state;
6285
- if (options.region && options.region !== region && !options.skipSpace && !token) {
6286
- 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.`));
6287
- return;
6324
+ let password;
6325
+ let region;
6326
+ if (state.region) {
6327
+ region = state.region;
6328
+ }
6329
+ if (!token && !options.skipSpace) {
6330
+ if (!requireAuthentication(state, verbose)) {
6331
+ const loginResult = await promptForLogin(verbose);
6332
+ if (!loginResult) {
6333
+ return;
6334
+ }
6335
+ await initializeSession();
6336
+ }
6337
+ const authenticatedState = state;
6338
+ password = authenticatedState.password;
6339
+ region = authenticatedState.region;
6340
+ if (options.region && options.region !== region) {
6341
+ 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
+ return;
6343
+ }
6344
+ mapiClient({
6345
+ token: {
6346
+ accessToken: password
6347
+ },
6348
+ region
6349
+ });
6350
+ } else if (state.isLoggedIn && state.password) {
6351
+ password = state.password;
6352
+ if (state.region) {
6353
+ region = state.region;
6354
+ }
6288
6355
  }
6289
- mapiClient({
6290
- token: {
6291
- accessToken: password
6292
- },
6293
- region
6294
- });
6295
6356
  const spinnerBlueprints = new Spinner({
6296
6357
  verbose: !isVitest
6297
6358
  });
@@ -6374,10 +6435,26 @@ program$3.command(`${commands.CREATE} [project-path]`).alias("c").description(`S
6374
6435
  throw new Error("User data is undefined");
6375
6436
  }
6376
6437
  userData = user;
6377
- } catch (error) {
6378
- konsola.error("Failed to fetch user info. Please login again.", error);
6379
- konsola.br();
6380
- return;
6438
+ } catch {
6439
+ konsola.error("Failed to fetch user info. Your session may have expired.");
6440
+ const loginResult = await promptForLogin(verbose);
6441
+ if (!loginResult) {
6442
+ konsola.br();
6443
+ return;
6444
+ }
6445
+ await initializeSession();
6446
+ const { password: newPassword, region: newRegion } = session().state;
6447
+ try {
6448
+ const user = await getUser(newPassword, newRegion);
6449
+ if (!user) {
6450
+ throw new Error("User data is undefined");
6451
+ }
6452
+ userData = user;
6453
+ } catch (retryError) {
6454
+ konsola.error("Failed to fetch user info after login.", retryError);
6455
+ konsola.br();
6456
+ return;
6457
+ }
6381
6458
  }
6382
6459
  const choices = [
6383
6460
  { name: "My personal account", value: "personal" }