@mriekki/project-forge 4.6.0 → 4.8.0

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.
package/src/cli.mjs CHANGED
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
4
  import readline from 'node:readline/promises';
5
5
  import { stdin as input, stdout as output } from 'node:process';
6
+ import { applyBootstrapMigrations, planBootstrapMigrations } from './bootstrap-migrations.mjs';
6
7
  import { applyProfile, checkProfile, loadProjectForgeBaseline, planProfile } from './engine.mjs';
7
8
  import { loadPackageData } from './package-data.mjs';
8
9
 
@@ -43,15 +44,20 @@ function inferConsumer(root) {
43
44
  return path.basename(root);
44
45
  }
45
46
 
46
- function printPlan(plan) {
47
+ function printPlan(plan, bootstrapMigrations = { rows: [], unsafe: [] }) {
47
48
  console.log(`Project Forge profile: ${plan.profileName}`);
49
+ if (bootstrapMigrations.rows.length) {
50
+ console.log('\nBOOTSTRAP MIGRATIONS');
51
+ for (const row of bootstrapMigrations.rows) console.log(`${row.state.padEnd(30)} ${row.path}`);
52
+ }
48
53
  for (const component of plan.plans) {
49
54
  console.log(`\n${component.componentId.toUpperCase()} ${component.component.commit}`);
50
55
  for (const row of component.rows) console.log(`${row.state.padEnd(20)} ${row.path}`);
51
56
  for (const retired of component.retired) console.log(`${retired.state.padEnd(20)} ${retired.path}`);
52
57
  }
53
- console.log(`\nUnsafe drift/conflicts: ${plan.unsafe.length}`);
54
- for (const item of plan.unsafe) console.log(` - ${item.component}: ${item.state}: ${item.path}`);
58
+ const unsafe = [...bootstrapMigrations.unsafe, ...plan.unsafe];
59
+ console.log(`\nUnsafe drift/conflicts: ${unsafe.length}`);
60
+ for (const item of unsafe) console.log(` - ${item.owner ?? item.component}: ${item.state}: ${item.path}${item.detail ? ` — ${item.detail}` : ''}`);
55
61
  }
56
62
 
57
63
  async function shouldApply(options) {
@@ -113,13 +119,17 @@ export async function run(argv = process.argv.slice(2), packageData = loadPackag
113
119
 
114
120
  if (command === 'check') {
115
121
  const result = checkProfile(packageData, profile, root);
122
+ const bootstrapMigrations = planBootstrapMigrations(packageData, root);
123
+ const bootstrapCurrent = bootstrapMigrations.rows.length === 0 && bootstrapMigrations.unsafe.length === 0;
116
124
  console.log(`Project Forge package baseline current: ${result.projectForgeCurrent ? 'yes' : 'no'}`);
125
+ console.log(`Bootstrap generated verifier: ${bootstrapCurrent ? 'PASS' : 'STALE OR UNSUPPORTED'}`);
117
126
  for (const component of result.plan.plans) {
118
127
  const clean = component.metadataCurrent && component.rows.every((row) => row.state === 'SAME') && component.retired.length === 0;
119
128
  console.log(`${component.componentId}: ${clean ? 'PASS' : 'STALE OR DRIFTED'}`);
120
129
  }
121
- console.log(`Project Forge check: ${result.clean ? 'PASS' : 'STALE OR DRIFTED'}`);
122
- return result.clean ? 0 : 1;
130
+ const clean = result.clean && bootstrapCurrent;
131
+ console.log(`Project Forge check: ${clean ? 'PASS' : 'STALE OR DRIFTED'}`);
132
+ return clean ? 0 : 1;
123
133
  }
124
134
 
125
135
  if (!['install', 'update'].includes(command)) throw new Error(`Unknown command: ${command}`);
@@ -127,10 +137,11 @@ export async function run(argv = process.argv.slice(2), packageData = loadPackag
127
137
  if (command === 'update' && !Object.keys(installed).length) throw new Error('PROJECT_FORGE_BASELINE.json is missing; use install first');
128
138
  if (installed.profile && installed.profile !== profile) throw new Error(`Installed Project Forge profile is ${installed.profile}; refusing implicit profile change to ${profile}`);
129
139
 
140
+ const bootstrapMigrations = planBootstrapMigrations(packageData, root);
130
141
  const plan = planProfile(packageData, profile, root, options.acceptedRetired);
131
- printPlan(plan);
132
- if (plan.unsafe.length) {
133
- console.log('\nRefusing apply. Resolve drift/conflicts and preview again. No managed files were changed.');
142
+ printPlan(plan, bootstrapMigrations);
143
+ if (plan.unsafe.length || bootstrapMigrations.unsafe.length) {
144
+ console.log('\nRefusing apply. Resolve drift/conflicts or the unsupported Bootstrap migration and preview again. No managed files were changed.');
134
145
  return 2;
135
146
  }
136
147
  const apply = await shouldApply(options);
@@ -144,6 +155,7 @@ export async function run(argv = process.argv.slice(2), packageData = loadPackag
144
155
  if (baselineLabels.length > 1) throw new Error(`Component baselines disagree on consumer baseline: ${baselineLabels.join(', ')}`);
145
156
  const consumer = options.consumer ?? installed.consumer ?? baselineConsumers[0] ?? inferConsumer(root);
146
157
  const consumerBaseline = options.consumerBaseline ?? installed.consumerBaseline ?? baselineLabels[0] ?? `${packageData.pkg.name}@${packageData.pkg.version}`;
158
+ applyBootstrapMigrations(packageData, bootstrapMigrations, root);
147
159
  applyProfile(packageData, plan, root, consumer, consumerBaseline);
148
160
  console.log(`\nProject Forge ${command}: PASS (${packageData.pkg.name}@${packageData.pkg.version})`);
149
161
  return 0;