@sitecore-content-sdk/nextjs 0.2.0-canary.13 → 0.2.0-canary.15
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/cjs/tools/codegen/extract-components.js +64 -0
- package/dist/cjs/tools/codegen/utils.js +255 -0
- package/dist/cjs/tools/index.js +3 -1
- package/dist/esm/tools/codegen/extract-components.js +57 -0
- package/dist/esm/tools/codegen/utils.js +212 -0
- package/dist/esm/tools/index.js +1 -0
- package/package.json +11 -5
- package/types/tools/codegen/extract-components.d.ts +10 -0
- package/types/tools/codegen/utils.d.ts +40 -0
- package/types/tools/index.d.ts +1 -0
|
@@ -0,0 +1,64 @@
|
|
|
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.extractComponents = void 0;
|
|
16
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
17
|
+
const utils_1 = require("./utils");
|
|
18
|
+
const core_1 = require("@sitecore-content-sdk/core");
|
|
19
|
+
const tools_1 = require("@sitecore-content-sdk/core/tools");
|
|
20
|
+
/**
|
|
21
|
+
* Extracts components from the app folder and sends them to XMCloud.
|
|
22
|
+
* @param {ExtractComponentsConfig} args - Config for components extraction
|
|
23
|
+
*/
|
|
24
|
+
const extractComponents = (args) => {
|
|
25
|
+
const authParams = {
|
|
26
|
+
clientId: process.env.SITECORE_AUTH_CLIENT_ID || '',
|
|
27
|
+
clientSecret: process.env.SITECORE_AUTH_CLIENT_SECRET || '',
|
|
28
|
+
endpoint: process.env.SITECORE_AUTH_ENDPOINT || core_1.constants.DEFAULT_SITECORE_AUTH_ENDPOINT,
|
|
29
|
+
audience: process.env.SITECORE_AUTH_AUDIENCE || core_1.constants.DEFAULT_SITECORE_AUTH_AUDIENCE,
|
|
30
|
+
};
|
|
31
|
+
return () => __awaiter(void 0, void 0, void 0, function* () {
|
|
32
|
+
if (!(0, utils_1.validateDeployContext)()) {
|
|
33
|
+
console.log(chalk_1.default.yellow('Skipping code extraction, not in deploy context'));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!(0, utils_1.validateConsent)()) {
|
|
37
|
+
console.log(chalk_1.default.yellow('Skipping code extraction, consent not given'));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const basePath = process.cwd();
|
|
41
|
+
try {
|
|
42
|
+
const bearer = yield (0, tools_1.fetchBearerToken)(authParams);
|
|
43
|
+
if (!bearer) {
|
|
44
|
+
console.error(chalk_1.default.red('Failed to get bearer token, aborting code extraction'));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const componentPaths = yield (0, utils_1.resolveComponentImportFiles)(basePath, args.componentMapPath);
|
|
48
|
+
const codeDispatches = Array.from(componentPaths, (mapEntry) => (0, utils_1.sendCode)({
|
|
49
|
+
file: {
|
|
50
|
+
name: mapEntry[0],
|
|
51
|
+
path: mapEntry[1],
|
|
52
|
+
type: utils_1.ExtractedFileType.Component,
|
|
53
|
+
},
|
|
54
|
+
token: bearer,
|
|
55
|
+
edgeUrl: args.scConfig.api.edge.edgeUrl,
|
|
56
|
+
}));
|
|
57
|
+
yield Promise.all(codeDispatches);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error(chalk_1.default.red('Error during component extraction:', error));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
exports.extractComponents = extractComponents;
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.sendCode = exports.resolveComponentImportFiles = exports.validateDeployContext = exports.validateConsent = exports.ExtractedFileType = void 0;
|
|
49
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
50
|
+
const path_1 = __importDefault(require("path"));
|
|
51
|
+
const fs_1 = __importDefault(require("fs"));
|
|
52
|
+
const ts = __importStar(require("typescript"));
|
|
53
|
+
const core_1 = require("@sitecore-content-sdk/core");
|
|
54
|
+
/**
|
|
55
|
+
* Type of file to be sent to the mesh endpoint
|
|
56
|
+
*/
|
|
57
|
+
var ExtractedFileType;
|
|
58
|
+
(function (ExtractedFileType) {
|
|
59
|
+
ExtractedFileType["Component"] = "component";
|
|
60
|
+
ExtractedFileType["Json"] = "json";
|
|
61
|
+
ExtractedFileType["Package"] = "package.json";
|
|
62
|
+
})(ExtractedFileType || (exports.ExtractedFileType = ExtractedFileType = {}));
|
|
63
|
+
/**
|
|
64
|
+
* Validates consent for code extraction procedures
|
|
65
|
+
* @returns {boolean} - true if consent is given, false otherwise
|
|
66
|
+
*/
|
|
67
|
+
const validateConsent = () => {
|
|
68
|
+
if (!process.env.EXTRACT_CONSENT) {
|
|
69
|
+
console.log(chalk_1.default.yellow('EXTRACT_CONSENT is not set'));
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
};
|
|
74
|
+
exports.validateConsent = validateConsent;
|
|
75
|
+
/**
|
|
76
|
+
* Validates if the current operation is done in Vercel, Netlify or XMCloud
|
|
77
|
+
* deploy context
|
|
78
|
+
* @returns {boolean} - true if in deploy context, false otherwise
|
|
79
|
+
*/
|
|
80
|
+
const validateDeployContext = () => {
|
|
81
|
+
if (process.env.NETLIFY && process.env.BUILD_ID) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
// workaround, Vercel does not have variables that are only accessible at build time
|
|
85
|
+
if (process.env.VERCEL && !process.env.VERCEL_REGION) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
if (process.env.SITECORE && process.env.BuildMetadata_BuildId) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
};
|
|
93
|
+
exports.validateDeployContext = validateDeployContext;
|
|
94
|
+
/**
|
|
95
|
+
* Parses the componentBuilder.ts file and returns a map of component names
|
|
96
|
+
* and their respective import strings
|
|
97
|
+
* @param {string} appPath path to the JSS app root
|
|
98
|
+
* @param {string} [componentMapPath] path to the app's component map file. Default: 'src/lib/componentMap.ts'
|
|
99
|
+
* @returns map of component names and their respective import strings
|
|
100
|
+
*/
|
|
101
|
+
const resolveComponentImportFiles = (appPath, componentMapPath = './src/lib/componentMap.ts') => {
|
|
102
|
+
appPath = path_1.default.isAbsolute(appPath) ? appPath : path_1.default.resolve(process.cwd(), appPath);
|
|
103
|
+
const tsConfig = ts.readConfigFile(path_1.default.resolve(appPath, 'tsconfig.json'), ts.sys.readFile);
|
|
104
|
+
if (tsConfig.error) {
|
|
105
|
+
throw new Error(`Error reading tsconfig.json from JSS app root: ${tsConfig.error.messageText}`);
|
|
106
|
+
}
|
|
107
|
+
const componentMapFullPath = path_1.default.isAbsolute(componentMapPath)
|
|
108
|
+
? componentMapPath
|
|
109
|
+
: path_1.default.resolve(appPath, componentMapPath);
|
|
110
|
+
const cliCompilerOptions = Object.assign(Object.assign({}, tsConfig.config.compilerOptions), { baseUrl: appPath });
|
|
111
|
+
const tsHost = ts.createCompilerHost(cliCompilerOptions, true);
|
|
112
|
+
const componentMapSourceFile = tsHost.getSourceFile(componentMapFullPath, ts.ScriptTarget.Latest, (msg) => {
|
|
113
|
+
throw new Error(`Failed to parse ${componentMapFullPath}: ${msg}`);
|
|
114
|
+
});
|
|
115
|
+
if (!componentMapSourceFile)
|
|
116
|
+
throw ReferenceError(`Failed to find file ${componentMapFullPath}`);
|
|
117
|
+
// this map matches all raw import strings (i.e. * as component) to import strings
|
|
118
|
+
const importStringsMap = {};
|
|
119
|
+
// this map will match component names only to full resolved source file paths
|
|
120
|
+
const componentImportsMap = new Map();
|
|
121
|
+
let mapExportName = '';
|
|
122
|
+
// all new xyz() statements in file
|
|
123
|
+
const newAssignments = [];
|
|
124
|
+
// all map.set() assignments in file
|
|
125
|
+
const mapAssignments = [];
|
|
126
|
+
// this function will traverse the map = new Map([/values/]) statement
|
|
127
|
+
// and get the component names registered in map from it
|
|
128
|
+
const traverseNewStatement = (node) => {
|
|
129
|
+
// going through map invocation, we're looking for outer array value
|
|
130
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
131
|
+
ts.forEachChild(node, (childNode) => {
|
|
132
|
+
// and then parse each individual array pair (i.e. ['MyComp', MyComp])
|
|
133
|
+
if (!ts.isArrayLiteralExpression(childNode)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const componentKey = childNode.elements[1].getText();
|
|
137
|
+
const componentImport = Object.keys(importStringsMap).find((importStatement) => {
|
|
138
|
+
const matcher = new RegExp(`\\b(${componentKey})\\b`);
|
|
139
|
+
return importStatement.match(matcher) !== null;
|
|
140
|
+
});
|
|
141
|
+
if (componentImport) {
|
|
142
|
+
const componentValue = importStringsMap[componentImport];
|
|
143
|
+
componentImportsMap.set(componentKey, componentValue);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
else if (node.getChildCount() > 0) {
|
|
148
|
+
ts.forEachChild(node, (childNode) => {
|
|
149
|
+
traverseNewStatement(childNode);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
// step 1: get all import statements, map assignments (map.set) and map inits (map = new Map()) from componentMap file
|
|
154
|
+
ts.forEachChild(componentMapSourceFile, (childNode) => {
|
|
155
|
+
var _a;
|
|
156
|
+
// first, all import statements are parsed
|
|
157
|
+
if (ts.isImportDeclaration(childNode) && childNode.importClause) {
|
|
158
|
+
// import path is extracted
|
|
159
|
+
const moduleName = childNode.moduleSpecifier.getText().replace(/['"]/g, '');
|
|
160
|
+
// unless the import is a nodeJS one, or points to dependency package, resolve full path to the imported source file
|
|
161
|
+
if (moduleName.startsWith('node:') || moduleName.indexOf('/node_modules') > -1) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const resolvedModule = ts.nodeModuleNameResolver(moduleName, componentMapFullPath, cliCompilerOptions, tsHost);
|
|
165
|
+
const resolvedFile = (_a = resolvedModule === null || resolvedModule === void 0 ? void 0 : resolvedModule.resolvedModule) === null || _a === void 0 ? void 0 : _a.resolvedFileName;
|
|
166
|
+
// module imports will be resolved to /node_modules location - we don't support that yet
|
|
167
|
+
if (resolvedFile && resolvedFile.indexOf('node_modules') === -1) {
|
|
168
|
+
importStringsMap[childNode.importClause.getText()] = path_1.default.resolve(resolvedFile);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
console.warn('Could not resolve a file for import %s', moduleName);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else if (ts.isExpressionStatement(childNode)) {
|
|
175
|
+
// parse map assignments (map.set(..)) to get registered components
|
|
176
|
+
ts.forEachChild(childNode, (expressionNode) => {
|
|
177
|
+
if (ts.isCallExpression(expressionNode) &&
|
|
178
|
+
expressionNode.expression.getText().indexOf('set') !== -1) {
|
|
179
|
+
// get map.set assignments
|
|
180
|
+
mapAssignments.push(expressionNode);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
else if (ts.isExportAssignment(childNode)) {
|
|
185
|
+
// get component map export variable
|
|
186
|
+
mapExportName = childNode.expression.getText();
|
|
187
|
+
}
|
|
188
|
+
else if (childNode.kind === ts.SyntaxKind.FirstStatement) {
|
|
189
|
+
// get potential map = new Map() assignments to extract initial component values from
|
|
190
|
+
newAssignments.push(childNode);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
// step 2: parse map assignments (from map.set and the new Map()) and retrieve import paths
|
|
194
|
+
// only for components registered into component map
|
|
195
|
+
for (const mapAssignment of newAssignments) {
|
|
196
|
+
// parse new Map() statement first
|
|
197
|
+
// only consider variable name for map that is exported
|
|
198
|
+
if (
|
|
199
|
+
// get the (maybe) exported new Map() statement
|
|
200
|
+
// matches i.e. export const map.. / export default const map.. / let map = .. / etc
|
|
201
|
+
mapAssignment
|
|
202
|
+
.getText()
|
|
203
|
+
.match(`^((export )|(export default ))?\\b(var|let|const)\\b\\s{1}\\b(${mapExportName})\\b`)) {
|
|
204
|
+
traverseNewStatement(mapAssignment);
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
for (const mapAssignment of mapAssignments) {
|
|
209
|
+
// only consider the map variable that is exported
|
|
210
|
+
if (mapAssignment.getText().startsWith(mapExportName)) {
|
|
211
|
+
const componentKey = mapAssignment.arguments[1].getText();
|
|
212
|
+
const componentImport = Object.keys(importStringsMap).find((importStatement) => {
|
|
213
|
+
const matcher = new RegExp(`\\b(${componentKey})\\b`);
|
|
214
|
+
return importStatement.match(matcher) !== null;
|
|
215
|
+
});
|
|
216
|
+
if (componentImport) {
|
|
217
|
+
const componentValue = importStringsMap[componentImport];
|
|
218
|
+
componentImportsMap.set(componentKey, componentValue);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return componentImportsMap;
|
|
223
|
+
};
|
|
224
|
+
exports.resolveComponentImportFiles = resolveComponentImportFiles;
|
|
225
|
+
const sendCode = (_a) => __awaiter(void 0, [_a], void 0, function* ({ file, token, edgeUrl, }) {
|
|
226
|
+
const meshEndpoint = `${edgeUrl || core_1.constants.SITECORE_EDGE_URL_DEFAULT}/api/v1/mesh`;
|
|
227
|
+
if (!fs_1.default.existsSync(file.path)) {
|
|
228
|
+
console.error(chalk_1.default.red(`Component file not found: ${file.path}`));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const code = fs_1.default.readFileSync(file.path);
|
|
232
|
+
const response = yield fetch(meshEndpoint, {
|
|
233
|
+
method: 'POST',
|
|
234
|
+
headers: {
|
|
235
|
+
Authorization: `Bearer ${token}`,
|
|
236
|
+
'Content-Type': 'application/json',
|
|
237
|
+
},
|
|
238
|
+
body: JSON.stringify({
|
|
239
|
+
name: file.name,
|
|
240
|
+
content: code.toString(),
|
|
241
|
+
labels: {
|
|
242
|
+
properties: {
|
|
243
|
+
type: file.type,
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
}),
|
|
247
|
+
});
|
|
248
|
+
if (!response.ok) {
|
|
249
|
+
console.error(chalk_1.default.red(`Failed to send extracted code from ${file.path}: ${response.statusText}`));
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
console.log(chalk_1.default.green(`Code from ${file.path} extracted and sent to mesh endpoint`));
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
exports.sendCode = sendCode;
|
package/dist/cjs/tools/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ModuleType = exports.generatePlugins = exports.getComponentList = exports.generateMetadata = exports.generateSites = void 0;
|
|
3
|
+
exports.extractComponents = exports.ModuleType = exports.generatePlugins = exports.getComponentList = exports.generateMetadata = exports.generateSites = void 0;
|
|
4
4
|
var tools_1 = require("@sitecore-content-sdk/core/tools");
|
|
5
5
|
Object.defineProperty(exports, "generateSites", { enumerable: true, get: function () { return tools_1.generateSites; } });
|
|
6
6
|
Object.defineProperty(exports, "generateMetadata", { enumerable: true, get: function () { return tools_1.generateMetadata; } });
|
|
7
7
|
Object.defineProperty(exports, "getComponentList", { enumerable: true, get: function () { return tools_1.getComponentList; } });
|
|
8
8
|
Object.defineProperty(exports, "generatePlugins", { enumerable: true, get: function () { return tools_1.generatePlugins; } });
|
|
9
9
|
Object.defineProperty(exports, "ModuleType", { enumerable: true, get: function () { return tools_1.ModuleType; } });
|
|
10
|
+
var extract_components_1 = require("./codegen/extract-components");
|
|
11
|
+
Object.defineProperty(exports, "extractComponents", { enumerable: true, get: function () { return extract_components_1.extractComponents; } });
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { ExtractedFileType, resolveComponentImportFiles, sendCode, validateConsent, validateDeployContext, } from './utils';
|
|
12
|
+
import { constants } from '@sitecore-content-sdk/core';
|
|
13
|
+
import { fetchBearerToken } from '@sitecore-content-sdk/core/tools';
|
|
14
|
+
/**
|
|
15
|
+
* Extracts components from the app folder and sends them to XMCloud.
|
|
16
|
+
* @param {ExtractComponentsConfig} args - Config for components extraction
|
|
17
|
+
*/
|
|
18
|
+
export const extractComponents = (args) => {
|
|
19
|
+
const authParams = {
|
|
20
|
+
clientId: process.env.SITECORE_AUTH_CLIENT_ID || '',
|
|
21
|
+
clientSecret: process.env.SITECORE_AUTH_CLIENT_SECRET || '',
|
|
22
|
+
endpoint: process.env.SITECORE_AUTH_ENDPOINT || constants.DEFAULT_SITECORE_AUTH_ENDPOINT,
|
|
23
|
+
audience: process.env.SITECORE_AUTH_AUDIENCE || constants.DEFAULT_SITECORE_AUTH_AUDIENCE,
|
|
24
|
+
};
|
|
25
|
+
return () => __awaiter(void 0, void 0, void 0, function* () {
|
|
26
|
+
if (!validateDeployContext()) {
|
|
27
|
+
console.log(chalk.yellow('Skipping code extraction, not in deploy context'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (!validateConsent()) {
|
|
31
|
+
console.log(chalk.yellow('Skipping code extraction, consent not given'));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const basePath = process.cwd();
|
|
35
|
+
try {
|
|
36
|
+
const bearer = yield fetchBearerToken(authParams);
|
|
37
|
+
if (!bearer) {
|
|
38
|
+
console.error(chalk.red('Failed to get bearer token, aborting code extraction'));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const componentPaths = yield resolveComponentImportFiles(basePath, args.componentMapPath);
|
|
42
|
+
const codeDispatches = Array.from(componentPaths, (mapEntry) => sendCode({
|
|
43
|
+
file: {
|
|
44
|
+
name: mapEntry[0],
|
|
45
|
+
path: mapEntry[1],
|
|
46
|
+
type: ExtractedFileType.Component,
|
|
47
|
+
},
|
|
48
|
+
token: bearer,
|
|
49
|
+
edgeUrl: args.scConfig.api.edge.edgeUrl,
|
|
50
|
+
}));
|
|
51
|
+
yield Promise.all(codeDispatches);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error(chalk.red('Error during component extraction:', error));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import * as ts from 'typescript';
|
|
14
|
+
import { constants } from '@sitecore-content-sdk/core';
|
|
15
|
+
/**
|
|
16
|
+
* Type of file to be sent to the mesh endpoint
|
|
17
|
+
*/
|
|
18
|
+
export var ExtractedFileType;
|
|
19
|
+
(function (ExtractedFileType) {
|
|
20
|
+
ExtractedFileType["Component"] = "component";
|
|
21
|
+
ExtractedFileType["Json"] = "json";
|
|
22
|
+
ExtractedFileType["Package"] = "package.json";
|
|
23
|
+
})(ExtractedFileType || (ExtractedFileType = {}));
|
|
24
|
+
/**
|
|
25
|
+
* Validates consent for code extraction procedures
|
|
26
|
+
* @returns {boolean} - true if consent is given, false otherwise
|
|
27
|
+
*/
|
|
28
|
+
export const validateConsent = () => {
|
|
29
|
+
if (!process.env.EXTRACT_CONSENT) {
|
|
30
|
+
console.log(chalk.yellow('EXTRACT_CONSENT is not set'));
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Validates if the current operation is done in Vercel, Netlify or XMCloud
|
|
37
|
+
* deploy context
|
|
38
|
+
* @returns {boolean} - true if in deploy context, false otherwise
|
|
39
|
+
*/
|
|
40
|
+
export const validateDeployContext = () => {
|
|
41
|
+
if (process.env.NETLIFY && process.env.BUILD_ID) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
// workaround, Vercel does not have variables that are only accessible at build time
|
|
45
|
+
if (process.env.VERCEL && !process.env.VERCEL_REGION) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (process.env.SITECORE && process.env.BuildMetadata_BuildId) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Parses the componentBuilder.ts file and returns a map of component names
|
|
55
|
+
* and their respective import strings
|
|
56
|
+
* @param {string} appPath path to the JSS app root
|
|
57
|
+
* @param {string} [componentMapPath] path to the app's component map file. Default: 'src/lib/componentMap.ts'
|
|
58
|
+
* @returns map of component names and their respective import strings
|
|
59
|
+
*/
|
|
60
|
+
export const resolveComponentImportFiles = (appPath, componentMapPath = './src/lib/componentMap.ts') => {
|
|
61
|
+
appPath = path.isAbsolute(appPath) ? appPath : path.resolve(process.cwd(), appPath);
|
|
62
|
+
const tsConfig = ts.readConfigFile(path.resolve(appPath, 'tsconfig.json'), ts.sys.readFile);
|
|
63
|
+
if (tsConfig.error) {
|
|
64
|
+
throw new Error(`Error reading tsconfig.json from JSS app root: ${tsConfig.error.messageText}`);
|
|
65
|
+
}
|
|
66
|
+
const componentMapFullPath = path.isAbsolute(componentMapPath)
|
|
67
|
+
? componentMapPath
|
|
68
|
+
: path.resolve(appPath, componentMapPath);
|
|
69
|
+
const cliCompilerOptions = Object.assign(Object.assign({}, tsConfig.config.compilerOptions), { baseUrl: appPath });
|
|
70
|
+
const tsHost = ts.createCompilerHost(cliCompilerOptions, true);
|
|
71
|
+
const componentMapSourceFile = tsHost.getSourceFile(componentMapFullPath, ts.ScriptTarget.Latest, (msg) => {
|
|
72
|
+
throw new Error(`Failed to parse ${componentMapFullPath}: ${msg}`);
|
|
73
|
+
});
|
|
74
|
+
if (!componentMapSourceFile)
|
|
75
|
+
throw ReferenceError(`Failed to find file ${componentMapFullPath}`);
|
|
76
|
+
// this map matches all raw import strings (i.e. * as component) to import strings
|
|
77
|
+
const importStringsMap = {};
|
|
78
|
+
// this map will match component names only to full resolved source file paths
|
|
79
|
+
const componentImportsMap = new Map();
|
|
80
|
+
let mapExportName = '';
|
|
81
|
+
// all new xyz() statements in file
|
|
82
|
+
const newAssignments = [];
|
|
83
|
+
// all map.set() assignments in file
|
|
84
|
+
const mapAssignments = [];
|
|
85
|
+
// this function will traverse the map = new Map([/values/]) statement
|
|
86
|
+
// and get the component names registered in map from it
|
|
87
|
+
const traverseNewStatement = (node) => {
|
|
88
|
+
// going through map invocation, we're looking for outer array value
|
|
89
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
90
|
+
ts.forEachChild(node, (childNode) => {
|
|
91
|
+
// and then parse each individual array pair (i.e. ['MyComp', MyComp])
|
|
92
|
+
if (!ts.isArrayLiteralExpression(childNode)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const componentKey = childNode.elements[1].getText();
|
|
96
|
+
const componentImport = Object.keys(importStringsMap).find((importStatement) => {
|
|
97
|
+
const matcher = new RegExp(`\\b(${componentKey})\\b`);
|
|
98
|
+
return importStatement.match(matcher) !== null;
|
|
99
|
+
});
|
|
100
|
+
if (componentImport) {
|
|
101
|
+
const componentValue = importStringsMap[componentImport];
|
|
102
|
+
componentImportsMap.set(componentKey, componentValue);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else if (node.getChildCount() > 0) {
|
|
107
|
+
ts.forEachChild(node, (childNode) => {
|
|
108
|
+
traverseNewStatement(childNode);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
// step 1: get all import statements, map assignments (map.set) and map inits (map = new Map()) from componentMap file
|
|
113
|
+
ts.forEachChild(componentMapSourceFile, (childNode) => {
|
|
114
|
+
var _a;
|
|
115
|
+
// first, all import statements are parsed
|
|
116
|
+
if (ts.isImportDeclaration(childNode) && childNode.importClause) {
|
|
117
|
+
// import path is extracted
|
|
118
|
+
const moduleName = childNode.moduleSpecifier.getText().replace(/['"]/g, '');
|
|
119
|
+
// unless the import is a nodeJS one, or points to dependency package, resolve full path to the imported source file
|
|
120
|
+
if (moduleName.startsWith('node:') || moduleName.indexOf('/node_modules') > -1) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const resolvedModule = ts.nodeModuleNameResolver(moduleName, componentMapFullPath, cliCompilerOptions, tsHost);
|
|
124
|
+
const resolvedFile = (_a = resolvedModule === null || resolvedModule === void 0 ? void 0 : resolvedModule.resolvedModule) === null || _a === void 0 ? void 0 : _a.resolvedFileName;
|
|
125
|
+
// module imports will be resolved to /node_modules location - we don't support that yet
|
|
126
|
+
if (resolvedFile && resolvedFile.indexOf('node_modules') === -1) {
|
|
127
|
+
importStringsMap[childNode.importClause.getText()] = path.resolve(resolvedFile);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.warn('Could not resolve a file for import %s', moduleName);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (ts.isExpressionStatement(childNode)) {
|
|
134
|
+
// parse map assignments (map.set(..)) to get registered components
|
|
135
|
+
ts.forEachChild(childNode, (expressionNode) => {
|
|
136
|
+
if (ts.isCallExpression(expressionNode) &&
|
|
137
|
+
expressionNode.expression.getText().indexOf('set') !== -1) {
|
|
138
|
+
// get map.set assignments
|
|
139
|
+
mapAssignments.push(expressionNode);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
else if (ts.isExportAssignment(childNode)) {
|
|
144
|
+
// get component map export variable
|
|
145
|
+
mapExportName = childNode.expression.getText();
|
|
146
|
+
}
|
|
147
|
+
else if (childNode.kind === ts.SyntaxKind.FirstStatement) {
|
|
148
|
+
// get potential map = new Map() assignments to extract initial component values from
|
|
149
|
+
newAssignments.push(childNode);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
// step 2: parse map assignments (from map.set and the new Map()) and retrieve import paths
|
|
153
|
+
// only for components registered into component map
|
|
154
|
+
for (const mapAssignment of newAssignments) {
|
|
155
|
+
// parse new Map() statement first
|
|
156
|
+
// only consider variable name for map that is exported
|
|
157
|
+
if (
|
|
158
|
+
// get the (maybe) exported new Map() statement
|
|
159
|
+
// matches i.e. export const map.. / export default const map.. / let map = .. / etc
|
|
160
|
+
mapAssignment
|
|
161
|
+
.getText()
|
|
162
|
+
.match(`^((export )|(export default ))?\\b(var|let|const)\\b\\s{1}\\b(${mapExportName})\\b`)) {
|
|
163
|
+
traverseNewStatement(mapAssignment);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const mapAssignment of mapAssignments) {
|
|
168
|
+
// only consider the map variable that is exported
|
|
169
|
+
if (mapAssignment.getText().startsWith(mapExportName)) {
|
|
170
|
+
const componentKey = mapAssignment.arguments[1].getText();
|
|
171
|
+
const componentImport = Object.keys(importStringsMap).find((importStatement) => {
|
|
172
|
+
const matcher = new RegExp(`\\b(${componentKey})\\b`);
|
|
173
|
+
return importStatement.match(matcher) !== null;
|
|
174
|
+
});
|
|
175
|
+
if (componentImport) {
|
|
176
|
+
const componentValue = importStringsMap[componentImport];
|
|
177
|
+
componentImportsMap.set(componentKey, componentValue);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return componentImportsMap;
|
|
182
|
+
};
|
|
183
|
+
export const sendCode = (_a) => __awaiter(void 0, [_a], void 0, function* ({ file, token, edgeUrl, }) {
|
|
184
|
+
const meshEndpoint = `${edgeUrl || constants.SITECORE_EDGE_URL_DEFAULT}/api/v1/mesh`;
|
|
185
|
+
if (!fs.existsSync(file.path)) {
|
|
186
|
+
console.error(chalk.red(`Component file not found: ${file.path}`));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const code = fs.readFileSync(file.path);
|
|
190
|
+
const response = yield fetch(meshEndpoint, {
|
|
191
|
+
method: 'POST',
|
|
192
|
+
headers: {
|
|
193
|
+
Authorization: `Bearer ${token}`,
|
|
194
|
+
'Content-Type': 'application/json',
|
|
195
|
+
},
|
|
196
|
+
body: JSON.stringify({
|
|
197
|
+
name: file.name,
|
|
198
|
+
content: code.toString(),
|
|
199
|
+
labels: {
|
|
200
|
+
properties: {
|
|
201
|
+
type: file.type,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
}),
|
|
205
|
+
});
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
console.error(chalk.red(`Failed to send extracted code from ${file.path}: ${response.statusText}`));
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
console.log(chalk.green(`Code from ${file.path} extracted and sent to mesh endpoint`));
|
|
211
|
+
}
|
|
212
|
+
});
|
package/dist/esm/tools/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sitecore-content-sdk/nextjs",
|
|
3
|
-
"version": "0.2.0-canary.
|
|
3
|
+
"version": "0.2.0-canary.15",
|
|
4
4
|
"main": "dist/cjs/index.js",
|
|
5
5
|
"module": "dist/esm/index.js",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -71,12 +71,18 @@
|
|
|
71
71
|
"@sitecore-cloudsdk/personalize": "^0.5.0",
|
|
72
72
|
"next": "^14.2.18",
|
|
73
73
|
"react": "^18.2.0",
|
|
74
|
-
"react-dom": "^18.2.0"
|
|
74
|
+
"react-dom": "^18.2.0",
|
|
75
|
+
"typescript": "~5.7.3"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"typescript": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
75
81
|
},
|
|
76
82
|
"dependencies": {
|
|
77
83
|
"@babel/parser": "^7.26.10",
|
|
78
|
-
"@sitecore-content-sdk/core": "0.2.0-canary.
|
|
79
|
-
"@sitecore-content-sdk/react": "0.2.0-canary.
|
|
84
|
+
"@sitecore-content-sdk/core": "0.2.0-canary.15",
|
|
85
|
+
"@sitecore-content-sdk/react": "0.2.0-canary.15",
|
|
80
86
|
"@vercel/kv": "^3.0.0",
|
|
81
87
|
"prop-types": "^15.8.1",
|
|
82
88
|
"recast": "^0.23.11",
|
|
@@ -85,7 +91,7 @@
|
|
|
85
91
|
},
|
|
86
92
|
"description": "",
|
|
87
93
|
"types": "types/index.d.ts",
|
|
88
|
-
"gitHead": "
|
|
94
|
+
"gitHead": "44bc7e454d2fc29207bc22992978ba0d439013d6",
|
|
89
95
|
"files": [
|
|
90
96
|
"dist",
|
|
91
97
|
"types",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SitecoreConfig } from '@sitecore-content-sdk/core/config';
|
|
2
|
+
export type ExtractComponentsConfig = {
|
|
3
|
+
scConfig: SitecoreConfig;
|
|
4
|
+
componentMapPath?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Extracts components from the app folder and sends them to XMCloud.
|
|
8
|
+
* @param {ExtractComponentsConfig} args - Config for components extraction
|
|
9
|
+
*/
|
|
10
|
+
export declare const extractComponents: (args: ExtractComponentsConfig) => () => Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Description properties for the files sent to the mesh endpoint
|
|
3
|
+
*/
|
|
4
|
+
export type ExtractedFile = {
|
|
5
|
+
name: string;
|
|
6
|
+
path: string;
|
|
7
|
+
type: ExtractedFileType;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Type of file to be sent to the mesh endpoint
|
|
11
|
+
*/
|
|
12
|
+
export declare enum ExtractedFileType {
|
|
13
|
+
Component = "component",
|
|
14
|
+
Json = "json",
|
|
15
|
+
Package = "package.json"
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Validates consent for code extraction procedures
|
|
19
|
+
* @returns {boolean} - true if consent is given, false otherwise
|
|
20
|
+
*/
|
|
21
|
+
export declare const validateConsent: () => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Validates if the current operation is done in Vercel, Netlify or XMCloud
|
|
24
|
+
* deploy context
|
|
25
|
+
* @returns {boolean} - true if in deploy context, false otherwise
|
|
26
|
+
*/
|
|
27
|
+
export declare const validateDeployContext: () => boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Parses the componentBuilder.ts file and returns a map of component names
|
|
30
|
+
* and their respective import strings
|
|
31
|
+
* @param {string} appPath path to the JSS app root
|
|
32
|
+
* @param {string} [componentMapPath] path to the app's component map file. Default: 'src/lib/componentMap.ts'
|
|
33
|
+
* @returns map of component names and their respective import strings
|
|
34
|
+
*/
|
|
35
|
+
export declare const resolveComponentImportFiles: (appPath: string, componentMapPath?: string) => Map<string, string>;
|
|
36
|
+
export declare const sendCode: ({ file, token, edgeUrl, }: {
|
|
37
|
+
file: ExtractedFile;
|
|
38
|
+
token: string;
|
|
39
|
+
edgeUrl?: string;
|
|
40
|
+
}) => Promise<void>;
|
package/types/tools/index.d.ts
CHANGED