changeledger 0.5.0 → 0.6.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/README.md +15 -2
- package/bin/changeledger.mjs +29 -2
- package/package.json +1 -1
- package/src/commands/check.mjs +10 -0
- package/src/commands/register.mjs +9 -1
- package/src/commands/view.mjs +4 -0
- package/src/config-migration.mjs +213 -0
- package/src/viewer/domain.mjs +280 -0
- package/src/viewer/public/api.js +28 -0
- package/src/viewer/public/app.js +529 -29
- package/src/viewer/public/index.html +2 -0
- package/src/viewer/public/styles.css +283 -0
- package/src/viewer/server/router.mjs +40 -14
- package/templates/config.yml +4 -0
- package/templates/contract/core.md +7 -0
- package/templates/contract/release.md +10 -0
|
@@ -1262,3 +1262,286 @@ body {
|
|
|
1262
1262
|
padding: 8px 8px;
|
|
1263
1263
|
}
|
|
1264
1264
|
}
|
|
1265
|
+
|
|
1266
|
+
/* config editor tabs and form (113924) */
|
|
1267
|
+
.config-section {
|
|
1268
|
+
border-bottom: 1px solid var(--line);
|
|
1269
|
+
}
|
|
1270
|
+
.config-tabs {
|
|
1271
|
+
display: flex;
|
|
1272
|
+
gap: 2px;
|
|
1273
|
+
padding: 12px 20px 0;
|
|
1274
|
+
border-bottom: 1px solid var(--line);
|
|
1275
|
+
}
|
|
1276
|
+
.config-tab {
|
|
1277
|
+
padding: 6px 14px;
|
|
1278
|
+
border: 1px solid transparent;
|
|
1279
|
+
border-bottom: none;
|
|
1280
|
+
border-radius: 6px 6px 0 0;
|
|
1281
|
+
background: transparent;
|
|
1282
|
+
color: var(--muted);
|
|
1283
|
+
cursor: pointer;
|
|
1284
|
+
font-size: 12px;
|
|
1285
|
+
font-weight: 500;
|
|
1286
|
+
position: relative;
|
|
1287
|
+
bottom: -1px;
|
|
1288
|
+
}
|
|
1289
|
+
.config-tab.active {
|
|
1290
|
+
background: var(--bg);
|
|
1291
|
+
border-color: var(--line);
|
|
1292
|
+
color: var(--text);
|
|
1293
|
+
}
|
|
1294
|
+
.config-form-structured {
|
|
1295
|
+
display: flex;
|
|
1296
|
+
flex-direction: column;
|
|
1297
|
+
gap: 0;
|
|
1298
|
+
}
|
|
1299
|
+
.config-group {
|
|
1300
|
+
margin: 0;
|
|
1301
|
+
padding: 14px 20px;
|
|
1302
|
+
border: none;
|
|
1303
|
+
border-bottom: 1px solid var(--line);
|
|
1304
|
+
}
|
|
1305
|
+
.config-group legend {
|
|
1306
|
+
font-size: 11px;
|
|
1307
|
+
font-weight: 600;
|
|
1308
|
+
color: var(--muted);
|
|
1309
|
+
padding: 0;
|
|
1310
|
+
margin-bottom: 8px;
|
|
1311
|
+
text-transform: uppercase;
|
|
1312
|
+
letter-spacing: 0.05em;
|
|
1313
|
+
}
|
|
1314
|
+
.config-group label {
|
|
1315
|
+
display: flex;
|
|
1316
|
+
flex-direction: column;
|
|
1317
|
+
gap: 4px;
|
|
1318
|
+
font-size: 12px;
|
|
1319
|
+
color: var(--muted);
|
|
1320
|
+
margin-bottom: 8px;
|
|
1321
|
+
}
|
|
1322
|
+
.config-group input[type="text"],
|
|
1323
|
+
.config-group input:not([type]) {
|
|
1324
|
+
padding: 6px 10px;
|
|
1325
|
+
border: 1px solid var(--line);
|
|
1326
|
+
border-radius: 6px;
|
|
1327
|
+
background: var(--bg);
|
|
1328
|
+
color: var(--text);
|
|
1329
|
+
font-size: 13px;
|
|
1330
|
+
}
|
|
1331
|
+
.config-group textarea {
|
|
1332
|
+
padding: 6px 10px;
|
|
1333
|
+
border: 1px solid var(--line);
|
|
1334
|
+
border-radius: 6px;
|
|
1335
|
+
background: var(--bg);
|
|
1336
|
+
color: var(--text);
|
|
1337
|
+
font-size: 12px;
|
|
1338
|
+
font-family: ui-monospace, monospace;
|
|
1339
|
+
resize: vertical;
|
|
1340
|
+
}
|
|
1341
|
+
.config-group label.config-checkbox {
|
|
1342
|
+
flex-direction: row;
|
|
1343
|
+
align-items: center;
|
|
1344
|
+
gap: 8px;
|
|
1345
|
+
color: var(--text);
|
|
1346
|
+
}
|
|
1347
|
+
.config-checkbox input[type="checkbox"] {
|
|
1348
|
+
width: 16px;
|
|
1349
|
+
height: 16px;
|
|
1350
|
+
accent-color: var(--accent);
|
|
1351
|
+
}
|
|
1352
|
+
.config-type-row {
|
|
1353
|
+
display: flex;
|
|
1354
|
+
align-items: center;
|
|
1355
|
+
gap: 16px;
|
|
1356
|
+
padding: 6px 0;
|
|
1357
|
+
margin: 0;
|
|
1358
|
+
border: 0;
|
|
1359
|
+
border-bottom: 1px solid var(--line);
|
|
1360
|
+
flex-wrap: wrap;
|
|
1361
|
+
}
|
|
1362
|
+
.config-type-row:last-child {
|
|
1363
|
+
border-bottom: none;
|
|
1364
|
+
}
|
|
1365
|
+
.config-group .config-type-row legend {
|
|
1366
|
+
min-width: 80px;
|
|
1367
|
+
font-size: 13px;
|
|
1368
|
+
color: var(--text);
|
|
1369
|
+
text-transform: none;
|
|
1370
|
+
letter-spacing: normal;
|
|
1371
|
+
margin: 0;
|
|
1372
|
+
}
|
|
1373
|
+
.config-type-row label:first-of-type {
|
|
1374
|
+
flex: 1 1 240px;
|
|
1375
|
+
}
|
|
1376
|
+
.config-type-row select {
|
|
1377
|
+
padding: 4px 8px;
|
|
1378
|
+
border: 1px solid var(--line);
|
|
1379
|
+
border-radius: 6px;
|
|
1380
|
+
background: var(--bg);
|
|
1381
|
+
color: var(--text);
|
|
1382
|
+
font-size: 12px;
|
|
1383
|
+
}
|
|
1384
|
+
.config-note {
|
|
1385
|
+
font-size: 11px;
|
|
1386
|
+
color: var(--muted);
|
|
1387
|
+
margin: 0 0 6px;
|
|
1388
|
+
}
|
|
1389
|
+
.config-readonly-note {
|
|
1390
|
+
font-style: italic;
|
|
1391
|
+
}
|
|
1392
|
+
.config-group-internal p {
|
|
1393
|
+
display: flex;
|
|
1394
|
+
gap: 12px;
|
|
1395
|
+
margin: 4px 0;
|
|
1396
|
+
font-size: 12px;
|
|
1397
|
+
}
|
|
1398
|
+
.config-readonly-label {
|
|
1399
|
+
color: var(--muted);
|
|
1400
|
+
font-family: ui-monospace, monospace;
|
|
1401
|
+
min-width: 120px;
|
|
1402
|
+
}
|
|
1403
|
+
.config-readonly-value {
|
|
1404
|
+
color: var(--text);
|
|
1405
|
+
}
|
|
1406
|
+
.config-migration-card {
|
|
1407
|
+
padding: 16px 20px;
|
|
1408
|
+
}
|
|
1409
|
+
.config-migration-card h3 {
|
|
1410
|
+
margin: 0 0 8px;
|
|
1411
|
+
font-size: 14px;
|
|
1412
|
+
color: #f7ab57;
|
|
1413
|
+
}
|
|
1414
|
+
.config-migration-preview {
|
|
1415
|
+
margin-top: 12px;
|
|
1416
|
+
}
|
|
1417
|
+
.config-migration-yaml {
|
|
1418
|
+
max-height: 300px;
|
|
1419
|
+
overflow: auto;
|
|
1420
|
+
padding: 10px;
|
|
1421
|
+
background: #0b0d11;
|
|
1422
|
+
border: 1px solid var(--line);
|
|
1423
|
+
border-radius: 6px;
|
|
1424
|
+
font-size: 11px;
|
|
1425
|
+
font-family: ui-monospace, monospace;
|
|
1426
|
+
color: #d9e1ea;
|
|
1427
|
+
margin: 8px 0;
|
|
1428
|
+
}
|
|
1429
|
+
.config-migration-ok {
|
|
1430
|
+
color: #56d364;
|
|
1431
|
+
font-size: 12px;
|
|
1432
|
+
}
|
|
1433
|
+
.config-future-schema p {
|
|
1434
|
+
padding: 0 20px;
|
|
1435
|
+
font-size: 12px;
|
|
1436
|
+
color: var(--muted);
|
|
1437
|
+
}
|
|
1438
|
+
.config-schema-badge {
|
|
1439
|
+
display: inline-block;
|
|
1440
|
+
margin: 12px 20px 4px;
|
|
1441
|
+
padding: 2px 8px;
|
|
1442
|
+
border-radius: 4px;
|
|
1443
|
+
background: rgba(248, 81, 73, 0.15);
|
|
1444
|
+
color: #ff8078;
|
|
1445
|
+
font-size: 11px;
|
|
1446
|
+
font-weight: 600;
|
|
1447
|
+
}
|
|
1448
|
+
.config-section-note {
|
|
1449
|
+
padding: 0 20px;
|
|
1450
|
+
font-size: 11px;
|
|
1451
|
+
color: var(--muted);
|
|
1452
|
+
font-style: italic;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
/* Native confirm dialog (cl-confirm-dialog) */
|
|
1456
|
+
.cl-confirm-dialog {
|
|
1457
|
+
margin: auto;
|
|
1458
|
+
padding: 28px 28px 24px;
|
|
1459
|
+
min-width: 320px;
|
|
1460
|
+
max-width: 440px;
|
|
1461
|
+
background: var(--panel);
|
|
1462
|
+
border: 1px solid var(--line);
|
|
1463
|
+
border-radius: 14px;
|
|
1464
|
+
box-shadow: 0 24px 72px rgba(0, 0, 0, 0.55);
|
|
1465
|
+
color: var(--text);
|
|
1466
|
+
}
|
|
1467
|
+
.cl-confirm-dialog::backdrop {
|
|
1468
|
+
background: rgba(4, 6, 10, 0.75);
|
|
1469
|
+
backdrop-filter: blur(4px);
|
|
1470
|
+
}
|
|
1471
|
+
.cl-confirm-message {
|
|
1472
|
+
margin: 0 0 20px;
|
|
1473
|
+
font-size: 14px;
|
|
1474
|
+
line-height: 1.55;
|
|
1475
|
+
color: var(--text);
|
|
1476
|
+
}
|
|
1477
|
+
.cl-confirm-actions {
|
|
1478
|
+
display: flex;
|
|
1479
|
+
justify-content: flex-end;
|
|
1480
|
+
gap: 8px;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
/* Toast notifications */
|
|
1484
|
+
.toast-container {
|
|
1485
|
+
position: fixed;
|
|
1486
|
+
bottom: 24px;
|
|
1487
|
+
right: 24px;
|
|
1488
|
+
z-index: 100;
|
|
1489
|
+
display: flex;
|
|
1490
|
+
flex-direction: column;
|
|
1491
|
+
gap: 8px;
|
|
1492
|
+
pointer-events: none;
|
|
1493
|
+
}
|
|
1494
|
+
.toast {
|
|
1495
|
+
padding: 12px 16px;
|
|
1496
|
+
border-radius: 10px;
|
|
1497
|
+
font-size: 13px;
|
|
1498
|
+
font-weight: 500;
|
|
1499
|
+
max-width: 340px;
|
|
1500
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45);
|
|
1501
|
+
animation: toast-in 0.2s ease;
|
|
1502
|
+
pointer-events: auto;
|
|
1503
|
+
}
|
|
1504
|
+
.toast-error {
|
|
1505
|
+
background: #2a1215;
|
|
1506
|
+
border: 1px solid rgba(248, 81, 73, 0.45);
|
|
1507
|
+
color: #ff8078;
|
|
1508
|
+
}
|
|
1509
|
+
.toast-info {
|
|
1510
|
+
background: var(--panel);
|
|
1511
|
+
border: 1px solid var(--line);
|
|
1512
|
+
color: var(--text);
|
|
1513
|
+
}
|
|
1514
|
+
@keyframes toast-in {
|
|
1515
|
+
from {
|
|
1516
|
+
opacity: 0;
|
|
1517
|
+
transform: translateY(8px);
|
|
1518
|
+
}
|
|
1519
|
+
to {
|
|
1520
|
+
opacity: 1;
|
|
1521
|
+
transform: translateY(0);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
/* Prompt input inside confirm dialog */
|
|
1526
|
+
.cl-prompt-label {
|
|
1527
|
+
display: block;
|
|
1528
|
+
margin-bottom: 6px;
|
|
1529
|
+
color: var(--muted);
|
|
1530
|
+
font-size: 12px;
|
|
1531
|
+
}
|
|
1532
|
+
.cl-prompt-input {
|
|
1533
|
+
display: block;
|
|
1534
|
+
width: 100%;
|
|
1535
|
+
margin: 0 0 16px;
|
|
1536
|
+
padding: 8px 12px;
|
|
1537
|
+
border: 1px solid var(--line);
|
|
1538
|
+
border-radius: 8px;
|
|
1539
|
+
background: var(--bg);
|
|
1540
|
+
color: var(--text);
|
|
1541
|
+
font-size: 13px;
|
|
1542
|
+
box-sizing: border-box;
|
|
1543
|
+
}
|
|
1544
|
+
.cl-prompt-input:focus {
|
|
1545
|
+
outline: none;
|
|
1546
|
+
border-color: var(--accent);
|
|
1547
|
+
}
|
|
@@ -5,8 +5,12 @@ import { gitRefs } from '../../git.mjs';
|
|
|
5
5
|
import { publicDir } from '../../paths.mjs';
|
|
6
6
|
import { loadRepoAsync } from '../../repo.mjs';
|
|
7
7
|
import {
|
|
8
|
+
applyConfigMigration,
|
|
8
9
|
changeStatus,
|
|
10
|
+
patchProjectConfig,
|
|
11
|
+
previewConfigMigration,
|
|
9
12
|
readProjectConfig,
|
|
13
|
+
readProjectConfigStructured,
|
|
10
14
|
repairProjectPath,
|
|
11
15
|
resolveProjects,
|
|
12
16
|
saveProjectConfig,
|
|
@@ -87,12 +91,15 @@ export function createRequestListener(cwd, localOnly, token) {
|
|
|
87
91
|
const route = url.pathname;
|
|
88
92
|
const params = url.searchParams;
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
const WRITE_ROUTES = new Set([
|
|
95
|
+
'/api/status',
|
|
96
|
+
'/api/project-config',
|
|
97
|
+
'/api/project-config-patch',
|
|
98
|
+
'/api/project-config-migrate-apply',
|
|
99
|
+
'/api/project-path',
|
|
100
|
+
'/api/project-remove',
|
|
101
|
+
]);
|
|
102
|
+
if (req.method === 'POST' && WRITE_ROUTES.has(route)) {
|
|
96
103
|
if (!isAuthorizedWrite(req, token)) {
|
|
97
104
|
send(res, 403, MIME['.json'], JSON.stringify({ error: 'unauthorized write' }));
|
|
98
105
|
req.destroy();
|
|
@@ -121,14 +128,17 @@ export function createRequestListener(cwd, localOnly, token) {
|
|
|
121
128
|
}
|
|
122
129
|
const { projects } = resolveProjects(cwd, localOnly);
|
|
123
130
|
const options = { localOnly };
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
let result;
|
|
132
|
+
if (route === '/api/status') result = changeStatus(projects, payload);
|
|
133
|
+
else if (route === '/api/project-config')
|
|
134
|
+
result = saveProjectConfig(projects, payload, options);
|
|
135
|
+
else if (route === '/api/project-config-patch')
|
|
136
|
+
result = patchProjectConfig(projects, payload);
|
|
137
|
+
else if (route === '/api/project-config-migrate-apply')
|
|
138
|
+
result = applyConfigMigration(projects, payload);
|
|
139
|
+
else if (route === '/api/project-path')
|
|
140
|
+
result = repairProjectPath(projects, payload, options);
|
|
141
|
+
else result = unregisterProject(projects, payload, options);
|
|
132
142
|
const { code, body } = result;
|
|
133
143
|
send(res, code, MIME['.json'], JSON.stringify(body));
|
|
134
144
|
} catch (error) {
|
|
@@ -154,6 +164,22 @@ export function createRequestListener(cwd, localOnly, token) {
|
|
|
154
164
|
send(res, code, MIME['.json'], JSON.stringify(body));
|
|
155
165
|
return;
|
|
156
166
|
}
|
|
167
|
+
if (route === '/api/project-config-structured') {
|
|
168
|
+
const { projects } = resolveProjects(cwd, localOnly);
|
|
169
|
+
const { code, body } = readProjectConfigStructured(projects, params.get('project'));
|
|
170
|
+
send(res, code, MIME['.json'], JSON.stringify(body));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (route === '/api/project-config-migrate-preview') {
|
|
174
|
+
const { projects } = resolveProjects(cwd, localOnly);
|
|
175
|
+
const { code, body } = previewConfigMigration(
|
|
176
|
+
projects,
|
|
177
|
+
params.get('project'),
|
|
178
|
+
params.get('revision'),
|
|
179
|
+
);
|
|
180
|
+
send(res, code, MIME['.json'], JSON.stringify(body));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
157
183
|
if (route === '/api/git') {
|
|
158
184
|
const rawId = params.get('id');
|
|
159
185
|
if (rawId && !/^[0-9]{8}-[0-9]{6}$/.test(rawId)) {
|
package/templates/config.yml
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# ChangeLedger — repo configuration
|
|
2
2
|
# Repo specifics: documentation language, change types and active stages per type.
|
|
3
3
|
|
|
4
|
+
# Schema version — used to detect and migrate outdated configurations.
|
|
5
|
+
# Run `changeledger config migrate --dry-run` to inspect available migrations.
|
|
6
|
+
schema_version: 1
|
|
7
|
+
|
|
4
8
|
# Language for generated documentation CONTENT. Structure (headings, keys, enums)
|
|
5
9
|
# is always English. See `changeledger context spec`.
|
|
6
10
|
language: en
|
|
@@ -60,5 +60,12 @@ Valid modes: implement, review, spec, release.
|
|
|
60
60
|
- `changeledger context release`: plan portable delivery metadata.
|
|
61
61
|
- `changeledger context <change-id>`: infer the correct context from lifecycle.
|
|
62
62
|
|
|
63
|
+
## Operational discovery
|
|
64
|
+
|
|
65
|
+
Prefer structured CLI queries before scanning files:
|
|
66
|
+
|
|
67
|
+
- `changeledger list --status approved`: find approved changes ready to implement.
|
|
68
|
+
- `changeledger graduate --pending`: find accepted changes whose graduation decision is unresolved.
|
|
69
|
+
|
|
63
70
|
Run `changeledger help` or `changeledger <command> --help` for exact CLI syntax.
|
|
64
71
|
Structure is always English; narrative content follows `.changeledger/config.yml`.
|
|
@@ -13,3 +13,13 @@ override its type with `release_impact` (`none|patch|minor|major`).
|
|
|
13
13
|
ChangeLedger owns portable membership and SemVer. The operating agent owns
|
|
14
14
|
stack-specific version files, project gates, commits, tags and publishing.
|
|
15
15
|
Never infer that every ChangeLedger repository uses npm or GitHub.
|
|
16
|
+
|
|
17
|
+
## Routine release preparation
|
|
18
|
+
|
|
19
|
+
Routine release preparation is operational work.
|
|
20
|
+
Version bumps, release manifests, quality gates, packaging, commits, tags and publishing do not require a ChangeLedger change by themselves.
|
|
21
|
+
Do not create a change only to group those routine steps.
|
|
22
|
+
|
|
23
|
+
If preparation reveals a functional fix, release-workflow change or persistent documentation, capture it as a separate change.
|
|
24
|
+
Complete that change and rerun `changeledger release plan` before `changeledger release record <version>`.
|
|
25
|
+
Keep this workflow stack-agnostic; do not assume npm, GitHub or specific manifest filenames.
|