@public-ui/kolibri-cli 4.0.0-rc.2 → 4.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -64,10 +64,12 @@ Target version of @public-ui/*: ${options.overwriteTargetVersion}
64
64
  Source folder to migrate: ${baseDir}
65
65
  `);
66
66
  if (!fs_1.default.existsSync(baseDir)) {
67
- throw (0, reuse_1.logAndCreateError)(`The specified source folder "${baseDir}" does not exist or is inaccessible. Please check the path and try again.`);
67
+ const absolutePath = path_1.default.resolve(process.cwd(), baseDir);
68
+ throw (0, reuse_1.logAndCreateError)(`The specified source folder "${absolutePath}" (${baseDir}) does not exist or is inaccessible. Please check the path and try again.`);
68
69
  }
69
70
  if (!(0, reuse_1.hasKoliBriTags)(baseDir)) {
70
- console.log(chalk_1.default.yellow(`No KoliBri components (web or React) found under "${baseDir}". Check the path or your task configuration.`));
71
+ const absolutePath = path_1.default.resolve(process.cwd(), baseDir);
72
+ console.log(chalk_1.default.yellow(`No KoliBri components (web or React) found under "${absolutePath}" (${baseDir}). Check the path or your task configuration.`));
71
73
  }
72
74
  if (!options.ignoreGreaterVersion && semver_1.default.lt(options.overwriteTargetVersion, options.overwriteCurrentVersion)) {
73
75
  throw (0, reuse_1.logAndCreateError)('Your current version of @public-ui/components is greater than the version of @public-ui/kolibri-cli. Please update @public-ui/kolibri-cli or force the migration with --ignore-greater-version.');
@@ -92,7 +94,6 @@ Source folder to migrate: ${baseDir}
92
94
  if (options.testTasks) {
93
95
  runner.registerTasks(test_1.testTasks);
94
96
  }
95
- let version = options.overwriteCurrentVersion;
96
97
  /**
97
98
  * Creates a replacer function for the package.json file.
98
99
  * @param {string} version Version to set
@@ -109,45 +110,44 @@ Source folder to migrate: ${baseDir}
109
110
  /**
110
111
  * Sets the version of the @public-ui/* packages in the package.json file.
111
112
  * @param {string} version Version to set
112
- * @param {Function} cb Callback function
113
113
  */
114
- function setVersionOfPublicUiPackages(version, cb) {
114
+ function setVersionOfPublicUiPackages(version) {
115
115
  let packageJson = (0, reuse_1.getContentOfProjectPkgJson)();
116
116
  packageJson = packageJson.replace(/"(@public-ui\/[^"]+)":\s*"(.*)"/g, createVersionReplacer(version));
117
117
  fs_1.default.writeFileSync(path_1.default.resolve(process.cwd(), 'package.json'), packageJson);
118
118
  runner.setProjectVersion(version);
119
119
  console.log(`- Update @public-ui/* to version ${version}`);
120
- (0, child_process_1.exec)((0, reuse_1.getPackageManagerCommand)('install'), (err) => {
121
- if (err) {
122
- console.error(`exec error: ${err.message}`);
123
- return;
124
- }
125
- cb();
126
- });
127
120
  }
128
121
  /**
129
- * Runs the task runner in a loop until all tasks are completed.
122
+ * Runs the task runner in batch mode with collected version steps.
130
123
  */
131
- function runLoop() {
124
+ async function runMigrationBatch() {
125
+ // Set execution mode to batch - tasks don't install immediately
126
+ runner.setExecutionMode('batch');
127
+ console.log(`\nStarting migration in batch mode...`);
128
+ // Start version is the current project version (from package.json or --overwrite-current-version)
129
+ const startVersion = options.overwriteCurrentVersion;
130
+ // Target version is --overwrite-target-version (CLI version = migration target)
131
+ const targetVersion = options.overwriteTargetVersion;
132
+ console.log(`Migration path: ${startVersion} → ${targetVersion}`);
133
+ // Run all tasks (they transform the code, not the dependencies)
132
134
  runner.run();
133
- if (version !== runner.getPendingMinVersion()) {
134
- // Tasks
135
- version = runner.getPendingMinVersion();
136
- setVersionOfPublicUiPackages(version, runLoop);
137
- }
138
- else if (semver_1.default.lt(version, options.overwriteTargetVersion)) {
139
- // CLI
140
- version = options.overwriteTargetVersion;
141
- setVersionOfPublicUiPackages(version, finish);
142
- }
143
- else if (semver_1.default.lt(version, options.overwriteCurrentVersion)) {
144
- // Components
145
- version = options.overwriteCurrentVersion;
146
- setVersionOfPublicUiPackages(version, finish);
147
- }
148
- else {
149
- finish();
150
- }
135
+ // Write final target version to package.json (only once)
136
+ setVersionOfPublicUiPackages(targetVersion);
137
+ // Install only once at the end
138
+ console.log(`\nInstalling dependencies once...`);
139
+ await new Promise((resolve, reject) => {
140
+ (0, child_process_1.exec)((0, reuse_1.getPackageManagerCommand)('install'), (err) => {
141
+ if (err) {
142
+ console.error(`exec error: ${err.message}`);
143
+ reject(err);
144
+ return;
145
+ }
146
+ resolve();
147
+ });
148
+ });
149
+ console.log(`Dependencies installed successfully.`);
150
+ finish();
151
151
  }
152
152
  /**
153
153
  * Prints the status of the task runner and the modified files.
@@ -221,7 +221,11 @@ If something is wrong, the migration can be reverted with ${chalk_1.default.ital
221
221
  const status = runner.getStatus();
222
222
  console.log(`
223
223
  Execute ${status.total} registered tasks...`);
224
- runLoop();
224
+ // Use optimized batch mode for single-pass installation
225
+ runMigrationBatch().catch((error) => {
226
+ console.error('Migration failed:', error);
227
+ process.exit(1);
228
+ });
225
229
  });
226
230
  });
227
231
  }
@@ -14,6 +14,8 @@ class AbstractTask {
14
14
  versionRange;
15
15
  taskDependencies;
16
16
  status = 'pending';
17
+ executionMode = 'immediate';
18
+ pendingExecutables = [];
17
19
  static instances = new Map();
18
20
  description;
19
21
  constructor(identifier, title, extensions, versionRange, taskDependencies = [], options = {}) {
@@ -49,5 +51,17 @@ class AbstractTask {
49
51
  getVersionRange() {
50
52
  return this.versionRange;
51
53
  }
54
+ setExecutionMode(mode) {
55
+ this.executionMode = mode;
56
+ }
57
+ getExecutionMode() {
58
+ return this.executionMode;
59
+ }
60
+ getPendingExecutables() {
61
+ return this.pendingExecutables;
62
+ }
63
+ clearPendingExecutables() {
64
+ this.pendingExecutables = [];
65
+ }
52
66
  }
53
67
  exports.AbstractTask = AbstractTask;
@@ -5,21 +5,45 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.TaskRunner = void 0;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
+ const child_process_1 = require("child_process");
8
9
  const fs_1 = __importDefault(require("fs"));
9
10
  const path_1 = __importDefault(require("path"));
10
11
  const semver_1 = __importDefault(require("semver"));
11
12
  const reuse_1 = require("../shares/reuse");
12
13
  const MIN_VERSION_OF_PUBLIC_UI = '1.4.2';
14
+ /**
15
+ * Displays a progress bar in the console.
16
+ * @param {number} current Current progress
17
+ * @param {number} total Total items
18
+ * @param {string} title Optional title
19
+ */
20
+ function displayProgressBar(current, total, title = '') {
21
+ const barLength = 30;
22
+ const percentage = total === 0 ? 0 : (current / total) * 100;
23
+ const filledLength = Math.round((barLength * current) / total);
24
+ const emptyLength = barLength - filledLength;
25
+ const bar = chalk_1.default.green('█'.repeat(filledLength)) + chalk_1.default.gray('░'.repeat(emptyLength));
26
+ const percentStr = percentage.toFixed(0).padStart(3, ' ');
27
+ // Bestimme die maximale Ziffernzahl des totals
28
+ const totalDigits = total.toString().length;
29
+ const currentStr = current.toString().padStart(totalDigits, ' ');
30
+ const taskStr = `${currentStr}/${total}`;
31
+ const titleStr = title ? ` ${title}` : '';
32
+ process.stdout.write(`\r${bar} ${percentStr}% [${taskStr}]${titleStr}`);
33
+ }
13
34
  class TaskRunner {
14
35
  tasks = new Map();
15
36
  baseDir = '/';
16
37
  cliVersion = '0.0.0';
17
38
  projectVersion = '0.0.0';
39
+ executionMode = 'immediate';
40
+ aggregatedCommands = new Map(); // taskId -> commands
18
41
  config = {
19
42
  migrate: {
20
43
  tasks: {},
21
44
  },
22
45
  };
46
+ completedTasks = 0;
23
47
  constructor(baseDir, cliVersion, projectVersion, config) {
24
48
  this.setBaseDir(baseDir);
25
49
  this.setCliVersion(cliVersion);
@@ -46,6 +70,12 @@ class TaskRunner {
46
70
  this.projectVersion = version;
47
71
  }
48
72
  }
73
+ setExecutionMode(mode) {
74
+ this.executionMode = mode;
75
+ this.tasks.forEach((task) => {
76
+ task.setExecutionMode(mode);
77
+ });
78
+ }
49
79
  setConfig(config) {
50
80
  if (config.migrate?.tasks) {
51
81
  this.config.migrate.tasks = {
@@ -86,15 +116,22 @@ class TaskRunner {
86
116
  }
87
117
  else {
88
118
  this.config.migrate.tasks[task.getIdentifier()] = true;
89
- if (task.getStatus() === 'pending' &&
90
- semver_1.default.satisfies(this.projectVersion, task.getVersionRange(), {
91
- includePrerelease: true,
92
- })) {
119
+ const isProjectVersionGreater = semver_1.default.gtr(this.projectVersion, task.getVersionRange(), {
120
+ includePrerelease: true,
121
+ });
122
+ const isCliVersionLower = semver_1.default.ltr(this.cliVersion, task.getVersionRange(), {
123
+ includePrerelease: true,
124
+ });
125
+ if (task.getStatus() === 'pending' && !isProjectVersionGreater && !isCliVersionLower) {
93
126
  // task.setStatus('running'); only of the task is async
94
127
  if (!this.tasks.has(task.getIdentifier())) {
95
128
  this.registerTask(task);
96
129
  }
97
130
  task.run(this.baseDir);
131
+ // Collect commands if in batch mode
132
+ if (this.executionMode === 'batch') {
133
+ this.aggregateCommandsFromTask(task);
134
+ }
98
135
  task.setStatus('done');
99
136
  }
100
137
  }
@@ -103,14 +140,59 @@ class TaskRunner {
103
140
  taskDependencies.forEach((dependentTask) => {
104
141
  this.dependentTaskRun(dependentTask, dependentTask.getTaskDependencies());
105
142
  });
106
- if (taskDependencies.every((dependentTask) => dependentTask.getStatus() === 'done')) {
143
+ if (taskDependencies.length === 0 || taskDependencies.every((dependentTask) => dependentTask.getStatus() === 'done')) {
144
+ displayProgressBar(this.completedTasks, this.tasks.size, task.getTitle());
107
145
  this.runTask(task);
146
+ this.completedTasks++;
108
147
  }
109
148
  }
110
149
  run() {
150
+ this.completedTasks = 0;
151
+ this.aggregatedCommands.clear();
152
+ displayProgressBar(0, this.tasks.size);
111
153
  this.tasks.forEach((task) => {
112
154
  this.dependentTaskRun(task, task.getTaskDependencies());
113
155
  });
156
+ displayProgressBar(this.tasks.size, this.tasks.size);
157
+ if (this.tasks.size > 0) {
158
+ console.log(); // New line after progress bar
159
+ }
160
+ // If in batch mode, collect and execute all commands at the end
161
+ if (this.executionMode === 'batch') {
162
+ this.executeAggregatedCommands();
163
+ }
164
+ }
165
+ aggregateCommandsFromTask(task) {
166
+ // HandleDependencyTask has prepareExecutables method
167
+ if ('prepareExecutables' in task && typeof task.prepareExecutables === 'function') {
168
+ const taskWithPrepare = task;
169
+ const commands = taskWithPrepare.prepareExecutables();
170
+ if (commands.length > 0) {
171
+ this.aggregatedCommands.set(task.getIdentifier(), commands);
172
+ }
173
+ }
174
+ }
175
+ executeAggregatedCommands() {
176
+ const allCommands = [];
177
+ this.aggregatedCommands.forEach((commands) => {
178
+ allCommands.push(...commands);
179
+ });
180
+ if (allCommands.length === 0) {
181
+ return;
182
+ }
183
+ console.log('\nExecuting aggregated dependency commands...');
184
+ allCommands.forEach((command) => {
185
+ try {
186
+ console.log(` Running: ${command}`);
187
+ (0, child_process_1.execSync)(command, {
188
+ encoding: 'utf8',
189
+ stdio: 'inherit',
190
+ });
191
+ }
192
+ catch (error) {
193
+ console.warn(`Warning: Failed to execute command: ${command}`, error);
194
+ }
195
+ });
114
196
  }
115
197
  getPendingMinVersion() {
116
198
  let version = this.cliVersion;
@@ -24,26 +24,39 @@ class HandleDependencyTask extends abstract_task_1.AbstractTask {
24
24
  }
25
25
  return this.instances.get(identifier);
26
26
  }
27
- run() {
27
+ /**
28
+ * Collect dependency commands to be executed in batch mode.
29
+ * Does NOT execute immediately - returns commands for aggregation.
30
+ */
31
+ prepareExecutables() {
32
+ const commands = [];
28
33
  if (Object.keys(this.dependencies ?? {}).length > 0) {
29
34
  let command = `${(0, reuse_1.getPackageManagerCommand)(this.command)}`;
30
35
  Object.keys(this.dependencies ?? {}).forEach((dependency) => {
31
36
  command += ` ${dependency}@${this.dependencies[dependency]}`;
32
37
  });
33
- try {
34
- (0, child_process_1.execSync)(command, {
35
- encoding: 'utf8',
36
- });
37
- }
38
- catch (error) {
39
- console.warn(error);
40
- }
38
+ commands.push(command);
41
39
  }
42
40
  if (Object.keys(this.devDependencies ?? {}).length > 0) {
43
41
  let command = `${(0, reuse_1.getPackageManagerCommand)(this.command)} -D`;
44
42
  Object.keys(this.devDependencies ?? {}).forEach((dependency) => {
45
43
  command += ` ${dependency}`;
46
44
  });
45
+ commands.push(command);
46
+ }
47
+ return commands;
48
+ }
49
+ run() {
50
+ // In batch mode, commands are collected and executed later
51
+ // This method is kept for backward compatibility but does nothing
52
+ // Actual execution happens in TaskRunner with aggregated commands
53
+ }
54
+ /**
55
+ * Execute collected commands immediately (used for backward compatibility).
56
+ */
57
+ executeImmediate() {
58
+ const commands = this.prepareExecutables();
59
+ commands.forEach((command) => {
47
60
  try {
48
61
  (0, child_process_1.execSync)(command, {
49
62
  encoding: 'utf8',
@@ -52,7 +65,7 @@ class HandleDependencyTask extends abstract_task_1.AbstractTask {
52
65
  catch (error) {
53
66
  console.warn(error);
54
67
  }
55
- }
68
+ });
56
69
  }
57
70
  }
58
71
  exports.HandleDependencyTask = HandleDependencyTask;
@@ -47,7 +47,7 @@ function filterFilesByExt(dir, ext) {
47
47
  let files = [];
48
48
  const dirPath = path_1.default.resolve(process.cwd(), dir);
49
49
  fs_1.default.readdirSync(dirPath).forEach((file) => {
50
- const fullPath = path_1.default.resolve(dir, file);
50
+ const fullPath = path_1.default.resolve(dirPath, file);
51
51
  if (fs_1.default.lstatSync(fullPath).isDirectory()) {
52
52
  files = files.concat(filterFilesByExt(fullPath, ext));
53
53
  }
@@ -65,7 +65,8 @@ function filterFilesByExt(dir, ext) {
65
65
  */
66
66
  function hasKoliBriTags(dir) {
67
67
  const regexes = [types_1.WEB_TAG_REGEX, types_1.REACT_TAG_REGEX];
68
- const files = filterFilesByExt(dir, types_1.MARKUP_EXTENSIONS);
68
+ const dirPath = path_1.default.resolve(process.cwd(), dir);
69
+ const files = filterFilesByExt(dirPath, types_1.MARKUP_EXTENSIONS);
69
70
  for (const file of files) {
70
71
  let fd;
71
72
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@public-ui/kolibri-cli",
3
- "version": "4.0.0-rc.2",
3
+ "version": "4.0.0-rc.4",
4
4
  "license": "EUPL-1.2",
5
5
  "homepage": "https://public-ui.github.io",
6
6
  "repository": {
@@ -30,13 +30,12 @@
30
30
  "prettier-plugin-organize-imports": "4.3.0",
31
31
  "semver": "7.7.3",
32
32
  "typed-bem": "1.0.2",
33
- "@public-ui/components": "4.0.0-rc.2"
33
+ "@public-ui/components": "4.0.0-rc.4"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "24.10.4",
37
37
  "@typescript-eslint/eslint-plugin": "7.18.0",
38
38
  "@typescript-eslint/parser": "7.18.0",
39
- "cpy-cli": "6.0.0",
40
39
  "cross-env": "10.1.0",
41
40
  "eslint": "8.57.1",
42
41
  "eslint-config-prettier": "9.1.2",
@@ -47,7 +46,6 @@
47
46
  "knip": "5.80.0",
48
47
  "mocha": "11.7.5",
49
48
  "nodemon": "3.1.11",
50
- "rimraf": "6.1.2",
51
49
  "ts-node": "10.9.2",
52
50
  "typescript": "5.9.3"
53
51
  },
@@ -59,14 +57,12 @@
59
57
  ],
60
58
  "scripts": {
61
59
  "build": "tsc",
62
- "reset": "pnpm i @public-ui/components@1.1.7",
63
60
  "format": "prettier -c src",
64
61
  "lint": "pnpm lint:eslint && pnpm lint:tsc",
65
62
  "lint:eslint": "eslint src",
66
63
  "lint:tsc": "tsc --noemit",
67
64
  "pretest:unit": "pnpm build",
68
- "restart": "pnpm reset && pnpm start",
69
- "start": "rimraf test && cpy \"../../samples/react/src/components\" test/src && cpy \"../../samples/react/public/*.html\" test/ && ts-node src/index.ts migrate --ignore-uncommitted-changes --test-tasks test",
65
+ "start": "ts-node src/index.ts migrate --ignore-uncommitted-changes --overwrite-current-version=2.2.19-rc.0 --overwrite-target-version=4.0.0 --test-tasks ../../../test-migration",
70
66
  "test:unit": "cross-env TS_NODE_PROJECT=tsconfig.test.json mocha --require ts-node/register test/**/*.ts --no-experimental-strip-types",
71
67
  "unused": "knip",
72
68
  "watch": "nodemon --ignore package.json src/index.ts migrate --ignore-uncommitted-changes --test-tasks test"