Implement --revert for own command

This commit is contained in:
Joseph Garrone 2024-12-24 01:10:32 +01:00
parent 6de5fd4f96
commit d2da43c617
5 changed files with 176 additions and 82 deletions

View File

@ -263,6 +263,7 @@ program
program
.command<{
path: string;
revert: boolean;
}>({
name: "own",
description: [
@ -286,14 +287,26 @@ program
"Example `--path admin/page/Login.tsx`"
].join(" ")
})
.option({
key: "revert",
name: (() => {
const name = "revert";
optionsKeys.push(name);
return name;
})(),
description: "Revert ownership claim over a given file or directory.",
defaultValue: false
})
.task({
skip,
handler: async ({ projectDirPath, path }) => {
handler: async ({ projectDirPath, path, revert }) => {
const { command } = await import("./own");
await command({
buildContext: getBuildContext({ projectDirPath }),
cliCommandOptions: { path }
cliCommandOptions: { path, isRevert: revert }
});
}
});

View File

@ -11,58 +11,109 @@ import {
} from "./postinstall/managedGitignoreFile";
import { isInside } from "./tools/isInside";
import chalk from "chalk";
import type { UiModuleMeta } from "./postinstall/uiModuleMeta";
import { command as command_postinstall } from "./postinstall";
export async function command(params: {
buildContext: BuildContext;
cliCommandOptions: {
path: string;
isRevert: boolean;
};
}) {
const { buildContext, cliCommandOptions } = params;
const fileOrDirectoryRelativePath = pathRelative(
buildContext.themeSrcDirPath,
getAbsoluteAndInOsFormatPath({
cwd: buildContext.themeSrcDirPath,
pathIsh: cliCommandOptions.path
})
);
const uiModuleMetas = await getUiModuleMetas({ buildContext });
const arr = uiModuleMetas
.map(uiModuleMeta => ({
uiModuleMeta,
fileRelativePaths: uiModuleMeta.files
.map(({ fileRelativePath }) => fileRelativePath)
.filter(
fileRelativePath =>
fileRelativePath === fileOrDirectoryRelativePath ||
isInside({
dirPath: fileOrDirectoryRelativePath,
filePath: fileRelativePath
})
)
}))
.filter(({ fileRelativePaths }) => fileRelativePaths.length !== 0);
const { targetFileRelativePathsByUiModuleMeta } = await (async () => {
const fileOrDirectoryRelativePath = pathRelative(
buildContext.themeSrcDirPath,
getAbsoluteAndInOsFormatPath({
cwd: buildContext.themeSrcDirPath,
pathIsh: cliCommandOptions.path
})
);
if (arr.length === 0) {
const arr = uiModuleMetas
.map(uiModuleMeta => ({
uiModuleMeta,
fileRelativePaths: uiModuleMeta.files
.map(({ fileRelativePath }) => fileRelativePath)
.filter(
fileRelativePath =>
fileRelativePath === fileOrDirectoryRelativePath ||
isInside({
dirPath: fileOrDirectoryRelativePath,
filePath: fileRelativePath
})
)
}))
.filter(({ fileRelativePaths }) => fileRelativePaths.length !== 0);
const targetFileRelativePathsByUiModuleMeta = new Map<UiModuleMeta, string[]>();
for (const { uiModuleMeta, fileRelativePaths } of arr) {
targetFileRelativePathsByUiModuleMeta.set(uiModuleMeta, fileRelativePaths);
}
return { targetFileRelativePathsByUiModuleMeta };
})();
if (targetFileRelativePathsByUiModuleMeta.size === 0) {
console.log(
chalk.yellow("There is no UI module files matching the provided path.")
);
process.exit(1);
}
const { ownedFilesRelativePaths: ownedFilesRelativePaths_before } =
const { ownedFilesRelativePaths: ownedFilesRelativePaths_current } =
await readManagedGitignoreFile({
buildContext
});
const ownedFilesRelativePaths_toAdd: string[] = [];
await (cliCommandOptions.isRevert ? command_revert : command_own)({
uiModuleMetas,
targetFileRelativePathsByUiModuleMeta,
ownedFilesRelativePaths_current,
buildContext
});
}
type Params_subcommands = {
uiModuleMetas: UiModuleMeta[];
targetFileRelativePathsByUiModuleMeta: Map<UiModuleMeta, string[]>;
ownedFilesRelativePaths_current: string[];
buildContext: BuildContext;
};
async function command_own(params: Params_subcommands) {
const {
uiModuleMetas,
targetFileRelativePathsByUiModuleMeta,
ownedFilesRelativePaths_current,
buildContext
} = params;
await writeManagedGitignoreFile({
buildContext,
uiModuleMetas,
ownedFilesRelativePaths: [
...ownedFilesRelativePaths_current,
...Array.from(targetFileRelativePathsByUiModuleMeta.values())
.flat()
.filter(
fileRelativePath =>
!ownedFilesRelativePaths_current.includes(fileRelativePath)
)
]
});
const writeActions: (() => Promise<void>)[] = [];
for (const { uiModuleMeta, fileRelativePaths } of arr) {
for (const [
uiModuleMeta,
fileRelativePaths
] of targetFileRelativePathsByUiModuleMeta.entries()) {
const uiModuleDirPath = await getInstalledModuleDirPath({
moduleName: uiModuleMeta.moduleName,
packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath),
@ -70,46 +121,81 @@ export async function command(params: {
});
for (const fileRelativePath of fileRelativePaths) {
if (ownedFilesRelativePaths_before.includes(fileRelativePath)) {
if (ownedFilesRelativePaths_current.includes(fileRelativePath)) {
console.log(
chalk.yellow(`You already have ownership over "${fileRelativePath}".`)
chalk.grey(`You already have ownership over '${fileRelativePath}'.`)
);
continue;
}
const sourceCode = await getUiModuleFileSourceCodeReadyToBeCopied({
buildContext,
fileRelativePath,
isOwnershipAction: true,
uiModuleName: uiModuleMeta.moduleName,
uiModuleDirPath,
uiModuleVersion: uiModuleMeta.version
});
writeActions.push(async () => {
const sourceCode = await getUiModuleFileSourceCodeReadyToBeCopied({
buildContext,
fileRelativePath,
isOwnershipAction: true,
uiModuleName: uiModuleMeta.moduleName,
uiModuleDirPath,
uiModuleVersion: uiModuleMeta.version
});
writeActions.push(() =>
fsPr.writeFile(
await fsPr.writeFile(
pathJoin(buildContext.themeSrcDirPath, fileRelativePath),
sourceCode
)
);
);
ownedFilesRelativePaths_toAdd.push(fileRelativePath);
console.log(chalk.green(`Ownership over '${fileRelativePath}' claimed.`));
});
}
}
if (ownedFilesRelativePaths_toAdd.length === 0) {
if (writeActions.length === 0) {
console.log(chalk.yellow("No new file claimed."));
process.exit(1);
return;
}
await Promise.all(writeActions.map(action => action()));
}
async function command_revert(params: Params_subcommands) {
const {
uiModuleMetas,
targetFileRelativePathsByUiModuleMeta,
ownedFilesRelativePaths_current,
buildContext
} = params;
const ownedFilesRelativePaths_toRemove = Array.from(
targetFileRelativePathsByUiModuleMeta.values()
)
.flat()
.filter(fileRelativePath => {
if (!ownedFilesRelativePaths_current.includes(fileRelativePath)) {
console.log(
chalk.grey(`Ownership over '${fileRelativePath}' wasn't claimed.`)
);
return false;
}
console.log(
chalk.green(`Ownership over '${fileRelativePath}' relinquished.`)
);
return true;
});
if (ownedFilesRelativePaths_toRemove.length === 0) {
console.log(chalk.yellow("No file relinquished."));
return;
}
await writeManagedGitignoreFile({
buildContext,
uiModuleMetas,
ownedFilesRelativePaths: [
...ownedFilesRelativePaths_before,
...ownedFilesRelativePaths_toAdd
]
ownedFilesRelativePaths: ownedFilesRelativePaths_current.filter(
fileRelativePath =>
!ownedFilesRelativePaths_toRemove.includes(fileRelativePath)
)
});
await command_postinstall({ buildContext });
}

View File

@ -35,18 +35,25 @@ export async function getUiModuleFileSourceCodeReadyToBeCopied(params: {
sourceCode = addCommentToSourceCode({
sourceCode,
fileRelativePath,
commentLines: isOwnershipAction
? [
`This file was claimed for ownership from ${uiModuleName} version ${uiModuleVersion}.`
]
: [
`WARNING: Before modifying this file run the following command:`,
``,
`$ npx keycloakify own --path '${fileRelativePath.split(pathSep).join("/")}'`,
``,
`This file comes from ${uiModuleName} version ${uiModuleVersion}.`,
`This file has been copied over to your repo by your postinstall script: \`npx keycloakify postinstall\``
]
commentLines: (() => {
const path = fileRelativePath.split(pathSep).join("/");
return isOwnershipAction
? [
`This file has been claimed for ownership from ${uiModuleName} version ${uiModuleVersion}.`,
`To relinquish ownership and restore this file to its original content, run the following command:`,
``,
`$ npx keycloakify own --revert --path '${path}'`
]
: [
`WARNING: Before modifying this file, run the following command:`,
``,
`$ npx keycloakify own --path '${path}'`,
``,
`This file is provided by ${uiModuleName} version ${uiModuleVersion}.`,
`It was copied into your repository by the postinstall script: \`keycloakify postinstall\`.`
];
})()
});
const destFilePath = pathJoin(buildContext.themeSrcDirPath, fileRelativePath);

View File

@ -9,7 +9,7 @@ import { dirname as pathDirname } from "path";
import { join as pathJoin } from "path";
import { existsAsync } from "../tools/fs.existsAsync";
import * as fsPr from "fs/promises";
import { getIsTrackedByGit } from "../tools/isTrackedByGit";
import { getIsKnownByGit } from "../tools/isKnownByGit";
import { untrackFromGit } from "../tools/untrackFromGit";
export async function command(params: { buildContext: BuildContext }) {
@ -65,19 +65,7 @@ export async function command(params: { buildContext: BuildContext }) {
return;
}
git_untrack: {
if (!doesFileExist) {
break git_untrack;
}
const isTracked = await getIsTrackedByGit({
filePath: destFilePath
});
if (!isTracked) {
break git_untrack;
}
if (await getIsKnownByGit({ filePath: destFilePath })) {
await untrackFromGit({
filePath: destFilePath
});

View File

@ -2,28 +2,28 @@ import * as child_process from "child_process";
import { dirname as pathDirname, basename as pathBasename } from "path";
import { Deferred } from "evt/tools/Deferred";
export function getIsTrackedByGit(params: { filePath: string }): Promise<boolean> {
export function getIsKnownByGit(params: { filePath: string }): Promise<boolean> {
const { filePath } = params;
const dIsTracked = new Deferred<boolean>();
const dIsKnownByGit = new Deferred<boolean>();
child_process.exec(
`git ls-files --error-unmatch ${pathBasename(filePath)}`,
{ cwd: pathDirname(filePath) },
error => {
if (error === null) {
dIsTracked.resolve(true);
dIsKnownByGit.resolve(true);
return;
}
if (error.code === 1) {
dIsTracked.resolve(false);
dIsKnownByGit.resolve(false);
return;
}
dIsTracked.reject(error);
dIsKnownByGit.reject(error);
}
);
return dIsTracked.pr;
return dIsKnownByGit.pr;
}