@ncoderz/awa 1.1.0 → 1.3.1
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/dist/chunk-ALEGCDAZ.js +625 -0
- package/dist/chunk-ALEGCDAZ.js.map +1 -0
- package/dist/config-LWUO5EXL.js +10 -0
- package/dist/config-LWUO5EXL.js.map +1 -0
- package/dist/index.js +295 -666
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/awa/.agent/skills/awa-usage/SKILL.md +3 -0
- package/templates/awa/.agents/skills/awa-usage/SKILL.md +3 -0
- package/templates/awa/.awa/.agent/schemas/REQ.schema.yaml +2 -2
- package/templates/awa/.claude/skills/awa-usage/SKILL.md +3 -0
- package/templates/awa/.gemini/skills/awa-usage/SKILL.md +3 -0
- package/templates/awa/.github/skills/awa-usage/SKILL.md +8 -0
- package/templates/awa/.kilocode/skills/awa-usage/SKILL.md +3 -0
- package/templates/awa/.opencode/skills/awa-usage/SKILL.md +3 -0
- package/templates/awa/.qwen/skills/awa-usage/SKILL.md +3 -0
- package/templates/awa/.roo/skills/awa-usage/SKILL.md +3 -0
- package/templates/awa/.windsurf/skills/awa-usage/SKILL.md +3 -0
- package/templates/awa/_partials/_skill.awa-usage.md +6 -0
- package/templates/awa/_partials/awa.usage.md +265 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ConfigError,
|
|
4
|
+
DiffError,
|
|
5
|
+
GenerationError,
|
|
6
|
+
TemplateError,
|
|
7
|
+
configLoader,
|
|
8
|
+
deleteFile,
|
|
9
|
+
ensureDir,
|
|
10
|
+
getCacheDir,
|
|
11
|
+
getTemplateDir,
|
|
12
|
+
logger,
|
|
13
|
+
pathExists,
|
|
14
|
+
readBinaryFile,
|
|
15
|
+
readTextFile,
|
|
16
|
+
rmDir,
|
|
17
|
+
walkDirectory,
|
|
18
|
+
writeTextFile
|
|
19
|
+
} from "./chunk-ALEGCDAZ.js";
|
|
2
20
|
|
|
3
21
|
// src/cli/index.ts
|
|
4
22
|
import { Command } from "commander";
|
|
@@ -6,7 +24,7 @@ import { Command } from "commander";
|
|
|
6
24
|
// src/_generated/package_info.ts
|
|
7
25
|
var PACKAGE_INFO = {
|
|
8
26
|
"name": "@ncoderz/awa",
|
|
9
|
-
"version": "1.1
|
|
27
|
+
"version": "1.3.1",
|
|
10
28
|
"author": "Richard Sewell <richard.sewell@ncoderz.com>",
|
|
11
29
|
"license": "MIT",
|
|
12
30
|
"description": "awa is an Agent Workflow for AIs. It is also a CLI tool to powerfully manage agent workflow files using templates."
|
|
@@ -71,6 +89,38 @@ function checkCodeAgainstSpec(markers, specs, config) {
|
|
|
71
89
|
});
|
|
72
90
|
}
|
|
73
91
|
}
|
|
92
|
+
const implementedComponents = new Set(
|
|
93
|
+
markers.markers.filter((m) => m.type === "component").map((m) => m.id)
|
|
94
|
+
);
|
|
95
|
+
for (const componentName of specs.componentNames) {
|
|
96
|
+
if (!implementedComponents.has(componentName)) {
|
|
97
|
+
const loc = specs.idLocations.get(componentName);
|
|
98
|
+
const specFile = loc ? void 0 : specs.specFiles.find((sf) => sf.componentNames.includes(componentName));
|
|
99
|
+
findings.push({
|
|
100
|
+
severity: "warning",
|
|
101
|
+
code: "uncovered-component",
|
|
102
|
+
message: `Component '${componentName}' has no @awa-component reference`,
|
|
103
|
+
filePath: loc?.filePath ?? specFile?.filePath,
|
|
104
|
+
line: loc?.line,
|
|
105
|
+
id: componentName
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const implementedIds = new Set(markers.markers.filter((m) => m.type === "impl").map((m) => m.id));
|
|
110
|
+
for (const acId of specs.acIds) {
|
|
111
|
+
if (!implementedIds.has(acId)) {
|
|
112
|
+
const loc = specs.idLocations.get(acId);
|
|
113
|
+
const specFile = loc ? void 0 : specs.specFiles.find((sf) => sf.acIds.includes(acId));
|
|
114
|
+
findings.push({
|
|
115
|
+
severity: "warning",
|
|
116
|
+
code: "unimplemented-ac",
|
|
117
|
+
message: `Acceptance criterion '${acId}' has no @awa-impl reference`,
|
|
118
|
+
filePath: loc?.filePath ?? specFile?.filePath,
|
|
119
|
+
line: loc?.line,
|
|
120
|
+
id: acId
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
74
124
|
return { findings };
|
|
75
125
|
}
|
|
76
126
|
|
|
@@ -104,13 +154,13 @@ var MARKER_TYPE_MAP = {
|
|
|
104
154
|
"@awa-test": "test",
|
|
105
155
|
"@awa-component": "component"
|
|
106
156
|
};
|
|
107
|
-
var IGNORE_FILE_RE =
|
|
157
|
+
var IGNORE_FILE_RE = new RegExp("@awa-ignore-file\\b");
|
|
108
158
|
var IGNORE_NEXT_LINE_RE = /@awa-ignore-next-line\b/;
|
|
109
159
|
var IGNORE_LINE_RE = /@awa-ignore\b/;
|
|
110
160
|
var IGNORE_START_RE = /@awa-ignore-start\b/;
|
|
111
161
|
var IGNORE_END_RE = /@awa-ignore-end\b/;
|
|
112
162
|
async function scanMarkers(config) {
|
|
113
|
-
const files = await collectCodeFiles(config.codeGlobs, config.
|
|
163
|
+
const files = await collectCodeFiles(config.codeGlobs, config.codeIgnore);
|
|
114
164
|
const markers = [];
|
|
115
165
|
const findings = [];
|
|
116
166
|
for (const filePath of files) {
|
|
@@ -992,7 +1042,7 @@ function collectAllCodeBlocks(section) {
|
|
|
992
1042
|
import { readFile as readFile4 } from "fs/promises";
|
|
993
1043
|
import { basename } from "path";
|
|
994
1044
|
async function parseSpecs(config) {
|
|
995
|
-
const files = await collectSpecFiles(config.specGlobs, config.
|
|
1045
|
+
const files = await collectSpecFiles(config.specGlobs, config.specIgnore);
|
|
996
1046
|
const specFiles = [];
|
|
997
1047
|
const requirementIds = /* @__PURE__ */ new Set();
|
|
998
1048
|
const acIds = /* @__PURE__ */ new Set();
|
|
@@ -1031,7 +1081,7 @@ async function parseSpecFile(filePath, crossRefPatterns) {
|
|
|
1031
1081
|
const crossRefs = [];
|
|
1032
1082
|
const idLocations = /* @__PURE__ */ new Map();
|
|
1033
1083
|
const reqIdRegex = /^###\s+([A-Z][A-Z0-9]*-\d+(?:\.\d+)?)\s*:/;
|
|
1034
|
-
const acIdRegex = /^-\s
|
|
1084
|
+
const acIdRegex = /^-\s+(?:\[[ x]\]\s+)?([A-Z][A-Z0-9]*-\d+(?:\.\d+)?_AC-\d+)\s/;
|
|
1035
1085
|
const propIdRegex = /^-\s+([A-Z][A-Z0-9]*_P-\d+)\s/;
|
|
1036
1086
|
const componentRegex = /^###\s+([A-Z][A-Z0-9]*-[A-Za-z][A-Za-z0-9]*(?:[A-Z][a-z0-9]*)*)\s*$/;
|
|
1037
1087
|
for (const [i, line] of lines.entries()) {
|
|
@@ -1156,9 +1206,30 @@ function checkSpecAgainstSpec(specs, markers, config) {
|
|
|
1156
1206
|
|
|
1157
1207
|
// src/core/check/types.ts
|
|
1158
1208
|
var DEFAULT_CHECK_CONFIG = {
|
|
1159
|
-
specGlobs: [
|
|
1160
|
-
|
|
1161
|
-
|
|
1209
|
+
specGlobs: [
|
|
1210
|
+
".awa/specs/ARCHITECTURE.md",
|
|
1211
|
+
".awa/specs/FEAT-*.md",
|
|
1212
|
+
".awa/specs/REQ-*.md",
|
|
1213
|
+
".awa/specs/DESIGN-*.md",
|
|
1214
|
+
".awa/specs/EXAMPLES-*.md",
|
|
1215
|
+
".awa/specs/API-*.tsp",
|
|
1216
|
+
".awa/tasks/TASK-*.md",
|
|
1217
|
+
".awa/plans/PLAN-*.md",
|
|
1218
|
+
".awa/align/ALIGN-*.md"
|
|
1219
|
+
],
|
|
1220
|
+
codeGlobs: [
|
|
1221
|
+
"**/*.{ts,js,tsx,jsx,mts,mjs,cjs,py,go,rs,java,kt,kts,cs,c,h,cpp,cc,cxx,hpp,hxx,swift,rb,php,scala,ex,exs,dart,lua,zig}"
|
|
1222
|
+
],
|
|
1223
|
+
specIgnore: [],
|
|
1224
|
+
codeIgnore: [
|
|
1225
|
+
"node_modules/**",
|
|
1226
|
+
"dist/**",
|
|
1227
|
+
"vendor/**",
|
|
1228
|
+
"target/**",
|
|
1229
|
+
"build/**",
|
|
1230
|
+
"out/**",
|
|
1231
|
+
".awa/**"
|
|
1232
|
+
],
|
|
1162
1233
|
ignoreMarkers: [],
|
|
1163
1234
|
markers: ["@awa-impl", "@awa-test", "@awa-component"],
|
|
1164
1235
|
idPattern: "([A-Z][A-Z0-9]*-\\d+(?:\\.\\d+)?(?:_AC-\\d+)?|[A-Z][A-Z0-9]*_P-\\d+)",
|
|
@@ -1170,576 +1241,6 @@ var DEFAULT_CHECK_CONFIG = {
|
|
|
1170
1241
|
specOnly: false
|
|
1171
1242
|
};
|
|
1172
1243
|
|
|
1173
|
-
// src/core/config.ts
|
|
1174
|
-
import { parse } from "smol-toml";
|
|
1175
|
-
|
|
1176
|
-
// src/types/index.ts
|
|
1177
|
-
var DiffError = class extends Error {
|
|
1178
|
-
constructor(message) {
|
|
1179
|
-
super(message);
|
|
1180
|
-
this.name = "DiffError";
|
|
1181
|
-
}
|
|
1182
|
-
};
|
|
1183
|
-
var ConfigError = class extends Error {
|
|
1184
|
-
constructor(message, code, filePath) {
|
|
1185
|
-
super(message);
|
|
1186
|
-
this.code = code;
|
|
1187
|
-
this.filePath = filePath;
|
|
1188
|
-
this.name = "ConfigError";
|
|
1189
|
-
}
|
|
1190
|
-
};
|
|
1191
|
-
var TemplateError = class extends Error {
|
|
1192
|
-
constructor(message, code, source) {
|
|
1193
|
-
super(message);
|
|
1194
|
-
this.code = code;
|
|
1195
|
-
this.source = source;
|
|
1196
|
-
this.name = "TemplateError";
|
|
1197
|
-
}
|
|
1198
|
-
};
|
|
1199
|
-
var GenerationError = class extends Error {
|
|
1200
|
-
constructor(message, code) {
|
|
1201
|
-
super(message);
|
|
1202
|
-
this.code = code;
|
|
1203
|
-
this.name = "GenerationError";
|
|
1204
|
-
}
|
|
1205
|
-
};
|
|
1206
|
-
|
|
1207
|
-
// src/utils/fs.ts
|
|
1208
|
-
import { mkdir, readdir, readFile as readFile5, rm, stat, writeFile } from "fs/promises";
|
|
1209
|
-
import { homedir } from "os";
|
|
1210
|
-
import { dirname, join as join2 } from "path";
|
|
1211
|
-
import { fileURLToPath } from "url";
|
|
1212
|
-
async function ensureDir(dirPath) {
|
|
1213
|
-
await mkdir(dirPath, { recursive: true });
|
|
1214
|
-
}
|
|
1215
|
-
async function pathExists(path) {
|
|
1216
|
-
try {
|
|
1217
|
-
await stat(path);
|
|
1218
|
-
return true;
|
|
1219
|
-
} catch {
|
|
1220
|
-
return false;
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
async function readTextFile(path) {
|
|
1224
|
-
return readFile5(path, "utf-8");
|
|
1225
|
-
}
|
|
1226
|
-
async function readBinaryFile(path) {
|
|
1227
|
-
return readFile5(path);
|
|
1228
|
-
}
|
|
1229
|
-
async function writeTextFile(path, content) {
|
|
1230
|
-
await ensureDir(dirname(path));
|
|
1231
|
-
await writeFile(path, content, "utf-8");
|
|
1232
|
-
}
|
|
1233
|
-
async function* walkDirectory(dir) {
|
|
1234
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
1235
|
-
for (const entry of entries) {
|
|
1236
|
-
const fullPath = join2(dir, entry.name);
|
|
1237
|
-
if (entry.isDirectory()) {
|
|
1238
|
-
if (entry.name.startsWith("_")) {
|
|
1239
|
-
continue;
|
|
1240
|
-
}
|
|
1241
|
-
yield* walkDirectory(fullPath);
|
|
1242
|
-
} else if (entry.isFile()) {
|
|
1243
|
-
if (entry.name.startsWith("_")) {
|
|
1244
|
-
continue;
|
|
1245
|
-
}
|
|
1246
|
-
yield fullPath;
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
function getCacheDir() {
|
|
1251
|
-
return join2(homedir(), ".cache", "awa", "templates");
|
|
1252
|
-
}
|
|
1253
|
-
function getTemplateDir() {
|
|
1254
|
-
const currentFile = fileURLToPath(import.meta.url);
|
|
1255
|
-
const currentDir = dirname(currentFile);
|
|
1256
|
-
if (currentDir.includes("/dist")) {
|
|
1257
|
-
return join2(dirname(currentDir), "templates");
|
|
1258
|
-
}
|
|
1259
|
-
return join2(currentDir, "..", "..", "templates");
|
|
1260
|
-
}
|
|
1261
|
-
async function rmDir(dirPath) {
|
|
1262
|
-
await rm(dirPath, { recursive: true, force: true });
|
|
1263
|
-
}
|
|
1264
|
-
async function deleteFile(filePath) {
|
|
1265
|
-
await rm(filePath, { force: true });
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
// src/utils/logger.ts
|
|
1269
|
-
import chalk2 from "chalk";
|
|
1270
|
-
var Logger = class {
|
|
1271
|
-
info(message) {
|
|
1272
|
-
console.log(chalk2.blue("\u2139"), message);
|
|
1273
|
-
}
|
|
1274
|
-
success(message) {
|
|
1275
|
-
console.log(chalk2.green("\u2714"), message);
|
|
1276
|
-
}
|
|
1277
|
-
warn(message) {
|
|
1278
|
-
console.warn(chalk2.yellow("\u26A0"), message);
|
|
1279
|
-
}
|
|
1280
|
-
error(message) {
|
|
1281
|
-
console.error(chalk2.red("\u2716"), message);
|
|
1282
|
-
}
|
|
1283
|
-
fileAction(action) {
|
|
1284
|
-
const { type, outputPath } = action;
|
|
1285
|
-
switch (type) {
|
|
1286
|
-
case "create":
|
|
1287
|
-
console.log(chalk2.green(" + "), chalk2.dim(outputPath));
|
|
1288
|
-
break;
|
|
1289
|
-
case "overwrite":
|
|
1290
|
-
console.log(chalk2.yellow(" ~ "), chalk2.dim(outputPath));
|
|
1291
|
-
break;
|
|
1292
|
-
case "skip-user":
|
|
1293
|
-
console.log(chalk2.blue(" - "), chalk2.dim(outputPath), chalk2.dim("(skipped)"));
|
|
1294
|
-
break;
|
|
1295
|
-
case "skip-empty":
|
|
1296
|
-
console.log(chalk2.dim(" \xB7 "), chalk2.dim(outputPath), chalk2.dim("(empty)"));
|
|
1297
|
-
break;
|
|
1298
|
-
case "skip-equal":
|
|
1299
|
-
console.log(chalk2.dim(" = "), chalk2.dim(outputPath), chalk2.dim("(unchanged)"));
|
|
1300
|
-
break;
|
|
1301
|
-
case "delete":
|
|
1302
|
-
console.log(chalk2.red(" \u2716 "), chalk2.dim(outputPath), chalk2.red("(deleted)"));
|
|
1303
|
-
break;
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
// @awa-impl: GEN-9_AC-1, GEN-9_AC-2, GEN-9_AC-3, GEN-9_AC-4, GEN-9_AC-5, GEN-9_AC-6
|
|
1307
|
-
summary(result) {
|
|
1308
|
-
console.log("");
|
|
1309
|
-
console.log(chalk2.bold("Summary:"));
|
|
1310
|
-
if (result.created === 0 && result.overwritten === 0 && result.deleted === 0) {
|
|
1311
|
-
console.log(chalk2.yellow(" \u26A0 No files were created, overwritten, or deleted"));
|
|
1312
|
-
}
|
|
1313
|
-
if (result.created > 0) {
|
|
1314
|
-
console.log(chalk2.green(` Created: ${result.created}`));
|
|
1315
|
-
}
|
|
1316
|
-
if (result.overwritten > 0) {
|
|
1317
|
-
console.log(chalk2.yellow(` Overwritten: ${result.overwritten}`));
|
|
1318
|
-
}
|
|
1319
|
-
if (result.deleted > 0) {
|
|
1320
|
-
console.log(chalk2.red(` Deleted: ${result.deleted}`));
|
|
1321
|
-
}
|
|
1322
|
-
if (result.skippedEqual > 0) {
|
|
1323
|
-
console.log(chalk2.dim(` Skipped (equal): ${result.skippedEqual}`));
|
|
1324
|
-
}
|
|
1325
|
-
if (result.skippedUser > 0) {
|
|
1326
|
-
console.log(chalk2.blue(` Skipped (user): ${result.skippedUser}`));
|
|
1327
|
-
}
|
|
1328
|
-
if (result.skippedEmpty > 0) {
|
|
1329
|
-
console.log(chalk2.dim(` Skipped (empty): ${result.skippedEmpty}`));
|
|
1330
|
-
}
|
|
1331
|
-
console.log("");
|
|
1332
|
-
}
|
|
1333
|
-
// @awa-impl: DIFF-4_AC-3
|
|
1334
|
-
diffLine(line, type) {
|
|
1335
|
-
switch (type) {
|
|
1336
|
-
case "add":
|
|
1337
|
-
console.log(chalk2.green(line));
|
|
1338
|
-
break;
|
|
1339
|
-
case "remove":
|
|
1340
|
-
console.log(chalk2.red(line));
|
|
1341
|
-
break;
|
|
1342
|
-
case "context":
|
|
1343
|
-
console.log(chalk2.dim(line));
|
|
1344
|
-
break;
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
// @awa-impl: DIFF-4_AC-4, DIFF-4_AC-5
|
|
1348
|
-
diffSummary(result) {
|
|
1349
|
-
console.log("");
|
|
1350
|
-
const filesCompared = result.identical + result.modified + result.newFiles + result.extraFiles + result.binaryDiffers + result.deleteListed;
|
|
1351
|
-
const differences = result.modified + result.newFiles + result.extraFiles + result.binaryDiffers + result.deleteListed;
|
|
1352
|
-
console.log(chalk2.bold(`${filesCompared} files compared, ${differences} differences`));
|
|
1353
|
-
if (!result.hasDifferences) {
|
|
1354
|
-
console.log(chalk2.green("\u2714 No differences found"));
|
|
1355
|
-
}
|
|
1356
|
-
console.log(chalk2.bold("Summary:"));
|
|
1357
|
-
console.log(chalk2.dim(` Identical: ${result.identical}`));
|
|
1358
|
-
if (result.modified > 0) {
|
|
1359
|
-
console.log(chalk2.yellow(` Modified: ${result.modified}`));
|
|
1360
|
-
}
|
|
1361
|
-
if (result.newFiles > 0) {
|
|
1362
|
-
console.log(chalk2.green(` New: ${result.newFiles}`));
|
|
1363
|
-
}
|
|
1364
|
-
if (result.extraFiles > 0) {
|
|
1365
|
-
console.log(chalk2.red(` Extra: ${result.extraFiles}`));
|
|
1366
|
-
}
|
|
1367
|
-
if (result.binaryDiffers > 0) {
|
|
1368
|
-
console.log(chalk2.red(` Binary differs: ${result.binaryDiffers}`));
|
|
1369
|
-
}
|
|
1370
|
-
if (result.deleteListed > 0) {
|
|
1371
|
-
console.log(chalk2.red(` Delete listed: ${result.deleteListed}`));
|
|
1372
|
-
}
|
|
1373
|
-
console.log("");
|
|
1374
|
-
}
|
|
1375
|
-
};
|
|
1376
|
-
var logger = new Logger();
|
|
1377
|
-
|
|
1378
|
-
// src/core/config.ts
|
|
1379
|
-
var DEFAULT_CONFIG_PATH = ".awa.toml";
|
|
1380
|
-
var ConfigLoader = class {
|
|
1381
|
-
// @awa-impl: CFG-1_AC-1, CFG-1_AC-2, CFG-1_AC-3, CFG-1_AC-4
|
|
1382
|
-
async load(configPath) {
|
|
1383
|
-
const pathToLoad = configPath ?? DEFAULT_CONFIG_PATH;
|
|
1384
|
-
const exists = await pathExists(pathToLoad);
|
|
1385
|
-
if (configPath && !exists) {
|
|
1386
|
-
throw new ConfigError(
|
|
1387
|
-
`Configuration file not found: ${configPath}`,
|
|
1388
|
-
"FILE_NOT_FOUND",
|
|
1389
|
-
configPath
|
|
1390
|
-
);
|
|
1391
|
-
}
|
|
1392
|
-
if (!configPath && !exists) {
|
|
1393
|
-
return null;
|
|
1394
|
-
}
|
|
1395
|
-
try {
|
|
1396
|
-
const content = await readTextFile(pathToLoad);
|
|
1397
|
-
const parsed = parse(content);
|
|
1398
|
-
const config = {};
|
|
1399
|
-
if (parsed.output !== void 0) {
|
|
1400
|
-
if (typeof parsed.output !== "string") {
|
|
1401
|
-
throw new ConfigError(
|
|
1402
|
-
`Invalid type for 'output': expected string, got ${typeof parsed.output}`,
|
|
1403
|
-
"INVALID_TYPE",
|
|
1404
|
-
pathToLoad
|
|
1405
|
-
);
|
|
1406
|
-
}
|
|
1407
|
-
config.output = parsed.output;
|
|
1408
|
-
}
|
|
1409
|
-
if (parsed.template !== void 0) {
|
|
1410
|
-
if (typeof parsed.template !== "string") {
|
|
1411
|
-
throw new ConfigError(
|
|
1412
|
-
`Invalid type for 'template': expected string, got ${typeof parsed.template}`,
|
|
1413
|
-
"INVALID_TYPE",
|
|
1414
|
-
pathToLoad
|
|
1415
|
-
);
|
|
1416
|
-
}
|
|
1417
|
-
config.template = parsed.template;
|
|
1418
|
-
}
|
|
1419
|
-
if (parsed.features !== void 0) {
|
|
1420
|
-
if (!Array.isArray(parsed.features) || !parsed.features.every((f) => typeof f === "string")) {
|
|
1421
|
-
throw new ConfigError(
|
|
1422
|
-
`Invalid type for 'features': expected array of strings`,
|
|
1423
|
-
"INVALID_TYPE",
|
|
1424
|
-
pathToLoad
|
|
1425
|
-
);
|
|
1426
|
-
}
|
|
1427
|
-
config.features = parsed.features;
|
|
1428
|
-
}
|
|
1429
|
-
if (parsed.preset !== void 0) {
|
|
1430
|
-
if (!Array.isArray(parsed.preset) || !parsed.preset.every((p) => typeof p === "string")) {
|
|
1431
|
-
throw new ConfigError(
|
|
1432
|
-
`Invalid type for 'preset': expected array of strings`,
|
|
1433
|
-
"INVALID_TYPE",
|
|
1434
|
-
pathToLoad
|
|
1435
|
-
);
|
|
1436
|
-
}
|
|
1437
|
-
config.preset = parsed.preset;
|
|
1438
|
-
}
|
|
1439
|
-
if (parsed["remove-features"] !== void 0) {
|
|
1440
|
-
if (!Array.isArray(parsed["remove-features"]) || !parsed["remove-features"].every((f) => typeof f === "string")) {
|
|
1441
|
-
throw new ConfigError(
|
|
1442
|
-
`Invalid type for 'remove-features': expected array of strings`,
|
|
1443
|
-
"INVALID_TYPE",
|
|
1444
|
-
pathToLoad
|
|
1445
|
-
);
|
|
1446
|
-
}
|
|
1447
|
-
config["remove-features"] = parsed["remove-features"];
|
|
1448
|
-
}
|
|
1449
|
-
if (parsed.force !== void 0) {
|
|
1450
|
-
if (typeof parsed.force !== "boolean") {
|
|
1451
|
-
throw new ConfigError(
|
|
1452
|
-
`Invalid type for 'force': expected boolean, got ${typeof parsed.force}`,
|
|
1453
|
-
"INVALID_TYPE",
|
|
1454
|
-
pathToLoad
|
|
1455
|
-
);
|
|
1456
|
-
}
|
|
1457
|
-
config.force = parsed.force;
|
|
1458
|
-
}
|
|
1459
|
-
if (parsed["dry-run"] !== void 0) {
|
|
1460
|
-
if (typeof parsed["dry-run"] !== "boolean") {
|
|
1461
|
-
throw new ConfigError(
|
|
1462
|
-
`Invalid type for 'dry-run': expected boolean, got ${typeof parsed["dry-run"]}`,
|
|
1463
|
-
"INVALID_TYPE",
|
|
1464
|
-
pathToLoad
|
|
1465
|
-
);
|
|
1466
|
-
}
|
|
1467
|
-
config["dry-run"] = parsed["dry-run"];
|
|
1468
|
-
}
|
|
1469
|
-
if (parsed.delete !== void 0) {
|
|
1470
|
-
if (typeof parsed.delete !== "boolean") {
|
|
1471
|
-
throw new ConfigError(
|
|
1472
|
-
`Invalid type for 'delete': expected boolean, got ${typeof parsed.delete}`,
|
|
1473
|
-
"INVALID_TYPE",
|
|
1474
|
-
pathToLoad
|
|
1475
|
-
);
|
|
1476
|
-
}
|
|
1477
|
-
config.delete = parsed.delete;
|
|
1478
|
-
}
|
|
1479
|
-
if (parsed.refresh !== void 0) {
|
|
1480
|
-
if (typeof parsed.refresh !== "boolean") {
|
|
1481
|
-
throw new ConfigError(
|
|
1482
|
-
`Invalid type for 'refresh': expected boolean, got ${typeof parsed.refresh}`,
|
|
1483
|
-
"INVALID_TYPE",
|
|
1484
|
-
pathToLoad
|
|
1485
|
-
);
|
|
1486
|
-
}
|
|
1487
|
-
config.refresh = parsed.refresh;
|
|
1488
|
-
}
|
|
1489
|
-
if (parsed.presets !== void 0) {
|
|
1490
|
-
if (parsed.presets === null || typeof parsed.presets !== "object" || Array.isArray(parsed.presets)) {
|
|
1491
|
-
throw new ConfigError(
|
|
1492
|
-
`Invalid type for 'presets': expected table of string arrays`,
|
|
1493
|
-
"INVALID_PRESET",
|
|
1494
|
-
pathToLoad
|
|
1495
|
-
);
|
|
1496
|
-
}
|
|
1497
|
-
const defs = {};
|
|
1498
|
-
for (const [presetName, value] of Object.entries(
|
|
1499
|
-
parsed.presets
|
|
1500
|
-
)) {
|
|
1501
|
-
if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
|
|
1502
|
-
throw new ConfigError(
|
|
1503
|
-
`Invalid preset '${presetName}': expected array of strings`,
|
|
1504
|
-
"INVALID_PRESET",
|
|
1505
|
-
pathToLoad
|
|
1506
|
-
);
|
|
1507
|
-
}
|
|
1508
|
-
defs[presetName] = value;
|
|
1509
|
-
}
|
|
1510
|
-
config.presets = defs;
|
|
1511
|
-
}
|
|
1512
|
-
if (parsed["list-unknown"] !== void 0) {
|
|
1513
|
-
if (typeof parsed["list-unknown"] !== "boolean") {
|
|
1514
|
-
throw new ConfigError(
|
|
1515
|
-
`Invalid type for 'list-unknown': expected boolean, got ${typeof parsed["list-unknown"]}`,
|
|
1516
|
-
"INVALID_TYPE",
|
|
1517
|
-
pathToLoad
|
|
1518
|
-
);
|
|
1519
|
-
}
|
|
1520
|
-
config["list-unknown"] = parsed["list-unknown"];
|
|
1521
|
-
}
|
|
1522
|
-
if (parsed.check !== void 0) {
|
|
1523
|
-
if (parsed.check === null || typeof parsed.check !== "object" || Array.isArray(parsed.check)) {
|
|
1524
|
-
throw new ConfigError(
|
|
1525
|
-
`Invalid type for 'check': expected table`,
|
|
1526
|
-
"INVALID_TYPE",
|
|
1527
|
-
pathToLoad
|
|
1528
|
-
);
|
|
1529
|
-
}
|
|
1530
|
-
config.check = parsed.check;
|
|
1531
|
-
}
|
|
1532
|
-
if (parsed.overlay !== void 0) {
|
|
1533
|
-
if (!Array.isArray(parsed.overlay) || !parsed.overlay.every((o) => typeof o === "string")) {
|
|
1534
|
-
throw new ConfigError(
|
|
1535
|
-
`Invalid type for 'overlay': expected array of strings`,
|
|
1536
|
-
"INVALID_TYPE",
|
|
1537
|
-
pathToLoad
|
|
1538
|
-
);
|
|
1539
|
-
}
|
|
1540
|
-
config.overlay = parsed.overlay;
|
|
1541
|
-
}
|
|
1542
|
-
if (parsed.targets !== void 0) {
|
|
1543
|
-
if (parsed.targets === null || typeof parsed.targets !== "object" || Array.isArray(parsed.targets)) {
|
|
1544
|
-
throw new ConfigError(
|
|
1545
|
-
`Invalid type for 'targets': expected table of target sections`,
|
|
1546
|
-
"INVALID_TYPE",
|
|
1547
|
-
pathToLoad
|
|
1548
|
-
);
|
|
1549
|
-
}
|
|
1550
|
-
const targets = {};
|
|
1551
|
-
for (const [targetName, targetValue] of Object.entries(
|
|
1552
|
-
parsed.targets
|
|
1553
|
-
)) {
|
|
1554
|
-
if (targetValue === null || typeof targetValue !== "object" || Array.isArray(targetValue)) {
|
|
1555
|
-
throw new ConfigError(
|
|
1556
|
-
`Invalid target '${targetName}': expected table`,
|
|
1557
|
-
"INVALID_TYPE",
|
|
1558
|
-
pathToLoad
|
|
1559
|
-
);
|
|
1560
|
-
}
|
|
1561
|
-
targets[targetName] = this.parseTargetSection(
|
|
1562
|
-
targetValue,
|
|
1563
|
-
targetName,
|
|
1564
|
-
pathToLoad
|
|
1565
|
-
);
|
|
1566
|
-
}
|
|
1567
|
-
config.targets = targets;
|
|
1568
|
-
}
|
|
1569
|
-
const knownKeys = /* @__PURE__ */ new Set([
|
|
1570
|
-
"output",
|
|
1571
|
-
"template",
|
|
1572
|
-
"features",
|
|
1573
|
-
"preset",
|
|
1574
|
-
"remove-features",
|
|
1575
|
-
"presets",
|
|
1576
|
-
"force",
|
|
1577
|
-
"dry-run",
|
|
1578
|
-
"delete",
|
|
1579
|
-
"refresh",
|
|
1580
|
-
"list-unknown",
|
|
1581
|
-
"check",
|
|
1582
|
-
"targets",
|
|
1583
|
-
"overlay"
|
|
1584
|
-
]);
|
|
1585
|
-
for (const key of Object.keys(parsed)) {
|
|
1586
|
-
if (!knownKeys.has(key)) {
|
|
1587
|
-
logger.warn(`Unknown configuration option: '${key}'`);
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
return config;
|
|
1591
|
-
} catch (error) {
|
|
1592
|
-
if (error instanceof ConfigError) {
|
|
1593
|
-
throw error;
|
|
1594
|
-
}
|
|
1595
|
-
throw new ConfigError(
|
|
1596
|
-
`Failed to parse TOML configuration: ${error instanceof Error ? error.message : String(error)}`,
|
|
1597
|
-
"PARSE_ERROR",
|
|
1598
|
-
pathToLoad
|
|
1599
|
-
);
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
// @awa-impl: CFG-4_AC-1, CFG-4_AC-2, CFG-4_AC-3, CFG-4_AC-4
|
|
1603
|
-
// @awa-impl: CLI-2_AC-2, CLI-2_AC-3, CLI-2_AC-4
|
|
1604
|
-
merge(cli, file) {
|
|
1605
|
-
const output = cli.output ?? file?.output;
|
|
1606
|
-
if (!output) {
|
|
1607
|
-
throw new ConfigError(
|
|
1608
|
-
"Output directory is required. Provide it as a positional argument or in the config file.",
|
|
1609
|
-
"MISSING_OUTPUT",
|
|
1610
|
-
null
|
|
1611
|
-
);
|
|
1612
|
-
}
|
|
1613
|
-
const template = cli.template ?? file?.template ?? null;
|
|
1614
|
-
const features = cli.features ?? file?.features ?? [];
|
|
1615
|
-
const preset = cli.preset ?? file?.preset ?? [];
|
|
1616
|
-
const removeFeatures = cli.removeFeatures ?? file?.["remove-features"] ?? [];
|
|
1617
|
-
const presets = file?.presets ?? {};
|
|
1618
|
-
const force = cli.force ?? file?.force ?? false;
|
|
1619
|
-
const dryRun = cli.dryRun ?? file?.["dry-run"] ?? false;
|
|
1620
|
-
const enableDelete = cli.delete ?? file?.delete ?? false;
|
|
1621
|
-
const refresh = cli.refresh ?? file?.refresh ?? false;
|
|
1622
|
-
const listUnknown = cli.listUnknown ?? file?.["list-unknown"] ?? false;
|
|
1623
|
-
const overlay = cli.overlay ?? file?.overlay ?? [];
|
|
1624
|
-
const json = cli.json ?? false;
|
|
1625
|
-
const summary = cli.summary ?? false;
|
|
1626
|
-
return {
|
|
1627
|
-
output,
|
|
1628
|
-
template,
|
|
1629
|
-
features,
|
|
1630
|
-
preset,
|
|
1631
|
-
removeFeatures,
|
|
1632
|
-
force,
|
|
1633
|
-
dryRun,
|
|
1634
|
-
delete: enableDelete,
|
|
1635
|
-
refresh,
|
|
1636
|
-
presets,
|
|
1637
|
-
listUnknown,
|
|
1638
|
-
overlay,
|
|
1639
|
-
json,
|
|
1640
|
-
summary
|
|
1641
|
-
};
|
|
1642
|
-
}
|
|
1643
|
-
// Parse a [targets.<name>] section, validating allowed keys and types
|
|
1644
|
-
parseTargetSection(section, targetName, configPath) {
|
|
1645
|
-
const target = {};
|
|
1646
|
-
const allowedKeys = /* @__PURE__ */ new Set(["output", "template", "features", "preset", "remove-features"]);
|
|
1647
|
-
for (const key of Object.keys(section)) {
|
|
1648
|
-
if (!allowedKeys.has(key)) {
|
|
1649
|
-
logger.warn(`Unknown option in target '${targetName}': '${key}'`);
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
if (section.output !== void 0) {
|
|
1653
|
-
if (typeof section.output !== "string") {
|
|
1654
|
-
throw new ConfigError(
|
|
1655
|
-
`Invalid type for 'targets.${targetName}.output': expected string, got ${typeof section.output}`,
|
|
1656
|
-
"INVALID_TYPE",
|
|
1657
|
-
configPath
|
|
1658
|
-
);
|
|
1659
|
-
}
|
|
1660
|
-
target.output = section.output;
|
|
1661
|
-
}
|
|
1662
|
-
if (section.template !== void 0) {
|
|
1663
|
-
if (typeof section.template !== "string") {
|
|
1664
|
-
throw new ConfigError(
|
|
1665
|
-
`Invalid type for 'targets.${targetName}.template': expected string, got ${typeof section.template}`,
|
|
1666
|
-
"INVALID_TYPE",
|
|
1667
|
-
configPath
|
|
1668
|
-
);
|
|
1669
|
-
}
|
|
1670
|
-
target.template = section.template;
|
|
1671
|
-
}
|
|
1672
|
-
if (section.features !== void 0) {
|
|
1673
|
-
if (!Array.isArray(section.features) || !section.features.every((f) => typeof f === "string")) {
|
|
1674
|
-
throw new ConfigError(
|
|
1675
|
-
`Invalid type for 'targets.${targetName}.features': expected array of strings`,
|
|
1676
|
-
"INVALID_TYPE",
|
|
1677
|
-
configPath
|
|
1678
|
-
);
|
|
1679
|
-
}
|
|
1680
|
-
target.features = section.features;
|
|
1681
|
-
}
|
|
1682
|
-
if (section.preset !== void 0) {
|
|
1683
|
-
if (!Array.isArray(section.preset) || !section.preset.every((p) => typeof p === "string")) {
|
|
1684
|
-
throw new ConfigError(
|
|
1685
|
-
`Invalid type for 'targets.${targetName}.preset': expected array of strings`,
|
|
1686
|
-
"INVALID_TYPE",
|
|
1687
|
-
configPath
|
|
1688
|
-
);
|
|
1689
|
-
}
|
|
1690
|
-
target.preset = section.preset;
|
|
1691
|
-
}
|
|
1692
|
-
if (section["remove-features"] !== void 0) {
|
|
1693
|
-
if (!Array.isArray(section["remove-features"]) || !section["remove-features"].every((f) => typeof f === "string")) {
|
|
1694
|
-
throw new ConfigError(
|
|
1695
|
-
`Invalid type for 'targets.${targetName}.remove-features': expected array of strings`,
|
|
1696
|
-
"INVALID_TYPE",
|
|
1697
|
-
configPath
|
|
1698
|
-
);
|
|
1699
|
-
}
|
|
1700
|
-
target["remove-features"] = section["remove-features"];
|
|
1701
|
-
}
|
|
1702
|
-
return target;
|
|
1703
|
-
}
|
|
1704
|
-
// Resolve a target by merging target config with root config (target overrides root via nullish coalescing)
|
|
1705
|
-
resolveTarget(targetName, fileConfig) {
|
|
1706
|
-
const targets = fileConfig.targets;
|
|
1707
|
-
if (!targets || Object.keys(targets).length === 0) {
|
|
1708
|
-
throw new ConfigError(
|
|
1709
|
-
"No targets defined in configuration. Add [targets.<name>] sections to .awa.toml.",
|
|
1710
|
-
"NO_TARGETS",
|
|
1711
|
-
null
|
|
1712
|
-
);
|
|
1713
|
-
}
|
|
1714
|
-
const target = targets[targetName];
|
|
1715
|
-
if (!target) {
|
|
1716
|
-
throw new ConfigError(
|
|
1717
|
-
`Unknown target: '${targetName}'. Available targets: ${Object.keys(targets).join(", ")}`,
|
|
1718
|
-
"UNKNOWN_TARGET",
|
|
1719
|
-
null
|
|
1720
|
-
);
|
|
1721
|
-
}
|
|
1722
|
-
return {
|
|
1723
|
-
...fileConfig,
|
|
1724
|
-
output: target.output ?? fileConfig.output,
|
|
1725
|
-
template: target.template ?? fileConfig.template,
|
|
1726
|
-
features: target.features ?? fileConfig.features,
|
|
1727
|
-
preset: target.preset ?? fileConfig.preset,
|
|
1728
|
-
"remove-features": target["remove-features"] ?? fileConfig["remove-features"],
|
|
1729
|
-
targets: void 0
|
|
1730
|
-
// Don't propagate targets into resolved config
|
|
1731
|
-
};
|
|
1732
|
-
}
|
|
1733
|
-
// Get all target names from config
|
|
1734
|
-
getTargetNames(fileConfig) {
|
|
1735
|
-
if (!fileConfig?.targets) {
|
|
1736
|
-
return [];
|
|
1737
|
-
}
|
|
1738
|
-
return Object.keys(fileConfig.targets);
|
|
1739
|
-
}
|
|
1740
|
-
};
|
|
1741
|
-
var configLoader = new ConfigLoader();
|
|
1742
|
-
|
|
1743
1244
|
// src/commands/check.ts
|
|
1744
1245
|
async function checkCommand(cliOptions) {
|
|
1745
1246
|
try {
|
|
@@ -1787,9 +1288,14 @@ function buildCheckConfig(fileConfig, cliOptions) {
|
|
|
1787
1288
|
...DEFAULT_CHECK_CONFIG.crossRefPatterns
|
|
1788
1289
|
];
|
|
1789
1290
|
const idPattern = typeof section?.["id-pattern"] === "string" ? section["id-pattern"] : DEFAULT_CHECK_CONFIG.idPattern;
|
|
1790
|
-
const
|
|
1791
|
-
|
|
1792
|
-
|
|
1291
|
+
const configSpecIgnore = toStringArray(section?.["spec-ignore"]) ?? [
|
|
1292
|
+
...DEFAULT_CHECK_CONFIG.specIgnore
|
|
1293
|
+
];
|
|
1294
|
+
const configCodeIgnore = toStringArray(section?.["code-ignore"]) ?? [
|
|
1295
|
+
...DEFAULT_CHECK_CONFIG.codeIgnore
|
|
1296
|
+
];
|
|
1297
|
+
const specIgnore = [...configSpecIgnore, ...cliOptions.specIgnore ?? []];
|
|
1298
|
+
const codeIgnore = [...configCodeIgnore, ...cliOptions.codeIgnore ?? []];
|
|
1793
1299
|
const ignoreMarkers = toStringArray(section?.["ignore-markers"]) ?? [
|
|
1794
1300
|
...DEFAULT_CHECK_CONFIG.ignoreMarkers
|
|
1795
1301
|
];
|
|
@@ -1801,7 +1307,8 @@ function buildCheckConfig(fileConfig, cliOptions) {
|
|
|
1801
1307
|
return {
|
|
1802
1308
|
specGlobs,
|
|
1803
1309
|
codeGlobs,
|
|
1804
|
-
|
|
1310
|
+
specIgnore,
|
|
1311
|
+
codeIgnore,
|
|
1805
1312
|
ignoreMarkers,
|
|
1806
1313
|
markers,
|
|
1807
1314
|
idPattern,
|
|
@@ -1882,12 +1389,12 @@ var batchRunner = new BatchRunner();
|
|
|
1882
1389
|
|
|
1883
1390
|
// src/core/differ.ts
|
|
1884
1391
|
import { tmpdir } from "os";
|
|
1885
|
-
import { join as
|
|
1392
|
+
import { join as join4, relative as relative2 } from "path";
|
|
1886
1393
|
import { structuredPatch } from "diff";
|
|
1887
1394
|
import { isBinaryFile as detectBinaryFile } from "isbinaryfile";
|
|
1888
1395
|
|
|
1889
1396
|
// src/core/delete-list.ts
|
|
1890
|
-
import { join as
|
|
1397
|
+
import { join as join2 } from "path";
|
|
1891
1398
|
var DELETE_LIST_FILENAME = "_delete.txt";
|
|
1892
1399
|
function parseDeleteList(content) {
|
|
1893
1400
|
const entries = [];
|
|
@@ -1914,7 +1421,7 @@ function resolveDeleteList(entries, activeFeatures) {
|
|
|
1914
1421
|
return entries.filter((e) => e.features === void 0 || !e.features.some((f) => activeSet.has(f))).map((e) => e.path);
|
|
1915
1422
|
}
|
|
1916
1423
|
async function loadDeleteList(templatePath) {
|
|
1917
|
-
const deleteListPath =
|
|
1424
|
+
const deleteListPath = join2(templatePath, DELETE_LIST_FILENAME);
|
|
1918
1425
|
if (!await pathExists(deleteListPath)) {
|
|
1919
1426
|
return [];
|
|
1920
1427
|
}
|
|
@@ -1923,12 +1430,12 @@ async function loadDeleteList(templatePath) {
|
|
|
1923
1430
|
}
|
|
1924
1431
|
|
|
1925
1432
|
// src/core/generator.ts
|
|
1926
|
-
import { join as
|
|
1433
|
+
import { join as join3, relative } from "path";
|
|
1927
1434
|
|
|
1928
1435
|
// src/core/resolver.ts
|
|
1929
1436
|
import { MultiSelectPrompt } from "@clack/core";
|
|
1930
1437
|
import { isCancel, multiselect } from "@clack/prompts";
|
|
1931
|
-
import
|
|
1438
|
+
import chalk2 from "chalk";
|
|
1932
1439
|
var _unicode = process.platform !== "win32";
|
|
1933
1440
|
var _s = (c, fb) => _unicode ? c : fb;
|
|
1934
1441
|
var _CHECKED = _s("\u25FC", "[+]");
|
|
@@ -1938,20 +1445,20 @@ var _BAR = _s("\u2502", "|");
|
|
|
1938
1445
|
var _BAR_END = _s("\u2514", "-");
|
|
1939
1446
|
function _renderDeleteItem(opt, state) {
|
|
1940
1447
|
const label = opt.label ?? opt.value;
|
|
1941
|
-
const hint = opt.hint ? ` ${
|
|
1448
|
+
const hint = opt.hint ? ` ${chalk2.dim(`(${opt.hint})`)}` : "";
|
|
1942
1449
|
switch (state) {
|
|
1943
1450
|
case "active":
|
|
1944
|
-
return `${
|
|
1451
|
+
return `${chalk2.cyan(_UNCHECKED_A)} ${label}${hint}`;
|
|
1945
1452
|
case "selected":
|
|
1946
|
-
return `${
|
|
1453
|
+
return `${chalk2.red(_CHECKED)} ${chalk2.dim(label)}${hint}`;
|
|
1947
1454
|
case "active-selected":
|
|
1948
|
-
return `${
|
|
1455
|
+
return `${chalk2.red(_CHECKED)} ${label}${hint}`;
|
|
1949
1456
|
case "cancelled":
|
|
1950
|
-
return
|
|
1457
|
+
return chalk2.strikethrough(chalk2.dim(label));
|
|
1951
1458
|
case "submitted":
|
|
1952
|
-
return
|
|
1459
|
+
return chalk2.dim(label);
|
|
1953
1460
|
default:
|
|
1954
|
-
return `${
|
|
1461
|
+
return `${chalk2.dim(_UNCHECKED)} ${chalk2.dim(label)}`;
|
|
1955
1462
|
}
|
|
1956
1463
|
}
|
|
1957
1464
|
async function deleteMultiselect(opts) {
|
|
@@ -1962,8 +1469,8 @@ async function deleteMultiselect(opts) {
|
|
|
1962
1469
|
required,
|
|
1963
1470
|
render() {
|
|
1964
1471
|
const self = this;
|
|
1965
|
-
const header = `${
|
|
1966
|
-
${
|
|
1472
|
+
const header = `${chalk2.gray(_BAR)}
|
|
1473
|
+
${chalk2.cyan(_BAR)} ${message}
|
|
1967
1474
|
`;
|
|
1968
1475
|
const getState = (opt, idx) => {
|
|
1969
1476
|
const active = idx === self.cursor;
|
|
@@ -1975,16 +1482,16 @@ ${chalk3.cyan(_BAR)} ${message}
|
|
|
1975
1482
|
};
|
|
1976
1483
|
switch (self.state) {
|
|
1977
1484
|
case "submit":
|
|
1978
|
-
return `${header}${
|
|
1485
|
+
return `${header}${chalk2.gray(_BAR)} ` + (self.options.filter((o) => self.value.includes(o.value)).map((o) => _renderDeleteItem(o, "submitted")).join(chalk2.dim(", ")) || chalk2.dim("none"));
|
|
1979
1486
|
case "cancel": {
|
|
1980
|
-
const cancelled = self.options.filter((o) => self.value.includes(o.value)).map((o) => _renderDeleteItem(o, "cancelled")).join(
|
|
1981
|
-
return `${header}${
|
|
1982
|
-
${
|
|
1487
|
+
const cancelled = self.options.filter((o) => self.value.includes(o.value)).map((o) => _renderDeleteItem(o, "cancelled")).join(chalk2.dim(", "));
|
|
1488
|
+
return `${header}${chalk2.gray(_BAR)} ${cancelled.trim() ? `${cancelled}
|
|
1489
|
+
${chalk2.gray(_BAR)}` : chalk2.dim("none")}`;
|
|
1983
1490
|
}
|
|
1984
1491
|
default:
|
|
1985
|
-
return `${header}${
|
|
1986
|
-
${
|
|
1987
|
-
${
|
|
1492
|
+
return `${header}${chalk2.cyan(_BAR)} ` + self.options.map((o, i) => _renderDeleteItem(o, getState(o, i))).join(`
|
|
1493
|
+
${chalk2.cyan(_BAR)} `) + `
|
|
1494
|
+
${chalk2.cyan(_BAR_END)}
|
|
1988
1495
|
`;
|
|
1989
1496
|
}
|
|
1990
1497
|
}
|
|
@@ -2254,7 +1761,7 @@ var FileGenerator = class {
|
|
|
2254
1761
|
const generatedOutputPaths = new Set(filesToProcess.map((f) => f.outputFile));
|
|
2255
1762
|
const deleteCandidates = [];
|
|
2256
1763
|
for (const relPath of deleteList) {
|
|
2257
|
-
const absPath =
|
|
1764
|
+
const absPath = join3(outputPath, relPath);
|
|
2258
1765
|
if (generatedOutputPaths.has(absPath)) {
|
|
2259
1766
|
logger.warn(
|
|
2260
1767
|
`Delete list entry '${relPath}' conflicts with generated file \u2014 skipping deletion`
|
|
@@ -2316,7 +1823,7 @@ var FileGenerator = class {
|
|
|
2316
1823
|
// @awa-impl: GEN-1_AC-1, GEN-1_AC-2, GEN-1_AC-3
|
|
2317
1824
|
computeOutputPath(templatePath, templateRoot, outputRoot) {
|
|
2318
1825
|
const relativePath = relative(templateRoot, templatePath);
|
|
2319
|
-
return
|
|
1826
|
+
return join3(outputRoot, relativePath);
|
|
2320
1827
|
}
|
|
2321
1828
|
};
|
|
2322
1829
|
var fileGenerator = new FileGenerator();
|
|
@@ -2358,8 +1865,8 @@ var DiffEngine = class {
|
|
|
2358
1865
|
}
|
|
2359
1866
|
const files = [];
|
|
2360
1867
|
for (const relPath of generatedFiles) {
|
|
2361
|
-
const generatedFilePath =
|
|
2362
|
-
const targetFilePath =
|
|
1868
|
+
const generatedFilePath = join4(tempPath, relPath);
|
|
1869
|
+
const targetFilePath = join4(targetPath, relPath);
|
|
2363
1870
|
if (targetFiles.has(relPath)) {
|
|
2364
1871
|
const fileDiff = await this.compareFiles(generatedFilePath, targetFilePath, relPath);
|
|
2365
1872
|
files.push(fileDiff);
|
|
@@ -2423,7 +1930,7 @@ var DiffEngine = class {
|
|
|
2423
1930
|
const systemTemp = tmpdir();
|
|
2424
1931
|
const timestamp = Date.now();
|
|
2425
1932
|
const random = Math.random().toString(36).substring(2, 8);
|
|
2426
|
-
const tempPath =
|
|
1933
|
+
const tempPath = join4(systemTemp, `awa-diff-${timestamp}-${random}`);
|
|
2427
1934
|
await ensureDir(tempPath);
|
|
2428
1935
|
return tempPath;
|
|
2429
1936
|
}
|
|
@@ -2576,18 +2083,18 @@ function writeJsonOutput(data) {
|
|
|
2576
2083
|
// src/core/overlay.ts
|
|
2577
2084
|
import { cp } from "fs/promises";
|
|
2578
2085
|
import { tmpdir as tmpdir2 } from "os";
|
|
2579
|
-
import { join as
|
|
2086
|
+
import { join as join6 } from "path";
|
|
2580
2087
|
|
|
2581
2088
|
// src/core/template-resolver.ts
|
|
2582
2089
|
import { createHash } from "crypto";
|
|
2583
|
-
import { rm
|
|
2584
|
-
import { isAbsolute, join as
|
|
2090
|
+
import { rm } from "fs/promises";
|
|
2091
|
+
import { isAbsolute, join as join5, resolve } from "path";
|
|
2585
2092
|
import degit from "degit";
|
|
2586
2093
|
var TemplateResolver = class {
|
|
2587
2094
|
// @awa-impl: CLI-3_AC-2, TPL-10_AC-1
|
|
2588
2095
|
async resolve(source, refresh) {
|
|
2589
2096
|
if (!source) {
|
|
2590
|
-
const bundledPath =
|
|
2097
|
+
const bundledPath = join5(getTemplateDir(), "awa");
|
|
2591
2098
|
return {
|
|
2592
2099
|
type: "bundled",
|
|
2593
2100
|
localPath: bundledPath,
|
|
@@ -2624,7 +2131,7 @@ var TemplateResolver = class {
|
|
|
2624
2131
|
try {
|
|
2625
2132
|
if (cacheExists && refresh) {
|
|
2626
2133
|
logger.info(`Refreshing template: ${source}`);
|
|
2627
|
-
await
|
|
2134
|
+
await rm(cachePath, { recursive: true, force: true });
|
|
2628
2135
|
} else {
|
|
2629
2136
|
logger.info(`Fetching template: ${source}`);
|
|
2630
2137
|
}
|
|
@@ -2664,7 +2171,7 @@ var TemplateResolver = class {
|
|
|
2664
2171
|
getCachePath(source) {
|
|
2665
2172
|
const hash = createHash("sha256").update(source).digest("hex").substring(0, 16);
|
|
2666
2173
|
const cacheDir = getCacheDir();
|
|
2667
|
-
return
|
|
2174
|
+
return join5(cacheDir, hash);
|
|
2668
2175
|
}
|
|
2669
2176
|
};
|
|
2670
2177
|
var templateResolver = new TemplateResolver();
|
|
@@ -2681,7 +2188,7 @@ async function resolveOverlays(overlays, refresh) {
|
|
|
2681
2188
|
async function buildMergedDir(baseDir, overlayDirs) {
|
|
2682
2189
|
const timestamp = Date.now();
|
|
2683
2190
|
const random = Math.random().toString(36).substring(2, 8);
|
|
2684
|
-
const tempPath =
|
|
2191
|
+
const tempPath = join6(tmpdir2(), `awa-overlay-${timestamp}-${random}`);
|
|
2685
2192
|
await ensureDir(tempPath);
|
|
2686
2193
|
await cp(baseDir, tempPath, { recursive: true });
|
|
2687
2194
|
for (const overlayDir of overlayDirs) {
|
|
@@ -2905,7 +2412,7 @@ async function diffCommand(cliOptions) {
|
|
|
2905
2412
|
import { intro as intro2, outro as outro2 } from "@clack/prompts";
|
|
2906
2413
|
|
|
2907
2414
|
// src/core/features/reporter.ts
|
|
2908
|
-
import
|
|
2415
|
+
import chalk3 from "chalk";
|
|
2909
2416
|
var FeaturesReporter = class {
|
|
2910
2417
|
// @awa-impl: DISC-6_AC-1, DISC-7_AC-1
|
|
2911
2418
|
/** Render the features report to stdout. */
|
|
@@ -2940,25 +2447,25 @@ var FeaturesReporter = class {
|
|
|
2940
2447
|
reportTable(scanResult, presets) {
|
|
2941
2448
|
const { features, filesScanned } = scanResult;
|
|
2942
2449
|
if (features.length === 0) {
|
|
2943
|
-
console.log(
|
|
2944
|
-
console.log(
|
|
2450
|
+
console.log(chalk3.yellow("No feature flags found."));
|
|
2451
|
+
console.log(chalk3.dim(`(${filesScanned} files scanned)`));
|
|
2945
2452
|
return;
|
|
2946
2453
|
}
|
|
2947
|
-
console.log(
|
|
2454
|
+
console.log(chalk3.bold(`Feature flags (${features.length} found):
|
|
2948
2455
|
`));
|
|
2949
2456
|
for (const feature of features) {
|
|
2950
|
-
console.log(` ${
|
|
2457
|
+
console.log(` ${chalk3.cyan(feature.name)}`);
|
|
2951
2458
|
for (const file of feature.files) {
|
|
2952
|
-
console.log(` ${
|
|
2459
|
+
console.log(` ${chalk3.dim(file)}`);
|
|
2953
2460
|
}
|
|
2954
2461
|
}
|
|
2955
2462
|
console.log("");
|
|
2956
|
-
console.log(
|
|
2463
|
+
console.log(chalk3.dim(`${filesScanned} files scanned`));
|
|
2957
2464
|
if (presets && Object.keys(presets).length > 0) {
|
|
2958
2465
|
console.log("");
|
|
2959
|
-
console.log(
|
|
2466
|
+
console.log(chalk3.bold("Presets (from .awa.toml):\n"));
|
|
2960
2467
|
for (const [name, flags] of Object.entries(presets)) {
|
|
2961
|
-
console.log(` ${
|
|
2468
|
+
console.log(` ${chalk3.green(name)}: ${flags.join(", ")}`);
|
|
2962
2469
|
}
|
|
2963
2470
|
}
|
|
2964
2471
|
}
|
|
@@ -2966,13 +2473,13 @@ var FeaturesReporter = class {
|
|
|
2966
2473
|
var featuresReporter = new FeaturesReporter();
|
|
2967
2474
|
|
|
2968
2475
|
// src/core/features/scanner.ts
|
|
2969
|
-
import { readdir
|
|
2970
|
-
import { join as
|
|
2476
|
+
import { readdir, readFile as readFile5 } from "fs/promises";
|
|
2477
|
+
import { join as join7, relative as relative3 } from "path";
|
|
2971
2478
|
var FEATURE_PATTERN = /it\.features\.(?:includes|indexOf)\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
2972
2479
|
async function* walkAllFiles(dir) {
|
|
2973
|
-
const entries = await
|
|
2480
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
2974
2481
|
for (const entry of entries) {
|
|
2975
|
-
const fullPath =
|
|
2482
|
+
const fullPath = join7(dir, entry.name);
|
|
2976
2483
|
if (entry.isDirectory()) {
|
|
2977
2484
|
yield* walkAllFiles(fullPath);
|
|
2978
2485
|
} else if (entry.isFile()) {
|
|
@@ -3000,7 +2507,7 @@ var FeatureScanner = class {
|
|
|
3000
2507
|
for await (const filePath of walkAllFiles(templatePath)) {
|
|
3001
2508
|
filesScanned++;
|
|
3002
2509
|
try {
|
|
3003
|
-
const content = await
|
|
2510
|
+
const content = await readFile5(filePath, "utf-8");
|
|
3004
2511
|
const flags = this.extractFlags(content);
|
|
3005
2512
|
const relPath = relative3(templatePath, filePath);
|
|
3006
2513
|
for (const flag of flags) {
|
|
@@ -3176,21 +2683,21 @@ async function generateCommand(cliOptions) {
|
|
|
3176
2683
|
import { intro as intro4, outro as outro4 } from "@clack/prompts";
|
|
3177
2684
|
|
|
3178
2685
|
// src/core/template-test/fixture-loader.ts
|
|
3179
|
-
import { readdir as
|
|
3180
|
-
import { basename as basename2, extname, join as
|
|
3181
|
-
import { parse
|
|
2686
|
+
import { readdir as readdir2 } from "fs/promises";
|
|
2687
|
+
import { basename as basename2, extname, join as join8 } from "path";
|
|
2688
|
+
import { parse } from "smol-toml";
|
|
3182
2689
|
async function discoverFixtures(templatePath) {
|
|
3183
|
-
const testsDir =
|
|
2690
|
+
const testsDir = join8(templatePath, "_tests");
|
|
3184
2691
|
let entries;
|
|
3185
2692
|
try {
|
|
3186
|
-
const dirEntries = await
|
|
2693
|
+
const dirEntries = await readdir2(testsDir, { withFileTypes: true });
|
|
3187
2694
|
entries = dirEntries.filter((e) => e.isFile() && extname(e.name) === ".toml").map((e) => e.name).sort();
|
|
3188
2695
|
} catch {
|
|
3189
2696
|
return [];
|
|
3190
2697
|
}
|
|
3191
2698
|
const fixtures = [];
|
|
3192
2699
|
for (const filename of entries) {
|
|
3193
|
-
const filePath =
|
|
2700
|
+
const filePath = join8(testsDir, filename);
|
|
3194
2701
|
const fixture = await parseFixture(filePath);
|
|
3195
2702
|
fixtures.push(fixture);
|
|
3196
2703
|
}
|
|
@@ -3198,7 +2705,7 @@ async function discoverFixtures(templatePath) {
|
|
|
3198
2705
|
}
|
|
3199
2706
|
async function parseFixture(filePath) {
|
|
3200
2707
|
const content = await readTextFile(filePath);
|
|
3201
|
-
const parsed =
|
|
2708
|
+
const parsed = parse(content);
|
|
3202
2709
|
const name = basename2(filePath, extname(filePath));
|
|
3203
2710
|
const features = toStringArray2(parsed.features) ?? [];
|
|
3204
2711
|
const preset = toStringArray2(parsed.preset) ?? [];
|
|
@@ -3221,61 +2728,61 @@ function toStringArray2(value) {
|
|
|
3221
2728
|
}
|
|
3222
2729
|
|
|
3223
2730
|
// src/core/template-test/reporter.ts
|
|
3224
|
-
import
|
|
2731
|
+
import chalk4 from "chalk";
|
|
3225
2732
|
function report2(result) {
|
|
3226
2733
|
console.log("");
|
|
3227
2734
|
for (const fixture of result.results) {
|
|
3228
2735
|
reportFixture(fixture);
|
|
3229
2736
|
}
|
|
3230
2737
|
console.log("");
|
|
3231
|
-
console.log(
|
|
2738
|
+
console.log(chalk4.bold("Test Summary:"));
|
|
3232
2739
|
console.log(` Total: ${result.total}`);
|
|
3233
|
-
console.log(
|
|
2740
|
+
console.log(chalk4.green(` Passed: ${result.passed}`));
|
|
3234
2741
|
if (result.failed > 0) {
|
|
3235
|
-
console.log(
|
|
2742
|
+
console.log(chalk4.red(` Failed: ${result.failed}`));
|
|
3236
2743
|
}
|
|
3237
2744
|
console.log("");
|
|
3238
2745
|
}
|
|
3239
2746
|
function reportFixture(fixture) {
|
|
3240
|
-
const icon = fixture.passed ?
|
|
2747
|
+
const icon = fixture.passed ? chalk4.green("\u2714") : chalk4.red("\u2716");
|
|
3241
2748
|
console.log(`${icon} ${fixture.name}`);
|
|
3242
2749
|
if (fixture.error) {
|
|
3243
|
-
console.log(
|
|
2750
|
+
console.log(chalk4.red(` Error: ${fixture.error}`));
|
|
3244
2751
|
return;
|
|
3245
2752
|
}
|
|
3246
2753
|
const missingFiles = fixture.fileResults.filter((r) => !r.found);
|
|
3247
2754
|
for (const missing of missingFiles) {
|
|
3248
|
-
console.log(
|
|
2755
|
+
console.log(chalk4.red(` Missing file: ${missing.path}`));
|
|
3249
2756
|
}
|
|
3250
2757
|
const snapshotFailures = fixture.snapshotResults.filter((r) => r.status !== "match");
|
|
3251
2758
|
for (const failure of snapshotFailures) {
|
|
3252
2759
|
switch (failure.status) {
|
|
3253
2760
|
case "mismatch":
|
|
3254
|
-
console.log(
|
|
2761
|
+
console.log(chalk4.yellow(` Snapshot mismatch: ${failure.path}`));
|
|
3255
2762
|
break;
|
|
3256
2763
|
case "missing-snapshot":
|
|
3257
|
-
console.log(
|
|
2764
|
+
console.log(chalk4.yellow(` Missing snapshot: ${failure.path}`));
|
|
3258
2765
|
break;
|
|
3259
2766
|
case "extra-snapshot":
|
|
3260
|
-
console.log(
|
|
2767
|
+
console.log(chalk4.yellow(` Extra snapshot (not in output): ${failure.path}`));
|
|
3261
2768
|
break;
|
|
3262
2769
|
}
|
|
3263
2770
|
}
|
|
3264
2771
|
}
|
|
3265
2772
|
|
|
3266
2773
|
// src/core/template-test/runner.ts
|
|
3267
|
-
import { mkdir as
|
|
2774
|
+
import { mkdir as mkdir2, rm as rm3 } from "fs/promises";
|
|
3268
2775
|
import { tmpdir as tmpdir3 } from "os";
|
|
3269
|
-
import { join as
|
|
2776
|
+
import { join as join10 } from "path";
|
|
3270
2777
|
|
|
3271
2778
|
// src/core/template-test/snapshot.ts
|
|
3272
|
-
import { mkdir
|
|
3273
|
-
import { join as
|
|
2779
|
+
import { mkdir, readdir as readdir3, rm as rm2 } from "fs/promises";
|
|
2780
|
+
import { join as join9, relative as relative4 } from "path";
|
|
3274
2781
|
async function walkRelative(dir, base) {
|
|
3275
2782
|
const results = [];
|
|
3276
|
-
const entries = await
|
|
2783
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
3277
2784
|
for (const entry of entries) {
|
|
3278
|
-
const fullPath =
|
|
2785
|
+
const fullPath = join9(dir, entry.name);
|
|
3279
2786
|
if (entry.isDirectory()) {
|
|
3280
2787
|
const sub = await walkRelative(fullPath, base);
|
|
3281
2788
|
results.push(...sub);
|
|
@@ -3292,8 +2799,8 @@ async function compareSnapshots(renderedDir, snapshotDir) {
|
|
|
3292
2799
|
const snapshotSet = new Set(snapshotFiles);
|
|
3293
2800
|
const renderedSet = new Set(renderedFiles);
|
|
3294
2801
|
for (const file of renderedFiles) {
|
|
3295
|
-
const renderedPath =
|
|
3296
|
-
const snapshotPath =
|
|
2802
|
+
const renderedPath = join9(renderedDir, file);
|
|
2803
|
+
const snapshotPath = join9(snapshotDir, file);
|
|
3297
2804
|
if (!snapshotSet.has(file)) {
|
|
3298
2805
|
results.push({ path: file, status: "missing-snapshot" });
|
|
3299
2806
|
continue;
|
|
@@ -3314,13 +2821,13 @@ async function compareSnapshots(renderedDir, snapshotDir) {
|
|
|
3314
2821
|
}
|
|
3315
2822
|
async function updateSnapshots(renderedDir, snapshotDir) {
|
|
3316
2823
|
if (await pathExists(snapshotDir)) {
|
|
3317
|
-
await
|
|
2824
|
+
await rm2(snapshotDir, { recursive: true, force: true });
|
|
3318
2825
|
}
|
|
3319
|
-
await
|
|
2826
|
+
await mkdir(snapshotDir, { recursive: true });
|
|
3320
2827
|
const files = await walkRelative(renderedDir, renderedDir);
|
|
3321
2828
|
for (const file of files) {
|
|
3322
|
-
const srcPath =
|
|
3323
|
-
const destPath =
|
|
2829
|
+
const srcPath = join9(renderedDir, file);
|
|
2830
|
+
const destPath = join9(snapshotDir, file);
|
|
3324
2831
|
const content = await readTextFile(srcPath);
|
|
3325
2832
|
await writeTextFile(destPath, content);
|
|
3326
2833
|
}
|
|
@@ -3328,9 +2835,9 @@ async function updateSnapshots(renderedDir, snapshotDir) {
|
|
|
3328
2835
|
|
|
3329
2836
|
// src/core/template-test/runner.ts
|
|
3330
2837
|
async function runFixture(fixture, templatePath, options, presetDefinitions = {}) {
|
|
3331
|
-
const tempDir =
|
|
2838
|
+
const tempDir = join10(tmpdir3(), `awa-test-${fixture.name}-${Date.now()}`);
|
|
3332
2839
|
try {
|
|
3333
|
-
await
|
|
2840
|
+
await mkdir2(tempDir, { recursive: true });
|
|
3334
2841
|
const features = featureResolver.resolve({
|
|
3335
2842
|
baseFeatures: [...fixture.features],
|
|
3336
2843
|
presetNames: [...fixture.preset],
|
|
@@ -3347,12 +2854,12 @@ async function runFixture(fixture, templatePath, options, presetDefinitions = {}
|
|
|
3347
2854
|
});
|
|
3348
2855
|
const fileResults = [];
|
|
3349
2856
|
for (const expectedFile of fixture.expectedFiles) {
|
|
3350
|
-
const fullPath =
|
|
2857
|
+
const fullPath = join10(tempDir, expectedFile);
|
|
3351
2858
|
const found = await pathExists(fullPath);
|
|
3352
2859
|
fileResults.push({ path: expectedFile, found });
|
|
3353
2860
|
}
|
|
3354
2861
|
const missingFiles = fileResults.filter((r) => !r.found);
|
|
3355
|
-
const snapshotDir =
|
|
2862
|
+
const snapshotDir = join10(templatePath, "_tests", fixture.name);
|
|
3356
2863
|
let snapshotResults = [];
|
|
3357
2864
|
if (options.updateSnapshots) {
|
|
3358
2865
|
await updateSnapshots(tempDir, snapshotDir);
|
|
@@ -3376,7 +2883,7 @@ async function runFixture(fixture, templatePath, options, presetDefinitions = {}
|
|
|
3376
2883
|
error: error instanceof Error ? error.message : String(error)
|
|
3377
2884
|
};
|
|
3378
2885
|
} finally {
|
|
3379
|
-
await
|
|
2886
|
+
await rm3(tempDir, { recursive: true, force: true }).catch(() => {
|
|
3380
2887
|
});
|
|
3381
2888
|
}
|
|
3382
2889
|
}
|
|
@@ -3434,6 +2941,91 @@ async function testCommand(options) {
|
|
|
3434
2941
|
}
|
|
3435
2942
|
}
|
|
3436
2943
|
|
|
2944
|
+
// src/utils/update-check.ts
|
|
2945
|
+
import chalk5 from "chalk";
|
|
2946
|
+
function compareSemver(a, b) {
|
|
2947
|
+
const partsA = a.split(".").map((s) => Number.parseInt(s, 10));
|
|
2948
|
+
const partsB = b.split(".").map((s) => Number.parseInt(s, 10));
|
|
2949
|
+
for (let i = 0; i < 3; i++) {
|
|
2950
|
+
const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);
|
|
2951
|
+
if (diff !== 0) return diff;
|
|
2952
|
+
}
|
|
2953
|
+
return 0;
|
|
2954
|
+
}
|
|
2955
|
+
function isMajorVersionBump(current, latest) {
|
|
2956
|
+
const currentMajor = Number.parseInt(current.split(".")[0] ?? "0", 10);
|
|
2957
|
+
const latestMajor = Number.parseInt(latest.split(".")[0] ?? "0", 10);
|
|
2958
|
+
return latestMajor > currentMajor;
|
|
2959
|
+
}
|
|
2960
|
+
async function checkForUpdate() {
|
|
2961
|
+
try {
|
|
2962
|
+
const response = await fetch("https://registry.npmjs.org/@ncoderz/awa/latest", {
|
|
2963
|
+
signal: AbortSignal.timeout(5e3)
|
|
2964
|
+
});
|
|
2965
|
+
if (!response.ok) return null;
|
|
2966
|
+
const data = await response.json();
|
|
2967
|
+
const latest = data.version;
|
|
2968
|
+
if (!latest || typeof latest !== "string") return null;
|
|
2969
|
+
const current = PACKAGE_INFO.version;
|
|
2970
|
+
const isOutdated = compareSemver(current, latest) < 0;
|
|
2971
|
+
return {
|
|
2972
|
+
current,
|
|
2973
|
+
latest,
|
|
2974
|
+
isOutdated,
|
|
2975
|
+
isMajorBump: isOutdated && isMajorVersionBump(current, latest)
|
|
2976
|
+
};
|
|
2977
|
+
} catch {
|
|
2978
|
+
return null;
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
function printUpdateWarning(log, result) {
|
|
2982
|
+
if (!result.isOutdated) return;
|
|
2983
|
+
console.log("");
|
|
2984
|
+
if (result.isMajorBump) {
|
|
2985
|
+
log.warn(
|
|
2986
|
+
chalk5.yellow(
|
|
2987
|
+
`New major version available: ${result.current} \u2192 ${result.latest} (breaking changes)`
|
|
2988
|
+
)
|
|
2989
|
+
);
|
|
2990
|
+
log.warn(chalk5.dim(" See https://github.com/ncoderz/awa/releases for details"));
|
|
2991
|
+
} else {
|
|
2992
|
+
log.warn(chalk5.yellow(`Update available: ${result.current} \u2192 ${result.latest}`));
|
|
2993
|
+
}
|
|
2994
|
+
log.warn(chalk5.dim(" Run `npm install -g @ncoderz/awa` to update"));
|
|
2995
|
+
console.log("");
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
// src/utils/update-check-cache.ts
|
|
2999
|
+
import { mkdir as mkdir3, readFile as readFile6, writeFile } from "fs/promises";
|
|
3000
|
+
import { homedir } from "os";
|
|
3001
|
+
import { dirname, join as join11 } from "path";
|
|
3002
|
+
var CACHE_DIR = join11(homedir(), ".cache", "awa");
|
|
3003
|
+
var CACHE_FILE = join11(CACHE_DIR, "update-check.json");
|
|
3004
|
+
var DEFAULT_INTERVAL_MS = 864e5;
|
|
3005
|
+
async function shouldCheck(intervalMs = DEFAULT_INTERVAL_MS) {
|
|
3006
|
+
try {
|
|
3007
|
+
const raw = await readFile6(CACHE_FILE, "utf-8");
|
|
3008
|
+
const data = JSON.parse(raw);
|
|
3009
|
+
if (typeof data.timestamp !== "number" || typeof data.latestVersion !== "string") {
|
|
3010
|
+
return true;
|
|
3011
|
+
}
|
|
3012
|
+
return Date.now() - data.timestamp >= intervalMs;
|
|
3013
|
+
} catch {
|
|
3014
|
+
return true;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
async function writeCache(latestVersion) {
|
|
3018
|
+
try {
|
|
3019
|
+
await mkdir3(dirname(CACHE_FILE), { recursive: true });
|
|
3020
|
+
const data = {
|
|
3021
|
+
timestamp: Date.now(),
|
|
3022
|
+
latestVersion
|
|
3023
|
+
};
|
|
3024
|
+
await writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
3025
|
+
} catch {
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3437
3029
|
// src/cli/index.ts
|
|
3438
3030
|
var version = PACKAGE_INFO.version;
|
|
3439
3031
|
var program = new Command();
|
|
@@ -3491,7 +3083,7 @@ program.command("diff").description("Compare template output with existing targe
|
|
|
3491
3083
|
});
|
|
3492
3084
|
program.command("check").description(
|
|
3493
3085
|
"Validate spec files against schemas and check traceability between code markers and specs"
|
|
3494
|
-
).option("-c, --config <path>", "Path to configuration file").option("--ignore <pattern...>", "Glob patterns
|
|
3086
|
+
).option("-c, --config <path>", "Path to configuration file").option("--spec-ignore <pattern...>", "Glob patterns to exclude from spec file scanning").option("--code-ignore <pattern...>", "Glob patterns to exclude from code file scanning").option("--format <format>", "Output format (text or json)", "text").option(
|
|
3495
3087
|
"--allow-warnings",
|
|
3496
3088
|
"Allow warnings without failing (default: warnings are errors)",
|
|
3497
3089
|
false
|
|
@@ -3502,7 +3094,8 @@ program.command("check").description(
|
|
|
3502
3094
|
).action(async (options) => {
|
|
3503
3095
|
const cliOptions = {
|
|
3504
3096
|
config: options.config,
|
|
3505
|
-
|
|
3097
|
+
specIgnore: options.specIgnore,
|
|
3098
|
+
codeIgnore: options.codeIgnore,
|
|
3506
3099
|
format: options.format,
|
|
3507
3100
|
allowWarnings: options.allowWarnings,
|
|
3508
3101
|
specOnly: options.specOnly
|
|
@@ -3528,5 +3121,41 @@ program.command("test").description("Run template test fixtures to verify expect
|
|
|
3528
3121
|
const exitCode = await testCommand(testOptions);
|
|
3529
3122
|
process.exit(exitCode);
|
|
3530
3123
|
});
|
|
3531
|
-
|
|
3124
|
+
var updateCheckPromise = null;
|
|
3125
|
+
var isJsonOrSummary = process.argv.includes("--json") || process.argv.includes("--summary");
|
|
3126
|
+
var isTTY = process.stdout.isTTY === true;
|
|
3127
|
+
var isDisabledByEnv = !!process.env.NO_UPDATE_NOTIFIER;
|
|
3128
|
+
if (!isJsonOrSummary && isTTY && !isDisabledByEnv) {
|
|
3129
|
+
updateCheckPromise = (async () => {
|
|
3130
|
+
try {
|
|
3131
|
+
const { configLoader: configLoader2 } = await import("./config-LWUO5EXL.js");
|
|
3132
|
+
const configPath = process.argv.indexOf("-c") !== -1 ? process.argv[process.argv.indexOf("-c") + 1] : process.argv.indexOf("--config") !== -1 ? process.argv[process.argv.indexOf("--config") + 1] : void 0;
|
|
3133
|
+
const fileConfig = await configLoader2.load(configPath ?? null);
|
|
3134
|
+
const updateCheckConfig = fileConfig?.["update-check"];
|
|
3135
|
+
if (updateCheckConfig?.enabled === false) return null;
|
|
3136
|
+
const intervalSeconds = updateCheckConfig?.interval ?? 86400;
|
|
3137
|
+
const intervalMs = intervalSeconds * 1e3;
|
|
3138
|
+
const needsCheck = await shouldCheck(intervalMs);
|
|
3139
|
+
if (!needsCheck) return null;
|
|
3140
|
+
const result = await checkForUpdate();
|
|
3141
|
+
if (result) {
|
|
3142
|
+
await writeCache(result.latest);
|
|
3143
|
+
}
|
|
3144
|
+
return result;
|
|
3145
|
+
} catch {
|
|
3146
|
+
return null;
|
|
3147
|
+
}
|
|
3148
|
+
})();
|
|
3149
|
+
}
|
|
3150
|
+
program.hook("postAction", async () => {
|
|
3151
|
+
if (!updateCheckPromise) return;
|
|
3152
|
+
try {
|
|
3153
|
+
const result = await updateCheckPromise;
|
|
3154
|
+
if (result?.isOutdated) {
|
|
3155
|
+
printUpdateWarning(logger, result);
|
|
3156
|
+
}
|
|
3157
|
+
} catch {
|
|
3158
|
+
}
|
|
3159
|
+
});
|
|
3160
|
+
program.parseAsync();
|
|
3532
3161
|
//# sourceMappingURL=index.js.map
|