@slicemachine/init 1.1.10-alpha.5 → 1.1.11-alpha.2

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.
@@ -0,0 +1,67 @@
1
+ import * as t from "io-ts";
2
+ import { Models } from "@slicemachine/core";
3
+ import { PrismicSharedConfigManager } from "@slicemachine/core/build/prismic";
4
+ import { Client, getAndValidateResponse } from "@slicemachine/client";
5
+
6
+ export class InitClient extends Client {
7
+ async listRepositories(): Promise<Models.Repository[]> {
8
+ return getAndValidateResponse<Models.Repository[]>(
9
+ this._get(`${this.apisEndpoints.Users}repositories`),
10
+ "repository list",
11
+ t.array(Models.Repository)
12
+ );
13
+ }
14
+
15
+ async createRepository(
16
+ domain: string,
17
+ framework: Models.Frameworks
18
+ ): Promise<string> {
19
+ const data = {
20
+ domain,
21
+ framework,
22
+ plan: "personal",
23
+ isAnnual: "false",
24
+ role: "developer",
25
+ };
26
+
27
+ return this._fetch({
28
+ method: "post",
29
+ url: `${this.apisEndpoints.Wroom}authentication/newrepository?app=slicemachine`,
30
+ data: data,
31
+ headers: {
32
+ Cookie: PrismicSharedConfigManager.get().cookies,
33
+ "User-Agent": "prismic-cli/sm", // special user agent just for this route.
34
+ },
35
+ }).then(() => domain);
36
+ }
37
+
38
+ async domainExist(domain: string): Promise<boolean> {
39
+ return getAndValidateResponse<boolean>(
40
+ this._get(
41
+ `${this.apisEndpoints.Wroom}app/dashboard/repositories/${domain}/exists`
42
+ ),
43
+ "repository exists",
44
+ t.boolean
45
+ );
46
+ }
47
+
48
+ async pushDocuments(
49
+ signature: string,
50
+ documents: Record<string, unknown>,
51
+ cookies: string
52
+ ) {
53
+ if (!this.repository) throw new Error("Repository undefined in the client");
54
+ const repositoryDirectUrl = new URL(this.apisEndpoints.Wroom);
55
+ repositoryDirectUrl.hostname = `${this.repository}.${repositoryDirectUrl.hostname}`;
56
+
57
+ return this._fetch({
58
+ method: "post",
59
+ url: `${repositoryDirectUrl.toString()}starter/documents`,
60
+ data: { signature, documents: JSON.stringify(documents) },
61
+ headers: {
62
+ Cookie: cookies,
63
+ "User-Agent": "prismic-cli/0", // special user agent just for this route.
64
+ },
65
+ });
66
+ }
67
+ }
@@ -1,33 +1,32 @@
1
1
  import type { Models } from "@slicemachine/core";
2
2
  import * as logs from "./logs";
3
- import * as Prismic from "@slicemachine/core/build/prismic";
3
+ import { InitClient } from "./client";
4
+ import Tracker from "../utils/tracker";
4
5
 
5
6
  export function createRepository(
7
+ client: InitClient,
6
8
  domain: string,
7
- framework: Models.Frameworks,
8
- cookies: string,
9
- base: string
9
+ framework: Models.Frameworks
10
10
  ): Promise<string> {
11
11
  const spinner = logs.spinner("Creating Prismic Repository");
12
12
  spinner.start();
13
13
 
14
- return Prismic.Communication.createRepository(
15
- domain,
16
- cookies,
17
- framework,
18
- base
19
- )
20
- .then((res) => {
21
- const addressUrl = new URL(base);
22
- const repoDomainName = res.data.domain || domain;
23
- addressUrl.hostname = `${repoDomainName}.${addressUrl.hostname}`;
14
+ return client
15
+ .createRepository(domain, framework)
16
+ .then((domain: string) => {
17
+ const addressUrl = new URL(client.apisEndpoints.Wroom);
18
+ addressUrl.hostname = `${domain}.${addressUrl.hostname}`;
24
19
  const address = addressUrl.toString();
25
20
  spinner.succeed(`We created your new repository ${address}`);
26
21
 
27
- return repoDomainName;
22
+ return domain;
28
23
  })
29
- .catch((error: Error) => {
24
+ .catch(async (error: Error) => {
30
25
  spinner.fail(`Error creating repository ${domain}`);
26
+ await Tracker.get().trackInitEndFail(
27
+ framework,
28
+ "Failed to create repository"
29
+ );
31
30
  if (error.message) {
32
31
  logs.writeError(error.message);
33
32
  }
@@ -0,0 +1,18 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ export async function lsdir(dir: string): Promise<Array<string>> {
5
+ return fs.promises.readdir(dir).then((dirs) => {
6
+ return dirs
7
+ .filter((name) => fs.statSync(path.join(dir, name)).isDirectory())
8
+ .map((subdirectory) => path.join(dir, subdirectory));
9
+ });
10
+ }
11
+
12
+ export async function lsfiles(dir: string): Promise<Array<string>> {
13
+ return fs.promises.readdir(dir).then((dirs) => {
14
+ return dirs
15
+ .filter((name) => fs.statSync(path.join(dir, name)).isFile())
16
+ .map((file) => path.join(dir, file));
17
+ });
18
+ }
@@ -1,7 +1,10 @@
1
+ import { ApplicationMode } from "@slicemachine/client";
1
2
  import util from "util";
2
3
  import { exec } from "child_process";
3
4
  export * as logs from "./logs";
4
5
  export { Auth } from "./auth";
6
+ export * from "./client";
7
+ export * from "./fs";
5
8
 
6
9
  export function findArgument(args: string[], name: string): string | undefined {
7
10
  const flagIndex: number = args.indexOf(`--${name}`);
@@ -23,3 +26,16 @@ export function findFlag(args: string[], name: string): boolean {
23
26
  export const execCommand: (
24
27
  command: string
25
28
  ) => Promise<{ stderr: string; stdout: string }> = util.promisify(exec);
29
+
30
+ export function getApplicationMode(
31
+ argumentValue: string | undefined
32
+ ): ApplicationMode | null {
33
+ switch (argumentValue) {
34
+ case ApplicationMode.PROD:
35
+ case ApplicationMode.STAGE:
36
+ case ApplicationMode.DEV:
37
+ return argumentValue;
38
+ default:
39
+ return null;
40
+ }
41
+ }
@@ -6,7 +6,7 @@ export enum EventType {
6
6
  DownloadLibrary = "SliceMachine Download Library",
7
7
  InitStart = "SliceMachine Init Start",
8
8
  InitIdentify = "SliceMachine Init Identify",
9
- InitDone = "SliceMachine Init Done",
9
+ InitEnd = "SliceMachine Init End",
10
10
  }
11
11
 
12
12
  type SegmentIdentifier = { userId: string } | { anonymousId: string };
@@ -114,8 +114,21 @@ export class InitTracker {
114
114
  return this._trackEvent(EventType.InitStart, { repo: repoDomain });
115
115
  }
116
116
 
117
- trackInitDone(framework: Models.Frameworks): Promise<void> {
118
- return this._trackEvent(EventType.InitDone, { framework });
117
+ trackInitEndSuccess(framework: Models.Frameworks): Promise<void> {
118
+ return this._trackEvent(EventType.InitEnd, {
119
+ framework,
120
+ repo: this.#repository,
121
+ result: "success",
122
+ });
123
+ }
124
+
125
+ trackInitEndFail(framework: Models.Frameworks, error: string): Promise<void> {
126
+ return this._trackEvent(EventType.InitEnd, {
127
+ framework,
128
+ repo: this.#repository,
129
+ result: "error",
130
+ error,
131
+ });
119
132
  }
120
133
  }
121
134
 
@@ -0,0 +1,44 @@
1
+ import { InitClient } from "./client";
2
+
3
+ export async function validateRepositoryName(
4
+ client: InitClient,
5
+ name: string
6
+ ): Promise<boolean> {
7
+ const domain = name.trim();
8
+ const errors = [];
9
+
10
+ const startsWithLetter = /^[a-zA-Z]/.test(domain);
11
+ if (!startsWithLetter) errors.push("Must start with a letter.");
12
+
13
+ const acceptedChars = /^[a-z0-9-]+$/.test(domain);
14
+ if (!acceptedChars)
15
+ errors.push("Must contain only lowercase letters, numbers and hyphens.");
16
+
17
+ const fourCharactersOrMore = domain.length >= 4;
18
+ if (!fourCharactersOrMore)
19
+ errors.push(
20
+ "Must have four or more alphanumeric characters and/or hyphens."
21
+ );
22
+
23
+ const endsWithALetterOrNumber = /[a-z0-9]$/.test(domain);
24
+ if (!endsWithALetterOrNumber)
25
+ errors.push("Must end in a letter or a number.");
26
+
27
+ const thirtyCharacterOrLess = domain.length <= 30;
28
+ if (!thirtyCharacterOrLess) errors.push("Must be 30 characters or less");
29
+
30
+ if (errors.length > 0) {
31
+ const errorString = errors.map((d, i) => `(${i + 1}: ${d}`).join(" ");
32
+ const msg = `Validation errors: ${errorString}`;
33
+ return Promise.reject(new Error(msg));
34
+ }
35
+
36
+ return client
37
+ .domainExist(domain)
38
+ .then((isAvailable) =>
39
+ isAvailable
40
+ ? true
41
+ : Promise.reject(new Error(`${name} is already in use`))
42
+ )
43
+ .catch(() => Promise.reject(new Error(`${name} is already in use`)));
44
+ }
@@ -1,171 +0,0 @@
1
- import { CustomType } from "@prismicio/types-internal/lib/customtypes";
2
- import { Slices, SliceSM } from "@slicemachine/core/build/models";
3
- import axios from "axios";
4
- import { logs } from "../../utils";
5
- import type { ApiEndpoints } from "./endpoints";
6
-
7
- export function handleErrors(prefix: string, err: unknown) {
8
- if (axios.isAxiosError(err) && err.response) {
9
- logs.writeError(
10
- `${prefix} | [${err.response.status}]: ${err.response.statusText}`
11
- );
12
- } else if (err instanceof Error) {
13
- logs.writeError(`${prefix} ${err.message}`);
14
- } else {
15
- logs.writeError(`${prefix} ${String(err)}`);
16
- }
17
- }
18
-
19
- export async function getRemoteSliceIds(
20
- customTypeApiEndpoint: ApiEndpoints["Models"],
21
- repository: string,
22
- authorization: string
23
- ): Promise<Array<string>> {
24
- const addr = `${stripLastSlash(customTypeApiEndpoint)}/slices`;
25
- return axios
26
- .get<Array<{ id: string }>>(addr, {
27
- headers: {
28
- Authorization: `Bearer ${authorization}`,
29
- repository,
30
- },
31
- })
32
- .then((res) => {
33
- return Array.isArray(res.data) ? res.data.map((model) => model.id) : [];
34
- });
35
- }
36
-
37
- async function sendModelToPrismic(
38
- repository: string,
39
- authorization: string,
40
- customTypesApiEndpoint: string,
41
- remoteSliceIds: Array<string>,
42
- model: SliceSM
43
- ): Promise<void> {
44
- const data = Slices.fromSM(model);
45
- const updateOrInsertUrl = `${customTypesApiEndpoint}slices/${
46
- remoteSliceIds.includes(model.id) ? "update" : "insert"
47
- }`;
48
-
49
- return axios
50
- .post(updateOrInsertUrl, data, {
51
- headers: {
52
- Authorization: `Bearer ${authorization}`,
53
- repository,
54
- },
55
- })
56
- .then(() => {
57
- return;
58
- })
59
- .catch((err) => {
60
- handleErrors(
61
- `sending slice ${model.id}, please try again. If the problem persists, contact us.`,
62
- err
63
- );
64
- throw err;
65
- });
66
- }
67
-
68
- export async function sendManyModelsToPrismic(
69
- repository: string,
70
- authorization: string,
71
- customTypesApiEndpoint: string,
72
- remoteSliceIds: Array<string>,
73
- models: Array<SliceSM>
74
- ): Promise<void> {
75
- return Promise.all(
76
- models.map((model) =>
77
- sendModelToPrismic(
78
- repository,
79
- authorization,
80
- customTypesApiEndpoint,
81
- remoteSliceIds,
82
- model
83
- )
84
- )
85
- )
86
- .then(() => {
87
- return;
88
- })
89
- .catch(() => {
90
- process.exit(1);
91
- });
92
- }
93
-
94
- function stripLastSlash(str: string) {
95
- return str.replace(/\/*$/g, "");
96
- }
97
-
98
- export function getRemoteCustomTypeIds(
99
- customTypeApiEndpoint: ApiEndpoints["Models"],
100
- repository: string,
101
- authorization: string
102
- ): Promise<Array<string>> {
103
- const addr = `${stripLastSlash(customTypeApiEndpoint)}/customtypes`;
104
- return axios
105
- .get<CustomType[]>(addr, {
106
- headers: {
107
- Authorization: `Bearer ${authorization}`,
108
- repository,
109
- },
110
- })
111
- .then((res) => {
112
- return Array.isArray(res.data) ? res.data.map((ct) => ct.id) : [];
113
- });
114
- }
115
-
116
- async function sendCustomTypeToPrismic(
117
- repository: string,
118
- authorization: string,
119
- customTypeApiEndpoint: string,
120
- remoteCustomTypeIds: Array<string>,
121
- customType: CustomType
122
- ): Promise<void> {
123
- const shouldUpdate = remoteCustomTypeIds.includes(customType.id);
124
- const addr = `${stripLastSlash(customTypeApiEndpoint)}/customtypes/${
125
- shouldUpdate ? "update" : "insert"
126
- }`;
127
-
128
- return axios
129
- .post(addr, customType, {
130
- headers: {
131
- repository,
132
- Authorization: `Bearer ${authorization}`,
133
- },
134
- })
135
- .then(() => {
136
- return;
137
- })
138
- .catch((err) => {
139
- handleErrors(
140
- `sending custom type ${customType.id}, please try again. If the problem persists, contact us.`,
141
- err
142
- );
143
- throw err;
144
- });
145
- }
146
-
147
- export async function sendManyCustomTypesToPrismic(
148
- repository: string,
149
- authorization: string,
150
- customTypeApiEndpoint: string,
151
- remoteCustomTypeIds: Array<string>,
152
- customTypes: Array<CustomType>
153
- ): Promise<void> {
154
- return Promise.all(
155
- customTypes.map((customType) =>
156
- sendCustomTypeToPrismic(
157
- repository,
158
- authorization,
159
- customTypeApiEndpoint,
160
- remoteCustomTypeIds,
161
- customType
162
- )
163
- )
164
- )
165
- .then(() => {
166
- return;
167
- })
168
- .catch(() => {
169
- process.exit(1);
170
- });
171
- }
@@ -1,20 +0,0 @@
1
- export type ApiEndpoints = {
2
- Models: string;
3
- AclProvider: string;
4
- };
5
-
6
- const ProductionApisEndpoints: ApiEndpoints = {
7
- Models: "https://customtypes.prismic.io/",
8
- AclProvider: "https://0yyeb2g040.execute-api.us-east-1.amazonaws.com/prod/",
9
- };
10
-
11
- const StageApisEndpoints: ApiEndpoints = {
12
- Models: "https://customtypes.wroom.io/",
13
- AclProvider: "https://2iamcvnxf4.execute-api.us-east-1.amazonaws.com/stage/",
14
- };
15
-
16
- export const getEndpointsFromBase = (base: string): ApiEndpoints => {
17
- const url = new URL(base);
18
- if (url.hostname === "wroom.io") return StageApisEndpoints;
19
- return ProductionApisEndpoints;
20
- };
@@ -1,72 +0,0 @@
1
- import axios from "axios";
2
- import * as t from "io-ts";
3
- import { pipe } from "fp-ts/function";
4
- import { fold } from "fp-ts/Either";
5
- import { Utils, Models, CONSTS } from "@slicemachine/core";
6
- import * as Prismic from "@slicemachine/core/build/prismic";
7
-
8
- export async function getUserProfile(
9
- cookies: string,
10
- base = CONSTS.DEFAULT_BASE
11
- ): Promise<Models.UserProfile> {
12
- const userServiceBase =
13
- CONSTS.DEFAULT_BASE === base
14
- ? CONSTS.USER_SERVICE_BASE
15
- : CONSTS.USER_SERVICE_STAGING_BASE;
16
-
17
- // note the auth server also provides a userId
18
- const url = new URL(userServiceBase);
19
- url.pathname = "profile";
20
-
21
- const endpoint = url.toString();
22
- const token = Utils.Cookie.parsePrismicAuthToken(cookies);
23
-
24
- return axios
25
- .get<Models.UserProfile>(endpoint, {
26
- headers: {
27
- Authorization: `Bearer ${token}`,
28
- },
29
- })
30
- .then((res) =>
31
- pipe(
32
- Models.UserProfile.decode(res.data),
33
- fold<t.Errors, Models.UserProfile, Models.UserProfile>(
34
- () => {
35
- throw new Error("Can't parse user profile");
36
- },
37
- (data: Models.UserProfile) => data
38
- )
39
- )
40
- );
41
- }
42
-
43
- export async function validateSessionAndGetProfile(
44
- base = CONSTS.DEFAULT_BASE
45
- ): Promise<{
46
- info: Models.UserInfo;
47
- profile: Models.UserProfile | null;
48
- } | null> {
49
- const config = Prismic.PrismicSharedConfigManager.get();
50
-
51
- if (!config.cookies.length) return Promise.resolve(null); // default config, logged out.
52
- if (base != config.base) return Promise.resolve(null); // not the same base so it doesn't
53
-
54
- try {
55
- const info = await Prismic.Communication.validateSession(
56
- config.cookies,
57
- base
58
- );
59
- const profile = await getUserProfile(config.cookies, base).catch(
60
- () => null
61
- );
62
- if (profile?.shortId) {
63
- Prismic.PrismicSharedConfigManager.setProperties({
64
- shortId: profile.shortId,
65
- intercomHash: profile.intercomHash,
66
- });
67
- }
68
- return { info, profile };
69
- } catch {
70
- return null;
71
- }
72
- }