Skip to content

Commit

Permalink
feat: add file:// linking
Browse files Browse the repository at this point in the history
  • Loading branch information
callmeteus committed Jan 29, 2024
1 parent aa97088 commit 87d497a
Show file tree
Hide file tree
Showing 13 changed files with 320 additions and 203 deletions.
9 changes: 8 additions & 1 deletion src/core/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ export class App {
private localConfig: IConfig = {};
private config: IConfig = {};

private packageJson: {
/**
* The package.json contents for the current project.
*/
public packageJson: {
name?: string;
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
Expand Down Expand Up @@ -115,13 +118,17 @@ export class App {
private setup() {
this.version = appPackageJson.version;

// If there's a global file
if (fs.existsSync(this.getGlobalConfigFilename())) {
// Load it
this.globalConfig = yaml.parse(
fs.readFileSync(this.getGlobalConfigFilename(), "utf-8")
);
}

// If there's a local file
if (fs.existsSync(this.getLocalConfigFilename())) {
// Load it
this.localConfig = yaml.parse(
fs.readFileSync(this.getLocalConfigFilename(), "utf-8")
);
Expand Down
13 changes: 13 additions & 0 deletions src/core/Command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Please, keep this file alphabetically ordered.
*/

export * from "./commands/link";

export * from "./commands/mklink";

export * from "./commands/performLinks";
export * from "./commands/processGlobalLinks";
export * from "./commands/resetGlobalLinks";

export * from "./commands/unlink";
218 changes: 18 additions & 200 deletions src/core/CommandProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
import { glob } from "glob";
import { App, ILinkOptions } from "./App";

import * as path from "path";
import * as fs from "fs";
import { App } from "./App";

import colors from "colors";
import deepmerge from "deepmerge";

import { logger } from "./Logger";
import { Yarn } from "../helpers/Yarn";
import { exitWithError } from "../helpers/Process";
import { YarnDetourer } from "./YarnDetourer";

import * as Commands from "./Command";

interface IOptions {
ignoreGlobalLinks?: boolean;
ignoreFileLinks?: boolean;
verbose?: boolean;
headless?: boolean;
}

export class CommandProcessor {
public static instance: CommandProcessor;

constructor(
protected options: IOptions = {}
public options: IOptions = {}
) {
this.setOptions(options);

CommandProcessor.instance = this;
}

/**
Expand All @@ -41,19 +42,23 @@ export class CommandProcessor {

switch(cmd) {
case undefined:
return this.performLinks();
return Commands.performLinks();

/**
* Link-related commands
*/

case "link":
return this.link(args as any);
return Commands.link(args as any);

case "mklink":
return this.mklink(args as any);
return Commands.mklink(args as any);

case "unlink":
return this.unlink(args as any);
return Commands.unlink(args as any);

case "reset-global-links":
return this.resetGlobalLinks(args as any);
return Commands.resetGlobalLinks(args as any);

case "global-hook": {
const detour = new YarnDetourer();
Expand Down Expand Up @@ -83,161 +88,6 @@ export class CommandProcessor {
logger.level = this.options.verbose ? "silly" : "info";
}

/**
* Creates a link for a given package.
* @param link The link options.
*/
public link(link: ILinkOptions) {
/*const app = App.instance();
// If no package was given
if (!link.package) {
// If has no package.json
if (!app.packageJson) {
throw new Error("No package name was given and also a package.json wasn't found.");
}
// Create a new link for it
const obj = app[link.global ? "globalConfig" : "localConfig"];
// Create a link for it
obj.links[app.packageJson.name] = process.cwd();
// Save the configuration files
app.save(link.global ? "global" : "local");
}*/
}

/**
* Creates multiple links for a given pattern.
* @param link The link options.
*/
public async mklink(link: {
pattern: string;
depth?: number;
}) {
// Remove the last slash from the pattern if has one
if (link.pattern.endsWith("/")) {
link.pattern = link.pattern.substring(0, link.pattern.length - 1);
}

const possibleProjectPaths = await glob.glob(link.pattern + "/", {
maxDepth: link.depth,
follow: true,
absolute: true,
cwd: process.cwd(),
ignore: "node_modules/**"
});

for(const projectPath of possibleProjectPaths) {
const packageJsonPath = path.resolve(projectPath, "package.json");

// Ignore if there's no package.json in the folder
if (!fs.existsSync(packageJsonPath)) {
continue;
}

const packageJson = require(packageJsonPath);

// Call a link for it
await Yarn.execYarn({
cmd: "link",
cwd: projectPath
});

logger.info("linked package \"%s\"", packageJson.name);
}
}

/**
* Deletes a link for a given package.
* @param unlink The link options.
*/
public async unlink(unlink: ILinkOptions) {
// If no package was given
if (!unlink.package) {
/*// If has no package.json
if (!this.packageJson) {
throw new Error("No package name was given and also a package.json wasn't found.");
}
// Create a new link for it
const obj = this[unlink.global ? "globalConfig" : "localConfig"];
// Create a link for it
obj.links[this.packageJson.name] = process.cwd();
// Save the configuration files
this.save(unlink.global ? "global" : "local");*/

return;
}

// If it's a global unlink
if (unlink.global) {
// Try finding the original link folder
const linkFolder = await Yarn.getGlobalLinkSymlinkPath(unlink.package);

// If no folder was found
if (!linkFolder) {
// Welp, there's nothing to do here
return exitWithError("Unable to determine the location for \"%s\"", unlink.package);
}

logger.info("the folder for %s is %s", unlink.package, linkFolder);

try {
// If the symlink still exists
if (fs.lstatSync(linkFolder).isSymbolicLink()) {
// If it's a broken symlink
if (!fs.existsSync(linkFolder)) {
// If it's not forcing a deletion
if (!unlink.force) {
return exitWithError("The given symlink doesn't resolve to anywhere. Use --force to abruptly delete the symlink.");
}

// Delete the symlink
await fs.promises.unlink(linkFolder);
} else {
// Just all unlink at the folder
await Yarn.execYarn({
cmd: "unlink",
cwd: path.resolve(linkFolder)
});
}
} else {
throw new Error("Invalid symlink found.");
}
} catch(e) {
console.error(e);

return exitWithError("There's no symlink for \"%s\"", unlink.package);
}
}

logger.info("the symlink for \"%s\" was sucessfully removed.", unlink.package);
}

/**
* Perform all links based on configurations.
*/
public performLinks() {
/*if (this.config.rules) {
for(const rule of this.config.rules) {
if ("includes" in rule) {
this.processIncludeRule(rule.includes);
}
}
}*/

// If isn't ignoring global links
if (!this.options.ignoreGlobalLinks) {
this.processGlobalLinks();
}

logger.info("all packages were linked sucessfully");
}

/**
* Processes a include rule.
* @param include The name that needs to appear in the package names.
Expand All @@ -251,36 +101,4 @@ export class CommandProcessor {
}
}
}*/

/**
* Processes all global links
*/
private async processGlobalLinks() {
for(const pkgName of await Yarn.getGloballyLinkedPackages()) {
await App.instance().performSingleLink(pkgName);

logger.info("sucessfully linked \"%s\"", pkgName);
}
}

/**
* Resets global links.
* @param opts Any options to be passed to the resetter.
*/
public async resetGlobalLinks(opts?: {
/**
* Will only delete broken symlinks.
*/
onlyBroken?: boolean;
}) {
// Retrieve all linked packages
for(const link of await Yarn.getGloballyLinkedPackages({
includeBroken: opts?.onlyBroken
})) {
// Unlink it
fs.unlinkSync(await Yarn.getGlobalLinkSymlinkPath(link));

logger.info("unlinked \"%s\"", link);
}
}
}
33 changes: 33 additions & 0 deletions src/core/commands/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Yarn } from "../../helpers/Yarn";
import { App, ILinkOptions } from "../App";

/**
* Creates a link for a given package.
* @param link The link options.
*/
export const link = async (link: ILinkOptions) => {
const app = App.instance();

// If no package was given
if (!link.package) {
// If has no package.json
/*if (!app.packageJson) {
throw new Error("No package name was given and also a package.json wasn't found.");
}
// Create a new link for it
const obj = app[link.global ? "globalConfig" : "localConfig"];
// Create a link for it
obj.links ??= {};
obj.links[app.packageJson.name] = process.cwd();
console.log(app);
// Save the configuration files
//app.save(link.global ? "global" : "local");*/

// Link the current package
await Yarn.link();
}
}
51 changes: 51 additions & 0 deletions src/core/commands/mklink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { glob } from "glob";
import * as path from "path";
import * as fs from "fs";
import { logger } from "../Logger";
import { Yarn } from "../../helpers/Yarn";

/**
* Creates multiple links for a given pattern.
* @param link The link options.
*/
export const mklink = async(link: {
pattern: string;
depth?: number;
}) => {
// If there's no pattern
if (!link.pattern) {
return logger.error("no pattern was given");
}

// Remove the last slash from the pattern if has one
if (link.pattern.endsWith("/")) {
link.pattern = link.pattern.substring(0, link.pattern.length - 1);
}

const possibleProjectPaths = await glob.glob(link.pattern + "/", {
maxDepth: link.depth,
follow: true,
absolute: true,
cwd: process.cwd(),
ignore: "node_modules/**"
});

for (const projectPath of possibleProjectPaths) {
const packageJsonPath = path.resolve(projectPath, "package.json");

// Ignore if there's no package.json in the folder
if (!fs.existsSync(packageJsonPath)) {
continue;
}

const packageJson = require(packageJsonPath);

// Call a link for it
await Yarn.execYarn({
cmd: "link",
cwd: projectPath
});

logger.info("linked package \"%s\"", packageJson.name);
}
};
Loading

0 comments on commit 87d497a

Please sign in to comment.