UNPKG

18.7 kBJavaScriptView Raw
1// src/index.ts
2import fixturify from "fixturify";
3import tmp from "tmp";
4import fs from "fs-extra";
5import path from "path";
6import resolvePackagePath from "resolve-package-path";
7import CacheGroup from "resolve-package-path/lib/cache-group.js";
8import binLinks from "bin-links";
9import walkSync from "walk-sync";
10import deepmerge from "deepmerge";
11import { findWorkspaceDir } from "@pnpm/find-workspace-dir";
12import { findWorkspacePackages } from "@pnpm/workspace.find-packages";
13import { packlist } from "@pnpm/fs.packlist";
14import { PackageCache } from "@embroider/shared-internals";
15tmp.setGracefulCleanup();
16var defaultFiles = {
17 "index.js": `
18 'use strict';
19 module.exports = {};`
20};
21var Project = class {
22 constructor(first, second, third, fourth) {
23 this.isDependency = true;
24 this._dependencies = {};
25 this._devDependencies = {};
26 this.dependencyLinks = /* @__PURE__ */ new Map();
27 this.linkIsDevDependency = /* @__PURE__ */ new Set();
28 this.usingHardLinks = true;
29 this.resolutionCache = new CacheGroup();
30 this.transitivePeersCache = /* @__PURE__ */ new Map();
31 this.knownWorkspaces = /* @__PURE__ */ 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 = /* @__PURE__ */ 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 = /* @__PURE__ */ 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 = /* @__PURE__ */ 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};
466function deserializePackageJson(serialized) {
467 return JSON.parse(serialized);
468}
469function keys(object) {
470 if (object !== null && (typeof object === "object" || Array.isArray(object))) {
471 return Object.keys(object);
472 } else {
473 return [];
474 }
475}
476function isProjectCallback(maybe) {
477 return typeof maybe === "function";
478}
479function 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}
487function 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}
497function 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}
507function isDirJSON(value) {
508 return typeof value === "object" && value !== null;
509}
510function getPackageName(pkg) {
511 return getString(pkg, "name", `package.json is missing a name.`);
512}
513function getPackageVersion(pkg) {
514 return getString(pkg, "version", `${getPackageName(pkg)}'s package.json is missing a version.`);
515}
516function 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}
526function 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}
533function isObject(e) {
534 return e !== null && typeof e === "object" && !Array.isArray(e);
535}
536function isErrnoException(e) {
537 return isObject(e) && "code" in e;
538}
539function 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}
551function 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}
563function 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}
573function 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}
591export {
592 Project
593};
594//# sourceMappingURL=index.js.map
\No newline at end of file