@squiz/dxp-cli-next 4.0.1-develop.1 → 4.1.0-develop.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/README.md CHANGED
@@ -20,6 +20,6 @@ Options:
20
20
  -h, --help display help for command
21
21
 
22
22
  Commands:
23
- auth log into the sxc
24
- td Template deployment commands
23
+ auth Authenticate into the DXP CLI
24
+ cmp Component Service Commands
25
25
  ```
@@ -0,0 +1,33 @@
1
+ import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
2
+ export declare const COOKIE_KEY = "dxp-sessionid";
3
+ /**
4
+ * ApiService provides HTTP access to the DXP systems.
5
+ * This class wraps the authorization model within the DXP system by seamlessly
6
+ * adding the session cookie to requests.
7
+ */
8
+ export declare class ApiService {
9
+ client: AxiosInstance;
10
+ constructor();
11
+ }
12
+ /**
13
+ * The API Service requestInterceptor will load the session cookie from
14
+ * storage and set it on any request. This ensures that post-login, all requests
15
+ * will seamlessly have the authorization required.
16
+ */
17
+ export declare function requestInterceptor(request: AxiosRequestConfig): Promise<AxiosRequestConfig<any>>;
18
+ /**
19
+ * The API Service responseInterceptor will handle the case that a
20
+ * "Set-Cookie" header has been returned from an API request and will
21
+ * save it accordingly for future use.
22
+ */
23
+ export declare function responseInterceptor(response: AxiosResponse): Promise<AxiosResponse<any, any>>;
24
+ export declare const MESSAGE_INVALID_REQUEST_NO_SESSIONID = "Invalid request: no session";
25
+ export declare const MESSAGE_FAILED_TO_CREATE_SESSION = "Failed to create session";
26
+ export declare const MESSAGE_FAILED_TO_REMOVE_SESSION = "Failed to remove user session";
27
+ export declare const MESSAGE_FAILED_TO_CREATE_ORG_SESSION = "Failed to create organisation session";
28
+ export declare class InvalidLoginSessionError extends Error {
29
+ constructor();
30
+ }
31
+ export declare class InvalidTenantError extends Error {
32
+ constructor();
33
+ }
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.InvalidTenantError = exports.InvalidLoginSessionError = exports.MESSAGE_FAILED_TO_CREATE_ORG_SESSION = exports.MESSAGE_FAILED_TO_REMOVE_SESSION = exports.MESSAGE_FAILED_TO_CREATE_SESSION = exports.MESSAGE_INVALID_REQUEST_NO_SESSIONID = exports.responseInterceptor = exports.requestInterceptor = exports.ApiService = exports.COOKIE_KEY = void 0;
16
+ const axios_1 = __importDefault(require("axios"));
17
+ const tough_cookie_1 = require("tough-cookie");
18
+ const ApplicationStore_1 = require("./ApplicationStore");
19
+ exports.COOKIE_KEY = 'dxp-sessionid';
20
+ /**
21
+ * ApiService provides HTTP access to the DXP systems.
22
+ * This class wraps the authorization model within the DXP system by seamlessly
23
+ * adding the session cookie to requests.
24
+ */
25
+ class ApiService {
26
+ constructor() {
27
+ this.client = axios_1.default.create({ validateStatus: null });
28
+ this.client.interceptors.request.use(requestInterceptor);
29
+ this.client.interceptors.response.use(responseInterceptor);
30
+ }
31
+ }
32
+ exports.ApiService = ApiService;
33
+ /**
34
+ * The API Service requestInterceptor will load the session cookie from
35
+ * storage and set it on any request. This ensures that post-login, all requests
36
+ * will seamlessly have the authorization required.
37
+ */
38
+ function requestInterceptor(request) {
39
+ return __awaiter(this, void 0, void 0, function* () {
40
+ const maybeSessionCookie = yield (0, ApplicationStore_1.getApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie);
41
+ if (!maybeSessionCookie) {
42
+ return request;
43
+ }
44
+ const sessionCookie = tough_cookie_1.Cookie.fromJSON(maybeSessionCookie);
45
+ if (!sessionCookie) {
46
+ return request;
47
+ }
48
+ request.headers = Object.assign(Object.assign({}, (request.headers || {})), { cookie: sessionCookie === null || sessionCookie === void 0 ? void 0 : sessionCookie.cookieString() });
49
+ return request;
50
+ });
51
+ }
52
+ exports.requestInterceptor = requestInterceptor;
53
+ /**
54
+ * The API Service responseInterceptor will handle the case that a
55
+ * "Set-Cookie" header has been returned from an API request and will
56
+ * save it accordingly for future use.
57
+ */
58
+ function responseInterceptor(response) {
59
+ var _a, _b;
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ if (response.status >= 400) {
62
+ yield handleSessionErrors(response);
63
+ }
64
+ const maybeSessionCookie = (_b = (_a = response.headers['set-cookie']) === null || _a === void 0 ? void 0 : _a.map(cookieHeader => tough_cookie_1.Cookie.parse(cookieHeader))) === null || _b === void 0 ? void 0 : _b.find(cookie => (cookie === null || cookie === void 0 ? void 0 : cookie.key) === exports.COOKIE_KEY);
65
+ if (!maybeSessionCookie) {
66
+ return response;
67
+ }
68
+ yield (0, ApplicationStore_1.saveApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie, JSON.stringify(maybeSessionCookie.toJSON()));
69
+ return response;
70
+ });
71
+ }
72
+ exports.responseInterceptor = responseInterceptor;
73
+ exports.MESSAGE_INVALID_REQUEST_NO_SESSIONID = 'Invalid request: no session';
74
+ exports.MESSAGE_FAILED_TO_CREATE_SESSION = 'Failed to create session';
75
+ exports.MESSAGE_FAILED_TO_REMOVE_SESSION = 'Failed to remove user session';
76
+ exports.MESSAGE_FAILED_TO_CREATE_ORG_SESSION = 'Failed to create organisation session';
77
+ class InvalidLoginSessionError extends Error {
78
+ constructor() {
79
+ super('Invalid session');
80
+ }
81
+ }
82
+ exports.InvalidLoginSessionError = InvalidLoginSessionError;
83
+ class InvalidTenantError extends Error {
84
+ constructor() {
85
+ super('Invalid tenant');
86
+ }
87
+ }
88
+ exports.InvalidTenantError = InvalidTenantError;
89
+ function handleSessionErrors(response) {
90
+ return __awaiter(this, void 0, void 0, function* () {
91
+ if (response.data.message === exports.MESSAGE_INVALID_REQUEST_NO_SESSIONID) {
92
+ yield (0, ApplicationStore_1.deleteApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie);
93
+ throw new InvalidLoginSessionError();
94
+ }
95
+ if (response.data.message === exports.MESSAGE_FAILED_TO_CREATE_SESSION) {
96
+ yield (0, ApplicationStore_1.deleteApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie);
97
+ throw new InvalidLoginSessionError();
98
+ }
99
+ if (response.data.message === exports.MESSAGE_FAILED_TO_CREATE_ORG_SESSION) {
100
+ throw new InvalidTenantError();
101
+ }
102
+ });
103
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const tough_cookie_1 = require("tough-cookie");
13
+ const ApiService_1 = require("./ApiService");
14
+ const ApplicationStore_1 = require("./ApplicationStore");
15
+ const VALID_COOKIE = new tough_cookie_1.Cookie({
16
+ key: ApiService_1.COOKIE_KEY,
17
+ value: 'valid-cookie-value',
18
+ });
19
+ describe('ApiService', () => {
20
+ describe('requestInterceptor', () => {
21
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
22
+ yield (0, ApplicationStore_1.deleteApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie);
23
+ }));
24
+ it('loads stored session cookie into request config', () => __awaiter(void 0, void 0, void 0, function* () {
25
+ yield (0, ApplicationStore_1.saveApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie, JSON.stringify(VALID_COOKIE.toJSON()));
26
+ const requestConfig = yield (0, ApiService_1.requestInterceptor)({});
27
+ expect(requestConfig).toHaveProperty('headers');
28
+ expect(requestConfig.headers).toHaveProperty('cookie');
29
+ expect(tough_cookie_1.Cookie.parse(requestConfig.headers['cookie'])).toHaveProperty('value', 'valid-cookie-value');
30
+ }));
31
+ it('does not use session cookie if none stored', () => __awaiter(void 0, void 0, void 0, function* () {
32
+ const requestConfig = yield (0, ApiService_1.requestInterceptor)({});
33
+ expect(requestConfig).toEqual({});
34
+ }));
35
+ });
36
+ describe('responseInterceptor', () => {
37
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
38
+ yield (0, ApplicationStore_1.deleteApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie);
39
+ }));
40
+ it('saves the set-cookie for a session into the store', () => __awaiter(void 0, void 0, void 0, function* () {
41
+ const setCookie = [VALID_COOKIE.toString()];
42
+ const response = {
43
+ data: {},
44
+ config: {},
45
+ status: 204,
46
+ statusText: '',
47
+ headers: { 'set-cookie': setCookie },
48
+ };
49
+ const postIntercept = yield (0, ApiService_1.responseInterceptor)(response);
50
+ const stored = yield (0, ApplicationStore_1.getApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie);
51
+ expect(stored).toBeDefined();
52
+ expect(JSON.parse(stored)).toMatchObject({
53
+ key: VALID_COOKIE.key,
54
+ value: VALID_COOKIE.value,
55
+ });
56
+ expect(postIntercept).toEqual(response);
57
+ }));
58
+ it('does not error if no set-cookie', () => __awaiter(void 0, void 0, void 0, function* () {
59
+ const response = {
60
+ data: {},
61
+ config: {},
62
+ status: 204,
63
+ statusText: '',
64
+ headers: {},
65
+ };
66
+ const postIntercept = yield (0, ApiService_1.responseInterceptor)(response);
67
+ expect(yield (0, ApplicationStore_1.getApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie)).toBeUndefined();
68
+ expect(postIntercept).toEqual(response);
69
+ }));
70
+ it('does not delete existing session cookie if no set-cookie', () => __awaiter(void 0, void 0, void 0, function* () {
71
+ yield (0, ApplicationStore_1.saveApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie, JSON.stringify(VALID_COOKIE.toJSON()));
72
+ const response = {
73
+ data: {},
74
+ config: {},
75
+ status: 204,
76
+ statusText: '',
77
+ headers: {},
78
+ };
79
+ const postIntercept = yield (0, ApiService_1.responseInterceptor)(response);
80
+ expect(yield (0, ApplicationStore_1.getApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie)).toEqual(JSON.stringify(VALID_COOKIE.toJSON()));
81
+ expect(postIntercept).toEqual(response);
82
+ }));
83
+ it('overwrites old session cookie when new set-cookie provided', () => __awaiter(void 0, void 0, void 0, function* () {
84
+ yield (0, ApplicationStore_1.saveApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie, JSON.stringify(VALID_COOKIE.toJSON()));
85
+ const response = {
86
+ data: { nested: 'rest' },
87
+ config: {},
88
+ status: 204,
89
+ statusText: '',
90
+ headers: {
91
+ 'set-cookie': [
92
+ tough_cookie_1.Cookie.fromJSON({
93
+ key: ApiService_1.COOKIE_KEY,
94
+ value: 'another-value',
95
+ }).toString(),
96
+ ],
97
+ },
98
+ };
99
+ const postIntercept = yield (0, ApiService_1.responseInterceptor)(response);
100
+ expect(JSON.parse((yield (0, ApplicationStore_1.getApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie)))).toMatchObject({ key: ApiService_1.COOKIE_KEY, value: 'another-value' });
101
+ expect(postIntercept).toEqual(response);
102
+ }));
103
+ it('errors with InvalidSession on failed session creation if no existing file', () => __awaiter(void 0, void 0, void 0, function* () {
104
+ const response = {
105
+ data: { message: ApiService_1.MESSAGE_FAILED_TO_CREATE_SESSION },
106
+ config: {},
107
+ status: 401,
108
+ statusText: '',
109
+ headers: {},
110
+ };
111
+ yield expect(() => (0, ApiService_1.responseInterceptor)(response)).rejects.toThrowError(ApiService_1.InvalidLoginSessionError);
112
+ }));
113
+ it('errors with InvalidSession on no session provided', () => __awaiter(void 0, void 0, void 0, function* () {
114
+ const response = {
115
+ data: { message: ApiService_1.MESSAGE_INVALID_REQUEST_NO_SESSIONID },
116
+ config: {},
117
+ status: 401,
118
+ statusText: '',
119
+ headers: {},
120
+ };
121
+ yield expect(() => (0, ApiService_1.responseInterceptor)(response)).rejects.toThrowError(ApiService_1.InvalidLoginSessionError);
122
+ }));
123
+ it('errors with InvalidSession on failed session creation and deletes existing session', () => __awaiter(void 0, void 0, void 0, function* () {
124
+ yield (0, ApplicationStore_1.saveApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie, JSON.stringify(VALID_COOKIE.toJSON()));
125
+ const response = {
126
+ data: { message: ApiService_1.MESSAGE_FAILED_TO_CREATE_SESSION },
127
+ config: {},
128
+ status: 401,
129
+ statusText: '',
130
+ headers: {},
131
+ };
132
+ yield expect(() => (0, ApiService_1.responseInterceptor)(response)).rejects.toThrowError(ApiService_1.InvalidLoginSessionError);
133
+ expect(yield (0, ApplicationStore_1.getApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie)).toBeFalsy();
134
+ }));
135
+ it('errors when organisation session cannot be created and does not delete session cookie', () => __awaiter(void 0, void 0, void 0, function* () {
136
+ yield (0, ApplicationStore_1.saveApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie, JSON.stringify(VALID_COOKIE.toJSON()));
137
+ const response = {
138
+ data: { message: ApiService_1.MESSAGE_FAILED_TO_CREATE_ORG_SESSION },
139
+ config: {},
140
+ status: 401,
141
+ statusText: '',
142
+ headers: {},
143
+ };
144
+ yield expect(() => (0, ApiService_1.responseInterceptor)(response)).rejects.toThrowError(ApiService_1.InvalidTenantError);
145
+ expect(yield (0, ApplicationStore_1.getApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie)).toBeTruthy();
146
+ }));
147
+ });
148
+ });
@@ -0,0 +1,7 @@
1
+ export declare class ApplicationConfig {
2
+ baseUrl: string;
3
+ region: string;
4
+ tenant: string;
5
+ constructor(baseUrl: string, region: string, tenant: string);
6
+ }
7
+ export declare function fetchApplicationConfig(tenantOverride?: string): Promise<ApplicationConfig>;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.fetchApplicationConfig = exports.ApplicationConfig = void 0;
13
+ const ApplicationStore_1 = require("./ApplicationStore");
14
+ class ApplicationConfig {
15
+ constructor(baseUrl, region, tenant) {
16
+ this.baseUrl = baseUrl;
17
+ this.region = region;
18
+ this.tenant = tenant;
19
+ if (!baseUrl)
20
+ throw new Error('No baseUrl configured');
21
+ if (!region)
22
+ throw new Error('No region configured');
23
+ if (!tenant)
24
+ throw new Error('No tenant configured');
25
+ }
26
+ }
27
+ exports.ApplicationConfig = ApplicationConfig;
28
+ function fetchApplicationConfig(tenantOverride) {
29
+ return __awaiter(this, void 0, void 0, function* () {
30
+ const stored = yield (0, ApplicationStore_1.getApplicationFile)(ApplicationStore_1.STORE_FILES.sessionConfig);
31
+ if (!stored) {
32
+ throw new Error('No stored config');
33
+ }
34
+ const parsed = JSON.parse(stored);
35
+ return new ApplicationConfig(parsed.baseUrl, parsed.region, tenantOverride || parsed.tenant);
36
+ });
37
+ }
38
+ exports.fetchApplicationConfig = fetchApplicationConfig;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const ApplicationStore_1 = require("./ApplicationStore");
13
+ const ApplicationConfig_1 = require("./ApplicationConfig");
14
+ describe('ApplicationConfig', () => {
15
+ it('asserts all fields to have non-empty string', () => {
16
+ expect(() => new ApplicationConfig_1.ApplicationConfig('', 'region', 'tenant')).toThrowError();
17
+ expect(() => new ApplicationConfig_1.ApplicationConfig('baseUrl', '', 'tenant')).toThrowError();
18
+ expect(() => new ApplicationConfig_1.ApplicationConfig('baseUrl', 'region', '')).toThrowError();
19
+ });
20
+ it('asserts all fields to have a string value', () => {
21
+ expect(
22
+ // @ts-expect-error requires string values
23
+ () => new ApplicationConfig_1.ApplicationConfig(undefined, 'region', 'tenant')).toThrowError();
24
+ expect(
25
+ // @ts-expect-error requires string values
26
+ () => new ApplicationConfig_1.ApplicationConfig('baseUrl', undefined, 'tenant')).toThrowError();
27
+ expect(
28
+ // @ts-expect-error requires string values
29
+ () => new ApplicationConfig_1.ApplicationConfig('baseUrl', 'region', undefined)).toThrowError();
30
+ });
31
+ describe('fetchApplicationConfig', () => {
32
+ beforeAll(() => __awaiter(void 0, void 0, void 0, function* () {
33
+ yield (0, ApplicationStore_1.deleteApplicationFile)(ApplicationStore_1.STORE_FILES.sessionConfig);
34
+ }));
35
+ it('loads previously saved config', () => __awaiter(void 0, void 0, void 0, function* () {
36
+ yield (0, ApplicationStore_1.saveApplicationFile)(ApplicationStore_1.STORE_FILES.sessionConfig, JSON.stringify({
37
+ baseUrl: 'baseUrl',
38
+ region: 'region',
39
+ tenant: 'tenant',
40
+ }));
41
+ const config = yield (0, ApplicationConfig_1.fetchApplicationConfig)();
42
+ expect(config).toMatchObject({
43
+ baseUrl: 'baseUrl',
44
+ region: 'region',
45
+ tenant: 'tenant',
46
+ });
47
+ }));
48
+ it('loads previously saved config and allows tenant override', () => __awaiter(void 0, void 0, void 0, function* () {
49
+ yield (0, ApplicationStore_1.saveApplicationFile)(ApplicationStore_1.STORE_FILES.sessionConfig, JSON.stringify({
50
+ baseUrl: 'baseUrl',
51
+ region: 'region',
52
+ tenant: 'tenant',
53
+ }));
54
+ const config = yield (0, ApplicationConfig_1.fetchApplicationConfig)('another tenant');
55
+ expect(config).toMatchObject({
56
+ baseUrl: 'baseUrl',
57
+ region: 'region',
58
+ tenant: 'another tenant',
59
+ });
60
+ }));
61
+ it('loads previously saved config with no tenant and with tenant override', () => __awaiter(void 0, void 0, void 0, function* () {
62
+ yield (0, ApplicationStore_1.saveApplicationFile)(ApplicationStore_1.STORE_FILES.sessionConfig, JSON.stringify({
63
+ baseUrl: 'baseUrl',
64
+ region: 'region',
65
+ }));
66
+ const config = yield (0, ApplicationConfig_1.fetchApplicationConfig)('another tenant');
67
+ expect(config).toMatchObject({
68
+ baseUrl: 'baseUrl',
69
+ region: 'region',
70
+ tenant: 'another tenant',
71
+ });
72
+ }));
73
+ it('errors when no tenant and no tenant override', () => __awaiter(void 0, void 0, void 0, function* () {
74
+ yield (0, ApplicationStore_1.saveApplicationFile)(ApplicationStore_1.STORE_FILES.sessionConfig, JSON.stringify({
75
+ baseUrl: 'baseUrl',
76
+ region: 'region',
77
+ }));
78
+ yield expect(() => (0, ApplicationConfig_1.fetchApplicationConfig)()).rejects.toThrowError();
79
+ }));
80
+ it('errors when no file is saved', () => __awaiter(void 0, void 0, void 0, function* () {
81
+ yield expect(() => (0, ApplicationConfig_1.fetchApplicationConfig)()).rejects.toThrowError();
82
+ }));
83
+ });
84
+ });
@@ -0,0 +1,13 @@
1
+ import type { Paths } from 'env-paths';
2
+ export declare const APP_NAME: string;
3
+ export interface FileStore {
4
+ name: string;
5
+ type: keyof Paths;
6
+ }
7
+ export declare const STORE_FILES: {
8
+ sessionCookie: FileStore;
9
+ sessionConfig: FileStore;
10
+ };
11
+ export declare function getApplicationFile({ name: filename, type }: FileStore): Promise<string | undefined>;
12
+ export declare function saveApplicationFile({ name: filename, type }: FileStore, content: string): Promise<void>;
13
+ export declare function deleteApplicationFile({ name: filename, type, }: FileStore): Promise<void>;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ var _a;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.deleteApplicationFile = exports.saveApplicationFile = exports.getApplicationFile = exports.STORE_FILES = exports.APP_NAME = void 0;
17
+ const env_paths_1 = __importDefault(require("env-paths"));
18
+ const promises_1 = __importDefault(require("fs/promises"));
19
+ const path_1 = __importDefault(require("path"));
20
+ exports.APP_NAME = (_a = process.env.APP_NAME) !== null && _a !== void 0 ? _a : 'dxp-cli';
21
+ exports.STORE_FILES = {
22
+ sessionCookie: { name: 'session-cookie', type: 'data' },
23
+ sessionConfig: { name: 'session-config', type: 'config' },
24
+ };
25
+ const APPLICATION_FILE_PATHS = (0, env_paths_1.default)(exports.APP_NAME);
26
+ function getApplicationFile({ name: filename, type }) {
27
+ return __awaiter(this, void 0, void 0, function* () {
28
+ try {
29
+ return yield promises_1.default.readFile(path_1.default.join(APPLICATION_FILE_PATHS[type], filename), {
30
+ encoding: 'utf-8',
31
+ });
32
+ }
33
+ catch (e) {
34
+ return;
35
+ }
36
+ });
37
+ }
38
+ exports.getApplicationFile = getApplicationFile;
39
+ function saveApplicationFile({ name: filename, type }, content) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ yield promises_1.default.mkdir(APPLICATION_FILE_PATHS[type], { recursive: true });
42
+ yield promises_1.default.writeFile(path_1.default.join(APPLICATION_FILE_PATHS[type], filename), content);
43
+ });
44
+ }
45
+ exports.saveApplicationFile = saveApplicationFile;
46
+ function deleteApplicationFile({ name: filename, type, }) {
47
+ return __awaiter(this, void 0, void 0, function* () {
48
+ yield promises_1.default.rm(path_1.default.join(APPLICATION_FILE_PATHS[type], filename), {
49
+ force: true,
50
+ });
51
+ });
52
+ }
53
+ exports.deleteApplicationFile = deleteApplicationFile;
@@ -18,7 +18,7 @@ describe('dxp', () => {
18
18
  const commandsText = (_a = stdout
19
19
  .match(/Commands\:(.*)/is)) === null || _a === void 0 ? void 0 : _a[1].split('\n').map(a => a.trim()).filter(a => !!a);
20
20
  expect(commandsText).toEqual([
21
- 'auth log into the sxc',
21
+ 'auth Authenticate into the DXP CLI',
22
22
  'td Template deployment commands',
23
23
  'cmp Component Service Commands',
24
24
  ]);
@@ -30,7 +30,7 @@ describe('dxp', () => {
30
30
  const commandsText = (_a = stdout
31
31
  .match(/Commands\:(.*)/is)) === null || _a === void 0 ? void 0 : _a[1].split('\n').map(a => a.trim()).filter(a => !!a);
32
32
  expect(commandsText).toEqual([
33
- 'auth log into the sxc',
33
+ 'auth Authenticate into the DXP CLI',
34
34
  'cmp Component Service Commands',
35
35
  ]);
36
36
  });
package/lib/auth/index.js CHANGED
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const commander_1 = require("commander");
7
7
  const login_1 = __importDefault(require("./login"));
8
8
  const authCommand = new commander_1.Command('auth');
9
- authCommand.description('log into the sxc').addCommand(login_1.default);
10
- //.addCommand(identityCommand);
9
+ authCommand
10
+ .description('Authenticate into the DXP CLI')
11
+ .addCommand(login_1.default);
11
12
  exports.default = authCommand;
@@ -1,3 +1,3 @@
1
1
  import { Command } from 'commander';
2
- declare const _default: Command;
3
- export default _default;
2
+ declare const loginCommand: Command;
3
+ export default loginCommand;
package/lib/auth/login.js CHANGED
@@ -1,10 +1,62 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ const cli_color_1 = require("cli-color");
3
13
  const commander_1 = require("commander");
4
- exports.default = new commander_1.Command('login')
14
+ const ApiService_1 = require("../ApiService");
15
+ const ApplicationStore_1 = require("../ApplicationStore");
16
+ // TODO: Confirm production URL
17
+ const PRODUCTION_URL = 'https://dxp.squiz.cloud';
18
+ const loginCommand = new commander_1.Command('login')
5
19
  .name('login')
6
- .argument('<username>', 'sxc username')
7
- .argument('<password>', 'sxc password')
8
- .action((username, password) => {
9
- console.log('NOT IMPLEMENTED ', { username });
20
+ .description('Login to the DXP platform')
21
+ .argument('<username>', 'dxp username')
22
+ .argument('<password>', 'dxp password')
23
+ .addOption(new commander_1.Option('--dxp-base-url <baseURL>', 'dxp cloud base url e.g. "https://develop-apps-dxp-console.dev.dxp.squiz.cloud/"')
24
+ .env('DXP_BASE_URL')
25
+ .default(PRODUCTION_URL))
26
+ .addOption(new commander_1.Option('--region <region>').choices(['au']).default('au'))
27
+ .addOption(new commander_1.Option('--tenant <tenantID>'))
28
+ .configureOutput({
29
+ outputError(str, write) {
30
+ write((0, cli_color_1.red)(str));
31
+ },
32
+ })
33
+ .action((username, password, options) => {
34
+ return handleLoginRequest({ username, password }, options);
10
35
  });
36
+ exports.default = loginCommand;
37
+ function handleLoginRequest(login, options) {
38
+ return __awaiter(this, void 0, void 0, function* () {
39
+ const apiService = new ApiService_1.ApiService();
40
+ // Just need to make sure no extra slash at the end
41
+ const baseUrl = options.dxpBaseUrl.replace(/\/$/, '');
42
+ const loginUrl = `${baseUrl}/__dxp/${options.region}/dxp/access/login`;
43
+ const loginString = Buffer.from(JSON.stringify(login)).toString('base64');
44
+ const loginResponse = yield apiService.client.post(loginUrl, {
45
+ token: loginString,
46
+ });
47
+ switch (loginResponse.status) {
48
+ case 204:
49
+ console.log('Login successful');
50
+ yield (0, ApplicationStore_1.saveApplicationFile)(ApplicationStore_1.STORE_FILES.sessionConfig, JSON.stringify({
51
+ baseUrl,
52
+ tenant: options.tenant,
53
+ region: options.region,
54
+ }));
55
+ return;
56
+ case 400:
57
+ loginCommand.error(loginResponse.data.message);
58
+ default:
59
+ loginCommand.error('An error occurred during login');
60
+ }
61
+ });
62
+ }
package/lib/cmp/deploy.js CHANGED
@@ -15,20 +15,36 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  const commander_1 = require("commander");
16
16
  const component_cli_lib_1 = require("@squiz/component-cli-lib");
17
17
  const cli_color_1 = __importDefault(require("cli-color"));
18
+ const ApiService_1 = require("../ApiService");
19
+ const ApplicationStore_1 = require("../ApplicationStore");
20
+ const ApplicationConfig_1 = require("../ApplicationConfig");
18
21
  const deployCommand = new commander_1.Command()
19
22
  .name('deploy')
20
23
  .argument('<source>', 'folder/file path containing the template files to deploy')
21
- .addOption(new commander_1.Option('-cu, --component-service-url <string>', 'Required: Url for the component service')
22
- .env('COMPONENT_SERVICE_URL')
23
- .makeOptionMandatory(true))
24
- .addOption(new commander_1.Option('-ru, --render-runtime-url <string>', 'Required: Url for the render runtime service')
25
- .env('COMPONENT_RENDER_SERVICE_URL')
26
- .makeOptionMandatory(true))
27
- .action((source, options, self) => __awaiter(void 0, void 0, void 0, function* () {
24
+ .addOption(new commander_1.Option('-cu, --component-service-url <string>', 'Override the component service url from login').env('COMPONENT_SERVICE_URL'))
25
+ .addOption(new commander_1.Option('-t, --tenant <string>', 'Tenant ID to deploy to. If not provided will use configured tenant from login'))
26
+ .action((source, options) => __awaiter(void 0, void 0, void 0, function* () {
27
+ var _a, _b;
28
+ if (!options.componentServiceUrl &&
29
+ !(yield (0, ApplicationStore_1.getApplicationFile)(ApplicationStore_1.STORE_FILES.sessionCookie))) {
30
+ deployCommand.error(cli_color_1.default.red('You must login to deploy components. See `dxp-next auth login`'));
31
+ }
32
+ const apiService = new ApiService_1.ApiService();
33
+ if ((_a = options.componentServiceUrl) === null || _a === void 0 ? void 0 : _a.match(/v1\/?$/)) {
34
+ console.warn(cli_color_1.default.yellow('WARN: Component service URL no longer requires /v1/ suffix'));
35
+ }
28
36
  try {
29
- return yield (0, component_cli_lib_1.uploadComponentFolder)(source, options.componentServiceUrl, options.renderRuntimeUrl);
37
+ const componentServiceUrl = ((_b = options.componentServiceUrl) === null || _b === void 0 ? void 0 : _b.replace(/v1\/?$/, '')) ||
38
+ (yield buildComponentServiceUrl(options.tenant));
39
+ return yield (0, component_cli_lib_1.uploadComponentFolder)(apiService.client, componentServiceUrl, source);
30
40
  }
31
41
  catch (error) {
42
+ if (error instanceof ApiService_1.InvalidLoginSessionError) {
43
+ deployCommand.error(cli_color_1.default.red('Login session expired. Please login again.'));
44
+ }
45
+ if (error instanceof ApiService_1.InvalidTenantError) {
46
+ deployCommand.error(cli_color_1.default.red('Cannot deploy to specified tenant'));
47
+ }
32
48
  if (error.message) {
33
49
  deployCommand.error(cli_color_1.default.red(error.message));
34
50
  }
@@ -39,3 +55,9 @@ const deployCommand = new commander_1.Command()
39
55
  }
40
56
  }));
41
57
  exports.default = deployCommand;
58
+ function buildComponentServiceUrl(tenantID) {
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ const existingConfig = yield (0, ApplicationConfig_1.fetchApplicationConfig)(tenantID);
61
+ return `${existingConfig.baseUrl}/__dxp/${existingConfig.region}/components-management/${existingConfig.tenant}/`;
62
+ });
63
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/dxp-cli-next",
3
- "version": "4.0.1-develop.1",
3
+ "version": "4.1.0-develop.1",
4
4
  "repository": {
5
5
  "url": "https://gitlab.squiz.net/developer-experience/dxp-cli-next"
6
6
  },
@@ -39,10 +39,13 @@
39
39
  "codecov"
40
40
  ],
41
41
  "dependencies": {
42
- "@squiz/component-cli-lib": "1.2.11",
42
+ "@squiz/component-cli-lib": "1.2.12",
43
43
  "@squiz/deployment-service-lib": "1.1.5-alpha.85",
44
+ "axios": "1.1.3",
44
45
  "cli-color": "2.0.3",
45
46
  "commander": "9.4.0",
47
+ "env-paths": "2.2.1",
48
+ "tough-cookie": "4.1.2",
46
49
  "update-notifier": "5.1.0"
47
50
  },
48
51
  "devDependencies": {
@@ -52,6 +55,7 @@
52
55
  "@types/cli-color": "2.0.2",
53
56
  "@types/jest": "28.1.6",
54
57
  "@types/node": "17.0.45",
58
+ "@types/tough-cookie": "4.0.2",
55
59
  "@types/update-notifier": "5.1.0",
56
60
  "@typescript-eslint/eslint-plugin": "5.30.7",
57
61
  "@typescript-eslint/parser": "5.30.7",
@@ -1,3 +0,0 @@
1
- import { Command } from 'commander';
2
- declare const _default: Command;
3
- export default _default;
@@ -1,8 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const commander_1 = require("commander");
4
- exports.default = new commander_1.Command('identity')
5
- .command('identity', 'returns the currently logged in users details')
6
- .action(() => {
7
- console.log('getting identity');
8
- });