UNPKG

4.25 kBJavaScriptView Raw
1const path = require('path');
2const fs = require('fs-extra');
3const { getCwd, getExt, splitHubSpotPath, splitLocalPath } = require('./path');
4const { walk } = require('./lib/walk');
5const { MODULE_EXTENSION } = require('./lib/constants');
6
7const isBool = x => !!x === x;
8
9/**
10 * @typedef {object} PathInput
11 * @property {string} path
12 * @property {boolean} isLocal
13 * @property {boolean} isHubSpot
14 */
15
16/**
17 * @param {PathInput} pathInput
18 * @returns {boolean}
19 */
20const isPathInput = pathInput => {
21 return !!(
22 pathInput &&
23 typeof pathInput.path === 'string' &&
24 (isBool(pathInput.isLocal) || isBool(pathInput.isHubSpot))
25 );
26};
27
28const throwInvalidPathInput = pathInput => {
29 if (isPathInput(pathInput)) return;
30 throw new TypeError('Expected PathInput');
31};
32
33/**
34 * @param {PathInput} pathInput
35 * @returns {boolean}
36 */
37const isModuleFolder = pathInput => {
38 throwInvalidPathInput(pathInput);
39 const _path = pathInput.isHubSpot
40 ? path.posix.normalize(pathInput.path)
41 : path.normalize(pathInput.path);
42 return getExt(_path) === MODULE_EXTENSION;
43};
44
45/**
46 * @param {PathInput} pathInput
47 * @returns {boolean}
48 * @throws {TypeError}
49 */
50const isModuleFolderChild = pathInput => {
51 throwInvalidPathInput(pathInput);
52 let pathParts = [];
53 if (pathInput.isLocal) {
54 pathParts = splitLocalPath(pathInput.path);
55 } else if (pathInput.isHubSpot) {
56 pathParts = splitHubSpotPath(pathInput.path);
57 }
58 const { length } = pathParts;
59 // Not a child path?
60 if (length <= 1) return false;
61 // Check if any parent folders are module folders.
62 return pathParts
63 .slice(0, length - 1)
64 .some(part => isModuleFolder({ ...pathInput, path: part }));
65};
66
67// Ids for testing
68const ValidationIds = {
69 SRC_REQUIRED: 'SRC_REQUIRED',
70 DEST_REQUIRED: 'DEST_REQUIRED',
71 MODULE_FOLDER_REQUIRED: 'MODULE_FOLDER_REQUIRED',
72 MODULE_TO_MODULE_NESTING: 'MODULE_TO_MODULE_NESTING',
73 MODULE_NESTING: 'MODULE_NESTING',
74};
75
76const getValidationResult = (id, message) => ({ id, message });
77
78/**
79 * @param {PathInput} src
80 * @param {PathInput} dest
81 * @returns {object[]}
82 */
83async function validateSrcAndDestPaths(src, dest) {
84 const results = [];
85 if (!isPathInput(src)) {
86 results.push(
87 getValidationResult(ValidationIds.SRC_REQUIRED, '`src` is required.')
88 );
89 }
90 if (!isPathInput(dest)) {
91 results.push(
92 getValidationResult(ValidationIds.DEST_REQUIRED, '`dest` is required.')
93 );
94 }
95 if (results.length) {
96 return results;
97 }
98 const [_src, _dest] = [src, dest].map(inputPath => {
99 const result = { ...inputPath };
100 if (result.isLocal) {
101 result.path = path.resolve(getCwd(), result.path);
102 } else if (result.isHubSpot) {
103 result.path = path.posix.normalize(result.path);
104 }
105 return result;
106 });
107 // src is a .module folder and dest is within a module. (Nesting)
108 // e.g. `upload foo.module bar.module/zzz`
109 if (isModuleFolder(_src) && isModuleFolderChild(_dest)) {
110 return results.concat(
111 getValidationResult(
112 ValidationIds.MODULE_TO_MODULE_NESTING,
113 '`src` is a module path and `dest` is within a module.'
114 )
115 );
116 }
117 // src is a .module folder but dest is not
118 // e.g. `upload foo.module bar`
119 if (isModuleFolder(_src) && !isModuleFolder(_dest)) {
120 return results.concat(
121 getValidationResult(
122 ValidationIds.MODULE_FOLDER_REQUIRED,
123 '`src` is a module path but `dest` is not.'
124 )
125 );
126 }
127 // src is a folder that includes modules and dest is within a module. (Nesting)
128 if (_src.isLocal && isModuleFolderChild(_dest)) {
129 const stat = await fs.stat(_src.path);
130 if (stat.isDirectory()) {
131 const files = await walk(_src.path);
132 const srcHasModulesChildren = files.some(file =>
133 isModuleFolderChild({ ..._src, path: file })
134 );
135 if (srcHasModulesChildren) {
136 return results.concat(
137 getValidationResult(
138 ValidationIds.MODULE_NESTING,
139 '`src` contains modules and `dest` is within a module.'
140 )
141 );
142 }
143 }
144 }
145 // TODO: Add local FS check for dest.isLocal to support `fetch`
146 return results;
147}
148
149module.exports = {
150 isModuleFolder,
151 isModuleFolderChild,
152 validateSrcAndDestPaths,
153 ValidationIds,
154};