1 |
|
2 | import fixturify from "fixturify";
|
3 | import tmp from "tmp";
|
4 | import fs from "fs-extra";
|
5 | import path from "path";
|
6 | import resolvePackagePath from "resolve-package-path";
|
7 | import CacheGroup from "resolve-package-path/lib/cache-group.js";
|
8 | import binLinks from "bin-links";
|
9 | import walkSync from "walk-sync";
|
10 | import deepmerge from "deepmerge";
|
11 | import { findWorkspaceDir } from "@pnpm/find-workspace-dir";
|
12 | import { findWorkspacePackages } from "@pnpm/workspace.find-packages";
|
13 | import { packlist } from "@pnpm/fs.packlist";
|
14 | import { PackageCache } from "@embroider/shared-internals";
|
15 | tmp.setGracefulCleanup();
|
16 | var defaultFiles = {
|
17 | "index.js": `
|
18 | 'use strict';
|
19 | module.exports = {};`
|
20 | };
|
21 | var Project = class {
|
22 | constructor(first, second, third, fourth) {
|
23 | this.isDependency = true;
|
24 | this._dependencies = {};
|
25 | this._devDependencies = {};
|
26 | this.dependencyLinks = new Map();
|
27 | this.linkIsDevDependency = new Set();
|
28 | this.usingHardLinks = true;
|
29 | this.resolutionCache = new CacheGroup();
|
30 | this.transitivePeersCache = new Map();
|
31 | this.knownWorkspaces = new Map();
|
32 | let name;
|
33 | let version;
|
34 | let files;
|
35 | let requestedRange;
|
36 | if (first == null) {
|
37 | } else if (typeof first === "string") {
|
38 | name = first;
|
39 | if (typeof second === "string") {
|
40 | version = second;
|
41 | if (third) {
|
42 | if (!isProjectCallback(third)) {
|
43 | ({ files, requestedRange } = third);
|
44 | }
|
45 | }
|
46 | } else {
|
47 | if (second) {
|
48 | if (!isProjectCallback(second)) {
|
49 | ({ version, files, requestedRange } = second);
|
50 | }
|
51 | }
|
52 | }
|
53 | } else {
|
54 | ({ name, version, files, requestedRange } = first);
|
55 | }
|
56 | let pkg = {};
|
57 | if (files && typeof (files == null ? void 0 : files["package.json"]) === "string") {
|
58 | pkg = JSON.parse(files["package.json"]);
|
59 | files = deepmerge({}, files);
|
60 | delete files["package.json"];
|
61 | }
|
62 | this.pkg = Object.assign({}, pkg, {
|
63 | name: name || pkg.name || "a-fixturified-project",
|
64 | version: version || pkg.version || "0.0.0",
|
65 | keywords: pkg.keywords || []
|
66 | });
|
67 | if (files) {
|
68 | this.files = deepmerge({}, { ...defaultFiles, ...files });
|
69 | } else {
|
70 | this.files = deepmerge({}, defaultFiles);
|
71 | }
|
72 | this.requestedRange = requestedRange || this.pkg.version;
|
73 | const arity = arguments.length;
|
74 | if (arity > 1) {
|
75 | fourth;
|
76 | const projectCallback = arguments[arity - 1];
|
77 | if (isProjectCallback(projectCallback)) {
|
78 | projectCallback(this);
|
79 | }
|
80 | }
|
81 | }
|
82 | set baseDir(dir) {
|
83 | if (this._baseDir) {
|
84 | throw new Error(`this Project already has a baseDir`);
|
85 | }
|
86 | this._baseDir = dir;
|
87 | }
|
88 | get baseDir() {
|
89 | if (!this._baseDir) {
|
90 | this._tmp = tmp.dirSync({ unsafeCleanup: true });
|
91 | this._baseDir = fs.realpathSync(this._tmp.name);
|
92 | }
|
93 | return this._baseDir;
|
94 | }
|
95 | get name() {
|
96 | return getPackageName(this.pkg);
|
97 | }
|
98 | set name(value) {
|
99 | this.pkg.name = value;
|
100 | }
|
101 | get version() {
|
102 | return getPackageVersion(this.pkg);
|
103 | }
|
104 | set version(value) {
|
105 | this.pkg.version = value;
|
106 | }
|
107 | static fromDir(baseDir, opts) {
|
108 | let project = new Project();
|
109 | project.readSync(baseDir, opts);
|
110 | return project;
|
111 | }
|
112 | mergeFiles(dirJSON) {
|
113 | this.files = deepmerge(this.files, dirJSON);
|
114 | }
|
115 | async write(dirJSON) {
|
116 | if (dirJSON) {
|
117 | this.mergeFiles(dirJSON);
|
118 | }
|
119 | await this.writeProject();
|
120 | await this.binLinks();
|
121 | }
|
122 | addDependency(first, second, third, fourth) {
|
123 | let projectCallback;
|
124 | const arity = arguments.length;
|
125 | if (arity > 1) {
|
126 | fourth;
|
127 | const maybeProjectCallback = arguments[arity - 1];
|
128 | if (isProjectCallback(maybeProjectCallback)) {
|
129 | projectCallback = maybeProjectCallback;
|
130 | }
|
131 | }
|
132 | if (isProjectCallback(second)) {
|
133 | second = void 0;
|
134 | }
|
135 | if (isProjectCallback(third)) {
|
136 | third = void 0;
|
137 | }
|
138 | return this.addDep(first, second, third, "_dependencies", projectCallback);
|
139 | }
|
140 | addDevDependency(first, second, third, fourth) {
|
141 | let projectCallback;
|
142 | const arity = arguments.length;
|
143 | if (arity > 1) {
|
144 | fourth;
|
145 | const maybeProjectCallback = arguments[arity - 1];
|
146 | if (isProjectCallback(maybeProjectCallback)) {
|
147 | projectCallback = maybeProjectCallback;
|
148 | }
|
149 | }
|
150 | if (isProjectCallback(second)) {
|
151 | second = void 0;
|
152 | }
|
153 | if (isProjectCallback(third)) {
|
154 | third = void 0;
|
155 | }
|
156 | return this.addDep(first, second, third, "_devDependencies", projectCallback);
|
157 | }
|
158 | removeDependency(name) {
|
159 | delete this._dependencies[name];
|
160 | this.dependencyLinks.delete(name);
|
161 | this.linkIsDevDependency.delete(name);
|
162 | }
|
163 | removeDevDependency(name) {
|
164 | delete this._devDependencies[name];
|
165 | this.dependencyLinks.delete(name);
|
166 | this.linkIsDevDependency.delete(name);
|
167 | }
|
168 | linkDependency(name, opts) {
|
169 | this.removeDependency(name);
|
170 | this.removeDevDependency(name);
|
171 | this.dependencyLinks.set(name, opts);
|
172 | }
|
173 | linkDevDependency(name, opts) {
|
174 | this.linkDependency(name, opts);
|
175 | this.linkIsDevDependency.add(name);
|
176 | }
|
177 | dependencyProjects() {
|
178 | return Object.keys(this._dependencies).map((dependency) => this._dependencies[dependency]);
|
179 | }
|
180 | devDependencyProjects() {
|
181 | return Object.keys(this._devDependencies).map((dependency) => this._devDependencies[dependency]);
|
182 | }
|
183 | clone() {
|
184 | let cloned = new this.constructor();
|
185 | cloned.pkg = JSON.parse(JSON.stringify(this.pkg));
|
186 | cloned.files = JSON.parse(JSON.stringify(this.files));
|
187 | for (let [name, depProject] of Object.entries(this._dependencies)) {
|
188 | cloned._dependencies[name] = depProject.clone();
|
189 | }
|
190 | for (let [name, depProject] of Object.entries(this._devDependencies)) {
|
191 | cloned._devDependencies[name] = depProject.clone();
|
192 | }
|
193 | cloned.dependencyLinks = new Map(this.dependencyLinks);
|
194 | cloned.linkIsDevDependency = new Set(this.linkIsDevDependency);
|
195 | cloned.requestedRange = this.requestedRange;
|
196 | return cloned;
|
197 | }
|
198 | dispose() {
|
199 | if (this._tmp) {
|
200 | this._tmp.removeCallback();
|
201 | }
|
202 | }
|
203 | async writeProject() {
|
204 | this.assignBaseDirs();
|
205 | let resolvedLinksMap = new Map();
|
206 | await this.discoverWorkspaces();
|
207 | this.writeFiles(resolvedLinksMap);
|
208 | await this.finalizeWrite(resolvedLinksMap);
|
209 | }
|
210 | assignBaseDirs() {
|
211 | this.baseDir;
|
212 | for (let depList of [this.dependencyProjects(), this.devDependencyProjects()]) {
|
213 | for (let dep of depList) {
|
214 | dep.baseDir = path.join(this.baseDir, "node_modules", dep.name);
|
215 | dep.assignBaseDirs();
|
216 | }
|
217 | }
|
218 | }
|
219 | writeFiles(resolvedLinksMap) {
|
220 | fixturify.writeSync(this.baseDir, this.files);
|
221 | for (let depList of [this.dependencyProjects(), this.devDependencyProjects()]) {
|
222 | for (let dep of depList) {
|
223 | dep.writeFiles(resolvedLinksMap);
|
224 | }
|
225 | }
|
226 | let resolvedLinks = this.resolveLinks();
|
227 | fs.outputJSONSync(path.join(this.baseDir, "package.json"), this.pkgJSONWithDeps(resolvedLinks), { spaces: 2 });
|
228 | resolvedLinksMap.set(this, resolvedLinks);
|
229 | }
|
230 | async finalizeWrite(resolvedLinksMap) {
|
231 | for (let [name, { dir: target }] of resolvedLinksMap.get(this)) {
|
232 | await this.writeLinkedPackage(name, target, path.join(this.baseDir, "node_modules", name));
|
233 | }
|
234 | for (let depList of [this.dependencyProjects(), this.devDependencyProjects()]) {
|
235 | for (let dep of depList) {
|
236 | await dep.finalizeWrite(resolvedLinksMap);
|
237 | }
|
238 | }
|
239 | }
|
240 | resolveLinks() {
|
241 | return new Map(
|
242 | [...this.dependencyLinks.entries()].map(([name, opts]) => {
|
243 | let dir;
|
244 | if ("baseDir" in opts) {
|
245 | let pkgJSONPath = resolvePackagePath(opts.resolveName || name, opts.baseDir, this.resolutionCache);
|
246 | if (!pkgJSONPath) {
|
247 | throw new Error(`failed to locate ${opts.resolveName || name} in ${opts.baseDir}`);
|
248 | }
|
249 | dir = path.dirname(pkgJSONPath);
|
250 | } else if ("target" in opts) {
|
251 | dir = opts.target;
|
252 | } else {
|
253 | dir = opts.project.baseDir;
|
254 | }
|
255 | let requestedRange;
|
256 | if (opts.requestedRange) {
|
257 | requestedRange = opts.requestedRange;
|
258 | } else if ("target" in opts || "baseDir" in opts) {
|
259 | requestedRange = fs.readJsonSync(path.join(dir, "package.json")).version;
|
260 | } else {
|
261 | requestedRange = opts.project.version;
|
262 | }
|
263 | return [name, { requestedRange, dir }];
|
264 | })
|
265 | );
|
266 | }
|
267 | async binLinks() {
|
268 | let nodeModules = path.join(this.baseDir, "node_modules");
|
269 | for (const { pkg, path: path2 } of readPackages(nodeModules)) {
|
270 | await binLinks({ pkg, path: path2, top: false, global: false, force: true });
|
271 | }
|
272 | }
|
273 | transitivePeers(dir) {
|
274 | let peers = this.transitivePeersCache.get(dir);
|
275 | if (peers) {
|
276 | return peers;
|
277 | }
|
278 | peers = new Set();
|
279 | this.transitivePeersCache.set(dir, peers);
|
280 | let pkg = PackageCache.shared("fixturify-project", this.baseDir).get(dir);
|
281 | let deps = pkg.dependencies;
|
282 | for (let dep of deps) {
|
283 | if (pkg.categorizeDependency(dep.name) === "dependencies") {
|
284 | for (let peer of this.transitivePeers(dep.root)) {
|
285 | if (pkg.hasDependency(peer)) {
|
286 | } else {
|
287 | peers.add(peer);
|
288 | }
|
289 | }
|
290 | }
|
291 | }
|
292 | for (let dep of deps) {
|
293 | if (pkg.categorizeDependency(dep.name) === "peerDependencies") {
|
294 | peers.add(dep.name);
|
295 | }
|
296 | }
|
297 | return peers;
|
298 | }
|
299 | async writeLinkedPackage(name, target, destination) {
|
300 | let targetPkg = fs.readJsonSync(`${target}/package.json`);
|
301 | let peers = this.transitivePeers(target);
|
302 | if (peers.size === 0) {
|
303 | fs.ensureSymlinkSync(target, destination, "dir");
|
304 | return;
|
305 | }
|
306 | await this.hardLinkContents(target, targetPkg, destination);
|
307 | let depsToLink = new Set();
|
308 | for (let section of ["dependencies", "peerDependencies"]) {
|
309 | if (targetPkg[section]) {
|
310 | for (let depName of Object.keys(targetPkg[section])) {
|
311 | if (peers.has(depName)) {
|
312 | continue;
|
313 | }
|
314 | depsToLink.add(depName);
|
315 | }
|
316 | }
|
317 | }
|
318 | for (let depName of depsToLink) {
|
319 | let depTarget = resolvePackagePath(depName, target, this.resolutionCache);
|
320 | if (!depTarget) {
|
321 | throw new Error(
|
322 | `[FixturifyProject] package ${name} in ${target} depends on ${depName} but we could not resolve it`
|
323 | );
|
324 | }
|
325 | await this.writeLinkedPackage(depName, path.dirname(depTarget), path.join(destination, "node_modules", depName));
|
326 | }
|
327 | }
|
328 | async discoverWorkspaces() {
|
329 | for (let opts of this.dependencyLinks.values()) {
|
330 | if (!("baseDir" in opts)) {
|
331 | continue;
|
332 | }
|
333 | let dir = opts.baseDir;
|
334 | if (this.knownWorkspaces.has(dir)) {
|
335 | continue;
|
336 | }
|
337 | let top = await findWorkspaceDir(dir);
|
338 | if (top) {
|
339 | let packages = await findWorkspacePackages(top);
|
340 | for (let { dir: dir2 } of packages) {
|
341 | this.knownWorkspaces.set(dir2, true);
|
342 | }
|
343 | if (!this.knownWorkspaces.has(dir)) {
|
344 | this.knownWorkspaces.set(dir, false);
|
345 | }
|
346 | } else {
|
347 | this.knownWorkspaces.set(dir, false);
|
348 | }
|
349 | }
|
350 | }
|
351 | async publishedPackageContents(packageDir, pkgJSON) {
|
352 | if (this.knownWorkspaces.get(packageDir)) {
|
353 | return await packlist(packageDir, { packageJsonCache: { packageDir: pkgJSON } });
|
354 | }
|
355 | return walkSync(packageDir, { directories: false, ignore: ["node_modules"] });
|
356 | }
|
357 | async hardLinkContents(source, pkgJSON, destination) {
|
358 | fs.ensureDirSync(destination);
|
359 | for (let relativePath of await this.publishedPackageContents(source, pkgJSON)) {
|
360 | fs.ensureDirSync(path.dirname(path.join(destination, relativePath)));
|
361 | this.hardLinkFile(path.join(source, relativePath), path.join(destination, relativePath));
|
362 | }
|
363 | }
|
364 | hardLinkFile(source, destination) {
|
365 | if (this.usingHardLinks) {
|
366 | try {
|
367 | fs.linkSync(source, destination);
|
368 | return;
|
369 | } catch (err) {
|
370 | if (err.code === "EEXIST") {
|
371 | debugger;
|
372 | }
|
373 | if (err.code !== "EXDEV") {
|
374 | throw err;
|
375 | }
|
376 | this.usingHardLinks = false;
|
377 | }
|
378 | }
|
379 | fs.copyFileSync(source, destination, fs.constants.COPYFILE_FICLONE | fs.constants.COPYFILE_EXCL);
|
380 | }
|
381 | readSync(baseDir, opts) {
|
382 | const files = fixturify.readSync(baseDir, {
|
383 | ignore: (opts == null ? void 0 : opts.linkDeps) || (opts == null ? void 0 : opts.linkDevDeps) ? ["node_modules"] : []
|
384 | });
|
385 | this.pkg = deserializePackageJson(getFile(files, "package.json"));
|
386 | this.requestedRange = this.version;
|
387 | delete files["package.json"];
|
388 | this.files = files;
|
389 | if ((opts == null ? void 0 : opts.linkDeps) || (opts == null ? void 0 : opts.linkDevDeps)) {
|
390 | if (this.pkg.dependencies) {
|
391 | for (let dep of Object.keys(this.pkg.dependencies)) {
|
392 | this.linkDependency(dep, { baseDir });
|
393 | }
|
394 | }
|
395 | if (this.pkg.devDependencies && opts.linkDevDeps) {
|
396 | for (let dep of Object.keys(this.pkg.devDependencies)) {
|
397 | this.linkDevDependency(dep, { baseDir });
|
398 | }
|
399 | }
|
400 | } else {
|
401 | const nodeModules = getFolder(files, "node_modules");
|
402 | delete files["node_modules"];
|
403 | keys(this.pkg.dependencies).forEach((dependency) => {
|
404 | this.addDependency(
|
405 | new this.constructor({ files: unwrapPackageName(nodeModules, dependency) })
|
406 | );
|
407 | });
|
408 | keys(this.pkg.devDependencies).forEach((dependency) => {
|
409 | this.addDevDependency(
|
410 | new this.constructor({ files: unwrapPackageName(nodeModules, dependency) })
|
411 | );
|
412 | });
|
413 | }
|
414 | }
|
415 | addDep(first, second, third, target, projectCallback) {
|
416 | let dep;
|
417 | if (first == null) {
|
418 | dep = new Project();
|
419 | } else if (typeof first === "string") {
|
420 | let name = first;
|
421 | if (typeof second === "string") {
|
422 | let version = second;
|
423 | dep = new Project(name, version, third, projectCallback);
|
424 | } else {
|
425 | dep = new Project(name, second, projectCallback);
|
426 | }
|
427 | } else if ("isDependency" in first) {
|
428 | dep = first;
|
429 | } else {
|
430 | dep = new Project(first, projectCallback);
|
431 | }
|
432 | this[target][dep.name] = dep;
|
433 | this.dependencyLinks.delete(dep.name);
|
434 | this.linkIsDevDependency.delete(dep.name);
|
435 | if (isProjectCallback(projectCallback)) {
|
436 | projectCallback(dep);
|
437 | }
|
438 | return dep;
|
439 | }
|
440 | pkgJSONWithDeps(resolvedLinks) {
|
441 | let dependencies = this.depsToObject(this.dependencyProjects());
|
442 | let devDependencies = this.depsToObject(this.devDependencyProjects());
|
443 | for (let [name, { requestedRange }] of resolvedLinks) {
|
444 | if (requestedRange === void 0) {
|
445 | throw new Error(
|
446 | `No version found for package ${name}. All dependencies must have both a name and version in their package.json.`
|
447 | );
|
448 | }
|
449 | if (this.linkIsDevDependency.has(name)) {
|
450 | devDependencies[name] = requestedRange;
|
451 | } else {
|
452 | dependencies[name] = requestedRange;
|
453 | }
|
454 | }
|
455 | return Object.assign(this.pkg, {
|
456 | dependencies,
|
457 | devDependencies
|
458 | });
|
459 | }
|
460 | depsToObject(deps) {
|
461 | let obj = {};
|
462 | deps.forEach((dep) => obj[dep.name] = dep.requestedRange);
|
463 | return obj;
|
464 | }
|
465 | };
|
466 | function deserializePackageJson(serialized) {
|
467 | return JSON.parse(serialized);
|
468 | }
|
469 | function keys(object) {
|
470 | if (object !== null && (typeof object === "object" || Array.isArray(object))) {
|
471 | return Object.keys(object);
|
472 | } else {
|
473 | return [];
|
474 | }
|
475 | }
|
476 | function isProjectCallback(maybe) {
|
477 | return typeof maybe === "function";
|
478 | }
|
479 | function getString(obj, propertyName, errorMessage) {
|
480 | const value = obj[propertyName];
|
481 | if (typeof value === "string") {
|
482 | return value;
|
483 | } else {
|
484 | throw new TypeError(errorMessage || `expected 'string' but got '${typeof value}'`);
|
485 | }
|
486 | }
|
487 | function getFile(dir, fileName) {
|
488 | const value = dir[fileName];
|
489 | if (typeof value === "string") {
|
490 | return value;
|
491 | } else if (typeof value === "object" && value !== null) {
|
492 | throw new TypeError(`Expected a file for name '${String(fileName)}' but got a 'Folder'`);
|
493 | } else {
|
494 | throw new TypeError(`Expected a file for name '${String(fileName)}' but got '${typeof value}'`);
|
495 | }
|
496 | }
|
497 | function getFolder(dir, fileName) {
|
498 | const value = dir[fileName];
|
499 | if (isDirJSON(value)) {
|
500 | return value;
|
501 | } else if (typeof value === "string") {
|
502 | throw new TypeError(`Expected a file for name '${String(fileName)}' but got 'File'`);
|
503 | } else {
|
504 | throw new TypeError(`Expected a folder for name '${String(fileName)}' but got '${typeof value}'`);
|
505 | }
|
506 | }
|
507 | function isDirJSON(value) {
|
508 | return typeof value === "object" && value !== null;
|
509 | }
|
510 | function getPackageName(pkg) {
|
511 | return getString(pkg, "name", `package.json is missing a name.`);
|
512 | }
|
513 | function getPackageVersion(pkg) {
|
514 | return getString(pkg, "version", `${getPackageName(pkg)}'s package.json is missing a version.`);
|
515 | }
|
516 | function parseScoped(name) {
|
517 | let matched = name.match(/(@[^@\/]+)\/(.*)/);
|
518 | if (matched) {
|
519 | return {
|
520 | scope: matched[1],
|
521 | name: matched[2]
|
522 | };
|
523 | }
|
524 | return null;
|
525 | }
|
526 | function unwrapPackageName(obj, packageName) {
|
527 | let scoped = parseScoped(packageName);
|
528 | if (scoped) {
|
529 | return getFolder(getFolder(obj, scoped.scope), scoped.name);
|
530 | }
|
531 | return getFolder(obj, packageName);
|
532 | }
|
533 | function isObject(e) {
|
534 | return e !== null && typeof e === "object" && !Array.isArray(e);
|
535 | }
|
536 | function isErrnoException(e) {
|
537 | return isObject(e) && "code" in e;
|
538 | }
|
539 | function readString(name) {
|
540 | try {
|
541 | return fs.readFileSync(name, "utf8");
|
542 | } catch (e) {
|
543 | if (isErrnoException(e)) {
|
544 | if (e.code === "ENOENT" || e.code === "EISDIR") {
|
545 | return;
|
546 | }
|
547 | }
|
548 | throw e;
|
549 | }
|
550 | }
|
551 | function readdir(name) {
|
552 | try {
|
553 | return fs.readdirSync(name);
|
554 | } catch (e) {
|
555 | if (isErrnoException(e)) {
|
556 | if (e.code === "ENOENT" || e.code === "ENOTDIR") {
|
557 | return [];
|
558 | }
|
559 | }
|
560 | throw e;
|
561 | }
|
562 | }
|
563 | function readPackage(dir) {
|
564 | if (dir) {
|
565 | const fileName = path.join(dir, "package.json");
|
566 | const content = readString(fileName);
|
567 | if (content) {
|
568 | return { pkg: deserializePackageJson(content), path: dir };
|
569 | }
|
570 | }
|
571 | return;
|
572 | }
|
573 | function readPackages(modulesPath) {
|
574 | const pkgs = [];
|
575 | for (const name of readdir(modulesPath)) {
|
576 | if (name.startsWith("@")) {
|
577 | const scopePath = path.join(modulesPath, name);
|
578 | for (const name2 of readdir(scopePath)) {
|
579 | const pkg = readPackage(path.join(scopePath, name2));
|
580 | if (pkg)
|
581 | pkgs.push(pkg);
|
582 | }
|
583 | } else {
|
584 | const pkg = readPackage(path.join(modulesPath, name));
|
585 | if (pkg)
|
586 | pkgs.push(pkg);
|
587 | }
|
588 | }
|
589 | return pkgs;
|
590 | }
|
591 | export {
|
592 | Project
|
593 | };
|
594 |
|
\ | No newline at end of file |