@navikt/aksel
Version:
Aksel command line interface. Codemods and other utilities for Aksel users.
337 lines (336 loc) • 15 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runTooling = runTooling;
const chalk_1 = __importDefault(require("chalk"));
const enquirer_1 = __importDefault(require("enquirer"));
const fast_glob_1 = __importDefault(require("fast-glob"));
const jscodeshift = __importStar(require("jscodeshift/src/Runner"));
const node_path_1 = __importDefault(require("node:path"));
const codeshift_utils_1 = require("../codemod/codeshift.utils");
const validation_1 = require("../codemod/validation");
const print_remaining_1 = require("./tasks/print-remaining");
const status_1 = require("./tasks/status");
// Constants
const TRANSFORMS = {
"css-tokens": "./transforms/darkside-tokens-css",
"scss-tokens": "./transforms/darkside-tokens-scss",
"less-tokens": "./transforms/darkside-tokens-less",
"js-tokens": "./transforms/darkside-tokens-js",
"tailwind-tokens": "./transforms/darkside-tokens-tailwind",
};
/**
* Main entry point for the tooling system
*/
function runTooling(options, program) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
console.info(chalk_1.default.greenBright.bold("\nWelcome to the Aksel v8 token migration tool!"));
const globList = (_a = options.glob) !== null && _a !== void 0 ? _a : (0, codeshift_utils_1.getDefaultGlob)(options === null || options === void 0 ? void 0 : options.ext);
console.info(chalk_1.default.gray(`Using glob pattern(s): ${globList}\nWorking directory: ${process.cwd()}\n`));
// Find matching files based on glob pattern
const filepaths = yield (0, fast_glob_1.default)(globList, {
cwd: process.cwd(),
ignore: codeshift_utils_1.GLOB_IGNORE_PATTERNS,
/**
* When globbing, do not follow symlinks to avoid processing files outside the directory.
* This is most likely to happen in monorepos where node_modules may contain symlinks to packages
* in other parts of the repo.
*
* While node_modules is already ignored via GLOB_IGNORE_PATTERNS, if user globs upwards (e.g., using '../src/**'),
* that ignore-pattern may be ignored, leading to unintended file processing.
*/
followSymbolicLinks: false,
});
if (options.dryRun) {
console.info(chalk_1.default.yellow("Running in dry-run mode, no changes will be made"));
}
// Show initial status
const initialStatus = (0, status_1.getStatus)(filepaths);
// Task execution loop
let task = yield getNextTask(initialStatus.status);
let currentStatus = initialStatus;
while (task !== "exit") {
console.info("\n\n");
try {
currentStatus = yield executeTask(task, filepaths, options, program, currentStatus, () => (0, status_1.getStatus)(filepaths, "no-print"));
}
catch (error) {
program.error(chalk_1.default.red("Error:", error.message));
}
task = yield getNextTask(currentStatus.status);
}
process.exit(0);
});
}
/**
* Executes the selected task
*/
function executeTask(task, filepaths, options, program, statusStore, updateStatus) {
return __awaiter(this, void 0, void 0, function* () {
switch (task) {
case "status":
return updateStatus();
case "print-remaining-tokens": {
const newStatus = updateStatus();
yield (0, print_remaining_1.printRemaining)(filepaths, newStatus.status);
return newStatus;
}
case "css-tokens":
case "scss-tokens":
case "less-tokens":
case "js-tokens":
case "tailwind-tokens": {
if (!options.force) {
(0, validation_1.validateGit)(options, program);
}
const scopedFiles = getScopedFilesForTask(task, filepaths, statusStore.status);
const tokensBefore = getTokenCount(statusStore.status, task);
const stats = yield runCodeshift(task, scopedFiles, {
dryRun: options.dryRun,
force: options.force,
});
const newStatus = updateStatus();
const tokensAfter = getTokenCount(newStatus.status, task);
printSummary(task, stats, tokensBefore, tokensAfter);
return newStatus;
}
case "run-all-migrations": {
const tasks = [
"css-tokens",
"scss-tokens",
"less-tokens",
"js-tokens",
"tailwind-tokens",
];
if (!options.force) {
(0, validation_1.validateGit)(options, program);
}
let currentStatus = statusStore;
const summaryData = [];
for (const migrationTask of tasks) {
console.info(`\nRunning ${migrationTask}...`);
const scopedFiles = getScopedFilesForTask(migrationTask, filepaths, currentStatus.status);
const tokensBefore = getTokenCount(currentStatus.status, migrationTask);
const stats = yield runCodeshift(migrationTask, scopedFiles, {
dryRun: options.dryRun,
force: true,
});
currentStatus = updateStatus();
const tokensAfter = getTokenCount(currentStatus.status, migrationTask);
summaryData.push({
task: migrationTask,
stats,
tokensBefore,
tokensAfter,
});
}
console.info(chalk_1.default.bold(`\nMigration Summary:`));
console.info("-".repeat(60));
for (const data of summaryData) {
const replaced = data.tokensBefore - data.tokensAfter;
const remaining = data.tokensAfter;
const icon = remaining === 0 ? "✨" : "⚠️";
console.info(`${chalk_1.default.bold(data.task)}:`);
console.info(` Files changed: ${data.stats.ok}`);
console.info(` Tokens replaced: ${replaced}`);
console.info(` ${icon} Remaining: ${remaining}`);
console.info("");
}
return currentStatus;
}
default:
program.error(chalk_1.default.red(`Unknown task: ${task}`));
return statusStore;
}
});
}
const JS_EXTENSIONS = [
"js",
"jsx",
"ts",
"tsx",
];
/**
* Filter files based on the selected task
*/
function getScopedFilesForTask(task, filepaths, status) {
let safeFilepaths = filepaths;
if (task === "js-tokens") {
safeFilepaths = filepaths.filter((f) => JS_EXTENSIONS.some((ext) => f.endsWith(`.${ext}`)));
}
return safeFilepaths.filter((f) => {
switch (task) {
case "css-tokens":
return !!status.css.legacy.find((config) => config.fileName === f);
case "scss-tokens":
return !!status.scss.legacy.find((config) => config.fileName === f);
case "less-tokens":
return !!status.less.legacy.find((config) => config.fileName === f);
case "js-tokens":
return !!status.js.legacy.find((config) => config.fileName === f);
case "tailwind-tokens":
return !!status.tailwind.legacy.find((config) => config.fileName === f);
default:
return false;
}
});
}
/**
* Runs the jscodeshift codemod for the selected task
*/
function runCodeshift(task, filepaths, options) {
return __awaiter(this, void 0, void 0, function* () {
if (!TRANSFORMS[task]) {
throw new Error(`No transform found for task: ${task}`);
}
const codemodPath = node_path_1.default.join(__dirname, `${TRANSFORMS[task]}.js`);
return yield jscodeshift.run(codemodPath, filepaths, {
babel: true,
ignorePattern: codeshift_utils_1.GLOB_IGNORE_PATTERNS,
parser: "tsx",
verbose: 2,
runInBand: true,
silent: false,
stdin: false,
dry: options === null || options === void 0 ? void 0 : options.dryRun,
force: options === null || options === void 0 ? void 0 : options.force,
print: false,
});
});
}
/**
* Prompts the user for the next task to run
*/
function getNextTask(status) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const getMessage = (base, tokens) => {
if (!status)
return base;
const fileCount = new Set(tokens.map((t) => t.fileName)).size;
if (fileCount === 0)
return `${base} (Done)`;
return `${base} (${fileCount} files)`;
};
const choices = [
{ message: "Check status", name: "status" },
{ message: "Print status", name: "print-remaining-tokens" },
{
message: getMessage("Migrate CSS tokens", (_b = (_a = status === null || status === void 0 ? void 0 : status.css) === null || _a === void 0 ? void 0 : _a.legacy) !== null && _b !== void 0 ? _b : []),
name: "css-tokens",
},
{
message: getMessage("Migrate Scss tokens", (_d = (_c = status === null || status === void 0 ? void 0 : status.scss) === null || _c === void 0 ? void 0 : _c.legacy) !== null && _d !== void 0 ? _d : []),
name: "scss-tokens",
},
{
message: getMessage("Migrate Less tokens", (_f = (_e = status === null || status === void 0 ? void 0 : status.less) === null || _e === void 0 ? void 0 : _e.legacy) !== null && _f !== void 0 ? _f : []),
name: "less-tokens",
},
{
message: getMessage("Migrate JS tokens", (_h = (_g = status === null || status === void 0 ? void 0 : status.js) === null || _g === void 0 ? void 0 : _g.legacy) !== null && _h !== void 0 ? _h : []),
name: "js-tokens",
},
{
message: getMessage("Migrate tailwind tokens", (_k = (_j = status === null || status === void 0 ? void 0 : status.tailwind) === null || _j === void 0 ? void 0 : _j.legacy) !== null && _k !== void 0 ? _k : []),
name: "tailwind-tokens",
},
{ message: "Run all migrations", name: "run-all-migrations" },
{ message: "Exit", name: "exit" },
];
try {
const response = yield enquirer_1.default.prompt({
type: "select",
name: "task",
message: "Task",
initial: "status",
choices,
onCancel: () => process.exit(1),
});
return response.task;
}
catch (error) {
if (error.isTtyError) {
console.info("Oops, something went wrong! Looks like @navikt/aksel can't run in this terminal. " +
"Contact Aksel for support if this persists, or try another terminal.");
}
else {
console.error(error);
}
process.exit(1);
}
});
}
function getTokenCount(status, task) {
switch (task) {
case "css-tokens":
return status.css.legacy.length;
case "scss-tokens":
return status.scss.legacy.length;
case "less-tokens":
return status.less.legacy.length;
case "js-tokens":
return status.js.legacy.length;
case "tailwind-tokens":
return status.tailwind.legacy.length;
default:
return 0;
}
}
function printSummary(task, stats, tokensBefore, tokensAfter) {
console.info(chalk_1.default.bold(`\nMigration Summary for ${task}:`));
console.info("-".repeat(40));
console.info(`✅ Files changed: ${stats.ok}`);
console.info(`✅ Tokens replaced: ${tokensBefore - tokensAfter}`);
if (tokensAfter > 0) {
console.info(chalk_1.default.yellow(`⚠️ Tokens remaining: ${tokensAfter} (manual intervention needed)`));
}
else {
console.info(chalk_1.default.green(`✨ Tokens remaining: ${tokensAfter}`));
}
}