@slicemachine/init 1.1.10-alpha.1 → 1.1.10-alpha.10

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.
@@ -1,26 +1,24 @@
1
1
  import * as inquirer from "inquirer";
2
2
  import Separator from "inquirer/lib/objects/separator";
3
- import { Utils, Models, CONSTS } from "@slicemachine/core";
4
- import * as Prismic from "@slicemachine/core/build/prismic";
3
+ import { Models } from "@slicemachine/core";
5
4
  import * as NodeUtils from "@slicemachine/core/build/node-utils";
6
5
  import { createRepository } from "../utils/create-repo";
7
- import { logs } from "../utils";
6
+ import { validateRepositoryName } from "../utils/validateRepositoryName";
7
+ import { InitClient, logs } from "../utils";
8
8
 
9
9
  export const CREATE_REPO = "$_CREATE_REPO"; // not a valid domain name
10
- const DEFAULT_BASE = CONSTS.DEFAULT_BASE;
11
10
 
12
- export function prettyRepoName(address: URL, value?: string): string {
13
- const repoName = value ? logs.cyan(value) : logs.dim.cyan("repo-name");
14
- return `${logs.cyan.dim(`${address.protocol}//`)}${repoName}${logs.cyan.dim(
15
- `.${address.hostname}`
16
- )}`;
11
+ export function prettyRepoName(address: URL, domain: string): string {
12
+ return `${logs.cyan.dim(`${address.protocol}//`)}${logs.cyan(
13
+ domain
14
+ )}${logs.cyan.dim(`.${address.hostname}`)}`;
17
15
  }
18
16
 
19
17
  export async function promptForRepoDomain(
20
- base: string,
18
+ client: InitClient,
21
19
  defaultValue?: string
22
20
  ): Promise<string> {
23
- const address = new URL(base);
21
+ const address = new URL(client.apisEndpoints.Wroom);
24
22
 
25
23
  logs.writeInfo(
26
24
  "The name acts as a domain/endpoint for your content repo and should be completely unique."
@@ -34,16 +32,9 @@ export async function promptForRepoDomain(
34
32
  type: "input",
35
33
  required: true,
36
34
  default: defaultValue,
37
- transformer: (value: string) =>
38
- prettyRepoName(address, value || defaultValue),
39
- async validate(name: string) {
40
- const result = await Prismic.Communication.validateRepositoryName(
41
- name,
42
- base,
43
- false
44
- );
45
- return result === name || result;
46
- },
35
+ transformer: (value: string | undefined) =>
36
+ prettyRepoName(address, value || defaultValue || "repository"),
37
+ validate: (name: string) => validateRepositoryName(client, name),
47
38
  },
48
39
  ])
49
40
  .then((res) => res.repoDomain);
@@ -122,47 +113,57 @@ export function sortReposForPrompt(
122
113
  }
123
114
 
124
115
  export async function chooseOrCreateARepository(
116
+ client: InitClient,
125
117
  cwd: string,
126
118
  framework: Models.Frameworks,
127
- cookies: string,
128
- base = DEFAULT_BASE,
129
- domain?: string
119
+ preSelectedRepository?: string
130
120
  ): Promise<string> {
131
- const token = Utils.Cookie.parsePrismicAuthToken(cookies);
132
- const repos = await Prismic.Communication.listRepositories(token, base);
133
-
134
- const hasRepo = domain && repos.find((d) => d.domain === domain);
135
- if (hasRepo) return domain;
136
-
137
- if (repos.length === 0) {
138
- const domainName = await promptForRepoDomain(base, domain);
139
- return await createRepository(domainName, framework, cookies, base);
121
+ const repositories: Models.Repository[] = await client.listRepositories();
122
+
123
+ const isPreSelectedValid =
124
+ preSelectedRepository &&
125
+ repositories.find(
126
+ (repository) => repository.domain === preSelectedRepository
127
+ );
128
+ if (isPreSelectedValid) return preSelectedRepository;
129
+
130
+ // No repository to display, ask for a new repository name to create it.
131
+ if (repositories.length === 0) {
132
+ const domainName = await promptForRepoDomain(client, preSelectedRepository);
133
+ await createRepository(client, domainName, framework);
134
+ return domainName;
140
135
  }
141
136
 
142
- const choices = sortReposForPrompt(repos, base, cwd);
143
-
137
+ // prepare the list of repositories to display
138
+ const choices = sortReposForPrompt(
139
+ repositories,
140
+ client.apisEndpoints.Wroom,
141
+ cwd
142
+ );
144
143
  const numberOfDisabledRepos = choices.filter((repo) => {
145
144
  if (repo instanceof Separator) return false;
146
145
  return repo.disabled;
147
146
  }).length;
148
147
 
149
- const res = await inquirer.prompt<{ chosenRepo: string }>([
148
+ // display the list of repositories and wait for the user to choose one.
149
+ const promptResult = await inquirer.prompt<{ chosenRepository: string }>([
150
150
  {
151
151
  type: "list",
152
- name: "chosenRepo",
152
+ name: "chosenRepository",
153
153
  default: 0,
154
154
  required: true,
155
155
  message: "Connect a Prismic Repository or create a new one",
156
156
  choices,
157
157
  pageSize: numberOfDisabledRepos + 2 <= 7 ? 7 : numberOfDisabledRepos + 2,
158
- // loop: false
159
158
  },
160
159
  ]);
161
160
 
162
- if (res.chosenRepo === CREATE_REPO) {
163
- const domainName = await promptForRepoDomain(base, domain);
164
- return await createRepository(domainName, framework, cookies, base);
161
+ // If the user has chosen to create a new repository.
162
+ if (promptResult.chosenRepository === CREATE_REPO) {
163
+ const domainName = await promptForRepoDomain(client, preSelectedRepository);
164
+ await createRepository(client, domainName, framework);
165
+ return domainName;
165
166
  }
166
167
 
167
- return res.chosenRepo;
168
+ return promptResult.chosenRepository;
168
169
  }
@@ -3,16 +3,14 @@ import type { Models } from "@slicemachine/core";
3
3
  import * as Prismic from "@slicemachine/core/build/prismic";
4
4
  import * as NodeUtils from "@slicemachine/core/build/node-utils";
5
5
  import { FrameworkResult } from "./detect-framework";
6
- import { logs } from "../utils";
6
+ import { InitClient, logs } from "../utils";
7
7
  import Tracker from "../utils/tracker";
8
8
 
9
- type Base = Prismic.Endpoints.Base;
10
-
11
9
  const defaultSliceMachineVersion = "0.0.41";
12
10
 
13
11
  export async function configureProject(
12
+ client: InitClient,
14
13
  cwd: string,
15
- base: Base,
16
14
  repositoryDomainName: string,
17
15
  framework: FrameworkResult,
18
16
  sliceLibPath: string[] = [],
@@ -45,7 +43,7 @@ export async function configureProject(
45
43
  ? manifest.content
46
44
  : { _latest: sliceMachineVersionInstalled }),
47
45
  apiEndpoint: Prismic.Endpoints.buildRepositoryEndpoint(
48
- base,
46
+ client.apisEndpoints.Wroom,
49
47
  repositoryDomainName
50
48
  ),
51
49
  libraries: [...libs, ...sliceLibPath], // odd case here for staters
@@ -2,12 +2,30 @@ import { CONSTS } from "@slicemachine/core";
2
2
  import * as NodeUtils from "@slicemachine/core/build/node-utils";
3
3
  import { logs } from "../utils";
4
4
 
5
- export function displayFinalMessage(cwd: string): void {
5
+ export function displayFinalMessage(
6
+ cwd: string,
7
+ wasStarter: boolean,
8
+ reponame: string,
9
+ base: string
10
+ ): void {
6
11
  const yarnLock = NodeUtils.Files.exists(NodeUtils.YarnLockPath(cwd));
7
12
  const command = `${yarnLock ? "yarn" : "npm"} run ${CONSTS.SCRIPT_NAME}`;
8
-
9
13
  console.log();
10
- console.log(
11
- `${logs.white("■")} Run ${logs.purple(command)} to start Slice Machine`
12
- );
14
+
15
+ if (wasStarter) {
16
+ const repoUrl = new URL(base);
17
+ repoUrl.hostname = `${reponame}.${repoUrl.hostname}`;
18
+ const urlAsString = repoUrl.toString();
19
+
20
+ const message = `${logs.white(
21
+ "■"
22
+ )} Start editing your content in Prismic ${logs.purple(urlAsString)}`;
23
+ console.log(message);
24
+ } else {
25
+ console.log(
26
+ `${logs.white("■")} Run ${logs.purple(
27
+ command
28
+ )} to launch Slice Machine and create your first Custom Type`
29
+ );
30
+ }
13
31
  }
@@ -1,21 +1,25 @@
1
1
  import { Models } from "@slicemachine/core";
2
- import { validateSessionAndGetProfile } from "../utils/communication";
3
- import { logs, Auth } from "../utils";
2
+ import { PrismicSharedConfigManager } from "@slicemachine/core/build/prismic";
3
+ import { logs, Auth, InitClient } from "../utils";
4
+
5
+ export async function loginOrBypass(
6
+ client: InitClient
7
+ ): Promise<Models.UserProfile> {
8
+ const user: Models.UserProfile | null = await client
9
+ .profile()
10
+ .catch(() => null);
4
11
 
5
- export async function loginOrBypass(base: string): Promise<{
6
- info: Models.UserInfo;
7
- profile: Models.UserProfile | null;
8
- } | null> {
9
- const user = await validateSessionAndGetProfile(base).catch((err) =>
10
- console.log(err)
11
- );
12
12
  if (user) {
13
- const email = user.info.email;
13
+ const email = user.email;
14
14
  logs.writeCheck(`Logged in as ${logs.bold(email)}`);
15
15
  return user;
16
- } else {
17
- await Auth.login(base);
18
- const user = await validateSessionAndGetProfile(base);
19
- return user;
20
16
  }
17
+
18
+ await Auth.login(client);
19
+
20
+ // update token used to make calls.
21
+ client.updateAuthenticationToken(PrismicSharedConfigManager.getAuth());
22
+
23
+ const userAfterLogin: Models.UserProfile = await client.profile();
24
+ return userAfterLogin;
21
25
  }
@@ -1,32 +1,31 @@
1
- import { parsePrismicAuthToken } from "@slicemachine/core/build/utils/cookie";
2
1
  import { retrieveManifest, Files } from "@slicemachine/core/build/node-utils";
3
2
  import path from "path";
4
- import { sendSlicesFromStarter } from "./starters/slices";
5
- import { sendCustomTypesFromStarter } from "./starters/custom-types";
3
+ import fs from "fs";
4
+ import { sendSlices } from "./starters/slices";
5
+ import { sendCustomTypes } from "./starters/custom-types";
6
+ import { sendDocuments } from "./starters/documents";
7
+ import { InitClient } from "../utils";
6
8
 
7
9
  export async function sendStarterData(
8
- repository: string,
9
- base: string,
10
- cookies: string,
11
- cwd: string
10
+ client: InitClient,
11
+ cwd: string,
12
+ pushDocuments = true
12
13
  ) {
13
- const smJson = retrieveManifest(cwd);
14
- const hasDocuments = Files.exists(path.join(cwd, "documents"));
14
+ const manifest = retrieveManifest(cwd);
15
+ const documentsPath = path.join(cwd, "documents");
16
+ const hasDocuments = Files.exists(documentsPath);
15
17
 
16
- if (smJson.exists === false || hasDocuments === false)
18
+ if (manifest.exists === false || hasDocuments === false)
17
19
  return Promise.resolve(false);
18
20
 
19
- const authTokenFromCookie = parsePrismicAuthToken(cookies);
21
+ if (manifest.content) await sendSlices(client, cwd, manifest.content);
22
+ await sendCustomTypes(client, cwd);
20
23
 
21
- if (smJson.content && smJson.content.libraries) {
22
- await sendSlicesFromStarter(
23
- base,
24
- repository,
25
- authTokenFromCookie,
26
- smJson.content.libraries,
27
- cwd
28
- );
24
+ // If the user choose not to push documents, we still delete the documents folder.
25
+ if (!pushDocuments) {
26
+ fs.rmSync(documentsPath, { recursive: true, force: true });
27
+ return Promise.resolve(true);
29
28
  }
30
29
 
31
- return sendCustomTypesFromStarter(repository, authTokenFromCookie, base, cwd);
30
+ return sendDocuments(client, cwd);
32
31
  }
@@ -1,15 +1,11 @@
1
1
  import { CustomType } from "@prismicio/types-internal/lib/customtypes";
2
2
  import { Files, CustomTypesPaths } from "@slicemachine/core/build/node-utils";
3
3
  import { isLeft } from "fp-ts/lib/Either";
4
- import {
5
- getRemoteCustomTypeIds,
6
- sendManyCustomTypesToPrismic,
7
- } from "./communication";
8
4
  import { promptToPushCustomTypes } from "./prompts";
9
- import { getEndpointsFromBase } from "./endpoints";
10
- import { logs } from "../../utils";
5
+ import { InitClient, logs } from "../../utils";
6
+ import { ClientError } from "@slicemachine/client";
11
7
 
12
- export function readCustomTypes(cwd: string): Array<CustomType> {
8
+ export function readLocalCustomTypes(cwd: string): Array<CustomType> {
13
9
  const customTypePaths = CustomTypesPaths(cwd);
14
10
  const dir = customTypePaths.value();
15
11
 
@@ -40,23 +36,15 @@ export function readCustomTypes(cwd: string): Array<CustomType> {
40
36
  return files;
41
37
  }
42
38
 
43
- export async function sendCustomTypesFromStarter(
44
- repository: string,
45
- authorization: string,
46
- base: string,
47
- cwd: string
48
- ) {
49
- const customTypeApiEndpoint = getEndpointsFromBase(base).Models;
39
+ export async function sendCustomTypes(client: InitClient, cwd: string) {
40
+ const localCustomTypes = readLocalCustomTypes(cwd);
50
41
 
51
- const customTypes = readCustomTypes(cwd);
42
+ // nothing to push
43
+ if (localCustomTypes.length === 0) return Promise.resolve(false);
52
44
 
53
- if (customTypes.length === 0) return Promise.resolve(false);
54
-
55
- const remoteCustomTypeIds = await getRemoteCustomTypeIds(
56
- customTypeApiEndpoint,
57
- repository,
58
- authorization
59
- );
45
+ const remoteCustomTypeIds = await client
46
+ .getCustomTypes()
47
+ .then((customTypes) => customTypes.map((customType) => customType.id));
60
48
 
61
49
  if (remoteCustomTypeIds.length) {
62
50
  const shouldPush = await promptToPushCustomTypes();
@@ -68,15 +56,26 @@ export async function sendCustomTypesFromStarter(
68
56
  );
69
57
  spinner.start();
70
58
 
71
- await sendManyCustomTypesToPrismic(
72
- repository,
73
- authorization,
74
- customTypeApiEndpoint,
75
- remoteCustomTypeIds,
76
- customTypes
77
- );
59
+ await Promise.all(
60
+ localCustomTypes.map(async (customType) => {
61
+ const promise = remoteCustomTypeIds.includes(customType.id)
62
+ ? client.updateCustomType(customType)
63
+ : client.insertCustomType(customType);
64
+
65
+ return promise.catch((error: ClientError) => {
66
+ logs.writeError(
67
+ `Sending custom type ${customType.id} - ${error.message}`
68
+ );
69
+
70
+ // throwing the error again to stop the Promise.all
71
+ throw error;
72
+ });
73
+ })
74
+ ).catch(() => {
75
+ // the error about the custom type that failed to be pushed should be in the terminal already.
76
+ process.exit(1);
77
+ });
78
78
 
79
79
  spinner.succeed();
80
-
81
80
  return Promise.resolve(true);
82
81
  }
@@ -0,0 +1,89 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import type { AxiosError } from "axios";
4
+
5
+ import * as t from "io-ts";
6
+ import { getOrElseW } from "fp-ts/Either";
7
+ import { InitClient, logs, lsdir, lsfiles } from "../../utils";
8
+ import { PrismicSharedConfigManager } from "@slicemachine/core/build/prismic/SharedConfig";
9
+
10
+ const SignatureFileReader = t.type({
11
+ signature: t.string,
12
+ });
13
+
14
+ type SignatureFile = t.TypeOf<typeof SignatureFileReader>;
15
+
16
+ export async function readSignatureFile(cwd: string): Promise<SignatureFile> {
17
+ const pathToFile = path.join(cwd, "documents", "index.json");
18
+ return fs.promises.readFile(pathToFile, "utf-8").then((res) => {
19
+ const data = JSON.parse(res) as unknown;
20
+ return getOrElseW(() => {
21
+ throw new Error("Unable to read document signature file");
22
+ })(SignatureFileReader.decode(data));
23
+ });
24
+ }
25
+
26
+ export async function readDocuments(
27
+ cwd: string
28
+ ): Promise<Record<string, unknown>> {
29
+ const documentDir = path.join(cwd, "documents");
30
+ const dirs = await lsdir(documentDir);
31
+
32
+ const files = (await Promise.all(dirs.map((dir) => lsfiles(dir)))).flat();
33
+
34
+ const documentObj = files.reduce<Record<string, unknown>>((acc, file) => {
35
+ const filename: string = path.parse(file).name;
36
+ const fileContent: string = fs.readFileSync(file, "utf-8");
37
+ const json = JSON.parse(fileContent) as Record<string, unknown>;
38
+
39
+ return { ...acc, [filename]: json };
40
+ }, {});
41
+
42
+ return documentObj;
43
+ }
44
+
45
+ export async function sendDocuments(
46
+ client: InitClient,
47
+ cwd: string
48
+ ): Promise<boolean> {
49
+ const pathToDocuments = path.join(cwd, "documents");
50
+ const pathToSignatureFile = path.join(pathToDocuments, "index.json");
51
+
52
+ // Without the signature, we can't push documents to Prismic.
53
+ if (!fs.existsSync(pathToSignatureFile)) return Promise.resolve(false);
54
+
55
+ const signatureObj = await readSignatureFile(cwd);
56
+ const documents: Record<string, unknown> = await readDocuments(cwd);
57
+
58
+ // No documents to push
59
+ if (Object.keys(documents).length === 0) return Promise.resolve(false);
60
+
61
+ const spinner = logs.spinner("Pushing existing documents to your repository");
62
+ spinner.start();
63
+
64
+ return client
65
+ .pushDocuments(
66
+ signatureObj.signature,
67
+ documents,
68
+ PrismicSharedConfigManager.get().cookies
69
+ )
70
+ .then(() => {
71
+ spinner.succeed();
72
+ fs.rmSync(pathToDocuments, { recursive: true, force: true });
73
+ return true;
74
+ })
75
+ .catch((e: AxiosError) => {
76
+ spinner.fail();
77
+ if (e.response?.data === "Repository should not contain documents") {
78
+ logs.writeError(
79
+ "The selected repository is not empty, documents cannot be uploaded. Please choose an empty repository or delete the documents contained in your repository."
80
+ );
81
+ } else {
82
+ logs.writeError(
83
+ "Sending documents failed, please try again. If the problem persists, contact us."
84
+ );
85
+ logs.writeError(`Full error: ${e.code || 500} - ${e.message}`);
86
+ }
87
+ process.exit(1);
88
+ });
89
+ }