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 program
.command<{ .command<{
path: string; path: string;
revert: boolean;
}>({ }>({
name: "own", name: "own",
description: [ description: [
@ -286,14 +287,26 @@ program
"Example `--path admin/page/Login.tsx`" "Example `--path admin/page/Login.tsx`"
].join(" ") ].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({ .task({
skip, skip,
handler: async ({ projectDirPath, path }) => { handler: async ({ projectDirPath, path, revert }) => {
const { command } = await import("./own"); const { command } = await import("./own");
await command({ await command({
buildContext: getBuildContext({ projectDirPath }), buildContext: getBuildContext({ projectDirPath }),
cliCommandOptions: { path } cliCommandOptions: { path, isRevert: revert }
}); });
} }
}); });

View File

@ -11,15 +11,21 @@ import {
} from "./postinstall/managedGitignoreFile"; } from "./postinstall/managedGitignoreFile";
import { isInside } from "./tools/isInside"; import { isInside } from "./tools/isInside";
import chalk from "chalk"; import chalk from "chalk";
import type { UiModuleMeta } from "./postinstall/uiModuleMeta";
import { command as command_postinstall } from "./postinstall";
export async function command(params: { export async function command(params: {
buildContext: BuildContext; buildContext: BuildContext;
cliCommandOptions: { cliCommandOptions: {
path: string; path: string;
isRevert: boolean;
}; };
}) { }) {
const { buildContext, cliCommandOptions } = params; const { buildContext, cliCommandOptions } = params;
const uiModuleMetas = await getUiModuleMetas({ buildContext });
const { targetFileRelativePathsByUiModuleMeta } = await (async () => {
const fileOrDirectoryRelativePath = pathRelative( const fileOrDirectoryRelativePath = pathRelative(
buildContext.themeSrcDirPath, buildContext.themeSrcDirPath,
getAbsoluteAndInOsFormatPath({ getAbsoluteAndInOsFormatPath({
@ -28,8 +34,6 @@ export async function command(params: {
}) })
); );
const uiModuleMetas = await getUiModuleMetas({ buildContext });
const arr = uiModuleMetas const arr = uiModuleMetas
.map(uiModuleMeta => ({ .map(uiModuleMeta => ({
uiModuleMeta, uiModuleMeta,
@ -46,23 +50,70 @@ export async function command(params: {
})) }))
.filter(({ fileRelativePaths }) => fileRelativePaths.length !== 0); .filter(({ fileRelativePaths }) => fileRelativePaths.length !== 0);
if (arr.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( console.log(
chalk.yellow("There is no UI module files matching the provided path.") chalk.yellow("There is no UI module files matching the provided path.")
); );
process.exit(1); process.exit(1);
} }
const { ownedFilesRelativePaths: ownedFilesRelativePaths_before } = const { ownedFilesRelativePaths: ownedFilesRelativePaths_current } =
await readManagedGitignoreFile({ await readManagedGitignoreFile({
buildContext 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>)[] = []; const writeActions: (() => Promise<void>)[] = [];
for (const { uiModuleMeta, fileRelativePaths } of arr) { for (const [
uiModuleMeta,
fileRelativePaths
] of targetFileRelativePathsByUiModuleMeta.entries()) {
const uiModuleDirPath = await getInstalledModuleDirPath({ const uiModuleDirPath = await getInstalledModuleDirPath({
moduleName: uiModuleMeta.moduleName, moduleName: uiModuleMeta.moduleName,
packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath), packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath),
@ -70,13 +121,14 @@ export async function command(params: {
}); });
for (const fileRelativePath of fileRelativePaths) { for (const fileRelativePath of fileRelativePaths) {
if (ownedFilesRelativePaths_before.includes(fileRelativePath)) { if (ownedFilesRelativePaths_current.includes(fileRelativePath)) {
console.log( console.log(
chalk.yellow(`You already have ownership over "${fileRelativePath}".`) chalk.grey(`You already have ownership over '${fileRelativePath}'.`)
); );
continue; continue;
} }
writeActions.push(async () => {
const sourceCode = await getUiModuleFileSourceCodeReadyToBeCopied({ const sourceCode = await getUiModuleFileSourceCodeReadyToBeCopied({
buildContext, buildContext,
fileRelativePath, fileRelativePath,
@ -86,30 +138,64 @@ export async function command(params: {
uiModuleVersion: uiModuleMeta.version uiModuleVersion: uiModuleMeta.version
}); });
writeActions.push(() => await fsPr.writeFile(
fsPr.writeFile(
pathJoin(buildContext.themeSrcDirPath, fileRelativePath), pathJoin(buildContext.themeSrcDirPath, fileRelativePath),
sourceCode 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.")); console.log(chalk.yellow("No new file claimed."));
process.exit(1); return;
} }
await Promise.all(writeActions.map(action => action())); 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({ await writeManagedGitignoreFile({
buildContext, buildContext,
uiModuleMetas, uiModuleMetas,
ownedFilesRelativePaths: [ ownedFilesRelativePaths: ownedFilesRelativePaths_current.filter(
...ownedFilesRelativePaths_before, fileRelativePath =>
...ownedFilesRelativePaths_toAdd !ownedFilesRelativePaths_toRemove.includes(fileRelativePath)
] )
}); });
await command_postinstall({ buildContext });
} }

View File

@ -35,18 +35,25 @@ export async function getUiModuleFileSourceCodeReadyToBeCopied(params: {
sourceCode = addCommentToSourceCode({ sourceCode = addCommentToSourceCode({
sourceCode, sourceCode,
fileRelativePath, fileRelativePath,
commentLines: isOwnershipAction commentLines: (() => {
const path = fileRelativePath.split(pathSep).join("/");
return isOwnershipAction
? [ ? [
`This file was claimed for ownership from ${uiModuleName} version ${uiModuleVersion}.` `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:`, `WARNING: Before modifying this file, run the following command:`,
``, ``,
`$ npx keycloakify own --path '${fileRelativePath.split(pathSep).join("/")}'`, `$ npx keycloakify own --path '${path}'`,
``, ``,
`This file comes from ${uiModuleName} version ${uiModuleVersion}.`, `This file is provided by ${uiModuleName} version ${uiModuleVersion}.`,
`This file has been copied over to your repo by your postinstall script: \`npx keycloakify postinstall\`` `It was copied into your repository by the postinstall script: \`keycloakify postinstall\`.`
] ];
})()
}); });
const destFilePath = pathJoin(buildContext.themeSrcDirPath, fileRelativePath); 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 { join as pathJoin } from "path";
import { existsAsync } from "../tools/fs.existsAsync"; import { existsAsync } from "../tools/fs.existsAsync";
import * as fsPr from "fs/promises"; import * as fsPr from "fs/promises";
import { getIsTrackedByGit } from "../tools/isTrackedByGit"; import { getIsKnownByGit } from "../tools/isKnownByGit";
import { untrackFromGit } from "../tools/untrackFromGit"; import { untrackFromGit } from "../tools/untrackFromGit";
export async function command(params: { buildContext: BuildContext }) { export async function command(params: { buildContext: BuildContext }) {
@ -65,19 +65,7 @@ export async function command(params: { buildContext: BuildContext }) {
return; return;
} }
git_untrack: { if (await getIsKnownByGit({ filePath: destFilePath })) {
if (!doesFileExist) {
break git_untrack;
}
const isTracked = await getIsTrackedByGit({
filePath: destFilePath
});
if (!isTracked) {
break git_untrack;
}
await untrackFromGit({ await untrackFromGit({
filePath: destFilePath 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 { dirname as pathDirname, basename as pathBasename } from "path";
import { Deferred } from "evt/tools/Deferred"; 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 { filePath } = params;
const dIsTracked = new Deferred<boolean>(); const dIsKnownByGit = new Deferred<boolean>();
child_process.exec( child_process.exec(
`git ls-files --error-unmatch ${pathBasename(filePath)}`, `git ls-files --error-unmatch ${pathBasename(filePath)}`,
{ cwd: pathDirname(filePath) }, { cwd: pathDirname(filePath) },
error => { error => {
if (error === null) { if (error === null) {
dIsTracked.resolve(true); dIsKnownByGit.resolve(true);
return; return;
} }
if (error.code === 1) { if (error.code === 1) {
dIsTracked.resolve(false); dIsKnownByGit.resolve(false);
return; return;
} }
dIsTracked.reject(error); dIsKnownByGit.reject(error);
} }
); );
return dIsTracked.pr; return dIsKnownByGit.pr;
} }