1 | const { resolve } = require('path');
|
2 |
|
3 | const uniqBy = (array, property) => {
|
4 | const map = new Map();
|
5 |
|
6 | for (const element of array) {
|
7 | if (element && element.hasOwnProperty(property)) {
|
8 | map.set(element[property], element);
|
9 | }
|
10 | }
|
11 |
|
12 | return Array.from(map.values());
|
13 | };
|
14 |
|
15 | const removeDuplicateBackends = backendEnvironments =>
|
16 | uniqBy(backendEnvironments, 'url');
|
17 |
|
18 | const fetchSampleBackends = async defaultSampleBackends => {
|
19 | try {
|
20 | const res = await fetch(
|
21 | 'https://fvp0esmt8f.execute-api.us-east-1.amazonaws.com/default/getSampleBackends'
|
22 | );
|
23 | const { sampleBackends } = await res.json();
|
24 |
|
25 | return removeDuplicateBackends([
|
26 | ...sampleBackends.environments,
|
27 | ...defaultSampleBackends.environments
|
28 | ]).map(({ url }) => url);
|
29 | } catch {
|
30 | return defaultSampleBackends.environments.map(({ url }) => url);
|
31 | }
|
32 | };
|
33 |
|
34 | async function createProjectFromVenia({ fs, tasks, options, sampleBackends }) {
|
35 | const npmCli = options.npmClient;
|
36 | const sampleBackendEnvironments = await fetchSampleBackends(sampleBackends);
|
37 |
|
38 | const toCopyFromPackageJson = [
|
39 | 'main',
|
40 | 'browser',
|
41 | 'dependencies',
|
42 | 'devDependencies',
|
43 | 'optionalDependencies',
|
44 | 'resolutions',
|
45 | 'engines',
|
46 | 'pwa-studio'
|
47 | ];
|
48 | const scriptsToCopy = [
|
49 | 'buildpack',
|
50 | 'build',
|
51 | 'build:analyze',
|
52 | 'build:dev',
|
53 | 'build:prod',
|
54 | 'build:report',
|
55 | 'clean',
|
56 | 'lint',
|
57 | 'prettier',
|
58 | 'prettier:check',
|
59 | 'prettier:fix',
|
60 | 'start',
|
61 | 'start:debug',
|
62 | 'watch'
|
63 | ];
|
64 | const scriptsToInsert = {
|
65 | storybook: 'start-storybook -p 9001 -c src/.storybook',
|
66 | 'storybook:build': 'build-storybook -c src/.storybook -o storybook-dist'
|
67 | };
|
68 |
|
69 | const filesToIgnore = [
|
70 | 'CHANGELOG*',
|
71 | 'LICENSE*',
|
72 | '_buildpack',
|
73 | '_buildpack/**',
|
74 |
|
75 |
|
76 | '**/__tests__',
|
77 | '**/__tests__/**'
|
78 | ];
|
79 | const ignoresGlob = `{${filesToIgnore.join(',')}}`;
|
80 |
|
81 | return {
|
82 | after({ options }) {
|
83 |
|
84 |
|
85 |
|
86 |
|
87 | fs.outputFileSync(
|
88 | resolve(options.directory, 'babel.config.js'),
|
89 | "module.exports = { presets: ['@magento/peregrine'] };\n",
|
90 | 'utf8'
|
91 | );
|
92 | },
|
93 | visitor: {
|
94 |
|
95 | 'package.json': ({
|
96 | path,
|
97 | targetPath,
|
98 | options: { name, author, backendUrl }
|
99 | }) => {
|
100 | const pkgTpt = fs.readJsonSync(path);
|
101 | const pkg = {
|
102 | name,
|
103 | private: true,
|
104 | version: '0.0.1',
|
105 | description:
|
106 | 'A new project based on @magento/venia-concept',
|
107 | author,
|
108 | license: 'UNLICENSED',
|
109 | scripts: {}
|
110 | };
|
111 | toCopyFromPackageJson.forEach(prop => {
|
112 | pkg[prop] = pkgTpt[prop];
|
113 | });
|
114 |
|
115 |
|
116 | if (sampleBackendEnvironments.includes(backendUrl)) {
|
117 | pkg.devDependencies = {
|
118 | ...pkg.devDependencies,
|
119 | '@magento/venia-sample-backends': '~0.0.1'
|
120 | };
|
121 | }
|
122 |
|
123 |
|
124 |
|
125 |
|
126 | const toPackageScript = script => {
|
127 | const outputScript = script.replace(/\bvenia\b/g, name);
|
128 | return npmCli === 'npm'
|
129 | ? outputScript.replace(/yarn run/g, 'npm run')
|
130 | : outputScript;
|
131 | };
|
132 |
|
133 | if (!pkgTpt.scripts) {
|
134 | throw new Error(
|
135 | JSON.stringify(pkgTpt, null, 2) +
|
136 | '\ndoes not have a "scripts"'
|
137 | );
|
138 | }
|
139 | scriptsToCopy.forEach(name => {
|
140 | if (pkgTpt.scripts[name]) {
|
141 | pkg.scripts[name] = toPackageScript(
|
142 | pkgTpt.scripts[name]
|
143 | );
|
144 | }
|
145 | });
|
146 | Object.keys(scriptsToInsert).forEach(name => {
|
147 | pkg.scripts[name] = toPackageScript(scriptsToInsert[name]);
|
148 | });
|
149 |
|
150 | if (process.env.DEBUG_PROJECT_CREATION) {
|
151 | setDebugDependencies(pkg, fs);
|
152 | }
|
153 |
|
154 | fs.outputJsonSync(targetPath, pkg, {
|
155 | spaces: 2
|
156 | });
|
157 | },
|
158 | '.graphqlconfig': ({ path, targetPath, options: { name } }) => {
|
159 | const config = fs.readJsonSync(path);
|
160 | config.projects[name] = config.projects.venia;
|
161 | delete config.projects.venia;
|
162 | fs.outputJsonSync(targetPath, config, { spaces: 2 });
|
163 | },
|
164 |
|
165 | [ignoresGlob]: tasks.IGNORE,
|
166 | '**/*': tasks.COPY
|
167 | }
|
168 | };
|
169 | }
|
170 |
|
171 | function setDebugDependencies(pkg, fs) {
|
172 | console.warn(
|
173 | 'DEBUG_PROJECT_CREATION: Debugging Venia _buildpack/create.js, so we will assume we are inside the pwa-studio repo and replace those package dependency declarations with local file paths.'
|
174 | );
|
175 |
|
176 | const { execSync } = require('child_process');
|
177 | const overridden = {};
|
178 | const monorepoDir = resolve(__dirname, '../../../');
|
179 |
|
180 |
|
181 |
|
182 | const yarnWorkspaceInfoCmd = 'yarn -s workspaces info';
|
183 | const workspaceInfo = execSync(yarnWorkspaceInfoCmd, { cwd: monorepoDir });
|
184 |
|
185 | let packageDirs;
|
186 | try {
|
187 |
|
188 | packageDirs = Object.entries(JSON.parse(workspaceInfo)).map(
|
189 | ([name, info]) => [name, resolve(monorepoDir, info.location)]
|
190 | );
|
191 | } catch (e) {
|
192 | throw new Error(
|
193 | `DEBUG_PROJECT_CREATION: Could not parse output of '${yarnWorkspaceInfoCmd}:\n${workspaceInfo}. Please check your version of yarn is v1.22.4+.\n${
|
194 | e.stack
|
195 | }`
|
196 | );
|
197 | }
|
198 |
|
199 |
|
200 | const transitivePackages = new Set([
|
201 | '@magento/pwa-buildpack',
|
202 | '@magento/upward-js'
|
203 | ]);
|
204 |
|
205 |
|
206 |
|
207 | const depTypes = [
|
208 | 'dependencies',
|
209 | 'devDependencies',
|
210 | 'optionalDependencies'
|
211 | ].filter(type => pkg.hasOwnProperty(type));
|
212 |
|
213 | const getNewestTarballIn = dir => {
|
214 | const tarballsInDir = fs
|
215 | .readdirSync(dir)
|
216 | .filter(filename => filename.endsWith('.tgz'));
|
217 | if (tarballsInDir.length === 0) {
|
218 | throw new Error('Found no new .tgz files in ${dir}.');
|
219 | }
|
220 |
|
221 | const tarballsWithModifiedTime = tarballsInDir.map(filename => ({
|
222 | filename,
|
223 | modified: fs.statSync(resolve(dir, filename)).mtime
|
224 | }));
|
225 |
|
226 | return tarballsWithModifiedTime.reduce((newest, candidate) =>
|
227 | candidate.modified > newest.modified ? candidate : newest
|
228 | ).filename;
|
229 | };
|
230 |
|
231 |
|
232 |
|
233 |
|
234 | for (const [name, packageDir] of packageDirs) {
|
235 |
|
236 | if (
|
237 | !depTypes.find(type => pkg[type].hasOwnProperty(name)) &&
|
238 | !transitivePackages.has(name)
|
239 | ) {
|
240 | continue;
|
241 | }
|
242 |
|
243 | console.warn(`DEBUG_PROJECT_CREATION: Packing ${name} for local usage`);
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 | let filename;
|
257 | let packOutput;
|
258 | try {
|
259 | packOutput = execSync('npm pack -s --ignore-scripts', {
|
260 | cwd: packageDir
|
261 | });
|
262 | filename = getNewestTarballIn(packageDir);
|
263 | } catch (e) {
|
264 | throw new Error(
|
265 | `DEBUG_PROJECT_CREATION: npm pack in ${name} package failed: output was ${packOutput}\n\nerror was ${
|
266 | e.message
|
267 | }`
|
268 | );
|
269 | }
|
270 |
|
271 |
|
272 |
|
273 | const localDep = `file://${resolve(packageDir, filename)}`;
|
274 |
|
275 |
|
276 |
|
277 | overridden[name] = localDep;
|
278 |
|
279 |
|
280 | const depType =
|
281 | depTypes.find(type => pkg[type].hasOwnProperty(name)) ||
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 | 'dependencies';
|
289 |
|
290 | pkg[depType][name] = localDep;
|
291 | }
|
292 |
|
293 | if (Object.keys(overridden).length > 0) {
|
294 | console.warn(
|
295 | 'DEBUG_PROJECT_CREATION: Resolved the following packages via local tarball',
|
296 | JSON.stringify(overridden, null, 2)
|
297 | );
|
298 |
|
299 |
|
300 |
|
301 |
|
302 | pkg.resolutions = Object.assign({}, pkg.resolutions, overridden);
|
303 | }
|
304 | }
|
305 |
|
306 | module.exports = createProjectFromVenia;
|